From 419dba1714cb23615cb6ebb93b0b6691db247584 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 28 Jan 2020 14:53:00 -0800 Subject: [PATCH] Fix import= something type only from a module --- src/compiler/checker.ts | 36 +++++++--- src/compiler/utilities.ts | 5 +- .../reference/importEquals1.errors.txt | 45 ++++++++++++ tests/baselines/reference/importEquals1.js | 69 +++++++++++++++++++ .../baselines/reference/importEquals1.symbols | 54 +++++++++++++++ tests/baselines/reference/importEquals1.types | 58 ++++++++++++++++ .../reference/importEquals2.errors.txt | 17 +++++ tests/baselines/reference/importEquals2.js | 39 +++++++++++ .../baselines/reference/importEquals2.symbols | 21 ++++++ tests/baselines/reference/importEquals2.types | 24 +++++++ .../externalModules/typeOnly/importEquals1.ts | 28 ++++++++ .../externalModules/typeOnly/importEquals2.ts | 13 ++++ 12 files changed, 397 insertions(+), 12 deletions(-) create mode 100644 tests/baselines/reference/importEquals1.errors.txt create mode 100644 tests/baselines/reference/importEquals1.js create mode 100644 tests/baselines/reference/importEquals1.symbols create mode 100644 tests/baselines/reference/importEquals1.types create mode 100644 tests/baselines/reference/importEquals2.errors.txt create mode 100644 tests/baselines/reference/importEquals2.js create mode 100644 tests/baselines/reference/importEquals2.symbols create mode 100644 tests/baselines/reference/importEquals2.types create mode 100644 tests/cases/conformance/externalModules/typeOnly/importEquals1.ts create mode 100644 tests/cases/conformance/externalModules/typeOnly/importEquals2.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b64f751fda0..c50fdbff2c4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2182,7 +2182,12 @@ namespace ts { function getTargetOfImportEqualsDeclaration(node: ImportEqualsDeclaration, dontResolveAlias: boolean): Symbol | undefined { if (node.moduleReference.kind === SyntaxKind.ExternalModuleReference) { - return resolveExternalModuleSymbol(resolveExternalModuleName(node, getExternalModuleImportEqualsDeclarationExpression(node))); + const immediate = resolveExternalModuleName(node, getExternalModuleImportEqualsDeclarationExpression(node)); + const resolved = resolveExternalModuleSymbol(immediate); + if (!markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, immediate)) { + markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, resolved); + } + return resolved; } return getSymbolOfPartOfRightHandSideOfImportEquals(node.moduleReference, dontResolveAlias); } @@ -2278,7 +2283,9 @@ namespace ts { else if (hasSyntheticDefault) { // per emit behavior, a synthetic default overrides a "real" .default member if `__esModule` is not present const resolved = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); - if (!isTypeOnly) markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, resolved); + if (!isTypeOnly && !markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, moduleSymbol)) { + markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, resolved); + } return resolved; } if (!isTypeOnly) markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, exportDefaultSymbol); @@ -2289,8 +2296,11 @@ namespace ts { function getTargetOfNamespaceImport(node: NamespaceImport, dontResolveAlias: boolean): Symbol | undefined { const moduleSpecifier = node.parent.parent.moduleSpecifier; const isTypeOnly = markSymbolOfAliasDeclarationIfTypeOnly(node); - const resolved = resolveESModuleSymbol(resolveExternalModuleName(node, moduleSpecifier), moduleSpecifier, dontResolveAlias, /*suppressUsageError*/ false); - if (!isTypeOnly) markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, resolved); + const immediate = resolveExternalModuleName(node, moduleSpecifier); + const resolved = resolveESModuleSymbol(immediate, moduleSpecifier, dontResolveAlias, /*suppressUsageError*/ false); + if (!isTypeOnly && !markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, immediate)) { + markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, resolved); + } return resolved; } @@ -2459,7 +2469,9 @@ namespace ts { function getTargetOfExportAssignment(node: ExportAssignment | BinaryExpression, dontResolveAlias: boolean): Symbol | undefined { const expression = (isExportAssignment(node) ? node.expression : node.right) as EntityNameExpression | ClassExpression; - return getTargetOfAliasLikeExpression(expression, dontResolveAlias); + const resolved = getTargetOfAliasLikeExpression(expression, dontResolveAlias); + markSymbolOfAliasDeclarationIfResolvesToTypeOnly(node, resolved); + return resolved; } function getTargetOfAliasLikeExpression(expression: Expression, dontResolveAlias: boolean) { @@ -2556,14 +2568,18 @@ namespace ts { return links.target; } - function markSymbolOfAliasDeclarationIfResolvesToTypeOnly(aliasDeclaration: TypeOnlyCompatibleAliasDeclaration | undefined, resolvesToSymbol: Symbol | undefined) { - if (!aliasDeclaration || !resolvesToSymbol) return; + function markSymbolOfAliasDeclarationIfResolvesToTypeOnly(aliasDeclaration: Declaration | undefined, resolvesToSymbol: Symbol | undefined): boolean { + if (!aliasDeclaration || !resolvesToSymbol) return false; const sourceSymbol = getSymbolOfNode(aliasDeclaration); const links = getSymbolLinks(sourceSymbol); if (links.typeOnlyDeclaration === undefined) { + resolvesToSymbol = resolvesToSymbol.exports?.get(InternalSymbolName.ExportEquals) ?? resolvesToSymbol; const typeOnly = find(resolvesToSymbol.declarations, isTypeOnlyImportOrExportDeclaration); - links.typeOnlyDeclaration = typeOnly ?? getSymbolLinks(resolvesToSymbol).typeOnlyDeclaration ?? false; + const decl = typeOnly ?? getSymbolLinks(resolvesToSymbol).typeOnlyDeclaration ?? false; + links.typeOnlyDeclaration = decl; + return !!decl; } + return false; } function markSymbolOfAliasDeclarationIfTypeOnly(aliasDeclaration: TypeOnlyCompatibleAliasDeclaration): boolean { @@ -2711,8 +2727,8 @@ namespace ts { throw Debug.assertNever(name, "Unknown entity name kind."); } Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); - if (isIdentifier(name) && symbol.flags & SymbolFlags.Alias) { - markSymbolOfAliasDeclarationIfResolvesToTypeOnly(getTypeOnlyCompatibleAliasDeclarationFromName(name), symbol); + if (isIdentifier(name) && (symbol.flags & SymbolFlags.Alias || name.parent.kind === SyntaxKind.ExportAssignment)) { + markSymbolOfAliasDeclarationIfResolvesToTypeOnly(getAliasDeclarationFromName(name), symbol); } return (symbol.flags & meaning) || dontResolveAlias ? symbol : resolveAlias(symbol); } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 863076f6783..830e28fc4ae 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2766,13 +2766,14 @@ namespace ts { node.kind === SyntaxKind.PropertyAssignment && isAliasableExpression((node as PropertyAssignment).initializer); } - export function getTypeOnlyCompatibleAliasDeclarationFromName(node: Identifier): TypeOnlyCompatibleAliasDeclaration | undefined { + export function getAliasDeclarationFromName(node: Identifier): Declaration | undefined { switch (node.parent.kind) { case SyntaxKind.ImportClause: case SyntaxKind.ImportSpecifier: case SyntaxKind.NamespaceImport: case SyntaxKind.ExportSpecifier: - return node.parent as TypeOnlyCompatibleAliasDeclaration; + case SyntaxKind.ExportAssignment: + return node.parent as Declaration; } } diff --git a/tests/baselines/reference/importEquals1.errors.txt b/tests/baselines/reference/importEquals1.errors.txt new file mode 100644 index 00000000000..8585ff46594 --- /dev/null +++ b/tests/baselines/reference/importEquals1.errors.txt @@ -0,0 +1,45 @@ +/d.ts(2,5): error TS1361: 'types' cannot be used as a value because it was imported using 'import type'. +/e.ts(2,5): error TS1361: 'types' cannot be used as a value because it was imported using 'import type'. +/f.ts(2,5): error TS1361: 'types' cannot be used as a value because it was imported using 'import type'. +/g.ts(2,5): error TS1361: 'types' cannot be used as a value because it was imported using 'import type'. + + +==== /a.ts (0 errors) ==== + export class A {} + +==== /b.ts (0 errors) ==== + import type * as types from './a'; + export = types; // Error + +==== /c.ts (0 errors) ==== + import * as types from './a'; + export = types; + +==== /d.ts (1 errors) ==== + import types from './b'; + new types.A(); // Error + ~~~~~ +!!! error TS1361: 'types' cannot be used as a value because it was imported using 'import type'. +!!! related TS1376 /b.ts:1:13: 'types' was imported here. + +==== /e.ts (1 errors) ==== + import types = require('./b'); + new types.A(); // Error + ~~~~~ +!!! error TS1361: 'types' cannot be used as a value because it was imported using 'import type'. +!!! related TS1376 /b.ts:1:13: 'types' was imported here. + +==== /f.ts (1 errors) ==== + import * as types from './b'; + new types.A(); // Error + ~~~~~ +!!! error TS1361: 'types' cannot be used as a value because it was imported using 'import type'. +!!! related TS1376 /b.ts:1:13: 'types' was imported here. + +==== /g.ts (1 errors) ==== + import type types from './c' + new types.A(); // Error + ~~~~~ +!!! error TS1361: 'types' cannot be used as a value because it was imported using 'import type'. +!!! related TS1376 /g.ts:1:8: 'types' was imported here. + \ No newline at end of file diff --git a/tests/baselines/reference/importEquals1.js b/tests/baselines/reference/importEquals1.js new file mode 100644 index 00000000000..1789b73d58f --- /dev/null +++ b/tests/baselines/reference/importEquals1.js @@ -0,0 +1,69 @@ +//// [tests/cases/conformance/externalModules/typeOnly/importEquals1.ts] //// + +//// [a.ts] +export class A {} + +//// [b.ts] +import type * as types from './a'; +export = types; // Error + +//// [c.ts] +import * as types from './a'; +export = types; + +//// [d.ts] +import types from './b'; +new types.A(); // Error + +//// [e.ts] +import types = require('./b'); +new types.A(); // Error + +//// [f.ts] +import * as types from './b'; +new types.A(); // Error + +//// [g.ts] +import type types from './c' +new types.A(); // Error + + +//// [a.js] +"use strict"; +exports.__esModule = true; +var A = /** @class */ (function () { + function A() { + } + return A; +}()); +exports.A = A; +//// [b.js] +"use strict"; +module.exports = types; +//// [c.js] +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +var types = __importStar(require("./a")); +module.exports = types; +//// [d.js] +"use strict"; +exports.__esModule = true; +new types.A(); // Error +//// [e.js] +"use strict"; +exports.__esModule = true; +new types.A(); // Error +//// [f.js] +"use strict"; +exports.__esModule = true; +new types.A(); // Error +//// [g.js] +"use strict"; +exports.__esModule = true; +new types.A(); // Error diff --git a/tests/baselines/reference/importEquals1.symbols b/tests/baselines/reference/importEquals1.symbols new file mode 100644 index 00000000000..195e8b6e371 --- /dev/null +++ b/tests/baselines/reference/importEquals1.symbols @@ -0,0 +1,54 @@ +=== /a.ts === +export class A {} +>A : Symbol(A, Decl(a.ts, 0, 0)) + +=== /b.ts === +import type * as types from './a'; +>types : Symbol(types, Decl(b.ts, 0, 11)) + +export = types; // Error +>types : Symbol(types, Decl(b.ts, 0, 11)) + +=== /c.ts === +import * as types from './a'; +>types : Symbol(types, Decl(c.ts, 0, 6)) + +export = types; +>types : Symbol(types, Decl(c.ts, 0, 6)) + +=== /d.ts === +import types from './b'; +>types : Symbol(types, Decl(d.ts, 0, 6)) + +new types.A(); // Error +>types.A : Symbol(types.A, Decl(a.ts, 0, 0)) +>types : Symbol(types, Decl(d.ts, 0, 6)) +>A : Symbol(types.A, Decl(a.ts, 0, 0)) + +=== /e.ts === +import types = require('./b'); +>types : Symbol(types, Decl(e.ts, 0, 0)) + +new types.A(); // Error +>types.A : Symbol(types.A, Decl(a.ts, 0, 0)) +>types : Symbol(types, Decl(e.ts, 0, 0)) +>A : Symbol(types.A, Decl(a.ts, 0, 0)) + +=== /f.ts === +import * as types from './b'; +>types : Symbol(types, Decl(f.ts, 0, 6)) + +new types.A(); // Error +>types.A : Symbol(types.A, Decl(a.ts, 0, 0)) +>types : Symbol(types, Decl(f.ts, 0, 6)) +>A : Symbol(types.A, Decl(a.ts, 0, 0)) + +=== /g.ts === +import type types from './c' +>types : Symbol(types, Decl(g.ts, 0, 6)) + +new types.A(); // Error +>types.A : Symbol(types.A, Decl(a.ts, 0, 0)) +>types : Symbol(types, Decl(g.ts, 0, 6)) +>A : Symbol(types.A, Decl(a.ts, 0, 0)) + diff --git a/tests/baselines/reference/importEquals1.types b/tests/baselines/reference/importEquals1.types new file mode 100644 index 00000000000..f9bb4f2697a --- /dev/null +++ b/tests/baselines/reference/importEquals1.types @@ -0,0 +1,58 @@ +=== /a.ts === +export class A {} +>A : A + +=== /b.ts === +import type * as types from './a'; +>types : typeof types + +export = types; // Error +>types : typeof types + +=== /c.ts === +import * as types from './a'; +>types : typeof types + +export = types; +>types : typeof types + +=== /d.ts === +import types from './b'; +>types : typeof types + +new types.A(); // Error +>new types.A() : types.A +>types.A : typeof types.A +>types : typeof types +>A : typeof types.A + +=== /e.ts === +import types = require('./b'); +>types : typeof types + +new types.A(); // Error +>new types.A() : types.A +>types.A : typeof types.A +>types : typeof types +>A : typeof types.A + +=== /f.ts === +import * as types from './b'; +>types : typeof types + +new types.A(); // Error +>new types.A() : types.A +>types.A : typeof types.A +>types : typeof types +>A : typeof types.A + +=== /g.ts === +import type types from './c' +>types : any + +new types.A(); // Error +>new types.A() : types.A +>types.A : typeof types.A +>types : typeof types +>A : typeof types.A + diff --git a/tests/baselines/reference/importEquals2.errors.txt b/tests/baselines/reference/importEquals2.errors.txt new file mode 100644 index 00000000000..a347e9c04bb --- /dev/null +++ b/tests/baselines/reference/importEquals2.errors.txt @@ -0,0 +1,17 @@ +/c.ts(2,7): error TS2339: Property 'A' does not exist on type 'typeof import("/a")'. + + +==== /c.ts (1 errors) ==== + import a = require('./b'); + new a.A(); // Error + ~ +!!! error TS2339: Property 'A' does not exist on type 'typeof import("/a")'. + +==== /a.ts (0 errors) ==== + class A {} + export type { A } + +==== /b.ts (0 errors) ==== + import * as a from './a'; + export = a; + \ No newline at end of file diff --git a/tests/baselines/reference/importEquals2.js b/tests/baselines/reference/importEquals2.js new file mode 100644 index 00000000000..05401a6cb53 --- /dev/null +++ b/tests/baselines/reference/importEquals2.js @@ -0,0 +1,39 @@ +//// [tests/cases/conformance/externalModules/typeOnly/importEquals2.ts] //// + +//// [a.ts] +class A {} +export type { A } + +//// [b.ts] +import * as a from './a'; +export = a; + +//// [c.ts] +import a = require('./b'); +new a.A(); // Error + + +//// [a.js] +"use strict"; +exports.__esModule = true; +var A = /** @class */ (function () { + function A() { + } + return A; +}()); +//// [b.js] +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +var a = __importStar(require("./a")); +module.exports = a; +//// [c.js] +"use strict"; +exports.__esModule = true; +var a = require("./b"); +new a.A(); // Error diff --git a/tests/baselines/reference/importEquals2.symbols b/tests/baselines/reference/importEquals2.symbols new file mode 100644 index 00000000000..84b5dbba668 --- /dev/null +++ b/tests/baselines/reference/importEquals2.symbols @@ -0,0 +1,21 @@ +=== /c.ts === +import a = require('./b'); +>a : Symbol(a, Decl(c.ts, 0, 0)) + +new a.A(); // Error +>a : Symbol(a, Decl(c.ts, 0, 0)) + +=== /a.ts === +class A {} +>A : Symbol(A, Decl(a.ts, 0, 0)) + +export type { A } +>A : Symbol(A, Decl(a.ts, 1, 13)) + +=== /b.ts === +import * as a from './a'; +>a : Symbol(a, Decl(b.ts, 0, 6)) + +export = a; +>a : Symbol(a, Decl(b.ts, 0, 6)) + diff --git a/tests/baselines/reference/importEquals2.types b/tests/baselines/reference/importEquals2.types new file mode 100644 index 00000000000..e02fd8846a3 --- /dev/null +++ b/tests/baselines/reference/importEquals2.types @@ -0,0 +1,24 @@ +=== /c.ts === +import a = require('./b'); +>a : typeof a + +new a.A(); // Error +>new a.A() : any +>a.A : any +>a : typeof a +>A : any + +=== /a.ts === +class A {} +>A : A + +export type { A } +>A : A + +=== /b.ts === +import * as a from './a'; +>a : typeof a + +export = a; +>a : typeof a + diff --git a/tests/cases/conformance/externalModules/typeOnly/importEquals1.ts b/tests/cases/conformance/externalModules/typeOnly/importEquals1.ts new file mode 100644 index 00000000000..e4ea63dcb6f --- /dev/null +++ b/tests/cases/conformance/externalModules/typeOnly/importEquals1.ts @@ -0,0 +1,28 @@ +// @esModuleInterop: true + +// @Filename: /a.ts +export class A {} + +// @Filename: /b.ts +import type * as types from './a'; +export = types; // Error + +// @Filename: /c.ts +import * as types from './a'; +export = types; + +// @Filename: /d.ts +import types from './b'; +new types.A(); // Error + +// @Filename: /e.ts +import types = require('./b'); +new types.A(); // Error + +// @Filename: /f.ts +import * as types from './b'; +new types.A(); // Error + +// @Filename: /g.ts +import type types from './c' +new types.A(); // Error diff --git a/tests/cases/conformance/externalModules/typeOnly/importEquals2.ts b/tests/cases/conformance/externalModules/typeOnly/importEquals2.ts new file mode 100644 index 00000000000..42f8318c706 --- /dev/null +++ b/tests/cases/conformance/externalModules/typeOnly/importEquals2.ts @@ -0,0 +1,13 @@ +// @esModuleInterop: true + +// @Filename: /a.ts +class A {} +export type { A } + +// @Filename: /b.ts +import * as a from './a'; +export = a; + +// @Filename: /c.ts +import a = require('./b'); +new a.A(); // Error