diff options
Diffstat (limited to 'examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/IRChecker.scala')
-rw-r--r-- | examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/IRChecker.scala | 854 |
1 files changed, 854 insertions, 0 deletions
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/IRChecker.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/IRChecker.scala new file mode 100644 index 0000000..6329826 --- /dev/null +++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/IRChecker.scala @@ -0,0 +1,854 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js tools ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.tools.optimizer + +import scala.language.implicitConversions + +import scala.annotation.switch + +import scala.collection.mutable + +import scala.scalajs.ir._ +import Definitions._ +import Trees._ +import Types._ + +import scala.scalajs.tools.logging._ + +/** Checker for the validity of the IR. */ +class IRChecker(analyzer: Analyzer, allClassDefs: Seq[ClassDef], logger: Logger) { + import IRChecker._ + + private var _errorCount: Int = 0 + def errorCount: Int = _errorCount + + private val classes: mutable.Map[String, CheckedClass] = { + mutable.Map.empty[String, CheckedClass] ++= + allClassDefs.map(new CheckedClass(_)).map(c => c.name -> c) + } + + def check(): Boolean = { + for { + classDef <- allClassDefs + if analyzer.classInfos(classDef.name.name).isNeededAtAll + } { + classDef.kind match { + case ClassKind.Class | ClassKind.ModuleClass => checkClass(classDef) + case ClassKind.TraitImpl => checkTraitImpl(classDef) + case _ => + } + } + errorCount == 0 + } + + def checkClass(classDef: ClassDef): Unit = { + if (!analyzer.classInfos(classDef.name.name).isAnySubclassInstantiated) + return + + for (member <- classDef.defs) { + implicit val ctx = ErrorContext(member) + member match { + // Scala declarations + case v @ VarDef(_, _, _, _) => + checkFieldDef(v, classDef) + case m: MethodDef if m.name.isInstanceOf[Ident] => + checkMethodDef(m, classDef) + + // Exports + case m: MethodDef if m.name.isInstanceOf[StringLiteral] => + checkExportedMethodDef(m, classDef) + case member @ PropertyDef(_: StringLiteral, _, _, _) => + checkExportedPropertyDef(member, classDef) + case member @ ConstructorExportDef(_, _, _) => + checkConstructorExportDef(member, classDef) + case member @ ModuleExportDef(_) => + checkModuleExportDef(member, classDef) + + // Anything else is illegal + case _ => + reportError(s"Illegal class member of type ${member.getClass.getName}") + } + } + } + + def checkTraitImpl(classDef: ClassDef): Unit = { + for (member <- classDef.defs) { + implicit val ctx = ErrorContext(member) + member match { + case m: MethodDef => + checkMethodDef(m, classDef) + case _ => + reportError(s"Invalid member for a TraitImpl") + } + } + } + + def checkFieldDef(fieldDef: VarDef, classDef: ClassDef): Unit = { + val VarDef(name, tpe, mutable, rhs) = fieldDef + implicit val ctx = ErrorContext(fieldDef) + + if (tpe == NoType) + reportError(s"VarDef cannot have type NoType") + else + typecheckExpect(rhs, Env.empty, tpe) + } + + def checkMethodDef(methodDef: MethodDef, classDef: ClassDef): Unit = { + val MethodDef(Ident(name, _), params, resultType, body) = methodDef + implicit val ctx = ErrorContext(methodDef) + + if (!analyzer.classInfos(classDef.name.name).methodInfos(name).isReachable) + return + + for (ParamDef(name, tpe, _) <- params) + if (tpe == NoType) + reportError(s"Parameter $name has type NoType") + + val resultTypeForSig = + if (isConstructorName(name)) NoType + else resultType + + val advertizedSig = (params.map(_.ptpe), resultTypeForSig) + val sigFromName = inferMethodType(name, + inTraitImpl = classDef.kind == ClassKind.TraitImpl) + if (advertizedSig != sigFromName) { + reportError( + s"The signature of ${classDef.name.name}.$name, which is "+ + s"$advertizedSig, does not match its name (should be $sigFromName).") + } + + val thisType = + if (!classDef.kind.isClass) NoType + else ClassType(classDef.name.name) + val bodyEnv = Env.fromSignature(thisType, params, resultType) + if (resultType == NoType) + typecheckStat(body, bodyEnv) + else + typecheckExpect(body, bodyEnv, resultType) + } + + def checkExportedMethodDef(methodDef: MethodDef, classDef: ClassDef): Unit = { + val MethodDef(_, params, resultType, body) = methodDef + implicit val ctx = ErrorContext(methodDef) + + if (!classDef.kind.isClass) { + reportError(s"Exported method def can only appear in a class") + return + } + + for (ParamDef(name, tpe, _) <- params) { + if (tpe == NoType) + reportError(s"Parameter $name has type NoType") + else if (tpe != AnyType) + reportError(s"Parameter $name of exported method def has type $tpe, "+ + "but must be Any") + } + + if (resultType != AnyType) { + reportError(s"Result type of exported method def is $resultType, "+ + "but must be Any") + } + + val thisType = ClassType(classDef.name.name) + val bodyEnv = Env.fromSignature(thisType, params, resultType) + .withArgumentsVar(methodDef.pos) + typecheckExpect(body, bodyEnv, resultType) + } + + def checkExportedPropertyDef(propDef: PropertyDef, classDef: ClassDef): Unit = { + val PropertyDef(_, getterBody, setterArg, setterBody) = propDef + implicit val ctx = ErrorContext(propDef) + + if (!classDef.kind.isClass) { + reportError(s"Exported property def can only appear in a class") + return + } + + val thisType = ClassType(classDef.name.name) + + if (getterBody != EmptyTree) { + val getterBodyEnv = Env.fromSignature(thisType, Nil, AnyType) + typecheckExpect(getterBody, getterBodyEnv, AnyType) + } + + if (setterBody != EmptyTree) { + if (setterArg.ptpe != AnyType) + reportError("Setter argument of exported property def has type "+ + s"${setterArg.ptpe}, but must be Any") + + val setterBodyEnv = Env.fromSignature(thisType, List(setterArg), NoType) + typecheckStat(setterBody, setterBodyEnv) + } + } + + def checkConstructorExportDef(ctorDef: ConstructorExportDef, + classDef: ClassDef): Unit = { + val ConstructorExportDef(_, params, body) = ctorDef + implicit val ctx = ErrorContext(ctorDef) + + if (!classDef.kind.isClass) { + reportError(s"Exported constructor def can only appear in a class") + return + } + + for (ParamDef(name, tpe, _) <- params) { + if (tpe == NoType) + reportError(s"Parameter $name has type NoType") + else if (tpe != AnyType) + reportError(s"Parameter $name of exported constructor def has type "+ + s"$tpe, but must be Any") + } + + val thisType = ClassType(classDef.name.name) + val bodyEnv = Env.fromSignature(thisType, params, NoType) + .withArgumentsVar(ctorDef.pos) + typecheckStat(body, bodyEnv) + } + + def checkModuleExportDef(moduleDef: ModuleExportDef, + classDef: ClassDef): Unit = { + implicit val ctx = ErrorContext(moduleDef) + + if (classDef.kind != ClassKind.ModuleClass) + reportError(s"Exported module def can only appear in a module class") + } + + def typecheckStat(tree: Tree, env: Env): Env = { + implicit val ctx = ErrorContext(tree) + + tree match { + case VarDef(ident, vtpe, mutable, rhs) => + typecheckExpect(rhs, env, vtpe) + env.withLocal(LocalDef(ident.name, vtpe, mutable)(tree.pos)) + + case Skip() => + env + + case Assign(select, rhs) => + select match { + case Select(_, Ident(name, _), false) => + /* TODO In theory this case would verify that we never assign to + * an immutable field. But we cannot do that because we *do* emit + * such assigns in constructors. + * In the future we might want to check that only these legal + * special cases happen, and nothing else. But it seems non-trivial + * to do so, so currently we trust scalac not to make us emit + * illegal assigns. + */ + //reportError(s"Assignment to immutable field $name.") + case VarRef(Ident(name, _), false) => + reportError(s"Assignment to immutable variable $name.") + case _ => + } + val lhsTpe = typecheckExpr(select, env) + val expectedRhsTpe = select match { + case _:JSDotSelect | _:JSBracketSelect => AnyType + case _ => lhsTpe + } + typecheckExpect(rhs, env, expectedRhsTpe) + env + + case StoreModule(cls, value) => + if (!cls.className.endsWith("$")) + reportError("StoreModule of non-module class $cls") + typecheckExpect(value, env, ClassType(cls.className)) + env + + case Block(stats) => + (env /: stats) { (prevEnv, stat) => + typecheckStat(stat, prevEnv) + } + env + + case Labeled(label, NoType, body) => + typecheckStat(body, env.withLabeledReturnType(label.name, AnyType)) + env + + case If(cond, thenp, elsep) => + typecheckExpect(cond, env, BooleanType) + typecheckStat(thenp, env) + typecheckStat(elsep, env) + env + + case While(cond, body, label) => + typecheckExpect(cond, env, BooleanType) + typecheckStat(body, env) + env + + case DoWhile(body, cond, label) => + typecheckStat(body, env) + typecheckExpect(cond, env, BooleanType) + env + + case Try(block, errVar, handler, finalizer) => + typecheckStat(block, env) + if (handler != EmptyTree) { + val handlerEnv = + env.withLocal(LocalDef(errVar.name, AnyType, false)(errVar.pos)) + typecheckStat(handler, handlerEnv) + } + if (finalizer != EmptyTree) { + typecheckStat(finalizer, env) + } + env + + case Match(selector, cases, default) => + typecheckExpr(selector, env) + for ((alts, body) <- cases) { + alts.foreach(typecheckExpr(_, env)) + typecheckStat(body, env) + } + typecheckStat(default, env) + env + + case Debugger() => + env + + case JSDelete(JSDotSelect(obj, prop)) => + typecheckExpr(obj, env) + env + + case JSDelete(JSBracketSelect(obj, prop)) => + typecheckExpr(obj, env) + typecheckExpr(prop, env) + env + + case _ => + typecheck(tree, env) + env + } + } + + def typecheckExpect(tree: Tree, env: Env, expectedType: Type)( + implicit ctx: ErrorContext): Unit = { + val tpe = typecheckExpr(tree, env) + if (!isSubtype(tpe, expectedType)) + reportError(s"$expectedType expected but $tpe found "+ + s"for tree of type ${tree.getClass.getName}") + } + + def typecheckExpr(tree: Tree, env: Env): Type = { + implicit val ctx = ErrorContext(tree) + if (tree.tpe == NoType) + reportError(s"Expression tree has type NoType") + typecheck(tree, env) + } + + def typecheck(tree: Tree, env: Env): Type = { + implicit val ctx = ErrorContext(tree) + + def checkApplyGeneric(methodName: String, methodFullName: String, + args: List[Tree], inTraitImpl: Boolean): Unit = { + val (methodParams, resultType) = inferMethodType(methodName, inTraitImpl) + if (args.size != methodParams.size) + reportError(s"Arity mismatch: ${methodParams.size} expected but "+ + s"${args.size} found") + for ((actual, formal) <- args zip methodParams) { + typecheckExpect(actual, env, formal) + } + if (!isConstructorName(methodName) && tree.tpe != resultType) + reportError(s"Call to $methodFullName of type $resultType "+ + s"typed as ${tree.tpe}") + } + + tree match { + // Control flow constructs + + case Block(statsAndExpr) => + val stats :+ expr = statsAndExpr + val envAfterStats = (env /: stats) { (prevEnv, stat) => + typecheckStat(stat, prevEnv) + } + typecheckExpr(expr, envAfterStats) + + case Labeled(label, tpe, body) => + typecheckExpect(body, env.withLabeledReturnType(label.name, tpe), tpe) + + case Return(expr, label) => + env.returnTypes.get(label.map(_.name)).fold[Unit] { + reportError(s"Cannot return to label $label.") + typecheckExpr(expr, env) + } { returnType => + typecheckExpect(expr, env, returnType) + } + + case If(cond, thenp, elsep) => + val tpe = tree.tpe + typecheckExpect(cond, env, BooleanType) + typecheckExpect(thenp, env, tpe) + typecheckExpect(elsep, env, tpe) + + case While(BooleanLiteral(true), body, label) if tree.tpe == NothingType => + typecheckStat(body, env) + + case Try(block, errVar, handler, finalizer) => + val tpe = tree.tpe + typecheckExpect(block, env, tpe) + if (handler != EmptyTree) { + val handlerEnv = + env.withLocal(LocalDef(errVar.name, AnyType, false)(errVar.pos)) + typecheckExpect(handler, handlerEnv, tpe) + } + if (finalizer != EmptyTree) { + typecheckStat(finalizer, env) + } + + case Throw(expr) => + typecheckExpr(expr, env) + + case Continue(label) => + /* Here we could check that it is indeed legal to break to the + * specified label. However, if we do anything illegal here, it will + * result in a SyntaxError in JavaScript anyway, so we do not really + * care. + */ + + case Match(selector, cases, default) => + val tpe = tree.tpe + typecheckExpr(selector, env) + for ((alts, body) <- cases) { + alts.foreach(typecheckExpr(_, env)) + typecheckExpect(body, env, tpe) + } + typecheckExpect(default, env, tpe) + + // Scala expressions + + case New(cls, ctor, args) => + val clazz = lookupClass(cls) + if (!clazz.kind.isClass) + reportError(s"new $cls which is not a class") + checkApplyGeneric(ctor.name, s"$cls.$ctor", args, + inTraitImpl = false) + + case LoadModule(cls) => + if (!cls.className.endsWith("$")) + reportError("LoadModule of non-module class $cls") + + case Select(qualifier, Ident(item, _), mutable) => + val qualType = typecheckExpr(qualifier, env) + qualType match { + case ClassType(cls) => + val clazz = lookupClass(cls) + if (!clazz.kind.isClass) { + reportError(s"Cannot select $item of non-class $cls") + } else { + clazz.lookupField(item).fold[Unit] { + reportError(s"Class $cls does not have a field $item") + } { fieldDef => + if (fieldDef.tpe != tree.tpe) + reportError(s"Select $cls.$item of type "+ + s"${fieldDef.tpe} typed as ${tree.tpe}") + if (fieldDef.mutable != mutable) + reportError(s"Select $cls.$item with "+ + s"mutable=${fieldDef.mutable} marked as mutable=$mutable") + } + } + case NullType | NothingType => + // always ok + case _ => + reportError(s"Cannot select $item of non-class type $qualType") + } + + case Apply(receiver, Ident(method, _), args) => + val receiverType = typecheckExpr(receiver, env) + checkApplyGeneric(method, s"$receiverType.$method", args, + inTraitImpl = false) + + case StaticApply(receiver, cls, Ident(method, _), args) => + typecheckExpect(receiver, env, cls) + checkApplyGeneric(method, s"$cls.$method", args, inTraitImpl = false) + + case TraitImplApply(impl, Ident(method, _), args) => + val clazz = lookupClass(impl) + if (clazz.kind != ClassKind.TraitImpl) + reportError(s"Cannot trait-impl apply method of non-trait-impl $impl") + checkApplyGeneric(method, s"$impl.$method", args, inTraitImpl = true) + + case UnaryOp(op, lhs) => + import UnaryOp._ + (op: @switch) match { + case `typeof` => + typecheckExpr(lhs, env) + case IntToLong => + typecheckExpect(lhs, env, IntType) + case LongToInt | LongToDouble => + typecheckExpect(lhs, env, LongType) + case DoubleToInt | DoubleToFloat | DoubleToLong => + typecheckExpect(lhs, env, DoubleType) + case Boolean_! => + typecheckExpect(lhs, env, BooleanType) + } + + case BinaryOp(op, lhs, rhs) => + import BinaryOp._ + (op: @switch) match { + case === | !== | String_+ => + typecheckExpr(lhs, env) + typecheckExpr(rhs, env) + case `in` => + typecheckExpect(lhs, env, ClassType(StringClass)) + typecheckExpr(rhs, env) + case `instanceof` => + typecheckExpr(lhs, env) + typecheckExpr(rhs, env) + case Int_+ | Int_- | Int_* | Int_/ | Int_% | + Int_| | Int_& | Int_^ | Int_<< | Int_>>> | Int_>> => + typecheckExpect(lhs, env, IntType) + typecheckExpect(rhs, env, IntType) + case Float_+ | Float_- | Float_* | Float_/ | Float_% => + typecheckExpect(lhs, env, FloatType) + typecheckExpect(lhs, env, FloatType) + case Long_+ | Long_- | Long_* | Long_/ | Long_% | + Long_| | Long_& | Long_^ | + Long_== | Long_!= | Long_< | Long_<= | Long_> | Long_>= => + typecheckExpect(lhs, env, LongType) + typecheckExpect(rhs, env, LongType) + case Long_<< | Long_>>> | Long_>> => + typecheckExpect(lhs, env, LongType) + typecheckExpect(rhs, env, IntType) + case Double_+ | Double_- | Double_* | Double_/ | Double_% | + Num_== | Num_!= | Num_< | Num_<= | Num_> | Num_>= => + typecheckExpect(lhs, env, DoubleType) + typecheckExpect(lhs, env, DoubleType) + case Boolean_== | Boolean_!= | Boolean_| | Boolean_& => + typecheckExpect(lhs, env, BooleanType) + typecheckExpect(rhs, env, BooleanType) + } + + case NewArray(tpe, lengths) => + for (length <- lengths) + typecheckExpect(length, env, IntType) + + case ArrayValue(tpe, elems) => + val elemType = arrayElemType(tpe) + for (elem <- elems) + typecheckExpect(elem, env, elemType) + + case ArrayLength(array) => + val arrayType = typecheckExpr(array, env) + if (!arrayType.isInstanceOf[ArrayType]) + reportError(s"Array type expected but $arrayType found") + + case ArraySelect(array, index) => + typecheckExpect(index, env, IntType) + typecheckExpr(array, env) match { + case arrayType: ArrayType => + if (tree.tpe != arrayElemType(arrayType)) + reportError(s"Array select of array type $arrayType typed as ${tree.tpe}") + case arrayType => + reportError(s"Array type expected but $arrayType found") + } + + case IsInstanceOf(expr, cls) => + typecheckExpr(expr, env) + + case AsInstanceOf(expr, cls) => + typecheckExpr(expr, env) + + case Unbox(expr, _) => + typecheckExpr(expr, env) + + case GetClass(expr) => + typecheckExpr(expr, env) + + // JavaScript expressions + + case JSNew(ctor, args) => + typecheckExpr(ctor, env) + for (arg <- args) + typecheckExpr(arg, env) + + case JSDotSelect(qualifier, item) => + typecheckExpr(qualifier, env) + + case JSBracketSelect(qualifier, item) => + typecheckExpr(qualifier, env) + typecheckExpr(item, env) + + case JSFunctionApply(fun, args) => + typecheckExpr(fun, env) + for (arg <- args) + typecheckExpr(arg, env) + + case JSDotMethodApply(receiver, method, args) => + typecheckExpr(receiver, env) + for (arg <- args) + typecheckExpr(arg, env) + + case JSBracketMethodApply(receiver, method, args) => + typecheckExpr(receiver, env) + typecheckExpr(method, env) + for (arg <- args) + typecheckExpr(arg, env) + + case JSUnaryOp(op, lhs) => + typecheckExpr(lhs, env) + + case JSBinaryOp(op, lhs, rhs) => + typecheckExpr(lhs, env) + typecheckExpr(rhs, env) + + case JSArrayConstr(items) => + for (item <- items) + typecheckExpr(item, env) + + case JSObjectConstr(fields) => + for ((_, value) <- fields) + typecheckExpr(value, env) + + case JSEnvInfo() => + + // Literals + + case _: Literal => + + // Atomic expressions + + case VarRef(Ident(name, _), mutable) => + env.locals.get(name).fold[Unit] { + reportError(s"Cannot find variable $name in scope") + } { localDef => + if (tree.tpe != localDef.tpe) + reportError(s"Variable $name of type ${localDef.tpe} "+ + s"typed as ${tree.tpe}") + if (mutable != localDef.mutable) + reportError(s"Variable $name with mutable=${localDef.mutable} "+ + s"marked as mutable=$mutable") + } + + case This() => + if (!isSubtype(env.thisTpe, tree.tpe)) + reportError(s"this of type ${env.thisTpe} typed as ${tree.tpe}") + + case Closure(captureParams, params, body, captureValues) => + if (captureParams.size != captureValues.size) + reportError("Mismatched size for captures: "+ + s"${captureParams.size} params vs ${captureValues.size} values") + + for ((ParamDef(name, ctpe, mutable), value) <- captureParams zip captureValues) { + if (mutable) + reportError(s"Capture parameter $name cannot be mutable") + if (ctpe == NoType) + reportError(s"Parameter $name has type NoType") + else + typecheckExpect(value, env, ctpe) + } + + for (ParamDef(name, ptpe, mutable) <- params) { + if (ptpe == NoType) + reportError(s"Parameter $name has type NoType") + else if (ptpe != AnyType) + reportError(s"Closure parameter $name has type $ptpe instead of any") + } + + val bodyEnv = Env.fromSignature( + AnyType, captureParams ++ params, AnyType) + typecheckExpect(body, bodyEnv, AnyType) + + case _ => + reportError(s"Invalid expression tree") + } + + tree.tpe + } + + def inferMethodType(encodedName: String, inTraitImpl: Boolean)( + implicit ctx: ErrorContext): (List[Type], Type) = { + def dropPrivateMarker(params: List[String]): List[String] = + if (params.nonEmpty && params.head.startsWith("p")) params.tail + else params + + if (isConstructorName(encodedName)) { + assert(!inTraitImpl, "Trait impl should not have a constructor") + val params = dropPrivateMarker( + encodedName.stripPrefix("init___").split("__").toList) + if (params == List("")) (Nil, NoType) + else (params.map(decodeType), NoType) + } else if (isReflProxyName(encodedName)) { + assert(!inTraitImpl, "Trait impl should not have refl proxy methods") + val params = dropPrivateMarker(encodedName.split("__").toList.tail) + (params.map(decodeType), AnyType) + } else { + val paramsAndResult0 = + encodedName.split("__").toList.tail + val paramsAndResult1 = + if (inTraitImpl) paramsAndResult0.tail + else paramsAndResult0 + val paramsAndResult = + dropPrivateMarker(paramsAndResult1) + (paramsAndResult.init.map(decodeType), decodeType(paramsAndResult.last)) + } + } + + def decodeType(encodedName: String)(implicit ctx: ErrorContext): Type = { + if (encodedName.isEmpty) NoType + else if (encodedName.charAt(0) == 'A') { + // array type + val dims = encodedName.indexWhere(_ != 'A') + val base = encodedName.substring(dims) + ArrayType(base, dims) + } else if (encodedName.length == 1) { + (encodedName.charAt(0): @switch) match { + case 'V' => NoType + case 'Z' => BooleanType + case 'C' | 'B' | 'S' | 'I' => IntType + case 'J' => LongType + case 'F' => FloatType + case 'D' => DoubleType + case 'O' => AnyType + case 'T' => ClassType(StringClass) // NOT StringType + } + } else if (encodedName == "sr_Nothing$") { + NothingType + } else if (encodedName == "sr_Null$") { + NullType + } else { + val clazz = lookupClass(encodedName) + if (clazz.kind == ClassKind.RawJSType) AnyType + else ClassType(encodedName) + } + } + + def arrayElemType(arrayType: ArrayType)(implicit ctx: ErrorContext): Type = { + if (arrayType.dimensions == 1) decodeType(arrayType.baseClassName) + else ArrayType(arrayType.baseClassName, arrayType.dimensions-1) + } + + def reportError(msg: String)(implicit ctx: ErrorContext): Unit = { + logger.error(s"$ctx: $msg") + _errorCount += 1 + } + + def lookupClass(className: String)(implicit ctx: ErrorContext): CheckedClass = { + classes.getOrElseUpdate(className, { + reportError(s"Cannot find class $className") + new CheckedClass(className, ClassKind.Class, + Some(ObjectClass), Set(ObjectClass)) + }) + } + + def lookupClass(classType: ClassType)(implicit ctx: ErrorContext): CheckedClass = + lookupClass(classType.className) + + def isSubclass(lhs: String, rhs: String)(implicit ctx: ErrorContext): Boolean = { + lookupClass(lhs).isSubclass(lookupClass(rhs)) + } + + def isSubtype(lhs: Type, rhs: Type)(implicit ctx: ErrorContext): Boolean = { + Types.isSubtype(lhs, rhs)(isSubclass) + } + + class Env( + /** Type of `this`. Can be NoType. */ + val thisTpe: Type, + /** Local variables in scope (including through closures). */ + val locals: Map[String, LocalDef], + /** Return types by label. */ + val returnTypes: Map[Option[String], Type] + ) { + def withThis(thisTpe: Type): Env = + new Env(thisTpe, this.locals, this.returnTypes) + + def withLocal(localDef: LocalDef): Env = + new Env(thisTpe, locals + (localDef.name -> localDef), returnTypes) + + def withLocals(localDefs: TraversableOnce[LocalDef]): Env = + new Env(thisTpe, locals ++ localDefs.map(d => d.name -> d), returnTypes) + + def withReturnType(returnType: Type): Env = + new Env(this.thisTpe, this.locals, returnTypes + (None -> returnType)) + + def withLabeledReturnType(label: String, returnType: Type): Env = + new Env(this.thisTpe, this.locals, returnTypes + (Some(label) -> returnType)) + + def withArgumentsVar(pos: Position): Env = + withLocal(LocalDef("arguments", AnyType, mutable = false)(pos)) + } + + object Env { + val empty: Env = new Env(NoType, Map.empty, Map.empty) + + def fromSignature(thisType: Type, params: List[ParamDef], + resultType: Type): Env = { + val paramLocalDefs = + for (p @ ParamDef(name, tpe, mutable) <- params) yield + name.name -> LocalDef(name.name, tpe, mutable)(p.pos) + new Env(thisType, paramLocalDefs.toMap, + Map(None -> (if (resultType == NoType) AnyType else resultType))) + } + } + + class CheckedClass( + val name: String, + val kind: ClassKind, + val superClassName: Option[String], + val ancestors: Set[String], + _fields: TraversableOnce[CheckedField] = Nil) { + + val fields = _fields.map(f => f.name -> f).toMap + + lazy val superClass = superClassName.map(classes) + + def this(classDef: ClassDef) = { + this(classDef.name.name, classDef.kind, + classDef.parent.map(_.name), + classDef.ancestors.map(_.name).toSet, + CheckedClass.collectFields(classDef)) + } + + def isSubclass(that: CheckedClass): Boolean = + this == that || ancestors.contains(that.name) + + def isAncestorOfHijackedClass: Boolean = + AncestorsOfHijackedClasses.contains(name) + + def lookupField(name: String): Option[CheckedField] = + fields.get(name).orElse(superClass.flatMap(_.lookupField(name))) + } + + object CheckedClass { + private def collectFields(classDef: ClassDef) = { + classDef.defs collect { + case VarDef(Ident(name, _), tpe, mutable, _) => + new CheckedField(name, tpe, mutable) + } + } + } + + class CheckedField(val name: String, val tpe: Type, val mutable: Boolean) +} + +object IRChecker { + private final class ErrorContext(val tree: Tree) extends AnyVal { + override def toString(): String = { + val pos = tree.pos + s"${pos.source}(${pos.line+1}:${pos.column+1}:${tree.getClass.getSimpleName})" + } + + def pos: Position = tree.pos + } + + private object ErrorContext { + implicit def tree2errorContext(tree: Tree): ErrorContext = + ErrorContext(tree) + + def apply(tree: Tree): ErrorContext = + new ErrorContext(tree) + } + + private def isConstructorName(name: String): Boolean = + name.startsWith("init___") + + private def isReflProxyName(name: String): Boolean = + name.endsWith("__") && !isConstructorName(name) + + case class LocalDef(name: String, tpe: Type, mutable: Boolean)(val pos: Position) +} |