-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Module or import
types
#22592
Changes from 7 commits
9c69227
e9d01ce
31afd58
6ceaee0
fa9fe50
5d2715c
79c4729
9e77045
080ba6f
0be6ce4
73075dd
fefa9da
b07fe67
20950c5
8a8efc9
7521c63
257b848
a06607a
87b102e
7770788
d24ae2e
fbd6ce8
0c143a0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2104,7 +2104,17 @@ namespace ts { | |
if (ambientModule) { | ||
return ambientModule; | ||
} | ||
const resolvedModule = getResolvedModule(getSourceFileOfNode(location), moduleReference); | ||
const currentSourceFile = getSourceFileOfNode(location); | ||
const resolvedModuleState = getResolvedModule(currentSourceFile, moduleReference); | ||
let resolvedModule: ResolvedModuleFull; | ||
if (currentSourceFile && resolvedModuleState === undefined) { | ||
// Fallback to uncached lookup for late-bound module names which have not been resolved | ||
const resolutions = host.resolveModuleName([moduleReference], currentSourceFile.fileName); | ||
resolvedModule = firstOrUndefined(resolutions); | ||
} | ||
else { | ||
resolvedModule = getResolvedModuleFromState(resolvedModuleState); | ||
} | ||
const resolutionDiagnostic = resolvedModule && getResolutionDiagnostic(compilerOptions, resolvedModule); | ||
const sourceFile = resolvedModule && !resolutionDiagnostic && host.getSourceFile(resolvedModule.resolvedFileName); | ||
if (sourceFile) { | ||
|
@@ -2182,15 +2192,15 @@ namespace ts { | |
// An external module with an 'export =' declaration may be referenced as an ES6 module provided the 'export =' | ||
// references a symbol that is at least declared as a module or a variable. The target of the 'export =' may | ||
// combine other declarations with the module or variable (e.g. a class/module, function/module, interface/variable). | ||
function resolveESModuleSymbol(moduleSymbol: Symbol, moduleReferenceExpression: Expression, dontResolveAlias: boolean): Symbol { | ||
function resolveESModuleSymbol(moduleSymbol: Symbol, referencingLocation: Node, dontResolveAlias: boolean): Symbol { | ||
const symbol = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias); | ||
if (!dontResolveAlias && symbol) { | ||
if (!(symbol.flags & (SymbolFlags.Module | SymbolFlags.Variable))) { | ||
error(moduleReferenceExpression, Diagnostics.Module_0_resolves_to_a_non_module_entity_and_cannot_be_imported_using_this_construct, symbolToString(moduleSymbol)); | ||
error(referencingLocation, Diagnostics.Module_0_resolves_to_a_non_module_entity_and_cannot_be_imported_using_this_construct, symbolToString(moduleSymbol)); | ||
return symbol; | ||
} | ||
if (compilerOptions.esModuleInterop) { | ||
const referenceParent = moduleReferenceExpression.parent; | ||
const referenceParent = referencingLocation.parent; | ||
if ( | ||
(isImportDeclaration(referenceParent) && getNamespaceDeclarationNode(referenceParent)) || | ||
isImportCall(referenceParent) | ||
|
@@ -8411,6 +8421,65 @@ namespace ts { | |
return links.resolvedType; | ||
} | ||
|
||
function getTypeFromImportTypeNode(node: ImportTypeNode): Type { | ||
const links = getNodeLinks(node); | ||
if (!links.resolvedType) { | ||
const argumentType = getTypeFromTypeNode(node.argument); | ||
const targetMeaning = node.isTypeOf ? SymbolFlags.Value : SymbolFlags.Type; | ||
// TODO: Future work: support unions/generics/whatever via a deferred import-type | ||
if (!argumentType || !(argumentType.flags & TypeFlags.StringLiteral)) { | ||
error(node.argument, Diagnostics.Import_specifier_must_be_a_string_literal_type_but_here_is_0, argumentType ? typeToString(argumentType) : "undefined"); | ||
return links.resolvedType = anyType; | ||
} | ||
const moduleName = (argumentType as StringLiteralType).value; | ||
const innerModuleSymbol = resolveExternalModule(node, moduleName, Diagnostics.Cannot_find_module_0, node, /*isForAugmentation*/ false); | ||
if (!innerModuleSymbol) { | ||
error(node, Diagnostics.Cannot_find_module_0, moduleName); | ||
return links.resolvedType = anyType; | ||
} | ||
const moduleSymbol = resolveExternalModuleSymbol(innerModuleSymbol, /*dontResolveAlias*/ false); | ||
if (!moduleSymbol) { | ||
error(node, Diagnostics.Cannot_find_module_0, moduleName); | ||
return links.resolvedType = anyType; | ||
} | ||
if (node.qualifier) { | ||
const nameStack: Identifier[] = []; | ||
let currentNamespace = moduleSymbol; | ||
let current = node.qualifier; | ||
while (true) { | ||
if (current.kind === SyntaxKind.Identifier) { | ||
nameStack.push(current); | ||
break; | ||
} | ||
else { | ||
nameStack.push(current.right); | ||
current = current.left; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that error message does not seem right, you probably want something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 'Doh, I put this one here as a placeholder and forgot to replace it with a new message. Thanks for the catch! |
||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unknowType? |
||
while (current = nameStack.pop()) { | ||
const meaning = nameStack.length ? SymbolFlags.Namespace : targetMeaning; | ||
const next = getSymbol(getExportsOfSymbol(getMergedSymbol(resolveSymbol(currentNamespace))), current.escapedText, meaning); | ||
if (!next) { | ||
error(current, Diagnostics.Namespace_0_has_no_exported_member_1, getFullyQualifiedName(currentNamespace), declarationNameToString(current)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i would just check the node to be a string literal. |
||
return links.resolvedType = anyType; | ||
} | ||
currentNamespace = next; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unknownType? |
||
} | ||
links.resolvedType = targetMeaning === SymbolFlags.Value ? getTypeOfSymbol(currentNamespace) : getDeclaredTypeOfSymbol(currentNamespace); | ||
} | ||
else { | ||
if (moduleSymbol.flags & targetMeaning) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should not resolveExternalModule already create an error? |
||
links.resolvedType = targetMeaning === SymbolFlags.Value ? getTypeOfSymbol(moduleSymbol) : getDeclaredTypeOfSymbol(moduleSymbol); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unknownType? |
||
else { | ||
error(node, targetMeaning === SymbolFlags.Value ? Diagnostics.Module_0_does_not_refer_to_a_value_but_is_used_as_a_value_here : Diagnostics.Module_0_does_not_refer_to_a_type_but_is_used_as_a_type_here, moduleName); | ||
links.resolvedType = anyType; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how would this fail? should not always return a symbol? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it can, was probably just being a bit too defensive (the function clearly can return |
||
} | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe extract to a recursive function to avoid the complexity. |
||
return links.resolvedType; | ||
} | ||
|
||
function getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node: TypeNode): Type { | ||
const links = getNodeLinks(node); | ||
if (!links.resolvedType) { | ||
|
@@ -8704,6 +8773,8 @@ namespace ts { | |
return getTypeFromConditionalTypeNode(<ConditionalTypeNode>node); | ||
case SyntaxKind.InferType: | ||
return getTypeFromInferTypeNode(<InferTypeNode>node); | ||
case SyntaxKind.ImportTypeNode: | ||
return getTypeFromImportTypeNode(<ImportTypeNode>node); | ||
// This function assumes that an identifier or qualified name is a type expression | ||
// Callers should first ensure this by calling isTypeNode | ||
case SyntaxKind.Identifier: | ||
|
@@ -20609,6 +20680,11 @@ namespace ts { | |
checkSourceElement(node.typeParameter); | ||
} | ||
|
||
function checkImportType(node: ImportTypeNode) { | ||
checkSourceElement(node.argument); | ||
getTypeFromTypeNode(node); | ||
} | ||
|
||
function isPrivateWithinAmbient(node: Node): boolean { | ||
return hasModifier(node, ModifierFlags.Private) && !!(node.flags & NodeFlags.Ambient); | ||
} | ||
|
@@ -24356,6 +24432,8 @@ namespace ts { | |
return checkConditionalType(<ConditionalTypeNode>node); | ||
case SyntaxKind.InferType: | ||
return checkInferType(<InferTypeNode>node); | ||
case SyntaxKind.ImportTypeNode: | ||
return checkImportType(<ImportTypeNode>node); | ||
case SyntaxKind.JSDocAugmentsTag: | ||
return checkJSDocAugmentsTag(node as JSDocAugmentsTag); | ||
case SyntaxKind.JSDocTypedefTag: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -951,6 +951,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 | ||
}, | ||
"Import specifier must be a string literal type, but here is '{0}'.": { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know. i am just saying whatever There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
}, | ||
|
||
"Duplicate identifier '{0}'.": { | ||
"category": "Error", | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure i understand why we need to do the resolution on two phases, given that we only support string literals as arguments to
import
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
String literal types. Right now it's not an error to say
the modules in question just only get loaded if they're already included in the build, otherwise produce an error. (see
importTypeNested
test.) I can restrain this further, but I don't much see the point in limiting it? I could also handle unions pretty easily as it turns out, too (by bypassing normal typequery logic I've given myself a place to return union types and do multiple namespace lookups).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we have chatted offline. i think the real value of such feature is to enable higher order types, since we have decided not to do that, i would say no need for the complexity. I would just remove all the deferred module resolution stuff, and keep it simple.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parsing a type to be future proof is fine. but just make it such that the argument has to be string literal node.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mhegazy Done. It now issues an error if not a string literal, and the late-resolution code has been removed.