mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-06 02:49:56 +00:00
* fix: resolve Java cast-wrapped and this.method() call edges
Two fixes for missing call edges in Java method resolution:
1. compound-receiver.ts — cast expression handling:
- Strip (Type) cast wrappers from receiver text, tracking the
outermost meaningful cast type
- Resolve directly to the cast type class (not the field's
declared type), since the cast narrows the receiver type
- Add this.field chain walker for field-access receivers
- Replace text → workingText throughout the function body
2. scope-resolver.ts:
- Enable resolveThisViaEnclosingClass: true for Java
(activates Case 0.5 in receiver-bound-calls.ts)
Verified on a large-scale Java codebase with no regressions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(scope-resolution): format compound-receiver.ts with prettier (#2353 review F10)
Mechanical prettier --write from repo root — 6 brace-expansion sites and one
ternary re-join, zero logic changes. Clears the quality/format CI failure
that was blocking CI Gate on PR #2353.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(scope-resolution): pin working Java cast-receiver shapes (#2353 review F3)
Fixture-backs the cast resolutions PR #2353 gets right — simple cast,
nested/CFR cast, cast over this.field, and the deliberate declared-type
fallback for a resolvable-shape cast to an unindexed type — each with a
same-named decoy method on the receiver's declared type so later refactors
cannot silently regress them. No resolver changes; tests are green as-is.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(scope-resolution): resolve nothing for unparseable cast types (#2353 review F1)
A receiver paren-group that is type-shaped but unparseable — generic
(List<String>), array (Foo[]), fully-qualified (com.example.Foo) — is a
cast whose type cannot be looked up. Stripping it and falling through
resolved the pre-cast expression's own declared type, emitting a
confident wrong CALLS edge. Classification is now three-way per peel:
simple identifier → capture (outermost wins), type-shaped-unparseable →
resolve nothing (pre-#2353 behavior; noise casts after a captured type
still win), anything else → not a cast, text left untouched. Cast
candidates require a non-empty trailing expression, so plain
parenthesized receivers never capture a cast type.
Red-first: all four shapes reproduced the wrong edge before the fix;
golden digest byte-stable after.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(scope-resolution): delete duplicate this.field walker, seed literal-this chain heads (#2353 review F4/F5/F7)
A/B against the fixture corpus confirmed the generic per-segment walker
(head resolved via the synthesized this typeBinding) already covers every
method-body this.field chain — only initializer contexts (instance
initializer block, field initializer) were walker-dependent, since no
function scope exists there to carry a this binding. Deleting the
duplicate walker removes the naive chainRest.split('.') (F5) and the
widened fieldFallback use (F7) with it; the findEnclosingClassDef head
seed is the deliberate residue covering initializer contexts —
head-resolution only, the per-segment walk stays the single shared
implementation. Post-seed edge set is byte-identical to pre-deletion.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(scope-resolution): gate cast stripping behind opt-in stripReceiverCastExpressions (#2353 review F2)
Cast handling in resolveCompoundReceiverClass now runs only for
languages that opt in via the new ScopeResolver toggle (default off);
Java is the sole opt-in. The peel loop is extracted into the pure,
exported stripCastWrappers helper (placed with the file's other pure
string helpers) so it can be unit-tested directly. Non-opting languages
see receiver text untouched — pre-#2353 behavior by construction
(golden digest unchanged, TS/C++/C# suites green, 796/796). Shared-code
comments are language-neutral per AGENTS.md; the contract JSDoc carries
the classifier grammar, the second-language escalation rule, and the
Case 3b/Case 4 pass-through non-goal.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(scope-resolution): cap cast-peel iterations in stripCastWrappers (#2353 review F8)
MAX_CAST_PEEL = 16 (each cast level costs at most two peels, so this
covers 8-level nesting with headroom — real cast nesting, including
decompiler output, is a handful of levels). Each peel rescans the
working text for its matching close paren, so pathological nested-paren
input was O(N²); the cap bounds it at O(N·16). Exceeding the cap bails
all-or-nothing with the original text (not-a-cast outcome). Adds the
helper's first unit tests: 14 scenarios covering capture, unparseable
shapes, redundant-paren unwrap, captured-type precedence, rawName
no-op, over/under-cap, and unbalanced-paren termination.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(scope-resolution): revert Java resolveThisViaEnclosingClass, pin Case 4 bare-this dispatch (#2353 review F6/F9)
Remove resolveThisViaEnclosingClass from the Java scope resolver: the
toggle's own contract doc prescribes keeping it disabled where Case 4
(the synthesized this typeBinding) already handles this, and Case 0.5's
C++-authored semantics (hiddenByName arity-hiding, method-before-field)
provably bypass the interface-dispatch fan-out only Case 4 emits.
A/B gate (new java-this-dispatch pinning fixtures): flag-off 7/7 green;
flag-on 2/7 red (hiddenByName drops the this.greet overload site —
masked by a free-call-fallback 'local-call' edge — and the
interface-dispatch fan-out is missing). Corpus A/B over all 54 java-*
fixtures: 2 fixtures differ — java-this-dispatch (reason
'local-call'→'global' on the bare-this overload site; +2
interface-dispatch fan-out edges flag-off) and java-this-field-chain
(2 initializer-context bare-this ACCESSES reads emitted only by Case
0.5, which Case 4 cannot resolve — no synthesized this binding without
a Function scope; the corresponding CALLS edges are unaffected via the
F4 commit's literal-this head seed).
Also (F9): insert Case 0.5 into the I4 case-order listings (contract +
receiver-bound-calls header, now 8-case, marked gated) so the next flag
flip is visible at review time; the two 'sole C++ language' comments
are accurate again unedited.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(scope-resolution): restrict literal-this head seed to initializer contexts (#2353 review follow-up)
Final-review finding (two independent reviewer angles): the literal-this
chain-head seed landed ungated in shared code, so any language's
this-headed chain in a scope without a synthesized this typeBinding —
including contexts where the language DELIBERATELY leaves this unbound
(object-literal methods, nested plain functions) — would seed from the
lexically enclosing class. isInitializerContext now permits the seed
only when no Function scope sits between the site and its class, which
is precisely the field-initializer / instance-initializer shape the
seed exists for. Adds a TS guard fixture pinning that an
object-literal method's this.field.method() chain emits no fabricated
edge (mechanism did not empirically reproduce even ungated — the
restriction is conservative hardening, and the pin keeps it that way).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(scope-resolution): attach stripCastWrappers JSDoc, fast-path non-paren receivers (#2353 review nits)
Two final-review nits: a blank line detached the helper's 30-line
classification-contract JSDoc from the declaration (IDE hover showed
nothing at call sites); and the gate now skips the helper call plus
result allocation for the majority of receivers that cannot be casts
because they do not start with '(' — the helper's own check stays as
the safety net.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* bench(scope-capture): rebaseline Java fingerprint for new #2357 fixtures
The scope-capture correctness fingerprint hashes captures over the
java-* fixture corpus; the three fixture dirs added by this PR
(java-cast-receiver, java-this-field-chain, java-this-dispatch) extend
that corpus, so the fingerprint moves. Verified purely additive: with
the three new dirs parked, the fingerprint reproduces the prior
baseline byte-identically — no emit/capture behavior changed.
--check now passes for all 14 languages.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: ww <ww@wwdeMacBook-Pro.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
113 lines
4.1 KiB
TypeScript
113 lines
4.1 KiB
TypeScript
/**
|
|
* Unit tests for `stripCastWrappers` — the pure cast-peeling helper in
|
|
* `compound-receiver.ts`, consumed by `resolveCompoundReceiverClass`
|
|
* when a language opts in via `stripReceiverCastExpressions`.
|
|
*
|
|
* PR #2353 review F8: the peel loop rescans the working text for the
|
|
* matching close paren on every iteration, so adversarial nested-paren
|
|
* input (`((((…))))`) cost O(N²) with no iteration cap (the file's
|
|
* `COMPOUND_RECEIVER_MAX_DEPTH` guard does not cover this loop). The
|
|
* fix adds `MAX_CAST_PEEL`; exceeding it bails all-or-nothing with the
|
|
* ORIGINAL text and the not-a-cast outcome. These are the helper's
|
|
* first unit tests — they also pin the three-way cast classification
|
|
* (KTD2: simple identifier captured / type-shaped-but-unparseable
|
|
* reported / anything else untouched) that the Java integration
|
|
* fixtures exercise only end-to-end.
|
|
*
|
|
* The helper is a pure text scan — no fixtures, no pipeline needed.
|
|
*/
|
|
|
|
import { describe, it, expect } from 'vitest';
|
|
import { stripCastWrappers } from '../../../src/core/ingestion/scope-resolution/passes/compound-receiver.js';
|
|
|
|
describe('stripCastWrappers — cast classification (KTD2)', () => {
|
|
it.each([
|
|
{ input: '((Foo)x)', workingText: 'x', castType: 'Foo' },
|
|
{ input: '((Target)((Object)expr))', workingText: 'expr', castType: 'Target' },
|
|
{ input: '( Foo ) x', workingText: 'x', castType: 'Foo' },
|
|
])('captures the simple cast type in $input', ({ input, workingText, castType }) => {
|
|
expect(stripCastWrappers(input)).toEqual({
|
|
workingText,
|
|
castType,
|
|
unresolvableCast: false,
|
|
});
|
|
});
|
|
|
|
it.each([
|
|
{ input: '(List<String>)obj' },
|
|
{ input: '(Foo[])obj' },
|
|
{ input: '(com.example.Foo)obj' },
|
|
{ input: '( com.example.Foo ) obj' },
|
|
])('reports the type-shaped but unparseable cast $input as unresolvable', ({ input }) => {
|
|
expect(stripCastWrappers(input)).toEqual({
|
|
workingText: input,
|
|
castType: undefined,
|
|
unresolvableCast: true,
|
|
});
|
|
});
|
|
|
|
it('leaves a parenthesized non-cast expression untouched', () => {
|
|
expect(stripCastWrappers('(a || b).field')).toEqual({
|
|
workingText: '(a || b).field',
|
|
castType: undefined,
|
|
unresolvableCast: false,
|
|
});
|
|
});
|
|
|
|
it('unwraps a plain parenthesized variable without capturing a cast type (KTD2 rule ii)', () => {
|
|
// `(foo)` in receiver position (as in `(foo).bar()`) is a
|
|
// redundant-paren unwrap of a VARIABLE — capturing `foo` as a cast
|
|
// type here is exactly F1's wrong-edge shape.
|
|
expect(stripCastWrappers('(foo)')).toEqual({
|
|
workingText: 'foo',
|
|
castType: undefined,
|
|
unresolvableCast: false,
|
|
});
|
|
});
|
|
|
|
it('keeps the captured type when a later cast group is unparseable (KTD2 rule iii)', () => {
|
|
expect(stripCastWrappers('(Target)(List<String>)obj')).toEqual({
|
|
workingText: 'obj',
|
|
castType: 'Target',
|
|
unresolvableCast: false,
|
|
});
|
|
});
|
|
|
|
it('leaves a typeBinding-rawName-shaped input untouched', () => {
|
|
// Case 3b / Case 4 pass-through shape (U5's known non-goal): the
|
|
// stripper must be a structural no-op on rawName inputs.
|
|
expect(stripCastWrappers('Factory.get_user()')).toEqual({
|
|
workingText: 'Factory.get_user()',
|
|
castType: undefined,
|
|
unresolvableCast: false,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('stripCastWrappers — MAX_CAST_PEEL iteration cap (#2353 review F8)', () => {
|
|
it('bails all-or-nothing with the original text when nesting exceeds the cap', () => {
|
|
const input = '('.repeat(100) + 'Type' + ')'.repeat(100);
|
|
expect(stripCastWrappers(input)).toEqual({
|
|
workingText: input,
|
|
castType: undefined,
|
|
unresolvableCast: false,
|
|
});
|
|
});
|
|
|
|
it('still unwraps nesting under the cap', () => {
|
|
const input = '('.repeat(10) + 'Type' + ')'.repeat(10);
|
|
expect(stripCastWrappers(input)).toEqual({
|
|
workingText: 'Type',
|
|
castType: undefined,
|
|
unresolvableCast: false,
|
|
});
|
|
});
|
|
|
|
it('terminates on unbalanced parens with the text untouched', () => {
|
|
expect(stripCastWrappers('(((')).toEqual({
|
|
workingText: '(((',
|
|
castType: undefined,
|
|
unresolvableCast: false,
|
|
});
|
|
});
|
|
});
|