Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions gitnexus-shared/src/scope-resolution/reference-site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* (defined in `./types.ts`).
*/

import type { ParameterTypeClass } from './symbol-definition.js';
import type { Range, ScopeId } from './types.js';

/**
Expand Down Expand Up @@ -79,4 +80,11 @@ export interface ReferenceSite {
* (C#: `42` → `'int'`, `"alice"` → `'string'`).
*/
readonly argumentTypes?: readonly string[];
/**
* Optional per-argument type-shape sidecar for languages that need
* cv/ref/pointer distinctions during constraint filtering. This is
* intentionally separate from `argumentTypes`, which stays normalized
* for existing overload narrowing and conversion-rank logic.
*/
readonly argumentTypeClasses?: readonly ParameterTypeClass[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
*/

import type { NodeLabel } from '../../graph/types.js';
import type { SymbolDefinition } from '../symbol-definition.js';
import type { ParameterTypeClass, SymbolDefinition } from '../symbol-definition.js';
import type { Callsite, DefId } from '../types.js';
import type { DefIndex } from '../def-index.js';
import type { QualifiedNameIndex } from '../qualified-name-index.js';
Expand Down Expand Up @@ -65,6 +65,13 @@ export interface ConstraintContext {
* `narrowOverloadCandidates`' `argTypes` parameter.
*/
readonly argumentTypes?: readonly string[];
/**
* Optional shape-preserving sidecar aligned with `argumentTypes`.
* Unknown or unsupported slots should be omitted by producers or
* marked with `indirection: 'unknown'`; consumers must preserve the
* monotonic fallback and return 'unknown' instead of guessing.
*/
readonly argumentTypeClasses?: readonly ParameterTypeClass[];
}

// ─── Owner-scoped contributor (concrete shape for `RegistryContributor`) ────
Expand Down
183 changes: 174 additions & 9 deletions gitnexus/src/core/ingestion/languages/cpp/captures.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Capture, CaptureMatch } from 'gitnexus-shared';
import type { Capture, CaptureMatch, ParameterTypeClass } from 'gitnexus-shared';
import {
findNodeAtRange,
nodeToCapture,
Expand All @@ -9,7 +9,11 @@ import { getCppParser, getCppScopeQuery } from './query.js';
import { getTreeSitterBufferSize } from '../../constants.js';
import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
import { splitCppInclude, splitCppUsingDecl } from './import-decomposer.js';
import { computeCppDeclarationArity, computeCppCallArity } from './arity-metadata.js';
import {
classifyCppParameterType,
computeCppDeclarationArity,
computeCppCallArity,
} from './arity-metadata.js';
import { markCppAnonymousNamespaceRange, markFileLocal } from './file-local-linkage.js';
import { markCppDependentBase } from './two-phase-lookup.js';
import { markCppAdlSiteArgs, markCppAdlSiteNoAdl, type CppAdlArgInfo } from './adl.js';
Expand Down Expand Up @@ -217,6 +221,14 @@ export function emitCppScopeCaptures(
JSON.stringify(argTypes),
);
}
const argTypeClasses = inferCppCallArgTypeClasses(cNode);
if (argTypeClasses !== undefined && argTypeClasses.length > 0) {
grouped['@reference.parameter-type-classes'] = syntheticCapture(
'@reference.parameter-type-classes',
cNode,
JSON.stringify(argTypeClasses),
);
}
}
}

Expand Down Expand Up @@ -683,6 +695,35 @@ function inferCppCallArgTypes(node: SyntaxNode): string[] | undefined {
return types.length > 0 ? types : undefined;
}

function inferCppCallArgTypeClasses(node: SyntaxNode): ParameterTypeClass[] | undefined {
const argList = node.childForFieldName('arguments');
if (argList === null) return undefined;

const classes: ParameterTypeClass[] = [];
for (let i = 0; i < argList.childCount; i++) {
const child = argList.child(i);
if (child === null) continue;
if (child.type === ',' || child.type === '(' || child.type === ')') continue;
const litType = inferCppLiteralType(child);
if (litType !== '') {
classes.push(valueTypeClass(litType));
} else if (child.type === 'identifier') {
classes.push(lookupDeclaredTypeClassForIdentifier(child));
} else {
classes.push(unknownTypeClass('unknown'));
}
}
return classes.length > 0 ? classes : undefined;
}

function valueTypeClass(base: string): ParameterTypeClass {
return { base, cv: 'none', indirection: 'value', pointerDepth: 0 };
}

function unknownTypeClass(base: string): ParameterTypeClass {
return { base, cv: 'unknown', indirection: 'unknown', pointerDepth: 0 };
}

/**
* Infer the canonical type name of a C++ literal AST node.
* Returns empty string for non-literal / unknown nodes.
Expand Down Expand Up @@ -750,6 +791,9 @@ function lookupDeclaredTypeForIdentifier(identNode: SyntaxNode): string {
}
if (scope === null) return '';

const paramType = lookupFunctionParameterType(scope, varName);
if (paramType !== '') return paramType;

// Scan declarations in the scope for a matching variable name
for (let i = 0; i < scope.childCount; i++) {
const stmt = scope.child(i);
Expand All @@ -763,18 +807,118 @@ function lookupDeclaredTypeForIdentifier(identNode: SyntaxNode): string {
// Check init_declarator children for the variable name
const declarator = stmt.childForFieldName('declarator');
if (declarator === null) continue;
if (declarator.type === 'init_declarator') {
const nameChild = declarator.childForFieldName('declarator');
if (nameChild !== null && nameChild.text === varName) {
return normalizeCppTypeText(typeNode.text);
}
} else if (declarator.text === varName) {
const nameChild = declaredNameNode(declarator);
if (nameChild !== null && extractDeclaratorLeafName(nameChild) === varName) {
return normalizeCppTypeText(typeNode.text);
}
}
return '';
}

function lookupDeclaredTypeClassForIdentifier(identNode: SyntaxNode): ParameterTypeClass {
const varName = identNode.text;
let scope: SyntaxNode | null = identNode.parent;
while (
scope !== null &&
scope.type !== 'compound_statement' &&
scope.type !== 'translation_unit'
) {
scope = scope.parent;
}
if (scope === null) return unknownTypeClass('unknown');

const paramTypeClass = lookupFunctionParameterTypeClass(scope, varName, identNode);
if (paramTypeClass !== undefined) return paramTypeClass;

for (let i = 0; i < scope.childCount; i++) {
const stmt = scope.child(i);
if (stmt === null || stmt.type !== 'declaration') continue;

const typeNode = stmt.childForFieldName('type');
if (typeNode === null) continue;
if (typeNode.type === 'placeholder_type_specifier') continue;

const declarator = stmt.childForFieldName('declarator');
if (declarator === null) continue;
const nameChild = declaredNameNode(declarator);
if (nameChild === null || extractDeclaratorLeafName(nameChild) !== varName) continue;

const typeClass = classifyCppParameterType(
typeNode.text,
nameChild.text,
stmt.text.replace(/;\s*$/, ''),
);
if (isKnownEnumName(identNode, typeClass.base)) {
return { ...typeClass, base: `enum:${typeClass.base}` };
}
return typeClass;
}
return unknownTypeClass('unknown');
}

function lookupFunctionParameterType(scope: SyntaxNode, varName: string): string {
const param = findEnclosingFunctionParameter(scope, varName);
if (param === null) return '';
const typeNode = param.childForFieldName('type');
if (typeNode === null) return '';
return normalizeCppTypeText(typeNode.text);
}

function lookupFunctionParameterTypeClass(
scope: SyntaxNode,
varName: string,
identNode: SyntaxNode,
): ParameterTypeClass | undefined {
const param = findEnclosingFunctionParameter(scope, varName);
if (param === null) return undefined;
const typeNode = param.childForFieldName('type');
if (typeNode === null) return undefined;
const declarator = param.childForFieldName('declarator');
if (declarator === null) return undefined;
const typeClass = classifyCppParameterType(typeNode.text, declarator.text, param.text);
if (isKnownEnumName(identNode, typeClass.base)) {
return { ...typeClass, base: `enum:${typeClass.base}` };
}
return typeClass;
}

function findEnclosingFunctionParameter(scope: SyntaxNode, varName: string): SyntaxNode | null {
let node: SyntaxNode | null = scope.parent;
while (node !== null) {
if (node.type === 'function_definition' || node.type === 'function_declarator') {
const fnDecl =
node.type === 'function_declarator'
? node
: findFirstDescendantOfType(node, 'function_declarator');
const params = fnDecl?.childForFieldName('parameters') ?? null;
if (params !== null) {
for (let i = 0; i < params.namedChildCount; i++) {
const param = params.namedChild(i);
if (param === null || param.type !== 'parameter_declaration') continue;
const declarator = param.childForFieldName('declarator');
if (declarator !== null && extractDeclaratorLeafName(declarator) === varName) {
return param;
}
}
}
return null;
}
node = node.parent;
}
return null;
}

function declaredNameNode(declarator: SyntaxNode): SyntaxNode | null {
if (declarator.type !== 'init_declarator') return declarator;
for (let i = 0; i < declarator.namedChildCount; i++) {
const child = declarator.namedChild(i);
if (child === null) continue;
if (child.type === 'identifier') return child;
if (child.type.endsWith('_declarator')) return child;
}
return declarator.childForFieldName('declarator');
}

/** Normalize a type-specifier text for argument type matching.
* Strips qualifiers (const, volatile), namespace prefixes (std::),
* and pointer/reference markers. */
Expand All @@ -786,6 +930,25 @@ function normalizeCppTypeText(text: string): string {
return t;
}

function isKnownEnumName(node: SyntaxNode, typeName: string): boolean {
if (typeName === '' || typeName === 'unknown') return false;
let root: SyntaxNode = node;
while (root.parent !== null) root = root.parent;
const stack: SyntaxNode[] = [root];
while (stack.length > 0) {
const cur = stack.pop()!;
if (cur.type === 'enum_specifier') {
const name = cur.childForFieldName('name');
if (name?.text === typeName) return true;
}
for (let i = 0; i < cur.childCount; i++) {
const child = cur.child(i);
if (child !== null) stack.push(child);
}
}
return false;
}

/**
* Detect whether a `namespace_definition` AST node is inline.
* Tree-sitter-cpp exposes the `inline` keyword as an anonymous child
Expand Down Expand Up @@ -1247,7 +1410,9 @@ function extractDeclaratorLeafName(node: SyntaxNode): string | null {
const next =
cur.childForFieldName('declarator') ??
// parenthesized_declarator: single named child
(cur.type === 'parenthesized_declarator' ? cur.namedChild(0) : null);
(cur.type === 'parenthesized_declarator' || cur.type.endsWith('_declarator')
? cur.namedChild(0)
: null);
if (next === null) return null;
cur = next;
}
Expand Down
Loading
Loading