//
// Copyright (c) 2009, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
// 7 Sep 09 Brian Frank Creation
//
**
** ClosureToImmutable processes each closure to determine
** its immutability. At this point, all the enclosed variables
** have been mapped to fields by ClosureVars. So we have
** three cases:
**
** 1. If every field is known const, then the function is
** always immutable, and we can just override isImmutable
** to return true.
**
** 2. If any field is known to never be const, then the function
** can never be immutable, and we just use Func defaults for
** isImmutable and toImmutable.
**
** 3. In the last case we have fields like Obj or List which require
** us calling toImmutable. In this case we generate a toImmutable
** method which constructs a new closure instance by calling
** toImmutable on each field.
**
**
class ClosureToImmutable : CompilerStep
{
//////////////////////////////////////////////////////////////////////////
// Constructor
//////////////////////////////////////////////////////////////////////////
new make(Compiler compiler) : super(compiler) {}
//////////////////////////////////////////////////////////////////////////
// Run
//////////////////////////////////////////////////////////////////////////
override Void run()
{
compiler.closures.each |c| { process(c) }
}
private Void process(ClosureExpr closure)
{
cls := closure.cls
// if always immutable, then override isImmutable
// to be true and set all the fields to be const;
// Func.toImmutable will do the right thing for us
if (isAlwaysImmutable(cls))
{
genIsImmutable(cls, LiteralExpr.makeTrue(cls.loc, ns))
setAllFieldsConst(cls)
return
}
// if never immutable then we inherit default toImmutable false
// and generate a toImmutable which raises NotImmutableErr with
// a meaningful error message
never := isNeverImmutable(cls)
if (never != null)
{
genToImmutableErr(cls, never)
return
}
// if we have made it here we are neither always immutable
// or never immutable - we could be immutable, but we have
// to call toImmutable on each of our fields
genToImmutable(cls)
}
**
** Are all the fields known to be const types?
**
Bool isAlwaysImmutable(TypeDef cls)
{
cls.fieldDefs.all |f| { f.fieldType.isConst }
}
**
** Are any of the fields known to never be immutable?
** If any field is not immutable, then return meaningful error message.
**
Str? isNeverImmutable(TypeDef cls)
{
field := cls.fieldDefs.find |f| { !f.fieldType.isConstFieldType }
if (field == null) return null
return "Closure field not const: " + (field.closureInfo ?: field.name)
}
**
** Set const flag on every field def.
**
Void setAllFieldsConst(TypeDef cls)
{
cls.fieldDefs.each |f| { f.flags = f.flags.or(FConst.Const) }
}
**
** Generate: 'isImmutable() { return result }'
**
private Void genIsImmutable(TypeDef cls, Expr result)
{
loc := cls.loc
m := MethodDef(loc, cls)
m.flags = FConst.Public + FConst.Synthetic + FConst.Override
m.name = "isImmutable"
m.ret = ns.boolType
m.code = Block(loc)
m.code.stmts.add(ReturnStmt.makeSynthetic(loc, result))
cls.addSlot(m)
}
**
** Generate toImmutable which raises an error with a nice error
** message as to why the function is not immutable.
**
** Obj toImmutable()
** {
** throw NotImmutableErr.make(msg);
** }
**
private Void genToImmutableErr(TypeDef cls, Str msg)
{
loc := cls.loc
m := stubToImmutable(cls)
ctor := CallExpr.makeWithMethod(loc, null, ns.notImmutableErrMake, [LiteralExpr.makeStr(loc, ns, msg)])
m.code.add(ThrowStmt(loc, ctor))
}
**
** Generate toImmutable by attempting to construct a copy
** of this closure with toImmutable called on every field
** along with a flag to keep track of which state we are in.
**
** Obj toImmutable()
** {
** r := make( (T1)f1.toImmutable, ... )
** r.isImmutable$ = true
** return true
** }
**
** Bool isImmutable() { immutable }
**
** private Bool immutable
**
private Void genToImmutable(TypeDef cls)
{
loc := cls.loc
// Bool immutable
immutableField := FieldDef(loc, cls)
immutableField.name = "immutable"
immutableField.fieldType = ns.boolType
immutableField.flags = FConst.Private + FConst.Storage + FConst.Synthetic
cls.addSlot(immutableField)
// Bool isImmutable() { immutable }
genIsImmutable(cls, FieldExpr(loc, ThisExpr(loc, cls), immutableField, false))
// Obj toImmutable()
m := stubToImmutable(cls)
// make( (T1)f1.toImmutable, ... )
ctor := cls.method("make")
args := Expr[,]
ctor.params.each |param|
{
field := cls.field(param.name)
if (field == null) throw Err("Closure param missing matched field $param.name")
fieldGet := FieldExpr(loc, ThisExpr(loc, cls), field, false)
if (field.fieldType.isConst)
{
args.add(fieldGet)
}
else
{
call := CallExpr.makeWithMethod(loc, fieldGet, ns.objToImmutable)
args.add(TypeCheckExpr.coerce(call, field.fieldType))
}
}
makeCall := CallExpr.makeWithMethod(loc, null, ctor, args)
// temp = make
temp := m.addLocalVar(cls, "temp", m.code)
m.code.add(BinaryExpr.makeAssign(
LocalVarExpr(loc, temp),
makeCall).toStmt)
// temp.immutable = true
m.code.add(BinaryExpr.makeAssign(
FieldExpr(loc, LocalVarExpr(loc, temp), immutableField, false),
LiteralExpr.makeTrue(loc, ns)).toStmt)
// return temp
m.code.add(ReturnStmt.makeSynthetic(loc, LocalVarExpr(loc, temp)))
}
** Stub the 'Obj toImmutable()' method
private MethodDef stubToImmutable(TypeDef cls)
{
loc := cls.loc
m := MethodDef(loc, cls)
m.flags = FConst.Public + FConst.Synthetic + FConst.Override
m.name = "toImmutable"
m.ret = ns.objType
m.code = Block(loc)
cls.addSlot(m)
return m
}
}