mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-11 03:38:07 +00:00
fix(cfg): harvest Kotlin x++/--x as a def (#2195)
The Kotlin harvester had no postfix_expression/prefix_expression case, so an increment/decrement fell to the default descent and recorded its operand as a use only — never a def. Every sibling harvester (Java/C#/C++/Dart/TS/PHP) models inc/dec, so a Kotlin counting loop (while/for using i++) silently dropped the loop-carried reaching-def of the counter. Add the case: def AND use the operand when it is a plain simple_identifier and the operator is ++/-- (other pre/postfix forms — -x, !x, x!!, x? — stay pure reads, byte-identical to the old descent). Characterization tests for postfix + prefix added; kotlin suite 30 passed, grammar gate + bench --check green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a805e8fa6f
commit
33446d61c3
2 changed files with 41 additions and 0 deletions
|
|
@ -425,6 +425,20 @@ export class KotlinHarvester {
|
|||
}
|
||||
return;
|
||||
}
|
||||
case 'postfix_expression':
|
||||
case 'prefix_expression': {
|
||||
// `x++` / `--x` — def AND use the operand when it is a plain identifier
|
||||
// and the operator is an increment/decrement. Other pre/postfix forms
|
||||
// (`-x`, `!x`, `x!!`, `x?`) are pure reads → walk the operand as a use.
|
||||
const operand = node.namedChild(0);
|
||||
if (operand?.type === 'simple_identifier' && this.isIncDec(node)) {
|
||||
this.def(operand, acc);
|
||||
this.use(operand, acc);
|
||||
} else if (operand) {
|
||||
this.walkValue(operand, acc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'navigation_expression': {
|
||||
// `a.b` / `a?.b` — value read of the chain root only; the suffix name is
|
||||
// not a scalar binding.
|
||||
|
|
@ -467,6 +481,15 @@ export class KotlinHarvester {
|
|||
}
|
||||
}
|
||||
|
||||
/** True iff `node` carries a `++` / `--` operator token (`x++` / `--x`). */
|
||||
private isIncDec(node: SyntaxNode): boolean {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const c = node.child(i);
|
||||
if (c && !c.isNamed && (c.text === '++' || c.text === '--')) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** The `= value` expression of a `property_declaration` (the child after `=`). */
|
||||
private propertyValue(node: SyntaxNode): SyntaxNode | undefined {
|
||||
let sawEq = false;
|
||||
|
|
|
|||
|
|
@ -316,6 +316,24 @@ describe('Kotlin CfgVisitor — def/use harvest', () => {
|
|||
expect(definesBinding(cfg, x)).toBe(true);
|
||||
expect(usesBinding(cfg, x)).toBe(true);
|
||||
});
|
||||
|
||||
const defStmtCount = (cfg: FunctionCfg, idx: number): number =>
|
||||
cfg.blocks.flatMap((bl) => bl.statements ?? []).filter((s) => s.defs.includes(idx)).length;
|
||||
|
||||
it('postfix `x++` defines AND uses the operand (#2195 P2)', () => {
|
||||
const cfg = kotlin.cfgOf(`fun f() { var x = 0; x++ }`);
|
||||
const x = bindingIdx(cfg, 'x');
|
||||
// `var x = 0` defs x once; `x++` must def it AGAIN (the loop-counter
|
||||
// reaching-def the bug dropped) — not record x as a use-only.
|
||||
expect(defStmtCount(cfg, x)).toBe(2);
|
||||
expect(usesBinding(cfg, x)).toBe(true);
|
||||
});
|
||||
|
||||
it('prefix `--x` defines the operand too (#2195 P2)', () => {
|
||||
const cfg = kotlin.cfgOf(`fun f() { var x = 0; --x }`);
|
||||
const x = bindingIdx(cfg, 'x');
|
||||
expect(defStmtCount(cfg, x)).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Kotlin CfgVisitor — functionStartColumn', () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue