Skip to content

Commit

Permalink
Support export type * (#52217)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewbranch authored Jan 20, 2023
1 parent 2acbcee commit f576398
Show file tree
Hide file tree
Showing 48 changed files with 1,519 additions and 72 deletions.
80 changes: 60 additions & 20 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3443,7 +3443,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (result && errorLocation && meaning & SymbolFlags.Value && result.flags & SymbolFlags.Alias && !(result.flags & SymbolFlags.Value) && !isValidTypeOnlyAliasUseSite(errorLocation)) {
const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(result, SymbolFlags.Value);
if (typeOnlyDeclaration) {
const message = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier
const message = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier || typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration || typeOnlyDeclaration.kind === SyntaxKind.NamespaceExport
? Diagnostics._0_cannot_be_used_as_a_value_because_it_was_exported_using_export_type
: Diagnostics._0_cannot_be_used_as_a_value_because_it_was_imported_using_import_type;
const unescapedName = unescapeLeadingUnderscores(name);
Expand All @@ -3464,7 +3464,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
diagnostic,
createDiagnosticForNode(
typeOnlyDeclaration,
typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier ? Diagnostics._0_was_exported_here : Diagnostics._0_was_imported_here,
typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier || typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration || typeOnlyDeclaration.kind === SyntaxKind.NamespaceExport
? Diagnostics._0_was_exported_here
: Diagnostics._0_was_imported_here,
unescapedName));
}

Expand Down Expand Up @@ -3861,15 +3863,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node: ImportEqualsDeclaration, resolved: Symbol | undefined) {
if (markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false) && !node.isTypeOnly) {
const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(getSymbolOfDeclaration(node))!;
const isExport = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier;
const isExport = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier || typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration;
const message = isExport
? Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_exported_using_export_type
: Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_imported_using_import_type;
const relatedMessage = isExport
? Diagnostics._0_was_exported_here
: Diagnostics._0_was_imported_here;

const name = unescapeLeadingUnderscores(typeOnlyDeclaration.name.escapedText);
// TODO: how to get name for export *?
const name = typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration ? "*" : unescapeLeadingUnderscores(typeOnlyDeclaration.name.escapedText);
addRelatedInfo(error(node.moduleReference, message), createDiagnosticForNode(typeOnlyDeclaration, relatedMessage, name));
}
}
Expand Down Expand Up @@ -4083,7 +4086,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (symbol.flags & SymbolFlags.Module) {
const exportSymbol = getExportsOfSymbol(symbol).get(name.escapedText);
const resolved = resolveSymbol(exportSymbol, dontResolveAlias);
markSymbolOfAliasDeclarationIfTypeOnly(specifier, exportSymbol, resolved, /*overwriteEmpty*/ false);
const exportStarDeclaration = getSymbolLinks(symbol).typeOnlyExportStarMap?.get(name.escapedText);
markSymbolOfAliasDeclarationIfTypeOnly(specifier, exportSymbol, resolved, /*overwriteEmpty*/ false, exportStarDeclaration, name.escapedText);
return resolved;
}
}
Expand Down Expand Up @@ -4444,6 +4448,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
immediateTarget: Symbol | undefined,
finalTarget: Symbol | undefined,
overwriteEmpty: boolean,
exportStarDeclaration?: ExportDeclaration & { readonly isTypeOnly: true },
exportStarName?: __String,
): boolean {
if (!aliasDeclaration || isPropertyAccessExpression(aliasDeclaration)) return false;

Expand All @@ -4455,6 +4461,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
links.typeOnlyDeclaration = aliasDeclaration;
return true;
}
if (exportStarDeclaration) {
const links = getSymbolLinks(sourceSymbol);
links.typeOnlyDeclaration = exportStarDeclaration;
if (sourceSymbol.escapedName !== exportStarName) {
links.typeOnlyExportStarName = exportStarName;
}
return true;
}

