summaryrefslogtreecommitdiff
path: root/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/IRChecker.scala
diff options
context:
space:
mode:
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.scala854
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)
+}