[compiler] Resolve members through template parameters.#9868
[compiler] Resolve members through template parameters.#9868witemple-msft wants to merge 19 commits intomicrosoft:mainfrom
Conversation
…ateparameter-metaproperties
commit: |
|
All changed packages have been documented.
Show changes
|
|
You can try these changes here
|
|
Solid illustration of functionality here (see |
…ateparameter-metaproperties
There was a problem hiding this comment.
Pull request overview
This PR introduces a new TemplateParameterAccess type to the TypeSpec compiler, enabling member access (.) and meta-member access (::) through template parameters based on their constraints. For instance, if R extends Resource and Resource guarantees a property id, then R.id can be used as a type in template declarations.
Changes:
- Adds a new
TemplateParameterAccessinternal type and comprehensive resolution logic inchecker.tsandname-resolver.tsfor resolving member/meta-member access through template parameter constraints - Updates all subsystems that handle the
Typeunion (semantic walker, type relation checker, string template utils, type name utils, completions, hover, document highlight) to recognize the new type - Updates dependent packages (
html-program-viewer,http-server-csharp,tspd) and adds tests for completions, hover, document highlighting, and reference resolution
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
packages/compiler/src/core/types.ts |
Adds TemplateParameterAccess interface and includes it in the Type union |
packages/compiler/src/core/checker.ts |
Core logic for resolving template parameter access (member & meta-member), caching, and completions |
packages/compiler/src/core/name-resolver.ts |
Adds getMetaMemberNames API to expose available meta-member names for completion and validation |
packages/compiler/src/core/semantic-walker.ts |
Adds navigation support for TemplateParameterAccess in the semantic walker |
packages/compiler/src/core/type-relation-checker.ts |
Treats TemplateParameterAccess like TemplateParameter (uses constraint for assignability) |
packages/compiler/src/core/helpers/type-name-utils.ts |
Returns path as the type name for TemplateParameterAccess |
packages/compiler/src/core/helpers/string-template-utils.ts |
Handles TemplateParameterAccess in string template serialization check |
packages/compiler/src/server/type-signature.ts |
Renders hover signature for TemplateParameterAccess as (template access) |
packages/compiler/test/checker/references.test.ts |
Tests for resolving references through template parameter constraints |
packages/compiler/test/checker/operations.test.ts |
Tests that operation parameters resolve correctly with template member types |
packages/compiler/test/server/completion.test.ts |
Tests for IDE completions through constrained template parameters |
packages/compiler/test/server/get-hover.test.ts |
Tests for hover information on template parameter access expressions |
packages/compiler/test/server/document-highlight.test.ts |
Tests for document highlighting of template access references |
packages/html-program-viewer/src/react/type-config.ts |
Registers TemplateParameterAccess in the program viewer UI |
packages/http-server-csharp/src/lib/service.ts |
Handles TemplateParameterAccess as an unsupported type (returns undefined) |
packages/tspd/src/ref-doc/utils/type-signature.ts |
Renders TemplateParameterAccess signature in documentation generation |
.chronus/changes/*.md |
Two changeset entries for the compiler (feature) and dependent packages (internal) |
.chronus/changes/witemple-msft-templateparameter-metaproperties-2026-2-2-13-17-55.md
Outdated
Show resolved
Hide resolved
| /** @internal */ | ||
| base: TemplateParameter | TemplateParameterAccess; | ||
| /** @internal */ | ||
| path: string; |
There was a problem hiding this comment.
what does path corespond to? and cacheKey?
There was a problem hiding this comment.
I'm still iterating on this and reviewing the agent's work. I'm a little suspicious of cacheKey myself, at least I feel it can be moved into checker state rather than placed on the Type itself, or potentially refactored into a clearer form of associativity.
The idea for path is that it caches the string that is shown to users in hover texts etc. so that it doesn't have to be recursively reconstructed N times per referent. That creates a potential exponential explosion problem (similar to naive fibonnaci or factorial) of string concatenations where many nodes can reference the same base, and then that base would have to compute its base paths N^2 times, so on.
The idea of cacheKey is basically this as I understand it:
- There is no declared symbol for a template parameter access that the binder provides, but we need a Sym for type/value resolution pipelines in the checker, so the checker creates a synthetic late-bound symbol for template parameter access at check-time and sets
sym.typeto the template parameter access node. - Example:
T.id::type,T.idgets a synthetic late-bound Sym. That Sym stands in for "the result of accessingidonTeven though there isn't a concrete property declaration onTyet. - The cacheKey prevents many symbols from being created for the same access. If we created a fresh late-bound symbol every time we saw the same logical chain, then two different instances of
T.idwould get different symbol identities, which would not cache coherently and they would not have stable symbol identity. Chained accesses would keep re-allocating intermediate results for the same logical thing. - The cache makes it so that for the same root template parameter instance by identity and logical access chain, the same synthetic symbol is reused so it is coherent across the program.
- The cache is only used when there is no mapper, because using the cache when a mapper is active would smear together multiple different meanings of
T.idunder different concrete type mapper contexts into the same Sym. So during instantiation, even partial instantiation, the cache is not used so that new symbols are created reflecting particular instances of the template parameter access.
I don't really think either of these things has to live on the Type itself, and I'm inclined to at least move the cacheKey off into a weakmap. The path, though, could be useful to have on the type so that things that observe the uninstantiated Type don't have to go mucking around in the checker to compute a display string for it.
There was a problem hiding this comment.
Moved cacheKey to checker state.
| * @param baseEntity Template parameter or prior template access chain. | ||
| * @returns The resolved member/meta-member type, or `errorType` when not guaranteed. | ||
| */ | ||
| function resolveTemplateAccessType( |
There was a problem hiding this comment.
would that work for values?
There was a problem hiding this comment.
Not currently. No extant access patterns can resolve to values, since it's just property references and metaproperties which are all types. The proposed ::name metaproperty would be an example of one that can resolve to a value (I think technically it should resolve to an indeterminate for accuracy). I'd rather just handle types here and deal with this when they can resolve to values, since it may involve additional machinery to make that work correctly.
…ateparameter-metaproperties
…ateparameter-metaproperties
…ateparameter-metaproperties
…ateparameter-metaproperties
This PR enables resolving member symbols (properties, metaproperties) through template parameters based on constraints.
It accomplishes this by adding a new Type node,
TemplateParameterAccess, which is a semantic equivalent of a member expression where the base of the member access is a template parameter or another template parameter access expression. These are only visible inside the context of template declarations. Otherwise, like template parameters, they become "resolved" to concrete types when the template is instantiated.This allows you to invoke metaproperties such as
::returnTypewhen a template item is constrained to anOperation. This is recursive, soI.o::returnTypeis allowed ifIis an interface that is proven to have an operation namedo. proven is the keyword. Members only resolve if the constraint guarantees their presence.This feature enables much more powerful templates. For example: