@typespec/compiler 1.13.0-dev.0 → 1.13.0-dev.10
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 +971 -62
- 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 +76 -7
- package/dist/src/core/messages.d.ts.map +1 -1
- package/dist/src/core/messages.js +26 -1
- package/dist/src/core/messages.js.map +1 -1
- package/dist/src/core/modifiers.d.ts.map +1 -1
- package/dist/src/core/modifiers.js +0 -11
- package/dist/src/core/modifiers.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/completion.js +14 -7
- package/dist/src/server/completion.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/fatal-error.d.ts +5 -0
- package/dist/src/server/fatal-error.d.ts.map +1 -0
- package/dist/src/server/fatal-error.js +24 -0
- package/dist/src/server/fatal-error.js.map +1 -0
- package/dist/src/server/server.js +4 -6
- package/dist/src/server/server.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/lib/prototypes.tsp +0 -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";
|
|
@@ -154,6 +154,7 @@ export function createChecker(program, resolver) {
|
|
|
154
154
|
* Key is the SymId of a node. It can be retrieved with getNodeSymId(node)
|
|
155
155
|
*/
|
|
156
156
|
const pendingResolutions = new PendingResolutions();
|
|
157
|
+
const spreadResolutionAncestors = new Map();
|
|
157
158
|
const postCheckValidators = [];
|
|
158
159
|
const typespecNamespaceBinding = resolver.symbols.global.exports.get("TypeSpec");
|
|
159
160
|
if (typespecNamespaceBinding) {
|
|
@@ -163,6 +164,10 @@ export function createChecker(program, resolver) {
|
|
|
163
164
|
* Tracking the template parameters used or not.
|
|
164
165
|
*/
|
|
165
166
|
const templateParameterUsageMap = new Map();
|
|
167
|
+
const templateAccessSymbolCache = new Map();
|
|
168
|
+
const templateAccessCacheKeys = new WeakMap();
|
|
169
|
+
const symbolCacheIds = new WeakMap();
|
|
170
|
+
let nextSymbolCacheId = 1;
|
|
166
171
|
const checker = {
|
|
167
172
|
getTypeForNode,
|
|
168
173
|
checkProgram,
|
|
@@ -344,7 +349,7 @@ export function createChecker(program, resolver) {
|
|
|
344
349
|
}));
|
|
345
350
|
return errorType;
|
|
346
351
|
}
|
|
347
|
-
if (entity.kind === "TemplateParameter") {
|
|
352
|
+
if (entity.kind === "TemplateParameter" || entity.kind === "TemplateParameterAccess") {
|
|
348
353
|
if (entity.constraint?.valueType) {
|
|
349
354
|
// means this template constraint will accept values
|
|
350
355
|
reportCheckerDiagnostic(createDiagnostic({
|
|
@@ -380,7 +385,7 @@ export function createChecker(program, resolver) {
|
|
|
380
385
|
// If a template parameter that can be a value is used where a value is expected,
|
|
381
386
|
// synthesize a template value placeholder even when the template parameter is mapped
|
|
382
387
|
// from an outer template declaration.
|
|
383
|
-
if (entity.kind === "TemplateParameter" &&
|
|
388
|
+
if ((entity.kind === "TemplateParameter" || entity.kind === "TemplateParameterAccess") &&
|
|
384
389
|
entity.constraint?.valueType &&
|
|
385
390
|
entity.constraint.type === undefined) {
|
|
386
391
|
// We must also observe that the template parameter is used here.
|
|
@@ -706,7 +711,12 @@ export function createChecker(program, resolver) {
|
|
|
706
711
|
function visit(node) {
|
|
707
712
|
const entity = checkNode(ctx, node);
|
|
708
713
|
let hasError = false;
|
|
709
|
-
if (entity !== null &&
|
|
714
|
+
if (entity !== null &&
|
|
715
|
+
"kind" in entity &&
|
|
716
|
+
(entity.kind === "TemplateParameter" || entity.kind === "TemplateParameterAccess")) {
|
|
717
|
+
if (entity.kind === "TemplateParameterAccess") {
|
|
718
|
+
return entity;
|
|
719
|
+
}
|
|
710
720
|
for (let i = index; i < templateParameters.length; i++) {
|
|
711
721
|
if (entity.node?.symbol === templateParameters[i].symbol) {
|
|
712
722
|
reportCheckerDiagnostic(createDiagnostic({ code: "invalid-template-default", target: node }));
|
|
@@ -1514,7 +1524,7 @@ export function createChecker(program, resolver) {
|
|
|
1514
1524
|
const indexers = [];
|
|
1515
1525
|
const modelOptions = options.filter((entry) => {
|
|
1516
1526
|
const [optionNode, option] = entry;
|
|
1517
|
-
if (option.kind === "TemplateParameter") {
|
|
1527
|
+
if (option.kind === "TemplateParameter" || option.kind === "TemplateParameterAccess") {
|
|
1518
1528
|
return false;
|
|
1519
1529
|
}
|
|
1520
1530
|
if (option.kind !== "Model") {
|
|
@@ -1726,6 +1736,10 @@ export function createChecker(program, resolver) {
|
|
|
1726
1736
|
const doc = paramDocs.get(name);
|
|
1727
1737
|
if (doc) {
|
|
1728
1738
|
docFromCommentForSym.set(memberSym, doc);
|
|
1739
|
+
const declarationSymbol = memberSym.node?.symbol;
|
|
1740
|
+
if (declarationSymbol && declarationSymbol !== memberSym) {
|
|
1741
|
+
docFromCommentForSym.set(declarationSymbol, doc);
|
|
1742
|
+
}
|
|
1729
1743
|
}
|
|
1730
1744
|
}
|
|
1731
1745
|
}
|
|
@@ -1896,15 +1910,24 @@ export function createChecker(program, resolver) {
|
|
|
1896
1910
|
compilerAssert(false, "Unreachable");
|
|
1897
1911
|
}
|
|
1898
1912
|
if (sym) {
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
else {
|
|
1903
|
-
return [sym];
|
|
1913
|
+
const relatedSource = getRelatedSymbolSource(sym);
|
|
1914
|
+
if (relatedSource) {
|
|
1915
|
+
return sym.flags & 4194304 /* SymbolFlags.LateBound */ ? [sym, relatedSource] : [relatedSource];
|
|
1904
1916
|
}
|
|
1917
|
+
return [sym];
|
|
1905
1918
|
}
|
|
1906
1919
|
return undefined; //sym?.symbolSource ?? sym;
|
|
1907
1920
|
}
|
|
1921
|
+
function getRelatedSymbolSource(sym) {
|
|
1922
|
+
if (sym.symbolSource) {
|
|
1923
|
+
return sym.symbolSource;
|
|
1924
|
+
}
|
|
1925
|
+
const type = sym.type;
|
|
1926
|
+
if (type && isType(type)) {
|
|
1927
|
+
return getTemplateAccessSymbolSource(type);
|
|
1928
|
+
}
|
|
1929
|
+
return undefined;
|
|
1930
|
+
}
|
|
1908
1931
|
function getTemplateDeclarationsForArgument(ctx, node) {
|
|
1909
1932
|
const ref = node.parent;
|
|
1910
1933
|
let resolved = resolveTypeReferenceSym(ctx, ref, false);
|
|
@@ -2186,32 +2209,43 @@ export function createChecker(program, resolver) {
|
|
|
2186
2209
|
}
|
|
2187
2210
|
}
|
|
2188
2211
|
else if (identifier.parent && identifier.parent.kind === SyntaxKind.MemberExpression) {
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2212
|
+
const memberExpression = identifier.parent;
|
|
2213
|
+
let baseType = getCompletionBaseType(memberExpression.base);
|
|
2214
|
+
let base = resolver.getNodeLinks(memberExpression.base).resolvedSymbol;
|
|
2215
|
+
if (base && base.flags & 128 /* SymbolFlags.Alias */) {
|
|
2216
|
+
base = getAliasedSymbol(CheckContext.DEFAULT, base);
|
|
2217
|
+
}
|
|
2218
|
+
if (!baseType && base) {
|
|
2219
|
+
baseType = getCompletionBaseTypeFromSymbol(base);
|
|
2220
|
+
}
|
|
2221
|
+
if (baseType && (!base || !!(base.flags & 1024 /* SymbolFlags.TemplateParameter */))) {
|
|
2222
|
+
if (memberExpression.selector === "::") {
|
|
2223
|
+
addMetaCompletionsForType(baseType);
|
|
2193
2224
|
}
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
const metaProperty = base.node;
|
|
2208
|
-
addCompletion("type", metaProperty.symbol);
|
|
2225
|
+
else {
|
|
2226
|
+
addMemberCompletionsForType(baseType);
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
if (base) {
|
|
2230
|
+
if (memberExpression.selector === "::") {
|
|
2231
|
+
if (base?.node === undefined && base?.declarations && base.declarations.length > 0) {
|
|
2232
|
+
// Process meta properties separately, such as `::parameters`, `::returnType`
|
|
2233
|
+
const nodeModels = base?.declarations[0];
|
|
2234
|
+
if (nodeModels.kind === SyntaxKind.OperationStatement) {
|
|
2235
|
+
const operation = nodeModels;
|
|
2236
|
+
addCompletion("parameters", operation.symbol);
|
|
2237
|
+
addCompletion("returnType", operation.symbol);
|
|
2209
2238
|
}
|
|
2210
2239
|
}
|
|
2211
|
-
else {
|
|
2212
|
-
|
|
2240
|
+
else if (base?.node?.kind === SyntaxKind.ModelProperty) {
|
|
2241
|
+
// Process meta properties separately, such as `::type`
|
|
2242
|
+
const metaProperty = base.node;
|
|
2243
|
+
addCompletion("type", metaProperty.symbol);
|
|
2213
2244
|
}
|
|
2214
2245
|
}
|
|
2246
|
+
else {
|
|
2247
|
+
addCompletions(base.exports ?? base.members);
|
|
2248
|
+
}
|
|
2215
2249
|
}
|
|
2216
2250
|
}
|
|
2217
2251
|
else {
|
|
@@ -2252,6 +2286,101 @@ export function createChecker(program, resolver) {
|
|
|
2252
2286
|
}
|
|
2253
2287
|
}
|
|
2254
2288
|
return completions;
|
|
2289
|
+
/** Resolve a usable base type for member/meta-member completions. */
|
|
2290
|
+
function getCompletionBaseType(base) {
|
|
2291
|
+
const entity = getTypeOrValueForNode(base, CheckContext.DEFAULT);
|
|
2292
|
+
if (!entity || !isType(entity) || isErrorType(entity)) {
|
|
2293
|
+
if (base.kind === SyntaxKind.Identifier) {
|
|
2294
|
+
const scopedTemplateParameter = getTemplateParameterTypeFromScope(base);
|
|
2295
|
+
if (scopedTemplateParameter) {
|
|
2296
|
+
return resolveTemplateConstraintType(scopedTemplateParameter);
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
const templateBase = probeTemplateAccessBaseEntity(base);
|
|
2300
|
+
return templateBase ? resolveTemplateConstraintType(templateBase) : undefined;
|
|
2301
|
+
}
|
|
2302
|
+
if (isTemplateAccessType(entity)) {
|
|
2303
|
+
return resolveTemplateConstraintType(entity);
|
|
2304
|
+
}
|
|
2305
|
+
return entity;
|
|
2306
|
+
}
|
|
2307
|
+
/** Resolve a completion base type from a symbol when node-based typing is unavailable. */
|
|
2308
|
+
function getCompletionBaseTypeFromSymbol(base) {
|
|
2309
|
+
if (base.flags & 4194304 /* SymbolFlags.LateBound */) {
|
|
2310
|
+
const lateBoundType = base.type;
|
|
2311
|
+
return lateBoundType && isType(lateBoundType)
|
|
2312
|
+
? resolveCompletionType(lateBoundType)
|
|
2313
|
+
: undefined;
|
|
2314
|
+
}
|
|
2315
|
+
if (base.flags & 1024 /* SymbolFlags.TemplateParameter */) {
|
|
2316
|
+
const mapped = checkTemplateParameterDeclaration(CheckContext.DEFAULT, getSymNode(base));
|
|
2317
|
+
return isType(mapped) ? resolveCompletionType(mapped) : undefined;
|
|
2318
|
+
}
|
|
2319
|
+
return undefined;
|
|
2320
|
+
}
|
|
2321
|
+
/** Normalize template access types to their effective constraint for completion. */
|
|
2322
|
+
function resolveCompletionType(type) {
|
|
2323
|
+
return isTemplateAccessType(type) ? resolveTemplateConstraintType(type) : type;
|
|
2324
|
+
}
|
|
2325
|
+
/** Add member completions based on the resolved base type kind. */
|
|
2326
|
+
function addMemberCompletionsForType(baseType) {
|
|
2327
|
+
switch (baseType.kind) {
|
|
2328
|
+
case "Model":
|
|
2329
|
+
for (const property of walkPropertiesInherited(baseType)) {
|
|
2330
|
+
const ownerSymbol = baseType.node?.symbol;
|
|
2331
|
+
const propertySymbol = (ownerSymbol ? getMemberSymbol(ownerSymbol, property.name) : undefined) ??
|
|
2332
|
+
property.node?.symbol;
|
|
2333
|
+
if (propertySymbol) {
|
|
2334
|
+
addCompletion(property.name, propertySymbol);
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
return;
|
|
2338
|
+
case "Interface":
|
|
2339
|
+
for (const [name, operation] of baseType.operations) {
|
|
2340
|
+
const operationSymbol = operation.node?.symbol;
|
|
2341
|
+
if (operationSymbol) {
|
|
2342
|
+
addCompletion(name, operationSymbol);
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
return;
|
|
2346
|
+
case "Enum":
|
|
2347
|
+
for (const [name, member] of baseType.members) {
|
|
2348
|
+
const enumMemberSymbol = member.node?.symbol;
|
|
2349
|
+
if (enumMemberSymbol) {
|
|
2350
|
+
addCompletion(name, enumMemberSymbol);
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
return;
|
|
2354
|
+
case "Union":
|
|
2355
|
+
for (const [name, variant] of baseType.variants) {
|
|
2356
|
+
if (typeof name === "string" && variant.node?.symbol) {
|
|
2357
|
+
addCompletion(name, variant.node.symbol);
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
return;
|
|
2361
|
+
case "Scalar":
|
|
2362
|
+
for (const [name, constructor] of baseType.constructors) {
|
|
2363
|
+
const constructorSymbol = constructor.node?.symbol;
|
|
2364
|
+
if (constructorSymbol) {
|
|
2365
|
+
addCompletion(name, constructorSymbol);
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
return;
|
|
2369
|
+
case "Namespace":
|
|
2370
|
+
addCompletions(baseType.node?.symbol?.exports);
|
|
2371
|
+
return;
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
/** Add `::` meta-member completions for the resolved base type. */
|
|
2375
|
+
function addMetaCompletionsForType(baseType) {
|
|
2376
|
+
const baseSymbol = getTypeSymbol(baseType);
|
|
2377
|
+
if (!baseSymbol) {
|
|
2378
|
+
return;
|
|
2379
|
+
}
|
|
2380
|
+
for (const metaMemberName of resolver.getMetaMemberNames(baseSymbol)) {
|
|
2381
|
+
addCompletion(metaMemberName, baseSymbol);
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2255
2384
|
function addCompletions(table) {
|
|
2256
2385
|
if (!table) {
|
|
2257
2386
|
return;
|
|
@@ -2273,7 +2402,7 @@ export function createChecker(program, resolver) {
|
|
|
2273
2402
|
}
|
|
2274
2403
|
}
|
|
2275
2404
|
function addCompletion(key, sym, options = {}) {
|
|
2276
|
-
if (sym.symbolSource) {
|
|
2405
|
+
if (sym.symbolSource && !(sym.flags & 4194304 /* SymbolFlags.LateBound */)) {
|
|
2277
2406
|
sym = sym.symbolSource;
|
|
2278
2407
|
}
|
|
2279
2408
|
if (!shouldAddCompletion(sym)) {
|
|
@@ -2330,7 +2459,7 @@ export function createChecker(program, resolver) {
|
|
|
2330
2459
|
}
|
|
2331
2460
|
resolvedOptions.locationContext ??= getLocationContext(program, node);
|
|
2332
2461
|
const sym = resolveTypeReferenceSymInternal(ctx, node, resolvedOptions);
|
|
2333
|
-
if (!resolvedOptions.resolveDeclarationOfTemplate) {
|
|
2462
|
+
if (ctx.mapper === undefined && !resolvedOptions.resolveDeclarationOfTemplate) {
|
|
2334
2463
|
referenceSymCache.set(node, sym);
|
|
2335
2464
|
}
|
|
2336
2465
|
return sym;
|
|
@@ -2373,6 +2502,10 @@ export function createChecker(program, resolver) {
|
|
|
2373
2502
|
if (!base) {
|
|
2374
2503
|
return undefined;
|
|
2375
2504
|
}
|
|
2505
|
+
const directTemplateAccessSym = tryResolveTemplateAccessSymbol(ctx, node, base);
|
|
2506
|
+
if (directTemplateAccessSym) {
|
|
2507
|
+
return directTemplateAccessSym;
|
|
2508
|
+
}
|
|
2376
2509
|
// when resolving a type reference based on an alias, unwrap the alias.
|
|
2377
2510
|
if (base.flags & 128 /* SymbolFlags.Alias */) {
|
|
2378
2511
|
if (!options.resolveDeclarationOfTemplate && isTemplatedNode(getSymNode(base))) {
|
|
@@ -2419,12 +2552,498 @@ export function createChecker(program, resolver) {
|
|
|
2419
2552
|
}
|
|
2420
2553
|
base = baseSym;
|
|
2421
2554
|
}
|
|
2422
|
-
const
|
|
2555
|
+
const templateAccessSym = tryResolveTemplateAccessSymbol(ctx, node, base);
|
|
2556
|
+
if (templateAccessSym) {
|
|
2557
|
+
return templateAccessSym;
|
|
2558
|
+
}
|
|
2559
|
+
const sym = resolveMemberInContainer(ctx, base, node, options);
|
|
2423
2560
|
checkSymbolAccess(options.locationContext, node, sym);
|
|
2424
2561
|
return sym;
|
|
2425
2562
|
}
|
|
2426
2563
|
compilerAssert(false, `Unknown type reference kind "${SyntaxKind[node.kind]}"`, node);
|
|
2427
2564
|
}
|
|
2565
|
+
/**
|
|
2566
|
+
* Resolve member/meta-member access rooted in a template parameter or template access chain.
|
|
2567
|
+
* Falls back to late-bound symbols when the concrete symbol cannot be safely determined.
|
|
2568
|
+
*
|
|
2569
|
+
* @param ctx Check context for mapper and usage observation.
|
|
2570
|
+
* @param node Member expression being resolved.
|
|
2571
|
+
* @param baseSym Resolved symbol for the member expression base.
|
|
2572
|
+
* @returns The resolved symbol for the template access, or `undefined` when not applicable.
|
|
2573
|
+
*/
|
|
2574
|
+
function tryResolveTemplateAccessSymbol(ctx, node, baseSym) {
|
|
2575
|
+
const mappedSymbol = tryResolveMappedTemplateAccessSymbol(ctx, node, baseSym);
|
|
2576
|
+
if (mappedSymbol) {
|
|
2577
|
+
return mappedSymbol;
|
|
2578
|
+
}
|
|
2579
|
+
const baseEntity = getTemplateAccessBaseEntity(ctx, node.base, baseSym);
|
|
2580
|
+
if (!baseEntity) {
|
|
2581
|
+
return undefined;
|
|
2582
|
+
}
|
|
2583
|
+
observeTemplateAccessBase(ctx, baseEntity);
|
|
2584
|
+
const accessedType = resolveTemplateAccessType(ctx, node, baseEntity);
|
|
2585
|
+
const useCache = ctx.mapper === undefined;
|
|
2586
|
+
if (isErrorType(accessedType)) {
|
|
2587
|
+
return createTemplateAccessSymbol(baseEntity, node, errorType, useCache);
|
|
2588
|
+
}
|
|
2589
|
+
if (node.selector === "." &&
|
|
2590
|
+
baseEntity.kind === "TemplateParameterAccess" &&
|
|
2591
|
+
baseEntity.node.selector === "::") {
|
|
2592
|
+
return getPreferredTemplateAccessSymbol(node, accessedType);
|
|
2593
|
+
}
|
|
2594
|
+
if (ctx.mapper !== undefined) {
|
|
2595
|
+
return getPreferredTemplateAccessSymbol(node, accessedType);
|
|
2596
|
+
}
|
|
2597
|
+
return createTemplateAccessSymbol(baseEntity, node, accessedType, useCache);
|
|
2598
|
+
}
|
|
2599
|
+
/**
|
|
2600
|
+
* Resolve template access directly from a mapped template argument when available.
|
|
2601
|
+
*
|
|
2602
|
+
* @param ctx Check context containing the active template mapper.
|
|
2603
|
+
* @param node Member expression being resolved.
|
|
2604
|
+
* @param baseSym Base symbol for the access expression.
|
|
2605
|
+
* @returns A concrete or late-bound symbol for the mapped access, or `undefined`.
|
|
2606
|
+
*/
|
|
2607
|
+
function tryResolveMappedTemplateAccessSymbol(ctx, node, baseSym) {
|
|
2608
|
+
if (!ctx.mapper || !(baseSym.flags & 1024 /* SymbolFlags.TemplateParameter */)) {
|
|
2609
|
+
return undefined;
|
|
2610
|
+
}
|
|
2611
|
+
const declared = checkTemplateParameterDeclaration(CheckContext.DEFAULT, getSymNode(baseSym));
|
|
2612
|
+
if (!isType(declared) || declared.kind !== "TemplateParameter") {
|
|
2613
|
+
return undefined;
|
|
2614
|
+
}
|
|
2615
|
+
const mapped = ctx.mapper.getMappedType(declared);
|
|
2616
|
+
if (!isType(mapped) || isTemplateAccessType(mapped)) {
|
|
2617
|
+
return undefined;
|
|
2618
|
+
}
|
|
2619
|
+
if (isUninstantiatedTemplateType(mapped)) {
|
|
2620
|
+
return undefined;
|
|
2621
|
+
}
|
|
2622
|
+
const resolvedType = node.selector === "."
|
|
2623
|
+
? resolveMemberTypeFromConstraint(mapped, node.id.sv)
|
|
2624
|
+
: resolveMetaTypeFromConstraint(ctx, mapped, node);
|
|
2625
|
+
if (!resolvedType) {
|
|
2626
|
+
return undefined;
|
|
2627
|
+
}
|
|
2628
|
+
return getPreferredTemplateAccessSymbol(node, resolvedType);
|
|
2629
|
+
}
|
|
2630
|
+
/** Return true when a type declaration is templated but has not been instantiated. */
|
|
2631
|
+
function isUninstantiatedTemplateType(type) {
|
|
2632
|
+
if ("templateMapper" in type && type.templateMapper !== undefined) {
|
|
2633
|
+
return false;
|
|
2634
|
+
}
|
|
2635
|
+
const node = type.node;
|
|
2636
|
+
return Boolean(node && "templateParameters" in node && node.templateParameters.length > 0);
|
|
2637
|
+
}
|
|
2638
|
+
/** Return true when the resolved type should remain late-bound due to templating. */
|
|
2639
|
+
function shouldUseLateBoundTemplateAccessType(type) {
|
|
2640
|
+
return "templateMapper" in type && type.templateMapper !== undefined;
|
|
2641
|
+
}
|
|
2642
|
+
/**
|
|
2643
|
+
* Prefer canonical member symbols for resolved template access results when they already exist.
|
|
2644
|
+
* This preserves symbol identity for downstream cloning and doc lookups in instantiated flows.
|
|
2645
|
+
*/
|
|
2646
|
+
function getPreferredTemplateAccessSymbol(node, type) {
|
|
2647
|
+
const canonicalMemberSymbol = getCanonicalTemplateAccessMemberSymbol(type);
|
|
2648
|
+
if (canonicalMemberSymbol) {
|
|
2649
|
+
return canonicalMemberSymbol;
|
|
2650
|
+
}
|
|
2651
|
+
if (!shouldUseLateBoundTemplateAccessType(type)) {
|
|
2652
|
+
return getTypeSymbol(type) ?? createLateBoundTypeSymbol(node, type);
|
|
2653
|
+
}
|
|
2654
|
+
return createLateBoundTypeSymbol(node, type);
|
|
2655
|
+
}
|
|
2656
|
+
/** Return the exact instantiated member symbol for a resolved type when one exists. */
|
|
2657
|
+
function getCanonicalTemplateAccessMemberSymbol(type) {
|
|
2658
|
+
switch (type.kind) {
|
|
2659
|
+
case "ModelProperty":
|
|
2660
|
+
return getExactInstantiatedMemberSymbol(type.model?.symbol, type.name, type);
|
|
2661
|
+
case "Operation":
|
|
2662
|
+
return getExactInstantiatedMemberSymbol(type.interface?.symbol, type.name, type);
|
|
2663
|
+
case "EnumMember":
|
|
2664
|
+
return getExactInstantiatedMemberSymbol(type.enum?.symbol, type.name, type);
|
|
2665
|
+
case "UnionVariant":
|
|
2666
|
+
return getExactInstantiatedMemberSymbol(type.union?.symbol, type.name, type);
|
|
2667
|
+
case "ScalarConstructor":
|
|
2668
|
+
return getExactInstantiatedMemberSymbol(type.scalar?.symbol, type.name, type);
|
|
2669
|
+
default:
|
|
2670
|
+
return undefined;
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
function getExactInstantiatedMemberSymbol(containerSymbol, memberName, type) {
|
|
2674
|
+
if (!containerSymbol || typeof memberName !== "string") {
|
|
2675
|
+
return undefined;
|
|
2676
|
+
}
|
|
2677
|
+
const memberSymbol = getMemberSymbol(containerSymbol, memberName);
|
|
2678
|
+
return memberSymbol?.type === type ? memberSymbol : undefined;
|
|
2679
|
+
}
|
|
2680
|
+
/**
|
|
2681
|
+
* Resolve the template entity (parameter or access) that acts as the base for a member expression.
|
|
2682
|
+
*/
|
|
2683
|
+
function getTemplateAccessBaseEntity(_ctx, baseNode, baseSym) {
|
|
2684
|
+
if (baseSym.flags & 4194304 /* SymbolFlags.LateBound */) {
|
|
2685
|
+
const lateBoundType = baseSym.type;
|
|
2686
|
+
if (lateBoundType && lateBoundType.kind === "TemplateParameterAccess") {
|
|
2687
|
+
return lateBoundType;
|
|
2688
|
+
}
|
|
2689
|
+
return undefined;
|
|
2690
|
+
}
|
|
2691
|
+
if (baseSym.flags & 1024 /* SymbolFlags.TemplateParameter */) {
|
|
2692
|
+
const baseSymbolNode = getSymNode(baseSym);
|
|
2693
|
+
const mapped = checkTemplateParameterDeclaration(CheckContext.DEFAULT, baseSymbolNode);
|
|
2694
|
+
return isType(mapped) && isTemplateAccessType(mapped) ? mapped : undefined;
|
|
2695
|
+
}
|
|
2696
|
+
if (baseNode.kind === SyntaxKind.Identifier) {
|
|
2697
|
+
const templateParameterType = getTemplateParameterTypeFromScope(baseNode);
|
|
2698
|
+
if (templateParameterType) {
|
|
2699
|
+
return templateParameterType;
|
|
2700
|
+
}
|
|
2701
|
+
return undefined;
|
|
2702
|
+
}
|
|
2703
|
+
return undefined;
|
|
2704
|
+
}
|
|
2705
|
+
/** Probe a node for a template access base without surfacing diagnostics. */
|
|
2706
|
+
function probeTemplateAccessBaseEntity(node) {
|
|
2707
|
+
const oldDiagnosticHook = onCheckerDiagnostic;
|
|
2708
|
+
onCheckerDiagnostic = () => { };
|
|
2709
|
+
const entity = checkTypeOrValueReference(CheckContext.DEFAULT, node, false);
|
|
2710
|
+
onCheckerDiagnostic = oldDiagnosticHook;
|
|
2711
|
+
return isType(entity) && isTemplateAccessType(entity) ? entity : undefined;
|
|
2712
|
+
}
|
|
2713
|
+
/** Resolve a template parameter type from lexical scope by identifier name. */
|
|
2714
|
+
function getTemplateParameterTypeFromScope(identifier) {
|
|
2715
|
+
const declaration = findTemplateParameterDeclarationInScope(identifier, identifier.sv);
|
|
2716
|
+
if (!declaration) {
|
|
2717
|
+
return undefined;
|
|
2718
|
+
}
|
|
2719
|
+
const mapped = checkTemplateParameterDeclaration(CheckContext.DEFAULT, declaration);
|
|
2720
|
+
return isType(mapped) && isTemplateAccessType(mapped) ? mapped : undefined;
|
|
2721
|
+
}
|
|
2722
|
+
/** Find the closest template parameter declaration matching the given name. */
|
|
2723
|
+
function findTemplateParameterDeclarationInScope(node, name) {
|
|
2724
|
+
let current = node.parent;
|
|
2725
|
+
while (current) {
|
|
2726
|
+
if ("templateParameters" in current && current.templateParameters) {
|
|
2727
|
+
const declaration = current.templateParameters.find((x) => x.id.sv === name);
|
|
2728
|
+
if (declaration) {
|
|
2729
|
+
return declaration;
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
current = current.parent;
|
|
2733
|
+
}
|
|
2734
|
+
return undefined;
|
|
2735
|
+
}
|
|
2736
|
+
/**
|
|
2737
|
+
* Resolve the resulting type for a template parameter access expression.
|
|
2738
|
+
*
|
|
2739
|
+
* @param ctx Check context for mapper-aware resolution.
|
|
2740
|
+
* @param node Access expression (`.` or `::`) being resolved.
|
|
2741
|
+
* @param baseEntity Template parameter or prior template access chain.
|
|
2742
|
+
* @returns The resolved member/meta-member type, or `errorType` when not guaranteed.
|
|
2743
|
+
*/
|
|
2744
|
+
function resolveTemplateAccessType(ctx, node, baseEntity) {
|
|
2745
|
+
const baseType = resolveTemplateAccessBaseType(ctx, baseEntity);
|
|
2746
|
+
if (!baseType) {
|
|
2747
|
+
if (hasErrorTemplateConstraint(baseEntity)) {
|
|
2748
|
+
return errorType;
|
|
2749
|
+
}
|
|
2750
|
+
reportTemplateAccessNotGuaranteed(node, baseEntity);
|
|
2751
|
+
return errorType;
|
|
2752
|
+
}
|
|
2753
|
+
const resolvedType = node.selector === "."
|
|
2754
|
+
? resolveMemberTypeFromConstraint(baseType, node.id.sv)
|
|
2755
|
+
: resolveMetaTypeFromConstraint(ctx, baseType, node);
|
|
2756
|
+
if (!resolvedType) {
|
|
2757
|
+
reportTemplateAccessNotGuaranteed(node, baseType);
|
|
2758
|
+
return errorType;
|
|
2759
|
+
}
|
|
2760
|
+
return resolvedType;
|
|
2761
|
+
}
|
|
2762
|
+
/**
|
|
2763
|
+
* Resolve the concrete base type that a template access should evaluate against.
|
|
2764
|
+
*
|
|
2765
|
+
* @param ctx Check context containing optional template mapper.
|
|
2766
|
+
* @param baseEntity Template parameter/access entity.
|
|
2767
|
+
* @returns The mapped or constrained base type, if determinable.
|
|
2768
|
+
*/
|
|
2769
|
+
function resolveTemplateAccessBaseType(ctx, baseEntity) {
|
|
2770
|
+
if (ctx.mapper && baseEntity.kind === "TemplateParameterAccess") {
|
|
2771
|
+
const mappedBaseType = resolveTemplateAccessBaseType(ctx, baseEntity.base);
|
|
2772
|
+
if (!mappedBaseType) {
|
|
2773
|
+
return undefined;
|
|
2774
|
+
}
|
|
2775
|
+
return baseEntity.node.selector === "."
|
|
2776
|
+
? resolveMemberTypeFromConstraint(mappedBaseType, baseEntity.node.id.sv)
|
|
2777
|
+
: resolveMetaTypeFromConstraint(ctx, mappedBaseType, baseEntity.node);
|
|
2778
|
+
}
|
|
2779
|
+
if (ctx.mapper && baseEntity.kind === "TemplateParameter") {
|
|
2780
|
+
const mapped = ctx.mapper.getMappedType(baseEntity);
|
|
2781
|
+
if (isType(mapped)) {
|
|
2782
|
+
if (isTemplateAccessType(mapped)) {
|
|
2783
|
+
return resolveTemplateConstraintType(mapped);
|
|
2784
|
+
}
|
|
2785
|
+
if (isUninstantiatedTemplateType(mapped)) {
|
|
2786
|
+
return resolveTemplateConstraintType(baseEntity);
|
|
2787
|
+
}
|
|
2788
|
+
return mapped;
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
return resolveTemplateConstraintType(baseEntity);
|
|
2792
|
+
}
|
|
2793
|
+
/**
|
|
2794
|
+
* Resolve the terminal non-template constraint type for a template access chain.
|
|
2795
|
+
*
|
|
2796
|
+
* @param templateType Template parameter or access node.
|
|
2797
|
+
* @returns The terminal constrained type, or `undefined` when missing/invalid/cyclic.
|
|
2798
|
+
*/
|
|
2799
|
+
function resolveTemplateConstraintType(templateType) {
|
|
2800
|
+
const visited = new Set();
|
|
2801
|
+
let current = templateType;
|
|
2802
|
+
while (true) {
|
|
2803
|
+
if (visited.has(current)) {
|
|
2804
|
+
return undefined;
|
|
2805
|
+
}
|
|
2806
|
+
visited.add(current);
|
|
2807
|
+
const constraintType = current.constraint?.type;
|
|
2808
|
+
if (!constraintType || isErrorType(constraintType)) {
|
|
2809
|
+
return undefined;
|
|
2810
|
+
}
|
|
2811
|
+
if (!isTemplateAccessType(constraintType)) {
|
|
2812
|
+
return constraintType;
|
|
2813
|
+
}
|
|
2814
|
+
current = constraintType;
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
/** Return true when a template access chain includes an error constraint. */
|
|
2818
|
+
function hasErrorTemplateConstraint(templateType) {
|
|
2819
|
+
let current = templateType;
|
|
2820
|
+
while (true) {
|
|
2821
|
+
const constraintType = current.constraint?.type;
|
|
2822
|
+
if (constraintType && isErrorType(constraintType)) {
|
|
2823
|
+
return true;
|
|
2824
|
+
}
|
|
2825
|
+
if (current.kind !== "TemplateParameterAccess") {
|
|
2826
|
+
return false;
|
|
2827
|
+
}
|
|
2828
|
+
current = current.base;
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
/** Track template parameter usage for a template access base chain. */
|
|
2832
|
+
function observeTemplateAccessBase(ctx, base) {
|
|
2833
|
+
const root = getTemplateAccessRoot(base);
|
|
2834
|
+
ctx.observeTemplateParameter(root);
|
|
2835
|
+
templateParameterUsageMap.set(root.node, true);
|
|
2836
|
+
}
|
|
2837
|
+
/** Return the root template parameter for a template access chain. */
|
|
2838
|
+
function getTemplateAccessRoot(base) {
|
|
2839
|
+
let current = base;
|
|
2840
|
+
while (current.kind === "TemplateParameterAccess") {
|
|
2841
|
+
current = current.base;
|
|
2842
|
+
}
|
|
2843
|
+
return current;
|
|
2844
|
+
}
|
|
2845
|
+
/** Resolve `.` access from a constrained type by kind-specific member lookup. */
|
|
2846
|
+
function resolveMemberTypeFromConstraint(constraintType, memberName) {
|
|
2847
|
+
const canonicalMemberType = getCanonicalResolvedMemberType(constraintType, memberName);
|
|
2848
|
+
if (canonicalMemberType) {
|
|
2849
|
+
return canonicalMemberType;
|
|
2850
|
+
}
|
|
2851
|
+
switch (constraintType.kind) {
|
|
2852
|
+
case "Model":
|
|
2853
|
+
for (const property of walkPropertiesInherited(constraintType)) {
|
|
2854
|
+
if (property.name === memberName) {
|
|
2855
|
+
return property;
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
return undefined;
|
|
2859
|
+
case "Interface":
|
|
2860
|
+
return constraintType.operations.get(memberName);
|
|
2861
|
+
case "Enum":
|
|
2862
|
+
return constraintType.members.get(memberName);
|
|
2863
|
+
case "Union":
|
|
2864
|
+
return constraintType.variants.get(memberName);
|
|
2865
|
+
case "Scalar":
|
|
2866
|
+
return constraintType.constructors.get(memberName);
|
|
2867
|
+
default:
|
|
2868
|
+
return undefined;
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
function getCanonicalResolvedMemberSymbol(containerType, memberName) {
|
|
2872
|
+
switch (containerType.kind) {
|
|
2873
|
+
case "Model":
|
|
2874
|
+
case "Interface":
|
|
2875
|
+
case "Enum":
|
|
2876
|
+
case "Union":
|
|
2877
|
+
case "Scalar":
|
|
2878
|
+
break;
|
|
2879
|
+
default:
|
|
2880
|
+
return undefined;
|
|
2881
|
+
}
|
|
2882
|
+
const containerSymbol = containerType.symbol;
|
|
2883
|
+
if (containerSymbol === undefined || !containerSymbol.members) {
|
|
2884
|
+
return undefined;
|
|
2885
|
+
}
|
|
2886
|
+
let memberSymbol = getMemberSymbol(containerSymbol, memberName);
|
|
2887
|
+
if (!memberSymbol?.type && containerSymbol.flags & 4194304 /* SymbolFlags.LateBound */) {
|
|
2888
|
+
lateBindMembers(containerType);
|
|
2889
|
+
memberSymbol = getMemberSymbol(containerSymbol, memberName);
|
|
2890
|
+
}
|
|
2891
|
+
return memberSymbol;
|
|
2892
|
+
}
|
|
2893
|
+
function getCanonicalResolvedMemberType(containerType, memberName) {
|
|
2894
|
+
const memberType = getCanonicalResolvedMemberSymbol(containerType, memberName)?.type;
|
|
2895
|
+
return memberType && isType(memberType) ? memberType : undefined;
|
|
2896
|
+
}
|
|
2897
|
+
/**
|
|
2898
|
+
* Resolve `::` meta-member access from a constrained type.
|
|
2899
|
+
*
|
|
2900
|
+
* @param ctx Check context used for symbol-to-entity evaluation.
|
|
2901
|
+
* @param constraintType Base constrained type.
|
|
2902
|
+
* @param node Meta-member expression node.
|
|
2903
|
+
* @returns The resolved meta-member type, `unknownType` for projection-only cases, or `undefined`.
|
|
2904
|
+
*/
|
|
2905
|
+
function resolveMetaTypeFromConstraint(ctx, constraintType, node) {
|
|
2906
|
+
if (constraintType.kind === "ModelProperty" && node.id.sv === "type") {
|
|
2907
|
+
return constraintType.type;
|
|
2908
|
+
}
|
|
2909
|
+
if (constraintType.kind === "Operation") {
|
|
2910
|
+
switch (node.id.sv) {
|
|
2911
|
+
case "parameters":
|
|
2912
|
+
return constraintType.parameters;
|
|
2913
|
+
case "returnType":
|
|
2914
|
+
return constraintType.returnType;
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
const constraintSymbol = getTypeSymbol(constraintType);
|
|
2918
|
+
if (!constraintSymbol) {
|
|
2919
|
+
return undefined;
|
|
2920
|
+
}
|
|
2921
|
+
const metaMemberNames = resolver.getMetaMemberNames(constraintSymbol);
|
|
2922
|
+
if (!metaMemberNames.includes(node.id.sv)) {
|
|
2923
|
+
return undefined;
|
|
2924
|
+
}
|
|
2925
|
+
if (isReflectionMetaProjectionSymbol(constraintSymbol)) {
|
|
2926
|
+
// Reflection model symbols expose meta-member names by projection, but their
|
|
2927
|
+
// underlying nodes are not concrete ModelProperty/Operation nodes.
|
|
2928
|
+
return unknownType;
|
|
2929
|
+
}
|
|
2930
|
+
const resolved = resolver.resolveMetaMemberByName(constraintSymbol, node.id.sv);
|
|
2931
|
+
if (resolved.resolutionResult & ResolutionResultFlags.Resolved && resolved.resolvedSymbol) {
|
|
2932
|
+
const entity = checkTypeOrValueReferenceSymbol(ctx, resolved.resolvedSymbol, node, false);
|
|
2933
|
+
if (entity === null) {
|
|
2934
|
+
return undefined;
|
|
2935
|
+
}
|
|
2936
|
+
if (entity.entityKind === "Indeterminate") {
|
|
2937
|
+
return entity.type;
|
|
2938
|
+
}
|
|
2939
|
+
return isType(entity) ? entity : undefined;
|
|
2940
|
+
}
|
|
2941
|
+
return unknownType;
|
|
2942
|
+
}
|
|
2943
|
+
/** Return true for TypeSpec.Reflection model symbols backed by projection metadata. */
|
|
2944
|
+
function isReflectionMetaProjectionSymbol(sym) {
|
|
2945
|
+
return (sym.node?.kind === SyntaxKind.ModelStatement &&
|
|
2946
|
+
sym.parent?.name === "Reflection" &&
|
|
2947
|
+
sym.parent?.parent?.name === "TypeSpec");
|
|
2948
|
+
}
|
|
2949
|
+
/** Report an invalid-ref diagnostic for unsupported template member/meta-member access. */
|
|
2950
|
+
function reportTemplateAccessNotGuaranteed(node, baseType) {
|
|
2951
|
+
reportCheckerDiagnostic(createDiagnostic({
|
|
2952
|
+
code: "invalid-ref",
|
|
2953
|
+
messageId: node.selector === "." ? "member" : "metaProperty",
|
|
2954
|
+
format: { kind: getTemplateAccessKindName(baseType), id: node.id.sv },
|
|
2955
|
+
target: node,
|
|
2956
|
+
}));
|
|
2957
|
+
}
|
|
2958
|
+
/** Get the diagnostic kind label used when template access resolution fails. */
|
|
2959
|
+
function getTemplateAccessKindName(type) {
|
|
2960
|
+
switch (type.kind) {
|
|
2961
|
+
case "Model":
|
|
2962
|
+
case "ModelProperty":
|
|
2963
|
+
case "Enum":
|
|
2964
|
+
case "Interface":
|
|
2965
|
+
case "Union":
|
|
2966
|
+
case "Operation":
|
|
2967
|
+
case "Scalar":
|
|
2968
|
+
case "TemplateParameter":
|
|
2969
|
+
case "TemplateParameterAccess":
|
|
2970
|
+
return type.kind;
|
|
2971
|
+
default:
|
|
2972
|
+
return "Type";
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
/** Type guard for template parameters and template parameter access types. */
|
|
2976
|
+
function isTemplateAccessType(type) {
|
|
2977
|
+
return type.kind === "TemplateParameter" || type.kind === "TemplateParameterAccess";
|
|
2978
|
+
}
|
|
2979
|
+
/**
|
|
2980
|
+
* Create (or retrieve from cache) a late-bound symbol representing template access.
|
|
2981
|
+
*
|
|
2982
|
+
* @param base Template parameter/access base.
|
|
2983
|
+
* @param node Member expression node.
|
|
2984
|
+
* @param constraintType Resolved constraint for the access result.
|
|
2985
|
+
* @param useCache Whether to reuse/access symbol cache.
|
|
2986
|
+
* @returns A symbol whose type is `TemplateParameterAccess`.
|
|
2987
|
+
*/
|
|
2988
|
+
function createTemplateAccessSymbol(base, node, constraintType, useCache = true) {
|
|
2989
|
+
const cacheKey = getTemplateAccessCacheKey(base, node);
|
|
2990
|
+
if (useCache) {
|
|
2991
|
+
const existing = templateAccessSymbolCache.get(cacheKey);
|
|
2992
|
+
if (existing) {
|
|
2993
|
+
return existing;
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
const constraint = {
|
|
2997
|
+
entityKind: "MixedParameterConstraint",
|
|
2998
|
+
node,
|
|
2999
|
+
type: constraintType,
|
|
3000
|
+
};
|
|
3001
|
+
const type = createAndFinishType({
|
|
3002
|
+
kind: "TemplateParameterAccess",
|
|
3003
|
+
node,
|
|
3004
|
+
base,
|
|
3005
|
+
path: printMemberExpressionPath(getTemplateAccessPath(base), node.selector, node.id.sv),
|
|
3006
|
+
constraint,
|
|
3007
|
+
});
|
|
3008
|
+
templateAccessCacheKeys.set(type, cacheKey);
|
|
3009
|
+
const symbol = createSymbol(node, node.id.sv, 4194304 /* SymbolFlags.LateBound */);
|
|
3010
|
+
mutate(symbol).type = type;
|
|
3011
|
+
if (useCache) {
|
|
3012
|
+
templateAccessSymbolCache.set(cacheKey, symbol);
|
|
3013
|
+
}
|
|
3014
|
+
return symbol;
|
|
3015
|
+
}
|
|
3016
|
+
/** Compute the user-facing access path for a template access chain. */
|
|
3017
|
+
function getTemplateAccessPath(base) {
|
|
3018
|
+
return base.kind === "TemplateParameterAccess" ? base.path : printIdentifier(base.node.id.sv);
|
|
3019
|
+
}
|
|
3020
|
+
/** Build a stable cache key for a template access symbol/type chain. */
|
|
3021
|
+
function getTemplateAccessCacheKey(base, node) {
|
|
3022
|
+
let baseKey;
|
|
3023
|
+
if (base.kind === "TemplateParameterAccess") {
|
|
3024
|
+
const cacheKey = templateAccessCacheKeys.get(base);
|
|
3025
|
+
compilerAssert(cacheKey, "Expected template access cache key");
|
|
3026
|
+
baseKey = cacheKey;
|
|
3027
|
+
}
|
|
3028
|
+
else {
|
|
3029
|
+
baseKey = `tp:${getSymbolCacheId(base.node.symbol)}`;
|
|
3030
|
+
}
|
|
3031
|
+
return `${baseKey}${node.selector}${node.id.sv}`;
|
|
3032
|
+
}
|
|
3033
|
+
/** Resolve the merged symbol associated with a type, when one exists. */
|
|
3034
|
+
function getTypeSymbol(type) {
|
|
3035
|
+
return type.node?.symbol ? getMergedSymbol(type.node.symbol) : undefined;
|
|
3036
|
+
}
|
|
3037
|
+
/** Get a stable numeric id for a symbol used in template access cache keys. */
|
|
3038
|
+
function getSymbolCacheId(sym) {
|
|
3039
|
+
const existing = symbolCacheIds.get(sym);
|
|
3040
|
+
if (existing !== undefined) {
|
|
3041
|
+
return existing;
|
|
3042
|
+
}
|
|
3043
|
+
const id = nextSymbolCacheId++;
|
|
3044
|
+
symbolCacheIds.set(sym, id);
|
|
3045
|
+
return id;
|
|
3046
|
+
}
|
|
2428
3047
|
function checkSymbolAccess(sourceLocation, node, symbol) {
|
|
2429
3048
|
if (!symbol)
|
|
2430
3049
|
return;
|
|
@@ -2468,12 +3087,25 @@ export function createChecker(program, resolver) {
|
|
|
2468
3087
|
target: node,
|
|
2469
3088
|
}));
|
|
2470
3089
|
}
|
|
2471
|
-
function resolveMemberInContainer(base, node, options) {
|
|
3090
|
+
function resolveMemberInContainer(ctx, base, node, options) {
|
|
3091
|
+
const symbolFromType = resolveMemberOnSymbolType(ctx, base, node);
|
|
3092
|
+
if (symbolFromType) {
|
|
3093
|
+
return symbolFromType;
|
|
3094
|
+
}
|
|
2472
3095
|
const { finalSymbol: sym, resolvedSymbol: nextSym } = resolver.resolveMemberExpressionForSym(base, node, options);
|
|
2473
3096
|
const symbol = nextSym ?? sym;
|
|
2474
3097
|
if (symbol) {
|
|
2475
3098
|
return symbol;
|
|
2476
3099
|
}
|
|
3100
|
+
// When the member wasn't found but the container has unknown members (e.g. from
|
|
3101
|
+
// template spreads or `is`), force-check the container type so that late-bound
|
|
3102
|
+
// members become available, then retry the lookup.
|
|
3103
|
+
if (node.selector === "." && base.flags & 118 /* SymbolFlags.MemberContainer */) {
|
|
3104
|
+
const resolvedMember = tryForceResolveLateBoundMember(ctx, base, node);
|
|
3105
|
+
if (resolvedMember) {
|
|
3106
|
+
return resolvedMember;
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
2477
3109
|
if (base.flags & 256 /* SymbolFlags.Namespace */) {
|
|
2478
3110
|
reportCheckerDiagnostic(createDiagnostic({
|
|
2479
3111
|
code: "invalid-ref",
|
|
@@ -2523,6 +3155,42 @@ export function createChecker(program, resolver) {
|
|
|
2523
3155
|
}
|
|
2524
3156
|
return undefined;
|
|
2525
3157
|
}
|
|
3158
|
+
/**
|
|
3159
|
+
* Attempt to resolve a late-bound member by force-checking the container type.
|
|
3160
|
+
* This handles the case where a model has `hasUnknownMembers` (e.g. from template
|
|
3161
|
+
* spreads or `is`) and the member only becomes known after the type is fully checked.
|
|
3162
|
+
*/
|
|
3163
|
+
function tryForceResolveLateBoundMember(ctx, base, node) {
|
|
3164
|
+
const links = getSymbolLinks(base);
|
|
3165
|
+
if (!links.hasUnknownMembers) {
|
|
3166
|
+
return undefined;
|
|
3167
|
+
}
|
|
3168
|
+
// Don't force-check if the container is currently being resolved (cycle).
|
|
3169
|
+
if (pendingResolutions.has(base, ResolutionKind.Type)) {
|
|
3170
|
+
return undefined;
|
|
3171
|
+
}
|
|
3172
|
+
// Force-check the container type to populate its members.
|
|
3173
|
+
pendingResolutions.start(base, ResolutionKind.Type);
|
|
3174
|
+
const type = checkTypeReferenceSymbol(ctx, base, node.base);
|
|
3175
|
+
pendingResolutions.finish(base, ResolutionKind.Type);
|
|
3176
|
+
if (isErrorType(type)) {
|
|
3177
|
+
return undefined;
|
|
3178
|
+
}
|
|
3179
|
+
// Late-bind the container and its members.
|
|
3180
|
+
switch (type.kind) {
|
|
3181
|
+
case "Model":
|
|
3182
|
+
case "Interface":
|
|
3183
|
+
case "Union":
|
|
3184
|
+
case "Enum":
|
|
3185
|
+
case "Scalar":
|
|
3186
|
+
lateBindMemberContainer(type);
|
|
3187
|
+
lateBindMembers(type);
|
|
3188
|
+
break;
|
|
3189
|
+
default:
|
|
3190
|
+
return undefined;
|
|
3191
|
+
}
|
|
3192
|
+
return getCanonicalResolvedMemberSymbol(type, node.id.sv);
|
|
3193
|
+
}
|
|
2526
3194
|
function getMemberKindName(node) {
|
|
2527
3195
|
switch (node.kind) {
|
|
2528
3196
|
case SyntaxKind.ModelStatement:
|
|
@@ -2540,6 +3208,100 @@ export function createChecker(program, resolver) {
|
|
|
2540
3208
|
return "Type";
|
|
2541
3209
|
}
|
|
2542
3210
|
}
|
|
3211
|
+
/**
|
|
3212
|
+
* Resolve a member/meta-member access from the base symbol's resolved type.
|
|
3213
|
+
*
|
|
3214
|
+
* @param ctx Check context.
|
|
3215
|
+
* @param base Base symbol.
|
|
3216
|
+
* @param node Member expression to resolve.
|
|
3217
|
+
* @returns Concrete symbol, late-bound symbol, or `undefined` when unresolved.
|
|
3218
|
+
*/
|
|
3219
|
+
function resolveMemberOnSymbolType(ctx, base, node) {
|
|
3220
|
+
const baseType = getMemberResolutionType(ctx, base);
|
|
3221
|
+
if (!baseType) {
|
|
3222
|
+
return undefined;
|
|
3223
|
+
}
|
|
3224
|
+
const resolvedBaseType = isTemplateAccessType(baseType)
|
|
3225
|
+
? resolveTemplateAccessBaseType(ctx, baseType)
|
|
3226
|
+
: baseType;
|
|
3227
|
+
if (!resolvedBaseType) {
|
|
3228
|
+
return undefined;
|
|
3229
|
+
}
|
|
3230
|
+
if (node.selector === ".") {
|
|
3231
|
+
const table = base.exports ?? base.members;
|
|
3232
|
+
if (table) {
|
|
3233
|
+
const directMember = resolver.getAugmentedSymbolTable(table).get(node.id.sv);
|
|
3234
|
+
if (directMember) {
|
|
3235
|
+
return directMember;
|
|
3236
|
+
}
|
|
3237
|
+
}
|
|
3238
|
+
const canonicalMember = getCanonicalResolvedMemberSymbol(resolvedBaseType, node.id.sv);
|
|
3239
|
+
if (canonicalMember) {
|
|
3240
|
+
return canonicalMember;
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
const resolvedType = node.selector === "."
|
|
3244
|
+
? resolveMemberTypeFromConstraint(resolvedBaseType, node.id.sv)
|
|
3245
|
+
: resolveMetaTypeFromConstraint(ctx, resolvedBaseType, node);
|
|
3246
|
+
if (!resolvedType) {
|
|
3247
|
+
return undefined;
|
|
3248
|
+
}
|
|
3249
|
+
if (node.selector === "::" &&
|
|
3250
|
+
node.id.sv === "type" &&
|
|
3251
|
+
resolvedType.kind === "TemplateParameterAccess" &&
|
|
3252
|
+
resolvedType.base.kind === "TemplateParameterAccess" &&
|
|
3253
|
+
resolvedType.base.node.selector === ".") {
|
|
3254
|
+
const sourceProperty = resolveTemplateConstraintType(resolvedType.base);
|
|
3255
|
+
if (sourceProperty) {
|
|
3256
|
+
return getTypeSymbol(sourceProperty) ?? createLateBoundTypeSymbol(node, sourceProperty);
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
return getPreferredTemplateAccessSymbol(node, resolvedType);
|
|
3260
|
+
}
|
|
3261
|
+
/** Resolve the effective type used for member lookup on a base symbol. */
|
|
3262
|
+
function getMemberResolutionType(_ctx, base) {
|
|
3263
|
+
if (base.flags & 4194304 /* SymbolFlags.LateBound */) {
|
|
3264
|
+
return base.type && isType(base.type) ? base.type : undefined;
|
|
3265
|
+
}
|
|
3266
|
+
if (base.flags & 65536 /* SymbolFlags.Member */) {
|
|
3267
|
+
return base.type && isType(base.type) ? base.type : undefined;
|
|
3268
|
+
}
|
|
3269
|
+
return undefined;
|
|
3270
|
+
}
|
|
3271
|
+
/** Create a late-bound symbol carrying a precomputed type for member resolution. */
|
|
3272
|
+
function createLateBoundTypeSymbol(node, type) {
|
|
3273
|
+
const symbol = createSymbol(node, node.id.sv, 4194304 /* SymbolFlags.LateBound */);
|
|
3274
|
+
mutate(symbol).type = type;
|
|
3275
|
+
const symbolSource = getTemplateAccessSymbolSource(type);
|
|
3276
|
+
if (symbolSource) {
|
|
3277
|
+
mutate(symbol).symbolSource = symbolSource;
|
|
3278
|
+
}
|
|
3279
|
+
return symbol;
|
|
3280
|
+
}
|
|
3281
|
+
/** Get the best source symbol to associate with a template-access-derived symbol. */
|
|
3282
|
+
function getTemplateAccessSymbolSource(type) {
|
|
3283
|
+
const symbolSource = type.kind === "ModelProperty"
|
|
3284
|
+
? getModelPropertySymbolSource(type)
|
|
3285
|
+
: (getCanonicalTemplateAccessMemberSymbol(type) ?? getTypeSymbol(type));
|
|
3286
|
+
return symbolSource?.symbolSource ?? symbolSource;
|
|
3287
|
+
}
|
|
3288
|
+
function getModelPropertySymbolSource(type) {
|
|
3289
|
+
let fallback;
|
|
3290
|
+
for (let property = type; property; property = property.sourceProperty) {
|
|
3291
|
+
const symbolSource = getCanonicalTemplateAccessMemberSymbol(property) ??
|
|
3292
|
+
(property.model?.symbol && typeof property.name === "string"
|
|
3293
|
+
? getMemberSymbol(property.model.symbol, property.name)
|
|
3294
|
+
: undefined) ??
|
|
3295
|
+
getTypeSymbol(property);
|
|
3296
|
+
if (symbolSource) {
|
|
3297
|
+
fallback ??= symbolSource;
|
|
3298
|
+
if (!property.sourceProperty) {
|
|
3299
|
+
return symbolSource;
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
}
|
|
3303
|
+
return fallback;
|
|
3304
|
+
}
|
|
2543
3305
|
/**
|
|
2544
3306
|
* Return the symbol that is aliased by this alias declaration. If no such symbol is aliased,
|
|
2545
3307
|
* return the symbol for the alias instead. For member containers which need to be late bound
|
|
@@ -2550,7 +3312,12 @@ export function createChecker(program, resolver) {
|
|
|
2550
3312
|
const node = getSymNode(aliasSymbol);
|
|
2551
3313
|
const links = resolver.getSymbolLinks(aliasSymbol);
|
|
2552
3314
|
if (!links.aliasResolutionIsTemplate) {
|
|
2553
|
-
|
|
3315
|
+
const aliased = links.aliasedSymbol ?? resolver.getNodeLinks(node).resolvedSymbol;
|
|
3316
|
+
if (aliased && isTemplatedNode(getSymNode(aliased))) {
|
|
3317
|
+
const aliasType = getTypeForNode(node, ctx);
|
|
3318
|
+
return lateBindContainer(aliasType, aliasSymbol);
|
|
3319
|
+
}
|
|
3320
|
+
return aliased;
|
|
2554
3321
|
}
|
|
2555
3322
|
// Otherwise for templates we need to get the type and retrieve the late bound symbol.
|
|
2556
3323
|
const aliasType = getTypeForNode(node, ctx);
|
|
@@ -2607,7 +3374,9 @@ export function createChecker(program, resolver) {
|
|
|
2607
3374
|
if (isValue(typeOrValue)) {
|
|
2608
3375
|
hasValue = true;
|
|
2609
3376
|
}
|
|
2610
|
-
else if ("kind" in typeOrValue &&
|
|
3377
|
+
else if ("kind" in typeOrValue &&
|
|
3378
|
+
(typeOrValue.kind === "TemplateParameter" ||
|
|
3379
|
+
typeOrValue.kind === "TemplateParameterAccess")) {
|
|
2611
3380
|
if (typeOrValue.constraint) {
|
|
2612
3381
|
if (typeOrValue.constraint.valueType) {
|
|
2613
3382
|
hasValue = true;
|
|
@@ -2636,7 +3405,9 @@ export function createChecker(program, resolver) {
|
|
|
2636
3405
|
let str = node.head.value;
|
|
2637
3406
|
for (const [span, typeOrValue] of spanTypeOrValues) {
|
|
2638
3407
|
if (typeOrValue !== null &&
|
|
2639
|
-
(!("kind" in typeOrValue) ||
|
|
3408
|
+
(!("kind" in typeOrValue) ||
|
|
3409
|
+
(typeOrValue.kind !== "TemplateParameter" &&
|
|
3410
|
+
typeOrValue.kind !== "TemplateParameterAccess"))) {
|
|
2640
3411
|
compilerAssert(typeOrValue !== null && isValue(typeOrValue), "Expected value.");
|
|
2641
3412
|
str += stringifyValueForTemplate(typeOrValue);
|
|
2642
3413
|
}
|
|
@@ -2748,6 +3519,7 @@ export function createChecker(program, resolver) {
|
|
|
2748
3519
|
for (const file of program.sourceFiles.values()) {
|
|
2749
3520
|
checkSourceFile(file);
|
|
2750
3521
|
}
|
|
3522
|
+
checkOrphanedFunctionImplementations();
|
|
2751
3523
|
internalDecoratorValidation();
|
|
2752
3524
|
assertNoPendingResolutions();
|
|
2753
3525
|
runPostValidators(postCheckValidators);
|
|
@@ -2774,6 +3546,36 @@ export function createChecker(program, resolver) {
|
|
|
2774
3546
|
}
|
|
2775
3547
|
}
|
|
2776
3548
|
}
|
|
3549
|
+
/**
|
|
3550
|
+
* Check that all function implementations exported via $functions have a corresponding
|
|
3551
|
+
* `extern fn` declaration in TypeSpec. Reports an error for each orphaned implementation.
|
|
3552
|
+
*/
|
|
3553
|
+
function checkOrphanedFunctionImplementations() {
|
|
3554
|
+
for (const file of program.jsSourceFiles.values()) {
|
|
3555
|
+
checkSymbolTableForOrphanedFunctions(file.symbol.exports, file);
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
function checkSymbolTableForOrphanedFunctions(exports, sourceFile) {
|
|
3559
|
+
if (!exports)
|
|
3560
|
+
return;
|
|
3561
|
+
for (const sym of exports.values()) {
|
|
3562
|
+
if (sym.flags & 2048 /* SymbolFlags.Function */ && sym.flags & 2097152 /* SymbolFlags.Implementation */) {
|
|
3563
|
+
const merged = getMergedSymbol(sym);
|
|
3564
|
+
const hasFunctionDeclaration = merged.declarations.some((d) => d.kind === SyntaxKind.FunctionDeclarationStatement);
|
|
3565
|
+
if (!hasFunctionDeclaration) {
|
|
3566
|
+
reportCheckerDiagnostic(createDiagnostic({
|
|
3567
|
+
code: "missing-extern-declaration",
|
|
3568
|
+
format: { name: sym.name },
|
|
3569
|
+
target: sourceFile,
|
|
3570
|
+
}));
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
// Recurse into namespace symbols
|
|
3574
|
+
if (sym.flags & 256 /* SymbolFlags.Namespace */ && sym.exports) {
|
|
3575
|
+
checkSymbolTableForOrphanedFunctions(sym.exports, sourceFile);
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
2777
3579
|
/** Report error with duplicate using in the same scope. */
|
|
2778
3580
|
function checkDuplicateUsings(file) {
|
|
2779
3581
|
const duplicateTrackers = new Map();
|
|
@@ -3265,7 +4067,7 @@ export function createChecker(program, resolver) {
|
|
|
3265
4067
|
if (target.kind === "Scalar" || target.kind === "ScalarConstructor") {
|
|
3266
4068
|
return target;
|
|
3267
4069
|
}
|
|
3268
|
-
else if (target.kind === "TemplateParameter") {
|
|
4070
|
+
else if (target.kind === "TemplateParameter" || target.kind === "TemplateParameterAccess") {
|
|
3269
4071
|
const callable = target.constraint && constraintIsCallable(target.constraint);
|
|
3270
4072
|
if (!callable) {
|
|
3271
4073
|
reportCheckerDiagnostic(createDiagnostic({
|
|
@@ -3751,7 +4553,7 @@ export function createChecker(program, resolver) {
|
|
|
3751
4553
|
return entity.type;
|
|
3752
4554
|
}
|
|
3753
4555
|
if (isType(entity)) {
|
|
3754
|
-
if (entity.kind === "TemplateParameter") {
|
|
4556
|
+
if (entity.kind === "TemplateParameter" || entity.kind === "TemplateParameterAccess") {
|
|
3755
4557
|
if (entity.constraint === undefined || entity.constraint.type !== undefined) {
|
|
3756
4558
|
// means this template constraint will accept values
|
|
3757
4559
|
reportCheckerDiagnostic(createDiagnostic({
|
|
@@ -3848,8 +4650,11 @@ export function createChecker(program, resolver) {
|
|
|
3848
4650
|
* access a symbol for a type that is created during the check phase.
|
|
3849
4651
|
*/
|
|
3850
4652
|
function lateBindMemberContainer(type) {
|
|
3851
|
-
|
|
4653
|
+
const existingSymbol = type.symbol;
|
|
4654
|
+
if (existingSymbol &&
|
|
4655
|
+
!(isTemplateInstance(type) && !(existingSymbol.flags & 4194304 /* SymbolFlags.LateBound */))) {
|
|
3852
4656
|
return;
|
|
4657
|
+
}
|
|
3853
4658
|
switch (type.kind) {
|
|
3854
4659
|
case "Model":
|
|
3855
4660
|
type.symbol = createSymbol(type.node, type.name, 2 /* SymbolFlags.Model */ | 4194304 /* SymbolFlags.LateBound */);
|
|
@@ -3906,6 +4711,10 @@ export function createChecker(program, resolver) {
|
|
|
3906
4711
|
}
|
|
3907
4712
|
const sym = createSymbol(member.node, member.name, kind | 4194304 /* SymbolFlags.LateBound */, containerSym);
|
|
3908
4713
|
mutate(sym).type = member;
|
|
4714
|
+
const symbolSource = getTypeSymbol(member);
|
|
4715
|
+
if (symbolSource) {
|
|
4716
|
+
mutate(sym).symbolSource = symbolSource.symbolSource ?? symbolSource;
|
|
4717
|
+
}
|
|
3909
4718
|
compilerAssert(containerSym.members, "containerSym.members is undefined");
|
|
3910
4719
|
containerMembers.set(member.name, sym);
|
|
3911
4720
|
}
|
|
@@ -3971,6 +4780,7 @@ export function createChecker(program, resolver) {
|
|
|
3971
4780
|
messageId: "modelExpression",
|
|
3972
4781
|
target: isExpr,
|
|
3973
4782
|
}));
|
|
4783
|
+
pendingResolutions.finish(modelSymId, ResolutionKind.BaseType);
|
|
3974
4784
|
return undefined;
|
|
3975
4785
|
}
|
|
3976
4786
|
else if (isExpr.kind === SyntaxKind.ArrayExpression) {
|
|
@@ -3986,12 +4796,14 @@ export function createChecker(program, resolver) {
|
|
|
3986
4796
|
target: target,
|
|
3987
4797
|
}));
|
|
3988
4798
|
}
|
|
4799
|
+
pendingResolutions.finish(modelSymId, ResolutionKind.BaseType);
|
|
3989
4800
|
return undefined;
|
|
3990
4801
|
}
|
|
3991
4802
|
isType = getTypeForNode(isExpr, ctx);
|
|
3992
4803
|
}
|
|
3993
4804
|
else {
|
|
3994
4805
|
reportCheckerDiagnostic(createDiagnostic({ code: "is-model", target: isExpr }));
|
|
4806
|
+
pendingResolutions.finish(modelSymId, ResolutionKind.BaseType);
|
|
3995
4807
|
return undefined;
|
|
3996
4808
|
}
|
|
3997
4809
|
pendingResolutions.finish(modelSymId, ResolutionKind.BaseType);
|
|
@@ -4019,12 +4831,37 @@ export function createChecker(program, resolver) {
|
|
|
4019
4831
|
}
|
|
4020
4832
|
return undefined;
|
|
4021
4833
|
}
|
|
4834
|
+
// Ancestors are models that already depend on this model via spread.
|
|
4835
|
+
const modelAncestors = spreadResolutionAncestors.get(modelSymId);
|
|
4836
|
+
if (targetSym && modelAncestors?.has(targetSym)) {
|
|
4837
|
+
if (ctx.mapper === undefined) {
|
|
4838
|
+
reportCheckerDiagnostic(createDiagnostic({
|
|
4839
|
+
code: "spread-model",
|
|
4840
|
+
messageId: "selfSpread",
|
|
4841
|
+
target: target,
|
|
4842
|
+
}));
|
|
4843
|
+
}
|
|
4844
|
+
return undefined;
|
|
4845
|
+
}
|
|
4846
|
+
if (targetSym) {
|
|
4847
|
+
let targetAncestors = spreadResolutionAncestors.get(targetSym);
|
|
4848
|
+
if (!targetAncestors) {
|
|
4849
|
+
targetAncestors = new Set();
|
|
4850
|
+
spreadResolutionAncestors.set(targetSym, targetAncestors);
|
|
4851
|
+
}
|
|
4852
|
+
targetAncestors.add(modelSymId);
|
|
4853
|
+
for (const ancestor of modelAncestors ?? []) {
|
|
4854
|
+
targetAncestors.add(ancestor);
|
|
4855
|
+
}
|
|
4856
|
+
}
|
|
4022
4857
|
const type = getTypeForNode(target, ctx);
|
|
4023
4858
|
return type;
|
|
4024
4859
|
}
|
|
4025
4860
|
function checkSpreadProperty(ctx, parentModelSym, targetNode, parentModel) {
|
|
4026
4861
|
const targetType = getTypeForNode(targetNode, ctx);
|
|
4027
|
-
if (targetType.kind === "TemplateParameter" ||
|
|
4862
|
+
if (targetType.kind === "TemplateParameter" ||
|
|
4863
|
+
targetType.kind === "TemplateParameterAccess" ||
|
|
4864
|
+
isErrorType(targetType)) {
|
|
4028
4865
|
return [[], undefined];
|
|
4029
4866
|
}
|
|
4030
4867
|
if (targetType.kind !== "Model") {
|
|
@@ -4082,25 +4919,27 @@ export function createChecker(program, resolver) {
|
|
|
4082
4919
|
type: undefined,
|
|
4083
4920
|
decorators: [],
|
|
4084
4921
|
});
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
target: prop,
|
|
4090
|
-
}));
|
|
4091
|
-
type.type = errorType;
|
|
4922
|
+
// Link the property early so that re-entrant access (e.g., A.a from another model)
|
|
4923
|
+
// finds this property via checkMemberSym without re-entering checkModelProperty.
|
|
4924
|
+
if (links) {
|
|
4925
|
+
linkType(ctx, links, type);
|
|
4092
4926
|
}
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
if (
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4927
|
+
type.type = getTypeForNode(prop.value, ctx);
|
|
4928
|
+
// Detect property-to-property cycles (e.g., A.a -> B.a -> A.a)
|
|
4929
|
+
if (hasPropertyTypeCycle(type)) {
|
|
4930
|
+
if (ctx.mapper === undefined) {
|
|
4931
|
+
reportCheckerDiagnostic(createDiagnostic({
|
|
4932
|
+
code: "circular-prop",
|
|
4933
|
+
format: { propName: name },
|
|
4934
|
+
target: prop,
|
|
4935
|
+
}));
|
|
4101
4936
|
}
|
|
4102
|
-
|
|
4103
|
-
|
|
4937
|
+
type.type = errorType;
|
|
4938
|
+
}
|
|
4939
|
+
if (prop.default) {
|
|
4940
|
+
const defaultValue = checkDefaultValue(ctx, prop.default, type.type);
|
|
4941
|
+
if (defaultValue !== null) {
|
|
4942
|
+
type.defaultValue = defaultValue;
|
|
4104
4943
|
}
|
|
4105
4944
|
}
|
|
4106
4945
|
type.decorators = checkDecorators(ctx, type, prop);
|
|
@@ -4108,14 +4947,41 @@ export function createChecker(program, resolver) {
|
|
|
4108
4947
|
linkMapper(type, ctx.mapper);
|
|
4109
4948
|
const shouldRunDecorators = !ctx.hasFlags(CheckFlags.InTemplateDeclaration);
|
|
4110
4949
|
if (!parentTemplate || shouldRunDecorators) {
|
|
4111
|
-
const docComment =
|
|
4950
|
+
const docComment = getDocCommentForSymbol(sym) ?? getOperationParameterDocComment(prop);
|
|
4112
4951
|
if (docComment) {
|
|
4113
4952
|
type.decorators.unshift(createDocFromCommentDecorator("self", docComment));
|
|
4114
4953
|
}
|
|
4115
4954
|
}
|
|
4116
|
-
pendingResolutions.finish(sym, ResolutionKind.Type);
|
|
4117
4955
|
return finishType(type, { skipDecorators: !shouldRunDecorators });
|
|
4118
4956
|
}
|
|
4957
|
+
/**
|
|
4958
|
+
* Detect cycles in the property type chain. Follows `.type` links as long as
|
|
4959
|
+
* they point to other ModelProperty types and checks whether we loop back to
|
|
4960
|
+
* the starting property.
|
|
4961
|
+
*/
|
|
4962
|
+
function hasPropertyTypeCycle(prop) {
|
|
4963
|
+
let current = prop.type;
|
|
4964
|
+
while (current !== undefined && current.kind === "ModelProperty") {
|
|
4965
|
+
if (current === prop)
|
|
4966
|
+
return true;
|
|
4967
|
+
current = current.type;
|
|
4968
|
+
}
|
|
4969
|
+
return false;
|
|
4970
|
+
}
|
|
4971
|
+
function getOperationParameterDocComment(prop) {
|
|
4972
|
+
if (prop.parent?.kind !== SyntaxKind.ModelExpression) {
|
|
4973
|
+
return undefined;
|
|
4974
|
+
}
|
|
4975
|
+
const signature = prop.parent.parent;
|
|
4976
|
+
if (signature?.kind !== SyntaxKind.OperationSignatureDeclaration) {
|
|
4977
|
+
return undefined;
|
|
4978
|
+
}
|
|
4979
|
+
const operation = signature.parent;
|
|
4980
|
+
if (operation?.kind !== SyntaxKind.OperationStatement) {
|
|
4981
|
+
return undefined;
|
|
4982
|
+
}
|
|
4983
|
+
return extractParamDocs(operation).get(prop.id.sv);
|
|
4984
|
+
}
|
|
4119
4985
|
function createDocFromCommentDecorator(key, doc) {
|
|
4120
4986
|
return {
|
|
4121
4987
|
decorator: docFromCommentDecorator,
|
|
@@ -4125,6 +4991,32 @@ export function createChecker(program, resolver) {
|
|
|
4125
4991
|
],
|
|
4126
4992
|
};
|
|
4127
4993
|
}
|
|
4994
|
+
function getDocCommentForSymbol(sym) {
|
|
4995
|
+
if (!sym) {
|
|
4996
|
+
return undefined;
|
|
4997
|
+
}
|
|
4998
|
+
const relatedSource = getRelatedSymbolSource(sym);
|
|
4999
|
+
return (docFromCommentForSym.get(sym) ??
|
|
5000
|
+
(relatedSource && docFromCommentForSym.get(relatedSource)) ??
|
|
5001
|
+
(sym.flags & 4194304 /* SymbolFlags.LateBound */ && sym.type && isType(sym.type)
|
|
5002
|
+
? getDocCommentForType(sym.type)
|
|
5003
|
+
: undefined));
|
|
5004
|
+
}
|
|
5005
|
+
function getDocCommentForType(type) {
|
|
5006
|
+
if (!type) {
|
|
5007
|
+
return undefined;
|
|
5008
|
+
}
|
|
5009
|
+
if (type.kind === "ModelProperty") {
|
|
5010
|
+
for (let property = type; property; property = property.sourceProperty) {
|
|
5011
|
+
const docComment = getDocCommentForSymbol(getTemplateAccessSymbolSource(property));
|
|
5012
|
+
if (docComment) {
|
|
5013
|
+
return docComment;
|
|
5014
|
+
}
|
|
5015
|
+
}
|
|
5016
|
+
return undefined;
|
|
5017
|
+
}
|
|
5018
|
+
return getDocCommentForSymbol(getTemplateAccessSymbolSource(type));
|
|
5019
|
+
}
|
|
4128
5020
|
function checkDefaultValue(ctx, defaultNode, type) {
|
|
4129
5021
|
if (isErrorType(type)) {
|
|
4130
5022
|
// if the prop type is an error we don't need to validate again.
|
|
@@ -4594,6 +5486,23 @@ export function createChecker(program, resolver) {
|
|
|
4594
5486
|
checkTemplateDeclaration(ctx, node);
|
|
4595
5487
|
const aliasSymId = getNodeSym(node);
|
|
4596
5488
|
if (pendingResolutions.has(aliasSymId, ResolutionKind.Type)) {
|
|
5489
|
+
// Re-entrant resolution detected. Check if the alias value node has already produced
|
|
5490
|
+
// a type (e.g., a model expression that creates and links its type before resolving
|
|
5491
|
+
// properties). In that case, the circular reference is safe.
|
|
5492
|
+
const valueSym = node.value.symbol;
|
|
5493
|
+
if (valueSym) {
|
|
5494
|
+
const valueLinks = getSymbolLinks(valueSym);
|
|
5495
|
+
const inProgressType = ctx.mapper === undefined
|
|
5496
|
+
? valueLinks.declaredType
|
|
5497
|
+
: valueLinks.instantiations?.get(ctx.mapper.args);
|
|
5498
|
+
if (inProgressType) {
|
|
5499
|
+
linkType(ctx, links, inProgressType);
|
|
5500
|
+
return inProgressType;
|
|
5501
|
+
}
|
|
5502
|
+
}
|
|
5503
|
+
if (node.value.kind === SyntaxKind.ModelExpression) {
|
|
5504
|
+
return getTypeForNode(node.value, ctx);
|
|
5505
|
+
}
|
|
4597
5506
|
if (ctx.mapper === undefined) {
|
|
4598
5507
|
reportCheckerDiagnostic(createDiagnostic({
|
|
4599
5508
|
code: "circular-alias-type",
|
|
@@ -5196,7 +6105,7 @@ export function createChecker(program, resolver) {
|
|
|
5196
6105
|
function cloneTypeForSymbol(sym, type, additionalProps = {}) {
|
|
5197
6106
|
let clone = initializeClone(type, additionalProps);
|
|
5198
6107
|
if ("decorators" in clone) {
|
|
5199
|
-
const docComment =
|
|
6108
|
+
const docComment = getDocCommentForSymbol(sym) ?? getDocCommentForType(type);
|
|
5200
6109
|
if (docComment) {
|
|
5201
6110
|
clone.decorators.push(createDocFromCommentDecorator("self", docComment));
|
|
5202
6111
|
}
|