|
|
@@ -351,6 +351,7 @@ function inferCppReceiverType(
|
|
|
receiverName: string,
|
|
|
ref: UnresolvedRef,
|
|
|
context: ResolutionContext,
|
|
|
+ depth = 0,
|
|
|
): string | null {
|
|
|
const source = context.readFile(ref.filePath);
|
|
|
if (!source) return null;
|
|
|
@@ -368,7 +369,15 @@ function inferCppReceiverType(
|
|
|
const declaratorMatch = line.match(declaratorRegex);
|
|
|
if (declaratorMatch) {
|
|
|
const normalized = normalizeCppTypeName(declaratorMatch[1] ?? '');
|
|
|
- if (normalized) return normalized;
|
|
|
+ if (normalized === 'auto') {
|
|
|
+ // `auto x = Foo::instance();` — the declared type is deduced; recover it
|
|
|
+ // from the initializer (call return type / construction) (#645).
|
|
|
+ const initType = inferCppAutoInitializerType(line, receiverName, ref, context, depth);
|
|
|
+ if (initType) return initType;
|
|
|
+ // No usable initializer on this line — keep scanning earlier ones.
|
|
|
+ } else if (normalized) {
|
|
|
+ return normalized;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -388,13 +397,158 @@ function inferCppReceiverType(
|
|
|
const declaratorMatch = line.match(declaratorRegex);
|
|
|
if (!declaratorMatch) continue;
|
|
|
const normalized = normalizeCppTypeName(declaratorMatch[1] ?? '');
|
|
|
- if (normalized) return normalized;
|
|
|
+ if (normalized && normalized !== 'auto') return normalized;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * Last `::`-separated segment of a (possibly namespace-qualified) C++ name.
|
|
|
+ */
|
|
|
+function cppLastSegment(name: string): string {
|
|
|
+ const parts = name.split('::').filter(Boolean);
|
|
|
+ return parts[parts.length - 1] ?? name;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Return type captured at extraction for `Class::method` (or a free function),
|
|
|
+ * read off the indexed node's `returnType` (#645). Null when not indexed or no
|
|
|
+ * return type was recorded (e.g. a `void`/primitive return).
|
|
|
+ */
|
|
|
+function lookupCppReturnType(
|
|
|
+ callee: string,
|
|
|
+ ref: UnresolvedRef,
|
|
|
+ context: ResolutionContext,
|
|
|
+): string | null {
|
|
|
+ let method = callee;
|
|
|
+ let cls: string | null = null;
|
|
|
+ if (callee.includes('::')) {
|
|
|
+ const parts = callee.split('::').filter(Boolean);
|
|
|
+ method = parts[parts.length - 1] ?? callee;
|
|
|
+ cls = parts.slice(0, -1).join('::');
|
|
|
+ }
|
|
|
+ const candidates = context.getNodesByName(method).filter(
|
|
|
+ (n) =>
|
|
|
+ (n.kind === 'method' || n.kind === 'function') &&
|
|
|
+ n.language === ref.language &&
|
|
|
+ !!n.returnType,
|
|
|
+ );
|
|
|
+ if (cls) {
|
|
|
+ const want = `${cls}::${method}`;
|
|
|
+ // The call site may name the class with MORE namespace qualification than
|
|
|
+ // the stored node (`details::registry::instance` at the call vs
|
|
|
+ // `registry::instance` on the node — the receiver type only carries the
|
|
|
+ // immediate class), or LESS. Accept an exact match or either being a
|
|
|
+ // namespace-suffix of the other; the shared `::<class>::<method>` tail keeps
|
|
|
+ // it specific.
|
|
|
+ const m = candidates.find(
|
|
|
+ (n) =>
|
|
|
+ n.qualifiedName === want ||
|
|
|
+ n.qualifiedName.endsWith(`::${want}`) ||
|
|
|
+ want.endsWith(`::${n.qualifiedName}`),
|
|
|
+ );
|
|
|
+ return m?.returnType ?? null;
|
|
|
+ }
|
|
|
+ return candidates.find((n) => n.kind === 'function')?.returnType ?? null;
|
|
|
+}
|
|
|
+
|
|
|
+/** Does the graph contain a class/struct named `name`'s last segment? */
|
|
|
+function cppClassExists(name: string, ref: UnresolvedRef, context: ResolutionContext): boolean {
|
|
|
+ const last = cppLastSegment(name);
|
|
|
+ return context
|
|
|
+ .getNodesByName(last)
|
|
|
+ .some((n) => (n.kind === 'class' || n.kind === 'struct') && n.language === ref.language);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Infer the class produced by a C++ call/construction expression, using return
|
|
|
+ * types captured at extraction (#645). Handles, in order:
|
|
|
+ * - `make_unique<T>()` / `make_shared<T>()` → T
|
|
|
+ * - single-level member call `recv.method()` → recv's type, then method's return
|
|
|
+ * - `Class::method()` / free `func()` → the callee's recorded return type
|
|
|
+ * - direct construction `Type()` / `ns::Type()` → Type
|
|
|
+ * Returns null when undeterminable. Callers MUST still validate the outer method
|
|
|
+ * exists on the result before creating an edge, so a wrong guess stays silent.
|
|
|
+ */
|
|
|
+function resolveCppCallResultType(
|
|
|
+ inner: string,
|
|
|
+ ref: UnresolvedRef,
|
|
|
+ context: ResolutionContext,
|
|
|
+ depth = 0,
|
|
|
+): string | null {
|
|
|
+ if (depth > 3) return null; // guard against pathological mutual recursion
|
|
|
+ const expr = inner.trim();
|
|
|
+
|
|
|
+ const make = expr.match(/(?:^|::)(?:make_unique|make_shared)\s*<\s*([A-Za-z_]\w*)/);
|
|
|
+ if (make) return make[1] ?? null;
|
|
|
+
|
|
|
+ // Single-level member call `recv.method` (the `manager.view().render()` shape).
|
|
|
+ const dotIdx = expr.lastIndexOf('.');
|
|
|
+ if (dotIdx > 0) {
|
|
|
+ const recv = expr.slice(0, dotIdx);
|
|
|
+ const method = expr.slice(dotIdx + 1);
|
|
|
+ if (recv.includes('.') || recv.includes('(') || recv.includes('::')) return null; // single level only
|
|
|
+ const recvType = inferCppReceiverType(recv, ref, context, depth + 1);
|
|
|
+ if (!recvType) return null;
|
|
|
+ return lookupCppReturnType(`${recvType}::${method}`, ref, context);
|
|
|
+ }
|
|
|
+
|
|
|
+ const ret = lookupCppReturnType(expr, ref, context);
|
|
|
+ if (ret) return ret;
|
|
|
+
|
|
|
+ // Direct construction — the callee itself names a class/struct.
|
|
|
+ if (cppClassExists(expr, ref, context)) return cppLastSegment(expr);
|
|
|
+
|
|
|
+ return null;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Recover the type of an `auto`-declared local from its initializer on the
|
|
|
+ * declaration line — `auto x = Foo::instance();`, `auto w = make_unique<W>();`,
|
|
|
+ * `auto p = new W();`, `auto w = Widget();` (#645).
|
|
|
+ */
|
|
|
+function inferCppAutoInitializerType(
|
|
|
+ line: string,
|
|
|
+ receiverName: string,
|
|
|
+ ref: UnresolvedRef,
|
|
|
+ context: ResolutionContext,
|
|
|
+ depth: number,
|
|
|
+): string | null {
|
|
|
+ const escaped = receiverName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
+ const m = line.match(new RegExp(`\\b${escaped}\\b\\s*=\\s*([^;]+)`));
|
|
|
+ if (!m || !m[1]) return null;
|
|
|
+ const init = m[1].trim();
|
|
|
+
|
|
|
+ const neu = init.match(/^new\s+([A-Za-z_][\w:]*)/);
|
|
|
+ if (neu && neu[1]) return cppLastSegment(neu[1]);
|
|
|
+
|
|
|
+ // A call or construction: `Foo(...)`, `A::b(...)`, `make_unique<T>(...)`.
|
|
|
+ const call = init.match(/^([A-Za-z_][\w:]*(?:\s*<[^>;]*>)?)\s*\(/);
|
|
|
+ if (call && call[1]) return resolveCppCallResultType(call[1].replace(/\s+/g, ''), ref, context, depth + 1);
|
|
|
+
|
|
|
+ return null;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Resolve a C++ chained call whose receiver is itself a call — encoded by the
|
|
|
+ * extractor as `<innerCallee>().<method>` (#645). The receiver's type is what
|
|
|
+ * the inner call returns; the outer method is then resolved and VALIDATED on it
|
|
|
+ * (resolveMethodOnType requires `cls::method` to exist), so a wrong inference
|
|
|
+ * produces no edge rather than a wrong one.
|
|
|
+ */
|
|
|
+export function matchCppCallChain(
|
|
|
+ ref: UnresolvedRef,
|
|
|
+ context: ResolutionContext,
|
|
|
+): ResolvedRef | null {
|
|
|
+ const m = ref.referenceName.match(/^(.+)\(\)\.(\w+)$/);
|
|
|
+ if (!m || !m[1] || !m[2]) return null;
|
|
|
+ const cls = resolveCppCallResultType(m[1], ref, context);
|
|
|
+ if (!cls) return null;
|
|
|
+ return resolveMethodOnType(cls, m[2], ref, context, 0.85, 'instance-method');
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* Java/Kotlin: infer a receiver's declared type by walking field declarations
|
|
|
* in the class enclosing the call site. The field's `signature` is already in
|
|
|
@@ -809,6 +963,14 @@ export function matchReference(
|
|
|
result = matchByQualifiedName(ref, context);
|
|
|
if (result) return result;
|
|
|
|
|
|
+ // 1b. C++ chained call whose receiver is another call — `Foo::instance().bar()`
|
|
|
+ // encoded as `Foo::instance().bar` by the extractor (#645). Resolve the
|
|
|
+ // receiver's type from what the inner call returns, then the method on it.
|
|
|
+ if (ref.language === 'cpp' || ref.language === 'c') {
|
|
|
+ result = matchCppCallChain(ref, context);
|
|
|
+ if (result) return result;
|
|
|
+ }
|
|
|
+
|
|
|
// 2. Method call pattern
|
|
|
result = matchMethodCall(ref, context);
|
|
|
if (result) return result;
|