package nl.utwente.viskell.haskell.type;
import nl.utwente.viskell.haskell.expr.Expression;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Implementation of a typechecker for a simplified variant of Haskell.
*/
public final class TypeChecker {
/**
* Logger for this class.
*/
private static final Logger logger = Logger.getLogger(TypeChecker.class.getName());
static {
TypeChecker.logger.setLevel(Level.WARNING);
// Changing this to Level.INFO will show debug messages.
}
/**
* Private constructor - methods in this class are static.
*/
private TypeChecker() {
}
public static void unify(final Expression context, final Type a, final Type b) throws HaskellTypeError {
TypeChecker.unify(context.toString(), a, b);
}
public static void unify(final String context, final Type a, final Type b) throws HaskellTypeError {
TypeChecker.logger.info(String.format("Unifying types %s and %s for context %s", a, b, context));
if (a.equals(b)) {
// for identical types unifying is trivial
} else if (a instanceof TypeVar) {
TypeVar va = (TypeVar) a;
// First, prevent ourselves from going into an infinite loop
if (b.containsOccurenceOf(va)) {
TypeChecker.logger.info(String.format("Recursion in types %s and %s for context %s", a, b, context));
throw new HaskellTypeError(String.format("%s ∈ %s in context of %s", a, b, context));
}
if (va.hasConcreteInstance()) {
// if a type variable has been instantiated already then we can just unify b with a concrete type of a
TypeChecker.unify(context, va.getInstantiatedType(), b);
} else if (b instanceof TypeVar) {
TypeVar vb = (TypeVar) b;
if (vb.hasConcreteInstance()) {
// with type variable b instantiated continue with unifying type variable a with the concrete type of b
TypeChecker.unify(context, va, vb.getInstantiatedType());
} else {
// two plain type variable are unified by sharing the internal reference of (future) type instance
vb.unifyWith(va);
}
} else if (b instanceof ConcreteType) {
ConcreteType tb = (ConcreteType) b;
// check that the type satisfy the constraints
TypeChecker.satisfyConstraints(tb, va.getConstraints(), context);
// then make the type variable instantiated by this concrete type
va.setConcreteInstance(tb);
}
} else if (b instanceof TypeVar && a instanceof ConcreteType) {
// Example: we have to unify Int and α.
// Same as above, but mirrored.
TypeChecker.unify(context, b, a);
} else if (a instanceof TypeCon && b instanceof TypeCon) {
final TypeCon ca = (TypeCon) a;
final TypeCon cb = (TypeCon) b;
// unification of type constructor is just name equality
if (! ca.getName().equals(cb.getName()))
{
TypeChecker.logger.info(String.format("Mismatching TypeCon %s and %s for context %s", a, b, context));
throw new HaskellTypeError(String.format("%s ⊥ %s in context of %s", a, b, context));
}
} else if (a instanceof FunType && b instanceof FunType) {
// Unifying function types is pairwise unification of its argument and result.
FunType fa = (FunType) a;
FunType fb = (FunType) b;
TypeChecker.unify(context, fa.getArgument(), fb.getArgument());
TypeChecker.unify(context, fa.getResult(), fb.getResult());
} else if (a instanceof TypeApp && b instanceof TypeApp) {
// Unifying type applications is pairwise unification of its typeFun and typeArg.
TypeApp ta = (TypeApp) a;
TypeApp tb = (TypeApp) b;
TypeChecker.unify(context, ta.getTypeFun(), tb.getTypeFun());
TypeChecker.unify(context, ta.getTypeArg(), tb.getTypeArg());
} else {
// Running out of things that can be unified, so bail out with a type error.
TypeChecker.logger.info(String.format("Given up to unify types %s and %s for context %s", a, b, context));
throw new HaskellTypeError(String.format("%s ⊥ %s in context of %s", a, b, context));
}
}
/**
* Check and enforce that a type matches a set of type class constraints.
*
* @param type A concrete type which is affected by the constraints
* @param constraints The set of constraint that need to be satisfied.
* @param context the expression string to use as context in errors.
* @throws HaskellTypeError if the constraints can no be satisfied by this type.
*/
protected static void satisfyConstraints(Type type, ConstraintSet constraints, String context) throws HaskellTypeError {
if (! constraints.hasConstraints()) {
// empty constraints are always satisfied.
return;
}
if (type instanceof TypeVar) {
TypeVar tv = (TypeVar) type;
if (tv.hasConcreteInstance()) {
// just constrain the concrete instantiation
TypeChecker.satisfyConstraints(tv.getInstantiatedType(), constraints, context);
return;
} else {
// add extra constraint for this type variable
tv.introduceConstrainst(constraints);
return;
}
} else if (type instanceof TypeCon) {
TypeCon tc = (TypeCon)type;
// directly check if all constraints are satisfied
if (constraints.allConstraintsMatch(tc)) {
return;
}
} else if (type instanceof TypeApp) {
TypeApp ta = (TypeApp)type;
List<Type> chain = ta.asFlattenedAppChain();
Type ctype = chain.remove(0);
// use the instantiated type instead, if available.
if (ctype instanceof TypeVar && ((TypeVar)ctype).hasConcreteInstance()) {
ctype = ((TypeVar)ctype).getInstantiatedType();
}
// check if the head of a type application chain is a known type constructor
if (ctype instanceof TypeCon) {
TypeCon tc = (TypeCon)ctype;
if (constraints.allConstraintsMatch(tc)) {
// also for all type arguments add implied constraint as needed
int arity = chain.size();
List<ConstraintSet> argConstraints = constraints.getImpliedArgConstraints(tc, arity);
for (int i = 0; i < arity; i++) {
TypeChecker.satisfyConstraints(chain.get(i), argConstraints.get(i), context);
}
// all satisfied, done
return;
}
} else if (ctype instanceof TypeVar) {
// in case of a type variable application we add the constraints
ta.extendConstraints(constraints);
((TypeVar)ctype).addConstrainedTypeApp(ta);
// done for now, the constraint satisfaction check is deferred to later
return;
}
}
// for now, constraining other types will fail.
TypeChecker.logger.info(String.format("Unable to unify types %s with constraints %s for context %s", type, constraints, context));
throw new HaskellTypeError(String.format("%s ∉ constraints of %s in context of %s", type, constraints, context));
}
}