All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scalajs.linker.checker.IRChecker.scala Maven / Gradle / Ivy

/*
 * Scala.js (https://www.scala-js.org/)
 *
 * Copyright EPFL.
 *
 * Licensed under Apache License 2.0
 * (https://www.apache.org/licenses/LICENSE-2.0).
 *
 * See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.
 */

package org.scalajs.linker.checker

import scala.annotation.switch

import scala.collection.mutable

import org.scalajs.ir._
import org.scalajs.ir.Names._
import org.scalajs.ir.Trees._
import org.scalajs.ir.Types._

import org.scalajs.logging._

import org.scalajs.linker.frontend.LinkingUnit
import org.scalajs.linker.standard.LinkedClass
import org.scalajs.linker.checker.ErrorReporter._

/** Checker for the validity of the IR. */
private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) {

  import IRChecker._
  import reporter.reportError

  private val classes: mutable.Map[ClassName, CheckedClass] = {
    val tups = for (classDef <- unit.classDefs) yield {
      implicit val ctx = ErrorContext(classDef)
      val c = new CheckedClass(classDef)
      c.name -> c
    }
    mutable.Map(tups: _*)
  }

  def check(): Unit = {
    for (classDef <- unit.classDefs) {
      implicit val ctx = ErrorContext(classDef)

      checkJSSuperClass(classDef)

      classDef.fields.foreach {
        case _: FieldDef            => // no further checks
        case JSFieldDef(_, name, _) => typecheckExpr(name, Env.empty)
      }

      classDef.methods.foreach(checkMethodDef(_, classDef))
      classDef.jsConstructorDef.foreach(checkJSConstructorDef(_, classDef))

      classDef.exportedMembers.foreach {
        case jsMethodDef: JSMethodDef =>
          checkJSMethodDef(jsMethodDef, classDef)

        case jsPropertyDef: JSPropertyDef =>
          checkJSPropertyDef(jsPropertyDef, classDef)
      }
    }

    for (topLevelExport <- unit.topLevelExports) {
      topLevelExport.tree match {
        case TopLevelMethodExportDef(_, methodDef) =>
          implicit val ctx = ErrorContext(methodDef)
          typecheckExpect(methodDef.body, Env.empty, AnyType)

        case _:TopLevelJSClassExportDef | _:TopLevelModuleExportDef |
            _:TopLevelFieldExportDef =>
      }
    }
  }

  private def checkJSSuperClass(classDef: LinkedClass): Unit = {
    implicit val ctx = ErrorContext(classDef)

    if (classDef.kind.isJSClass) {
      classDef.jsSuperClass.fold {
        // .get is OK: the ClassDefChecker checks that a super class is present.
        val superClass = lookupClass(classDef.superClass.get.name)
        if (superClass.jsClassCaptures.isDefined)
          reportError(i"super class ${superClass.name} may not have jsClassCaptures")
        else if (superClass.kind == ClassKind.NativeJSClass && superClass.jsNativeLoadSpec.isEmpty)
          reportError(i"Native super class ${superClass.name} must have a native load spec")
      } { tree =>
        typecheckExpect(tree, Env.empty, AnyType)
      }
    } else {
      assert(classDef.jsSuperClass.isEmpty) // checked by ClassDefChecker
    }
  }

  private def checkMethodDef(methodDef: MethodDef,
      classDef: LinkedClass): Unit = {

    val MethodDef(flags, MethodIdent(name), _, params, resultType, body) =
      methodDef
    implicit val ctx = ErrorContext(methodDef)

    val static = flags.namespace.isStatic

    val advertizedSig = (params.map(_.ptpe), resultType)
    val sigFromName = inferMethodType(name, static)
    if (advertizedSig != sigFromName) {
      reportError(
          i"The signature of ${classDef.name.name}.$name, which is "+
          i"$advertizedSig, does not match its name (should be $sigFromName).")
    }

    body.fold {
      // Abstract
      reportError(
          i"The abstract method ${classDef.name.name}.$name survived the " +
          "Analyzer (this is a bug)")
    } { body =>
      val bodyEnv =
        if (flags.namespace.isConstructor) Env.forConstructorOf(classDef.name.name)
        else Env.empty

      typecheckExpect(body, bodyEnv, resultType)
    }
  }

  private def checkJSConstructorDef(ctorDef: JSConstructorDef,
      clazz: LinkedClass): Unit =  {
    val JSConstructorDef(flags, params, restParam, body) = ctorDef
    implicit val ctx = ErrorContext(ctorDef)

    // JS constructors only get a valid `this` after the super call.

    val bodyEnv = Env.forConstructorOf(clazz.name.name)
    body.beforeSuper.foreach(typecheck(_, bodyEnv))
    body.superCall.args.foreach(typecheckExprOrSpread(_, bodyEnv))
    body.afterSuper.foreach(typecheck(_, bodyEnv))

    val resultType = body.afterSuper.lastOption.fold[Type](NoType)(_.tpe)
    if (resultType == NoType)
      reportError(i"${AnyType} expected but $resultType found for JS constructor body")
  }

  private def checkJSMethodDef(methodDef: JSMethodDef,
      clazz: LinkedClass): Unit =  {
    val JSMethodDef(flags, pName, params, restParam, body) = methodDef
    implicit val ctx = ErrorContext(methodDef)

    val static = flags.namespace.isStatic

    typecheckExpr(pName, Env.empty)

    typecheckExpect(body, Env.empty, AnyType)
  }

  private def checkJSPropertyDef(propDef: JSPropertyDef,
      clazz: LinkedClass): Unit =  {
    val JSPropertyDef(flags, pName, getterBody, setterArgAndBody) = propDef
    implicit val ctx = ErrorContext(propDef)

    typecheckExpr(pName, Env.empty)

    getterBody.foreach(typecheckExpr(_, Env.empty))

    setterArgAndBody.foreach { case (_, body) =>
      typecheck(body, Env.empty)
    }
  }

  private def typecheckExpect(tree: Tree, env: Env, expectedType: Type)(
      implicit ctx: ErrorContext): Unit = {
    typecheck(tree, env)

    if (!isSubtype(tree.tpe, expectedType)) {
      reportError(i"$expectedType expected but ${tree.tpe} found "+
          i"for tree of type ${tree.getClass.getName}")
    }
  }

  private def typecheckExpr(tree: Tree, env: Env)(
      implicit ctx: ErrorContext): Unit = {
    typecheckExpect(tree, env, AnyType)
  }

  private def typecheckExprOrSpread(tree: TreeOrJSSpread, env: Env)(
      implicit ctx: ErrorContext): Unit = {
    tree match {
      case JSSpread(items) =>
        typecheckExpr(items, env)
      case tree: Tree =>
        typecheckExpr(tree, env)
    }
  }

  private def typecheck(tree: Tree, env: Env): Unit = {
    implicit val ctx = ErrorContext(tree)

    def checkApplyGeneric(receiverTypeForError: Any, methodName: MethodName,
        args: List[Tree], tpe: Type, isStatic: Boolean): Unit = {
      val (methodParams, resultType) = inferMethodType(methodName, isStatic)
      for ((actual, formal) <- args zip methodParams) {
        typecheckExpect(actual, env, formal)
      }
      if (tpe != resultType)
        reportError(i"Call to $receiverTypeForError.$methodName of type $resultType typed as ${tree.tpe}")
    }

    tree match {
      // Definitions

      case VarDef(ident, _, vtpe, _, rhs) =>
        typecheckExpect(rhs, env, vtpe)

      // Control flow constructs

      case Skip() =>

      case Block(trees) =>
        trees.foreach(typecheck(_, env))

      case Labeled(label, tpe, body) =>
        typecheckExpect(body, env.withLabeledReturnType(label.name, tpe), tpe)

      case Assign(lhs, rhs) =>
        def checkNonStaticField(receiver: Tree, name: FieldName): Unit = {
          receiver match {
            case This() if env.inConstructorOf == Some(name.className) =>
              // ok
            case _ =>
              if (lookupClass(name.className).lookupField(name).exists(!_.flags.isMutable))
                reportError(i"Assignment to immutable field $name.")
          }
        }

        lhs match {
          case Select(receiver, FieldIdent(name)) =>
            checkNonStaticField(receiver, name)
          case JSPrivateSelect(receiver, FieldIdent(name)) =>
            checkNonStaticField(receiver, name)
          case SelectStatic(FieldIdent(name)) =>
            val c = lookupClass(name.className)
            for {
              f <- c.lookupStaticField(name)
              if !f.flags.isMutable
            } {
              reportError(i"Assignment to immutable static field $name.")
            }

          case _:VarRef | _:ArraySelect | _:RecordSelect | _:JSSelect |
              _:JSSuperSelect | _:JSGlobalRef =>
        }
        typecheckExpr(lhs, env)
        typecheckExpect(rhs, env, lhs.tpe)

      case Return(expr, label) =>
        val returnType = env.returnTypes(label.name)
        if (returnType == NoType)
          typecheckExpr(expr, env)
        else
          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(cond, body) =>
        typecheckExpect(cond, env, BooleanType)
        typecheck(body, env)

      case ForIn(obj, keyVar, _, body) =>
        typecheckExpr(obj, env)
        typecheck(body, env)

      case TryCatch(block, errVar, _, handler) =>
        val tpe = tree.tpe
        typecheckExpect(block, env, tpe)
        typecheckExpect(handler, env, tpe)

      case TryFinally(block, finalizer) =>
        val tpe = tree.tpe
        typecheckExpect(block, env, tpe)
        typecheck(finalizer, env)

      case Throw(expr) =>
        typecheckExpr(expr, env)

      case Match(selector, cases, default) =>
        // Typecheck the selector as an int or a java.lang.String
        typecheck(selector, env)
        if (!isSubtype(selector.tpe, IntType) && !isSubtype(selector.tpe, BoxedStringType)) {
          reportError(
              i"int or java.lang.String expected but ${selector.tpe} found" +
              i"for tree of type ${selector.getClass.getName}")
        }

        // The alternatives are MatchableLiterals, no point typechecking them
        val tpe = tree.tpe
        for ((_, body) <- cases)
          typecheckExpect(body, env, tpe)
        typecheckExpect(default, env, tpe)

      case Debugger() =>

      // Scala expressions

      case New(className, ctor, args) =>
        val clazz = lookupClass(className)
        if (clazz.kind != ClassKind.Class)
          reportError(i"new $className which is not a class")
        checkApplyGeneric(className, ctor.name, args, NoType, isStatic = false)

      case LoadModule(className) =>
        val clazz = lookupClass(className)
        if (clazz.kind != ClassKind.ModuleClass)
          reportError("LoadModule of non-module class $className")

      case StoreModule() =>
        // Nothing to check; everything is checked in ClassDefChecker
        ()

      case Select(qualifier, FieldIdent(item)) =>
        val className = item.className
        val c = lookupClass(className)
        val kind = c.kind
        if (!kind.isClass) {
          reportError(i"Cannot select $item of non-class $className")
          typecheckExpr(qualifier, env)
        } else {
          typecheckExpect(qualifier, env, ClassType(className, nullable = true))

          /* Actually checking the field is done only if the class has
           * instances (including instances of subclasses).
           *
           * This is necessary because the BaseLinker can completely get rid
           * of all the fields of a class that has no instance. Obviously in
           * such cases, the only value that `qualifier` can assume is
           * `null`, and the `Select` will fail with an NPE. But the IR is
           * still valid per se.
           *
           * See #3060.
           */
          if (c.hasInstances) {
            c.lookupField(item).fold[Unit] {
              reportError(i"Class $className does not have a field $item")
            } { fieldDef =>
              if (fieldDef.tpe != tree.tpe)
                reportError(i"Select $className.$item of type "+
                    i"${fieldDef.tpe} typed as ${tree.tpe}")
            }
          }
        }

      case SelectStatic(FieldIdent(item)) =>
        val className = item.className
        val checkedClass = lookupClass(className)
        if (checkedClass.kind.isJSType) {
          reportError(i"Cannot select static $item of JS type $className")
        } else {
          checkedClass.lookupStaticField(item).fold[Unit] {
            reportError(i"Class $className does not have a static field $item")
          } { fieldDef =>
            if (fieldDef.tpe != tree.tpe)
              reportError(i"SelectStatic $className.$item of type "+
                  i"${fieldDef.tpe} typed as ${tree.tpe}")
          }
        }

      case SelectJSNativeMember(className, MethodIdent(member)) =>
        val checkedClass = lookupClass(className)
        if (!checkedClass.hasJSNativeMember(member))
          reportError(i"Class $className does not have JS native member $member")

      case Apply(flags, receiver, MethodIdent(method), args) =>
        if (flags.isPrivate)
          reportError("Illegal flag for Apply: Private")
        typecheckExpr(receiver, env)
        val fullCheck = receiver.tpe match {
          case ClassType(className, _) =>
            /* For class types, we only perform full checks if the class has
             * instances. This is necessary because the BaseLinker can
             * completely get rid of all the method *definitions* for the call
             * method. In that case, the classes references in the *signature*
             * of the method might not have been made reachable, and hence
             * inferring the type signature might fail. Obviously in such cases,
             * the only value that `receiver` can assume is `null`, and the
             * `Apply` will fail with an NPE, so the types of the arguments are
             * irreleant.
             */
            lookupClass(className).hasInstances
          case NullType | NothingType =>
            // By a similar argument, we must not perform full checks here
            false
          case _ =>
            true
        }
        if (fullCheck) {
          checkApplyGeneric(receiver.tpe, method, args, tree.tpe, isStatic = false)
        } else {
          for (arg <- args)
            typecheckExpr(arg, env)
        }

      case ApplyStatically(_, receiver, className, MethodIdent(method), args) =>
        typecheckExpect(receiver, env, ClassType(className, nullable = true))
        checkApplyGeneric(className, method, args, tree.tpe, isStatic = false)

      case ApplyStatic(_, className, MethodIdent(method), args) =>
        checkApplyGeneric(className, method, args, tree.tpe, isStatic = true)

      case ApplyDynamicImport(_, className, MethodIdent(method), args) =>
        checkApplyGeneric(className, method, args, AnyType, isStatic = true)

        val resultType = method.resultTypeRef
        if (resultType != ClassRef(ObjectClass)) {
          reportError(
              i"illegal dynamic import call to $className.$method " +
              i"with non-object result type: $resultType")
        }

      case UnaryOp(op, lhs) =>
        import UnaryOp._
        val expectedArgType = (op: @switch) match {
          case Boolean_! =>
            BooleanType
          case CharToInt =>
            CharType
          case ByteToInt =>
            ByteType
          case ShortToInt =>
            ShortType
          case IntToLong | IntToDouble | IntToChar | IntToByte | IntToShort =>
            IntType
          case LongToInt | LongToDouble | LongToFloat =>
            LongType
          case FloatToDouble =>
            FloatType
          case DoubleToInt | DoubleToFloat | DoubleToLong =>
            DoubleType
          case String_length =>
            StringType
          case CheckNotNull =>
            AnyType
          case Class_name | Class_isPrimitive | Class_isInterface |
              Class_isArray | Class_componentType | Class_superClass =>
            ClassType(ClassClass, nullable = false)
        }
        typecheckExpect(lhs, env, expectedArgType)

      case BinaryOp(op, lhs, rhs) =>
        import BinaryOp._
        val expectedLhsType = (op: @switch) match {
          case === | !== | String_+ =>
            AnyType
          case Boolean_== | Boolean_!= | Boolean_| | Boolean_& =>
            BooleanType
          case Int_+ | Int_- | Int_* | Int_/ | Int_% |
              Int_| | Int_& | Int_^ | Int_<< | Int_>>> | Int_>> |
              Int_== | Int_!= | Int_< | Int_<= | Int_> | Int_>= =>
            IntType
          case Long_+ | Long_- | Long_* | Long_/ | Long_% |
              Long_| | Long_& | Long_^ | Long_<< | Long_>>> | Long_>> |
              Long_== | Long_!= | Long_< | Long_<= | Long_> | Long_>= =>
            LongType
          case Float_+ | Float_- | Float_* | Float_/ | Float_% =>
            FloatType
          case Double_+ | Double_- | Double_* | Double_/ | Double_% |
              Double_== | Double_!= |
              Double_< | Double_<= | Double_> | Double_>= =>
            DoubleType
          case String_charAt =>
            StringType
          case Class_isInstance | Class_isAssignableFrom | Class_cast |
              Class_newArray =>
            ClassType(ClassClass, nullable = false)
        }
        val expectedRhsType = (op: @switch) match {
          case Long_<< | Long_>>> | Long_>> | String_charAt | Class_newArray =>
            IntType
          case Class_isInstance | Class_cast =>
            AnyType
          case _ =>
            expectedLhsType
        }
        typecheckExpect(lhs, env, expectedLhsType)
        typecheckExpect(rhs, env, expectedRhsType)

      case NewArray(typeRef, length) =>
        typecheckExpect(length, env, IntType)

      case ArrayValue(typeRef, elems) =>
        val elemType = arrayElemType(typeRef)
        for (elem <- elems)
          typecheckExpect(elem, env, elemType)

      case ArrayLength(array) =>
        typecheckExpr(array, env)
        if (!array.tpe.isInstanceOf[ArrayType])
          reportError(i"Array type expected but ${array.tpe} found")

      case ArraySelect(array, index) =>
        typecheckExpect(index, env, IntType)
        typecheckExpr(array, env)
        array.tpe match {
          case arrayType: ArrayType =>
            if (tree.tpe != arrayElemType(arrayType))
              reportError(i"Array select of array type $arrayType typed as ${tree.tpe}")
          case arrayType =>
            reportError(i"Array type expected but $arrayType found")
        }

      case IsInstanceOf(expr, testType) =>
        typecheckExpr(expr, env)
        checkIsAsInstanceTargetType(testType)

      case AsInstanceOf(expr, tpe) =>
        typecheckExpr(expr, env)
        checkIsAsInstanceTargetType(tpe)

      case GetClass(expr) =>
        typecheckExpr(expr, env)

      case Clone(expr) =>
        typecheckExpect(expr, env, ClassType(CloneableClass, nullable = true))

      case IdentityHashCode(expr) =>
        typecheckExpr(expr, env)

      case WrapAsThrowable(expr) =>
        typecheckExpr(expr, env)

      case UnwrapFromThrowable(expr) =>
        typecheckExpect(expr, env, ClassType(ThrowableClass, nullable = true))

      // JavaScript expressions

      case JSNew(ctor, args) =>
        typecheckExpr(ctor, env)
        for (arg <- args)
          typecheckExprOrSpread(arg, env)

      case JSPrivateSelect(qualifier, field) =>
        typecheckExpr(qualifier, env)
        val className = field.name.className
        val checkedClass = lookupClass(className)
        if (!checkedClass.kind.isJSClass && checkedClass.kind != ClassKind.AbstractJSType) {
          reportError(i"Cannot select JS private field $field of non-JS class $className")
        } else {
          if (checkedClass.lookupField(field.name).isEmpty)
            reportError(i"JS class $className does not have a field $field")
          /* The declared type of the field is irrelevant here. It is only
           * relevant for its initialization value. The type of the selection
           * is always `any`.
           */
        }

      case JSSelect(qualifier, item) =>
        typecheckExpr(qualifier, env)
        typecheckExpr(item, env)

      case JSFunctionApply(fun, args) =>
        typecheckExpr(fun, env)
        for (arg <- args)
          typecheckExprOrSpread(arg, env)

      case JSMethodApply(receiver, method, args) =>
        typecheckExpr(receiver, env)
        typecheckExpr(method, env)
        for (arg <- args)
          typecheckExprOrSpread(arg, env)

      case JSSuperSelect(superClass, qualifier, item) =>
        typecheckExpr(superClass, env)
        typecheckExpr(qualifier, env)
        typecheckExpr(item, env)

      case JSSuperMethodCall(superClass, receiver, method, args) =>
        typecheckExpr(superClass, env)
        typecheckExpr(receiver, env)
        typecheckExpr(method, env)
        for (arg <- args)
          typecheckExprOrSpread(arg, env)

      case JSImportCall(arg) =>
        typecheckExpr(arg, env)

      case JSNewTarget() =>

      case JSImportMeta() =>

      case LoadJSConstructor(className) =>
        val clazz = lookupClass(className)
        val valid = clazz.kind match {
          case ClassKind.JSClass       => true
          case ClassKind.NativeJSClass => true
          case _                       => false
        }
        if (!valid)
          reportError(i"JS class type expected but $className found")
        else if (clazz.jsClassCaptures.nonEmpty)
          reportError(i"Cannot load JS constructor of non-top-level class $className")
        else if (clazz.kind == ClassKind.NativeJSClass && clazz.jsNativeLoadSpec.isEmpty)
          reportError(i"Cannot load JS constructor of native JS class $className without native load spec")

      case LoadJSModule(className) =>
        val clazz = lookupClass(className)
        val valid = clazz.kind match {
          case ClassKind.JSModuleClass       => true
          case ClassKind.NativeJSModuleClass => true
          case _                             => false
        }
        if (!valid)
          reportError(i"JS module class type expected but $className found")
        else if (clazz.kind == ClassKind.NativeJSModuleClass && clazz.jsNativeLoadSpec.isEmpty)
          reportError(i"Cannot load JS module of native JS module class $className without native load spec")

      case JSDelete(qualifier, item) =>
        typecheckExpr(qualifier, env)
        typecheckExpr(item, 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)
          typecheckExprOrSpread(item, env)

      case JSObjectConstr(fields) =>
        for ((key, value) <- fields) {
          typecheckExpr(key, env)
          typecheckExpr(value, env)
        }

      case JSGlobalRef(_) =>

      case JSTypeOfGlobalRef(_) =>

      case JSLinkingInfo() =>

      // Literals

      case _: Literal =>

      // Atomic expressions

      case _: VarRef =>

      case This() =>

      case Closure(arrow, captureParams, params, restParam, body, captureValues) =>
        assert(captureParams.size == captureValues.size) // checked by ClassDefChecker

        // Check compliance of captureValues wrt. captureParams in the current env
        for ((ParamDef(_, _, ctpe, _), value) <- captureParams zip captureValues) {
          typecheckExpect(value, env, ctpe)
        }

        // Then check the closure params and body in its own env
        typecheckExpect(body, Env.empty, AnyType)

      case CreateJSClass(className, captureValues) =>
        val clazz = lookupClass(className)
        clazz.jsClassCaptures.fold {
          reportError(i"Invalid CreateJSClass of top-level class $className")
        } { captureParams =>
          if (captureParams.size != captureValues.size) {
            reportError("Mismatched size for class captures: " +
                i"${captureParams.size} params vs ${captureValues.size} values")
          }

          for ((ParamDef(_, _, ctpe, _), value) <- captureParams.zip(captureValues))
            typecheckExpect(value, env, ctpe)
        }

      case _:RecordSelect | _:RecordValue | _:Transient | _:JSSuperConstructorCall =>
        reportError("invalid tree")
    }
  }

  private def checkIsAsInstanceTargetType(tpe: Type)(
      implicit ctx: ErrorContext): Unit = {
    tpe match {
      case ClassType(className, _) =>
        val kind = lookupClass(className).kind
        if (kind.isJSType) {
          reportError(
              i"JS type $className is not a valid target type for " +
              "Is/AsInstanceOf")
        }

      case _ =>
        // Non ClassTypes are checked by the ClassDef checker.
    }
  }

  private def inferMethodType(methodName: MethodName, isStatic: Boolean)(
      implicit ctx: ErrorContext): (List[Type], Type) = {

    val paramTypes = methodName.paramTypeRefs.map(typeRefToType)
    val resultType = typeRefToType(methodName.resultTypeRef)
    (paramTypes, resultType)
  }

  private def typeRefToType(typeRef: TypeRef)(
      implicit ctx: ErrorContext): Type = {
    typeRef match {
      case PrimRef(tpe)               => tpe
      case ClassRef(className)        => classNameToType(className)
      case arrayTypeRef: ArrayTypeRef => ArrayType(arrayTypeRef, nullable = true)
    }
  }

  private def classNameToType(className: ClassName)(
      implicit ctx: ErrorContext): Type = {
    if (className == ObjectClass) {
      AnyType
    } else {
      val kind = lookupClass(className).kind
      if (kind.isJSType) AnyType
      else ClassType(className, nullable = true)
    }
  }

  private def arrayElemType(arrayType: ArrayType)(
      implicit ctx: ErrorContext): Type = {
    arrayElemType(arrayType.arrayTypeRef)
  }

  private def arrayElemType(arrayTypeRef: ArrayTypeRef)(
      implicit ctx: ErrorContext): Type = {
    val ArrayTypeRef(base, dimensions) = arrayTypeRef
    if (dimensions == 1)
      typeRefToType(base)
    else
      ArrayType(ArrayTypeRef(base, dimensions - 1), nullable = true)
  }

  private def lookupClass(className: ClassName)(
      implicit ctx: ErrorContext): CheckedClass = {
    classes.getOrElseUpdate(className, {
      reportError(i"Cannot find class $className")
      new CheckedClass(className, ClassKind.Class, None, Some(ObjectClass),
          Set(ObjectClass), hasInstances = true, None, Nil, Set.empty)
    })
  }

  private def lookupClass(classType: ClassType)(
      implicit ctx: ErrorContext): CheckedClass = {
    lookupClass(classType.className)
  }

  private def lookupClass(classRef: ClassRef)(
      implicit ctx: ErrorContext): CheckedClass = {
    lookupClass(classRef.className)
  }

  private def isSubclass(lhs: ClassName, rhs: ClassName)(
      implicit ctx: ErrorContext): Boolean = {
    lookupClass(lhs).ancestors.contains(rhs)
  }

  private def isSubtype(lhs: Type, rhs: Type)(
      implicit ctx: ErrorContext): Boolean = {
    Types.isSubtype(lhs, rhs)(isSubclass)
  }

  private class Env(
      /** Return types by label. */
      val returnTypes: Map[LabelName, Type],
      /** Whether we're in a constructor of the class */
      val inConstructorOf: Option[ClassName]
  ) {
    import Env._

    def withLabeledReturnType(label: LabelName, returnType: Type): Env =
      new Env(returnTypes + (label -> returnType), this.inConstructorOf)
  }

  private object Env {
    val empty: Env = new Env(Map.empty, None)

    def forConstructorOf(className: ClassName): Env =
      new Env(Map.empty, inConstructorOf = Some(className))
  }

  private class CheckedClass(
      val name: ClassName,
      val kind: ClassKind,
      val jsClassCaptures: Option[List[ParamDef]],
      val superClassName: Option[ClassName],
      val ancestors: Set[ClassName],
      val hasInstances: Boolean,
      val jsNativeLoadSpec: Option[JSNativeLoadSpec],
      _fields: List[CheckedField],
      val jsNativeMembers: Set[MethodName]) {

    val fields = _fields.filter(!_.flags.namespace.isStatic).map(f => f.name -> f).toMap
    val staticFields = _fields.filter(_.flags.namespace.isStatic).map(f => f.name -> f).toMap

    lazy val superClass = superClassName.map(classes)

    def this(classDef: LinkedClass)(implicit ctx: ErrorContext) = {
      this(classDef.name.name, classDef.kind,
          classDef.jsClassCaptures,
          classDef.superClass.map(_.name),
          classDef.ancestors.toSet,
          classDef.hasInstances,
          classDef.jsNativeLoadSpec,
          CheckedClass.checkedFieldsOf(classDef),
          classDef.jsNativeMembers.map(_.name.name).toSet)
    }

    def lookupField(name: FieldName): Option[CheckedField] =
      fields.get(name)

    def lookupStaticField(name: FieldName): Option[CheckedField] =
      staticFields.get(name)

    def hasJSNativeMember(name: MethodName): Boolean =
      jsNativeMembers.contains(name)
  }

  private object CheckedClass {
    private def checkedFieldsOf(classDef: LinkedClass): List[CheckedField] = {
      classDef.fields.collect {
        case FieldDef(flags, FieldIdent(name), _, tpe) =>
          new CheckedField(flags, name, tpe)
      }
    }
  }

  private class CheckedField(val flags: MemberFlags, val name: FieldName,
      val tpe: Type)
}

object IRChecker {
  private val BoxedStringType = ClassType(BoxedStringClass, nullable = true)

  /** Checks that the IR in a [[frontend.LinkingUnit LinkingUnit]] is correct.
   *
   *  @return Count of IR checking errors (0 in case of success)
   */
  def check(unit: LinkingUnit, logger: Logger): Int = {
    val reporter = new LoggerErrorReporter(logger)
    new IRChecker(unit, reporter).check()
    reporter.errorCount
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy