Skip to content

Commit cc6c2d5

Browse files
committed
fix(55994): add support for type-checking import attributes in static imports
1 parent 01a51d2 commit cc6c2d5

10 files changed

+278
-2
lines changed

src/compiler/checker.ts

+34
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ import {
407407
IdentifierTypePredicate,
408408
idText,
409409
IfStatement,
410+
ImportAttribute,
410411
ImportAttributes,
411412
ImportCall,
412413
ImportClause,
@@ -2178,6 +2179,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
21782179
var deferredGlobalImportMetaType: ObjectType;
21792180
var deferredGlobalImportMetaExpressionType: ObjectType;
21802181
var deferredGlobalImportCallOptionsType: ObjectType | undefined;
2182+
var deferredGlobalImportAttributesType: ObjectType | undefined;
21812183
var deferredGlobalDisposableType: ObjectType | undefined;
21822184
var deferredGlobalAsyncDisposableType: ObjectType | undefined;
21832185
var deferredGlobalExtractSymbol: Symbol | undefined;
@@ -11533,6 +11535,25 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1153311535
return widenTypeForVariableLikeDeclaration(getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true, CheckMode.Normal), declaration, reportErrors);
1153411536
}
1153511537

11538+
function getTypeFromImportAttributes(node: ImportAttributes): Type {
11539+
const links = getNodeLinks(node);
11540+
if (!links.resolvedType) {
11541+
const symbol = createSymbol(SymbolFlags.ObjectLiteral, InternalSymbolName.ImportAttributes);
11542+
const members = createSymbolTable();
11543+
forEach(node.elements, attr => {
11544+
const member = createSymbol(SymbolFlags.Property, isIdentifier(attr.name) ? attr.name.escapedText : escapeLeadingUnderscores(attr.name.text));
11545+
member.parent = symbol;
11546+
member.links.type = checkImportAttribute(attr);
11547+
member.links.target = member;
11548+
members.set(member.escapedName, member);
11549+
});
11550+
const type = createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray);
11551+
type.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.NonInferrableType;
11552+
links.resolvedType = type;
11553+
}
11554+
return links.resolvedType;
11555+
}
11556+
1153611557
function isGlobalSymbolConstructor(node: Node) {
1153711558
const symbol = getSymbolOfNode(node);
1153811559
const globalSymbol = getGlobalESSymbolConstructorTypeSymbol(/*reportErrors*/ false);
@@ -16357,6 +16378,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1635716378
return (deferredGlobalImportCallOptionsType ||= getGlobalType("ImportCallOptions" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType;
1635816379
}
1635916380

16381+
function getGlobalImportAttributesType(reportErrors: boolean) {
16382+
return (deferredGlobalImportAttributesType ||= getGlobalType("ImportAttributes" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType;
16383+
}
16384+
1636016385
function getGlobalESSymbolConstructorSymbol(reportErrors: boolean): Symbol | undefined {
1636116386
return deferredGlobalESSymbolConstructorSymbol ||= getGlobalValueSymbol("Symbol" as __String, reportErrors);
1636216387
}
@@ -45827,6 +45852,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4582745852
function checkImportAttributes(declaration: ImportDeclaration | ExportDeclaration) {
4582845853
const node = declaration.attributes;
4582945854
if (node) {
45855+
const importAttributesType = getGlobalImportAttributesType(/*reportErrors*/ true);
45856+
if (importAttributesType !== emptyObjectType) {
45857+
checkTypeAssignableTo(getTypeFromImportAttributes(node), getNullableType(importAttributesType, TypeFlags.Undefined), node);
45858+
}
45859+
4583045860
const validForTypeAttributes = isExclusivelyTypeOnlyImportOrExport(declaration);
4583145861
const override = getResolutionModeOverride(node, validForTypeAttributes ? grammarErrorOnNode : undefined);
4583245862
const isImportAttributes = declaration.attributes.token === SyntaxKind.WithKeyword;
@@ -45856,6 +45886,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4585645886
}
4585745887
}
4585845888

45889+
function checkImportAttribute(node: ImportAttribute) {
45890+
return getRegularTypeOfLiteralType(checkExpressionCached(node.value));
45891+
}
45892+
4585945893
function checkImportDeclaration(node: ImportDeclaration) {
4586045894
if (checkGrammarModuleElementContext(node, isInJSFile(node) ? Diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_module : Diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_namespace_or_module)) {
4586145895
// If we hit an import declaration in an illegal context, just bail out to avoid cascading errors.

src/compiler/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -5971,6 +5971,7 @@ export const enum InternalSymbolName {
59715971
Default = "default", // Default export symbol (technically not wholly internal, but included here for usability)
59725972
This = "this",
59735973
InstantiationExpression = "__instantiationExpression", // Instantiation expressions
5974+
ImportAttributes = "__importAttributes",
59745975
}
59755976

59765977
/**

tests/baselines/reference/api/typescript.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -7069,6 +7069,7 @@ declare namespace ts {
70697069
Default = "default",
70707070
This = "this",
70717071
InstantiationExpression = "__instantiationExpression",
7072+
ImportAttributes = "__importAttributes",
70727073
}
70737074
/**
70747075
* This represents a string whose leading underscore have been escaped by adding extra leading underscores.

tests/baselines/reference/importAssertionNonstring.errors.txt

+29-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,29 @@
1+
mod.mts(1,37): error TS2322: Type '{ field: 0; }' is not assignable to type 'ImportAttributes'.
2+
Property 'field' is incompatible with index signature.
3+
Type 'number' is not assignable to type 'string'.
14
mod.mts(1,52): error TS2837: Import assertion values must be string literal expressions.
25
mod.mts(3,52): error TS2837: Import assertion values must be string literal expressions.
6+
mod.mts(5,37): error TS2322: Type '{ field: RegExp; }' is not assignable to type 'ImportAttributes'.
7+
Property 'field' is incompatible with index signature.
8+
Type 'RegExp' is not assignable to type 'string'.
39
mod.mts(5,52): error TS2837: Import assertion values must be string literal expressions.
10+
mod.mts(7,37): error TS2322: Type '{ field: string[]; }' is not assignable to type 'ImportAttributes'.
11+
Property 'field' is incompatible with index signature.
12+
Type 'string[]' is not assignable to type 'string'.
413
mod.mts(7,52): error TS2837: Import assertion values must be string literal expressions.
14+
mod.mts(9,37): error TS2322: Type '{ field: { a: number; }; }' is not assignable to type 'ImportAttributes'.
15+
Property 'field' is incompatible with index signature.
16+
Type '{ a: number; }' is not assignable to type 'string'.
517
mod.mts(9,52): error TS2837: Import assertion values must be string literal expressions.
618
mod.mts(11,66): error TS2837: Import assertion values must be string literal expressions.
719

820

9-
==== mod.mts (6 errors) ====
21+
==== mod.mts (10 errors) ====
1022
import * as thing1 from "./mod.mjs" assert {field: 0};
23+
~~~~~~~~~~~~~~~~~
24+
!!! error TS2322: Type '{ field: 0; }' is not assignable to type 'ImportAttributes'.
25+
!!! error TS2322: Property 'field' is incompatible with index signature.
26+
!!! error TS2322: Type 'number' is not assignable to type 'string'.
1127
~
1228
!!! error TS2837: Import assertion values must be string literal expressions.
1329

@@ -16,14 +32,26 @@ mod.mts(11,66): error TS2837: Import assertion values must be string literal exp
1632
!!! error TS2837: Import assertion values must be string literal expressions.
1733

1834
import * as thing3 from "./mod.mjs" assert {field: /a/g};
35+
~~~~~~~~~~~~~~~~~~~~
36+
!!! error TS2322: Type '{ field: RegExp; }' is not assignable to type 'ImportAttributes'.
37+
!!! error TS2322: Property 'field' is incompatible with index signature.
38+
!!! error TS2322: Type 'RegExp' is not assignable to type 'string'.
1939
~~~~
2040
!!! error TS2837: Import assertion values must be string literal expressions.
2141

2242
import * as thing4 from "./mod.mjs" assert {field: ["a"]};
43+
~~~~~~~~~~~~~~~~~~~~~
44+
!!! error TS2322: Type '{ field: string[]; }' is not assignable to type 'ImportAttributes'.
45+
!!! error TS2322: Property 'field' is incompatible with index signature.
46+
!!! error TS2322: Type 'string[]' is not assignable to type 'string'.
2347
~~~~~
2448
!!! error TS2837: Import assertion values must be string literal expressions.
2549

2650
import * as thing5 from "./mod.mjs" assert {field: { a: 0 }};
51+
~~~~~~~~~~~~~~~~~~~~~~~~
52+
!!! error TS2322: Type '{ field: { a: number; }; }' is not assignable to type 'ImportAttributes'.
53+
!!! error TS2322: Property 'field' is incompatible with index signature.
54+
!!! error TS2322: Type '{ a: number; }' is not assignable to type 'string'.
2755
~~~~~~~~
2856
!!! error TS2837: Import assertion values must be string literal expressions.
2957

tests/baselines/reference/importAttributes6.errors.txt

+29-1
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,53 @@
1+
mod.mts(1,37): error TS2322: Type '{ field: 0; }' is not assignable to type 'ImportAttributes'.
2+
Property 'field' is incompatible with index signature.
3+
Type 'number' is not assignable to type 'string'.
14
mod.mts(1,51): error TS2858: Import attribute values must be string literal expressions.
25
mod.mts(2,51): error TS2858: Import attribute values must be string literal expressions.
6+
mod.mts(3,37): error TS2322: Type '{ field: RegExp; }' is not assignable to type 'ImportAttributes'.
7+
Property 'field' is incompatible with index signature.
8+
Type 'RegExp' is not assignable to type 'string'.
39
mod.mts(3,51): error TS2858: Import attribute values must be string literal expressions.
10+
mod.mts(4,37): error TS2322: Type '{ field: string[]; }' is not assignable to type 'ImportAttributes'.
11+
Property 'field' is incompatible with index signature.
12+
Type 'string[]' is not assignable to type 'string'.
413
mod.mts(4,51): error TS2858: Import attribute values must be string literal expressions.
14+
mod.mts(5,37): error TS2322: Type '{ field: { a: number; }; }' is not assignable to type 'ImportAttributes'.
15+
Property 'field' is incompatible with index signature.
16+
Type '{ a: number; }' is not assignable to type 'string'.
517
mod.mts(5,51): error TS2858: Import attribute values must be string literal expressions.
618
mod.mts(6,65): error TS2858: Import attribute values must be string literal expressions.
719

820

9-
==== mod.mts (6 errors) ====
21+
==== mod.mts (10 errors) ====
1022
import * as thing1 from "./mod.mjs" with { field: 0 };
23+
~~~~~~~~~~~~~~~~~
24+
!!! error TS2322: Type '{ field: 0; }' is not assignable to type 'ImportAttributes'.
25+
!!! error TS2322: Property 'field' is incompatible with index signature.
26+
!!! error TS2322: Type 'number' is not assignable to type 'string'.
1127
~
1228
!!! error TS2858: Import attribute values must be string literal expressions.
1329
import * as thing2 from "./mod.mjs" with { field: `a` };
1430
~~~
1531
!!! error TS2858: Import attribute values must be string literal expressions.
1632
import * as thing3 from "./mod.mjs" with { field: /a/g };
33+
~~~~~~~~~~~~~~~~~~~~
34+
!!! error TS2322: Type '{ field: RegExp; }' is not assignable to type 'ImportAttributes'.
35+
!!! error TS2322: Property 'field' is incompatible with index signature.
36+
!!! error TS2322: Type 'RegExp' is not assignable to type 'string'.
1737
~~~~
1838
!!! error TS2858: Import attribute values must be string literal expressions.
1939
import * as thing4 from "./mod.mjs" with { field: ["a"] };
40+
~~~~~~~~~~~~~~~~~~~~~
41+
!!! error TS2322: Type '{ field: string[]; }' is not assignable to type 'ImportAttributes'.
42+
!!! error TS2322: Property 'field' is incompatible with index signature.
43+
!!! error TS2322: Type 'string[]' is not assignable to type 'string'.
2044
~~~~~
2145
!!! error TS2858: Import attribute values must be string literal expressions.
2246
import * as thing5 from "./mod.mjs" with { field: { a: 0 } };
47+
~~~~~~~~~~~~~~~~~~~~~~~~
48+
!!! error TS2322: Type '{ field: { a: number; }; }' is not assignable to type 'ImportAttributes'.
49+
!!! error TS2322: Property 'field' is incompatible with index signature.
50+
!!! error TS2322: Type '{ a: number; }' is not assignable to type 'string'.
2351
~~~~~~~~
2452
!!! error TS2858: Import attribute values must be string literal expressions.
2553
import * as thing6 from "./mod.mjs" with { type: "json", field: 0..toString() };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
b.ts(7,27): error TS2322: Type '{ type: "not-json"; }' is not assignable to type 'ImportAttributes'.
2+
Types of property 'type' are incompatible.
3+
Type '"not-json"' is not assignable to type '"json"'.
4+
b.ts(11,25): error TS2322: Type '{ with: { type: "not-json"; }; }' is not assignable to type 'ImportCallOptions'.
5+
The types of 'with.type' are incompatible between these types.
6+
Type '"not-json"' is not assignable to type '"json"'.
7+
8+
9+
==== ./a.ts (0 errors) ====
10+
export default {};
11+
12+
==== ./b.ts (2 errors) ====
13+
declare global {
14+
interface ImportAttributes {
15+
type: "json"
16+
}
17+
}
18+
19+
import * as ns from "./a" with { type: "not-json" };
20+
~~~~~~~~~~~~~~~~~~~~~~~~~
21+
!!! error TS2322: Type '{ type: "not-json"; }' is not assignable to type 'ImportAttributes'.
22+
!!! error TS2322: Types of property 'type' are incompatible.
23+
!!! error TS2322: Type '"not-json"' is not assignable to type '"json"'.
24+
void ns;
25+
26+
async function f() {
27+
await import("./a", {
28+
~
29+
with: {
30+
~~~~~~~~~~~~~~~
31+
type: "not-json",
32+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
33+
},
34+
~~~~~~~~~~
35+
});
36+
~~~~~
37+
!!! error TS2322: Type '{ with: { type: "not-json"; }; }' is not assignable to type 'ImportCallOptions'.
38+
!!! error TS2322: The types of 'with.type' are incompatible between these types.
39+
!!! error TS2322: Type '"not-json"' is not assignable to type '"json"'.
40+
}
41+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//// [tests/cases/conformance/importAttributes/importAttributes9.ts] ////
2+
3+
//// [a.ts]
4+
export default {};
5+
6+
//// [b.ts]
7+
declare global {
8+
interface ImportAttributes {
9+
type: "json"
10+
}
11+
}
12+
13+
import * as ns from "./a" with { type: "not-json" };
14+
void ns;
15+
16+
async function f() {
17+
await import("./a", {
18+
with: {
19+
type: "not-json",
20+
},
21+
});
22+
}
23+
24+
25+
//// [a.js]
26+
export default {};
27+
//// [b.js]
28+
import * as ns from "./a" with { type: "not-json" };
29+
void ns;
30+
async function f() {
31+
await import("./a", {
32+
with: {
33+
type: "not-json",
34+
},
35+
});
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//// [tests/cases/conformance/importAttributes/importAttributes9.ts] ////
2+
3+
=== ./a.ts ===
4+
5+
export default {};
6+
7+
=== ./b.ts ===
8+
declare global {
9+
>global : Symbol(global, Decl(b.ts, 0, 0))
10+
11+
interface ImportAttributes {
12+
>ImportAttributes : Symbol(ImportAttributes, Decl(lib.es5.d.ts, --, --), Decl(b.ts, 0, 16))
13+
14+
type: "json"
15+
>type : Symbol(ImportAttributes.type, Decl(b.ts, 1, 33))
16+
}
17+
}
18+
19+
import * as ns from "./a" with { type: "not-json" };
20+
>ns : Symbol(ns, Decl(b.ts, 6, 6))
21+
22+
void ns;
23+
>ns : Symbol(ns, Decl(b.ts, 6, 6))
24+
25+
async function f() {
26+
>f : Symbol(f, Decl(b.ts, 7, 8))
27+
28+
await import("./a", {
29+
>"./a" : Symbol(ns, Decl(a.ts, 0, 0))
30+
31+
with: {
32+
>with : Symbol(with, Decl(b.ts, 10, 25))
33+
34+
type: "not-json",
35+
>type : Symbol(type, Decl(b.ts, 11, 15))
36+
37+
},
38+
});
39+
}
40+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//// [tests/cases/conformance/importAttributes/importAttributes9.ts] ////
2+
3+
=== ./a.ts ===
4+
export default {};
5+
>{} : {}
6+
7+
=== ./b.ts ===
8+
declare global {
9+
>global : any
10+
11+
interface ImportAttributes {
12+
type: "json"
13+
>type : "json"
14+
}
15+
}
16+
17+
import * as ns from "./a" with { type: "not-json" };
18+
>ns : typeof ns
19+
>type : any
20+
21+
void ns;
22+
>void ns : undefined
23+
>ns : typeof ns
24+
25+
async function f() {
26+
>f : () => Promise<void>
27+
28+
await import("./a", {
29+
>await import("./a", { with: { type: "not-json", }, }) : typeof ns
30+
>import("./a", { with: { type: "not-json", }, }) : Promise<typeof ns>
31+
>"./a" : "./a"
32+
>{ with: { type: "not-json", }, } : { with: { type: "not-json"; }; }
33+
34+
with: {
35+
>with : { type: "not-json"; }
36+
>{ type: "not-json", } : { type: "not-json"; }
37+
38+
type: "not-json",
39+
>type : "not-json"
40+
>"not-json" : "not-json"
41+
42+
},
43+
});
44+
}
45+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// @module: esnext
2+
// @target: esnext
3+
// @filename: ./a.ts
4+
export default {};
5+
6+
// @filename: ./b.ts
7+
declare global {
8+
interface ImportAttributes {
9+
type: "json"
10+
}
11+
}
12+
13+
import * as ns from "./a" with { type: "not-json" };
14+
void ns;
15+
16+
async function f() {
17+
await import("./a", {
18+
with: {
19+
type: "not-json",
20+
},
21+
});
22+
}

0 commit comments

Comments
 (0)