From c24f75fd73e37b843870d9478c818c732a065291 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Fri, 12 Dec 2014 11:48:46 -0800 Subject: [PATCH 1/2] defer decision whether import used on the right side of import declaration should be considered referenced --- src/compiler/checker.ts | 70 +++++++++++++++---- src/compiler/types.ts | 1 + .../importedAliasesInTypePositions.js | 55 +++++++++++++++ .../importedAliasesInTypePositions.types | 40 +++++++++++ .../importedAliasesInTypePositions.ts | 19 +++++ 5 files changed, 170 insertions(+), 15 deletions(-) create mode 100644 tests/baselines/reference/importedAliasesInTypePositions.js create mode 100644 tests/baselines/reference/importedAliasesInTypePositions.types create mode 100644 tests/cases/compiler/importedAliasesInTypePositions.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9cff537b07f..24f5a2f528d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4734,14 +4734,47 @@ module ts { return type; } } + /*Transitively mark all linked imports as referenced*/ + function markLinkedImportsAsReferenced(node: ImportDeclaration): void { + var nodeLinks = getNodeLinks(node); + while (nodeLinks.importOnRightSide) { + var rightSide = nodeLinks.importOnRightSide; + nodeLinks.importOnRightSide = undefined; + + getSymbolLinks(rightSide).referenced = true; + nodeLinks = getNodeLinks(rightSide.declarations[0]) + } + } function checkIdentifier(node: Identifier): Type { var symbol = getResolvedSymbol(node); if (symbol.flags & SymbolFlags.Import) { - // Mark the import as referenced so that we emit it in the final .js file. - // exception: identifiers that appear in type queries, const enums, modules that contain only const enums - getSymbolLinks(symbol).referenced = getSymbolLinks(symbol).referenced || (!isInTypeQuery(node) && !isConstEnumOrConstEnumOnlyModule(resolveImport(symbol))); + var symbolLinks = getSymbolLinks(symbol); + if (!symbolLinks.referenced) { + var importOrExportAssignment = getLeftSideOfImportOrExportAssignment(node); + + // decision about whether import is referenced can be made now if + // - import that are used anywhere except right side of import declarations + // - imports that are used on the right side of exported import declarations + // for other cases defer decision until the check of left side + if (!importOrExportAssignment || + (importOrExportAssignment.flags & NodeFlags.Export) || + (importOrExportAssignment.kind === SyntaxKind.ExportAssignment)) { + // Mark the import as referenced so that we emit it in the final .js file. + // exception: identifiers that appear in type queries, const enums, modules that contain only const enums + symbolLinks.referenced = !isInTypeQuery(node) && !isConstEnumOrConstEnumOnlyModule(resolveImport(symbol)); + } + else { + var nodeLinks = getNodeLinks(importOrExportAssignment); + Debug.assert(!nodeLinks.importOnRightSide); + nodeLinks.importOnRightSide = symbol; + } + } + + if (symbolLinks.referenced) { + markLinkedImportsAsReferenced(symbol.declarations[0]); + } } checkCollisionWithCapturedSuperVariable(node, node); @@ -8872,6 +8905,8 @@ module ts { if (symbol && symbol.flags & SymbolFlags.Import) { // Mark the import as referenced so that we emit it in the final .js file. getSymbolLinks(symbol).referenced = true; + // mark any import declarations that depend upon this import as referenced + markLinkedImportsAsReferenced(symbol.declarations[0]) } } @@ -9097,19 +9132,24 @@ module ts { return false; } + function getLeftSideOfImportOrExportAssignment(nodeOnRightSide: EntityName): ImportDeclaration | ExportAssignment { + while (nodeOnRightSide.parent.kind === SyntaxKind.QualifiedName) { + nodeOnRightSide = nodeOnRightSide.parent; + } + + if (nodeOnRightSide.parent.kind === SyntaxKind.ImportDeclaration) { + return (nodeOnRightSide.parent).moduleReference === nodeOnRightSide && nodeOnRightSide.parent; + } + + if (nodeOnRightSide.parent.kind === SyntaxKind.ExportAssignment) { + return (nodeOnRightSide.parent).exportName === nodeOnRightSide && nodeOnRightSide.parent; + } + + return undefined; + } + function isInRightSideOfImportOrExportAssignment(node: EntityName) { - while (node.parent.kind === SyntaxKind.QualifiedName) { - node = node.parent; - } - - if (node.parent.kind === SyntaxKind.ImportDeclaration) { - return (node.parent).moduleReference === node; - } - if (node.parent.kind === SyntaxKind.ExportAssignment) { - return (node.parent).exportName === node; - } - - return false; + return getLeftSideOfImportOrExportAssignment(node) !== undefined; } function isRightSideOfQualifiedNameOrPropertyAccess(node: Node) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index a77961da586..9ba8f3aa21c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1216,6 +1216,7 @@ module ts { isVisible?: boolean; // Is this node visible localModuleName?: string; // Local name for module instance assignmentChecks?: Map; // Cache of assignment checks + importOnRightSide?: Symbol; // for import declarations - import that appear on the right side } export const enum TypeFlags { diff --git a/tests/baselines/reference/importedAliasesInTypePositions.js b/tests/baselines/reference/importedAliasesInTypePositions.js new file mode 100644 index 00000000000..fb56b1ac0d7 --- /dev/null +++ b/tests/baselines/reference/importedAliasesInTypePositions.js @@ -0,0 +1,55 @@ +//// [tests/cases/compiler/importedAliasesInTypePositions.ts] //// + +//// [file1.ts] +export module elaborate.nested.mod.name { + export class ReferredTo { + doSomething(): void { + } + } +} + +//// [file2.ts] +import RT_ALIAS = require("file1"); +import ReferredTo = RT_ALIAS.elaborate.nested.mod.name.ReferredTo; + +export module ImportingModule { + class UsesReferredType { + constructor(private referred: ReferredTo) { } + } +} + +//// [file1.js] +define(["require", "exports"], function (require, exports) { + var elaborate; + (function (elaborate) { + var nested; + (function (nested) { + var mod; + (function (mod) { + var name; + (function (name) { + var ReferredTo = (function () { + function ReferredTo() { + } + ReferredTo.prototype.doSomething = function () { + }; + return ReferredTo; + })(); + name.ReferredTo = ReferredTo; + })(name = mod.name || (mod.name = {})); + })(mod = nested.mod || (nested.mod = {})); + })(nested = elaborate.nested || (elaborate.nested = {})); + })(elaborate = exports.elaborate || (exports.elaborate = {})); +}); +//// [file2.js] +define(["require", "exports"], function (require, exports) { + var ImportingModule; + (function (ImportingModule) { + var UsesReferredType = (function () { + function UsesReferredType(referred) { + this.referred = referred; + } + return UsesReferredType; + })(); + })(ImportingModule = exports.ImportingModule || (exports.ImportingModule = {})); +}); diff --git a/tests/baselines/reference/importedAliasesInTypePositions.types b/tests/baselines/reference/importedAliasesInTypePositions.types new file mode 100644 index 00000000000..ed78188d275 --- /dev/null +++ b/tests/baselines/reference/importedAliasesInTypePositions.types @@ -0,0 +1,40 @@ +=== tests/cases/compiler/file2.ts === +import RT_ALIAS = require("file1"); +>RT_ALIAS : typeof RT_ALIAS + +import ReferredTo = RT_ALIAS.elaborate.nested.mod.name.ReferredTo; +>ReferredTo : typeof ReferredTo +>RT_ALIAS : typeof RT_ALIAS +>elaborate : typeof RT_ALIAS.elaborate +>nested : typeof RT_ALIAS.elaborate.nested +>mod : typeof RT_ALIAS.elaborate.nested.mod +>name : typeof RT_ALIAS.elaborate.nested.mod.name +>ReferredTo : ReferredTo + +export module ImportingModule { +>ImportingModule : typeof ImportingModule + + class UsesReferredType { +>UsesReferredType : UsesReferredType + + constructor(private referred: ReferredTo) { } +>referred : ReferredTo +>ReferredTo : ReferredTo + } +} +=== tests/cases/compiler/file1.ts === +export module elaborate.nested.mod.name { +>elaborate : typeof elaborate +>nested : typeof nested +>mod : typeof mod +>name : typeof name + + export class ReferredTo { +>ReferredTo : ReferredTo + + doSomething(): void { +>doSomething : () => void + } + } +} + diff --git a/tests/cases/compiler/importedAliasesInTypePositions.ts b/tests/cases/compiler/importedAliasesInTypePositions.ts new file mode 100644 index 00000000000..f1dd66f60a7 --- /dev/null +++ b/tests/cases/compiler/importedAliasesInTypePositions.ts @@ -0,0 +1,19 @@ +// @module:amd +// @Filename: file1.ts +export module elaborate.nested.mod.name { + export class ReferredTo { + doSomething(): void { + } + } +} + +// @Filename: file2.ts +// @module: amd +import RT_ALIAS = require("file1"); +import ReferredTo = RT_ALIAS.elaborate.nested.mod.name.ReferredTo; + +export module ImportingModule { + class UsesReferredType { + constructor(private referred: ReferredTo) { } + } +} \ No newline at end of file From 5b38cb9a6975c0a72526db4a92190dcbf483eb29 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Tue, 16 Dec 2014 00:34:51 -0800 Subject: [PATCH 2/2] harden 'get import declaration' logic --- src/compiler/checker.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 24f5a2f528d..20033d4c80b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4742,7 +4742,9 @@ module ts { nodeLinks.importOnRightSide = undefined; getSymbolLinks(rightSide).referenced = true; - nodeLinks = getNodeLinks(rightSide.declarations[0]) + Debug.assert((rightSide.flags & SymbolFlags.Import) !== 0); + + nodeLinks = getNodeLinks(getDeclarationOfKind(rightSide, SyntaxKind.ImportDeclaration)) } } @@ -4773,7 +4775,7 @@ module ts { } if (symbolLinks.referenced) { - markLinkedImportsAsReferenced(symbol.declarations[0]); + markLinkedImportsAsReferenced(getDeclarationOfKind(symbol, SyntaxKind.ImportDeclaration)); } } @@ -8906,7 +8908,7 @@ module ts { // Mark the import as referenced so that we emit it in the final .js file. getSymbolLinks(symbol).referenced = true; // mark any import declarations that depend upon this import as referenced - markLinkedImportsAsReferenced(symbol.declarations[0]) + markLinkedImportsAsReferenced(getDeclarationOfKind(symbol, SyntaxKind.ImportDeclaration)) } }