UNPKG

jsx

Version:

a faster, safer, easier JavaScript

1,263 lines (1,151 loc) 76.2 kB
/* * 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.