const links = getSymbolLinks(sourceSymbol);
return markSymbolOfAliasDeclarationIfTypeOnlyWorker(links, immediateTarget, overwriteEmpty)
Expand All @@ -4480,7 +4494,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return links.typeOnlyDeclaration || undefined;
}
if (links.typeOnlyDeclaration) {
return getAllSymbolFlags(resolveAlias(links.typeOnlyDeclaration.symbol)) & include ? links.typeOnlyDeclaration : undefined;
const resolved = links.typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration
? resolveSymbol(getExportsOfModule(links.typeOnlyDeclaration.symbol.parent!).get(links.typeOnlyExportStarName || symbol.escapedName))!
: resolveAlias(links.typeOnlyDeclaration.symbol);
return getAllSymbolFlags(resolved) & include ? links.typeOnlyDeclaration : undefined;
}
return undefined;
}
Expand Down Expand Up @@ -5203,7 +5220,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

function getExportsOfModule(moduleSymbol: Symbol): SymbolTable {
const links = getSymbolLinks(moduleSymbol);
return links.resolvedExports || (links.resolvedExports = getExportsOfModuleWorker(moduleSymbol));
if (!links.resolvedExports) {
const { exports, typeOnlyExportStarMap } = getExportsOfModuleWorker(moduleSymbol);
links.resolvedExports = exports;
links.typeOnlyExportStarMap = typeOnlyExportStarMap;
}
return links.resolvedExports;
}

interface ExportCollisionTracker {
Expand Down Expand Up @@ -5243,21 +5265,38 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
});
}

