Skip to content

Commit b05dde7

Browse files
authored
Update type-only import semantics to allow type queries (#36092)
* Change type-only semantics to allow type queries * Don’t error using type-only import in ambient context * Fix default import * Fix namespace import * Update more baselines * Prevent circular resolution * Track const enum expression usage * Update baselines * Perf tuning 1 * Test commit for perf impact * Weave type-only alias declaration finding into alias resolution * Fix namespace import of type-only exported symbols * type-only exports do not contribute to the module object type * Update APIs * Fix enum casing, remove type-only conversion suggestion * Short circuit type-only checks in resolveEntityName faster * Fix casing in API * Remove unused parameter * Fix error on qualified names in type queries * Allow type-only imports in computed property names * Fix computed property names of types and abstract members * Remove unused util * Commit missing baselines * Rename “check” functions so as not to overload the word “check”
1 parent 0276e7f commit b05dde7

File tree

76 files changed

+1406
-326
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+1406
-326
lines changed

src/compiler/checker.ts

+131-156
Large diffs are not rendered by default.

src/compiler/commandLineParser.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -475,9 +475,9 @@ namespace ts {
475475
{
476476
name: "importsNotUsedAsValues",
477477
type: createMapFromTemplate({
478-
remove: importsNotUsedAsValues.Remove,
479-
preserve: importsNotUsedAsValues.Preserve,
480-
error: importsNotUsedAsValues.Error
478+
remove: ImportsNotUsedAsValues.Remove,
479+
preserve: ImportsNotUsedAsValues.Preserve,
480+
error: ImportsNotUsedAsValues.Error
481481
}),
482482
affectsEmit: true,
483483
affectsSemanticDiagnostics: true,

src/compiler/diagnosticMessages.json

+15-7
Original file line numberDiff line numberDiff line change
@@ -1055,11 +1055,15 @@
10551055
"category": "Error",
10561056
"code": 1359
10571057
},
1058-
"Type-only {0} must reference a type, but '{1}' is a value.": {
1058+
"Did you mean to parenthesize this function type?": {
1059+
"category": "Error",
1060+
"code": 1360
1061+
},
1062+
"'{0}' cannot be used as a value because it was imported using 'import type'.": {
10591063
"category": "Error",
10601064
"code": 1361
10611065
},
1062-
"Enum '{0}' cannot be used as a value because only its type has been imported.": {
1066+
"'{0}' cannot be used as a value because it was exported using 'export type'.": {
10631067
"category": "Error",
10641068
"code": 1362
10651069
},
@@ -1099,10 +1103,6 @@
10991103
"category": "Error",
11001104
"code": 1371
11011105
},
1102-
"This import may be converted to a type-only import.": {
1103-
"category": "Suggestion",
1104-
"code": 1372
1105-
},
11061106
"Convert to type-only import": {
11071107
"category": "Message",
11081108
"code": 1373
@@ -1115,9 +1115,17 @@
11151115
"category": "Error",
11161116
"code": 1375
11171117
},
1118+
"'{0}' was imported here.": {
1119+
"category": "Message",
1120+
"code": 1376
1121+
},
1122+
"'{0}' was exported here.": {
1123+
"category": "Message",
1124+
"code": 1377
1125+
},
11181126
"Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext' or 'system', and the 'target' option is set to 'es2017' or higher.": {
11191127
"category": "Error",
1120-
"code": 1376
1128+
"code": 1378
11211129
},
11221130
"The types of '{0}' are incompatible between these types.": {
11231131
"category": "Error",

src/compiler/transformers/ts.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -2778,8 +2778,8 @@ namespace ts {
27782778
// Elide the declaration if the import clause was elided.
27792779
const importClause = visitNode(node.importClause, visitImportClause, isImportClause);
27802780
return importClause ||
2781-
compilerOptions.importsNotUsedAsValues === importsNotUsedAsValues.Preserve ||
2782-
compilerOptions.importsNotUsedAsValues === importsNotUsedAsValues.Error
2781+
compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Preserve ||
2782+
compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error
27832783
? updateImportDeclaration(
27842784
node,
27852785
/*decorators*/ undefined,
@@ -2931,7 +2931,7 @@ namespace ts {
29312931
if (isExternalModuleImportEqualsDeclaration(node)) {
29322932
const isReferenced = resolver.isReferencedAliasDeclaration(node);
29332933
// If the alias is unreferenced but we want to keep the import, replace with 'import "mod"'.
2934-
if (!isReferenced && compilerOptions.importsNotUsedAsValues === importsNotUsedAsValues.Preserve) {
2934+
if (!isReferenced && compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Preserve) {
29352935
return setOriginalNode(
29362936
setTextRange(
29372937
createImportDeclaration(

src/compiler/types.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -2538,6 +2538,7 @@ namespace ts {
25382538
}
25392539

25402540
export type ImportOrExportSpecifier = ImportSpecifier | ExportSpecifier;
2541+
export type TypeOnlyCompatibleAliasDeclaration = ImportClause | NamespaceImport | ImportOrExportSpecifier;
25412542

25422543
/**
25432544
* This is either an `export =` or an `export default` declaration.
@@ -4062,7 +4063,8 @@ namespace ts {
40624063
instantiations?: Map<Type>; // Instantiations of generic type alias (undefined if non-generic)
40634064
inferredClassSymbol?: Map<TransientSymbol>; // Symbol of an inferred ES5 constructor function
40644065
mapper?: TypeMapper; // Type mapper for instantiation alias
4065-
referenced?: boolean; // True if alias symbol has been referenced as a value
4066+
referenced?: boolean; // True if alias symbol has been referenced as a value that can be emitted
4067+
constEnumReferenced?: boolean; // True if alias symbol resolves to a const enum and is referenced as a value ('referenced' will be false)
40664068
containingType?: UnionOrIntersectionType; // Containing union or intersection type for synthetic property
40674069
leftSpread?: Symbol; // Left source for synthetic spread property
40684070
rightSpread?: Symbol; // Right source for synthetic spread property
@@ -4085,6 +4087,7 @@ namespace ts {
40854087
deferralConstituents?: Type[]; // Calculated list of constituents for a deferred type
40864088
deferralParent?: Type; // Source union/intersection of a deferred type
40874089
cjsExportMerged?: Symbol; // Version of the symbol with all non export= exports merged with the export= target
4090+
typeOnlyDeclaration?: TypeOnlyCompatibleAliasDeclaration | false; // First resolved alias declaration that makes the symbol only usable in type constructs
40884091
}
40894092

40904093
/* @internal */
@@ -5046,7 +5049,7 @@ namespace ts {
50465049
/*@internal*/generateCpuProfile?: string;
50475050
/*@internal*/help?: boolean;
50485051
importHelpers?: boolean;
5049-
importsNotUsedAsValues?: importsNotUsedAsValues;
5052+
importsNotUsedAsValues?: ImportsNotUsedAsValues;
50505053
/*@internal*/init?: boolean;
50515054
inlineSourceMap?: boolean;
50525055
inlineSources?: boolean;
@@ -5168,7 +5171,7 @@ namespace ts {
51685171
ReactNative = 3
51695172
}
51705173

5171-
export const enum importsNotUsedAsValues {
5174+
export const enum ImportsNotUsedAsValues {
51725175
Remove,
51735176
Preserve,
51745177
Error

src/compiler/utilities.ts

+44-8
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,6 @@ namespace ts {
4444
return (symbol.flags & SymbolFlags.Transient) !== 0;
4545
}
4646

47-
export function isTypeOnlyAlias(symbol: Symbol): symbol is TransientSymbol & { immediateTarget: Symbol } {
48-
return isTransientSymbol(symbol) && !!symbol.immediateTarget;
49-
}
50-
51-
export function isTypeOnlyEnumAlias(symbol: Symbol): ReturnType<typeof isTypeOnlyAlias> {
52-
return isTypeOnlyAlias(symbol) && !!(symbol.immediateTarget.flags & SymbolFlags.Enum);
53-
}
54-
5547
const stringWriter = createSingleLineStringWriter();
5648

5749
function createSingleLineStringWriter(): EmitTextWriter {
@@ -1779,6 +1771,27 @@ namespace ts {
17791771
}
17801772
}
17811773

1774+
export function isPartOfTypeQuery(node: Node) {
1775+
while (node.kind === SyntaxKind.QualifiedName || node.kind === SyntaxKind.Identifier) {
1776+
node = node.parent;
1777+
}
1778+
return node.kind === SyntaxKind.TypeQuery;
1779+
}
1780+
1781+
export function isPartOfPossiblyValidTypeOrAbstractComputedPropertyName(node: Node) {
1782+
while (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.PropertyAccessExpression) {
1783+
node = node.parent;
1784+
}
1785+
if (node.kind !== SyntaxKind.ComputedPropertyName) {
1786+
return false;
1787+
}
1788+
if (hasModifier(node.parent, ModifierFlags.Abstract)) {
1789+
return true;
1790+
}
1791+
const containerKind = node.parent.parent.kind;
1792+
return containerKind === SyntaxKind.InterfaceDeclaration || containerKind === SyntaxKind.TypeLiteral;
1793+
}
1794+
17821795
export function isExternalModuleImportEqualsDeclaration(node: Node): node is ImportEqualsDeclaration & { moduleReference: ExternalModuleReference } {
17831796
return node.kind === SyntaxKind.ImportEqualsDeclaration && (<ImportEqualsDeclaration>node).moduleReference.kind === SyntaxKind.ExternalModuleReference;
17841797
}
@@ -2285,6 +2298,19 @@ namespace ts {
22852298
return node.kind === SyntaxKind.ImportDeclaration && !!node.importClause && !!node.importClause.name;
22862299
}
22872300

2301+
export function forEachImportClauseDeclaration<T>(node: ImportClause, action: (declaration: ImportClause | NamespaceImport | ImportSpecifier) => T | undefined): T | undefined {
2302+
if (node.name) {
2303+
const result = action(node);
2304+
if (result) return result;
2305+
}
2306+
if (node.namedBindings) {
2307+
const result = isNamespaceImport(node.namedBindings)
2308+
? action(node.namedBindings)
2309+
: forEach(node.namedBindings.elements, action);
2310+
if (result) return result;
2311+
}
2312+
}
2313+
22882314
export function hasQuestionToken(node: Node) {
22892315
if (node) {
22902316
switch (node.kind) {
@@ -2734,6 +2760,16 @@ namespace ts {
27342760
node.kind === SyntaxKind.PropertyAssignment && isAliasableExpression((node as PropertyAssignment).initializer);
27352761
}
27362762

2763+
export function getTypeOnlyCompatibleAliasDeclarationFromName(node: Identifier): TypeOnlyCompatibleAliasDeclaration | undefined {
2764+
switch (node.parent.kind) {
2765+
case SyntaxKind.ImportClause:
2766+
case SyntaxKind.ImportSpecifier:
2767+
case SyntaxKind.NamespaceImport:
2768+
case SyntaxKind.ExportSpecifier:
2769+
return node.parent as TypeOnlyCompatibleAliasDeclaration;
2770+
}
2771+
}
2772+
27372773
function isAliasableExpression(e: Expression) {
27382774
return isEntityNameExpression(e) || isClassExpression(e);
27392775
}

src/compiler/utilitiesPublic.ts

+6-7
Original file line numberDiff line numberDiff line change
@@ -1721,16 +1721,15 @@ namespace ts {
17211721
return isImportSpecifier(node) || isExportSpecifier(node);
17221722
}
17231723

1724-
export function isTypeOnlyImportOrExportName(node: Node): boolean {
1725-
if (node.kind !== SyntaxKind.Identifier) {
1726-
return false;
1727-
}
1728-
switch (node.parent.kind) {
1724+
export function isTypeOnlyImportOrExportDeclaration(node: Node): node is TypeOnlyCompatibleAliasDeclaration {
1725+
switch (node.kind) {
17291726
case SyntaxKind.ImportSpecifier:
17301727
case SyntaxKind.ExportSpecifier:
1731-
return (node.parent as ImportSpecifier | ExportSpecifier).parent.parent.isTypeOnly;
1728+
return (node as ImportOrExportSpecifier).parent.parent.isTypeOnly;
1729+
case SyntaxKind.NamespaceImport:
1730+
return (node as NamespaceImport).parent.isTypeOnly;
17321731
case SyntaxKind.ImportClause:
1733-
return (node.parent as ImportClause).isTypeOnly;
1732+
return (node as ImportClause).isTypeOnly;
17341733
default:
17351734
return false;
17361735
}

src/services/symbolDisplay.ts

-9
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@
22
namespace ts.SymbolDisplay {
33
// TODO(drosen): use contextual SemanticMeaning.
44
export function getSymbolKind(typeChecker: TypeChecker, symbol: Symbol, location: Node): ScriptElementKind {
5-
while (isTypeOnlyAlias(symbol)) {
6-
symbol = symbol.immediateTarget;
7-
}
8-
95
const result = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location);
106
if (result !== ScriptElementKind.unknown) {
117
return result;
@@ -125,11 +121,6 @@ namespace ts.SymbolDisplay {
125121
// TODO(drosen): Currently completion entry details passes the SemanticMeaning.All instead of using semanticMeaning of location
126122
export function getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker: TypeChecker, symbol: Symbol, sourceFile: SourceFile, enclosingDeclaration: Node | undefined,
127123
location: Node, semanticMeaning = getMeaningFromLocation(location), alias?: Symbol): SymbolDisplayPartsDocumentationAndSymbolKind {
128-
129-
while (isTypeOnlyAlias(symbol)) {
130-
symbol = symbol.immediateTarget;
131-
}
132-
133124
const displayParts: SymbolDisplayPart[] = [];
134125
let documentation: SymbolDisplayPart[] = [];
135126
let tags: JSDocTagInfo[] = [];

tests/baselines/reference/ambient.js

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//// [tests/cases/conformance/externalModules/typeOnly/ambient.ts] ////
2+
3+
//// [a.ts]
4+
export class A { a!: string }
5+
6+
//// [b.ts]
7+
import type { A } from './a';
8+
declare class B extends A {}
9+
declare namespace ns {
10+
class C extends A {}
11+
}
12+
13+
14+
//// [a.js]
15+
"use strict";
16+
exports.__esModule = true;
17+
var A = /** @class */ (function () {
18+
function A() {
19+
}
20+
return A;
21+
}());
22+
exports.A = A;
23+
//// [b.js]
24+
"use strict";
25+
exports.__esModule = true;
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
=== /a.ts ===
2+
export class A { a!: string }
3+
>A : Symbol(A, Decl(a.ts, 0, 0))
4+
>a : Symbol(A.a, Decl(a.ts, 0, 16))
5+
6+
=== /b.ts ===
7+
import type { A } from './a';
8+
>A : Symbol(A, Decl(b.ts, 0, 13))
9+
10+
declare class B extends A {}
11+
>B : Symbol(B, Decl(b.ts, 0, 29))
12+
>A : Symbol(A, Decl(b.ts, 0, 13))
13+
14+
declare namespace ns {
15+
>ns : Symbol(ns, Decl(b.ts, 1, 28))
16+
17+
class C extends A {}
18+
>C : Symbol(C, Decl(b.ts, 2, 22))
19+
>A : Symbol(A, Decl(b.ts, 0, 13))
20+
}
21+
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
=== /a.ts ===
2+
export class A { a!: string }
3+
>A : A
4+
>a : string
5+
6+
=== /b.ts ===
7+
import type { A } from './a';
8+
>A : A
9+
10+
declare class B extends A {}
11+
>B : B
12+
>A : A
13+
14+
declare namespace ns {
15+
>ns : typeof ns
16+
17+
class C extends A {}
18+
>C : C
19+
>A : A
20+
}
21+

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

+4-3
Original file line numberDiff line numberDiff line change
@@ -1547,6 +1547,7 @@ declare namespace ts {
15471547
name: Identifier;
15481548
}
15491549
export type ImportOrExportSpecifier = ImportSpecifier | ExportSpecifier;
1550+
export type TypeOnlyCompatibleAliasDeclaration = ImportClause | NamespaceImport | ImportOrExportSpecifier;
15501551
/**
15511552
* This is either an `export =` or an `export default` declaration.
15521553
* Unless `isExportEquals` is set, this node was parsed as an `export default`.
@@ -2651,7 +2652,7 @@ declare namespace ts {
26512652
experimentalDecorators?: boolean;
26522653
forceConsistentCasingInFileNames?: boolean;
26532654
importHelpers?: boolean;
2654-
importsNotUsedAsValues?: importsNotUsedAsValues;
2655+
importsNotUsedAsValues?: ImportsNotUsedAsValues;
26552656
inlineSourceMap?: boolean;
26562657
inlineSources?: boolean;
26572658
isolatedModules?: boolean;
@@ -2750,7 +2751,7 @@ declare namespace ts {
27502751
React = 2,
27512752
ReactNative = 3
27522753
}
2753-
export enum importsNotUsedAsValues {
2754+
export enum ImportsNotUsedAsValues {
27542755
Remove = 0,
27552756
Preserve = 1,
27562757
Error = 2
@@ -3739,7 +3740,7 @@ declare namespace ts {
37393740
function isTemplateLiteralToken(node: Node): node is TemplateLiteralToken;
37403741
function isTemplateMiddleOrTemplateTail(node: Node): node is TemplateMiddle | TemplateTail;
37413742
function isImportOrExportSpecifier(node: Node): node is ImportSpecifier | ExportSpecifier;
3742-
function isTypeOnlyImportOrExportName(node: Node): boolean;
3743+
function isTypeOnlyImportOrExportDeclaration(node: Node): node is TypeOnlyCompatibleAliasDeclaration;
37433744
function isStringTextContainingNode(node: Node): node is StringLiteral | TemplateLiteralToken;
37443745
function isModifier(node: Node): node is Modifier;
37453746
function isEntityName(node: Node): node is EntityName;

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

+4-3
Original file line numberDiff line numberDiff line change
@@ -1547,6 +1547,7 @@ declare namespace ts {
15471547
name: Identifier;
15481548
}
15491549
export type ImportOrExportSpecifier = ImportSpecifier | ExportSpecifier;
1550+
export type TypeOnlyCompatibleAliasDeclaration = ImportClause | NamespaceImport | ImportOrExportSpecifier;
15501551
/**
15511552
* This is either an `export =` or an `export default` declaration.
15521553
* Unless `isExportEquals` is set, this node was parsed as an `export default`.
@@ -2651,7 +2652,7 @@ declare namespace ts {
26512652
experimentalDecorators?: boolean;
26522653
forceConsistentCasingInFileNames?: boolean;
26532654
importHelpers?: boolean;
2654-
importsNotUsedAsValues?: importsNotUsedAsValues;
2655+
importsNotUsedAsValues?: ImportsNotUsedAsValues;
26552656
inlineSourceMap?: boolean;
26562657
inlineSources?: boolean;
26572658
isolatedModules?: boolean;
@@ -2750,7 +2751,7 @@ declare namespace ts {
27502751
React = 2,
27512752
ReactNative = 3
27522753
}
2753-
export enum importsNotUsedAsValues {
2754+
export enum ImportsNotUsedAsValues {
27542755
Remove = 0,
27552756
Preserve = 1,
27562757
Error = 2
@@ -3739,7 +3740,7 @@ declare namespace ts {
37393740
function isTemplateLiteralToken(node: Node): node is TemplateLiteralToken;
37403741
function isTemplateMiddleOrTemplateTail(node: Node): node is TemplateMiddle | TemplateTail;
37413742
function isImportOrExportSpecifier(node: Node): node is ImportSpecifier | ExportSpecifier;
3742-
function isTypeOnlyImportOrExportName(node: Node): boolean;
3743+
function isTypeOnlyImportOrExportDeclaration(node: Node): node is TypeOnlyCompatibleAliasDeclaration;
37433744
function isStringTextContainingNode(node: Node): node is StringLiteral | TemplateLiteralToken;
37443745
function isModifier(node: Node): node is Modifier;
37453746
function isEntityName(node: Node): node is EntityName;

0 commit comments

Comments
 (0)