Skip to content

Narrow is_a? checks joined by || to their union type - #64

Open
apiology wants to merge 3 commits into
masterfrom
fix-isa-or-narrowing
Open

Narrow is_a? checks joined by || to their union type#64
apiology wants to merge 3 commits into
masterfrom
fix-isa-or-narrowing

Conversation

@apiology

@apiology apiology commented Aug 21, 2026

Copy link
Copy Markdown
Owner

This PR was written by Claude Code on behalf of @apiology.

Problem: solargraph typecheck --level strong reports a call as accepting the full, unnarrowed union of a variable's declared type, even inside an if branch that has already ruled out some of those types via ||-joined is_a? checks.

class Repro
  # @param e [Array<Symbol>, String]
  def eval_function_call(e); end

  # @param e [Array<Symbol>, String, Integer]
  def eval(e)
    if e.is_a?(Array) || e.is_a?(String)
      eval_function_call(e)  # reports e as Array<Symbol>, String, Integer
    end
  end
end

This produces a false Wrong argument type problem for any code relying on this common idiom to narrow a variable before use.

Solution: When both sides of an || are plain is_a? checks on the same variable, narrow that variable to the union of the two checked types inside the if branch; any other shape - different variables, or a side that isn't a plain is_a? call - is left unnarrowed, same as before.

Known limitation: a chain of three or more is_a? checks (a.is_a?(X) || a.is_a?(Y) || a.is_a?(Z)) still isn't narrowed, since the AST nests as (X || Y) || Z and the outer left side is an :or node rather than a :send. This degrades to the pre-existing no-narrowing behavior rather than narrowing incorrectly.

FlowSensitiveTyping#process_or never computed any true-branch
narrowing for an ||-joined condition, so a check like
e.is_a?(Array) || e.is_a?(String) left e at its full declared type
inside the if body instead of narrowing to Array | String.

Add process_or_isa_union, which narrows only when both sides of the
|| are plain is_a? calls on the same variable, using the same
process_facts/find_var/ComplexType.parse primitives process_isa
already uses. Any other shape (different variables, or a side that
isn't a plain is_a? call) is left unnarrowed rather than guessed at.
Cut the process_or_isa_union docstring and the process_or
comment explaining the || narrowing exception down to the
1-3 line budget, keeping only the non-obvious why.
@apiology
apiology marked this pull request as ready for review September 7, 2026 00:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant