Class: RubyIndexer::Entry::Signature
- Inherits:
-
Object
- Object
- RubyIndexer::Entry::Signature
- Defined in:
- lib/ruby_indexer/lib/ruby_indexer/entry.rb
Overview
Ruby doesn’t support method overloading, so a method will have only one signature. However RBS can represent the concept of method overloading, with different return types based on the arguments passed, so we need to store all the signatures.
Instance Attribute Summary collapse
-
#parameters ⇒ Object
readonly
: Array.
Instance Method Summary collapse
-
#format ⇒ Object
Returns a string with the decorated names of the parameters of this member.
-
#initialize(parameters) ⇒ Signature
constructor
: (Array parameters) -> void.
- #keyword_arguments_match?(args, names) ⇒ Boolean
-
#matches?(arguments) ⇒ Boolean
Returns ‘true` if the given call node arguments array matches this method signature.
- #positional_arguments_match?(positional_args, forwarding_arguments, keyword_args, min_pos, max_pos) ⇒ Boolean
Constructor Details
Instance Attribute Details
#parameters ⇒ Object (readonly)
: Array
490 491 492 |
# File 'lib/ruby_indexer/lib/ruby_indexer/entry.rb', line 490 def parameters @parameters end |
Instance Method Details
#format ⇒ Object
Returns a string with the decorated names of the parameters of this member. E.g.: ‘(a, b = 1, c: 2)` : -> String
499 500 501 |
# File 'lib/ruby_indexer/lib/ruby_indexer/entry.rb', line 499 def format @parameters.map(&:decorated_name).join(", ") end |
#keyword_arguments_match?(args, names) ⇒ Boolean
584 585 586 587 588 589 590 591 592 593 594 595 596 |
# File 'lib/ruby_indexer/lib/ruby_indexer/entry.rb', line 584 def keyword_arguments_match?(args, names) return true unless args return true if args.any? { |arg| arg.is_a?(Prism::AssocSplatNode) } arg_names = args.filter_map do |arg| next unless arg.is_a?(Prism::AssocNode) key = arg.key key.value&.to_sym if key.is_a?(Prism::SymbolNode) end (arg_names - names).empty? end |
#matches?(arguments) ⇒ Boolean
Returns ‘true` if the given call node arguments array matches this method signature. This method will prefer returning `true` for situations that cannot be analyzed statically, like the presence of splats, keyword splats or forwarding arguments.
Since this method is used to detect which overload should be displayed in signature help, it will also return ‘true` if there are missing arguments since the user may not be done typing yet. For example:
“‘ruby def foo(a, b); end # All of the following are considered matches because the user might be in the middle of typing and we have to # show them the signature foo foo(1) foo(1, 2) “` : (Array arguments) -> bool
519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 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 |
# File 'lib/ruby_indexer/lib/ruby_indexer/entry.rb', line 519 def matches?(arguments) min_pos = 0 max_pos = 0 #: (Integer | Float) names = [] has_forward = false #: bool has_keyword_rest = false #: bool @parameters.each do |param| case param when RequiredParameter min_pos += 1 max_pos += 1 when OptionalParameter max_pos += 1 when RestParameter max_pos = Float::INFINITY when ForwardingParameter max_pos = Float::INFINITY has_forward = true when KeywordParameter, OptionalKeywordParameter names << param.name when KeywordRestParameter has_keyword_rest = true end end keyword_hash_nodes, positional_args = arguments.partition { |arg| arg.is_a?(Prism::KeywordHashNode) } keyword_args = keyword_hash_nodes.first #: as Prism::KeywordHashNode? &.elements forwarding_arguments, positionals = positional_args.partition do |arg| arg.is_a?(Prism::ForwardingArgumentsNode) end return true if has_forward && min_pos == 0 # If the only argument passed is a forwarding argument, then anything will match (positionals.empty? && forwarding_arguments.any?) || ( # Check if positional arguments match. This includes required, optional, rest arguments. We also need to # verify if there's a trailing forwarding argument, like `def foo(a, ...); end` positional_arguments_match?(positionals, forwarding_arguments, keyword_args, min_pos, max_pos) && # If the positional arguments match, we move on to checking keyword, optional keyword and keyword rest # arguments. If there's a forward argument, then it will always match. If the method accepts a keyword rest # (**kwargs), then we can't analyze statically because the user could be passing a hash and we don't know # what the runtime values inside the hash are. # # If none of those match, then we verify if the user is passing the expect names for the keyword arguments (has_forward || has_keyword_rest || keyword_arguments_match?(keyword_args, names)) ) end |
#positional_arguments_match?(positional_args, forwarding_arguments, keyword_args, min_pos, max_pos) ⇒ Boolean
571 572 573 574 575 576 577 578 579 580 581 |
# File 'lib/ruby_indexer/lib/ruby_indexer/entry.rb', line 571 def positional_arguments_match?(positional_args, forwarding_arguments, keyword_args, min_pos, max_pos) # If the method accepts at least one positional argument and a splat has been passed (min_pos > 0 && positional_args.any? { |arg| arg.is_a?(Prism::SplatNode) }) || # If there's at least one positional argument unaccounted for and a keyword splat has been passed (min_pos - positional_args.length > 0 && keyword_args&.any? { |arg| arg.is_a?(Prism::AssocSplatNode) }) || # If there's at least one positional argument unaccounted for and a forwarding argument has been passed (min_pos - positional_args.length > 0 && forwarding_arguments.any?) || # If the number of positional arguments is within the expected range (min_pos > 0 && positional_args.length <= max_pos) || (min_pos == 0 && positional_args.empty?) end |