Ruby: Add CaseElseBranch AST node to distinguish else-branch from its body#21991
Open
Copilot wants to merge 4 commits into
Open
Ruby: Add CaseElseBranch AST node to distinguish else-branch from its body#21991Copilot wants to merge 4 commits into
Copilot wants to merge 4 commits into
Conversation
Copilot created this pull request from a session on behalf of
aschackmull
June 16, 2026 08:27
View session
Contributor
There was a problem hiding this comment.
Pull request overview
This pull request updates the Ruby AST model for case expressions to represent the else branch as a distinct CaseElseBranch node (separate from its body), aligning it more closely with WhenClause / InClause structure and enabling callers to refer to the else-branch node itself.
Changes:
- Introduces
CaseElseBranchas a new AST node and updatesCaseExpr.getElseBranch()to return it (breaking change). - Implements
CaseElseBranchvia synthesized-node wrapping in the AST synthesis system, with corresponding internal AST updates. - Updates control-flow and dataflow handling plus library tests/expected outputs and adds a breaking change note.
Show a summary per file
| File | Description |
|---|---|
| ruby/ql/test/library-tests/modules/modules.expected | Updates expected module-enclosing output for the new else-branch node. |
| ruby/ql/test/library-tests/modules/methods.expected | Updates expected method-enclosing output for the new else-branch node. |
| ruby/ql/test/library-tests/ast/control/CaseExpr.ql | Adjusts library-test query to reflect getElseBranch()’s new return type. |
| ruby/ql/test/library-tests/ast/AstDesugar.expected | Updates desugaring AST expectations to include CaseElseBranch and its body. |
| ruby/ql/test/library-tests/ast/Ast.expected | Updates AST expectations to show CaseElseBranch wrapping the else body. |
| ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll | Updates case-branch last-eval logic to follow else branch via .getElseBranch().getBody(). |
| ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImpl.qll | Adds a transparent CFG tree wrapper for CaseElseBranch delegating to its body. |
| ruby/ql/lib/codeql/ruby/ast/internal/Synthesis.qll | Adds synth kind + synthesis wiring for CaseElseBranch and updates pattern desugar synthesis. |
| ruby/ql/lib/codeql/ruby/ast/internal/Control.qll | Wraps case/in else branches with the synthesized CaseElseBranch node; adds implementation class. |
| ruby/ql/lib/codeql/ruby/ast/internal/AST.qll | Registers TCaseElseBranchSynth in cached AST node kinds/classes. |
| ruby/ql/lib/codeql/ruby/ast/Control.qll | Exposes public CaseElseBranch API and updates CaseExpr branch/else docs and return type. |
| ruby/ql/lib/change-notes/2026-06-15-case-else-branch.md | Adds breaking-change release note for the getElseBranch() return type change. |
Copilot's findings
- Files reviewed: 12/12 changed files
- Comments generated: 1
| --- | ||
| category: breaking | ||
| --- | ||
| * The `else` branch of a `case` expression is no longer represented as a `StmtSequence` directly. Instead, a new `CaseElseBranch` AST node wraps the body (a `StmtSequence`). `CaseExpr.getElseBranch()` now returns a `CaseElseBranch`, and the body of the else branch can be accessed via `CaseElseBranch.getBody()`. |
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.
In Ruby's
caseexpression, theelsebranch was directly represented as aStmtSequence, unlikeWhenClauseandInClausewhich both have a body distinct from the branch node itself. This inconsistency made it impossible to refer to the else-branch as a node separate from its contents.Changes
New AST node:
CaseElseBranchCaseExpr.getElseBranch()now returns aCaseElseBranchinstead ofStmtSequenceCaseElseBranch.getBody()returns theStmtSequencebody — mirrorsWhenClause.getBody()/InClause.getBody()Implementation (synthesized node approach)
CaseElseBranchKind/TCaseElseBranchSynthto the synthesis system — a wrapper node injected between theCaseExprand the existingElse/StmtSequencereal node, preserving location from the underlying tree-sitterElsenodeCaseWhenClause.getBranchandCaseMatch.getBranchnow return the synthesized wrapper instead of the rawElsenode for the else indexTestPatternDesugar(expr in pattern) updated to wrap its synthesizedElseSynthin aCaseElseBranchSynthCFG
CaseElseBranchTree— transparent to control flow; delegatesfirst/lastto the bodyBreaking change
CaseExpr.getElseBranch()return type changed fromStmtSequencetoCaseElseBranch; callers needing the body should call.getBody()