anchor.diff:    return true;
   }
   
  +/// Types are equal if they are structurally equal up to renaming of bound type
  +/// variables and equating all top types.
  +///
  +/// We ignore renaming of bound type variables because we operate on de Bruijn
  +/// indices, not names.
  +bool typeEqual(Rti s, Rti t) {
  +  if (_Utils.isIdentical(s, t)) return true;
  +
  +  if (isTopType(s)) return isTopType(t);
  +
  +  int sKind = Rti._getKind(s);
  +  int tKind = Rti._getKind(t);
  +  if (sKind != tKind) return false;
  +
  +  switch (sKind) {
  +    case Rti.kindStar:
  +    case Rti.kindQuestion:
  +    case Rti.kindFutureOr:
  +      return typeEqual(
  +          _castToRti(Rti._getPrimary(s)), _castToRti(Rti._getPrimary(t)));
  +
  +    case Rti.kindInterface:
  +      if (Rti._getInterfaceName(s) != Rti._getInterfaceName(t)) return false;
  +      return typesEqual(
  +          Rti._getInterfaceTypeArguments(s), Rti._getInterfaceTypeArguments(t));
  +
  +    case Rti.kindBinding:
  +      return typeEqual(Rti._getBindingBase(s), Rti._getBindingBase(t)) &&
  +          typesEqual(Rti._getBindingArguments(s), Rti._getBindingArguments(t));
  +
  +    case Rti.kindFunction:
  +      return typeEqual(Rti._getReturnType(s), Rti._getReturnType(t)) &&
  +          functionParametersEqual(
  +              Rti._getFunctionParameters(s), Rti._getFunctionParameters(t));
  +
  +    case Rti.kindGenericFunction:
  +      return typeEqual(
  +              Rti._getGenericFunctionBase(s), Rti._getGenericFunctionBase(t)) &&
  +          typesEqual(Rti._getGenericFunctionBounds(s),
  +              Rti._getGenericFunctionBounds(t));
  +
  +    default:
  +      return false;
  +  }
  +}
  +
  +bool typesEqual(Object sArray, Object tArray) {
  +  int sLength = _Utils.arrayLength(sArray);
  +  int tLength = _Utils.arrayLength(tArray);
  +  if (sLength != tLength) return false;
  +  for (int i = 0; i < sLength; i++) {
  +    if (!typeEqual(_castToRti(_Utils.arrayAt(sArray, i)),
  +        _castToRti(_Utils.arrayAt(tArray, i)))) return false;
  +  }
  +  return true;
  +}
  +
  +bool namedTypesEqual(Object sArray, Object tArray) {
  +  int sLength = _Utils.arrayLength(sArray);
  +  int tLength = _Utils.arrayLength(tArray);
  +  assert(sLength.isEven);
  +  assert(tLength.isEven);
  +  if (sLength != tLength) return false;
  +  for (int i = 0; i < sLength; i += 2) {
  +    if (_Utils.asString(_Utils.arrayAt(sArray, i)) !=
  +        _Utils.asString(_Utils.arrayAt(tArray, i))) return false;
  +    if (!typeEqual(_castToRti(_Utils.arrayAt(sArray, i + 1)),
  +        _castToRti(_Utils.arrayAt(tArray, i + 1)))) return false;
  +  }
  +  return true;
  +}
  +
  +// TODO(fishythefish): Support required named parameters.
  +bool functionParametersEqual(
  +        _FunctionParameters sParameters, _FunctionParameters tParameters) =>
  +    typesEqual(_FunctionParameters._getRequiredPositional(sParameters),
  +        _FunctionParameters._getRequiredPositional(tParameters)) &&
  +    typesEqual(_FunctionParameters._getOptionalPositional(sParameters),
  +        _FunctionParameters._getOptionalPositional(tParameters)) &&
  +    namedTypesEqual(_FunctionParameters._getOptionalNamed(sParameters),
  +        _FunctionParameters._getOptionalNamed(tParameters));
  +
   bool isTopType(Rti t) =>
       isDynamicType(t) || isVoidType(t) || isObjectType(t) || isJsInteropType(t);
   
   bool isDynamicType(Rti t) => Rti._getKind(t) == Rti.kindDynamic;
   bool isVoidType(Rti t) => Rti._getKind(t) == Rti.kindVoid;
   bool isJsInteropType(Rti t) => Rti._getKind(t) == Rti.kindAny;
  +
   bool isFutureOrType(Rti t) => Rti._getKind(t) == Rti.kindFutureOr;
  -bool isFunctionKind(Rti t) => Rti._getKind(t) == Rti.kindFunction;
  +
  +bool isGenericFunctionKind(Rti t) => Rti._getKind(t) == Rti.kindGenericFunction;
  +
   bool isGenericFunctionTypeParameter(Rti t) =>
       Rti._getKind(t) == Rti.kindGenericFunctionParameter;
   
  
positive.code:     var cache = evalCache(universe);
      var probe = _cacheGet(cache, recipe);
      if (probe != null) return _castToRti(probe);
      var rti = _parseRecipe(universe, null, recipe);
      _cacheSet(cache, recipe, rti);
      return rti;
    }
  
negative.code:   }
  
    // TODO(fishythefish): Support required named parameters.
    Rti returnType = Rti._getReturnType(functionType);
    _FunctionParameters parameters = Rti._getFunctionParameters(functionType);
    var requiredPositional =
        _FunctionParameters._getRequiredPositional(parameters);
    int requiredPositionalLength = _Utils.arrayLength(requiredPositional);
  