Skip to content

POC: ambient module declarations keyed on import attributes - #63901

Open
Bartek Iwańczuk (bartlomieju) wants to merge 1 commit into
microsoft:mainfrom
bartlomieju:feat/ambient-module-import-attributes
Open

POC: ambient module declarations keyed on import attributes#63901
Bartek Iwańczuk (bartlomieju) wants to merge 1 commit into
microsoft:mainfrom
bartlomieju:feat/ambient-module-import-attributes

Conversation

@bartlomieju

@bartlomieju Bartek Iwańczuk (bartlomieju) commented Aug 20, 2026

Copy link
Copy Markdown

This is a proof-of-concept implementation of the syntax proposed in #46135,
ambient module declarations that are keyed on import attributes:

declare module "*" with { type: "text" } {
  const data: string;
  export default data;
}
declare module "*" with { type: "bytes" } {
  const data: Uint8Array<ArrayBuffer>;
  export default data;
}

import text from "./file.txt" with { type: "text" };   // string
import bytes from "./file.txt" with { type: "bytes" };  // Uint8Array

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 way
to type these imports. Extension-based ambients
(declare module "*.txt") get close, but they cannot distinguish two imports of
the same file by attribute, which is exactly what type: "text" vs
type: "bytes" needs. This has surfaced now because we're removing our fork of
TypeScript, where we handled text/bytes imports explicitly as special cases.

Closes #46135

What this does

  • Parser: a string-literal-named ambient module may carry a with { ... } clause
    between the name and the body, reusing the existing import-attributes parser.
    declare global is excluded.
  • AST: ModuleDeclaration gains an optional Attributes (ImportAttributes)
    member (schema plus regen).
  • Binder: an attribute-keyed ambient module is recorded on the source file in a
    new AttributedAmbientModules list and given a distinct symbol name
    ("*" with {type=text}), so it neither merges with nor shadows a plain
    declare module "*", and does not participate in normal pattern/ambient
    resolution.
  • Checker: when a module specifier carries import attributes,
    resolveExternalModule consults the attribute-keyed declarations before file
    resolution. 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.
  • Printer: declaration emit round-trips the clause.

Precedence and semantics chosen (all up for debate)

  • An attribute-keyed ambient match wins over the specifier's own file resolution.
    This is what lets import x from "./real.ts" with { type: "text" } be typed by
    the ambient rather than by ./real.ts, and it is the capability plain
    extension 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 an
    import whose attributes match no attribute-keyed declaration falls straight
    through to normal resolution. resolveJsonModule behavior is unaffected.
  • Attribute matching is exact set-equality on string-valued attributes to start
    with, as suggested in the issue.

Open questions for the discussion

  • Real-module precedence: should an attribute-keyed ambient always beat file
    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.
  • Pattern specificity: this reuses pattern ambient module rules, where
    specificity is the prefix length before *. That means *.svg does not
    outrank * (both have an empty prefix), and the tie is resolved by declaration
    order, same as today's pattern ambients. Should trailing patterns like *.svg
    be made more specific?
  • resolveJsonModule interaction: should a user-written
    declare module "*" with { type: "json" } be allowed to override the built-in
    JSON typing, or should type: "json" remain reserved?
  • Scope: dynamic import("x", { with: { ... } }) and import-type nodes are not
    wired 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/importAttributes
cover: text vs bytes discrimination on the same file; an attribute-keyed ambient
winning over a real .ts module; type: "json" and a plain declare 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.

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.
Copilot AI balanced review requested due to automatic review settings August 20, 2026 08:55
@typescript-automation typescript-automation Bot added the For Uncommitted Bug PR for untriaged, rejected, closed or missing bug label Aug 20, 2026
@github-project-automation github-project-automation Bot moved this to Not started in PR Backlog Aug 20, 2026
@typescript-automation

Copy link
Copy Markdown

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
@typescript-automation

Copy link
Copy Markdown

This PR doesn't have any linked issues. Please open an issue that references this PR. From there we can discuss and prioritise.

@typescript-automation

Copy link
Copy Markdown

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 PatternAmbientModules makes 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, while ls/autoimport/fix.go:551 always emits attributes: 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})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

For Uncommitted Bug PR for untriaged, rejected, closed or missing bug

Projects

Status: Not started

Development

Successfully merging this pull request may close these issues.

Ambient Module Declarations for Import Attributes (formerly known as Import Assertions)

3 participants