mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-28 06:22:22 +00:00
Closes #1762. `val animal: Animal = Dog(); animal.speak()` resolved to `Animal.speak` (or no edge to Dog at all) under `REGISTRY_PRIMARY_KOTLIN=1` because the Kotlin scope query emits BOTH an annotation type-binding (`animal -> Animal`) and a constructor- inferred type-binding (`animal -> Dog`). The generic scope-extractor ranks annotation sources higher than constructor-inferred sources (see `typeBindingStrength` in scope-extractor.ts), so the annotation always won and `animal.speak()` dispatched against the static type. Kotlin's virtual dispatch semantics expect the dynamic type — the overriding `Dog.speak` should win when the RHS is a constructor call, because that's what runs at runtime. Fix: in `emitKotlinScopeCaptures`, suppress the `@type-binding. annotation` capture when the underlying `property_declaration` has a `call_expression` value sibling. The constructor-inferred capture remains, becomes the sole binding for the variable, and receiver-bound resolution dispatches against the constructed class (and walks its MRO). This is intentionally Kotlin-specific — flipping precedence globally would change behavior for other languages whose static-type annotations are still the right binding when present. Kotlin is the language where the constructor RHS is the dispatch target by design. Verification (REGISTRY_PRIMARY_KOTLIN=1): - Forced-mode: 21 -> 20 failing of 175 (1 fewer; test 1715 in `test/integration/resolvers/kotlin.test.ts` now green). - Default-mode Kotlin: 175/175 unchanged. - Full resolver suite: 2216/2216 unchanged. - Remaining 20 failures are tracked by sibling sub-issues (#1758, #1759, #1760, #1761, #1763). Does NOT add Kotlin to MIGRATED_LANGUAGES per parent #1746 flip criteria. Closes #1762. Refs #1746. Co-authored-by: Test <test@example.com>
This commit is contained in:
parent
7596d8f9ce
commit
2dd1065f0e
1 changed files with 40 additions and 1 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import type { Capture, CaptureMatch } from 'gitnexus-shared';
|
||||
import type { Capture, CaptureMatch, Range } from 'gitnexus-shared';
|
||||
import {
|
||||
findNodeAtRange,
|
||||
nodeToCapture,
|
||||
|
|
@ -73,6 +73,30 @@ export function emitKotlinScopeCaptures(
|
|||
if (navNode === null || !shouldEmitReadMember(navNode)) continue;
|
||||
}
|
||||
|
||||
// Virtual dispatch via constructor type (#1762). When a property
|
||||
// declaration carries BOTH an explicit type annotation AND a
|
||||
// constructor-style call value (e.g. `val animal: Animal = Dog()`),
|
||||
// suppress the annotation capture so the constructor-inferred
|
||||
// binding wins. This matches Kotlin's virtual dispatch semantics:
|
||||
// `animal.speak()` should resolve to the overriding `Dog.speak`
|
||||
// (the dynamic type), not `Animal.speak` (the static annotation).
|
||||
//
|
||||
// The annotation source has higher precedence than constructor-
|
||||
// inferred in the generic scope-extractor (see
|
||||
// `typeBindingStrength` in scope-extractor.ts), so the only way to
|
||||
// make the constructor type prevail is to drop the annotation at
|
||||
// emission time.
|
||||
if (
|
||||
grouped['@type-binding.annotation'] !== undefined &&
|
||||
grouped['@type-binding.name'] !== undefined &&
|
||||
grouped['@type-binding.type'] !== undefined
|
||||
) {
|
||||
const annotation = grouped['@type-binding.annotation']!;
|
||||
if (propertyDeclHasConstructorValue(tree.rootNode, annotation.range)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (grouped['@scope.function'] !== undefined) {
|
||||
out.push(grouped);
|
||||
const fnNode = findNodeAtRange(
|
||||
|
|
@ -646,6 +670,21 @@ function shouldEmitReadMember(navNode: SyntaxNode): boolean {
|
|||
return true;
|
||||
}
|
||||
|
||||
/** True when the property_declaration anchored at `range` has a
|
||||
* `call_expression` value sibling (i.e. `val x: T = Foo()`). Used to
|
||||
* suppress the explicit-annotation type-binding capture so the
|
||||
* constructor-inferred binding wins (#1762). */
|
||||
function propertyDeclHasConstructorValue(rootNode: SyntaxNode, range: Range): boolean {
|
||||
const propNode = findNodeAtRange(rootNode, range, 'property_declaration');
|
||||
if (propNode === null) return false;
|
||||
const variable = propNode.namedChildren.find((c) => c.type === 'variable_declaration');
|
||||
if (variable === undefined) return false;
|
||||
const value = propNode.namedChildren.find(
|
||||
(c) => c.id !== variable.id && c.type !== 'binding_pattern_kind',
|
||||
);
|
||||
return value?.type === 'call_expression';
|
||||
}
|
||||
|
||||
function callArguments(callNode: SyntaxNode): SyntaxNode[] {
|
||||
const suffix = callNode.namedChildren.find((child) => child.type === 'call_suffix');
|
||||
if (suffix === undefined) return [];
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue