jsx
Version:
a faster, safer, easier JavaScript
1,263 lines (1,151 loc) • 76.2 kB
JSX
/*
* Copyright (c) 2012 DeNA Co., Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
import "./analysis.jsx";
import "./type.jsx";
import "./util.jsx";
import "./statement.jsx";
import "./expression.jsx";
import "./parser.jsx";
import "./doc.jsx";
import "./optimizer.jsx";
mixin TemplateDefinition {
function buildInstantiationContext (errors : CompileError[], token : Token, formalTypeArgs : Token[], actualTypeArgs : Type[]) : InstantiationContext {
// check number of type arguments
if (formalTypeArgs.length != actualTypeArgs.length) {
errors.push(new CompileError(token, "wrong number of template arguments (expected " + formalTypeArgs.length as string + ", got " + actualTypeArgs.length as string + ")"));
return null;
}
// build typemap
var typemap = new Map.<Type>;
for (var i = 0; i < formalTypeArgs.length; ++i) {
typemap[formalTypeArgs[i].getValue()] = actualTypeArgs[i];
}
return new InstantiationContext(errors, typemap);
}
}
class ClassDefinition implements Stashable {
static const IS_CONST = 1;
static const IS_ABSTRACT = 2;
static const IS_FINAL = 4;
static const IS_STATIC = 8;
static const IS_NATIVE = 16;
static const IS_OVERRIDE = 32;
static const IS_INTERFACE = 64;
static const IS_MIXIN = 128;
static const IS_FAKE = 256; // used for marking a JS non-class object that should be treated like a JSX class instance (e.g. window)
static const IS_READONLY = 512;
static const IS_INLINE = 1024;
static const IS_PURE = 2048; // constexpr (intended for for native functions)
static const IS_DELETE = 4096; // used for disabling the default constructor
static const IS_GENERATOR = 8192;
static const IS_EXPORT = 16384; // no overloading, no minification of method / variable names
var _parser : Parser;
var _token : Token;
var _className : string;
var _flags : number;
var _extendType : ParsedObjectType; // null for interfaces, mixins, and Object class only
var _implementTypes : ParsedObjectType[];
var _members : MemberDefinition[];
var _inners : ClassDefinition[];
var _templateInners : TemplateClassDefinition[];
var _objectTypesUsed : ParsedObjectType[];
var _docComment : DocComment;
var _baseClassDef : ClassDefinition = null;
var _outerClassDef : ClassDefinition = null;
var _nativeSource : Token = null;
function constructor (token : Token, className : string, flags : number, extendType : ParsedObjectType, implementTypes : ParsedObjectType[], members : MemberDefinition[], inners : ClassDefinition[], templateInners : TemplateClassDefinition[], objectTypesUsed : ParsedObjectType[], docComment : DocComment) {
this._parser = null;
this._token = token;
this._className = className;
this._flags = flags;
this._extendType = extendType;
this._implementTypes = implementTypes;
this._members = members;
this._inners = inners;
this._templateInners = templateInners;
this._objectTypesUsed = objectTypesUsed;
this._docComment = docComment;
this._resetMembersClassDef();
}
function serialize () : variant {
// FIXME implement in a way that is compatible with JSX
return {
"token" : this._token,
"name" : this._className,
"flags" : this._flags,
"extends" : Serializer.<ParsedObjectType>.serializeNullable(this._extendType),
"implements" : Serializer.<ParsedObjectType>.serializeArray(this._implementTypes),
"members" : Serializer.<MemberDefinition>.serializeArray(this._members)
} : Map.<variant>;
}
static function serialize (classDefs : ClassDefinition[]) : variant {
var s = new variant[];
for (var i = 0; i < classDefs.length; ++i)
s[i] = classDefs[i].serialize();
return s;
}
function getParser () : Parser {
return this._parser;
}
function setParser (parser : Parser) : void {
this._parser = parser;
}
function getNativeSource () : Token {
return this._nativeSource;
}
function setNativeSource (nativeSource : Token) : void {
this._nativeSource = nativeSource;
}
function getToken () : Token {
return this._token;
}
function className () : string {
return this._className;
}
function classFullName () : string {
return this._outerClassDef != null ? this._outerClassDef.classFullName() + "." + this._className : this.className();
}
function flags () : number {
return this._flags;
}
function setFlags (flags : number) : void {
this._flags = flags;
}
function extendType () : ParsedObjectType {
return this._extendType;
}
function implementTypes () : ParsedObjectType[] {
return this._implementTypes;
}
function members () : MemberDefinition[] {
return this._members;
}
function setOuterClassDef (outer : ClassDefinition) : void {
this._outerClassDef = outer;
}
function getOuterClassDef () : ClassDefinition {
return this._outerClassDef;
}
function getInnerClasses () : ClassDefinition[] {
return this._inners;
}
function getTemplateInnerClasses () : TemplateClassDefinition[] {
return this._templateInners;
}
function getDocComment () : DocComment {
return this._docComment;
}
function setDocComment (docComment : DocComment) : void {
this._docComment = docComment;
}
function forEachClassToBase (cb : function(:ClassDefinition):boolean) : boolean {
if (! cb(this))
return false;
for (var i = this._implementTypes.length - 1; i >= 0; --i) {
if (! cb(this._implementTypes[i].getClassDef()))
return false;
}
if (this._extendType != null) {
if (! this._extendType.getClassDef().forEachClassToBase(cb))
return false;
}
return true;
}
function forEachClassFromBase (cb : function(:ClassDefinition):boolean) : boolean {
if (this._extendType != null)
if (! this._extendType.getClassDef().forEachClassFromBase(cb))
return false;
for (var i = 0; i < this._implementTypes.length; ++i) {
if (! cb(this._implementTypes[i].getClassDef()))
return false;
}
if (! cb(this))
return false;
return true;
}
function forEachMember (cb : function(:MemberDefinition):boolean) : boolean {
for (var i = 0; i < this._members.length; ++i) {
if (! cb(this._members[i]))
return false;
}
return true;
}
function forEachMemberVariable (cb : function(:MemberVariableDefinition):boolean) : boolean {
for (var i = 0; i < this._members.length; ++i) {
if (this._members[i] instanceof MemberVariableDefinition) {
if (! cb(this._members[i] as MemberVariableDefinition))
return false;
}
}
return true;
}
function forEachMemberFunction (cb : function(:MemberFunctionDefinition):boolean) : boolean {
for (var i = 0; i < this._members.length; ++i) {
if (this._members[i] instanceof MemberFunctionDefinition) {
if (! cb(this._members[i] as MemberFunctionDefinition))
return false;
}
}
return true;
}
function forEachInnerClass (cb : function(:ClassDefinition):boolean) : boolean {
for (var i = 0; i < this._inners.length; ++i) {
if (! cb(this._inners[i]))
return false;
}
return true;
}
function _resetMembersClassDef () : void {
// member defintions
for (var i = 0; i < this._members.length; ++i) {
this._members[i].setClassDef(this);
this._members[i].forEachClosure(function setClassDef(funcDef) {
funcDef.setClassDef(this);
return funcDef.forEachClosure(setClassDef);
});
}
// member classes
for (var i = 0; i < this._inners.length; ++i) {
this._inners[i].setOuterClassDef(this);
this._inners[i]._resetMembersClassDef();
}
for (var i = 0; i < this._templateInners.length; ++i) {
this._templateInners[i].setOuterClassDef(this);
}
}
static const GET_MEMBER_MODE_ALL = 0; // looks for functions or variables from the class and all super classes
static const GET_MEMBER_MODE_CLASS_ONLY = 1; // looks for functions or variables within the class
static const GET_MEMBER_MODE_SUPER = 2; // looks for functions with body in super classes
static const GET_MEMBER_MODE_FUNCTION_WITH_BODY = 3; // looks for function with body
function getMemberTypeByName (errors : CompileError[], token : Token, name : string, isStatic : boolean, typeArgs : Type[], mode : number) : Type {
// returns an array to support function overloading
var types = new Type[];
function pushMatchingMember(classDef : ClassDefinition) : void {
if (mode != ClassDefinition.GET_MEMBER_MODE_SUPER) {
for (var i = 0; i < classDef._members.length; ++i) {
var member = classDef._members[i];
if ((member.flags() & ClassDefinition.IS_DELETE) != 0) {
// skip
} else if (((member.flags() & ClassDefinition.IS_STATIC) != 0) == isStatic
&& name == member.name()) {
if (member instanceof MemberVariableDefinition) {
if ((member.flags() & ClassDefinition.IS_OVERRIDE) == 0) {
var type = (member as MemberVariableDefinition).getType();
// ignore member variables that failed in type deduction (already reported as a compile error)
// it is guranteed by _assertMemberVariableIsDefinable that there would not be a property with same name using different type, so we can use the first one (declarations might be found more than once using the "abstract" attribute)
if (type != null && types.length == 0)
types[0] = type;
}
} else if (member instanceof MemberFunctionDefinition) {
// member function
if (member instanceof InstantiatedMemberFunctionDefinition) {
// skip
} else {
if (member instanceof TemplateFunctionDefinition) {
if ((member = (member as TemplateFunctionDefinition).instantiateTemplateFunction(errors, token, typeArgs)) == null) {
return;
}
}
if ((member as MemberFunctionDefinition).getStatements() != null || mode != ClassDefinition.GET_MEMBER_MODE_FUNCTION_WITH_BODY) {
for (var j = 0; j < types.length; ++j) {
if (Util.typesAreEqual((member as MemberFunctionDefinition).getArgumentTypes(), (types[j] as ResolvedFunctionType).getArgumentTypes())) {
break;
}
}
if (j == types.length) {
types.push((member as MemberFunctionDefinition).getType());
}
}
}
} else {
throw new Error("logic flaw");
}
}
}
} else {
// for searching super classes, change mode GET_MEMBER_MODE_SUPER to GET_MEMBER_MODE_FUNCTION_WITH_BODY
mode = ClassDefinition.GET_MEMBER_MODE_FUNCTION_WITH_BODY;
}
if (mode != ClassDefinition.GET_MEMBER_MODE_CLASS_ONLY) {
if (classDef._extendType != null) {
pushMatchingMember(classDef._extendType.getClassDef());
}
for (var i = 0; i < classDef._implementTypes.length; ++i) {
pushMatchingMember(classDef._implementTypes[i].getClassDef());
}
}
}
pushMatchingMember(this);
switch (types.length) {
case 0:
return null;
case 1:
return types[0];
default:
return new FunctionChoiceType(types.map.<ResolvedFunctionType>(function(t) { return t as ResolvedFunctionType; }));
}
}
function lookupInnerClass (className : string) : ClassDefinition {
for (var i = 0; i < this._inners.length; ++i) {
var inner = this._inners[i];
if (inner.className() == className)
return inner;
}
return null;
}
function lookupTemplateInnerClass (errors : CompileError[], request : TemplateInstantiationRequest, postInstantiationCallback : (Parser,ClassDefinition)->ClassDefinition) : ClassDefinition {
var instantiateCallback = this.createGetTemplateClassCallback(errors, request, postInstantiationCallback);
if (instantiateCallback != null)
return instantiateCallback(errors, request, postInstantiationCallback);
return null;
}
function createGetTemplateClassCallback (errors : CompileError[], request : TemplateInstantiationRequest, postInstantiationCallback : (Parser,ClassDefinition)->ClassDefinition) : (CompileError[],TemplateInstantiationRequest,(Parser,ClassDefinition)->ClassDefinition)->ClassDefinition {
// lookup the already-instantiated class
for (var i = 0; i < this._inners.length; ++i) {
var classDef = this._inners[i];
if (classDef instanceof InstantiatedClassDefinition
&& (classDef as InstantiatedClassDefinition).getTemplateClassName() == request.getClassName()
&& Util.typesAreEqual((classDef as InstantiatedClassDefinition).getTypeArguments(), request.getTypeArguments())) {
return function (_ : CompileError[], __ : TemplateInstantiationRequest, ___ : (Parser,ClassDefinition)->ClassDefinition) : ClassDefinition {
return classDef;
};
}
}
// create instantiation callback
for (var i = 0; i < this._templateInners.length; ++i) {
var templateDef = this._templateInners[i];
if (templateDef.className() == request.getClassName()) {
return function (_ : CompileError[], __ : TemplateInstantiationRequest, ___ : (Parser,ClassDefinition)->ClassDefinition) : ClassDefinition {
var classDef = templateDef.instantiateTemplateClass(errors, request);
if (classDef == null) {
return null;
}
this._inners.push(classDef);
classDef.setParser(this._parser);
classDef.resolveTypes(new AnalysisContext(errors, this._parser, null));
postInstantiationCallback(this._parser, classDef);
return classDef;
};
}
}
return null;
}
function instantiate (instantiationContext : InstantiationContext) : ClassDefinition {
var context = new InstantiationContext(instantiationContext.errors, instantiationContext.typemap);
// instantiate the members
var succeeded = true;
var members = new MemberDefinition[];
for (var i = 0; i < this._members.length; ++i) {
var member = this._members[i].instantiate(context);
if (member == null)
succeeded = false;
members[i] = member;
}
var inners = new ClassDefinition[];
for (var i = 0; i < this._inners.length; ++i) {
var inner = this._inners[i].instantiate(context);
if (inner == null)
succeeded = false;
inners[i] = inner;
}
var templateInners = new TemplateClassDefinition[];
for (var i = 0; i < this._templateInners.length; ++i) {
var templateInner = this._templateInners[i].instantiate(context);
if (templateInner == null)
succeeded = false;
templateInners[i] = templateInner;
}
// done
if (! succeeded)
return null;
var extendType = null : ParsedObjectType;
if (this._extendType != null) {
var type = this._extendType.instantiate(instantiationContext);
if (! (type instanceof ParsedObjectType)) {
instantiationContext.errors.push(new CompileError(this._extendType.getToken(), "non-object type is not extensible"));
return null;
}
extendType = type as ParsedObjectType;
}
var implementTypes = new ParsedObjectType[];
for (var i = 0; i < this._implementTypes.length; ++i) {
var type = this._implementTypes[i].instantiate(instantiationContext);
if (! (type instanceof ParsedObjectType)) {
instantiationContext.errors.push(new CompileError(this._implementTypes[i].getToken(), "non-object type is not extensible"));
return null;
}
implementTypes[i] = type as ParsedObjectType;
}
return new ClassDefinition(
this._token,
this._className,
this._flags,
extendType,
implementTypes,
members,
inners,
templateInners,
context.objectTypesUsed,
this._docComment
);
}
function resolveTypes (context : AnalysisContext) : void {
// resolve types used
for (var i = 0; i < this._objectTypesUsed.length; ++i)
this._objectTypesUsed[i].resolveType(context);
for (var i = 0; i < this._inners.length; ++i)
this._inners[i].resolveTypes(context);
// resolve base classes
if (this._extendType != null) {
var baseClass = this._extendType.getClassDef();
if (baseClass != null) {
if ((baseClass.flags() & ClassDefinition.IS_FINAL) != 0)
context.errors.push(new CompileError(this._extendType.getToken(), "cannot extend a final class"));
else if ((baseClass.flags() & ClassDefinition.IS_INTERFACE) != 0)
context.errors.push(new CompileError(this._extendType.getToken(), "cannot extend an interface, use the 'implements' keyword"));
else if ((baseClass.flags() & ClassDefinition.IS_MIXIN) != 0)
context.errors.push(new CompileError(this._extendType.getToken(), "cannot extend an mixin, use the 'implements' keyword"));
}
}
for (var i = 0; i < this._implementTypes.length; ++i) {
var baseClass = this._implementTypes[i].getClassDef();
if (baseClass != null) {
if ((baseClass.flags() & (ClassDefinition.IS_INTERFACE | ClassDefinition.IS_MIXIN)) == 0) {
context.errors.push(new CompileError(this._implementTypes[i].getToken(), "cannot implement a class (only interfaces can be implemented)"));
} else {
for (var j = i + 1; j < this._implementTypes.length; ++j) {
if (this._implementTypes[j].getClassDef() == baseClass) {
context.errors.push(new CompileError(this._implementTypes[i].getToken(), "cannot implement the same interface more than once"));
break;
}
}
}
}
}
// create default constructor if no constructors exist
if (this.forEachMemberFunction(function (funcDef) { return funcDef.name() != "constructor"; })) {
var isNative = (this.flags() & ClassDefinition.IS_NATIVE) != 0;
var func = new MemberFunctionDefinition(
this._token,
new Token("constructor", true),
ClassDefinition.IS_FINAL | (this.flags() & (ClassDefinition.IS_NATIVE | ClassDefinition.IS_EXPORT)),
Type.voidType,
new ArgumentDeclaration[],
isNative ? (null) : new LocalVariable[],
isNative ? (null) : new Statement[],
new MemberFunctionDefinition[],
this._token, /* FIXME */
null);
func.setClassDef(this);
this._members.push(func);
}
// substitute access to inner class with single ClassExpression
this.forEachMemberFunction(function (funcDef) {
return funcDef.forEachStatement(function (statement) {
return statement.forEachExpression(function onExpr(expr : Expression, replaceCb : (Expression)->void) : boolean {
expr.forEachExpression(onExpr);
if (expr instanceof PropertyExpression && (expr as PropertyExpression).getExpr() instanceof ClassExpression) {
var propExpr = expr as PropertyExpression;
var identifierToken = propExpr.getIdentifierToken();
var receiverType = (propExpr.getExpr() as ClassExpression).getType() as ParsedObjectType;
var receiverClassDef = receiverType.getClassDef();
if (receiverClassDef) {
receiverClassDef.forEachInnerClass(function (classDef) {
if (classDef.className() == identifierToken.getValue()) {
var objectType = new ParsedObjectType(
new QualifiedName(identifierToken, receiverType),
propExpr.getTypeArguments()
);
objectType.resolveType(context);
replaceCb(new ClassExpression(propExpr.getToken(), objectType));
return false;
}
return true;
});
}
else {
return true;
}
}
return true;
});
});
});
}
function setAnalysisContextOfVariables (context : AnalysisContext) : void {
for (var i = 0; i < this._members.length; ++i) {
var member = this._members[i];
if (member instanceof MemberVariableDefinition)
(member as MemberVariableDefinition).setAnalysisContext(context);
}
}
function analyze (context : AnalysisContext) : void {
try {
this._analyzeClassDef(context);
} catch (e : Error) {
var token = this.getToken();
var srcPos = token != null ? Util.format(" at file %1, line %2", [token.getFilename(), token.getLineNumber() as string]) : "";
e.message = Util.format("fatal error while analyzing class %1%2\n%3", [this.className(), srcPos, e.message]);
throw e;
}
this._analyzeMembers(context);
}
function _analyzeClassDef (context : AnalysisContext) : void {
this._baseClassDef = this.extendType() != null ? this.extendType().getClassDef() : null;
var implementClassDefs = this.implementTypes().map.<ClassDefinition>(function (type) {
return type.getClassDef();
});
// check that inheritance is not in loop, and that classes are extended, and interfaces / mixins are implemented
if ((this.flags() & (ClassDefinition.IS_INTERFACE | ClassDefinition.IS_MIXIN)) == 0) {
if (this._baseClassDef != null) {
if ((this._baseClassDef.flags() & ClassDefinition.IS_FINAL) != 0) {
context.errors.push(new CompileError(this.getToken(), "cannot extend final class '" + this._baseClassDef.className() + "'"));
return;
}
if ((this._baseClassDef.flags() & (ClassDefinition.IS_INTERFACE | ClassDefinition.IS_MIXIN)) != 0) {
context.errors.push(new CompileError(this.getToken(), "interfaces (or mixins) should be implemented, not extended"));
return;
}
if (! this._baseClassDef.forEachClassToBase(function (classDef : ClassDefinition) : boolean {
if (this == classDef) {
context.errors.push(new CompileError(this.getToken(), "class inheritance is in loop"));
return false;
}
return true;
})) {
return;
}
}
} else {
for (var i = 0; i < implementClassDefs.length; ++i) {
if ((implementClassDefs[i].flags() & (ClassDefinition.IS_INTERFACE | ClassDefinition.IS_MIXIN)) == 0) {
context.errors.push(new CompileError(this.getToken(), "class '" + implementClassDefs[i].className() + "' can only be extended, not implemented"));
return;
}
if (! implementClassDefs[i].forEachClassToBase(function (classDef) {
if (this == classDef) {
context.errors.push(new CompileError(this.getToken(), "class inheritance is in loop"));
return false;
}
return true;
})) {
return;
}
}
}
// check that none of the mixins are implemented twice
var allMixins = new ClassDefinition[];
if (! this.forEachClassToBase(function (classDef) {
if ((classDef.flags() & ClassDefinition.IS_MIXIN) != 0) {
if (allMixins.indexOf(classDef) != -1) {
context.errors.push(new CompileError(this.getToken(), "mixin '" + classDef.className() + "' is implemented twice"));
return false;
}
allMixins.push(classDef);
}
return true;
})) {
return;
}
// check that the properties of the class does not conflict with those in base classes or implemented interfaces
for (var i = 0; i < this._members.length; ++i) {
this._assertMemberIsDefinable(context, this._members[i], this, this._members[i].getToken());
}
// check that the properties of the implemented interfaces does not conflict with those in base classes or other implement interfaces
for (var i = 0; i < this._implementTypes.length; ++i) {
var interfaceDef = this._implementTypes[i].getClassDef();
for (var j = 0; j < interfaceDef._members.length; ++j)
this._assertMemberIsDefinable(context, interfaceDef._members[j], interfaceDef, this._implementTypes[i].getToken());
}
// check that the member functions with "override" attribute are in fact overridable
if ((this._flags & (ClassDefinition.IS_INTERFACE | ClassDefinition.IS_MIXIN)) == 0) {
for (var i = 0; i < this._members.length; ++i)
if (this._members[i] instanceof MemberFunctionDefinition && (this._members[i].flags() & ClassDefinition.IS_OVERRIDE) != 0)
if (this._assertFunctionIsOverridableInBaseClasses(context, this._members[i] as MemberFunctionDefinition) == null)
context.errors.push(new CompileError(this._members[i].getNameToken(), "could not find function definition in base classes / mixins to be overridden"));
for (var i = 0; i < this._implementTypes.length; ++i) {
if ((this._implementTypes[i].getClassDef().flags() & ClassDefinition.IS_MIXIN) == 0)
continue;
var theMixin = this._implementTypes[i].getClassDef();
var overrideFunctions = new MemberDefinition[];
theMixin._getMembers(overrideFunctions, true, ClassDefinition.IS_OVERRIDE, ClassDefinition.IS_OVERRIDE);
for (var j = 0; j < overrideFunctions.length; ++j) {
var done = false;
if (this._baseClassDef != null)
if (this._baseClassDef._assertFunctionIsOverridable(context, overrideFunctions[j] as MemberFunctionDefinition) != null)
done = true;
// check sibling interfaces / mixins
for (var k = 0; k < i; ++k) {
if (this._implementTypes[k].getClassDef()._assertFunctionIsOverridable(context, overrideFunctions[j] as MemberFunctionDefinition) != null) {
done = true;
break;
}
}
// check parent interfaces / mixins of the mixin
for (var k = 0; k < theMixin._implementTypes.length; ++k) {
if (theMixin._implementTypes[k].getClassDef()._assertFunctionIsOverridable(context, overrideFunctions[j] as MemberFunctionDefinition) != null) {
done = true;
break;
}
}
if (! done)
context.errors.push(new CompileError(this.getToken(), "could not find function definition to be overridden by '" + overrideFunctions[j].getNotation() + "'"));
}
}
}
// check that there are no "abstract" members for a concrete class
if ((this._flags & (ClassDefinition.IS_ABSTRACT | ClassDefinition.IS_INTERFACE | ClassDefinition.IS_MIXIN)) == 0) {
var abstractMembers = new MemberDefinition[];
this.forEachClassToBase(function (classDef) {
return classDef.forEachMember(function (member) {
if ((member.flags() & ClassDefinition.IS_ABSTRACT) != 0) {
for (var i = 0; i < abstractMembers.length; ++i) {
if (ClassDefinition.membersAreEqual(abstractMembers[i], member)) {
break;
}
}
if (i == abstractMembers.length) {
abstractMembers[i] = member;
}
}
return true;
});
});
this.forEachClassToBase(function (classDef) {
return classDef.forEachMember(function (member) {
if (abstractMembers.length == 0) {
return false;
}
if ((member.flags() & ClassDefinition.IS_ABSTRACT) == 0) {
for (var i = 0; i < abstractMembers.length; ++i) {
if (ClassDefinition.membersAreEqual(abstractMembers[i], member)) {
abstractMembers.splice(i, 1);
break;
}
}
}
return true;
});
});
if (abstractMembers.length != 0) {
var msg = "class should be declared as 'abstract' since the following members do not have concrete definition: ";
for (var i = 0; i < abstractMembers.length; ++i) {
if (i != 0)
msg += ", ";
msg += abstractMembers[i].getNotation();
}
context.errors.push(new CompileError(this.getToken(), msg));
}
}
// check that there are no conflicting exports (note: conflict names bet. var defs and functions are prohibited anyways)
var usedNames = new Map.<MemberDefinition>;
this._getMembers([], function (member) {
if (! (member instanceof MemberFunctionDefinition)) {
return false;
}
if ((member.flags() & (ClassDefinition.IS_STATIC | ClassDefinition.IS_EXPORT)) != ClassDefinition.IS_EXPORT) {
return false;
}
if (! usedNames.hasOwnProperty(member.name())) {
usedNames[member.name()] = member;
return false;
}
var existingDef = usedNames[member.name()];
if (existingDef.getType().equals(member.getType())) {
return false;
}
if ((this._flags & ClassDefinition.IS_EXPORT) != 0 && member.name() == "constructor") {
var errMsg = "only one constructor is exportable, please mark others using the __noexport__ attribute";
} else {
errMsg = "methods with __export__ attribute cannot be overloaded";
}
context.errors.push(
new CompileError(member.getToken(), errMsg)
.addCompileNote(new CompileNote(usedNames[member.name()].getToken(), "previously defined here")));
return false;
});
}
function _analyzeMembers (context : AnalysisContext) : void {
for (var i = 0; i < this._members.length; ++i) {
var member = this._members[i];
if (member instanceof MemberFunctionDefinition) {
if (! (member instanceof TemplateFunctionDefinition)) {
(member as MemberFunctionDefinition).analyze(context);
}
} else {
// Just sets the initial values; analysis of member variables is performed lazily (and those that where never analyzed will be removed by dead code elimination)
var varDef = member as MemberVariableDefinition;
if (varDef.getInitialValue() == null) {
varDef.setInitialValue(Expression.getDefaultValueExpressionOf(varDef.getType()));
}
}
}
}
function analyzeUnusedVariables () : void {
for (var i = 0; i < this._members.length; ++i) {
var member = this._members[i];
if (member instanceof MemberVariableDefinition)
(member as MemberVariableDefinition).getType();
}
}
function isConvertibleTo (classDef : ClassDefinition) : boolean {
if (this == classDef)
return true;
if (classDef.className() == "Object")
return true;
if (this._extendType != null && this._extendType.getClassDef().isConvertibleTo(classDef))
return true;
for (var i = 0; i < this._implementTypes.length; ++i)
if (this._implementTypes[i].getClassDef().isConvertibleTo(classDef))
return true;
return false;
}
function _assertMemberIsDefinable (context : AnalysisContext, member : MemberDefinition, memberClassDef : ClassDefinition, token : Token) : boolean {
if ((member.flags() & ClassDefinition.IS_STATIC) != 0)
return true;
for (var numImplementsToCheck = 0; numImplementsToCheck < this._implementTypes.length; ++numImplementsToCheck)
if (memberClassDef == this._implementTypes[numImplementsToCheck].getClassDef())
break;
var isCheckingSibling = numImplementsToCheck != this._implementTypes.length;
if (member instanceof MemberVariableDefinition) {
if (this._extendType != null && ! this._extendType.getClassDef()._assertMemberVariableIsDefinable(context, member as MemberVariableDefinition, memberClassDef, token))
return false;
for (var i = 0; i < numImplementsToCheck; ++i) {
if (! this._implementTypes[i].getClassDef()._assertMemberVariableIsDefinable(context, member as MemberVariableDefinition, memberClassDef, token))
return false;
}
} else { // function
if (this._extendType != null && ! this._extendType.getClassDef()._assertMemberFunctionIsDefinable(context, member as MemberFunctionDefinition, memberClassDef, token, false))
return false;
for (var i = 0; i < numImplementsToCheck; ++i) {
if (memberClassDef != this._implementTypes[i].getClassDef() && ! this._implementTypes[i].getClassDef()._assertMemberFunctionIsDefinable(context, member as MemberFunctionDefinition, memberClassDef, token, isCheckingSibling))
return false;
}
}
return true;
}
function _assertMemberVariableIsDefinable (context : AnalysisContext, member : MemberVariableDefinition, memberClassDef : ClassDefinition, token : Token) : boolean {
for (var i = 0; i < this._members.length; ++i) {
if (this._members[i].name() == member.name()) {
if ((this._members[i].flags() & ClassDefinition.IS_ABSTRACT) == 0) {
context.errors.push(new CompileError(member.getNameToken(), Util.format("cannot define property '%1', the name is already used in class '%2'", [member.getNotation(), this.className()])));
return false;
}
if (! this._members[i].getType().equals(member.getType())) {
context.errors.push(new CompileError(member.getNameToken(), Util.format("cannot override property '%1' of type '%2' with different type '%3'", [member.getNotation(), this._members[i].getType().toString(), member.getType().toString() ])));
return false;
}
}
}
if (this._extendType != null && ! this._extendType.getClassDef()._assertMemberVariableIsDefinable(context, member, memberClassDef, token))
return false;
for (var i = 0; i < this._implementTypes.length; ++i)
if (! this._implementTypes[i].getClassDef()._assertMemberVariableIsDefinable(context, member, memberClassDef, token))
return false;
return true;
}
function _assertMemberFunctionIsDefinable (context : AnalysisContext, member : MemberFunctionDefinition, memberClassDef : ClassDefinition, token : Token, reportOverridesAsWell : boolean) : boolean {
if (member.name() == "constructor")
return true;
for (var i = 0; i < this._members.length; ++i) {
if (this._members[i].name() != member.name())
continue;
if (this._members[i] instanceof MemberVariableDefinition) {
var error = new CompileError(member.getNameToken(), "definition of the function conflicts with property '" + this._members[i].getNameToken().getValue() + "'");
error.addCompileNote(new CompileNote(this._members[i].getNameToken(), "property with the same name has been found here"));
context.errors.push(error);
return false;
}
if (! Util.typesAreEqual((this._members[i] as MemberFunctionDefinition).getArgumentTypes(), member.getArgumentTypes()))
continue;
if ((member.flags() & ClassDefinition.IS_OVERRIDE) == 0) {
context.errors.push(new CompileError(member.getNameToken(), "overriding functions must have 'override' attribute set (defined in base class '" + this.className() + "')"));
return false;
}
if (reportOverridesAsWell && (this._members[i].flags() & ClassDefinition.IS_OVERRIDE) != 0) {
context.errors.push(new CompileError(member.getNameToken(), "definition of the function conflicts with sibling mix-in '" + this.className() + "'"));
return false;
}
// assertion of function being overridden does not have 'final' attribute is done by assertFunctionIsOverridable
return true;
}
// delegate to base classes
if (this._extendType != null && ! this._extendType.getClassDef()._assertMemberFunctionIsDefinable(context, member, memberClassDef, token, false))
return false;
for (var i = 0; i < this._implementTypes.length; ++i)
if (! this._implementTypes[i].getClassDef()._assertMemberFunctionIsDefinable(context, member, memberClassDef, token, false))
return false;
return true;
}
function _assertFunctionIsOverridable (context : AnalysisContext, overrideDef : MemberFunctionDefinition) : Nullable.<boolean> {
for (var i = 0; i < this._members.length; ++i) {
if (this._members[i].name() == overrideDef.name()
&& this._members[i] instanceof MemberFunctionDefinition
&& ((this._members[i] as MemberFunctionDefinition).flags() & ClassDefinition.IS_STATIC) == 0
&& Util.typesAreEqual((this._members[i] as MemberFunctionDefinition).getArgumentTypes(), overrideDef.getArgumentTypes())) {
if ((this._members[i].flags() & ClassDefinition.IS_FINAL) != 0) {
context.errors.push(new CompileError(overrideDef.getToken(), "cannot override final function defined in class '" + this.className() + "'"));
return false;
}
var overrideReturnType = overrideDef.getReturnType();
var memberReturnType = (this._members[i] as MemberFunctionDefinition).getReturnType();
if (! (overrideReturnType.equals(memberReturnType) || overrideReturnType.isConvertibleTo(memberReturnType))
|| (memberReturnType instanceof NullableType && ! (overrideReturnType instanceof NullableType))) {
// only allow narrowing the return type
context.errors.push(new CompileError(overrideDef.getToken(), "return type '" + overrideReturnType.toString() + "' is not convertible to '" + memberReturnType.toString() + "'"));
return false;
} else {
return true;
}
}
}
return this._assertFunctionIsOverridableInBaseClasses(context, overrideDef);
}
function _assertFunctionIsOverridableInBaseClasses (context : AnalysisContext, member : MemberFunctionDefinition) : Nullable.<boolean> {
if (this._extendType != null) {
var ret = this._extendType.getClassDef()._assertFunctionIsOverridable(context, member);
if (ret != null)
return ret;
}
for (var i = 0; i < this._implementTypes.length; ++i) {
var ret = this._implementTypes[i].getClassDef()._assertFunctionIsOverridable(context, member);
if (ret != null)
return ret;
}
return null;
}
function _getMembers(list : MemberDefinition[], cb : function (member : MemberDefinition) : boolean) : void {
// fill in the definitions of base classes
if (this._baseClassDef != null)
this._baseClassDef._getMembers(list, cb);
for (var i = 0; i < this._implementTypes.length; ++i)
this._implementTypes[i].getClassDef()._getMembers(list, cb);
// fill in the definitions of members
for (var i = 0; i < this._members.length; ++i) {
if (cb(this._members[i]))
list.push(this._members[i]);
}
}
function _getMembers (list : MemberDefinition[], functionOnly : boolean, flagsMask : number, flagsMaskMatch : number) : void {
this._getMembers(list, function (member) {
if (functionOnly && ! (member instanceof MemberFunctionDefinition))
return false;
if ((member.flags() & flagsMask) != flagsMaskMatch)
return false;
for (var j = 0; j < list.length; ++j)
if (list[j].name() == member.name())
if ((list[j] instanceof MemberVariableDefinition) || Util.typesAreEqual((list[j] as MemberFunctionDefinition).getArgumentTypes(), (member as MemberFunctionDefinition).getArgumentTypes()))
return false;
return true;
});
}
function hasDefaultConstructor () : boolean {
var hasCtorWithArgs = false;
for (var i = 0; i < this._members.length; ++i) {
var member = this._members[i];
if (member.name() == "constructor" && (member.flags() & ClassDefinition.IS_STATIC) == 0 && member instanceof MemberFunctionDefinition) {
if ((member as MemberFunctionDefinition).getArguments().length == 0)
return true;
hasCtorWithArgs = true;
}
}
return ! hasCtorWithArgs;
}
static function membersAreEqual (x : MemberDefinition, y : MemberDefinition) : boolean {
if (x.name() != y.name())
return false;
if (x instanceof MemberFunctionDefinition) {
if (! (y instanceof MemberFunctionDefinition))
return false;
if (! Util.typesAreEqual((x as MemberFunctionDefinition).getArgumentTypes(), (y as MemberFunctionDefinition).getArgumentTypes()))
return false;
} else {
if (! (y instanceof MemberVariableDefinition))
return false;
}
return true;
}
}
// abstract class deriving Member(Function|Variable)Definition
abstract class MemberDefinition implements Stashable {
var _token : Token;
var _nameToken : Token;
var _flags : number;
var _closures : MemberFunctionDefinition[];
var _docComment : DocComment;
var _classDef : ClassDefinition;
function constructor (token : Token, nameToken : Token, flags : number, closures : MemberFunctionDefinition[], docComment : DocComment) {
this._token = token;
this._nameToken = nameToken; // may be null
this._flags = flags;
assert closures != null;
this._closures = closures;
this._docComment = docComment;
this._classDef = null;
}
abstract function serialize () : variant;
abstract function instantiate (instantiationContext : InstantiationContext) : MemberDefinition;
abstract function getType () : Type;
// token of "function" or "var"
function getToken () : Token {
return this._token;
}
function getNameToken () : Token {
return this._nameToken;
}
function name () : string {
return this._nameToken.getValue();
}
function flags () : number {
return this._flags;
}
function setFlags (flags : number) : void {
this._flags = flags;
}
function getClosures () : MemberFunctionDefinition[] {
return this._closures;
}
function forEachClosure (cb : function(:MemberFunctionDefinition):boolean) : boolean {
if (this._closures != null)
for (var i = 0; i < this._closures.length; ++i)
if (! cb(this._closures[i]))
return false;
return true;
}
function getDocComment () : DocComment {
return this._docComment;
}
function setDocComment (docComment : DocComment) : void {
this._docComment = docComment;
}
function getClassDef () : ClassDefinition {
return this._classDef;
}
function setClassDef (classDef : ClassDefinition) : void {
this._classDef = classDef;
}
abstract function getNotation() : string;
function _instantiateClosures(instantiationContext : InstantiationContext) : MemberFunctionDefinition[] {
var closures = new MemberFunctionDefinition[];
for (var i = 0; i < this._closures.length; ++i) {
closures[i] = this._closures[i].instantiate(instantiationContext);
}
return closures;
}
function _updateLinkFromExpressionToClosuresUponInstantiation(instantiatedExpr : Expression, instantiatedClosures : MemberFunctionDefinition[]) : void {
function onExpr(expr : Expression) : boolean {
if (expr instanceof FunctionExpression) {
for (var i = 0; i < this._closures.length; ++i) {
if (this._closures[i] == (expr as FunctionExpression).getFuncDef())
break;
}
if (i == this._closures.length)
throw new Error("logic flaw, cannot find the closure");
(expr as FunctionExpression).setFuncDef(instantiatedClosures[i]);
}
return expr.forEachExpression(onExpr);
}
onExpr(instantiatedExpr);
}
}
class MemberVariableDefinition extends MemberDefinition {
static const NOT_ANALYZED = 0;
static const IS_ANALYZING = 1;
static const ANALYZE_SUCEEDED = 2;
static const ANALYZE_FAILED = 3;
var _type : Type; // may be null
var _initialValue : Expression; // may be null
var _analyzeState : number;
var _analysisContext : AnalysisContext;
function constructor (token : Token, name : Token, flags : number, type : Type, initialValue : Expression, closures : MemberFunctionDefinition[], docComment : DocComment) {
super(token, name, flags, closures, docComment);
this._type = type;
this._initialValue = initialValue;
this._analyzeState = MemberVariableDefinition.NOT_ANALYZED;
this._analysisContext = null;
}
override function instantiate (instantiationContext : InstantiationContext) : MemberDefinition {
var type = this._type != null ? this._type.instantiate(instantiationContext) : null;
var initialValue : Expression = null;
if (this._initialValue != null) {
initialValue = this._initialValue.clone();
initialValue.instantiate(instantiationContext);
var closures = this._instantiateClosures(instantiationContext);
this._updateLinkFromExpressionToClosuresUponInstantiation(initialValue, closures);
} else {
closures = [] : MemberFunctionDefinition[];
}
return new MemberVariableDefinition(this._token, this._nameToken, this._flags, type, initialValue, closures, null);
}
override function toString () : string {
return this.name() + " : " + this._type.toString();
}
override function serialize () : variant {
return {
"token" : Serializer.<Token>.serializeNullable(this._token),
"nameToken" : Serializer.<Token>.serializeNullable(this._nameToken),
"flags" : this.flags(),
"type" : Serializer.<Type>.serializeNullable(this._type),
"initialValue" : Serializer.<Expression>.serializeNullable(this._initialValue)
} : Map.<variant>;
}
function setAnalysisContext (context : AnalysisContext) : void {
this._analysisContext = context.clone();
}
override function getType () : Type {
switch (this._analyzeState) {
case MemberVariableDefinition.NOT_ANALYZED:
try {
this._analyzeState = MemberVariableDefinition.IS_ANALYZING;
if (this._initialValue != null) {
if (this._initialValue instanceof ClassExpression) {
this._analysisContext.errors.push(new CompileError(this._initialValue._token, "cannot assign a class"));
return null;
}
if (! this._initialValue.analyze(this._analysisContext, null))
return null;
var ivType = this._initialValue.getType();
if (this._type == null) {
if (ivType.equals(Type.nullType)) {
this._analysisContext.errors.push(new CompileError(this._initialValue.getToken(), "cannot assign null to an unknown type"));
return null;
}
if (ivType.equals(Type.voidType)) {
this._analysisContext.errors.push(new CompileError(this._initialValue.getToken(), "cannot assign void"));
return null;
}
this._type = ivType.asAssignableType();
} else if (! ivType.isConvertibleTo(this._type)) {
this._analysisContext.errors.push(new CompileError(this._nameToken,
"the variable is declared as '" + this._type.toString() + "' but initial value is '" + ivType.toString() + "'"));
}
}
this._analyzeState = MemberVariableDefinition.ANALYZE_SUCEEDED;
} finally {
if (this._analyzeState != MemberVariableDefinition.ANALYZE_SUCEEDED)
this._analyzeState = MemberVariableDefinition.ANALYZE_FAILED;
}
break;
case MemberVariableDefinition.IS_ANALYZING:
this._analysisContext.errors.push(new CompileError(this.getNameToken(),
"please declare type of variable '" + this.name() + "' (detected recursion while trying to reduce type)"));
break;
default:
break;
}
return this._type;
}
function getInitialValue () : Expression {
return this._initialValue;
}
function setInitialValue (initialValue : Expression) : void {
this._initialValue = initialValue;
}
override function getNotation() : string {
var classDef = this.getClassDef();
var s = (classDef != null ? classDef.className(): "<<unknown>>");
s += (this.flags() & ClassDefinition.IS_STATIC) != 0 ? "." : "#";
s += this.name();
return s;
}
}
class MemberFunctionDefinition extends MemberDefinition implements Block {
var _returnType : Type;
var _args : ArgumentDeclaration[];
var _locals : LocalVariable[];
var _statements : Statement[];
var _lastTokenOfBody : Token;
var _parent : MemberFunctionDefinition; // null for the outermost closure of static variable initialization expression
var _funcLocal : LocalVariable;
function constructor (token : Token, name : Token, flags : number, returnType : Type, args : ArgumentDeclaration[], locals : LocalVariable[], statements : Statement[], closures : MemberFunctionDefinition[], lastTokenOfBody : Token, docComment : DocComment) {
super(token, name, flags, closures, docComment);
this._returnType = returnType;
this._args = args;
this._locals = locals;
this._statements = statements;
this._lastTokenOfBody = lastTokenOfBody;
this._parent = null;
this._funcLocal = null;
this._classDef = null;
for (var i = 0; i < this._closures.length; ++i)
this._closures[i].setParent(this);
}
function isAnonymous() : boolean { // for anonymous function expression
return this._nameToken == null;
}
function isGenerator() : boolean {
return (this._flags & ClassDefinition.IS_GENERATOR) != 0;
}
/**
* Returns a simple notation of the function like "Class.classMethod(:string):void" or "Class.instanceMethod(:string):void".
*/
override function getNotation() : string {
var classDef = this.getClassDef();
var s = (classDef != null ? classDef.className(): "<<unknown>>");
s += (this.flags() & ClassDefinition.IS_STATIC) != 0 ? "." : "#";
s += this.getNameToken() != null ? this.name() : "$" + this.getToken().getLineNumber() as string + "_" + this.getToken().getColumnNumber() as string;
s += "(";
s += this._args.map.<string>(function (arg) {
return ":" + arg.getType().toString();
}).join(",");
s += ")";
return s;
}
override function toString () : string {
var argsText = this._args.map.<string>(function (arg) {
return arg.getName().getValue() + " : " + arg.getType().toString();
}).join(", ");
return "function " +
this.name() +
"(" + argsText + ") : " +
this._returnType.toString();
}
override function instantiate (instantiationContext : InstantiationContext) : MemberFunctionDefinition {
return this._instantiateCore(
instantiationContext,
function (token, name, flags, returnType, args, locals, statements, closures, lastTokenOfBody, docComment) {
return new MemberFunctionDefinition(token, name, flags, returnType, args, locals, statements, closures, lastTokenOfBody, docComment);
});
}
function _instantiateCore (instantiationContext : InstantiationContext, constructCallback : function(:Token,:Token,:number,:Type,:ArgumentDeclaration[],:LocalVariable[],:Statement[],:MemberFunctionDefinition[],:Token,:DocComment):MemberFunctionDefinition) : MemberFunctionDefinition {
// rewrite arguments (and push the instantiated args)
var args = new ArgumentDeclaration[];
for (var i = 0; i < this._args.length; ++i) {
args[i] = this._args[i].instantiateAndPush(instantiationContext);
}
// rewrite function body
if (this._statements != null) {
// clone and rewrite the types of local variables
var locals = new LocalVariable[];
for (var i = 0; i < this._locals.length; ++i) {
locals[i] = this._locals[i].instantiateAndPush(instantiationContext);
}
var caughtVariables = new CaughtVariable[]; // stored by the order they are defined, and 'shift'ed
Util.forEachStatement(function onStatement(statement : Statement) : boolean {
if (statement instanceof CatchStatement) {
caughtVariables.push((statement as CatchStatement).getLocal().instantiateAndPush(instantiationContext));
}
return statement.forEachStatement(onStatement);
}, this._statements);
// clone and rewrite the types of the statements
var statements = new Statement[];
for (var i = 0; i < this._statements.