package nl.utwente.viskell.haskell.type; import com.google.common.collect.Sets; import java.util.*; import java.util.stream.Collectors; public final class ConstraintSet { /** * A set of type class constraints belonging to a single type object. */ TreeSet<TypeClass> constraints; public ConstraintSet() { this.constraints = new TreeSet<>(); } public ConstraintSet(TreeSet<TypeClass> constraints) { this.constraints = constraints; } /** * @return Whether this constraint set is not empty. */ public boolean hasConstraints() { return ! this.constraints.isEmpty(); } /** * @param tc the type class to extend this constraint set with */ protected void addExtraConstraint(TypeClass tc) { this.constraints.add(tc); this.simplifyConstraints(); } /** * @param extras additional constraint set to extend this constraint set with */ protected void addExtraConstraint(ConstraintSet extras) { this.constraints.addAll(extras.constraints); this.simplifyConstraints(); } /** * Checks whether the given type is within the constraints. * If the set of constraints is empty, every type is within the constraints. * * @param type The type to check. * @return Whether the given type is within the constraints of this type. */ protected boolean allConstraintsMatch(TypeCon con) { return this.constraints.stream().allMatch(c -> c.hasType(con)); } /** * @param con the type constructor to check. * @param arity number of arguments to get the constraints for. * @return A arity long list of constraints set that are required by instances that match the type constructor. */ protected List<ConstraintSet> getImpliedArgConstraints(TypeCon con, int arity) { List<ConstraintSet> results = new ArrayList<>(arity); for (int i = 0; i < arity; i++) { results.add(new ConstraintSet()); } for (TypeClass typeClass : this.constraints) { int n = typeClass.lookupConstrainedArgs(con); for (int i = 0; i < n; i++) { results.get(i).addExtraConstraint(typeClass); } } return results; } /** * simplify the constraint set by removing super class implications */ private void simplifyConstraints() { if (this.constraints.size() <= 1) { return; } Set<TypeClass> allSupers = new TreeSet<>(); for (TypeClass tc : this.constraints) { allSupers.addAll(tc.getSupers()); } this.constraints = new TreeSet<>(Sets.difference(this.constraints, allSupers)); return; } /** * Merge this constrain set with another, while also simplifying and checking satisfiability. * @param other constraint set to merge with * @throws HaskellTypeError if the combined constraint set is not satisfiable. */ protected void mergeConstraintsWith(ConstraintSet other) throws HaskellTypeError { this.constraints = new TreeSet<>(Sets.union(this.constraints, other.constraints)); this.simplifyConstraints(); this.checkSatisfiable(); } /** * Check if type constructors exist that can satisfy all the constraint in this set. * @throws HaskellTypeError if this constraint set is not satisfiable. * */ private void checkSatisfiable() throws HaskellTypeError { if (this.constraints.size() <= 1) { return; } if (this.constraints.stream().map(TypeClass::allInstanceTypeCons).reduce(Sets::intersection).get().isEmpty()) { throw new HaskellTypeError("no known type constructor satisfies all of " + this.toString()); } } protected Optional<ConcreteType> tryGetDefaulted() { Set<TypeClass> classes = this.constraints; // search through the type classes for a suitable default while (!classes.isEmpty()) { for (TypeClass tc : classes) { if (tc.getDefaultType().isPresent()) { TypeCon def = tc.getDefaultType().get(); if (this.allConstraintsMatch(def)) { return Optional.of(def); } } } // fall back on super classes classes = classes.stream().flatMap(tc -> tc.getSupers().stream()).collect(Collectors.toSet()); } return Optional.empty(); } /** * @param typeText the String representation of type being constrained * @param The fixity of the context the type is shown in. * @return The readable representation of this type for in the UI. */ public String prettyPrintWith(String typeText, final int fixity) { if (this.constraints.isEmpty()) { return typeText; } else if (fixity < 9 && this.constraints.size() == 1) { return this.constraints.iterator().next().getName() + " " + typeText; } else { final StringBuilder out = new StringBuilder(); out.append("("); int i = 0; for (TypeClass tc : this.constraints) { out.append(tc.getName()); if (i + 1 < this.constraints.size()) { out.append("+"); } i++; } out.append(" "); out.append(typeText); out.append(")"); return out.toString(); } } @Override public ConstraintSet clone() { return new ConstraintSet(new TreeSet<>(this.constraints)); } @Override public String toString() { return Arrays.toString(this.constraints.stream().map(c -> c.getName()).toArray()); } @Override public boolean equals(Object other) { if (! (other instanceof ConstraintSet)) { return false; } return this.constraints.equals(((ConstraintSet)other).constraints); } public int count() { return this.constraints.size(); } }