Fan

 

class

compiler::ConstChecks

sys::Obj
  compiler::CompilerSupport
    compiler::CompilerStep
      compiler::ConstChecks
//
// Copyright (c) 2009, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//    9 Apr 09  Brian Frank  Creation
//

**
** ConstChecks adds hooks into constructors and it-blocks
** to ensure that an attempt to set a const field will throw
** ConstErr if not in the objects constructor.
**
** For each it-block which sets const fields:
**
**   doCall(Foo it)
**   {
**     this.checkInCtor(it)
**     ...
**   }
**
** For each constructor which takes an it-block:
**
**   new make(..., |This| f)
**   {
**     f?.enterCtor(this)
**     ...
**     f?.exitCtor()  // for every return
**     return
**   }
**
**
class ConstChecks : CompilerStep
{

//////////////////////////////////////////////////////////////////////////
// Constructor
//////////////////////////////////////////////////////////////////////////

  new make(Compiler compiler)
    : super(compiler)
  {
  }

//////////////////////////////////////////////////////////////////////////
// Run
//////////////////////////////////////////////////////////////////////////

  override Void run()
  {
    log.debug("ConstChecks")

    // walk all the closures
    compiler.closures.each |ClosureExpr c| { processClosure(c) }

    // walk all the constructors
    types.each |TypeDef t|
    {
      t.ctorDefs.each |MethodDef ctor| { processCtor(ctor) }
    }
  }

//////////////////////////////////////////////////////////////////////////
// Process Closure
//////////////////////////////////////////////////////////////////////////

  private Void processClosure(ClosureExpr c)
  {
    // don't process anything but it-blocks which use const fields
    if (!c.isItBlock || !c.setsConst) return

    // add inCtor check
    loc := c.location
    check := CallExpr.makeWithMethod(loc, ThisExpr(loc), ns.funcCheckInCtor, [ItExpr(loc)])
    check.noLeave
    c.doCall.code.stmts.insert(0, check.toStmt)
  }

//////////////////////////////////////////////////////////////////////////
// Process Constructor
//////////////////////////////////////////////////////////////////////////

  private Void processCtor(MethodDef ctor)
  {
    // only process constructors with an it-block as last arg
    if (ctor.params.isEmpty) return
    lastArg := ctor.params.last.paramType.deref.toNonNullable as FuncType
    if (lastArg == null || lastArg.params.size != 1) return
    this.curCtor = ctor

    // add enterCtor
    loc := ctor.location
    enter := CallExpr.makeWithMethod(loc, LocalVarExpr(loc, itBlockVar), ns.funcEnterCtor, [ThisExpr(loc)])
    enter.isSafe = true
    enter.noLeave
    ctor.code.stmts.insert(0, enter.toStmt)

    // walk all the statements and insert exitCtor before each return
    ctor.code.walk(this, VisitDepth.stmt)
  }

  override Stmt[]? visitStmt(Stmt stmt)
  {
    if (stmt.id !== StmtId.returnStmt) return null
    loc := stmt.location
    exit := CallExpr.makeWithMethod(loc, LocalVarExpr(loc, itBlockVar), ns.funcExitCtor)
    exit.isSafe = true
    exit.noLeave
    return [exit.toStmt, stmt]
  }

  private MethodVar itBlockVar() { curCtor.vars[curCtor.params.size-1] }

//////////////////////////////////////////////////////////////////////////
// Fields
//////////////////////////////////////////////////////////////////////////

  MethodDef? curCtor
}