Method: Parlour::TypeParser#parse_sig_into_methods

Defined in:
lib/parlour/type_parser.rb

#parse_sig_into_methods(path, is_within_eigenclass: false) ⇒ <RbiGenerator::Method>

Given a path to a sig in the AST, finds the associated definition and parses them into methods. This will raise an exception if the sig is invalid. Usually this will return one method; the only exception currently is for attributes, where multiple can be declared in one call, e.g. attr_reader :x, :y, :z.

Parameters:

  • path (NodePath)

    The sig to parse.

  • is_within_eigenclass (Boolean) (defaults to: false)

    Whether the method definition this sig is associated with appears inside an eigenclass definition. If true, the returned method is made a class method. If the method definition is already a class method, an exception is thrown as the method will be a class method of the eigenclass, which Parlour can’t represent.

Returns:



535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
# File 'lib/parlour/type_parser.rb', line 535

def parse_sig_into_methods(path, is_within_eigenclass: false)
  sig_block_node = path.traverse(ast)

  # A :def node represents a definition like "def x; end"
  # A :defs node represents a definition like "def self.x; end"
  def_node = path.sibling(1).traverse(ast)
  if def_node.type == :send && [:def, :defs].include?(def_node.children.last.type)
    # bypass inline modifier (e.g. "private")
    def_node = def_node.children.last
  end
  case def_node.type
  when :def
    class_method = false
    def_names = [def_node.to_a[0].to_s]
    def_params = def_node.to_a[1].to_a
    kind = :def
  when :defs
    parse_err 'targeted definitions on a non-self target are not supported', def_node \
      unless def_node.to_a[0].type == :self
    class_method = true
    def_names = [def_node.to_a[1].to_s]
    def_params = def_node.to_a[2].to_a
    kind = :def
  when :send
    target, method_name, *parameters = *def_node

    parse_err 'node after a sig must be a method definition', def_node \
      unless [:attr_reader, :attr_writer, :attr_accessor].include?(method_name) \
        || target != nil

    parse_err 'typed attribute should have at least one name', def_node if parameters&.length == 0

    kind = :attr
    attr_direction = method_name.to_s.gsub('attr_', '').to_sym
    def_names = T.must(parameters).map { |param| param.to_a[0].to_s }
    class_method = false
  else
    parse_err 'node after a sig must be a method definition', def_node
  end

  if is_within_eigenclass
    parse_err 'cannot represent multiple levels of eigenclassing', def_node if class_method
    class_method = true
  end

  this_sig = parse_sig_into_sig(path)
  params = this_sig.params
  return_type = this_sig.return_type

  if kind == :def
    # Sorbet allows a trailing blockarg that's not in the sig
    if params &&
       def_params.length == params.length + 1 &&
       def_params[-1].type == :blockarg
      def_params = def_params[0...-1]
    end

    parse_err 'mismatching number of arguments in sig and def', sig_block_node \
      if params && def_params.length != params.length

    # sig_args will look like:
    #   [(pair (sym :x) <type>), (pair (sym :y) <type>), ...]
    # def_params will look like:
    #   [(arg :x), (arg :y), ...]
    parameters = params \
      ? zip_by(params, ->x{ x.to_a[0].to_a[0] }, def_params, ->x{ x.to_a[0] })
        .map do |sig_arg, def_param|

          arg_name = def_param.to_a[0]

          # TODO: anonymous restarg
          full_name = arg_name.to_s
          full_name = "*#{arg_name}"  if def_param.type == :restarg
          full_name = "**#{arg_name}" if def_param.type == :kwrestarg
          full_name = "#{arg_name}:"  if def_param.type == :kwarg || def_param.type == :kwoptarg
          full_name = "&#{arg_name}"  if def_param.type == :blockarg

          default = def_param.to_a[1] ? node_to_s(def_param.to_a[1]) : nil
          type = node_to_s(sig_arg.to_a[1])

          RbiGenerator::Parameter.new(full_name, type: type, default: default)
        end
      : []

    # There should only be one ever here, but future-proofing anyway
    def_names.map do |def_name|
      RbiGenerator::Method.new(
        generator,
        def_name,
        parameters,
        return_type,
        type_parameters: this_sig.type_parameters,
        override: this_sig.override,
        overridable: this_sig.overridable,
        abstract: this_sig.abstract,
        final: this_sig.final,
        class_method: class_method
      )
    end
  elsif kind == :attr
    case attr_direction
    when :reader, :accessor
      parse_err "attr_#{attr_direction} sig should have no parameters", sig_block_node \
        if params && params.length > 0

      parse_err "attr_#{attr_direction} sig should have non-void return", sig_block_node \
        unless return_type

      attr_type = return_type
    when :writer
      # These are special and can only have one name
      raise 'typed attr_writer can only have one name' if def_names.length > 1

      def_name = def_names[0]
      parse_err "attr_writer sig should take one argument with the property's name", sig_block_node \
        if !params || params.length != 1 || params[0].to_a[0].to_a[0].to_s != def_name

      parse_err "attr_writer sig should have non-void return", sig_block_node \
        if return_type.nil?

      attr_type = T.must(node_to_s(params[0].to_a[1]))
    else
      raise "unknown attribute direction #{attr_direction}"
    end

    def_names.map do |def_name|
      RbiGenerator::Attribute.new(
        generator,
        def_name,
        attr_direction,
        attr_type,
        class_attribute: class_method
      )
    end
  else
    raise "unknown definition kind #{kind}"
  end
end