mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-03 02:21:44 +00:00
fix(kotlin): see through braced if-branches in callable alternatives (#3354)
tree-sitter-kotlin wraps a braced branch as
`control_structure_body > statements > <expr>`, so for any non-empty
block kotlinValueAlternatives saw exactly one named child (`statements`)
and pushed the wrapper itself as the branch value. operandSyntax emits
nothing for a `statements` node, so
`val run = if (c) { ::f } else { ::g }; run()` produced no flow edges,
and the "multi-statement block stays opaque" guard could never fire.
Descend one level through `statements` and require exactly one
non-comment expression there. Empty blocks (`{}` has no named children),
multi-statement blocks, and `if` without `else` still return the whole
`if` as one opaque source. The doc comment now describes that.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6acca5a77b
commit
beeb570b82
3 changed files with 43 additions and 8 deletions
|
|
@ -95,19 +95,25 @@ const KOTLIN_CALLABLE_CAPTURE_OPTIONS = {
|
|||
const isKotlinComment = (node: SyntaxNode): boolean =>
|
||||
node.type === 'line_comment' || node.type === 'multiline_comment';
|
||||
|
||||
const namedNonComments = (node: SyntaxNode): SyntaxNode[] =>
|
||||
node.namedChildren.filter(
|
||||
(child): child is SyntaxNode => child !== null && !isKotlinComment(child),
|
||||
);
|
||||
|
||||
/**
|
||||
* Branches of a Kotlin value-selecting expression (#3354). `a ?: b` is a
|
||||
* FIELDLESS `elvis_expression` (positional operands), so the shared
|
||||
* field-based rule never saw it and only the last operand flowed. An
|
||||
* `if_expression` fields its branches as `control_structure_body` wrappers:
|
||||
* a wrapper holding one expression is that expression's value, anything
|
||||
* else (a multi-statement block) keeps the whole `if` one opaque source.
|
||||
* a bare branch (`if (c) ::f`) holds the expression directly, a braced one
|
||||
* (`if (c) { ::f }`) nests it one level deeper in a `statements` node. A
|
||||
* branch holding exactly one expression is that expression's value; an
|
||||
* empty or multi-statement block, or an `if` without `else`, keeps the
|
||||
* whole `if` one opaque source.
|
||||
*/
|
||||
function kotlinValueAlternatives(node: SyntaxNode): readonly SyntaxNode[] | undefined {
|
||||
if (node.type === 'elvis_expression') {
|
||||
const operands = node.namedChildren.filter(
|
||||
(child): child is SyntaxNode => child !== null && !isKotlinComment(child),
|
||||
);
|
||||
const operands = namedNonComments(node);
|
||||
return operands.length === 2 ? operands : undefined;
|
||||
}
|
||||
if (node.type !== 'if_expression') return undefined;
|
||||
|
|
@ -115,9 +121,9 @@ function kotlinValueAlternatives(node: SyntaxNode): readonly SyntaxNode[] | unde
|
|||
for (const field of ['consequence', 'alternative'] as const) {
|
||||
const body = node.childForFieldName(field);
|
||||
if (body === null) return [node];
|
||||
const values = body.namedChildren.filter(
|
||||
(child): child is SyntaxNode => child !== null && !isKotlinComment(child),
|
||||
);
|
||||
let values = namedNonComments(body);
|
||||
const [block] = values;
|
||||
if (values.length === 1 && block?.type === 'statements') values = namedNonComments(block);
|
||||
const [value] = values;
|
||||
if (values.length !== 1 || value === undefined) return [node];
|
||||
branches.push(value);
|
||||
|
|
|
|||
|
|
@ -11,3 +11,20 @@ fun ifExpression(fast: Boolean) {
|
|||
val run = if (fast) ::runThen else ::runElse
|
||||
run()
|
||||
}
|
||||
|
||||
fun runBracedThen() {}
|
||||
fun runBracedElse() {}
|
||||
|
||||
fun braced(fast: Boolean) {
|
||||
val run = if (fast) { ::runBracedThen } else { ::runBracedElse }
|
||||
run()
|
||||
}
|
||||
|
||||
fun log() {}
|
||||
fun runBlockThen() {}
|
||||
fun runBlockElse() {}
|
||||
|
||||
fun multiStatement(fast: Boolean) {
|
||||
val run = if (fast) { log(); ::runBlockThen } else { ::runBlockElse }
|
||||
run()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,18 @@ describe('Kotlin callable chosen by `?:` / `if` expression', () => {
|
|||
expect.arrayContaining(['ifExpression → runThen', 'ifExpression → runElse']),
|
||||
);
|
||||
});
|
||||
|
||||
// A braced branch nests its value in a `statements` node one level below
|
||||
// the `control_structure_body` wrapper.
|
||||
it('`if (c) { ::f } else { ::g }` reaches both branches', () => {
|
||||
expect(callsOf(result)).toEqual(
|
||||
expect.arrayContaining(['braced → runBracedThen', 'braced → runBracedElse']),
|
||||
);
|
||||
});
|
||||
|
||||
it('a multi-statement branch keeps the whole `if` opaque', () => {
|
||||
expect(callsOf(result).filter((edge) => edge.startsWith('multiStatement →'))).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Swift callable chosen by `??` / `?:`', () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue