@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.
Files changed (70) hide show
  1. package/dist/manifest.js +2 -2
  2. package/dist/src/config/config-loader.d.ts.map +1 -1
  3. package/dist/src/config/config-loader.js +18 -1
  4. package/dist/src/config/config-loader.js.map +1 -1
  5. package/dist/src/config/config-schema.d.ts.map +1 -1
  6. package/dist/src/config/config-schema.js +9 -0
  7. package/dist/src/config/config-schema.js.map +1 -1
  8. package/dist/src/config/config-to-options.d.ts.map +1 -1
  9. package/dist/src/config/config-to-options.js +35 -0
  10. package/dist/src/config/config-to-options.js.map +1 -1
  11. package/dist/src/config/types.d.ts +18 -0
  12. package/dist/src/config/types.d.ts.map +1 -1
  13. package/dist/src/core/checker.d.ts.map +1 -1
  14. package/dist/src/core/checker.js +971 -62
  15. package/dist/src/core/checker.js.map +1 -1
  16. package/dist/src/core/entrypoint-resolution.d.ts.map +1 -1
  17. package/dist/src/core/entrypoint-resolution.js +7 -0
  18. package/dist/src/core/entrypoint-resolution.js.map +1 -1
  19. package/dist/src/core/helpers/string-template-utils.d.ts.map +1 -1
  20. package/dist/src/core/helpers/string-template-utils.js +1 -0
  21. package/dist/src/core/helpers/string-template-utils.js.map +1 -1
  22. package/dist/src/core/helpers/syntax-utils.d.ts +1 -0
  23. package/dist/src/core/helpers/syntax-utils.d.ts.map +1 -1
  24. package/dist/src/core/helpers/syntax-utils.js +3 -0
  25. package/dist/src/core/helpers/syntax-utils.js.map +1 -1
  26. package/dist/src/core/helpers/type-name-utils.d.ts.map +1 -1
  27. package/dist/src/core/helpers/type-name-utils.js +2 -0
  28. package/dist/src/core/helpers/type-name-utils.js.map +1 -1
  29. package/dist/src/core/messages.d.ts +76 -7
  30. package/dist/src/core/messages.d.ts.map +1 -1
  31. package/dist/src/core/messages.js +26 -1
  32. package/dist/src/core/messages.js.map +1 -1
  33. package/dist/src/core/modifiers.d.ts.map +1 -1
  34. package/dist/src/core/modifiers.js +0 -11
  35. package/dist/src/core/modifiers.js.map +1 -1
  36. package/dist/src/core/name-resolver.d.ts +2 -0
  37. package/dist/src/core/name-resolver.d.ts.map +1 -1
  38. package/dist/src/core/name-resolver.js +35 -2
  39. package/dist/src/core/name-resolver.js.map +1 -1
  40. package/dist/src/core/semantic-walker.d.ts.map +1 -1
  41. package/dist/src/core/semantic-walker.js +14 -0
  42. package/dist/src/core/semantic-walker.js.map +1 -1
  43. package/dist/src/core/type-relation-checker.d.ts.map +1 -1
  44. package/dist/src/core/type-relation-checker.js +2 -1
  45. package/dist/src/core/type-relation-checker.js.map +1 -1
  46. package/dist/src/core/types.d.ts +14 -1
  47. package/dist/src/core/types.d.ts.map +1 -1
  48. package/dist/src/core/types.js.map +1 -1
  49. package/dist/src/init/scaffold.js +16 -11
  50. package/dist/src/init/scaffold.js.map +1 -1
  51. package/dist/src/server/completion.js +14 -7
  52. package/dist/src/server/completion.js.map +1 -1
  53. package/dist/src/server/entrypoint-resolver.d.ts.map +1 -1
  54. package/dist/src/server/entrypoint-resolver.js +13 -0
  55. package/dist/src/server/entrypoint-resolver.js.map +1 -1
  56. package/dist/src/server/fatal-error.d.ts +5 -0
  57. package/dist/src/server/fatal-error.d.ts.map +1 -0
  58. package/dist/src/server/fatal-error.js +24 -0
  59. package/dist/src/server/fatal-error.js.map +1 -0
  60. package/dist/src/server/server.js +4 -6
  61. package/dist/src/server/server.js.map +1 -1
  62. package/dist/src/server/serverlib.d.ts.map +1 -1
  63. package/dist/src/server/serverlib.js +1 -1
  64. package/dist/src/server/serverlib.js.map +1 -1
  65. package/dist/src/server/type-signature.js +17 -1
  66. package/dist/src/server/type-signature.js.map +1 -1
  67. package/lib/prototypes.tsp +0 -1
  68. package/package.json +1 -1
  69. package/templates/__snapshots__/rest/tspconfig.yaml +10 -8
  70. package/templates/scaffolding.json +3 -0
@@ -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 && "kind" in entity && entity.kind === "TemplateParameter") {
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
- if (sym.symbolSource) {
1900
- return [sym.symbolSource];
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
- let base = resolver.getNodeLinks(identifier.parent.base).resolvedSymbol;
2190
- if (base) {
2191
- if (base.flags & 128 /* SymbolFlags.Alias */) {
2192
- base = getAliasedSymbol(CheckContext.DEFAULT, base);
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
- if (base) {
2195
- if (identifier.parent.selector === "::") {
2196
- if (base?.node === undefined && base?.declarations && base.declarations.length > 0) {
2197
- // Process meta properties separately, such as `::parameters`, `::returnType`
2198
- const nodeModels = base?.declarations[0];
2199
- if (nodeModels.kind === SyntaxKind.OperationStatement) {
2200
- const operation = nodeModels;
2201
- addCompletion("parameters", operation.symbol);
2202
- addCompletion("returnType", operation.symbol);
2203
- }
2204
- }
2205
- else if (base?.node?.kind === SyntaxKind.ModelProperty) {
2206
- // Process meta properties separately, such as `::type`
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
- addCompletions(base.exports ?? base.members);
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 sym = resolveMemberInContainer(base, node, options);
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
- return links.aliasedSymbol ?? resolver.getNodeLinks(node).resolvedSymbol;
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 && typeOrValue.kind === "TemplateParameter") {
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) || typeOrValue.kind !== "TemplateParameter")) {
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
- if (type.symbol)
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" || isErrorType(targetType)) {
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
- if (pendingResolutions.has(sym, ResolutionKind.Type) && ctx.mapper === undefined) {
4086
- reportCheckerDiagnostic(createDiagnostic({
4087
- code: "circular-prop",
4088
- format: { propName: name },
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
- else {
4094
- pendingResolutions.start(sym, ResolutionKind.Type);
4095
- type.type = getTypeForNode(prop.value, ctx);
4096
- if (prop.default) {
4097
- const defaultValue = checkDefaultValue(ctx, prop.default, type.type);
4098
- if (defaultValue !== null) {
4099
- type.defaultValue = defaultValue;
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
- if (links) {
4103
- linkType(ctx, links, type);
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 = docFromCommentForSym.get(sym);
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 = docFromCommentForSym.get(sym);
6108
+ const docComment = getDocCommentForSymbol(sym) ?? getDocCommentForType(type);
5200
6109
  if (docComment) {
5201
6110
  clone.decorators.push(createDocFromCommentDecorator("self", docComment));
5202
6111
  }