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

Support export type * #52217

Merged
merged 8 commits into from
Jan 20, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
78 changes: 59 additions & 19 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3386,7 +3386,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 @@ -3407,7 +3407,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 @@ -3804,15 +3806,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 @@ -4025,7 +4028,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 @@ -4386,6 +4390,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 @@ -4397,6 +4403,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 @@ -4422,7 +4436,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 @@ -5133,7 +5150,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 @@ -5173,21 +5195,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 @@ -5196,7 +5235,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 @@ -5221,6 +5260,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 @@ -12131,7 +12176,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 @@ -42983,13 +43028,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 @@ -1196,10 +1196,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 @@ -3817,16 +3817,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 @@ -5802,6 +5811,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 @@ -54,6 +54,7 @@ import {
EnumDeclaration,
every,
ExportAssignment,
ExportDeclaration,
ExportSpecifier,
Expression,
FileReference,
Expand Down Expand Up @@ -91,7 +92,6 @@ import {
Identifier,
ImportClause,
ImportEqualsDeclaration,
ImportOrExportSpecifier,
ImportSpecifier,
ImportTypeNode,
isAccessExpression,
Expand Down Expand Up @@ -208,6 +208,7 @@ import {
NamedExportBindings,
NamedImportBindings,
NamespaceBody,
NamespaceExport,
NamespaceImport,
NewExpression,
Node,
Expand Down Expand Up @@ -261,6 +262,8 @@ import {
TypeElement,
TypeNode,
TypeOnlyAliasDeclaration,
TypeOnlyExportDeclaration,
TypeOnlyImportDeclaration,
TypeParameterDeclaration,
TypeReferenceType,
UnaryExpression,
Expand Down Expand Up @@ -1448,19 +1451,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
13 changes: 7 additions & 6 deletions src/services/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ import {
isTypeLiteralNode,
isTypeNode,
isTypeOfExpression,
isTypeOnlyImportDeclaration,
isTypeOnlyImportOrExportDeclaration,
isTypeReferenceType,
isValidTypeOnlyAliasUseSite,
Expand Down Expand Up @@ -351,7 +352,7 @@ import {
typeHasCallOrConstructSignatures,
TypeLiteralNode,
TypeNode,
TypeOnlyAliasDeclaration,
TypeOnlyImportDeclaration,
TypeQueryNode,
TypeReferenceNode,
unescapeLeadingUnderscores,
Expand Down Expand Up @@ -466,8 +467,8 @@ interface SymbolOriginInfoResolvedExport extends SymbolOriginInfo {
moduleSpecifier: string;
}

interface SymbolOriginInfoTypeOnlyAlias extends SymbolOriginInfo {
declaration: TypeOnlyAliasDeclaration;
interface SymbolOriginInfoTypeOnlyImport extends SymbolOriginInfo {
declaration: TypeOnlyImportDeclaration;
}

interface SymbolOriginInfoObjectLiteralMethod extends SymbolOriginInfo {
Expand Down Expand Up @@ -508,7 +509,7 @@ function originIsNullableMember(origin: SymbolOriginInfo): boolean {
return !!(origin.kind & SymbolOriginInfoKind.Nullable);
}

function originIsTypeOnlyAlias(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoTypeOnlyAlias {
function originIsTypeOnlyAlias(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoTypeOnlyImport {
return !!(origin && origin.kind & SymbolOriginInfoKind.TypeOnlyAlias);
}

Expand Down Expand Up @@ -3330,9 +3331,9 @@ function getCompletionData(
symbolToSortTextMap[getSymbolId(symbol)] = SortText.GlobalsOrKeywords;
}
if (typeOnlyAliasNeedsPromotion && !(symbol.flags & SymbolFlags.Value)) {
const typeOnlyAliasDeclaration = symbol.declarations && find(symbol.declarations, isTypeOnlyImportOrExportDeclaration);
const typeOnlyAliasDeclaration = symbol.declarations && find(symbol.declarations, isTypeOnlyImportDeclaration);
if (typeOnlyAliasDeclaration) {
const origin: SymbolOriginInfoTypeOnlyAlias = { kind: SymbolOriginInfoKind.TypeOnlyAlias, declaration: typeOnlyAliasDeclaration };
const origin: SymbolOriginInfoTypeOnlyImport = { kind: SymbolOriginInfoKind.TypeOnlyAlias, declaration: typeOnlyAliasDeclaration };
symbolToOriginInfoMap[i] = origin;
}
}
Expand Down
Loading