mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-08-28 05:25:25 +00:00
fix(type-resolution): address PR #387 review — dead code, nullable_type, scope boundaries + integration tests
- Remove dead replayPendingItems array and inert if-block in type-env.ts - Add nullable_type fallback in extractKotlinDeclaration for val x: User? local vars - Tighten isCSharpNullableDecl to avoid substring false positives on type names - Add missing scope boundaries: function_expression (TS), constructor_declaration/ local_function_statement/lambda_expression (C#) in null-check narrowing walkers - Extend null-check narrowing fixtures and add 4 integration tests covering: Kotlin local variable nullable, C# constructor + lambda, TS function expression
This commit is contained in:
parent
b6947b0c02
commit
06994e474a
12 changed files with 105 additions and 18 deletions
|
|
@ -894,20 +894,12 @@ export const buildTypeEnv = (
|
|||
// - fixpoint: users → User[]
|
||||
// - replay: users now typed → u → User
|
||||
if (pendingForLoops.length > 0 && config.extractForLoopBinding) {
|
||||
const replayPendingItems: Array<{ scope: string } & PendingAssignment> = [];
|
||||
for (const { node, scope } of pendingForLoops) {
|
||||
if (!env.has(scope)) env.set(scope, new Map());
|
||||
const scopeEnv = env.get(scope)!;
|
||||
config.extractForLoopBinding(node, { scopeEnv, declarationTypeNodes, scope, returnTypeLookup });
|
||||
}
|
||||
// Collect any new pending items from replay-produced variables.
|
||||
// Re-walk the for-loop bodies to pick up field/method chains on the now-typed loop vars.
|
||||
// For simplicity, run a mini-fixpoint on any pending items that were already collected
|
||||
// but couldn't resolve because they depended on the loop variable.
|
||||
if (replayPendingItems.length > 0) {
|
||||
resolveFixpointBindings(replayPendingItems, env, returnTypeLookup, symbolTable);
|
||||
}
|
||||
// Also re-run the main fixpoint to resolve items that depended on loop variables.
|
||||
// Re-run the main fixpoint to resolve items that depended on loop variables.
|
||||
// Only needed if replay actually produced new bindings.
|
||||
const unresolvedBefore = pendingItems.filter((item) => {
|
||||
const scopeEnv = env.get(item.scope);
|
||||
|
|
|
|||
|
|
@ -296,18 +296,19 @@ const findCSharpIfConsequenceBlock = (expr: SyntaxNode): SyntaxNode | undefined
|
|||
}
|
||||
return undefined;
|
||||
}
|
||||
if (current.type === 'block' || current.type === 'method_declaration') return undefined;
|
||||
if (current.type === 'block' || current.type === 'method_declaration'
|
||||
|| current.type === 'constructor_declaration' || current.type === 'local_function_statement'
|
||||
|| current.type === 'lambda_expression') return undefined;
|
||||
current = current.parent;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/** Check if a C# declaration type node represents a nullable type.
|
||||
* Checks for nullable_type node or text containing '?' or 'null'. */
|
||||
* Checks for nullable_type AST node or '?' in the type text (e.g., User?). */
|
||||
const isCSharpNullableDecl = (declTypeNode: SyntaxNode): boolean => {
|
||||
if (declTypeNode.type === 'nullable_type') return true;
|
||||
const text = declTypeNode.text;
|
||||
return text.includes('?') || text.includes('null');
|
||||
return declTypeNode.text.includes('?');
|
||||
};
|
||||
|
||||
const extractPatternBinding: PatternBindingExtractor = (node, scopeEnv, declarationTypeNodes, scope) => {
|
||||
|
|
|
|||
|
|
@ -295,7 +295,8 @@ const extractKotlinDeclaration: TypeBindingExtractor = (node: SyntaxNode, env: M
|
|||
const varDecl = findChildByType(node, 'variable_declaration');
|
||||
if (varDecl) {
|
||||
const nameNode = findChildByType(varDecl, 'simple_identifier');
|
||||
const typeNode = findChildByType(varDecl, 'user_type');
|
||||
const typeNode = findChildByType(varDecl, 'user_type')
|
||||
?? findChildByType(varDecl, 'nullable_type');
|
||||
if (!nameNode || !typeNode) return;
|
||||
const varName = extractVarName(nameNode);
|
||||
const typeName = extractSimpleTypeName(typeNode);
|
||||
|
|
|
|||
|
|
@ -529,8 +529,8 @@ const findIfConsequenceBlock = (binaryExpr: SyntaxNode): SyntaxNode | undefined
|
|||
return undefined;
|
||||
}
|
||||
// Stop climbing at function/block boundaries — don't cross scope
|
||||
if (current.type === 'function_declaration' || current.type === 'arrow_function'
|
||||
|| current.type === 'method_definition') return undefined;
|
||||
if (current.type === 'function_declaration' || current.type === 'function_expression'
|
||||
|| current.type === 'arrow_function' || current.type === 'method_definition') return undefined;
|
||||
current = current.parent;
|
||||
}
|
||||
return undefined;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,18 @@
|
|||
using NullCheck.Models;
|
||||
using System;
|
||||
|
||||
namespace NullCheck.Services
|
||||
{
|
||||
public class App
|
||||
{
|
||||
public App(User? x)
|
||||
{
|
||||
if (x != null)
|
||||
{
|
||||
x.Save();
|
||||
}
|
||||
}
|
||||
|
||||
public void ProcessInequality(User x)
|
||||
{
|
||||
if (x != null)
|
||||
|
|
@ -19,5 +28,17 @@ namespace NullCheck.Services
|
|||
x.Save();
|
||||
}
|
||||
}
|
||||
|
||||
public void ProcessInLambda(User? x)
|
||||
{
|
||||
Action act = () =>
|
||||
{
|
||||
if (x != null)
|
||||
{
|
||||
x.Save();
|
||||
}
|
||||
};
|
||||
act();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,3 +3,7 @@ package models
|
|||
class User {
|
||||
fun save() {}
|
||||
}
|
||||
|
||||
fun findUser(): User? {
|
||||
return null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,17 @@
|
|||
package services
|
||||
|
||||
import models.User
|
||||
import models.findUser
|
||||
|
||||
fun processNullable(x: User?) {
|
||||
if (x != null) {
|
||||
x.save()
|
||||
}
|
||||
}
|
||||
|
||||
fun processLocalNullable() {
|
||||
val x: User? = findUser()
|
||||
if (x != null) {
|
||||
x.save()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,3 +17,9 @@ function processUndefined(x: User | undefined) {
|
|||
x.save();
|
||||
}
|
||||
}
|
||||
|
||||
const processFuncExpr = function(x: User | null) {
|
||||
if (x !== null) {
|
||||
x.save();
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1452,4 +1452,20 @@ describe('C# null-check narrowing resolution (Phase C)', () => {
|
|||
);
|
||||
expect(wrongCall).toBeUndefined();
|
||||
});
|
||||
|
||||
it('resolves x.Save() inside constructor via null-check narrowing', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const saveCall = calls.find(c =>
|
||||
c.target === 'Save' && c.source === 'App' && c.targetFilePath.includes('User'),
|
||||
);
|
||||
expect(saveCall).toBeDefined();
|
||||
});
|
||||
|
||||
it('resolves x.Save() inside lambda via null-check narrowing', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const saveCall = calls.find(c =>
|
||||
c.target === 'Save' && c.source === 'ProcessInLambda' && c.targetFilePath.includes('User'),
|
||||
);
|
||||
expect(saveCall).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1523,4 +1523,12 @@ describe('Kotlin null-check narrowing resolution (Phase C)', () => {
|
|||
);
|
||||
expect(wrongCall).toBeUndefined();
|
||||
});
|
||||
|
||||
it('resolves x.save() from local variable val x: User? via null-check narrowing', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const saveCall = calls.find(c =>
|
||||
c.target === 'save' && c.source === 'processLocalNullable' && c.targetFilePath.includes('User'),
|
||||
);
|
||||
expect(saveCall).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2213,4 +2213,12 @@ describe('TypeScript null-check narrowing resolution (Phase C)', () => {
|
|||
);
|
||||
expect(saveCall).toBeDefined();
|
||||
});
|
||||
|
||||
it('resolves x.save() inside function expression null-check (processFuncExpr)', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const saveCall = calls.find(c =>
|
||||
c.target === 'save' && c.source === 'processFuncExpr' && c.targetFilePath.includes('models'),
|
||||
);
|
||||
expect(saveCall).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -96,6 +96,22 @@ The goal is not to build a compiler. The goal is to support high-value static an
|
|||
|
||||
## Open Phases
|
||||
|
||||
### Phase P: Polymorphism & Overloading
|
||||
|
||||
**Plan:** `docs/plans/2026-03-19-feat-polymorphism-overloading-type-resolution-plan.md`
|
||||
|
||||
Four incremental phases:
|
||||
1. **Parameter type metadata** — extend `SymbolDefinition` with `parameterTypes: string[]` extracted during parsing
|
||||
2. **Overload disambiguation** — filter overloaded methods by argument literal types at call sites
|
||||
3. **Constructor-visible virtual dispatch** — `Base b = new Derived(); b.method()` resolves to `Derived#method` when constructor type is a known subclass
|
||||
4. **Covariant return type awareness** — prefer child's return type over inherited definition
|
||||
|
||||
Languages benefiting: Java, Kotlin, C#, C++, TypeScript (overloading). All OOP languages (virtual dispatch).
|
||||
|
||||
**Impact: High | Effort: High**
|
||||
|
||||
---
|
||||
|
||||
### Phase S: Swift Parity
|
||||
|
||||
**Blocked on** tree-sitter-swift Node 22 compatibility.
|
||||
|
|
@ -145,10 +161,12 @@ config.validate(); // missed
|
|||
```
|
||||
Milestone D (Phases A, B, C) ✅ ──┐
|
||||
├──→ Phase 14 (cross-file)
|
||||
Phase P (polymorphism) ───────────┤
|
||||
│
|
||||
Phase S (Swift parity) ───────────┘
|
||||
|
||||
Phase S is independent of Phase 14.
|
||||
Phase 14 depends on Milestone D being stable.
|
||||
Phase P and Phase S are independent of each other and Phase 14.
|
||||
Phase 14 benefits from Phase P (better per-file resolution = fewer cross-file gaps).
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -187,6 +205,10 @@ Consolidated Phases 10–13 into 3 balanced phases. Loop-fixpoint bridge, MRO-aw
|
|||
|
||||
Export-type index, cross-file binding propagation.
|
||||
|
||||
### Milestone P — Polymorphism & Overloading (Phase P)
|
||||
|
||||
Parameter type metadata, overload disambiguation, constructor-visible virtual dispatch, covariant return types.
|
||||
|
||||
### Milestone S — Swift Parity (Phase S)
|
||||
|
||||
For-loop binding, assignment chains, `guard let` narrowing. Blocked on tree-sitter-swift Node 22.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue