gitnexus 1.6.5 → 1.6.6-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/_shared/index.d.ts +2 -2
  2. package/dist/_shared/index.d.ts.map +1 -1
  3. package/dist/_shared/index.js.map +1 -1
  4. package/dist/_shared/scope-resolution/registries/context.d.ts +27 -0
  5. package/dist/_shared/scope-resolution/registries/context.d.ts.map +1 -1
  6. package/dist/_shared/scope-resolution/registries/context.js.map +1 -1
  7. package/dist/_shared/scope-resolution/symbol-definition.d.ts +20 -0
  8. package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
  9. package/dist/core/ingestion/language-provider.d.ts +29 -0
  10. package/dist/core/ingestion/languages/c-cpp.js +46 -0
  11. package/dist/core/ingestion/languages/cpp/arity-metadata.d.ts +17 -0
  12. package/dist/core/ingestion/languages/cpp/arity-metadata.js +51 -2
  13. package/dist/core/ingestion/languages/cpp/arity.d.ts +4 -1
  14. package/dist/core/ingestion/languages/cpp/arity.js +4 -1
  15. package/dist/core/ingestion/languages/cpp/captures.js +73 -0
  16. package/dist/core/ingestion/languages/cpp/constraint-extractor.d.ts +73 -0
  17. package/dist/core/ingestion/languages/cpp/constraint-extractor.js +308 -0
  18. package/dist/core/ingestion/languages/cpp/constraint-filter.d.ts +31 -0
  19. package/dist/core/ingestion/languages/cpp/constraint-filter.js +135 -0
  20. package/dist/core/ingestion/languages/cpp/scope-resolver.js +6 -0
  21. package/dist/core/ingestion/languages/cpp/type-classifier.d.ts +26 -0
  22. package/dist/core/ingestion/languages/cpp/type-classifier.js +49 -0
  23. package/dist/core/ingestion/model/symbol-table.d.ts +2 -1
  24. package/dist/core/ingestion/model/symbol-table.js +3 -0
  25. package/dist/core/ingestion/parsing-processor.js +35 -2
  26. package/dist/core/ingestion/scope-extractor.js +63 -0
  27. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +21 -1
  28. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +1 -0
  29. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +14 -0
  30. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +12 -0
  31. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +11 -1
  32. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +50 -21
  33. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +30 -7
  34. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +49 -18
  35. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +1 -1
  36. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +10 -4
  37. package/dist/core/ingestion/scope-resolution/pipeline/run.js +1 -0
  38. package/dist/core/ingestion/utils/template-arguments.d.ts +19 -0
  39. package/dist/core/ingestion/utils/template-arguments.js +30 -0
  40. package/dist/core/ingestion/workers/parse-worker.d.ts +2 -1
  41. package/dist/core/ingestion/workers/parse-worker.js +1 -0
  42. package/package.json +1 -1
@@ -0,0 +1,308 @@
1
+ /**
2
+ * Extract C++ template constraint expressions for SFINAE-aware overload
3
+ * narrowing (issue #1579). Recognizes 3 AST shapes:
4
+ *
5
+ * F1 — unqualified non-type template param default:
6
+ * `template<class T, enable_if_t<P, int> = 0> void f(T);`
7
+ * F2 — `std::`-qualified variant (canonical ticket form):
8
+ * `template<class T, std::enable_if_t<P, int> = 0> void f(T);`
9
+ * F4 — C++20 leading requires-clause:
10
+ * `template<class T> requires P void f(T);`
11
+ *
12
+ * Deferred (return `{kind:'unknown'}`):
13
+ * F3 — void-default `typename = enable_if_t<P>` (cppref labels this
14
+ * `/* WRONG *\/` because adjacent overloads collapse to redeclarations)
15
+ * F5 — trailing requires (`void f(T) requires P;`)
16
+ * `requires_expression` blocks (`requires { typename T::U; }`)
17
+ * `decltype(...)`, fold-expressions, user-defined `_v` aliases.
18
+ *
19
+ * The output payload is opaque to shared code — only
20
+ * `constraint-filter.ts` consumes it. See ISO `[temp.constr.normal]` /
21
+ * `<https://en.cppreference.com/w/cpp/language/constraints>` for the
22
+ * normalization the Kleene 3-valued evaluator implements.
23
+ */
24
+ /**
25
+ * Walk a `template_declaration` AST node and extract its constraint
26
+ * payload. Caller is responsible for passing the OUTER `template_declaration`
27
+ * — for class-member template functions, that means the enclosing
28
+ * template_declaration of the class OR of the method, whichever
29
+ * directly precedes the function definition.
30
+ *
31
+ * Returns `undefined` when the template_declaration declares no
32
+ * constraints worth tracking (no enable_if default, no requires clause).
33
+ * Returns a payload whose `expr.kind === 'unknown'` when constraints are
34
+ * present but the extractor cannot model them — monotonicity guarantees
35
+ * the filter keeps the candidate in that case.
36
+ */
37
+ export function extractCppTemplateConstraints(templateDecl, funcDeclarator) {
38
+ const paramList = childOfType(templateDecl, 'template_parameter_list');
39
+ if (paramList === null)
40
+ return undefined;
41
+ const templateParams = [];
42
+ const exprs = [];
43
+ for (let i = 0; i < paramList.namedChildCount; i++) {
44
+ const param = paramList.namedChild(i);
45
+ if (param === null)
46
+ continue;
47
+ if (param.type === 'type_parameter_declaration' ||
48
+ param.type === 'optional_type_parameter_declaration' ||
49
+ param.type === 'variadic_type_parameter_declaration') {
50
+ const id = firstDescendantOfType(param, 'type_identifier');
51
+ if (id !== null)
52
+ templateParams.push(id.text);
53
+ continue;
54
+ }
55
+ // Non-type parameter — F1 / F2 default-value carries the enable_if
56
+ // predicate. Shape: `optional_parameter_declaration` with field
57
+ // `default_value`, whose value is a `template_type` named
58
+ // `enable_if_t` (F1) or a qualified version (F2).
59
+ if (param.type === 'optional_parameter_declaration') {
60
+ const defaultVal = param.childForFieldName('default_value');
61
+ const typeNode = param.childForFieldName('type');
62
+ const candidate = extractEnableIfPredicate(typeNode);
63
+ if (candidate !== undefined) {
64
+ exprs.push(candidate);
65
+ }
66
+ else if (defaultVal !== null) {
67
+ // Default-value-as-predicate not yet supported. Bail conservatively.
68
+ exprs.push({ kind: 'unknown' });
69
+ }
70
+ }
71
+ }
72
+ // F4 — C++20 leading `requires` clause. Tree-sitter-cpp exposes it as a
73
+ // `requires_clause` child of `template_declaration` (sibling of the
74
+ // template_parameter_list).
75
+ const requiresClause = childOfType(templateDecl, 'requires_clause');
76
+ if (requiresClause !== null) {
77
+ const parsed = parseRequiresClause(requiresClause);
78
+ if (parsed !== undefined)
79
+ exprs.push(parsed);
80
+ }
81
+ if (templateParams.length === 0 && exprs.length === 0)
82
+ return undefined;
83
+ const paramArgIndex = buildParamArgIndex(templateParams, funcDeclarator);
84
+ const expr = exprs.length === 0
85
+ ? { kind: 'unknown' }
86
+ : exprs.length === 1
87
+ ? exprs[0]
88
+ : { kind: 'and', children: exprs };
89
+ return { templateParams, paramArgIndex, expr };
90
+ }
91
+ /**
92
+ * Inspect a non-type template parameter's declared type to see whether
93
+ * it's `enable_if_t<P, T>` (F1) or `std::enable_if_t<P, T>` (F2). When
94
+ * matched, extract the predicate `P` and return it as a `ConstraintExpr`.
95
+ *
96
+ * Returns undefined when the parameter's type is not enable_if (so the
97
+ * caller can decide whether to bail or ignore).
98
+ */
99
+ function extractEnableIfPredicate(typeNode) {
100
+ if (typeNode === null)
101
+ return undefined;
102
+ // Unwrap a type_descriptor wrapper (when present).
103
+ let t = typeNode;
104
+ if (t.type === 'type_descriptor') {
105
+ t = t.childForFieldName('type') ?? firstDescendantOfType(t, 'template_type');
106
+ }
107
+ // F2 shape: tree-sitter-cpp models `std::enable_if_t<...>` as
108
+ // `qualified_identifier` whose `name` field is the `template_type`.
109
+ // F1 shape (unqualified `enable_if_t<...>`) is `template_type` directly.
110
+ if (t !== null && t.type === 'qualified_identifier') {
111
+ const inner = t.childForFieldName('name') ?? firstDescendantOfType(t, 'template_type');
112
+ if (inner !== null && inner.type === 'template_type') {
113
+ t = inner;
114
+ }
115
+ }
116
+ if (t === null || t.type !== 'template_type')
117
+ return undefined;
118
+ const nameNode = t.childForFieldName('name');
119
+ if (nameNode === null)
120
+ return undefined;
121
+ const tail = stripQualifiedPrefix(nameNode.text);
122
+ if (tail !== 'enable_if_t' && tail !== 'enable_if')
123
+ return undefined;
124
+ // Predicate is the first template argument of enable_if_t.
125
+ const argList = t.childForFieldName('arguments') ?? childOfType(t, 'template_argument_list');
126
+ if (argList === null)
127
+ return { kind: 'unknown' };
128
+ for (let i = 0; i < argList.namedChildCount; i++) {
129
+ const arg = argList.namedChild(i);
130
+ if (arg === null)
131
+ continue;
132
+ if (arg.type !== 'type_descriptor')
133
+ continue;
134
+ const inner = arg.childForFieldName('type') ?? arg.namedChild(0);
135
+ if (inner === null)
136
+ continue;
137
+ return parseAtomicOrBoolean(inner);
138
+ }
139
+ return { kind: 'unknown' };
140
+ }
141
+ /** Parse a requires-clause body. The body is a binary or unary expression
142
+ * over atomic predicates (variable templates like `is_integral_v<T>`). */
143
+ function parseRequiresClause(requiresClause) {
144
+ // tree-sitter-cpp exposes the expression as a named child or via a
145
+ // `constraint` field. Probe both.
146
+ let expr = requiresClause.childForFieldName('constraint');
147
+ if (expr === null) {
148
+ for (let i = 0; i < requiresClause.namedChildCount; i++) {
149
+ const c = requiresClause.namedChild(i);
150
+ if (c === null)
151
+ continue;
152
+ // Skip the `requires` keyword token.
153
+ if (c.type === 'requires')
154
+ continue;
155
+ expr = c;
156
+ break;
157
+ }
158
+ }
159
+ if (expr === null)
160
+ return undefined;
161
+ return parseAtomicOrBoolean(expr);
162
+ }
163
+ /**
164
+ * Recursively parse a constraint sub-expression. Recognizes:
165
+ * - `template_type` / `template_function` named `<predicate>_v` → atomic
166
+ * - binary_expression with `&&` / `||` → conjunction / disjunction
167
+ * - unary_expression with `!` → negation
168
+ * - parenthesized_expression → unwrap
169
+ * - anything else → `{kind:'unknown'}` (monotonicity-safe)
170
+ *
171
+ * `requires_expression` blocks intentionally fall through to 'unknown'
172
+ * — they need substitution semantics we don't model in V1.
173
+ */
174
+ function parseAtomicOrBoolean(node) {
175
+ // Unwrap parentheses.
176
+ if (node.type === 'parenthesized_expression') {
177
+ const inner = node.namedChild(0);
178
+ return inner === null ? { kind: 'unknown' } : parseAtomicOrBoolean(inner);
179
+ }
180
+ // Boolean composition.
181
+ if (node.type === 'binary_expression') {
182
+ const left = node.childForFieldName('left');
183
+ const right = node.childForFieldName('right');
184
+ const opNode = node.childForFieldName('operator');
185
+ if (left !== null && right !== null && opNode !== null) {
186
+ const op = opNode.text;
187
+ const l = parseAtomicOrBoolean(left);
188
+ const r = parseAtomicOrBoolean(right);
189
+ if (op === '&&')
190
+ return { kind: 'and', children: [l, r] };
191
+ if (op === '||')
192
+ return { kind: 'or', children: [l, r] };
193
+ }
194
+ return { kind: 'unknown' };
195
+ }
196
+ if (node.type === 'unary_expression') {
197
+ const opNode = node.childForFieldName('operator') ?? node.namedChild(0);
198
+ const arg = node.childForFieldName('argument') ?? node.namedChild(1) ?? node.namedChild(0);
199
+ if (opNode !== null && opNode.text === '!' && arg !== null && arg !== opNode) {
200
+ return { kind: 'not', child: parseAtomicOrBoolean(arg) };
201
+ }
202
+ return { kind: 'unknown' };
203
+ }
204
+ // Atomic predicate — `template_type` is the typical shape for variable
205
+ // templates like `is_integral_v<T>`. Some grammar variants surface it as
206
+ // `template_function` or via a `qualified_identifier` wrapper.
207
+ if (node.type === 'template_type' || node.type === 'template_function') {
208
+ return parseAtomicTemplate(node);
209
+ }
210
+ if (node.type === 'qualified_identifier') {
211
+ // `std::is_integral_v<T>` shape (without template_type wrapping).
212
+ const inner = node.childForFieldName('name');
213
+ if (inner !== null && (inner.type === 'template_type' || inner.type === 'template_function')) {
214
+ return parseAtomicTemplate(inner);
215
+ }
216
+ return { kind: 'unknown' };
217
+ }
218
+ // `requires { typename T::U; }` blocks and decltype: out of V1 scope.
219
+ return { kind: 'unknown' };
220
+ }
221
+ function parseAtomicTemplate(t) {
222
+ const nameNode = t.childForFieldName('name');
223
+ if (nameNode === null)
224
+ return { kind: 'unknown' };
225
+ const name = stripQualifiedPrefix(nameNode.text);
226
+ const argList = t.childForFieldName('arguments') ?? childOfType(t, 'template_argument_list');
227
+ const args = [];
228
+ if (argList !== null) {
229
+ for (let i = 0; i < argList.namedChildCount; i++) {
230
+ const arg = argList.namedChild(i);
231
+ if (arg === null)
232
+ continue;
233
+ if (arg.type !== 'type_descriptor')
234
+ continue;
235
+ const inner = arg.childForFieldName('type') ?? arg.namedChild(0);
236
+ if (inner === null)
237
+ continue;
238
+ // For Tier-A predicates the args are bare template-parameter names
239
+ // (`T`, `U`). Anything more elaborate is bailed via 'unknown' at the
240
+ // top level if needed; here we just record the textual identifier.
241
+ const id = inner.type === 'type_identifier' ? inner : firstDescendantOfType(inner, 'type_identifier');
242
+ args.push(id !== null ? id.text : inner.text);
243
+ }
244
+ }
245
+ return { kind: 'atomic', name, args };
246
+ }
247
+ /** Build a `paramName → call-site argument index` map by scanning the
248
+ * function's parameter list for parameters typed by each template param. */
249
+ function buildParamArgIndex(templateParams, funcDeclarator) {
250
+ const out = {};
251
+ if (funcDeclarator === null || templateParams.length === 0)
252
+ return out;
253
+ const paramList = funcDeclarator.childForFieldName('parameters');
254
+ if (paramList === null)
255
+ return out;
256
+ let argIdx = 0;
257
+ for (let i = 0; i < paramList.childCount; i++) {
258
+ const p = paramList.child(i);
259
+ if (p === null)
260
+ continue;
261
+ if (p.type !== 'parameter_declaration' &&
262
+ p.type !== 'optional_parameter_declaration' &&
263
+ p.type !== 'variadic_parameter_declaration') {
264
+ continue;
265
+ }
266
+ const typeNode = p.childForFieldName('type');
267
+ if (typeNode !== null) {
268
+ const tname = bareTypeIdentifier(typeNode);
269
+ if (tname !== null && templateParams.includes(tname) && !(tname in out)) {
270
+ out[tname] = argIdx;
271
+ }
272
+ }
273
+ argIdx++;
274
+ }
275
+ return out;
276
+ }
277
+ function bareTypeIdentifier(typeNode) {
278
+ if (typeNode.type === 'type_identifier')
279
+ return typeNode.text;
280
+ // Allow `T const`, `T&`, `T*` shapes — the inner type_identifier still wins.
281
+ const id = firstDescendantOfType(typeNode, 'type_identifier');
282
+ return id !== null ? id.text : null;
283
+ }
284
+ function stripQualifiedPrefix(text) {
285
+ const idx = text.lastIndexOf('::');
286
+ return idx >= 0 ? text.slice(idx + 2) : text;
287
+ }
288
+ function childOfType(node, type) {
289
+ for (let i = 0; i < node.childCount; i++) {
290
+ const c = node.child(i);
291
+ if (c !== null && c.type === type)
292
+ return c;
293
+ }
294
+ return null;
295
+ }
296
+ function firstDescendantOfType(node, type) {
297
+ if (node.type === type)
298
+ return node;
299
+ for (let i = 0; i < node.childCount; i++) {
300
+ const c = node.child(i);
301
+ if (c === null)
302
+ continue;
303
+ const hit = firstDescendantOfType(c, type);
304
+ if (hit !== null)
305
+ return hit;
306
+ }
307
+ return null;
308
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Kleene 3-valued evaluator + curated 4-predicate registry +
3
+ * `cppConstraintCompatibility` hook export for SFINAE / `requires`-clause
4
+ * filtering (issue #1579).
5
+ *
6
+ * Semantics:
7
+ * - `'incompatible'` → predicate provably fails for these argumentTypes
8
+ * (ISO `[temp.constr.atomic]` "not satisfied")
9
+ * - `'compatible'` → predicate provably holds
10
+ * - `'unknown'` → cannot decide (missing arg-type info, predicate
11
+ * not in registry, AST shape bailed during extraction). The shared
12
+ * filter keeps the candidate on `'unknown'` — monotonicity guarantee.
13
+ *
14
+ * Kleene rules (extension of ISO's 2-valued short-circuit conjunction in
15
+ * `<https://en.cppreference.com/w/cpp/language/constraints>`):
16
+ * AND: incompatible if any child incompatible; compatible iff all
17
+ * children compatible; otherwise unknown.
18
+ * OR: compatible if any child compatible; incompatible iff all
19
+ * children incompatible; otherwise unknown.
20
+ * NOT: flip compatible↔incompatible; pass through unknown.
21
+ */
22
+ import type { ArityVerdict, Callsite, ConstraintContext, SymbolDefinition } from '../../../../_shared/index.js';
23
+ import type { ConstraintExpr, CppConstraintPayload } from './constraint-extractor.js';
24
+ /** Public surface — registered as `ScopeResolver.constraintCompatibility`. */
25
+ export declare function cppConstraintCompatibility(_callsite: Callsite, def: SymbolDefinition, ctx: ConstraintContext): ArityVerdict;
26
+ /** Exposed for unit tests — lets `cpp-constraint.test.ts` assert
27
+ * `expect(getRegistrySize()).toBe(4)` without exporting the Map itself. */
28
+ export declare function getRegistrySize(): number;
29
+ /** Exposed for unit tests covering the Kleene 3-valued truth table
30
+ * directly, without an AST round-trip. */
31
+ export declare function evaluateForTest(expr: ConstraintExpr, payload: CppConstraintPayload, ctx: ConstraintContext): ArityVerdict;
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Kleene 3-valued evaluator + curated 4-predicate registry +
3
+ * `cppConstraintCompatibility` hook export for SFINAE / `requires`-clause
4
+ * filtering (issue #1579).
5
+ *
6
+ * Semantics:
7
+ * - `'incompatible'` → predicate provably fails for these argumentTypes
8
+ * (ISO `[temp.constr.atomic]` "not satisfied")
9
+ * - `'compatible'` → predicate provably holds
10
+ * - `'unknown'` → cannot decide (missing arg-type info, predicate
11
+ * not in registry, AST shape bailed during extraction). The shared
12
+ * filter keeps the candidate on `'unknown'` — monotonicity guarantee.
13
+ *
14
+ * Kleene rules (extension of ISO's 2-valued short-circuit conjunction in
15
+ * `<https://en.cppreference.com/w/cpp/language/constraints>`):
16
+ * AND: incompatible if any child incompatible; compatible iff all
17
+ * children compatible; otherwise unknown.
18
+ * OR: compatible if any child compatible; incompatible iff all
19
+ * children incompatible; otherwise unknown.
20
+ * NOT: flip compatible↔incompatible; pass through unknown.
21
+ */
22
+ import { classifyType } from './type-classifier.js';
23
+ /**
24
+ * Curated Tier-A predicate registry — the four canonical
25
+ * `<type_traits>` variable templates whose truth tables are closed-form
26
+ * over our coarse `TypeClass` enum.
27
+ *
28
+ * Deferred predicates that need a cv/ref/pointer sidecar on
29
+ * `normalizeCppParamType` (today the normalizer strips those markers
30
+ * before storage) live in #1579 as one-line follow-up adds.
31
+ */
32
+ // ISO `<type_traits>` treats `bool`, `char`, and the signed/unsigned char
33
+ // variants as integral types (§21.3.4 Table 48), so `is_integral_v<bool>`
34
+ // and `is_integral_v<char>` must both yield `true`. We keep the `TypeClass`
35
+ // enum precise (separate `'bool'` / `'char'` buckets) so that
36
+ // `is_same_v<bool, int>` still resolves to `'incompatible'`; the integral-
37
+ // family widening lives here in the predicate evaluators instead.
38
+ function isIntegralClass(c) {
39
+ return c === 'integral' || c === 'bool' || c === 'char';
40
+ }
41
+ const REGISTRY = new Map([
42
+ ['is_integral_v', (cls) => verdictFromBool(isIntegralClass(cls[0]), cls)],
43
+ ['is_floating_point_v', (cls) => verdictFromBool(cls[0] === 'floating', cls)],
44
+ [
45
+ 'is_arithmetic_v',
46
+ (cls) => verdictFromBool(isIntegralClass(cls[0]) || cls[0] === 'floating', cls),
47
+ ],
48
+ // NOTE: cv-qualifiers are stripped by `normalizeCppParamType` before the
49
+ // type token reaches `classifyType`, so `is_same_v<const T, T>` returns
50
+ // `'compatible'` instead of the ISO-correct `false`. Tracked under the
51
+ // cv-sidecar refactor in #1579's "Out of scope" list; until that lands
52
+ // this approximation matches the common `is_same_v<T, ConcreteType>`
53
+ // dispatch idiom and silently degrades on cv-distinct compares.
54
+ [
55
+ 'is_same_v',
56
+ (cls) => {
57
+ if (cls.length < 2 || cls[0] === 'unknown' || cls[1] === 'unknown')
58
+ return 'unknown';
59
+ return cls[0] === cls[1] ? 'compatible' : 'incompatible';
60
+ },
61
+ ],
62
+ ]);
63
+ function verdictFromBool(predicate, cls) {
64
+ if (cls[0] === 'unknown')
65
+ return 'unknown';
66
+ return predicate ? 'compatible' : 'incompatible';
67
+ }
68
+ /** Public surface — registered as `ScopeResolver.constraintCompatibility`. */
69
+ export function cppConstraintCompatibility(_callsite, def, ctx) {
70
+ const payload = def.templateConstraints;
71
+ if (payload === undefined)
72
+ return 'unknown';
73
+ return evaluate(payload.expr, payload, ctx);
74
+ }
75
+ function evaluate(expr, payload, ctx) {
76
+ switch (expr.kind) {
77
+ case 'unknown':
78
+ return 'unknown';
79
+ case 'atomic': {
80
+ const evaluator = REGISTRY.get(expr.name);
81
+ if (evaluator === undefined)
82
+ return 'unknown';
83
+ const classes = expr.args.map((paramName) => {
84
+ const argIdx = payload.paramArgIndex[paramName];
85
+ if (argIdx === undefined)
86
+ return 'unknown';
87
+ const token = ctx.argumentTypes?.[argIdx];
88
+ if (token === undefined || token === '')
89
+ return 'unknown';
90
+ return classifyType(token);
91
+ });
92
+ return evaluator(classes);
93
+ }
94
+ case 'and': {
95
+ let result = 'compatible';
96
+ for (const child of expr.children) {
97
+ const v = evaluate(child, payload, ctx);
98
+ if (v === 'incompatible')
99
+ return 'incompatible';
100
+ if (v === 'unknown')
101
+ result = 'unknown';
102
+ }
103
+ return result;
104
+ }
105
+ case 'or': {
106
+ let result = 'incompatible';
107
+ for (const child of expr.children) {
108
+ const v = evaluate(child, payload, ctx);
109
+ if (v === 'compatible')
110
+ return 'compatible';
111
+ if (v === 'unknown')
112
+ result = 'unknown';
113
+ }
114
+ return result;
115
+ }
116
+ case 'not': {
117
+ const v = evaluate(expr.child, payload, ctx);
118
+ if (v === 'compatible')
119
+ return 'incompatible';
120
+ if (v === 'incompatible')
121
+ return 'compatible';
122
+ return 'unknown';
123
+ }
124
+ }
125
+ }
126
+ /** Exposed for unit tests — lets `cpp-constraint.test.ts` assert
127
+ * `expect(getRegistrySize()).toBe(4)` without exporting the Map itself. */
128
+ export function getRegistrySize() {
129
+ return REGISTRY.size;
130
+ }
131
+ /** Exposed for unit tests covering the Kleene 3-valued truth table
132
+ * directly, without an AST round-trip. */
133
+ export function evaluateForTest(expr, payload, ctx) {
134
+ return evaluate(expr, payload, ctx);
135
+ }
@@ -13,6 +13,7 @@ import { populateCppDependentBases, clearCppDependentBases, isCppDependentBaseMe
13
13
  import { populateCppAssociatedNamespaces, clearCppAdlState, pickCppAdlCandidates } from './adl.js';
14
14
  import { clearCppInlineNamespaces, populateCppInlineNamespaceScopes, resolveCppQualifiedNamespaceMember, } from './inline-namespaces.js';
15
15
  import { populateCppRangeBindings } from './range-bindings.js';
16
+ import { cppConstraintCompatibility } from './constraint-filter.js';
16
17
  /**
17
18
  * C++ `ScopeResolver` registered in `SCOPE_RESOLVERS` and consumed by
18
19
  * the generic `runScopeResolution` orchestrator (RFC #909 Ring 3).
@@ -58,6 +59,11 @@ export const cppScopeResolver = {
58
59
  // Adapter: cppArityCompatibility predates ScopeResolver and uses
59
60
  // (def, callsite). ScopeResolver contract is (callsite, def).
60
61
  arityCompatibility: (callsite, def) => cppArityCompatibility(def, callsite),
62
+ // SFINAE / `requires`-clause aware overload filter (issue #1579).
63
+ // Drops candidates whose template constraints (`enable_if_t<P, T>`,
64
+ // C++20 `requires P`) provably fail at the call site. Three-valued —
65
+ // `'unknown'` keeps the candidate, preserving "degrade not lie".
66
+ constraintCompatibility: cppConstraintCompatibility,
61
67
  buildMro: (graph, parsedFiles, nodeLookup) => buildMro(graph, parsedFiles, nodeLookup, defaultLinearize),
62
68
  populateOwners: (parsed) => {
63
69
  populateClassOwnedMembers(parsed);
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Coarse-grained type classifier for C++ constraint evaluation
3
+ * (`<https://en.cppreference.com/w/cpp/types/is_integral>`,
4
+ * `<https://en.cppreference.com/w/cpp/types/is_floating_point>`).
5
+ *
6
+ * Maps a normalized type token (as produced by `normalizeCppParamType` /
7
+ * the call-site inference in `captures.ts`) to one of the categories
8
+ * the `<type_traits>` predicate registry uses for SFINAE filtering.
9
+ *
10
+ * Intentionally coarse: cv / pointer / reference qualifiers are stripped
11
+ * upstream by `normalizeCppParamType`. Tier-A predicates
12
+ * (`is_integral_v`, `is_floating_point_v`, `is_arithmetic_v`, `is_same_v`)
13
+ * are insensitive to those modifiers per ISO `<type_traits>` semantics
14
+ * ("including any cv-qualified variants").
15
+ */
16
+ export type TypeClass = 'integral' | 'floating' | 'bool' | 'char' | 'string' | 'null' | 'class' | 'unknown';
17
+ /**
18
+ * Classify a normalized C++ type token. The mapping mirrors the literal-
19
+ * inference table in `captures.ts:inferCppLiteralType` plus the std::
20
+ * normalization in `arity-metadata.ts:normalizeCppParamType`.
21
+ *
22
+ * Caller note: token must already be normalized (no `const`, no `&` / `*`,
23
+ * no `std::` prefix). Tokens passed via `ConstraintContext.argumentTypes`
24
+ * coming from `inferCppCallArgTypes` satisfy this.
25
+ */
26
+ export declare function classifyType(token: string): TypeClass;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Coarse-grained type classifier for C++ constraint evaluation
3
+ * (`<https://en.cppreference.com/w/cpp/types/is_integral>`,
4
+ * `<https://en.cppreference.com/w/cpp/types/is_floating_point>`).
5
+ *
6
+ * Maps a normalized type token (as produced by `normalizeCppParamType` /
7
+ * the call-site inference in `captures.ts`) to one of the categories
8
+ * the `<type_traits>` predicate registry uses for SFINAE filtering.
9
+ *
10
+ * Intentionally coarse: cv / pointer / reference qualifiers are stripped
11
+ * upstream by `normalizeCppParamType`. Tier-A predicates
12
+ * (`is_integral_v`, `is_floating_point_v`, `is_arithmetic_v`, `is_same_v`)
13
+ * are insensitive to those modifiers per ISO `<type_traits>` semantics
14
+ * ("including any cv-qualified variants").
15
+ */
16
+ /**
17
+ * Classify a normalized C++ type token. The mapping mirrors the literal-
18
+ * inference table in `captures.ts:inferCppLiteralType` plus the std::
19
+ * normalization in `arity-metadata.ts:normalizeCppParamType`.
20
+ *
21
+ * Caller note: token must already be normalized (no `const`, no `&` / `*`,
22
+ * no `std::` prefix). Tokens passed via `ConstraintContext.argumentTypes`
23
+ * coming from `inferCppCallArgTypes` satisfy this.
24
+ */
25
+ export function classifyType(token) {
26
+ if (token.length === 0)
27
+ return 'unknown';
28
+ switch (token) {
29
+ case 'int':
30
+ return 'integral';
31
+ case 'double':
32
+ case 'float':
33
+ return 'floating';
34
+ case 'bool':
35
+ return 'bool';
36
+ case 'char':
37
+ return 'char';
38
+ case 'string':
39
+ return 'string';
40
+ case 'null':
41
+ return 'null';
42
+ default:
43
+ // After normalization, anything that isn't a recognized primitive
44
+ // is assumed to be a class-like type. The Tier-A predicate registry
45
+ // doesn't introspect class types — `is_integral_v` etc. simply
46
+ // returns `false` for `'class'`, matching ISO behavior.
47
+ return 'class';
48
+ }
49
+ }
@@ -33,7 +33,7 @@
33
33
  * import from `./model/` here, you are going the wrong way — move the
34
34
  * logic up the dependency chain instead.
35
35
  */
36
- import type { NodeLabel, SymbolDefinition } from '../../../_shared/index.js';
36
+ import type { NodeLabel, ParameterTypeClass, SymbolDefinition } from '../../../_shared/index.js';
37
37
  /**
38
38
  * Class-like NodeLabels — used for qualifiedName fallback inside
39
39
  * `SymbolTable.add()` and (via import into `model/registration-table.ts`)
@@ -97,6 +97,7 @@ export interface AddMetadata {
97
97
  parameterCount?: number;
98
98
  requiredParameterCount?: number;
99
99
  parameterTypes?: string[];
100
+ parameterTypeClasses?: ParameterTypeClass[];
100
101
  returnType?: string;
101
102
  declaredType?: string;
102
103
  templateArguments?: string[];
@@ -128,6 +128,9 @@ export const createSymbolTable = () => {
128
128
  ...(metadata?.parameterTypes !== undefined
129
129
  ? { parameterTypes: metadata.parameterTypes }
130
130
  : {}),
131
+ ...(metadata?.parameterTypeClasses !== undefined
132
+ ? { parameterTypeClasses: metadata.parameterTypeClasses }
133
+ : {}),
131
134
  ...(metadata?.returnType !== undefined ? { returnType: metadata.returnType } : {}),
132
135
  ...(metadata?.declaredType !== undefined ? { declaredType: metadata.declaredType } : {}),
133
136
  ...(metadata?.templateArguments !== undefined