Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 2fac535

Browse filesBrowse files
authored
Fix type-only imports in interface 'extends' and import=/export= (microsoft#36496)
* Handle when files get checked in different orders * Fix interface extends clause * Fix import= something type only from a module * Revert apparently unnecessary addition * Revert "Revert apparently unnecessary addition" This reverts commit 7444b0b. * Disallow `import = a.b.c` on anything with type-only imports * Safety first * Add test for TS Server single-file open * Add big comment * Extract error reporting function for import aliases * Delete blank line * Un-export, comment, and colocate some utils * Combine 3 type-only marking function calls into one * Add more export default tests
1 parent aec732a commit 2fac535
Copy full SHA for 2fac535
Expand file treeCollapse file tree

31 files changed

+1298
-52
lines changed
Open diff view settings
Collapse file

‎src/compiler/checker.ts‎

Copy file name to clipboardExpand all lines: src/compiler/checker.ts
+92-29Lines changed: 92 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2221,9 +2221,30 @@ namespace ts {
22212221

22222222
function getTargetOfImportEqualsDeclaration(node: ImportEqualsDeclaration, dontResolveAlias: boolean): Symbol | undefined {
22232223
if (node.moduleReference.kind === SyntaxKind.ExternalModuleReference) {
2224-
return resolveExternalModuleSymbol(resolveExternalModuleName(node, getExternalModuleImportEqualsDeclarationExpression(node)));
2224+
const immediate = resolveExternalModuleName(node, getExternalModuleImportEqualsDeclarationExpression(node));
2225+
const resolved = resolveExternalModuleSymbol(immediate);
2226+
markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false);
2227+
return resolved;
2228+
}
2229+
const resolved = getSymbolOfPartOfRightHandSideOfImportEquals(node.moduleReference, dontResolveAlias);
2230+
checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node, resolved);
2231+
return resolved;
2232+
}
2233+
2234+
function checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node: ImportEqualsDeclaration, resolved: Symbol | undefined) {
2235+
if (markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false)) {
2236+
const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(getSymbolOfNode(node))!;
2237+
const message = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier
2238+
? Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_exported_using_export_type
2239+
: Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_imported_using_import_type;
2240+
const relatedMessage = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier
2241+
? Diagnostics._0_was_exported_here
2242+
: Diagnostics._0_was_imported_here;
2243+
// Non-null assertion is safe because the optionality comes from ImportClause,
2244+
// but if an ImportClause was the typeOnlyDeclaration, it had to have a `name`.
2245+
const name = unescapeLeadingUnderscores(typeOnlyDeclaration.name!.escapedText);
2246+
addRelatedInfo(error(node.moduleReference, message), createDiagnosticForNode(typeOnlyDeclaration, relatedMessage, name));
22252247
}
2226-
return getSymbolOfPartOfRightHandSideOfImportEquals(node.moduleReference, dontResolveAlias);
22272248
}
22282249

22292250
function resolveExportByName(moduleSymbol: Symbol, name: __String, sourceNode: TypeOnlyCompatibleAliasDeclaration | undefined, dontResolveAlias: boolean) {
@@ -2232,8 +2253,9 @@ namespace ts {
22322253
return getPropertyOfType(getTypeOfSymbol(exportValue), name);
22332254
}
22342255
const exportSymbol = moduleSymbol.exports!.get(name);
2235-
markSymbolOfAliasDeclarationIfResolvesToTypeOnly(sourceNode, exportSymbol);
2236-
return resolveSymbol(exportSymbol, dontResolveAlias);
2256+
const resolved = resolveSymbol(exportSymbol, dontResolveAlias);
2257+
markSymbolOfAliasDeclarationIfTypeOnly(sourceNode, exportSymbol, resolved, /*overwriteEmpty*/ false);
2258+
return resolved;
22372259
}
22382260

