fix(cpp): strip template args from out-of-line method receiver qualifiers (#1309)

template<typename T> T Box<T>::get() stored qualified_name Box<T>::get —
the <T> qualifier never matched the class node indexed as Box, so the
method didn't link to its class, while the inline form of the same
method produced Box::get. ICU-shaped multi-line template parameter
lists leaked whole <…> blocks (newlines included) into qualified_name,
exceeding NAME_MAX for downstream consumers.

extractCppReceiverType now applies stripCppTemplateArgs (the #1043
normalization for base-class refs) to the receiver qualifier.

fmt re-index: template-arg-in-qualifier names 25 -> 4 (remaining are a
FMT_BEGIN_EXPORT misparse artifact and gmock conversion-operator names,
both distinct pre-existing shapes), node count byte-stable at 7,536.

Fixes #1286

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-07-16 15:04:38 -05:00
committed by GitHub
co-authored by Claude Fable 5
parent e1f339f732
commit 4dd29ea5c1
3 changed files with 70 additions and 1 deletions
+60
View File
@@ -4074,6 +4074,66 @@ class Both : public Base<char>, public Plain {};
});
});
describe('C++ out-of-line template method receivers (#1286)', () => {
// `template<typename T> T Box<T>::get()` used to store qualified_name
// `Box<T>::get` — the `<T>` qualifier never matched the class node indexed
// as `Box`, and long multi-line parameter lists could push qualified_name
// past NAME_MAX. Inline definitions of the same method produce `Box::get`,
// so the out-of-line form must normalize to the identical name.
it('strips the template parameter list from the receiver qualifier', () => {
const code = `template <typename T>
class Box {
public:
T get() const;
void set(T v);
private:
T value;
};
template <typename T> T Box<T>::get() const { return value; }
template <typename T> void Box<T>::set(T v) { value = v; }
`;
const result = extractFromSource('box.cpp', code);
expect(result.errors).toHaveLength(0);
const methods = result.nodes.filter((n) => n.kind === 'method');
const qns = methods.map((n) => n.qualifiedName).sort();
// Out-of-line definitions carry the SAME qualifier as the class node.
expect(qns).toContain('Box::get');
expect(qns).toContain('Box::set');
expect(qns.find((q) => q?.includes('<'))).toBeUndefined();
// Names themselves stay clean.
expect(methods.map((n) => n.name).sort()).toEqual(expect.arrayContaining(['get', 'set']));
});
it('multi-line template parameter lists cannot leak into qualified_name (NAME_MAX overflow shape)', () => {
// The ICU capi_helper.h shape: enormous multi-line parameter names made
// qualified_name 272 bytes (> NAME_MAX 255) including embedded newlines.
const code = `template <typename CType,
typename CPPType,
int32_t kMagicValidationSentinelConstantForTheHelperTemplateClassInstanceGuardLong>
class ApiHelper {
public:
CPPType* validate();
};
template <typename CType,
typename CPPType,
int32_t kMagicValidationSentinelConstantForTheHelperTemplateClassInstanceGuardLong>
CPPType* ApiHelper<CType,
CPPType,
kMagicValidationSentinelConstantForTheHelperTemplateClassInstanceGuardLong>::validate() {
return nullptr;
}
`;
const result = extractFromSource('capi_helper.h', code);
const validate = result.nodes.find((n) => n.kind === 'method' && n.name === 'validate' && n.qualifiedName?.includes('::'));
expect(validate).toBeDefined();
expect(validate!.qualifiedName).toBe('ApiHelper::validate');
expect(validate!.qualifiedName!.length).toBeLessThan(255);
expect(validate!.qualifiedName).not.toMatch(/[<>\n]/);
});
});
describe('C++ stack-allocation construction (#1035)', () => {
// `Calculator calc(0)` (direct-init) and `Widget w{1, 2}` (brace-init) carry
// the constructor args directly on the declarator — no call/new node — so