Name where inference stopped in typecheck inference errors - #1313
Name where inference stopped in typecheck inference errors#1313apiology wants to merge 3 commits into
Conversation
'Variable type could not be inferred for X' and 'P#m return type could not be inferred' name the assigned variable or the method, not the part of the expression where inference stopped. Both now carry a plain-language cause naming it: Variable type could not be inferred for n: `lenght` could not be resolved on Array<String> Report#run return type could not be inferred: `lenght` could not be resolved on Array<String> A name that matched no definition renders as '`name` could not be resolved on Type'; a definition whose return type will not resolve renders as 'could not determine the return type of `name` on Type'. Messages are byte-identical to before whenever no cause can be named, or when the failing call is the one already named. The method-return path matters most for code with no @type tags at all, which is the common case: a bare-variable return delegates to that variable's own assignment, so the cause survives one hop. Substrate for future diagnostics: Chain#trace records per-link resolution (receiver type, pin count, inferred type) and Chain#first_undefined_link exposes the first failure; both uncached, error-path only. Pin::BaseVariable#probe_blame and Pin::Method#probe_blame apply the walk to assignments and return nodes. The spec matrix pairs each shape: the no-@type form first, then the tagged variant, since most code carries no tags. Three existing specs asserting the exact old method-return string were updated to the superset text (strong_spec x2, alpha_spec x1). Two pending specs record problems found while testing: an unrelated parser gap, and a plain typo on an untagged local that produces no diagnostic at all. Suite: 1638 examples, 0 failures attributable (5 diagnostics failures are a pre-existing spec-ordering artifact, reproduced identically on stock master with the same chunk pairing). Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01H1FEjW6nMpZrWPmeWX9miT
| next if node.nil? || node.type == :NIL || node.type == :nil | ||
| rng = Range.from_node(node) | ||
| next if rng.nil? | ||
| # @sg-ignore Need to add nil check here |
There was a problem hiding this comment.
What's the actual typecheck error here? If it's related to the above line's nil check, is there an open PR or issue addressing that?
There was a problem hiding this comment.
The actual error, from solargraph typecheck --level strong lib/solargraph/pin/base_variable.rb with only this line removed:
lib/solargraph/pin/base_variable.rb:190: Unresolved call to filename on Solargraph::Location, nil
That is not a tool limitation ��� the nil check really was missing. Pin::Base#location is typed Location, nil, and probe_blame here guarded only closure.nil? before calling location.filename. Pin::Method#probe_blame, added in this same PR, does guard it: return nil if node.nil? || method_body_node.nil? || location.nil?.
The guard is now extended to return nil if closure.nil? || location.nil?.
Measured after adding the guard: removing the @sg-ignore again still reports the identical Unresolved call to filename on Solargraph::Location, nil at that line, so the suppression is still needed and its cause is now the same return-guard narrowing gap as the line below ��� #1254. Retagged to match, in the same wording used in Pin::Method#probe_blame.
This comment was written by Claude Code on behalf of @apiology.
| clip = api_map.clip_at(location.filename, rng.ending) | ||
| chain = Parser.chain(node, nil, nil) | ||
| # @sg-ignore closure nil-guarded at method entry; return-guard | ||
| # narrowing not tracked - https://github.com/castwide/solargraph/issues/1254 |
There was a problem hiding this comment.
Ditto on actual error
There was a problem hiding this comment.
lib/solargraph/pin/base_variable.rb:193: Wrong argument type for Solargraph::Source::Chain#first_undefined_link: name_pin expected Solargraph::Pin::Base, received Solargraph::Pin::Closure, nil
closure is Pin::Closure, nil and is guarded by return nil if closure.nil? at method entry, but the narrowing is not carried past the return guard ��� #1254 ("Flow-sensitive typing doesn't narrow past a raise/return-based nil guard"). No code change here; the existing tag matches the observed error.
This comment was written by Claude Code on behalf of @apiology.
Pin::Base#location is typed Location, nil. Pin::BaseVariable#probe_blame guarded only closure.nil? and then called location.filename, so a pin without a location raised NoMethodError on the diagnostic path. Pin::Method#probe_blame already guards location.nil? at method entry; this makes the variable side match. Removing the @sg-ignore on that line reported: lib/solargraph/pin/base_variable.rb:190: Unresolved call to filename on Solargraph::Location, nil which was a real missing check rather than a typechecker limitation, so the comment text no longer described what it was suppressing. With the guard in place, removing the @sg-ignore still reports the same error, because flow-sensitive typing does not carry the narrowing past a return-based nil guard - castwide#1254. Retagged with that cause, in the same wording used in Pin::Method#probe_blame. Verified against the pre-change branch: rspec 1638 examples / 0 failures / 62 pending, rubocop 71 offenses, solargraph typecheck --level strong 527 problems - all three unchanged. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01CXmnT5gSB1PheL9UbiGEVA
The comment shipped a literal "see #NNNN". No open castwide/solargraph issue matches numblock or numbered-parameter handling, so there is no number to fill in. The sentence stands on its own without the pointer. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01CXmnT5gSB1PheL9UbiGEVA
Problem
A local with a declared type, whose expression calls a method that does not exist:
typecheck --level strongreports one problem for this file, and it names only the variable:Nothing says which of
'a',no_such, orupcaseinference stopped at, and there is no other problem reported in the file to cross-reference. Diagnosing it means bisecting the expression by hand.The same gap appears in code with no tags at all, where the method-return check fires instead:
The first message names the method, not the part of the expression responsible. The second names the underlying cause but sits at
def fetchon line 9, not at the expression inrowsthat failed — and in a real file it can be far from it, among unrelated problems.Solution
Both messages now carry a plain-language cause naming where inference stopped and what it was called on:
A name that matched no definition renders as
`name` could not be resolved on Type; a definition whose declared return type will not resolve renders ascould not determine the return type of `name` on Type. Messages stay byte-identical when no cause can be named, and when the failing call is the one already named.The method-return path matters most for code with no
@typetags at all, which is the common case. In the second example the return value is the bare variableys, so the walk continues into that variable's own assignment — which is why the cause namesfetch, a call that appears nowhere in the return expression itself.Mechanism:
Chain#tracere-walks the chain exactly as#definedoes, recording each link's receiver type, matched pin count, and inferred type;Chain#first_undefined_linkreturns the first failing record. Both are uncached and only invoked on the error path.Pin::BaseVariable#probe_blameandPin::Method#probe_blameapply the walk to assignments and return nodes respectively.Test plan: a message matrix in strong_spec asserts the complete rendered string for ten cases — four expression shapes, each in an untagged and a tagged form, plus a control asserting that correcting a misspelled method name reports nothing at all, plus a guardrail asserting the message is unchanged when only the final call fails. Six of the ten fail before the change; the other four pin behavior that must not shift. Three existing specs asserting the exact old method-return string were updated to the superset text. Two pending specs record problems found while testing: an unrelated parser gap, and a missing diagnostic for a call to a nonexistent method on an untagged local. That second case is pinned from both sides: an active spec asserts that no variable-validation message is produced today, and the pending spec asserts the diagnostic that should be reported. Full suite: 1638 examples, 5 failures, all 5 in spec/diagnostics and all 5 reproducing identically on unmodified master.
Opened as a draft. This PR was written by Claude (Anthropic's Claude Code) on behalf of @apiology.
🤖 Generated with Claude Code
https://claude.ai/code/session_01H1FEjW6nMpZrWPmeWX9miT