Narrow bare, implicit-self attr_reader-style accessor calls - #53
Narrow bare, implicit-self attr_reader-style accessor calls#53apiology wants to merge 2 commits into
Conversation
A nil-guard on a bare call (e.g. 'return nil if steps.nil?', where steps is an argless method like an attr_reader) previously left a later call to the same accessor (e.g. 'steps.empty?') unnarrowed -- FlowSensitiveTyping's chain-narrowing (added for explicit-receiver chains like 'pin.location') only resolved a single-word chain via find_var, which looks up tracked local/instance variables and can never match a method call. chain_pin now recognizes when a length-1 chain word actually came from a :send node (a method call, since the parser only emits :lvar for names already assigned as locals in scope) rather than an :lvar node, and synthesizes a pin rooted at the enclosing closure instead of a variable's. FlowSensitiveTyping now takes that closure as a constructor argument from each node processor's `region.closure`. process_call_chain's bare-truthy-check handling is extended from chain_words.length >= 2 to length >= 1 for the same reason, so 'return nil unless steps' narrows the same way 'return nil if steps.nil?' does. SKIP=Solargraph: this branch (castwide#1258, unmerged) already has 28 pre-existing `solargraph typecheck --level strong` problems in these files before this commit; this change adds none (verified line-by-line against the pre-existing baseline). Fixes castwide#1258 (comment) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PMbuVPLPj8CjHG8EkEQjrh
#53 added a required closure parameter to FlowSensitiveTyping#initialize and updated every caller it knew about - but its branch is based on castwide#1258, not castwide#1259 (already merged into this integration branch separately), so it never saw case_node.rb (added by castwide#1259) or the already-existing call in orasgn_node.rb that castwide#1259 also touches. Both call sites already had region.closure in scope; added it as the 5th argument, matching every other already-updated caller (and_node.rb, if_node.rb, or_node.rb, while_node.rb). Verified: spec/parser/flow_sensitive_typing_spec.rb (65 examples), spec/parser (323 examples), and spec/source_map/clip_spec.rb all pass locally with 0 failures.
|
Claude: drafted while auditing This fix narrows a bare accessor when the guarded expression is called again directly, but the narrowing doesn't propagate through an assignment into a fresh local. Reproduction# typed: true
# frozen_string_literal: true
class Repro
# @return [Array<Hash>, nil]
attr_reader :steps
# @return [Boolean]
def works
return false if steps.nil?
steps.empty? # 0 problems - narrows correctly
end
# @return [Boolean]
def still_broken
return false if steps.nil?
local = steps
local.empty? # Unresolved call to empty?
end
endConfirmed against the current fork tip (this PR's commit is already an ancestor). |
…nment #53 (comment) reported that narrowing a bare, implicit-self accessor doesn't propagate through an assignment into a fresh local (e.g. local = steps; local.empty? left unresolved). Verified against this branch's HEAD (bbaf7f9) that the reported repro already resolves correctly: solargraph typecheck --level strong reports 0 problems, and Chain#infer for local.empty? resolves local to ::Array<::Hash>. BaseVariable#probe re-infers a local's assignment expression at its own source position via Chain#infer, and that position already falls inside the narrowed presence range recorded by FlowSensitiveTyping for the bare steps call, so the fact carries through without any code change needed. This adds the missing spec coverage for that path so a future regression here is caught. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RiL3AMsRKcUzfrTHTsVzxv
|
Reopening to nudge CI ��� push at 2026-08-06T19:01:06Z never triggered a synchronize event (no check-runs created for commit 70992a5 after 20+ minutes, while other branches in this repo triggered CI within seconds). |
|
Claude: investigated this report. Doesn't reproduce at this branch's current HEAD, commit bbaf7f9. Checked two ways:
Root cause it isn't hitting: Added a spec ( GitHub Actions hasn't picked up this commit yet (unrelated platform outage), so CI hasn't confirmed this independently yet. If you were seeing this fail in the downstream project outside this minimal repro, a fuller reproduction (e.g. the local reassigned conditionally, or read across a block boundary) would help narrow down what's different. |
…only inference Pulls in two new upstream commits on top of the already-merged castwide#1259 base: - Recognize multi-statement raise/fail branches in flow-sensitive typing: always_leaves_compound_statement? now recurses into :begin nodes' last child, so a clause like `msg = "bad"; raise msg` is still recognized as unconditionally leaving, not just a single bare `raise`/`fail` send. - Infer only the lhs type for `x || raise(...)` and `x ||= raise(...)`: new logic in node_chainer.rb and Chain::Or#resolve treats a never-returning rhs the same way as the flow-sensitive-typing narrowing already does, so the combined type is just the lhs's non-nil type instead of a union with the (unreachable) rhs. Conflict in lib/solargraph/parser/flow_sensitive_typing.rb: the upstream commits move always_leaves_compound_statement? out of FlowSensitiveTyping and into the shared Solargraph::Parser::ParserGem::NodeMethods module (aliased as Solargraph::Parser::NodeMethods, already included by FlowSensitiveTyping), so it can be reused from node_chainer.rb, and extend it with :begin-node support. Removed the now-duplicate local copy in favor of the shared one; kept this branch's own :closure attr_reader addition (from #53, needed by every other FlowSensitiveTyping.new caller already updated on this branch). Also dropped two now-superfluous `@sg-ignore Need to add nil check here` comments in node_chainer.rb (above the always_leaves_compound_statement?(or_asgn_rhs_node) and always_leaves_compound_statement?(or_rhs_node) calls): on the PR's own source branch, NodeChainer.chain's node arguments are inferred as `Array, nil` throughout that branch (a broad, unrelated pre-existing mismatch visible across dozens of lines when typechecked standalone), so the @sg-ignore was suppressing a real mismatch there. On this integration branch those same node variables are already correctly typed `Parser::AST::Node, nil` (fixed by an earlier-merged PR), and always_leaves_compound_statement?'s own param is already declared nilable, so passing them needs no suppression - the local Solargraph typecheck hook correctly flagged both comments as unneeded. Verified: spec/parser/flow_sensitive_typing_spec.rb, spec/parser/node_methods_spec.rb, spec/source/chain/or_spec.rb, spec/parser/node_chainer_spec.rb (134 examples, 0 failures, 4 pending), and a broader safety net — spec/parser, spec/source, spec/source_map/clip_spec.rb (475 examples, 0 failures, 14 pending) — all passing locally.
Summary
A nil-guard on a bare, implicit-self call (e.g.
return nil if steps.nil?, wherestepsis an argless method like anattr_reader) left a later call to the same accessor (e.g.steps.empty?) unnarrowed — reported at castwide#1258 (comment).Root cause:
FlowSensitiveTyping#chain_pin's length-1 branch always went throughfind_var, which only matches tracked local/instance-variable pins. A barestepsreference is a:sendnode (method call), not:lvar, so lookup silently found nothing.chain_pinnow distinguishes a length-1 chain word rooted in a real:lvar/:ivarnode from one rooted in a:sendnode (a 0-arg self-call), and synthesizes a pin against the enclosing closure (now threaded intoFlowSensitiveTypingfrom each node processor'sregion.closure) instead of a variable's.process_call_chain's bare-truthy-guard handling is extended from chain length >= 2 to >= 1 for the same reason.Based on
gh-fix-1249(castwide/solargraph#1258) since it depends on that PR's chain-narrowing infrastructure. Oncecastwide/solargraph#1258merges, this should be retargeted atcastwide/solargraph:master(or opened fresh there).Test plan
.nil?guard, bare truthy guard) tospec/parser/flow_sensitive_typing_spec.rbbundle exec rspec-- 1629 examples, 0 failuresbundle exec rubocopon changed files -- no offensesbundle exec solargraph typecheck --level strongon changed files -- 28 pre-existing problems ongh-fix-1249unmodified; this diff adds none (verified line-by-line)🤖 Generated with Claude Code
https://claude.ai/code/session_01PMbuVPLPj8CjHG8EkEQjrh