POC: ambient module declarations keyed on import attributes#4666
Open
bartlomieju wants to merge 3 commits into
Open
POC: ambient module declarations keyed on import attributes#4666bartlomieju wants to merge 3 commits into
bartlomieju wants to merge 3 commits into
Conversation
Proof-of-concept for microsoft/TypeScript#46135: allow a `with { ... }` clause on ambient module declarations, so the same file can be typed differently based on an import's attributes: declare module "*" with { type: "text" } { const d: string; export default d; } declare module "*" with { type: "bytes" } { const d: Uint8Array; export default d; } import text from "./f.txt" with { type: "text" }; // string import bytes from "./f.txt" with { type: "bytes" }; // Uint8Array - AST: optional Attributes member on ModuleDeclaration (schema + regen) - Parser: parse the with/assert clause on string-literal ambient modules - Binder: record attribute-keyed ambients separately with a distinct symbol name so they neither merge with nor shadow a plain `declare module "*"` - Checker: consult attribute-keyed ambients during module resolution when the import carries matching attributes, before file resolution; type:"json" and attribute-less imports are unaffected - Printer: round-trip the clause in declaration emit Adds conformance tests for text/bytes discrimination, real-module override, type:"json" left unaffected, pattern specificity, and declaration-emit round-trip. Refs microsoft/TypeScript#46135, denoland/deno#36124
Author
|
@microsoft-github-policy-service agree |
Contributor
There was a problem hiding this comment.
Pull request overview
Adds proof-of-concept support for ambient module declarations selected by import attributes.
Changes:
- Extends parsing, AST generation, printing, and declaration emit.
- Adds binder/checker resolution for attribute-keyed ambient modules.
- Adds conformance tests and baselines for matching, precedence, JSON, and emit.
Reviewed changes
Copilot reviewed 34 out of 37 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
testdata/tests/cases/conformance/importAttributes/ambientModuleImportAttributesTextAndBytes.ts |
Tests attribute-based type discrimination. |
testdata/tests/cases/conformance/importAttributes/ambientModuleImportAttributesPatternSpecificity.ts |
Tests wildcard specificity. |
testdata/tests/cases/conformance/importAttributes/ambientModuleImportAttributesOverridesRealModule.ts |
Tests precedence over resolved files. |
testdata/tests/cases/conformance/importAttributes/ambientModuleImportAttributesJsonUnaffected.ts |
Tests JSON and plain ambient behavior. |
testdata/tests/cases/conformance/importAttributes/ambientModuleImportAttributesDeclarationEmit.ts |
Tests declaration round-tripping. |
testdata/baselines/reference/conformance/ambientModuleImportAttributesTextAndBytes.types |
Type baseline. |
testdata/baselines/reference/conformance/ambientModuleImportAttributesTextAndBytes.symbols |
Symbol baseline. |
testdata/baselines/reference/conformance/ambientModuleImportAttributesTextAndBytes.errors.txt |
Diagnostic baseline. |
testdata/baselines/reference/conformance/ambientModuleImportAttributesPatternSpecificity.types |
Specificity type baseline. |
testdata/baselines/reference/conformance/ambientModuleImportAttributesPatternSpecificity.symbols |
Specificity symbol baseline. |
testdata/baselines/reference/conformance/ambientModuleImportAttributesOverridesRealModule.types |
Precedence type baseline. |
testdata/baselines/reference/conformance/ambientModuleImportAttributesOverridesRealModule.symbols |
Precedence symbol baseline. |
testdata/baselines/reference/conformance/ambientModuleImportAttributesOverridesRealModule.errors.txt |
Precedence diagnostic baseline. |
testdata/baselines/reference/conformance/ambientModuleImportAttributesJsonUnaffected.types |
JSON type baseline. |
testdata/baselines/reference/conformance/ambientModuleImportAttributesJsonUnaffected.symbols |
JSON symbol baseline. |
testdata/baselines/reference/conformance/ambientModuleImportAttributesDeclarationEmit.types |
Emit type baseline. |
testdata/baselines/reference/conformance/ambientModuleImportAttributesDeclarationEmit.symbols |
Emit symbol baseline. |
testdata/baselines/reference/conformance/ambientModuleImportAttributesDeclarationEmit.js |
Declaration output baseline. |
internal/transformers/declarations/transform.go |
Preserves attributes during declaration transforms. |
internal/printer/printer.go |
Prints module import attributes. |
internal/parser/reparser.go |
Updates module factory calls. |
internal/parser/parser.go |
Parses attributed ambient modules. |
internal/parser/jsdoc.go |
Updates JSDoc module construction. |
internal/ls/codeactions_fixmissingtypeannotation.go |
Updates namespace construction. |
internal/checker/nodebuilder_hover.go |
Updates hover module construction. |
internal/checker/checker.go |
Resolves matching attributed ambients. |
internal/binder/binder.go |
Records and uniquely binds attributed ambients. |
internal/ast/utilities.go |
Adds attribute mapping and key generation. |
internal/ast/ast.go |
Adds attributed ambient metadata. |
internal/ast/ast_generated.go |
Adds module attributes to the Go AST. |
internal/api/encoder/encoder_generated.go |
Encodes the new AST child. |
internal/api/encoder/decoder_generated.go |
Decodes the new AST child. |
_scripts/ast.json |
Extends the AST schema. |
_packages/native-preview/src/ast/visitor.generated.ts |
Visits module attributes. |
_packages/native-preview/src/ast/factory.generated.ts |
Creates and updates attributed modules. |
_packages/native-preview/src/ast/ast.generated.ts |
Exposes module attributes. |
_packages/native-preview/src/api/node/protocol.generated.ts |
Adds attributes to protocol children. |
Files not reviewed (3)
- internal/api/encoder/decoder_generated.go: Generated file
- internal/api/encoder/encoder_generated.go: Generated file
- internal/ast/ast_generated.go: Generated file
- Consult attribute-keyed ambients before the plain ambient module, so an
attributed import wins over a plain `declare module` of the same specifier,
not just over file resolution.
- Fix specificity so an exact attribute-keyed specifier is never overwritten by
a later wildcard; an exact match now wins immediately.
- Correct the specificity doc comment (a trailing `*.ext` pattern does not
outrank a bare `*`).
- Validate that attribute values on an ambient module declaration are string
literals (TS2858), mirroring import/export attribute validation.
- Quote names and values in the attribute key so distinct attribute sets cannot
collide into one symbol key (e.g. `{ a: "b,c=d" }` vs `{ a: "b", c: "d" }`).
Adds conformance tests for exact precedence, invalid attribute values, and
distinct attribute keys.
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
microsoft/TypeScript#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. I'm opening it against the Go port
because that is where I need it, but the design questions are language-level and
apply equally to the TypeScript compiler.
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. Today the only workaround is a custom
CompilerHostmodule-resolution hook, which is unavailable when a tool shells out to
tsgo(ortsc) as a standalone--projectbuild. Deno tracks the missing type support indenoland/deno#36124; microsoft/TypeScript#46135 is the sanctioned upstream
mechanism for expressing it in
.d.ts. 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 forkof TypeScript, where we handled text/bytes imports explicitly as special cases.
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 #46135 and the precedence discussion in
Implement Import Assertions (stage 3) TypeScript#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 #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
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 "*"leftunaffected; pattern specificity; and declaration-emit round-trip. The existing
suite passes with no baseline changes.
Disclosure: I used AI (Claude and Codex) to research and implement this change, but I reviewed it manually before opening a PR.