Skip to content

Commit

Permalink
Module or import types (#22592)
Browse files Browse the repository at this point in the history
* Type side of import types

* Value side of import types

* Accept library changes

* Refined implementation, more tests

* Allow resolutions to be performed late if the resolution still results in a file already in the build

* Add another test case

* Add some jsdoc usages

* Allow nodebuilder to use import types where appropriate

* Parse & check generic instantiations

* use import types in nodebuilder for typeof module symbols

* Wire up go to definition for import types

* Accept updated type/symbol baselines now that symbols are wired in

* PR feedback

* Fix changes from merge

* Walk back late import handling

* Remove unused diagnostic

* Remove unrelated changes

* Use recursive function over loop

* Emit type arguments

* undo unrelated change

* Test for and support import type nodes in bundled declaration emit
  • Loading branch information
weswigham authored Apr 2, 2018
1 parent 5c44241 commit 414bc49
Show file tree
Hide file tree
Showing 287 changed files with 3,825 additions and 1,278 deletions.
188 changes: 163 additions & 25 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,18 @@
"category": "Error",
"code": 1338
},
"Module '{0}' does not refer to a value, but is used as a value here.": {
"category": "Error",
"code": 1339
},
"Module '{0}' does not refer to a type, but is used as a type here.": {
"category": "Error",
"code": 1340
},
"Type arguments cannot be used here.": {
"category": "Error",
"code": 1342
},

