Skip to content

Strip nil from or-expression fallbacks in conditional branch values - #1309

Draft
apiology wants to merge 1 commit into
castwide:masterfrom
apiology:fix-or-in-branch-nil
Draft

Strip nil from or-expression fallbacks in conditional branch values#1309
apiology wants to merge 1 commit into
castwide:masterfrom
apiology:fix-or-in-branch-nil

Conversation

@apiology

Copy link
Copy Markdown
Contributor

At strong level, a method whose declared @return is ::Boolean is flagged Declared return type ::Boolean does not match inferred type ::Boolean, nil when an or-expression with a nullable left operand is the value of a conditional branch ��� even though nil can't escape the ||:

class Container
  # @param m [Module, nil]
  # @param expected [Module]
  # @return [Boolean]
  def check(m, expected)
    if m.nil?
      fallback
    else
      m <= expected || fallback  # Module#<= returns bool?
    end
  end

  # @return [Boolean]
  def fallback
    true
  end
end
shape inferred
m <= expected || fallback as method tail ::Boolean ���
same, after return fallback if m.nil? guard ::Boolean ���
same, as the else-branch value of if/else ::Boolean, nil ���

Root cause: DeepInference.reduce_to_value_nodes flattens an :or node into both operand nodes, typing each independently and unioning ��� bypassing Chain::Or#resolve, which strips nil from the union unless every operand is nullable. Method-tail or-expressions go through NodeChainer ��� Chain::Or and get that treatment; conditional branch values go through reduce_to_value_nodes and don't.

The fix drops the :or special case so the node stays whole and routes through Chain::Or like any other or-expression. Two existing node-level specs pinned the flattening as a contract (return 1 || "2" ��� two literal nodes); updated to expect the intact :or node ��� the operand union is preserved by Chain::Or.

Test plan:

  • New failing-first specs: node_methods_spec (or-node stays whole) and strong_spec (no false positive on the repro shape)
  • Full suite: 1,626 examples, 0 failures, 60 pending

���� This PR was written by Claude (Anthropic's AI assistant) on behalf of @apiology.

���� Generated with Claude Code

https://claude.ai/code/session_01H1FEjW6nMpZrWPmeWX9miT

reduce_to_value_nodes flattened an :or node into both operand nodes, so
each was typed independently and unioned -- bypassing Chain::Or, which
already strips nil from the union when the right operand is not
nullable. A bare 'x || fallback' method tail inferred correctly, but the
same expression as an if/else branch value leaked the left operand's nil
into the method's inferred return type.

Keep the :or node whole so it routes through Chain::Or like any other
or-expression.
apiology added a commit to apiology/solargraph that referenced this pull request Aug 16, 2026
…ional branch values

# Conflicts:
#	lib/solargraph/parser/parser_gem/node_methods.rb
apiology added a commit to apiology/solargraph that referenced this pull request Aug 16, 2026
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