package freeboogie.tc;
import java.io.StringWriter;
import java.math.BigInteger;
import java.util.HashMap;
import freeboogie.ast.*;
import freeboogie.ast.utils.PrettyPrinter;
import freeboogie.util.Closure;
import freeboogie.util.Err;
/**
* Typechecks an AST. Errors are reported using the class {@code Err}.
* It maps expressions to types.
*
* It also acts more-or-less as a Facade for the whole package.
*
* @author rgrig
* @author reviewed by TODO
*/
@SuppressWarnings("unused") // many unused parameters
public class TypeChecker extends Evaluator<Type> {
// used for primitive types
private PrimitiveType boolType, intType, refType, nameType, anyType;
// used to signal an error in a subexpression.
// the content is the same as for anyType but it's a different reference
// (unique while typechecking)
private PrimitiveType errType;
private SymbolTable st;
private GlobalsCollector gc;
private BlockFlowGraphs flowGraphs;
// where there any type errors?
private boolean errors;
// maps expressions to their types
private HashMap<Expr, Type> typeOf;
// maps implementations to procedures
private UsageToDefMap<Implementation, Procedure> implProc;
// maps implementation params to procedure params
private UsageToDefMap<VariableDecl, VariableDecl> paramMap;
// Maps type variables to the real types.
// Gets set by the |check| functions.
private HashMap<String, Type> typeVar;
// to get unique names for the type variables
private int typeVarCnt;
private static final String TYPE_VAR_PREFIX = " tv";
// contains space so that it can't come from parsing
// === public interface ===
/**
* Typechecks an AST.
* @param ast the AST to check
* @return whether there were any errors while typechecking (or in earlier phases)
*/
public boolean process(Declaration ast) {
boolType = PrimitiveType.mk(PrimitiveType.Ptype.BOOL);
intType = PrimitiveType.mk(PrimitiveType.Ptype.INT);
refType = PrimitiveType.mk(PrimitiveType.Ptype.REF);
nameType = PrimitiveType.mk(PrimitiveType.Ptype.NAME);
anyType = PrimitiveType.mk(PrimitiveType.Ptype.ANY);
errType = PrimitiveType.mk(PrimitiveType.Ptype.ERROR);
typeOf = new HashMap<Expr, Type>();
typeVar = new HashMap<String, Type>();
errors = false;
typeVarCnt = 0;
// build symbol table
SymbolTableBuilder stb = new SymbolTableBuilder();
if (stb.process(ast)) return true;
st = stb.getST();
gc = stb.getGC();
// check implementations
ImplementationChecker ic = new ImplementationChecker();
if (ic.process(ast, gc)) return true;
implProc = ic.getImplProc();
paramMap = ic.getParamMap();
// check blocks
flowGraphs = new BlockFlowGraphs();
if (flowGraphs.process(ast)) return true;
// do the typecheck
ast.eval(this);
return errors;
}
/**
* Returns the flow graph of {@code impl}.
* @param impl the implementation whose flow graph is requested
* @return the flow graph of {@code impl}
*/
public SimpleGraph<Block> getFlowGraph(Implementation impl) {
return flowGraphs.getFlowGraph(impl);
}
/**
* Returns the map of expressions to types.
* @return the map of expressions to types.
*/
public HashMap<Expr, Type> getTypes() {
return typeOf;
}
/**
* Returns the map from implementations to procedures.
* @return the map from implementations to procedures
*/
public UsageToDefMap<Implementation, Procedure> getImplProc() {
return implProc;
}
/**
* Returns the map from implementation parameters to procedure parameters.
* @return the map from implementation parameters to procedure parameters
*/
public UsageToDefMap<VariableDecl, VariableDecl> getParamMap() {
return paramMap;
}
/**
* Returns the symbol table.
* @return the symbol table
*/
public SymbolTable getST() {
return st;
}
// === helper methods ===
// report an error and set the errors flag
// TODO: perhaps do some smarter formating
private void report(AstLocation l, String s) {
Err.error("" + l + ": " + s + ".");
errors = true;
}
// assumes |d| is a list of |VariableDecl|
// gives a TupleType with the types in that list
private TupleType tupleTypeOfDecl(Declaration d) {
if (d == null) return null;
assert d instanceof VariableDecl;
VariableDecl vd = (VariableDecl)d;
return TupleType.mk(vd.getType(), tupleTypeOfDecl(vd.getTail()));
}
// TODO: don't forget to check that the where expressions are booleans
// strip DepType since only the prover can handle the where clauses
// transform one element tuples into the types they contain
private Type strip(Type t) {
if (t instanceof DepType)
return strip(((DepType)t).getType());
else if (t instanceof TupleType) {
TupleType tt = (TupleType)t;
if (tt.getTail() == null) return strip(tt.getType());
}
return t;
}
// replaces all occurrences of UserType(a) with UserType(b)
private Type subst(Type t, String a, String b) {
if (t instanceof UserType) {
UserType tt = (UserType)t;
if (tt.getName().equals(a)) return UserType.mk(b, tt.loc());
return t;
} else if (t instanceof ArrayType) {
ArrayType tt = (ArrayType)t;
return ArrayType.mk(
subst(tt.getRowType(), a, b),
subst(tt.getColType(), a, b),
subst(tt.getElemType(), a, b),
tt.loc());
} else if (t instanceof GenericType) {
GenericType tt = (GenericType)t;
return GenericType.mk(
subst(tt.getParam(), a, b),
subst(tt.getType(), a, b),
tt.loc());
} else if (t instanceof DepType) {
DepType tt = (DepType)t;
return subst(tt.getType(), a, b);
}
assert t == null || t instanceof PrimitiveType;
return t;
}
// returns the name of the type variable or null if |t| is not a type variable
private String typeVarName(Type t) {
if (t instanceof UserType) {
UserType s = (UserType)t;
if (typeVar.containsKey(s.getName()))
return s.getName();
}
return null;
}
// If |a| is a type variable, then unify it with b.
// Returns whether a unification was performed.
private boolean unify(Type a, Type b) {
String an = typeVarName(a);
if (an == null) return false;
String bn;
while ((bn = typeVarName(b)) != null && typeVar.get(bn) != null)
b = typeVar.get(bn);
Type c = typeVar.get(an);
sub(c, b); sub(b, c);
typeVar.put(an, b);
return true;
}
private boolean sub(PrimitiveType a, PrimitiveType b) {
return a.getPtype() == b.getPtype();
}
private boolean sub(ArrayType a, ArrayType b) {
if (!sub(b.getRowType(), a.getRowType())) return false;
if (a.getColType()==null ^ b.getColType() == null) return false;
else if (a.getColType() != null)
if (!sub(b.getColType(), a.getColType())) return false;
return sub(a.getElemType(), b.getElemType());
}
// TODO: Is this OK?
private boolean sub(UserType a, UserType b) {
return a.getName().equals(b.getName());
}
private boolean sub(GenericType a, GenericType b) {
if (!sub(a.getParam(), b.getParam()) || !sub(b.getParam(), a.getParam()))
return false;
return sub(a.getType(), b.getType());
}
private boolean sub(TupleType a, TupleType b) {
if (!sub(a.getType(), b.getType())) return false;
TupleType ta = a.getTail();
TupleType tb = b.getTail();
if (ta == tb) return true;
if (ta == null ^ tb == null) return false;
return sub(ta, tb);
}
// returns (a <: b)
private boolean sub(Type a, Type b) {
// get rid of where clauses and make tuples with one element non-tuples
a = strip(a); b = strip(b);
if (a == b) return true; // the common case
if (a == errType || b == errType) return true; // don't trickle up errors
// an empty tuple is only the same with an empty tuple
if (a == null ^ b == null) return false;
// check if b is ANY
if (b instanceof PrimitiveType) {
PrimitiveType sb = (PrimitiveType)b;
if (sb.getPtype() == PrimitiveType.Ptype.ANY) return true;
}
// the `generics' hack
if (unify(a, b) || unify(b, a)) return true;
// allow T to be used when <tv>T is needed and tv is a type variable
if (b instanceof GenericType && !(a instanceof GenericType)) {
GenericType sb = (GenericType)b;
if (typeVarName(sb.getParam()) != null && sub(a, sb.getType()))
return unify(sb.getParam(), anyType);
}
// allow <x>T to be used where T is expected
if (a instanceof GenericType && !(b instanceof GenericType)) {
GenericType sa = (GenericType)a;
if (sub(sa.getType(), b)) return true;
}
// the main check
if (a instanceof PrimitiveType && b instanceof PrimitiveType)
return sub((PrimitiveType)a, (PrimitiveType)b);
else if (a instanceof ArrayType && b instanceof ArrayType)
return sub((ArrayType)a, (ArrayType)b);
else if (a instanceof UserType && b instanceof UserType)
return sub((UserType)a, (UserType)b);
else if (a instanceof GenericType && b instanceof GenericType)
return sub((GenericType)a, (GenericType)b);
else if (a instanceof TupleType && b instanceof TupleType)
return sub((TupleType)a, (TupleType)b);
else
return false;
}
/**
* If {@code a} cannot be used where {@code b} is expected then an error
* at location {@code l} is produced and {@code errors} is set.
*/
private void check(Type a, Type b, AstLocation l) {
if (sub(a, b)) return;
report(l, "Found type " + TypeUtils.typeToString(a)
+ " instead of " + TypeUtils.typeToString(b));
}
/**
* Same as {@code check}, except it is more picky about the types:
* They must be exactly the same.
*/
private void checkExact(Type a, Type b, AstLocation l) {
if (sub(a, b) || sub(b, a)) return;
report(l, "Unrelated types: "
+ TypeUtils.typeToString(a) + " and " + TypeUtils.typeToString(b));
}
// === visiting operators ===
@Override
public PrimitiveType eval(UnaryOp unaryOp, UnaryOp.Op op, Expr e) {
Type t = strip(e.eval(this));
switch (op) {
case MINUS:
check(t, intType, e.loc());
typeOf.put(unaryOp, intType);
return intType;
case NOT:
check(t, boolType, e.loc());
typeOf.put(unaryOp, boolType);
return boolType;
default:
assert false;
return null; // dumb compiler
}
}
@Override
public PrimitiveType eval(BinaryOp binaryOp, BinaryOp.Op op, Expr left, Expr right) {
Type l = strip(left.eval(this));
Type r = strip(right.eval(this));
switch (op) {
case PLUS:
case MINUS:
case MUL:
case DIV:
case MOD:
// integer arguments and integer result
check(l, intType, left.loc());
check(r, intType, right.loc());
typeOf.put(binaryOp, intType);
return intType;
case LT:
case LE:
case GE:
case GT:
// integer arguments and boolean result
check(l, intType, left.loc());
check(r, intType, right.loc());
typeOf.put(binaryOp, boolType);
return boolType;
case EQUIV:
case IMPLIES:
case AND:
case OR:
// boolean arguments and boolean result
check(l, boolType, left.loc());
check(r, boolType, right.loc());
typeOf.put(binaryOp, boolType);
return boolType;
case SUBTYPE:
// l subtype of r and boolean result (TODO: a user type is a subtype of a user type)
check(l, r, left.loc());
typeOf.put(binaryOp, boolType);
return boolType;
case EQ:
case NEQ:
// typeOf(l) == typeOf(r) and boolean result
checkExact(l, r, binaryOp.loc());
typeOf.put(binaryOp, boolType);
return boolType;
default:
assert false;
return errType; // dumb compiler
}
}
// === visiting atoms, including arrays with that `generic' hack ===
@Override
public Type eval(AtomId atomId, String id) {
Declaration d = st.ids.def(atomId);
Type t = errType;
if (d == null)
// HACK for `generics'
t = UserType.mk(id);
else {
if (d instanceof VariableDecl)
t = ((VariableDecl)d).getType();
else if (d instanceof ConstDecl)
t = ((ConstDecl)d).getType();
else
assert false;
}
typeOf.put(atomId, t);
return t;
}
@Override
public PrimitiveType eval(AtomNum atomNum, BigInteger val) {
typeOf.put(atomNum, intType);
return intType;
}
@Override
public PrimitiveType eval(AtomLit atomLit, AtomLit.AtomType val) {
switch (val) {
case TRUE:
case FALSE:
typeOf.put(atomLit, boolType);
return boolType;
case NULL:
typeOf.put(atomLit, refType);
return refType;
default:
assert false;
return errType; // dumb compiler
}
}
@Override
public Type eval(AtomOld atomOld, Expr e) {
Type t = e.eval(this);
typeOf.put(atomOld, t);
return t;
}
@Override
public PrimitiveType eval(AtomQuant atomQuant, AtomQuant.QuantType quant, Declaration vars, Trigger trig, Expr e) {
Type t = e.eval(this);
check(t, boolType, e.loc());
typeOf.put(atomQuant, boolType);
return boolType;
}
@Override
public Type eval(AtomFun atomFun, String function, Exprs args) {
Function d = st.funcs.def(atomFun);
Signature sig = d.getSig();
Declaration fargs = sig.getArgs();
Type at = strip(args == null? null : (TupleType)args.eval(this));
Type fat = strip(tupleTypeOfDecl(fargs));
check(at, fat, args == null? atomFun.loc() : args.loc());
Type rt = strip(tupleTypeOfDecl(sig.getResults()));
typeOf.put(atomFun, rt);
return rt;
}
@Override
public Type eval(AtomCast atomCast, Expr e, Type type) {
e.eval(this);
typeOf.put(atomCast, type);
return type;
}
@Override
public Type eval(AtomIdx atomIdx, Atom atom, Index idx) {
Type t = strip(atom.eval(this));
if (t == errType) return errType;
if (!(t instanceof ArrayType)) {
Err.error("" + atom.loc() + ": Must be an array.");
errors = true;
return null;
}
ArrayType at = (ArrayType)t;
Type et = at.getElemType();
// the bulk of the `generic' hack
String resultTypeVar = null;
String freshTypeVar = null;
if (et instanceof UserType) {
UserType uet = (UserType)et;
if (st.types.def(uet) == null) {
// make uet a type variable
resultTypeVar = uet.getName();
freshTypeVar = TYPE_VAR_PREFIX + typeVarCnt++;
at = (ArrayType)subst(at, resultTypeVar, freshTypeVar);
typeVar.put(freshTypeVar, null);
}
}
// look at indexing types
check(idx.getA().eval(this), at.getRowType(), idx.getA().loc());
if (idx.getB() != null)
check(idx.getB().eval(this), at.getColType(), idx.getB().loc());
// get the result type in case it was a type variable
if (resultTypeVar != null) {
et = typeVar.get(freshTypeVar);
if (et == null) {
report(atomIdx.loc(), "Can't deduce array type");
et = errType;
}
typeVar.remove(freshTypeVar);
}
typeOf.put(atomIdx, et);
return et;
}
// === visit commands ===
@Override
public Type eval(AssignmentCmd assignmentCmd, Expr lhs, Expr rhs) {
Type lt = strip(lhs.eval(this));
Type rt = strip(rhs.eval(this));
check(rt, lt, assignmentCmd.loc());
return null;
}
@Override
public Type eval(AssertAssumeCmd assertAssumeCmd, AssertAssumeCmd.CmdType type, Expr expr) {
Type t = expr.eval(this);
check(t, boolType, assertAssumeCmd.loc());
return null;
}
@Override
public Type eval(CallCmd callCmd, String procedure, Identifiers results, Exprs args) {
Procedure p = st.procs.def(callCmd);
Signature sig = p.getSig();
Declaration fargs = sig.getArgs();
// check the actual arguments against the formal ones
Type at = strip(args == null? null : args.eval(this));
Type fat = strip(tupleTypeOfDecl(fargs));
check(at, fat, (args == null? callCmd.loc() : args.loc()));
// check the assignment of the results
Type lt = strip(results == null? null : results.eval(this));
Type rt = strip(tupleTypeOfDecl(sig.getResults()));
check(rt, lt, callCmd.loc());
return null;
}
// === visit dependent types ===
@Override
public DepType eval(DepType depType, Type type, Expr pred) {
Type t = pred.eval(this);
check(t, boolType, pred.loc());
return null;
}
// === visit Exprs and Identifiers to make TupleType-s ===
@Override
public TupleType eval(Exprs exprs, Expr expr, Exprs tail) {
Type t = expr.eval(this);
assert t != null; // shouldn't have nested tuples
TupleType tt = tail == null? null : (TupleType)tail.eval(this);
TupleType rt = TupleType.mk(t, tt);
typeOf.put(exprs, rt);
return rt;
}
@Override
public TupleType eval(Identifiers identifiers, AtomId id, Identifiers tail) {
Type t = id.eval(this);
TupleType tt = tail == null? null : (TupleType)tail.eval(this);
TupleType rt = TupleType.mk(t, tt);
// TODO: put this in typeOf?
return rt;
}
// === visit various things that must have boolean params ===
@Override
public Type eval(Axiom axiom, Expr expr, Declaration tail) {
Type t = expr.eval(this);
check(t, boolType, expr.loc());
if (tail != null) tail.eval(this);
return null;
}
@Override
public Type eval(Specification specification, Specification.SpecType type, Expr expr, boolean free, Specification tail) {
Type t = null;
switch (type) {
case REQUIRES:
case ENSURES:
t = expr.eval(this);
check(t, boolType, expr.loc());
case MODIFIES:
break;
default:
assert false;
return errType; // dumb compiler
}
if (tail != null) tail.eval(this);
return null;
}
// === do not look at block successors ===
@Override
public Type eval(Block block, String name, Commands cmds, Identifiers succ, Block tail) {
if (cmds != null) cmds.eval(this);
if (tail != null) tail.eval(this);
return null;
}
}