22392261
function isSyntacticDefault(node: Node) {
@@ -2273,8 +2295,6 @@ namespace ts {
22732295

22742296
function getTargetOfImportClause(node: ImportClause, dontResolveAlias: boolean): Symbol | undefined {
22752297
const moduleSymbol = resolveExternalModuleName(node, node.parent.moduleSpecifier);
2276-
markSymbolOfAliasDeclarationIfTypeOnly(node);
2277-
22782298
if (moduleSymbol) {
22792299
let exportDefaultSymbol: Symbol | undefined;
22802300
if (isShorthandAmbientModuleSymbol(moduleSymbol)) {
@@ -2316,16 +2336,21 @@ namespace ts {
23162336
}
23172337
else if (hasSyntheticDefault) {
23182338
// per emit behavior, a synthetic default overrides a "real" .default member if `__esModule` is not present
2319-
return resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias);
2339+
const resolved = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias);
2340+
markSymbolOfAliasDeclarationIfTypeOnly(node, moduleSymbol, resolved, /*overwriteTypeOnly*/ false);
2341+
return resolved;
23202342
}
2343+
markSymbolOfAliasDeclarationIfTypeOnly(node, exportDefaultSymbol, /*finalTarget*/ undefined, /*overwriteTypeOnly*/ false);
23212344
return exportDefaultSymbol;
23222345
}
23232346
}
23242347

23252348
function getTargetOfNamespaceImport(node: NamespaceImport, dontResolveAlias: boolean): Symbol | undefined {
23262349
const moduleSpecifier = node.parent.parent.moduleSpecifier;
2327-
markSymbolOfAliasDeclarationIfTypeOnly(node);
2328-
return resolveESModuleSymbol(resolveExternalModuleName(node, moduleSpecifier), moduleSpecifier, dontResolveAlias, /*suppressUsageError*/ false);
2350+
const immediate = resolveExternalModuleName(node, moduleSpecifier);
2351+
const resolved = resolveESModuleSymbol(immediate, moduleSpecifier, dontResolveAlias, /*suppressUsageError*/ false);
2352+
markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false);
2353+
return resolved;
23292354
}
23302355

23312356
function getTargetOfNamespaceExport(node: NamespaceExport, dontResolveAlias: boolean): Symbol | undefined {
@@ -2371,8 +2396,9 @@ namespace ts {
23712396
if (symbol.flags & SymbolFlags.Module) {
23722397
const name = (specifier.propertyName ?? specifier.name).escapedText;
23732398
const exportSymbol = getExportsOfSymbol(symbol).get(name);
2374-
markSymbolOfAliasDeclarationIfResolvesToTypeOnly(specifier, exportSymbol);
2375-
return resolveSymbol(exportSymbol, dontResolveAlias);
2399+
const resolved = resolveSymbol(exportSymbol, dontResolveAlias);
2400+
markSymbolOfAliasDeclarationIfTypeOnly(specifier, exportSymbol, resolved, /*overwriteEmpty*/ false);
2401+
return resolved;
23762402
}
23772403
}
23782404

@@ -2472,24 +2498,28 @@ namespace ts {
24722498
}
24732499

24742500
function getTargetOfImportSpecifier(node: ImportSpecifier, dontResolveAlias: boolean): Symbol | undefined {
2475-
markSymbolOfAliasDeclarationIfTypeOnly(node);
2476-
return getExternalModuleMember(node.parent.parent.parent, node, dontResolveAlias);
2501+
const resolved = getExternalModuleMember(node.parent.parent.parent, node, dontResolveAlias);
2502+
markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false);
2503+
return resolved;
24772504
}
24782505

24792506
function getTargetOfNamespaceExportDeclaration(node: NamespaceExportDeclaration, dontResolveAlias: boolean): Symbol {
24802507
return resolveExternalModuleSymbol(node.parent.symbol, dontResolveAlias);
24812508
}
24822509

