Skip to content

Commit

Permalink
Update type-only import semantics to allow type queries (#36092)
Browse files Browse the repository at this point in the history
* 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”
  • Loading branch information
andrewbranch authored Jan 23, 2020
1 parent 0276e7f commit b05dde7
Show file tree
Hide file tree
Showing 76 changed files with 1,406 additions and 326 deletions.
287 changes: 131 additions & 156 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,9 +475,9 @@ namespace ts {
{
name: "importsNotUsedAsValues",
type: createMapFromTemplate({
remove: importsNotUsedAsValues.Remove,
preserve: importsNotUsedAsValues.Preserve,
error: importsNotUsedAsValues.Error
remove: ImportsNotUsedAsValues.Remove,
preserve: ImportsNotUsedAsValues.Preserve,
error: ImportsNotUsedAsValues.Error
}),
affectsEmit: true,
affectsSemanticDiagnostics: true,
Expand Down
22 changes: 15 additions & 7 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1055,11 +1055,15 @@
"category": "Error",
"code": 1359
},
"Type-only {0} must reference a type, but '{1}' is a value.": {
"Did you mean to parenthesize this function type?": {
"category": "Error",
"code": 1360
},
"'{0}' cannot be used as a value because it was imported using 'import type'.": {
"category": "Error",
"code": 1361
},
"Enum '{0}' cannot be used as a value because only its type has been imported.": {
"'{0}' cannot be used as a value because it was exported using 'export type'.": {
"category": "Error",
"code": 1362
},
Expand Down Expand Up @@ -1099,10 +1103,6 @@
"category": "Error",
"code": 1371
},
"This import may be converted to a type-only import.": {
"category": "Suggestion",
"code": 1372
},
"Convert to type-only import": {
"category": "Message",
"code": 1373
Expand All @@ -1115,9 +1115,17 @@
"category": "Error",
"code": 1375
},
"'{0}' was imported here.": {
"category": "Message",
"code": 1376
},
"'{0}' was exported here.": {
"category": "Message",
"code": 1377
},
"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.": {
"category": "Error",
"code": 1376
"code": 1378
},
"The types of '{0}' are incompatible between these types.": {
"category": "Error",
Expand Down
6 changes: 3 additions & 3 deletions src/compiler/transformers/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2778,8 +2778,8 @@ namespace ts {
// Elide the declaration if the import clause was elided.
const importClause = visitNode(node.importClause, visitImportClause, isImportClause);
return importClause ||
compilerOptions.importsNotUsedAsValues === importsNotUsedAsValues.Preserve ||
compilerOptions.importsNotUsedAsValues === importsNotUsedAsValues.Error
compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Preserve ||
compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error
? updateImportDeclaration(
node,
/*decorators*/ undefined,
Expand Down Expand Up @@ -2931,7 +2931,7 @@ namespace ts {
if (isExternalModuleImportEqualsDeclaration(node)) {
const isReferenced = resolver.isReferencedAliasDeclaration(node);
// If the alias is unreferenced but we want to keep the import, replace with 'import "mod"'.
if (!isReferenced && compilerOptions.importsNotUsedAsValues === importsNotUsedAsValues.Preserve) {
if (!isReferenced && compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Preserve) {
return setOriginalNode(
setTextRange(
createImportDeclaration(
Expand Down
9 changes: 6 additions & 3 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2538,6 +2538,7 @@ namespace ts {
}

export type ImportOrExportSpecifier = ImportSpecifier | ExportSpecifier;
export type TypeOnlyCompatibleAliasDeclaration = ImportClause | NamespaceImport | ImportOrExportSpecifier;

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

/* @internal */
Expand Down Expand Up @@ -5046,7 +5049,7 @@ namespace ts {
/*@internal*/generateCpuProfile?: string;
/*@internal*/help?: boolean;
importHelpers?: boolean;
importsNotUsedAsValues?: importsNotUsedAsValues;
importsNotUsedAsValues?: ImportsNotUsedAsValues;
/*@internal*/init?: boolean;
inlineSourceMap?: boolean;
inlineSources?: boolean;
Expand Down Expand Up @@ -5168,7 +5171,7 @@ namespace ts {
ReactNative = 3
}

export const enum importsNotUsedAsValues {
export const enum ImportsNotUsedAsValues {
Remove,
Preserve,
Error
Expand Down
52 changes: 44 additions & 8 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,6 @@ namespace ts {
return (symbol.flags & SymbolFlags.Transient) !== 0;
}

export function isTypeOnlyAlias(symbol: Symbol): symbol is TransientSymbol & { immediateTarget: Symbol } {
return isTransientSymbol(symbol) && !!symbol.immediateTarget;
}

export function isTypeOnlyEnumAlias(symbol: Symbol): ReturnType<typeof isTypeOnlyAlias> {
return isTypeOnlyAlias(symbol) && !!(symbol.immediateTarget.flags & SymbolFlags.Enum);
}

const stringWriter = createSingleLineStringWriter();

function createSingleLineStringWriter(): EmitTextWriter {
Expand Down Expand Up @@ -1779,6 +1771,27 @@ namespace ts {
}
}

export function isPartOfTypeQuery(node: Node) {
while (node.kind === SyntaxKind.QualifiedName || node.kind === SyntaxKind.Identifier) {
node = node.parent;
}
return node.kind === SyntaxKind.TypeQuery;
}

export function isPartOfPossiblyValidTypeOrAbstractComputedPropertyName(node: Node) {
while (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.PropertyAccessExpression) {
node = node.parent;
}
if (node.kind !== SyntaxKind.ComputedPropertyName) {
return false;
}
if (hasModifier(node.parent, ModifierFlags.Abstract)) {
return true;
}
const containerKind = node.parent.parent.kind;
return containerKind === SyntaxKind.InterfaceDeclaration || containerKind === SyntaxKind.TypeLiteral;
}

export function isExternalModuleImportEqualsDeclaration(node: Node): node is ImportEqualsDeclaration & { moduleReference: ExternalModuleReference } {
return node.kind === SyntaxKind.ImportEqualsDeclaration && (<ImportEqualsDeclaration>node).moduleReference.kind === SyntaxKind.ExternalModuleReference;
}
Expand Down Expand Up @@ -2285,6 +2298,19 @@ namespace ts {
return node.kind === SyntaxKind.ImportDeclaration && !!node.importClause && !!node.importClause.name;
}

export function forEachImportClauseDeclaration<T>(node: ImportClause, action: (declaration: ImportClause | NamespaceImport | ImportSpecifier) => T | undefined): T | undefined {
if (node.name) {
const result = action(node);
if (result) return result;
}
if (node.namedBindings) {
const result = isNamespaceImport(node.namedBindings)
? action(node.namedBindings)
: forEach(node.namedBindings.elements, action);
if (result) return result;
}
}

export function hasQuestionToken(node: Node) {
if (node) {
switch (node.kind) {
Expand Down Expand Up @@ -2734,6 +2760,16 @@ namespace ts {
node.kind === SyntaxKind.PropertyAssignment && isAliasableExpression((node as PropertyAssignment).initializer);
}

export function getTypeOnlyCompatibleAliasDeclarationFromName(node: Identifier): TypeOnlyCompatibleAliasDeclaration | undefined {
switch (node.parent.kind) {
case SyntaxKind.ImportClause:
case SyntaxKind.ImportSpecifier:
case SyntaxKind.NamespaceImport:
case SyntaxKind.ExportSpecifier:
return node.parent as TypeOnlyCompatibleAliasDeclaration;
}
}

function isAliasableExpression(e: Expression) {
return isEntityNameExpression(e) || isClassExpression(e);
}
Expand Down
13 changes: 6 additions & 7 deletions src/compiler/utilitiesPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1721,16 +1721,15 @@ namespace ts {
return isImportSpecifier(node) || isExportSpecifier(node);
}

export function isTypeOnlyImportOrExportName(node: Node): boolean {
if (node.kind !== SyntaxKind.Identifier) {
return false;
}
switch (node.parent.kind) {
export function isTypeOnlyImportOrExportDeclaration(node: Node): node is TypeOnlyCompatibleAliasDeclaration {
switch (node.kind) {
case SyntaxKind.ImportSpecifier:
case SyntaxKind.ExportSpecifier:
return (node.parent as ImportSpecifier | ExportSpecifier).parent.parent.isTypeOnly;
return (node as ImportOrExportSpecifier).parent.parent.isTypeOnly;
case SyntaxKind.NamespaceImport:
return (node as NamespaceImport).parent.isTypeOnly;
case SyntaxKind.ImportClause:
return (node.parent as ImportClause).isTypeOnly;
return (node as ImportClause).isTypeOnly;
default:
return false;
}
Expand Down
9 changes: 0 additions & 9 deletions src/services/symbolDisplay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@
namespace ts.SymbolDisplay {
// TODO(drosen): use contextual SemanticMeaning.
export function getSymbolKind(typeChecker: TypeChecker, symbol: Symbol, location: Node): ScriptElementKind {
while (isTypeOnlyAlias(symbol)) {
symbol = symbol.immediateTarget;
}

const result = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location);
if (result !== ScriptElementKind.unknown) {
return result;
Expand Down Expand Up @@ -125,11 +121,6 @@ namespace ts.SymbolDisplay {
// TODO(drosen): Currently completion entry details passes the SemanticMeaning.All instead of using semanticMeaning of location
export function getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker: TypeChecker, symbol: Symbol, sourceFile: SourceFile, enclosingDeclaration: Node | undefined,
location: Node, semanticMeaning = getMeaningFromLocation(location), alias?: Symbol): SymbolDisplayPartsDocumentationAndSymbolKind {

while (isTypeOnlyAlias(symbol)) {
symbol = symbol.immediateTarget;
}

const displayParts: SymbolDisplayPart[] = [];
let documentation: SymbolDisplayPart[] = [];
let tags: JSDocTagInfo[] = [];
Expand Down
25 changes: 25 additions & 0 deletions tests/baselines/reference/ambient.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//// [tests/cases/conformance/externalModules/typeOnly/ambient.ts] ////

//// [a.ts]
export class A { a!: string }

//// [b.ts]
import type { A } from './a';
declare class B extends A {}
declare namespace ns {
class C extends A {}
}


//// [a.js]
"use strict";
exports.__esModule = true;
var A = /** @class */ (function () {
function A() {
}
return A;
}());
exports.A = A;
//// [b.js]
"use strict";
exports.__esModule = true;
21 changes: 21 additions & 0 deletions tests/baselines/reference/ambient.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
=== /a.ts ===
export class A { a!: string }
>A : Symbol(A, Decl(a.ts, 0, 0))
>a : Symbol(A.a, Decl(a.ts, 0, 16))

=== /b.ts ===
import type { A } from './a';
>A : Symbol(A, Decl(b.ts, 0, 13))

declare class B extends A {}
>B : Symbol(B, Decl(b.ts, 0, 29))
>A : Symbol(A, Decl(b.ts, 0, 13))

declare namespace ns {
>ns : Symbol(ns, Decl(b.ts, 1, 28))

class C extends A {}
>C : Symbol(C, Decl(b.ts, 2, 22))
>A : Symbol(A, Decl(b.ts, 0, 13))
}

21 changes: 21 additions & 0 deletions tests/baselines/reference/ambient.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
=== /a.ts ===
export class A { a!: string }
>A : A
>a : string

=== /b.ts ===
import type { A } from './a';
>A : A

declare class B extends A {}
>B : B
>A : A

declare namespace ns {
>ns : typeof ns

class C extends A {}
>C : C
>A : A
}

7 changes: 4 additions & 3 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1547,6 +1547,7 @@ declare namespace ts {
name: Identifier;
}
export type ImportOrExportSpecifier = ImportSpecifier | ExportSpecifier;
export type TypeOnlyCompatibleAliasDeclaration = ImportClause | NamespaceImport | ImportOrExportSpecifier;
/**
* This is either an `export =` or an `export default` declaration.
* Unless `isExportEquals` is set, this node was parsed as an `export default`.
Expand Down Expand Up @@ -2651,7 +2652,7 @@ declare namespace ts {
experimentalDecorators?: boolean;
forceConsistentCasingInFileNames?: boolean;
importHelpers?: boolean;
importsNotUsedAsValues?: importsNotUsedAsValues;
importsNotUsedAsValues?: ImportsNotUsedAsValues;
inlineSourceMap?: boolean;
inlineSources?: boolean;
isolatedModules?: boolean;
Expand Down Expand Up @@ -2750,7 +2751,7 @@ declare namespace ts {
React = 2,
ReactNative = 3
}
export enum importsNotUsedAsValues {
export enum ImportsNotUsedAsValues {
Remove = 0,
Preserve = 1,
Error = 2
Expand Down Expand Up @@ -3739,7 +3740,7 @@ declare namespace ts {
function isTemplateLiteralToken(node: Node): node is TemplateLiteralToken;
function isTemplateMiddleOrTemplateTail(node: Node): node is TemplateMiddle | TemplateTail;
function isImportOrExportSpecifier(node: Node): node is ImportSpecifier | ExportSpecifier;
function isTypeOnlyImportOrExportName(node: Node): boolean;
function isTypeOnlyImportOrExportDeclaration(node: Node): node is TypeOnlyCompatibleAliasDeclaration;
function isStringTextContainingNode(node: Node): node is StringLiteral | TemplateLiteralToken;
function isModifier(node: Node): node is Modifier;
function isEntityName(node: Node): node is EntityName;
Expand Down
7 changes: 4 additions & 3 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1547,6 +1547,7 @@ declare namespace ts {
name: Identifier;
}
export type ImportOrExportSpecifier = ImportSpecifier | ExportSpecifier;
export type TypeOnlyCompatibleAliasDeclaration = ImportClause | NamespaceImport | ImportOrExportSpecifier;
/**
* This is either an `export =` or an `export default` declaration.
* Unless `isExportEquals` is set, this node was parsed as an `export default`.
Expand Down Expand Up @@ -2651,7 +2652,7 @@ declare namespace ts {
experimentalDecorators?: boolean;
forceConsistentCasingInFileNames?: boolean;
importHelpers?: boolean;
importsNotUsedAsValues?: importsNotUsedAsValues;
importsNotUsedAsValues?: ImportsNotUsedAsValues;
inlineSourceMap?: boolean;
inlineSources?: boolean;
isolatedModules?: boolean;
Expand Down Expand Up @@ -2750,7 +2751,7 @@ declare namespace ts {
React = 2,
ReactNative = 3
}
export enum importsNotUsedAsValues {
export enum ImportsNotUsedAsValues {
Remove = 0,
Preserve = 1,
Error = 2
Expand Down Expand Up @@ -3739,7 +3740,7 @@ declare namespace ts {
function isTemplateLiteralToken(node: Node): node is TemplateLiteralToken;
function isTemplateMiddleOrTemplateTail(node: Node): node is TemplateMiddle | TemplateTail;
function isImportOrExportSpecifier(node: Node): node is ImportSpecifier | ExportSpecifier;
function isTypeOnlyImportOrExportName(node: Node): boolean;
function isTypeOnlyImportOrExportDeclaration(node: Node): node is TypeOnlyCompatibleAliasDeclaration;
function isStringTextContainingNode(node: Node): node is StringLiteral | TemplateLiteralToken;
function isModifier(node: Node): node is Modifier;
function isEntityName(node: Node): node is EntityName;
Expand Down
Loading

0 comments on commit b05dde7

Please sign in to comment.