assemblyscript/src/program.ts

1795 lines
65 KiB
TypeScript
Raw Normal View History

2017-09-28 13:08:25 +02:00
import {
2017-12-24 03:19:47 +01:00
initialize as initializeBuiltins
} from "./builtins";
2017-09-28 13:08:25 +02:00
2017-12-24 03:19:47 +01:00
import {
Target
} from "./compiler";
import {
PATH_DELIMITER,
GETTER_PREFIX,
SETTER_PREFIX,
STATIC_DELIMITER,
INSTANCE_DELIMITER
2017-12-24 03:19:47 +01:00
} from "./constants";
import {
DiagnosticCode,
DiagnosticMessage,
DiagnosticEmitter
} from "./diagnostics";
import {
Type,
typesToString
} from "./types";
import {
I64
} from "./util/i64";
import {
2017-10-02 12:52:15 +02:00
ModifierKind,
2017-09-28 13:08:25 +02:00
Node,
NodeKind,
2017-10-02 12:52:15 +02:00
Source,
Range,
TypeNode,
2017-12-24 03:19:47 +01:00
TypeParameter,
Decorator,
Expression,
IdentifierExpression,
PropertyAccessExpression,
StringLiteralExpression,
2017-12-16 02:27:39 +01:00
CallExpression,
2017-09-28 13:08:25 +02:00
2017-12-24 03:19:47 +01:00
Statement,
2017-09-28 13:08:25 +02:00
ClassDeclaration,
DeclarationStatement,
EnumDeclaration,
EnumValueDeclaration,
ExportMember,
ExportStatement,
2017-09-28 13:08:25 +02:00
FieldDeclaration,
FunctionDeclaration,
ImportDeclaration,
ImportStatement,
InterfaceDeclaration,
MethodDeclaration,
NamespaceDeclaration,
TypeDeclaration,
VariableLikeDeclarationStatement,
2017-09-28 13:08:25 +02:00
VariableDeclaration,
VariableStatement,
hasModifier,
mangleInternalName
2017-09-28 13:08:25 +02:00
} from "./ast";
2017-12-24 03:19:47 +01:00
import {
NativeType
} from "./module";
2017-09-28 13:08:25 +02:00
class QueuedExport {
isReExport: bool;
referencedName: string;
member: ExportMember;
}
class QueuedImport {
2017-10-02 12:52:15 +02:00
internalName: string;
referencedName: string;
declaration: ImportDeclaration;
}
2017-12-05 01:45:15 +01:00
const noTypesYet: Map<string,Type> = new Map();
2017-10-07 14:29:43 +02:00
2017-12-13 04:46:05 +01:00
/** Represents an AssemblyScript program. */
2017-09-28 13:08:25 +02:00
export class Program extends DiagnosticEmitter {
/** Array of source files. */
2017-09-28 13:08:25 +02:00
sources: Source[];
/** Diagnostic offset used where sequentially obtaining the next diagnostic. */
2017-09-28 13:08:25 +02:00
diagnosticsOffset: i32 = 0;
/** WebAssembly target. */
target: Target = Target.WASM32; // set on initialization
/** Elements by internal name. */
elements: Map<string,Element> = new Map();
/** Types by internal name. */
2017-12-05 01:45:15 +01:00
types: Map<string,Type> = noTypesYet;
2017-12-20 13:36:39 +01:00
/** Declared type aliases. */
typeAliases: Map<string,TypeNode> = new Map();
/** Exports of individual files by internal name. Not global exports. */
exports: Map<string,Element> = new Map();
2017-09-28 13:08:25 +02:00
2017-12-13 04:46:05 +01:00
/** Constructs a new program, optionally inheriting parser diagnostics. */
2017-09-28 13:08:25 +02:00
constructor(diagnostics: DiagnosticMessage[] | null = null) {
super(diagnostics);
this.sources = [];
2017-09-28 13:08:25 +02:00
}
/** Initializes the program and its elements prior to compilation. */
initialize(target: Target = Target.WASM32): void {
2017-09-28 13:08:25 +02:00
this.target = target;
2017-12-05 01:45:15 +01:00
this.types = new Map([
["i8", Type.i8],
["i16", Type.i16],
["i32", Type.i32],
["i64", Type.i64],
["isize", target == Target.WASM64 ? Type.isize64 : Type.isize32],
["u8", Type.u8],
["u16", Type.u16],
["u32", Type.u32],
["u64", Type.u64],
["usize", target == Target.WASM64 ? Type.usize64 : Type.usize32],
["bool", Type.bool],
["f32", Type.f32],
["f64", Type.f64],
2017-12-16 02:27:39 +01:00
["void", Type.void],
["number", Type.f64],
["boolean", Type.bool]
2017-12-05 01:45:15 +01:00
]);
2017-12-01 02:08:03 +01:00
initializeBuiltins(this);
2017-09-28 13:08:25 +02:00
const queuedExports = new Map<string,QueuedExport>();
const queuedImports = new Array<QueuedImport>();
2017-09-28 13:08:25 +02:00
// build initial lookup maps of internal names to declarations
for (let i = 0, k = this.sources.length; i < k; ++i) {
const source = this.sources[i];
const statements = source.statements;
for (let j = 0, l = statements.length; j < l; ++j) {
const statement = statements[j];
2017-09-28 13:08:25 +02:00
switch (statement.kind) {
case NodeKind.CLASS:
this.initializeClass(<ClassDeclaration>statement);
break;
case NodeKind.ENUM:
this.initializeEnum(<EnumDeclaration>statement);
break;
case NodeKind.EXPORT:
2017-10-02 12:52:15 +02:00
this.initializeExports(<ExportStatement>statement, queuedExports);
break;
2017-09-28 13:08:25 +02:00
case NodeKind.FUNCTION:
this.initializeFunction(<FunctionDeclaration>statement);
break;
case NodeKind.IMPORT:
2017-10-02 12:52:15 +02:00
this.initializeImports(<ImportStatement>statement, queuedExports, queuedImports);
2017-09-28 13:08:25 +02:00
break;
case NodeKind.INTERFACE:
this.initializeInterface(<InterfaceDeclaration>statement);
break;
case NodeKind.NAMESPACE:
this.initializeNamespace(<NamespaceDeclaration>statement);
break;
case NodeKind.TYPEDECLARATION:
this.initializeType(<TypeDeclaration>statement);
break;
2017-09-28 13:08:25 +02:00
case NodeKind.VARIABLE:
this.initializeVariables(<VariableStatement>statement);
break;
}
}
}
let element: Element | null;
2017-12-13 04:46:05 +01:00
// queued imports should be resolvable now through traversing exports and queued exports
for (let j = 0; j < queuedImports.length;) {
const queuedImport = queuedImports[j];
element = this.tryResolveImport(queuedImport.referencedName, queuedExports);
if (element) {
this.elements.set(queuedImport.internalName, element);
queuedImports.splice(j, 1);
} else {
this.error(DiagnosticCode.Module_0_has_no_exported_member_1, queuedImport.declaration.range, (<ImportStatement>queuedImport.declaration.parent).path.value, queuedImport.declaration.externalIdentifier.name);
++j;
}
}
2017-12-13 04:46:05 +01:00
// queued exports should be resolvable now that imports are finalized
for (let [exportName, queuedExport] of queuedExports) {
let currentExport: QueuedExport | null = queuedExport;
do {
if (currentExport.isReExport) {
element = this.exports.get(currentExport.referencedName);
if (element) {
this.exports.set(exportName, element);
break;
}
currentExport = queuedExports.get(currentExport.referencedName);
if (!currentExport)
this.error(DiagnosticCode.Module_0_has_no_exported_member_1, queuedExport.member.externalIdentifier.range, (<StringLiteralExpression>(<ExportStatement>queuedExport.member.parent).path).value, queuedExport.member.externalIdentifier.name);
} else {
element = this.elements.get(currentExport.referencedName);
if (element)
this.exports.set(exportName, element);
else
this.error(DiagnosticCode.Cannot_find_name_0, queuedExport.member.range, queuedExport.member.identifier.name);
break;
}
} while (currentExport);
2017-09-28 13:08:25 +02:00
}
}
2017-12-13 04:46:05 +01:00
/** Tries to resolve an import by traversing exports and queued exports. */
private tryResolveImport(referencedName: string, queuedExports: Map<string,QueuedExport>): Element | null {
let element: Element | null;
do {
element = this.exports.get(referencedName);
if (element)
return element;
const queuedExport = queuedExports.get(referencedName);
if (!queuedExport)
return null;
if (queuedExport.isReExport) {
referencedName = queuedExport.referencedName;
continue;
}
return this.elements.get(queuedExport.referencedName);
} while (true);
}
private initializeClass(declaration: ClassDeclaration, namespace: Element | null = null): void {
const internalName = declaration.internalName;
2017-11-17 14:33:51 +01:00
if (this.elements.has(internalName)) {
2017-12-18 03:46:36 +01:00
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName);
2017-11-17 14:33:51 +01:00
return;
}
const prototype = new ClassPrototype(this, declaration.name.name, internalName, declaration);
prototype.namespace = namespace;
2017-11-17 14:33:51 +01:00
this.elements.set(internalName, prototype);
// add program-level alias if annotated as @global
2017-12-16 20:08:33 +01:00
if (hasDecorator("global", declaration.decorators)) {
2017-12-18 03:46:36 +01:00
if (this.elements.has(declaration.name.name))
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName);
2017-12-16 20:08:33 +01:00
else
2017-12-18 03:46:36 +01:00
this.elements.set(declaration.name.name, prototype);
2017-12-16 20:08:33 +01:00
}
// add as namespace member if applicable
if (namespace) {
if (namespace.members) {
2017-12-18 03:46:36 +01:00
if (namespace.members.has(declaration.name.name)) {
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName);
return;
}
} else
namespace.members = new Map();
2017-12-18 03:46:36 +01:00
namespace.members.set(declaration.name.name, prototype);
// otherwise add to file-level exports if exported
} else if (prototype.isExported) {
if (this.exports.has(internalName)) {
2017-12-18 03:46:36 +01:00
this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, declaration.name.range, internalName);
return;
}
this.exports.set(internalName, prototype);
}
// initialize members
const memberDeclarations = declaration.members;
for (let i = 0, k = memberDeclarations.length; i < k; ++i) {
switch (memberDeclarations[i].kind) {
case NodeKind.FIELD:
this.initializeField(<FieldDeclaration>memberDeclarations[i], prototype);
break;
case NodeKind.METHOD: // also getter/setter
this.initializeMethod(<MethodDeclaration>memberDeclarations[i], prototype);
break;
default:
throw new Error("class member expected");
}
}
2017-09-28 13:08:25 +02:00
}
2017-11-17 14:33:51 +01:00
private initializeField(declaration: FieldDeclaration, classPrototype: ClassPrototype): void {
const name = declaration.name.name;
const internalName = declaration.internalName;
// static fields become global variables
if (hasModifier(ModifierKind.STATIC, declaration.modifiers)) {
if (this.elements.has(internalName)) {
2017-12-18 03:46:36 +01:00
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, declaration.internalName);
return;
}
if (classPrototype.members) {
if (classPrototype.members.has(name)) {
2017-12-18 03:46:36 +01:00
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, declaration.internalName);
return;
}
} else
classPrototype.members = new Map();
2017-12-27 22:38:32 +01:00
const staticField = new Global(this, name, internalName, declaration, null);
classPrototype.members.set(name, staticField);
this.elements.set(internalName, staticField);
// instance fields are remembered until resolved
} else {
if (classPrototype.instanceMembers) {
if (classPrototype.instanceMembers.has(name)) {
2017-12-18 03:46:36 +01:00
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, declaration.internalName);
return;
}
} else
classPrototype.instanceMembers = new Map();
2017-12-27 22:38:32 +01:00
const instanceField = new FieldPrototype(classPrototype, name, internalName, declaration);
classPrototype.instanceMembers.set(name, instanceField);
}
}
2017-11-17 14:33:51 +01:00
private initializeMethod(declaration: MethodDeclaration, classPrototype: ClassPrototype): void {
let isGetter = false;
if ((isGetter = hasModifier(ModifierKind.GET, declaration.modifiers)) || hasModifier(ModifierKind.SET, declaration.modifiers)) {
this.initializeAccessor(declaration, classPrototype, isGetter);
return;
}
const name = declaration.name.name;
const internalName = declaration.internalName;
// static methods become global functions
if (hasModifier(ModifierKind.STATIC, declaration.modifiers)) {
if (this.elements.has(internalName)) {
2017-12-18 03:46:36 +01:00
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, declaration.internalName);
return;
}
if (classPrototype.members) {
if (classPrototype.members.has(name)) {
2017-12-18 03:46:36 +01:00
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, declaration.internalName);
return;
}
} else
classPrototype.members = new Map();
2017-12-15 15:00:19 +01:00
const staticPrototype: FunctionPrototype = new FunctionPrototype(this, name, internalName, declaration, null);
classPrototype.members.set(name, staticPrototype);
this.elements.set(internalName, staticPrototype);
// instance methods are remembered until resolved
} else {
if (classPrototype.instanceMembers) {
if (classPrototype.instanceMembers.has(name)) {
2017-12-18 03:46:36 +01:00
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, declaration.internalName);
return;
}
} else
classPrototype.instanceMembers = new Map();
2017-12-15 15:00:19 +01:00
const instancePrototype: FunctionPrototype = new FunctionPrototype(this, name, internalName, declaration, classPrototype);
classPrototype.instanceMembers.set(name, instancePrototype);
}
2017-09-28 13:08:25 +02:00
}
private initializeAccessor(declaration: MethodDeclaration, classPrototype: ClassPrototype, isGetter: bool): void {
const propertyName = declaration.name.name;
const internalPropertyName = declaration.internalName;
let propertyElement = this.elements.get(internalPropertyName);
if (propertyElement) {
if (propertyElement.kind != ElementKind.PROPERTY || (isGetter ? (<Property>propertyElement).getterPrototype : (<Property>propertyElement).setterPrototype)) {
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalPropertyName);
return;
}
} else
propertyElement = new Property(this, propertyName, internalPropertyName, classPrototype);
let name = (isGetter ? GETTER_PREFIX : SETTER_PREFIX) + propertyName;
// static accessors become global functions
if (hasModifier(ModifierKind.STATIC, declaration.modifiers)) {
const internalStaticName = classPrototype.internalName + STATIC_DELIMITER + name;
if (this.elements.has(internalStaticName)) {
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalStaticName);
return;
}
const staticPrototype = new FunctionPrototype(this, name, internalStaticName, declaration, null);
if (isGetter)
(<Property>propertyElement).getterPrototype = staticPrototype;
else
(<Property>propertyElement).setterPrototype = staticPrototype;
if (!classPrototype.members)
classPrototype.members = new Map();
classPrototype.members.set(propertyName, propertyElement); // checked above
this.elements.set(internalPropertyName, propertyElement);
// instance accessors are remembered until resolved
} else {
const internalInstanceName = classPrototype.internalName + INSTANCE_DELIMITER + name;
if (classPrototype.instanceMembers) {
if (classPrototype.instanceMembers.has(name)) {
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, declaration.internalName);
return;
}
} else
classPrototype.instanceMembers = new Map();
const instancePrototype = new FunctionPrototype(this, name, internalInstanceName, declaration, classPrototype);
if (isGetter)
(<Property>propertyElement).getterPrototype = instancePrototype;
else
(<Property>propertyElement).setterPrototype = instancePrototype;
classPrototype.instanceMembers.set(name, propertyElement);
}
}
private initializeEnum(declaration: EnumDeclaration, namespace: Element | null = null): void {
const internalName = declaration.internalName;
if (this.elements.has(internalName)) {
2017-12-18 03:46:36 +01:00
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName);
return;
}
2017-12-27 22:38:32 +01:00
const enm = new Enum(this, declaration.name.name, internalName, declaration);
enm.namespace = namespace;
this.elements.set(internalName, enm);
2017-12-13 23:24:13 +01:00
if (namespace) {
if (namespace.members) {
2017-12-18 03:46:36 +01:00
if (namespace.members.has(declaration.name.name)) {
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName);
return;
}
2017-12-13 23:24:13 +01:00
} else
namespace.members = new Map();
2017-12-18 03:46:36 +01:00
namespace.members.set(declaration.name.name, enm);
2017-12-13 23:24:13 +01:00
} else if (enm.isExported) {
if (this.exports.has(internalName)) {
2017-12-18 03:46:36 +01:00
this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, declaration.name.range, internalName);
return;
}
this.exports.set(internalName, enm);
}
2017-12-13 23:24:13 +01:00
const values = declaration.values;
2017-11-17 14:33:51 +01:00
for (let i: i32 = 0, k: i32 = values.length; i < k; ++i)
this.initializeEnumValue(values[i], enm);
2017-09-28 13:08:25 +02:00
}
private initializeEnumValue(declaration: EnumValueDeclaration, enm: Enum): void {
const name = declaration.name.name;
const internalName = declaration.internalName;
if (enm.members) {
if (enm.members.has(name)) {
2017-12-18 03:46:36 +01:00
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName);
return;
}
} else
enm.members = new Map();
2017-12-27 22:38:32 +01:00
const value = new EnumValue(enm, this, name, internalName, declaration);
2017-11-17 14:33:51 +01:00
enm.members.set(name, value);
}
2017-10-02 12:52:15 +02:00
private initializeExports(statement: ExportStatement, queuedExports: Map<string,QueuedExport>): void {
const members = statement.members;
for (let i = 0, k = members.length; i < k; ++i)
this.initializeExport(members[i], statement.internalPath, queuedExports);
}
private initializeExport(member: ExportMember, internalPath: string | null, queuedExports: Map<string,QueuedExport>): void {
const externalName: string = member.range.source.internalPath + PATH_DELIMITER + member.externalIdentifier.name;
if (this.exports.has(externalName)) {
this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, member.externalIdentifier.range, externalName);
2017-11-17 14:33:51 +01:00
return;
}
let referencedName: string;
// export local element
2017-11-17 14:33:51 +01:00
if (internalPath == null) {
referencedName = member.range.source.internalPath + PATH_DELIMITER + member.identifier.name;
// resolve right away if the element exists
if (this.elements.has(referencedName)) {
this.exports.set(externalName, <Element>this.elements.get(referencedName));
return;
}
// otherwise queue it
if (queuedExports.has(externalName)) {
this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, member.externalIdentifier.range, externalName);
return;
}
const queuedExport = new QueuedExport();
queuedExport.isReExport = false;
queuedExport.referencedName = referencedName; // -> internal name
queuedExport.member = member;
queuedExports.set(externalName, queuedExport);
// export external element
2017-11-17 14:33:51 +01:00
} else {
referencedName = (<string>internalPath) + PATH_DELIMITER + member.identifier.name;
// resolve right away if the export exists
if (this.exports.has(referencedName)) {
this.exports.set(externalName, <Element>this.exports.get(referencedName));
return;
}
// walk already known queued exports
const seen = new Set<QueuedExport>();
let queuedExport: QueuedExport | null;
while (queuedExport = queuedExports.get(referencedName)) {
if (queuedExport.isReExport) {
if (this.exports.has(queuedExport.referencedName)) {
this.exports.set(externalName, <Element>this.exports.get(referencedName));
return;
}
referencedName = queuedExport.referencedName;
if (seen.has(queuedExport))
break;
seen.add(queuedExport);
} else {
if (this.elements.has(queuedExport.referencedName)) {
this.exports.set(externalName, <Element>this.elements.get(referencedName));
return;
}
break;
}
}
// otherwise queue it
if (queuedExports.has(externalName)) {
this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, member.externalIdentifier.range, externalName);
return;
}
const queuedReExport = new QueuedExport();
queuedReExport.isReExport = true;
queuedReExport.referencedName = referencedName; // -> export name
queuedReExport.member = member;
queuedExports.set(externalName, queuedReExport);
}
2017-09-28 13:08:25 +02:00
}
private initializeFunction(declaration: FunctionDeclaration, namespace: Element | null = null): void {
const internalName = declaration.internalName;
2017-11-17 14:33:51 +01:00
if (this.elements.has(internalName)) {
2017-12-18 03:46:36 +01:00
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName);
2017-11-17 14:33:51 +01:00
return;
}
const prototype = new FunctionPrototype(this, declaration.name.name, internalName, declaration, null);
prototype.namespace = namespace;
2017-11-17 14:33:51 +01:00
this.elements.set(internalName, prototype);
2017-12-13 23:24:13 +01:00
2017-12-16 20:08:33 +01:00
if (hasDecorator("global", declaration.decorators)) {
2017-12-18 03:46:36 +01:00
if (this.elements.has(declaration.name.name))
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName);
2017-12-16 20:08:33 +01:00
else
2017-12-18 03:46:36 +01:00
this.elements.set(declaration.name.name, prototype);
2017-12-16 20:08:33 +01:00
}
2017-12-13 23:24:13 +01:00
if (namespace) {
if (namespace.members) {
2017-12-18 03:46:36 +01:00
if (namespace.members.has(declaration.name.name)) {
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName);
return;
}
} else
namespace.members = new Map();
2017-12-18 03:46:36 +01:00
namespace.members.set(declaration.name.name, prototype);
2017-12-13 23:24:13 +01:00
} else if (prototype.isExported) {
if (this.exports.has(internalName)) {
2017-12-18 03:46:36 +01:00
this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, declaration.name.range, internalName);
return;
}
this.exports.set(internalName, prototype);
}
2017-09-28 13:08:25 +02:00
}
2017-10-02 12:52:15 +02:00
private initializeImports(statement: ImportStatement, queuedExports: Map<string,QueuedExport>, queuedImports: QueuedImport[]): void {
const declarations = statement.declarations;
if (declarations) {
for (let i = 0, k = declarations.length; i < k; ++i)
this.initializeImport(declarations[i], statement.internalPath, queuedExports, queuedImports);
} else if (statement.namespaceName) {
const internalName = statement.range.source.internalPath + "/" + statement.namespaceName.name;
if (this.elements.has(internalName)) {
this.error(DiagnosticCode.Duplicate_identifier_0, statement.namespaceName.range, internalName);
return;
}
this.error(DiagnosticCode.Operation_not_supported, statement.range); // TODO
} else
assert(false);
2017-09-28 13:08:25 +02:00
}
private initializeImport(declaration: ImportDeclaration, internalPath: string, queuedExports: Map<string,QueuedExport>, queuedImports: QueuedImport[]): void {
const internalName = declaration.internalName;
if (this.elements.has(internalName)) {
2017-12-18 03:46:36 +01:00
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName);
return;
}
let referencedName = internalPath + PATH_DELIMITER + declaration.externalIdentifier.name;
// resolve right away if the export exists
if (this.exports.has(referencedName)) {
this.elements.set(internalName, <Element>this.exports.get(referencedName));
return;
}
// walk already known queued exports
const seen = new Set<QueuedExport>();
let queuedExport: QueuedExport | null;
while (queuedExport = queuedExports.get(referencedName)) {
if (queuedExport.isReExport) {
if (this.exports.has(queuedExport.referencedName)) {
this.elements.set(internalName, <Element>this.exports.get(referencedName));
return;
}
referencedName = queuedExport.referencedName;
if (seen.has(queuedExport))
break;
seen.add(queuedExport);
} else {
if (this.elements.has(queuedExport.referencedName)) {
this.elements.set(internalName, <Element>this.elements.get(referencedName));
return;
}
2017-10-02 12:52:15 +02:00
break;
}
}
// otherwise queue it
const queuedImport = new QueuedImport();
queuedImport.internalName = internalName;
queuedImport.referencedName = referencedName;
queuedImport.declaration = declaration;
queuedImports.push(queuedImport);
2017-09-28 13:08:25 +02:00
}
private initializeInterface(declaration: InterfaceDeclaration, namespace: Element | null = null): void {
const internalName = declaration.internalName;
if (this.elements.has(internalName)) {
2017-12-18 03:46:36 +01:00
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName);
return;
}
const prototype = new InterfacePrototype(this, declaration.name.name, internalName, declaration);
prototype.namespace = namespace;
this.elements.set(internalName, prototype);
2017-12-13 04:46:05 +01:00
2017-12-13 23:24:13 +01:00
if (namespace) {
if (namespace.members) {
if (namespace.members.has(prototype.internalName)) {
2017-12-18 03:46:36 +01:00
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName);
return;
}
} else
namespace.members = new Map();
namespace.members.set(prototype.internalName, prototype);
2017-12-13 23:24:13 +01:00
} else if (prototype.isExported) {
if (this.exports.has(internalName)) {
2017-12-18 03:46:36 +01:00
this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, declaration.name.range, internalName);
return;
}
this.exports.set(internalName, prototype);
}
2017-12-13 23:24:13 +01:00
const memberDeclarations = declaration.members;
for (let i = 0, k = memberDeclarations.length; i < k; ++i) {
switch (memberDeclarations[i].kind) {
2017-09-28 13:08:25 +02:00
case NodeKind.FIELD:
this.initializeField(<FieldDeclaration>memberDeclarations[i], prototype);
2017-09-28 13:08:25 +02:00
break;
case NodeKind.METHOD:
this.initializeMethod(<MethodDeclaration>memberDeclarations[i], prototype);
2017-09-28 13:08:25 +02:00
break;
default:
throw new Error("interface member expected");
2017-09-28 13:08:25 +02:00
}
}
}
private initializeNamespace(declaration: NamespaceDeclaration, parentNamespace: Element | null = null): void {
const internalName = declaration.internalName;
2017-12-13 04:46:05 +01:00
let namespace = this.elements.get(internalName);
if (!namespace) {
2017-12-27 22:38:32 +01:00
namespace = new Namespace(this, declaration.name.name, internalName, declaration);
namespace.namespace = parentNamespace;
2017-11-17 14:33:51 +01:00
this.elements.set(internalName, namespace);
}
2017-12-13 23:24:13 +01:00
if (parentNamespace) {
if (parentNamespace.members) {
2017-12-18 03:46:36 +01:00
if (parentNamespace.members.has(declaration.name.name)) {
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName);
return;
}
} else
parentNamespace.members = new Map();
2017-12-18 03:46:36 +01:00
parentNamespace.members.set(declaration.name.name, namespace);
2017-12-13 23:24:13 +01:00
} else if (namespace.isExported) {
if (this.exports.has(internalName)) {
2017-12-18 03:46:36 +01:00
this.error(DiagnosticCode.Export_declaration_conflicts_with_exported_declaration_of_0, declaration.name.range, internalName);
return;
}
this.exports.set(internalName, namespace);
}
2017-12-13 23:24:13 +01:00
const members = declaration.members;
for (let i = 0, k = members.length; i < k; ++i) {
switch (members[i].kind) {
2017-09-28 13:08:25 +02:00
case NodeKind.CLASS:
this.initializeClass(<ClassDeclaration>members[i], namespace);
2017-09-28 13:08:25 +02:00
break;
case NodeKind.ENUM:
this.initializeEnum(<EnumDeclaration>members[i], namespace);
2017-09-28 13:08:25 +02:00
break;
case NodeKind.FUNCTION:
this.initializeFunction(<FunctionDeclaration>members[i], namespace);
2017-09-28 13:08:25 +02:00
break;
case NodeKind.INTERFACE:
this.initializeInterface(<InterfaceDeclaration>members[i], namespace);
2017-09-28 13:08:25 +02:00
break;
case NodeKind.NAMESPACE:
this.initializeNamespace(<NamespaceDeclaration>members[i], namespace);
2017-09-28 13:08:25 +02:00
break;
case NodeKind.TYPEDECLARATION:
this.initializeType(<TypeDeclaration>members[i], namespace);
break;
2017-09-28 13:08:25 +02:00
case NodeKind.VARIABLE:
this.initializeVariables(<VariableStatement>members[i], namespace);
2017-09-28 13:08:25 +02:00
break;
default:
throw new Error("unexpected namespace member");
}
}
}
private initializeType(declaration: TypeDeclaration, namespace: Element | null = null): void {
// type aliases are program globals
// TODO: what about namespaced types?
const name = declaration.name.name;
2017-12-20 13:36:39 +01:00
if (this.types.has(name) || this.typeAliases.has(name)) {
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, name);
return;
}
2017-12-20 13:36:39 +01:00
this.typeAliases.set(name, declaration.alias);
}
private initializeVariables(statement: VariableStatement, namespace: Element | null = null): void {
const declarations = statement.declarations;
for (let i = 0, k = declarations.length; i < k; ++i) {
const declaration = declarations[i];
const internalName = declaration.internalName;
if (this.elements.has(internalName)) {
2017-12-18 03:46:36 +01:00
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName);
continue;
}
2017-12-27 22:38:32 +01:00
const global = new Global(this, declaration.name.name, internalName, declaration, null);
global.namespace = namespace;
this.elements.set(internalName, global);
2017-12-13 23:24:13 +01:00
2017-12-16 20:08:33 +01:00
if (hasDecorator("global", declaration.decorators)) {
2017-12-18 03:46:36 +01:00
if (this.elements.has(declaration.name.name))
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName);
2017-12-16 20:08:33 +01:00
else
2017-12-18 03:46:36 +01:00
this.elements.set(declaration.name.name, global);
2017-12-16 20:08:33 +01:00
}
2017-12-13 23:24:13 +01:00
if (namespace) {
if (namespace.members) {
2017-12-18 03:46:36 +01:00
if (namespace.members.has(declaration.name.name)) {
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName);
continue;
}
} else
namespace.members = new Map();
2017-12-18 03:46:36 +01:00
namespace.members.set(declaration.name.name, global);
} else if (global.isExported) {
if (this.exports.has(internalName))
2017-12-18 03:46:36 +01:00
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName);
2017-12-13 23:24:13 +01:00
else
this.exports.set(internalName, global);
}
2017-09-28 13:08:25 +02:00
}
}
2017-12-13 04:46:05 +01:00
/** Resolves a {@link TypeNode} to a concrete {@link Type}. */
resolveType(node: TypeNode, contextualTypeArguments: Map<string,Type> | null = null, reportNotFound: bool = true): Type | null {
// resolve parameters
const k = node.typeArguments.length;
const paramTypes = new Array<Type>(k);
for (let i = 0; i < k; ++i) {
const paramType = this.resolveType(node.typeArguments[i], contextualTypeArguments, reportNotFound);
if (!paramType)
return null;
paramTypes[i] = paramType;
}
let globalName = node.identifier.name;
if (k) // can't be a placeholder if it has parameters
globalName += typesToString(paramTypes);
else if (contextualTypeArguments) {
const placeholderType = contextualTypeArguments.get(globalName);
if (placeholderType)
return placeholderType;
}
let type: Type | null;
// check file-global type
if (type = this.types.get(node.range.source.internalPath + PATH_DELIMITER + globalName))
return type;
// check program-global type
if (type = this.types.get(globalName))
return type;
2017-12-20 13:36:39 +01:00
// check type alias
let alias = this.typeAliases.get(globalName);
2017-12-20 13:36:39 +01:00
if (alias && (type = this.resolveType(alias, null, reportNotFound)))
return type;
if (reportNotFound)
this.error(DiagnosticCode.Cannot_find_name_0, node.identifier.range, globalName);
return null;
}
2017-12-13 23:24:13 +01:00
/** Resolves an array of type parameters to concrete types. */
2017-11-20 23:39:50 +01:00
resolveTypeArguments(typeParameters: TypeParameter[], typeArgumentNodes: TypeNode[] | null, contextualTypeArguments: Map<string,Type> | null = null, alternativeReportNode: Node | null = null): Type[] | null {
const parameterCount = typeParameters.length;
const argumentCount = typeArgumentNodes ? typeArgumentNodes.length : 0;
if (parameterCount != argumentCount) {
if (argumentCount)
2017-11-20 23:39:50 +01:00
this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, Range.join((<TypeNode[]>typeArgumentNodes)[0].range, (<TypeNode[]>typeArgumentNodes)[argumentCount - 1].range), parameterCount.toString(10), argumentCount.toString(10));
else if (alternativeReportNode)
this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, alternativeReportNode.range.atEnd, parameterCount.toString(10), "0");
return null;
}
const typeArguments = new Array<Type>(parameterCount);
for (let i = 0; i < parameterCount; ++i) {
const type = this.resolveType((<TypeNode[]>typeArgumentNodes)[i], contextualTypeArguments, true); // reports
if (!type)
return null;
// TODO: check extendsType
typeArguments[i] = type;
2017-09-28 13:08:25 +02:00
}
return typeArguments;
}
/** Resolves an identifier to the element it refers to. */
2017-12-13 23:24:13 +01:00
resolveIdentifier(identifier: IdentifierExpression, contextualFunction: Function): Element | null {
const name = identifier.name;
const local = contextualFunction.locals.get(name);
2017-12-13 23:24:13 +01:00
if (local)
return local;
2017-12-13 23:24:13 +01:00
let element: Element | null;
let namespace: Element | null;
// search parent namespaces if applicable
if (contextualFunction && (namespace = contextualFunction.prototype.namespace)) {
do {
if (element = this.elements.get(namespace.internalName + STATIC_DELIMITER + name))
return element;
} while (namespace = namespace.namespace);
}
// search current file
2017-12-13 23:24:13 +01:00
if (element = this.elements.get(identifier.range.source.internalPath + PATH_DELIMITER + name))
return element;
// search global scope
2017-12-13 23:24:13 +01:00
if (element = this.elements.get(name))
return element;
2017-12-13 23:24:13 +01:00
this.error(DiagnosticCode.Cannot_find_name_0, identifier.range, name);
return null;
}
/** Resolves a property access the element it refers to. */
resolvePropertyAccess(propertyAccess: PropertyAccessExpression, contextualFunction: Function): Element | null {
const expression = propertyAccess.expression;
2017-12-13 23:24:13 +01:00
let target: Element | null = null;
if (expression.kind == NodeKind.IDENTIFIER) {
target = this.resolveIdentifier(<IdentifierExpression>expression, contextualFunction);
} else if (expression.kind == NodeKind.PROPERTYACCESS) {
target = this.resolvePropertyAccess(<PropertyAccessExpression>expression, contextualFunction);
} else
throw new Error("unexpected target kind");
if (!target)
return null;
const propertyName = propertyAccess.property.name;
if (target.members) {
const member = target.members.get(propertyName);
if (member)
return member;
}
2017-12-13 23:24:13 +01:00
this.error(DiagnosticCode.Property_0_does_not_exist_on_type_1, expression.range, (<PropertyAccessExpression>expression).property.name, target.internalName);
return null;
}
2017-11-17 14:33:51 +01:00
resolveElement(expression: Expression, contextualFunction: Function): Element | null {
2017-11-20 23:39:50 +01:00
// this -> Class
if (expression.kind == NodeKind.THIS) {
if (contextualFunction.instanceMethodOf)
2017-11-17 14:33:51 +01:00
return contextualFunction.instanceMethodOf;
this.error(DiagnosticCode._this_cannot_be_referenced_in_current_location, expression.range);
return null;
}
// local or global name
if (expression.kind == NodeKind.IDENTIFIER) {
2017-12-13 23:24:13 +01:00
return this.resolveIdentifier(<IdentifierExpression>expression, contextualFunction);
// static or instance property (incl. enum values) or method
} else if (expression.kind == NodeKind.PROPERTYACCESS) {
2017-12-13 23:24:13 +01:00
return this.resolvePropertyAccess(<PropertyAccessExpression>expression, contextualFunction);
2017-12-16 02:27:39 +01:00
// instantiation
} else if (expression.kind == NodeKind.NEW) {
return this.resolveElement((<CallExpression>expression).expression, contextualFunction);
}
2017-12-24 03:19:47 +01:00
throw new Error("not implemented");
}
}
2017-12-16 02:27:39 +01:00
function hasDecorator(name: string, decorators: Decorator[] | null): bool {
if (decorators)
for (let i = 0, k = decorators.length; i < k; ++i) {
const decorator = decorators[i];
const expression = decorator.name;
if (expression.kind == NodeKind.IDENTIFIER && decorator.arguments.length <= 1 && (<IdentifierExpression>expression).name == name)
2017-12-16 02:27:39 +01:00
return true;
}
2017-12-16 02:27:39 +01:00
return false;
}
2017-12-13 04:46:05 +01:00
/** Indicates the specific kind of an {@link Element}. */
export enum ElementKind {
2017-12-16 17:54:53 +01:00
/** A {@link Global}. */
GLOBAL,
/** A {@link Local}. */
LOCAL,
2017-12-13 04:46:05 +01:00
/** An {@link Enum}. */
ENUM,
2017-12-13 04:46:05 +01:00
/** An {@link EnumValue}. */
ENUMVALUE,
2017-12-13 04:46:05 +01:00
/** A {@link FunctionPrototype}. */
2017-11-17 14:33:51 +01:00
FUNCTION_PROTOTYPE,
2017-12-13 04:46:05 +01:00
/** A {@link Function}. */
FUNCTION,
2017-12-16 17:54:53 +01:00
/** A {@link ClassPrototype}. */
CLASS_PROTOTYPE,
/** A {@link Class}. */
CLASS,
2017-12-13 04:46:05 +01:00
/** An {@link InterfacePrototype}. */
2017-11-17 14:33:51 +01:00
INTERFACE_PROTOTYPE,
2017-12-13 04:46:05 +01:00
/** An {@link Interface}. */
INTERFACE,
2017-12-16 17:54:53 +01:00
/** A {@link FieldPrototype}. */
FIELD_PROTOTYPE,
/** A {@link Field}. */
FIELD,
/** A {@link PropertyContainer}. */
2017-12-16 17:54:53 +01:00
PROPERTY,
2017-12-13 04:46:05 +01:00
/** A {@link Namespace}. */
NAMESPACE
}
2017-12-13 04:46:05 +01:00
/** Indicates traits of an {@link Element}. */
export enum ElementFlags {
/** No flags set. */
NONE = 0,
/** Is compiled. */
COMPILED = 1 << 0,
/** Is an import. */
IMPORTED = 1 << 1,
/** Is an export. */
EXPORTED = 1 << 2,
/** Is built-in. */
BUILTIN = 1 << 3,
/** Is declared. */
DECLARED = 1 << 4,
/** Is generic. */
GENERIC = 1 << 5,
/** Is constant. */
CONSTANT = 1 << 6,
/** Has constant value. */
CONSTANT_VALUE = 1 << 7,
/** Is instance member. */
INSTANCE = 1 << 8,
/** Is getter. */
GETTER = 1 << 9,
/** Is setter. */
2017-12-16 02:27:39 +01:00
SETTER = 1 << 10,
/** Is global. */
GLOBAL = 1 << 11,
/** Is read-only. */
READONLY = 1 << 12,
/** Is a public member. */
PUBLIC = 1 << 13,
/** Is a protected member. */
PROTECTED = 1 << 14,
/** Is a private member. */
PRIVATE = 1 << 15
2017-12-13 04:46:05 +01:00
}
2017-11-17 14:33:51 +01:00
/** Base class of all program elements. */
export abstract class Element {
2017-12-13 04:46:05 +01:00
/** Specific element kind. */
kind: ElementKind;
2017-12-13 04:46:05 +01:00
/** Containing {@link Program}. */
program: Program;
2017-12-27 22:38:32 +01:00
/** Simple name. */
simpleName: string;
2017-12-13 04:46:05 +01:00
/** Internal name referring to this element. */
internalName: string;
2017-12-13 04:46:05 +01:00
/** Element flags. */
flags: ElementFlags = ElementFlags.NONE;
/** Namespaced member elements. */
members: Map<string,Element> | null = null;
/** Parent namespace, if applicable. */
namespace: Element | null = null;
2017-12-13 04:46:05 +01:00
/** Constructs a new element, linking it to its containing {@link Program}. */
2017-12-27 22:38:32 +01:00
protected constructor(program: Program, simpleName: string, internalName: string) {
this.program = program;
2017-12-27 22:38:32 +01:00
this.simpleName = simpleName;
this.internalName = internalName;
}
2017-12-13 04:46:05 +01:00
/** Whether compiled or not. */
get isCompiled(): bool { return (this.flags & ElementFlags.COMPILED) != 0; }
set isCompiled(is: bool) { if (is) this.flags |= ElementFlags.COMPILED; else this.flags &= ~ElementFlags.COMPILED; }
/** Whether imported or not. */
get isImported(): bool { return (this.flags & ElementFlags.IMPORTED) != 0; }
set isImported(is: bool) { if (is) this.flags |= ElementFlags.IMPORTED; else this.flags &= ~ElementFlags.IMPORTED; }
/** Whether exported or not. */
get isExported(): bool { return (this.flags & ElementFlags.EXPORTED) != 0; }
set isExported(is: bool) { if (is) this.flags |= ElementFlags.EXPORTED; else this.flags &= ~ElementFlags.EXPORTED; }
/** Whether built-in or not. */
get isBuiltIn(): bool { return (this.flags & ElementFlags.BUILTIN) != 0; }
set isBuiltIn(is: bool) { if (is) this.flags |= ElementFlags.BUILTIN; else this.flags &= ~ElementFlags.BUILTIN; }
/** Whether declared or not. */
get isDeclared(): bool { return (this.flags & ElementFlags.DECLARED) != 0; }
set isDeclared(is: bool) { if (is) this.flags |= ElementFlags.DECLARED; else this.flags &= ~ElementFlags.DECLARED; }
/** Whether generic or not. */
get isGeneric(): bool { return (this.flags & ElementFlags.GENERIC) != 0; }
set isGeneric(is: bool) { if (is) this.flags |= ElementFlags.GENERIC; else this.flags &= ~ElementFlags.GENERIC; }
/** Whether constant or not. */
get isConstant(): bool { return (this.flags & ElementFlags.CONSTANT) != 0; }
set isConstant(is: bool) { if (is) this.flags |= ElementFlags.CONSTANT; else this.flags &= ~ElementFlags.CONSTANT; }
/** Whether mutable or not. */
get isMutable(): bool { return !(this.flags & ElementFlags.CONSTANT); } // reuses constant flag
set isMutable(is: bool) { if (is) this.flags &= ~ElementFlags.CONSTANT; else this.flags |= ElementFlags.CONSTANT; }
/** Whether this element has a constant value or not. */
get hasConstantValue(): bool { return (this.flags & ElementFlags.CONSTANT_VALUE) != 0; }
set hasConstantValue(is: bool) { if (is) this.flags |= ElementFlags.CONSTANT_VALUE; else this.flags &= ~ElementFlags.CONSTANT_VALUE; }
/** Whether an instance member or not. */
get isInstance(): bool { return (this.flags & ElementFlags.INSTANCE) != 0; }
set isInstance(is: bool) { if (is) this.flags |= ElementFlags.INSTANCE; else this.flags &= ~ElementFlags.INSTANCE; }
2017-12-16 02:27:39 +01:00
/** Whether a member of the global namespace or not. */
get isGlobal(): bool { return (this.flags & ElementFlags.GLOBAL) != 0; }
set isGlobal(is: bool) { if (is) this.flags |= ElementFlags.GLOBAL; else this.flags &= ~ElementFlags.GLOBAL; }
}
2017-12-16 02:27:39 +01:00
/** A namespace. */
2017-11-17 14:33:51 +01:00
export class Namespace extends Element {
// All elements have namespace semantics. This is an explicitly declared one.
2017-11-17 14:33:51 +01:00
kind = ElementKind.NAMESPACE;
2017-12-13 04:46:05 +01:00
/** Declaration reference. */
declaration: NamespaceDeclaration | null; // more specific
2017-11-17 14:33:51 +01:00
2017-12-13 04:46:05 +01:00
/** Constructs a new namespace. */
2017-12-27 22:38:32 +01:00
constructor(program: Program, simpleName: string, internalName: string, declaration: NamespaceDeclaration | null = null) {
super(program, simpleName, internalName);
2017-12-13 04:46:05 +01:00
if ((this.declaration = declaration) && this.declaration.modifiers) {
for (let i = 0, k = this.declaration.modifiers.length; i < k; ++i) {
2017-12-13 04:46:05 +01:00
switch (this.declaration.modifiers[i].modifierKind) {
case ModifierKind.IMPORT: this.isImported = true; break;
case ModifierKind.EXPORT: this.isExported = true; break;
case ModifierKind.DECLARE: this.isDeclared = true; break;
default: throw new Error("unexpected modifier");
}
}
}
2017-11-17 14:33:51 +01:00
}
}
/** An enum. */
export class Enum extends Element {
kind = ElementKind.ENUM;
2017-12-13 04:46:05 +01:00
/** Declaration reference. */
declaration: EnumDeclaration | null;
2017-12-13 04:46:05 +01:00
/** Constructs a new enum. */
2017-12-27 22:38:32 +01:00
constructor(program: Program, simpleName: string, internalName: string, declaration: EnumDeclaration | null = null) {
super(program, simpleName, internalName);
2017-12-13 04:46:05 +01:00
if ((this.declaration = declaration) && this.declaration.modifiers) {
for (let i = 0, k = this.declaration.modifiers.length; i < k; ++i) {
2017-12-13 04:46:05 +01:00
switch (this.declaration.modifiers[i].modifierKind) {
case ModifierKind.EXPORT: this.isExported = true; break;
case ModifierKind.IMPORT: this.isImported = true; break;
case ModifierKind.DECLARE: this.isDeclared = true; break;
case ModifierKind.CONST: this.isConstant = true; break;
default: throw new Error("unexpected modifier");
}
}
}
}
}
2017-11-17 14:33:51 +01:00
/** An enum value. */
export class EnumValue extends Element {
kind = ElementKind.ENUMVALUE;
2017-12-13 04:46:05 +01:00
/** Declaration reference. */
declaration: EnumValueDeclaration | null;
2017-12-13 04:46:05 +01:00
/** Parent enum. */
enum: Enum;
2017-12-13 04:46:05 +01:00
/** Constant value, if applicable. */
constantValue: i32 = 0;
2017-12-27 22:38:32 +01:00
constructor(enm: Enum, program: Program, simpleName: string, internalName: string, declaration: EnumValueDeclaration | null = null) {
super(program, simpleName, internalName);
this.enum = enm;
2017-12-16 02:27:39 +01:00
this.declaration = declaration;
}
}
2017-11-17 14:33:51 +01:00
/** A global variable. */
export class Global extends Element {
kind = ElementKind.GLOBAL;
2017-12-13 04:46:05 +01:00
/** Declaration reference. */
declaration: VariableLikeDeclarationStatement | null;
2017-12-13 04:46:05 +01:00
/** Resolved type, if resolved. */
type: Type | null;
2017-12-13 04:46:05 +01:00
/** Constant integer value, if applicable. */
2017-11-17 14:33:51 +01:00
constantIntegerValue: I64 | null = null;
2017-12-13 04:46:05 +01:00
/** Constant float value, if applicable. */
constantFloatValue: f64 = 0;
2017-12-27 22:38:32 +01:00
constructor(program: Program, simpleName: string, internalName: string, declaration: VariableLikeDeclarationStatement | null = null, type: Type | null = null) {
super(program, simpleName, internalName);
2017-12-13 04:46:05 +01:00
if (this.declaration = declaration) {
if (this.declaration.modifiers) {
for (let i = 0, k = this.declaration.modifiers.length; i < k; ++i) {
2017-12-13 04:46:05 +01:00
switch (this.declaration.modifiers[i].modifierKind) {
case ModifierKind.IMPORT: this.isImported = true; break;
case ModifierKind.EXPORT: this.isExported = true; break;
case ModifierKind.CONST: this.isConstant = true; break;
case ModifierKind.DECLARE: this.isDeclared = true; break;
case ModifierKind.STATIC: break; // static fields become globals
2017-12-13 04:46:05 +01:00
default: throw new Error("unexpected modifier");
}
}
}
} else {
this.hasConstantValue = true; // built-ins have constant values
}
this.type = type; // resolved later if `null`
}
2017-12-15 15:00:19 +01:00
withConstantIntegerValue(lo: i32, hi: i32): this {
this.constantIntegerValue = new I64(lo, hi);
this.hasConstantValue = true;
2017-12-15 17:23:04 +01:00
this.isMutable = false;
2017-12-15 15:00:19 +01:00
return this;
}
withConstantFloatValue(value: f64): this {
this.constantFloatValue = value;
this.hasConstantValue = true;
2017-12-15 17:23:04 +01:00
this.isMutable = false;
2017-12-15 15:00:19 +01:00
return this;
}
}
2017-11-17 14:33:51 +01:00
/** A function parameter. */
export class Parameter {
2017-12-13 04:46:05 +01:00
// not an Element on its own
/** Parameter name. */
name: string;
2017-12-13 04:46:05 +01:00
/** Parameter type. */
type: Type;
2017-12-13 04:46:05 +01:00
/** Parameter initializer. */
2017-11-20 23:39:50 +01:00
initializer: Expression | null;
2017-12-13 04:46:05 +01:00
/** Constructs a new function parameter. */
2017-11-20 23:39:50 +01:00
constructor(name: string, type: Type, initializer: Expression | null = null) {
this.name = name;
this.type = type;
2017-11-20 23:39:50 +01:00
this.initializer = initializer;
}
}
2017-11-17 14:33:51 +01:00
/** A function local. */
export class Local extends Element {
kind = ElementKind.LOCAL;
2017-12-13 04:46:05 +01:00
/** Local index. */
index: i32;
2017-12-13 04:46:05 +01:00
/** Local type. */
type: Type;
2017-12-27 22:38:32 +01:00
constructor(program: Program, simpleName: string, index: i32, type: Type) {
super(program, simpleName, simpleName);
this.index = index;
this.type = type;
}
}
2017-11-17 14:33:51 +01:00
/** A yet unresolved function prototype. */
export class FunctionPrototype extends Element {
2017-11-17 14:33:51 +01:00
kind = ElementKind.FUNCTION_PROTOTYPE;
2017-12-13 04:46:05 +01:00
/** Declaration reference. */
declaration: FunctionDeclaration | null;
2017-12-15 15:00:19 +01:00
/** If an instance method, the class prototype reference. */
2017-11-17 14:33:51 +01:00
classPrototype: ClassPrototype | null;
2017-12-13 04:46:05 +01:00
/** Resolved instances. */
2017-11-17 14:33:51 +01:00
instances: Map<string,Function> = new Map();
2017-12-13 04:46:05 +01:00
/** Constructs a new function prototype. */
2017-12-15 15:00:19 +01:00
constructor(program: Program, simpleName: string, internalName: string, declaration: FunctionDeclaration | null, classPrototype: ClassPrototype | null = null) {
2017-12-27 22:38:32 +01:00
super(program, simpleName, internalName);
2017-12-13 04:46:05 +01:00
if (this.declaration = declaration) {
if (this.declaration.modifiers)
for (let i = 0, k = this.declaration.modifiers.length; i < k; ++i) {
2017-12-13 04:46:05 +01:00
switch (this.declaration.modifiers[i].modifierKind) {
case ModifierKind.IMPORT: this.isImported = true; break;
case ModifierKind.EXPORT: this.isExported = true; break;
case ModifierKind.DECLARE: this.isDeclared = true; break;
case ModifierKind.GET: this.isGetter = true; break;
case ModifierKind.SET: this.isSetter = true; break;
2017-12-16 17:54:53 +01:00
case ModifierKind.STATIC:
case ModifierKind.ABSTRACT:
case ModifierKind.PRIVATE:
case ModifierKind.PROTECTED:
case ModifierKind.PUBLIC: break; // already handled
2017-12-13 04:46:05 +01:00
default: throw new Error("unexpected modifier");
}
}
if (this.declaration.typeParameters.length)
this.isGeneric = true;
}
if (this.classPrototype = classPrototype) {
this.isInstance = true;
}
}
2017-12-13 04:46:05 +01:00
/** Whether a getter function or not. */
get isGetter(): bool { return (this.flags & ElementFlags.GETTER) != 0; }
set isGetter(is: bool) { if (is) this.flags |= ElementFlags.GETTER; else this.flags &= ~ElementFlags.GETTER; }
/** Whether a setter function or not. */
get isSetter(): bool { return (this.flags & ElementFlags.SETTER) != 0; }
set isSetter(is: bool) { if (is) this.flags |= ElementFlags.SETTER; else this.flags &= ~ElementFlags.SETTER; }
// Whether a getter/setter function or not.
get isAccessor(): bool { return (this.flags & (ElementFlags.GETTER | ElementFlags.SETTER)) != 0; }
resolve(typeArguments: Type[] | null = null, contextualTypeArguments: Map<string,Type> | null = null): Function | null {
const instanceKey = typeArguments ? typesToString(typeArguments, "", "") : "";
let instance = this.instances.get(instanceKey);
if (instance)
return instance;
const declaration = this.declaration;
if (!declaration)
throw new Error("declaration expected"); // cannot resolve built-ins
// override call specific contextual type arguments
let i: i32, k: i32;
if (typeArguments && (k = typeArguments.length)) {
const inheritedTypeArguments: Map<string,Type> | null = contextualTypeArguments;
contextualTypeArguments = new Map();
if (inheritedTypeArguments)
for (let [name, type] of inheritedTypeArguments)
contextualTypeArguments.set(name, type);
for (i = 0; i < k; ++i)
contextualTypeArguments.set(declaration.typeParameters[i].identifier.name, typeArguments[i]);
}
// resolve parameters
k = declaration.parameters.length;
const parameters = new Array<Parameter>(k);
const parameterTypes = new Array<Type>(k);
let typeNode: TypeNode | null;
2017-12-24 03:19:47 +01:00
for (i = 0; i < k; ++i) {
if (typeNode = declaration.parameters[i].type) {
const type = this.program.resolveType(typeNode, contextualTypeArguments, true); // reports
if (type) {
2017-12-18 03:46:36 +01:00
parameters[i] = new Parameter(declaration.parameters[i].name.name, type);
parameterTypes[i] = type;
} else
return null;
} else
2017-12-15 15:00:19 +01:00
return null;
}
// resolve return type
let returnType: Type;
if (this.isSetter) {
returnType = Type.void; // not annotated
} else {
if (typeNode = declaration.returnType) {
const type = this.program.resolveType(<TypeNode>typeNode, contextualTypeArguments, true); // reports
if (type)
returnType = type;
else
return null;
} else
return null;
}
let internalName = this.internalName;
if (instanceKey.length)
internalName += "<" + instanceKey + ">";
2017-11-17 14:33:51 +01:00
instance = new Function(this, internalName, typeArguments, parameters, returnType, null); // TODO: class
this.instances.set(instanceKey, instance);
return instance;
}
2017-11-20 23:39:50 +01:00
resolveInclTypeArguments(typeArgumentNodes: TypeNode[] | null, contextualTypeArguments: Map<string,Type> | null, alternativeReportNode: Node | null): Function | null {
let resolvedTypeArguments: Type[] | null;
if (this.isGeneric) {
2017-12-24 03:19:47 +01:00
assert(typeArgumentNodes != null && typeArgumentNodes.length != 0);
2017-11-20 23:39:50 +01:00
if (!this.declaration)
throw new Error("declaration expected");
2017-11-20 23:39:50 +01:00
resolvedTypeArguments = this.program.resolveTypeArguments(this.declaration.typeParameters, typeArgumentNodes, contextualTypeArguments, alternativeReportNode);
if (!resolvedTypeArguments)
return null;
} else {
2017-12-15 15:00:19 +01:00
assert(typeArgumentNodes == null || typeArgumentNodes.length == 0);
2017-11-20 23:39:50 +01:00
resolvedTypeArguments = [];
}
return this.resolve(resolvedTypeArguments, contextualTypeArguments);
}
2017-12-15 15:00:19 +01:00
toString(): string { return this.simpleName; }
}
2017-11-17 14:33:51 +01:00
/** A resolved function. */
export class Function extends Element {
2017-11-17 14:33:51 +01:00
kind = ElementKind.FUNCTION;
2017-11-20 23:39:50 +01:00
2017-12-13 04:46:05 +01:00
/** Prototype reference. */
prototype: FunctionPrototype;
2017-11-20 23:39:50 +01:00
/** Concrete type arguments. */
typeArguments: Type[] | null;
2017-11-26 04:03:28 +01:00
/** Concrete function parameters. Excluding `this` if an instance method. */
parameters: Parameter[];
2017-11-20 23:39:50 +01:00
/** Concrete return type. */
returnType: Type;
2017-12-15 15:00:19 +01:00
/** If an instance method, the concrete class it is a member of. */
2017-11-17 14:33:51 +01:00
instanceMethodOf: Class | null;
2017-11-20 23:39:50 +01:00
/** Map of locals by name. */
locals: Map<string,Local> = new Map();
2017-11-20 23:39:50 +01:00
/** List of additional non-parameter locals. */
additionalLocals: Type[] = [];
/** Current break context label. */
breakContext: string | null = null;
2017-11-20 23:39:50 +01:00
/** Contextual type arguments. */
2017-12-15 15:00:19 +01:00
contextualTypeArguments: Map<string,Type> | null;
2017-12-08 16:11:58 +01:00
private nextBreakId: i32 = 0;
private breakStack: i32[] | null = null;
2017-11-20 23:39:50 +01:00
/** Constructs a new concrete function. */
constructor(prototype: FunctionPrototype, internalName: string, typeArguments: Type[] | null, parameters: Parameter[], returnType: Type, instanceMethodOf: Class | null) {
2017-12-27 22:38:32 +01:00
super(prototype.program, prototype.simpleName, internalName);
2017-12-13 04:46:05 +01:00
this.prototype = prototype;
this.typeArguments = typeArguments;
this.parameters = parameters;
this.returnType = returnType;
this.instanceMethodOf = instanceMethodOf;
2017-12-13 04:46:05 +01:00
this.flags = prototype.flags;
let localIndex = 0;
if (instanceMethodOf) {
2017-12-15 15:00:19 +01:00
assert(this.isInstance);
2017-12-01 02:08:03 +01:00
this.locals.set("this", new Local(prototype.program, "this", localIndex++, instanceMethodOf.type));
2017-12-15 15:00:19 +01:00
if (instanceMethodOf.contextualTypeArguments) {
if (!this.contextualTypeArguments) this.contextualTypeArguments = new Map();
for (let [name, type] of instanceMethodOf.contextualTypeArguments)
this.contextualTypeArguments.set(name, type);
}
} else
assert(!this.isInstance);
for (let i = 0, k = parameters.length; i < k; ++i) {
const parameter = parameters[i];
2017-12-01 02:08:03 +01:00
this.locals.set(parameter.name, new Local(prototype.program, parameter.name, localIndex++, parameter.type));
}
}
2017-11-20 23:39:50 +01:00
/** Adds a local of the specified type, with an optional name. */
addLocal(type: Type, name: string | null = null): Local {
// if it has a name, check previously as this method will throw otherwise
let localIndex = this.parameters.length + this.additionalLocals.length;
2017-11-26 04:03:28 +01:00
if (this.isInstance) localIndex++; // plus 'this'
const local = new Local(this.prototype.program, name ? name : "anonymous$" + localIndex.toString(10), localIndex, type);
if (name) {
if (this.locals.has(name))
throw new Error("unexpected duplicate local name");
this.locals.set(name, local);
}
this.additionalLocals.push(type);
return local;
}
2017-12-13 23:24:13 +01:00
private tempI32s: Local[] | null = null;
private tempI64s: Local[] | null = null;
private tempF32s: Local[] | null = null;
private tempF64s: Local[] | null = null;
2017-12-13 23:24:13 +01:00
/** Gets a free temporary local of the specified type. */
getTempLocal(type: Type): Local {
2017-12-13 23:24:13 +01:00
let temps: Local[] | null;
2017-12-24 03:19:47 +01:00
switch (type.toNativeType()) {
case NativeType.I32: temps = this.tempI32s; break;
case NativeType.I64: temps = this.tempI64s; break;
case NativeType.F32: temps = this.tempF32s; break;
case NativeType.F64: temps = this.tempF64s; break;
default: throw new Error("unexpected type");
}
2017-12-13 23:24:13 +01:00
if (temps && temps.length > 0)
return temps.pop();
return this.addLocal(type);
}
2017-12-13 23:24:13 +01:00
/** Frees the temporary local for reuse. */
freeTempLocal(local: Local): void {
let temps: Local[];
2017-12-24 03:19:47 +01:00
switch (local.type.toNativeType()) {
2017-12-13 23:24:13 +01:00
case NativeType.I32: temps = this.tempI32s || (this.tempI32s = []); break;
case NativeType.I64: temps = this.tempI64s || (this.tempI64s = []); break;
case NativeType.F32: temps = this.tempF32s || (this.tempF32s = []); break;
case NativeType.F64: temps = this.tempF64s || (this.tempF64s = []); break;
default: throw new Error("unexpected type");
}
temps.push(local);
}
2017-12-13 23:24:13 +01:00
/** Gets and immediately frees a temporary local of the specified type. */
getAndFreeTempLocal(type: Type): Local {
let temps: Local[];
2017-12-24 03:19:47 +01:00
switch (type.toNativeType()) {
2017-12-13 23:24:13 +01:00
case NativeType.I32: temps = this.tempI32s || (this.tempI32s = []); break;
case NativeType.I64: temps = this.tempI64s || (this.tempI64s = []); break;
case NativeType.F32: temps = this.tempF32s || (this.tempF32s = []); break;
case NativeType.F64: temps = this.tempF64s || (this.tempF64s = []); break;
default: throw new Error("unexpected type");
}
if (temps.length > 0)
return temps[temps.length - 1];
let local: Local = this.addLocal(type);
temps.push(local);
return local;
}
2017-11-20 23:39:50 +01:00
/** Enters a(nother) break context. */
enterBreakContext(): string {
const id = this.nextBreakId++;
2017-12-08 16:11:58 +01:00
if (!this.breakStack)
this.breakStack = [ id ];
else
this.breakStack.push(id);
return this.breakContext = id.toString(10);
}
2017-11-20 23:39:50 +01:00
/** Leaves the current break context. */
leaveBreakContext(): void {
2017-12-08 16:11:58 +01:00
assert(this.breakStack != null);
const length = (<i32[]>this.breakStack).length;
2017-12-08 16:11:58 +01:00
assert(length > 0);
(<i32[]>this.breakStack).pop();
if (length > 1) {
2017-12-24 03:19:47 +01:00
this.breakContext = (<i32[]>this.breakStack)[length - 2].toString(10);
2017-12-08 16:11:58 +01:00
} else {
this.breakContext = null;
2017-12-08 16:11:58 +01:00
this.breakStack = null;
}
}
2017-12-15 15:00:19 +01:00
/** Finalizes the function once compiled, releasing no longer needed resources. */
finalize(): void {
assert(!this.breakStack || !this.breakStack.length, "break stack is not empty");
this.breakStack = null;
this.breakContext = null;
this.tempI32s = this.tempI64s = this.tempF32s = this.tempF64s = null;
}
2017-12-16 17:54:53 +01:00
/** Returns the TypeScript representation of this function. */
2017-12-15 15:00:19 +01:00
toString(): string { return this.prototype.simpleName; }
2017-12-16 17:54:53 +01:00
/** Returns the function type TypeScript representation of this function.*/
toTypeString(): string { throw new Error("not implemented"); }
}
2017-11-17 14:33:51 +01:00
/** A yet unresolved instance field prototype. */
export class FieldPrototype extends Element {
2017-11-17 14:33:51 +01:00
kind = ElementKind.FIELD_PROTOTYPE;
2017-12-13 04:46:05 +01:00
/** Declaration reference. */
2017-11-17 14:33:51 +01:00
declaration: FieldDeclaration | null;
2017-12-13 04:46:05 +01:00
/** Parent class prototype. */
2017-11-17 14:33:51 +01:00
classPrototype: ClassPrototype;
2017-12-13 04:46:05 +01:00
/** Constructs a new field prototype. */
2017-12-27 22:38:32 +01:00
constructor(classPrototype: ClassPrototype, simpleName: string, internalName: string, declaration: FieldDeclaration | null = null) {
super(classPrototype.program, simpleName, internalName);
2017-11-17 14:33:51 +01:00
this.classPrototype = classPrototype;
2017-12-13 04:46:05 +01:00
if ((this.declaration = declaration) && this.declaration.modifiers) {
for (let i = 0, k = this.declaration.modifiers.length; i < k; ++i) {
2017-12-13 04:46:05 +01:00
switch (this.declaration.modifiers[i].modifierKind) {
case ModifierKind.EXPORT: this.isExported = true; break;
2017-12-16 02:27:39 +01:00
case ModifierKind.READONLY: this.isReadonly = true; break;
case ModifierKind.PRIVATE:
case ModifierKind.PROTECTED:
case ModifierKind.PUBLIC:
2017-12-16 02:27:39 +01:00
case ModifierKind.STATIC: break; // already handled
2017-12-24 03:19:47 +01:00
default: assert(false);
2017-12-13 04:46:05 +01:00
}
}
}
2017-11-17 14:33:51 +01:00
}
2017-12-16 02:27:39 +01:00
/** Whether the field is read-only or not. */
get isReadonly(): bool { return (this.flags & ElementFlags.READONLY) != 0; }
set isReadonly(is: bool) { if (is) this.flags |= ElementFlags.READONLY; else this.flags &= ~ElementFlags.READONLY; }
2017-11-17 14:33:51 +01:00
}
/** A resolved instance field. */
export class Field extends Element {
kind = ElementKind.FIELD;
2017-12-13 04:46:05 +01:00
/** Field prototype reference. */
prototype: FieldPrototype;
/** Resolved type. */
2017-11-17 14:33:51 +01:00
type: Type;
2017-12-13 04:46:05 +01:00
/** Constant integer value, if applicable. */
2017-11-17 14:33:51 +01:00
constantIntegerValue: I64 | null = null;
2017-12-13 04:46:05 +01:00
/** Constant float value, if applicable. */
2017-11-17 14:33:51 +01:00
constantFloatValue: f64 = 0;
2017-12-13 04:46:05 +01:00
/** Constructs a new field. */
constructor(prototype: FieldPrototype, internalName: string, type: Type) {
2017-12-27 22:38:32 +01:00
super(prototype.program, prototype.simpleName, internalName);
2017-12-13 04:46:05 +01:00
this.flags = prototype.flags;
2017-11-17 14:33:51 +01:00
this.type = type;
}
}
/** A property comprised of a getter and a setter function. */
export class Property extends Element {
2017-12-16 17:54:53 +01:00
kind = ElementKind.PROPERTY;
2017-12-16 17:54:53 +01:00
/** Parent class prototype. */
parent: ClassPrototype;
/** Getter prototype. */
getterPrototype: FunctionPrototype | null = null;
/** Setter prototype. */
setterPrototype: FunctionPrototype | null = null;
/** Constructs a new property prototype. */
constructor(program: Program, simpleName: string, internalName: string, parent: ClassPrototype) {
2017-12-27 22:38:32 +01:00
super(program, simpleName, internalName);
this.parent = parent;
2017-12-16 17:54:53 +01:00
}
}
2017-11-17 14:33:51 +01:00
/** A yet unresolved class prototype. */
2017-12-13 23:24:13 +01:00
export class ClassPrototype extends Element {
2017-11-17 14:33:51 +01:00
kind = ElementKind.CLASS_PROTOTYPE;
2017-12-13 04:46:05 +01:00
/** Declaration reference. */
declaration: ClassDeclaration | null;
2017-12-13 04:46:05 +01:00
/** Resolved instances. */
2017-12-13 23:24:13 +01:00
instances: Map<string,Class> = new Map();
/** Instance member prototypes. */
instanceMembers: Map<string,Element> | null = null;
2017-12-15 15:00:19 +01:00
constructor(program: Program, simpleName: string, internalName: string, declaration: ClassDeclaration | null = null) {
2017-12-27 22:38:32 +01:00
super(program, simpleName, internalName);
2017-12-13 04:46:05 +01:00
if (this.declaration = declaration) {
if (this.declaration.modifiers) {
for (let i = 0, k = this.declaration.modifiers.length; i < k; ++i) {
2017-12-13 04:46:05 +01:00
switch (this.declaration.modifiers[i].modifierKind) {
case ModifierKind.IMPORT: this.isImported = true; break;
case ModifierKind.EXPORT: this.isExported = true; break;
case ModifierKind.DECLARE: this.isDeclared = true; break;
default: throw new Error("unexpected modifier");
}
}
}
if (this.declaration.typeParameters.length)
this.isGeneric = true;
}
}
2017-11-17 14:33:51 +01:00
resolve(typeArguments: Type[], contextualTypeArguments: Map<string,Type> | null): Class {
const instanceKey = typesToString(typeArguments, "", "");
let instance = this.instances.get(instanceKey);
if (instance)
return instance;
const declaration = this.declaration;
2017-12-15 15:00:19 +01:00
if (!declaration)
throw new Error("declaration expected"); // cannot resolve built-ins
2017-12-15 15:00:19 +01:00
// override call specific contextual type arguments
let i: i32, k = typeArguments.length;
2017-12-15 15:00:19 +01:00
if (k) {
const inheritedTypeArguments: Map<string,Type> | null = contextualTypeArguments;
contextualTypeArguments = new Map();
if (inheritedTypeArguments)
for (let [name, type] of inheritedTypeArguments)
contextualTypeArguments.set(name, type);
for (i = 0; i < k; ++i)
contextualTypeArguments.set(declaration.typeParameters[i].identifier.name, typeArguments[i]);
}
// TODO: set up instance fields and methods
if (this.instanceMembers)
for (let [key, member] of this.instanceMembers) {
switch (member.kind) {
case ElementKind.FIELD_PROTOTYPE: break;
case ElementKind.FUNCTION_PROTOTYPE: break;
default: throw new Error("unexpected instance member");
}
}
let internalName = this.internalName;
2017-12-15 15:00:19 +01:00
if (instanceKey.length)
internalName += "<" + instanceKey + ">";
instance = new Class(this, internalName, typeArguments, null); // TODO: base class
instance.contextualTypeArguments = contextualTypeArguments;
this.instances.set(instanceKey, instance);
return instance;
}
2017-11-20 23:39:50 +01:00
resolveInclTypeArguments(typeArgumentNodes: TypeNode[] | null, contextualTypeArguments: Map<string,Type> | null, alternativeReportNode: Node | null): Class | null {
let resolvedTypeArguments: Type[] | null;
if (this.isGeneric) {
2017-12-15 15:00:19 +01:00
assert(typeArgumentNodes != null && typeArgumentNodes.length != 0);
2017-11-20 23:39:50 +01:00
if (!this.declaration)
throw new Error("declaration expected"); // generic built-in
2017-11-20 23:39:50 +01:00
resolvedTypeArguments = this.program.resolveTypeArguments(this.declaration.typeParameters, typeArgumentNodes, contextualTypeArguments, alternativeReportNode);
if (!resolvedTypeArguments)
return null;
} else {
2017-12-15 15:00:19 +01:00
assert(typeArgumentNodes == null || !typeArgumentNodes.length);
2017-11-20 23:39:50 +01:00
resolvedTypeArguments = [];
}
return this.resolve(resolvedTypeArguments, contextualTypeArguments);
}
2017-12-15 15:00:19 +01:00
toString(): string { return this.simpleName; }
}
2017-11-17 14:33:51 +01:00
/** A resolved class. */
2017-12-13 23:24:13 +01:00
export class Class extends Element {
2017-11-17 14:33:51 +01:00
kind = ElementKind.CLASS;
2017-12-13 04:46:05 +01:00
/** Prototype reference. */
prototype: ClassPrototype;
/** Resolved type arguments. */
typeArguments: Type[];
2017-12-13 04:46:05 +01:00
/** Resolved class type. */
type: Type;
2017-12-13 04:46:05 +01:00
/** Base class, if applicable. */
base: Class | null;
2017-12-15 15:00:19 +01:00
/** Contextual type arguments for fields and methods. */
contextualTypeArguments: Map<string,Type> | null = null;
2017-12-13 04:46:05 +01:00
/** Constructs a new class. */
constructor(prototype: ClassPrototype, internalName: string, typeArguments: Type[] = [], base: Class | null = null) {
2017-12-27 22:38:32 +01:00
super(prototype.program, prototype.simpleName, internalName);
2017-12-13 04:46:05 +01:00
this.prototype = prototype;
this.flags = prototype.flags;
this.typeArguments = typeArguments;
2017-12-13 04:46:05 +01:00
this.type = (prototype.program.target == Target.WASM64 ? Type.usize64 : Type.usize32).asClass(this);
2017-12-13 23:24:13 +01:00
this.base = base;
2017-12-13 23:24:13 +01:00
// inherit contextual type arguments from base class
2017-12-15 15:00:19 +01:00
if (base && base.contextualTypeArguments) {
if (!this.contextualTypeArguments) this.contextualTypeArguments = new Map();
for (let [name, type] of base.contextualTypeArguments)
this.contextualTypeArguments.set(name, type);
2017-12-15 15:00:19 +01:00
}
// apply instance-specific contextual type arguments
2017-12-13 04:46:05 +01:00
const declaration: ClassDeclaration | null = this.prototype.declaration;
2017-12-05 13:35:14 +01:00
if (declaration) { // irrelevant for built-ins
const typeParameters: TypeParameter[] = declaration.typeParameters;
if (typeParameters.length != typeArguments.length)
throw new Error("unexpected type argument count mismatch");
const k = typeArguments.length;
2017-12-15 15:00:19 +01:00
if (k) {
if (!this.contextualTypeArguments)
this.contextualTypeArguments = new Map();
for (let i = 0; i < k; ++i)
2017-12-15 15:00:19 +01:00
this.contextualTypeArguments.set(typeParameters[i].identifier.name, typeArguments[i]);
}
}
}
2017-12-01 02:08:03 +01:00
2017-12-15 15:00:19 +01:00
toString(): string { return this.prototype.simpleName; }
}
2017-12-13 23:24:13 +01:00
/** A yet unresolved interface. */
2017-11-17 14:33:51 +01:00
export class InterfacePrototype extends ClassPrototype {
2017-11-17 14:33:51 +01:00
kind = ElementKind.INTERFACE_PROTOTYPE;
2017-12-13 04:46:05 +01:00
/** Declaration reference. */
2017-12-13 23:24:13 +01:00
declaration: InterfaceDeclaration | null; // more specific
2017-12-13 04:46:05 +01:00
/** Constructs a new interface prototype. */
2017-12-15 15:00:19 +01:00
constructor(program: Program, simpleName: string, internalName: string, declaration: InterfaceDeclaration | null = null) {
super(program, simpleName, internalName, declaration);
}
}
2017-11-17 14:33:51 +01:00
/** A resolved interface. */
export class Interface extends Class {
2017-11-17 14:33:51 +01:00
kind = ElementKind.INTERFACE;
2017-12-13 04:46:05 +01:00
/** Prototype reference. */
2017-12-13 23:24:13 +01:00
prototype: InterfacePrototype; // more specific
2017-12-13 04:46:05 +01:00
/** Base interface, if applcable. */
2017-12-13 23:24:13 +01:00
base: Interface | null; // more specific
2017-12-13 04:46:05 +01:00
/** Constructs a new interface. */
constructor(prototype: InterfacePrototype, internalName: string, typeArguments: Type[] = [], base: Interface | null = null) {
super(prototype, internalName, typeArguments, base);
2017-09-28 13:08:25 +02:00
}
}