From 99b0fdf7a89dce4cdc7a2477a0732d1bffc95b51 Mon Sep 17 00:00:00 2001 From: dcodeIO Date: Wed, 13 Dec 2017 23:24:13 +0100 Subject: [PATCH] Namespaces --- README.md | 2 +- bin/asc.js | 3 +- bin/asc.json | 2 +- src/ast.ts | 14 +- src/builtins.ts | 2 +- src/compiler.ts | 138 +++++++++- src/diagnosticMessages.generated.ts | 4 + src/diagnosticMessages.json | 2 + src/diagnostics.ts | 2 +- src/index.ts | 6 +- src/parser.ts | 16 +- src/program.ts | 245 +++++++++++------- std/portable.d.ts | 24 +- tests/compiler/export.optimized.wast | 5 + tests/compiler/export.ts | 4 +- tests/compiler/export.wast | 8 + tests/compiler/import.optimized-inlined.wast | 5 + tests/compiler/import.optimized.wast | 6 +- tests/compiler/import.ts | 4 +- tests/compiler/import.wast | 10 +- .../compiler/namespace.optimized-inlined.wast | 11 + tests/compiler/namespace.optimized.wast | 12 + tests/compiler/namespace.ts | 13 + tests/compiler/namespace.wast | 58 +++++ tests/compiler/reexport.ts | 4 +- tests/compiler/reexport.wast | 10 +- tests/parser/namespace.ts | 11 + tests/parser/namespace.ts.fixture.ts | 15 ++ tests/parser/var.ts | 3 + tests/parser/var.ts.fixture.ts | 3 + 30 files changed, 514 insertions(+), 128 deletions(-) create mode 100644 tests/compiler/namespace.optimized-inlined.wast create mode 100644 tests/compiler/namespace.optimized.wast create mode 100644 tests/compiler/namespace.ts create mode 100644 tests/compiler/namespace.wast create mode 100644 tests/parser/namespace.ts create mode 100644 tests/parser/namespace.ts.fixture.ts create mode 100644 tests/parser/var.ts create mode 100644 tests/parser/var.ts.fixture.ts diff --git a/README.md b/README.md index d256e0e1..690586a9 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ AssemblyScript NEXT **AssemblyScript** is a new compiler targeting [WebAssembly](http://webassembly.org) while utilizing [TypeScript](http://www.typescriptlang.org)'s syntax and [node](https://nodejs.org)'s vibrant ecosystem. Instead of requiring complex toolchains to set up, you can simply `npm install` it - or run it in a browser. -By compiling syntactially but not necessarily semantically valid TypeScript to [Binaryen](https://github.com/WebAssembly/binaryen) IR, the resulting module can be validated, optimized, emitted in WebAssembly text or binary format and converted to [asm.js](http://asmjs.org) as a polyfill. +By compiling syntactially (not necessarily semantically) valid TypeScript to [Binaryen](https://github.com/WebAssembly/binaryen) IR, the resulting module can be validated, optimized, emitted in WebAssembly text or binary format and converted to [asm.js](http://asmjs.org) as a polyfill. The compiler itself utilizes "portable definitions" so it can be compiled to both JavaScript using `tsc` and, eventually, to WebAssembly using `asc`. diff --git a/bin/asc.js b/bin/asc.js index eea283af..e48a8e6e 100644 --- a/bin/asc.js +++ b/bin/asc.js @@ -130,7 +130,8 @@ args._.forEach(filename => { var options = assemblyscript.createOptions(); assemblyscript.setTarget(options, 0); assemblyscript.setNoTreeShaking(options, args.noTreeShaking); -assemblyscript.setNoDebug(options, args.noDebug); +assemblyscript.setNoAssert(options, args.noAssert); +// TODO: noDebug binaryen feature, removing names the debug section var module = assemblyscript.compile(parser, options); checkDiagnostics(parser); diff --git a/bin/asc.json b/bin/asc.json index cf79db79..b6d9f113 100644 --- a/bin/asc.json +++ b/bin/asc.json @@ -43,7 +43,7 @@ "desc": "Disables tree-shaking.", "type": "boolean" }, - "noDebug": { + "noAssert": { "desc": "Disables assertions.", "type": "boolean" }, diff --git a/src/ast.ts b/src/ast.ts index ffdd4b77..e444e255 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -1809,12 +1809,16 @@ export function mangleInternalName(declaration: DeclarationStatement): string { } } } - if (!declaration.parent) + let parent: Node | null = declaration.parent; + if (!parent) return name; - if (declaration.parent.kind == NodeKind.CLASS) - return (declaration.parent).internalName + (hasModifier(ModifierKind.STATIC, declaration.modifiers) ? STATIC_DELIMITER : INSTANCE_DELIMITER) + name; - if (declaration.parent.kind == NodeKind.NAMESPACE || declaration.parent.kind == NodeKind.ENUM) - return (declaration.parent).internalName + STATIC_DELIMITER + name; + if (declaration.kind == NodeKind.VARIABLEDECLARATION && parent.kind == NodeKind.VARIABLE) // skip over + if (!(parent = parent.parent)) + return name; + if (parent.kind == NodeKind.CLASS) + return (parent).internalName + (hasModifier(ModifierKind.STATIC, declaration.modifiers) ? STATIC_DELIMITER : INSTANCE_DELIMITER) + name; + if (parent.kind == NodeKind.NAMESPACE || parent.kind == NodeKind.ENUM) + return (parent).internalName + STATIC_DELIMITER + name; return declaration.range.source.internalPath + PATH_DELIMITER + name; } diff --git a/src/builtins.ts b/src/builtins.ts index 9d39d869..5c0f4c4b 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -494,7 +494,7 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty return module.createUnreachable(); arg0 = compiler.compileExpression(operands[0], Type.i32); // reports compiler.currentType = Type.void; - return compiler.options.noDebug + return compiler.options.noAssert ? module.createNop() : module.createIf( module.createUnary(UnaryOp.EqzI32, arg0), diff --git a/src/compiler.ts b/src/compiler.ts index 183f1a10..73bcd025 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -119,7 +119,7 @@ export class Options { /** If true, compiles everything instead of just reachable code. */ noTreeShaking: bool = false; /** If true, replaces assertions with nops. */ - noDebug: bool = false; + noAssert: bool = false; } const enum ConversionKind { @@ -306,7 +306,7 @@ export class Compiler extends DiagnosticEmitter { throw new Error("unexpected missing global"); if (!this.compileGlobal(element)) return null; - if (declaration.range.source.isEntry && (declaration.parent).parent == declaration.range.source && hasModifier(ModifierKind.EXPORT, declaration.modifiers)) { + if (isModuleExport(element, declaration)) { if ((element).hasConstantValue) this.module.addGlobalExport(element.internalName, declaration.identifier.name); else @@ -470,7 +470,7 @@ export class Compiler extends DiagnosticEmitter { const instance: Function | null = this.compileFunctionUsingTypeArguments(element, typeArguments, contextualTypeArguments, alternativeReportNode); if (!instance) return; - if (declaration.range.source.isEntry && declaration.parent == declaration.range.source && hasModifier(ModifierKind.EXPORT, declaration.modifiers)) + if (isModuleExport(instance, declaration)) this.module.addFunctionExport(instance.internalName, declaration.identifier.name); } @@ -527,7 +527,7 @@ export class Compiler extends DiagnosticEmitter { // create the function const internalName: string = instance.internalName; - if (instance.isDeclared) { + if (instance.isDeclared) { // TODO: use parent namespace as externalModuleName, if applicable this.module.addFunctionImport(internalName, "env", declaration.identifier.name, typeRef); } else { this.module.addFunction(internalName, typeRef, typesToNativeTypes(instance.additionalLocals), this.module.createBlock(null, stmts, NativeType.None)); @@ -573,7 +573,6 @@ export class Compiler extends DiagnosticEmitter { throw new Error("unexpected namespace member"); } } - throw new Error("not implemented"); } compileNamespace(ns: Namespace): void { @@ -1834,7 +1833,115 @@ export class Compiler extends DiagnosticEmitter { return this.compileExpression(expression.expression, contextualType, ConversionKind.NONE); } - compilePropertyAccessExpression(expression: PropertyAccessExpression, contextualType: Type): ExpressionRef { + compilePropertyAccessExpression(propertyAccess: PropertyAccessExpression, contextualType: Type): ExpressionRef { + const expression: Expression = propertyAccess.expression; + const propertyName: string = propertyAccess.property.name; + + // the lhs expression is either 'this', 'super', an identifier or another property access + let target: Element | null; + switch (expression.kind) { + + default: + throw new Error("unexpected expression kind"); + + case NodeKind.THIS: + if (!this.currentFunction.instanceMethodOf) { + this.error(DiagnosticCode._this_cannot_be_referenced_in_current_location, expression.range); + return this.module.createUnreachable(); + } + target = this.currentFunction.instanceMethodOf; + break; + + case NodeKind.SUPER: + if (!(this.currentFunction.instanceMethodOf && this.currentFunction.instanceMethodOf.base)) { + this.error(DiagnosticCode._super_can_only_be_referenced_in_a_derived_class, expression.range); + return this.module.createUnreachable(); + } + target = this.currentFunction.instanceMethodOf.base; + break; + + case NodeKind.IDENTIFIER: + target = this.program.resolveIdentifier(expression, this.currentFunction); // reports + break; + + case NodeKind.PROPERTYACCESS: + target = this.program.resolvePropertyAccess(expression, this.currentFunction); // reports + break; + } + if (!target) + return this.module.createUnreachable(); + + // look up the property within the target to obtain the actual element + let element: Element | null; + let expr: ExpressionRef; + switch (target.kind) { + + // handle enum value right away + + case ElementKind.ENUM: + element = (target).members.get(propertyName); + if (!element) { + this.error(DiagnosticCode.Property_0_does_not_exist_on_type_1, propertyAccess.property.range, propertyName); + return this.module.createUnreachable(); + } + this.currentType = Type.i32; + return (element).hasConstantValue + ? this.module.createI32((element).constantValue) + : this.module.createGetGlobal((element).internalName, NativeType.I32); + + // postpone everything else + + case ElementKind.LOCAL: + element = (target).type.classType; + if (!element) { + this.error(DiagnosticCode.Property_0_does_not_exist_on_type_1, propertyAccess.property.range, propertyName, (target).type.toString()); + return this.module.createUnreachable(); + } + target = element; + break; + + case ElementKind.GLOBAL: + if (!this.compileGlobal(target)) + return this.module.createUnreachable(); + element = ((target).type).classType; + if (!element) { + this.error(DiagnosticCode.Property_0_does_not_exist_on_type_1, propertyAccess.property.range, propertyName, (target).type.toString()); + return this.module.createUnreachable(); + } + target = element; + break; + + case ElementKind.NAMESPACE: + element = (target).members.get(propertyName); + if (!(element && element.isExported)) { + this.error(DiagnosticCode.Namespace_0_has_no_exported_member_1, propertyAccess.property.range, (target).internalName, propertyName); + return this.module.createUnreachable(); + } + target = element; + break; + + default: + throw new Error("unexpected target kind"); + } + + // handle the element + switch (element.kind) { + + case ElementKind.LOCAL: + return this.module.createGetLocal((element).index, typeToNativeType(this.currentType = (element).type)); + + case ElementKind.GLOBAL: + this.compileGlobal(element); + return this.module.createGetGlobal((element).internalName, typeToNativeType(this.currentType = (element).type)); + + case ElementKind.FUNCTION: // getter + if (!(element).prototype.isGetter) { + this.error(DiagnosticCode.Property_0_does_not_exist_on_type_1, propertyAccess.property.range, propertyName, element.internalName); + return this.module.createUnreachable(); + } + return this.compileCall(element, [], propertyAccess); + } + this.error(DiagnosticCode.Operation_not_supported, propertyAccess.range); throw new Error("not implemented"); } @@ -2031,3 +2138,22 @@ function typesToSignatureName(paramTypes: Type[], returnType: Type): string { sb.push(typeToSignatureNamePart(returnType)); return sb.join(""); } + +function isModuleExport(element: Element, declaration: DeclarationStatement): bool { + if (!element.isExported) + return false; + if (declaration.range.source.isEntry) + return true; + let parentNode: Node | null = declaration.parent; + if (!parentNode) + return false; + if (parentNode.kind == NodeKind.VARIABLE) + if (!(parentNode = parentNode.parent)) + return false; + if (parentNode.kind != NodeKind.NAMESPACE && parentNode.kind != NodeKind.CLASS) + return false; + let parent: Element | null = element.program.elements.get((parentNode).internalName); + if (!parent) + return false; + return isModuleExport(parent, parentNode); +} diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index 6c2a899e..7b2bac02 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -57,6 +57,7 @@ export enum DiagnosticCode { Type_0_is_not_generic = 2315, Type_0_is_not_assignable_to_type_1 = 2322, _this_cannot_be_referenced_in_current_location = 2332, + _super_can_only_be_referenced_in_a_derived_class = 2335, Property_0_does_not_exist_on_type_1 = 2339, Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures = 2349, The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access = 2357, @@ -69,6 +70,7 @@ export enum DiagnosticCode { Expected_0_arguments_but_got_1 = 2554, Expected_at_least_0_arguments_but_got_1 = 2555, Expected_0_type_arguments_but_got_1 = 2558, + Namespace_0_has_no_exported_member_1 = 2694, File_0_not_found = 6054 } @@ -130,6 +132,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 2315: return "Type '{0}' is not generic."; case 2322: return "Type '{0}' is not assignable to type '{1}'."; case 2332: return "'this' cannot be referenced in current location."; + case 2335: return "'super' can only be referenced in a derived class."; case 2339: return "Property '{0}' does not exist on type '{1}'."; case 2349: return "Cannot invoke an expression whose type lacks a call signature. Type '{0}' has no compatible call signatures."; case 2357: return "The operand of an increment or decrement operator must be a variable or a property access."; @@ -142,6 +145,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 2554: return "Expected {0} arguments, but got {1}."; case 2555: return "Expected at least {0} arguments, but got {1}."; case 2558: return "Expected {0} type arguments, but got {1}."; + case 2694: return "Namespace '{0}' has no exported member '{1}'."; case 6054: return "File '{0}' not found."; default: return ""; } diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index 3f137ba6..bd73d801 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -57,6 +57,7 @@ "Type '{0}' is not generic.": 2315, "Type '{0}' is not assignable to type '{1}'.": 2322, "'this' cannot be referenced in current location.": 2332, + "'super' can only be referenced in a derived class.": 2335, "Property '{0}' does not exist on type '{1}'.": 2339, "Cannot invoke an expression whose type lacks a call signature. Type '{0}' has no compatible call signatures.": 2349, "The operand of an increment or decrement operator must be a variable or a property access.": 2357, @@ -69,6 +70,7 @@ "Expected {0} arguments, but got {1}.": 2554, "Expected at least {0} arguments, but got {1}.": 2555, "Expected {0} type arguments, but got {1}.": 2558, + "Namespace '{0}' has no exported member '{1}'.": 2694, "File '{0}' not found.": 6054 } diff --git a/src/diagnostics.ts b/src/diagnostics.ts index ccb70ee7..c1b6c494 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -155,7 +155,7 @@ export abstract class DiagnosticEmitter { const message: DiagnosticMessage = DiagnosticMessage.create(code, category, arg0, arg1).withRange(range); this.diagnostics.push(message); console.log(formatDiagnosticMessage(message, true, true) + "\n"); // temporary - // console.log(new Error().stack); + console.log(new Error("stack").stack); } error(code: DiagnosticCode, range: Range, arg0: string | null = null, arg1: string | null = null): void { diff --git a/src/index.ts b/src/index.ts index ba34828e..0c8d793a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -84,9 +84,9 @@ export function setNoTreeShaking(options: Options, noTreeShaking: bool): void { options.noTreeShaking = noTreeShaking; } -/** Sets the `noDebug` option. */ -export function setNoDebug(options: Options, noDebug: bool): void { - options.noDebug = noDebug; +/** Sets the `noAssert` option. */ +export function setNoAssert(options: Options, noAssert: bool): void { + options.noAssert = noAssert; } /** Compiles the sources computed by the parser to a module. */ diff --git a/src/parser.ts b/src/parser.ts index 020791f9..2bce10a9 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -22,6 +22,7 @@ import { // expressions AssertionKind, + CallExpression, Expression, IdentifierExpression, StringLiteralExpression, @@ -31,6 +32,7 @@ import { BreakStatement, ClassDeclaration, ContinueStatement, + DeclarationStatement, Decorator, DoStatement, EnumDeclaration, @@ -39,6 +41,7 @@ import { ExportMember, ExportStatement, ExpressionStatement, + FieldDeclaration, ForStatement, FunctionDeclaration, IfStatement, @@ -47,9 +50,8 @@ import { MethodDeclaration, Modifier, ModifierKind, - DeclarationStatement, + NamespaceDeclaration, Parameter, - FieldDeclaration, ReturnStatement, Statement, SwitchCase, @@ -61,8 +63,7 @@ import { VariableDeclaration, WhileStatement, - hasModifier, - NamespaceDeclaration + hasModifier } from "./ast"; @@ -1514,6 +1515,13 @@ export class Parser extends DiagnosticEmitter { if (token == Token.DOT) { if (next.kind == NodeKind.IDENTIFIER) { expr = Expression.createPropertyAccess(expr, next, tn.range(startPos, tn.pos)); + } else if (next.kind == NodeKind.CALL) { // amend + let propertyCall: CallExpression = next; + if (propertyCall.expression.kind == NodeKind.IDENTIFIER) { + propertyCall.expression = Expression.createPropertyAccess(expr, propertyCall.expression, tn.range(startPos, tn.pos)); + } else + throw new Error("unexpected expression kind"); + expr = propertyCall; } else { this.error(DiagnosticCode.Identifier_expected, next.range); return null; diff --git a/src/program.ts b/src/program.ts index 2915c46a..d5379153 100644 --- a/src/program.ts +++ b/src/program.ts @@ -210,8 +210,9 @@ export class Program extends DiagnosticEmitter { } while (true); } - private initializeClass(declaration: ClassDeclaration): void { - const internalName: string = declaration.internalName; + private initializeClass(declaration: ClassDeclaration, namespace: Namespace | null = null): void { + throw new Error("not implemented"); + /* const internalName: string = declaration.internalName; if (this.elements.has(internalName)) { this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); return; @@ -233,11 +234,12 @@ export class Program extends DiagnosticEmitter { this.initializeMethod(memberDeclaration, prototype); else throw new Error("unexpected class member"); - } + } */ } private initializeField(declaration: FieldDeclaration, classPrototype: ClassPrototype): void { - const name: string = declaration.identifier.name; + throw new Error("not implemented"); + /* const name: string = declaration.identifier.name; if (classPrototype.members.has(name)) { this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, declaration.internalName); return; @@ -249,11 +251,12 @@ export class Program extends DiagnosticEmitter { } else { const field: FieldPrototype = new FieldPrototype(classPrototype, internalName, declaration); classPrototype.members.set(name, field); - } + } */ } private initializeMethod(declaration: MethodDeclaration, classPrototype: ClassPrototype): void { - let name: string = declaration.identifier.name; + throw new Error("not implemented"); + /* let name: string = declaration.identifier.name; const internalName: string = declaration.internalName; if (classPrototype.members.has(name)) { this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); @@ -273,23 +276,30 @@ export class Program extends DiagnosticEmitter { } } } - classPrototype.members.set(name, func); + classPrototype.members.set(name, func); */ } - private initializeEnum(declaration: EnumDeclaration): void { + private initializeEnum(declaration: EnumDeclaration, namespace: Namespace | null = null): void { const internalName: string = declaration.internalName; - if (this.elements.has(internalName)) { - this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); - return; - } const enm: Enum = new Enum(this, internalName, declaration); - this.elements.set(internalName, enm); - if (enm.isExported) { + + if (this.elements.has(internalName)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); + else + this.elements.set(internalName, enm); + + if (namespace) { + if (namespace.members.has(declaration.identifier.name)) { + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); + } else + namespace.members.set(declaration.identifier.name, enm); + } else if (enm.isExported) { if (this.exports.has(internalName)) this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, declaration.identifier.range, internalName); else this.exports.set(internalName, enm); } + const values: EnumValueDeclaration[] = declaration.members; for (let i: i32 = 0, k: i32 = values.length; i < k; ++i) this.initializeEnumValue(values[i], enm); @@ -386,15 +396,22 @@ export class Program extends DiagnosticEmitter { } } - private initializeFunction(declaration: FunctionDeclaration): void { + private initializeFunction(declaration: FunctionDeclaration, namespace: Namespace | null = null): void { const internalName: string = declaration.internalName; const prototype: FunctionPrototype = new FunctionPrototype(this, internalName, declaration, null); + if (this.elements.has(internalName)) { this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); return; } this.elements.set(internalName, prototype); - if (prototype.isExported) { + + if (namespace) { + if (namespace.members.has(declaration.identifier.name)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); + else + namespace.members.set(declaration.identifier.name, prototype); + } else if (prototype.isExported) { if (this.exports.has(internalName)) this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, declaration.identifier.range, internalName); else @@ -455,20 +472,27 @@ export class Program extends DiagnosticEmitter { queuedImports.push(queuedImport); } - private initializeInterface(declaration: InterfaceDeclaration): void { + private initializeInterface(declaration: InterfaceDeclaration, namespace: Namespace | null = null): void { const internalName: string = declaration.internalName; const prototype: InterfacePrototype = new InterfacePrototype(this, internalName, declaration); + if (this.elements.has(internalName)) this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); else this.elements.set(internalName, prototype); - if (prototype.isExported) { + if (namespace) { + if (namespace.members.has(prototype.internalName)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); + else + namespace.members.set(prototype.internalName, prototype); + } else if (prototype.isExported) { if (this.exports.has(internalName)) this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, declaration.identifier.range, internalName); else this.exports.set(internalName, prototype); } + const memberDeclarations: DeclarationStatement[] = declaration.members; for (let j: i32 = 0, l: i32 = memberDeclarations.length; j < l; ++j) { const memberDeclaration: DeclarationStatement = memberDeclarations[j]; @@ -488,48 +512,54 @@ export class Program extends DiagnosticEmitter { } } - private initializeNamespace(declaration: NamespaceDeclaration): void { + private initializeNamespace(declaration: NamespaceDeclaration, parentNamespace: Namespace | null = null): void { const internalName: string = declaration.internalName; const namespace: Namespace = new Namespace(this, internalName, declaration); if (this.elements.has(internalName)) this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); - else { + else this.elements.set(internalName, namespace); - if (namespace.isExported) { + + if (parentNamespace) { + if (parentNamespace.members.has(declaration.identifier.name)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); + else + parentNamespace.members.set(declaration.identifier.name, namespace); + } else if (namespace.isExported) { if (this.exports.has(internalName)) this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, declaration.identifier.range, internalName); else this.exports.set(internalName, namespace); - } } + const members: Statement[] = declaration.members; for (let i: i32 = 0, k: i32 = members.length; i < k; ++i) { const statement: Statement = members[i]; switch (statement.kind) { case NodeKind.CLASS: - this.initializeClass(statement); + this.initializeClass(statement, namespace); break; case NodeKind.ENUM: - this.initializeEnum(statement); + this.initializeEnum(statement, namespace); break; case NodeKind.FUNCTION: - this.initializeFunction(statement); + this.initializeFunction(statement, namespace); break; case NodeKind.INTERFACE: - this.initializeInterface(statement); + this.initializeInterface(statement, namespace); break; case NodeKind.NAMESPACE: - this.initializeNamespace(statement); + this.initializeNamespace(statement, namespace); break; case NodeKind.VARIABLE: - this.initializeVariables(statement, true); + this.initializeVariables(statement, namespace); break; default: @@ -538,17 +568,24 @@ export class Program extends DiagnosticEmitter { } } - private initializeVariables(statement: VariableStatement, isNamespaceMember: bool = false): void { + private initializeVariables(statement: VariableStatement, namespace: Namespace | null = null): void { const declarations: VariableDeclaration[] = statement.declarations; for (let i: i32 = 0, k: i32 = declarations.length; i < k; ++i) { const declaration: VariableDeclaration = declarations[i]; const internalName: string = declaration.internalName; const global: Global = new Global(this, internalName, declaration, null); + if (this.elements.has(internalName)) this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); else { this.elements.set(internalName, global); - if (global.isExported) { + + if (namespace) { + if (namespace.members.has(declaration.identifier.name)) + this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); + else + namespace.members.set(declaration.identifier.name, global); + } else if (global.isExported) { if (this.exports.has(internalName)) this.error(DiagnosticCode.Duplicate_identifier_0, declaration.identifier.range, internalName); else @@ -596,7 +633,7 @@ export class Program extends DiagnosticEmitter { return null; } - /** Resolves {@link TypeParameter}s to concrete {@link Type}s. */ + /** Resolves an array of type parameters to concrete types. */ resolveTypeArguments(typeParameters: TypeParameter[], typeArgumentNodes: TypeNode[] | null, contextualTypeArguments: Map | null = null, alternativeReportNode: Node | null = null): Type[] | null { const parameterCount: i32 = typeParameters.length; const argumentCount: i32 = typeArgumentNodes ? typeArgumentNodes.length : 0; @@ -618,6 +655,49 @@ export class Program extends DiagnosticEmitter { return typeArguments; } + /** Resolves an identifier to the element is refers to. */ + resolveIdentifier(identifier: IdentifierExpression, contextualFunction: Function): Element | null { + const name: string = identifier.name; + const local: Local | null = contextualFunction.locals.get(name); + if (local) + return local; + let element: Element | null; + if (element = this.elements.get(identifier.range.source.internalPath + PATH_DELIMITER + name)) + return element; + if (element = this.elements.get(name)) + return element; + this.error(DiagnosticCode.Cannot_find_name_0, identifier.range, name); + return null; + } + + /** Resolves a property access the element it refers to. */ + resolvePropertyAccess(propertyAccess: PropertyAccessExpression, contextualFunction: Function): Element | null { + const expression: Expression = propertyAccess.expression; + let target: Element | null = null; + if (expression.kind == NodeKind.IDENTIFIER) { + target = this.resolveIdentifier(expression, contextualFunction); + } else if (expression.kind == NodeKind.PROPERTYACCESS) { + target = this.resolvePropertyAccess(expression, contextualFunction); + } else + throw new Error("unexpected target kind"); + if (!target) + return null; + const propertyName: string = propertyAccess.property.name; + let member: Element | null = null; + if (target.kind == ElementKind.ENUM) + member = (target).members.get(propertyName); + else if (target.kind == ElementKind.NAMESPACE) + member = (target).members.get(propertyName); + // else if (target.kind == ElementKind.CLASS_PROTOTYPE) + // member = (target).members.get(propertyName); + // else if (target.kind == ElementKind.CLASS) + // member = (target).members.get(propertyName); + if (member) + return member; + this.error(DiagnosticCode.Property_0_does_not_exist_on_type_1, expression.range, (expression).property.name, target.internalName); + return null; + } + resolveElement(expression: Expression, contextualFunction: Function): Element | null { // this -> Class @@ -630,38 +710,11 @@ export class Program extends DiagnosticEmitter { // local or global name if (expression.kind == NodeKind.IDENTIFIER) { - const name: string = (expression).name; - const local: Local | null = contextualFunction.locals.get(name); - if (local) - return local; - const fileGlobalElement: Element | null = this.elements.get(expression.range.source.internalPath + PATH_DELIMITER + name); - if (fileGlobalElement) - return fileGlobalElement; - const programGlobalElement: Element | null = this.elements.get(name); - if (programGlobalElement) - return programGlobalElement; - this.error(DiagnosticCode.Cannot_find_name_0, expression.range, name); - return null; + return this.resolveIdentifier(expression, contextualFunction); // static or instance property (incl. enum values) or method } else if (expression.kind == NodeKind.PROPERTYACCESS) { - const target: Element | null = this.resolveElement((expression).expression, contextualFunction); // reports - if (!target) - return null; - const propertyName: string = (expression).property.name; - let member: Element | null = null; - if (target.kind == ElementKind.ENUM) - member = (target).members.get(propertyName); - else if (target.kind == ElementKind.CLASS_PROTOTYPE) - member = (target).members.get(propertyName); - else if (target.kind == ElementKind.CLASS) - member = (target).members.get(propertyName); - else if (target.kind == ElementKind.NAMESPACE) - member = (target).members.get(propertyName); - if (member) - return member; - this.error(DiagnosticCode.Property_0_does_not_exist_on_type_1, expression.range, (expression).property.name, target.internalName); - return null; + return this.resolvePropertyAccess(expression, contextualFunction); } throw new Error("not implemented: " + expression.kind); @@ -1130,13 +1183,14 @@ export class Function extends Element { return local; } - private tempI32s: Local[] = []; - private tempI64s: Local[] = []; - private tempF32s: Local[] = []; - private tempF64s: Local[] = []; + private tempI32s: Local[] | null = null; + private tempI64s: Local[] | null = null; + private tempF32s: Local[] | null = null; + private tempF64s: Local[] | null = null; + /** Gets a free temporary local of the specified type. */ getTempLocal(type: Type): Local { - let temps: Local[]; + let temps: Local[] | null; switch (typeToNativeType(type)) { case NativeType.I32: temps = this.tempI32s; break; case NativeType.I64: temps = this.tempI64s; break; @@ -1144,30 +1198,32 @@ export class Function extends Element { case NativeType.F64: temps = this.tempF64s; break; default: throw new Error("unexpected type"); } - if (temps.length > 0) + if (temps && temps.length > 0) return temps.pop(); return this.addLocal(type); } + /** Frees the temporary local for reuse. */ freeTempLocal(local: Local): void { let temps: Local[]; switch (typeToNativeType(local.type)) { - case NativeType.I32: temps = this.tempI32s; break; - case NativeType.I64: temps = this.tempI64s; break; - case NativeType.F32: temps = this.tempF32s; break; - case NativeType.F64: temps = this.tempF64s; break; + case NativeType.I32: temps = this.tempI32s || (this.tempI32s = []); break; + case NativeType.I64: temps = this.tempI64s || (this.tempI64s = []); break; + case NativeType.F32: temps = this.tempF32s || (this.tempF32s = []); break; + case NativeType.F64: temps = this.tempF64s || (this.tempF64s = []); break; default: throw new Error("unexpected type"); } temps.push(local); } + /** Gets and immediately frees a temporary local of the specified type. */ getAndFreeTempLocal(type: Type): Local { let temps: Local[]; switch (typeToNativeType(type)) { - case NativeType.I32: temps = this.tempI32s; break; - case NativeType.I64: temps = this.tempI64s; break; - case NativeType.F32: temps = this.tempF32s; break; - case NativeType.F64: temps = this.tempF64s; break; + case NativeType.I32: temps = this.tempI32s || (this.tempI32s = []); break; + case NativeType.I64: temps = this.tempI64s || (this.tempI64s = []); break; + case NativeType.F32: temps = this.tempF32s || (this.tempF32s = []); break; + case NativeType.F64: temps = this.tempF64s || (this.tempF64s = []); break; default: throw new Error("unexpected type"); } if (temps.length > 0) @@ -1250,17 +1306,25 @@ export class Field extends Element { } /** A yet unresolved class prototype. */ -export class ClassPrototype extends Namespace { +export class ClassPrototype extends Element { kind = ElementKind.CLASS_PROTOTYPE; /** Declaration reference. */ declaration: ClassDeclaration | null; /** Resolved instances. */ - instances: Map; + instances: Map = new Map(); + /** Static fields. */ + staticFields: Map | null = null; + /** Static methods. */ + staticMethods: Map | null = null; + /** Static getters. */ + staticGetters: Map | null = null; + /** Static setters. */ + staticSetters: Map | null = null; constructor(program: Program, internalName: string, declaration: ClassDeclaration | null = null) { - super(program, internalName, null); + super(program, internalName); if (this.declaration = declaration) { if (this.declaration.modifiers) { for (let i: i32 = 0, k: i32 = this.declaration.modifiers.length; i < k; ++i) { @@ -1275,7 +1339,6 @@ export class ClassPrototype extends Namespace { if (this.declaration.typeParameters.length) this.isGeneric = true; } - this.instances = new Map(); } resolve(typeArguments: Type[], contextualTypeArguments: Map | null): Class { @@ -1305,7 +1368,7 @@ export class ClassPrototype extends Namespace { } /** A resolved class. */ -export class Class extends Namespace { +export class Class extends Element { kind = ElementKind.CLASS; @@ -1317,19 +1380,27 @@ export class Class extends Namespace { type: Type; /** Base class, if applicable. */ base: Class | null; - + /** Contextual type argumentsfor fields and methods. */ contextualTypeArguments: Map = new Map(); + /** Instance fields. */ + instanceFields: Map | null = null; + /** Instance methods. */ + instanceMethods: Map | null = null; + /** Instance getters. */ + instanceGetters: Map | null = null; + /** Instance setters. */ + instanceSetters: Map | null = null; /** Constructs a new class. */ constructor(prototype: ClassPrototype, internalName: string, typeArguments: Type[] = [], base: Class | null = null) { - super(prototype.program, internalName, prototype.declaration); + super(prototype.program, internalName); this.prototype = prototype; this.flags = prototype.flags; this.typeArguments = typeArguments; - this.base = base; this.type = (prototype.program.target == Target.WASM64 ? Type.usize64 : Type.usize32).asClass(this); + this.base = base; - // inherit base class contextual type arguments + // inherit contextual type arguments from base class if (base) for (let [name, type] of base.contextualTypeArguments) this.contextualTypeArguments.set(name, type); @@ -1350,13 +1421,13 @@ export class Class extends Namespace { } } -/** A yet unresvoled interface. */ +/** A yet unresolved interface. */ export class InterfacePrototype extends ClassPrototype { kind = ElementKind.INTERFACE_PROTOTYPE; /** Declaration reference. */ - declaration: InterfaceDeclaration | null; + declaration: InterfaceDeclaration | null; // more specific /** Constructs a new interface prototype. */ constructor(program: Program, internalName: string, declaration: InterfaceDeclaration | null = null) { @@ -1370,9 +1441,9 @@ export class Interface extends Class { kind = ElementKind.INTERFACE; /** Prototype reference. */ - prototype: InterfacePrototype; + prototype: InterfacePrototype; // more specific /** Base interface, if applcable. */ - base: Interface | null; + base: Interface | null; // more specific /** Constructs a new interface. */ constructor(prototype: InterfacePrototype, internalName: string, typeArguments: Type[] = [], base: Interface | null = null) { diff --git a/std/portable.d.ts b/std/portable.d.ts index 05319dca..95ec1941 100644 --- a/std/portable.d.ts +++ b/std/portable.d.ts @@ -87,30 +87,31 @@ declare class String { static fromCodePoint(cp: i32): string; static fromCodePoints(arr: i32[]): string; readonly length: i32; + private constructor(); indexOf(subject: string): i32; + lastIndexOf(subject: string): i32; charCodeAt(index: i32): i32; substring(from: i32, to?: i32): string; startsWith(subject: string): bool; endsWith(subject: string): bool; replace(search: string, replacement: string): string; + toString(): string; } -declare class Boolean {} +interface Boolean {} declare class Number { - toString(radix: i32): string; + private constructor(); + toString(radix?: i32): string; } -declare class Object {} +interface Object {} -declare class Function { - /** @deprecated */ - apply(subject: any): any; -} +interface Function {} -declare class RegExp {} +interface RegExp {} -declare interface IArguments {} +interface IArguments {} declare class Error { constructor(message: string); @@ -119,7 +120,8 @@ declare class Error { } declare class Symbol { - static iterator: symbol; + private constructor(); + static readonly iterator: symbol; } declare class Set { @@ -139,7 +141,7 @@ declare class Map { [Symbol.iterator](): Iterator<[K, V]>; } -declare interface Iterator {} +interface Iterator {} declare namespace JSON { /** @deprecated */ diff --git a/tests/compiler/export.optimized.wast b/tests/compiler/export.optimized.wast index d80eec1d..a30cebe0 100644 --- a/tests/compiler/export.optimized.wast +++ b/tests/compiler/export.optimized.wast @@ -1,5 +1,6 @@ (module (type $iii (func (param i32 i32) (result i32))) + (type $v (func)) (global $export/a i32 (i32.const 1)) (global $export/b i32 (i32.const 2)) (memory $0 1) @@ -7,6 +8,7 @@ (export "renamed_sub" (func $export/sub)) (export "a" (global $export/a)) (export "renamed_b" (global $export/b)) + (export "two" (func $export/ns.two)) (export "memory" (memory $0)) (func $export/add (; 0 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) (i32.add @@ -20,4 +22,7 @@ (get_local $1) ) ) + (func $export/ns.two (; 2 ;) (type $v) + (nop) + ) ) diff --git a/tests/compiler/export.ts b/tests/compiler/export.ts index ec033cd6..1094aff3 100644 --- a/tests/compiler/export.ts +++ b/tests/compiler/export.ts @@ -14,7 +14,7 @@ const b: i32 = 2; export { b as renamed_b }; -/* export namespace ns { +export namespace ns { function one(): void {} export function two(): void {} -} */ +} diff --git a/tests/compiler/export.wast b/tests/compiler/export.wast index a3cfe3d4..7873c8e4 100644 --- a/tests/compiler/export.wast +++ b/tests/compiler/export.wast @@ -1,5 +1,6 @@ (module (type $iii (func (param i32 i32) (result i32))) + (type $v (func)) (global $export/a i32 (i32.const 1)) (global $export/b i32 (i32.const 2)) (global $HEAP_START i32 (i32.const 4)) @@ -8,6 +9,7 @@ (export "renamed_sub" (func $export/sub)) (export "a" (global $export/a)) (export "renamed_b" (global $export/b)) + (export "two" (func $export/ns.two)) (export "memory" (memory $0)) (func $export/add (; 0 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32) (return @@ -25,6 +27,8 @@ ) ) ) + (func $export/ns.two (; 2 ;) (type $v) + ) ) (; [program.elements] @@ -60,9 +64,13 @@ export/sub export/a export/b + export/ns + export/ns.one + export/ns.two [program.exports] export/add export/renamed_sub export/a export/renamed_b + export/ns ;) diff --git a/tests/compiler/import.optimized-inlined.wast b/tests/compiler/import.optimized-inlined.wast index 0fa17f93..abbb867f 100644 --- a/tests/compiler/import.optimized-inlined.wast +++ b/tests/compiler/import.optimized-inlined.wast @@ -41,5 +41,10 @@ ) ) ) + (block + (block $__inlined_func$export/ns.two + (nop) + ) + ) ) ) diff --git a/tests/compiler/import.optimized.wast b/tests/compiler/import.optimized.wast index f291faf1..678d3079 100644 --- a/tests/compiler/import.optimized.wast +++ b/tests/compiler/import.optimized.wast @@ -16,7 +16,10 @@ (get_local $1) ) ) - (func $start (; 2 ;) (type $v) + (func $export/ns.two (; 2 ;) (type $v) + (nop) + ) + (func $start (; 3 ;) (type $v) (drop (i32.add (call $export/add @@ -29,5 +32,6 @@ ) ) ) + (call $export/ns.two) ) ) diff --git a/tests/compiler/import.ts b/tests/compiler/import.ts index 04c5f08d..b6ef62ff 100644 --- a/tests/compiler/import.ts +++ b/tests/compiler/import.ts @@ -1,3 +1,5 @@ -import { add, renamed_sub as sub, a, renamed_b as b } from "./export"; +import { add, renamed_sub as sub, a, renamed_b as b, ns as renamed_ns } from "./export"; add(a, b) + sub(b, a); + +renamed_ns.two(); diff --git a/tests/compiler/import.wast b/tests/compiler/import.wast index 166928a5..083436e9 100644 --- a/tests/compiler/import.wast +++ b/tests/compiler/import.wast @@ -23,7 +23,9 @@ ) ) ) - (func $start (; 2 ;) (type $v) + (func $export/ns.two (; 2 ;) (type $v) + ) + (func $start (; 3 ;) (type $v) (drop (i32.add (call $export/add @@ -36,6 +38,7 @@ ) ) ) + (call $export/ns.two) ) ) (; @@ -72,13 +75,18 @@ export/sub export/a export/b + export/ns + export/ns.one + export/ns.two import/add import/sub import/a import/b + import/renamed_ns [program.exports] export/add export/renamed_sub export/a export/renamed_b + export/ns ;) diff --git a/tests/compiler/namespace.optimized-inlined.wast b/tests/compiler/namespace.optimized-inlined.wast new file mode 100644 index 00000000..34f16cbe --- /dev/null +++ b/tests/compiler/namespace.optimized-inlined.wast @@ -0,0 +1,11 @@ +(module + (type $v (func)) + (memory $0 1) + (export "test" (func $namespace/test)) + (export "memory" (memory $0)) + (func $namespace/test (; 0 ;) (type $v) + (block $__inlined_func$namespace/Outer.Inner.aFunc + (nop) + ) + ) +) diff --git a/tests/compiler/namespace.optimized.wast b/tests/compiler/namespace.optimized.wast new file mode 100644 index 00000000..d20ed921 --- /dev/null +++ b/tests/compiler/namespace.optimized.wast @@ -0,0 +1,12 @@ +(module + (type $v (func)) + (memory $0 1) + (export "test" (func $namespace/test)) + (export "memory" (memory $0)) + (func $namespace/Outer.Inner.aFunc (; 0 ;) (type $v) + (nop) + ) + (func $namespace/test (; 1 ;) (type $v) + (call $namespace/Outer.Inner.aFunc) + ) +) diff --git a/tests/compiler/namespace.ts b/tests/compiler/namespace.ts new file mode 100644 index 00000000..0591f630 --- /dev/null +++ b/tests/compiler/namespace.ts @@ -0,0 +1,13 @@ +namespace Outer { + export namespace Inner { + export let aVar: i32; + export function aFunc(): void {} + export enum anEnum { ONE = 1 } + } +} + +export function test(): void { + Outer.Inner.aVar; + Outer.Inner.aFunc(); + Outer.Inner.anEnum.ONE; +} diff --git a/tests/compiler/namespace.wast b/tests/compiler/namespace.wast new file mode 100644 index 00000000..e004dffe --- /dev/null +++ b/tests/compiler/namespace.wast @@ -0,0 +1,58 @@ +(module + (type $v (func)) + (global $namespace/Outer.Inner.aVar (mut i32) (i32.const 0)) + (global $HEAP_START i32 (i32.const 4)) + (memory $0 1) + (export "test" (func $namespace/test)) + (export "memory" (memory $0)) + (func $namespace/Outer.Inner.aFunc (; 0 ;) (type $v) + ) + (func $namespace/test (; 1 ;) (type $v) + (drop + (get_global $namespace/Outer.Inner.aVar) + ) + (call $namespace/Outer.Inner.aFunc) + (drop + (get_global $namespace/Outer.Inner.anEnum.ONE) + ) + ) +) +(; +[program.elements] + clz + ctz + popcnt + rotl + rotr + abs + ceil + copysign + floor + max + min + nearest + sqrt + trunc + current_memory + grow_memory + unreachable + load + store + reinterpret + select + sizeof + changetype + isNaN + isFinite + assert + parseInt + parseFloat + namespace/Outer + namespace/Outer.Inner + namespace/Outer.Inner.aVar + namespace/Outer.Inner.aFunc + namespace/Outer.Inner.anEnum + namespace/test +[program.exports] + namespace/test +;) diff --git a/tests/compiler/reexport.ts b/tests/compiler/reexport.ts index 96a27828..495c7c84 100644 --- a/tests/compiler/reexport.ts +++ b/tests/compiler/reexport.ts @@ -1,7 +1,9 @@ export { add, renamed_sub, a as renamed_a, renamed_b as rerenamed_b } from "./export"; -import { add as imported_add, renamed_sub as imported_sub } from "./export"; +import { add as imported_add, renamed_sub as imported_sub, ns as imported_ns } from "./export"; export { imported_add as renamed_add, imported_sub as rerenamed_sub }; imported_add(1, 2) + imported_sub(3, 4); + +export { ns as renamed_ns } from "./export"; diff --git a/tests/compiler/reexport.wast b/tests/compiler/reexport.wast index 1d3b1c74..efbdf5b6 100644 --- a/tests/compiler/reexport.wast +++ b/tests/compiler/reexport.wast @@ -29,7 +29,9 @@ ) ) ) - (func $start (; 2 ;) (type $v) + (func $export/ns.two (; 2 ;) (type $v) + ) + (func $start (; 3 ;) (type $v) (drop (i32.add (call $export/add @@ -78,17 +80,23 @@ export/sub export/a export/b + export/ns + export/ns.one + export/ns.two reexport/imported_add reexport/imported_sub + reexport/imported_ns [program.exports] export/add export/renamed_sub export/a export/renamed_b + export/ns reexport/add reexport/renamed_sub reexport/renamed_a reexport/rerenamed_b reexport/renamed_add reexport/rerenamed_sub + reexport/renamed_ns ;) diff --git a/tests/parser/namespace.ts b/tests/parser/namespace.ts new file mode 100644 index 00000000..0d0bf369 --- /dev/null +++ b/tests/parser/namespace.ts @@ -0,0 +1,11 @@ +declare namespace A { + namespace B { + export namespace C { + let aVar: i32; + const aConst: i32; + function aFunc(): void {} + enum AnEnum {} + class AClass {} + } + } +} diff --git a/tests/parser/namespace.ts.fixture.ts b/tests/parser/namespace.ts.fixture.ts new file mode 100644 index 00000000..b3c6fe3a --- /dev/null +++ b/tests/parser/namespace.ts.fixture.ts @@ -0,0 +1,15 @@ +declare namespace A { +namespace B { +export namespace C { +let aVar: i32; +const aConst: i32; +function aFunc(): void { +} +enum AnEnum { + +} +class AClass { +} +} +} +} diff --git a/tests/parser/var.ts b/tests/parser/var.ts new file mode 100644 index 00000000..1b93002d --- /dev/null +++ b/tests/parser/var.ts @@ -0,0 +1,3 @@ +var a: i32; +let b: i32; +const c: i32; diff --git a/tests/parser/var.ts.fixture.ts b/tests/parser/var.ts.fixture.ts new file mode 100644 index 00000000..38af1b61 --- /dev/null +++ b/tests/parser/var.ts.fixture.ts @@ -0,0 +1,3 @@ +let a: i32; +let b: i32; +const c: i32;