POC: ambient module declarations keyed on import attributes - #63901
Open
Bartek Iwańczuk (bartlomieju) wants to merge 1 commit into
Open
POC: ambient module declarations keyed on import attributes#63901Bartek Iwańczuk (bartlomieju) wants to merge 1 commit into
Bartek Iwańczuk (bartlomieju) wants to merge 1 commit into
Conversation
Implements the syntax proposed in microsoft#46135: a string-literal-named ambient module may carry a `with { ... }` clause, and an import that carries matching attributes resolves to it. Originally opened against the Go port as microsoft/typescript-go#4666, which was closed when development moved back to this repository.
|
This PR doesn't have any linked issues. Please open an issue that references this PR. From there we can discuss and prioritise. |
1 similar comment
|
This PR doesn't have any linked issues. Please open an issue that references this PR. From there we can discuss and prioritise. |
Copilot started reviewing on behalf of
Bartek Iwańczuk (bartlomieju)
August 20, 2026 08:58
View session
|
The TypeScript team hasn't accepted the linked issue #46135. If you can get it accepted, this PR will have a better chance of being reviewed. |
Contributor
There was a problem hiding this comment.
Pull request overview
Adds proof-of-concept support for ambient modules selected by static import attributes.
Changes:
- Extends parsing, AST encoding, printing, binding, and checking.
- Adds attribute matching and precedence rules.
- Adds conformance tests and baselines.
Reviewed changes
Copilot reviewed 44 out of 47 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
tsc/testdata/tests/cases/conformance/importAttributes/ambientModuleImportAttributesTextAndBytes.ts |
Tests attribute-based type selection. |
tsc/testdata/tests/cases/conformance/importAttributes/ambientModuleImportAttributesPatternSpecificity.ts |
Tests wildcard specificity. |
tsc/testdata/tests/cases/conformance/importAttributes/ambientModuleImportAttributesOverridesRealModule.ts |
Tests precedence over real modules. |
tsc/testdata/tests/cases/conformance/importAttributes/ambientModuleImportAttributesJsonUnaffected.ts |
Tests JSON and plain ambients. |
tsc/testdata/tests/cases/conformance/importAttributes/ambientModuleImportAttributesInvalidValue.ts |
Tests invalid attribute values. |
tsc/testdata/tests/cases/conformance/importAttributes/ambientModuleImportAttributesExactPrecedence.ts |
Tests exact-match precedence. |
tsc/testdata/tests/cases/conformance/importAttributes/ambientModuleImportAttributesDistinctKeys.ts |
Tests collision-resistant keys. |
tsc/testdata/tests/cases/conformance/importAttributes/ambientModuleImportAttributesDeclarationEmit.ts |
Tests declaration emit. |
tsc/testdata/baselines/reference/conformance/ambientModuleImportAttributesTextAndBytes.types |
Type baseline. |
tsc/testdata/baselines/reference/conformance/ambientModuleImportAttributesTextAndBytes.symbols |
Symbol baseline. |
tsc/testdata/baselines/reference/conformance/ambientModuleImportAttributesTextAndBytes.errors.txt |
Diagnostic baseline. |
tsc/testdata/baselines/reference/conformance/ambientModuleImportAttributesPatternSpecificity.types |
Type baseline. |
tsc/testdata/baselines/reference/conformance/ambientModuleImportAttributesPatternSpecificity.symbols |
Symbol baseline. |
tsc/testdata/baselines/reference/conformance/ambientModuleImportAttributesOverridesRealModule.types |
Type baseline. |
tsc/testdata/baselines/reference/conformance/ambientModuleImportAttributesOverridesRealModule.symbols |
Symbol baseline. |
tsc/testdata/baselines/reference/conformance/ambientModuleImportAttributesOverridesRealModule.errors.txt |
Diagnostic baseline. |
tsc/testdata/baselines/reference/conformance/ambientModuleImportAttributesJsonUnaffected.types |
Type baseline. |
tsc/testdata/baselines/reference/conformance/ambientModuleImportAttributesJsonUnaffected.symbols |
Symbol baseline. |
tsc/testdata/baselines/reference/conformance/ambientModuleImportAttributesInvalidValue.types |
Type baseline. |
tsc/testdata/baselines/reference/conformance/ambientModuleImportAttributesInvalidValue.symbols |
Symbol baseline. |
tsc/testdata/baselines/reference/conformance/ambientModuleImportAttributesInvalidValue.errors.txt |
Diagnostic baseline. |
tsc/testdata/baselines/reference/conformance/ambientModuleImportAttributesExactPrecedence.types |
Type baseline. |
tsc/testdata/baselines/reference/conformance/ambientModuleImportAttributesExactPrecedence.symbols |
Symbol baseline. |
tsc/testdata/baselines/reference/conformance/ambientModuleImportAttributesDistinctKeys.types |
Type baseline. |
tsc/testdata/baselines/reference/conformance/ambientModuleImportAttributesDistinctKeys.symbols |
Symbol baseline. |
tsc/testdata/baselines/reference/conformance/ambientModuleImportAttributesDeclarationEmit.types |
Type baseline. |
tsc/testdata/baselines/reference/conformance/ambientModuleImportAttributesDeclarationEmit.symbols |
Symbol baseline. |
tsc/testdata/baselines/reference/conformance/ambientModuleImportAttributesDeclarationEmit.js |
Emit baseline. |
tsc/internal/transformers/declarations/transform.go |
Preserves module attributes. |
tsc/internal/printer/printer.go |
Prints attributed declarations. |
tsc/internal/parser/reparser.go |
Updates module construction. |
tsc/internal/parser/parser.go |
Parses ambient attributes. |
tsc/internal/parser/jsdoc.go |
Updates module factory calls. |
tsc/internal/ls/codeactions_fixmissingtypeannotation.go |
Updates namespace construction. |
tsc/internal/checker/nodebuilder_hover.go |
Updates hover construction. |
tsc/internal/checker/checker.go |
Matches and resolves attributed ambients. |
tsc/internal/binder/binder.go |
Binds attributed ambient symbols. |
tsc/internal/ast/utilities.go |
Generates attribute maps and keys. |
tsc/internal/ast/ast.go |
Adds attributed ambient metadata. |
tsc/internal/ast/ast_generated.go |
Extends the generated Go AST. |
tsc/internal/api/encoder/encoder_generated.go |
Encodes module attributes. |
tsc/internal/api/encoder/decoder_generated.go |
Decodes module attributes. |
tools/scripts/tsc/ast.json |
Extends the AST schema. |
packages/typescript/src/ast/visitor.generated.ts |
Visits module attributes. |
packages/typescript/src/ast/factory.generated.ts |
Creates attributed modules. |
packages/typescript/src/ast/ast.generated.ts |
Exposes module attributes. |
packages/typescript/src/api/node/protocol.generated.ts |
Adds attributes to protocol children. |
Files not reviewed (3)
- tsc/internal/api/encoder/decoder_generated.go: Generated file
- tsc/internal/api/encoder/encoder_generated.go: Generated file
- tsc/internal/ast/ast_generated.go: Generated file
Suppressed comments (1)
tsc/internal/binder/binder.go:799
- Keeping attributed modules out of
PatternAmbientModulesmakes the auto-import extractor misclassify every attributed declaration as a non-pattern ambient (ls/autoimport/extract.go:128-134). It then indexes the exports under the raw module name, whilels/autoimport/fix.go:551always emitsattributes: nil; completions can therefore insert invalid plain imports (including imports from the literal"*"). Exclude attributed declarations from auto-import indexing until fixes can preserve their attributes, and cover this with a fourslash completion test.
b.file.AttributedAmbientModules = append(b.file.AttributedAmbientModules, &ast.AttributedAmbientModule{Pattern: pattern, Attributes: attributes, Symbol: symbol})
Comment on lines
+794
to
+799
| } else if attributes := node.AsModuleDeclaration().Attributes; attributes != nil { | ||
| // An ambient module keyed on import attributes (microsoft/TypeScript#46135). | ||
| // It is recorded separately and only consulted when an import specifies | ||
| // matching attributes, so it neither participates in normal pattern/ambient | ||
| // resolution nor shadows a plain `declare module` of the same specifier. | ||
| b.file.AttributedAmbientModules = append(b.file.AttributedAmbientModules, &ast.AttributedAmbientModule{Pattern: pattern, Attributes: attributes, Symbol: symbol}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is a proof-of-concept implementation of the syntax proposed in #46135,
ambient module declarations that are keyed on import attributes:
It is a discussion-starter; the goal is to make the proposal concrete enough to
argue about precedence and matching rules on real code.
This was originally opened against the Go port as
microsoft/typescript-go#4666 (microsoft/typescript-go#4666)
and was closed when development moved back to this repository; this is the same
change ported to the current layout. The earlier review discussion, including a
round of Copilot feedback on precedence, matching, and validation, is on that PR.
Motivation
Runtimes and bundlers (Deno, Bun, Node) already support
import x from "./f" with { type: "text" | "bytes" }, but TypeScript has no wayto type these imports. Extension-based ambients
(
declare module "*.txt") get close, but they cannot distinguish two imports ofthe same file by attribute, which is exactly what
type: "text"vstype: "bytes"needs. This has surfaced now because we're removing our fork ofTypeScript, where we handled text/bytes imports explicitly as special cases.
Closes #46135
What this does
with { ... }clausebetween the name and the body, reusing the existing import-attributes parser.
declare globalis excluded.ModuleDeclarationgains an optionalAttributes(ImportAttributes)member (schema plus regen).
new
AttributedAmbientModuleslist and given a distinct symbol name(
"*" with {type=text}), so it neither merges with nor shadows a plaindeclare module "*", and does not participate in normal pattern/ambientresolution.
resolveExternalModuleconsults the attribute-keyed declarations before fileresolution. A declaration matches when its attribute set exactly equals the
import's attributes and its specifier pattern matches; pattern specificity
reuses the existing "longest prefix before
*" rule.Precedence and semantics chosen (all up for debate)
This is what lets
import x from "./real.ts" with { type: "text" }be typed bythe ambient rather than by
./real.ts, and it is the capability plainextension ambients cannot provide. Whether the real module should sometimes win
is the central open question (see Ambient Module Declarations for Import Attributes (formerly known as Import Assertions) #46135 and the precedence discussion in
Implement Import Assertions (stage 3) #40694, comment 918343758).
type: "json"is untouched: the built-in JSON typing path is unchanged, and animport whose attributes match no attribute-keyed declaration falls straight
through to normal resolution.
resolveJsonModulebehavior is unaffected.with, as suggested in the issue.
Open questions for the discussion
resolution (this PR), or only when the specifier does not otherwise resolve, or
only for non-relative specifiers? This is the crux of Ambient Module Declarations for Import Attributes (formerly known as Import Assertions) #46135.
specificity is the prefix length before
*. That means*.svgdoes notoutrank
*(both have an empty prefix), and the tie is resolved by declarationorder, same as today's pattern ambients. Should trailing patterns like
*.svgbe made more specific?
resolveJsonModuleinteraction: should a user-writtendeclare module "*" with { type: "json" }be allowed to override the built-inJSON typing, or should
type: "json"remain reserved?import("x", { with: { ... } })and import-type nodes are notwired up yet; only static import/export declarations are. (Happy to update the
PR to handle that too.)
Tests
New conformance cases under
tsc/testdata/tests/cases/conformance/importAttributescover: text vs bytes discrimination on the same file; an attribute-keyed ambient
winning over a real
.tsmodule;type: "json"and a plaindeclare module "*"left unaffected; pattern specificity; and declaration-emit round-trip. The
existing suite passes with no other baseline changes.
Disclosure: I used AI (Claude and Codex) to research and implement this change,
but I reviewed it manually before opening a PR.