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 {
|
2018-01-21 17:52:44 +01:00
|
|
|
Options
|
2017-12-24 03:19:47 +01:00
|
|
|
} from "./compiler";
|
|
|
|
|
|
|
|
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,
|
2017-10-19 18:55:27 +02:00
|
|
|
Range,
|
|
|
|
TypeNode,
|
2017-12-24 03:19:47 +01:00
|
|
|
TypeParameter,
|
|
|
|
Decorator,
|
2018-01-06 10:20:38 +01:00
|
|
|
DecoratorKind,
|
2017-12-24 03:19:47 +01:00
|
|
|
|
2017-10-19 18:55:27 +02:00
|
|
|
Expression,
|
2018-01-06 10:20:38 +01:00
|
|
|
ElementAccessExpression,
|
2017-10-19 18:55:27 +02:00
|
|
|
IdentifierExpression,
|
2018-01-06 10:20:38 +01:00
|
|
|
LiteralExpression,
|
|
|
|
LiteralKind,
|
2017-10-19 18:55:27 +02:00
|
|
|
PropertyAccessExpression,
|
|
|
|
StringLiteralExpression,
|
2018-01-06 10:20:38 +01:00
|
|
|
SuperExpression,
|
|
|
|
ThisExpression,
|
2017-12-16 02:27:39 +01:00
|
|
|
CallExpression,
|
2018-01-01 20:27:21 +01:00
|
|
|
NewExpression,
|
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,
|
2017-09-29 17:25:02 +02:00
|
|
|
ExportMember,
|
|
|
|
ExportStatement,
|
2017-09-28 13:08:25 +02:00
|
|
|
FieldDeclaration,
|
|
|
|
FunctionDeclaration,
|
|
|
|
ImportDeclaration,
|
|
|
|
ImportStatement,
|
|
|
|
InterfaceDeclaration,
|
|
|
|
MethodDeclaration,
|
|
|
|
NamespaceDeclaration,
|
2017-12-19 17:49:15 +01:00
|
|
|
TypeDeclaration,
|
2017-10-19 18:55:27 +02:00
|
|
|
VariableLikeDeclarationStatement,
|
2017-09-28 13:08:25 +02:00
|
|
|
VariableDeclaration,
|
2017-10-11 17:03:22 +02:00
|
|
|
VariableStatement,
|
|
|
|
|
2018-01-01 20:27:21 +01:00
|
|
|
hasDecorator,
|
2017-12-27 02:37:53 +01:00
|
|
|
hasModifier,
|
2018-01-03 18:33:27 +01:00
|
|
|
mangleInternalName,
|
2018-01-06 10:20:38 +01:00
|
|
|
getFirstDecorator
|
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
|
|
|
|
2018-01-19 16:13:14 +01:00
|
|
|
/** Path delimiter inserted between file system levels. */
|
|
|
|
export const PATH_DELIMITER = "/";
|
|
|
|
/** Substitution used to indicate the parent directory. */
|
|
|
|
export const PARENT_SUBST = "..";
|
|
|
|
/** Function name prefix used for getters. */
|
|
|
|
export const GETTER_PREFIX = "get:";
|
|
|
|
/** Function name prefix used for setters. */
|
|
|
|
export const SETTER_PREFIX = "set:";
|
|
|
|
/** Delimiter used between class names and instance members. */
|
|
|
|
export const INSTANCE_DELIMITER = "#";
|
|
|
|
/** Delimiter used between class and namespace names and static members. */
|
|
|
|
export const STATIC_DELIMITER = ".";
|
|
|
|
|
2017-09-29 17:25:02 +02:00
|
|
|
class QueuedExport {
|
2017-12-02 01:14:15 +01:00
|
|
|
isReExport: bool;
|
2017-10-11 17:03:22 +02:00
|
|
|
referencedName: string;
|
2017-09-29 17:25:02 +02:00
|
|
|
member: ExportMember;
|
|
|
|
}
|
|
|
|
|
|
|
|
class QueuedImport {
|
2017-10-02 12:52:15 +02:00
|
|
|
internalName: string;
|
2017-12-02 01:14:15 +01:00
|
|
|
referencedName: string;
|
2017-09-29 17:25:02 +02:00
|
|
|
declaration: ImportDeclaration;
|
|
|
|
}
|
|
|
|
|
2017-12-28 04:09:40 +01:00
|
|
|
const noTypesYet = new Map<string,Type>();
|
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 {
|
|
|
|
|
2017-10-19 18:55:27 +02:00
|
|
|
/** Array of source files. */
|
2017-09-28 13:08:25 +02:00
|
|
|
sources: Source[];
|
2017-10-19 18:55:27 +02:00
|
|
|
/** Diagnostic offset used where sequentially obtaining the next diagnostic. */
|
2017-09-28 13:08:25 +02:00
|
|
|
diagnosticsOffset: i32 = 0;
|
2018-01-21 17:52:44 +01:00
|
|
|
/** Compiler options. */
|
|
|
|
options: Options;
|
2017-10-19 18:55:27 +02:00
|
|
|
/** 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();
|
2018-01-18 01:46:41 +01:00
|
|
|
/** Exports of individual files by exported internal name. Not global exports. */
|
2017-10-19 18:55:27 +02:00
|
|
|
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);
|
2017-12-27 02:37:53 +01:00
|
|
|
this.sources = [];
|
2017-09-28 13:08:25 +02:00
|
|
|
}
|
|
|
|
|
2017-10-19 18:55:27 +02:00
|
|
|
/** Initializes the program and its elements prior to compilation. */
|
2018-01-21 17:52:44 +01:00
|
|
|
initialize(options: Options): void {
|
|
|
|
this.options = options;
|
2017-12-11 02:03:15 +01:00
|
|
|
|
2017-12-05 01:45:15 +01:00
|
|
|
this.types = new Map([
|
|
|
|
["i8", Type.i8],
|
|
|
|
["i16", Type.i16],
|
|
|
|
["i32", Type.i32],
|
|
|
|
["i64", Type.i64],
|
2018-01-21 17:52:44 +01:00
|
|
|
["isize", options.isizeType],
|
2017-12-05 01:45:15 +01:00
|
|
|
["u8", Type.u8],
|
|
|
|
["u16", Type.u16],
|
|
|
|
["u32", Type.u32],
|
|
|
|
["u64", Type.u64],
|
2018-01-21 17:52:44 +01:00
|
|
|
["usize", options.usizeType],
|
2017-12-05 01:45:15 +01:00
|
|
|
["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
|
|
|
|
2017-12-28 04:09:40 +01:00
|
|
|
var queuedExports = new Map<string,QueuedExport>();
|
|
|
|
var queuedImports = new Array<QueuedImport>();
|
2018-01-07 15:07:46 +01:00
|
|
|
var queuedDerivedClasses = new Array<ClassPrototype>();
|
2017-09-28 13:08:25 +02:00
|
|
|
|
2017-10-11 17:03:22 +02:00
|
|
|
// build initial lookup maps of internal names to declarations
|
2017-12-28 04:09:40 +01:00
|
|
|
for (var i = 0, k = this.sources.length; i < k; ++i) {
|
|
|
|
var source = this.sources[i];
|
|
|
|
var statements = source.statements;
|
|
|
|
for (var j = 0, l = statements.length; j < l; ++j) {
|
|
|
|
var statement = statements[j];
|
2017-09-28 13:08:25 +02:00
|
|
|
switch (statement.kind) {
|
|
|
|
|
2018-01-15 19:00:18 +01:00
|
|
|
case NodeKind.CLASSDECLARATION:
|
2018-01-07 15:07:46 +01:00
|
|
|
this.initializeClass(<ClassDeclaration>statement, queuedDerivedClasses);
|
2017-09-28 13:08:25 +02:00
|
|
|
break;
|
|
|
|
|
2018-01-15 19:00:18 +01:00
|
|
|
case NodeKind.ENUMDECLARATION:
|
2017-09-28 13:08:25 +02:00
|
|
|
this.initializeEnum(<EnumDeclaration>statement);
|
|
|
|
break;
|
|
|
|
|
2017-09-29 17:25:02 +02:00
|
|
|
case NodeKind.EXPORT:
|
2017-10-02 12:52:15 +02:00
|
|
|
this.initializeExports(<ExportStatement>statement, queuedExports);
|
2017-09-29 17:25:02 +02:00
|
|
|
break;
|
|
|
|
|
2018-01-15 19:00:18 +01:00
|
|
|
case NodeKind.FUNCTIONDECLARATION:
|
2017-09-28 13:08:25 +02:00
|
|
|
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;
|
|
|
|
|
2018-01-15 19:00:18 +01:00
|
|
|
case NodeKind.INTERFACEDECLARATION:
|
2017-09-28 13:08:25 +02:00
|
|
|
this.initializeInterface(<InterfaceDeclaration>statement);
|
|
|
|
break;
|
|
|
|
|
2018-01-15 19:00:18 +01:00
|
|
|
case NodeKind.NAMESPACEDECLARATION:
|
2018-01-07 15:07:46 +01:00
|
|
|
this.initializeNamespace(<NamespaceDeclaration>statement, queuedDerivedClasses, null);
|
2017-09-28 13:08:25 +02:00
|
|
|
break;
|
|
|
|
|
2017-12-19 17:49:15 +01:00
|
|
|
case NodeKind.TYPEDECLARATION:
|
2018-01-01 20:27:21 +01:00
|
|
|
this.initializeTypeAlias(<TypeDeclaration>statement);
|
2017-12-19 17:49:15 +01:00
|
|
|
break;
|
|
|
|
|
2017-09-28 13:08:25 +02:00
|
|
|
case NodeKind.VARIABLE:
|
|
|
|
this.initializeVariables(<VariableStatement>statement);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-28 04:09:40 +01:00
|
|
|
var element: Element | null;
|
2017-12-02 01:14:15 +01:00
|
|
|
|
2017-12-13 04:46:05 +01:00
|
|
|
// queued imports should be resolvable now through traversing exports and queued exports
|
2017-12-28 04:09:40 +01:00
|
|
|
for (i = 0; i < queuedImports.length;) {
|
|
|
|
var queuedImport = queuedImports[i];
|
2017-12-02 01:14:15 +01:00
|
|
|
element = this.tryResolveImport(queuedImport.referencedName, queuedExports);
|
|
|
|
if (element) {
|
|
|
|
this.elements.set(queuedImport.internalName, element);
|
2017-12-28 04:09:40 +01:00
|
|
|
queuedImports.splice(i, 1);
|
2017-12-02 01:14:15 +01:00
|
|
|
} else {
|
|
|
|
this.error(DiagnosticCode.Module_0_has_no_exported_member_1, queuedImport.declaration.range, (<ImportStatement>queuedImport.declaration.parent).path.value, queuedImport.declaration.externalIdentifier.name);
|
2017-12-28 04:09:40 +01:00
|
|
|
++i;
|
2017-09-29 17:25:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-13 04:46:05 +01:00
|
|
|
// queued exports should be resolvable now that imports are finalized
|
2017-12-28 04:09:40 +01:00
|
|
|
for (var [exportName, queuedExport] of queuedExports) {
|
|
|
|
var currentExport: QueuedExport | null = queuedExport; // nullable below
|
2017-12-02 01:14:15 +01:00
|
|
|
do {
|
|
|
|
if (currentExport.isReExport) {
|
2017-12-27 02:37:53 +01:00
|
|
|
element = this.exports.get(currentExport.referencedName);
|
2017-12-02 01:14:15 +01:00
|
|
|
if (element) {
|
|
|
|
this.exports.set(exportName, element);
|
|
|
|
break;
|
|
|
|
}
|
2017-12-27 02:37:53 +01:00
|
|
|
currentExport = queuedExports.get(currentExport.referencedName);
|
2017-12-02 01:14:15 +01:00
|
|
|
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 {
|
2018-01-17 02:08:14 +01:00
|
|
|
if (
|
|
|
|
(element = this.elements.get(currentExport.referencedName)) || // normal export
|
|
|
|
(element = this.elements.get(currentExport.member.identifier.name)) // stdlib re-export
|
|
|
|
)
|
2017-12-02 01:14:15 +01:00
|
|
|
this.exports.set(exportName, element);
|
|
|
|
else
|
|
|
|
this.error(DiagnosticCode.Cannot_find_name_0, queuedExport.member.range, queuedExport.member.identifier.name);
|
2017-09-29 17:25:02 +02:00
|
|
|
break;
|
|
|
|
}
|
2017-12-02 01:14:15 +01:00
|
|
|
} while (currentExport);
|
2017-09-28 13:08:25 +02:00
|
|
|
}
|
2018-01-07 15:07:46 +01:00
|
|
|
|
|
|
|
// resolve base prototypes of derived classes
|
|
|
|
for (i = 0, k = queuedDerivedClasses.length; i < k; ++i) {
|
|
|
|
var derivedDeclaration = queuedDerivedClasses[i].declaration;
|
|
|
|
assert(derivedDeclaration != null);
|
|
|
|
var derivedType = (<ClassDeclaration>derivedDeclaration).extendsType;
|
|
|
|
assert(derivedType != null);
|
|
|
|
var resolved = this.resolveIdentifier((<TypeNode>derivedType).identifier, null); // reports
|
|
|
|
if (resolved) {
|
|
|
|
if (resolved.element.kind != ElementKind.CLASS_PROTOTYPE) {
|
|
|
|
this.error(DiagnosticCode.A_class_may_only_extend_another_class, (<TypeNode>derivedType).range);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
queuedDerivedClasses[i].basePrototype = <ClassPrototype>resolved.element;
|
|
|
|
}
|
|
|
|
}
|
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. */
|
2017-12-02 01:14:15 +01:00
|
|
|
private tryResolveImport(referencedName: string, queuedExports: Map<string,QueuedExport>): Element | null {
|
2017-12-28 04:09:40 +01:00
|
|
|
var element: Element | null;
|
2017-12-02 01:14:15 +01:00
|
|
|
do {
|
2017-12-27 02:37:53 +01:00
|
|
|
element = this.exports.get(referencedName);
|
2017-12-02 01:14:15 +01:00
|
|
|
if (element)
|
|
|
|
return element;
|
2017-12-28 04:09:40 +01:00
|
|
|
var queuedExport = queuedExports.get(referencedName);
|
2017-12-02 01:14:15 +01:00
|
|
|
if (!queuedExport)
|
|
|
|
return null;
|
|
|
|
if (queuedExport.isReExport) {
|
|
|
|
referencedName = queuedExport.referencedName;
|
|
|
|
continue;
|
|
|
|
}
|
2017-12-27 02:37:53 +01:00
|
|
|
return this.elements.get(queuedExport.referencedName);
|
2017-12-02 01:14:15 +01:00
|
|
|
} while (true);
|
|
|
|
}
|
|
|
|
|
2018-01-27 05:35:14 +01:00
|
|
|
private checkGlobalAlias(element: Element, declaration: DeclarationStatement): bool {
|
2018-01-18 23:34:12 +01:00
|
|
|
if (hasDecorator("global", declaration.decorators) || (declaration.range.source.isStdlib && assert(declaration.parent).kind == NodeKind.SOURCE && element.is(ElementFlags.EXPORTED))) {
|
2018-01-12 15:36:17 +01:00
|
|
|
if (this.elements.has(declaration.name.name))
|
|
|
|
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, element.internalName);
|
2018-01-17 02:08:14 +01:00
|
|
|
else {
|
2018-01-12 15:36:17 +01:00
|
|
|
this.elements.set(declaration.name.name, element);
|
2018-01-17 02:08:14 +01:00
|
|
|
this.exports.set(declaration.name.name, element);
|
2018-01-27 05:35:14 +01:00
|
|
|
return true;
|
2018-01-17 02:08:14 +01:00
|
|
|
}
|
2018-01-12 15:36:17 +01:00
|
|
|
}
|
2018-01-27 05:35:14 +01:00
|
|
|
return false;
|
2018-01-12 15:36:17 +01:00
|
|
|
}
|
|
|
|
|
2018-01-07 15:07:46 +01:00
|
|
|
private initializeClass(declaration: ClassDeclaration, queuedDerivedClasses: ClassPrototype[], namespace: Element | null = null): void {
|
2018-01-18 01:46:41 +01:00
|
|
|
var internalName = declaration.fileLevelInternalName;
|
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;
|
|
|
|
}
|
2017-12-28 04:09:40 +01:00
|
|
|
var prototype = new ClassPrototype(this, declaration.name.name, internalName, declaration);
|
2017-12-27 19:17:29 +01:00
|
|
|
prototype.namespace = namespace;
|
2017-11-17 14:33:51 +01:00
|
|
|
this.elements.set(internalName, prototype);
|
2017-12-15 02:50:55 +01:00
|
|
|
|
2018-01-27 05:35:14 +01:00
|
|
|
if (hasDecorator("unmanaged", declaration.decorators)) {
|
|
|
|
prototype.isUnmanaged = true;
|
2018-01-04 01:36:26 +01:00
|
|
|
if (declaration.implementsTypes && declaration.implementsTypes.length)
|
|
|
|
this.error(DiagnosticCode.Structs_cannot_implement_interfaces, Range.join(declaration.name.range, declaration.implementsTypes[declaration.implementsTypes.length - 1].range));
|
|
|
|
} else if (declaration.implementsTypes.length)
|
|
|
|
throw new Error("not implemented");
|
2017-12-16 20:08:33 +01:00
|
|
|
|
2018-01-07 15:07:46 +01:00
|
|
|
// remember classes that extend another one
|
|
|
|
if (declaration.extendsType)
|
|
|
|
queuedDerivedClasses.push(prototype);
|
|
|
|
|
2017-12-27 02:37:53 +01:00
|
|
|
// add as namespace member if applicable
|
2017-12-15 02:50:55 +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);
|
2017-12-15 02:50:55 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
namespace.members = new Map();
|
2017-12-18 03:46:36 +01:00
|
|
|
namespace.members.set(declaration.name.name, prototype);
|
2017-12-27 02:37:53 +01:00
|
|
|
|
|
|
|
// otherwise add to file-level exports if exported
|
2018-01-18 23:34:12 +01:00
|
|
|
} else if (prototype.is(ElementFlags.EXPORTED)) {
|
2017-12-15 02:50:55 +01:00
|
|
|
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);
|
2017-12-15 02:50:55 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.exports.set(internalName, prototype);
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
2017-12-15 02:50:55 +01:00
|
|
|
|
2017-12-27 02:37:53 +01:00
|
|
|
// initialize members
|
2017-12-28 04:09:40 +01:00
|
|
|
var memberDeclarations = declaration.members;
|
|
|
|
for (var i = 0, k = memberDeclarations.length; i < k; ++i) {
|
2018-01-02 03:54:06 +01:00
|
|
|
var memberDeclaration = memberDeclarations[i];
|
|
|
|
switch (memberDeclaration.kind) {
|
2017-12-15 02:50:55 +01:00
|
|
|
|
2018-01-15 19:00:18 +01:00
|
|
|
case NodeKind.FIELDDECLARATION:
|
2018-01-02 03:54:06 +01:00
|
|
|
this.initializeField(<FieldDeclaration>memberDeclaration, prototype);
|
2017-12-15 02:50:55 +01:00
|
|
|
break;
|
|
|
|
|
2018-01-15 19:00:18 +01:00
|
|
|
case NodeKind.METHODDECLARATION:
|
2018-01-02 03:54:06 +01:00
|
|
|
var isGetter: bool;
|
|
|
|
if ((isGetter = hasModifier(ModifierKind.GET, memberDeclaration.modifiers)) || hasModifier(ModifierKind.SET, memberDeclaration.modifiers))
|
|
|
|
this.initializeAccessor(<MethodDeclaration>memberDeclaration, prototype, isGetter);
|
|
|
|
else
|
|
|
|
this.initializeMethod(<MethodDeclaration>memberDeclaration, prototype);
|
2017-12-15 02:50:55 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2017-12-27 02:37:53 +01:00
|
|
|
throw new Error("class member expected");
|
2017-12-15 02:50:55 +01:00
|
|
|
}
|
|
|
|
}
|
2018-01-27 05:35:14 +01:00
|
|
|
|
|
|
|
if (this.checkGlobalAlias(prototype, declaration)) {
|
|
|
|
if (declaration.name.name === "String") {
|
|
|
|
var instance = prototype.resolve(null, null);
|
|
|
|
if (instance) {
|
|
|
|
assert(!this.types.has("string"));
|
|
|
|
this.types.set("string", instance.type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-09-28 13:08:25 +02:00
|
|
|
}
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
private initializeField(declaration: FieldDeclaration, classPrototype: ClassPrototype): void {
|
2017-12-28 04:09:40 +01:00
|
|
|
var name = declaration.name.name;
|
2018-01-18 01:46:41 +01:00
|
|
|
var internalName = declaration.fileLevelInternalName;
|
2017-12-15 02:50:55 +01:00
|
|
|
|
|
|
|
// static fields become global variables
|
|
|
|
if (hasModifier(ModifierKind.STATIC, declaration.modifiers)) {
|
|
|
|
if (this.elements.has(internalName)) {
|
2018-01-18 01:46:41 +01:00
|
|
|
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName);
|
2017-12-15 02:50:55 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (classPrototype.members) {
|
|
|
|
if (classPrototype.members.has(name)) {
|
2018-01-18 01:46:41 +01:00
|
|
|
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName);
|
2017-12-15 02:50:55 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
classPrototype.members = new Map();
|
2018-01-04 06:00:42 +01:00
|
|
|
var staticField = new Global(this, name, internalName, declaration, Type.void);
|
2017-12-15 02:50:55 +01:00
|
|
|
classPrototype.members.set(name, staticField);
|
|
|
|
this.elements.set(internalName, staticField);
|
|
|
|
|
|
|
|
// instance fields are remembered until resolved
|
2017-10-19 18:55:27 +02:00
|
|
|
} else {
|
2017-12-15 02:50:55 +01:00
|
|
|
if (classPrototype.instanceMembers) {
|
|
|
|
if (classPrototype.instanceMembers.has(name)) {
|
2018-01-18 01:46:41 +01:00
|
|
|
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName);
|
2017-12-15 02:50:55 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
classPrototype.instanceMembers = new Map();
|
2017-12-28 04:09:40 +01:00
|
|
|
var instanceField = new FieldPrototype(classPrototype, name, internalName, declaration);
|
2017-12-15 02:50:55 +01:00
|
|
|
classPrototype.instanceMembers.set(name, instanceField);
|
|
|
|
}
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
private initializeMethod(declaration: MethodDeclaration, classPrototype: ClassPrototype): void {
|
2017-12-28 04:09:40 +01:00
|
|
|
var name = declaration.name.name;
|
2018-01-18 01:46:41 +01:00
|
|
|
var internalName = declaration.fileLevelInternalName;
|
2018-01-06 10:20:38 +01:00
|
|
|
var instancePrototype: FunctionPrototype | null = null;
|
2017-12-15 02:50:55 +01:00
|
|
|
|
|
|
|
// static methods become global functions
|
|
|
|
if (hasModifier(ModifierKind.STATIC, declaration.modifiers)) {
|
2018-01-27 05:35:14 +01:00
|
|
|
assert(declaration.name.kind != NodeKind.CONSTRUCTOR);
|
2017-12-27 02:37:53 +01:00
|
|
|
|
2017-12-15 02:50:55 +01:00
|
|
|
if (this.elements.has(internalName)) {
|
2018-01-18 01:46:41 +01:00
|
|
|
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName);
|
2017-12-15 02:50:55 +01:00
|
|
|
return;
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
2017-12-15 02:50:55 +01:00
|
|
|
if (classPrototype.members) {
|
|
|
|
if (classPrototype.members.has(name)) {
|
2018-01-18 01:46:41 +01:00
|
|
|
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName);
|
2017-12-15 02:50:55 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
classPrototype.members = new Map();
|
2017-12-28 04:09:40 +01:00
|
|
|
var staticPrototype = new FunctionPrototype(this, name, internalName, declaration, null);
|
2017-12-15 02:50:55 +01:00
|
|
|
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)) {
|
2018-01-18 01:46:41 +01:00
|
|
|
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalName);
|
2017-12-15 02:50:55 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
classPrototype.instanceMembers = new Map();
|
2018-01-06 10:20:38 +01:00
|
|
|
instancePrototype = new FunctionPrototype(this, name, internalName, declaration, classPrototype);
|
2018-01-27 05:35:14 +01:00
|
|
|
// if (classPrototype.isUnmanaged && instancePrototype.isAbstract) {
|
|
|
|
// this.error( Unmanaged classes cannot declare abstract methods. );
|
2018-01-04 01:36:26 +01:00
|
|
|
// }
|
2017-12-15 02:50:55 +01:00
|
|
|
classPrototype.instanceMembers.set(name, instancePrototype);
|
2018-01-27 05:35:14 +01:00
|
|
|
if (declaration.name.kind == NodeKind.CONSTRUCTOR) {
|
|
|
|
if (classPrototype.constructorPrototype)
|
|
|
|
this.error(DiagnosticCode.Multiple_constructor_implementations_are_not_allowed, declaration.name.range);
|
|
|
|
else
|
|
|
|
classPrototype.constructorPrototype = instancePrototype;
|
|
|
|
}
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
2018-01-06 10:20:38 +01:00
|
|
|
|
|
|
|
// handle operator annotations. operators are instance methods taking a second argument of the
|
|
|
|
// instance's type. return values vary depending on the operation.
|
|
|
|
if (declaration.decorators) {
|
|
|
|
for (var i = 0, k = declaration.decorators.length; i < k; ++i) {
|
|
|
|
var decorator = declaration.decorators[i];
|
|
|
|
if (decorator.decoratorKind == DecoratorKind.OPERATOR) {
|
|
|
|
if (!instancePrototype) {
|
|
|
|
this.error(DiagnosticCode.Operation_not_supported, decorator.range);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
var numArgs = decorator.arguments && decorator.arguments.length || 0;
|
|
|
|
if (numArgs == 1) {
|
|
|
|
var firstArg = (<Expression[]>decorator.arguments)[0];
|
|
|
|
if (firstArg.kind == NodeKind.LITERAL && (<LiteralExpression>firstArg).literalKind == LiteralKind.STRING) {
|
|
|
|
switch ((<StringLiteralExpression>firstArg).value) {
|
|
|
|
|
|
|
|
case "[]":
|
2018-01-13 23:38:07 +01:00
|
|
|
classPrototype.fnIndexedGet = instancePrototype.simpleName;
|
2018-01-06 10:20:38 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case "[]=":
|
2018-01-13 23:38:07 +01:00
|
|
|
classPrototype.fnIndexedSet = instancePrototype.simpleName;
|
2018-01-06 10:20:38 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case "+":
|
2018-01-13 23:38:07 +01:00
|
|
|
classPrototype.fnConcat = instancePrototype.simpleName;
|
2018-01-06 10:20:38 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case "==":
|
2018-01-13 23:38:07 +01:00
|
|
|
classPrototype.fnEquals = instancePrototype.simpleName;
|
2018-01-06 10:20:38 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
default: // TBD: does it make sense to provide more, even though not JS/TS-compatible?
|
|
|
|
this.error(DiagnosticCode.Operation_not_supported, firstArg.range);
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
this.error(DiagnosticCode.String_literal_expected, firstArg.range);
|
|
|
|
} else
|
|
|
|
this.error(DiagnosticCode.Expected_0_arguments_but_got_1, decorator.range, "1", numArgs.toString(0));
|
|
|
|
} else if (decorator.decoratorKind != DecoratorKind.CUSTOM) // methods support @operator only
|
|
|
|
this.error(DiagnosticCode.Operation_not_supported, decorator.range);
|
|
|
|
}
|
|
|
|
}
|
2017-09-28 13:08:25 +02:00
|
|
|
}
|
|
|
|
|
2017-12-27 02:37:53 +01:00
|
|
|
private initializeAccessor(declaration: MethodDeclaration, classPrototype: ClassPrototype, isGetter: bool): void {
|
2017-12-28 04:09:40 +01:00
|
|
|
var propertyName = declaration.name.name;
|
2018-01-18 01:46:41 +01:00
|
|
|
var internalPropertyName = declaration.fileLevelInternalName;
|
2017-12-27 02:37:53 +01:00
|
|
|
|
2017-12-28 04:09:40 +01:00
|
|
|
var propertyElement = this.elements.get(internalPropertyName);
|
2017-12-27 02:37:53 +01:00
|
|
|
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);
|
|
|
|
|
2017-12-28 04:09:40 +01:00
|
|
|
var name = (isGetter ? GETTER_PREFIX : SETTER_PREFIX) + propertyName;
|
2017-12-27 02:37:53 +01:00
|
|
|
|
|
|
|
// static accessors become global functions
|
|
|
|
if (hasModifier(ModifierKind.STATIC, declaration.modifiers)) {
|
2017-12-28 04:09:40 +01:00
|
|
|
var internalStaticName = classPrototype.internalName + STATIC_DELIMITER + name;
|
2017-12-27 02:37:53 +01:00
|
|
|
if (this.elements.has(internalStaticName)) {
|
|
|
|
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalStaticName);
|
|
|
|
return;
|
|
|
|
}
|
2017-12-28 04:09:40 +01:00
|
|
|
var staticPrototype = new FunctionPrototype(this, name, internalStaticName, declaration, null);
|
2017-12-27 02:37:53 +01:00
|
|
|
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 {
|
2017-12-28 04:09:40 +01:00
|
|
|
var internalInstanceName = classPrototype.internalName + INSTANCE_DELIMITER + name;
|
2017-12-27 02:37:53 +01:00
|
|
|
if (classPrototype.instanceMembers) {
|
|
|
|
if (classPrototype.instanceMembers.has(name)) {
|
2018-01-18 01:46:41 +01:00
|
|
|
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, internalPropertyName);
|
2017-12-27 02:37:53 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
classPrototype.instanceMembers = new Map();
|
2017-12-28 04:09:40 +01:00
|
|
|
var instancePrototype = new FunctionPrototype(this, name, internalInstanceName, declaration, classPrototype);
|
2017-12-27 02:37:53 +01:00
|
|
|
if (isGetter)
|
|
|
|
(<Property>propertyElement).getterPrototype = instancePrototype;
|
|
|
|
else
|
|
|
|
(<Property>propertyElement).setterPrototype = instancePrototype;
|
|
|
|
classPrototype.instanceMembers.set(name, propertyElement);
|
2018-01-05 18:19:32 +01:00
|
|
|
this.elements.set(internalPropertyName, propertyElement);
|
2017-12-27 02:37:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-15 02:50:55 +01:00
|
|
|
private initializeEnum(declaration: EnumDeclaration, namespace: Element | null = null): void {
|
2018-01-18 01:46:41 +01:00
|
|
|
var internalName = declaration.fileLevelInternalName;
|
2017-12-15 02:50:55 +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-12-15 02:50:55 +01:00
|
|
|
return;
|
|
|
|
}
|
2017-12-28 04:09:40 +01:00
|
|
|
var enm = new Enum(this, declaration.name.name, internalName, declaration);
|
2017-12-27 19:17:29 +01:00
|
|
|
enm.namespace = namespace;
|
2017-12-15 02:50:55 +01:00
|
|
|
this.elements.set(internalName, enm);
|
2017-12-13 23:24:13 +01:00
|
|
|
|
2018-01-12 15:36:17 +01:00
|
|
|
this.checkGlobalAlias(enm, declaration);
|
|
|
|
|
2017-12-13 23:24:13 +01:00
|
|
|
if (namespace) {
|
2017-12-15 02:50:55 +01:00
|
|
|
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);
|
2017-12-15 02:50:55 +01:00
|
|
|
return;
|
|
|
|
}
|
2017-12-13 23:24:13 +01:00
|
|
|
} else
|
2017-12-15 02:50:55 +01:00
|
|
|
namespace.members = new Map();
|
2017-12-18 03:46:36 +01:00
|
|
|
namespace.members.set(declaration.name.name, enm);
|
2018-01-18 23:34:12 +01:00
|
|
|
} else if (enm.is(ElementFlags.EXPORTED)) {
|
2017-12-15 02:50:55 +01:00
|
|
|
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);
|
2017-12-15 02:50:55 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.exports.set(internalName, enm);
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
2017-12-13 23:24:13 +01:00
|
|
|
|
2017-12-28 04:09:40 +01:00
|
|
|
var values = declaration.values;
|
|
|
|
for (var i = 0, k = values.length; i < k; ++i)
|
2017-11-17 14:33:51 +01:00
|
|
|
this.initializeEnumValue(values[i], enm);
|
2017-09-28 13:08:25 +02:00
|
|
|
}
|
|
|
|
|
2017-10-19 18:55:27 +02:00
|
|
|
private initializeEnumValue(declaration: EnumValueDeclaration, enm: Enum): void {
|
2017-12-28 04:09:40 +01:00
|
|
|
var name = declaration.name.name;
|
2018-01-18 01:46:41 +01:00
|
|
|
var internalName = declaration.fileLevelInternalName;
|
2017-12-15 02:50:55 +01:00
|
|
|
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);
|
2017-12-15 02:50:55 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
enm.members = new Map();
|
2017-12-28 04:09:40 +01:00
|
|
|
var value = new EnumValue(enm, this, name, internalName, declaration);
|
2017-11-17 14:33:51 +01:00
|
|
|
enm.members.set(name, value);
|
2017-09-29 17:25:02 +02:00
|
|
|
}
|
|
|
|
|
2017-10-02 12:52:15 +02:00
|
|
|
private initializeExports(statement: ExportStatement, queuedExports: Map<string,QueuedExport>): void {
|
2017-12-28 04:09:40 +01:00
|
|
|
var members = statement.members;
|
|
|
|
for (var i = 0, k = members.length; i < k; ++i)
|
2017-10-19 18:55:27 +02:00
|
|
|
this.initializeExport(members[i], statement.internalPath, queuedExports);
|
2017-09-29 17:25:02 +02:00
|
|
|
}
|
|
|
|
|
2017-10-19 18:55:27 +02:00
|
|
|
private initializeExport(member: ExportMember, internalPath: string | null, queuedExports: Map<string,QueuedExport>): void {
|
2017-12-28 04:09:40 +01:00
|
|
|
var externalName = member.range.source.internalPath + PATH_DELIMITER + member.externalIdentifier.name;
|
2017-12-02 01:14:15 +01:00
|
|
|
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;
|
|
|
|
}
|
2017-12-28 04:09:40 +01:00
|
|
|
var referencedName: string;
|
|
|
|
var queuedExport: QueuedExport | null;
|
2017-12-02 01:14:15 +01:00
|
|
|
|
|
|
|
// export local element
|
2017-11-17 14:33:51 +01:00
|
|
|
if (internalPath == null) {
|
2017-12-02 01:14:15 +01:00
|
|
|
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;
|
|
|
|
}
|
2017-12-28 04:09:40 +01:00
|
|
|
queuedExport = new QueuedExport();
|
2017-12-02 01:14:15 +01:00
|
|
|
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 {
|
2017-12-06 17:47:48 +01:00
|
|
|
referencedName = (<string>internalPath) + PATH_DELIMITER + member.identifier.name;
|
2017-12-02 01:14:15 +01:00
|
|
|
|
|
|
|
// 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
|
2017-12-28 04:09:40 +01:00
|
|
|
var seen = new Set<QueuedExport>();
|
2017-12-27 02:37:53 +01:00
|
|
|
while (queuedExport = queuedExports.get(referencedName)) {
|
2017-12-02 01:14:15 +01:00
|
|
|
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;
|
|
|
|
}
|
2017-12-28 04:09:40 +01:00
|
|
|
queuedExport = new QueuedExport();
|
|
|
|
queuedExport.isReExport = true;
|
|
|
|
queuedExport.referencedName = referencedName; // -> export name
|
|
|
|
queuedExport.member = member;
|
|
|
|
queuedExports.set(externalName, queuedExport);
|
2017-09-29 17:25:02 +02:00
|
|
|
}
|
2017-09-28 13:08:25 +02:00
|
|
|
}
|
|
|
|
|
2017-12-15 02:50:55 +01:00
|
|
|
private initializeFunction(declaration: FunctionDeclaration, namespace: Element | null = null): void {
|
2018-01-18 01:46:41 +01:00
|
|
|
var internalName = declaration.fileLevelInternalName;
|
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;
|
|
|
|
}
|
2017-12-28 04:09:40 +01:00
|
|
|
var prototype = new FunctionPrototype(this, declaration.name.name, internalName, declaration, null);
|
2017-12-27 19:17:29 +01:00
|
|
|
prototype.namespace = namespace;
|
2017-11-17 14:33:51 +01:00
|
|
|
this.elements.set(internalName, prototype);
|
2017-12-13 23:24:13 +01:00
|
|
|
|
2018-01-12 15:36:17 +01:00
|
|
|
this.checkGlobalAlias(prototype, declaration);
|
2017-12-16 20:08:33 +01:00
|
|
|
|
2017-12-13 23:24:13 +01:00
|
|
|
if (namespace) {
|
2017-12-15 02:50:55 +01:00
|
|
|
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);
|
2017-12-15 02:50:55 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
namespace.members = new Map();
|
2017-12-18 03:46:36 +01:00
|
|
|
namespace.members.set(declaration.name.name, prototype);
|
2018-01-18 23:34:12 +01:00
|
|
|
} else if (prototype.is(ElementFlags.EXPORTED)) {
|
2017-12-15 02:50:55 +01:00
|
|
|
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);
|
2017-12-15 02:50:55 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.exports.set(internalName, prototype);
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
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 {
|
2017-12-28 04:09:40 +01:00
|
|
|
var declarations = statement.declarations;
|
2017-12-23 00:48:54 +01:00
|
|
|
if (declarations) {
|
2017-12-28 04:09:40 +01:00
|
|
|
for (var i = 0, k = declarations.length; i < k; ++i)
|
2017-12-27 02:37:53 +01:00
|
|
|
this.initializeImport(declarations[i], statement.internalPath, queuedExports, queuedImports);
|
2017-12-23 00:48:54 +01:00
|
|
|
} else if (statement.namespaceName) {
|
2017-12-28 04:09:40 +01:00
|
|
|
var internalName = statement.range.source.internalPath + "/" + statement.namespaceName.name;
|
2017-12-23 00:48:54 +01:00
|
|
|
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
|
2018-01-05 18:19:32 +01:00
|
|
|
}
|
2017-09-28 13:08:25 +02:00
|
|
|
}
|
|
|
|
|
2017-10-19 18:55:27 +02:00
|
|
|
private initializeImport(declaration: ImportDeclaration, internalPath: string, queuedExports: Map<string,QueuedExport>, queuedImports: QueuedImport[]): void {
|
2018-01-18 01:46:41 +01:00
|
|
|
var internalName = declaration.fileLevelInternalName;
|
2017-12-02 01:14:15 +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-12-02 01:14:15 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-12-28 04:09:40 +01:00
|
|
|
var referencedName = internalPath + PATH_DELIMITER + declaration.externalIdentifier.name;
|
2017-12-02 01:14:15 +01:00
|
|
|
|
|
|
|
// 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
|
2017-12-28 04:09:40 +01:00
|
|
|
var seen = new Set<QueuedExport>();
|
|
|
|
var queuedExport: QueuedExport | null;
|
2017-12-27 02:37:53 +01:00
|
|
|
while (queuedExport = queuedExports.get(referencedName)) {
|
2017-12-02 01:14:15 +01:00
|
|
|
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;
|
2017-12-02 01:14:15 +01:00
|
|
|
}
|
2017-09-29 17:25:02 +02:00
|
|
|
}
|
2017-12-02 01:14:15 +01:00
|
|
|
|
|
|
|
// otherwise queue it
|
2017-12-28 04:09:40 +01:00
|
|
|
var queuedImport = new QueuedImport();
|
2017-12-02 01:14:15 +01:00
|
|
|
queuedImport.internalName = internalName;
|
|
|
|
queuedImport.referencedName = referencedName;
|
|
|
|
queuedImport.declaration = declaration;
|
|
|
|
queuedImports.push(queuedImport);
|
2017-09-28 13:08:25 +02:00
|
|
|
}
|
|
|
|
|
2017-12-15 02:50:55 +01:00
|
|
|
private initializeInterface(declaration: InterfaceDeclaration, namespace: Element | null = null): void {
|
2018-01-18 01:46:41 +01:00
|
|
|
var internalName = declaration.fileLevelInternalName;
|
2017-12-15 02:50:55 +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-12-15 02:50:55 +01:00
|
|
|
return;
|
|
|
|
}
|
2017-12-28 04:09:40 +01:00
|
|
|
var prototype = new InterfacePrototype(this, declaration.name.name, internalName, declaration);
|
2017-12-27 19:17:29 +01:00
|
|
|
prototype.namespace = namespace;
|
2017-12-15 02:50:55 +01:00
|
|
|
this.elements.set(internalName, prototype);
|
2017-12-13 04:46:05 +01:00
|
|
|
|
2018-01-12 15:36:17 +01:00
|
|
|
this.checkGlobalAlias(prototype, declaration);
|
|
|
|
|
2017-12-13 23:24:13 +01:00
|
|
|
if (namespace) {
|
2017-12-15 02:50:55 +01:00
|
|
|
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);
|
2017-12-15 02:50:55 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
namespace.members = new Map();
|
|
|
|
namespace.members.set(prototype.internalName, prototype);
|
2018-01-18 23:34:12 +01:00
|
|
|
} else if (prototype.is(ElementFlags.EXPORTED)) {
|
2017-12-15 02:50:55 +01:00
|
|
|
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);
|
2017-12-15 02:50:55 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.exports.set(internalName, prototype);
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
2017-12-13 23:24:13 +01:00
|
|
|
|
2017-12-28 04:09:40 +01:00
|
|
|
var memberDeclarations = declaration.members;
|
|
|
|
for (var i = 0, k = memberDeclarations.length; i < k; ++i) {
|
2018-01-02 03:54:06 +01:00
|
|
|
var memberDeclaration = memberDeclarations[i];
|
|
|
|
switch (memberDeclaration.kind) {
|
2017-09-28 13:08:25 +02:00
|
|
|
|
2018-01-15 19:00:18 +01:00
|
|
|
case NodeKind.FIELDDECLARATION:
|
2018-01-02 03:54:06 +01:00
|
|
|
this.initializeField(<FieldDeclaration>memberDeclaration, prototype);
|
2017-09-28 13:08:25 +02:00
|
|
|
break;
|
|
|
|
|
2018-01-15 19:00:18 +01:00
|
|
|
case NodeKind.METHODDECLARATION:
|
2018-01-02 03:54:06 +01:00
|
|
|
var isGetter: bool;
|
|
|
|
if ((isGetter = hasModifier(ModifierKind.GET, memberDeclaration.modifiers)) || hasModifier(ModifierKind.SET, memberDeclaration.modifiers))
|
|
|
|
this.initializeAccessor(<MethodDeclaration>memberDeclaration, prototype, isGetter);
|
|
|
|
else
|
|
|
|
this.initializeMethod(<MethodDeclaration>memberDeclaration, prototype);
|
2017-09-28 13:08:25 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2017-12-27 02:37:53 +01:00
|
|
|
throw new Error("interface member expected");
|
2017-09-28 13:08:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-07 15:07:46 +01:00
|
|
|
private initializeNamespace(declaration: NamespaceDeclaration, queuedExtendingClasses: ClassPrototype[], parentNamespace: Element | null = null): void {
|
2018-01-18 01:46:41 +01:00
|
|
|
var internalName = declaration.fileLevelInternalName;
|
2017-12-13 04:46:05 +01:00
|
|
|
|
2017-12-28 04:09:40 +01:00
|
|
|
var namespace = this.elements.get(internalName);
|
2017-12-15 02:50:55 +01:00
|
|
|
if (!namespace) {
|
2017-12-27 22:38:32 +01:00
|
|
|
namespace = new Namespace(this, declaration.name.name, internalName, declaration);
|
2017-12-27 19:17:29 +01:00
|
|
|
namespace.namespace = parentNamespace;
|
2017-11-17 14:33:51 +01:00
|
|
|
this.elements.set(internalName, namespace);
|
2018-01-12 15:36:17 +01:00
|
|
|
this.checkGlobalAlias(namespace, declaration);
|
2017-12-15 02:50:55 +01:00
|
|
|
}
|
2017-12-13 23:24:13 +01:00
|
|
|
|
|
|
|
if (parentNamespace) {
|
2017-12-15 02:50:55 +01:00
|
|
|
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);
|
2017-12-15 02:50:55 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
parentNamespace.members = new Map();
|
2017-12-18 03:46:36 +01:00
|
|
|
parentNamespace.members.set(declaration.name.name, namespace);
|
2018-01-18 23:34:12 +01:00
|
|
|
} else if (namespace.is(ElementFlags.EXPORTED)) {
|
2017-12-15 02:50:55 +01:00
|
|
|
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);
|
2017-12-15 02:50:55 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.exports.set(internalName, namespace);
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
2017-12-13 23:24:13 +01:00
|
|
|
|
2017-12-28 04:09:40 +01:00
|
|
|
var members = declaration.members;
|
|
|
|
for (var i = 0, k = members.length; i < k; ++i) {
|
2017-12-15 02:50:55 +01:00
|
|
|
switch (members[i].kind) {
|
2017-09-28 13:08:25 +02:00
|
|
|
|
2018-01-15 19:00:18 +01:00
|
|
|
case NodeKind.CLASSDECLARATION:
|
2018-01-07 15:07:46 +01:00
|
|
|
this.initializeClass(<ClassDeclaration>members[i], queuedExtendingClasses, namespace);
|
2017-09-28 13:08:25 +02:00
|
|
|
break;
|
|
|
|
|
2018-01-15 19:00:18 +01:00
|
|
|
case NodeKind.ENUMDECLARATION:
|
2017-12-15 02:50:55 +01:00
|
|
|
this.initializeEnum(<EnumDeclaration>members[i], namespace);
|
2017-09-28 13:08:25 +02:00
|
|
|
break;
|
|
|
|
|
2018-01-15 19:00:18 +01:00
|
|
|
case NodeKind.FUNCTIONDECLARATION:
|
2017-12-15 02:50:55 +01:00
|
|
|
this.initializeFunction(<FunctionDeclaration>members[i], namespace);
|
2017-09-28 13:08:25 +02:00
|
|
|
break;
|
|
|
|
|
2018-01-15 19:00:18 +01:00
|
|
|
case NodeKind.INTERFACEDECLARATION:
|
2017-12-15 02:50:55 +01:00
|
|
|
this.initializeInterface(<InterfaceDeclaration>members[i], namespace);
|
2017-09-28 13:08:25 +02:00
|
|
|
break;
|
|
|
|
|
2018-01-15 19:00:18 +01:00
|
|
|
case NodeKind.NAMESPACEDECLARATION:
|
2018-01-07 15:07:46 +01:00
|
|
|
this.initializeNamespace(<NamespaceDeclaration>members[i], queuedExtendingClasses, namespace);
|
2017-09-28 13:08:25 +02:00
|
|
|
break;
|
|
|
|
|
2017-12-19 17:49:15 +01:00
|
|
|
case NodeKind.TYPEDECLARATION:
|
2018-01-02 03:54:06 +01:00
|
|
|
// this.initializeTypeAlias(<TypeDeclaration>members[i], namespace);
|
|
|
|
// TODO: what about namespaced types?
|
|
|
|
this.error(DiagnosticCode.Operation_not_supported, members[i].range);
|
2017-12-19 17:49:15 +01:00
|
|
|
break;
|
|
|
|
|
2017-09-28 13:08:25 +02:00
|
|
|
case NodeKind.VARIABLE:
|
2017-12-15 02:50:55 +01:00
|
|
|
this.initializeVariables(<VariableStatement>members[i], namespace);
|
2017-09-28 13:08:25 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2018-01-02 03:54:06 +01:00
|
|
|
throw new Error("namespace member expected");
|
2017-09-28 13:08:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-01 20:27:21 +01:00
|
|
|
private initializeTypeAlias(declaration: TypeDeclaration, namespace: Element | null = null): void {
|
2017-12-19 17:49:15 +01:00
|
|
|
// type aliases are program globals
|
2017-12-27 19:17:29 +01:00
|
|
|
// TODO: what about namespaced types?
|
2017-12-28 04:09:40 +01:00
|
|
|
var name = declaration.name.name;
|
2017-12-20 13:36:39 +01:00
|
|
|
if (this.types.has(name) || this.typeAliases.has(name)) {
|
2017-12-19 17:49:15 +01:00
|
|
|
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);
|
2017-12-19 17:49:15 +01:00
|
|
|
}
|
|
|
|
|
2017-12-15 02:50:55 +01:00
|
|
|
private initializeVariables(statement: VariableStatement, namespace: Element | null = null): void {
|
2017-12-28 04:09:40 +01:00
|
|
|
var declarations = statement.declarations;
|
|
|
|
for (var i = 0, k = declarations.length; i < k; ++i) {
|
|
|
|
var declaration = declarations[i];
|
2018-01-18 01:46:41 +01:00
|
|
|
var internalName = declaration.fileLevelInternalName;
|
2017-12-15 02:50:55 +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-12-15 02:50:55 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-01-04 06:00:42 +01:00
|
|
|
var global = new Global(this, declaration.name.name, internalName, declaration, /* resolved later */ Type.void);
|
2017-12-27 19:17:29 +01:00
|
|
|
global.namespace = namespace;
|
2017-12-15 02:50:55 +01:00
|
|
|
this.elements.set(internalName, global);
|
2017-12-13 23:24:13 +01:00
|
|
|
|
2018-01-12 15:36:17 +01:00
|
|
|
// differs a bit from this.checkGlobalAlias in that it checks the statement's parent
|
2018-01-18 23:34:12 +01:00
|
|
|
if (hasDecorator("global", declaration.decorators) || (declaration.range.source.isStdlib && assert(statement.parent).kind == NodeKind.SOURCE && global.is(ElementFlags.EXPORTED))) {
|
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) {
|
2017-12-15 02:50:55 +01:00
|
|
|
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);
|
2017-12-15 02:50:55 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
namespace.members = new Map();
|
2017-12-18 03:46:36 +01:00
|
|
|
namespace.members.set(declaration.name.name, global);
|
2018-01-18 23:34:12 +01:00
|
|
|
} else if (global.is(ElementFlags.EXPORTED)) {
|
2017-12-15 02:50:55 +01:00
|
|
|
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
|
2017-12-15 02:50:55 +01:00
|
|
|
this.exports.set(internalName, global);
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
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}. */
|
2017-10-19 18:55:27 +02:00
|
|
|
resolveType(node: TypeNode, contextualTypeArguments: Map<string,Type> | null = null, reportNotFound: bool = true): Type | null {
|
2018-01-01 20:27:21 +01:00
|
|
|
var globalName = node.identifier.name;
|
|
|
|
var localName = node.range.source.internalPath + PATH_DELIMITER + node.identifier.name;
|
|
|
|
|
|
|
|
var element: Element | null;
|
|
|
|
|
|
|
|
// check file-global / program-global element
|
|
|
|
if ((element = this.elements.get(localName)) || (element = this.elements.get(globalName))) {
|
|
|
|
switch (element.kind) {
|
|
|
|
case ElementKind.CLASS_PROTOTYPE:
|
|
|
|
var instance = (<ClassPrototype>element).resolveInclTypeArguments(node.typeArguments, contextualTypeArguments, null); // reports
|
|
|
|
return instance ? instance.type : null;
|
|
|
|
}
|
|
|
|
}
|
2017-10-19 18:55:27 +02:00
|
|
|
|
|
|
|
// resolve parameters
|
2017-12-28 04:09:40 +01:00
|
|
|
var k = node.typeArguments.length;
|
|
|
|
var paramTypes = new Array<Type>(k);
|
|
|
|
for (var i = 0; i < k; ++i) {
|
|
|
|
var paramType = this.resolveType(node.typeArguments[i], contextualTypeArguments, reportNotFound);
|
2017-10-19 18:55:27 +02:00
|
|
|
if (!paramType)
|
|
|
|
return null;
|
2017-12-27 02:37:53 +01:00
|
|
|
paramTypes[i] = paramType;
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
|
2018-01-01 20:27:21 +01:00
|
|
|
if (k) { // can't be a placeholder if it has parameters
|
|
|
|
var instanceKey = typesToString(paramTypes);
|
|
|
|
if (instanceKey.length) {
|
|
|
|
localName += "<" + instanceKey + ">";
|
|
|
|
globalName += "<" + instanceKey + ">";
|
|
|
|
}
|
|
|
|
} else if (contextualTypeArguments) {
|
2017-12-28 04:09:40 +01:00
|
|
|
var placeholderType = contextualTypeArguments.get(globalName);
|
2017-10-19 18:55:27 +02:00
|
|
|
if (placeholderType)
|
|
|
|
return placeholderType;
|
|
|
|
}
|
|
|
|
|
2017-12-28 04:09:40 +01:00
|
|
|
var type: Type | null;
|
2017-10-19 18:55:27 +02:00
|
|
|
|
2018-01-01 20:27:21 +01:00
|
|
|
// check file-global / program-global type
|
|
|
|
if ((type = this.types.get(localName)) || (type = this.types.get(globalName)))
|
2017-10-19 18:55:27 +02:00
|
|
|
return type;
|
|
|
|
|
2017-12-20 13:36:39 +01:00
|
|
|
// check type alias
|
2017-12-28 04:09:40 +01:00
|
|
|
var alias = this.typeAliases.get(globalName);
|
2017-12-20 13:36:39 +01:00
|
|
|
if (alias && (type = this.resolveType(alias, null, reportNotFound)))
|
|
|
|
return type;
|
|
|
|
|
2017-10-19 18:55:27 +02:00
|
|
|
if (reportNotFound)
|
|
|
|
this.error(DiagnosticCode.Cannot_find_name_0, node.identifier.range, globalName);
|
|
|
|
|
|
|
|
return null;
|
2017-09-29 17:25:02 +02:00
|
|
|
}
|
|
|
|
|
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 {
|
2017-12-28 04:09:40 +01:00
|
|
|
var parameterCount = typeParameters.length;
|
|
|
|
var argumentCount = typeArgumentNodes ? typeArgumentNodes.length : 0;
|
2017-10-19 18:55:27 +02:00
|
|
|
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));
|
2017-10-19 18:55:27 +02:00
|
|
|
else if (alternativeReportNode)
|
|
|
|
this.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, alternativeReportNode.range.atEnd, parameterCount.toString(10), "0");
|
|
|
|
return null;
|
|
|
|
}
|
2017-12-28 04:09:40 +01:00
|
|
|
var typeArguments = new Array<Type>(parameterCount);
|
|
|
|
for (var i = 0; i < parameterCount; ++i) {
|
|
|
|
var type = this.resolveType((<TypeNode[]>typeArgumentNodes)[i], contextualTypeArguments, true); // reports
|
2017-10-19 18:55:27 +02:00
|
|
|
if (!type)
|
|
|
|
return null;
|
|
|
|
// TODO: check extendsType
|
|
|
|
typeArguments[i] = type;
|
2017-09-28 13:08:25 +02:00
|
|
|
}
|
2017-10-19 18:55:27 +02:00
|
|
|
return typeArguments;
|
|
|
|
}
|
|
|
|
|
2017-12-27 19:17:29 +01:00
|
|
|
/** Resolves an identifier to the element it refers to. */
|
2018-01-24 03:08:09 +01:00
|
|
|
resolveIdentifier(identifier: IdentifierExpression, contextualFunction: Function | null, contextualEnum: Enum | null = null): ResolvedElement | null {
|
2017-12-28 04:09:40 +01:00
|
|
|
var name = identifier.name;
|
2017-12-27 19:17:29 +01:00
|
|
|
|
2017-12-28 04:09:40 +01:00
|
|
|
var element: Element | null;
|
|
|
|
var namespace: Element | null;
|
2018-01-24 03:08:09 +01:00
|
|
|
var reference: Element | null;
|
2017-12-27 19:17:29 +01:00
|
|
|
|
2018-01-24 03:08:09 +01:00
|
|
|
// check siblings
|
|
|
|
if (contextualEnum) {
|
|
|
|
|
|
|
|
if (contextualEnum.members && (element = contextualEnum.members.get(name)) && element.kind == ElementKind.ENUMVALUE)
|
|
|
|
return (resolvedElement || (resolvedElement = new ResolvedElement())).set(element);
|
|
|
|
|
|
|
|
} else if (contextualFunction) {
|
2018-01-18 23:34:12 +01:00
|
|
|
|
2018-01-07 15:07:46 +01:00
|
|
|
// check locals
|
2018-01-24 03:08:09 +01:00
|
|
|
if (element = contextualFunction.flow.getScopedLocal(name))
|
|
|
|
return (resolvedElement || (resolvedElement = new ResolvedElement())).set(element);
|
2018-01-07 15:07:46 +01:00
|
|
|
|
|
|
|
// search contextual parent namespaces if applicable
|
|
|
|
if (namespace = contextualFunction.prototype.namespace) {
|
|
|
|
do {
|
|
|
|
if (element = this.elements.get(namespace.internalName + STATIC_DELIMITER + name))
|
|
|
|
// if ((namespace.members && (element = namespace.members.get(name))) || (element = this.elements.get(namespace.internalName + STATIC_DELIMITER + name)))
|
|
|
|
return (resolvedElement || (resolvedElement = new ResolvedElement())).set(element);
|
|
|
|
} while (namespace = namespace.namespace);
|
|
|
|
}
|
2017-12-27 19:17:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// search current file
|
2017-12-13 23:24:13 +01:00
|
|
|
if (element = this.elements.get(identifier.range.source.internalPath + PATH_DELIMITER + name))
|
2018-01-05 18:19:32 +01:00
|
|
|
return (resolvedElement || (resolvedElement = new ResolvedElement())).set(element);
|
2017-12-27 19:17:29 +01:00
|
|
|
|
|
|
|
// search global scope
|
2017-12-13 23:24:13 +01:00
|
|
|
if (element = this.elements.get(name))
|
2018-01-05 18:19:32 +01:00
|
|
|
return (resolvedElement || (resolvedElement = new ResolvedElement())).set(element);
|
2017-12-27 19:17:29 +01:00
|
|
|
|
2017-12-13 23:24:13 +01:00
|
|
|
this.error(DiagnosticCode.Cannot_find_name_0, identifier.range, name);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2018-01-01 20:27:21 +01:00
|
|
|
/** Resolves a property access to the element it refers to. */
|
2018-01-03 18:33:27 +01:00
|
|
|
resolvePropertyAccess(propertyAccess: PropertyAccessExpression, contextualFunction: Function): ResolvedElement | null {
|
|
|
|
// start by resolving the lhs target (expression before the last dot)
|
|
|
|
var targetExpression = propertyAccess.expression;
|
2018-01-05 18:19:32 +01:00
|
|
|
if (!(resolvedElement = this.resolveExpression(targetExpression, contextualFunction)))
|
2017-12-13 23:24:13 +01:00
|
|
|
return null;
|
2018-01-05 18:19:32 +01:00
|
|
|
var target = resolvedElement.element;
|
2018-01-02 03:54:06 +01:00
|
|
|
|
2018-01-03 18:33:27 +01:00
|
|
|
// at this point we know exactly what the target is, so look up the element within
|
2017-12-28 04:09:40 +01:00
|
|
|
var propertyName = propertyAccess.property.name;
|
2018-01-04 06:00:42 +01:00
|
|
|
var targetType: Type;
|
2018-01-07 15:07:46 +01:00
|
|
|
var member: Element | null;
|
2018-01-17 02:08:14 +01:00
|
|
|
|
|
|
|
// Resolve variable-likes to their class type first
|
2018-01-01 20:27:21 +01:00
|
|
|
switch (target.kind) {
|
|
|
|
|
|
|
|
case ElementKind.GLOBAL:
|
|
|
|
case ElementKind.LOCAL:
|
2018-01-04 06:00:42 +01:00
|
|
|
case ElementKind.FIELD:
|
2018-01-17 02:08:14 +01:00
|
|
|
if (!(targetType = (<VariableLikeElement>target).type).classType) {
|
|
|
|
console.log(propertyAccess.property.name + " on " + targetType);
|
|
|
|
this.error(DiagnosticCode.Property_0_does_not_exist_on_type_1, propertyAccess.property.range, propertyName, targetType.toString());
|
|
|
|
return null;
|
|
|
|
}
|
2018-01-04 06:00:42 +01:00
|
|
|
target = <Class>targetType.classType;
|
2018-01-17 02:08:14 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case ElementKind.PROPERTY:
|
|
|
|
var getter = assert((<Property>target).getterPrototype).resolve(); // reports
|
|
|
|
if (!getter)
|
|
|
|
return null;
|
|
|
|
if (!(targetType = getter.returnType).classType) {
|
|
|
|
this.error(DiagnosticCode.Property_0_does_not_exist_on_type_1, propertyAccess.property.range, propertyName, targetType.toString());
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
target = <Class>targetType.classType;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Look up the member within
|
|
|
|
switch (target.kind) {
|
2018-01-01 20:27:21 +01:00
|
|
|
|
2018-01-07 15:07:46 +01:00
|
|
|
case ElementKind.CLASS_PROTOTYPE:
|
|
|
|
case ElementKind.CLASS:
|
|
|
|
do {
|
|
|
|
if (target.members && (member = target.members.get(propertyName)))
|
2018-01-03 18:33:27 +01:00
|
|
|
return resolvedElement.set(member).withTarget(target, targetExpression);
|
2018-01-07 15:07:46 +01:00
|
|
|
// check inherited static members on the base prototype while target is a class prototype
|
|
|
|
if (target.kind == ElementKind.CLASS_PROTOTYPE) {
|
|
|
|
if ((<ClassPrototype>target).basePrototype)
|
|
|
|
target = <ClassPrototype>(<ClassPrototype>target).basePrototype;
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
// or inherited instance members on the cbase class while target is a class instance
|
|
|
|
} else if (target.kind == ElementKind.CLASS) {
|
|
|
|
if ((<Class>target).base)
|
|
|
|
target = <Class>(<Class>target).base;
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
} else
|
|
|
|
break;
|
|
|
|
} while (true);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default: // enums or other namespace-like elements
|
|
|
|
if (target.members && (member = target.members.get(propertyName)))
|
|
|
|
return resolvedElement.set(member).withTarget(target, targetExpression);
|
2018-01-01 20:27:21 +01:00
|
|
|
break;
|
2017-12-15 02:50:55 +01:00
|
|
|
}
|
2018-01-17 02:08:14 +01:00
|
|
|
|
2018-01-03 18:33:27 +01:00
|
|
|
this.error(DiagnosticCode.Property_0_does_not_exist_on_type_1, propertyAccess.property.range, propertyName, target.internalName);
|
2017-12-13 23:24:13 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2018-01-03 18:33:27 +01:00
|
|
|
resolveElementAccess(elementAccess: ElementAccessExpression, contextualFunction: Function): ResolvedElement | null {
|
|
|
|
// start by resolving the lhs target (expression before the last dot)
|
|
|
|
var targetExpression = elementAccess.expression;
|
2018-01-05 18:19:32 +01:00
|
|
|
if (!(resolvedElement = this.resolveExpression(targetExpression, contextualFunction)))
|
2017-10-19 18:55:27 +02:00
|
|
|
return null;
|
2018-01-05 18:19:32 +01:00
|
|
|
var target = resolvedElement.element;
|
|
|
|
switch (target.kind) {
|
2018-01-07 15:07:46 +01:00
|
|
|
|
2018-01-13 23:38:07 +01:00
|
|
|
case ElementKind.GLOBAL:
|
|
|
|
case ElementKind.LOCAL:
|
|
|
|
case ElementKind.FIELD:
|
|
|
|
var type = (<VariableLikeElement>target).type;
|
2018-01-05 18:19:32 +01:00
|
|
|
if (type.classType) {
|
2018-01-13 23:38:07 +01:00
|
|
|
var indexedGetName = (target = type.classType).prototype.fnIndexedGet;
|
|
|
|
var indexedGet: Element | null;
|
|
|
|
if (indexedGetName != null && target.members && (indexedGet = target.members.get(indexedGetName)) && indexedGet.kind == ElementKind.FUNCTION_PROTOTYPE)
|
|
|
|
return resolvedElement.set(indexedGet).withTarget(type.classType, targetExpression);
|
2018-01-05 18:19:32 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2018-01-07 15:07:46 +01:00
|
|
|
this.error(DiagnosticCode.Index_signature_is_missing_in_type_0, targetExpression.range, target.internalName);
|
2018-01-05 18:19:32 +01:00
|
|
|
return null;
|
2018-01-03 18:33:27 +01:00
|
|
|
}
|
2017-10-19 18:55:27 +02:00
|
|
|
|
2018-01-03 18:33:27 +01:00
|
|
|
resolveExpression(expression: Expression, contextualFunction: Function): ResolvedElement | null {
|
|
|
|
var classType: Class | null;
|
|
|
|
switch (expression.kind) {
|
2017-10-19 18:55:27 +02:00
|
|
|
|
2018-01-03 18:33:27 +01:00
|
|
|
case NodeKind.THIS: // -> Class
|
|
|
|
if (classType = contextualFunction.instanceMethodOf)
|
2018-01-05 18:19:32 +01:00
|
|
|
return (resolvedElement || (resolvedElement = new ResolvedElement())).set(classType);
|
2018-01-03 18:33:27 +01:00
|
|
|
this.error(DiagnosticCode._this_cannot_be_referenced_in_current_location, expression.range);
|
|
|
|
return null;
|
|
|
|
|
|
|
|
case NodeKind.SUPER: // -> Class
|
|
|
|
if ((classType = contextualFunction.instanceMethodOf) && (classType = classType.base))
|
2018-01-05 18:19:32 +01:00
|
|
|
return (resolvedElement || (resolvedElement = new ResolvedElement())).set(classType);
|
2018-01-03 18:33:27 +01:00
|
|
|
this.error(DiagnosticCode._super_can_only_be_referenced_in_a_derived_class, expression.range);
|
|
|
|
return null;
|
2017-12-16 02:27:39 +01:00
|
|
|
|
2018-01-03 18:33:27 +01:00
|
|
|
case NodeKind.IDENTIFIER:
|
|
|
|
return this.resolveIdentifier(<IdentifierExpression>expression, contextualFunction);
|
|
|
|
|
|
|
|
case NodeKind.PROPERTYACCESS:
|
|
|
|
return this.resolvePropertyAccess(<PropertyAccessExpression>expression, contextualFunction);
|
|
|
|
|
|
|
|
case NodeKind.ELEMENTACCESS:
|
|
|
|
return this.resolveElementAccess(<ElementAccessExpression>expression, contextualFunction);
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
2018-01-05 18:19:32 +01:00
|
|
|
this.error(DiagnosticCode.Operation_not_supported, expression.range);
|
|
|
|
return null;
|
2018-01-03 18:33:27 +01:00
|
|
|
}
|
|
|
|
}
|
2017-10-19 18:55:27 +02:00
|
|
|
|
2018-01-03 18:33:27 +01:00
|
|
|
/** Common result structure returned when calling any of the resolve functions on a {@link Program}. */
|
|
|
|
export class ResolvedElement {
|
|
|
|
|
|
|
|
/** The target element, if a property or element access */
|
|
|
|
target: Element | null;
|
2018-01-05 18:19:32 +01:00
|
|
|
/** The target element's expression, if a property or element access. */
|
2018-01-03 18:33:27 +01:00
|
|
|
targetExpression: Expression | null;
|
|
|
|
/** The element being accessed. */
|
|
|
|
element: Element;
|
|
|
|
|
2018-01-05 18:19:32 +01:00
|
|
|
/** Clears the target and sets the resolved element. */
|
2018-01-03 18:33:27 +01:00
|
|
|
set(element: Element): this {
|
|
|
|
this.target = null;
|
|
|
|
this.targetExpression = null;
|
|
|
|
this.element = element;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2018-01-05 18:19:32 +01:00
|
|
|
/** Sets the resolved target in addition to the previously set element. */
|
2018-01-03 18:33:27 +01:00
|
|
|
withTarget(target: Element, targetExpression: Expression): this {
|
|
|
|
this.target = target;
|
|
|
|
this.targetExpression = targetExpression;
|
|
|
|
return this;
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-05 18:19:32 +01:00
|
|
|
// Cached result structure instance
|
|
|
|
var resolvedElement: ResolvedElement | null;
|
2018-01-03 18:33:27 +01:00
|
|
|
|
2017-12-13 04:46:05 +01:00
|
|
|
/** Indicates the specific kind of an {@link Element}. */
|
2017-10-19 18:55:27 +02:00
|
|
|
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}. */
|
2017-10-19 18:55:27 +02:00
|
|
|
ENUM,
|
2017-12-13 04:46:05 +01:00
|
|
|
/** An {@link EnumValue}. */
|
2017-10-19 18:55:27 +02:00
|
|
|
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}. */
|
2017-10-19 18:55:27 +02:00
|
|
|
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}. */
|
2017-10-19 18:55:27 +02:00
|
|
|
INTERFACE,
|
2017-12-16 17:54:53 +01:00
|
|
|
/** A {@link FieldPrototype}. */
|
|
|
|
FIELD_PROTOTYPE,
|
|
|
|
/** A {@link Field}. */
|
|
|
|
FIELD,
|
2018-01-17 02:08:14 +01:00
|
|
|
/** A {@link Property}. */
|
2017-12-16 17:54:53 +01:00
|
|
|
PROPERTY,
|
2017-12-13 04:46:05 +01:00
|
|
|
/** A {@link Namespace}. */
|
2017-10-19 18:55:27 +02:00
|
|
|
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,
|
2018-01-18 23:34:12 +01:00
|
|
|
/** Has a constant value and is therefore inlined. */
|
|
|
|
INLINED = 1 << 7,
|
2017-12-13 04:46:05 +01:00
|
|
|
/** 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. */
|
2017-12-21 10:14:53 +01:00
|
|
|
READONLY = 1 << 12,
|
|
|
|
/** Is a public member. */
|
|
|
|
PUBLIC = 1 << 13,
|
|
|
|
/** Is a protected member. */
|
|
|
|
PROTECTED = 1 << 14,
|
|
|
|
/** Is a private member. */
|
2018-01-04 01:36:26 +01:00
|
|
|
PRIVATE = 1 << 15,
|
|
|
|
/** Is an abstract member. */
|
|
|
|
ABSTRACT = 1 << 16,
|
2018-01-27 05:35:14 +01:00
|
|
|
/** Is an unmanaged class with limited capabilites. */
|
|
|
|
UNMANAGED = 1 << 17,
|
2018-01-05 18:19:32 +01:00
|
|
|
/** Has already inherited base class static members. */
|
2018-01-18 23:34:12 +01:00
|
|
|
HAS_STATIC_BASE_MEMBERS = 1 << 18,
|
|
|
|
/** Is scoped. */
|
2018-01-24 03:08:09 +01:00
|
|
|
SCOPED = 1 << 19,
|
|
|
|
/** Is the start function. */
|
|
|
|
START = 1 << 20
|
2017-12-13 04:46:05 +01:00
|
|
|
}
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
/** Base class of all program elements. */
|
2017-10-19 18:55:27 +02:00
|
|
|
export abstract class Element {
|
|
|
|
|
2017-12-13 04:46:05 +01:00
|
|
|
/** Specific element kind. */
|
2017-10-19 18:55:27 +02:00
|
|
|
kind: ElementKind;
|
2017-12-13 04:46:05 +01:00
|
|
|
/** Containing {@link Program}. */
|
2017-10-19 18:55:27 +02:00
|
|
|
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. */
|
2017-10-19 18:55:27 +02:00
|
|
|
internalName: string;
|
2017-12-13 04:46:05 +01:00
|
|
|
/** Element flags. */
|
|
|
|
flags: ElementFlags = ElementFlags.NONE;
|
2017-12-15 02:50:55 +01:00
|
|
|
/** Namespaced member elements. */
|
|
|
|
members: Map<string,Element> | null = null;
|
2017-12-27 19:17:29 +01:00
|
|
|
/** Parent namespace, if applicable. */
|
|
|
|
namespace: Element | null = null;
|
2017-10-19 18:55:27 +02:00
|
|
|
|
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) {
|
2017-10-19 18:55:27 +02:00
|
|
|
this.program = program;
|
2017-12-27 22:38:32 +01:00
|
|
|
this.simpleName = simpleName;
|
2017-10-19 18:55:27 +02:00
|
|
|
this.internalName = internalName;
|
|
|
|
}
|
2017-12-13 04:46:05 +01:00
|
|
|
|
2018-01-18 23:34:12 +01:00
|
|
|
/** Tests if this element has a specific flag or flags. */
|
|
|
|
is(flag: ElementFlags): bool { return (this.flags & flag) == flag; }
|
2017-12-13 04:46:05 +01:00
|
|
|
|
2018-01-18 23:34:12 +01:00
|
|
|
/** Sets a specific flag or flags. */
|
|
|
|
set(flag: ElementFlags): void { this.flags |= flag; }
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
|
2017-12-16 02:27:39 +01:00
|
|
|
/** A namespace. */
|
2017-11-17 14:33:51 +01:00
|
|
|
export class Namespace extends Element {
|
|
|
|
|
2017-12-27 19:17:29 +01:00
|
|
|
// 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. */
|
2017-12-15 02:50:55 +01:00
|
|
|
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) {
|
2017-12-28 04:09:40 +01:00
|
|
|
for (var i = 0, k = this.declaration.modifiers.length; i < k; ++i) {
|
2017-12-13 04:46:05 +01:00
|
|
|
switch (this.declaration.modifiers[i].modifierKind) {
|
2018-01-18 23:34:12 +01:00
|
|
|
case ModifierKind.IMPORT: this.set(ElementFlags.IMPORTED); break;
|
|
|
|
case ModifierKind.EXPORT: this.set(ElementFlags.EXPORTED); break;
|
|
|
|
case ModifierKind.DECLARE: this.set(ElementFlags.DECLARED); break;
|
2017-12-13 04:46:05 +01:00
|
|
|
default: throw new Error("unexpected modifier");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-11-17 14:33:51 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** An enum. */
|
2017-12-15 02:50:55 +01:00
|
|
|
export class Enum extends Element {
|
2017-10-19 18:55:27 +02:00
|
|
|
|
|
|
|
kind = ElementKind.ENUM;
|
2017-12-13 04:46:05 +01:00
|
|
|
|
|
|
|
/** Declaration reference. */
|
2017-10-19 18:55:27 +02:00
|
|
|
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) {
|
2017-12-28 04:09:40 +01:00
|
|
|
for (var i = 0, k = this.declaration.modifiers.length; i < k; ++i) {
|
2017-12-13 04:46:05 +01:00
|
|
|
switch (this.declaration.modifiers[i].modifierKind) {
|
2018-01-18 23:34:12 +01:00
|
|
|
case ModifierKind.EXPORT: this.set(ElementFlags.EXPORTED); break;
|
|
|
|
case ModifierKind.IMPORT: this.set(ElementFlags.IMPORTED); break;
|
|
|
|
case ModifierKind.DECLARE: this.set(ElementFlags.DECLARED); break;
|
|
|
|
case ModifierKind.CONST: this.set(ElementFlags.CONSTANT); break;
|
2017-12-13 04:46:05 +01:00
|
|
|
default: throw new Error("unexpected modifier");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
/** An enum value. */
|
2017-10-19 18:55:27 +02:00
|
|
|
export class EnumValue extends Element {
|
|
|
|
|
|
|
|
kind = ElementKind.ENUMVALUE;
|
2017-12-13 04:46:05 +01:00
|
|
|
|
|
|
|
/** Declaration reference. */
|
2017-10-19 18:55:27 +02:00
|
|
|
declaration: EnumValueDeclaration | null;
|
2017-12-13 04:46:05 +01:00
|
|
|
/** Parent enum. */
|
2017-10-19 18:55:27 +02:00
|
|
|
enum: Enum;
|
2017-12-13 04:46:05 +01:00
|
|
|
/** Constant value, if applicable. */
|
2017-10-19 18:55:27 +02:00
|
|
|
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);
|
2017-10-19 18:55:27 +02:00
|
|
|
this.enum = enm;
|
2017-12-16 02:27:39 +01:00
|
|
|
this.declaration = declaration;
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-28 15:17:35 +01:00
|
|
|
export class VariableLikeElement extends Element {
|
2017-10-19 18:55:27 +02:00
|
|
|
|
2017-12-28 15:17:35 +01:00
|
|
|
// kind varies
|
2017-12-13 04:46:05 +01:00
|
|
|
|
|
|
|
/** Declaration reference. */
|
2017-10-19 18:55:27 +02:00
|
|
|
declaration: VariableLikeDeclarationStatement | null;
|
2018-01-04 06:00:42 +01:00
|
|
|
/** Variable type. Is {@link Type.void} for type-inferred {@link Global}s before compilation. */
|
|
|
|
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-10-19 18:55:27 +02:00
|
|
|
constantFloatValue: f64 = 0;
|
|
|
|
|
2017-12-28 15:17:35 +01:00
|
|
|
withConstantIntegerValue(lo: i32, hi: i32): this {
|
|
|
|
this.constantIntegerValue = new I64(lo, hi);
|
2018-01-18 23:34:12 +01:00
|
|
|
this.set(ElementFlags.CONSTANT | ElementFlags.INLINED);
|
2017-12-28 15:17:35 +01:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
withConstantFloatValue(value: f64): this {
|
|
|
|
this.constantFloatValue = value;
|
2018-01-18 23:34:12 +01:00
|
|
|
this.set(ElementFlags.CONSTANT | ElementFlags.INLINED);
|
2017-12-28 15:17:35 +01:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** A global variable. */
|
|
|
|
export class Global extends VariableLikeElement {
|
|
|
|
|
|
|
|
kind = ElementKind.GLOBAL;
|
|
|
|
|
2018-01-04 06:00:42 +01:00
|
|
|
constructor(program: Program, simpleName: string, internalName: string, declaration: VariableLikeDeclarationStatement | null = null, type: Type) {
|
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) {
|
2017-12-28 04:09:40 +01:00
|
|
|
for (var i = 0, k = this.declaration.modifiers.length; i < k; ++i) {
|
2017-12-13 04:46:05 +01:00
|
|
|
switch (this.declaration.modifiers[i].modifierKind) {
|
2018-01-18 23:34:12 +01:00
|
|
|
case ModifierKind.IMPORT: this.set(ElementFlags.IMPORTED); break;
|
|
|
|
case ModifierKind.EXPORT: this.set(ElementFlags.EXPORTED); break;
|
|
|
|
case ModifierKind.CONST: this.set(ElementFlags.CONSTANT); break;
|
|
|
|
case ModifierKind.LET: this.set(ElementFlags.SCOPED); break;
|
|
|
|
case ModifierKind.DECLARE: this.set(ElementFlags.DECLARED); break;
|
|
|
|
case ModifierKind.READONLY: this.set(this.declaration.initializer ? ElementFlags.CONSTANT | ElementFlags.READONLY : ElementFlags.READONLY); break;
|
2017-12-15 02:50:55 +01:00
|
|
|
case ModifierKind.STATIC: break; // static fields become globals
|
2017-12-13 04:46:05 +01:00
|
|
|
default: throw new Error("unexpected modifier");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2018-01-18 23:34:12 +01:00
|
|
|
this.set(ElementFlags.CONSTANT | ElementFlags.INLINED); // built-ins have constant values
|
2017-12-13 04:46:05 +01:00
|
|
|
}
|
2018-01-04 06:00:42 +01:00
|
|
|
this.type = type; // resolved later if `void`
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
/** A function parameter. */
|
2017-10-19 18:55:27 +02:00
|
|
|
export class Parameter {
|
|
|
|
|
2017-12-13 04:46:05 +01:00
|
|
|
// not an Element on its own
|
|
|
|
|
|
|
|
/** Parameter name. */
|
2017-10-19 18:55:27 +02:00
|
|
|
name: string;
|
2017-12-13 04:46:05 +01:00
|
|
|
/** Parameter type. */
|
2017-10-19 18:55:27 +02:00
|
|
|
type: Type;
|
2017-12-13 04:46:05 +01:00
|
|
|
/** Parameter initializer. */
|
2017-11-20 23:39:50 +01:00
|
|
|
initializer: Expression | null;
|
2017-10-19 18:55:27 +02:00
|
|
|
|
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) {
|
2017-10-19 18:55:27 +02:00
|
|
|
this.name = name;
|
|
|
|
this.type = type;
|
2017-11-20 23:39:50 +01:00
|
|
|
this.initializer = initializer;
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
/** A function local. */
|
2017-12-28 15:17:35 +01:00
|
|
|
export class Local extends VariableLikeElement {
|
2017-10-19 18:55:27 +02:00
|
|
|
|
|
|
|
kind = ElementKind.LOCAL;
|
2017-12-13 04:46:05 +01:00
|
|
|
|
|
|
|
/** Local index. */
|
2017-10-19 18:55:27 +02:00
|
|
|
index: i32;
|
|
|
|
|
2017-12-27 22:38:32 +01:00
|
|
|
constructor(program: Program, simpleName: string, index: i32, type: Type) {
|
|
|
|
super(program, simpleName, simpleName);
|
2017-10-19 18:55:27 +02:00
|
|
|
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-10-19 18:55:27 +02:00
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
kind = ElementKind.FUNCTION_PROTOTYPE;
|
2017-12-13 04:46:05 +01:00
|
|
|
|
|
|
|
/** Declaration reference. */
|
2017-10-19 18:55:27 +02:00
|
|
|
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();
|
2018-01-01 20:27:21 +01:00
|
|
|
/** Class type arguments, if a partially resolved method of a generic class. */
|
|
|
|
classTypeArguments: Type[] | null = null;
|
2017-10-19 18:55:27 +02:00
|
|
|
|
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)
|
2017-12-28 04:09:40 +01:00
|
|
|
for (var i = 0, k = this.declaration.modifiers.length; i < k; ++i) {
|
2017-12-13 04:46:05 +01:00
|
|
|
switch (this.declaration.modifiers[i].modifierKind) {
|
2018-01-18 23:34:12 +01:00
|
|
|
case ModifierKind.IMPORT: this.set(ElementFlags.IMPORTED); break;
|
|
|
|
case ModifierKind.EXPORT: this.set(ElementFlags.EXPORTED); break;
|
|
|
|
case ModifierKind.DECLARE: this.set(ElementFlags.DECLARED); break;
|
|
|
|
case ModifierKind.GET: this.set(ElementFlags.GETTER); break;
|
|
|
|
case ModifierKind.SET: this.set(ElementFlags.SETTER); 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)
|
2018-01-18 23:34:12 +01:00
|
|
|
this.set(ElementFlags.GENERIC);
|
2017-12-13 04:46:05 +01:00
|
|
|
}
|
2018-01-01 20:27:21 +01:00
|
|
|
if (this.classPrototype = classPrototype)
|
2018-01-18 23:34:12 +01:00
|
|
|
this.set(ElementFlags.INSTANCE);
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
|
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; }
|
2017-10-19 18:55:27 +02:00
|
|
|
|
2017-12-27 02:37:53 +01:00
|
|
|
// Whether a getter/setter function or not.
|
|
|
|
get isAccessor(): bool { return (this.flags & (ElementFlags.GETTER | ElementFlags.SETTER)) != 0; }
|
|
|
|
|
2018-01-01 20:27:21 +01:00
|
|
|
resolve(functionTypeArguments: Type[] | null = null, contextualTypeArguments: Map<string,Type> | null = null): Function | null {
|
|
|
|
var instanceKey = functionTypeArguments ? typesToString(functionTypeArguments) : "";
|
2017-12-28 04:09:40 +01:00
|
|
|
var instance = this.instances.get(instanceKey);
|
2017-10-19 18:55:27 +02:00
|
|
|
if (instance)
|
|
|
|
return instance;
|
2018-01-02 03:54:06 +01:00
|
|
|
|
2017-12-28 04:09:40 +01:00
|
|
|
var declaration = this.declaration;
|
2017-10-19 18:55:27 +02:00
|
|
|
if (!declaration)
|
2018-01-02 03:54:06 +01:00
|
|
|
throw new Error("cannot resolve built-ins");
|
2017-10-19 18:55:27 +02:00
|
|
|
|
2018-01-01 20:27:21 +01:00
|
|
|
// inherit contextual type arguments
|
|
|
|
var inheritedTypeArguments = contextualTypeArguments;
|
|
|
|
contextualTypeArguments = new Map();
|
|
|
|
if (inheritedTypeArguments)
|
|
|
|
for (var [inheritedName, inheritedType] of inheritedTypeArguments)
|
|
|
|
contextualTypeArguments.set(inheritedName, inheritedType);
|
|
|
|
|
2017-12-28 04:09:40 +01:00
|
|
|
var i: i32, k: i32;
|
2018-01-01 20:27:21 +01:00
|
|
|
|
2018-01-02 03:54:06 +01:00
|
|
|
// inherit class type arguments if a partially resolved instance method (classTypeArguments is set)
|
|
|
|
if (this.classTypeArguments) {
|
|
|
|
if (!this.classPrototype)
|
|
|
|
throw new Error("partially resolved instance method must reference its class prototype");
|
|
|
|
var classDeclaration = (<ClassPrototype>this.classPrototype).declaration;
|
2018-01-01 20:27:21 +01:00
|
|
|
if (!classDeclaration)
|
2018-01-02 03:54:06 +01:00
|
|
|
throw new Error("cannot resolve built-ins");
|
2018-01-01 20:27:21 +01:00
|
|
|
var classTypeParameters = classDeclaration.typeParameters;
|
|
|
|
if ((k = this.classTypeArguments.length) != classTypeParameters.length)
|
2018-01-02 03:54:06 +01:00
|
|
|
throw new Error("type argument count mismatch");
|
2017-10-19 18:55:27 +02:00
|
|
|
for (i = 0; i < k; ++i)
|
2018-01-01 20:27:21 +01:00
|
|
|
contextualTypeArguments.set(classTypeParameters[i].identifier.name, this.classTypeArguments[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// override call specific contextual type arguments
|
|
|
|
var functionTypeParameters = declaration.typeParameters;
|
|
|
|
if (functionTypeArguments && (k = functionTypeArguments.length)) {
|
|
|
|
if (k != functionTypeParameters.length)
|
2018-01-02 03:54:06 +01:00
|
|
|
throw new Error("type argument count mismatch");
|
2018-01-01 20:27:21 +01:00
|
|
|
for (i = 0; i < k; ++i)
|
|
|
|
contextualTypeArguments.set(functionTypeParameters[i].identifier.name, functionTypeArguments[i]);
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// resolve parameters
|
2018-01-06 10:20:38 +01:00
|
|
|
// TODO: 'this' type
|
2017-10-19 18:55:27 +02:00
|
|
|
k = declaration.parameters.length;
|
2017-12-28 04:09:40 +01:00
|
|
|
var parameters = new Array<Parameter>(k);
|
|
|
|
var parameterTypes = new Array<Type>(k);
|
|
|
|
var typeNode: TypeNode | null;
|
2017-12-24 03:19:47 +01:00
|
|
|
for (i = 0; i < k; ++i) {
|
|
|
|
if (typeNode = declaration.parameters[i].type) {
|
2017-12-28 04:09:40 +01:00
|
|
|
var parameterType = this.program.resolveType(typeNode, contextualTypeArguments, true); // reports
|
|
|
|
if (parameterType) {
|
2018-01-14 21:17:43 +01:00
|
|
|
parameters[i] = new Parameter(declaration.parameters[i].name.name, parameterType, declaration.parameters[i].initializer);
|
2017-12-28 04:09:40 +01:00
|
|
|
parameterTypes[i] = parameterType;
|
2017-10-19 18:55:27 +02:00
|
|
|
} else
|
|
|
|
return null;
|
|
|
|
} else
|
2017-12-15 15:00:19 +01:00
|
|
|
return null;
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// resolve return type
|
2018-01-06 10:20:38 +01:00
|
|
|
// TODO: 'this' type
|
2017-12-28 04:09:40 +01:00
|
|
|
var returnType: Type;
|
2017-12-27 02:37:53 +01:00
|
|
|
if (this.isSetter) {
|
|
|
|
returnType = Type.void; // not annotated
|
|
|
|
} else {
|
|
|
|
if (typeNode = declaration.returnType) {
|
2017-12-28 04:09:40 +01:00
|
|
|
var type = this.program.resolveType(<TypeNode>typeNode, contextualTypeArguments, true); // reports
|
2017-12-27 02:37:53 +01:00
|
|
|
if (type)
|
|
|
|
returnType = type;
|
|
|
|
else
|
|
|
|
return null;
|
|
|
|
} else
|
2017-10-19 18:55:27 +02:00
|
|
|
return null;
|
2017-12-27 02:37:53 +01:00
|
|
|
}
|
2017-10-19 18:55:27 +02:00
|
|
|
|
2017-12-28 04:09:40 +01:00
|
|
|
var internalName = this.internalName;
|
2017-10-19 18:55:27 +02:00
|
|
|
if (instanceKey.length)
|
|
|
|
internalName += "<" + instanceKey + ">";
|
2018-01-01 20:27:21 +01:00
|
|
|
var classInstance: Class | null = null;
|
|
|
|
if (this.classPrototype) {
|
|
|
|
classInstance = this.classPrototype.resolve(this.classTypeArguments, contextualTypeArguments); // reports
|
|
|
|
if (!classInstance)
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
instance = new Function(this, internalName, functionTypeArguments, parameters, returnType, classInstance);
|
|
|
|
instance.contextualTypeArguments = contextualTypeArguments;
|
2017-10-19 18:55:27 +02:00
|
|
|
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 {
|
2018-01-01 20:27:21 +01:00
|
|
|
var resolvedTypeArguments: Type[] | null = null;
|
2018-01-18 23:34:12 +01:00
|
|
|
if (this.is(ElementFlags.GENERIC)) {
|
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)
|
2018-01-02 03:54:06 +01:00
|
|
|
throw new Error("cannot resolve built-ins");
|
2017-11-20 23:39:50 +01:00
|
|
|
resolvedTypeArguments = this.program.resolveTypeArguments(this.declaration.typeParameters, typeArgumentNodes, contextualTypeArguments, alternativeReportNode);
|
|
|
|
if (!resolvedTypeArguments)
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return this.resolve(resolvedTypeArguments, contextualTypeArguments);
|
|
|
|
}
|
2017-12-15 15:00:19 +01:00
|
|
|
|
2018-01-01 20:27:21 +01:00
|
|
|
resolvePartial(classTypeArguments: Type[] | null): FunctionPrototype | null {
|
2018-01-02 03:54:06 +01:00
|
|
|
if (!this.classPrototype)
|
|
|
|
throw new Error("partially resolved instance method must reference its class prototype");
|
2018-01-01 20:27:21 +01:00
|
|
|
if (classTypeArguments && classTypeArguments.length) {
|
|
|
|
var partialPrototype = new FunctionPrototype(this.program, this.simpleName, this.internalName, this.declaration, this.classPrototype);
|
|
|
|
partialPrototype.flags = this.flags;
|
|
|
|
partialPrototype.classTypeArguments = classTypeArguments;
|
|
|
|
return partialPrototype;
|
|
|
|
}
|
|
|
|
return this; // no need to clone
|
|
|
|
}
|
|
|
|
|
2017-12-15 15:00:19 +01:00
|
|
|
toString(): string { return this.simpleName; }
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
/** A resolved function. */
|
|
|
|
export class Function extends Element {
|
2017-10-19 18:55:27 +02:00
|
|
|
|
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. */
|
2017-12-27 02:37:53 +01:00
|
|
|
typeArguments: Type[] | null;
|
2017-11-26 04:03:28 +01:00
|
|
|
/** Concrete function parameters. Excluding `this` if an instance method. */
|
2017-10-19 18:55:27 +02:00
|
|
|
parameters: Parameter[];
|
2017-11-20 23:39:50 +01:00
|
|
|
/** Concrete return type. */
|
2017-10-19 18:55:27 +02:00
|
|
|
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. */
|
2017-10-19 18:55:27 +02:00
|
|
|
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. */
|
2017-10-19 18:55:27 +02:00
|
|
|
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;
|
2018-01-18 23:34:12 +01:00
|
|
|
/** Current control flow. */
|
|
|
|
flow: Flow;
|
2017-10-19 18:55:27 +02:00
|
|
|
|
2017-12-08 16:11:58 +01:00
|
|
|
private nextBreakId: i32 = 0;
|
|
|
|
private breakStack: i32[] | null = null;
|
2017-10-19 18:55:27 +02:00
|
|
|
|
2017-11-20 23:39:50 +01:00
|
|
|
/** Constructs a new concrete function. */
|
2017-12-27 02:37:53 +01:00
|
|
|
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;
|
2017-10-19 18:55:27 +02:00
|
|
|
this.typeArguments = typeArguments;
|
|
|
|
this.parameters = parameters;
|
|
|
|
this.returnType = returnType;
|
|
|
|
this.instanceMethodOf = instanceMethodOf;
|
2017-12-13 04:46:05 +01:00
|
|
|
this.flags = prototype.flags;
|
2017-12-28 04:09:40 +01:00
|
|
|
var localIndex = 0;
|
2017-10-19 18:55:27 +02:00
|
|
|
if (instanceMethodOf) {
|
2018-01-18 23:34:12 +01:00
|
|
|
assert(this.is(ElementFlags.INSTANCE)); // internal error
|
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) {
|
2018-01-01 20:27:21 +01:00
|
|
|
if (!this.contextualTypeArguments)
|
|
|
|
this.contextualTypeArguments = new Map();
|
|
|
|
for (var [inheritedName, inheritedType] of instanceMethodOf.contextualTypeArguments)
|
|
|
|
this.contextualTypeArguments.set(inheritedName, inheritedType);
|
2017-12-15 15:00:19 +01:00
|
|
|
}
|
|
|
|
} else
|
2018-01-18 23:34:12 +01:00
|
|
|
assert(!this.is(ElementFlags.INSTANCE)); // internal error
|
2017-12-28 04:09:40 +01:00
|
|
|
for (var i = 0, k = parameters.length; i < k; ++i) {
|
|
|
|
var 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-10-19 18:55:27 +02:00
|
|
|
}
|
2018-01-18 23:34:12 +01:00
|
|
|
this.flow = Flow.create(this);
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
|
2017-11-20 23:39:50 +01:00
|
|
|
/** Adds a local of the specified type, with an optional name. */
|
2017-10-19 18:55:27 +02:00
|
|
|
addLocal(type: Type, name: string | null = null): Local {
|
|
|
|
// if it has a name, check previously as this method will throw otherwise
|
2017-12-28 04:09:40 +01:00
|
|
|
var localIndex = this.parameters.length + this.additionalLocals.length;
|
2018-01-18 23:34:12 +01:00
|
|
|
if (this.is(ElementFlags.INSTANCE)) localIndex++; // plus 'this'
|
2017-12-28 04:09:40 +01:00
|
|
|
var local = new Local(this.prototype.program, name ? name : "anonymous$" + localIndex.toString(10), localIndex, type);
|
2017-10-19 18:55:27 +02:00
|
|
|
if (name) {
|
2017-12-27 02:37:53 +01:00
|
|
|
if (this.locals.has(name))
|
2018-01-02 03:54:06 +01:00
|
|
|
throw new Error("duplicate local name");
|
2017-12-27 02:37:53 +01:00
|
|
|
this.locals.set(name, local);
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
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-12 09:32:03 +01:00
|
|
|
|
2017-12-13 23:24:13 +01:00
|
|
|
/** Gets a free temporary local of the specified type. */
|
2017-12-12 09:32:03 +01:00
|
|
|
getTempLocal(type: Type): Local {
|
2017-12-28 04:09:40 +01:00
|
|
|
var temps: Local[] | null;
|
2017-12-24 03:19:47 +01:00
|
|
|
switch (type.toNativeType()) {
|
2017-12-12 09:32:03 +01:00
|
|
|
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;
|
2018-01-02 03:54:06 +01:00
|
|
|
default: throw new Error("concrete type expected");
|
2017-12-12 09:32:03 +01:00
|
|
|
}
|
2018-01-01 20:27:21 +01:00
|
|
|
return temps && temps.length > 0
|
|
|
|
? temps.pop()
|
|
|
|
: this.addLocal(type);
|
2017-12-12 09:32:03 +01:00
|
|
|
}
|
|
|
|
|
2017-12-13 23:24:13 +01:00
|
|
|
/** Frees the temporary local for reuse. */
|
2017-12-12 09:32:03 +01:00
|
|
|
freeTempLocal(local: Local): void {
|
2017-12-28 04:09:40 +01:00
|
|
|
var temps: Local[];
|
2018-01-02 03:54:06 +01:00
|
|
|
assert(local.type != null); // internal error
|
2017-12-28 15:20:37 +01:00
|
|
|
switch ((<Type>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;
|
2018-01-02 03:54:06 +01:00
|
|
|
default: throw new Error("concrete type expected");
|
2017-12-12 09:32:03 +01:00
|
|
|
}
|
|
|
|
temps.push(local);
|
|
|
|
}
|
|
|
|
|
2017-12-13 23:24:13 +01:00
|
|
|
/** Gets and immediately frees a temporary local of the specified type. */
|
2017-12-12 09:32:03 +01:00
|
|
|
getAndFreeTempLocal(type: Type): Local {
|
2017-12-28 04:09:40 +01:00
|
|
|
var 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;
|
2018-01-02 03:54:06 +01:00
|
|
|
default: throw new Error("concrete type expected");
|
2017-12-12 09:32:03 +01:00
|
|
|
}
|
|
|
|
if (temps.length > 0)
|
|
|
|
return temps[temps.length - 1];
|
2017-12-28 04:09:40 +01:00
|
|
|
var local: Local = this.addLocal(type);
|
2017-12-12 09:32:03 +01:00
|
|
|
temps.push(local);
|
|
|
|
return local;
|
|
|
|
}
|
|
|
|
|
2017-11-20 23:39:50 +01:00
|
|
|
/** Enters a(nother) break context. */
|
2017-10-19 18:55:27 +02:00
|
|
|
enterBreakContext(): string {
|
2017-12-28 04:09:40 +01:00
|
|
|
var 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-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
|
2017-11-20 23:39:50 +01:00
|
|
|
/** Leaves the current break context. */
|
2017-10-19 18:55:27 +02:00
|
|
|
leaveBreakContext(): void {
|
2017-12-08 16:11:58 +01:00
|
|
|
assert(this.breakStack != null);
|
2017-12-28 04:09:40 +01:00
|
|
|
var 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 {
|
2017-10-19 18:55:27 +02:00
|
|
|
this.breakContext = null;
|
2017-12-08 16:11:58 +01:00
|
|
|
this.breakStack = null;
|
|
|
|
}
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
2017-12-15 15:00:19 +01:00
|
|
|
|
|
|
|
/** Finalizes the function once compiled, releasing no longer needed resources. */
|
|
|
|
finalize(): void {
|
2018-01-02 03:54:06 +01:00
|
|
|
assert(!this.breakStack || !this.breakStack.length); // internal error
|
2017-12-15 15:00:19 +01:00
|
|
|
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-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
/** A yet unresolved instance field prototype. */
|
2018-01-17 02:08:14 +01:00
|
|
|
export class FieldPrototype extends Element {
|
2017-10-19 18:55:27 +02:00
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
kind = ElementKind.FIELD_PROTOTYPE;
|
2017-12-13 04:46:05 +01:00
|
|
|
|
2018-01-17 02:08:14 +01:00
|
|
|
/** Declaration reference. */
|
|
|
|
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) {
|
2017-12-28 04:09:40 +01:00
|
|
|
for (var i = 0, k = this.declaration.modifiers.length; i < k; ++i) {
|
2017-12-13 04:46:05 +01:00
|
|
|
switch (this.declaration.modifiers[i].modifierKind) {
|
2018-01-18 23:34:12 +01:00
|
|
|
case ModifierKind.EXPORT: this.set(ElementFlags.EXPORTED); break;
|
|
|
|
case ModifierKind.READONLY: this.set(ElementFlags.READONLY); break;
|
2017-12-21 10:14:53 +01:00
|
|
|
case ModifierKind.PRIVATE:
|
|
|
|
case ModifierKind.PROTECTED:
|
|
|
|
case ModifierKind.PUBLIC:
|
2017-12-16 02:27:39 +01:00
|
|
|
case ModifierKind.STATIC: break; // already handled
|
2018-01-02 03:54:06 +01:00
|
|
|
default: throw new Error("unexpected modifier");
|
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. */
|
2018-01-17 02:08:14 +01:00
|
|
|
export class Field extends VariableLikeElement {
|
2017-11-17 14:33:51 +01:00
|
|
|
|
|
|
|
kind = ElementKind.FIELD;
|
2017-12-13 04:46:05 +01:00
|
|
|
|
|
|
|
/** Field prototype reference. */
|
|
|
|
prototype: FieldPrototype;
|
2018-01-01 20:27:21 +01:00
|
|
|
/** Field memory offset, if an instance field. */
|
|
|
|
memoryOffset: i32 = -1;
|
2017-11-17 14:33:51 +01:00
|
|
|
|
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);
|
2018-01-03 18:33:27 +01:00
|
|
|
this.prototype = prototype;
|
2017-12-13 04:46:05 +01:00
|
|
|
this.flags = prototype.flags;
|
2017-11-17 14:33:51 +01:00
|
|
|
this.type = type;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-27 02:37:53 +01:00
|
|
|
/** A property comprised of a getter and a setter function. */
|
|
|
|
export class Property extends Element {
|
2017-12-16 17:54:53 +01:00
|
|
|
|
2017-12-27 02:37:53 +01:00
|
|
|
kind = ElementKind.PROPERTY;
|
2017-12-16 17:54:53 +01:00
|
|
|
|
|
|
|
/** Parent class prototype. */
|
2017-12-27 02:37:53 +01:00
|
|
|
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);
|
2017-12-27 02:37:53 +01:00
|
|
|
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. */
|
2017-10-19 18:55:27 +02:00
|
|
|
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();
|
2017-12-15 02:50:55 +01:00
|
|
|
/** Instance member prototypes. */
|
|
|
|
instanceMembers: Map<string,Element> | null = null;
|
2018-01-07 15:07:46 +01:00
|
|
|
/** Base class prototype, if applicable. */
|
|
|
|
basePrototype: ClassPrototype | null = null; // set in Program#initialize
|
2018-01-27 05:35:14 +01:00
|
|
|
/** Constructor prototype. */
|
|
|
|
constructorPrototype: FunctionPrototype | null = null;
|
2017-10-19 18:55:27 +02:00
|
|
|
|
2018-01-06 10:20:38 +01:00
|
|
|
/** Overloaded indexed get method, if any. */
|
2018-01-13 23:38:07 +01:00
|
|
|
fnIndexedGet: string | null = null;
|
2018-01-06 10:20:38 +01:00
|
|
|
/** Overloaded indexed set method, if any. */
|
2018-01-13 23:38:07 +01:00
|
|
|
fnIndexedSet: string | null = null;
|
2018-01-06 10:20:38 +01:00
|
|
|
/** Overloaded concatenation method, if any. */
|
2018-01-13 23:38:07 +01:00
|
|
|
fnConcat: string | null = null;
|
2018-01-06 10:20:38 +01:00
|
|
|
/** Overloaded equality comparison method, if any. */
|
2018-01-13 23:38:07 +01:00
|
|
|
fnEquals: string | null = null;
|
2018-01-06 10:20:38 +01:00
|
|
|
|
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) {
|
2017-12-28 04:09:40 +01:00
|
|
|
for (var i = 0, k = this.declaration.modifiers.length; i < k; ++i) {
|
2017-12-13 04:46:05 +01:00
|
|
|
switch (this.declaration.modifiers[i].modifierKind) {
|
2018-01-18 23:34:12 +01:00
|
|
|
case ModifierKind.IMPORT: this.set(ElementFlags.IMPORTED); break;
|
|
|
|
case ModifierKind.EXPORT: this.set(ElementFlags.EXPORTED); break;
|
|
|
|
case ModifierKind.DECLARE: this.set(ElementFlags.DECLARED); break;
|
2017-12-13 04:46:05 +01:00
|
|
|
default: throw new Error("unexpected modifier");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (this.declaration.typeParameters.length)
|
2018-01-18 23:34:12 +01:00
|
|
|
this.set(ElementFlags.GENERIC);
|
2017-12-13 04:46:05 +01:00
|
|
|
}
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
|
2018-01-27 05:35:14 +01:00
|
|
|
/** Whether an unamanaged class or not. */
|
|
|
|
get isUnmanaged(): bool { return (this.flags & ElementFlags.UNMANAGED) != 0; }
|
|
|
|
set isUnmanaged(is: bool) { if (is) this.flags |= ElementFlags.UNMANAGED; else this.flags &= ~ElementFlags.UNMANAGED; }
|
2018-01-04 01:36:26 +01:00
|
|
|
|
|
|
|
resolve(typeArguments: Type[] | null, contextualTypeArguments: Map<string,Type> | null = null): Class | null {
|
2018-01-01 20:27:21 +01:00
|
|
|
var instanceKey = typeArguments ? typesToString(typeArguments) : "";
|
2017-12-28 04:09:40 +01:00
|
|
|
var instance = this.instances.get(instanceKey);
|
2017-10-19 18:55:27 +02:00
|
|
|
if (instance)
|
|
|
|
return instance;
|
2018-01-01 20:27:21 +01:00
|
|
|
|
2017-12-28 04:09:40 +01:00
|
|
|
var declaration = this.declaration;
|
2017-12-15 15:00:19 +01:00
|
|
|
if (!declaration)
|
2018-01-02 03:54:06 +01:00
|
|
|
throw new Error("cannot resolve built-ins");
|
2017-12-15 15:00:19 +01:00
|
|
|
|
2018-01-01 20:27:21 +01:00
|
|
|
// inherit contextual type arguments
|
|
|
|
var inheritedTypeArguments = contextualTypeArguments;
|
|
|
|
contextualTypeArguments = new Map();
|
|
|
|
if (inheritedTypeArguments)
|
|
|
|
for (var [inheritedName, inheritedType] of inheritedTypeArguments)
|
|
|
|
contextualTypeArguments.set(inheritedName, inheritedType);
|
2017-12-15 15:00:19 +01:00
|
|
|
|
2018-01-04 01:36:26 +01:00
|
|
|
var baseClass: Class | null = null;
|
|
|
|
if (declaration.extendsType) {
|
|
|
|
var baseClassType = this.program.resolveType(declaration.extendsType, null); // reports
|
|
|
|
if (!baseClassType)
|
|
|
|
return null;
|
|
|
|
if (!(baseClass = baseClassType.classType)) {
|
|
|
|
this.program.error(DiagnosticCode.A_class_may_only_extend_another_class, declaration.extendsType.range);
|
|
|
|
return null;
|
|
|
|
}
|
2018-01-27 05:35:14 +01:00
|
|
|
if (baseClass.prototype.isUnmanaged != this.isUnmanaged) {
|
2018-01-04 01:36:26 +01:00
|
|
|
this.program.error(DiagnosticCode.Structs_cannot_extend_classes_and_vice_versa, Range.join(declaration.name.range, declaration.extendsType.range));
|
|
|
|
return null;
|
|
|
|
}
|
2018-01-05 18:19:32 +01:00
|
|
|
} else
|
|
|
|
this.flags |= ElementFlags.HAS_STATIC_BASE_MEMBERS; // fwiw
|
2018-01-01 20:27:21 +01:00
|
|
|
|
|
|
|
// override call specific contextual type arguments if provided
|
|
|
|
var i: i32, k: i32;
|
2018-01-02 03:54:06 +01:00
|
|
|
if (typeArguments) {
|
|
|
|
if ((k = typeArguments.length) != declaration.typeParameters.length)
|
|
|
|
throw new Error("type argument count mismatch");
|
|
|
|
for (var i = 0; i < k; ++i)
|
2018-01-01 20:27:21 +01:00
|
|
|
contextualTypeArguments.set(declaration.typeParameters[i].identifier.name, typeArguments[i]);
|
2018-01-02 03:54:06 +01:00
|
|
|
} else if (declaration.typeParameters.length)
|
|
|
|
throw new Error("type argument count mismatch");
|
2017-12-15 15:00:19 +01:00
|
|
|
|
2017-12-28 04:09:40 +01:00
|
|
|
var internalName = this.internalName;
|
2017-12-15 15:00:19 +01:00
|
|
|
if (instanceKey.length)
|
|
|
|
internalName += "<" + instanceKey + ">";
|
2018-01-04 01:36:26 +01:00
|
|
|
instance = new Class(this, internalName, typeArguments, baseClass);
|
2017-12-15 15:00:19 +01:00
|
|
|
instance.contextualTypeArguments = contextualTypeArguments;
|
|
|
|
this.instances.set(instanceKey, instance);
|
2018-01-01 20:27:21 +01:00
|
|
|
|
2018-01-13 23:38:07 +01:00
|
|
|
var memoryOffset: u32 = 0;
|
2018-01-04 01:36:26 +01:00
|
|
|
if (baseClass) {
|
2018-01-13 23:38:07 +01:00
|
|
|
memoryOffset = baseClass.currentMemoryOffset;
|
2018-01-04 01:36:26 +01:00
|
|
|
if (baseClass.members) {
|
|
|
|
if (!instance.members)
|
|
|
|
instance.members = new Map();
|
|
|
|
for (var inheritedMember of baseClass.members.values())
|
|
|
|
instance.members.set(inheritedMember.simpleName, inheritedMember);
|
|
|
|
}
|
|
|
|
}
|
2018-01-01 20:27:21 +01:00
|
|
|
|
|
|
|
if (this.instanceMembers)
|
|
|
|
for (var member of this.instanceMembers.values()) {
|
|
|
|
switch (member.kind) {
|
|
|
|
|
|
|
|
case ElementKind.FIELD_PROTOTYPE: // fields are layed out in advance
|
|
|
|
if (!instance.members)
|
|
|
|
instance.members = new Map();
|
|
|
|
var fieldDeclaration = (<FieldPrototype>member).declaration;
|
|
|
|
if (!fieldDeclaration)
|
2018-01-02 03:54:06 +01:00
|
|
|
throw new Error("cannot resolve built-ins");
|
2018-01-01 20:27:21 +01:00
|
|
|
if (!fieldDeclaration.type)
|
|
|
|
throw new Error("type expected"); // TODO: check if parent class defines a type for it already
|
|
|
|
var fieldType = this.program.resolveType(fieldDeclaration.type, instance.contextualTypeArguments); // reports
|
|
|
|
if (fieldType) {
|
|
|
|
var fieldInstance = new Field(<FieldPrototype>member, (<FieldPrototype>member).internalName, fieldType);
|
2018-01-13 23:38:07 +01:00
|
|
|
switch (fieldType.byteSize) { // align
|
2018-01-01 20:27:21 +01:00
|
|
|
case 1: break;
|
|
|
|
case 2: if (memoryOffset & 1) ++memoryOffset; break;
|
|
|
|
case 4: if (memoryOffset & 3) memoryOffset = (memoryOffset | 3) + 1; break;
|
|
|
|
case 8: if (memoryOffset & 7) memoryOffset = (memoryOffset | 7) + 1; break;
|
|
|
|
default: assert(false);
|
|
|
|
}
|
|
|
|
fieldInstance.memoryOffset = memoryOffset;
|
|
|
|
memoryOffset += fieldType.byteSize;
|
|
|
|
instance.members.set(member.simpleName, fieldInstance);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ElementKind.FUNCTION_PROTOTYPE: // instance methods remain partially resolved prototypes until compiled
|
|
|
|
if (!instance.members)
|
|
|
|
instance.members = new Map();
|
|
|
|
var methodPrototype = (<FunctionPrototype>member).resolvePartial(typeArguments); // reports
|
|
|
|
if (methodPrototype)
|
|
|
|
instance.members.set(member.simpleName, methodPrototype);
|
|
|
|
break;
|
|
|
|
|
2018-01-14 21:17:43 +01:00
|
|
|
case ElementKind.PROPERTY: // instance properties are cloned with partially resolved getters and setters
|
2018-01-05 18:19:32 +01:00
|
|
|
if (!instance.members)
|
|
|
|
instance.members = new Map();
|
2018-01-14 21:17:43 +01:00
|
|
|
assert((<Property>member).getterPrototype);
|
|
|
|
var instanceProperty = new Property(this.program, member.simpleName, member.internalName, this);
|
|
|
|
instanceProperty.getterPrototype = (<FunctionPrototype>(<Property>member).getterPrototype).resolvePartial(typeArguments);
|
|
|
|
if ((<Property>member).setterPrototype)
|
|
|
|
instanceProperty.setterPrototype = (<FunctionPrototype>(<Property>member).setterPrototype).resolvePartial(typeArguments);
|
|
|
|
instance.members.set(member.simpleName, instanceProperty);
|
2018-01-05 18:19:32 +01:00
|
|
|
break;
|
|
|
|
|
2018-01-01 20:27:21 +01:00
|
|
|
default:
|
2018-01-05 18:19:32 +01:00
|
|
|
throw new Error("instance member expected: " + member.kind);
|
2018-01-01 20:27:21 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-13 23:38:07 +01:00
|
|
|
instance.currentMemoryOffset = memoryOffset; // sizeof<this>() is its byte size in memory
|
2017-12-15 15:00:19 +01:00
|
|
|
return instance;
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
2017-11-20 23:39:50 +01:00
|
|
|
|
|
|
|
resolveInclTypeArguments(typeArgumentNodes: TypeNode[] | null, contextualTypeArguments: Map<string,Type> | null, alternativeReportNode: Node | null): Class | null {
|
2018-01-02 03:54:06 +01:00
|
|
|
var resolvedTypeArguments: Type[] | null = null;
|
2018-01-18 23:34:12 +01:00
|
|
|
if (this.is(ElementFlags.GENERIC)) {
|
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)
|
2018-01-02 03:54:06 +01:00
|
|
|
throw new Error("cannot resolve built-ins");
|
2017-11-20 23:39:50 +01:00
|
|
|
resolvedTypeArguments = this.program.resolveTypeArguments(this.declaration.typeParameters, typeArgumentNodes, contextualTypeArguments, alternativeReportNode);
|
|
|
|
if (!resolvedTypeArguments)
|
|
|
|
return null;
|
2018-01-02 03:54:06 +01:00
|
|
|
} else
|
2017-12-15 15:00:19 +01:00
|
|
|
assert(typeArgumentNodes == null || !typeArgumentNodes.length);
|
2017-11-20 23:39:50 +01:00
|
|
|
return this.resolve(resolvedTypeArguments, contextualTypeArguments);
|
|
|
|
}
|
2017-12-15 15:00:19 +01:00
|
|
|
|
|
|
|
toString(): string { return this.simpleName; }
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
|
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-10-19 18:55:27 +02:00
|
|
|
|
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. */
|
2018-01-01 20:27:21 +01:00
|
|
|
typeArguments: Type[] | null;
|
2017-12-13 04:46:05 +01:00
|
|
|
/** Resolved class type. */
|
2017-10-19 18:55:27 +02:00
|
|
|
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;
|
2018-01-13 23:38:07 +01:00
|
|
|
/** Current member memory offset. */
|
|
|
|
currentMemoryOffset: u32 = 0;
|
2017-10-19 18:55:27 +02:00
|
|
|
|
2017-12-13 04:46:05 +01:00
|
|
|
/** Constructs a new class. */
|
2018-01-01 20:27:21 +01:00
|
|
|
constructor(prototype: ClassPrototype, internalName: string, typeArguments: Type[] | null = null, 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;
|
2017-10-19 18:55:27 +02:00
|
|
|
this.typeArguments = typeArguments;
|
2018-01-21 17:52:44 +01:00
|
|
|
this.type = prototype.program.options.usizeType.asClass(this);
|
2017-12-13 23:24:13 +01:00
|
|
|
this.base = base;
|
2017-10-19 18:55:27 +02:00
|
|
|
|
2018-01-05 18:19:32 +01:00
|
|
|
// inherit static members and contextual type arguments from base class
|
|
|
|
if (base) {
|
|
|
|
if (base.contextualTypeArguments) {
|
|
|
|
if (!this.contextualTypeArguments)
|
|
|
|
this.contextualTypeArguments = new Map();
|
|
|
|
for (var [baseName, baseType] of base.contextualTypeArguments)
|
|
|
|
this.contextualTypeArguments.set(baseName, baseType);
|
|
|
|
}
|
2017-12-15 15:00:19 +01:00
|
|
|
}
|
2017-10-19 18:55:27 +02:00
|
|
|
|
|
|
|
// apply instance-specific contextual type arguments
|
2017-12-28 04:09:40 +01:00
|
|
|
var declaration = this.prototype.declaration;
|
2018-01-01 20:27:21 +01:00
|
|
|
var i: i32, k: i32;
|
2017-12-05 13:35:14 +01:00
|
|
|
if (declaration) { // irrelevant for built-ins
|
2017-12-28 04:09:40 +01:00
|
|
|
var typeParameters = declaration.typeParameters;
|
2018-01-01 20:27:21 +01:00
|
|
|
if (typeArguments) {
|
2018-01-02 03:54:06 +01:00
|
|
|
if ((k = typeArguments.length) != typeParameters.length)
|
|
|
|
throw new Error("type argument count mismatch");
|
|
|
|
if (k) {
|
2018-01-01 20:27:21 +01:00
|
|
|
if (!this.contextualTypeArguments)
|
|
|
|
this.contextualTypeArguments = new Map();
|
|
|
|
for (i = 0; i < k; ++i)
|
|
|
|
this.contextualTypeArguments.set(typeParameters[i].identifier.name, typeArguments[i]);
|
|
|
|
}
|
2018-01-02 03:54:06 +01:00
|
|
|
} else if (typeParameters.length)
|
|
|
|
throw new Error("type argument count mismatch");
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
}
|
2017-12-01 02:08:03 +01:00
|
|
|
|
2017-12-15 15:00:19 +01:00
|
|
|
toString(): string { return this.prototype.simpleName; }
|
2017-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
|
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-10-19 18:55:27 +02:00
|
|
|
|
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-10-19 18:55:27 +02:00
|
|
|
|
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-10-19 18:55:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-17 14:33:51 +01:00
|
|
|
/** A resolved interface. */
|
|
|
|
export class Interface extends Class {
|
2017-10-19 18:55:27 +02:00
|
|
|
|
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-10-19 18:55:27 +02:00
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
2018-01-18 23:34:12 +01:00
|
|
|
|
|
|
|
/** Control flow flags indicating specific conditions. */
|
|
|
|
export const enum FlowFlags {
|
2018-01-19 04:16:18 +01:00
|
|
|
/** No specific conditions. */
|
2018-01-18 23:34:12 +01:00
|
|
|
NONE = 0,
|
2018-01-19 04:16:18 +01:00
|
|
|
/** This branch always returns. */
|
2018-01-18 23:34:12 +01:00
|
|
|
RETURNS = 1 << 0,
|
2018-01-19 04:16:18 +01:00
|
|
|
/** This branch possibly throws. */
|
|
|
|
POSSIBLY_THROWS = 1 << 1,
|
2018-01-23 15:44:25 +01:00
|
|
|
/** This branch possible breaks. */
|
|
|
|
POSSIBLY_BREAKS = 1 << 2,
|
|
|
|
/** This branch possible continues. */
|
|
|
|
POSSIBLY_CONTINUES = 1 << 3
|
2018-01-18 23:34:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/** A control flow evaluator. */
|
|
|
|
export class Flow {
|
|
|
|
|
|
|
|
/** Parent flow. */
|
|
|
|
parent: Flow | null;
|
|
|
|
/** Flow flags indicating specific conditions. */
|
|
|
|
flags: FlowFlags;
|
|
|
|
/** Function this flow belongs to. */
|
|
|
|
currentFunction: Function;
|
|
|
|
/** The label we break to when encountering a continue statement. */
|
|
|
|
continueLabel: string | null;
|
|
|
|
/** The label we break to when encountering a break statement. */
|
|
|
|
breakLabel: string | null;
|
|
|
|
/** Scoped local variables. */
|
|
|
|
scopedLocals: Map<string,Local> | null = null;
|
|
|
|
|
|
|
|
/** Creates the parent flow of the specified function. */
|
|
|
|
static create(currentFunction: Function): Flow {
|
|
|
|
var parentFlow = new Flow();
|
2018-01-19 04:16:18 +01:00
|
|
|
parentFlow.parent = null;
|
2018-01-18 23:34:12 +01:00
|
|
|
parentFlow.flags = FlowFlags.NONE;
|
|
|
|
parentFlow.currentFunction = currentFunction;
|
|
|
|
parentFlow.continueLabel = null;
|
|
|
|
parentFlow.breakLabel = null;
|
|
|
|
return parentFlow;
|
|
|
|
}
|
|
|
|
|
|
|
|
private constructor() { }
|
|
|
|
|
|
|
|
/** Tests if this flow has the specified flag or flags. */
|
|
|
|
is(flag: FlowFlags): bool { return (this.flags & flag) == flag; }
|
|
|
|
|
|
|
|
/** Sets the specified flag or flags. */
|
|
|
|
set(flag: FlowFlags): void { this.flags |= flag; }
|
|
|
|
|
2018-01-19 04:16:18 +01:00
|
|
|
/** Enters a new branch or scope and returns the new flow. */
|
|
|
|
enterBranchOrScope(): Flow {
|
2018-01-18 23:34:12 +01:00
|
|
|
var branchFlow = new Flow();
|
|
|
|
branchFlow.parent = this;
|
|
|
|
branchFlow.flags = this.flags;
|
|
|
|
branchFlow.currentFunction = this.currentFunction;
|
|
|
|
branchFlow.continueLabel = this.continueLabel;
|
|
|
|
branchFlow.breakLabel = this.breakLabel;
|
|
|
|
return branchFlow;
|
|
|
|
}
|
|
|
|
|
2018-01-19 04:16:18 +01:00
|
|
|
/** Leaves the current branch or scope and returns the parent flow. */
|
|
|
|
leaveBranchOrScope(): Flow {
|
|
|
|
var parent = assert(this.parent);
|
2018-01-23 15:44:25 +01:00
|
|
|
|
|
|
|
// Free block-scoped locals
|
2018-01-18 23:34:12 +01:00
|
|
|
if (this.scopedLocals) {
|
|
|
|
for (var scopedLocal of this.scopedLocals.values())
|
|
|
|
this.currentFunction.freeTempLocal(scopedLocal);
|
|
|
|
this.scopedLocals = null;
|
|
|
|
}
|
2018-01-23 15:44:25 +01:00
|
|
|
|
|
|
|
// Propagate flags to parent
|
2018-01-19 04:16:18 +01:00
|
|
|
if (this.is(FlowFlags.POSSIBLY_THROWS))
|
2018-01-23 15:44:25 +01:00
|
|
|
parent.set(FlowFlags.POSSIBLY_THROWS);
|
|
|
|
if (this.is(FlowFlags.POSSIBLY_BREAKS) && parent.breakLabel == this.breakLabel)
|
|
|
|
parent.set(FlowFlags.POSSIBLY_BREAKS);
|
|
|
|
if (this.is(FlowFlags.POSSIBLY_CONTINUES) && parent.continueLabel == this.continueLabel)
|
|
|
|
parent.set(FlowFlags.POSSIBLY_CONTINUES);
|
2018-01-19 04:16:18 +01:00
|
|
|
|
|
|
|
return parent;
|
2018-01-18 23:34:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Adds a new scoped local of the specified name. */
|
|
|
|
addScopedLocal(name: string, type: Type, reportNode: Node): void {
|
|
|
|
var scopedLocal = this.currentFunction.getTempLocal(type);
|
|
|
|
if (!this.scopedLocals)
|
|
|
|
this.scopedLocals = new Map();
|
|
|
|
else if (this.scopedLocals.has(name)) {
|
|
|
|
this.currentFunction.program.error(DiagnosticCode.Duplicate_identifier_0, reportNode.range);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.scopedLocals.set(name, scopedLocal);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Gets the local of the specified name in the current scope. */
|
|
|
|
getScopedLocal(name: string): Local | null {
|
|
|
|
var local: Local | null;
|
|
|
|
var current: Flow | null = this;
|
|
|
|
do {
|
|
|
|
if (current.scopedLocals && (local = current.scopedLocals.get(name)))
|
|
|
|
return local;
|
|
|
|
} while (current = current.parent);
|
|
|
|
return this.currentFunction.locals.get(name);
|
|
|
|
}
|
2018-01-19 04:16:18 +01:00
|
|
|
|
|
|
|
/** Finalizes this flow. Must be the topmost parent flow of the function. */
|
|
|
|
finalize(): bool {
|
|
|
|
assert(this.parent == null, "must be the topmost parent flow");
|
|
|
|
this.continueLabel = null;
|
|
|
|
this.breakLabel = null;
|
|
|
|
return this.is(FlowFlags.RETURNS);
|
|
|
|
}
|
2018-01-18 23:34:12 +01:00
|
|
|
}
|