mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-10 03:27:59 +00:00
feat: 7 Swift features — if/guard let, await/try, for-in, enum cases, self/super, optional chaining, multi-inheritance
Covers all high and medium impact gaps from the Swift feature coverage analysis: 1. if let / guard let bindings: add if_statement and guard_statement to DECLARATION_NODE_TYPES, extract varName and value for Tier 2 return-type propagation (callResult, copy, fieldAccess, methodCallResult) 2. await / try expression unwrapping: add unwrapSwiftExpression() that strips await_expression and try_expression wrappers before checking for call_expression. Applied in extractPendingAssignment, extractInitializer, and scanConstructorBinding. 3. for item in collection: add extractForLoopBinding for Swift with extractSwiftElementTypeFromTypeNode that handles [User] array sugar and Array<User> generic types. Registered in typeConfig. 4. Multiple inheritance specifiers: already working — tree-sitter queries match all inheritance_specifier occurrences automatically. Verified, no code changes needed. 5. Enum case extraction: add (enum_entry (simple_identifier) @name) @definition.property query to SWIFT_QUERIES. 6. self/super resolution: unskipped both describe.skip test suites (tree-sitter-swift 0.7.1 ships prebuilds, Node 22 build issue resolved). Both pass — 5 previously-skipped tests now running. 7. Optional chaining obj?.method(): already working — tree-sitter-swift parses the ? transparently. Verified, no code changes needed. Tests: 3,603 → 3,608 (5 unskipped self/super tests) Swift tests: 23 → 28 passing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
99f0aaaea5
commit
16b1a63134
3 changed files with 254 additions and 6 deletions
|
|
@ -889,6 +889,9 @@ export const SWIFT_QUERIES = `
|
|||
; Properties (stored and computed)
|
||||
(property_declaration (pattern (simple_identifier) @name)) @definition.property
|
||||
|
||||
; Enum cases
|
||||
(enum_entry (simple_identifier) @name) @definition.property
|
||||
|
||||
; Imports
|
||||
(import_declaration (identifier (simple_identifier) @import.source)) @import
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,33 @@
|
|||
import type { SyntaxNode } from '../utils.js';
|
||||
import type { LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor, InitializerExtractor, ClassNameLookup, ConstructorBindingScanner, PendingAssignmentExtractor, PendingAssignment } from './types.js';
|
||||
import { extractSimpleTypeName, extractVarName, hasTypeAnnotation } from './shared.js';
|
||||
import type { LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor, InitializerExtractor, ClassNameLookup, ConstructorBindingScanner, PendingAssignmentExtractor, PendingAssignment, ForLoopExtractor } from './types.js';
|
||||
import { extractSimpleTypeName, extractVarName, hasTypeAnnotation, extractElementTypeFromString, resolveIterableElementType } from './shared.js';
|
||||
import { findChild } from '../resolvers/utils.js';
|
||||
|
||||
const DECLARATION_NODE_TYPES: ReadonlySet<string> = new Set([
|
||||
'property_declaration',
|
||||
'if_statement',
|
||||
'guard_statement',
|
||||
]);
|
||||
|
||||
const FOR_LOOP_NODE_TYPES: ReadonlySet<string> = new Set([
|
||||
'for_statement',
|
||||
]);
|
||||
|
||||
/**
|
||||
* Unwrap Swift `await_expression` and `try_expression` nodes to find the inner
|
||||
* call_expression or other value node. `try` nodes contain a `try_operator` child
|
||||
* that must be skipped.
|
||||
*/
|
||||
function unwrapSwiftExpression(node: SyntaxNode): SyntaxNode {
|
||||
if (node.type === 'await_expression' || node.type === 'try_expression') {
|
||||
for (let i = 0; i < node.namedChildCount; i++) {
|
||||
const child = node.namedChild(i);
|
||||
if (child && child.type !== 'try_operator') return unwrapSwiftExpression(child);
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/** Swift: let x: Foo = ... */
|
||||
const extractDeclaration: TypeBindingExtractor = (node: SyntaxNode, env: Map<string, string>): void => {
|
||||
// Swift property_declaration has pattern and type_annotation
|
||||
|
|
@ -52,8 +73,18 @@ const extractInitializer: InitializerExtractor = (node: SyntaxNode, env: Map<str
|
|||
if (!pattern) return;
|
||||
const varName = extractVarName(pattern) ?? pattern.text;
|
||||
if (!varName || env.has(varName)) return;
|
||||
// Find call_expression in the value
|
||||
const callExpr = findChild(node, 'call_expression');
|
||||
// Find call_expression in the value (unwrap await/try)
|
||||
let callExpr = findChild(node, 'call_expression');
|
||||
if (!callExpr) {
|
||||
// Check for await_expression or try_expression wrapping a call_expression
|
||||
for (let i = 0; i < node.namedChildCount; i++) {
|
||||
const child = node.namedChild(i);
|
||||
if (child && (child.type === 'await_expression' || child.type === 'try_expression')) {
|
||||
const unwrapped = unwrapSwiftExpression(child);
|
||||
if (unwrapped.type === 'call_expression') { callExpr = unwrapped; break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!callExpr) return;
|
||||
const callee = callExpr.firstNamedChild;
|
||||
if (!callee) return;
|
||||
|
|
@ -90,6 +121,11 @@ const scanConstructorBinding: ConstructorBindingScanner = (node) => {
|
|||
for (let i = 0; i < node.namedChildCount; i++) {
|
||||
const child = node.namedChild(i);
|
||||
if (child?.type === 'call_expression') { callExpr = child; break; }
|
||||
// Unwrap await/try to find inner call_expression
|
||||
if (child && (child.type === 'await_expression' || child.type === 'try_expression')) {
|
||||
const unwrapped = unwrapSwiftExpression(child);
|
||||
if (unwrapped.type === 'call_expression') { callExpr = unwrapped; break; }
|
||||
}
|
||||
}
|
||||
if (!callExpr) return undefined;
|
||||
const callee = callExpr.firstNamedChild;
|
||||
|
|
@ -119,6 +155,82 @@ const scanConstructorBinding: ConstructorBindingScanner = (node) => {
|
|||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract the variable name from an if_statement or guard_statement with optional binding.
|
||||
* Pattern: `if let varName = expr` / `guard let varName = expr`
|
||||
* AST: if_statement/guard_statement contains value_binding_pattern, then simple_identifier (varName),
|
||||
* then call_expression/simple_identifier/navigation_expression (value).
|
||||
*/
|
||||
function extractIfGuardBinding(node: SyntaxNode, scopeEnv: ReadonlyMap<string, string>): PendingAssignment | undefined {
|
||||
// Find value_binding_pattern to confirm this is an optional binding
|
||||
let hasValueBinding = false;
|
||||
let varName: string | undefined;
|
||||
let valueNode: SyntaxNode | null = null;
|
||||
|
||||
for (let i = 0; i < node.namedChildCount; i++) {
|
||||
const child = node.namedChild(i);
|
||||
if (!child) continue;
|
||||
if (child.type === 'value_binding_pattern') {
|
||||
hasValueBinding = true;
|
||||
continue;
|
||||
}
|
||||
if (hasValueBinding && !varName && child.type === 'simple_identifier') {
|
||||
varName = child.text;
|
||||
continue;
|
||||
}
|
||||
if (varName && !valueNode) {
|
||||
// Skip type annotations and binding operators
|
||||
if (child.type === 'type_annotation') continue;
|
||||
valueNode = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasValueBinding || !varName || !valueNode || scopeEnv.has(varName)) return undefined;
|
||||
|
||||
// Unwrap await/try
|
||||
valueNode = unwrapSwiftExpression(valueNode);
|
||||
|
||||
// simple_identifier → copy
|
||||
if (valueNode.type === 'simple_identifier') {
|
||||
return { kind: 'copy', lhs: varName, rhs: valueNode.text };
|
||||
}
|
||||
|
||||
// navigation_expression → fieldAccess
|
||||
if (valueNode.type === 'navigation_expression') {
|
||||
const receiver = valueNode.firstNamedChild;
|
||||
const suffix = valueNode.lastNamedChild;
|
||||
if (receiver?.type === 'simple_identifier' && suffix?.type === 'navigation_suffix') {
|
||||
const field = suffix.lastNamedChild;
|
||||
if (field?.type === 'simple_identifier') {
|
||||
return { kind: 'fieldAccess', lhs: varName, receiver: receiver.text, field: field.text };
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// call_expression → callResult or methodCallResult
|
||||
if (valueNode.type === 'call_expression') {
|
||||
const callee = valueNode.firstNamedChild;
|
||||
if (!callee) return undefined;
|
||||
if (callee.type === 'simple_identifier') {
|
||||
return { kind: 'callResult', lhs: varName, callee: callee.text };
|
||||
}
|
||||
if (callee.type === 'navigation_expression') {
|
||||
const receiver = callee.firstNamedChild;
|
||||
const suffix = callee.lastNamedChild;
|
||||
if (receiver?.type === 'simple_identifier' && suffix?.type === 'navigation_suffix') {
|
||||
const method = suffix.lastNamedChild;
|
||||
if (method?.type === 'simple_identifier') {
|
||||
return { kind: 'methodCallResult', lhs: varName, receiver: receiver.text, method: method.text };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Swift: extract pending assignments for Tier 2 return-type propagation.
|
||||
* Handles:
|
||||
|
|
@ -126,8 +238,17 @@ const scanConstructorBinding: ConstructorBindingScanner = (node) => {
|
|||
* let result = user.save() → methodCallResult
|
||||
* let name = user.name → fieldAccess
|
||||
* let copy = user → copy
|
||||
* let user = await getUser() → callResult (unwrapped)
|
||||
* let user = try getUser() → callResult (unwrapped)
|
||||
* if let user = getUser() → callResult (optional binding)
|
||||
* guard let user = getUser() → callResult (optional binding)
|
||||
*/
|
||||
const extractPendingAssignment: PendingAssignmentExtractor = (node, scopeEnv) => {
|
||||
// Handle if_statement and guard_statement optional bindings
|
||||
if (node.type === 'if_statement' || node.type === 'guard_statement') {
|
||||
return extractIfGuardBinding(node, scopeEnv);
|
||||
}
|
||||
|
||||
if (node.type !== 'property_declaration') return undefined;
|
||||
// Skip if type annotation exists — extractDeclaration handles it
|
||||
if (hasTypeAnnotation(node)) return undefined;
|
||||
|
|
@ -154,6 +275,9 @@ const extractPendingAssignment: PendingAssignmentExtractor = (node, scopeEnv) =>
|
|||
}
|
||||
if (!valueNode) return undefined;
|
||||
|
||||
// Unwrap await/try expressions (Feature 2)
|
||||
valueNode = unwrapSwiftExpression(valueNode);
|
||||
|
||||
// let copy = user → copy
|
||||
if (valueNode.type === 'simple_identifier') {
|
||||
return { kind: 'copy', lhs, rhs: valueNode.text };
|
||||
|
|
@ -198,11 +322,132 @@ const extractPendingAssignment: PendingAssignmentExtractor = (node, scopeEnv) =>
|
|||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Swift: extract loop variable type binding from `for item in collection`.
|
||||
* AST: for_statement with pattern > simple_identifier (loop var) and
|
||||
* a simple_identifier/call_expression (collection).
|
||||
*/
|
||||
const extractForLoopBinding: ForLoopExtractor = (node, { scopeEnv, declarationTypeNodes, scope, returnTypeLookup }): void => {
|
||||
if (node.type !== 'for_statement') return;
|
||||
|
||||
// Find the loop variable from the pattern child
|
||||
let loopVarName: string | undefined;
|
||||
let iterableNode: SyntaxNode | null = null;
|
||||
|
||||
// for_statement children: pattern (loop var), then the iterable expression, then the body
|
||||
for (let i = 0; i < node.namedChildCount; i++) {
|
||||
const child = node.namedChild(i);
|
||||
if (!child) continue;
|
||||
if (child.type === 'pattern' || child.type === 'simple_identifier') {
|
||||
if (!loopVarName) {
|
||||
// The loop variable - may be inside a pattern node or a direct simple_identifier
|
||||
loopVarName = child.type === 'pattern' ? child.text : child.text;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// After we found the loop var, the next expression-like node is the iterable
|
||||
if (loopVarName && !iterableNode) {
|
||||
if (child.type === 'simple_identifier' || child.type === 'call_expression' ||
|
||||
child.type === 'navigation_expression') {
|
||||
iterableNode = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!loopVarName || !iterableNode) return;
|
||||
|
||||
let iterableName: string | undefined;
|
||||
let callExprElementType: string | undefined;
|
||||
|
||||
if (iterableNode.type === 'simple_identifier') {
|
||||
iterableName = iterableNode.text;
|
||||
} else if (iterableNode.type === 'navigation_expression') {
|
||||
// collection.property
|
||||
const suffix = iterableNode.lastNamedChild;
|
||||
if (suffix?.type === 'navigation_suffix') {
|
||||
const prop = suffix.lastNamedChild;
|
||||
if (prop?.type === 'simple_identifier') iterableName = prop.text;
|
||||
} else if (suffix?.type === 'simple_identifier') {
|
||||
iterableName = suffix.text;
|
||||
}
|
||||
} else if (iterableNode.type === 'call_expression') {
|
||||
// getItems() or collection.values()
|
||||
const fn = iterableNode.firstNamedChild;
|
||||
let callee: string | undefined;
|
||||
if (fn?.type === 'simple_identifier') {
|
||||
callee = fn.text;
|
||||
} else if (fn?.type === 'navigation_expression') {
|
||||
const obj = fn.firstNamedChild;
|
||||
const suffix = fn.lastNamedChild;
|
||||
if (obj?.type === 'simple_identifier') iterableName = obj.text;
|
||||
if (suffix?.type === 'navigation_suffix') {
|
||||
const m = suffix.lastNamedChild;
|
||||
if (m?.type === 'simple_identifier') callee = m.text;
|
||||
} else if (suffix?.type === 'simple_identifier') {
|
||||
callee = suffix.text;
|
||||
}
|
||||
}
|
||||
if (callee) {
|
||||
const rawReturn = returnTypeLookup.lookupRawReturnType(callee);
|
||||
if (rawReturn) callExprElementType = extractElementTypeFromString(rawReturn);
|
||||
}
|
||||
}
|
||||
|
||||
if (!iterableName && !callExprElementType) return;
|
||||
|
||||
let elementType: string | undefined;
|
||||
if (callExprElementType) {
|
||||
elementType = callExprElementType;
|
||||
} else if (iterableName) {
|
||||
// Try to resolve element type from the iterable's declared type
|
||||
elementType = resolveIterableElementType(
|
||||
iterableName, node, scopeEnv, declarationTypeNodes, scope,
|
||||
extractSwiftElementTypeFromTypeNode,
|
||||
);
|
||||
}
|
||||
|
||||
if (elementType && !scopeEnv.has(loopVarName)) {
|
||||
(scopeEnv as Map<string, string>).set(loopVarName, elementType);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract element type from a Swift type annotation AST node.
|
||||
* Handles: [User] (array sugar), Array<User>, Set<User>, etc.
|
||||
*/
|
||||
function extractSwiftElementTypeFromTypeNode(typeNode: SyntaxNode): string | undefined {
|
||||
// Swift array sugar: [User] — parsed as array_type > user_type > type_identifier
|
||||
if (typeNode.type === 'array_type') {
|
||||
const inner = typeNode.firstNamedChild;
|
||||
if (inner) return extractSimpleTypeName(inner);
|
||||
}
|
||||
// Generic type: Array<User>, Set<User>
|
||||
if (typeNode.type === 'user_type') {
|
||||
// Check for generic args: user_type > type_identifier + type_arguments
|
||||
for (let i = 0; i < typeNode.namedChildCount; i++) {
|
||||
const child = typeNode.namedChild(i);
|
||||
if (child?.type === 'type_arguments') {
|
||||
const lastArg = child.lastNamedChild;
|
||||
if (lastArg) return extractSimpleTypeName(lastArg);
|
||||
}
|
||||
}
|
||||
}
|
||||
// type_annotation wrapping
|
||||
if (typeNode.type === 'type_annotation') {
|
||||
const inner = typeNode.firstNamedChild;
|
||||
if (inner) return extractSwiftElementTypeFromTypeNode(inner);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export const typeConfig: LanguageTypeConfig = {
|
||||
declarationNodeTypes: DECLARATION_NODE_TYPES,
|
||||
forLoopNodeTypes: FOR_LOOP_NODE_TYPES,
|
||||
extractDeclaration,
|
||||
extractParameter,
|
||||
extractInitializer,
|
||||
scanConstructorBinding,
|
||||
extractPendingAssignment,
|
||||
extractForLoopBinding,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ describe.skipIf(!swiftAvailable)('Swift constructor-inferred type resolution', (
|
|||
// The self/super resolution code already exists in type-env.ts lookupInEnv (lines 56-66).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe.skip('Swift self resolution', () => {
|
||||
describe.skipIf(!swiftAvailable)('Swift self resolution', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
|
|
@ -91,7 +91,7 @@ describe.skip('Swift self resolution', () => {
|
|||
// findEnclosingParentClassName in type-env.ts already has Swift inheritance_specifier handler.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe.skip('Swift parent resolution', () => {
|
||||
describe.skipIf(!swiftAvailable)('Swift parent resolution', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue