mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-11-14 19:41:47 +08:00
fix(cpp): a receiver declared through a typedef or using alias calls the type it names (#2399)
google/leveldb's MemTable::Add calls `table_.Insert(buf)` on `Table table_;`, declared next to `typedef SkipList<const char*, KeyComparator> Table;` in the class. Receiver inference returned `Table`, no class `Table` has an `Insert` (the one class `Table` is the unrelated leveldb::Table, and C++ type_alias nodes record no aliased type), so the call fell through to the receiver-word guess HandleTable::Insert. It now reaches SkipList::Insert. cpp-type-aliases.ts reads the alias from source and looks it up the way C++ does: an alias declared earlier in the calling function, the caller's class and its bases (read from the class's own base clause), then the namespaces around it. A qualified name resolves a segment at a time (Table::Iterator is SkipList::Iterator). Aliases of a template parameter, a dependent typename or a decltype name no particular class. The constructor path uses the same lookup, so `Table::Iterator iter(&table_);` constructs SkipList::Iterator and an alias hides an outer class of its name. When the aliased type lacks the method, a `.` call or a call through a pointer on a receiver the caller itself declares gets no guessed edge. A `->` through an iterator or smart pointer, a project class template (whose specializations can add members) and declarations read from outside the caller keep the previous behavior. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
9a274a4b47
commit
02d22aebed
@@ -7,6 +7,8 @@
|
||||
* - the type is looked up in the LEXICAL namespaces of the call site — for
|
||||
* `second::use()` writing `Widget w;`, `second::Widget` before a global
|
||||
* `Widget`; a `::T` spelling is global only;
|
||||
* - a type named through a `typedef` / `using` alias is the type the alias
|
||||
* names, looked up from the alias's own scope (cpp-type-aliases.ts);
|
||||
* - among that type's constructors, the single overload whose parameter
|
||||
* count admits the argument count wins (defaults and `...` widen a range);
|
||||
* two admitting overloads (`T(int)` / `T(double)` for `T w(x)`) resolve to
|
||||
@@ -17,6 +19,7 @@
|
||||
*/
|
||||
import type { Node } from '../types';
|
||||
import type { ResolvedRef, ResolutionContext, UnresolvedRef } from './types';
|
||||
import { resolveCppAliasedType } from './cpp-type-aliases';
|
||||
|
||||
const CONSTRUCTOR_REF = /^(.*)::([^:]+)\/(\d+)$/;
|
||||
|
||||
@@ -80,14 +83,26 @@ function constructorShape(signature: string | undefined): { min: number; max: nu
|
||||
export function matchCppConstructor(ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null {
|
||||
const match = ref.referenceName.match(CONSTRUCTOR_REF);
|
||||
if (!match) return null;
|
||||
const [, rawType, name, count] = match;
|
||||
const type = rawType!.replace(/^::/, '');
|
||||
if (type.split('::').pop() !== name) return null;
|
||||
const [, rawType, ctorName, count] = match;
|
||||
let type = rawType!.replace(/^::/, '');
|
||||
if (type.split('::').pop() !== ctorName) return null;
|
||||
const argc = Number(count);
|
||||
|
||||
// Innermost lexical namespace first, then outward, then global.
|
||||
const caller = context.getNodeById?.(ref.fromNodeId);
|
||||
const scopes = rawType!.startsWith('::') ? [] : (caller?.qualifiedName.split('::') ?? []);
|
||||
let scopes = rawType!.startsWith('::') ? [] : (caller?.qualifiedName.split('::') ?? []);
|
||||
// A type named through an alias (`Table::Iterator iter(&table_);` under
|
||||
// `typedef SkipList<…> Table;`) constructs the type the alias names, looked
|
||||
// up from where the alias wrote it — never an outer class the alias hides.
|
||||
if (!rawType!.startsWith('::')) {
|
||||
const aliased = resolveCppAliasedType(type, ref, context);
|
||||
if (aliased === null) return null;
|
||||
if (aliased) {
|
||||
type = [...aliased.target, ...aliased.rest].join('::');
|
||||
scopes = aliased.scope ? aliased.scope.split('::') : [];
|
||||
}
|
||||
}
|
||||
const name = type.split('::').pop()!;
|
||||
const candidates: string[] = [];
|
||||
for (let i = scopes.length; i > 0; i--) candidates.push(`${scopes.slice(0, i).join('::')}::${type}`);
|
||||
candidates.push(type);
|
||||
@@ -98,7 +113,7 @@ export function matchCppConstructor(ref: UnresolvedRef, context: ResolutionConte
|
||||
.filter((n) => n.language === 'cpp' && (n.kind === 'class' || n.kind === 'struct' || n.kind === 'union'));
|
||||
if (owners.length === 0) continue;
|
||||
const constructors = context
|
||||
.getNodesByName(name!)
|
||||
.getNodesByName(name)
|
||||
.filter((n) => n.language === 'cpp' && n.kind === 'method' && n.qualifiedName === `${qualified}::${name}`);
|
||||
// Brace-init prefers an initializer_list overload over arity — that
|
||||
// choice needs the argument types, so decline.
|
||||
|
||||
Reference in New Issue
Block a user