"Duplicate identifier '{0}'.": {
"category": "Error",
Expand Down
18 changes: 18 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,8 @@ namespace ts {
return emitMappedType(<MappedTypeNode>node);
case SyntaxKind.LiteralType:
return emitLiteralType(<LiteralTypeNode>node);
case SyntaxKind.ImportTypeNode:
return emitImportTypeNode(<ImportTypeNode>node);
case SyntaxKind.JSDocAllType:
write("*");
return;
Expand Down Expand Up @@ -1327,6 +1329,22 @@ namespace ts {
emitExpression(node.literal);
}

function emitImportTypeNode(node: ImportTypeNode) {
if (node.isTypeOf) {
writeKeyword("typeof");
writeSpace();
}
writeKeyword("import");
writePunctuation("(");
emit(node.argument);
writePunctuation(")");
if (node.qualifier) {
writePunctuation(".");
emit(node.qualifier);
}
emitTypeArguments(node, node.typeArguments);
}

//
// Binding patterns
//
Expand Down
18 changes: 18 additions & 0 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,24 @@ namespace ts {
: node;
}

export function createImportTypeNode(argument: TypeNode, qualifier?: EntityName, typeArguments?: ReadonlyArray<TypeNode>, isTypeOf?: boolean) {
const node = <ImportTypeNode>createSynthesizedNode(SyntaxKind.ImportTypeNode);
node.argument = argument;
node.qualifier = qualifier;
node.typeArguments = asNodeArray(typeArguments);
node.isTypeOf = isTypeOf;
return node;
}

export function updateImportTypeNode(node: ImportTypeNode, argument: TypeNode, qualifier?: EntityName, typeArguments?: ReadonlyArray<TypeNode>, isTypeOf?: boolean) {
return node.argument !== argument
|| node.qualifier !== qualifier
|| node.typeArguments !== typeArguments
|| node.isTypeOf !== isTypeOf
? updateNode(createImportTypeNode(argument, qualifier, typeArguments, isTypeOf), node)
: node;
}

export function createParenthesizedType(type: TypeNode) {
const node = <ParenthesizedTypeNode>createSynthesizedNode(SyntaxKind.ParenthesizedType);
node.type = type;
Expand Down
31 changes: 30 additions & 1 deletion src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,10 @@ namespace ts {
visitNode(cbNode, (<ConditionalTypeNode>node).falseType);
case SyntaxKind.InferType:
return visitNode(cbNode, (<InferTypeNode>node).typeParameter);
case SyntaxKind.ImportTypeNode:
return visitNode(cbNode, (<ImportTypeNode>node).argument) ||
visitNode(cbNode, (<ImportTypeNode>node).qualifier) ||
visitNodes(cbNode, cbNodes, (<ImportTypeNode>node).typeArguments);
case SyntaxKind.ParenthesizedType:
case SyntaxKind.TypeOperator:
return visitNode(cbNode, (<ParenthesizedTypeNode | TypeOperatorNode>node).type);
Expand Down Expand Up @@ -2733,6 +2737,28 @@ namespace ts {
return finishNode(node);
}

function isStartOfTypeOfImportType() {
nextToken();
return token() === SyntaxKind.ImportKeyword;
}

function parseImportType(): ImportTypeNode {
sourceFile.flags |= NodeFlags.PossiblyContainsDynamicImport;
const node = createNode(SyntaxKind.ImportTypeNode) as ImportTypeNode;
if (parseOptional(SyntaxKind.TypeOfKeyword)) {
node.isTypeOf = true;
}
parseExpected(SyntaxKind.ImportKeyword);
parseExpected(SyntaxKind.OpenParenToken);
node.argument = parseType();
parseExpected(SyntaxKind.CloseParenToken);
if (parseOptional(SyntaxKind.DotToken)) {
node.qualifier = parseEntityName(/*allowReservedWords*/ true, Diagnostics.Type_expected);
}
node.typeArguments = tryParseTypeArguments();
return finishNode(node);
}

function nextTokenIsNumericLiteral() {
return nextToken() === SyntaxKind.NumericLiteral;
}
Expand Down Expand Up @@ -2780,13 +2806,15 @@ namespace ts {
}
}
case SyntaxKind.TypeOfKeyword:
return parseTypeQuery();
return lookAhead(isStartOfTypeOfImportType) ? parseImportType() : parseTypeQuery();
case SyntaxKind.OpenBraceToken:
return lookAhead(isStartOfMappedType) ? parseMappedType() : parseTypeLiteral();
case SyntaxKind.OpenBracketToken:
return parseTupleType();
case SyntaxKind.OpenParenToken:
return parseParenthesizedType();
case SyntaxKind.ImportKeyword:
return parseImportType();
default:
return parseTypeReference();
}
Expand Down Expand Up @@ -2822,6 +2850,7 @@ namespace ts {
case SyntaxKind.ExclamationToken:
case SyntaxKind.DotDotDotToken:
case SyntaxKind.InferKeyword:
case SyntaxKind.ImportKeyword:
return true;
case SyntaxKind.MinusToken:
return !inStartOfParameter && lookAhead(nextTokenIsNumericLiteral);
Expand Down
12 changes: 11 additions & 1 deletion src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1686,10 +1686,20 @@ namespace ts {
else if (isImportCall(node) && node.arguments.length === 1 && isStringLiteralLike(node.arguments[0])) {
imports = append(imports, node.arguments[0] as StringLiteralLike);
}
else if (isLiteralImportTypeNode(node)) {
imports = append(imports, node.argument.literal);
}
else {
forEachChild(node, collectDynamicImportOrRequireCalls);
collectDynamicImportOrRequireCallsForEachChild(node);
if (hasJSDocNodes(node)) {
forEach(node.jsDoc, collectDynamicImportOrRequireCallsForEachChild);
}
}
}

function collectDynamicImportOrRequireCallsForEachChild(node: Node) {
forEachChild(node, collectDynamicImportOrRequireCalls);
}
}

/** This should have similar behavior to 'processSourceFile' without diagnostics or mutation. */
Expand Down
18 changes: 15 additions & 3 deletions src/compiler/transformers/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -450,9 +450,9 @@ namespace ts {
return setCommentRange(updated, getCommentRange(original));
}

function rewriteModuleSpecifier<T extends Node>(parent: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration, input: T): T | StringLiteral {
function rewriteModuleSpecifier<T extends Node>(parent: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration | ImportTypeNode, input: T): T | StringLiteral {
if (!input) return;
resultHasExternalModuleIndicator = resultHasExternalModuleIndicator || parent.kind !== SyntaxKind.ModuleDeclaration;
resultHasExternalModuleIndicator = resultHasExternalModuleIndicator || (parent.kind !== SyntaxKind.ModuleDeclaration && parent.kind !== SyntaxKind.ImportTypeNode);
if (input.kind === SyntaxKind.StringLiteral && isBundledEmit) {
const newName = getExternalModuleNameFromDeclaration(context.getEmitHost(), resolver, parent);
if (newName) {
Expand Down Expand Up @@ -765,6 +765,16 @@ namespace ts {
case SyntaxKind.ConstructorType: {
return cleanup(updateConstructorTypeNode(input, visitNodes(input.typeParameters, visitDeclarationSubtree), updateParamsList(input, input.parameters), visitNode(input.type, visitDeclarationSubtree)));
}
case SyntaxKind.ImportTypeNode: {
if (!isLiteralImportTypeNode(input)) return cleanup(input);
return cleanup(updateImportTypeNode(
input,
updateLiteralTypeNode(input.argument, rewriteModuleSpecifier(input, input.argument.literal)),
input.qualifier,
visitNodes(input.typeArguments, visitDeclarationSubtree, isTypeNode),
input.isTypeOf
));
}
default: Debug.assertNever(input, `Attempted to process unhandled node kind: ${(ts as any).SyntaxKind[(input as any).kind]}`);
}
}
Expand Down Expand Up @@ -1264,7 +1274,8 @@ namespace ts {
| TypeReferenceNode
| ConditionalTypeNode
| FunctionTypeNode
| ConstructorTypeNode;
| ConstructorTypeNode
| ImportTypeNode;

function isProcessedComponent(node: Node): node is ProcessedComponent {
switch (node.kind) {
Expand All @@ -1285,6 +1296,7 @@ namespace ts {
case SyntaxKind.ConditionalType:
case SyntaxKind.FunctionType:
case SyntaxKind.ConstructorType:
case SyntaxKind.ImportTypeNode:
return true;
}
return false;
Expand Down
25 changes: 19 additions & 6 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ namespace ts {
IndexedAccessType,
MappedType,
LiteralType,
ImportTypeNode,
// Binding patterns
ObjectBindingPattern,
ArrayBindingPattern,
Expand Down Expand Up @@ -445,7 +446,7 @@ namespace ts {
FirstFutureReservedWord = ImplementsKeyword,
LastFutureReservedWord = YieldKeyword,
FirstTypeNode = TypePredicate,
LastTypeNode = LiteralType,
LastTypeNode = ImportTypeNode,
FirstPunctuation = OpenBraceToken,
LastPunctuation = CaretEqualsToken,
FirstToken = Unknown,
Expand Down Expand Up @@ -1067,6 +1068,16 @@ namespace ts {
| SyntaxKind.NeverKeyword;
}

export interface ImportTypeNode extends NodeWithTypeArguments {
kind: SyntaxKind.ImportTypeNode;
isTypeOf?: boolean;
argument: TypeNode;
qualifier?: EntityName;
}

/* @internal */
export type LiteralImportTypeNode = ImportTypeNode & { argument: LiteralTypeNode & { literal: StringLiteral } };

export interface ThisTypeNode extends TypeNode {
kind: SyntaxKind.ThisType;
}
Expand All @@ -1081,12 +1092,15 @@ namespace ts {
kind: SyntaxKind.ConstructorType;
}

export interface NodeWithTypeArguments extends TypeNode {
typeArguments?: NodeArray<TypeNode>;
}

export type TypeReferenceType = TypeReferenceNode | ExpressionWithTypeArguments;

export interface TypeReferenceNode extends TypeNode {
export interface TypeReferenceNode extends NodeWithTypeArguments {
kind: SyntaxKind.TypeReference;
typeName: EntityName;
typeArguments?: NodeArray<TypeNode>;
}

export interface TypePredicateNode extends TypeNode {
Expand Down Expand Up @@ -1696,11 +1710,10 @@ namespace ts {
expression: ImportExpression;
}

export interface ExpressionWithTypeArguments extends TypeNode {
export interface ExpressionWithTypeArguments extends NodeWithTypeArguments {
kind: SyntaxKind.ExpressionWithTypeArguments;
parent?: HeritageClause;
expression: LeftHandSideExpression;
typeArguments?: NodeArray<TypeNode>;
}

export interface NewExpression extends PrimaryExpression, Declaration {
Expand Down Expand Up @@ -3244,7 +3257,7 @@ namespace ts {
isOptionalParameter(node: ParameterDeclaration): boolean;
moduleExportsSomeValue(moduleReferenceExpression: Expression): boolean;
isArgumentsLocalBinding(node: Identifier): boolean;
getExternalModuleFileFromDeclaration(declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration): SourceFile;
getExternalModuleFileFromDeclaration(declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration | ImportTypeNode): SourceFile;
getTypeReferenceDirectivesForEntityName(name: EntityNameOrEntityNameExpression): string[];
getTypeReferenceDirectivesForSymbol(symbol: Symbol, meaning?: SymbolFlags): string[];
isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean;
Expand Down
12 changes: 10 additions & 2 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,12 @@ namespace ts {
return n.kind === SyntaxKind.CallExpression && (<CallExpression>n).expression.kind === SyntaxKind.ImportKeyword;
}

export function isLiteralImportTypeNode(n: Node): n is LiteralImportTypeNode {
return n.kind === SyntaxKind.ImportTypeNode &&
(n as ImportTypeNode).argument.kind === SyntaxKind.LiteralType &&
isStringLiteral(((n as ImportTypeNode).argument as LiteralTypeNode).literal);
}

export function isPrologueDirective(node: Node): node is PrologueDirective {
return node.kind === SyntaxKind.ExpressionStatement
&& (<ExpressionStatement>node).expression.kind === SyntaxKind.StringLiteral;
Expand Down Expand Up @@ -1697,13 +1703,15 @@ namespace ts {
}
}

export function getExternalModuleName(node: AnyImportOrReExport): Expression {
export function getExternalModuleName(node: AnyImportOrReExport | ImportTypeNode): Expression {
switch (node.kind) {
case SyntaxKind.ImportDeclaration:
case SyntaxKind.ExportDeclaration:
return node.moduleSpecifier;
case SyntaxKind.ImportEqualsDeclaration:
return node.moduleReference.kind === SyntaxKind.ExternalModuleReference ? node.moduleReference.expression : undefined;
case SyntaxKind.ImportTypeNode:
return isLiteralImportTypeNode(node) ? node.argument.literal : undefined;
default:
return Debug.assertNever(node);
}
Expand Down Expand Up @@ -2809,7 +2817,7 @@ namespace ts {
return file.moduleName || getExternalModuleNameFromPath(host, file.fileName);
}

export function getExternalModuleNameFromDeclaration(host: EmitHost, resolver: EmitResolver, declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration): string {
export function getExternalModuleNameFromDeclaration(host: EmitHost, resolver: EmitResolver, declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration | ImportTypeNode): string {
const file = resolver.getExternalModuleFileFromDeclaration(declaration);
if (!file || file.isDeclarationFile) {
return undefined;
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,14 @@ namespace ts {
return updateInferTypeNode(<InferTypeNode>node,
visitNode((<InferTypeNode>node).typeParameter, visitor, isTypeParameterDeclaration));

case SyntaxKind.ImportTypeNode:
return updateImportTypeNode(<ImportTypeNode>node,
visitNode((<ImportTypeNode>node).argument, visitor, isTypeNode),
visitNode((<ImportTypeNode>node).qualifier, visitor, isEntityName),
visitNodes((<ImportTypeNode>node).typeArguments, visitor, isTypeNode),
(<ImportTypeNode>node).isTypeOf
);

case SyntaxKind.ParenthesizedType:
return updateParenthesizedType(<ParenthesizedTypeNode>node,
visitNode((<ParenthesizedTypeNode>node).type, visitor, isTypeNode));
Expand Down
8 changes: 4 additions & 4 deletions tests/baselines/reference/aliasAssignments.errors.txt
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
tests/cases/compiler/aliasAssignments_1.ts(3,1): error TS2322: Type '1' is not assignable to type 'typeof "tests/cases/compiler/aliasAssignments_moduleA"'.
tests/cases/compiler/aliasAssignments_1.ts(5,1): error TS2322: Type 'typeof "tests/cases/compiler/aliasAssignments_moduleA"' is not assignable to type 'number'.
tests/cases/compiler/aliasAssignments_1.ts(3,1): error TS2322: Type '1' is not assignable to type 'typeof import("tests/cases/compiler/aliasAssignments_moduleA")'.
tests/cases/compiler/aliasAssignments_1.ts(5,1): error TS2322: Type 'typeof import("tests/cases/compiler/aliasAssignments_moduleA")' is not assignable to type 'number'.


==== tests/cases/compiler/aliasAssignments_1.ts (2 errors) ====
import moduleA = require("./aliasAssignments_moduleA");
var x = moduleA;
x = 1; // Should be error
~
!!! error TS2322: Type '1' is not assignable to type 'typeof "tests/cases/compiler/aliasAssignments_moduleA"'.
!!! error TS2322: Type '1' is not assignable to type 'typeof import("tests/cases/compiler/aliasAssignments_moduleA")'.
var y = 1;
y = moduleA; // should be error
~
!!! error TS2322: Type 'typeof "tests/cases/compiler/aliasAssignments_moduleA"' is not assignable to type 'number'.
!!! error TS2322: Type 'typeof import("tests/cases/compiler/aliasAssignments_moduleA")' is not assignable to type 'number'.

==== tests/cases/compiler/aliasAssignments_moduleA.ts (0 errors) ====
export class someClass {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ var x: foo.A = foo.bar("hello"); // foo.A should be ok but foo.bar should be err

=== tests/cases/compiler/aliasOnMergedModuleInterface_0.ts ===
declare module "foo"
>"foo" : typeof "foo"
>"foo" : typeof import("foo")
{
module B {
>B : any
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
tests/cases/compiler/a.ts(2,5): error TS2339: Property 'default' does not exist on type 'typeof "tests/cases/compiler/b"'.
tests/cases/compiler/a.ts(3,5): error TS2339: Property 'default' does not exist on type 'typeof "tests/cases/compiler/b"'.
tests/cases/compiler/a.ts(2,5): error TS2339: Property 'default' does not exist on type 'typeof import("tests/cases/compiler/b")'.
tests/cases/compiler/a.ts(3,5): error TS2339: Property 'default' does not exist on type 'typeof import("tests/cases/compiler/b")'.


==== tests/cases/compiler/a.ts (2 errors) ====
import Foo = require("./b");
Foo.default.bar();
~~~~~~~
!!! error TS2339: Property 'default' does not exist on type 'typeof "tests/cases/compiler/b"'.
!!! error TS2339: Property 'default' does not exist on type 'typeof import("tests/cases/compiler/b")'.
Foo.default.default.foo();
~~~~~~~
!!! error TS2339: Property 'default' does not exist on type 'typeof "tests/cases/compiler/b"'.
!!! error TS2339: Property 'default' does not exist on type 'typeof import("tests/cases/compiler/b")'.
==== tests/cases/compiler/b.d.ts (0 errors) ====
export function foo();

Expand Down
Loading

0 comments on commit 414bc49

Please sign in to comment.