Skip to content

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

Draft
apiology wants to merge 3 commits into
castwide:masterfrom
apiology:fix-isa-or-narrowing
Draft

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

Conversation

@apiology

@apiology apiology commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

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.
@apiology

Copy link
Copy Markdown
Contributor Author

Opened against the wrong repo (castwide/solargraph instead of apiology/solargraph). Reopening correctly.

@apiology apiology closed this Aug 21, 2026
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.
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