mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-25 05:53:44 +00:00
* fix(cpp): resolve qualified receiver types by namespace, not by tail
Cause: a C++ receiver declared with a qualified type (`rp::C x = ...`,
`void f(a::Config c)`, `rp::Box<int> b`) typed nothing. The capture keeps
the qualifier, but the shared receiver lookups read a qualifier only as a
`.` namespace-import handle, and C++ has neither: `#include` binds no
handle and the index keys `rp::C` under `C`. The two places that did type
qualified spellings — the `auto x = ns::T{}` brace-init rule and qualified
member fields — reduced them to their tail, which binds the NEAREST
same-named class: `auto x = rp::C{}` reached a global `C` through another
include, and `a::Config` inside a `namespace b` declaring its own Config
reached `b::Config` (both confident wrong CALLS edges).
Fix: a ScopeResolver opt-in, `resolveQualifiedTypeSpellings`, that C++
sets. When a qualified declared or return type binds nothing,
`resolveClassBindingForName` and the compound resolver's return-type lookup
fall back to `resolveQualifiedInheritanceBase` — the route qualified base
classes and qualified type references already take: the qualifier is
matched against the candidate's namespace and a tie resolves nothing. It
runs after every existing lookup and after the template-argument match, so
it adds edges and never retargets one. The brace-init rule now captures the
whole qualified_identifier, and non-generic qualified member fields keep
their qualifier, so both resolve through the same route; generic members
stay tail-reduced because specialization matching keys on the bare base
name.
Corpus A/B (lang-resolution, all languages): no edge outside the new
fixture moved; drops unchanged. bench/scope-capture cpp re-baselined (one
brace-init capture text, plus the new fixture).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cpp): bind qualified declarations that have no initializer
Cause: `rp::C x;` and `rp::C* x = nullptr;` emitted no type binding at
all. The plain-declaration annotation rules accept only a bare
`type_identifier` or `template_type`, and tree-sitter-cpp parses a
qualified type as a `qualified_identifier` wrapping one of those, so the
receiver of `x.m()` was untyped (no edge, and no drop either). The
initialized form `rp::C x = ...` was already captured by the `type: (_)`
assignment rule.
Fix: two @type-binding.annotation rules on the OUTER qualified_identifier
(plain declarator, and pointer inside an init_declarator) — the qualified
twins of the bare rules, one pattern per declarator shape like the
qualified member-field rules, with no node-type alternation. The spelling
is kept as written and resolved by the qualifier-aware route from the
previous commit.
Capture diff over the cpp corpus: 29 added @type-binding.annotation
matches on qualified locals, nothing removed or changed. Corpus A/B: no
edge or drop outside the new fixture moved. bench/scope-capture cpp
re-baselined.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cpp): record return types of header function prototypes
Cause: only function DEFINITIONS (and in-class method prototypes)
recorded a return type. A free-function prototype — `D makeD();`,
`namespace rp { C make(); }` — recorded none, and the usual layout is
"header declares, .cpp defines": the .cpp is never included, so an
includer saw no return fact at all and `auto x = makeD(); x.m()` /
`makeD().m()` typed nothing (the inline form recorded a drop, the bound
form not even that).
Fix: @type-binding.return rules for a prototype with a plain or pointer
return. In a function body the same syntax declares a block-scope
function (the most vexing parse, `D x();`), whose binding would be
hoisted to the module and type every undeclared `x` in the file, so
captures.ts keeps a prototype match only at namespace scope — the
translation unit or a namespace / extern "C" body, through preprocessor
conditionals (include guards), single-declaration extern "C" and
template prefixes. A prototype declared in a namespace keeps its binding
in that namespace instead of the module: the binding's scope is also
where its type is looked up, and from the module a global `Widget` in
the same header won over `rp::Widget`. Callers read namespace members by
definition identity, which joins in any scope, and namespace members are
never mirrored to includers by bare name anyway.
A reference return (`D& f();`) gets no rule: such a function is not
declared as a Function at all (no declaration pattern), so a return fact
would have nothing to attach to.
Capture diff over the cpp corpus: one added match outside the new
fixture (cpp-return-type's header prototype, same type as its
definition); the gate drops only the fixture's deliberate `E y();`.
Corpus A/B: no edge or drop outside the fixture moved.
bench/scope-capture cpp re-baselined.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(scope): type an inline call receiver from its callee's own return type
Cause: the compound resolver's free-call branch (`make().m()`) read the
callee's return type BY NAME from the caller's scope chain, while the
chain follow that types the bound form (`auto x = make(); x.m()`) reads
it BY DEFINITION IDENTITY (`producerTypeRef`). A callee that arrives by a
channel nothing mirrors a return binding through — a C++ using-directive
— therefore typed the bound form and dropped the inline one.
Fix: when the name read finds nothing, fall back to the return type the
callee's definition declares, via `producerTypeRef` (now exported from
imported-return-types.ts; the only change there). Only for a callee the
name actually BINDS at the call: `findExportedDefByName` falls back to the
first workspace callable of that name, and reading a return type off that
guess is not evidence. Measured: without the binding check the corpus
gained 15 non-C++ edges (Go range/chain factories, Swift call results),
all reached through that workspace-name guess and one of them provably
wrong (swift-return-type's `user.save()` bound to another fixture's
User); with it, no edge outside the new fixture moves.
Corpus A/B: edges and drops outside the fixture identical to main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cpp): type receivers produced by a qualified factory callee
Cause: `auto x = rp::make(); x.m()` bound `x` to the literal type `auto`
— the bare `auto x = f()` constructor rule matches only an identifier
callee, so the generic assignment rule captured the placeholder — and
`rp::make().m()` reached the compound resolver as the free call
`rp::make`, whose name binds nothing. Neither had any route from the
qualified callee to its definition, so both typed nothing (`auto x =
rp::C()` likewise).
Fix:
- captures.ts promotes `auto x = ns::f()` / `auto x = ns::T()` (a
qualified_identifier whose innermost name is a plain identifier) to
@type-binding.constructor with the whole callee as the type, the
qualified twin of the bare rule; template factories keep their own
rule. interpret.ts drops any leftover `auto` / `decltype(auto)` binding,
which named no type.
- New ScopeResolver hook `splitQualifiedCallee` maps a callee spelling onto
the receiver/member pair the language's qualified CALL captures use (C++:
innermost scope segment, `a:🅱️:make` -> `b` / `make`), so shared code
never splits on `::`. The compound resolver's free-call branch, when the
callee's name binds nothing, resolves the definition through the existing
`resolveQualifiedReceiverMember` hook — the answer the call itself got —
and reads that definition's own return type by identity
(`producerTypeRef`). `auto x = rp::C()` resolves through the
qualified-type route instead.
Capture diff over the cpp corpus: one changed match outside the new
fixture (cpp-method-enrichment `auto category = Animal::classify(...)`
now binds the callee instead of `auto`; a class static method, which the
namespace lookup does not answer, so it stays untyped as before). Corpus
A/B: no edge or drop outside the fixture moved. bench/scope-capture cpp
re-baselined. The new cpp-captures rows also pin the namespace-scope gate
on prototype return types from the earlier commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cpp): bind qualified receiver owners
Preserve complete namespace qualifiers and declaration-owned return facts. Resolve inline namespace and chained alias types without borrowing unrelated functions or classes; cover review collision and header-order cases.
* fix(cpp): honor qualified type visibility
Carry header alias activation positions through include chains. Refuse unresolved type-owned qualifiers without rejecting value names, and align the lexical-ownership fixture with the scope-index contract.
* style(cpp): format inline namespace lookup
* fix(cpp): preserve factory lookup position
Keep later local type declarations from hiding an earlier namespace
factory. Name overload and class-factory recall limits explicitly.
Refs #3656
* fix(cpp): expand factory return aliases
Apply the provider qualified-type hook in producer-return lookup.
Keep authoritative refusals and the original declaration context.
Refs #3656
---------
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
84 lines
2.9 KiB
TypeScript
84 lines
2.9 KiB
TypeScript
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
import fs from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { runPipelineFromRepo } from '../../src/core/ingestion/pipeline.js';
|
|
|
|
describe('C++ lexical qualified factory boundaries', () => {
|
|
let root: string;
|
|
let result: Awaited<ReturnType<typeof runPipelineFromRepo>>;
|
|
beforeAll(async () => {
|
|
root = await fs.mkdtemp(path.join(os.tmpdir(), 'cpp-template-review-'));
|
|
await fs.writeFile(
|
|
path.join(root, 'original.h'),
|
|
`
|
|
namespace original { struct C { int m(); }; }
|
|
namespace alias = original;
|
|
`,
|
|
);
|
|
await fs.writeFile(
|
|
path.join(root, 'inner.h'),
|
|
`
|
|
#include "original.h"
|
|
namespace inner {
|
|
namespace alias { struct C { int m(); }; }
|
|
int nearer(alias::C c) { return c.m(); }
|
|
}
|
|
`,
|
|
);
|
|
await fs.writeFile(
|
|
path.join(root, 'main.cpp'),
|
|
`
|
|
#include "inner.h"
|
|
namespace actual { struct Product { int m(); }; }
|
|
struct Wrong { int m(); };
|
|
struct Factory { actual::Product make(); };
|
|
struct AutoFactory { auto make() { return actual::Product{}; } };
|
|
Wrong make();
|
|
int qualified_method() { Factory f; return f.make().m(); }
|
|
int auto_method() { AutoFactory f; return f.make().m(); }
|
|
struct A { int m(); };
|
|
struct B { int m(); };
|
|
namespace rp { A produce(); }
|
|
struct Actual { static B produce(); };
|
|
template<class rp> int function_template() { return rp::produce().m(); }
|
|
template<class rp> A* pointer_template() { rp::produce().m(); return nullptr; }
|
|
struct Owner { template<class rp> int member_template() { return rp::produce().m(); } };
|
|
int concrete() { return rp::produce().m(); }
|
|
`,
|
|
);
|
|
result = await runPipelineFromRepo(root, () => {}, {});
|
|
}, 120000);
|
|
afterAll(async () => {
|
|
if (root) await fs.rm(root, { recursive: true, force: true });
|
|
});
|
|
function targets(caller: string): string[] {
|
|
return [...result.graph.iterRelationships()]
|
|
.filter(
|
|
(edge) =>
|
|
edge.type === 'CALLS' &&
|
|
result.graph.getNode(edge.sourceId)?.properties.name === caller &&
|
|
result.graph.getNode(edge.targetId)?.properties.name === 'm',
|
|
)
|
|
.map((edge) => edge.targetId)
|
|
.sort();
|
|
}
|
|
it('lets a nearer namespace hide an outer alias', () => {
|
|
expect(targets('nearer')).toEqual(['Method:inner.h:C.m#0']);
|
|
});
|
|
it('does not borrow a free prototype for a qualified method return', () => {
|
|
expect(targets('qualified_method')).toEqual(['Method:main.cpp:Product.m#0']);
|
|
});
|
|
it.each(['function_template', 'member_template', 'pointer_template'])(
|
|
'%s retains dependent lookup',
|
|
(caller) => {
|
|
expect(targets(caller)).toEqual([]);
|
|
},
|
|
);
|
|
it('does not borrow a free return annotation for an undeduced auto method', () => {
|
|
expect(targets('auto_method')).toEqual([]);
|
|
});
|
|
it('keeps the nondependent namespace factory', () => {
|
|
expect(targets('concrete')).toEqual(['Method:main.cpp:A.m#0']);
|
|
});
|
|
});
|