diff --git a/NOTICE b/NOTICE index a557707c9a..a85e051cd1 100644 --- a/NOTICE +++ b/NOTICE @@ -9,6 +9,7 @@ under the licensing terms detailed in LICENSE: * Norton Wang * Alan Pierce * Palmer +* Andy Hanson Portions of this software are derived from third-party works licensed under the following terms: diff --git a/src/ast.ts b/src/ast.ts index ed3d07cf60..6998f03252 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -1821,7 +1821,10 @@ export class ImportStatement extends Statement { path: StringLiteralExpression; /** Normalized path. */ normalizedPath: string; - /** Mangled internal path being referenced. */ + /** + * Mangled internal path being referenced. + * Note: actual referenced path may be this + "/index". See `lookupSourceByPath` in `program.ts`. + */ internalPath: string; } @@ -1972,9 +1975,15 @@ export function mangleInternalName(declaration: DeclarationStatement, asGlobal: return mangleInternalName(parent, asGlobal) + STATIC_DELIMITER + name; } - return asGlobal - ? name - : declaration.range.source.internalPath + PATH_DELIMITER + name; + return asGlobal ? name : getSourceLevelName(declaration.range.source, name); +} + +export function getSourceLevelName({ internalPath }: Source, simpleName: string): string { + return getSourceLevelNameFromInternalPath(internalPath, simpleName); +} + +export function getSourceLevelNameFromInternalPath(internalPath: string, simpleName: string): string { + return internalPath + PATH_DELIMITER + simpleName; } /** Mangles an external to an internal path. */ diff --git a/src/compiler.ts b/src/compiler.ts index 77bdc5e8f4..f31ae60b6c 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -144,7 +144,8 @@ import { nodeIsConstantValue, isLastStatement, - findDecorator + findDecorator, + getSourceLevelName } from "./ast"; import { @@ -1266,9 +1267,7 @@ export class Compiler extends DiagnosticEmitter { if (!members) return; // filespace for (let i = 0, k = members.length; i < k; ++i) { let member = members[i]; - let element = fileLevelExports.get( - statement.range.source.internalPath + PATH_DELIMITER + member.externalName.text - ); + let element = fileLevelExports.get(getSourceLevelName(statement.range.source, member.externalName.text)); if (!element) continue; // reported in Program#initialize switch (element.kind) { case ElementKind.CLASS_PROTOTYPE: { diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index a9672b6348..9f5a89b21e 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -85,6 +85,7 @@ export enum DiagnosticCode { Duplicate_identifier_0 = 2300, Cannot_find_name_0 = 2304, Module_0_has_no_exported_member_1 = 2305, + Module_0_has_already_exported_a_member_named_1_Consider_explicitly_re_exporting_to_resolve_the_ambiguity = 2308, Generic_type_0_requires_1_type_argument_s = 2314, Type_0_is_not_generic = 2315, Type_0_is_not_assignable_to_type_1 = 2322, @@ -204,6 +205,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 2300: return "Duplicate identifier '{0}'."; case 2304: return "Cannot find name '{0}'."; case 2305: return "Module '{0}' has no exported member '{1}'."; + case 2308: return "Module {0} has already exported a member named '{1}'. Consider explicitly re-exporting to resolve the ambiguity."; case 2314: return "Generic type '{0}' requires {1} type argument(s)."; case 2315: return "Type '{0}' is not generic."; case 2322: return "Type '{0}' is not assignable to type '{1}'."; diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index fca908525e..bda1fe0696 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -79,6 +79,7 @@ "Duplicate identifier '{0}'.": 2300, "Cannot find name '{0}'.": 2304, "Module '{0}' has no exported member '{1}'.": 2305, + "Module {0} has already exported a member named '{1}'. Consider explicitly re-exporting to resolve the ambiguity.": 2308, "Generic type '{0}' requires {1} type argument(s).": 2314, "Type '{0}' is not generic.": 2315, "Type '{0}' is not assignable to type '{1}'.": 2322, diff --git a/src/program.ts b/src/program.ts index fad25d7437..f05e9864a6 100644 --- a/src/program.ts +++ b/src/program.ts @@ -5,7 +5,6 @@ import { CommonFlags, - PATH_DELIMITER, STATIC_DELIMITER, INSTANCE_DELIMITER, LIBRARY_PREFIX, @@ -67,7 +66,9 @@ import { VariableStatement, decoratorNameToKind, - findDecorator + findDecorator, + getSourceLevelNameFromInternalPath, + getSourceLevelName, } from "./ast"; import { @@ -105,7 +106,8 @@ import { } from "./module"; import { - CharCode + CharCode, + multiMapAdd } from "./util"; import { @@ -115,15 +117,29 @@ import { /** Represents a yet unresolved import. */ class QueuedImport { localName: string; - externalName: string; - externalNameAlt: string; - declaration: ImportDeclaration | null; // not set if a filespace + exportingSource: Source; // Source that should export (or re-export) exportName + // Null for a filespace import. This is the simple name of the export. + exportName: string | null; + // Null for a filespace import. + declaration: ImportDeclaration | null; } -/** Represents a yet unresolved export. */ +/** + * Represents a yet unresolved export. + * + * Note that only `export { x };` or `export { x } from "./foo";` may be unresolved; + * a direct export is always resolved immediately. + * + * `export *` is handled by an `exportStars` map, not by a QueuedExport. + */ class QueuedExport { - externalName: string; - isReExport: bool; + /** + * Only set for a local re-export `export { x };`. + * Internal name of the thing being reexported. NOT `member.externalName.text`. + */ + localInternalName: string | null; + /** Only set for a module re-export `export { x } from "./foo";`. */ + reExportFromSource: Source | null; member: ExportMember; } @@ -136,7 +152,7 @@ class TypeAlias { /** Represents a module-level export. */ class ModuleExport { element: Element; - identifier: IdentifierExpression; + identifier: IdentifierExpression | null; } /** Represents the kind of an operator overload. */ @@ -325,6 +341,9 @@ export class Program extends DiagnosticEmitter { typeAliases: Map = new Map(); /** File-level exports by exported name. */ fileLevelExports: Map = new Map(); + // Maps each re-export from an `export *` (internal path of the re-export location) to the Source that it came from. + // Used to report errors when two `export *`s conflict. (An `export *` does not conflict with an own export.) + exportsFromExportStar = new Set(); /** Module-level exports by exported name. */ moduleLevelExports: Map = new Map(); @@ -434,6 +453,8 @@ export class Program extends DiagnosticEmitter { var queuedExports = new Map(); var queuedExtends = new Array(); var queuedImplements = new Array(); + // Map from a file to the files it re-exports via `export * from "./foo";` + var exportStars = new Map(); // build initial lookup maps of internal names to declarations for (let i = 0, k = this.sources.length; i < k; ++i) { @@ -458,7 +479,7 @@ export class Program extends DiagnosticEmitter { break; } case NodeKind.EXPORT: { - this.initializeExports(statement, queuedExports); + this.initializeExports(source, statement, queuedExports, exportStars); break; } case NodeKind.FUNCTIONDECLARATION: { @@ -466,7 +487,7 @@ export class Program extends DiagnosticEmitter { break; } case NodeKind.IMPORT: { - this.initializeImports(statement, queuedExports, queuedImports); + this.initializeImports(statement, queuedImports); break; } case NodeKind.INTERFACEDECLARATION: { @@ -490,91 +511,17 @@ export class Program extends DiagnosticEmitter { } // queued imports should be resolvable now through traversing exports and queued exports - for (let i = 0; i < queuedImports.length;) { - let queuedImport = queuedImports[i]; - let declaration = queuedImport.declaration; - if (declaration) { // named - let element = this.tryLocateImport(queuedImport.externalName, queuedExports); - if (element) { - this.elementsLookup.set(queuedImport.localName, element); - queuedImports.splice(i, 1); - } else { - if (element = this.tryLocateImport(queuedImport.externalNameAlt, queuedExports)) { - this.elementsLookup.set(queuedImport.localName, element); - queuedImports.splice(i, 1); - } else { - this.error( - DiagnosticCode.Module_0_has_no_exported_member_1, - declaration.range, - (declaration.parent).path.value, - declaration.externalName.text - ); - ++i; - } - } - } else { // filespace - let element = this.elementsLookup.get(queuedImport.externalName); - if (element) { - this.elementsLookup.set(queuedImport.localName, element); - queuedImports.splice(i, 1); - } else { - if (element = this.elementsLookup.get(queuedImport.externalNameAlt)) { - this.elementsLookup.set(queuedImport.localName, element); - queuedImports.splice(i, 1); - } else { - assert(false); // already reported by the parser not finding the file - ++i; - } - } - } + for (const queuedImport of queuedImports) { + this.resolveAndSetQueuedImport(queuedImport, queuedExports, exportStars); } // queued exports should be resolvable now that imports are finalized - for (let [exportName, queuedExport] of queuedExports) { - let currentExport: QueuedExport | null = queuedExport; // nullable below - let element: Element | null; - do { - if (currentExport.isReExport) { - if (element = this.fileLevelExports.get(currentExport.externalName)) { - this.setExportAndCheckLibrary( - exportName, - element, - queuedExport.member.externalName - ); - break; - } - currentExport = queuedExports.get(currentExport.externalName); - if (!currentExport) { - this.error( - DiagnosticCode.Module_0_has_no_exported_member_1, - queuedExport.member.externalName.range, - ((queuedExport.member.parent).path).value, - queuedExport.member.externalName.text - ); - } - } else { - if ( - // normal export - (element = this.elementsLookup.get(currentExport.externalName)) || - // library re-export - (element = this.elementsLookup.get(currentExport.member.name.text)) - ) { - this.setExportAndCheckLibrary( - exportName, - element, - queuedExport.member.externalName - ); - } else { - this.error( - DiagnosticCode.Cannot_find_name_0, - queuedExport.member.range, queuedExport.member.name.text - ); - } - break; - } - } while (currentExport); + for (let [exportInternalName, queuedExport] of queuedExports) { + this.resolveAndSetQueuedExport(exportInternalName, queuedExport, queuedExports, exportStars, new Set()); } + this.resolveExportStars(exportStars); + // resolve base prototypes of derived classes var resolver = this.resolver; for (let i = 0, k = queuedExtends.length; i < k; ++i) { @@ -726,6 +673,160 @@ export class Program extends DiagnosticEmitter { } } + private resolveAndSetQueuedImport( + { declaration, exportingSource, exportName, localName }: QueuedImport, + queuedReExports: Map, + exportStars: Map + ): void { + if (declaration) { // named + let element = this.resolveAndSetExport( + exportingSource, assert(exportName), queuedReExports, exportStars, new Set()); + if (element) { + this.elementsLookup.set(localName, element); + } else { + this.error( + DiagnosticCode.Module_0_has_no_exported_member_1, + declaration.range, + (declaration.parent).path.value, + declaration.externalName.text + ); + } + } else { // filespace + // Filespace lookup must succeed, because would have failed already in the parser if the file didn't exist. + this.elementsLookup.set(localName, this.elementsLookup.get(FILESPACE_PREFIX + exportingSource.internalPath)!); + } + } + + private resolveExportStars(exportStars: Map): void { + // Source -> list of Sources we need to copy its exports to. + var reverse = new Map(); + for (const [to, froms] of exportStars) { + for (const from of froms) { + multiMapAdd(reverse, from, to); + } + } + + // For each original export, find all sources it needs to be (transitively) re-exported to. + for (const [originalSource, directReExportDestinations] of reverse) { + const originalFilespace = this.getFilespace(originalSource); + const destinations = directReExportDestinations.slice(); + const seenDestinations = new Set(); + seenDestinations.add(originalSource); // If A reexports all from B and vice-versa, don't want to loop back to A. + while (destinations.length) { + const destination = destinations.pop()!; + if (seenDestinations.has(destination)) continue; + seenDestinations.add(destination); + + for (const transitiveDestination of reverse.get(destination) || []) { + destinations.push(transitiveDestination); + } + + for (const [simpleName, element] of originalFilespace.members) { + const internalName = getSourceLevelName(destination, simpleName); + if (this.fileLevelExports.has(internalName)) { + // An own export overrides an `export *` re-export. Only another `export *` re-export is a problem. + if (this.exportsFromExportStar.has(internalName)) { + this.error( + DiagnosticCode.Module_0_has_already_exported_a_member_named_1_Consider_explicitly_re_exporting_to_resolve_the_ambiguity, + destination.range, + destination.normalizedPath, + simpleName, + ); + } + } else { + this.setExportFromExportStar(element, destination, simpleName); + } + } + } + } + } + + private resolveAndSetQueuedExport( + // `exportInternalName` is the internal name of the alias, and `externalName` is the internal name of the original. + exportInternalName: string, + { localInternalName, reExportFromSource, member }: QueuedExport, + queuedExports: Map, + exportStars: Map, + seenSources: Set, + ): Element | null { + const originalSimpleName = member.name.text; + const element = reExportFromSource + // `export { x } from "./foo";` + ? this.resolveAndSetExport( + reExportFromSource, originalSimpleName, queuedExports, exportStars, seenSources) + // `export { x };`: normal export || library re-export + : this.elementsLookup.get(localInternalName!) || this.elementsLookup.get(originalSimpleName); + if (element) { + // `resolveAndSetExport` sets the export on the original; this sets the export on the alias. + this.setExportAndCheckLibrary( + exportInternalName, element, member.range.source, member.externalName.text, member.range); + } + else { + this.error( + DiagnosticCode.Module_0_has_no_exported_member_1, + member.externalName.range, + (member.parent).path!.value, + originalSimpleName, + ); + } + return element; + } + + /** + * Does not report errors. Caller should report an error on null if needed. + * (If a file has two `export *`s, only one of them should have the export, so the other is not an error.) + */ + private resolveAndSetExport( + exportingSource: Source, + exportSimpleName: string, + queuedExports: Map, + exportStars: Map, + seenSources: Set, + ): Element | null { + const exportInternalPath = getSourceLevelName(exportingSource, exportSimpleName); + const cached = this.fileLevelExports.get(exportInternalPath); + if (cached) return cached; + + const queuedExport = queuedExports.get(exportInternalPath); + if (queuedExport) { + // This redirects to another queued export, so resolve that. + return this.resolveAndSetQueuedExport(exportSimpleName, queuedExport, queuedExports, exportStars, seenSources); + } + + return this.resolveAndSetReExportFromExportStar( + exportingSource, exportSimpleName, queuedExports, exportStars, seenSources); + } + + private resolveAndSetReExportFromExportStar( + exportingSource: Source, + exportSimpleName: string, + queuedExports: Map, + exportStars: Map, + seenSources: Set, + ): Element | null { + // Avoid infinite loops -- don't look in the same source more than once. + if (seenSources.has(exportingSource)) { + return null; + } + seenSources.add(exportingSource); + + for (const reExportedSource of exportStars.get(exportingSource) || []) { + const element = this.resolveAndSetExport( + reExportedSource, + exportSimpleName, + queuedExports, + exportStars, + seenSources + ); + if (element) { + this.setExportFromExportStar(element, exportingSource, exportSimpleName); + return element; + // If there are two reexports with the same name, we will error in `resolveExportStars`. + } + } + return null; + } + /** Sets a constant integer value. */ setConstantInteger(globalName: string, type: Type, value: I64): void { assert(type.is(TypeFlags.INTEGER)); @@ -744,26 +845,6 @@ export class Program extends DiagnosticEmitter { ); } - /** Tries to locate an import by traversing exports and queued exports. */ - private tryLocateImport( - externalName: string, - queuedNamedExports: Map - ): Element | null { - var element: Element | null; - var fileLevelExports = this.fileLevelExports; - do { - if (element = fileLevelExports.get(externalName)) return element; - let queuedExport = queuedNamedExports.get(externalName); - if (!queuedExport) break; - if (queuedExport.isReExport) { - externalName = queuedExport.externalName; - continue; - } - return this.elementsLookup.get(queuedExport.externalName); - } while (true); - return null; - } - /** Checks that only supported decorators are present. */ private checkDecorators( decorators: DecoratorNode[], @@ -1436,46 +1517,52 @@ export class Program extends DiagnosticEmitter { } private initializeExports( + currentSource: Source, statement: ExportStatement, - queuedExports: Map + queuedExports: Map, + exportStars: Map, ): void { var members = statement.members; if (members) { // named for (let i = 0, k = members.length; i < k; ++i) { this.initializeExport(members[i], statement.internalPath, queuedExports); } - } else { // TODO: filespace - this.error( - DiagnosticCode.Operation_not_supported, - statement.range - ); + } else { // `export * from "./foo";` + const target = this.lookupSourceByPath(assert(statement.normalizedPath))!; + multiMapAdd(exportStars, currentSource, target); } } + private setExportFromExportStar( + element: Element, + reExportingSource: Source, + simpleName: string, + ): void { + const internalName = getSourceLevelName(reExportingSource, simpleName); + this.exportsFromExportStar.add(internalName); + this.setExportAndCheckLibrary(internalName, element, reExportingSource, simpleName, /*range*/ null); + } + private setExportAndCheckLibrary( internalName: string, element: Element, - externalIdentifier: IdentifierExpression + exportingSource: Source, + simpleName: string, + range: Range | null, ): void { // add to file-level exports this.fileLevelExports.set(internalName, element); // add to filespace - var internalPath = externalIdentifier.range.source.internalPath; - var prefix = FILESPACE_PREFIX + internalPath; - var filespace = this.elementsLookup.get(prefix); - if (!filespace) filespace = assert(this.elementsLookup.get(prefix + PATH_DELIMITER + "index")); - assert(filespace.kind == ElementKind.FILESPACE); - var simpleName = externalIdentifier.text; - (filespace).members.set(simpleName, element); + this.getFilespace(exportingSource).members.set(simpleName, element); // add global alias if a top-level export of a library file - var source = externalIdentifier.range.source; - if (source.isLibrary) { + if (exportingSource.isLibrary) { if (this.elementsLookup.has(simpleName)) { this.error( DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, - externalIdentifier.range, simpleName + range || exportingSource.range, + simpleName ); } else { element.internalName = simpleName; @@ -1483,20 +1570,26 @@ export class Program extends DiagnosticEmitter { } // add module level export if a top-level export of an entry file - } else if (source.isEntry) { - this.moduleLevelExports.set(externalIdentifier.text, { + } else if (exportingSource.isEntry) { + this.moduleLevelExports.set(simpleName, { element, - identifier: externalIdentifier + identifier: null }); } } + private getFilespace(source: Source): Filespace { + const filespace = this.elementsLookup.get(FILESPACE_PREFIX + source.internalPath)!; + assert(filespace.kind == ElementKind.FILESPACE); + return filespace; + } + private initializeExport( member: ExportMember, - internalPath: string | null, + internalPath: string | null, // internal path of original export for re-export queuedExports: Map ): void { - var externalName = member.range.source.internalPath + PATH_DELIMITER + member.externalName.text; + var externalName = getSourceLevelName(member.range.source, member.externalName.text); if (this.fileLevelExports.has(externalName)) { this.error( DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, @@ -1506,18 +1599,19 @@ export class Program extends DiagnosticEmitter { } var referencedName: string; var referencedElement: Element | null; - var queuedExport: QueuedExport | null; // export local element if (internalPath == null) { - referencedName = member.range.source.internalPath + PATH_DELIMITER + member.name.text; + referencedName = getSourceLevelName(member.range.source, member.name.text); // resolve right away if the element exists if (this.elementsLookup.has(referencedName)) { this.setExportAndCheckLibrary( externalName, this.elementsLookup.get(referencedName), - member.externalName + member.range.source, + member.externalName.text, + member.range, ); return; } @@ -1530,15 +1624,15 @@ export class Program extends DiagnosticEmitter { ); return; } - queuedExport = new QueuedExport(); - queuedExport.isReExport = false; - queuedExport.externalName = referencedName; // -> here: local name + const queuedExport = new QueuedExport(); + queuedExport.localInternalName = referencedName; + queuedExport.reExportFromSource = null; queuedExport.member = member; queuedExports.set(externalName, queuedExport); // export external element } else { - referencedName = internalPath + PATH_DELIMITER + member.name.text; + referencedName = getSourceLevelNameFromInternalPath(internalPath, member.name.text); // resolve right away if the export exists referencedElement = this.elementsLookup.get(referencedName); @@ -1546,41 +1640,13 @@ export class Program extends DiagnosticEmitter { this.setExportAndCheckLibrary( externalName, referencedElement, - member.externalName + member.range.source, + member.externalName.text, + member.range, ); return; } - // walk already known queued exports - let seen = new Set(); - while (queuedExport = queuedExports.get(referencedName)) { - if (queuedExport.isReExport) { - referencedElement = this.fileLevelExports.get(queuedExport.externalName); - if (referencedElement) { - this.setExportAndCheckLibrary( - externalName, - referencedElement, - member.externalName - ); - return; - } - referencedName = queuedExport.externalName; - if (seen.has(queuedExport)) break; - seen.add(queuedExport); - } else { - referencedElement = this.elementsLookup.get(queuedExport.externalName); - if (referencedElement) { - this.setExportAndCheckLibrary( - externalName, - referencedElement, - member.externalName - ); - return; - } - break; - } - } - // otherwise queue it if (queuedExports.has(externalName)) { this.error( @@ -1589,9 +1655,9 @@ export class Program extends DiagnosticEmitter { ); return; } - queuedExport = new QueuedExport(); - queuedExport.isReExport = true; - queuedExport.externalName = referencedName; // -> here: external name + const queuedExport = new QueuedExport(); + queuedExport.localInternalName = null; + queuedExport.reExportFromSource = this.lookupSourceByPath(internalPath)!; queuedExport.member = member; queuedExports.set(externalName, queuedExport); } @@ -1677,7 +1743,6 @@ export class Program extends DiagnosticEmitter { private initializeImports( statement: ImportStatement, - queuedExports: Map, queuedImports: QueuedImport[] ): void { var declarations = statement.declarations; @@ -1686,16 +1751,12 @@ export class Program extends DiagnosticEmitter { this.initializeImport( declarations[i], statement.internalPath, - queuedExports, queuedImports + queuedImports ); } } else if (statement.namespaceName) { // import * as simpleName from "file" let simpleName = statement.namespaceName.text; - let internalName = ( - statement.range.source.internalPath + - PATH_DELIMITER + - simpleName - ); + let internalName = getSourceLevelName(statement.range.source, simpleName); if (this.elementsLookup.has(internalName)) { this.error( DiagnosticCode.Duplicate_identifier_0, @@ -1715,10 +1776,9 @@ export class Program extends DiagnosticEmitter { // otherwise queue it let queuedImport = new QueuedImport(); queuedImport.localName = internalName; - let externalName = FILESPACE_PREFIX + statement.internalPath; - queuedImport.externalName = externalName; - queuedImport.externalNameAlt = externalName + PATH_DELIMITER + "index"; - queuedImport.declaration = null; // filespace + queuedImport.exportingSource = this.lookupSourceByPath(statement.internalPath)!; + queuedImport.exportName = null; + queuedImport.declaration = null; queuedImports.push(queuedImport); } } @@ -1726,7 +1786,6 @@ export class Program extends DiagnosticEmitter { private initializeImport( declaration: ImportDeclaration, internalPath: string, - queuedNamedExports: Map, queuedImports: QueuedImport[] ): void { var localName = declaration.fileLevelInternalName; @@ -1738,7 +1797,8 @@ export class Program extends DiagnosticEmitter { return; } - var externalName = internalPath + PATH_DELIMITER + declaration.externalName.text; + const exportName = declaration.externalName.text; + var externalName = getSourceLevelNameFromInternalPath(internalPath, exportName); // resolve right away if the exact export exists var element: Element | null; @@ -1748,25 +1808,11 @@ export class Program extends DiagnosticEmitter { } // otherwise queue it - const indexPart = PATH_DELIMITER + "index"; - var queuedImport = new QueuedImport(); + const queuedImport = new QueuedImport(); queuedImport.localName = localName; - if (internalPath.endsWith(indexPart)) { - queuedImport.externalName = externalName; // try exact first - queuedImport.externalNameAlt = ( - internalPath.substring(0, internalPath.length - indexPart.length + 1) + - declaration.externalName.text - ); - } else { - queuedImport.externalName = externalName; // try exact first - queuedImport.externalNameAlt = ( - internalPath + - indexPart + - PATH_DELIMITER + - declaration.externalName.text - ); - } - queuedImport.declaration = declaration; // named + queuedImport.exportingSource = this.lookupSourceByPath(internalPath)!; + queuedImport.exportName = exportName; + queuedImport.declaration = declaration; queuedImports.push(queuedImport); } diff --git a/src/resolver.ts b/src/resolver.ts index 7d23410e41..ce3d01741e 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -44,7 +44,8 @@ import { LiteralKind, ParenthesizedExpression, AssertionExpression, - Expression + Expression, + getSourceLevelName } from "./ast"; import { @@ -103,7 +104,7 @@ export class Resolver extends DiagnosticEmitter { var typeNode = node; var simpleName = typeNode.name.text; var globalName = simpleName; - var localName = typeNode.range.source.internalPath + PATH_DELIMITER + simpleName; // TODO cache + var localName = getSourceLevelName(typeNode.range.source, simpleName); // TODO cache // check file-global / program-global enum or class { @@ -336,7 +337,7 @@ export class Resolver extends DiagnosticEmitter { // search current file var elementsLookup = this.program.elementsLookup; - if (element = elementsLookup.get(identifier.range.source.internalPath + PATH_DELIMITER + name)) { + if (element = elementsLookup.get(getSourceLevelName(identifier.range.source, name))) { this.currentThisExpression = null; this.currentElementExpression = null; return element; // GLOBAL, FUNCTION_PROTOTYPE, CLASS_PROTOTYPE diff --git a/src/util/index.ts b/src/util/index.ts index 5b23eacce6..54a3314ea8 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -8,3 +8,4 @@ export * from "./charcode"; export * from "./path"; export * from "./text"; export * from "./binary"; +export * from "./map"; diff --git a/src/util/map.ts b/src/util/map.ts new file mode 100644 index 0000000000..4881d7045d --- /dev/null +++ b/src/util/map.ts @@ -0,0 +1,10 @@ +export function multiMapAdd(map: Map, key: K, value: V) : void { + const values = map.get(key); + if (values) { + if (!values.includes(value)) { + values.push(value); + } + } else { + map.set(key, [value]); + } +} diff --git a/tests/compiler/exportFromIndex/index.optimized.wat b/tests/compiler/exportFromIndex/index.optimized.wat new file mode 100644 index 0000000000..8484f0139f --- /dev/null +++ b/tests/compiler/exportFromIndex/index.optimized.wat @@ -0,0 +1,8 @@ +(module + (global $exportFromIndex/x i32 (i32.const 0)) + (global $exportFromIndex/y i32 (i32.const 0)) + (memory $0 0) + (export "memory" (memory $0)) + (export "x" (global $exportFromIndex/x)) + (export "y" (global $exportFromIndex/y)) +) diff --git a/tests/compiler/exportFromIndex/index.ts b/tests/compiler/exportFromIndex/index.ts new file mode 100644 index 0000000000..21d7267afe --- /dev/null +++ b/tests/compiler/exportFromIndex/index.ts @@ -0,0 +1,2 @@ +export const x = 0; +export const y = x; diff --git a/tests/compiler/exportFromIndex/index.untouched.wat b/tests/compiler/exportFromIndex/index.untouched.wat new file mode 100644 index 0000000000..7eef1babef --- /dev/null +++ b/tests/compiler/exportFromIndex/index.untouched.wat @@ -0,0 +1,9 @@ +(module + (global $exportFromIndex/index/x i32 (i32.const 0)) + (global $exportFromIndex/index/y i32 (i32.const 0)) + (global $HEAP_BASE i32 (i32.const 8)) + (memory $0 0) + (export "memory" (memory $0)) + (export "x" (global $exportFromIndex/index/x)) + (export "y" (global $exportFromIndex/index/y)) +) diff --git a/tests/compiler/exportFromIndexUser.optimized.wat b/tests/compiler/exportFromIndexUser.optimized.wat new file mode 100644 index 0000000000..23da3862e2 --- /dev/null +++ b/tests/compiler/exportFromIndexUser.optimized.wat @@ -0,0 +1,4 @@ +(module + (memory $0 0) + (export "memory" (memory $0)) +) diff --git a/tests/compiler/exportFromIndexUser.ts b/tests/compiler/exportFromIndexUser.ts new file mode 100644 index 0000000000..608a7df67b --- /dev/null +++ b/tests/compiler/exportFromIndexUser.ts @@ -0,0 +1,4 @@ +import { x } from "./exportFromIndex"; +import { x as x2 } from "./exportFromIndex/index"; + +const y = x + x2; diff --git a/tests/compiler/exportFromIndexUser.untouched.wat b/tests/compiler/exportFromIndexUser.untouched.wat new file mode 100644 index 0000000000..bd08bc53a5 --- /dev/null +++ b/tests/compiler/exportFromIndexUser.untouched.wat @@ -0,0 +1,8 @@ +(module + (global $exportFromIndex/index/x i32 (i32.const 0)) + (global $exportFromIndex/index/y i32 (i32.const 0)) + (global $exportFromIndexUser/y i32 (i32.const 0)) + (global $HEAP_BASE i32 (i32.const 8)) + (memory $0 0) + (export "memory" (memory $0)) +) diff --git a/tests/compiler/mandelbrot.optimized.wat b/tests/compiler/mandelbrot.optimized.wat index f88ce7e635..0d26e9bd25 100644 --- a/tests/compiler/mandelbrot.optimized.wat +++ b/tests/compiler/mandelbrot.optimized.wat @@ -5,7 +5,7 @@ (type $FFFF (func (param f64 f64 f64) (result f64))) (memory $0 0) (export "memory" (memory $0)) - (export "computeLine" (func $../../examples/mandelbrot/assembly/index/computeLine)) + (export "computeLine" (func $../../examples/mandelbrot/assembly/computeLine)) (func $~lib/math/NativeMath.log (; 0 ;) (; has Stack IR ;) (type $FF) (param $0 f64) (result f64) (local $1 i32) (local $2 i32) @@ -277,7 +277,7 @@ (f64.const 0) ) ) - (func $../../examples/mandelbrot/assembly/index/clamp (; 2 ;) (; has Stack IR ;) (type $FFFF) (param $0 f64) (param $1 f64) (param $2 f64) (result f64) + (func $../../examples/mandelbrot/assembly/clamp (; 2 ;) (; has Stack IR ;) (type $FFFF) (param $0 f64) (param $1 f64) (param $2 f64) (result f64) (f64.min (f64.max (get_local $0) @@ -286,7 +286,7 @@ (get_local $2) ) ) - (func $../../examples/mandelbrot/assembly/index/computeLine (; 3 ;) (; has Stack IR ;) (type $iiiiv) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) + (func $../../examples/mandelbrot/assembly/computeLine (; 3 ;) (; has Stack IR ;) (type $iiiiv) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (local $4 f64) (local $5 f64) (local $6 f64) @@ -525,7 +525,7 @@ (i32.trunc_u/f64 (f64.mul (f64.const 2047) - (call $../../examples/mandelbrot/assembly/index/clamp + (call $../../examples/mandelbrot/assembly/clamp (f64.div (f64.sub (f64.convert_u/i32 diff --git a/tests/compiler/reexport.optimized.wat b/tests/compiler/reexport.optimized.wat index 77fb6a049a..0f3865657d 100644 --- a/tests/compiler/reexport.optimized.wat +++ b/tests/compiler/reexport.optimized.wat @@ -4,6 +4,7 @@ (global $export/a i32 (i32.const 1)) (global $export/b i32 (i32.const 2)) (global $export/c i32 (i32.const 3)) + (global $reexport2/export2 i32 (i32.const 0)) (memory $0 0) (export "memory" (memory $0)) (export "add" (func $export/add)) @@ -17,6 +18,8 @@ (export "renamed_add" (func $export/add)) (export "rerenamed_sub" (func $export/mul)) (export "renamed_ns.two" (func $export/ns.two)) + (export "export2" (global $reexport2/export2)) + (export "renamed_add_2" (func $export/add)) (start $start) (func $export/add (; 0 ;) (; has Stack IR ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) (i32.add diff --git a/tests/compiler/reexport.ts b/tests/compiler/reexport.ts index c48dbf71ea..3ae35326fc 100644 --- a/tests/compiler/reexport.ts +++ b/tests/compiler/reexport.ts @@ -24,3 +24,5 @@ export { imported_add(1, 2) + imported_sub(3, 4); export { ns as renamed_ns } from "./export"; + +export * from "./reexport2"; diff --git a/tests/compiler/reexport.untouched.wat b/tests/compiler/reexport.untouched.wat index 099943f949..972b636287 100644 --- a/tests/compiler/reexport.untouched.wat +++ b/tests/compiler/reexport.untouched.wat @@ -4,6 +4,7 @@ (global $export/a i32 (i32.const 1)) (global $export/b i32 (i32.const 2)) (global $export/c i32 (i32.const 3)) + (global $reexport2/export2 i32 (i32.const 0)) (global $HEAP_BASE i32 (i32.const 8)) (memory $0 0) (export "memory" (memory $0)) @@ -18,6 +19,8 @@ (export "renamed_add" (func $export/add)) (export "rerenamed_sub" (func $export/mul)) (export "renamed_ns.two" (func $export/ns.two)) + (export "export2" (global $reexport2/export2)) + (export "renamed_add_2" (func $export/add)) (start $start) (func $export/add (; 0 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) (i32.add diff --git a/tests/compiler/reexport2.optimized.wat b/tests/compiler/reexport2.optimized.wat new file mode 100644 index 0000000000..0852b091bd --- /dev/null +++ b/tests/compiler/reexport2.optimized.wat @@ -0,0 +1,48 @@ +(module + (type $iii (func (param i32 i32) (result i32))) + (type $v (func)) + (global $reexport2/export2 i32 (i32.const 0)) + (global $export/a i32 (i32.const 1)) + (global $export/b i32 (i32.const 2)) + (global $export/c i32 (i32.const 3)) + (memory $0 0) + (export "memory" (memory $0)) + (export "export2" (global $reexport2/export2)) + (export "renamed_add_2" (func $export/add)) + (export "add" (func $export/add)) + (export "renamed_mul" (func $export/mul)) + (export "rerenamed_mul" (func $export/mul)) + (export "a" (global $export/a)) + (export "renamed_b" (global $export/b)) + (export "renamed_c" (global $export/c)) + (export "rerenamed_c" (global $export/c)) + (export "renamed_add" (func $export/add)) + (export "rerenamed_sub" (func $export/mul)) + (start $start) + (func $export/add (; 0 ;) (; has Stack IR ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) + (i32.add + (get_local $0) + (get_local $1) + ) + ) + (func $export/mul (; 1 ;) (; has Stack IR ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) + (i32.mul + (get_local $0) + (get_local $1) + ) + ) + (func $start (; 2 ;) (; has Stack IR ;) (type $v) + (drop + (i32.add + (call $export/add + (i32.const 1) + (i32.const 2) + ) + (call $export/mul + (i32.const 3) + (i32.const 4) + ) + ) + ) + ) +) diff --git a/tests/compiler/reexport2.ts b/tests/compiler/reexport2.ts new file mode 100644 index 0000000000..7972d3cc63 --- /dev/null +++ b/tests/compiler/reexport2.ts @@ -0,0 +1,4 @@ +export const export2: i32 = 0; +// Intentional loop of `export *`. Should not cause infinite loop. +export * from "./reexport"; +export { add as renamed_add_2 } from "./reexport"; diff --git a/tests/compiler/reexport2.untouched.wat b/tests/compiler/reexport2.untouched.wat new file mode 100644 index 0000000000..8c81be55e8 --- /dev/null +++ b/tests/compiler/reexport2.untouched.wat @@ -0,0 +1,49 @@ +(module + (type $iii (func (param i32 i32) (result i32))) + (type $v (func)) + (global $reexport2/export2 i32 (i32.const 0)) + (global $export/a i32 (i32.const 1)) + (global $export/b i32 (i32.const 2)) + (global $export/c i32 (i32.const 3)) + (global $HEAP_BASE i32 (i32.const 8)) + (memory $0 0) + (export "memory" (memory $0)) + (export "export2" (global $reexport2/export2)) + (export "renamed_add_2" (func $export/add)) + (export "add" (func $export/add)) + (export "renamed_mul" (func $export/mul)) + (export "rerenamed_mul" (func $export/mul)) + (export "a" (global $export/a)) + (export "renamed_b" (global $export/b)) + (export "renamed_c" (global $export/c)) + (export "rerenamed_c" (global $export/c)) + (export "renamed_add" (func $export/add)) + (export "rerenamed_sub" (func $export/mul)) + (start $start) + (func $export/add (; 0 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) + (i32.add + (get_local $0) + (get_local $1) + ) + ) + (func $export/mul (; 1 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) + (i32.mul + (get_local $0) + (get_local $1) + ) + ) + (func $start (; 2 ;) (type $v) + (drop + (i32.add + (call $export/add + (i32.const 1) + (i32.const 2) + ) + (call $export/mul + (i32.const 3) + (i32.const 4) + ) + ) + ) + ) +) diff --git a/tests/compiler/reexportUser.optimized.wat b/tests/compiler/reexportUser.optimized.wat new file mode 100644 index 0000000000..93e2ce5048 --- /dev/null +++ b/tests/compiler/reexportUser.optimized.wat @@ -0,0 +1,47 @@ +(module + (type $iii (func (param i32 i32) (result i32))) + (type $ii (func (param i32) (result i32))) + (type $v (func)) + (memory $0 0) + (export "memory" (memory $0)) + (export "f" (func $reexportUser/f)) + (start $start) + (func $export/add (; 0 ;) (; has Stack IR ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) + (i32.add + (get_local $0) + (get_local $1) + ) + ) + (func $export/mul (; 1 ;) (; has Stack IR ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) + (i32.mul + (get_local $0) + (get_local $1) + ) + ) + (func $reexportUser/f (; 2 ;) (; has Stack IR ;) (type $ii) (param $0 i32) (result i32) + (call $export/add + (get_local $0) + (call $export/add + (call $export/add + (get_local $0) + (i32.const 0) + ) + (i32.const 0) + ) + ) + ) + (func $start (; 3 ;) (; has Stack IR ;) (type $v) + (drop + (i32.add + (call $export/add + (i32.const 1) + (i32.const 2) + ) + (call $export/mul + (i32.const 3) + (i32.const 4) + ) + ) + ) + ) +) diff --git a/tests/compiler/reexportUser.ts b/tests/compiler/reexportUser.ts new file mode 100644 index 0000000000..75b05bc498 --- /dev/null +++ b/tests/compiler/reexportUser.ts @@ -0,0 +1,6 @@ +import { renamed_add_2, export2 } from "./reexport2"; +import * as re from "./reexport2"; + +export function f(x: i32): i32 { + return renamed_add_2(x, re.renamed_add_2(re.add(x, export2), re.export2)); +} diff --git a/tests/compiler/reexportUser.untouched.wat b/tests/compiler/reexportUser.untouched.wat new file mode 100644 index 0000000000..99758ef417 --- /dev/null +++ b/tests/compiler/reexportUser.untouched.wat @@ -0,0 +1,52 @@ +(module + (type $iii (func (param i32 i32) (result i32))) + (type $ii (func (param i32) (result i32))) + (type $v (func)) + (global $reexport2/export2 i32 (i32.const 0)) + (global $export/a i32 (i32.const 1)) + (global $export/b i32 (i32.const 2)) + (global $export/c i32 (i32.const 3)) + (global $HEAP_BASE i32 (i32.const 8)) + (memory $0 0) + (export "memory" (memory $0)) + (export "f" (func $reexportUser/f)) + (start $start) + (func $export/add (; 0 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) + (i32.add + (get_local $0) + (get_local $1) + ) + ) + (func $export/mul (; 1 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) + (i32.mul + (get_local $0) + (get_local $1) + ) + ) + (func $reexportUser/f (; 2 ;) (type $ii) (param $0 i32) (result i32) + (call $export/add + (get_local $0) + (call $export/add + (call $export/add + (get_local $0) + (get_global $reexport2/export2) + ) + (get_global $reexport2/export2) + ) + ) + ) + (func $start (; 3 ;) (type $v) + (drop + (i32.add + (call $export/add + (i32.const 1) + (i32.const 2) + ) + (call $export/mul + (i32.const 3) + (i32.const 4) + ) + ) + ) + ) +) diff --git a/tests/compiler/rereexport.untouched.wat b/tests/compiler/rereexport.untouched.wat index bf236b9366..259c7342b2 100644 --- a/tests/compiler/rereexport.untouched.wat +++ b/tests/compiler/rereexport.untouched.wat @@ -4,6 +4,7 @@ (global $export/a i32 (i32.const 1)) (global $export/b i32 (i32.const 2)) (global $export/c i32 (i32.const 3)) + (global $reexport2/export2 i32 (i32.const 0)) (global $HEAP_BASE i32 (i32.const 8)) (memory $0 0) (export "memory" (memory $0))