logo

class

compiler::CheckErrors

sys::Obj
  compiler::CompilerSupport
    compiler::CompilerStep
      compiler::CheckErrors
    1  //
    2  // Copyright (c) 2006, Brian Frank and Andy Frank
    3  // Licensed under the Academic Free License version 3.0
    4  //
    5  // History:
    6  //    2 Dec 05  Brian Frank  Creation
    7  //   17 Sep 06  Brian Frank  Ported from Java to Fan
    8  //
    9  
   10  **
   11  ** CheckErrors walks the tree of statements and expressions looking
   12  ** for errors the compiler can detect such as invalid type usage.  We
   13  ** attempt to leave all the error reporting to this step, so that we
   14  ** can batch report as many errors as possible.
   15  **
   16  ** Since CheckErrors already performs a full tree walk down to each leaf
   17  ** expression, we also do a couple of other AST decorations in this step:
   18  **   1) add temp local for field assignments like return ++x
   19  **   2) add temp local for returns inside protected region
   20  **   3) check for field accessor optimization
   21  **   4) check for field storage requirements
   22  **   5) add implicit cast when assigning Obj to non-Obj
   23  **
   24  class CheckErrors : CompilerStep
   25  {
   26  
   27  //////////////////////////////////////////////////////////////////////////
   28  // Constructor
   29  //////////////////////////////////////////////////////////////////////////
   30  
   31    new make(Compiler compiler)
   32      : super(compiler)
   33    {
   34    }
   35  
   36  //////////////////////////////////////////////////////////////////////////
   37  // Run
   38  //////////////////////////////////////////////////////////////////////////
   39  
   40    override Void run()
   41    {
   42      log.debug("CheckErrors")
   43      walk(types, VisitDepth.expr)
   44      bombIfErr
   45    }
   46  
   47  //////////////////////////////////////////////////////////////////////////
   48  // TypeDef
   49  //////////////////////////////////////////////////////////////////////////
   50  
   51    override Void visitTypeDef(TypeDef t)
   52    {
   53      // check type flags
   54      checkTypeFlags(t)
   55  
   56      // check for abstract slots in concrete class
   57      checkAbstractSlots(t)
   58  
   59      // check for const slots in const class
   60      checkConstType(t)
   61  
   62      // check some knuckle head doesn't override type
   63      if (t.slotDef("type") != null && !compiler.isSys)
   64        err("Cannot override Obj.type() knuckle head", t.slotDef("type").location)
   65    }
   66  
   67    private Void checkTypeFlags(TypeDef t)
   68    {
   69      flags := t.flags
   70      loc := t.location
   71  
   72      // these modifiers are never allowed on a type
   73      if (flags & FConst.Ctor != 0)      err("Cannot use 'new' modifier on type", loc)
   74      if (flags & FConst.Native != 0)    err("Cannot use 'native' modifier on type", loc)
   75      if (flags & FConst.Override != 0)  err("Cannot use 'override' modifier on type", loc)
   76      if (flags & FConst.Private != 0)   err("Cannot use 'private' modifier on type", loc)
   77      if (flags & FConst.Protected != 0) err("Cannot use 'protected' modifier on type", loc)
   78      if (flags & FConst.Static != 0)    err("Cannot use 'static' modifier on type", loc)
   79      if (flags & FConst.Virtual != 0)   err("Cannot use 'virtual' modifier on type", loc)
   80      if (flags & Parser.Readonly != 0)  err("Cannot use 'readonly' modifier on type", loc)
   81  
   82      // check invalid protection combinations
   83      checkProtectionFlags(flags, loc)
   84  
   85      // check abstract and final
   86      if ((flags & FConst.Abstract != 0) && (flags & FConst.Final != 0))
   87        err("Invalid combination of 'abstract' and 'final' modifiers", loc)
   88    }
   89  
   90    private Void checkAbstractSlots(TypeDef t)
   91    {
   92      // if already abstract, nothing to check
   93      if (t.isAbstract) return
   94  
   95      errForDef := false
   96      closure := |CSlot slot|
   97      {
   98        if (!slot.isAbstract) return
   99        if (slot.parent === t)
  100        {
  101          if (!errForDef)
  102          {
  103            err("Class '$t.name' must be abstract since it contains abstract slots", t.location)
  104            errForDef = true
  105          }
  106        }
  107        else
  108        {
  109          err("Class '$t.name' must be abstract since it inherits but doesn't override '$slot.qname'", t.location)
  110        }
  111      }
  112  
  113      if (compiler.input.isTest)
  114        t.slots.values.sort.each(closure)
  115      else
  116        t.slots.each(closure)
  117    }
  118  
  119    private Void checkConstType(TypeDef t)
  120    {
  121      // if not const, nothing to check
  122      if (!t.isConst)
  123      {
  124        // non-const cannot inherit from const class
  125        if (t.base != null && t.base.isConst)
  126          err("Non-const class '$t.name' cannot subclass const class '$t.base'", t.location)
  127        return
  128      }
  129  
  130      // const class cannot inherit from non-const class
  131      if (t.base != null && t.base != ns.objType && !t.base.isConst)
  132        err("Const class '$t.name' cannot subclass non-const class '$t.base'", t.location)
  133  
  134      // check that each field is const (don't worry about statics
  135      // because they are forced to be const in another check)
  136      t.fieldDefs.each |FieldDef f|
  137      {
  138        if (!f.isConst && !f.isStatic)
  139          err("Const class '$t.name' cannot contain non-const field '$f.name'", f.location)
  140      }
  141    }
  142  
  143  //////////////////////////////////////////////////////////////////////////
  144  // FieldDef
  145  //////////////////////////////////////////////////////////////////////////
  146  
  147    override Void visitFieldDef(FieldDef f)
  148    {
  149      // if this field overrides a concrete field,
  150      // then it never gets its own storage
  151      if (f.concreteBase != null)
  152        f.flags &= ~FConst.Storage
  153  
  154      // check for invalid flags
  155      checkFieldFlags(f)
  156  
  157      // mixins cannot have non-abstract fields
  158      if (curType.isMixin && !f.isAbstract && !f.isStatic)
  159        err("Mixin field '$f.name' must be abstract", f.location)
  160  
  161      // abstract field cannot have initialization
  162      if (f.isAbstract && f.init != null)
  163        err("Abstract field '$f.name' cannot have initializer", f.init.location)
  164  
  165      // abstract field cannot have getter/setter
  166      if (f.isAbstract && (f.hasGet || f.hasSet))
  167        err("Abstract field '$f.name' cannot have getter or setter", f.location)
  168    }
  169  
  170    private Void checkFieldFlags(FieldDef f)
  171    {
  172      flags := f.flags
  173      loc   := f.location
  174  
  175      // these modifiers are never allowed on a field
  176      if (flags & FConst.Ctor != 0)    err("Cannot use 'new' modifier on field", loc)
  177      if (flags & FConst.Final != 0)   err("Cannot use 'final' modifier on field", loc)
  178      if (flags & FConst.Native != 0)  err("Cannot use 'native' modifier on field", loc)
  179  
  180      // check invalid protection combinations
  181      checkProtectionFlags(flags, loc)
  182  
  183      // if const
  184      if (flags & FConst.Const != 0)
  185      {
  186        // invalid const flag combo
  187        if (flags & FConst.Abstract != 0) err("Invalid combination of 'const' and 'abstract' modifiers", loc)
  188        else if (flags & FConst.Override != 0) err("Invalid combination of 'const' and 'override' modifiers", loc)
  189        else if (flags & FConst.Virtual != 0) err("Invalid combination of 'const' and 'virtual' modifiers", loc)
  190  
  191        // invalid type
  192        if (!isConstFieldType(f.fieldType))
  193          err("Const field '$f.name' has non-const type '$f.fieldType'", loc)
  194      }
  195      else
  196      {
  197        // static fields must be const
  198        if (flags & FConst.Static != 0) err("Static field '$f.name' must be const", loc)
  199      }
  200  
  201      // check invalid protection combinations on setter (getter
  202      // can no modifiers which is checked in the parser)
  203      if (f.setter != null)
  204      {
  205        fieldProtection  := flags & ~Parser.ProtectionMask
  206        setterProtection := f.set.flags & ~Parser.ProtectionMask
  207        if (fieldProtection != setterProtection)
  208        {
  209          // verify protection flag combinations
  210          checkProtectionFlags(f.set.flags, loc)
  211  
  212          // verify that setter has narrowed protection
  213          if (fieldProtection & FConst.Private != 0)
  214          {
  215            if (setterProtection & FConst.Public != 0)    err("Setter cannot have wider visibility than the field", loc)
  216            if (setterProtection & FConst.Protected != 0) err("Setter cannot have wider visibility than the field", loc)
  217            if (setterProtection & FConst.Internal != 0)  err("Setter cannot have wider visibility than the field", loc)
  218          }
  219          else if (fieldProtection & FConst.Internal != 0)
  220          {
  221            if (setterProtection & FConst.Public != 0)    err("Setter cannot have wider visibility than the field", loc)
  222            if (setterProtection & FConst.Protected != 0) err("Setter cannot have wider visibility than the field", loc)
  223          }
  224          else if (fieldProtection & FConst.Protected != 0)
  225          {
  226            if (setterProtection & FConst.Public != 0)    err("Setter cannot have wider visibility than the field", loc)
  227          }
  228        }
  229      }
  230    }
  231  
  232    private Bool isConstFieldType(CType t)
  233    {
  234      if (t.isConst) return true
  235      t = t.deref
  236  
  237      if (t is ListType)
  238      {
  239        list := t as ListType
  240        return isConstFieldType(list.v)
  241      }
  242  
  243      if (t is MapType)
  244      {
  245        map := t as MapType
  246        return isConstFieldType(map.k) && isConstFieldType(map.v)
  247      }
  248  
  249      return false
  250    }
  251  
  252  //////////////////////////////////////////////////////////////////////////
  253  // MethodDef
  254  //////////////////////////////////////////////////////////////////////////
  255  
  256    override Void visitMethodDef(MethodDef m)
  257    {
  258      // check invalid use of flags
  259      checkMethodFlags(m)
  260  
  261      // check parameters
  262      checkParams(m)
  263  
  264      // check ctors call super (or another this) ctor
  265      if (m.isCtor()) checkCtor(m)
  266    }
  267  
  268    private Void checkMethodFlags(MethodDef m)
  269    {
  270      // check field accessors in checkFieldFlags
  271      if (m.isFieldAccessor) return
  272  
  273      flags := m.flags
  274      loc := m.location
  275  
  276      // these modifiers are never allowed on a method
  277      if (flags & FConst.Final != 0)     err("Cannot use 'final' modifier on method", loc)
  278      if (flags & FConst.Const != 0)     err("Cannot use 'const' modifier on method", loc)
  279      if (flags & Parser.Readonly != 0)  err("Cannot use 'readonly' modifier on method", loc)
  280  
  281      // check invalid protection combinations
  282      checkProtectionFlags(flags, loc)
  283  
  284      // check invalid constructor flags
  285      if (flags & FConst.Ctor != 0)
  286      {
  287        if (flags & FConst.Abstract != 0) err("Invalid combination of 'new' and 'abstract' modifiers", loc)
  288        else if (flags & FConst.Override != 0) err("Invalid combination of 'new' and 'override' modifiers", loc)
  289        else if (flags & FConst.Virtual != 0) err("Invalid combination of 'new' and 'virtual' modifiers", loc)
  290        if (flags & FConst.Native != 0)   err("Invalid combination of 'new' and 'native' modifiers", loc)
  291        if (flags & FConst.Static != 0)   err("Invalid combination of 'new' and 'static' modifiers", loc)
  292      }
  293  
  294      // check invalid static flags
  295      if (flags & FConst.Static != 0)
  296      {
  297        if (flags & FConst.Abstract != 0) err("Invalid combination of 'static' and 'abstract' modifiers", loc)
  298        else if (flags & FConst.Override != 0) err("Invalid combination of 'static' and 'override' modifiers", loc)
  299        else if (flags & FConst.Virtual != 0) err("Invalid combination of 'static' and 'virtual' modifiers", loc)
  300      }
  301  
  302      // check invalid abstract flags
  303      if (flags & FConst.Abstract != 0)
  304      {
  305        if (flags & FConst.Native != 0) err("Invalid combination of 'abstract' and 'native' modifiers", loc)
  306      }
  307  
  308      // normalize method flags after checking
  309      if (m.flags & FConst.Static != 0)
  310        m.flags |= FConst.Const;
  311    }
  312  
  313    private Void checkParams(MethodDef m)
  314    {
  315      // check that defs are contiguous after first one
  316      seenDef := false
  317      m.paramDefs.each |ParamDef p|
  318      {
  319        checkParam(p)
  320        if (seenDef)
  321        {
  322          if (p.def == null)
  323            err("Parameter '$p.name' must have default", p.location)
  324        }
  325        else
  326        {
  327          seenDef = p.def != null
  328        }
  329      }
  330    }
  331  
  332    private Void checkParam(ParamDef p)
  333    {
  334      // check parameter default type
  335      if (p.def != null && !p.def.fits(p.paramType))
  336        err("'$p.def.ctype' is not assignable to '$p.paramType'", p.def.location)
  337    }
  338  
  339    private Void checkCtor(MethodDef m)
  340    {
  341      // mixins cannot have constructors
  342      if (curType.isMixin)
  343        err("Mixins cannot have constructors", m.location)
  344  
  345      // ensure super/this constructor is called
  346      if (m.ctorChain == null && !compiler.isSys && !curType.base.isObj && !curType.isSynthetic)
  347        err("Must call super class constructor in '$m.name'", m.location)
  348    }
  349  
  350  //////////////////////////////////////////////////////////////////////////
  351  // Statements
  352  //////////////////////////////////////////////////////////////////////////
  353  
  354    override Void enterStmt(Stmt stmt)
  355    {
  356      if (stmt.id == StmtId.tryStmt) protectedRegionDepth++
  357    }
  358  
  359    override Void exitStmt(Stmt stmt)
  360    {
  361      if (stmt.id == StmtId.tryStmt) protectedRegionDepth--
  362    }
  363  
  364    override Void enterFinally(TryStmt stmt)
  365    {
  366      finallyDepth++
  367    }
  368  
  369    override Void exitFinally(TryStmt stmt)
  370    {
  371      finallyDepth--
  372    }
  373  
  374    override Void visitStmt(Stmt stmt)
  375    {
  376      switch (stmt.id)
  377      {
  378        case StmtId.expr:          checkExprStmt((ExprStmt)stmt)
  379        case StmtId.ifStmt:        checkIf((IfStmt)stmt)
  380        case StmtId.returnStmt:    checkReturn((ReturnStmt)stmt)
  381        case StmtId.throwStmt:     checkThrow((ThrowStmt)stmt)
  382        case StmtId.forStmt:       checkFor((ForStmt)stmt)
  383        case StmtId.whileStmt:     checkWhile((WhileStmt)stmt)
  384        case StmtId.breakStmt:     checkBreak((BreakStmt)stmt)
  385        case StmtId.continueStmt:  checkContinue((ContinueStmt)stmt)
  386        case StmtId.tryStmt:       checkTry((TryStmt)stmt)
  387        case StmtId.switchStmt:    checkSwitch((SwitchStmt)stmt)
  388      }
  389    }
  390  
  391    private Void checkExprStmt(ExprStmt stmt)
  392    {
  393      if (!stmt.expr.isStmt)
  394        err("Not a statement", stmt.expr.location)
  395    }
  396  
  397    private Void checkIf(IfStmt stmt)
  398    {
  399      if (!stmt.condition.ctype.isBool)
  400      {
  401        if (stmt.condition.ctype.isObj)
  402          stmt.condition = cast(stmt.condition, ns.boolType)
  403        else
  404          err("If condition must be Bool, not '$stmt.condition.ctype'", stmt.condition.location)
  405      }
  406    }
  407  
  408    private Void checkThrow(ThrowStmt stmt)
  409    {
  410      if (!stmt.exception.fits(ns.errType))
  411      {
  412        if (stmt.exception.ctype.isObj)
  413          stmt.exception = cast(stmt.exception, ns.errType)
  414        else
  415          err("Must throw Err, not '$stmt.exception.ctype'", stmt.exception.location)
  416      }
  417    }
  418  
  419    private Void checkFor(ForStmt stmt)
  420    {
  421      if (stmt.condition != null && !stmt.condition.ctype.isBool)
  422      {
  423        if (stmt.condition.ctype.isObj)
  424          stmt.condition = cast(stmt.condition, ns.boolType)
  425        else
  426          err("For condition must be Bool, not '$stmt.condition.ctype'", stmt.condition.location)
  427      }
  428    }
  429  
  430    private Void checkWhile(WhileStmt stmt)
  431    {
  432      if (!stmt.condition.ctype.isBool)
  433      {
  434        if (stmt.condition.ctype.isObj)
  435          stmt.condition = cast(stmt.condition, ns.boolType)
  436        else
  437          err("While condition must be Bool, not '$stmt.condition.ctype'", stmt.condition.location)
  438      }
  439    }
  440  
  441    private Void checkBreak(BreakStmt stmt)
  442    {
  443      if (stmt.loop == null)
  444        err("Break outside of loop (break is implicit in switch)", stmt.location)
  445  
  446      // can't leave control of a finally block
  447      if (finallyDepth > 0)
  448        err("Cannot leave finally block", stmt.location)
  449    }
  450  
  451    private Void checkContinue(ContinueStmt stmt)
  452    {
  453      if (stmt.loop == null)
  454        err("Continue outside of loop", stmt.location)
  455  
  456      // can't leave control of a finally block
  457      if (finallyDepth > 0)
  458        err("Cannot leave finally block", stmt.location)
  459    }
  460  
  461    private Void checkReturn(ReturnStmt stmt)
  462    {
  463      ret := curMethod.ret
  464      if (stmt.expr == null)
  465      {
  466        // this is just a sanity check - it should be caught in parser
  467        if (!ret.isVoid)
  468          err("Must return a value from non-Void method", stmt.location)
  469      }
  470      else
  471      {
  472        if (!stmt.expr.fits(ret))
  473        {
  474          if (stmt.expr.ctype.isObj)
  475            stmt.expr = cast(stmt.expr, ret)
  476          else
  477            err("Cannot return '$stmt.expr.ctype' as '$ret'", stmt.expr.location)
  478        }
  479      }
  480  
  481      // can't leave control of a finally block
  482      if (finallyDepth > 0)
  483        err("Cannot leave finally block", stmt.location)
  484  
  485      // add temp local var if returning from a protected region,
  486      // we always call this variable "$return" and reuse it if
  487      // already declared by a previous return
  488      if (stmt.expr != null && protectedRegionDepth > 0)
  489      {
  490        v := curMethod.vars.find |MethodVar v->Bool| { return v.name == "\$return" }
  491        if (v == null) v = curMethod.addLocalVar(stmt.expr.ctype, "\$return", null)
  492        stmt.leaveVar = v
  493      }
  494    }
  495  
  496    private Void checkTry(TryStmt stmt)
  497    {
  498      caught := CType[,]
  499      stmt.catches.each |Catch c|
  500      {
  501        CType errType := c.errType
  502        if (errType == null) errType = ns.errType
  503        if (!errType.fits(ns.errType))
  504          err("Must catch Err, not '$c.errType'", c.errType.location)
  505        else if (errType.fitsAny(caught))
  506          err("Already caught '$errType'", c.location)
  507        caught.add(errType)
  508      }
  509    }
  510  
  511    private Void checkSwitch(SwitchStmt stmt)
  512    {
  513      dups := Int:Int[:]
  514  
  515      stmt.cases.each |Case c|
  516      {
  517        for (i:=0; i<c.cases.size; ++i)
  518        {
  519          expr := c.cases[i]
  520  
  521          // check comparability of condition and each case
  522          checkCompare(expr, stmt.condition)
  523  
  524          // check for dups
  525          literal := expr.asTableSwitchCase
  526          if (literal != null)
  527          {
  528            if (dups[literal] == null)
  529              dups[literal] = literal
  530            else
  531              err("Duplicate case label", expr.location)
  532          }
  533        }
  534      }
  535    }
  536  
  537  //////////////////////////////////////////////////////////////////////////
  538  // Expr
  539  //////////////////////////////////////////////////////////////////////////
  540  
  541    override Expr visitExpr(Expr expr)
  542    {
  543      switch (expr.id)
  544      {
  545        case ExprId.rangeLiteral:   checkRangeLiteral((RangeLiteralExpr)expr)
  546        case ExprId.simpleLiteral:  checkSimpleLiteral((SimpleLiteralExpr)expr)
  547        case ExprId.boolNot:        checkBool((UnaryExpr)expr)
  548        case ExprId.assign:         checkAssign((BinaryExpr)expr)
  549        case ExprId.boolOr:
  550        case ExprId.boolAnd:        checkBools((CondExpr)expr)
  551        case ExprId.same:
  552        case ExprId.notSame:        checkSame((BinaryExpr)expr)
  553        case ExprId.shortcut:       checkShortcut((ShortcutExpr)expr)
  554        case ExprId.call:           checkCall((CallExpr)expr)
  555        case ExprId.field:          checkField((FieldExpr)expr)
  556        case ExprId.thisExpr:       checkThis((ThisExpr)expr)
  557        case ExprId.superExpr:      checkSuper((SuperExpr)expr)
  558        case ExprId.isExpr:
  559        case ExprId.asExpr:
  560        case ExprId.cast:           checkTypeCheck((TypeCheckExpr)expr)
  561        case ExprId.ternary:        checkTernary((TernaryExpr)expr)
  562        case ExprId.withBlock:      checkWithBlock((WithBlockExpr)expr)
  563      }
  564      return expr
  565    }
  566  
  567    private Void checkRangeLiteral(RangeLiteralExpr range)
  568    {
  569      if (!range.start.ctype.isInt || !range.end.ctype.isInt)
  570        err("Range must be Int..Int, not '${range.start.ctype}..${range.end.ctype}'", range.location)
  571    }
  572  
  573    private Void checkSimpleLiteral(SimpleLiteralExpr simple)
  574    {
  575      t := simple.ctype
  576      m := simple.method = t.method("fromStr")
  577  
  578      // resolve fromStr method
  579      if (m == null)
  580      {
  581        err("Simple type '$t' missing 'fromStr' method", simple.location)
  582        return
  583      }
  584  
  585      // check fromStr is static
  586      if (!m.isStatic)
  587        err("Simple type '${t}.fromStr' not static method", simple.location)
  588  
  589      // check fromStr return
  590      if (m.returnType != t)
  591        err("Simple type '${t}.fromStr' returns wrong type", simple.location)
  592  
  593      // check fromStr parameters
  594      if (m.params.size < 1)
  595      {
  596        err("Simple type '${t}.fromStr' not enough parameters", simple.location)
  597      }
  598      else
  599      {
  600        if (!m.params[0].paramType.isStr)
  601          err("Simple type '${t}.fromStr' first parameter not Str", simple.location)
  602        if (m.params.size > 1 && !m.params[1].hasDefault)
  603          err("Simple type '${t}.fromStr' too many parameters", simple.location)
  604      }
  605  
  606      // check argument is a string
  607      argType := simple.arg.ctype
  608      if (!argType.isStr)
  609        err("Simple literal requires 'Str' argument, not '$argType'", simple.arg.location)
  610    }
  611  
  612    private Void checkBool(UnaryExpr expr)
  613    {
  614      operand := expr.operand.ctype
  615      if (!operand.isBool)
  616      {
  617        if (operand.isObj)
  618          expr.operand = cast(expr.operand, ns.boolType)
  619        else
  620          err("Cannot apply '$expr.opToken.symbol' operator to '$operand'", expr.location)
  621      }
  622    }
  623  
  624    private Void checkBools(CondExpr expr)
  625    {
  626      expr.operands.each |Expr operand, Int i|
  627      {
  628        if (!operand.ctype.isBool)
  629        {
  630          if (operand.ctype.isObj)
  631            expr.operands[i] = cast(operand, ns.boolType)
  632          else
  633            err("Cannot apply '$expr.opToken.symbol' operator to '$operand.ctype'", operand.location)
  634        }
  635      }
  636    }
  637  
  638    private Void checkSame(BinaryExpr expr)
  639    {
  640      checkCompare(expr.lhs, expr.rhs)
  641    }
  642  
  643    private Void checkCompare(Expr lhs, Expr rhs)
  644    {
  645      if (!lhs.fits(rhs.ctype) && !rhs.fits(lhs.ctype))
  646        err("Incomparable types '$lhs.ctype' and '$rhs.ctype'", lhs.location)
  647    }
  648  
  649    private Void checkAssign(BinaryExpr expr)
  650    {
  651      // check that rhs is assignable to lhs
  652      if (!expr.rhs.fits(expr.lhs.ctype))
  653      {
  654        if (expr.rhs.ctype.isObj)
  655          expr.rhs = cast(expr.rhs, expr.lhs.ctype)
  656        else
  657          err("'$expr.rhs.ctype' is not assignable to '$expr.lhs.ctype'", expr.rhs.location)
  658      }
  659  
  660      // check that lhs is assignable
  661      if (!expr.lhs.isAssignable)
  662        err("Left hand side is not assignable", expr.lhs.location)
  663  
  664      // check left hand side field (common code with checkShortcut)
  665      if (expr.lhs.id === ExprId.field)
  666        checkAssignField((FieldExpr)expr.lhs, expr.rhs)
  667  
  668      // take this opportunity to generate a temp local variable if needed
  669      if (expr.leave && expr.lhs.assignRequiresTempVar)
  670        expr.tempVar = curMethod.addLocalVar(expr.ctype, null, null)
  671    }
  672  
  673    private Void checkShortcut(ShortcutExpr shortcut)
  674    {
  675      switch (shortcut.opToken)
  676      {
  677        // comparable
  678        case Token.eq:
  679        case Token.notEq:
  680          checkCompare(shortcut.target, shortcut.args.first)
  681      }
  682  
  683      // if assignment
  684      if (shortcut.isAssign)
  685      {
  686        // check that lhs is assignable
  687        if (!shortcut.target.isAssignable)
  688          err("Target is not assignable", shortcut.target.location)
  689  
  690        // check left hand side field (common code with checkAssign)
  691        if (shortcut.target.id === ExprId.field)
  692          checkAssignField((FieldExpr)shortcut.target, shortcut.args.first)
  693      }
  694  
  695      // take this oppotunity to generate a temp local variable if needed
  696      if (shortcut.leave && shortcut.isAssign && shortcut.target.assignRequiresTempVar)
  697        shortcut.tempVar = curMethod.addLocalVar(shortcut.ctype, null, null)
  698  
  699      // perform normal call checking
  700      checkCall(shortcut)
  701    }
  702  
  703    private Void checkAssignField(FieldExpr lhs, Expr rhs)
  704    {
  705      field := ((FieldExpr)lhs).field
  706  
  707      // check protection scope (which might be more narrow than the scope
  708      // of the entire field as checked in checkProtection by checkField)
  709      if (field.setter != null && slotProtectionErr(field) == null)
  710        checkSlotProtection(field.setter, lhs.location, true)
  711  
  712      // if not-const we are done
  713      if (!field.isConst) return
  714  
  715      // check attempt to set field outside of owning class
  716      if (field.parent != curType)
  717      {
  718        err("Cannot set const field '$field.qname'", lhs.location)
  719        return
  720      }
  721  
  722      // check attempt to set static field outside of static initializer
  723      if (field.isStatic && !curMethod.isStaticInit)
  724      {
  725        err("Cannot set const static field '$field.name' outside of static initializer", lhs.location)
  726        return
  727      }
  728  
  729      // check attempt to set instance field outside of ctor
  730      if (!field.isStatic && !(curMethod.isInstanceInit || curMethod.isCtor))
  731      {
  732        err("Cannot set const field '$field.name' outside of constructor", lhs.location)
  733        return
  734      }
  735  
  736      // if collection, verify that rhs assignment is call to toImmutable()
  737      ftype := field.fieldType
  738      if (ftype.fits(ns.listType) || ftype.fits(ns.mapType) && rhs != null)
  739      {
  740        if (rhs.id != ExprId.call || rhs->method->qname != "sys::${ftype.name}.toImmutable")
  741          err("Must call ${ftype.name}.toImmutable() when setting const field '$field.name'", rhs.location)
  742      }
  743    }
  744  
  745    private Void checkCall(CallExpr call)
  746    {
  747      m := call.method
  748      if (m == null)
  749      {
  750        err("Something wrong with method call?", call.location)
  751        return
  752      }
  753  
  754      name := m.name
  755  
  756      // check protection scope
  757      checkSlotProtection(call.method, call.location)
  758  
  759      // check arguments
  760      if (!call.isDynamic) checkArgs(call)
  761  
  762      // if constructor
  763      if (m.isCtor && !call.isCtorChain)
  764      {
  765        // ensure we aren't calling constructors on an instance
  766        if (call.target != null && call.target.id !== ExprId.staticTarget)
  767          err("Cannot call constructor '$name' on instance", call.location)
  768  
  769        // ensure we aren't calling a constructor on an abstract class
  770        if (m.parent.isAbstract)
  771          err("Calling constructor on abstract class", call.location)
  772      }
  773  
  774      // ensure we aren't calling static methods on an instance
  775      if (m.isStatic)
  776      {
  777        if (call.target != null && call.target.id !== ExprId.staticTarget)
  778          err("Cannot call static method '$name' on instance", call.location)
  779      }
  780  
  781      // ensure we can't calling an instance method statically
  782      if (!m.isStatic && !m.isCtor)
  783      {
  784        if (call.target == null || call.target.id === ExprId.staticTarget)
  785          err("Cannot call instance method '$name' in static context", call.location)
  786      }
  787  
  788      // if using super check that concrete
  789      if (call.target != null && call.target.id === ExprId.superExpr)
  790      {
  791        if (m.isAbstract)
  792          err("Cannot use super to call abstract method '$m.qname'", call.target.location)
  793      }
  794    }
  795  
  796    private Void checkField(FieldExpr f)
  797    {
  798      field := f.field
  799  
  800      // check protection scope
  801      checkSlotProtection(field, f.location)
  802  
  803      // ensure we aren't calling static methods on an instance
  804      if (field.isStatic)
  805      {
  806        if (f.target != null && f.target.id !== ExprId.staticTarget)
  807          err("Cannot access static field '$f.name' on instance", f.location)
  808      }
  809  
  810      // if instance field
  811      else
  812      {
  813        if (f.target == null || f.target.id === ExprId.staticTarget)
  814          err("Cannot access instance field '$f.name' in static context", f.location)
  815      }
  816  
  817      // if using super check that concrete
  818      if (f.target != null && f.target.id === ExprId.superExpr)
  819      {
  820        if (field.isAbstract)
  821          err("Cannot use super to access abstract field '$field.qname'", f.target.location)
  822      }
  823  
  824      // if using the field's accessor method
  825      if (f.useAccessor)
  826      {
  827        // check if we can optimize out the accessor (required for constants)
  828        f.useAccessor = useFieldAccessor(field)
  829  
  830        // check that we aren't using an field accessor inside of itself
  831        if (curMethod != null && (field.getter === curMethod || field.setter === curMethod))
  832          err("Cannot use field accessor inside accessor itself - use '@' operator", f.location)
  833      }
  834  
  835      // if accessing storage directly
  836      else
  837      {
  838        // check that the current class gets access to direct
  839        // field storage (only defining class gets it); allow closures
  840        // same scope priviledges as enclosing class
  841        enclosing := curType.isClosure ? curType.closure.enclosingType : curType
  842        if (!field.isConst && field.parent != curType && field.parent != enclosing)
  843        {
  844          err("Field storage for '$field.qname' not accessible", f.location)
  845        }
  846  
  847        // sanity check that field has storage
  848        else if (!field.isStorage)
  849        {
  850          if (field is FieldDef && ((FieldDef)field).concreteBase != null)
  851            err("Field storage of inherited field '${field->concreteBase->qname}' not accessible (might try super)", f.location)
  852          else
  853            err("Invalid storage access of field '$field.qname' which doesn't have storage", f.location)
  854        }
  855      }
  856    }
  857  
  858    private Bool useFieldAccessor(CField f)
  859    {
  860      // if there is no getter, then use field directly (constants)
  861      if (f.getter == null) return false
  862  
  863      // always use accessor if field is imported from another
  864      // pod (in which case it isn't a def in my compilation unit)
  865      def := f as FieldDef
  866      if (def == null) return true
  867  
  868      // if virtual and/or override then always use accessor
  869      if (def.isVirtual || def.isOverride)
  870        return true
  871  
  872      // if the field has synthetic getter and setter, then
  873      // we can safely optimize internal field accessors to
  874      // use field directly
  875      if (!def.hasGet && !def.hasSet)
  876        return false
  877  
  878      // use accessor since there is a custom getter or setter
  879      return true
  880    }
  881  
  882    private Void checkThis(ThisExpr expr)
  883    {
  884      if (inStatic)
  885        err("Cannot access 'this' in static context", expr.location)
  886    }
  887  
  888    private Void checkSuper(SuperExpr expr)
  889    {
  890      if (inStatic)
  891        err("Cannot access 'super' in static context", expr.location)
  892  
  893      if (curType.isMixin)
  894      {
  895        if (expr.explicitType == null)
  896          err("Must use named 'super' inside mixin", expr.location)
  897        else if (!expr.explicitType.isMixin)
  898          err("Cannot use 'Obj.super' inside mixin (yeah I know - take it up with Sun)", expr.location)
  899      }
  900  
  901      if (expr.explicitType != null)
  902      {
  903        if (!curType.fits(expr.explicitType))
  904          err("Named super '$expr.explicitType' not a super class of '$curType.name'", expr.location)
  905      }
  906    }
  907  
  908    private Void checkTypeCheck(TypeCheckExpr expr)
  909    {
  910      check := expr.check
  911      target := expr.target.ctype
  912      if (!check.fits(target) && !target.fits(check))
  913        err("Inconvertible types '$target' and '$check'", expr.location)
  914    }
  915  
  916    private Void checkTernary(TernaryExpr expr)
  917    {
  918      if (!expr.condition.ctype.isBool)
  919      {
  920        if (expr.condition.ctype.isObj)
  921          expr.condition = cast(expr.condition, ns.boolType)
  922        else
  923          err("Ternary condition must be Bool, not '$expr.condition.ctype'", expr.condition.location)
  924      }
  925    }
  926  
  927    private Void checkWithBlock(WithBlockExpr expr)
  928    {
  929      expr.subs.each |Expr sub|
  930      {
  931        if (!sub.isStmt) err("Not a statement", sub.location)
  932      }
  933    }
  934  
  935  //////////////////////////////////////////////////////////////////////////
  936  // Check Args
  937  //////////////////////////////////////////////////////////////////////////
  938  
  939    private Void checkArgs(CallExpr call)
  940    {
  941      params := call.method.params
  942      name := call.name
  943      args := call.args
  944      isErr := false
  945  
  946      // if we are calling callx(A, B...) on a FuncType, then
  947      // use the first class Func signature rather than the
  948      // version of callx which got picked because we might have
  949      // picked the wrong callx version
  950      sig := call.method.parent as FuncType
  951      if (sig != null && name.startsWith("call") && name.size == 5)
  952      {
  953        if (sig.params.size != args.size)
  954        {
  955          isErr = true
  956        }
  957        else
  958        {
  959          sig.params.each |CType p, Int i|
  960          {
  961            arg := args[i]
  962            if (!arg.fits(p))
  963            {
  964              if (arg.ctype.isObj)
  965                args[i] = cast(arg, p)
  966              else
  967                isErr = true
  968            }
  969          }
  970        }
  971      }
  972  
  973      // if more args than params, always an err
  974      else if (params.size < args.size)
  975      {
  976        isErr = true
  977      }
  978  
  979      // check each arg against each parameter
  980      else
  981      {
  982        params.each |CParam p, Int i|
  983        {
  984          if (i >= args.size)
  985          {
  986            // param has a default value, then that is ok
  987            if (!p.hasDefault) isErr = true
  988          }
  989          else
  990          {
  991            // ensure arg fits parameter type (or auto-cast)
  992            arg := args[i]
  993            if (!arg.fits(p.paramType))
  994            {
  995              if (arg.ctype.isObj)
  996                args[i] = cast(arg, p.paramType)
  997              else
  998                isErr = true
  999            }
 1000          }
 1001        }
 1002      }
 1003  
 1004      if (!isErr) return
 1005  
 1006      msg := "Invalid args "
 1007      if (sig != null)
 1008        msg += "|" + sig.params.join(", ") + "|"
 1009      else
 1010        msg += call.method.nameAndParamTypesToStr
 1011      msg += ", not (" + args.join(", ", |Expr e->Str| { return "$e.ctype" }) + ")"
 1012      err(msg, call.location)
 1013    }
 1014  
 1015  //////////////////////////////////////////////////////////////////////////
 1016  // Flag Utils
 1017  //////////////////////////////////////////////////////////////////////////
 1018  
 1019    private Void checkProtectionFlags(Int flags, Location loc)
 1020    {
 1021      isPublic    := flags & FConst.Public    != 0
 1022      isProtected := flags & FConst.Protected != 0
 1023      isPrivate   := flags & FConst.Private   != 0
 1024      isInternal  := flags & FConst.Internal  != 0
 1025      isVirtual   := flags & FConst.Virtual   != 0
 1026      isOverride  := flags & FConst.Override  != 0
 1027  
 1028      if (isPublic)
 1029      {
 1030        if (isProtected) err("Invalid combination of 'public' and 'protected' modifiers", loc)
 1031        if (isPrivate)   err("Invalid combination of 'public' and 'private' modifiers", loc)
 1032        if (isInternal)  err("Invalid combination of 'public' and 'internal' modifiers", loc)
 1033      }
 1034      else if (isProtected)
 1035      {
 1036        if (isPrivate)   err("Invalid combination of 'protected' and 'private' modifiers", loc)
 1037        if (isInternal)  err("Invalid combination of 'protected' and 'internal' modifiers", loc)
 1038      }
 1039      else if (isPrivate)
 1040      {
 1041        if (isInternal)  err("Invalid combination of 'private' and 'internal' modifiers", loc)
 1042        if (isVirtual && !isOverride) err("Invalid combination of 'private' and 'virtual' modifiers", loc)
 1043      }
 1044    }
 1045  
 1046    private Void checkSlotProtection(CSlot slot, Location loc, Bool setter := false)
 1047    {
 1048      errMsg := slotProtectionErr(slot, setter)
 1049      if (errMsg != null) err(errMsg, loc)
 1050    }
 1051  
 1052    private Str slotProtectionErr(CSlot slot, Bool setter := false)
 1053    {
 1054      msg := setter ? "setter of field" : (slot is CMethod ? "method" : "field")
 1055  
 1056      // short circuit if method on myself
 1057      if (curType == slot.parent)
 1058        return null
 1059  
 1060      // allow closures same scope priviledges as enclosing class
 1061      myType := curType
 1062      if (myType.isClosure)
 1063        myType = curType.closure.enclosingType
 1064  
 1065      if (slot.isPrivate && myType != slot.parent)
 1066        return "Private $msg '$slot.qname' not accessible"
 1067  
 1068      else if (slot.isProtected && !myType.fits(slot.parent))
 1069        return "Protected $msg '$slot.qname' not accessible"
 1070  
 1071      else if (slot.isInternal && myType.pod != slot.parent.pod)
 1072        return "Internal $msg '$slot.qname' not accessible"
 1073  
 1074      else
 1075        return null
 1076    }
 1077  
 1078  //////////////////////////////////////////////////////////////////////////
 1079  // Utils
 1080  //////////////////////////////////////////////////////////////////////////
 1081  
 1082    private static Expr cast(Expr target, CType to)
 1083    {
 1084      return TypeCheckExpr.cast(target, to)
 1085    }
 1086  
 1087  //////////////////////////////////////////////////////////////////////////
 1088  // Fields
 1089  //////////////////////////////////////////////////////////////////////////
 1090  
 1091    private Int protectedRegionDepth := 0  // try statement depth
 1092    private Int finallyDepth := 0          // finally block depth
 1093  }