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.
- package/dist/_shared/index.d.ts +2 -2
- package/dist/_shared/index.d.ts.map +1 -1
- package/dist/_shared/index.js.map +1 -1
- package/dist/_shared/scope-resolution/registries/context.d.ts +27 -0
- package/dist/_shared/scope-resolution/registries/context.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/registries/context.js.map +1 -1
- package/dist/_shared/scope-resolution/symbol-definition.d.ts +20 -0
- package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
- package/dist/core/ingestion/language-provider.d.ts +29 -0
- package/dist/core/ingestion/languages/c-cpp.js +46 -0
- package/dist/core/ingestion/languages/cpp/arity-metadata.d.ts +17 -0
- package/dist/core/ingestion/languages/cpp/arity-metadata.js +51 -2
- package/dist/core/ingestion/languages/cpp/arity.d.ts +4 -1
- package/dist/core/ingestion/languages/cpp/arity.js +4 -1
- package/dist/core/ingestion/languages/cpp/captures.js +73 -0
- package/dist/core/ingestion/languages/cpp/constraint-extractor.d.ts +73 -0
- package/dist/core/ingestion/languages/cpp/constraint-extractor.js +308 -0
- package/dist/core/ingestion/languages/cpp/constraint-filter.d.ts +31 -0
- package/dist/core/ingestion/languages/cpp/constraint-filter.js +135 -0
- package/dist/core/ingestion/languages/cpp/scope-resolver.js +6 -0
- package/dist/core/ingestion/languages/cpp/type-classifier.d.ts +26 -0
- package/dist/core/ingestion/languages/cpp/type-classifier.js +49 -0
- package/dist/core/ingestion/model/symbol-table.d.ts +2 -1
- package/dist/core/ingestion/model/symbol-table.js +3 -0
- package/dist/core/ingestion/parsing-processor.js +35 -2
- package/dist/core/ingestion/scope-extractor.js +63 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +21 -1
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +1 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +14 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +12 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +11 -1
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +50 -21
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +30 -7
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +49 -18
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +1 -1
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +10 -4
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +1 -0
- package/dist/core/ingestion/utils/template-arguments.d.ts +19 -0
- package/dist/core/ingestion/utils/template-arguments.js +30 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +2 -1
- package/dist/core/ingestion/workers/parse-worker.js +1 -0
- 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
|