Skip to content
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

Module or import types #22592

Merged
merged 23 commits into from
Apr 2, 2018
Merged
Show file tree
Hide file tree
Changes from 14 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
202 changes: 174 additions & 28 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,22 @@
"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
},
"Import specifier must be a string literal type, but here is '{0}'.": {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit, not sure the type name here gives you much value.. we only allow string literals, not even strings.. so i would just say only string literals are valid import specifiers.

Copy link
Member Author

Choose a reason for hiding this comment

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

We'll parse any type there to be permissive, though; this is more of a grammar error.

Copy link
Contributor

Choose a reason for hiding this comment

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

I know. i am just saying whatever {0} is, the error message is just saying you cannot have it.. in other words, the type name is extraneous

Copy link
Member Author

Choose a reason for hiding this comment

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

I find including the type in the error message useful as frequently an error only exists because I believe that the type is correct at a given location; so seeing where reality differs from my expectation is useful.

"category": "Error",
"code": 1341
},
"Type arguments cannot be used here.": {
"category": "Error",
"code": 1342
},

"Duplicate identifier '{0}'.": {
"category": "Error",
Expand Down
17 changes: 17 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,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 @@ -1325,6 +1327,21 @@ 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);
}
}

//
// 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
16 changes: 10 additions & 6 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -665,7 +665,8 @@ namespace ts {
getSourceFileFromReference,
sourceFileToPackageName,
redirectTargetsSet,
isEmittedFile
isEmittedFile,
resolveModuleName: resolveModuleNamesWorker
};

