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.
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 |