@@ -9,11 +9,16 @@ class FlowSensitiveTyping
99 # @param ivars [Array<Solargraph::Pin::InstanceVariable>]
1010 # @param enclosing_breakable_pin [Solargraph::Pin::Breakable, nil]
1111 # @param enclosing_compound_statement_pin [Solargraph::Pin::CompoundStatement, nil]
12- def initialize locals , ivars , enclosing_breakable_pin , enclosing_compound_statement_pin
12+ # @param closure [Solargraph::Pin::Closure] The pin enclosing the
13+ # code being processed (e.g. the current method), used to
14+ # resolve a bare, implicit-self call like 'steps' as a call to
15+ # a 0-arg method rather than a local variable.
16+ def initialize locals , ivars , enclosing_breakable_pin , enclosing_compound_statement_pin , closure
1317 @locals = locals
1418 @ivars = ivars
1519 @enclosing_breakable_pin = enclosing_breakable_pin
1620 @enclosing_compound_statement_pin = enclosing_compound_statement_pin
21+ @closure = closure
1722 end
1823
1924 # @param and_node [Parser::AST::Node]
@@ -431,8 +436,10 @@ def find_var variable_name, position
431436 end
432437
433438 # Finds (for a single tracked local/instance variable) or builds
434- # (for a chain of simple calls off of one, e.g. ['pin', 'location'])
435- # the pin flow-sensitive-typing facts should be recorded against.
439+ # (for a chain of simple calls off of one, e.g. ['pin', 'location'],
440+ # or for a bare/explicit-self 0-arg method call, e.g. ['steps'] from
441+ # 'steps' or 'self.steps') the pin flow-sensitive-typing facts
442+ # should be recorded against.
436443 #
437444 # A synthesized pin's type is computed lazily, from `node` itself,
438445 # by Pin::BaseVariable#probe the same way a real local variable's
@@ -447,8 +454,19 @@ def find_var variable_name, position
447454 # @param position [Position]
448455 # @return [Solargraph::Pin::LocalVariable, Solargraph::Pin::InstanceVariable, nil]
449456 def chain_pin chain_words , node , position
450- # @sg-ignore chain_words is never empty - callers already checked
451- return find_var ( chain_words . first , position ) if chain_words . length == 1
457+ if chain_words . length == 1
458+ # A bare word is ambiguous from chain_words alone -- 'steps'
459+ # could be a real local variable (node.type == :lvar) or a
460+ # 0-arg method call to self (node.type == :send, since the
461+ # parser only emits :lvar for a name already assigned as a
462+ # local in this scope). Only the former is a tracked variable.
463+ # @sg-ignore chain_words is never empty - callers already checked
464+ return find_var ( chain_words . first , position ) unless node . is_a? ( ::Parser ::AST ::Node ) && node . type == :send
465+
466+ return unless closure
467+
468+ return self_call_pin ( node )
469+ end
452470
453471 # @sg-ignore chain_words is never empty - callers already checked
454472 root_pin = find_var ( chain_words . first , position )
@@ -463,6 +481,26 @@ def chain_pin chain_words, node, position
463481 )
464482 end
465483
484+ # Builds the synthesized pin for a bare, implicit-self call to a
485+ # 0-arg method, e.g. 'steps'. Rooted at `closure` rather than at a
486+ # tracked variable's pin, since there is no variable to inherit a
487+ # closure from. Named after the bare method word itself (not
488+ # e.g. 'self.steps') so it lines up with how Chain::Call#resolve
489+ # looks up a head-position call: by the call's word, via
490+ # ApiMap#var_at_location.
491+ #
492+ # @param node [Parser::AST::Node] the call node, e.g. 'steps'
493+ # @return [Solargraph::Pin::LocalVariable]
494+ def self_call_pin node
495+ Pin ::LocalVariable . new (
496+ location : Location . from_node ( node ) ,
497+ closure : closure ,
498+ name : node . children [ 1 ] . to_s ,
499+ assignment : node ,
500+ source : :flow_sensitive_typing
501+ )
502+ end
503+
466504 # @param isa_node [Parser::AST::Node]
467505 # @param true_presences [Array<Range>]
468506 # @param false_presences [Array<Range>]
@@ -592,10 +630,11 @@ def process_variable node, true_presences, false_presences
592630 end
593631
594632 # Handles a bare truthy check on a call chain, e.g. 'pin.location'
595- # in 'return nil unless pin.location'. Bare references to a single
596- # local/instance variable are handled by #process_variable instead;
597- # this only fires once there's an explicit receiver (chain_words
598- # has more than one word).
633+ # in 'return nil unless pin.location', or on a bare, implicit-self
634+ # 0-arg method call, e.g. 'steps' in 'return nil unless steps'.
635+ # Bare references to a single local/instance *variable* are
636+ # handled by #process_variable instead (node.type would be :lvar
637+ # or :ivar there, not :send, so this never double-processes them).
599638 #
600639 # @param node [Parser::AST::Node]
601640 # @param true_presences [Array<Range>]
@@ -609,7 +648,7 @@ def process_call_chain node, true_presences, false_presences
609648 return if %i[ nil? ! ] . include? ( node . children [ 1 ] )
610649
611650 chain_words = parse_receiver_chain ( node )
612- return if chain_words . nil? || chain_words . length < 2
651+ return if chain_words . nil? || chain_words . empty?
613652
614653 # @sg-ignore Range.from_node is nil only for a node without
615654 # source location info, which doesn't happen for real parsed
@@ -677,7 +716,7 @@ def always_leaves_compound_statement? clause_node
677716 clause_node . children [ 0 ] . nil? && %i[ raise fail ] . include? ( clause_node . children [ 1 ] )
678717 end
679718
680- attr_reader :locals , :ivars , :enclosing_breakable_pin , :enclosing_compound_statement_pin
719+ attr_reader :locals , :ivars , :enclosing_breakable_pin , :enclosing_compound_statement_pin , :closure
681720 end
682721 end
683722end
0 commit comments