24832510
function getTargetOfExportSpecifier(node: ExportSpecifier, meaning: SymbolFlags, dontResolveAlias?: boolean) {
2484-
markSymbolOfAliasDeclarationIfTypeOnly(node);
2485-
return node.parent.parent.moduleSpecifier ?
2511+
const resolved = node.parent.parent.moduleSpecifier ?
24862512
getExternalModuleMember(node.parent.parent, node, dontResolveAlias) :
24872513
resolveEntityName(node.propertyName || node.name, meaning, /*ignoreErrors*/ false, dontResolveAlias);
2514+
markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false);
2515+
return resolved;
24882516
}
24892517

24902518
function getTargetOfExportAssignment(node: ExportAssignment | BinaryExpression, dontResolveAlias: boolean): Symbol | undefined {
24912519
const expression = (isExportAssignment(node) ? node.expression : node.right) as EntityNameExpression | ClassExpression;
2492-
return getTargetOfAliasLikeExpression(expression, dontResolveAlias);
2520+
const resolved = getTargetOfAliasLikeExpression(expression, dontResolveAlias);
2521+
markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false);
2522+
return resolved;
24932523
}
24942524

24952525
function getTargetOfAliasLikeExpression(expression: Expression, dontResolveAlias: boolean) {
@@ -2586,22 +2616,55 @@ namespace ts {
25862616
return links.target;
25872617
}
25882618

2589-
function markSymbolOfAliasDeclarationIfResolvesToTypeOnly(aliasDeclaration: TypeOnlyCompatibleAliasDeclaration | undefined, resolvesToSymbol: Symbol | undefined) {
2590-
if (!aliasDeclaration || !resolvesToSymbol) return;
2619+
/**
2620+
* Marks a symbol as type-only if its declaration is syntactically type-only.
2621+
* If it is not itself marked type-only, but resolves to a type-only alias
2622+
* somewhere in its resolution chain, save a reference to the type-only alias declaration
2623+
* so the alias _not_ marked type-only can be identified as _transitively_ type-only.
2624+
*
2625+
* This function is called on each alias declaration that could be type-only or resolve to
2626+
* another type-only alias during `resolveAlias`, so that later, when an alias is used in a
2627+
* JS-emitting expression, we can quickly determine if that symbol is effectively type-only
2628+
* and issue an error if so.
2629+
*
2630+
* @param aliasDeclaration The alias declaration not marked as type-only
2631+
* has already been marked as not resolving to a type-only alias. Used when recursively resolving qualified
2632+
* names of import aliases, e.g. `import C = a.b.C`. If namespace `a` is not found to be type-only, the
2633+
* import declaration will initially be marked as not resolving to a type-only symbol. But, namespace `b`
2634+
* must still be checked for a type-only marker, overwriting the previous negative result if found.
2635+
* @param immediateTarget The symbol to which the alias declaration immediately resolves
2636+
* @param finalTarget The symbol to which the alias declaration ultimately resolves
2637+
* @param overwriteEmpty Checks `resolvesToSymbol` for type-only declarations even if `aliasDeclaration`
2638+
*/
2639+
function markSymbolOfAliasDeclarationIfTypeOnly(
2640+
aliasDeclaration: Declaration | undefined,
2641+
immediateTarget: Symbol | undefined,
2642+
finalTarget: Symbol | undefined,
2643+
overwriteEmpty: boolean,
2644+
): boolean {
2645+
if (!aliasDeclaration) return false;
2646+
2647+
// If the declaration itself is type-only, mark it and return.
2648+
// No need to check what it resolves to.
25912649
const sourceSymbol = getSymbolOfNode(aliasDeclaration);
2592-
const links = getSymbolLinks(sourceSymbol);
2593-
if (links.typeOnlyDeclaration === undefined) {
2594-
const typeOnly = find(resolvesToSymbol.declarations, isTypeOnlyImportOrExportDeclaration);
2595-
links.typeOnlyDeclaration = typeOnly ?? getSymbolLinks(resolvesToSymbol).typeOnlyDeclaration ?? false;
2650+
if (isTypeOnlyImportOrExportDeclaration(aliasDeclaration)) {
2651+
const links = getSymbolLinks(sourceSymbol);
2652+
links.typeOnlyDeclaration = aliasDeclaration;
2653+
return true;
25962654
}
2655+
2656+
const links = getSymbolLinks(sourceSymbol);
2657+
return markSymbolOfAliasDeclarationIfTypeOnlyWorker(links, immediateTarget, overwriteEmpty)
2658+
|| markSymbolOfAliasDeclarationIfTypeOnlyWorker(links, finalTarget, overwriteEmpty);
25972659
}
25982660

2599-
function markSymbolOfAliasDeclarationIfTypeOnly(aliasDeclaration: TypeOnlyCompatibleAliasDeclaration) {
2600-
if (isTypeOnlyImportOrExportDeclaration(aliasDeclaration)) {
2601-
const symbol = getSymbolOfNode(aliasDeclaration);
2602-
const links = getSymbolLinks(symbol);
2603-
links.typeOnlyDeclaration = aliasDeclaration;
2661+
function markSymbolOfAliasDeclarationIfTypeOnlyWorker(aliasDeclarationLinks: SymbolLinks, target: Symbol | undefined, overwriteEmpty: boolean): boolean {
2662+
if (target && (aliasDeclarationLinks.typeOnlyDeclaration === undefined || overwriteEmpty && aliasDeclarationLinks.typeOnlyDeclaration === false)) {
2663+
const exportSymbol = target.exports?.get(InternalSymbolName.ExportEquals) ?? target;
2664+
const typeOnly = exportSymbol.declarations && find(exportSymbol.declarations, isTypeOnlyImportOrExportDeclaration);
2665+
aliasDeclarationLinks.typeOnlyDeclaration = typeOnly ?? getSymbolLinks(exportSymbol).typeOnlyDeclaration ?? false;
26042666
}
2667+
return !!aliasDeclarationLinks.typeOnlyDeclaration;
26052668
}
26062669

26072670
/** Indicates that a symbol directly or indirectly resolves to a type-only import or export. */
@@ -2739,8 +2802,8 @@ namespace ts {
27392802
throw Debug.assertNever(name, "Unknown entity name kind.");
27402803
}
27412804
Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here.");
2742-
if (isIdentifier(name) && symbol.flags & SymbolFlags.Alias) {
2743-
markSymbolOfAliasDeclarationIfResolvesToTypeOnly(getTypeOnlyCompatibleAliasDeclarationFromName(name), symbol);
2805+
if (isEntityName(name) && (symbol.flags & SymbolFlags.Alias || name.parent.kind === SyntaxKind.ExportAssignment)) {
2806+
markSymbolOfAliasDeclarationIfTypeOnly(getAliasDeclarationFromName(name), symbol, /*finalTarget*/ undefined, /*overwriteEmpty*/ true);
27442807
}
27452808
return (symbol.flags & meaning) || dontResolveAlias ? symbol : resolveAlias(symbol);
27462809
}
Collapse file

‎src/compiler/core.ts‎

Copy file name to clipboardExpand all lines: src/compiler/core.ts
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2011,4 +2011,4 @@ namespace ts {
20112011
}
20122012
}
20132013
}
2014-
}
2014+
}
Collapse file

‎src/compiler/diagnosticMessages.json‎

Copy file name to clipboardExpand all lines: src/compiler/diagnosticMessages.json
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,6 +1127,14 @@
11271127
"category": "Error",
11281128
"code": 1378
11291129
},
1130+
"An import alias cannot reference a declaration that was exported using 'export type'.": {
1131+
"category": "Error",
1132+
"code": 1379
1133+
},
1134+
"An import alias cannot reference a declaration that was imported using 'import type'.": {
1135+
"category": "Error",
1136+
"code": 1380
1137+
},
11301138

11311139
"The types of '{0}' are incompatible between these types.": {
11321140
"category": "Error",

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.