/**
  * Given a partial solution to our type argument inference, replace any uses of type parameters
  * that have been solved with their arguments.
  *
  * <p>That is: Let S be a partial solution to our inference (i.e. we have inferred type arguments
  * for some types) Let S be a map {@code (T0 -> A0, T1 -> A1, ..., TN -> AN)} where Ti is a type
  * parameter and Ai is its solved argument. For all uses of Ti in this constraint, replace them
  * with Ai.
  *
  * <p>For the mapping {@code (T0 -> A0)}, the following constraint: {@code ArrayList<T0> <<
  * List<T1>}
  *
  * <p>Becomes: {@code ArrayList<A0> << List<T1>}
  *
  * <p>A constraint: {@code T0 << T1}
  *
  * <p>Becomes: {@code A0 << T1}
  *
  * @param substitutions a mapping of target type parameter to the type argument to
  * @return a new constraint that contains no use of the keys in substitutions
  */
 public AFConstraint substitute(final Map<TypeVariable, AnnotatedTypeMirror> substitutions) {
   final AnnotatedTypeMirror newArgument =
       TypeArgInferenceUtil.substitute(substitutions, argument);
   final AnnotatedTypeMirror newFormalParameter =
       TypeArgInferenceUtil.substitute(substitutions, formalParameter);
   return construct(newArgument, newFormalParameter);
 }
 /**
  * @param targets the type parameters whose arguments we are trying to solve for
  * @return true if this constraint can't be broken up into other constraints or further simplified
  *     In general, if either argument or formal parameter is a use of the type parameters we are
  *     inferring over then the constraint cannot be reduced further
  */
 public boolean isIrreducible(final Set<TypeVariable> targets) {
   return TypeArgInferenceUtil.isATarget(argument, targets)
       || TypeArgInferenceUtil.isATarget(formalParameter, targets);
 }