/** * @fileoverview AssemblyScript's intermediate representation. * * The compiler uses Binaryen IR, which is fairly low level, as its * primary intermediate representation, with the following structures * holding any higher level information that cannot be represented by * Binaryen IR alone, for example higher level types. * * Similar to the AST being composed of `Node`s in `Source`s, the IR is * composed of `Element`s in a `Program`. Each class or function is * represented by a "prototype" holding all the relevant information, * including each's concrete instances. If a class or function is not * generic, there is exactly one instance, otherwise there is one for * each concrete set of type arguments. * * @license Apache-2.0 */ // Element Base class of all elements // ├─DeclaredElement Base class of elements with a declaration // │ ├─TypedElement Base class of elements resolving to a type // │ │ ├─TypeDefinition Type alias declaration // │ │ ├─VariableLikeElement Base class of all variable-like elements // │ │ │ ├─EnumValue Enum value // │ │ │ ├─Global File global // │ │ │ ├─Local Function local // │ │ │ └─Property Class property (incl. instance fields) // │ │ ├─IndexSignature Class index signature // │ │ ├─Function Concrete function instance // │ │ └─Class Concrete class instance // │ ├─Namespace Namespace with static members // │ ├─FunctionPrototype Prototype of concrete function instances // │ ├─PropertyPrototype Prototype of concrete property instances // │ └─ClassPrototype Prototype of concrete class instances // └─File File, analogous to Source in the AST import { CommonFlags, PATH_DELIMITER, STATIC_DELIMITER, INSTANCE_DELIMITER, GETTER_PREFIX, SETTER_PREFIX, INNER_DELIMITER, INDEX_SUFFIX, STUB_DELIMITER, CommonNames, Feature, Target, featureToString } from "./common"; import { Options } from "./compiler"; import { Range, DiagnosticCode, DiagnosticMessage, DiagnosticEmitter } from "./diagnostics"; import { Type, TypeKind, Signature, TypeFlags } from "./types"; import { Token } from "./tokenizer"; import { Node, NodeKind, Source, SourceKind, DecoratorNode, DecoratorKind, TypeParameterNode, TypeNode, NamedTypeNode, FunctionTypeNode, ArrowKind, Expression, IdentifierExpression, LiteralKind, StringLiteralExpression, Statement, ClassDeclaration, DeclarationStatement, EnumDeclaration, EnumValueDeclaration, ExportMember, ExportDefaultStatement, ExportStatement, FieldDeclaration, FunctionDeclaration, ImportDeclaration, ImportStatement, InterfaceDeclaration, MethodDeclaration, NamespaceDeclaration, TypeDeclaration, VariableDeclaration, VariableLikeDeclarationStatement, VariableStatement, ParameterKind, ParameterNode, TypeName } from "./ast"; import { Module, FunctionRef, MemorySegment, getFunctionName } from "./module"; import { CharCode, writeI8, writeI16, writeI32, writeF32, writeF64, writeI64, writeI32AsI64, writeI64AsI32 } from "./util"; import { Resolver } from "./resolver"; import { Flow, LocalFlags } from "./flow"; import { Parser } from "./parser"; import { BuiltinNames, builtinFunctions, builtinVariables_onAccess } from "./builtins"; // Memory manager constants const AL_SIZE = 16; const AL_MASK = AL_SIZE - 1; /** Represents a yet unresolved `import`. */ class QueuedImport { constructor( /** File being imported into. */ public localFile: File, /** Identifier within the local file. */ public localIdentifier: IdentifierExpression, /** Identifier within the other file. Is an `import *` if not set. */ public foreignIdentifier: IdentifierExpression | null, /** Path to the other file. */ public foreignPath: string, /** Alternative path to the other file. */ public foreignPathAlt: string ) {} } /** Represents a yet unresolved `export`. */ class QueuedExport { constructor( /** Identifier within the local file. */ public localIdentifier: IdentifierExpression, /** Identifier within the other file. */ public foreignIdentifier: IdentifierExpression, /** Path to the other file if a re-export. */ public foreignPath: string | null, /** Alternative path to the other file if a re-export. */ public foreignPathAlt: string | null ) {} } /** Represents a yet unresolved `export *`. */ class QueuedExportStar { // stored in a map with localFile as the key constructor( /** Path to the other file. */ public foreignPath: string, /** Alternative path to the other file. */ public foreignPathAlt: string, /** Reference to the path literal for reporting. */ public pathLiteral: StringLiteralExpression ) {} } /** Represents the kind of an operator overload. */ export enum OperatorKind { Invalid, // indexed access IndexedGet, // a[] IndexedSet, // a[]=b UncheckedIndexedGet, // unchecked(a[]) UncheckedIndexedSet, // unchecked(a[]=b) // binary Add, // a + b Sub, // a - b Mul, // a * b Div, // a / b Rem, // a % b Pow, // a ** b BitwiseAnd, // a & b BitwiseOr, // a | b BitwiseXor, // a ^ b BitwiseShl, // a << b BitwiseShr, // a >> b BitwiseShrU, // a >>> b Eq, // a == b, a === b Ne, // a != b, a !== b Gt, // a > b Ge, // a >= b Lt, // a < b Le, // a <= b // unary prefix Plus, // +a Minus, // -a Not, // !a BitwiseNot, // ~a PrefixInc, // ++a PrefixDec, // --a // unary postfix PostfixInc, // a++ PostfixDec // a-- // not overridable: // LogicalAnd // a && b // LogicalOr // a || b } export namespace OperatorKind { /** Returns the operator kind represented by the specified decorator and string argument. */ export function fromDecorator(decoratorKind: DecoratorKind, arg: string): OperatorKind { assert(arg.length); switch (decoratorKind) { case DecoratorKind.Operator: case DecoratorKind.OperatorBinary: { switch (arg.charCodeAt(0)) { case CharCode.OpenBracket: { if (arg == "[]") return OperatorKind.IndexedGet; if (arg == "[]=") return OperatorKind.IndexedSet; break; } case CharCode.OpenBrace: { if (arg == "{}") return OperatorKind.UncheckedIndexedGet; if (arg == "{}=") return OperatorKind.UncheckedIndexedSet; break; } case CharCode.Plus: { if (arg == "+") return OperatorKind.Add; break; } case CharCode.Minus: { if (arg == "-") return OperatorKind.Sub; break; } case CharCode.Asterisk: { if (arg == "*") return OperatorKind.Mul; if (arg == "**") return OperatorKind.Pow; break; } case CharCode.Slash: { if (arg == "/") return OperatorKind.Div; break; } case CharCode.Percent: { if (arg == "%") return OperatorKind.Rem; break; } case CharCode.Ampersand: { if (arg == "&") return OperatorKind.BitwiseAnd; break; } case CharCode.Bar: { if (arg == "|") return OperatorKind.BitwiseOr; break; } case CharCode.Caret: { if (arg == "^") return OperatorKind.BitwiseXor; break; } case CharCode.Equals: { if (arg == "==") return OperatorKind.Eq; break; } case CharCode.Exclamation: { if (arg == "!=") return OperatorKind.Ne; break; } case CharCode.GreaterThan: { if (arg == ">") return OperatorKind.Gt; if (arg == ">=") return OperatorKind.Ge; if (arg == ">>") return OperatorKind.BitwiseShr; if (arg == ">>>") return OperatorKind.BitwiseShrU; break; } case CharCode.LessThan: { if (arg == "<") return OperatorKind.Lt; if (arg == "<=") return OperatorKind.Le; if (arg == "<<") return OperatorKind.BitwiseShl; break; } } break; } case DecoratorKind.OperatorPrefix: { switch (arg.charCodeAt(0)) { case CharCode.Plus: { if (arg == "+") return OperatorKind.Plus; if (arg == "++") return OperatorKind.PrefixInc; break; } case CharCode.Minus: { if (arg == "-") return OperatorKind.Minus; if (arg == "--") return OperatorKind.PrefixDec; break; } case CharCode.Exclamation: { if (arg == "!") return OperatorKind.Not; break; } case CharCode.Tilde: { if (arg == "~") return OperatorKind.BitwiseNot; break; } } break; } case DecoratorKind.OperatorPostfix: { switch (arg.charCodeAt(0)) { case CharCode.Plus: { if (arg == "++") return OperatorKind.PostfixInc; break; } case CharCode.Minus: { if (arg == "--") return OperatorKind.PostfixDec; break; } } break; } } return OperatorKind.Invalid; } /** Converts a binary operator token to the respective operator kind. */ export function fromBinaryToken(token: Token): OperatorKind { switch (token) { case Token.Plus: case Token.Plus_Equals: return OperatorKind.Add; case Token.Minus: case Token.Minus_Equals: return OperatorKind.Sub; case Token.Asterisk: case Token.Asterisk_Equals: return OperatorKind.Mul; case Token.Slash: case Token.Slash_Equals: return OperatorKind.Div; case Token.Percent: case Token.Percent_Equals: return OperatorKind.Rem; case Token.Asterisk_Asterisk: case Token.Asterisk_Asterisk_Equals: return OperatorKind.Pow; case Token.Ampersand: case Token.Ampersand_Equals: return OperatorKind.BitwiseAnd; case Token.Bar: case Token.Bar_Equals: return OperatorKind.BitwiseOr; case Token.Caret: case Token.Caret_Equals: return OperatorKind.BitwiseXor; case Token.LessThan_LessThan: case Token.LessThan_LessThan_Equals: return OperatorKind.BitwiseShl; case Token.GreaterThan_GreaterThan: case Token.GreaterThan_GreaterThan_Equals: return OperatorKind.BitwiseShr; case Token.GreaterThan_GreaterThan_GreaterThan: case Token.GreaterThan_GreaterThan_GreaterThan_Equals: return OperatorKind.BitwiseShrU; case Token.Equals_Equals: return OperatorKind.Eq; case Token.Exclamation_Equals: return OperatorKind.Ne; case Token.GreaterThan: return OperatorKind.Gt; case Token.GreaterThan_Equals: return OperatorKind.Ge; case Token.LessThan: return OperatorKind.Lt; case Token.LessThan_Equals: return OperatorKind.Le; } return OperatorKind.Invalid; } /** Converts a unary prefix operator token to the respective operator kind. */ export function fromUnaryPrefixToken(token: Token): OperatorKind { switch (token) { case Token.Plus: return OperatorKind.Plus; case Token.Minus: return OperatorKind.Minus; case Token.Exclamation: return OperatorKind.Not; case Token.Tilde: return OperatorKind.BitwiseNot; case Token.Plus_Plus: return OperatorKind.PrefixInc; case Token.Minus_Minus: return OperatorKind.PrefixDec; } return OperatorKind.Invalid; } /** Converts a unary postfix operator token to the respective operator kind. */ export function fromUnaryPostfixToken(token: Token): OperatorKind { switch (token) { case Token.Plus_Plus: return OperatorKind.PostfixInc; case Token.Minus_Minus: return OperatorKind.PostfixDec; } return OperatorKind.Invalid; } } /** Represents an AssemblyScript program. */ export class Program extends DiagnosticEmitter { /** Constructs a new program, optionally inheriting parser diagnostics. */ constructor( /** Compiler options. */ public options: Options, /** Shared array of diagnostic messages (emitted so far). */ diagnostics: DiagnosticMessage[] | null = null ) { super(diagnostics); this.module = Module.create(options.stackSize > 0, options.sizeTypeRef); this.parser = new Parser(this.diagnostics, this.sources); this.resolver = new Resolver(this); let nativeFile = new File(this, Source.native); this.nativeFile = nativeFile; this.filesByName.set(nativeFile.internalName, nativeFile); } /** Module instance. */ module: Module; /** Parser instance. */ parser!: Parser; /** Resolver instance. */ resolver!: Resolver; /** Array of sources. */ sources: Source[] = []; /** Diagnostic offset used where successively obtaining the next diagnostic. */ diagnosticsOffset: i32 = 0; /** Special native code file. */ nativeFile!: File; /** Next class id. */ nextClassId: u32 = 0; /** Next signature id. */ nextSignatureId: i32 = 0; /** An indicator if the program has been initialized. */ initialized: bool = false; // Lookup maps /** Files by unique internal name. */ filesByName: Map = new Map(); /** Elements by unique internal name in element space. */ elementsByName: Map = new Map(); /** Elements by declaration. */ elementsByDeclaration: Map = new Map(); /** Element instances by unique internal name. */ instancesByName: Map = new Map(); /** Classes wrapping basic types like `i32`. */ wrapperClasses: Map = new Map(); /** Managed classes contained in the program, by id. */ managedClasses: Map = new Map(); /** A set of unique function signatures contained in the program, by id. */ uniqueSignatures: Map = new Map(); /** Module imports. */ moduleImports: Map> = new Map(); // Standard library /** Gets the standard `ArrayBufferView` instance. */ get arrayBufferViewInstance(): Class { let cached = this._arrayBufferViewInstance; if (!cached) this._arrayBufferViewInstance = cached = this.requireClass(CommonNames.ArrayBufferView); return cached; } private _arrayBufferViewInstance: Class | null = null; /** Gets the standard `ArrayBuffer` instance. */ get arrayBufferInstance(): Class { let cached = this._arrayBufferInstance; if (!cached) this._arrayBufferInstance = cached = this.requireClass(CommonNames.ArrayBuffer); return cached; } private _arrayBufferInstance: Class | null = null; /** Gets the standard `Array` prototype. */ get arrayPrototype(): ClassPrototype { let cached = this._arrayPrototype; if (!cached) this._arrayPrototype = cached = this.require(CommonNames.Array, ElementKind.ClassPrototype); return cached; } private _arrayPrototype: ClassPrototype | null = null; /** Gets the standard `StaticArray` prototype. */ get staticArrayPrototype(): ClassPrototype { let cached = this._staticArrayPrototype; if (!cached) this._staticArrayPrototype = cached = this.require(CommonNames.StaticArray, ElementKind.ClassPrototype); return cached; } private _staticArrayPrototype: ClassPrototype | null = null; /** Gets the standard `Set` prototype. */ get setPrototype(): ClassPrototype { let cached = this._setPrototype; if (!cached) this._setPrototype = cached = this.require(CommonNames.Set, ElementKind.ClassPrototype); return cached; } private _setPrototype: ClassPrototype | null = null; /** Gets the standard `Map` prototype. */ get mapPrototype(): ClassPrototype { let cached = this._mapPrototype; if (!cached) this._mapPrototype = cached = this.require(CommonNames.Map, ElementKind.ClassPrototype); return cached; } private _mapPrototype: ClassPrototype | null = null; /** Gets the standard `Function` prototype. */ get functionPrototype(): ClassPrototype { let cached = this._functionPrototype; if (!cached) this._functionPrototype = cached = this.require(CommonNames.Function, ElementKind.ClassPrototype); return cached; } private _functionPrototype: ClassPrototype | null = null; /** Gets the standard `Int8Array` prototype. */ get int8ArrayPrototype(): ClassPrototype { let cached = this._int8ArrayPrototype; if (!cached) this._int8ArrayPrototype = cached = this.require(CommonNames.Int8Array, ElementKind.ClassPrototype); return cached; } private _int8ArrayPrototype: ClassPrototype | null = null; /** Gets the standard `Int16Array` prototype. */ get int16ArrayPrototype(): ClassPrototype { let cached = this._int16ArrayPrototype; if (!cached) this._int16ArrayPrototype = cached = this.require(CommonNames.Int16Array, ElementKind.ClassPrototype); return cached; } private _int16ArrayPrototype: ClassPrototype | null = null; /** Gets the standard `Int32Array` prototype. */ get int32ArrayPrototype(): ClassPrototype { let cached = this._int32ArrayPrototype; if (!cached) this._int32ArrayPrototype = cached = this.require(CommonNames.Int32Array, ElementKind.ClassPrototype); return cached; } private _int32ArrayPrototype: ClassPrototype | null = null; /** Gets the standard `Int64Array` prototype. */ get int64ArrayPrototype(): ClassPrototype { let cached = this._int64ArrayPrototype; if (!cached) this._int64ArrayPrototype = cached = this.require(CommonNames.Int64Array, ElementKind.ClassPrototype); return cached; } private _int64ArrayPrototype: ClassPrototype | null = null; /** Gets the standard `Uint8Array` prototype. */ get uint8ArrayPrototype(): ClassPrototype { let cached = this._uint8ArrayPrototype; if (!cached) this._uint8ArrayPrototype = cached = this.require(CommonNames.Uint8Array, ElementKind.ClassPrototype); return cached; } private _uint8ArrayPrototype: ClassPrototype | null = null; /** Gets the standard `Uint8ClampedArray` prototype. */ get uint8ClampedArrayPrototype(): ClassPrototype { let cached = this._uint8ClampedArrayPrototype; if (!cached) this._uint8ClampedArrayPrototype = cached = this.require(CommonNames.Uint8ClampedArray, ElementKind.ClassPrototype); return cached; } private _uint8ClampedArrayPrototype: ClassPrototype | null = null; /** Gets the standard `Uint16Array` prototype. */ get uint16ArrayPrototype(): ClassPrototype { let cached = this._uint16ArrayPrototype; if (!cached) this._uint16ArrayPrototype = cached = this.require(CommonNames.Uint16Array, ElementKind.ClassPrototype); return cached; } private _uint16ArrayPrototype: ClassPrototype | null = null; /** Gets the standard `Uint32Array` prototype. */ get uint32ArrayPrototype(): ClassPrototype { let cached = this._uint32ArrayPrototype; if (!cached) this._uint32ArrayPrototype = cached = this.require(CommonNames.Uint32Array, ElementKind.ClassPrototype); return cached; } private _uint32ArrayPrototype: ClassPrototype | null = null; /** Gets the standard `Uint64Array` prototype. */ get uint64ArrayPrototype(): ClassPrototype { let cached = this._uint64ArrayPrototype; if (!cached) this._uint64ArrayPrototype = cached = this.require(CommonNames.Uint64Array, ElementKind.ClassPrototype); return cached; } private _uint64ArrayPrototype: ClassPrototype | null = null; /** Gets the standard `Float32Array` prototype. */ get float32ArrayPrototype(): ClassPrototype { let cached = this._float32ArrayPrototype; if (!cached) this._float32ArrayPrototype = cached = this.require(CommonNames.Float32Array, ElementKind.ClassPrototype); return cached; } private _float32ArrayPrototype: ClassPrototype | null = null; /** Gets the standard `Float64Array` prototype. */ get float64ArrayPrototype(): ClassPrototype { let cached = this._float64ArrayPrototype; if (!cached) this._float64ArrayPrototype = cached = this.require(CommonNames.Float64Array, ElementKind.ClassPrototype); return cached; } private _float64ArrayPrototype: ClassPrototype | null = null; /** Gets the standard `String` instance. */ get stringInstance(): Class { let cached = this._stringInstance; if (!cached) this._stringInstance = cached = this.requireClass(CommonNames.String); return cached; } private _stringInstance: Class | null = null; /** Gets the standard `RefString` instance. */ get refStringInstance(): Class { let cached = this._refStringInstance; if (!cached) this._refStringInstance = cached = this.requireClass(CommonNames.RefString); return cached; } private _refStringInstance: Class | null = null; /** Gets the standard `RegExp` instance. */ get regexpInstance(): Class { let cached = this._regexpInstance; if (!cached) this._regexpInstance = cached = this.requireClass(CommonNames.RegExp); return cached; } private _regexpInstance: Class | null = null; /** Gets the standard `Object` prototype. */ get objectPrototype(): ClassPrototype { let cached = this._objectPrototype; if (!cached) this._objectPrototype = cached = this.require(CommonNames.Object, ElementKind.ClassPrototype); return cached; } private _objectPrototype: ClassPrototype | null = null; /** Gets the standard `Object` instance. */ get objectInstance(): Class { let cached = this._objectInstance; if (!cached) this._objectInstance = cached = this.requireClass(CommonNames.Object); return cached; } private _objectInstance: Class | null = null; /** Gets the standard `TemplateStringsArray` instance. */ get templateStringsArrayInstance(): Class { let cached = this._templateStringsArrayInstance; if (!cached) this._templateStringsArrayInstance = cached = this.requireClass(CommonNames.TemplateStringsArray); return cached; } private _templateStringsArrayInstance: Class | null = null; /** Gets the standard `abort` instance, if not explicitly disabled. */ get abortInstance(): Function | null { let prototype = this.lookup(CommonNames.abort); if (!prototype || prototype.kind != ElementKind.FunctionPrototype) return null; return this.resolver.resolveFunction(prototype, null); } // Runtime interface /** Gets the runtime `__alloc(size: usize): usize` instance. */ get allocInstance(): Function { let cached = this._allocInstance; if (!cached) this._allocInstance = cached = this.requireFunction(CommonNames.alloc); return cached; } private _allocInstance: Function | null = null; /** Gets the runtime `__realloc(ptr: usize, newSize: usize): usize` instance. */ get reallocInstance(): Function { let cached = this._reallocInstance; if (!cached) this._reallocInstance = cached = this.requireFunction(CommonNames.realloc); return cached; } private _reallocInstance: Function | null = null; /** Gets the runtime `__free(ptr: usize): void` instance. */ get freeInstance(): Function { let cached = this._freeInstance; if (!cached) this._freeInstance = cached = this.requireFunction(CommonNames.free); return cached; } private _freeInstance: Function | null = null; /** Gets the runtime `__new(size: usize, id: u32): usize` instance. */ get newInstance(): Function { let cached = this._newInstance; if (!cached) this._newInstance = cached = this.requireFunction(CommonNames.new_); return cached; } private _newInstance: Function | null = null; /** Gets the runtime `__renew(ptr: usize, size: usize): usize` instance. */ get renewInstance(): Function { let cached = this._renewInstance; if (!cached) this._renewInstance = cached = this.requireFunction(CommonNames.renew); return cached; } private _renewInstance: Function | null = null; /** Gets the runtime `__link(parentPtr: usize, childPtr: usize, expectMultiple: bool): void` instance. */ get linkInstance(): Function { let cached = this._linkInstance; if (!cached) this._linkInstance = cached = this.requireFunction(CommonNames.link); return cached; } private _linkInstance: Function | null = null; /** Gets the runtime `__collect(): void` instance. */ get collectInstance(): Function { let cached = this._collectInstance; if (!cached) this._collectInstance = cached = this.requireFunction(CommonNames.collect); return cached; } private _collectInstance: Function | null = null; /** Gets the runtime `__visit(ptr: usize, cookie: u32): void` instance. */ get visitInstance(): Function { let cached = this._visitInstance; if (!cached) this._visitInstance = cached = this.requireFunction(CommonNames.visit); return cached; } private _visitInstance: Function | null = null; /** Gets the runtime `__newBuffer(size: usize, id: u32, data: usize = 0): usize` instance. */ get newBufferInstance(): Function { let cached = this._newBufferInstance; if (!cached) this._newBufferInstance = cached = this.requireFunction(CommonNames.newBuffer); return cached; } private _newBufferInstance: Function | null = null; /** Gets the runtime `__newArray(length: i32, alignLog2: usize, id: u32, data: usize = 0): usize` instance. */ get newArrayInstance(): Function { let cached = this._newArrayInstance; if (!cached) this._newArrayInstance = cached = this.requireFunction(CommonNames.newArray); return cached; } private _newArrayInstance: Function | null = null; /** Gets the runtime's internal `BLOCK` instance. */ get BLOCKInstance(): Class { let cached = this._BLOCKInstance; if (!cached) this._BLOCKInstance = cached = this.requireClass(CommonNames.BLOCK); return cached; } private _BLOCKInstance: Class | null = null; /** Gets the runtime's internal `OBJECT` instance. */ get OBJECTInstance(): Class { let cached = this._OBJECTInstance; if (!cached) this._OBJECTInstance = cached = this.requireClass(CommonNames.OBJECT); return cached; } private _OBJECTInstance: Class | null = null; // Utility /** Obtains the source matching the specified internal path. */ getSource(internalPath: string): string | null { let sources = this.sources; for (let i = 0; i < sources.length; ++i) { let source = sources[i]; if (source.internalPath == internalPath) return source.text; } return null; } /** Gets the overhead of a memory manager block. */ get blockOverhead(): i32 { // BLOCK | data... // ^ 16b alignment return this.BLOCKInstance.nextMemoryOffset; } /** Gets the overhead of a managed object, excl. block overhead, incl. alignment. */ get objectOverhead(): i32 { // OBJECT+align | data... // └ 0 ┘ ^ 16b alignment return (this.OBJECTInstance.nextMemoryOffset - this.blockOverhead + AL_MASK) & ~AL_MASK; } /** Gets the total overhead of a managed object, incl. block overhead. */ get totalOverhead(): i32 { // BLOCK | OBJECT+align | data... // └ = TOTAL ┘ ^ 16b alignment return this.blockOverhead + this.objectOverhead; } searchFunctionByRef(ref: FunctionRef): Function | null { const modifiedFunctionName = getFunctionName(ref); if (modifiedFunctionName) { const instancesByName = this.instancesByName; if (instancesByName.has(modifiedFunctionName)) { const element = assert(instancesByName.get(modifiedFunctionName)); if (element.kind == ElementKind.Function) { return element; } } } return null; } /** Computes the next properly aligned offset of a memory manager block, given the current bump offset. */ computeBlockStart(currentOffset: i32): i32 { let blockOverhead = this.blockOverhead; return ((currentOffset + blockOverhead + AL_MASK) & ~AL_MASK) - blockOverhead; } /** Computes the next properly aligned offset of a memory manager block, given the current bump offset. */ computeBlockStart64(currentOffset: i64): i64 { let blockOverhead = i64_new(this.blockOverhead); return i64_sub(i64_align(i64_add(currentOffset, blockOverhead), AL_SIZE), blockOverhead); } /** Computes the size of a memory manager block, excl. block overhead. */ computeBlockSize(payloadSize: i32, isManaged: bool): i32 { // see: std/rt/tlsf.ts, computeSize; becomes mmInfo if (isManaged) payloadSize += this.objectOverhead; // we know that payload must be aligned, and that block sizes must be chosen // so that blocks are adjacent with the next payload aligned. hence, block // size is payloadSize rounded up to where the next block would start: let blockSize = this.computeBlockStart(payloadSize); // make sure that block size is valid according to TLSF requirements let blockOverhead = this.blockOverhead; let blockMinsize = ((3 * this.options.usizeType.byteSize + blockOverhead + AL_MASK) & ~AL_MASK) - blockOverhead; if (blockSize < blockMinsize) blockSize = blockMinsize; const blockMaxsize = 1 << 30; // 1 << (FL_BITS + SB_BITS - 1), exclusive const tagsMask = 3; if (blockSize >= blockMaxsize || (blockSize & tagsMask) != 0) { throw new Error("invalid block size"); } return blockSize; } /** Creates a native variable declaration. */ makeNativeVariableDeclaration( /** The simple name of the variable */ name: string, /** Flags indicating specific traits, e.g. `CONST`. */ flags: CommonFlags = CommonFlags.None ): VariableDeclaration { let range = Source.native.range; return Node.createVariableDeclaration( Node.createIdentifierExpression(name, range), null, flags, null, null, range ); } /** Creates a native type declaration. */ makeNativeTypeDeclaration( /** The simple name of the type. */ name: string, /** Flags indicating specific traits, e.g. `GENERIC`. */ flags: CommonFlags = CommonFlags.None ): TypeDeclaration { let range = Source.native.range; let identifier = Node.createIdentifierExpression(name, range); return Node.createTypeDeclaration( identifier, null, flags, null, Node.createOmittedType(range), range ); } // a dummy signature for programmatically generated native functions private nativeDummySignature: FunctionTypeNode | null = null; /** Creates a native function declaration. */ makeNativeFunctionDeclaration( /** The simple name of the function. */ name: string, /** Flags indicating specific traits, e.g. `DECLARE`. */ flags: CommonFlags = CommonFlags.None ): FunctionDeclaration { let range = Source.native.range; let signature = this.nativeDummySignature; if (!signature) { this.nativeDummySignature = signature = Node.createFunctionType([], Node.createNamedType( // ^ AST signature doesn't really matter, is overridden anyway Node.createSimpleTypeName(CommonNames.void_, range), null, false, range ), null, false, range ); } return Node.createFunctionDeclaration( Node.createIdentifierExpression(name, range), null, flags, null, signature, null, ArrowKind.None, range ); } /** Creates a native namespace declaration. */ makeNativeNamespaceDeclaration( /** The simple name of the namespace. */ name: string, /** Flags indicating specific traits, e.g. `EXPORT`. */ flags: CommonFlags = CommonFlags.None ): NamespaceDeclaration { let range = Source.native.range; return Node.createNamespaceDeclaration( Node.createIdentifierExpression(name, range), null, flags, [], range ); } /** Creates a native function. */ makeNativeFunction( /** The simple name of the function. */ name: string, /** Concrete function signature. */ signature: Signature, /** Parent element, usually a file, class or namespace. */ parent: Element = this.nativeFile, /** Flags indicating specific traits, e.g. `GENERIC`. */ flags: CommonFlags = CommonFlags.None, /** Decorator flags representing built-in decorators. */ decoratorFlags: DecoratorFlags = DecoratorFlags.None ): Function { return new Function( name, new FunctionPrototype( name, parent, this.makeNativeFunctionDeclaration(name, flags), decoratorFlags ), null, signature ); } /** Gets the (possibly merged) program element linked to the specified declaration. */ getElementByDeclaration(declaration: DeclarationStatement): DeclaredElement | null { let elementsByDeclaration = this.elementsByDeclaration; return elementsByDeclaration.has(declaration) ? assert(elementsByDeclaration.get(declaration)) : null; } /** Initializes the program and its elements prior to compilation. */ initialize(): void { if (this.initialized) return; this.initialized = true; let options = this.options; // register native types this.registerNativeType(CommonNames.i8, Type.i8); this.registerNativeType(CommonNames.i16, Type.i16); this.registerNativeType(CommonNames.i32, Type.i32); this.registerNativeType(CommonNames.i64, Type.i64); this.registerNativeType(CommonNames.isize, options.isizeType); this.registerNativeType(CommonNames.u8, Type.u8); this.registerNativeType(CommonNames.u16, Type.u16); this.registerNativeType(CommonNames.u32, Type.u32); this.registerNativeType(CommonNames.u64, Type.u64); this.registerNativeType(CommonNames.usize, options.usizeType); this.registerNativeType(CommonNames.bool, Type.bool); this.registerNativeType(CommonNames.f32, Type.f32); this.registerNativeType(CommonNames.f64, Type.f64); this.registerNativeType(CommonNames.void_, Type.void); this.registerNativeType(CommonNames.number, Type.f64); // alias this.registerNativeType(CommonNames.boolean, Type.bool); // alias this.nativeFile.add(CommonNames.native, new TypeDefinition( CommonNames.native, this.nativeFile, this.makeNativeTypeDeclaration(CommonNames.native, CommonFlags.Export | CommonFlags.Generic), DecoratorFlags.Builtin )); this.nativeFile.add(CommonNames.indexof, new TypeDefinition( CommonNames.indexof, this.nativeFile, this.makeNativeTypeDeclaration(CommonNames.indexof, CommonFlags.Export | CommonFlags.Generic), DecoratorFlags.Builtin )); this.nativeFile.add(CommonNames.valueof, new TypeDefinition( CommonNames.valueof, this.nativeFile, this.makeNativeTypeDeclaration(CommonNames.valueof, CommonFlags.Export | CommonFlags.Generic), DecoratorFlags.Builtin )); this.nativeFile.add(CommonNames.returnof, new TypeDefinition( CommonNames.returnof, this.nativeFile, this.makeNativeTypeDeclaration(CommonNames.returnof, CommonFlags.Export | CommonFlags.Generic), DecoratorFlags.Builtin )); this.nativeFile.add(CommonNames.nonnull, new TypeDefinition( CommonNames.nonnull, this.nativeFile, this.makeNativeTypeDeclaration(CommonNames.nonnull, CommonFlags.Export | CommonFlags.Generic), DecoratorFlags.Builtin )); // The following types might not be enabled by compiler options, so the // compiler needs to check this condition whenever such a value is created // respectively stored or loaded. this.registerNativeType(CommonNames.v128, Type.v128); this.registerNativeType(CommonNames.ref_func, Type.func); this.registerNativeType(CommonNames.ref_extern, Type.extern); this.registerNativeType(CommonNames.ref_any, Type.any); this.registerNativeType(CommonNames.ref_eq, Type.eq); this.registerNativeType(CommonNames.ref_struct, Type.struct); this.registerNativeType(CommonNames.ref_array, Type.array); this.registerNativeType(CommonNames.ref_i31, Type.i31); this.registerNativeType(CommonNames.ref_string, Type.string); this.registerNativeType(CommonNames.ref_stringview_wtf8, Type.stringview_wtf8); this.registerNativeType(CommonNames.ref_stringview_wtf16, Type.stringview_wtf16); this.registerNativeType(CommonNames.ref_stringview_iter, Type.stringview_iter); // register compiler hints this.registerConstantInteger(CommonNames.ASC_TARGET, Type.i32, i64_new(options.isWasm64 ? Target.Wasm64 : Target.Wasm32)); this.registerConstantInteger(CommonNames.ASC_RUNTIME, Type.i32, i64_new(options.runtime)); this.registerConstantInteger(CommonNames.ASC_NO_ASSERT, Type.bool, i64_new(options.noAssert ? 1 : 0, 0)); this.registerConstantInteger(CommonNames.ASC_MEMORY_BASE, Type.i32, i64_new(options.memoryBase, 0)); this.registerConstantInteger(CommonNames.ASC_TABLE_BASE, Type.i32, i64_new(options.tableBase, 0)); this.registerConstantInteger(CommonNames.ASC_OPTIMIZE_LEVEL, Type.i32, i64_new(options.optimizeLevelHint, 0)); this.registerConstantInteger(CommonNames.ASC_SHRINK_LEVEL, Type.i32, i64_new(options.shrinkLevelHint, 0)); this.registerConstantInteger(CommonNames.ASC_LOW_MEMORY_LIMIT, Type.i32, i64_new(options.lowMemoryLimit, 0)); this.registerConstantInteger(CommonNames.ASC_EXPORT_RUNTIME, Type.bool, i64_new(options.exportRuntime ? 1 : 0, 0)); this.registerConstantInteger(CommonNames.ASC_VERSION_MAJOR, Type.i32, i64_new(options.bundleMajorVersion)); this.registerConstantInteger(CommonNames.ASC_VERSION_MINOR, Type.i32, i64_new(options.bundleMinorVersion)); this.registerConstantInteger(CommonNames.ASC_VERSION_PATCH, Type.i32, i64_new(options.bundlePatchVersion)); // register feature hints this.registerConstantInteger(CommonNames.ASC_FEATURE_SIGN_EXTENSION, Type.bool, i64_new(options.hasFeature(Feature.SignExtension) ? 1 : 0, 0)); this.registerConstantInteger(CommonNames.ASC_FEATURE_MUTABLE_GLOBALS, Type.bool, i64_new(options.hasFeature(Feature.MutableGlobals) ? 1 : 0, 0)); this.registerConstantInteger(CommonNames.ASC_FEATURE_NONTRAPPING_F2I, Type.bool, i64_new(options.hasFeature(Feature.NontrappingF2I) ? 1 : 0, 0)); this.registerConstantInteger(CommonNames.ASC_FEATURE_BULK_MEMORY, Type.bool, i64_new(options.hasFeature(Feature.BulkMemory) ? 1 : 0, 0)); this.registerConstantInteger(CommonNames.ASC_FEATURE_SIMD, Type.bool, i64_new(options.hasFeature(Feature.Simd) ? 1 : 0, 0)); this.registerConstantInteger(CommonNames.ASC_FEATURE_THREADS, Type.bool, i64_new(options.hasFeature(Feature.Threads) ? 1 : 0, 0)); this.registerConstantInteger(CommonNames.ASC_FEATURE_EXCEPTION_HANDLING, Type.bool, i64_new(options.hasFeature(Feature.ExceptionHandling) ? 1 : 0, 0)); this.registerConstantInteger(CommonNames.ASC_FEATURE_TAIL_CALLS, Type.bool, i64_new(options.hasFeature(Feature.TailCalls) ? 1 : 0, 0)); this.registerConstantInteger(CommonNames.ASC_FEATURE_REFERENCE_TYPES, Type.bool, i64_new(options.hasFeature(Feature.ReferenceTypes) ? 1 : 0, 0)); this.registerConstantInteger(CommonNames.ASC_FEATURE_MULTI_VALUE, Type.bool, i64_new(options.hasFeature(Feature.MultiValue) ? 1 : 0, 0)); this.registerConstantInteger(CommonNames.ASC_FEATURE_GC, Type.bool, i64_new(options.hasFeature(Feature.GC) ? 1 : 0, 0)); this.registerConstantInteger(CommonNames.ASC_FEATURE_MEMORY64, Type.bool, i64_new(options.hasFeature(Feature.Memory64) ? 1 : 0, 0)); this.registerConstantInteger(CommonNames.ASC_FEATURE_RELAXED_SIMD, Type.bool, i64_new(options.hasFeature(Feature.RelaxedSimd) ? 1 : 0, 0)); this.registerConstantInteger(CommonNames.ASC_FEATURE_EXTENDED_CONST, Type.bool, i64_new(options.hasFeature(Feature.ExtendedConst) ? 1 : 0, 0)); this.registerConstantInteger(CommonNames.ASC_FEATURE_STRINGREF, Type.bool, i64_new(options.hasFeature(Feature.Stringref) ? 1 : 0, 0)); // remember deferred elements let queuedImports = new Array(); let queuedExports = new Map>(); let queuedExportsStar = new Map(); let queuedExtends = new Array(); let queuedImplements = new Array(); // initialize relevant declaration-like statements of the entire program for (let i = 0, k = this.sources.length; i < k; ++i) { let source = this.sources[i]; let file = new File(this, source); this.filesByName.set(file.internalName, file); let statements = source.statements; for (let j = 0, l = statements.length; j < l; ++j) { let statement = statements[j]; switch (statement.kind) { case NodeKind.Export: { this.initializeExports(statement, file, queuedExports, queuedExportsStar); break; } case NodeKind.ExportDefault: { this.initializeExportDefault(statement, file, queuedExtends, queuedImplements); break; } case NodeKind.Import: { this.initializeImports(statement, file, queuedImports, queuedExports); break; } case NodeKind.Variable: { this.initializeVariables(statement, file); break; } case NodeKind.ClassDeclaration: { this.initializeClass(statement, file, queuedExtends, queuedImplements); break; } case NodeKind.EnumDeclaration: { this.initializeEnum(statement, file); break; } case NodeKind.FunctionDeclaration: { this.initializeFunction(statement, file); break; } case NodeKind.InterfaceDeclaration: { this.initializeInterface(statement, file, queuedExtends); break; } case NodeKind.NamespaceDeclaration: { this.initializeNamespace(statement, file, queuedExtends, queuedImplements); break; } case NodeKind.TypeDeclaration: { this.initializeTypeDefinition(statement, file); break; } } } } // queued exports * should be linkable now that all files have been processed // TODO: for (let [file, starExports] of queuedExportsStar) { for (let _keys = Map_keys(queuedExportsStar), i = 0, k = _keys.length; i < k; ++i) { let file = _keys[i]; let starExports = assert(queuedExportsStar.get(file)); for (let j = 0, l = starExports.length; j < l; ++j) { let exportStar = unchecked(starExports[j]); let foreignFile = this.lookupForeignFile(exportStar.foreignPath, exportStar.foreignPathAlt); if (!foreignFile) { this.error( DiagnosticCode.File_0_not_found, exportStar.pathLiteral.range, exportStar.pathLiteral.value ); continue; } file.ensureExportStar(foreignFile); } } // queued imports should be resolvable now through traversing exports and queued exports. // note that imports may depend upon imports, so repeat until there's no more progress. do { let i = 0, madeProgress = false; while (i < queuedImports.length) { let queuedImport = queuedImports[i]; let localIdentifier = queuedImport.localIdentifier; let foreignIdentifier = queuedImport.foreignIdentifier; // File must be found here, as it would otherwise already have been reported by the parser let foreignFile = assert(this.lookupForeignFile(queuedImport.foreignPath, queuedImport.foreignPathAlt)); if (foreignIdentifier) { // i.e. import { foo [as bar] } from "./baz" let element = this.lookupForeign( foreignIdentifier.text, foreignFile, queuedExports ); if (element) { queuedImport.localFile.add( localIdentifier.text, element, localIdentifier // isImport ); queuedImports.splice(i, 1); madeProgress = true; } else { ++i; } } else { // i.e. import * as bar from "./bar" let localFile = queuedImport.localFile; let localName = localIdentifier.text; localFile.add( localName, foreignFile.asAliasNamespace( localName, localFile, localIdentifier ), localIdentifier // isImport ); queuedImports.splice(i, 1); madeProgress = true; } } if (!madeProgress) { // report queued imports we were unable to resolve for (let j = 0, l = queuedImports.length; j < l; ++j) { let queuedImport = queuedImports[j]; let foreignIdentifier = queuedImport.foreignIdentifier; if (foreignIdentifier) { this.error( DiagnosticCode.Module_0_has_no_exported_member_1, foreignIdentifier.range, queuedImport.foreignPath, foreignIdentifier.text ); } } break; } } while (true); // queued exports should be resolvable now that imports are finalized // TODO: for (let [file, exports] of queuedExports) { for (let _keys = Map_keys(queuedExports), i = 0, k = _keys.length; i < k; ++i) { let file = unchecked(_keys[i]); let exports = assert(queuedExports.get(file)); // TODO: for (let [exportName, queuedExport] of exports) { for (let exportNames = Map_keys(exports), j = 0, l = exportNames.length; j < l; ++j) { let exportName = unchecked(exportNames[j]); let queuedExport = assert(exports.get(exportName)); let localName = queuedExport.localIdentifier.text; let foreignPath = queuedExport.foreignPath; if (foreignPath) { // i.e. export { foo [as bar] } from "./baz" // File must be found here, as it would otherwise already have been reported by the parser let foreignFile = assert(this.lookupForeignFile(foreignPath, assert(queuedExport.foreignPathAlt))); let element = this.lookupForeign(localName, foreignFile, queuedExports); if (element) { file.ensureExport(exportName, element); } else { this.error( DiagnosticCode.Module_0_has_no_exported_member_1, queuedExport.localIdentifier.range, foreignPath, localName ); } } else { // i.e. export { foo [as bar] } let element = file.getMember(localName); if (element) { file.ensureExport(exportName, element); } else { let globalElement = this.lookup(localName); if (globalElement && isDeclaredElement(globalElement.kind)) { // export { memory } file.ensureExport(exportName, globalElement); } else { this.error( DiagnosticCode.Module_0_has_no_exported_member_1, queuedExport.foreignIdentifier.range, file.internalName, queuedExport.foreignIdentifier.text ); } } } } } // register foundational classes with fixed ids assert(this.objectInstance.id == 0); assert(this.arrayBufferInstance.id == 1); assert(this.stringInstance.id == 2); assert(this.arrayBufferViewInstance.id == 3); // register classes backing basic types this.registerWrapperClass(Type.i8, CommonNames.I8); this.registerWrapperClass(Type.i16, CommonNames.I16); this.registerWrapperClass(Type.i32, CommonNames.I32); this.registerWrapperClass(Type.i64, CommonNames.I64); this.registerWrapperClass(options.isizeType, CommonNames.Isize); this.registerWrapperClass(Type.u8, CommonNames.U8); this.registerWrapperClass(Type.u16, CommonNames.U16); this.registerWrapperClass(Type.u32, CommonNames.U32); this.registerWrapperClass(Type.u64, CommonNames.U64); this.registerWrapperClass(options.usizeType, CommonNames.Usize); this.registerWrapperClass(Type.bool, CommonNames.Bool); this.registerWrapperClass(Type.f32, CommonNames.F32); this.registerWrapperClass(Type.f64, CommonNames.F64); if (options.hasFeature(Feature.Simd)) this.registerWrapperClass(Type.v128, CommonNames.V128); if (options.hasFeature(Feature.ReferenceTypes)) { this.registerWrapperClass(Type.func, CommonNames.RefFunc); this.registerWrapperClass(Type.extern, CommonNames.RefExtern); if (options.hasFeature(Feature.GC)) { this.registerWrapperClass(Type.any, CommonNames.RefAny); this.registerWrapperClass(Type.eq, CommonNames.RefEq); this.registerWrapperClass(Type.struct, CommonNames.RefStruct); this.registerWrapperClass(Type.array, CommonNames.RefArray); this.registerWrapperClass(Type.i31, CommonNames.RefI31); } if (options.hasFeature(Feature.Stringref)) { this.registerWrapperClass(Type.string, CommonNames.RefString); } } // resolve prototypes of extended classes or interfaces let resolver = this.resolver; for (let i = 0, k = queuedExtends.length; i < k; ++i) { let thisPrototype = queuedExtends[i]; let extendsNode = assert(thisPrototype.extendsNode); // must be present if in queuedExtends let baseElement = resolver.resolveTypeName(extendsNode.name, thisPrototype.parent); if (!baseElement) continue; if (thisPrototype.kind == ElementKind.ClassPrototype) { if (baseElement.kind == ElementKind.ClassPrototype) { let basePrototype = baseElement; if (basePrototype.hasDecorator(DecoratorFlags.Final)) { this.error( DiagnosticCode.Class_0_is_final_and_cannot_be_extended, extendsNode.range, basePrototype.identifierNode.text ); } if ( basePrototype.hasDecorator(DecoratorFlags.Unmanaged) != thisPrototype.hasDecorator(DecoratorFlags.Unmanaged) ) { this.error( DiagnosticCode.Unmanaged_classes_cannot_extend_managed_classes_and_vice_versa, Range.join(thisPrototype.identifierNode.range, extendsNode.range) ); } if (!thisPrototype.extends(basePrototype)) { thisPrototype.basePrototype = basePrototype; } else { this.error( DiagnosticCode._0_is_referenced_directly_or_indirectly_in_its_own_base_expression, basePrototype.identifierNode.range, basePrototype.identifierNode.text, ); } } else { this.error( DiagnosticCode.A_class_may_only_extend_another_class, extendsNode.range ); } } else if (thisPrototype.kind == ElementKind.InterfacePrototype) { if (baseElement.kind == ElementKind.InterfacePrototype) { const basePrototype = baseElement; if (!thisPrototype.extends(basePrototype)) { thisPrototype.basePrototype = basePrototype; } else { this.error( DiagnosticCode._0_is_referenced_directly_or_indirectly_in_its_own_base_expression, basePrototype.identifierNode.range, basePrototype.identifierNode.text, ); } } else { this.error( DiagnosticCode.An_interface_can_only_extend_an_interface, extendsNode.range ); } } } // check override for (let i = 0, k = queuedExtends.length; i < k; i++) { let prototype = queuedExtends[i]; let instanesMembers = prototype.instanceMembers; if (instanesMembers) { let members = Map_values(instanesMembers); for (let j = 0, k = members.length; j < k; j++) { let member = members[j]; let declaration = member.declaration; if (declaration.is(CommonFlags.Override)) { let basePrototype = prototype.basePrototype; let hasOverride = false; while (basePrototype) { let instanceMembers = basePrototype.instanceMembers; if (instanceMembers) { if (instanceMembers.has(member.name)) { hasOverride = true; break; } } basePrototype = basePrototype.basePrototype; } if (!hasOverride) { let basePrototype = assert(prototype.basePrototype); this.error( DiagnosticCode.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0, declaration.name.range, basePrototype.name ); } } } } } // resolve prototypes of implemented interfaces for (let i = 0, k = queuedImplements.length; i < k; ++i) { let thisPrototype = queuedImplements[i]; let implementsNodes = assert(thisPrototype.implementsNodes); // must be present if in queuedImplements for (let j = 0, l = implementsNodes.length; j < l; ++j) { let implementsNode = implementsNodes[j]; let interfaceElement = resolver.resolveTypeName(implementsNode.name, thisPrototype.parent); if (!interfaceElement) continue; if (interfaceElement.kind == ElementKind.InterfacePrototype) { let interfacePrototype = interfaceElement; let interfacePrototypes = thisPrototype.interfacePrototypes; if (!interfacePrototypes) thisPrototype.interfacePrototypes = interfacePrototypes = new Array(); interfacePrototypes.push(interfacePrototype); } else { this.error( DiagnosticCode.A_class_can_only_implement_an_interface, implementsNode.range ); } } } // process overrides in extended classes and implemented interfaces for (let i = 0, k = queuedExtends.length; i < k; ++i) { let thisPrototype = queuedExtends[i]; let basePrototype = thisPrototype.basePrototype; if (basePrototype) { this.processOverrides(thisPrototype, basePrototype); } } for (let i = 0, k = queuedImplements.length; i < k; ++i) { let thisPrototype = queuedImplements[i]; let basePrototype = thisPrototype.basePrototype; let interfacePrototypes = thisPrototype.interfacePrototypes; if (basePrototype) { this.processOverrides(thisPrototype, basePrototype); } if (interfacePrototypes) { for (let j = 0, l = interfacePrototypes.length; j < l; ++j) { this.processOverrides(thisPrototype, interfacePrototypes[j]); } } } // set up global aliases { let globalAliases = options.globalAliases; if (!globalAliases) globalAliases = new Map(); if (!globalAliases.has(CommonNames.abort)) { globalAliases.set(CommonNames.abort, BuiltinNames.abort); } if (!globalAliases.has(CommonNames.trace)) { globalAliases.set(CommonNames.trace, BuiltinNames.trace); } if (!globalAliases.has(CommonNames.seed)) { globalAliases.set(CommonNames.seed, BuiltinNames.seed); } if (!globalAliases.has(CommonNames.Math)) { globalAliases.set(CommonNames.Math, CommonNames.NativeMath); } if (!globalAliases.has(CommonNames.Mathf)) { globalAliases.set(CommonNames.Mathf, CommonNames.NativeMathf); } // TODO: for (let [alias, name] of globalAliases) { for (let _keys = Map_keys(globalAliases), i = 0, k = _keys.length; i < k; ++i) { let alias = unchecked(_keys[i]); let name = changetype(globalAliases.get(alias)); assert(name != null); if (!name.length) { this.elementsByName.delete(alias); continue; } let firstChar = name.charCodeAt(0); if (firstChar >= CharCode._0 && firstChar <= CharCode._9) { this.registerConstantInteger(alias, Type.i32, i64_new(parseInt(name, 10))); } else { let elementsByName = this.elementsByName; if (elementsByName.has(name)) { elementsByName.set(alias, assert(elementsByName.get(name))); } else { this.error(DiagnosticCode.Element_0_not_found, null, name); } } } } // mark module exports, i.e. to apply proper wrapping behavior on the boundaries // TODO: for (let file of this.filesByName.values()) { for (let _values = Map_values(this.filesByName), i = 0, k = _values.length; i < k; ++i) { let file = unchecked(_values[i]); if (file.source.sourceKind == SourceKind.UserEntry) { this.markModuleExports(file); } } } /** Processes overridden members by this class in a base class. */ private processOverrides( thisPrototype: ClassPrototype, basePrototype: ClassPrototype, ): void { // Note that we don't know concrete instances of class members, yet. Type // checking of concrete (generic) instances happens upon resolve. let thisInstanceMembers = thisPrototype.instanceMembers; if (thisInstanceMembers) { let thisMembers = Map_values(thisInstanceMembers); let seen: Set | null = null; do { let baseInstanceMembers = basePrototype.instanceMembers; if (baseInstanceMembers) { for (let j = 0, l = thisMembers.length; j < l; ++j) { let thisMember = thisMembers[j]; if (baseInstanceMembers.has(thisMember.name)) { let baseMember = assert(baseInstanceMembers.get(thisMember.name)); this.doProcessOverride(thisPrototype, thisMember, basePrototype, baseMember); } } } // A class can have a base class and multiple interfaces, but from the // base member alone we only get one. Make sure we don't miss any. let baseInterfacePrototypes = basePrototype.interfacePrototypes; if (baseInterfacePrototypes) { for (let i = 0, k = baseInterfacePrototypes.length; i < k; ++i) { let baseInterfacePrototype = baseInterfacePrototypes[i]; if (baseInterfacePrototype != basePrototype) { this.processOverrides(thisPrototype, baseInterfacePrototype); } } } let nextPrototype = basePrototype.basePrototype; if (!nextPrototype) break; // Break on circular inheritance. Is diagnosed later, when resolved. if (!seen) seen = new Set(); seen.add(basePrototype); if (seen.has(nextPrototype)) break; // Otherwise traverse to next base prototype. basePrototype = nextPrototype; } while (true); } } /** Processes a single overridden member by this class in a base class. */ private doProcessOverride( thisClass: ClassPrototype, thisMember: DeclaredElement, baseClass: ClassPrototype, baseMember: DeclaredElement ): void { // Constructors and private members do not override if (thisMember.isAny(CommonFlags.Constructor | CommonFlags.Private)) return; if ( thisMember.kind == ElementKind.FunctionPrototype && baseMember.kind == ElementKind.FunctionPrototype ) { let thisMethod = thisMember; let baseMethod = baseMember; if (!thisMethod.visibilityEquals(baseMethod)) { this.errorRelated( DiagnosticCode.Overload_signatures_must_all_be_public_private_or_protected, thisMethod.identifierNode.range, baseMethod.identifierNode.range ); } baseMember.set(CommonFlags.Overridden); let overrides = baseMethod.unboundOverrides; if (!overrides) baseMethod.unboundOverrides = overrides = new Set(); overrides.add(thisMember); let baseMethodInstances = baseMethod.instances; if (baseMethodInstances) { for (let _values = Map_values(baseMethodInstances), a = 0, b = _values.length; a < b; ++a) { let baseMethodInstance = _values[a]; baseMethodInstance.set(CommonFlags.Overridden); } } } else if ( thisMember.kind == ElementKind.PropertyPrototype && baseMember.kind == ElementKind.PropertyPrototype ) { let thisProperty = thisMember; let baseProperty = baseMember; if (!thisProperty.visibilityEquals(baseProperty)) { this.errorRelated( DiagnosticCode.Overload_signatures_must_all_be_public_private_or_protected, thisProperty.identifierNode.range, baseProperty.identifierNode.range ); } if (baseProperty.parent.kind != ElementKind.InterfacePrototype) { // Interface fields/properties can be implemented by either, but other // members must match to retain compatiblity with TS/JS. let thisIsField = thisProperty.isField; if (thisIsField != baseProperty.isField) { if (thisIsField) { // base is property this.errorRelated( DiagnosticCode._0_is_defined_as_an_accessor_in_class_1_but_is_overridden_here_in_2_as_an_instance_property, thisProperty.identifierNode.range, baseProperty.identifierNode.range, thisProperty.name, baseClass.internalName, thisClass.internalName ); } else { // this is property, base is field this.errorRelated( DiagnosticCode._0_is_defined_as_a_property_in_class_1_but_is_overridden_here_in_2_as_an_accessor, thisProperty.identifierNode.range, baseProperty.identifierNode.range, thisProperty.name, baseClass.internalName, thisClass.internalName ); } return; } else if (thisIsField) { // base is also field // Fields don't override other fields and can only be redeclared return; } } baseProperty.set(CommonFlags.Overridden); let baseGetter = baseProperty.getterPrototype; if (baseGetter) { baseGetter.set(CommonFlags.Overridden); let thisGetter = thisProperty.getterPrototype; if (thisGetter) { let overrides = baseGetter.unboundOverrides; if (!overrides) baseGetter.unboundOverrides = overrides = new Set(); overrides.add(thisGetter); } let baseGetterInstances = baseGetter.instances; if (baseGetterInstances) { for (let _values = Map_values(baseGetterInstances), a = 0, b = _values.length; a < b; ++a) { let baseGetterInstance = _values[a]; baseGetterInstance.set(CommonFlags.Overridden); } } } let baseSetter = baseProperty.setterPrototype; if (baseSetter && thisProperty.setterPrototype) { baseSetter.set(CommonFlags.Overridden); let thisSetter = thisProperty.setterPrototype; if (thisSetter) { let overrides = baseSetter.unboundOverrides; if (!overrides) baseSetter.unboundOverrides = overrides = new Set(); overrides.add(thisSetter); } let baseSetterInstances = baseSetter.instances; if (baseSetterInstances) { for (let _values = Map_values(baseSetterInstances), a = 0, b = _values.length; a < b; ++a) { let baseSetterInstance = _values[a]; baseSetterInstance.set(CommonFlags.Overridden); } } } } else { this.errorRelated( DiagnosticCode.Property_0_in_type_1_is_not_assignable_to_the_same_property_in_base_type_2, thisMember.identifierNode.range, baseMember.identifierNode.range, thisMember.name, thisClass.internalName, baseClass.internalName ); } } /** Looks up the element of the specified name in the global scope. */ lookup(name: string): Element | null { let elements = this.elementsByName; if (elements.has(name)) return assert(elements.get(name)); return null; } /** Requires that a global library element of the specified kind is present and returns it. */ private require(name: string, kind: ElementKind): Element { let element = this.lookup(name); if (!element) throw new Error(`Missing standard library component: ${name}`); if (element.kind != kind) throw Error(`Invalid standard library component kind: ${name}`); return element; } /** Requires that a global variable is present and returns it. */ requireGlobal(name: string): Global { return this.require(name, ElementKind.Global); } /** Requires that a non-generic global class is present and returns it. */ requireClass(name: string): Class { let prototype = this.require(name, ElementKind.ClassPrototype); let resolved = this.resolver.resolveClass(prototype, null); if (!resolved) throw new Error(`Invalid standard library class: ${name}`); return resolved; } /** Requires that a global function is present and returns it. */ requireFunction(name: string, typeArguments: Type[] | null = null): Function { let prototype = this.require(name, ElementKind.FunctionPrototype); let resolved = this.resolver.resolveFunction(prototype, typeArguments); if (!resolved) throw new Error(`Invalid standard library function: ${name}`); return resolved; } /** Marks all exports of the specified file as module exports. */ private markModuleExports(file: File): void { let exports = file.exports; if (exports) { // TODO: for (let element of exports.values()) { for (let _values = Map_values(exports), j = 0, l = _values.length; j < l; ++j) { let element = unchecked(_values[j]); this.markModuleExport(element); } } let exportsStar = file.exportsStar; if (exportsStar) { for (let i = 0, k = exportsStar.length; i < k; ++i) { this.markModuleExports(exportsStar[i]); } } } /** Marks an element and its children as a module export. */ private markModuleExport(element: Element): void { element.set(CommonFlags.ModuleExport); switch (element.kind) { case ElementKind.ClassPrototype: { let instanceMembers = (element).instanceMembers; if (instanceMembers) { // TODO: for (let member of instanceMembers.values()) { for (let _values = Map_values(instanceMembers), i = 0, k = _values.length; i < k; ++i) { let member = unchecked(_values[i]); this.markModuleExport(member); } } break; } case ElementKind.PropertyPrototype: { let propertyPrototype = element; let getterPrototype = propertyPrototype.getterPrototype; if (getterPrototype) this.markModuleExport(getterPrototype); let setterPrototype = propertyPrototype.setterPrototype; if (setterPrototype) this.markModuleExport(setterPrototype); break; } case ElementKind.Property: case ElementKind.Function: case ElementKind.Class: assert(false); // assumes that there are no instances yet } let staticMembers = element.members; if (staticMembers) { // TODO: for (let member of staticMembers.values()) { for (let _values = Map_values(staticMembers), i = 0, k = _values.length; i < k; ++i) { let member = unchecked(_values[i]); this.markModuleExport(member); } } } /** Marks an element as a module import. */ markModuleImport(moduleName: string, name: string, element: Element): void { element.set(CommonFlags.ModuleImport); let moduleImports = this.moduleImports; let module: Map; if (moduleImports.has(moduleName)) { module = assert(moduleImports.get(moduleName)); } else { module = new Map(); moduleImports.set(moduleName, module); } module.set(name, element); } /** Registers a native type with the program. */ private registerNativeType(name: string, type: Type): void { let element = new TypeDefinition( name, this.nativeFile, this.makeNativeTypeDeclaration(name, CommonFlags.Export), DecoratorFlags.Builtin ); element.setType(type); this.nativeFile.add(name, element); } /** Registers the wrapper class of a non-class type. */ private registerWrapperClass(type: Type, className: string): void { let wrapperClasses = this.wrapperClasses; assert(!type.isInternalReference && !wrapperClasses.has(type)); let element = assert(this.lookup(className)); assert(element.kind == ElementKind.ClassPrototype); let classElement = assert(this.resolver.resolveClass(element, null)); classElement.wrappedType = type; wrapperClasses.set(type, classElement); } /** Registers a constant integer value within the global scope. */ registerConstantInteger(name: string, type: Type, value: i64): void { assert(type.isIntegerInclReference); let global = new Global( name, this.nativeFile, DecoratorFlags.Lazy, this.makeNativeVariableDeclaration(name, CommonFlags.Const | CommonFlags.Export) ); global.setConstantIntegerValue(value, type); this.nativeFile.add(name, global); } /** Registers a constant float value within the global scope. */ private registerConstantFloat(name: string, type: Type, value: f64): void { assert(type.isFloatValue); let global = new Global( name, this.nativeFile, DecoratorFlags.Lazy, this.makeNativeVariableDeclaration(name, CommonFlags.Const | CommonFlags.Export) ); global.setConstantFloatValue(value, type); this.nativeFile.add(name, global); } /** Ensures that the given global element exists. Attempts to merge duplicates. */ ensureGlobal(name: string, element: DeclaredElement): DeclaredElement { let elementsByName = this.elementsByName; if (elementsByName.has(name)) { let existing = assert(elementsByName.get(name)); // NOTE: this is effectively only performed when merging native types with // their respective namespaces in std/builtins, but can also trigger when a // user has multiple global elements of the same name in different files, // which might result in unexpected shared symbols accross files. considering // this a wonky feature for now that we might want to revisit later. if (existing != element) { let merged = tryMerge(existing, element); if (!merged) { if (isDeclaredElement(existing.kind)) { this.errorRelated( DiagnosticCode.Duplicate_identifier_0, element.identifierNode.range, (existing).declaration.name.range, name ); } else { this.error( DiagnosticCode.Duplicate_identifier_0, element.identifierNode.range, name ); } return element; } element = merged; } } elementsByName.set(name, element); return element; } /** Tries to locate a foreign file given its normalized path. */ private lookupForeignFile( /** Normalized path to the other file. */ foreignPath: string, /** Alternative normalized path to the other file. */ foreignPathAlt: string ): File | null { let filesByName = this.filesByName; return filesByName.has(foreignPath) ? assert(filesByName.get(foreignPath)) : filesByName.has(foreignPathAlt) ? assert(filesByName.get(foreignPathAlt)) : null; } /** Tries to locate a foreign element by traversing exports and queued exports. */ private lookupForeign( /** Identifier within the other file. */ foreignName: string, /** The other file. */ foreignFile: File, /** So far queued exports. */ queuedExports: Map> ): DeclaredElement | null { do { // check if already resolved let element = foreignFile.lookupExport(foreignName); if (element) return element; // follow queued exports if (queuedExports.has(foreignFile)) { let fileQueuedExports = assert(queuedExports.get(foreignFile)); if (fileQueuedExports.has(foreignName)) { let queuedExport = assert(fileQueuedExports.get(foreignName)); let queuedExportForeignPath = queuedExport.foreignPath; // re-exported from another file if (queuedExportForeignPath) { let otherFile = this.lookupForeignFile(queuedExportForeignPath, assert(queuedExport.foreignPathAlt)); if (!otherFile) return null; foreignName = queuedExport.localIdentifier.text; foreignFile = otherFile; continue; } // exported from this file element = foreignFile.getMember(queuedExport.localIdentifier.text); if (element) return element; } } break; } while (true); // follow star exports let exportsStar = foreignFile.exportsStar; if (exportsStar) { for (let i = 0, k = exportsStar.length; i < k; ++i) { let element = this.lookupForeign(foreignName, exportsStar[i], queuedExports); if (element) return element; } } return null; } /** Validates that only supported decorators are present. */ private checkDecorators( /** Decorators present on an element. */ decorators: DecoratorNode[] | null, /** Accepted decorator flags. Emits diagnostics if any other decorators are present. */ acceptedFlags: DecoratorFlags ): DecoratorFlags { let flags = DecoratorFlags.None; if (decorators) { for (let i = 0, k = decorators.length; i < k; ++i) { let decorator = decorators[i]; let kind = DecoratorKind.fromNode(decorator.name); let flag = DecoratorFlags.fromKind(kind); if (flag) { if (!(acceptedFlags & flag)) { this.error( DiagnosticCode.Decorator_0_is_not_valid_here, decorator.range, decorator.name.range.toString() ); } else if (flags & flag) { this.error( DiagnosticCode.Duplicate_decorator, decorator.range ); } else { flags |= flag; } } } } return flags; } /** Checks whether a particular feature is enabled. */ checkFeatureEnabled(feature: Feature, reportNode: Node): bool { if (!this.options.hasFeature(feature)) { this.error( DiagnosticCode.Feature_0_is_not_enabled, reportNode.range, featureToString(feature) ); return false; } return true; } /** Checks whether a particular type is supported. */ checkTypeSupported(type: Type, reportNode: Node): bool { switch (type.kind) { case TypeKind.V128: return this.checkFeatureEnabled(Feature.Simd, reportNode); case TypeKind.Func: case TypeKind.Extern: // Non-nullability is introduced by typed function references (here part of GC) if (!type.is(TypeFlags.Nullable)) return this.checkFeatureEnabled(Feature.GC, reportNode); return this.checkFeatureEnabled(Feature.ReferenceTypes, reportNode); case TypeKind.Any: case TypeKind.Eq: case TypeKind.Struct: case TypeKind.Array: case TypeKind.I31: { return this.checkFeatureEnabled(Feature.ReferenceTypes, reportNode) && this.checkFeatureEnabled(Feature.GC, reportNode); } case TypeKind.String: case TypeKind.StringviewWTF8: case TypeKind.StringviewWTF16: case TypeKind.StringviewIter: { return this.checkFeatureEnabled(Feature.ReferenceTypes, reportNode) && this.checkFeatureEnabled(Feature.Stringref, reportNode); } } let classReference = type.getClass(); if (classReference) { do { let typeArguments = classReference.typeArguments; if (typeArguments) { for (let i = 0, k = typeArguments.length; i < k; ++i) { if (!this.checkTypeSupported(typeArguments[i], reportNode)) { return false; } } } classReference = classReference.base; } while (classReference); } else { let signatureReference = type.getSignature(); if (signatureReference) { let thisType = signatureReference.thisType; if (thisType) { if (!this.checkTypeSupported(thisType, reportNode)) { return false; } } let parameterTypes = signatureReference.parameterTypes; for (let i = 0, k = parameterTypes.length; i < k; ++i) { if (!this.checkTypeSupported(parameterTypes[i], reportNode)) { return false; } } let returnType = signatureReference.returnType; if (!this.checkTypeSupported(returnType, reportNode)) { return false; } } } return true; } /** Initializes a class declaration. */ private initializeClass( /** The declaration to initialize. */ declaration: ClassDeclaration, /** Parent element, usually a file or namespace. */ parent: Element, /** So far queued `extends` clauses. */ queuedExtends: ClassPrototype[], /** So far queued `implements` clauses. */ queuedImplements: ClassPrototype[] ): ClassPrototype | null { let name = declaration.name.text; let element = new ClassPrototype( name, parent, declaration, this.checkDecorators(declaration.decorators, DecoratorFlags.Global | DecoratorFlags.Final | DecoratorFlags.Unmanaged ) ); if (!parent.add(name, element)) return null; // remember classes that implement interfaces let implementsTypes = declaration.implementsTypes; if (implementsTypes) { let numImplementsTypes = implementsTypes.length; if (numImplementsTypes) { // cannot implement interfaces when unmanaged if (element.hasDecorator(DecoratorFlags.Unmanaged)) { this.error( DiagnosticCode.Unmanaged_classes_cannot_implement_interfaces, Range.join( declaration.name.range, implementsTypes[numImplementsTypes - 1].range ) ); } else { queuedImplements.push(element); } } } // remember classes that extend another class if (declaration.extendsType) { queuedExtends.push(element); } else if ( !element.hasDecorator(DecoratorFlags.Unmanaged) && element.internalName != BuiltinNames.Object ) { element.implicitlyExtendsObject = true; } // initialize members let memberDeclarations = declaration.members; for (let i = 0, k = memberDeclarations.length; i < k; ++i) { let memberDeclaration = memberDeclarations[i]; switch (memberDeclaration.kind) { case NodeKind.FieldDeclaration: { this.initializeField(memberDeclaration, element); break; } case NodeKind.MethodDeclaration: { let methodDeclaration = memberDeclaration; if (memberDeclaration.isAny(CommonFlags.Get | CommonFlags.Set)) { this.initializeProperty(methodDeclaration, element); } else { let method = this.initializeMethod(methodDeclaration, element); if (method && methodDeclaration.name.kind == NodeKind.Constructor) { element.constructorPrototype = method; } } break; } case NodeKind.IndexSignature: break; // ignored for now default: assert(false); // class member expected } } return element; } /** Initializes a field of a class or interface. */ private initializeField( /** The declaration to initialize. */ declaration: FieldDeclaration, /** Parent class. */ parent: ClassPrototype ): void { let name = declaration.name.text; let decorators = declaration.decorators; let element: DeclaredElement; let acceptedFlags: DecoratorFlags = DecoratorFlags.Unsafe; if (parent.is(CommonFlags.Ambient)) { acceptedFlags |= DecoratorFlags.External; } if (declaration.is(CommonFlags.Static)) { // global variable assert(parent.kind != ElementKind.InterfacePrototype); acceptedFlags |= DecoratorFlags.Lazy; if (declaration.is(CommonFlags.Readonly)) { acceptedFlags |= DecoratorFlags.Inline; } element = new Global( name, parent, this.checkDecorators(decorators, acceptedFlags), declaration ); if (!parent.add(name, element)) return; } else { // actual instance field assert(!declaration.isAny(CommonFlags.Abstract | CommonFlags.Get | CommonFlags.Set)); element = PropertyPrototype.forField( name, parent, declaration, this.checkDecorators(decorators, acceptedFlags) ); if (!parent.addInstance(name, element)) return; } } /** Initializes a method of a class or interface. */ private initializeMethod( /** The declaration to initialize. */ declaration: MethodDeclaration, /** Parent class. */ parent: ClassPrototype ): FunctionPrototype | null { let name = declaration.name.text; let isStatic = declaration.is(CommonFlags.Static); let acceptedFlags = DecoratorFlags.Inline | DecoratorFlags.Unsafe; if (!declaration.is(CommonFlags.Generic)) { acceptedFlags |= DecoratorFlags.OperatorBinary | DecoratorFlags.OperatorPrefix | DecoratorFlags.OperatorPostfix; } if (parent.is(CommonFlags.Ambient)) { acceptedFlags |= DecoratorFlags.External; } if (declaration.range.source.isLibrary) { acceptedFlags |= DecoratorFlags.Builtin; } let element = new FunctionPrototype( name, parent, declaration, this.checkDecorators(declaration.decorators, acceptedFlags) ); if (element.hasDecorator(DecoratorFlags.Builtin) && !builtinFunctions.has(element.internalName)) { this.error( DiagnosticCode.Not_implemented_0, declaration.range, `Builtin '${element.internalName}'` ); } if (isStatic) { // global function assert(declaration.name.kind != NodeKind.Constructor); if (!parent.add(name, element)) return null; } else { // actual instance method if (!parent.addInstance(name, element)) return null; } this.checkOperatorOverloads(declaration.decorators, element, parent); return element; } /** Checks that operator overloads are generally valid, if present. */ private checkOperatorOverloads( /** Decorators to check. */ decorators: DecoratorNode[] | null, /** Decorated method. */ prototype: FunctionPrototype, /** Parent class. */ classPrototype: ClassPrototype ): void { if (decorators) { for (let i = 0, k = decorators.length; i < k; ++i) { let decorator: DecoratorNode = decorators[i]; // FIXME: why does tsc want a type here? switch (decorator.decoratorKind) { case DecoratorKind.Operator: case DecoratorKind.OperatorBinary: case DecoratorKind.OperatorPrefix: case DecoratorKind.OperatorPostfix: { let args = decorator.args; let numArgs = args ? args.length : 0; if (numArgs == 1) { let firstArg = (decorator.args)[0]; if (firstArg.isLiteralKind(LiteralKind.String)) { let text = (firstArg).value; let kind = OperatorKind.fromDecorator(decorator.decoratorKind, text); if (kind == OperatorKind.Invalid) { this.error( DiagnosticCode._0_is_not_a_valid_operator, firstArg.range, text ); } else { let overloads = classPrototype.operatorOverloadPrototypes; if (overloads.has(kind)) { this.error( DiagnosticCode.Duplicate_function_implementation, firstArg.range ); } else { prototype.operatorKind = kind; overloads.set(kind, prototype); } } } else { this.error( DiagnosticCode.String_literal_expected, firstArg.range ); } } else { this.error( DiagnosticCode.Expected_0_arguments_but_got_1, decorator.range, "1", numArgs.toString() ); } } } } } } /** Ensures that the property introduced by the specified getter or setter exists.*/ private ensureProperty( /** The declaration of the getter or setter introducing the property. */ declaration: MethodDeclaration, /** Parent class. */ parent: ClassPrototype ): PropertyPrototype | null { let name = declaration.name.text; if (declaration.is(CommonFlags.Static)) { let parentMembers = parent.members; if (parentMembers && parentMembers.has(name)) { let element = assert(parentMembers.get(name)); if (element.kind == ElementKind.PropertyPrototype) return element; } else { let element = new PropertyPrototype(name, parent, declaration); if (!parent.add(name, element)) return null; return element; } } else { let parentMembers = parent.instanceMembers; if (parentMembers && parentMembers.has(name)) { let element = assert(parentMembers.get(name)); if (element.kind == ElementKind.PropertyPrototype) return element; } else { let element = new PropertyPrototype(name, parent, declaration); if (!parent.addInstance(name, element)) return null; return element; } } this.error( DiagnosticCode.Duplicate_property_0, declaration.name.range, name ); return null; } /** Initializes a property of a class. */ private initializeProperty( /** The declaration of the getter or setter. */ declaration: MethodDeclaration, /** Parent class. */ parent: ClassPrototype ): void { let property = this.ensureProperty(declaration, parent); if (!property) return; let name = declaration.name.text; let isGetter = declaration.is(CommonFlags.Get); if (isGetter) { if (property.getterPrototype) { this.error( DiagnosticCode.Duplicate_property_0, declaration.name.range, name ); return; } } else { if (property.setterPrototype) { this.error( DiagnosticCode.Duplicate_property_0, declaration.name.range, name ); return; } } let element = new FunctionPrototype( (isGetter ? GETTER_PREFIX : SETTER_PREFIX) + name, property.parent, // same level as property declaration, this.checkDecorators(declaration.decorators, DecoratorFlags.Inline | DecoratorFlags.Unsafe ) ); if (isGetter) { property.getterPrototype = element; } else { property.setterPrototype = element; } } /** Initializes an enum. */ private initializeEnum( /** The declaration to initialize. */ declaration: EnumDeclaration, /** Parent element, usually a file or namespace. */ parent: Element ): Enum | null { let name = declaration.name.text; let element = new Enum( name, parent, declaration, this.checkDecorators(declaration.decorators, DecoratorFlags.Global | DecoratorFlags.Inline | DecoratorFlags.Lazy ) ); if (!parent.add(name, element)) return null; let values = declaration.values; for (let i = 0, k = values.length; i < k; ++i) { this.initializeEnumValue(values[i], element); } return element; } /** Initializes an enum value. */ private initializeEnumValue( /** The declaration to initialize. */ declaration: EnumValueDeclaration, /** Parent enum. */ parent: Enum ): void { let name = declaration.name.text; let element = new EnumValue( name, parent, declaration, this.checkDecorators(declaration.decorators, DecoratorFlags.None ) ); if (!parent.add(name, element)) return; } /** Initializes an `export` statement. */ private initializeExports( /** The statement to initialize. */ statement: ExportStatement, /** Parent file. */ parent: File, /** So far queued `export`s. */ queuedExports: Map>, /** So far queued `export *`s. */ queuedExportsStar: Map ): void { let members = statement.members; if (members) { // export { foo, bar } [from "./baz"] for (let i = 0, k = members.length; i < k; ++i) { this.initializeExport(members[i], parent, statement.internalPath, queuedExports); } } else { // export * from "./baz" let queued: QueuedExportStar[]; if (queuedExportsStar.has(parent)) queued = assert(queuedExportsStar.get(parent)); else queuedExportsStar.set(parent, queued = []); let foreignPath = statement.internalPath!; // must be set for export * queued.push(new QueuedExportStar( foreignPath, foreignPath.endsWith(INDEX_SUFFIX) // strip or add index depending on what's already present ? foreignPath.substring(0, foreignPath.length - INDEX_SUFFIX.length) : foreignPath + INDEX_SUFFIX, assert(statement.path) )); } } /** Initializes a single `export` member. Does not handle `export *`. */ private initializeExport( /** The member to initialize. */ member: ExportMember, /** Local file. */ localFile: File, /** Path to the other file, if present. */ foreignPath: string | null, /** So far queued `export`s. */ queuedExports: Map> ): void { let localName = member.localName.text; let foreignName = member.exportedName.text; // check for duplicates let element = localFile.lookupExport(foreignName); if (element) { this.error( DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, member.exportedName.range, foreignName ); return; } // local element, i.e. export { foo [as bar] } if (foreignPath == null) { // resolve right away if the local element already exists if (element = localFile.getMember(localName)) { localFile.ensureExport(foreignName, element); // otherwise queue it } else { let queued: Map; if (queuedExports.has(localFile)) queued = assert(queuedExports.get(localFile)); else queuedExports.set(localFile, queued = new Map()); queued.set(foreignName, new QueuedExport( member.localName, member.exportedName, null, null )); } // foreign element, i.e. export { foo } from "./bar" } else { let queued: Map; if (queuedExports.has(localFile)) queued = assert(queuedExports.get(localFile)); else queuedExports.set(localFile, queued = new Map()); queued.set(foreignName, new QueuedExport( member.localName, member.exportedName, foreignPath, foreignPath.endsWith(INDEX_SUFFIX) // strip or add index depending on what's already present ? foreignPath.substring(0, foreignPath.length - INDEX_SUFFIX.length) : foreignPath + INDEX_SUFFIX )); } } private initializeExportDefault( /** The statement to initialize. */ statement: ExportDefaultStatement, /** Parent file. */ parent: File, /** So far queued `extends` clauses. */ queuedExtends: Array, /** So far queued `implements` clauses. */ queuedImplements: ClassPrototype[] ): void { let declaration = statement.declaration; let element: DeclaredElement | null = null; switch (declaration.kind) { case NodeKind.EnumDeclaration: { element = this.initializeEnum(declaration, parent); break; } case NodeKind.FunctionDeclaration: { element = this.initializeFunction(declaration, parent); break; } case NodeKind.ClassDeclaration: { element = this.initializeClass(declaration, parent, queuedExtends, queuedImplements); break; } case NodeKind.InterfaceDeclaration: { element = this.initializeInterface(declaration, parent, queuedExtends); break; } case NodeKind.NamespaceDeclaration: { element = this.initializeNamespace(declaration, parent, queuedExtends, queuedImplements); break; } default: assert(false); } if (element) { let exports = parent.exports; if (!exports) parent.exports = exports = new Map(); else { if (exports.has("default")) { let existing = assert(exports.get("default")); this.errorRelated( DiagnosticCode.Duplicate_identifier_0, declaration.name.range, existing.declaration.name.range, "default" ); return; } } exports.set("default", element); } } /** Initializes an `import` statement. */ private initializeImports( /** The statement to initialize. */ statement: ImportStatement, /** Parent file. */ parent: File, /** So far queued `import`s. */ queuedImports: QueuedImport[], /** So far queued `export`s. */ queuedExports: Map> ): void { let declarations = statement.declarations; if (declarations) { // import { foo [as bar] } from "./baz" for (let i = 0, k = declarations.length; i < k; ++i) { this.initializeImport( declarations[i], parent, statement.internalPath, queuedImports, queuedExports ); } } else { let namespaceName = statement.namespaceName; if (namespaceName) { // import * as foo from "./bar" queuedImports.push(new QueuedImport( parent, namespaceName, null, // indicates import * statement.internalPath, statement.internalPath + INDEX_SUFFIX )); } else { // import "./foo" } } } /** Initializes a single `import` declaration. Does not handle `import *`. */ private initializeImport( // { foo [as bar] } /** The declaration to initialize. */ declaration: ImportDeclaration, /** Parent file. */ parent: File, /** Path to the other file. */ foreignPath: string, /** So far queued `import`s. */ queuedImports: QueuedImport[], /** So far queued `export`s. */ queuedExports: Map> ): void { let foreignPathAlt = foreignPath.endsWith(INDEX_SUFFIX) // strip or add index depending on what's already present ? foreignPath.substring(0, foreignPath.length - INDEX_SUFFIX.length) : foreignPath + INDEX_SUFFIX; // resolve right away if the element exists let foreignFile = this.lookupForeignFile(foreignPath, foreignPathAlt); if (foreignFile) { let element = this.lookupForeign(declaration.foreignName.text, foreignFile, queuedExports); if (element) { parent.add(declaration.name.text, element, declaration.name /* isImport */); return; } } // otherwise queue it queuedImports.push(new QueuedImport( parent, declaration.name, declaration.foreignName, foreignPath, foreignPathAlt )); } /** Initializes a function. Does not handle methods. */ private initializeFunction( /** The declaration to initialize. */ declaration: FunctionDeclaration, /** Parent element, usually a file or namespace. */ parent: Element ): FunctionPrototype | null { let name = declaration.name.text; let validDecorators = DecoratorFlags.Unsafe; if (declaration.is(CommonFlags.Ambient)) { validDecorators |= DecoratorFlags.External | DecoratorFlags.ExternalJs; } else { validDecorators |= DecoratorFlags.Inline; if (declaration.range.source.isLibrary || declaration.is(CommonFlags.Export)) { validDecorators |= DecoratorFlags.Lazy; } } if (!declaration.is(CommonFlags.Instance)) { if (parent.kind != ElementKind.ClassPrototype) { validDecorators |= DecoratorFlags.Global; } } if (declaration.range.source.isLibrary) { validDecorators |= DecoratorFlags.Builtin; } let element = new FunctionPrototype( name, parent, declaration, this.checkDecorators(declaration.decorators, validDecorators) ); if (element.hasDecorator(DecoratorFlags.Builtin) && !builtinFunctions.has(element.internalName)) { this.error( DiagnosticCode.Not_implemented_0, declaration.range, `Builtin '${element.internalName}'` ); } if (!parent.add(name, element)) return null; return element; } /** Initializes an interface. */ private initializeInterface( /** The declaration to initialize. */ declaration: InterfaceDeclaration, /** Parent element, usually a file or namespace. */ parent: Element, /** So far queued `extends` clauses. */ queuedExtends: ClassPrototype[], ): InterfacePrototype | null { let name = declaration.name.text; let element = new InterfacePrototype( name, parent, declaration, this.checkDecorators(declaration.decorators, DecoratorFlags.Global ) ); if (!parent.add(name, element)) return null; // remember interfaces that extend another interface if (declaration.extendsType) queuedExtends.push(element); let memberDeclarations = declaration.members; for (let i = 0, k = memberDeclarations.length; i < k; ++i) { let memberDeclaration = memberDeclarations[i]; switch (memberDeclaration.kind) { case NodeKind.FieldDeclaration: { this.initializeFieldAsProperty(memberDeclaration, element); break; } case NodeKind.MethodDeclaration: { let methodDeclaration = memberDeclaration; if (memberDeclaration.isAny(CommonFlags.Get | CommonFlags.Set)) { this.initializeProperty(methodDeclaration, element); } else { this.initializeMethod(methodDeclaration, element); } break; } default: assert(false); // interface member expected } } return element; } /** Initializes a field of an interface, as a property. */ private initializeFieldAsProperty( /** Field declaration. */ declaration: FieldDeclaration, /** Parent interface. */ parent: InterfacePrototype ): void { let typeNode = declaration.type; if (!typeNode) typeNode = Node.createOmittedType(declaration.name.range.atEnd); this.initializeProperty( Node.createMethodDeclaration( declaration.name, declaration.decorators, declaration.flags | CommonFlags.Get, null, Node.createFunctionType( [], typeNode, null, false, declaration.range ), null, declaration.range ), parent ); if (!declaration.is(CommonFlags.Readonly)) { this.initializeProperty( Node.createMethodDeclaration( declaration.name, declaration.decorators, declaration.flags | CommonFlags.Set, null, Node.createFunctionType( [ Node.createParameter( ParameterKind.Default, declaration.name, typeNode, null, declaration.name.range ) ], Node.createOmittedType(declaration.name.range.atEnd), null, false, declaration.range ), null, declaration.range ), parent ); } } /** Initializes a namespace. */ private initializeNamespace( /** The declaration to initialize. */ declaration: NamespaceDeclaration, /** Parent element, usually a file or another namespace. */ parent: Element, /** So far queued `extends` clauses. */ queuedExtends: ClassPrototype[], /** So far queued `implements` clauses. */ queuedImplements: ClassPrototype[] ): DeclaredElement | null { let name = declaration.name.text; let original = new Namespace( name, parent, declaration, this.checkDecorators(declaration.decorators, DecoratorFlags.Global) ); if (!parent.add(name, original)) return null; let element = assert(parent.getMember(name)); // possibly merged let members = declaration.members; for (let i = 0, k = members.length; i < k; ++i) { let member = members[i]; switch (member.kind) { case NodeKind.ClassDeclaration: { this.initializeClass(member, original, queuedExtends, queuedImplements); break; } case NodeKind.EnumDeclaration: { this.initializeEnum(member, original); break; } case NodeKind.FunctionDeclaration: { this.initializeFunction(member, original); break; } case NodeKind.InterfaceDeclaration: { this.initializeInterface(member, original, queuedExtends); break; } case NodeKind.NamespaceDeclaration: { this.initializeNamespace(member, original, queuedExtends, queuedImplements); break; } case NodeKind.TypeDeclaration: { this.initializeTypeDefinition(member, original); break; } case NodeKind.Variable: { this.initializeVariables(member, original); break; } default: assert(false); // namespace member expected } } if (original != element) copyMembers(original, element); // keep original parent return element; } /** Initializes a `type` definition. */ private initializeTypeDefinition( /** The declaration to initialize. */ declaration: TypeDeclaration, /** Parent element, usually a file or namespace. */ parent: Element ): void { let name = declaration.name.text; let element = new TypeDefinition( name, parent, declaration, this.checkDecorators(declaration.decorators, DecoratorFlags.None) ); parent.add(name, element); // reports } /** Initializes a variable statement. */ private initializeVariables( /** The statement to initialize. */ statement: VariableStatement, /** Parent element, usually a file or namespace. */ parent: Element ): void { let declarations = statement.declarations; for (let i = 0, k = declarations.length; i < k; ++i) { let declaration = declarations[i]; let name = declaration.name.text; let acceptedFlags = DecoratorFlags.Global | DecoratorFlags.Lazy; if (declaration.is(CommonFlags.Ambient)) { acceptedFlags |= DecoratorFlags.External; } if (declaration.is(CommonFlags.Const)) { acceptedFlags |= DecoratorFlags.Inline; } if (declaration.range.source.isLibrary) { acceptedFlags |= DecoratorFlags.Builtin; } let element = new Global( name, parent, this.checkDecorators(declaration.decorators, acceptedFlags), declaration ); if (element.hasDecorator(DecoratorFlags.Builtin) && !builtinVariables_onAccess.has(element.internalName)) { this.error( DiagnosticCode.Not_implemented_0, declaration.range, `Builtin '${element.internalName}'` ); } if (!parent.add(name, element)) continue; // reports } } /** Determines the element type of a built-in array. */ // determineBuiltinArrayType(target: Class): Type | null { // switch (target.internalName) { // case BuiltinSymbols.Int8Array: return Type.i8; // case BuiltinSymbols.Uint8ClampedArray: // case BuiltinSymbols.Uint8Array: return Type.u8; // case BuiltinSymbols.Int16Array: return Type.i16; // case BuiltinSymbols.Uint16Array: return Type.u16; // case BuiltinSymbols.Int32Array: return Type.i32; // case BuiltinSymbols.Uint32Array: return Type.u32; // case BuiltinSymbols.Int64Array: return Type.i64; // case BuiltinSymbols.Uint64Array: return Type.u64; // case BuiltinSymbols.Float32Array: return Type.f32; // case BuiltinSymbols.Float64Array: return Type.f64; // } // let current: Class | null = target; // let arrayPrototype = this.arrayPrototype; // do { // if (current.prototype == arrayPrototype) { // Array // let typeArguments = assert(current.typeArguments); // assert(typeArguments.length == 1); // return typeArguments[0]; // } // } while (current = current.base); // return null; // } } /** Indicates the specific kind of an {@link Element}. */ export const enum ElementKind { /** A {@link Global}. */ Global, /** A {@link Local}. */ Local, /** An {@link Enum}. */ Enum, /** An {@link EnumValue}. */ EnumValue, /** A {@link FunctionPrototype}. */ FunctionPrototype, /** A {@link Function}. */ Function, /** A {@link ClassPrototype}. */ ClassPrototype, /** A {@link Class}. */ Class, /** An {@link InterfacePrototype}. */ InterfacePrototype, /** An {@link Interface}. */ Interface, /** A {@link PropertyPrototype}. */ PropertyPrototype, /** A {@link Property}. */ Property, /** A {@link Namespace}. */ Namespace, /** A {@link File}. */ File, /** A {@link TypeDefinition}. */ TypeDefinition, /** An {@link IndexSignature}. */ IndexSignature } /** Indicates built-in decorators that are present. */ export enum DecoratorFlags { /** No flags set. */ None = 0, /** Is a program global. */ Global = 1 << 0, /** Is a binary operator overload. */ OperatorBinary = 1 << 1, /** Is a unary prefix operator overload. */ OperatorPrefix = 1 << 2, /** Is a unary postfix operator overload. */ OperatorPostfix = 1 << 3, /** Is an unmanaged class. */ Unmanaged = 1 << 4, /** Is a final class. */ Final = 1 << 5, /** Is always inlined. */ Inline = 1 << 6, /** Is using a different external name. */ External = 1 << 7, /** Has external JavaScript code. */ ExternalJs = 1 << 8, /** Is a builtin. */ Builtin = 1 << 9, /** Is compiled lazily. */ Lazy = 1 << 10, /** Is considered unsafe code. */ Unsafe = 1 << 11 } export namespace DecoratorFlags { /** Translates a decorator kind to the respective decorator flag. */ export function fromKind(kind: DecoratorKind): DecoratorFlags { switch (kind) { case DecoratorKind.Global: return DecoratorFlags.Global; case DecoratorKind.Operator: case DecoratorKind.OperatorBinary: return DecoratorFlags.OperatorBinary; case DecoratorKind.OperatorPrefix: return DecoratorFlags.OperatorPrefix; case DecoratorKind.OperatorPostfix: return DecoratorFlags.OperatorPostfix; case DecoratorKind.Unmanaged: return DecoratorFlags.Unmanaged; case DecoratorKind.Final: return DecoratorFlags.Final; case DecoratorKind.Inline: return DecoratorFlags.Inline; case DecoratorKind.External: return DecoratorFlags.External; case DecoratorKind.ExternalJs: return DecoratorFlags.ExternalJs; case DecoratorKind.Builtin: return DecoratorFlags.Builtin; case DecoratorKind.Lazy: return DecoratorFlags.Lazy; case DecoratorKind.Unsafe: return DecoratorFlags.Unsafe; default: return DecoratorFlags.None; } } } /** Base class of all program elements. */ export abstract class Element { /** Parent element. */ parent!: Element; /** Common flags indicating specific traits. */ flags: CommonFlags = CommonFlags.None; /** Decorator flags indicating annotated traits. */ decoratorFlags: DecoratorFlags = DecoratorFlags.None; /** Member elements. */ members: Map | null = null; /** Shadowing type in type space, if any. */ shadowType: TypeDefinition | null = null; /** Constructs a new program element. */ protected constructor( /** Specific element kind. */ public kind: ElementKind, /** Simple name. */ public name: string, /** Internal name referring to this element. */ public internalName: string, /** Containing {@link Program}. */ public program: Program, /** Parent element. */ parent: Element | null ) { this.program = program; this.name = name; this.internalName = internalName; if (parent) { this.parent = parent; } else { assert(this.kind == ElementKind.File); this.parent = this; // special case to keep this.parent non-nullable } } /** Gets the enclosing file. */ get file(): File { let current: Element = this; do { current = current.parent; if (current.kind == ElementKind.File) return current; } while (true); } /** Tests if this element has a specific flag or flags. */ is(flag: CommonFlags): bool { return (this.flags & flag) == flag; } /** Tests if this element has any of the specified flags. */ isAny(flags: CommonFlags): bool { return (this.flags & flags) != 0; } /** Sets a specific flag or flags. */ set(flag: CommonFlags): void { this.flags |= flag; } /** Unsets the specific flag or flags. */ unset(flag: CommonFlags): void {this.flags &= ~flag; } /** Tests if this element has a specific decorator flag or flags. */ hasDecorator(flag: DecoratorFlags): bool { return (this.decoratorFlags & flag) == flag; } /** Tests if this element has any of the specified decorator flags. */ hasAnyDecorator(flags: DecoratorFlags): bool { return (this.decoratorFlags & flags) != 0; } /** Get the member with the specified name, if any. */ getMember(name: string): DeclaredElement | null { let members = this.members; if (members && members.has(name)) return assert(members.get(name)); return null; } /** Looks up the element with the specified name relative to this element. */ lookup(name: string, isType: bool = false): Element | null { return this.parent.lookup(name, isType); } /** Adds an element as a member of this one. Reports and returns `false` if a duplicate. */ add(name: string, element: DeclaredElement, localIdentifierIfImport: IdentifierExpression | null = null): bool { let originalDeclaration = element.declaration; let members = this.members; if (!members) this.members = members = new Map(); else if (members.has(name)) { let existing = assert(members.get(name)); if (existing.parent != this) { // override non-own element } else { let merged = tryMerge(existing, element); if (merged) { element = merged; // use merged element } else { let reportedIdentifier = localIdentifierIfImport ? localIdentifierIfImport : element.identifierNode; if (isDeclaredElement(existing.kind)) { this.program.errorRelated( DiagnosticCode.Duplicate_identifier_0, reportedIdentifier.range, (existing).identifierNode.range, reportedIdentifier.text ); } else { this.program.error( DiagnosticCode.Duplicate_identifier_0, reportedIdentifier.range, reportedIdentifier.text ); } return false; } } } members.set(name, element); let program = this.program; if (element.kind != ElementKind.FunctionPrototype || !(element).isBound) { // prefer unbound prototypes in global lookup maps program.elementsByName.set(element.internalName, element); program.elementsByDeclaration.set(originalDeclaration, element); } return true; } /** Checks if this element is public, explicitly or implicitly. */ get isPublic(): bool { return !this.isAny(CommonFlags.Private | CommonFlags.Protected); } /** Checks if this element is implicitly public, i.e. not explicitly declared to be. */ get isImplicitlyPublic(): bool { return this.isPublic && !this.is(CommonFlags.Public); } /** Checks if the visibility of this element equals the specified. */ visibilityEquals(other: Element): bool { if (this.isPublic == other.isPublic) return true; const vis = CommonFlags.Private | CommonFlags.Protected; return (this.flags & vis) == (other.flags & vis); } /** Tests if this element is bound to a class. */ get isBound(): bool { let parent = this.parent; switch (parent.kind) { case ElementKind.Class: case ElementKind.Interface: return true; } return false; } /** Gets the class or interface this element is bound to, if any. */ getBoundClassOrInterface(): Class | null { let parent = this.parent; switch (parent.kind) { case ElementKind.Class: case ElementKind.Interface: return parent; } return null; } /** Returns a string representation of this element. */ toString(): string { return `${this.internalName}, kind=${this.kind}`; } } // Kinds of all declared elements let declaredElements = new Set(); /** Tests if the specified element kind indicates a declared element. */ export function isDeclaredElement(kind: ElementKind): bool { return declaredElements.has(kind); } /** Base class of elements with an associated declaration statement. */ export abstract class DeclaredElement extends Element { /** Constructs a new declared program element. */ protected constructor( /** Specific element kind. */ kind: ElementKind, /** Simple name. */ name: string, /** Internal name referring to this element. */ internalName: string, /** Containing {@link Program}. */ program: Program, /** Parent element. */ parent: Element | null, /** Declaration reference. */ public declaration: DeclarationStatement ) { super(kind, name, internalName, program, parent); declaredElements.add(kind); // It is necessary to have access to identifiers of all members and exports // for reporting purposes and this is the lowest common denominator. Comes // at the expense of not having more specific type information in derived // classes, though. Instead, derived classes implement getters for other // important AST nodes directly through manual casting, allowing the resolver // etc. to not worry about actual declarations. this.declaration = declaration; this.flags = declaration.flags; // inherit } /** Tests if this element is a library element. */ get isDeclaredInLibrary(): bool { return this.declaration.range.source.isLibrary; } /** Gets the associated identifier node. */ get identifierNode(): IdentifierExpression { return this.declaration.name; } /** Gets the signature node, if applicable, along the identifier node. */ get identifierAndSignatureRange(): Range { let declaration = this.declaration; let identifierNode = declaration.name; if (declaration.kind == NodeKind.FunctionDeclaration || declaration.kind == NodeKind.MethodDeclaration) { let signatureNode = (declaration).signature; if (identifierNode.range.source == signatureNode.range.source) { return Range.join(identifierNode.range, signatureNode.range); } } return identifierNode.range; } /** Gets the assiciated decorator nodes. */ get decoratorNodes(): DecoratorNode[] | null { return this.declaration.decorators; } } // Kinds of all typed elements let typedElements = new Set(); /** Checks if the specified element kind indicates a typed element. */ export function isTypedElement(kind: ElementKind): bool { return typedElements.has(kind); } /** Base class of elements that can be resolved to a concrete type. */ export abstract class TypedElement extends DeclaredElement { /** Resolved type. Set once `is(RESOLVED)`, otherwise void. */ type: Type = Type.void; constructor( /** Specific element kind. */ kind: ElementKind, /** Simple name. */ name: string, /** Internal name referring to this element. */ internalName: string, /** Containing {@link Program}. */ program: Program, /** Parent element. */ parent: Element | null, /** Declaration reference. */ declaration: DeclarationStatement ) { super(kind, name, internalName, program, parent, declaration); typedElements.add(kind); } /** Sets the resolved type of this element. */ setType(type: Type): void { assert(!this.is(CommonFlags.Resolved)); this.type = type; this.set(CommonFlags.Resolved); } } /** A file representing the implicit top-level namespace of a source. */ export class File extends Element { /** File exports. */ exports: Map | null = null; /** File re-exports. */ exportsStar: File[] | null = null; /** Top-level start function of this file. */ startFunction!: Function; /** Array of `import * as X` alias namespaces of this file. */ aliasNamespaces: Array = new Array(); /** Constructs a new file. */ constructor( /** Program this file belongs to. */ program: Program, /** Source of this file. */ public source: Source ) { super( ElementKind.File, source.normalizedPath, source.internalPath, program, null // special case for files ); this.source = source; assert(!program.filesByName.has(this.internalName)); program.filesByName.set(this.internalName, this); let startFunction = this.program.makeNativeFunction( `start:${this.internalName}`, Signature.create(program, [], Type.void), this ); startFunction.internalName = startFunction.name; this.startFunction = startFunction; } /* @override */ add(name: string, element: DeclaredElement, localIdentifierIfImport: IdentifierExpression | null = null): bool { if (element.hasDecorator(DecoratorFlags.Global)) { element = this.program.ensureGlobal(name, element); // possibly merged globally } if (!super.add(name, element, localIdentifierIfImport)) return false; element = assert(this.getMember(name)); // possibly merged locally if (element.is(CommonFlags.Export) && !localIdentifierIfImport) { this.ensureExport( element.name, element ); } return true; } /* @override */ getMember(name: string): DeclaredElement | null { let element = super.getMember(name); if (element) return element; let exportsStar = this.exportsStar; if (exportsStar) { for (let i = 0, k = exportsStar.length; i < k; ++i) { if (element = exportsStar[i].getMember(name)) return element; } } return null; } /* @override */ lookup(name: string, isType: bool = false): Element | null { let element = this.getMember(name); if (element) return element; return this.program.lookup(name); // has no meaningful parent } /** Ensures that an element is an export of this file. */ ensureExport(name: string, element: DeclaredElement): void { let exports = this.exports; if (!exports) this.exports = exports = new Map(); exports.set(name, element); if (this.source.sourceKind == SourceKind.LibraryEntry) this.program.ensureGlobal(name, element); // Also, add to the namespaces that capture our exports for(let i = 0; i < this.aliasNamespaces.length; i++) { let ns = this.aliasNamespaces[i]; ns.add(name, element); } } /** Ensures that another file is a re-export of this file. */ ensureExportStar(file: File): void { let exportsStar = this.exportsStar; if (!exportsStar) this.exportsStar = exportsStar = []; else if (exportsStar.includes(file)) return; exportsStar.push(file); } /** Looks up the export of the specified name. */ lookupExport(name: string): DeclaredElement | null { let exports = this.exports; if (exports && exports.has(name)) return assert(exports.get(name)); let exportsStar = this.exportsStar; if (exportsStar) { for (let i = 0, k = exportsStar.length; i < k; ++i) { let element = exportsStar[i].lookupExport(name); if (element) return element; } } return null; } /** Creates an imported namespace from this file. */ asAliasNamespace( name: string, parent: Element, localIdentifier: IdentifierExpression ): Namespace { let declaration = this.program.makeNativeNamespaceDeclaration(name); declaration.name = localIdentifier; let ns = new Namespace(name, parent, declaration); ns.set(CommonFlags.Scoped); this.copyExportsToNamespace(ns); // NOTE: Some exports are still queued, and can't yet be added here, // so we remember all the alias namespaces and add to them as well // when adding an element to the file. this.aliasNamespaces.push(ns); return ns; } /** Recursively copies the exports of this file to the specified namespace. */ private copyExportsToNamespace(ns: Namespace): void { let exports = this.exports; if (exports) { // TODO: for (let [memberName, member] of exports) { for (let _keys = Map_keys(exports), i = 0, k = _keys.length; i < k; ++i) { let memberName = unchecked(_keys[i]); let member = assert(exports.get(memberName)); ns.add(memberName, member); } } let exportsStar = this.exportsStar; if (exportsStar) { for (let i = 0, k = exportsStar.length; i < k; ++i) { exportsStar[i].copyExportsToNamespace(ns); } } } } /** A type definition. */ export class TypeDefinition extends TypedElement { /** Constructs a new type definition. */ constructor( /** Simple name. */ name: string, /** Parent element, usually a file or namespace. */ parent: Element, /** Declaration reference. */ declaration: TypeDeclaration, /** Pre-checked flags indicating built-in decorators. */ decoratorFlags: DecoratorFlags = DecoratorFlags.None ) { super( ElementKind.TypeDefinition, name, mangleInternalName(name, parent, false), parent.program, parent, declaration ); this.decoratorFlags = decoratorFlags; } /** Gets the associated type parameter nodes. */ get typeParameterNodes(): TypeParameterNode[] | null { return (this.declaration).typeParameters; } /** Gets the associated type node. */ get typeNode(): TypeNode { return (this.declaration).type; } } /** A namespace that differs from a file in being user-declared with a name. */ export class Namespace extends DeclaredElement { /** Constructs a new namespace. */ constructor( /** Simple name. */ name: string, /** Parent element, usually a file or another namespace. */ parent: Element, /** Declaration reference. */ declaration: NamespaceDeclaration, /** Pre-checked flags indicating built-in decorators. */ decoratorFlags: DecoratorFlags = DecoratorFlags.None ) { super( ElementKind.Namespace, name, mangleInternalName(name, parent, false), parent.program, parent, declaration ); this.decoratorFlags = decoratorFlags; } /* @override */ lookup(name: string, isType: bool = false): Element | null { let member = this.getMember(name); if (member) return member; return super.lookup(name, isType); } } /** An enum. */ export class Enum extends TypedElement { /** Constructs a new enum. */ constructor( /** Simple name. */ name: string, /** Parent element, usually a file or namespace. */ parent: Element, /** Declaration reference. */ declaration: EnumDeclaration, /** Pre-checked flags indicating built-in decorators. */ decoratorFlags: DecoratorFlags = DecoratorFlags.None ) { super( ElementKind.Enum, name, mangleInternalName(name, parent, false), parent.program, parent, declaration ); this.decoratorFlags = decoratorFlags; this.setType(Type.i32); } /* @override */ lookup(name: string, isType: bool = false): Element | null { let member = this.getMember(name); if (member) return member; return super.lookup(name, isType); } } /** Indicates the kind of an inlined constant value. */ export const enum ConstantValueKind { /** No constant value. */ None, /** Constant integer value. */ Integer, /** Constant float value. */ Float } /** Base class of all variable-like program elements. */ export abstract class VariableLikeElement extends TypedElement { /** Constant value kind. */ constantValueKind: ConstantValueKind = ConstantValueKind.None; /** Constant integer value, if applicable. */ constantIntegerValue: i64 = i64_zero; /** Constant float value, if applicable. */ constantFloatValue: f64 = 0; /** Constructs a new variable-like element. */ protected constructor( /** Specific element kind. */ kind: ElementKind, /** Simple name. */ name: string, /** Parent element, usually a file, namespace or class. */ parent: Element, /** Declaration reference. Creates a native declaration if omitted. */ declaration: VariableLikeDeclarationStatement = parent.program.makeNativeVariableDeclaration(name) ) { super( kind, name, mangleInternalName(name, parent, declaration.is(CommonFlags.Instance)), parent.program, parent, declaration ); this.flags = declaration.flags; } /** Gets the associated type node.s */ get typeNode(): TypeNode | null { return (this.declaration).type; } /** Gets the associated initializer node. */ get initializerNode(): Expression | null { return (this.declaration).initializer; } /** Applies a constant integer value to this element. */ setConstantIntegerValue(value: i64, type: Type): void { assert(type.isIntegerInclReference); this.type = type; this.constantValueKind = ConstantValueKind.Integer; this.constantIntegerValue = value; this.set(CommonFlags.Const | CommonFlags.Inlined | CommonFlags.Resolved); } /** Applies a constant float value to this element. */ setConstantFloatValue(value: f64, type: Type): void { assert(type.isFloatValue); this.type = type; this.constantValueKind = ConstantValueKind.Float; this.constantFloatValue = value; this.set(CommonFlags.Const | CommonFlags.Inlined | CommonFlags.Resolved); } } /** An enum value. */ export class EnumValue extends VariableLikeElement { /** Constructs a new enum value. */ constructor( /** Simple name. */ name: string, /** Parent enum. */ parent: Enum, /** Declaration reference. */ declaration: EnumValueDeclaration, /** Pre-checked flags indicating built-in decorators. */ decoratorFlags: DecoratorFlags = DecoratorFlags.None ) { super( ElementKind.EnumValue, name, parent, declaration ); this.decoratorFlags = decoratorFlags; this.setType(Type.i32); } /** Whether this enum value is immutable. */ isImmutable: bool = false; /** Gets the associated value node. */ get valueNode(): Expression | null { return (this.declaration).initializer; } } /** A global variable. */ export class Global extends VariableLikeElement { /** Constructs a new global variable. */ constructor( /** Simple name. */ name: string, /** Parent element, usually a file, namespace or static class. */ parent: Element, /** Pre-checked flags indicating built-in decorators. */ decoratorFlags: DecoratorFlags, /** Declaration reference. Creates a native declaration if omitted. */ declaration: VariableLikeDeclarationStatement = parent.program.makeNativeVariableDeclaration(name) ) { super( ElementKind.Global, name, parent, declaration ); this.decoratorFlags = decoratorFlags; } } /** A function parameter. */ export class Parameter { /** Constructs a new function parameter. */ constructor( /** Parameter name. */ public name: string, /** Parameter type. */ public type: Type, /** Parameter initializer, if present. */ public initializer: Expression | null = null ) {} } /** A local variable. */ export class Local extends VariableLikeElement { /** Original name of the (temporary) local. */ private originalName: string; /** Constructs a new local variable. */ constructor( /** Simple name. */ name: string, /** Zero-based index within the enclosing function. `-1` indicates a dummy local. */ public index: i32, /** Resolved type. */ type: Type, /** Parent function. */ parent: Function, /** Declaration reference. */ declaration: VariableLikeDeclarationStatement = parent.program.makeNativeVariableDeclaration(name) ) { super( ElementKind.Local, name, parent, declaration ); this.originalName = name; this.index = index; assert(type != Type.void); this.setType(type); } } /** A yet unresolved function prototype. */ export class FunctionPrototype extends DeclaredElement { /** Operator kind, if an overload. */ operatorKind: OperatorKind = OperatorKind.Invalid; /** Already resolved instances. */ instances: Map | null = null; /** Methods overriding this one, if any. These are unbound. */ unboundOverrides: Set | null = null; /** Clones of this prototype that are bound to specific classes. */ private boundPrototypes: Map | null = null; /** Constructs a new function prototype. */ constructor( /** Simple name */ name: string, /** Parent element, usually a file, namespace or class (if a method). */ parent: Element, /** Declaration reference. */ declaration: FunctionDeclaration, /** Pre-checked flags indicating built-in decorators. */ decoratorFlags: DecoratorFlags = DecoratorFlags.None ) { super( ElementKind.FunctionPrototype, name, mangleInternalName(name, parent, declaration.is(CommonFlags.Instance)), parent.program, parent, declaration ); this.decoratorFlags = decoratorFlags; } /** Gets the associated type parameter nodes. */ get typeParameterNodes(): TypeParameterNode[] | null { return (this.declaration).typeParameters; } /** Gets the associated function type node. */ get functionTypeNode(): FunctionTypeNode { return (this.declaration).signature; } /** Gets the associated body node. */ get bodyNode(): Statement | null { return (this.declaration).body; } /** Gets the arrow function kind. */ get arrowKind(): ArrowKind { return (this.declaration).arrowKind; } /** Creates a clone of this prototype that is bound to a concrete class instead. */ toBound(classInstance: Class): FunctionPrototype { assert(this.is(CommonFlags.Instance)); assert(!this.isBound); let boundPrototypes = this.boundPrototypes; if (!boundPrototypes) this.boundPrototypes = boundPrototypes = new Map(); else if (boundPrototypes.has(classInstance)) return assert(boundPrototypes.get(classInstance)); let declaration = this.declaration; assert(declaration.kind == NodeKind.MethodDeclaration); let bound = new FunctionPrototype( this.name, classInstance, // now bound declaration, this.decoratorFlags ); bound.flags = this.flags; bound.operatorKind = this.operatorKind; bound.unboundOverrides = this.unboundOverrides; // NOTE: this.instances holds instances per bound class / unbound boundPrototypes.set(classInstance, bound); return bound; } /** Gets the resolved instance for the specified instance key, if already resolved. */ getResolvedInstance(instanceKey: string): Function | null { let instances = this.instances; if (instances && instances.has(instanceKey)) return assert(instances.get(instanceKey)); return null; } /** Sets the resolved instance for the specified instance key. */ setResolvedInstance(instanceKey: string, instance: Function): void { let instances = this.instances; if (!instances) this.instances = instances = new Map(); else assert(!instances.has(instanceKey)); instances.set(instanceKey, instance); } } /** A resolved function. */ export class Function extends TypedElement { /** Function prototype. */ prototype: FunctionPrototype; /** Function signature. */ signature: Signature; /** Array of locals by index. */ localsByIndex: Local[] = []; /** Concrete type arguments. */ typeArguments: Type[] | null; /** Contextual type arguments. */ contextualTypeArguments: Map | null; /** Default control flow. */ flow!: Flow; /** Remembered debug locations. */ debugLocations: Range[] = []; /** Function reference, if compiled. */ ref: FunctionRef = 0; /** Varargs stub for calling with omitted arguments. */ varargsStub: Function | null = null; /** Stub for calling overrides. */ overrideStub: Function | null = null; /** Runtime memory segment, if created. */ memorySegment: MemorySegment | null = null; /** Original function, if a stub. Otherwise `this`. */ original!: Function; /** Counting id of inline operations involving this function. */ nextInlineId: i32 = 0; /** Counting id of anonymous inner functions. */ nextAnonymousId: i32 = 0; /** Constructs a new concrete function. */ constructor( /** Name incl. type parameters, i.e. `foo`. */ nameInclTypeParameters: string, /** Respective function prototype. */ prototype: FunctionPrototype, /** Concrete type arguments. */ typeArguments: Type[] | null, /** Concrete signature. */ signature: Signature, // pre-resolved /** Contextual type arguments inherited from its parent class, if any. */ contextualTypeArguments: Map | null = null ) { super( ElementKind.Function, nameInclTypeParameters, mangleInternalName(nameInclTypeParameters, prototype.parent, prototype.is(CommonFlags.Instance)), prototype.program, prototype.parent, prototype.declaration ); this.prototype = prototype; this.typeArguments = typeArguments; this.signature = signature; this.flags = prototype.flags | CommonFlags.Resolved; this.decoratorFlags = prototype.decoratorFlags; this.contextualTypeArguments = contextualTypeArguments; this.original = this; let program = prototype.program; this.type = signature.type; let flow = Flow.createDefault(this); this.flow = flow; if (!prototype.is(CommonFlags.Ambient)) { let localIndex = 0; let thisType = signature.thisType; if (thisType) { let local = new Local( CommonNames.this_, localIndex++, thisType, this ); let scopedLocals = this.flow.scopedLocals; if (!scopedLocals) this.flow.scopedLocals = scopedLocals = new Map(); scopedLocals.set(CommonNames.this_, local); this.localsByIndex[local.index] = local; flow.setLocalFlag(local.index, LocalFlags.Initialized); } let parameterTypes = signature.parameterTypes; for (let i = 0, k = parameterTypes.length; i < k; ++i) { let parameterType = parameterTypes[i]; let parameterName = this.getParameterName(i); let local = new Local( parameterName, localIndex++, parameterType, this ); let scopedLocals = this.flow.scopedLocals; if (!scopedLocals) this.flow.scopedLocals = scopedLocals = new Map(); scopedLocals.set(parameterName, local); this.localsByIndex[local.index] = local; flow.setLocalFlag(local.index, LocalFlags.Initialized); } } registerConcreteElement(program, this); } /** Gets the types of additional locals that are not parameters. */ getNonParameterLocalTypes(): Type[] { let localsByIndex = this.localsByIndex; let signature = this.signature; let numTotal = localsByIndex.length; let numFixed = signature.parameterTypes.length; if (signature.thisType) ++numFixed; let numAdditional = numTotal - numFixed; let types = new Array(numAdditional); for (let i = 0; i < numAdditional; ++i) { types[i] = localsByIndex[numFixed + i].type; } return types; } /** Gets the name of the parameter at the specified index. */ getParameterName(index: i32): string { let parameters = (this.declaration).signature.parameters; return parameters.length > index ? parameters[index].name.text : getDefaultParameterName(index); } /** Creates a stub for use with this function, i.e. for varargs or override calls. */ newStub(postfix: string, requiredParameters: i32 = this.signature.requiredParameters): Function { let stub = new Function( this.original.name + STUB_DELIMITER + postfix, this.prototype, this.typeArguments, this.signature.clone(requiredParameters), this.contextualTypeArguments ); stub.original = this.original; stub.set(this.flags & ~CommonFlags.Compiled | CommonFlags.Stub); return stub; } /** Adds a local of the specified type, with an optional name. */ addLocal(type: Type, name: string | null = null, declaration: VariableDeclaration | null = null): Local { // if it has a name, check previously as this method will throw otherwise let localsByIndex = this.localsByIndex; let localIndex = localsByIndex.length; let localName = name != null ? name : localIndex.toString(); if (!declaration) declaration = this.program.makeNativeVariableDeclaration(localName); let local = new Local(localName, localIndex, type, this, declaration); if (name) { let defaultFlow = this.flow; let scopedLocals = defaultFlow.scopedLocals; if (!scopedLocals) defaultFlow.scopedLocals = scopedLocals = new Map(); if (scopedLocals.has(name)) throw new Error("duplicate local name"); scopedLocals.set(name, local); } localsByIndex[localIndex] = local; return local; } /* @override */ lookup(name: string, isType: bool = false): Element | null { if (!isType) { let scopedLocals = this.flow.scopedLocals; if (scopedLocals && scopedLocals.has(name)) { return assert(scopedLocals.get(name)); } } return super.lookup(name, isType); } // used by flows to keep track of break labels nextBreakId: i32 = 0; breakStack: i32[] | null = null; /** Finalizes the function once compiled, releasing no longer needed resources. */ finalize(module: Module, ref: FunctionRef): void { this.ref = ref; let breakStack = this.breakStack; assert(!breakStack || !breakStack.length); // should be empty this.breakStack = null; this.addDebugInfo(module, ref); } addDebugInfo(module: Module, ref: FunctionRef): void { if (this.program.options.sourceMap) { let debugLocations = this.debugLocations; for (let i = 0, k = debugLocations.length; i < k; ++i) { let range = debugLocations[i]; let source = range.source; module.setDebugLocation( ref, range.debugInfoRef, source.debugInfoIndex, source.lineAt(range.start), source.columnAt() - 1 // source maps are 0-based ); } } if (this.program.options.debugInfo) { let localNameMap = new Set(); let localsByIndex = this.localsByIndex; for (let i = 0, k = localsByIndex.length; i < k; i++) { let localName = localsByIndex[i].name; if (localNameMap.has(localName)) { localName = `${localName}|${i}`; } localNameMap.add(localName); module.setLocalName(ref, i, localName); } } } } /** A property comprised of a getter and a setter function. */ export class PropertyPrototype extends DeclaredElement { /** Field declaration, if a field. */ fieldDeclaration: FieldDeclaration | null = null; /** Getter prototype. */ getterPrototype: FunctionPrototype | null = null; /** Setter prototype. */ setterPrototype: FunctionPrototype | null = null; /** Property instance, if resolved. */ instance: Property | null = null; /** Clones of this prototype that are bound to specific classes. */ private boundPrototypes: Map | null = null; /** Creates a property prototype representing a field. */ static forField( /** Simple name. */ name: string, /** Parent element. Always a class prototype. */ parent: ClassPrototype, /** Declaration of the field. */ fieldDeclaration: FieldDeclaration, /** Pre-checked flags indicating built-in decorators. */ decoratorFlags: DecoratorFlags, ): PropertyPrototype { // A field is a property with an attached memory offset. Unlike normal // properties, accessors for fields are not explicitly declared, so we // declare them implicitly here and compile them as built-ins when used. // As a result, explicit and implicit accessors can override each other, // which is useful when implementing interfaces declaring "fields". Such // fields are satisfied by either a field or a normal property, so the // override stub at the interface needs to handle both interchangeably. let nativeRange = Source.native.range; let typeNode = fieldDeclaration.type; if (!typeNode) typeNode = Node.createOmittedType(fieldDeclaration.name.range.atEnd); let getterDeclaration = new MethodDeclaration( // get name(): type fieldDeclaration.name, fieldDeclaration.decorators, fieldDeclaration.flags | CommonFlags.Instance | CommonFlags.Get, null, new FunctionTypeNode([], typeNode, null, false, nativeRange), null, nativeRange ); let setterDeclaration = new MethodDeclaration( // set name(name: type) fieldDeclaration.name, fieldDeclaration.decorators, fieldDeclaration.flags | CommonFlags.Instance | CommonFlags.Set, null, new FunctionTypeNode( [ new ParameterNode( ParameterKind.Default, fieldDeclaration.name, typeNode, null, nativeRange ) ], new NamedTypeNode( new TypeName( new IdentifierExpression("", false, nativeRange), null, nativeRange ), null, false, nativeRange ), null, false, nativeRange ), null, nativeRange ); let prototype = new PropertyPrototype(name, parent, getterDeclaration); prototype.fieldDeclaration = fieldDeclaration; prototype.decoratorFlags = decoratorFlags; prototype.getterPrototype = new FunctionPrototype(GETTER_PREFIX + name, parent, getterDeclaration, decoratorFlags); prototype.setterPrototype = new FunctionPrototype(SETTER_PREFIX + name, parent, setterDeclaration, decoratorFlags); return prototype; } /** Constructs a new property prototype. */ constructor( /** Simple name. */ name: string, /** Parent element. Either a class prototype or instance. */ parent: Element, /** Declaration of the getter or setter introducing the property. */ firstDeclaration: FunctionDeclaration ) { super( ElementKind.PropertyPrototype, name, mangleInternalName(name, parent, firstDeclaration.is(CommonFlags.Instance)), parent.program, parent, firstDeclaration ); this.flags &= ~(CommonFlags.Get | CommonFlags.Set); } /** Tests if this property prototype represents a field. */ get isField(): bool { return this.fieldDeclaration != null; } /** Gets the associated type node. */ get typeNode(): TypeNode | null { let fieldDeclaration = this.fieldDeclaration; if (fieldDeclaration) return fieldDeclaration.type; let getterPrototype = this.getterPrototype; if (getterPrototype) { let getterDeclaration = getterPrototype.declaration; if (getterDeclaration.kind == NodeKind.FunctionDeclaration) { return (getterDeclaration).signature.returnType; } } let setterPrototype = this.setterPrototype; if (setterPrototype) { let setterDeclaration = setterPrototype.declaration; if (setterDeclaration.kind == NodeKind.FunctionDeclaration) { let setterParameters = (setterDeclaration).signature.parameters; if (setterParameters.length) return setterParameters[0].type; } } return null; } /** Gets the associated initializer node. */ get initializerNode(): Expression | null { let fieldDeclaration = this.fieldDeclaration; if (fieldDeclaration) return fieldDeclaration.initializer; return null; } /** Gets the associated parameter index. Set if declared as a constructor parameter, otherwise `-1`. */ get parameterIndex(): i32 { let fieldDeclaration = this.fieldDeclaration; if (fieldDeclaration) return fieldDeclaration.parameterIndex; return -1; } /** Gets the respective `this` type. */ get thisType(): Type { let parent = this.parent; assert(parent.kind == ElementKind.Class); return (parent).type; } /** Creates a clone of this property prototype that is bound to a concrete class. */ toBound(classInstance: Class): PropertyPrototype { assert(this.is(CommonFlags.Instance)); assert(!this.isBound); let boundPrototypes = this.boundPrototypes; if (!boundPrototypes) this.boundPrototypes = boundPrototypes = new Map(); else if (boundPrototypes.has(classInstance)) return assert(boundPrototypes.get(classInstance)); let firstDeclaration = this.declaration; assert(firstDeclaration.kind == NodeKind.MethodDeclaration); let bound = new PropertyPrototype( this.name, classInstance, // now bound firstDeclaration ); bound.flags = this.flags; bound.fieldDeclaration = this.fieldDeclaration; let getterPrototype = this.getterPrototype; if (getterPrototype) { bound.getterPrototype = getterPrototype.toBound(classInstance); } let setterPrototype = this.setterPrototype; if (setterPrototype) { bound.setterPrototype = setterPrototype.toBound(classInstance); } boundPrototypes.set(classInstance, bound); return bound; } } /** A resolved property. */ export class Property extends VariableLikeElement { /** Prototype reference. */ prototype: PropertyPrototype; /** Getter instance. */ getterInstance: Function | null = null; /** Setter instance. */ setterInstance: Function | null = null; /** Field memory offset, if a (layed out) instance field. */ memoryOffset: i32 = -1; /** Constructs a new property prototype. */ constructor( /** Respective property prototype. */ prototype: PropertyPrototype, /** Parent element, usually a static class prototype or class instance. */ parent: Element ) { super( ElementKind.Property, prototype.name, parent, prototype.isField ? assert(prototype.fieldDeclaration) : Node.createVariableDeclaration( prototype.identifierNode, null, prototype.flags & CommonFlags.Instance, null, null, prototype.identifierNode.range ) ); this.prototype = prototype; this.flags = prototype.flags; this.decoratorFlags = prototype.decoratorFlags; if (this.is(CommonFlags.Instance)) { registerConcreteElement(this.program, this); } } /** Tests if this property represents a field. */ get isField(): bool { return this.prototype.isField; } } /** A resolved index signature. */ export class IndexSignature extends TypedElement { /** Constructs a new index prototype. */ constructor( /** Parent class. */ parent: Class ) { super( ElementKind.IndexSignature, "[]", parent.internalName + "[]", parent.program, parent, parent.program.makeNativeVariableDeclaration("[]") // is fine ); } /** Obtains the getter instance. */ getGetterInstance(isUnchecked: bool): Function | null { return (this.parent).lookupOverload(OperatorKind.IndexedGet, isUnchecked); } /** Obtains the setter instance. */ getSetterInstance(isUnchecked: bool): Function | null { return (this.parent).lookupOverload(OperatorKind.IndexedSet, isUnchecked); } } /** A yet unresolved class prototype. */ export class ClassPrototype extends DeclaredElement { /** Instance member prototypes. */ instanceMembers: Map | null = null; /** Base class prototype, if applicable. */ basePrototype: ClassPrototype | null = null; /** Interface prototypes, if applicable. */ interfacePrototypes: InterfacePrototype[] | null = null; /** Constructor prototype. */ constructorPrototype: FunctionPrototype | null = null; /** Operator overload prototypes. */ operatorOverloadPrototypes: Map = new Map(); /** Already resolved instances. */ instances: Map | null = null; /** Classes extending this class. */ extenders: Set = new Set(); /** Whether this class implicitly extends `Object`. */ implicitlyExtendsObject: bool = false; constructor( /** Simple name. */ name: string, /** Parent element, usually a file or namespace. */ parent: Element, /** Declaration reference. */ declaration: ClassDeclaration, /** Pre-checked flags indicating built-in decorators. */ decoratorFlags: DecoratorFlags = DecoratorFlags.None, _isInterface: bool = false // FIXME ) { super( _isInterface ? ElementKind.InterfacePrototype : ElementKind.ClassPrototype, name, mangleInternalName(name, parent, declaration.is(CommonFlags.Instance)), parent.program, parent, declaration ); this.decoratorFlags = decoratorFlags; } /** Gets the associated type parameter nodes. */ get typeParameterNodes(): TypeParameterNode[] | null { return (this.declaration).typeParameters; } /** Gets the associated extends node. */ get extendsNode(): NamedTypeNode | null { return (this.declaration).extendsType; } /** Gets the associated implements nodes. */ get implementsNodes(): NamedTypeNode[] | null { return (this.declaration).implementsTypes; } /** Tests if this prototype is of a builtin array type (Array/TypedArray). */ get isBuiltinArray(): bool { let arrayBufferViewInstance = this.program.arrayBufferViewInstance; return arrayBufferViewInstance && this.extends(arrayBufferViewInstance.prototype); } /** Tests if this prototype extends the specified. */ extends(basePtototype: ClassPrototype | null): bool { let current: ClassPrototype | null = this; let seen = new Set(); do { // cannot directly or indirectly extend itself if (seen.has(current)) break; seen.add(current); if (current == basePtototype) return true; current = current.basePrototype; } while (current); return false; } /** Adds an element as an instance member of this one. Returns the previous element if a duplicate. */ addInstance(name: string, element: DeclaredElement): bool { let originalDeclaration = element.declaration; let instanceMembers = this.instanceMembers; if (!instanceMembers) this.instanceMembers = instanceMembers = new Map(); else if (instanceMembers.has(name)) { let existing = assert(instanceMembers.get(name)); let merged = tryMerge(existing, element); if (!merged) { if (isDeclaredElement(existing.kind)) { this.program.errorRelated( DiagnosticCode.Duplicate_identifier_0, element.identifierNode.range, (existing).declaration.name.range, element.identifierNode.text ); } else { this.program.error( DiagnosticCode.Duplicate_identifier_0, element.identifierNode.range, element.identifierNode.text ); } return false; } element = merged; } instanceMembers.set(name, element); if (element.is(CommonFlags.Export) && this.is(CommonFlags.ModuleExport)) { element.set(CommonFlags.ModuleExport); // propagate } this.program.elementsByDeclaration.set(originalDeclaration, element); return true; } /** Gets the resolved instance for the specified instance key, if already resolved. */ getResolvedInstance(instanceKey: string): Class | null { let instances = this.instances; if (instances && instances.has(instanceKey)) return instances.get(instanceKey); return null; } /** Sets the resolved instance for the specified instance key. */ setResolvedInstance(instanceKey: string, instance: Class): void { let instances = this.instances; if (!instances) this.instances = instances = new Map(); else assert(!instances.has(instanceKey)); instances.set(instanceKey, instance); } } /** A resolved class. */ export class Class extends TypedElement { /** Class prototype. */ prototype: ClassPrototype; /** Resolved type arguments. */ typeArguments: Type[] | null; /** Base class, if any. */ base: Class | null = null; /** Directly implemented interfaces, if any. */ interfaces: Set | null = null; /** Contextual type arguments for fields and methods. */ contextualTypeArguments: Map | null = null; /** Current member memory offset. */ nextMemoryOffset: u32 = 0; /** Constructor instance. */ constructorInstance: Function | null = null; /** Operator overloads. */ operatorOverloads: Map | null = null; /** Index signature, if present. */ indexSignature: IndexSignature | null = null; /** Unique class id. */ private _id: u32 = 0; /** Runtime type information flags. */ rttiFlags: u32 = 0; /** Wrapped type, if a wrapper for a basic type. */ wrappedType: Type | null = null; /** Classes directly or indirectly extending this class, if any. */ extenders: Set | null = null; /** Classes directly or indirectly implementing this interface, if any. */ implementers: Set | null = null; /** Whether the field initialization check has already been performed. */ didCheckFieldInitialization: bool = false; /** Runtime visitor function reference. */ visitRef: FunctionRef = 0; /** Gets the unique runtime id of this class. */ get id(): u32 { return this._id; // unmanaged remains 0 (=ArrayBuffer) } /** Tests if this class is of a builtin array type (Array/TypedArray). */ get isBuiltinArray(): bool { return this.prototype.isBuiltinArray; } /** Tests if this class is array-like. */ get isArrayLike(): bool { if (this.isBuiltinArray) return true; let lengthField = this.getMember("length"); if (!lengthField) return false; return ( ( lengthField.kind == ElementKind.Property && (lengthField).getterInstance != null ) || ( lengthField.kind == ElementKind.PropertyPrototype && (lengthField).getterPrototype != null // TODO: resolve & check type? ) ) && ( this.lookupOverload(OperatorKind.IndexedGet) != null || this.lookupOverload(OperatorKind.UncheckedIndexedGet) != null ); } /** Tests if this is an interface. */ get isInterface(): bool { return this.kind == ElementKind.Interface; } /** Constructs a new class. */ constructor( /** Name incl. type parameters, i.e. `Foo`. */ nameInclTypeParameters: string, /** The respective class prototype. */ prototype: ClassPrototype, /** Concrete type arguments, if any. */ typeArguments: Type[] | null = null, _isInterface: bool = false // FIXME ) { super( _isInterface ? ElementKind.Interface : ElementKind.Class, nameInclTypeParameters, mangleInternalName(nameInclTypeParameters, prototype.parent, prototype.is(CommonFlags.Instance)), prototype.program, prototype.parent, prototype.declaration ); this.prototype = prototype; this.flags = prototype.flags; this.decoratorFlags = prototype.decoratorFlags; this.typeArguments = typeArguments; let program = this.program; let usizeType = program.options.usizeType; let type = new Type(usizeType.kind, usizeType.flags & ~TypeFlags.Value | TypeFlags.Reference, usizeType.size); type.classReference = this; this.setType(type); if (!this.hasDecorator(DecoratorFlags.Unmanaged)) { let id = program.nextClassId++; this._id = id; program.managedClasses.set(id, this); } // apply pre-checked instance-specific contextual type arguments let typeParameters = prototype.typeParameterNodes; if (typeArguments) { let numTypeArguments = typeArguments.length; if (!typeParameters || numTypeArguments != typeParameters.length) { throw new Error("type argument count mismatch"); } if (numTypeArguments) { let contextualTypeArguments = this.contextualTypeArguments; if (!contextualTypeArguments) this.contextualTypeArguments = contextualTypeArguments = new Map(); for (let i = 0; i < numTypeArguments; ++i) { contextualTypeArguments.set(typeParameters[i].name.text, typeArguments[i]); } } } else if (typeParameters && typeParameters.length > 0) { throw new Error("type argument count mismatch"); } registerConcreteElement(program, this); } /** Computes the least upper bound of two class types. */ static leastUpperBound(a: Class, b: Class): Class | null { if (a == b) return a; let candidates = new Set(); candidates.add(a); candidates.add(b); while (true) { let aBase = a.base; let bBase = b.base; if (!aBase && !bBase) return null; // none if (aBase) { if (candidates.has(aBase)) return aBase; candidates.add(a = aBase); } if (bBase) { if (candidates.has(bBase)) return bBase; candidates.add(b = bBase); } } } /** Sets the base class. */ setBase(base: Class): void { assert(!this.base); this.base = base; // Inherit contextual type arguments from base class let inheritedTypeArguments = base.contextualTypeArguments; if (inheritedTypeArguments) { let contextualTypeArguments = this.contextualTypeArguments; // TODO: for (let [baseName, baseType] of inheritedTypeArguments) { for (let _keys = Map_keys(inheritedTypeArguments), i = 0, k = _keys.length; i < k; ++i) { let baseName = unchecked(_keys[i]); let baseType = assert(inheritedTypeArguments.get(baseName)); if (!contextualTypeArguments) { this.contextualTypeArguments = contextualTypeArguments = new Map(); contextualTypeArguments.set(baseName, baseType); } else if (!contextualTypeArguments.has(baseName)) { contextualTypeArguments.set(baseName, baseType); } } } // This class and its extenders now extend each direct or indirect base class base.propagateExtenderUp(this); let extenders = this.extenders; if (extenders) { for (let _values = Set_values(extenders), i = 0, k = _values.length; i < k; ++i) { let extender = _values[i]; base.propagateExtenderUp(extender); } } // Direct or indirect base interfaces are now implemented by this class and its extenders let nextBase: Class | null = base; do { let baseInterfaces = nextBase.interfaces; if (baseInterfaces) { for (let _values = Set_values(baseInterfaces), i = 0, k = _values.length; i < k; ++i) { let baseInterface = _values[i]; this.propagateInterfaceDown(baseInterface); } } nextBase = nextBase.base; } while (nextBase); } /** Propagates an extender to this class and its base classes. */ private propagateExtenderUp(extender: Class): void { // Start with this class, adding the extender to it. Repeat for the class's // bases that are indirectly extended by the extender. let nextBase: Class | null = this; do { let extenders = nextBase.extenders; if (!extenders) nextBase.extenders = extenders = new Set(); extenders.add(extender); nextBase = nextBase.base; } while (nextBase); } /** Propagates an interface and its base interfaces to this class and its extenders. */ private propagateInterfaceDown(iface: Interface): void { // Start with the interface itself, adding this class and its extenders to // its implementers. Repeat for the interface's bases that are indirectly // implemented by means of being extended by the interface. let nextIface: Interface | null = iface; let extenders = this.extenders; do { let implementers = nextIface.implementers; if (!implementers) nextIface.implementers = implementers = new Set(); implementers.add(this); if (extenders) { for (let _values = Set_values(extenders), i = 0, k = _values.length; i < k; ++i) { let extender = _values[i]; implementers.add(extender); } } nextIface = nextIface.base; } while (nextIface); } /** Adds an interface. */ addInterface(iface: Interface): void { let interfaces = this.interfaces; if (!interfaces) this.interfaces = interfaces = new Set(); interfaces.add(iface); // This class and its extenders now implement the interface and its bases this.propagateInterfaceDown(iface); } /** Tests if a value of this class type is assignable to a target of the specified class type. */ isAssignableTo(target: Class): bool { // Q: When does the assignment in the comment below succeed? if (target.isInterface) { if (this.isInterface) { // targetInterface = thisInterface return this == target || this.extends(target); } else { // targetInterface = thisClass return this.implements(target); } } else { if (this.isInterface) { // targetClass = thisInterface return target == this.program.objectInstance; } else { // targetClass = thisClass return this == target || this.extends(target); } } } /** Tests if any subclass of this class is assignable to a target of the specified class type. */ hasSubclassAssignableTo(target: Class): bool { // Q: When can the cast in the comment below succeed? (while an assignment would not) if (target.isInterface) { if (this.isInterface) { // thisInterface return this.hasImplementerImplementing(target); } else { // thisClass return this.hasExtenderImplementing(target); } } else { if (this.isInterface) { // thisInterface return this.hasImplementer(target); } else { // thisClass return this.hasExtender(target); } } } /** Looks up the operator overload of the specified kind. */ lookupOverload(kind: OperatorKind, unchecked: bool = false): Function | null { if (unchecked) { switch (kind) { case OperatorKind.IndexedGet: { let uncheckedOverload = this.lookupOverload(OperatorKind.UncheckedIndexedGet); if (uncheckedOverload) return uncheckedOverload; break; } case OperatorKind.IndexedSet: { let uncheckedOverload = this.lookupOverload(OperatorKind.UncheckedIndexedSet); if (uncheckedOverload) return uncheckedOverload; break; } default: assert(false); } } let instance: Class | null = this; do { let overloads = instance.operatorOverloads; if (overloads != null && overloads.has(kind)) { return assert(overloads.get(kind)); } instance = instance.base; } while (instance); return null; } /** Gets the method of the specified name, resolved with the given type arguments. */ getMethod(name: string, typeArguments: Type[] | null = null): Function | null { let member = this.getMember(name); if (member && member.kind == ElementKind.FunctionPrototype) { return this.program.resolver.resolveFunction(member, typeArguments); } return null; } /** Calculates the memory offset of the specified field. */ offsetof(fieldName: string): u32 { let member = assert(this.getMember(fieldName)); assert(member.kind == ElementKind.PropertyPrototype); let prototype = member; let property = prototype.instance; if (property) { // would have failed before assert(property.isField && property.memoryOffset >= 0); return property.memoryOffset; } return 0; } /** Creates a buffer suitable to hold a runtime instance of this class. */ createBuffer(overhead: i32 = 0): Uint8Array { let program = this.program; let payloadSize = this.nextMemoryOffset + overhead; let blockSize = program.computeBlockSize(payloadSize, true); // excl. overhead let buffer = new Uint8Array(program.blockOverhead + blockSize); let OBJECT = program.OBJECTInstance; OBJECT.writeField("mmInfo", blockSize, buffer, 0); OBJECT.writeField("gcInfo", 0, buffer, 0); OBJECT.writeField("gcInfo2", 0, buffer, 0); OBJECT.writeField("rtId", this.id, buffer, 0); OBJECT.writeField("rtSize", payloadSize, buffer, 0); return buffer; } /** Writes a field value to a buffer and returns the number of bytes written. */ writeField(name: string, value: T, buffer: Uint8Array, baseOffset: i32 = this.program.totalOverhead): i32 { let member = this.getMember(name); if (member && member.kind == ElementKind.PropertyPrototype) { let prototype = member; let property = prototype.instance; // resolved during class finalization if (!property) return 0; // failed before assert(property.isField && property.memoryOffset >= 0); let offset = baseOffset + property.memoryOffset; let typeKind = property.type.kind; switch (typeKind) { case TypeKind.I8: case TypeKind.U8: { assert(!i64_is(value)); writeI8(i32(value), buffer, offset); return 1; } case TypeKind.I16: case TypeKind.U16: { assert(!i64_is(value)); writeI16(i32(value), buffer, offset); return 2; } case TypeKind.I32: case TypeKind.U32: { assert(!i64_is(value)); writeI32(i32(value), buffer, offset); return 4; } case TypeKind.Isize: case TypeKind.Usize: { if (this.program.options.isWasm64) { if (i64_is(value)) { writeI64(value, buffer, offset); } else { writeI32AsI64(i32(value), buffer, offset, typeKind == TypeKind.Usize); } return 8; } else { if (i64_is(value)) { writeI64AsI32(value, buffer, offset, typeKind == TypeKind.Usize); } else { writeI32(i32(value), buffer, offset); } return 4; } } case TypeKind.I64: case TypeKind.U64: { if (i64_is(value)) { writeI64(value, buffer, offset); } else { writeI32AsI64(i32(value), buffer, offset, typeKind == TypeKind.U64); } return 8; } case TypeKind.F32: { assert(!i64_is(value)); writeF32(f32(value), buffer, offset); return 4; } case TypeKind.F64: { assert(!i64_is(value)); writeF64(f64(value), buffer, offset); return 8; } } } assert(false); return 0; } /** Tests if this class extends the specified prototype. */ extendsPrototype(prototype: ClassPrototype): bool { return this.prototype.extends(prototype); } /** Gets the concrete type arguments to the specified extendend prototype. */ getTypeArgumentsTo(extendedPrototype: ClassPrototype): Type[] | null { let current: Class | null = this; do { if (current.prototype == extendedPrototype) return current.typeArguments; current = current.base; } while (current); return null; } /** Gets the value type of an array. Must be an array. */ getArrayValueType(): Type { let current: Class = this; let program = this.program; let arrayPrototype = program.arrayPrototype; if (this.extendsPrototype(arrayPrototype)) { return this.getTypeArgumentsTo(arrayPrototype)![0]; } let staticArrayPrototype = program.staticArrayPrototype; if (this.extendsPrototype(staticArrayPrototype)) { return this.getTypeArgumentsTo(staticArrayPrototype)![0]; } let abvInstance = program.arrayBufferViewInstance; while (current.base != abvInstance) { current = assert(current.base); } let prototype = current.prototype; switch (prototype.name.charCodeAt(0)) { case CharCode.F: { if (prototype == program.float32ArrayPrototype) return Type.f32; if (prototype == program.float64ArrayPrototype) return Type.f64; break; } case CharCode.I: { if (prototype == program.int8ArrayPrototype) return Type.i8; if (prototype == program.int16ArrayPrototype) return Type.i16; if (prototype == program.int32ArrayPrototype) return Type.i32; if (prototype == program.int64ArrayPrototype) return Type.i64; break; } case CharCode.U: { if (prototype == program.uint8ArrayPrototype) return Type.u8; if (prototype == program.uint8ClampedArrayPrototype) return Type.u8; if (prototype == program.uint16ArrayPrototype) return Type.u16; if (prototype == program.uint32ArrayPrototype) return Type.u32; if (prototype == program.uint64ArrayPrototype) return Type.u64; break; } } assert(false); return Type.void; } /** Tests if this class is pointerfree. Useful to know for the GC. */ get isPointerfree(): bool { let program = this.program; let instanceMembers = this.members; if (instanceMembers) { // Check that there are no managed instance fields for (let _values = Map_values(instanceMembers), i = 0, k = _values.length; i < k; ++i) { let member = unchecked(_values[i]); if (member.kind == ElementKind.PropertyPrototype) { let prototype = member; let property = prototype.instance; // resolved during class finalization if (!property) continue; // failed earlier if (property.isField && property.type.isManaged) return false; } } // Check that this isn't a managed collection if (instanceMembers.has(CommonNames.visit)) { let prototype = this.prototype; if ( prototype == program.arrayPrototype || prototype == program.staticArrayPrototype || prototype == program.setPrototype || prototype == program.mapPrototype ) { // Note that we cannot know for sure anymore as soon as the collection // is extended, because user code may implement a custom visitor. let typeArguments = assert(this.getTypeArgumentsTo(prototype)); for (let i = 0, k = typeArguments.length; i < k; ++i) { if (typeArguments[i].isManaged) return false; } return true; } return false; // has a custom __visit } } return true; } /** Tests if this class or interface extends the given class or interface. */ extends(other: Class): bool { return other.hasExtender(this); } /** Tests if this class has a direct or indirect extender matching the given class. */ hasExtender(other: Class): bool { let extenders = this.extenders; return extenders != null && extenders.has(other); } /** Tests if this class has a direct or indirect extender that implements the given interface. */ hasExtenderImplementing(other: Interface): bool { let extenders = this.extenders; if (extenders) { for (let _values = Set_values(extenders), i = 0, k = _values.length; i < k; ++i) { let extender = _values[i]; if (extender.implements(other)) return true; } } return false; } /** Tests if this class directly or indirectly implements the given interface. */ implements(other: Interface): bool { return other.hasImplementer(this); } /** Tests if this interface has a direct or indirect implementer matching the given class. */ hasImplementer(other: Class): bool { let implementers = this.implementers; return implementers != null && implementers.has(other); } /** Tests if this interface has an implementer implementing the given interface. */ hasImplementerImplementing(other: Interface): bool { let implementers = this.implementers; if (implementers) { for (let _values = Set_values(implementers), i = 0, k = _values.length; i < k; ++i) { let implementer = _values[i]; if (implementer.implements(other)) return true; } } return false; } } /** A yet unresolved interface. */ export class InterfacePrototype extends ClassPrototype { /** Constructs a new interface prototype. */ constructor( name: string, parent: Element, declaration: InterfaceDeclaration, decoratorFlags: DecoratorFlags ) { super( name, parent, declaration, decoratorFlags, true ); } } /** A resolved interface. */ export class Interface extends Class { // FIXME /** Constructs a new interface. */ constructor( /** Name incl. type parameters, i.e. `Foo`. */ nameInclTypeParameters: string, /** The respective class prototype. */ prototype: InterfacePrototype, /** Concrete type arguments, if any. */ typeArguments: Type[] | null = null, ) { super( nameInclTypeParameters, prototype, typeArguments, true ); } } /** Registers a concrete element with a program. */ function registerConcreteElement(program: Program, element: Element): void { assert(!program.instancesByName.has(element.internalName)); program.instancesByName.set(element.internalName, element); } /** Attempts to merge two elements. Returns the merged element on success. */ function tryMerge(older: Element, newer: Element): DeclaredElement | null { // NOTE: some of the following cases are not supported by TS, not sure why exactly. // suggesting to just merge what seems to be possible for now and revisit later. assert(older.program == newer.program); if (newer.members) return null; let merged: DeclaredElement | null = null; switch (older.kind) { case ElementKind.FunctionPrototype: { switch (newer.kind) { case ElementKind.Namespace: { copyMembers(newer, older); merged = older; break; } case ElementKind.TypeDefinition: { if (!older.shadowType) { older.shadowType = newer; copyMembers(newer, older); merged = older; } break; } } break; } case ElementKind.ClassPrototype: case ElementKind.Enum: { if (newer.kind == ElementKind.Namespace) { copyMembers(newer, older); merged = older; break; } break; } case ElementKind.Namespace: { switch (newer.kind) { case ElementKind.Enum: case ElementKind.ClassPrototype: // TS2434 case ElementKind.FunctionPrototype: { // TS2434 copyMembers(older, newer); merged = newer; break; } case ElementKind.Namespace: { copyMembers(newer, older); merged = older; break; } case ElementKind.TypeDefinition: { if (!older.shadowType) { older.shadowType = newer; copyMembers(newer, older); merged = older; } break; } } break; } case ElementKind.Global: { if (newer.kind == ElementKind.TypeDefinition) { if (!older.shadowType) { older.shadowType = newer; copyMembers(newer, older); merged = older; } } break; } case ElementKind.TypeDefinition: { switch (newer.kind) { case ElementKind.Global: case ElementKind.FunctionPrototype: case ElementKind.Namespace: { if (!newer.shadowType) { newer.shadowType = older; copyMembers(older, newer); merged = newer; } break; } } break; } } if (merged) { let olderIsExport = older.is(CommonFlags.Export) || older.hasDecorator(DecoratorFlags.Global); let newerIsExport = newer.is(CommonFlags.Export) || newer.hasDecorator(DecoratorFlags.Global); if (olderIsExport != newerIsExport) { older.program.error( DiagnosticCode.Individual_declarations_in_merged_declaration_0_must_be_all_exported_or_all_local, merged.identifierNode.range, merged.identifierNode.text ); } } return merged; } /** Copies the members of `src` to `dest`. */ function copyMembers(src: Element, dest: Element): void { let srcMembers = src.members; if (srcMembers) { let destMembers = dest.members; if (!destMembers) dest.members = destMembers = new Map(); // TODO: for (let [memberName, member] of srcMembers) { for (let _keys = Map_keys(srcMembers), i = 0, k = _keys.length; i < k; ++i) { let memberName = unchecked(_keys[i]); let member = assert(srcMembers.get(memberName)); destMembers.set(memberName, member); } } } /** Mangles the internal name of an element with the specified name that is a child of the given parent. */ export function mangleInternalName( name: string, parent: Element, isInstance: bool, asGlobal: bool = false ): string { switch (parent.kind) { case ElementKind.File: { if (asGlobal) return name; return parent.internalName + PATH_DELIMITER + name; } case ElementKind.Function: { if (asGlobal) return name; assert(!isInstance); return parent.internalName + INNER_DELIMITER + name; } case ElementKind.PropertyPrototype: // properties are just containers case ElementKind.Property: { // parent = parent.parent; // fall-through } default: { return ( mangleInternalName(parent.name, parent.parent, parent.is(CommonFlags.Instance), asGlobal) + (isInstance ? INSTANCE_DELIMITER : STATIC_DELIMITER) + name ); } } } // Cached default parameter names used where names are unknown. let cachedDefaultParameterNames: string[] = []; /** Gets the cached default parameter name for the specified index. */ export function getDefaultParameterName(index: i32): string { for (let i = cachedDefaultParameterNames.length; i <= index; ++i) { cachedDefaultParameterNames.push(`$${i}`); } return cachedDefaultParameterNames[index]; }