package nl.utwente.viskell.haskell.type;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import com.google.common.collect.ImmutableList;
/**
* Variable type.
*/
public class TypeVar extends Type {
/**
* An optional mutable reference to a concrete type.
*/
protected final static class TypeInstance {
/**
* The textual representation of the type variable.
*/
private final String name;
/**
* Whether this type variable was created internally in typechecking process, and preferably should not be shown to the user.
*/
private final boolean internal;
/**
* Whether this type variable is rigid (meaning that it can't be unified with a more specific or concrete type)
*/
private boolean isRigid;
/**
* A concrete instantiated type or null.
*/
private ConcreteType type;
/**
* The type constraints for this instance.
*/
private ConstraintSet constraints;
/**
* List of type applications that needs their constraints checked once this variable is instantiated.
*/
private List<TypeApp> associatedTypeApps;
/**
* Internal List of all type variables that refer to this instance.
* this list contains the creating type variable as well as the variables have unified with this.
* It is using WeakReference to avoid holding on to too much old type variables.
*/
private LinkedList<WeakReference<TypeVar>> unifiedVars;
/**
* @param name The textual representation of the type variable.
* @param internal whether this an internally generated type variable
* @param type The concrete instance of this type, might be null.
* @param constraints The set of constraints for this type.
*/
private TypeInstance(TypeVar creator, String name, boolean internal, boolean isRigid, ConcreteType type, final ConstraintSet constraints) {
this.name = name;
this.internal = internal;
this.isRigid = isRigid;
this.type = type;
this.constraints = constraints;
this.unifiedVars = new LinkedList<>();
this.unifiedVars.add(new WeakReference<>(creator));
this.associatedTypeApps = new ArrayList<>();
}
/**
* Make this type variable rigid
* @throws IllegalStateException when is isPresent() is true
*/
protected void makeRigid() {
if (this.type != null) {
throw new IllegalStateException("Type instance already set");
}
this.isRigid = true;
}
/**
* @return Whether there is a concrete type.
*/
private boolean isPresent() {
return this.type != null;
}
/**
* @throws NullPointerException when isPresent() is false
* @return The concrete type instance
*/
private ConcreteType get() {
if (this.type == null) {
throw new NullPointerException("Getting invalid type instance");
}
return this.type;
}
/**
* @throws IllegalStateException when is isPresent() is true
* @throws HaskellTypeError if this type variable is rigid
* @param The new instance of this type.
*/
private void set(ConcreteType ctype) throws HaskellTypeError {
if (this.type != null) {
throw new IllegalStateException("Type instance already set");
}
if (this.isRigid) {
throw new HaskellTypeError("Can not unify a rigid type variable " + this.name + " with concrete type " + ctype.prettyPrint());
}
this.type = ctype;
// now the type variable is instantiated we need check all deferred typeapp constraints
for (TypeApp tapp : this.associatedTypeApps) {
TypeChecker.satisfyConstraints(tapp, this.constraints, "typeapp constraint with " + this.getName());
// once satisfied the constraints are not needed anymore
tapp.clearConstraints();
}
this.associatedTypeApps = ImmutableList.of();
}
/**
* Deeply unifying all aspects of type instances
* @param the other type instance.
* @throws HaskellTypeError if the combined constraint set of the typevar instances is not satisfiable.
* @throws HaskellTypeError if a rigid type variable gets unified with more constraints or another rigid type variable
*/
private void unifyWith(TypeInstance other) throws HaskellTypeError {
if (this == other) {
return;
}
if (this.isRigid && other.isRigid) {
throw new HaskellTypeError("Can not unify a rigid type variable " + this.name + " with another rigid type variable " + other.name);
}
// if one of the type variables is rigid, first check that the constraints are identical
if ((this.isRigid || other.isRigid) && ! this.constraints.equals(other.constraints)) {
throw new HaskellTypeError("Can not add extra constraints to a rigid type variable " + this.name);
}
other.constraints.mergeConstraintsWith(this.constraints);
other.associatedTypeApps.addAll(this.associatedTypeApps);
other.unifiedVars.addAll(this.unifiedVars);
// Go through all type variable associated with both type instances, to make sure all of them are unified to the same instance.
for (ListIterator<WeakReference<TypeVar>> iter = this.unifiedVars.listIterator(); iter.hasNext();) {
TypeVar referer = iter.next().get();
if (referer == null) {
iter.remove();
} else {
referer.instance = other;
}
}
}
/**
* @return The textual representation of the type variable.
*/
private String getName() {
return this.name;
}
/**
* @param The fixity of the context the type is shown in.
* @return The readable representation of this type for in the UI.
*/
private final String prettyPrint(final int fixity) {
if (this.type != null) {
return this.type.prettyPrint(fixity);
}
return this.constraints.prettyPrintWith(this.getName(), fixity);
}
protected void defaultOrElse(ConcreteType backupType) throws HaskellTypeError {
if (this.type == null) {
this.set(this.constraints.tryGetDefaulted().orElse(backupType));
}
}
}
/**
* The reference to the potential concrete instance for this type.
*/
private TypeInstance instance;
/**
* @param name The textual representation of the type variable.
* Identifiers are not used in the type checking progress,
* different {@code TypeVar} instances with the same name are not equal.
* @param internal whether this an internally generated type variable
*/
public TypeVar(final String name, final boolean internal) {
this(name, internal, false, new ConstraintSet(), null);
}
/**
* @param name The textual representation of the type variable.
* Identifiers are not used in the type checking progress,
* different {@code TypeVar} instances with the same name are not equal.
* @param internal whether this an internally generated type variable
* @param constraints The set of constraints for this type.
* @param instance The concrete instance of this type, might be null.
*/
private TypeVar(final String name, final boolean internal, final boolean isRigid, final ConstraintSet constraints, final ConcreteType type) {
this.instance = new TypeInstance(this, name.toLowerCase(), internal, isRigid, type, constraints);
}
/**
* @return The name of this variable type.
*/
public final String getName() {
return this.instance.getName();
}
/**
* @return Whether this type variable has been instantiated with a concrete type.
*/
public final boolean hasConcreteInstance() {
return this.instance.isPresent();
}
/**
* @throws NullPointerException when hasInstance() is false
* @return The concrete type this type variable has been instantiated with.
*/
public final ConcreteType getInstantiatedType() {
return this.instance.get();
}
/*
* @throws IllegalStateException when hasInstance() is true
* @throws HaskellTypeError if this type variable is rigid
* @param The concrete type this type variable is unified with
*/
public final void setConcreteInstance(ConcreteType type) throws HaskellTypeError {
this.instance.set(type);
}
/**
* Use the same type instance for both type variable, effectively unifying them.
* @param the other type variable.
* @throws HaskellTypeError if the combined constraint set of the type variables is not satisfiable.
*/
public final void unifyWith(TypeVar other) throws HaskellTypeError {
if (this.instance.isRigid) {
other.instance.unifyWith(this.instance);
} else if (other.instance.isRigid || this.instance.internal) {
this.instance.unifyWith(other.instance);
} else {
other.instance.unifyWith(this.instance);
}
}
/**
* @return The set of type class constraints associated with this type variable
*/
public ConstraintSet getConstraints() {
return this.instance.constraints;
}
/**
* Extends the constraint set with extra type class.
* @param typeClass to be added to this type variable
*/
protected void introduceConstraint(TypeClass typeClass) {
this.instance.constraints.addExtraConstraint(typeClass);
}
/**
* Extends the constraint set with extra type class.
* @param constraints set to be added to this type variable
*/
protected void introduceConstrainst(ConstraintSet constraints) {
this.instance.constraints.addExtraConstraint(constraints);
}
/**
* Adds a type application that requires constraint checking once this type variable gets instantiated
* @param typeapp to add
*/
protected void addConstrainedTypeApp(TypeApp typeapp) {
if (! this.instance.associatedTypeApps.contains(typeapp)) {
this.instance.associatedTypeApps.add(typeapp);
}
}
@Override
public final String prettyPrint(final int fixity) {
return this.instance.prettyPrint(fixity);
}
@Override
protected String prettyPrintAppChain(int fixity, List<Type> args) {
if (this.instance.isPresent()) {
return this.instance.get().prettyPrintAppChain(fixity, args);
}
return super.prettyPrintAppChain(fixity, args);
}
@Override
public Type getFresh(TypeScope scope) {
if (this.instance.isPresent()) {
return this.instance.get().getFresh(scope);
}
return scope.pickFreshTypeVar(this);
}
/**
* This internal method should only be called from TypeScope
* @param staleToFresh The mapping between known type instances and their related fresh type variables.
* @return A refreshed type variable.
*/
protected TypeVar pickFreshTypeVarInstance(IdentityHashMap<TypeVar.TypeInstance, TypeVar> staleToFresh) {
if (staleToFresh.containsKey(this.instance)) {
return staleToFresh.get(this.instance);
}
if (this.instance.isRigid) {
//FIXME this is a ugly workaround to make to rigid typevars unify with fresh copies of themselves
//TODO remove this special case once type scoping is dealt with properly for whole lambdas
this.instance.unifiedVars = new LinkedList<>();
this.instance.unifiedVars.add(new WeakReference<>(this));
return this;
}
TypeVar fresh = new TypeVar(this.instance.name, this.instance.internal, this.instance.isRigid, this.instance.constraints.clone(), null);
staleToFresh.put(this.instance, fresh);
return fresh;
}
@Override
public Type getConcrete() {
if (this.hasConcreteInstance()) {
return this.getInstantiatedType();
} else {
return this;
}
}
@Override
public boolean containsOccurenceOf(TypeVar tvar) {
// If type variable share the same instance then they have been unified to a single one.
if (this.instance == tvar.instance) {
return true;
}
if (!this.instance.isPresent()) {
return false;
}
return this.instance.get().containsOccurenceOf(tvar);
}
@Override
public final String toString() {
String constr = this.instance.constraints.toString();
String fmt = this.instance.isRigid ? "forall %s.(%s)%s" : "%s(%s)%s";
String tmp = String.format(fmt, this.getName(), Integer.toHexString(this.instance.hashCode()), constr);
return this.instance.isPresent() ? tmp + ":" + this.instance.get().toString() : tmp;
}
/**
* This method simply checks whether the reference of instance in the type variables are the same.
* The uniqueness of type variable depends on this because the meaning of the name is context sensitive.
* This is why we leave the task of using the right type variable to the programmer that implements this type system
* - there would be no clear and easy way of doing this automagically.
*
* @param obj The object to compare with.
* @return Whether the given object is equal to this object.
*/
@Override
public boolean equals(Object obj) {
if (!(obj instanceof TypeVar)) {
return false;
}
TypeVar other = (TypeVar) obj;
return this.instance == other.instance;
}
}