verifyCompilerOptions();
Expand Down Expand Up @@ -739,7 +740,7 @@ namespace ts {
const result: ResolvedModuleFull[] = [];
for (const moduleName of moduleNames) {
const resolvedModule = file.resolvedModules.get(moduleName);
result.push(resolvedModule);
result.push(getResolvedModuleFromState(resolvedModule));
}
return result;
}
Expand Down Expand Up @@ -768,7 +769,7 @@ namespace ts {
const moduleName = moduleNames[i];
// If the source file is unchanged and doesnt have invalidated resolution, reuse the module resolutions
if (file === oldSourceFile && !hasInvalidatedResolution(oldSourceFile.path)) {
const oldResolvedModule = oldSourceFile && oldSourceFile.resolvedModules.get(moduleName);
const oldResolvedModule = getResolvedModuleFromState(oldSourceFile && oldSourceFile.resolvedModules.get(moduleName));
if (oldResolvedModule) {
if (isTraceEnabled(options, host)) {
trace(host, Diagnostics.Reusing_resolution_of_module_0_to_file_1_from_old_program, moduleName, containingFile);
Expand Down Expand Up @@ -834,7 +835,7 @@ namespace ts {
// If we change our policy of rechecking failed lookups on each program create,
// we should adjust the value returned here.
function moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName: string, oldProgramState: OldProgramState): boolean {
const resolutionToFile = getResolvedModule(oldProgramState.oldSourceFile, moduleName);
const resolutionToFile = getResolvedModuleFromState(getResolvedModule(oldProgramState.oldSourceFile, moduleName));
const resolvedFile = resolutionToFile && oldProgramState.program && oldProgramState.program.getSourceFile(resolutionToFile.resolvedFileName);
if (resolutionToFile && resolvedFile && !resolvedFile.externalModuleIndicator) {
// In the old program, we resolved to an ambient module that was in the same
Expand Down Expand Up @@ -1017,10 +1018,10 @@ namespace ts {
const oldProgramState: OldProgramState = { program: oldProgram, oldSourceFile, modifiedFilePaths };
const resolutions = resolveModuleNamesReusingOldState(moduleNames, newSourceFilePath, newSourceFile, oldProgramState);
// ensure that module resolution results are still correct
const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, moduleResolutionIsEqualTo);
const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, moduleResolutionIsEqualTo, getResolvedModuleFromState);
if (resolutionsChanged) {
oldProgram.structureIsReused = StructureIsReused.SafeModules;
newSourceFile.resolvedModules = zipToMap(moduleNames, resolutions);
newSourceFile.resolvedModules = zipToMap(moduleNames, map(resolutions, r => ({ tag: "success" as "success", data: r })));
}
else {
newSourceFile.resolvedModules = oldSourceFile.resolvedModules;
Expand Down Expand Up @@ -1673,6 +1674,9 @@ 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 || (imports = [])).push(node.argument.literal);
}
else {
forEachChild(node, collectDynamicImportOrRequireCalls);
}
Expand Down
33 changes: 27 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;
Copy link
Member Author

Choose a reason for hiding this comment

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

Are you curious why I didn't modify type queries to allow import type nodes somehow? (Perhaps because you dislike 1-token loohaheads or think this feels inelegant?) Well over here is a drop from awhile ago when I tried implementing this that way before, and it was really not pretty. Specifically, most places expect a type query's exprName to be an Expression (or specifically an entity name), and some kind of import type node (and it is definitely a type node when it's not inside a type query, making the problem more difficult, since it ends up as both a TypeNode and an Expression branded node) is not usually an expression, which breaks expectations everywhere. Wrapping all the new behavior up into one new type (node) kind is the neatest way to handle the new feature, without breaking all existing consumers. 😄

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 @@ -2569,7 +2582,7 @@ namespace ts {
// Stores a mapping 'external module reference text' -> 'resolved file name' | undefined
// It is used to resolve module names in the checker.
// Content of this field should never be used directly - use getResolvedModuleFileName/setResolvedModuleFileName functions instead
/* @internal */ resolvedModules: Map<ResolvedModuleFull | undefined>;
/* @internal */ resolvedModules: Map<ResolvedModuleState>;
/* @internal */ resolvedTypeReferenceDirectiveNames: Map<ResolvedTypeReferenceDirective>;
/* @internal */ imports: ReadonlyArray<StringLiteralLike>;
// Identifier only if `declare global`
Expand All @@ -2583,6 +2596,9 @@ namespace ts {
/* @internal */ localJsxFactory?: EntityName;
}

/* @internal */
export type ResolvedModuleState = { tag: "success", data: ResolvedModuleFull } | { tag: "fail" };

export interface Bundle extends Node {
kind: SyntaxKind.Bundle;
sourceFiles: ReadonlyArray<SourceFile>;
Expand Down Expand Up @@ -2705,6 +2721,9 @@ namespace ts {
/* @internal */ redirectTargetsSet: Map<true>;
/** Is the file emitted file */
/* @internal */ isEmittedFile(file: string): boolean;

/* Used by the type checker to resolve module names which are encountered late */
/* @internal */ resolveModuleName(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModuleFull[];
}

/* @internal */
Expand Down Expand Up @@ -2778,6 +2797,8 @@ namespace ts {
getSourceFiles(): ReadonlyArray<SourceFile>;
getSourceFile(fileName: string): SourceFile;
getResolvedTypeReferenceDirectives(): ReadonlyMap<ResolvedTypeReferenceDirective>;

resolveModuleName(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModuleFull[];
}

export interface TypeChecker {
Expand Down
27 changes: 20 additions & 7 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,21 @@ namespace ts {
return node.end - node.pos;
}

export function getResolvedModule(sourceFile: SourceFile, moduleNameText: string): ResolvedModuleFull | undefined {
export function getResolvedModule(sourceFile: SourceFile, moduleNameText: string): ResolvedModuleState {
return sourceFile && sourceFile.resolvedModules && sourceFile.resolvedModules.get(moduleNameText);
}

export function getResolvedModuleFromState(state: ResolvedModuleState): ResolvedModuleFull | undefined {
return state && state.tag === "success" ? state.data : undefined;
}

const failedLookup: { tag: "fail" } = { tag: "fail" };
export function setResolvedModule(sourceFile: SourceFile, moduleNameText: string, resolvedModule: ResolvedModuleFull): void {
if (!sourceFile.resolvedModules) {
sourceFile.resolvedModules = createMap<ResolvedModuleFull>();
sourceFile.resolvedModules = createMap<ResolvedModuleState>();
}

sourceFile.resolvedModules.set(moduleNameText, resolvedModule);
sourceFile.resolvedModules.set(moduleNameText, resolvedModule ? { tag: "success", data: resolvedModule } : failedLookup);
}

export function setResolvedTypeReferenceDirective(sourceFile: SourceFile, typeReferenceDirectiveName: string, resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective): void {
Expand Down Expand Up @@ -124,16 +129,18 @@ namespace ts {
return oldResolution.resolvedFileName === newResolution.resolvedFileName && oldResolution.primary === newResolution.primary;
}

export function hasChangesInResolutions<T>(
export function hasChangesInResolutions<T, U = T>(
names: ReadonlyArray<string>,
newResolutions: ReadonlyArray<T>,
oldResolutions: ReadonlyMap<T>,
comparer: (oldResolution: T, newResolution: T) => boolean): boolean {
oldResolutions: ReadonlyMap<U>,
comparer: (oldResolution: T, newResolution: T) => boolean,
oldMapper?: (x: U) => T): boolean {
Debug.assert(names.length === newResolutions.length);

for (let i = 0; i < names.length; i++) {
const newResolution = newResolutions[i];
const oldResolution = oldResolutions && oldResolutions.get(names[i]);
const oldRes = oldResolutions && oldResolutions.get(names[i]);
const oldResolution = oldMapper ? oldMapper(oldRes) : oldRes as {} as T;
const changed =
oldResolution
? !newResolution || !comparer(oldResolution, newResolution)
Expand Down Expand Up @@ -726,6 +733,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
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
Loading