Conversation
…onformance-based `raise` is not its own AST node type in the parser gem - it parses as a plain method call (:send), unlike return/next/redo/retry which are real node types. FlowSensitiveTyping#always_leaves_compound_statement? checked for a :raise node type that never occurs, so a guard like `raise 'no' if x.is_a?(Array)` never narrowed x's type for the rest of the method: exclude_return_type stayed nil and ComplexType#exclude was never invoked with real data. Separately, ComplexType#exclude and UniqueType#exclude used plain array subtraction (items - exclude_types.items), which only removes members equal by ==/hash to something in the exclude list. That misses excluding a parameterized member (Array<Symbol, Array>) via its plain form (Array), the same conformance gap intersect_with already avoids by matching via conforms_to? instead of equality. Fixed both #exclude implementations to reject a member when it conforms to one of the excluded types - narrower members are excluded by a broader exclude type, but not the reverse (excluding Array<Integer> leaves a plain Array member alone).
ComplexType::UniqueType#exclude was rewritten to match by conformance rather than equality, but had no direct test - only ComplexType#exclude (spec/complex_type/exclude_spec.rb) was covered.
Compress four over-budget comments this PR added down to the 1-3 line docstring/inline budget, keeping only the non-obvious why.
undercover flags line 115 (return self if exclude_types.nil?) as untested on this branch. The two conformance cases already have specs here; only the nil short-circuit was missing.
3 tasks
The three markers in Solargraph.assert_or_log each read "flow sensitive typing needs to handle 'raise if'" - which is what this branch now does. With the fix in place the strong typecheck reports all three as "Unneeded @sg-ignore comment", so CI went from 527 problems on master to 532 rather than staying level. Verified on a warm cache: lib/solargraph.rb reports 0 problems after the deletion, against three before it.
Position.normalize and NodeMethods.pack_name each guard on every member of their declared type, so the conformance-based #exclude added on this branch leaves nothing behind and falls back to UNDEFINED. Both sites are unreachable at runtime, making "Unresolved call to class" and "Unresolved call to nil?" false positives. castwide#1277 returns UniqueType::BOT for an exhausted exclusion, which resolves both. Marking them here rather than carrying that change too, so the two branches do not both edit #exclude. Also drops the catalogued count for "flow sensitive typing needs to handle 'raise if'" from 5 to 2, covering the three markers the previous commit removed. The catalogue was already one high before this branch: master states 5 and carries 4.
Position.from_offset carried an ignore reading "flow sensitive typing needs to handle 'raise if'". This branch handles that, and the error the marker suppresses now has no nil in it at all: character expected Integer, received Integer, Float, Rational, Complex A probe differing only in the subtrahend type isolates the cause: no Integer#- overload accepts nil, so none is selected and all four return types union. The nil is newline_index, un-narrowed inside the while condition that assigns it - the gap the ignore two lines above already names. So this one belongs to the catalogued "if foo = bar" reason, not to raise-if. Bookkeeping in rules.rb follows: the raise-if entry goes away with its last marker, the "if foo = bar" entry goes 2 to 3 and moves up to stay in descending order, and its 3 now matches the three markers that carry it. The entry deleted here also restores the sort, which the previous commit broke by decrementing 5 to 2 in place.
apiology
marked this pull request as ready for review
September 9, 2026 20:07
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR was written by Claude Code on behalf of @apiology.
Problem: A
raiseguard never narrows the type of the variable it tests, sotypecheck --level strongreports a mismatch against a type the guard has already ruled out.This affects every raise guard, and it fails silently:
raiseis a plain:sendnode rather than a node type of its own, soFlowSensitiveTyping#always_leaves_compound_statement?tested for a:raisenode the parser gem never produces and the narrowing fact was never attached at all.Solution: Recognize a bare, receiverless
raisecall by name inalways_leaves_compound_statement?.Then make
#excludeconformance-based, since the plain array subtraction it used matched only by==and so never removed a parameterized member such asArray<Symbol, Array>when the guard excludedArray. The direction is one-way on purpose: excludingArray<Integer>leaves a plain, unparameterizedArraymember in place.