@typespec/compiler 1.13.0-dev.0 → 1.13.0-dev.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/manifest.js +2 -2
- package/dist/src/config/config-loader.d.ts.map +1 -1
- package/dist/src/config/config-loader.js +18 -1
- package/dist/src/config/config-loader.js.map +1 -1
- package/dist/src/config/config-schema.d.ts.map +1 -1
- package/dist/src/config/config-schema.js +9 -0
- package/dist/src/config/config-schema.js.map +1 -1
- package/dist/src/config/config-to-options.d.ts.map +1 -1
- package/dist/src/config/config-to-options.js +35 -0
- package/dist/src/config/config-to-options.js.map +1 -1
- package/dist/src/config/types.d.ts +18 -0
- package/dist/src/config/types.d.ts.map +1 -1
- package/dist/src/core/checker.d.ts.map +1 -1
- package/dist/src/core/checker.js +818 -44
- package/dist/src/core/checker.js.map +1 -1
- package/dist/src/core/entrypoint-resolution.d.ts.map +1 -1
- package/dist/src/core/entrypoint-resolution.js +7 -0
- package/dist/src/core/entrypoint-resolution.js.map +1 -1
- package/dist/src/core/helpers/string-template-utils.d.ts.map +1 -1
- package/dist/src/core/helpers/string-template-utils.js +1 -0
- package/dist/src/core/helpers/string-template-utils.js.map +1 -1
- package/dist/src/core/helpers/syntax-utils.d.ts +1 -0
- package/dist/src/core/helpers/syntax-utils.d.ts.map +1 -1
- package/dist/src/core/helpers/syntax-utils.js +3 -0
- package/dist/src/core/helpers/syntax-utils.js.map +1 -1
- package/dist/src/core/helpers/type-name-utils.d.ts.map +1 -1
- package/dist/src/core/helpers/type-name-utils.js +2 -0
- package/dist/src/core/helpers/type-name-utils.js.map +1 -1
- package/dist/src/core/messages.d.ts +56 -2
- package/dist/src/core/messages.d.ts.map +1 -1
- package/dist/src/core/messages.js +18 -0
- package/dist/src/core/messages.js.map +1 -1
- package/dist/src/core/name-resolver.d.ts +2 -0
- package/dist/src/core/name-resolver.d.ts.map +1 -1
- package/dist/src/core/name-resolver.js +35 -2
- package/dist/src/core/name-resolver.js.map +1 -1
- package/dist/src/core/semantic-walker.d.ts.map +1 -1
- package/dist/src/core/semantic-walker.js +14 -0
- package/dist/src/core/semantic-walker.js.map +1 -1
- package/dist/src/core/type-relation-checker.d.ts.map +1 -1
- package/dist/src/core/type-relation-checker.js +2 -1
- package/dist/src/core/type-relation-checker.js.map +1 -1
- package/dist/src/core/types.d.ts +14 -1
- package/dist/src/core/types.d.ts.map +1 -1
- package/dist/src/core/types.js.map +1 -1
- package/dist/src/init/scaffold.js +16 -11
- package/dist/src/init/scaffold.js.map +1 -1
- package/dist/src/server/entrypoint-resolver.d.ts.map +1 -1
- package/dist/src/server/entrypoint-resolver.js +13 -0
- package/dist/src/server/entrypoint-resolver.js.map +1 -1
- package/dist/src/server/serverlib.d.ts.map +1 -1
- package/dist/src/server/serverlib.js +1 -1
- package/dist/src/server/serverlib.js.map +1 -1
- package/dist/src/server/type-signature.js +17 -1
- package/dist/src/server/type-signature.js.map +1 -1
- package/package.json +1 -1
- package/templates/__snapshots__/rest/tspconfig.yaml +10 -8
- package/templates/scaffolding.json +3 -0
package/dist/src/core/checker.js
CHANGED
|
@@ -11,7 +11,7 @@ import { compilerAssert, createDiagnosticCollector, ignoreDiagnostics, reportDep
|
|
|
11
11
|
import { validateInheritanceDiscriminatedUnions } from "./helpers/discriminator-utils.js";
|
|
12
12
|
import { getLocationContext } from "./helpers/location-context.js";
|
|
13
13
|
import { explainStringTemplateNotSerializable } from "./helpers/string-template-utils.js";
|
|
14
|
-
import { typeReferenceToString } from "./helpers/syntax-utils.js";
|
|
14
|
+
import { printIdentifier, printMemberExpressionPath, typeReferenceToString, } from "./helpers/syntax-utils.js";
|
|
15
15
|
import { getEntityName, getTypeName } from "./helpers/type-name-utils.js";
|
|
16
16
|
import { marshalTypeForJs, unmarshalJsToValue } from "./js-marshaller.js";
|
|
17
17
|
import { createDiagnostic } from "./messages.js";
|
|
@@ -163,6 +163,10 @@ export function createChecker(program, resolver) {
|
|
|
163
163
|
* Tracking the template parameters used or not.
|
|
164
164
|
*/
|
|
165
165
|
const templateParameterUsageMap = new Map();
|
|
166
|
+
const templateAccessSymbolCache = new Map();
|
|
167
|
+
const templateAccessCacheKeys = new WeakMap();
|
|
168
|
+
const symbolCacheIds = new WeakMap();
|
|
169
|
+
let nextSymbolCacheId = 1;
|
|
166
170
|
const checker = {
|
|
167
171
|
getTypeForNode,
|
|
168
172
|
checkProgram,
|
|
@@ -344,7 +348,7 @@ export function createChecker(program, resolver) {
|
|
|
344
348
|
}));
|
|
345
349
|
return errorType;
|
|
346
350
|
}
|
|
347
|
-
if (entity.kind === "TemplateParameter") {
|
|
351
|
+
if (entity.kind === "TemplateParameter" || entity.kind === "TemplateParameterAccess") {
|
|
348
352
|
if (entity.constraint?.valueType) {
|
|
349
353
|
// means this template constraint will accept values
|
|
350
354
|
reportCheckerDiagnostic(createDiagnostic({
|
|
@@ -380,7 +384,7 @@ export function createChecker(program, resolver) {
|
|
|
380
384
|
// If a template parameter that can be a value is used where a value is expected,
|
|
381
385
|
// synthesize a template value placeholder even when the template parameter is mapped
|
|
382
386
|
// from an outer template declaration.
|
|
383
|
-
if (entity.kind === "TemplateParameter" &&
|
|
387
|
+
if ((entity.kind === "TemplateParameter" || entity.kind === "TemplateParameterAccess") &&
|
|
384
388
|
entity.constraint?.valueType &&
|
|
385
389
|
entity.constraint.type === undefined) {
|
|
386
390
|
// We must also observe that the template parameter is used here.
|
|
@@ -706,7 +710,12 @@ export function createChecker(program, resolver) {
|
|
|
706
710
|
function visit(node) {
|
|
707
711
|
const entity = checkNode(ctx, node);
|
|
708
712
|
let hasError = false;
|
|
709
|
-
if (entity !== null &&
|
|
713
|
+
if (entity !== null &&
|
|
714
|
+
"kind" in entity &&
|
|
715
|
+
(entity.kind === "TemplateParameter" || entity.kind === "TemplateParameterAccess")) {
|
|
716
|
+
if (entity.kind === "TemplateParameterAccess") {
|
|
717
|
+
return entity;
|
|
718
|
+
}
|
|
710
719
|
for (let i = index; i < templateParameters.length; i++) {
|
|
711
720
|
if (entity.node?.symbol === templateParameters[i].symbol) {
|
|
712
721
|
reportCheckerDiagnostic(createDiagnostic({ code: "invalid-template-default", target: node }));
|
|
@@ -1514,7 +1523,7 @@ export function createChecker(program, resolver) {
|
|
|
1514
1523
|
const indexers = [];
|
|
1515
1524
|
const modelOptions = options.filter((entry) => {
|
|
1516
1525
|
const [optionNode, option] = entry;
|
|
1517
|
-
if (option.kind === "TemplateParameter") {
|
|
1526
|
+
if (option.kind === "TemplateParameter" || option.kind === "TemplateParameterAccess") {
|
|
1518
1527
|
return false;
|
|
1519
1528
|
}
|
|
1520
1529
|
if (option.kind !== "Model") {
|
|
@@ -1726,6 +1735,10 @@ export function createChecker(program, resolver) {
|
|
|
1726
1735
|
const doc = paramDocs.get(name);
|
|
1727
1736
|
if (doc) {
|
|
1728
1737
|
docFromCommentForSym.set(memberSym, doc);
|
|
1738
|
+
const declarationSymbol = memberSym.node?.symbol;
|
|
1739
|
+
if (declarationSymbol && declarationSymbol !== memberSym) {
|
|
1740
|
+
docFromCommentForSym.set(declarationSymbol, doc);
|
|
1741
|
+
}
|
|
1729
1742
|
}
|
|
1730
1743
|
}
|
|
1731
1744
|
}
|
|
@@ -1896,15 +1909,24 @@ export function createChecker(program, resolver) {
|
|
|
1896
1909
|
compilerAssert(false, "Unreachable");
|
|
1897
1910
|
}
|
|
1898
1911
|
if (sym) {
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
else {
|
|
1903
|
-
return [sym];
|
|
1912
|
+
const relatedSource = getRelatedSymbolSource(sym);
|
|
1913
|
+
if (relatedSource) {
|
|
1914
|
+
return sym.flags & 4194304 /* SymbolFlags.LateBound */ ? [sym, relatedSource] : [relatedSource];
|
|
1904
1915
|
}
|
|
1916
|
+
return [sym];
|
|
1905
1917
|
}
|
|
1906
1918
|
return undefined; //sym?.symbolSource ?? sym;
|
|
1907
1919
|
}
|
|
1920
|
+
function getRelatedSymbolSource(sym) {
|
|
1921
|
+
if (sym.symbolSource) {
|
|
1922
|
+
return sym.symbolSource;
|
|
1923
|
+
}
|
|
1924
|
+
const type = sym.type;
|
|
1925
|
+
if (type && isType(type)) {
|
|
1926
|
+
return getTemplateAccessSymbolSource(type);
|
|
1927
|
+
}
|
|
1928
|
+
return undefined;
|
|
1929
|
+
}
|
|
1908
1930
|
function getTemplateDeclarationsForArgument(ctx, node) {
|
|
1909
1931
|
const ref = node.parent;
|
|
1910
1932
|
let resolved = resolveTypeReferenceSym(ctx, ref, false);
|
|
@@ -2186,32 +2208,43 @@ export function createChecker(program, resolver) {
|
|
|
2186
2208
|
}
|
|
2187
2209
|
}
|
|
2188
2210
|
else if (identifier.parent && identifier.parent.kind === SyntaxKind.MemberExpression) {
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2211
|
+
const memberExpression = identifier.parent;
|
|
2212
|
+
let baseType = getCompletionBaseType(memberExpression.base);
|
|
2213
|
+
let base = resolver.getNodeLinks(memberExpression.base).resolvedSymbol;
|
|
2214
|
+
if (base && base.flags & 128 /* SymbolFlags.Alias */) {
|
|
2215
|
+
base = getAliasedSymbol(CheckContext.DEFAULT, base);
|
|
2216
|
+
}
|
|
2217
|
+
if (!baseType && base) {
|
|
2218
|
+
baseType = getCompletionBaseTypeFromSymbol(base);
|
|
2219
|
+
}
|
|
2220
|
+
if (baseType && (!base || !!(base.flags & 1024 /* SymbolFlags.TemplateParameter */))) {
|
|
2221
|
+
if (memberExpression.selector === "::") {
|
|
2222
|
+
addMetaCompletionsForType(baseType);
|
|
2193
2223
|
}
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
const metaProperty = base.node;
|
|
2208
|
-
addCompletion("type", metaProperty.symbol);
|
|
2224
|
+
else {
|
|
2225
|
+
addMemberCompletionsForType(baseType);
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
if (base) {
|
|
2229
|
+
if (memberExpression.selector === "::") {
|
|
2230
|
+
if (base?.node === undefined && base?.declarations && base.declarations.length > 0) {
|
|
2231
|
+
// Process meta properties separately, such as `::parameters`, `::returnType`
|
|
2232
|
+
const nodeModels = base?.declarations[0];
|
|
2233
|
+
if (nodeModels.kind === SyntaxKind.OperationStatement) {
|
|
2234
|
+
const operation = nodeModels;
|
|
2235
|
+
addCompletion("parameters", operation.symbol);
|
|
2236
|
+
addCompletion("returnType", operation.symbol);
|
|
2209
2237
|
}
|
|
2210
2238
|
}
|
|
2211
|
-
else {
|
|
2212
|
-
|
|
2239
|
+
else if (base?.node?.kind === SyntaxKind.ModelProperty) {
|
|
2240
|
+
// Process meta properties separately, such as `::type`
|
|
2241
|
+
const metaProperty = base.node;
|
|
2242
|
+
addCompletion("type", metaProperty.symbol);
|
|
2213
2243
|
}
|
|
2214
2244
|
}
|
|
2245
|
+
else {
|
|
2246
|
+
addCompletions(base.exports ?? base.members);
|
|
2247
|
+
}
|
|
2215
2248
|
}
|
|
2216
2249
|
}
|
|
2217
2250
|
else {
|
|
@@ -2252,6 +2285,101 @@ export function createChecker(program, resolver) {
|
|
|
2252
2285
|
}
|
|
2253
2286
|
}
|
|
2254
2287
|
return completions;
|
|
2288
|
+
/** Resolve a usable base type for member/meta-member completions. */
|
|
2289
|
+
function getCompletionBaseType(base) {
|
|
2290
|
+
const entity = getTypeOrValueForNode(base, CheckContext.DEFAULT);
|
|
2291
|
+
if (!entity || !isType(entity) || isErrorType(entity)) {
|
|
2292
|
+
if (base.kind === SyntaxKind.Identifier) {
|
|
2293
|
+
const scopedTemplateParameter = getTemplateParameterTypeFromScope(base);
|
|
2294
|
+
if (scopedTemplateParameter) {
|
|
2295
|
+
return resolveTemplateConstraintType(scopedTemplateParameter);
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
const templateBase = probeTemplateAccessBaseEntity(base);
|
|
2299
|
+
return templateBase ? resolveTemplateConstraintType(templateBase) : undefined;
|
|
2300
|
+
}
|
|
2301
|
+
if (isTemplateAccessType(entity)) {
|
|
2302
|
+
return resolveTemplateConstraintType(entity);
|
|
2303
|
+
}
|
|
2304
|
+
return entity;
|
|
2305
|
+
}
|
|
2306
|
+
/** Resolve a completion base type from a symbol when node-based typing is unavailable. */
|
|
2307
|
+
function getCompletionBaseTypeFromSymbol(base) {
|
|
2308
|
+
if (base.flags & 4194304 /* SymbolFlags.LateBound */) {
|
|
2309
|
+
const lateBoundType = base.type;
|
|
2310
|
+
return lateBoundType && isType(lateBoundType)
|
|
2311
|
+
? resolveCompletionType(lateBoundType)
|
|
2312
|
+
: undefined;
|
|
2313
|
+
}
|
|
2314
|
+
if (base.flags & 1024 /* SymbolFlags.TemplateParameter */) {
|
|
2315
|
+
const mapped = checkTemplateParameterDeclaration(CheckContext.DEFAULT, getSymNode(base));
|
|
2316
|
+
return isType(mapped) ? resolveCompletionType(mapped) : undefined;
|
|
2317
|
+
}
|
|
2318
|
+
return undefined;
|
|
2319
|
+
}
|
|
2320
|
+
/** Normalize template access types to their effective constraint for completion. */
|
|
2321
|
+
function resolveCompletionType(type) {
|
|
2322
|
+
return isTemplateAccessType(type) ? resolveTemplateConstraintType(type) : type;
|
|
2323
|
+
}
|
|
2324
|
+
/** Add member completions based on the resolved base type kind. */
|
|
2325
|
+
function addMemberCompletionsForType(baseType) {
|
|
2326
|
+
switch (baseType.kind) {
|
|
2327
|
+
case "Model":
|
|
2328
|
+
for (const property of walkPropertiesInherited(baseType)) {
|
|
2329
|
+
const ownerSymbol = baseType.node?.symbol;
|
|
2330
|
+
const propertySymbol = (ownerSymbol ? getMemberSymbol(ownerSymbol, property.name) : undefined) ??
|
|
2331
|
+
property.node?.symbol;
|
|
2332
|
+
if (propertySymbol) {
|
|
2333
|
+
addCompletion(property.name, propertySymbol);
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
return;
|
|
2337
|
+
case "Interface":
|
|
2338
|
+
for (const [name, operation] of baseType.operations) {
|
|
2339
|
+
const operationSymbol = operation.node?.symbol;
|
|
2340
|
+
if (operationSymbol) {
|
|
2341
|
+
addCompletion(name, operationSymbol);
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
return;
|
|
2345
|
+
case "Enum":
|
|
2346
|
+
for (const [name, member] of baseType.members) {
|
|
2347
|
+
const enumMemberSymbol = member.node?.symbol;
|
|
2348
|
+
if (enumMemberSymbol) {
|
|
2349
|
+
addCompletion(name, enumMemberSymbol);
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
return;
|
|
2353
|
+
case "Union":
|
|
2354
|
+
for (const [name, variant] of baseType.variants) {
|
|
2355
|
+
if (typeof name === "string" && variant.node?.symbol) {
|
|
2356
|
+
addCompletion(name, variant.node.symbol);
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
return;
|
|
2360
|
+
case "Scalar":
|
|
2361
|
+
for (const [name, constructor] of baseType.constructors) {
|
|
2362
|
+
const constructorSymbol = constructor.node?.symbol;
|
|
2363
|
+
if (constructorSymbol) {
|
|
2364
|
+
addCompletion(name, constructorSymbol);
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
return;
|
|
2368
|
+
case "Namespace":
|
|
2369
|
+
addCompletions(baseType.node?.symbol?.exports);
|
|
2370
|
+
return;
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
/** Add `::` meta-member completions for the resolved base type. */
|
|
2374
|
+
function addMetaCompletionsForType(baseType) {
|
|
2375
|
+
const baseSymbol = getTypeSymbol(baseType);
|
|
2376
|
+
if (!baseSymbol) {
|
|
2377
|
+
return;
|
|
2378
|
+
}
|
|
2379
|
+
for (const metaMemberName of resolver.getMetaMemberNames(baseSymbol)) {
|
|
2380
|
+
addCompletion(metaMemberName, baseSymbol);
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2255
2383
|
function addCompletions(table) {
|
|
2256
2384
|
if (!table) {
|
|
2257
2385
|
return;
|
|
@@ -2273,7 +2401,7 @@ export function createChecker(program, resolver) {
|
|
|
2273
2401
|
}
|
|
2274
2402
|
}
|
|
2275
2403
|
function addCompletion(key, sym, options = {}) {
|
|
2276
|
-
if (sym.symbolSource) {
|
|
2404
|
+
if (sym.symbolSource && !(sym.flags & 4194304 /* SymbolFlags.LateBound */)) {
|
|
2277
2405
|
sym = sym.symbolSource;
|
|
2278
2406
|
}
|
|
2279
2407
|
if (!shouldAddCompletion(sym)) {
|
|
@@ -2330,7 +2458,7 @@ export function createChecker(program, resolver) {
|
|
|
2330
2458
|
}
|
|
2331
2459
|
resolvedOptions.locationContext ??= getLocationContext(program, node);
|
|
2332
2460
|
const sym = resolveTypeReferenceSymInternal(ctx, node, resolvedOptions);
|
|
2333
|
-
if (!resolvedOptions.resolveDeclarationOfTemplate) {
|
|
2461
|
+
if (ctx.mapper === undefined && !resolvedOptions.resolveDeclarationOfTemplate) {
|
|
2334
2462
|
referenceSymCache.set(node, sym);
|
|
2335
2463
|
}
|
|
2336
2464
|
return sym;
|
|
@@ -2373,6 +2501,10 @@ export function createChecker(program, resolver) {
|
|
|
2373
2501
|
if (!base) {
|
|
2374
2502
|
return undefined;
|
|
2375
2503
|
}
|
|
2504
|
+
const directTemplateAccessSym = tryResolveTemplateAccessSymbol(ctx, node, base);
|
|
2505
|
+
if (directTemplateAccessSym) {
|
|
2506
|
+
return directTemplateAccessSym;
|
|
2507
|
+
}
|
|
2376
2508
|
// when resolving a type reference based on an alias, unwrap the alias.
|
|
2377
2509
|
if (base.flags & 128 /* SymbolFlags.Alias */) {
|
|
2378
2510
|
if (!options.resolveDeclarationOfTemplate && isTemplatedNode(getSymNode(base))) {
|
|
@@ -2419,12 +2551,498 @@ export function createChecker(program, resolver) {
|
|
|
2419
2551
|
}
|
|
2420
2552
|
base = baseSym;
|
|
2421
2553
|
}
|
|
2422
|
-
const
|
|
2554
|
+
const templateAccessSym = tryResolveTemplateAccessSymbol(ctx, node, base);
|
|
2555
|
+
if (templateAccessSym) {
|
|
2556
|
+
return templateAccessSym;
|
|
2557
|
+
}
|
|
2558
|
+
const sym = resolveMemberInContainer(ctx, base, node, options);
|
|
2423
2559
|
checkSymbolAccess(options.locationContext, node, sym);
|
|
2424
2560
|
return sym;
|
|
2425
2561
|
}
|
|
2426
2562
|
compilerAssert(false, `Unknown type reference kind "${SyntaxKind[node.kind]}"`, node);
|
|
2427
2563
|
}
|
|
2564
|
+
/**
|
|
2565
|
+
* Resolve member/meta-member access rooted in a template parameter or template access chain.
|
|
2566
|
+
* Falls back to late-bound symbols when the concrete symbol cannot be safely determined.
|
|
2567
|
+
*
|
|
2568
|
+
* @param ctx Check context for mapper and usage observation.
|
|
2569
|
+
* @param node Member expression being resolved.
|
|
2570
|
+
* @param baseSym Resolved symbol for the member expression base.
|
|
2571
|
+
* @returns The resolved symbol for the template access, or `undefined` when not applicable.
|
|
2572
|
+
*/
|
|
2573
|
+
function tryResolveTemplateAccessSymbol(ctx, node, baseSym) {
|
|
2574
|
+
const mappedSymbol = tryResolveMappedTemplateAccessSymbol(ctx, node, baseSym);
|
|
2575
|
+
if (mappedSymbol) {
|
|
2576
|
+
return mappedSymbol;
|
|
2577
|
+
}
|
|
2578
|
+
const baseEntity = getTemplateAccessBaseEntity(ctx, node.base, baseSym);
|
|
2579
|
+
if (!baseEntity) {
|
|
2580
|
+
return undefined;
|
|
2581
|
+
}
|
|
2582
|
+
observeTemplateAccessBase(ctx, baseEntity);
|
|
2583
|
+
const accessedType = resolveTemplateAccessType(ctx, node, baseEntity);
|
|
2584
|
+
const useCache = ctx.mapper === undefined;
|
|
2585
|
+
if (isErrorType(accessedType)) {
|
|
2586
|
+
return createTemplateAccessSymbol(baseEntity, node, errorType, useCache);
|
|
2587
|
+
}
|
|
2588
|
+
if (node.selector === "." &&
|
|
2589
|
+
baseEntity.kind === "TemplateParameterAccess" &&
|
|
2590
|
+
baseEntity.node.selector === "::") {
|
|
2591
|
+
return getPreferredTemplateAccessSymbol(node, accessedType);
|
|
2592
|
+
}
|
|
2593
|
+
if (ctx.mapper !== undefined) {
|
|
2594
|
+
return getPreferredTemplateAccessSymbol(node, accessedType);
|
|
2595
|
+
}
|
|
2596
|
+
return createTemplateAccessSymbol(baseEntity, node, accessedType, useCache);
|
|
2597
|
+
}
|
|
2598
|
+
/**
|
|
2599
|
+
* Resolve template access directly from a mapped template argument when available.
|
|
2600
|
+
*
|
|
2601
|
+
* @param ctx Check context containing the active template mapper.
|
|
2602
|
+
* @param node Member expression being resolved.
|
|
2603
|
+
* @param baseSym Base symbol for the access expression.
|
|
2604
|
+
* @returns A concrete or late-bound symbol for the mapped access, or `undefined`.
|
|
2605
|
+
*/
|
|
2606
|
+
function tryResolveMappedTemplateAccessSymbol(ctx, node, baseSym) {
|
|
2607
|
+
if (!ctx.mapper || !(baseSym.flags & 1024 /* SymbolFlags.TemplateParameter */)) {
|
|
2608
|
+
return undefined;
|
|
2609
|
+
}
|
|
2610
|
+
const declared = checkTemplateParameterDeclaration(CheckContext.DEFAULT, getSymNode(baseSym));
|
|
2611
|
+
if (!isType(declared) || declared.kind !== "TemplateParameter") {
|
|
2612
|
+
return undefined;
|
|
2613
|
+
}
|
|
2614
|
+
const mapped = ctx.mapper.getMappedType(declared);
|
|
2615
|
+
if (!isType(mapped) || isTemplateAccessType(mapped)) {
|
|
2616
|
+
return undefined;
|
|
2617
|
+
}
|
|
2618
|
+
if (isUninstantiatedTemplateType(mapped)) {
|
|
2619
|
+
return undefined;
|
|
2620
|
+
}
|
|
2621
|
+
const resolvedType = node.selector === "."
|
|
2622
|
+
? resolveMemberTypeFromConstraint(mapped, node.id.sv)
|
|
2623
|
+
: resolveMetaTypeFromConstraint(ctx, mapped, node);
|
|
2624
|
+
if (!resolvedType) {
|
|
2625
|
+
return undefined;
|
|
2626
|
+
}
|
|
2627
|
+
return getPreferredTemplateAccessSymbol(node, resolvedType);
|
|
2628
|
+
}
|
|
2629
|
+
/** Return true when a type declaration is templated but has not been instantiated. */
|
|
2630
|
+
function isUninstantiatedTemplateType(type) {
|
|
2631
|
+
if ("templateMapper" in type && type.templateMapper !== undefined) {
|
|
2632
|
+
return false;
|
|
2633
|
+
}
|
|
2634
|
+
const node = type.node;
|
|
2635
|
+
return Boolean(node && "templateParameters" in node && node.templateParameters.length > 0);
|
|
2636
|
+
}
|
|
2637
|
+
/** Return true when the resolved type should remain late-bound due to templating. */
|
|
2638
|
+
function shouldUseLateBoundTemplateAccessType(type) {
|
|
2639
|
+
return "templateMapper" in type && type.templateMapper !== undefined;
|
|
2640
|
+
}
|
|
2641
|
+
/**
|
|
2642
|
+
* Prefer canonical member symbols for resolved template access results when they already exist.
|
|
2643
|
+
* This preserves symbol identity for downstream cloning and doc lookups in instantiated flows.
|
|
2644
|
+
*/
|
|
2645
|
+
function getPreferredTemplateAccessSymbol(node, type) {
|
|
2646
|
+
const canonicalMemberSymbol = getCanonicalTemplateAccessMemberSymbol(type);
|
|
2647
|
+
if (canonicalMemberSymbol) {
|
|
2648
|
+
return canonicalMemberSymbol;
|
|
2649
|
+
}
|
|
2650
|
+
if (!shouldUseLateBoundTemplateAccessType(type)) {
|
|
2651
|
+
return getTypeSymbol(type) ?? createLateBoundTypeSymbol(node, type);
|
|
2652
|
+
}
|
|
2653
|
+
return createLateBoundTypeSymbol(node, type);
|
|
2654
|
+
}
|
|
2655
|
+
/** Return the exact instantiated member symbol for a resolved type when one exists. */
|
|
2656
|
+
function getCanonicalTemplateAccessMemberSymbol(type) {
|
|
2657
|
+
switch (type.kind) {
|
|
2658
|
+
case "ModelProperty":
|
|
2659
|
+
return getExactInstantiatedMemberSymbol(type.model?.symbol, type.name, type);
|
|
2660
|
+
case "Operation":
|
|
2661
|
+
return getExactInstantiatedMemberSymbol(type.interface?.symbol, type.name, type);
|
|
2662
|
+
case "EnumMember":
|
|
2663
|
+
return getExactInstantiatedMemberSymbol(type.enum?.symbol, type.name, type);
|
|
2664
|
+
case "UnionVariant":
|
|
2665
|
+
return getExactInstantiatedMemberSymbol(type.union?.symbol, type.name, type);
|
|
2666
|
+
case "ScalarConstructor":
|
|
2667
|
+
return getExactInstantiatedMemberSymbol(type.scalar?.symbol, type.name, type);
|
|
2668
|
+
default:
|
|
2669
|
+
return undefined;
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
function getExactInstantiatedMemberSymbol(containerSymbol, memberName, type) {
|
|
2673
|
+
if (!containerSymbol || typeof memberName !== "string") {
|
|
2674
|
+
return undefined;
|
|
2675
|
+
}
|
|
2676
|
+
const memberSymbol = getMemberSymbol(containerSymbol, memberName);
|
|
2677
|
+
return memberSymbol?.type === type ? memberSymbol : undefined;
|
|
2678
|
+
}
|
|
2679
|
+
/**
|
|
2680
|
+
* Resolve the template entity (parameter or access) that acts as the base for a member expression.
|
|
2681
|
+
*/
|
|
2682
|
+
function getTemplateAccessBaseEntity(_ctx, baseNode, baseSym) {
|
|
2683
|
+
if (baseSym.flags & 4194304 /* SymbolFlags.LateBound */) {
|
|
2684
|
+
const lateBoundType = baseSym.type;
|
|
2685
|
+
if (lateBoundType && lateBoundType.kind === "TemplateParameterAccess") {
|
|
2686
|
+
return lateBoundType;
|
|
2687
|
+
}
|
|
2688
|
+
return undefined;
|
|
2689
|
+
}
|
|
2690
|
+
if (baseSym.flags & 1024 /* SymbolFlags.TemplateParameter */) {
|
|
2691
|
+
const baseSymbolNode = getSymNode(baseSym);
|
|
2692
|
+
const mapped = checkTemplateParameterDeclaration(CheckContext.DEFAULT, baseSymbolNode);
|
|
2693
|
+
return isType(mapped) && isTemplateAccessType(mapped) ? mapped : undefined;
|
|
2694
|
+
}
|
|
2695
|
+
if (baseNode.kind === SyntaxKind.Identifier) {
|
|
2696
|
+
const templateParameterType = getTemplateParameterTypeFromScope(baseNode);
|
|
2697
|
+
if (templateParameterType) {
|
|
2698
|
+
return templateParameterType;
|
|
2699
|
+
}
|
|
2700
|
+
return undefined;
|
|
2701
|
+
}
|
|
2702
|
+
return undefined;
|
|
2703
|
+
}
|
|
2704
|
+
/** Probe a node for a template access base without surfacing diagnostics. */
|
|
2705
|
+
function probeTemplateAccessBaseEntity(node) {
|
|
2706
|
+
const oldDiagnosticHook = onCheckerDiagnostic;
|
|
2707
|
+
onCheckerDiagnostic = () => { };
|
|
2708
|
+
const entity = checkTypeOrValueReference(CheckContext.DEFAULT, node, false);
|
|
2709
|
+
onCheckerDiagnostic = oldDiagnosticHook;
|
|
2710
|
+
return isType(entity) && isTemplateAccessType(entity) ? entity : undefined;
|
|
2711
|
+
}
|
|
2712
|
+
/** Resolve a template parameter type from lexical scope by identifier name. */
|
|
2713
|
+
function getTemplateParameterTypeFromScope(identifier) {
|
|
2714
|
+
const declaration = findTemplateParameterDeclarationInScope(identifier, identifier.sv);
|
|
2715
|
+
if (!declaration) {
|
|
2716
|
+
return undefined;
|
|
2717
|
+
}
|
|
2718
|
+
const mapped = checkTemplateParameterDeclaration(CheckContext.DEFAULT, declaration);
|
|
2719
|
+
return isType(mapped) && isTemplateAccessType(mapped) ? mapped : undefined;
|
|
2720
|
+
}
|
|
2721
|
+
/** Find the closest template parameter declaration matching the given name. */
|
|
2722
|
+
function findTemplateParameterDeclarationInScope(node, name) {
|
|
2723
|
+
let current = node.parent;
|
|
2724
|
+
while (current) {
|
|
2725
|
+
if ("templateParameters" in current && current.templateParameters) {
|
|
2726
|
+
const declaration = current.templateParameters.find((x) => x.id.sv === name);
|
|
2727
|
+
if (declaration) {
|
|
2728
|
+
return declaration;
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
current = current.parent;
|
|
2732
|
+
}
|
|
2733
|
+
return undefined;
|
|
2734
|
+
}
|
|
2735
|
+
/**
|
|
2736
|
+
* Resolve the resulting type for a template parameter access expression.
|
|
2737
|
+
*
|
|
2738
|
+
* @param ctx Check context for mapper-aware resolution.
|
|
2739
|
+
* @param node Access expression (`.` or `::`) being resolved.
|
|
2740
|
+
* @param baseEntity Template parameter or prior template access chain.
|
|
2741
|
+
* @returns The resolved member/meta-member type, or `errorType` when not guaranteed.
|
|
2742
|
+
*/
|
|
2743
|
+
function resolveTemplateAccessType(ctx, node, baseEntity) {
|
|
2744
|
+
const baseType = resolveTemplateAccessBaseType(ctx, baseEntity);
|
|
2745
|
+
if (!baseType) {
|
|
2746
|
+
if (hasErrorTemplateConstraint(baseEntity)) {
|
|
2747
|
+
return errorType;
|
|
2748
|
+
}
|
|
2749
|
+
reportTemplateAccessNotGuaranteed(node, baseEntity);
|
|
2750
|
+
return errorType;
|
|
2751
|
+
}
|
|
2752
|
+
const resolvedType = node.selector === "."
|
|
2753
|
+
? resolveMemberTypeFromConstraint(baseType, node.id.sv)
|
|
2754
|
+
: resolveMetaTypeFromConstraint(ctx, baseType, node);
|
|
2755
|
+
if (!resolvedType) {
|
|
2756
|
+
reportTemplateAccessNotGuaranteed(node, baseType);
|
|
2757
|
+
return errorType;
|
|
2758
|
+
}
|
|
2759
|
+
return resolvedType;
|
|
2760
|
+
}
|
|
2761
|
+
/**
|
|
2762
|
+
* Resolve the concrete base type that a template access should evaluate against.
|
|
2763
|
+
*
|
|
2764
|
+
* @param ctx Check context containing optional template mapper.
|
|
2765
|
+
* @param baseEntity Template parameter/access entity.
|
|
2766
|
+
* @returns The mapped or constrained base type, if determinable.
|
|
2767
|
+
*/
|
|
2768
|
+
function resolveTemplateAccessBaseType(ctx, baseEntity) {
|
|
2769
|
+
if (ctx.mapper && baseEntity.kind === "TemplateParameterAccess") {
|
|
2770
|
+
const mappedBaseType = resolveTemplateAccessBaseType(ctx, baseEntity.base);
|
|
2771
|
+
if (!mappedBaseType) {
|
|
2772
|
+
return undefined;
|
|
2773
|
+
}
|
|
2774
|
+
return baseEntity.node.selector === "."
|
|
2775
|
+
? resolveMemberTypeFromConstraint(mappedBaseType, baseEntity.node.id.sv)
|
|
2776
|
+
: resolveMetaTypeFromConstraint(ctx, mappedBaseType, baseEntity.node);
|
|
2777
|
+
}
|
|
2778
|
+
if (ctx.mapper && baseEntity.kind === "TemplateParameter") {
|
|
2779
|
+
const mapped = ctx.mapper.getMappedType(baseEntity);
|
|
2780
|
+
if (isType(mapped)) {
|
|
2781
|
+
if (isTemplateAccessType(mapped)) {
|
|
2782
|
+
return resolveTemplateConstraintType(mapped);
|
|
2783
|
+
}
|
|
2784
|
+
if (isUninstantiatedTemplateType(mapped)) {
|
|
2785
|
+
return resolveTemplateConstraintType(baseEntity);
|
|
2786
|
+
}
|
|
2787
|
+
return mapped;
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
return resolveTemplateConstraintType(baseEntity);
|
|
2791
|
+
}
|
|
2792
|
+
/**
|
|
2793
|
+
* Resolve the terminal non-template constraint type for a template access chain.
|
|
2794
|
+
*
|
|
2795
|
+
* @param templateType Template parameter or access node.
|
|
2796
|
+
* @returns The terminal constrained type, or `undefined` when missing/invalid/cyclic.
|
|
2797
|
+
*/
|
|
2798
|
+
function resolveTemplateConstraintType(templateType) {
|
|
2799
|
+
const visited = new Set();
|
|
2800
|
+
let current = templateType;
|
|
2801
|
+
while (true) {
|
|
2802
|
+
if (visited.has(current)) {
|
|
2803
|
+
return undefined;
|
|
2804
|
+
}
|
|
2805
|
+
visited.add(current);
|
|
2806
|
+
const constraintType = current.constraint?.type;
|
|
2807
|
+
if (!constraintType || isErrorType(constraintType)) {
|
|
2808
|
+
return undefined;
|
|
2809
|
+
}
|
|
2810
|
+
if (!isTemplateAccessType(constraintType)) {
|
|
2811
|
+
return constraintType;
|
|
2812
|
+
}
|
|
2813
|
+
current = constraintType;
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
/** Return true when a template access chain includes an error constraint. */
|
|
2817
|
+
function hasErrorTemplateConstraint(templateType) {
|
|
2818
|
+
let current = templateType;
|
|
2819
|
+
while (true) {
|
|
2820
|
+
const constraintType = current.constraint?.type;
|
|
2821
|
+
if (constraintType && isErrorType(constraintType)) {
|
|
2822
|
+
return true;
|
|
2823
|
+
}
|
|
2824
|
+
if (current.kind !== "TemplateParameterAccess") {
|
|
2825
|
+
return false;
|
|
2826
|
+
}
|
|
2827
|
+
current = current.base;
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
/** Track template parameter usage for a template access base chain. */
|
|
2831
|
+
function observeTemplateAccessBase(ctx, base) {
|
|
2832
|
+
const root = getTemplateAccessRoot(base);
|
|
2833
|
+
ctx.observeTemplateParameter(root);
|
|
2834
|
+
templateParameterUsageMap.set(root.node, true);
|
|
2835
|
+
}
|
|
2836
|
+
/** Return the root template parameter for a template access chain. */
|
|
2837
|
+
function getTemplateAccessRoot(base) {
|
|
2838
|
+
let current = base;
|
|
2839
|
+
while (current.kind === "TemplateParameterAccess") {
|
|
2840
|
+
current = current.base;
|
|
2841
|
+
}
|
|
2842
|
+
return current;
|
|
2843
|
+
}
|
|
2844
|
+
/** Resolve `.` access from a constrained type by kind-specific member lookup. */
|
|
2845
|
+
function resolveMemberTypeFromConstraint(constraintType, memberName) {
|
|
2846
|
+
const canonicalMemberType = getCanonicalResolvedMemberType(constraintType, memberName);
|
|
2847
|
+
if (canonicalMemberType) {
|
|
2848
|
+
return canonicalMemberType;
|
|
2849
|
+
}
|
|
2850
|
+
switch (constraintType.kind) {
|
|
2851
|
+
case "Model":
|
|
2852
|
+
for (const property of walkPropertiesInherited(constraintType)) {
|
|
2853
|
+
if (property.name === memberName) {
|
|
2854
|
+
return property;
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
return undefined;
|
|
2858
|
+
case "Interface":
|
|
2859
|
+
return constraintType.operations.get(memberName);
|
|
2860
|
+
case "Enum":
|
|
2861
|
+
return constraintType.members.get(memberName);
|
|
2862
|
+
case "Union":
|
|
2863
|
+
return constraintType.variants.get(memberName);
|
|
2864
|
+
case "Scalar":
|
|
2865
|
+
return constraintType.constructors.get(memberName);
|
|
2866
|
+
default:
|
|
2867
|
+
return undefined;
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
function getCanonicalResolvedMemberSymbol(containerType, memberName) {
|
|
2871
|
+
switch (containerType.kind) {
|
|
2872
|
+
case "Model":
|
|
2873
|
+
case "Interface":
|
|
2874
|
+
case "Enum":
|
|
2875
|
+
case "Union":
|
|
2876
|
+
case "Scalar":
|
|
2877
|
+
break;
|
|
2878
|
+
default:
|
|
2879
|
+
return undefined;
|
|
2880
|
+
}
|
|
2881
|
+
const containerSymbol = containerType.symbol;
|
|
2882
|
+
if (containerSymbol === undefined || !containerSymbol.members) {
|
|
2883
|
+
return undefined;
|
|
2884
|
+
}
|
|
2885
|
+
let memberSymbol = getMemberSymbol(containerSymbol, memberName);
|
|
2886
|
+
if (!memberSymbol?.type && containerSymbol.flags & 4194304 /* SymbolFlags.LateBound */) {
|
|
2887
|
+
lateBindMembers(containerType);
|
|
2888
|
+
memberSymbol = getMemberSymbol(containerSymbol, memberName);
|
|
2889
|
+
}
|
|
2890
|
+
return memberSymbol;
|
|
2891
|
+
}
|
|
2892
|
+
function getCanonicalResolvedMemberType(containerType, memberName) {
|
|
2893
|
+
const memberType = getCanonicalResolvedMemberSymbol(containerType, memberName)?.type;
|
|
2894
|
+
return memberType && isType(memberType) ? memberType : undefined;
|
|
2895
|
+
}
|
|
2896
|
+
/**
|
|
2897
|
+
* Resolve `::` meta-member access from a constrained type.
|
|
2898
|
+
*
|
|
2899
|
+
* @param ctx Check context used for symbol-to-entity evaluation.
|
|
2900
|
+
* @param constraintType Base constrained type.
|
|
2901
|
+
* @param node Meta-member expression node.
|
|
2902
|
+
* @returns The resolved meta-member type, `unknownType` for projection-only cases, or `undefined`.
|
|
2903
|
+
*/
|
|
2904
|
+
function resolveMetaTypeFromConstraint(ctx, constraintType, node) {
|
|
2905
|
+
if (constraintType.kind === "ModelProperty" && node.id.sv === "type") {
|
|
2906
|
+
return constraintType.type;
|
|
2907
|
+
}
|
|
2908
|
+
if (constraintType.kind === "Operation") {
|
|
2909
|
+
switch (node.id.sv) {
|
|
2910
|
+
case "parameters":
|
|
2911
|
+
return constraintType.parameters;
|
|
2912
|
+
case "returnType":
|
|
2913
|
+
return constraintType.returnType;
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
const constraintSymbol = getTypeSymbol(constraintType);
|
|
2917
|
+
if (!constraintSymbol) {
|
|
2918
|
+
return undefined;
|
|
2919
|
+
}
|
|
2920
|
+
const metaMemberNames = resolver.getMetaMemberNames(constraintSymbol);
|
|
2921
|
+
if (!metaMemberNames.includes(node.id.sv)) {
|
|
2922
|
+
return undefined;
|
|
2923
|
+
}
|
|
2924
|
+
if (isReflectionMetaProjectionSymbol(constraintSymbol)) {
|
|
2925
|
+
// Reflection model symbols expose meta-member names by projection, but their
|
|
2926
|
+
// underlying nodes are not concrete ModelProperty/Operation nodes.
|
|
2927
|
+
return unknownType;
|
|
2928
|
+
}
|
|
2929
|
+
const resolved = resolver.resolveMetaMemberByName(constraintSymbol, node.id.sv);
|
|
2930
|
+
if (resolved.resolutionResult & ResolutionResultFlags.Resolved && resolved.resolvedSymbol) {
|
|
2931
|
+
const entity = checkTypeOrValueReferenceSymbol(ctx, resolved.resolvedSymbol, node, false);
|
|
2932
|
+
if (entity === null) {
|
|
2933
|
+
return undefined;
|
|
2934
|
+
}
|
|
2935
|
+
if (entity.entityKind === "Indeterminate") {
|
|
2936
|
+
return entity.type;
|
|
2937
|
+
}
|
|
2938
|
+
return isType(entity) ? entity : undefined;
|
|
2939
|
+
}
|
|
2940
|
+
return unknownType;
|
|
2941
|
+
}
|
|
2942
|
+
/** Return true for TypeSpec.Reflection model symbols backed by projection metadata. */
|
|
2943
|
+
function isReflectionMetaProjectionSymbol(sym) {
|
|
2944
|
+
return (sym.node?.kind === SyntaxKind.ModelStatement &&
|
|
2945
|
+
sym.parent?.name === "Reflection" &&
|
|
2946
|
+
sym.parent?.parent?.name === "TypeSpec");
|
|
2947
|
+
}
|
|
2948
|
+
/** Report an invalid-ref diagnostic for unsupported template member/meta-member access. */
|
|
2949
|
+
function reportTemplateAccessNotGuaranteed(node, baseType) {
|
|
2950
|
+
reportCheckerDiagnostic(createDiagnostic({
|
|
2951
|
+
code: "invalid-ref",
|
|
2952
|
+
messageId: node.selector === "." ? "member" : "metaProperty",
|
|
2953
|
+
format: { kind: getTemplateAccessKindName(baseType), id: node.id.sv },
|
|
2954
|
+
target: node,
|
|
2955
|
+
}));
|
|
2956
|
+
}
|
|
2957
|
+
/** Get the diagnostic kind label used when template access resolution fails. */
|
|
2958
|
+
function getTemplateAccessKindName(type) {
|
|
2959
|
+
switch (type.kind) {
|
|
2960
|
+
case "Model":
|
|
2961
|
+
case "ModelProperty":
|
|
2962
|
+
case "Enum":
|
|
2963
|
+
case "Interface":
|
|
2964
|
+
case "Union":
|
|
2965
|
+
case "Operation":
|
|
2966
|
+
case "Scalar":
|
|
2967
|
+
case "TemplateParameter":
|
|
2968
|
+
case "TemplateParameterAccess":
|
|
2969
|
+
return type.kind;
|
|
2970
|
+
default:
|
|
2971
|
+
return "Type";
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
/** Type guard for template parameters and template parameter access types. */
|
|
2975
|
+
function isTemplateAccessType(type) {
|
|
2976
|
+
return type.kind === "TemplateParameter" || type.kind === "TemplateParameterAccess";
|
|
2977
|
+
}
|
|
2978
|
+
/**
|
|
2979
|
+
* Create (or retrieve from cache) a late-bound symbol representing template access.
|
|
2980
|
+
*
|
|
2981
|
+
* @param base Template parameter/access base.
|
|
2982
|
+
* @param node Member expression node.
|
|
2983
|
+
* @param constraintType Resolved constraint for the access result.
|
|
2984
|
+
* @param useCache Whether to reuse/access symbol cache.
|
|
2985
|
+
* @returns A symbol whose type is `TemplateParameterAccess`.
|
|
2986
|
+
*/
|
|
2987
|
+
function createTemplateAccessSymbol(base, node, constraintType, useCache = true) {
|
|
2988
|
+
const cacheKey = getTemplateAccessCacheKey(base, node);
|
|
2989
|
+
if (useCache) {
|
|
2990
|
+
const existing = templateAccessSymbolCache.get(cacheKey);
|
|
2991
|
+
if (existing) {
|
|
2992
|
+
return existing;
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
const constraint = {
|
|
2996
|
+
entityKind: "MixedParameterConstraint",
|
|
2997
|
+
node,
|
|
2998
|
+
type: constraintType,
|
|
2999
|
+
};
|
|
3000
|
+
const type = createAndFinishType({
|
|
3001
|
+
kind: "TemplateParameterAccess",
|
|
3002
|
+
node,
|
|
3003
|
+
base,
|
|
3004
|
+
path: printMemberExpressionPath(getTemplateAccessPath(base), node.selector, node.id.sv),
|
|
3005
|
+
constraint,
|
|
3006
|
+
});
|
|
3007
|
+
templateAccessCacheKeys.set(type, cacheKey);
|
|
3008
|
+
const symbol = createSymbol(node, node.id.sv, 4194304 /* SymbolFlags.LateBound */);
|
|
3009
|
+
mutate(symbol).type = type;
|
|
3010
|
+
if (useCache) {
|
|
3011
|
+
templateAccessSymbolCache.set(cacheKey, symbol);
|
|
3012
|
+
}
|
|
3013
|
+
return symbol;
|
|
3014
|
+
}
|
|
3015
|
+
/** Compute the user-facing access path for a template access chain. */
|
|
3016
|
+
function getTemplateAccessPath(base) {
|
|
3017
|
+
return base.kind === "TemplateParameterAccess" ? base.path : printIdentifier(base.node.id.sv);
|
|
3018
|
+
}
|
|
3019
|
+
/** Build a stable cache key for a template access symbol/type chain. */
|
|
3020
|
+
function getTemplateAccessCacheKey(base, node) {
|
|
3021
|
+
let baseKey;
|
|
3022
|
+
if (base.kind === "TemplateParameterAccess") {
|
|
3023
|
+
const cacheKey = templateAccessCacheKeys.get(base);
|
|
3024
|
+
compilerAssert(cacheKey, "Expected template access cache key");
|
|
3025
|
+
baseKey = cacheKey;
|
|
3026
|
+
}
|
|
3027
|
+
else {
|
|
3028
|
+
baseKey = `tp:${getSymbolCacheId(base.node.symbol)}`;
|
|
3029
|
+
}
|
|
3030
|
+
return `${baseKey}${node.selector}${node.id.sv}`;
|
|
3031
|
+
}
|
|
3032
|
+
/** Resolve the merged symbol associated with a type, when one exists. */
|
|
3033
|
+
function getTypeSymbol(type) {
|
|
3034
|
+
return type.node?.symbol ? getMergedSymbol(type.node.symbol) : undefined;
|
|
3035
|
+
}
|
|
3036
|
+
/** Get a stable numeric id for a symbol used in template access cache keys. */
|
|
3037
|
+
function getSymbolCacheId(sym) {
|
|
3038
|
+
const existing = symbolCacheIds.get(sym);
|
|
3039
|
+
if (existing !== undefined) {
|
|
3040
|
+
return existing;
|
|
3041
|
+
}
|
|
3042
|
+
const id = nextSymbolCacheId++;
|
|
3043
|
+
symbolCacheIds.set(sym, id);
|
|
3044
|
+
return id;
|
|
3045
|
+
}
|
|
2428
3046
|
function checkSymbolAccess(sourceLocation, node, symbol) {
|
|
2429
3047
|
if (!symbol)
|
|
2430
3048
|
return;
|
|
@@ -2468,7 +3086,11 @@ export function createChecker(program, resolver) {
|
|
|
2468
3086
|
target: node,
|
|
2469
3087
|
}));
|
|
2470
3088
|
}
|
|
2471
|
-
function resolveMemberInContainer(base, node, options) {
|
|
3089
|
+
function resolveMemberInContainer(ctx, base, node, options) {
|
|
3090
|
+
const symbolFromType = resolveMemberOnSymbolType(ctx, base, node);
|
|
3091
|
+
if (symbolFromType) {
|
|
3092
|
+
return symbolFromType;
|
|
3093
|
+
}
|
|
2472
3094
|
const { finalSymbol: sym, resolvedSymbol: nextSym } = resolver.resolveMemberExpressionForSym(base, node, options);
|
|
2473
3095
|
const symbol = nextSym ?? sym;
|
|
2474
3096
|
if (symbol) {
|
|
@@ -2540,6 +3162,100 @@ export function createChecker(program, resolver) {
|
|
|
2540
3162
|
return "Type";
|
|
2541
3163
|
}
|
|
2542
3164
|
}
|
|
3165
|
+
/**
|
|
3166
|
+
* Resolve a member/meta-member access from the base symbol's resolved type.
|
|
3167
|
+
*
|
|
3168
|
+
* @param ctx Check context.
|
|
3169
|
+
* @param base Base symbol.
|
|
3170
|
+
* @param node Member expression to resolve.
|
|
3171
|
+
* @returns Concrete symbol, late-bound symbol, or `undefined` when unresolved.
|
|
3172
|
+
*/
|
|
3173
|
+
function resolveMemberOnSymbolType(ctx, base, node) {
|
|
3174
|
+
const baseType = getMemberResolutionType(ctx, base);
|
|
3175
|
+
if (!baseType) {
|
|
3176
|
+
return undefined;
|
|
3177
|
+
}
|
|
3178
|
+
const resolvedBaseType = isTemplateAccessType(baseType)
|
|
3179
|
+
? resolveTemplateAccessBaseType(ctx, baseType)
|
|
3180
|
+
: baseType;
|
|
3181
|
+
if (!resolvedBaseType) {
|
|
3182
|
+
return undefined;
|
|
3183
|
+
}
|
|
3184
|
+
if (node.selector === ".") {
|
|
3185
|
+
const table = base.exports ?? base.members;
|
|
3186
|
+
if (table) {
|
|
3187
|
+
const directMember = resolver.getAugmentedSymbolTable(table).get(node.id.sv);
|
|
3188
|
+
if (directMember) {
|
|
3189
|
+
return directMember;
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
const canonicalMember = getCanonicalResolvedMemberSymbol(resolvedBaseType, node.id.sv);
|
|
3193
|
+
if (canonicalMember) {
|
|
3194
|
+
return canonicalMember;
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
const resolvedType = node.selector === "."
|
|
3198
|
+
? resolveMemberTypeFromConstraint(resolvedBaseType, node.id.sv)
|
|
3199
|
+
: resolveMetaTypeFromConstraint(ctx, resolvedBaseType, node);
|
|
3200
|
+
if (!resolvedType) {
|
|
3201
|
+
return undefined;
|
|
3202
|
+
}
|
|
3203
|
+
if (node.selector === "::" &&
|
|
3204
|
+
node.id.sv === "type" &&
|
|
3205
|
+
resolvedType.kind === "TemplateParameterAccess" &&
|
|
3206
|
+
resolvedType.base.kind === "TemplateParameterAccess" &&
|
|
3207
|
+
resolvedType.base.node.selector === ".") {
|
|
3208
|
+
const sourceProperty = resolveTemplateConstraintType(resolvedType.base);
|
|
3209
|
+
if (sourceProperty) {
|
|
3210
|
+
return getTypeSymbol(sourceProperty) ?? createLateBoundTypeSymbol(node, sourceProperty);
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
return getPreferredTemplateAccessSymbol(node, resolvedType);
|
|
3214
|
+
}
|
|
3215
|
+
/** Resolve the effective type used for member lookup on a base symbol. */
|
|
3216
|
+
function getMemberResolutionType(_ctx, base) {
|
|
3217
|
+
if (base.flags & 4194304 /* SymbolFlags.LateBound */) {
|
|
3218
|
+
return base.type && isType(base.type) ? base.type : undefined;
|
|
3219
|
+
}
|
|
3220
|
+
if (base.flags & 65536 /* SymbolFlags.Member */) {
|
|
3221
|
+
return base.type && isType(base.type) ? base.type : undefined;
|
|
3222
|
+
}
|
|
3223
|
+
return undefined;
|
|
3224
|
+
}
|
|
3225
|
+
/** Create a late-bound symbol carrying a precomputed type for member resolution. */
|
|
3226
|
+
function createLateBoundTypeSymbol(node, type) {
|
|
3227
|
+
const symbol = createSymbol(node, node.id.sv, 4194304 /* SymbolFlags.LateBound */);
|
|
3228
|
+
mutate(symbol).type = type;
|
|
3229
|
+
const symbolSource = getTemplateAccessSymbolSource(type);
|
|
3230
|
+
if (symbolSource) {
|
|
3231
|
+
mutate(symbol).symbolSource = symbolSource;
|
|
3232
|
+
}
|
|
3233
|
+
return symbol;
|
|
3234
|
+
}
|
|
3235
|
+
/** Get the best source symbol to associate with a template-access-derived symbol. */
|
|
3236
|
+
function getTemplateAccessSymbolSource(type) {
|
|
3237
|
+
const symbolSource = type.kind === "ModelProperty"
|
|
3238
|
+
? getModelPropertySymbolSource(type)
|
|
3239
|
+
: (getCanonicalTemplateAccessMemberSymbol(type) ?? getTypeSymbol(type));
|
|
3240
|
+
return symbolSource?.symbolSource ?? symbolSource;
|
|
3241
|
+
}
|
|
3242
|
+
function getModelPropertySymbolSource(type) {
|
|
3243
|
+
let fallback;
|
|
3244
|
+
for (let property = type; property; property = property.sourceProperty) {
|
|
3245
|
+
const symbolSource = getCanonicalTemplateAccessMemberSymbol(property) ??
|
|
3246
|
+
(property.model?.symbol && typeof property.name === "string"
|
|
3247
|
+
? getMemberSymbol(property.model.symbol, property.name)
|
|
3248
|
+
: undefined) ??
|
|
3249
|
+
getTypeSymbol(property);
|
|
3250
|
+
if (symbolSource) {
|
|
3251
|
+
fallback ??= symbolSource;
|
|
3252
|
+
if (!property.sourceProperty) {
|
|
3253
|
+
return symbolSource;
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
}
|
|
3257
|
+
return fallback;
|
|
3258
|
+
}
|
|
2543
3259
|
/**
|
|
2544
3260
|
* Return the symbol that is aliased by this alias declaration. If no such symbol is aliased,
|
|
2545
3261
|
* return the symbol for the alias instead. For member containers which need to be late bound
|
|
@@ -2550,7 +3266,12 @@ export function createChecker(program, resolver) {
|
|
|
2550
3266
|
const node = getSymNode(aliasSymbol);
|
|
2551
3267
|
const links = resolver.getSymbolLinks(aliasSymbol);
|
|
2552
3268
|
if (!links.aliasResolutionIsTemplate) {
|
|
2553
|
-
|
|
3269
|
+
const aliased = links.aliasedSymbol ?? resolver.getNodeLinks(node).resolvedSymbol;
|
|
3270
|
+
if (aliased && isTemplatedNode(getSymNode(aliased))) {
|
|
3271
|
+
const aliasType = getTypeForNode(node, ctx);
|
|
3272
|
+
return lateBindContainer(aliasType, aliasSymbol);
|
|
3273
|
+
}
|
|
3274
|
+
return aliased;
|
|
2554
3275
|
}
|
|
2555
3276
|
// Otherwise for templates we need to get the type and retrieve the late bound symbol.
|
|
2556
3277
|
const aliasType = getTypeForNode(node, ctx);
|
|
@@ -2607,7 +3328,9 @@ export function createChecker(program, resolver) {
|
|
|
2607
3328
|
if (isValue(typeOrValue)) {
|
|
2608
3329
|
hasValue = true;
|
|
2609
3330
|
}
|
|
2610
|
-
else if ("kind" in typeOrValue &&
|
|
3331
|
+
else if ("kind" in typeOrValue &&
|
|
3332
|
+
(typeOrValue.kind === "TemplateParameter" ||
|
|
3333
|
+
typeOrValue.kind === "TemplateParameterAccess")) {
|
|
2611
3334
|
if (typeOrValue.constraint) {
|
|
2612
3335
|
if (typeOrValue.constraint.valueType) {
|
|
2613
3336
|
hasValue = true;
|
|
@@ -2636,7 +3359,9 @@ export function createChecker(program, resolver) {
|
|
|
2636
3359
|
let str = node.head.value;
|
|
2637
3360
|
for (const [span, typeOrValue] of spanTypeOrValues) {
|
|
2638
3361
|
if (typeOrValue !== null &&
|
|
2639
|
-
(!("kind" in typeOrValue) ||
|
|
3362
|
+
(!("kind" in typeOrValue) ||
|
|
3363
|
+
(typeOrValue.kind !== "TemplateParameter" &&
|
|
3364
|
+
typeOrValue.kind !== "TemplateParameterAccess"))) {
|
|
2640
3365
|
compilerAssert(typeOrValue !== null && isValue(typeOrValue), "Expected value.");
|
|
2641
3366
|
str += stringifyValueForTemplate(typeOrValue);
|
|
2642
3367
|
}
|
|
@@ -3265,7 +3990,7 @@ export function createChecker(program, resolver) {
|
|
|
3265
3990
|
if (target.kind === "Scalar" || target.kind === "ScalarConstructor") {
|
|
3266
3991
|
return target;
|
|
3267
3992
|
}
|
|
3268
|
-
else if (target.kind === "TemplateParameter") {
|
|
3993
|
+
else if (target.kind === "TemplateParameter" || target.kind === "TemplateParameterAccess") {
|
|
3269
3994
|
const callable = target.constraint && constraintIsCallable(target.constraint);
|
|
3270
3995
|
if (!callable) {
|
|
3271
3996
|
reportCheckerDiagnostic(createDiagnostic({
|
|
@@ -3751,7 +4476,7 @@ export function createChecker(program, resolver) {
|
|
|
3751
4476
|
return entity.type;
|
|
3752
4477
|
}
|
|
3753
4478
|
if (isType(entity)) {
|
|
3754
|
-
if (entity.kind === "TemplateParameter") {
|
|
4479
|
+
if (entity.kind === "TemplateParameter" || entity.kind === "TemplateParameterAccess") {
|
|
3755
4480
|
if (entity.constraint === undefined || entity.constraint.type !== undefined) {
|
|
3756
4481
|
// means this template constraint will accept values
|
|
3757
4482
|
reportCheckerDiagnostic(createDiagnostic({
|
|
@@ -3848,8 +4573,11 @@ export function createChecker(program, resolver) {
|
|
|
3848
4573
|
* access a symbol for a type that is created during the check phase.
|
|
3849
4574
|
*/
|
|
3850
4575
|
function lateBindMemberContainer(type) {
|
|
3851
|
-
|
|
4576
|
+
const existingSymbol = type.symbol;
|
|
4577
|
+
if (existingSymbol &&
|
|
4578
|
+
!(isTemplateInstance(type) && !(existingSymbol.flags & 4194304 /* SymbolFlags.LateBound */))) {
|
|
3852
4579
|
return;
|
|
4580
|
+
}
|
|
3853
4581
|
switch (type.kind) {
|
|
3854
4582
|
case "Model":
|
|
3855
4583
|
type.symbol = createSymbol(type.node, type.name, 2 /* SymbolFlags.Model */ | 4194304 /* SymbolFlags.LateBound */);
|
|
@@ -3906,6 +4634,10 @@ export function createChecker(program, resolver) {
|
|
|
3906
4634
|
}
|
|
3907
4635
|
const sym = createSymbol(member.node, member.name, kind | 4194304 /* SymbolFlags.LateBound */, containerSym);
|
|
3908
4636
|
mutate(sym).type = member;
|
|
4637
|
+
const symbolSource = getTypeSymbol(member);
|
|
4638
|
+
if (symbolSource) {
|
|
4639
|
+
mutate(sym).symbolSource = symbolSource.symbolSource ?? symbolSource;
|
|
4640
|
+
}
|
|
3909
4641
|
compilerAssert(containerSym.members, "containerSym.members is undefined");
|
|
3910
4642
|
containerMembers.set(member.name, sym);
|
|
3911
4643
|
}
|
|
@@ -4024,7 +4756,9 @@ export function createChecker(program, resolver) {
|
|
|
4024
4756
|
}
|
|
4025
4757
|
function checkSpreadProperty(ctx, parentModelSym, targetNode, parentModel) {
|
|
4026
4758
|
const targetType = getTypeForNode(targetNode, ctx);
|
|
4027
|
-
if (targetType.kind === "TemplateParameter" ||
|
|
4759
|
+
if (targetType.kind === "TemplateParameter" ||
|
|
4760
|
+
targetType.kind === "TemplateParameterAccess" ||
|
|
4761
|
+
isErrorType(targetType)) {
|
|
4028
4762
|
return [[], undefined];
|
|
4029
4763
|
}
|
|
4030
4764
|
if (targetType.kind !== "Model") {
|
|
@@ -4108,7 +4842,7 @@ export function createChecker(program, resolver) {
|
|
|
4108
4842
|
linkMapper(type, ctx.mapper);
|
|
4109
4843
|
const shouldRunDecorators = !ctx.hasFlags(CheckFlags.InTemplateDeclaration);
|
|
4110
4844
|
if (!parentTemplate || shouldRunDecorators) {
|
|
4111
|
-
const docComment =
|
|
4845
|
+
const docComment = getDocCommentForSymbol(sym) ?? getOperationParameterDocComment(prop);
|
|
4112
4846
|
if (docComment) {
|
|
4113
4847
|
type.decorators.unshift(createDocFromCommentDecorator("self", docComment));
|
|
4114
4848
|
}
|
|
@@ -4116,6 +4850,20 @@ export function createChecker(program, resolver) {
|
|
|
4116
4850
|
pendingResolutions.finish(sym, ResolutionKind.Type);
|
|
4117
4851
|
return finishType(type, { skipDecorators: !shouldRunDecorators });
|
|
4118
4852
|
}
|
|
4853
|
+
function getOperationParameterDocComment(prop) {
|
|
4854
|
+
if (prop.parent?.kind !== SyntaxKind.ModelExpression) {
|
|
4855
|
+
return undefined;
|
|
4856
|
+
}
|
|
4857
|
+
const signature = prop.parent.parent;
|
|
4858
|
+
if (signature?.kind !== SyntaxKind.OperationSignatureDeclaration) {
|
|
4859
|
+
return undefined;
|
|
4860
|
+
}
|
|
4861
|
+
const operation = signature.parent;
|
|
4862
|
+
if (operation?.kind !== SyntaxKind.OperationStatement) {
|
|
4863
|
+
return undefined;
|
|
4864
|
+
}
|
|
4865
|
+
return extractParamDocs(operation).get(prop.id.sv);
|
|
4866
|
+
}
|
|
4119
4867
|
function createDocFromCommentDecorator(key, doc) {
|
|
4120
4868
|
return {
|
|
4121
4869
|
decorator: docFromCommentDecorator,
|
|
@@ -4125,6 +4873,32 @@ export function createChecker(program, resolver) {
|
|
|
4125
4873
|
],
|
|
4126
4874
|
};
|
|
4127
4875
|
}
|
|
4876
|
+
function getDocCommentForSymbol(sym) {
|
|
4877
|
+
if (!sym) {
|
|
4878
|
+
return undefined;
|
|
4879
|
+
}
|
|
4880
|
+
const relatedSource = getRelatedSymbolSource(sym);
|
|
4881
|
+
return (docFromCommentForSym.get(sym) ??
|
|
4882
|
+
(relatedSource && docFromCommentForSym.get(relatedSource)) ??
|
|
4883
|
+
(sym.flags & 4194304 /* SymbolFlags.LateBound */ && sym.type && isType(sym.type)
|
|
4884
|
+
? getDocCommentForType(sym.type)
|
|
4885
|
+
: undefined));
|
|
4886
|
+
}
|
|
4887
|
+
function getDocCommentForType(type) {
|
|
4888
|
+
if (!type) {
|
|
4889
|
+
return undefined;
|
|
4890
|
+
}
|
|
4891
|
+
if (type.kind === "ModelProperty") {
|
|
4892
|
+
for (let property = type; property; property = property.sourceProperty) {
|
|
4893
|
+
const docComment = getDocCommentForSymbol(getTemplateAccessSymbolSource(property));
|
|
4894
|
+
if (docComment) {
|
|
4895
|
+
return docComment;
|
|
4896
|
+
}
|
|
4897
|
+
}
|
|
4898
|
+
return undefined;
|
|
4899
|
+
}
|
|
4900
|
+
return getDocCommentForSymbol(getTemplateAccessSymbolSource(type));
|
|
4901
|
+
}
|
|
4128
4902
|
function checkDefaultValue(ctx, defaultNode, type) {
|
|
4129
4903
|
if (isErrorType(type)) {
|
|
4130
4904
|
// if the prop type is an error we don't need to validate again.
|
|
@@ -5196,7 +5970,7 @@ export function createChecker(program, resolver) {
|
|
|
5196
5970
|
function cloneTypeForSymbol(sym, type, additionalProps = {}) {
|
|
5197
5971
|
let clone = initializeClone(type, additionalProps);
|
|
5198
5972
|
if ("decorators" in clone) {
|
|
5199
|
-
const docComment =
|
|
5973
|
+
const docComment = getDocCommentForSymbol(sym) ?? getDocCommentForType(type);
|
|
5200
5974
|
if (docComment) {
|
|
5201
5975
|
clone.decorators.push(createDocFromCommentDecorator("self", docComment));
|
|
5202
5976
|
}
|