From c0ef9da564f4ce7efd1e9dde771de52dbc5d544f Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Wed, 5 Sep 2018 20:56:55 -0700 Subject: [PATCH 1/3] Support `export *` --- NOTICE | 1 + src/ast.ts | 21 +- src/compiler.ts | 7 +- src/diagnosticMessages.generated.ts | 2 + src/diagnosticMessages.json | 1 + src/program.ts | 464 ++++++++++-------- src/resolver.ts | 7 +- src/util/MultiMap.ts | 22 + src/util/index.ts | 1 + .../exportFromIndex/index.optimized.wat | 8 + tests/compiler/exportFromIndex/index.ts | 2 + .../exportFromIndex/index.untouched.wat | 9 + .../exportFromIndexUser.optimized.wat | 4 + tests/compiler/exportFromIndexUser.ts | 4 + .../exportFromIndexUser.untouched.wat | 8 + tests/compiler/mandelbrot.optimized.wat | 8 +- tests/compiler/mandelbrot.untouched.wat | 14 +- tests/compiler/reexport.optimized.wat | 3 + tests/compiler/reexport.ts | 2 + tests/compiler/reexport.untouched.wat | 3 + tests/compiler/reexport2.optimized.wat | 48 ++ tests/compiler/reexport2.ts | 4 + tests/compiler/reexport2.untouched.wat | 49 ++ tests/compiler/reexportUser.optimized.wat | 47 ++ tests/compiler/reexportUser.ts | 6 + tests/compiler/reexportUser.untouched.wat | 52 ++ 26 files changed, 570 insertions(+), 227 deletions(-) create mode 100644 src/util/MultiMap.ts create mode 100644 tests/compiler/exportFromIndex/index.optimized.wat create mode 100644 tests/compiler/exportFromIndex/index.ts create mode 100644 tests/compiler/exportFromIndex/index.untouched.wat create mode 100644 tests/compiler/exportFromIndexUser.optimized.wat create mode 100644 tests/compiler/exportFromIndexUser.ts create mode 100644 tests/compiler/exportFromIndexUser.untouched.wat create mode 100644 tests/compiler/reexport2.optimized.wat create mode 100644 tests/compiler/reexport2.ts create mode 100644 tests/compiler/reexport2.untouched.wat create mode 100644 tests/compiler/reexportUser.optimized.wat create mode 100644 tests/compiler/reexportUser.ts create mode 100644 tests/compiler/reexportUser.untouched.wat diff --git a/NOTICE b/NOTICE index 7f93b281a4..13f4ea79bc 100644 --- a/NOTICE +++ b/NOTICE @@ -8,6 +8,7 @@ under the licensing terms detailed in LICENSE: * Igor Sbitnev * Norton Wang * Alan Pierce +* 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..9eae7ce8c4 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -1972,9 +1972,24 @@ 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 stripIndex(internalPath) + PATH_DELIMITER + simpleName; +} + +// "foo/index" -> "foo" +// "foo/bar" -> itself +export function stripIndex(internalPath: string): string { + const indexPart = PATH_DELIMITER + "index"; + return internalPath.endsWith(indexPart) + ? internalPath.substring(0, internalPath.length - indexPart.length) + : internalPath; } /** 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..746ca8fefe 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -83,6 +83,7 @@ export enum DiagnosticCode { A_class_may_only_extend_another_class = 1311, A_parameter_property_cannot_be_declared_using_a_rest_parameter = 1317, Duplicate_identifier_0 = 2300, + Identifier_0_is_re_exported_from_modules_1_and_2 = 2301, Cannot_find_name_0 = 2304, Module_0_has_no_exported_member_1 = 2305, Generic_type_0_requires_1_type_argument_s = 2314, @@ -202,6 +203,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 1311: return "A class may only extend another class."; case 1317: return "A parameter property cannot be declared using a rest parameter."; case 2300: return "Duplicate identifier '{0}'."; + case 2301: return "Identifier '{0}' is re-exported from modules '{1}' and '{2}'."; case 2304: return "Cannot find name '{0}'."; case 2305: return "Module '{0}' has no exported member '{1}'."; case 2314: return "Generic type '{0}' requires {1} type argument(s)."; diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index fca908525e..9abf05d11b 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -77,6 +77,7 @@ "A parameter property cannot be declared using a rest parameter.": 1317, "Duplicate identifier '{0}'.": 2300, + "Identifier '{0}' is re-exported from modules '{1}' and '{2}'.": 2301, "Cannot find name '{0}'.": 2304, "Module '{0}' has no exported member '{1}'.": 2305, "Generic type '{0}' requires {1} type argument(s).": 2314, diff --git a/src/program.ts b/src/program.ts index ad6d785c99..128930f253 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,10 @@ import { VariableStatement, decoratorNameToKind, - findDecorator + findDecorator, + getSourceLevelNameFromInternalPath, + getSourceLevelName, + stripIndex } from "./ast"; import { @@ -105,7 +107,8 @@ import { } from "./module"; import { - CharCode + CharCode, + MultiMap } from "./util"; import { @@ -115,15 +118,27 @@ 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 + externalName: string; // Internal name of the thing being imported + // 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 { + // Internal name of the thing being reexported. NOT `member.externalName.text`. externalName: string; - isReExport: bool; + // Set if reexporting from a different Source. + reExportFromSource: Source | null; member: ExportMember; } @@ -136,7 +151,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 +340,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 Map(); /** Module-level exports by exported name. */ moduleLevelExports: Map = new Map(); @@ -434,6 +452,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 MultiMap(); // build initial lookup maps of internal names to declarations for (let i = 0, k = this.sources.length; i < k; ++i) { @@ -458,7 +478,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 +486,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 +510,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, - currentExport.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, - currentExport.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 +672,164 @@ export class Program extends DiagnosticEmitter { } } + private resolveAndSetQueuedImport( + { declaration, exportingSource, exportName, externalName, localName }: QueuedImport, + queuedReExports: Map, + exportStars: MultiMap + ): void { + if (declaration) { // named + let element = this.resolveAndSetExport( + exportingSource, assert(exportName), externalName, 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(externalName)!); + } + } + + private resolveExportStars(exportStars: MultiMap): void { + // Source -> list of Sources we need to copy its exports to. + var reverse = new MultiMap(); + for (const [to, froms] of exportStars) { + for (const from of froms) { + reverse.add(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. + const previousReExport = this.exportsFromExportStar.get(internalName); + if (previousReExport) { + this.error( + DiagnosticCode.Identifier_0_is_re_exported_from_modules_1_and_2, + destination.range, + simpleName, + previousReExport.normalizedPath, + originalSource.normalizedPath + ); + } + } else { + this.setExportFromExportStar(internalName, element, destination, simpleName, originalSource); + } + } + } + } + } + + private resolveAndSetQueuedExport( + // `exportInternalName` is the internal name of the alias, and `externalName` is the internal name of the original. + exportInternalName: string, + { reExportFromSource, externalName, member }: QueuedExport, + queuedExports: Map, + exportStars: MultiMap, + seenSources: Set, + ): Element | null { + const originalSimpleName = member.name.text; + const element = reExportFromSource + // `export { x } from "./foo";` + ? this.resolveAndSetExport( + reExportFromSource, originalSimpleName, externalName, queuedExports, exportStars, seenSources) + // `export { x };`: normal export || library re-export + : this.elementsLookup.get(externalName) || 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, + externalName: string, + queuedExports: Map, + exportStars: MultiMap, + seenSources: Set, + ): Element | null { + const cached = this.fileLevelExports.get(externalName); + if (cached) return cached; + + const queuedExport = queuedExports.get(externalName); + if (queuedExport) { + // This redirects to another queued export, so resolve that. + return this.resolveAndSetQueuedExport(exportSimpleName, queuedExport, queuedExports, exportStars, seenSources); + } + + return this.resolveAndSetReExportFromExportStar( + exportingSource, exportSimpleName, externalName, queuedExports, exportStars, seenSources); + } + + private resolveAndSetReExportFromExportStar( + exportingSource: Source, + exportSimpleName: string, + externalName: string, + queuedExports: Map, + exportStars: MultiMap, + 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, + getSourceLevelName(reExportedSource, exportSimpleName), + queuedExports, + exportStars, + seenSources + ); + if (element) { + this.setExportFromExportStar(externalName, element, exportingSource, exportSimpleName, reExportedSource); + 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 +848,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 +1520,53 @@ export class Program extends DiagnosticEmitter { } private initializeExports( + currentSource: Source, statement: ExportStatement, - queuedExports: Map + queuedExports: Map, + exportStars: MultiMap, ): 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))!; + exportStars.add(currentSource, target); } } + private setExportFromExportStar( + internalName: string, + element: Element, + reExportingSource: Source, + simpleName: string, + originalSource: Source, + ): void { + this.exportsFromExportStar.set(internalName, originalSource); + 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 +1574,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 + stripIndex(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 +1603,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 +1628,15 @@ export class Program extends DiagnosticEmitter { ); return; } - queuedExport = new QueuedExport(); - queuedExport.isReExport = false; - queuedExport.externalName = referencedName; // -> here: local name + const queuedExport = new QueuedExport(); + queuedExport.externalName = 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 +1644,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 +1659,9 @@ export class Program extends DiagnosticEmitter { ); return; } - queuedExport = new QueuedExport(); - queuedExport.isReExport = true; - queuedExport.externalName = referencedName; // -> here: external name + const queuedExport = new QueuedExport(); + queuedExport.externalName = referencedName; + queuedExport.reExportFromSource = this.lookupSourceByPath(internalPath)!; queuedExport.member = member; queuedExports.set(externalName, queuedExport); } @@ -1677,7 +1747,6 @@ export class Program extends DiagnosticEmitter { private initializeImports( statement: ImportStatement, - queuedExports: Map, queuedImports: QueuedImport[] ): void { var declarations = statement.declarations; @@ -1686,16 +1755,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 +1780,10 @@ 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.externalName = FILESPACE_PREFIX + statement.internalPath; + queuedImport.exportName = null; + queuedImport.declaration = null; queuedImports.push(queuedImport); } } @@ -1726,7 +1791,6 @@ export class Program extends DiagnosticEmitter { private initializeImport( declaration: ImportDeclaration, internalPath: string, - queuedNamedExports: Map, queuedImports: QueuedImport[] ): void { var localName = declaration.fileLevelInternalName; @@ -1738,7 +1802,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 +1813,12 @@ 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.externalName = externalName; + queuedImport.exportName = exportName; + queuedImport.declaration = declaration; queuedImports.push(queuedImport); } @@ -2199,7 +2251,7 @@ export class Filespace extends Element { program: Program, source: Source ) { - super(program, source.internalPath, FILESPACE_PREFIX + source.internalPath); + super(program, source.internalPath, FILESPACE_PREFIX + stripIndex(source.internalPath)); this.members = new Map(); } } 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/MultiMap.ts b/src/util/MultiMap.ts new file mode 100644 index 0000000000..576aa6dccb --- /dev/null +++ b/src/util/MultiMap.ts @@ -0,0 +1,22 @@ +export class MultiMap { + private map = new Map(); + + add(key: K, value: V): void { + const all = this.map.get(key); + if (all) { + if (!all.includes(value)) { + all.push(value); + } + } else { + this.map.set(key, [value]); + } + } + + get(t: K): V[] { + return this.map.get(t) || []; + } + + [Symbol.iterator](): Iterator<[K, V[]]> { + return this.map[Symbol.iterator](); + } +} diff --git a/src/util/index.ts b/src/util/index.ts index 5b23eacce6..2263b76bd7 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 "./MultiMap"; 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..0fcf4ad5d1 --- /dev/null +++ b/tests/compiler/exportFromIndex/index.untouched.wat @@ -0,0 +1,9 @@ +(module + (global $exportFromIndex/x i32 (i32.const 0)) + (global $exportFromIndex/y i32 (i32.const 0)) + (global $HEAP_BASE i32 (i32.const 8)) + (memory $0 0) + (export "memory" (memory $0)) + (export "x" (global $exportFromIndex/x)) + (export "y" (global $exportFromIndex/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..c96a033afa --- /dev/null +++ b/tests/compiler/exportFromIndexUser.untouched.wat @@ -0,0 +1,8 @@ +(module + (global $exportFromIndex/x i32 (i32.const 0)) + (global $exportFromIndex/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/mandelbrot.untouched.wat b/tests/compiler/mandelbrot.untouched.wat index 2aa859fe7c..46f6ccf4cc 100644 --- a/tests/compiler/mandelbrot.untouched.wat +++ b/tests/compiler/mandelbrot.untouched.wat @@ -3,12 +3,12 @@ (type $FF (func (param f64) (result f64))) (type $Fi (func (param f64) (result i32))) (type $FFFF (func (param f64 f64 f64) (result f64))) - (global $../../examples/mandelbrot/assembly/index/NUM_COLORS i32 (i32.const 2048)) + (global $../../examples/mandelbrot/assembly/NUM_COLORS i32 (i32.const 2048)) (global $~lib/math/NativeMath.LN2 f64 (f64.const 0.6931471805599453)) (global $HEAP_BASE i32 (i32.const 8)) (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 ;) (type $FF) (param $0 f64) (result f64) (local $1 i64) (local $2 i32) @@ -319,7 +319,7 @@ (f64.const 0) ) ) - (func $../../examples/mandelbrot/assembly/index/clamp (; 2 ;) (type $FFFF) (param $0 f64) (param $1 f64) (param $2 f64) (result f64) + (func $../../examples/mandelbrot/assembly/clamp (; 2 ;) (type $FFFF) (param $0 f64) (param $1 f64) (param $2 f64) (result f64) (f64.min (f64.max (get_local $0) @@ -328,7 +328,7 @@ (get_local $2) ) ) - (func $../../examples/mandelbrot/assembly/index/computeLine (; 3 ;) (type $iiiiv) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) + (func $../../examples/mandelbrot/assembly/computeLine (; 3 ;) (type $iiiiv) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (local $4 f64) (local $5 f64) (local $6 f64) @@ -584,11 +584,11 @@ (f64.mul (f64.convert_s/i32 (i32.sub - (get_global $../../examples/mandelbrot/assembly/index/NUM_COLORS) + (get_global $../../examples/mandelbrot/assembly/NUM_COLORS) (i32.const 1) ) ) - (call $../../examples/mandelbrot/assembly/index/clamp + (call $../../examples/mandelbrot/assembly/clamp (f64.div (f64.sub (f64.convert_u/i32 @@ -609,7 +609,7 @@ ) ) (i32.sub - (get_global $../../examples/mandelbrot/assembly/index/NUM_COLORS) + (get_global $../../examples/mandelbrot/assembly/NUM_COLORS) (i32.const 1) ) ) 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) + ) + ) + ) + ) +) From 371f2fe85e6b9d53badadc65b6f43c2835e7a2ef Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Sun, 9 Sep 2018 10:04:18 -0700 Subject: [PATCH 2/3] Code review --- src/ast.ts | 16 ++-- src/diagnosticMessages.generated.ts | 4 +- src/diagnosticMessages.json | 2 +- src/program.ts | 88 +++++++++---------- src/util/MultiMap.ts | 26 ++---- .../exportFromIndex/index.untouched.wat | 8 +- .../exportFromIndexUser.untouched.wat | 4 +- tests/compiler/mandelbrot.untouched.wat | 14 +-- 8 files changed, 69 insertions(+), 93 deletions(-) diff --git a/src/ast.ts b/src/ast.ts index 9eae7ce8c4..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; } @@ -1980,16 +1983,7 @@ export function getSourceLevelName({ internalPath }: Source, simpleName: string) } export function getSourceLevelNameFromInternalPath(internalPath: string, simpleName: string): string { - return stripIndex(internalPath) + PATH_DELIMITER + simpleName; -} - -// "foo/index" -> "foo" -// "foo/bar" -> itself -export function stripIndex(internalPath: string): string { - const indexPart = PATH_DELIMITER + "index"; - return internalPath.endsWith(indexPart) - ? internalPath.substring(0, internalPath.length - indexPart.length) - : internalPath; + return internalPath + PATH_DELIMITER + simpleName; } /** Mangles an external to an internal path. */ diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index 746ca8fefe..9f5a89b21e 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -83,9 +83,9 @@ export enum DiagnosticCode { A_class_may_only_extend_another_class = 1311, A_parameter_property_cannot_be_declared_using_a_rest_parameter = 1317, Duplicate_identifier_0 = 2300, - Identifier_0_is_re_exported_from_modules_1_and_2 = 2301, 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, @@ -203,9 +203,9 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 1311: return "A class may only extend another class."; case 1317: return "A parameter property cannot be declared using a rest parameter."; case 2300: return "Duplicate identifier '{0}'."; - case 2301: return "Identifier '{0}' is re-exported from modules '{1}' and '{2}'."; 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 9abf05d11b..bda1fe0696 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -77,9 +77,9 @@ "A parameter property cannot be declared using a rest parameter.": 1317, "Duplicate identifier '{0}'.": 2300, - "Identifier '{0}' is re-exported from modules '{1}' and '{2}'.": 2301, "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 128930f253..f05e9864a6 100644 --- a/src/program.ts +++ b/src/program.ts @@ -69,7 +69,6 @@ import { findDecorator, getSourceLevelNameFromInternalPath, getSourceLevelName, - stripIndex } from "./ast"; import { @@ -108,7 +107,7 @@ import { import { CharCode, - MultiMap + multiMapAdd } from "./util"; import { @@ -119,7 +118,6 @@ import { class QueuedImport { localName: string; exportingSource: Source; // Source that should export (or re-export) exportName - externalName: string; // Internal name of the thing being imported // Null for a filespace import. This is the simple name of the export. exportName: string | null; // Null for a filespace import. @@ -135,9 +133,12 @@ class QueuedImport { * `export *` is handled by an `exportStars` map, not by a QueuedExport. */ class QueuedExport { - // Internal name of the thing being reexported. NOT `member.externalName.text`. - externalName: string; - // Set if reexporting from a different Source. + /** + * 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; } @@ -342,7 +343,7 @@ export class Program extends DiagnosticEmitter { 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 Map(); + exportsFromExportStar = new Set(); /** Module-level exports by exported name. */ moduleLevelExports: Map = new Map(); @@ -453,7 +454,7 @@ export class Program extends DiagnosticEmitter { 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 MultiMap(); + var exportStars = new Map(); // build initial lookup maps of internal names to declarations for (let i = 0, k = this.sources.length; i < k; ++i) { @@ -673,13 +674,13 @@ export class Program extends DiagnosticEmitter { } private resolveAndSetQueuedImport( - { declaration, exportingSource, exportName, externalName, localName }: QueuedImport, + { declaration, exportingSource, exportName, localName }: QueuedImport, queuedReExports: Map, - exportStars: MultiMap + exportStars: Map ): void { if (declaration) { // named let element = this.resolveAndSetExport( - exportingSource, assert(exportName), externalName, queuedReExports, exportStars, new Set()); + exportingSource, assert(exportName), queuedReExports, exportStars, new Set()); if (element) { this.elementsLookup.set(localName, element); } else { @@ -692,16 +693,16 @@ export class Program extends DiagnosticEmitter { } } 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(externalName)!); + this.elementsLookup.set(localName, this.elementsLookup.get(FILESPACE_PREFIX + exportingSource.internalPath)!); } } - private resolveExportStars(exportStars: MultiMap): void { + private resolveExportStars(exportStars: Map): void { // Source -> list of Sources we need to copy its exports to. - var reverse = new MultiMap(); + var reverse = new Map(); for (const [to, froms] of exportStars) { for (const from of froms) { - reverse.add(from, to); + multiMapAdd(reverse, from, to); } } @@ -716,7 +717,7 @@ export class Program extends DiagnosticEmitter { if (seenDestinations.has(destination)) continue; seenDestinations.add(destination); - for (const transitiveDestination of reverse.get(destination)) { + for (const transitiveDestination of reverse.get(destination) || []) { destinations.push(transitiveDestination); } @@ -724,18 +725,16 @@ export class Program extends DiagnosticEmitter { 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. - const previousReExport = this.exportsFromExportStar.get(internalName); - if (previousReExport) { + if (this.exportsFromExportStar.has(internalName)) { this.error( - DiagnosticCode.Identifier_0_is_re_exported_from_modules_1_and_2, + DiagnosticCode.Module_0_has_already_exported_a_member_named_1_Consider_explicitly_re_exporting_to_resolve_the_ambiguity, destination.range, + destination.normalizedPath, simpleName, - previousReExport.normalizedPath, - originalSource.normalizedPath ); } } else { - this.setExportFromExportStar(internalName, element, destination, simpleName, originalSource); + this.setExportFromExportStar(element, destination, simpleName); } } } @@ -745,18 +744,18 @@ export class Program extends DiagnosticEmitter { private resolveAndSetQueuedExport( // `exportInternalName` is the internal name of the alias, and `externalName` is the internal name of the original. exportInternalName: string, - { reExportFromSource, externalName, member }: QueuedExport, + { localInternalName, reExportFromSource, member }: QueuedExport, queuedExports: Map, - exportStars: MultiMap, + exportStars: Map, seenSources: Set, ): Element | null { const originalSimpleName = member.name.text; const element = reExportFromSource // `export { x } from "./foo";` ? this.resolveAndSetExport( - reExportFromSource, originalSimpleName, externalName, queuedExports, exportStars, seenSources) + reExportFromSource, originalSimpleName, queuedExports, exportStars, seenSources) // `export { x };`: normal export || library re-export - : this.elementsLookup.get(externalName) || this.elementsLookup.get(originalSimpleName); + : 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( @@ -780,30 +779,29 @@ export class Program extends DiagnosticEmitter { private resolveAndSetExport( exportingSource: Source, exportSimpleName: string, - externalName: string, queuedExports: Map, - exportStars: MultiMap, + exportStars: Map, seenSources: Set, ): Element | null { - const cached = this.fileLevelExports.get(externalName); + const exportInternalPath = getSourceLevelName(exportingSource, exportSimpleName); + const cached = this.fileLevelExports.get(exportInternalPath); if (cached) return cached; - const queuedExport = queuedExports.get(externalName); + 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, externalName, queuedExports, exportStars, seenSources); + exportingSource, exportSimpleName, queuedExports, exportStars, seenSources); } private resolveAndSetReExportFromExportStar( exportingSource: Source, exportSimpleName: string, - externalName: string, queuedExports: Map, - exportStars: MultiMap, + exportStars: Map, seenSources: Set, ): Element | null { // Avoid infinite loops -- don't look in the same source more than once. @@ -812,17 +810,16 @@ export class Program extends DiagnosticEmitter { } seenSources.add(exportingSource); - for (const reExportedSource of exportStars.get(exportingSource)) { + for (const reExportedSource of exportStars.get(exportingSource) || []) { const element = this.resolveAndSetExport( reExportedSource, exportSimpleName, - getSourceLevelName(reExportedSource, exportSimpleName), queuedExports, exportStars, seenSources ); if (element) { - this.setExportFromExportStar(externalName, element, exportingSource, exportSimpleName, reExportedSource); + this.setExportFromExportStar(element, exportingSource, exportSimpleName); return element; // If there are two reexports with the same name, we will error in `resolveExportStars`. } @@ -1523,7 +1520,7 @@ export class Program extends DiagnosticEmitter { currentSource: Source, statement: ExportStatement, queuedExports: Map, - exportStars: MultiMap, + exportStars: Map, ): void { var members = statement.members; if (members) { // named @@ -1532,18 +1529,17 @@ export class Program extends DiagnosticEmitter { } } else { // `export * from "./foo";` const target = this.lookupSourceByPath(assert(statement.normalizedPath))!; - exportStars.add(currentSource, target); + multiMapAdd(exportStars, currentSource, target); } } private setExportFromExportStar( - internalName: string, element: Element, reExportingSource: Source, simpleName: string, - originalSource: Source, ): void { - this.exportsFromExportStar.set(internalName, originalSource); + const internalName = getSourceLevelName(reExportingSource, simpleName); + this.exportsFromExportStar.add(internalName); this.setExportAndCheckLibrary(internalName, element, reExportingSource, simpleName, /*range*/ null); } @@ -1583,7 +1579,7 @@ export class Program extends DiagnosticEmitter { } private getFilespace(source: Source): Filespace { - const filespace = this.elementsLookup.get(FILESPACE_PREFIX + stripIndex(source.internalPath))!; + const filespace = this.elementsLookup.get(FILESPACE_PREFIX + source.internalPath)!; assert(filespace.kind == ElementKind.FILESPACE); return filespace; } @@ -1629,7 +1625,7 @@ export class Program extends DiagnosticEmitter { return; } const queuedExport = new QueuedExport(); - queuedExport.externalName = referencedName; + queuedExport.localInternalName = referencedName; queuedExport.reExportFromSource = null; queuedExport.member = member; queuedExports.set(externalName, queuedExport); @@ -1660,7 +1656,7 @@ export class Program extends DiagnosticEmitter { return; } const queuedExport = new QueuedExport(); - queuedExport.externalName = referencedName; + queuedExport.localInternalName = null; queuedExport.reExportFromSource = this.lookupSourceByPath(internalPath)!; queuedExport.member = member; queuedExports.set(externalName, queuedExport); @@ -1781,7 +1777,6 @@ export class Program extends DiagnosticEmitter { let queuedImport = new QueuedImport(); queuedImport.localName = internalName; queuedImport.exportingSource = this.lookupSourceByPath(statement.internalPath)!; - queuedImport.externalName = FILESPACE_PREFIX + statement.internalPath; queuedImport.exportName = null; queuedImport.declaration = null; queuedImports.push(queuedImport); @@ -1816,7 +1811,6 @@ export class Program extends DiagnosticEmitter { const queuedImport = new QueuedImport(); queuedImport.localName = localName; queuedImport.exportingSource = this.lookupSourceByPath(internalPath)!; - queuedImport.externalName = externalName; queuedImport.exportName = exportName; queuedImport.declaration = declaration; queuedImports.push(queuedImport); @@ -2251,7 +2245,7 @@ export class Filespace extends Element { program: Program, source: Source ) { - super(program, source.internalPath, FILESPACE_PREFIX + stripIndex(source.internalPath)); + super(program, source.internalPath, FILESPACE_PREFIX + source.internalPath); this.members = new Map(); } } diff --git a/src/util/MultiMap.ts b/src/util/MultiMap.ts index 576aa6dccb..4881d7045d 100644 --- a/src/util/MultiMap.ts +++ b/src/util/MultiMap.ts @@ -1,22 +1,10 @@ -export class MultiMap { - private map = new Map(); - - add(key: K, value: V): void { - const all = this.map.get(key); - if (all) { - if (!all.includes(value)) { - all.push(value); - } - } else { - this.map.set(key, [value]); +export function multiMapAdd(map: Map, key: K, value: V) : void { + const values = map.get(key); + if (values) { + if (!values.includes(value)) { + values.push(value); } - } - - get(t: K): V[] { - return this.map.get(t) || []; - } - - [Symbol.iterator](): Iterator<[K, V[]]> { - return this.map[Symbol.iterator](); + } else { + map.set(key, [value]); } } diff --git a/tests/compiler/exportFromIndex/index.untouched.wat b/tests/compiler/exportFromIndex/index.untouched.wat index 0fcf4ad5d1..7eef1babef 100644 --- a/tests/compiler/exportFromIndex/index.untouched.wat +++ b/tests/compiler/exportFromIndex/index.untouched.wat @@ -1,9 +1,9 @@ (module - (global $exportFromIndex/x i32 (i32.const 0)) - (global $exportFromIndex/y i32 (i32.const 0)) + (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/x)) - (export "y" (global $exportFromIndex/y)) + (export "x" (global $exportFromIndex/index/x)) + (export "y" (global $exportFromIndex/index/y)) ) diff --git a/tests/compiler/exportFromIndexUser.untouched.wat b/tests/compiler/exportFromIndexUser.untouched.wat index c96a033afa..bd08bc53a5 100644 --- a/tests/compiler/exportFromIndexUser.untouched.wat +++ b/tests/compiler/exportFromIndexUser.untouched.wat @@ -1,6 +1,6 @@ (module - (global $exportFromIndex/x i32 (i32.const 0)) - (global $exportFromIndex/y i32 (i32.const 0)) + (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) diff --git a/tests/compiler/mandelbrot.untouched.wat b/tests/compiler/mandelbrot.untouched.wat index 46f6ccf4cc..2aa859fe7c 100644 --- a/tests/compiler/mandelbrot.untouched.wat +++ b/tests/compiler/mandelbrot.untouched.wat @@ -3,12 +3,12 @@ (type $FF (func (param f64) (result f64))) (type $Fi (func (param f64) (result i32))) (type $FFFF (func (param f64 f64 f64) (result f64))) - (global $../../examples/mandelbrot/assembly/NUM_COLORS i32 (i32.const 2048)) + (global $../../examples/mandelbrot/assembly/index/NUM_COLORS i32 (i32.const 2048)) (global $~lib/math/NativeMath.LN2 f64 (f64.const 0.6931471805599453)) (global $HEAP_BASE i32 (i32.const 8)) (memory $0 0) (export "memory" (memory $0)) - (export "computeLine" (func $../../examples/mandelbrot/assembly/computeLine)) + (export "computeLine" (func $../../examples/mandelbrot/assembly/index/computeLine)) (func $~lib/math/NativeMath.log (; 0 ;) (type $FF) (param $0 f64) (result f64) (local $1 i64) (local $2 i32) @@ -319,7 +319,7 @@ (f64.const 0) ) ) - (func $../../examples/mandelbrot/assembly/clamp (; 2 ;) (type $FFFF) (param $0 f64) (param $1 f64) (param $2 f64) (result f64) + (func $../../examples/mandelbrot/assembly/index/clamp (; 2 ;) (type $FFFF) (param $0 f64) (param $1 f64) (param $2 f64) (result f64) (f64.min (f64.max (get_local $0) @@ -328,7 +328,7 @@ (get_local $2) ) ) - (func $../../examples/mandelbrot/assembly/computeLine (; 3 ;) (type $iiiiv) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) + (func $../../examples/mandelbrot/assembly/index/computeLine (; 3 ;) (type $iiiiv) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (local $4 f64) (local $5 f64) (local $6 f64) @@ -584,11 +584,11 @@ (f64.mul (f64.convert_s/i32 (i32.sub - (get_global $../../examples/mandelbrot/assembly/NUM_COLORS) + (get_global $../../examples/mandelbrot/assembly/index/NUM_COLORS) (i32.const 1) ) ) - (call $../../examples/mandelbrot/assembly/clamp + (call $../../examples/mandelbrot/assembly/index/clamp (f64.div (f64.sub (f64.convert_u/i32 @@ -609,7 +609,7 @@ ) ) (i32.sub - (get_global $../../examples/mandelbrot/assembly/NUM_COLORS) + (get_global $../../examples/mandelbrot/assembly/index/NUM_COLORS) (i32.const 1) ) ) From c36afd4b48661d55545aa68b896fc9e22db18fde Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Sun, 9 Sep 2018 18:48:38 -0700 Subject: [PATCH 3/3] Rename file --- src/util/index.ts | 2 +- src/util/{MultiMap.ts => map.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/util/{MultiMap.ts => map.ts} (100%) diff --git a/src/util/index.ts b/src/util/index.ts index 2263b76bd7..54a3314ea8 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -8,4 +8,4 @@ export * from "./charcode"; export * from "./path"; export * from "./text"; export * from "./binary"; -export * from "./MultiMap"; +export * from "./map"; diff --git a/src/util/MultiMap.ts b/src/util/map.ts similarity index 100% rename from src/util/MultiMap.ts rename to src/util/map.ts