Skip to content

Module or import types #22592

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Apr 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9c69227
Type side of import types
weswigham Mar 14, 2018
e9d01ce
Value side of import types
weswigham Mar 14, 2018
31afd58
Accept library changes
weswigham Mar 14, 2018
6ceaee0
Refined implementation, more tests
weswigham Mar 14, 2018
fa9fe50
Allow resolutions to be performed late if the resolution still result…
weswigham Mar 15, 2018
5d2715c
Add another test case
weswigham Mar 15, 2018
79c4729
Add some jsdoc usages
weswigham Mar 15, 2018
9e77045
Merge branch 'master' into import-types
weswigham Mar 23, 2018
080ba6f
Allow nodebuilder to use import types where appropriate
weswigham Mar 23, 2018
0be6ce4
Parse & check generic instantiations
weswigham Mar 23, 2018
73075dd
use import types in nodebuilder for typeof module symbols
weswigham Mar 23, 2018
fefa9da
Wire up go to definition for import types
weswigham Mar 24, 2018
b07fe67
Accept updated type/symbol baselines now that symbols are wired in
weswigham Mar 24, 2018
20950c5
PR feedback
weswigham Mar 30, 2018
8a8efc9
Merge branch 'master' into import-types
weswigham Mar 30, 2018
7521c63
Fix changes from merge
weswigham Mar 30, 2018
257b848
Walk back late import handling
weswigham Mar 30, 2018
a06607a
Remove unused diagnostic
weswigham Mar 30, 2018
87b102e
Remove unrelated changes
weswigham Mar 30, 2018
7770788
Use recursive function over loop
weswigham Mar 30, 2018
d24ae2e
Emit type arguments
weswigham Mar 30, 2018
fbd6ce8
undo unrelated change
weswigham Mar 30, 2018
0c143a0
Test for and support import type nodes in bundled declaration emit
weswigham Mar 30, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -671,6 +671,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 @@ -1338,6 +1340,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 @@ -802,6 +802,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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@weswigham is in intentional that this is the only SyntaxKind with the suffix Node?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

T.T no, I just unconsciously reused the interface name (which always uses Node) for the syntax kind. Will fix.

// 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 @@ -1066,6 +1067,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 @@ -1080,12 +1091,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 @@ -1695,11 +1709,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 @@ -3243,7 +3256,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 @@ -726,6 +726,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 @@ -1680,13 +1686,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 @@ -2788,7 +2796,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