function getExportsOfModuleWorker(moduleSymbol: Symbol): SymbolTable {
function getExportsOfModuleWorker(moduleSymbol: Symbol) {
const visitedSymbols: Symbol[] = [];
let typeOnlyExportStarMap: UnderscoreEscapedMap<ExportDeclaration & { readonly isTypeOnly: true }> | undefined;
const nonTypeOnlyNames = new Set<__String>();

// A module defined by an 'export=' consists of one export that needs to be resolved
moduleSymbol = resolveExternalModuleSymbol(moduleSymbol);
const exports = visit(moduleSymbol) || emptySymbols;

return visit(moduleSymbol) || emptySymbols;
if (typeOnlyExportStarMap) {
nonTypeOnlyNames.forEach(name => typeOnlyExportStarMap!.delete(name));
}

return {
exports,
typeOnlyExportStarMap,
};

// The ES6 spec permits export * declarations in a module to circularly reference the module itself. For example,
// module 'a' can 'export * from "b"' and 'b' can 'export * from "a"' without error.
function visit(symbol: Symbol | undefined): SymbolTable | undefined {
function visit(symbol: Symbol | undefined, exportStar?: ExportDeclaration, isTypeOnly?: boolean): SymbolTable | undefined {
if (!isTypeOnly && symbol?.exports) {
// Add non-type-only names before checking if we've visited this module,
// because we might have visited it via an 'export type *', and visiting
// again with 'export *' will override the type-onlyness of its exports.
symbol.exports.forEach((_, name) => nonTypeOnlyNames.add(name));
}
if (!(symbol && symbol.exports && pushIfUnique(visitedSymbols, symbol))) {
return;
}
const symbols = new Map(symbol.exports);

// All export * declarations are collected in an __export symbol by the binder
const exportStars = symbol.exports.get(InternalSymbolName.ExportStar);
if (exportStars) {
Expand All @@ -5266,7 +5305,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (exportStars.declarations) {
for (const node of exportStars.declarations) {
const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier!);
const exportedSymbols = visit(resolvedModule);
const exportedSymbols = visit(resolvedModule, node as ExportDeclaration, isTypeOnly || (node as ExportDeclaration).isTypeOnly);
extendExportSymbols(
nestedSymbols,
exportedSymbols,
Expand All @@ -5291,6 +5330,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
});
extendExportSymbols(symbols, nestedSymbols);
}
if (exportStar?.isTypeOnly) {
typeOnlyExportStarMap ??= new Map();
symbols.forEach((_, escapedName) => typeOnlyExportStarMap!.set(
escapedName,
exportStar as ExportDeclaration & { readonly isTypeOnly: true }));
}
return symbols;
}
}
Expand Down Expand Up @@ -8597,7 +8642,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
for (const node of symbol.declarations) {
const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier!);
if (!resolvedModule) continue;
addResult(factory.createExportDeclaration(/*modifiers*/ undefined, /*isTypeOnly*/ false, /*exportClause*/ undefined, factory.createStringLiteral(getSpecifierForModuleSymbol(resolvedModule, context))), ModifierFlags.None);
addResult(factory.createExportDeclaration(/*modifiers*/ undefined, /*isTypeOnly*/ (node as ExportDeclaration).isTypeOnly, /*exportClause*/ undefined, factory.createStringLiteral(getSpecifierForModuleSymbol(resolvedModule, context))), ModifierFlags.None);
}
}
}
Expand Down Expand Up @@ -12219,7 +12264,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!links[resolutionKind]) {
const isStatic = resolutionKind === MembersOrExportsResolutionKind.resolvedExports;
const earlySymbols = !isStatic ? symbol.members :
symbol.flags & SymbolFlags.Module ? getExportsOfModuleWorker(symbol) :
symbol.flags & SymbolFlags.Module ? getExportsOfModuleWorker(symbol).exports :
symbol.exports;

// In the event we recursively resolve the members/exports of the symbol, we
Expand Down Expand Up @@ -43738,13 +43783,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function checkGrammarExportDeclaration(node: ExportDeclaration): boolean {
if (node.isTypeOnly) {
if (node.exportClause?.kind === SyntaxKind.NamedExports) {
return checkGrammarNamedImportsOrExports(node.exportClause);
}
else {
return grammarErrorOnNode(node, Diagnostics.Only_named_exports_may_use_export_type);
}
if (node.isTypeOnly && node.exportClause?.kind === SyntaxKind.NamedExports) {
return checkGrammarNamedImportsOrExports(node.exportClause);
}
return false;
}
Expand Down
4 changes: 0 additions & 4 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1232,10 +1232,6 @@
"category": "Error",
"code": 1382
},
"Only named exports may use 'export type'.": {
"category": "Error",
"code": 1383
},
"Function type notation must be parenthesized when used in a union type.": {
"category": "Error",
"code": 1385
Expand Down
13 changes: 12 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3788,16 +3788,25 @@ export type TypeOnlyCompatibleAliasDeclaration =
| ImportEqualsDeclaration
| NamespaceImport
| ImportOrExportSpecifier
| ExportDeclaration
| NamespaceExport
;

export type TypeOnlyAliasDeclaration =
export type TypeOnlyImportDeclaration =
| ImportClause & { readonly isTypeOnly: true, readonly name: Identifier }
| ImportEqualsDeclaration & { readonly isTypeOnly: true }
| NamespaceImport & { readonly parent: ImportClause & { readonly isTypeOnly: true } }
| ImportSpecifier & ({ readonly isTypeOnly: true } | { readonly parent: NamedImports & { readonly parent: ImportClause & { readonly isTypeOnly: true } } })
;

export type TypeOnlyExportDeclaration =
| ExportSpecifier & ({ readonly isTypeOnly: true } | { readonly parent: NamedExports & { readonly parent: ExportDeclaration & { readonly isTypeOnly: true } } })
| ExportDeclaration & { readonly isTypeOnly: true } // export * from "mod"
| NamespaceExport & { readonly parent: ExportDeclaration & { readonly isTypeOnly: true } } // export * as ns from "mod"
;

export type TypeOnlyAliasDeclaration = TypeOnlyImportDeclaration | TypeOnlyExportDeclaration;

/**
* This is either an `export =` or an `export default` declaration.
* Unless `isExportEquals` is set, this node was parsed as an `export default`.
Expand Down Expand Up @@ -5792,6 +5801,8 @@ export interface SymbolLinks {
deferralParent?: Type; // Source union/intersection of a deferred type
cjsExportMerged?: Symbol; // Version of the symbol with all non export= exports merged with the export= target
typeOnlyDeclaration?: TypeOnlyAliasDeclaration | false; // First resolved alias declaration that makes the symbol only usable in type constructs
typeOnlyExportStarMap?: UnderscoreEscapedMap<ExportDeclaration & { readonly isTypeOnly: true }>; // Set on a module symbol when some of its exports were resolved through a 'export type * from "mod"' declaration
typeOnlyExportStarName?: __String; // Set to the name of the symbol re-exported by an 'export type *' declaration, when different from the symbol name
isConstructorDeclaredProperty?: boolean; // Property declared through 'this.x = ...' assignment in constructor
tupleLabelDeclaration?: NamedTupleMember | ParameterDeclaration; // Declaration associated with the tuple's label
accessibleChainCache?: Map<string, Symbol[] | undefined>;
Expand Down
29 changes: 23 additions & 6 deletions src/compiler/utilitiesPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import {
EnumDeclaration,
every,
ExportAssignment,
ExportDeclaration,
ExportSpecifier,
Expression,
FileReference,
Expand Down Expand Up @@ -92,7 +93,6 @@ import {
Identifier,
ImportClause,
ImportEqualsDeclaration,
ImportOrExportSpecifier,
ImportSpecifier,
ImportTypeNode,
isAccessExpression,
Expand Down Expand Up @@ -214,6 +214,7 @@ import {
NamedExportBindings,
NamedImportBindings,
NamespaceBody,
NamespaceExport,
NamespaceImport,
NewExpression,
Node,
Expand Down Expand Up @@ -270,6 +271,8 @@ import {
TypeElement,
TypeNode,
TypeOnlyAliasDeclaration,
TypeOnlyExportDeclaration,
TypeOnlyImportDeclaration,
TypeParameterDeclaration,
TypeReferenceType,
UnaryExpression,
Expand Down Expand Up @@ -1480,19 +1483,33 @@ export function isImportOrExportSpecifier(node: Node): node is ImportSpecifier |
return isImportSpecifier(node) || isExportSpecifier(node);
}

export function isTypeOnlyImportOrExportDeclaration(node: Node): node is TypeOnlyAliasDeclaration {
export function isTypeOnlyImportDeclaration(node: Node): node is TypeOnlyImportDeclaration {
switch (node.kind) {
case SyntaxKind.ImportSpecifier:
case SyntaxKind.ExportSpecifier:
return (node as ImportOrExportSpecifier).isTypeOnly || (node as ImportOrExportSpecifier).parent.parent.isTypeOnly;
return (node as ImportSpecifier).isTypeOnly || (node as ImportSpecifier).parent.parent.isTypeOnly;
case SyntaxKind.NamespaceImport:
return (node as NamespaceImport).parent.isTypeOnly;
case SyntaxKind.ImportClause:
case SyntaxKind.ImportEqualsDeclaration:
return (node as ImportClause | ImportEqualsDeclaration).isTypeOnly;
default:
return false;
}
return false;
}

export function isTypeOnlyExportDeclaration(node: Node): node is TypeOnlyExportDeclaration {
switch (node.kind) {
case SyntaxKind.ExportSpecifier:
return (node as ExportSpecifier).isTypeOnly || (node as ExportSpecifier).parent.parent.isTypeOnly;
case SyntaxKind.ExportDeclaration:
return (node as ExportDeclaration).isTypeOnly && !!(node as ExportDeclaration).moduleSpecifier && !(node as ExportDeclaration).exportClause;
case SyntaxKind.NamespaceExport:
return (node as NamespaceExport).parent.isTypeOnly;
}
return false;
}

export function isTypeOnlyImportOrExportDeclaration(node: Node): node is TypeOnlyAliasDeclaration {
return isTypeOnlyImportDeclaration(node) || isTypeOnlyExportDeclaration(node);
}

export function isAssertionKey(node: Node): node is AssertionKey {
Expand Down
Loading

0 comments on commit f576398

Please sign in to comment.