Fix flow-sensitive narrowing after raise, make ComplexType#exclude conformance-based - #66
Draft
apiology wants to merge 1 commit into
Draft
Fix flow-sensitive narrowing after raise, make ComplexType#exclude conformance-based#66apiology wants to merge 1 commit into
apiology wants to merge 1 commit into
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).
apiology
added a commit
that referenced
this pull request
Aug 22, 2026
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.
raise 'no' if x.is_a?(Array)never narrowsx's type for the rest of the method, becauseraiseis not its own AST node type — theparsergem represents it as a plain method call (:send), unlikereturn/next/redo/retrywhich are real node types.FlowSensitiveTyping#always_leaves_compound_statement?checked for a:raisenode type that can never occur, so the narrowing fact was silently never attached.typecheck --level strongreported:Declared return type ::Symbol, ::String does not match inferred type ::Symbol, ::Array<::Symbol, ::Array>.Fixed by recognizing a bare, receiver-less
raisecall by name inalways_leaves_compound_statement?.Separately, once narrowing does produce an exclude type,
ComplexType#excludeandUniqueType#excludeused plain array subtraction (items - exclude_types.items), matching only by==/hash. That never matches a parameterized member (Array<Symbol, Array>) against the plain type being excluded (Array), unlikeintersect_with, which already handles this viaconforms_to?. Fixed#excludeto reject a member when it conforms to one of the excluded types. This is intentionally one-directional: excludingArrayremovesArray<Symbol, Array>(everyArray<Symbol, Array>is anArray), but excludingArray<Integer>does not remove a plain, unparameterizedArraymember (not everyArrayis anArray<Integer>).Test plan:
spec/parser/flow_sensitive_typing_spec.rb: raise-guard excludes a parameterized member, leaves an unparameterized member alone, excludes a narrower member than the class actually tested.bin/solargraph typecheck --level strongon the reported repro: 0 problems.