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.verbose("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 (is ListType)
  238      {
  239        list := t as ListType
  240        return isConstFieldType(list.v)
  241      }
  242  
  243      if (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        if (seenDef)
  320        {
  321          if (p.def == null)
  322            err("Parameter '$p.name' must have default", p.location)
  323        }
  324        else
  325        {
  326          seenDef = p.def != null
  327        }
  328      }
  329    }
  330  
  331    private Void checkCtor(MethodDef m)
  332    {
  333      // mixins cannot have constructors
  334      if (curType.isMixin)
  335        err("Mixins cannot have constructors", m.location)
  336  
  337      // ensure super/this constructor is called
  338      if (m.ctorChain == null && !compiler.isSys && !curType.base.isObj && !curType.isSynthetic)
  339        err("Must call super class constructor in '$m.name'", m.location)
  340    }
  341  
  342  //////////////////////////////////////////////////////////////////////////
  343  // Statements
  344  //////////////////////////////////////////////////////////////////////////
  345  
  346    override Void enterStmt(Stmt stmt)
  347    {
  348      if (stmt.id == StmtId.tryStmt) protectedRegionDepth++
  349    }
  350  
  351    override Void exitStmt(Stmt stmt)
  352    {
  353      if (stmt.id == StmtId.tryStmt) protectedRegionDepth--
  354    }
  355  
  356    override Void enterFinally(TryStmt stmt)
  357    {
  358      finallyDepth++
  359    }
  360  
  361    override Void exitFinally(TryStmt stmt)
  362    {
  363      finallyDepth--
  364    }
  365  
  366    override Void visitStmt(Stmt stmt)
  367    {
  368      switch (stmt.id)
  369      {
  370        case StmtId.expr:          checkExprStmt((ExprStmt)stmt)
  371        case StmtId.ifStmt:        checkIf((IfStmt)stmt)
  372        case StmtId.returnStmt:    checkReturn((ReturnStmt)stmt)
  373        case StmtId.throwStmt:     checkThrow((ThrowStmt)stmt)
  374        case StmtId.forStmt:       checkFor((ForStmt)stmt)
  375        case StmtId.whileStmt:     checkWhile((WhileStmt)stmt)
  376        case StmtId.breakStmt:     checkBreak((BreakStmt)stmt)
  377        case StmtId.continueStmt:  checkContinue((ContinueStmt)stmt)
  378        case StmtId.tryStmt:       checkTry((TryStmt)stmt)
  379        case StmtId.switchStmt:    checkSwitch((SwitchStmt)stmt)
  380      }
  381    }
  382  
  383    private Void checkExprStmt(ExprStmt stmt)
  384    {
  385      if (!stmt.expr.isStmt)
  386        err("Not a statement", stmt.expr.location)
  387    }
  388  
  389    private Void checkIf(IfStmt stmt)
  390    {
  391      if (!stmt.condition.ctype.isBool)
  392      {
  393        if (stmt.condition.ctype.isObj)
  394          stmt.condition = cast(stmt.condition, ns.boolType)
  395        else
  396          err("If condition must be Bool, not '$stmt.condition.ctype'", stmt.condition.location)
  397      }
  398    }
  399  
  400    private Void checkThrow(ThrowStmt stmt)
  401    {
  402      if (!stmt.exception.fits(ns.errType))
  403      {
  404        if (stmt.exception.ctype.isObj)
  405          stmt.exception = cast(stmt.exception, ns.errType)
  406        else
  407          err("Must throw Err, not '$stmt.exception.ctype'", stmt.exception.location)
  408      }
  409    }
  410  
  411    private Void checkFor(ForStmt stmt)
  412    {
  413      if (stmt.condition != null && !stmt.condition.ctype.isBool)
  414      {
  415        if (stmt.condition.ctype.isObj)
  416          stmt.condition = cast(stmt.condition, ns.boolType)
  417        else
  418          err("For condition must be Bool, not '$stmt.condition.ctype'", stmt.condition.location)
  419      }
  420    }
  421  
  422    private Void checkWhile(WhileStmt stmt)
  423    {
  424      if (!stmt.condition.ctype.isBool)
  425      {
  426        if (stmt.condition.ctype.isObj)
  427          stmt.condition = cast(stmt.condition, ns.boolType)
  428        else
  429          err("While condition must be Bool, not '$stmt.condition.ctype'", stmt.condition.location)
  430      }
  431    }
  432  
  433    private Void checkBreak(BreakStmt stmt)
  434    {
  435      if (stmt.loop == null)
  436        err("Break outside of loop (break is implicit in switch)", stmt.location)
  437  
  438      // can't leave control of a finally block
  439      if (finallyDepth > 0)
  440        err("Cannot leave finally block", stmt.location)
  441    }
  442  
  443    private Void checkContinue(ContinueStmt stmt)
  444    {
  445      if (stmt.loop == null)
  446        err("Continue outside of loop", stmt.location)
  447  
  448      // can't leave control of a finally block
  449      if (finallyDepth > 0)
  450        err("Cannot leave finally block", stmt.location)
  451    }
  452  
  453    private Void checkReturn(ReturnStmt stmt)
  454    {
  455      ret := curMethod.ret
  456      if (stmt.expr == null)
  457      {
  458        // this is just a sanity check - it should be caught in parser
  459        if (!ret.isVoid)
  460          err("Must return a value from non-Void method", stmt.location)
  461      }
  462      else
  463      {
  464        if (!stmt.expr.fits(ret))
  465        {
  466          if (stmt.expr.ctype.isObj)
  467            stmt.expr = cast(stmt.expr, ret)
  468          else
  469            err("Cannot return '$stmt.expr.ctype' as '$ret'", stmt.expr.location)
  470        }
  471      }
  472  
  473      // can't leave control of a finally block
  474      if (finallyDepth > 0)
  475        err("Cannot leave finally block", stmt.location)
  476  
  477      // add temp local var if returning from a protected region,
  478      // we always call this variable "$return" and reuse it if
  479      // already declared by a previous return
  480      if (stmt.expr != null && protectedRegionDepth > 0)
  481      {
  482        v := curMethod.vars.find |MethodVar v->Bool| { return v.name == "\$return" }
  483        if (v == null) v = curMethod.addLocalVar(stmt.expr.ctype, "\$return"null)
  484        stmt.leaveVar = v
  485      }
  486    }
  487  
  488    private Void checkTry(TryStmt stmt)
  489    {
  490      caught := CType[,]
  491      stmt.catches.each |Catch c|
  492      {
  493        CType errType := c.errType
  494        if (errType == null) errType = ns.errType
  495        if (!errType.fits(ns.errType))
  496          err("Must catch Err, not '$c.errType'", c.errType.location)
  497        else if (errType.fitsAny(caught))
  498          err("Already caught '$errType'", c.location)
  499        caught.add(errType)
  500      }
  501    }
  502  
  503    private Void checkSwitch(SwitchStmt stmt)
  504    {
  505      dups := Int:Int[:]
  506  
  507      stmt.cases.each |Case c|
  508      {
  509        for (i:=0; i<c.cases.size; ++i)
  510        {
  511          expr := c.cases[i]
  512  
  513          // check comparability of condition and each case
  514          checkCompare(expr, stmt.condition)
  515  
  516          // check for dups
  517          literal := expr.asTableSwitchCase
  518          if (literal != null)
  519          {
  520            if (dups[literal] == null)
  521              dups[literal] = literal
  522            else
  523              err("Duplicate case label", expr.location)
  524          }
  525        }
  526      }
  527    }
  528  
  529  //////////////////////////////////////////////////////////////////////////
  530  // Expr
  531  //////////////////////////////////////////////////////////////////////////
  532  
  533    override Expr visitExpr(Expr expr)
  534    {
  535      switch (expr.id)
  536      {
  537        case ExprId.rangeLiteral:   checkRangeLiteral((RangeLiteralExpr)expr)
  538        case ExprId.simpleLiteral:  checkSimpleLiteral((SimpleLiteralExpr)expr)
  539        case ExprId.boolNot:        checkBool((UnaryExpr)expr)
  540        case ExprId.assign:         checkAssign((BinaryExpr)expr)
  541        case ExprId.boolOr:
  542        case ExprId.boolAnd:        checkBools((CondExpr)expr)
  543        case ExprId.same:
  544        case ExprId.notSame:        checkSame((BinaryExpr)expr)
  545        case ExprId.shortcut:       checkShortcut((ShortcutExpr)expr)
  546        case ExprId.call:           checkCall((CallExpr)expr)
  547        case ExprId.field:          checkField((FieldExpr)expr)
  548        case ExprId.thisExpr:       checkThis((ThisExpr)expr)
  549        case ExprId.superExpr:      checkSuper((SuperExpr)expr)
  550        case ExprId.isExpr:
  551        case ExprId.asExpr:
  552        case ExprId.cast:           checkTypeCheck((TypeCheckExpr)expr)
  553        case ExprId.ternary:        checkTernary((TernaryExpr)expr)
  554        case ExprId.withBlock:      checkWithBlock((WithBlockExpr)expr)
  555      }
  556      return expr
  557    }
  558  
  559    private Void checkRangeLiteral(RangeLiteralExpr range)
  560    {
  561      if (!range.start.ctype.isInt || !range.end.ctype.isInt)
  562        err("Range must be Int..Int, not '${range.start.ctype}..${range.end.ctype}'", range.location)
  563    }
  564  
  565    private Void checkSimpleLiteral(SimpleLiteralExpr simple)
  566    {
  567      t := simple.ctype
  568      m := simple.method = t.method("fromStr")
  569  
  570      // resolve fromStr method
  571      if (m == null)
  572      {
  573        err("Simple type '$t' missing 'fromStr' method", simple.location)
  574        return
  575      }
  576  
  577      // check fromStr is static
  578      if (!m.isStatic)
  579        err("Simple type '${t}.fromStr' not static method", simple.location)
  580  
  581      // check fromStr return
  582      if (m.returnType != t)
  583        err("Simple type '${t}.fromStr' returns wrong type", simple.location)
  584  
  585      // check fromStr parameters
  586      if (m.params.size < 1)
  587      {
  588        err("Simple type '${t}.fromStr' not enough parameters", simple.location)
  589      }
  590      else
  591      {
  592        if (!m.params[0].paramType.isStr)
  593          err("Simple type '${t}.fromStr' first parameter not Str", simple.location)
  594        if (m.params.size > 1 && !m.params[1].hasDefault)
  595          err("Simple type '${t}.fromStr' too many parameters", simple.location)
  596      }
  597  
  598      // check argument is a string
  599      argType := simple.arg.ctype
  600      if (!argType.isStr)
  601        err("Simple literal requires 'Str' argument, not '$argType'", simple.arg.location)
  602    }
  603  
  604    private Void checkBool(UnaryExpr expr)
  605    {
  606      operand := expr.operand.ctype
  607      if (!operand.isBool)
  608      {
  609        if (operand.isObj)
  610          expr.operand = cast(expr.operand, ns.boolType)
  611        else
  612          err("Cannot apply '$expr.opToken.symbol' operator to '$operand'", expr.location)
  613      }
  614    }
  615  
  616    private Void checkBools(CondExpr expr)
  617    {
  618      expr.operands.each |Expr operand, Int i|
  619      {
  620        if (!operand.ctype.isBool)
  621        {
  622          if (operand.ctype.isObj)
  623            expr.operands[i] = cast(operand, ns.boolType)
  624          else
  625            err("Cannot apply '$expr.opToken.symbol' operator to '$operand.ctype'", operand.location)
  626        }
  627      }
  628    }
  629  
  630    private Void checkSame(BinaryExpr expr)
  631    {
  632      checkCompare(expr.lhs, expr.rhs)
  633    }
  634  
  635    private Void checkCompare(Expr lhs, Expr rhs)
  636    {
  637      if (!lhs.fits(rhs.ctype) && !rhs.fits(lhs.ctype))
  638        err("Incomparable types '$lhs.ctype' and '$rhs.ctype'", lhs.location)
  639    }
  640  
  641    private Void checkAssign(BinaryExpr expr)
  642    {
  643      // check that rhs is assignable to lhs
  644      if (!expr.rhs.fits(expr.lhs.ctype))
  645      {
  646        if (expr.rhs.ctype.isObj)
  647          expr.rhs = cast(expr.rhs, expr.lhs.ctype)
  648        else
  649          err("'$expr.rhs.ctype' is not assignable to '$expr.lhs.ctype'", expr.rhs.location)
  650      }
  651  
  652      // check that lhs is assignable
  653      if (!expr.lhs.isAssignable)
  654        err("Left hand side is not assignable", expr.lhs.location)
  655  
  656      // check left hand side field (common code with checkShortcut)
  657      if (expr.lhs.id === ExprId.field)
  658        checkAssignField((FieldExpr)expr.lhs, expr.rhs)
  659  
  660      // take this opportunity to generate a temp local variable if needed
  661      if (expr.leave && expr.lhs.assignRequiresTempVar)
  662        expr.tempVar = curMethod.addLocalVar(expr.ctype, nullnull)
  663    }
  664  
  665    private Void checkShortcut(ShortcutExpr shortcut)
  666    {
  667      switch (shortcut.opToken)
  668      {
  669        // comparable
  670        case Token.eq:
  671        case Token.notEq:
  672          checkCompare(shortcut.target, shortcut.args.first)
  673      }
  674  
  675      // if assignment
  676      if (shortcut.isAssign)
  677      {
  678        // check that lhs is assignable
  679        if (!shortcut.target.isAssignable)
  680          err("Target is not assignable", shortcut.target.location)
  681  
  682        // check left hand side field (common code with checkAssign)
  683        if (shortcut.target.id === ExprId.field)
  684          checkAssignField((FieldExpr)shortcut.target, shortcut.args.first)
  685      }
  686  
  687      // take this oppotunity to generate a temp local variable if needed
  688      if (shortcut.leave && shortcut.isAssign && shortcut.target.assignRequiresTempVar)
  689        shortcut.tempVar = curMethod.addLocalVar(shortcut.ctype, nullnull)
  690  
  691      // perform normal call checking
  692      checkCall(shortcut)
  693    }
  694  
  695    private Void checkAssignField(FieldExpr lhs, Expr rhs)
  696    {
  697      field := ((FieldExpr)lhs).field
  698  
  699      // check protection scope (which might be more narrow than the scope
  700      // of the entire field as checked in checkProtection by checkField)
  701      if (field.setter != null && slotProtectionErr(field) == null)
  702        checkSlotProtection(field.setter, lhs.location, true)
  703  
  704      // if not-const we are done
  705      if (!field.isConst) return
  706  
  707      // check attempt to set field outside of owning class
  708      if (field.parent != curType)
  709      {
  710        err("Cannot set const field '$field.qname'", lhs.location)
  711        return
  712      }
  713  
  714      // check attempt to set static field outside of static initializer
  715      if (field.isStatic && !curMethod.isStaticInit)
  716      {
  717        err("Cannot set const static field '$field.name' outside of static initializer", lhs.location)
  718        return
  719      }
  720  
  721      // check attempt to set instance field outside of ctor
  722      if (!field.isStatic && !(curMethod.isInstanceInit || curMethod.isCtor))
  723      {
  724        err("Cannot set const field '$field.name' outside of constructor", lhs.location)
  725        return
  726      }
  727  
  728      // if collection, verify that rhs assignment is call to toImmutable()
  729      ftype := field.fieldType
  730      if (ftype.fits(ns.listType) || ftype.fits(ns.mapType) && rhs != null)
  731      {
  732        if (rhs.id != ExprId.call || rhs->method->qname != "sys::${ftype.name}.toImmutable")
  733          err("Must call ${ftype.name}.toImmutable() when setting const field '$field.name'", rhs.location)
  734      }
  735    }
  736  
  737    private Void checkCall(CallExpr call)
  738    {
  739      m := call.method
  740      if (m == null)
  741      {
  742        err("Something wrong with method call?", call.location)
  743        return
  744      }
  745  
  746      name := m.name
  747  
  748      // check protection scope
  749      checkSlotProtection(call.method, call.location)
  750  
  751      // check arguments
  752      if (!call.isDynamic) checkArgs(call)
  753  
  754      // if constructor
  755      if (m.isCtor && !call.isCtorChain)
  756      {
  757        // ensure we aren't calling constructors on an instance
  758        if (call.target != null && call.target.id !== ExprId.staticTarget)
  759          err("Cannot call constructor '$name' on instance", call.location)
  760  
  761        // ensure we aren't calling a constructor on an abstract class
  762        if (m.parent.isAbstract)
  763          err("Calling constructor on abstract class", call.location)
  764      }
  765  
  766      // ensure we aren't calling static methods on an instance
  767      if (m.isStatic)
  768      {
  769        if (call.target != null && call.target.id !== ExprId.staticTarget)
  770          err("Cannot call static method '$name' on instance", call.location)
  771      }
  772  
  773      // ensure we can't calling an instance method statically
  774      if (!m.isStatic && !m.isCtor)
  775      {
  776        if (call.target == null || call.target.id === ExprId.staticTarget)
  777          err("Cannot call instance method '$name' in static context", call.location)
  778      }
  779  
  780      // if using super check that concrete
  781      if (call.target != null && call.target.id === ExprId.superExpr)
  782      {
  783        if (m.isAbstract)
  784          err("Cannot use super to call abstract method '$m.qname'", call.target.location)
  785      }
  786    }
  787  
  788    private Void checkField(FieldExpr f)
  789    {
  790      field := f.field
  791  
  792      // check protection scope
  793      checkSlotProtection(field, f.location)
  794  
  795      // ensure we aren't calling static methods on an instance
  796      if (field.isStatic)
  797      {
  798        if (f.target != null && f.target.id !== ExprId.staticTarget)
  799          err("Cannot access static field '$f.name' on instance", f.location)
  800      }
  801  
  802      // if instance field
  803      else
  804      {
  805        if (f.target == null || f.target.id === ExprId.staticTarget)
  806          err("Cannot access instance field '$f.name' in static context", f.location)
  807      }
  808  
  809      // if using super check that concrete
  810      if (f.target != null && f.target.id === ExprId.superExpr)
  811      {
  812        if (field.isAbstract)
  813          err("Cannot use super to access abstract field '$field.qname'", f.target.location)
  814      }
  815  
  816      // if using the field's accessor method
  817      if (f.useAccessor)
  818      {
  819        // check if we can optimize out the accessor (required for constants)
  820        f.useAccessor = useFieldAccessor(field)
  821  
  822        // check that we aren't using an field accessor inside of itself
  823        if (curMethod != null && (field.getter === curMethod || field.setter === curMethod))
  824          err("Cannot use field accessor inside accessor itself - use '@' operator", f.location)
  825      }
  826  
  827      // if accessing storage directly
  828      else
  829      {
  830        // check that the current class gets access to direct
  831        // field storage (only defining class gets it); allow closures
  832        // same scope priviledges as enclosing class
  833        enclosing := curType.isClosure ? curType.closure.enclosingType : curType
  834        if (!field.isConst && field.parent != curType && field.parent != enclosing)
  835        {
  836          err("Field storage for '$field.qname' not accessible", f.location)
  837        }
  838  
  839        // sanity check that field has storage
  840        else if (!field.isStorage)
  841        {
  842          if (field is FieldDef && ((FieldDef)field).concreteBase != null)
  843            err("Field storage of inherited field '${field->concreteBase->qname}' not accessible (might try super)", f.location)
  844          else
  845            err("Invalid storage access of field '$field.qname' which doesn't have storage", f.location)
  846        }
  847      }
  848    }
  849  
  850    private Bool useFieldAccessor(CField f)
  851    {
  852      // if there is no getter, then use field directly (constants)
  853      if (f.getter == null) return false
  854  
  855      // always use accessor if field is imported from another
  856      // pod (in which case it isn't a def in my compilation unit)
  857      def := f as FieldDef
  858      if (def == null) return true
  859  
  860      // if virtual and/or override then always use accessor
  861      if (def.isVirtual || def.isOverride)
  862        return true
  863  
  864      // if the field has synthetic getter and setter, then
  865      // we can safely optimize internal field accessors to
  866      // use field directly
  867      if (!def.hasGet && !def.hasSet)
  868        return false
  869  
  870      // use accessor since there is a custom getter or setter
  871      return true
  872    }
  873  
  874    private Void checkThis(ThisExpr expr)
  875    {
  876      if (inStatic)
  877        err("Cannot access 'this' in static context", expr.location)
  878    }
  879  
  880    private Void checkSuper(SuperExpr expr)
  881    {
  882      if (inStatic)
  883        err("Cannot access 'super' in static context", expr.location)
  884  
  885      if (curType.isMixin)
  886      {
  887        if (expr.explicitType == null)
  888          err("Must use named 'super' inside mixin", expr.location)
  889        else if (!expr.explicitType.isMixin)
  890          err("Cannot use 'Obj.super' inside mixin (yeah I know - take it up with Sun)", expr.location)
  891      }
  892  
  893      if (expr.explicitType != null)
  894      {
  895        if (!curType.fits(expr.explicitType))
  896          err("Named super '$expr.explicitType' not a super class of '$curType.name'", expr.location)
  897      }
  898    }
  899  
  900    private Void checkTypeCheck(TypeCheckExpr expr)
  901    {
  902      check := expr.check
  903      target := expr.target.ctype
  904      if (!check.fits(target) && !target.fits(check))
  905        err("Inconvertible types '$target' and '$check'", expr.location)
  906    }
  907  
  908    private Void checkTernary(TernaryExpr expr)
  909    {
  910      if (!expr.condition.ctype.isBool)
  911      {
  912        if (expr.condition.ctype.isObj)
  913          expr.condition = cast(expr.condition, ns.boolType)
  914        else
  915          err("Ternary condition must be Bool, not '$expr.condition.ctype'", expr.condition.location)
  916      }
  917    }
  918  
  919    private Void checkWithBlock(WithBlockExpr expr)
  920    {
  921      expr.subs.each |Expr sub|
  922      {
  923        if (!sub.isStmt) err("Not a statement", sub.location)
  924      }
  925    }
  926  
  927  //////////////////////////////////////////////////////////////////////////
  928  // Check Args
  929  //////////////////////////////////////////////////////////////////////////
  930  
  931    private Void checkArgs(CallExpr call)
  932    {
  933      params := call.method.params
  934      name := call.name
  935      args := call.args
  936      isErr := false
  937  
  938      // if we are calling callx(A, B...) on a FuncType, then
  939      // use the first class Func signature rather than the
  940      // version of callx which got picked because we might have
  941      // picked the wrong callx version
  942      sig := call.method.parent as FuncType
  943      if (sig != null && name.startsWith("call") && name.size == 5)
  944      {
  945        if (sig.params.size != args.size)
  946        {
  947          isErr = true
  948        }
  949        else
  950        {
  951          sig.params.each |CType p, Int i|
  952          {
  953            arg := args[i]
  954            if (!arg.fits(p))
  955            {
  956              if (arg.ctype.isObj)
  957                args[i] = cast(arg, p)
  958              else
  959                isErr = true
  960            }
  961          }
  962        }
  963      }
  964  
  965      // if more args than params, always an err
  966      else if (params.size < args.size)
  967      {
  968        isErr = true
  969      }
  970  
  971      // check each arg against each parameter
  972      else
  973      {
  974        params.each |CParam p, Int i|
  975        {
  976          if (i >= args.size)
  977          {
  978            // param has a default value, then that is ok
  979            if (!p.hasDefault) isErr = true
  980          }
  981          else
  982          {
  983            // ensure arg fits parameter type (or auto-cast)
  984            arg := args[i]
  985            if (!arg.fits(p.paramType))
  986            {
  987              if (arg.ctype.isObj)
  988                args[i] = cast(arg, p.paramType)
  989              else
  990                isErr = true
  991            }
  992          }
  993        }
  994      }
  995  
  996      if (!isErr) return
  997  
  998      msg := "Invalid args "
  999      if (sig != null)
 1000        msg += "|" + sig.params.join(", ") + "|"
 1001      else
 1002        msg += call.method.nameAndParamTypesToStr
 1003      msg += ", not (" + args.join(", ", |Expr e->Str| { return "$e.ctype" }) + ")"
 1004      err(msg, call.location)
 1005    }
 1006  
 1007  //////////////////////////////////////////////////////////////////////////
 1008  // Flag Utils
 1009  //////////////////////////////////////////////////////////////////////////
 1010  
 1011    private Void checkProtectionFlags(Int flags, Location loc)
 1012    {
 1013      isPublic    := flags & FConst.Public    != 0
 1014      isProtected := flags & FConst.Protected != 0
 1015      isPrivate   := flags & FConst.Private   != 0
 1016      isInternal  := flags & FConst.Internal  != 0
 1017      isVirtual   := flags & FConst.Virtual   != 0
 1018      isOverride  := flags & FConst.Override  != 0
 1019  
 1020      if (isPublic)
 1021      {
 1022        if (isProtected) err("Invalid combination of 'public' and 'protected' modifiers", loc)
 1023        if (isPrivate)   err("Invalid combination of 'public' and 'private' modifiers", loc)
 1024        if (isInternal)  err("Invalid combination of 'public' and 'internal' modifiers", loc)
 1025      }
 1026      else if (isProtected)
 1027      {
 1028        if (isPrivate)   err("Invalid combination of 'protected' and 'private' modifiers", loc)
 1029        if (isInternal)  err("Invalid combination of 'protected' and 'internal' modifiers", loc)
 1030      }
 1031      else if (isPrivate)
 1032      {
 1033        if (isInternal)  err("Invalid combination of 'private' and 'internal' modifiers", loc)
 1034        if (isVirtual && !isOverride) err("Invalid combination of 'private' and 'virtual' modifiers", loc)
 1035      }
 1036    }
 1037  
 1038    private Void checkSlotProtection(CSlot slot, Location loc, Bool setter := false)
 1039    {
 1040      errMsg := slotProtectionErr(slot, setter)
 1041      if (errMsg != null) err(errMsg, loc)
 1042    }
 1043  
 1044    private Str slotProtectionErr(CSlot slot, Bool setter := false)
 1045    {
 1046      msg := setter ? "setter of field" : (slot is CMethod ? "method" : "field")
 1047  
 1048      // short circuit if method on myself
 1049      if (curType == slot.parent)
 1050        return null
 1051  
 1052      // allow closures same scope priviledges as enclosing class
 1053      myType := curType
 1054      if (myType.isClosure)
 1055        myType = curType.closure.enclosingType
 1056  
 1057      if (slot.isPrivate && myType != slot.parent)
 1058        return "Private $msg '$slot.qname' not accessible"
 1059  
 1060      else if (slot.isProtected && !myType.fits(slot.parent))
 1061        return "Protected $msg '$slot.qname' not accessible"
 1062  
 1063      else if (slot.isInternal && myType.pod != slot.parent.pod)
 1064        return "Internal $msg '$slot.qname' not accessible"
 1065  
 1066      else
 1067        return null
 1068    }
 1069  
 1070  //////////////////////////////////////////////////////////////////////////
 1071  // Utils
 1072  //////////////////////////////////////////////////////////////////////////
 1073  
 1074    private static Expr cast(Expr target, CType to)
 1075    {
 1076      return TypeCheckExpr.cast(target, to)
 1077    }
 1078  
 1079  //////////////////////////////////////////////////////////////////////////
 1080  // Fields
 1081  //////////////////////////////////////////////////////////////////////////
 1082  
 1083    private Int protectedRegionDepth := 0  // try statement depth
 1084    private Int finallyDepth := 0          // finally block depth
 1085  }