@Override
 public FunctionTypeImpl substitute(Type[] argumentTypes, Type[] parameterTypes) {
   if (argumentTypes.length != parameterTypes.length) {
     throw new IllegalArgumentException(
         "argumentTypes.length ("
             + argumentTypes.length
             + ") != parameterTypes.length ("
             + parameterTypes.length
             + ")");
   }
   if (argumentTypes.length == 0) {
     return this;
   }
   Element element = getElement();
   FunctionTypeImpl newType =
       (element instanceof ExecutableElement)
           ? new FunctionTypeImpl((ExecutableElement) element)
           : new FunctionTypeImpl((FunctionTypeAliasElement) element);
   newType.setReturnType(returnType.substitute(argumentTypes, parameterTypes));
   newType.setNormalParameterTypes(
       substitute(normalParameterTypes, argumentTypes, parameterTypes));
   newType.setOptionalParameterTypes(
       substitute(optionalParameterTypes, argumentTypes, parameterTypes));
   newType.namedParameterTypes = substitute(namedParameterTypes, argumentTypes, parameterTypes);
   return newType;
 }
 /**
  * Determine whether the given field's type is changed when type parameters from the defining
  * type's declaration are replaced with the actual type arguments from the defining type.
  *
  * @param baseField the base field
  * @param definingType the type defining the parameters and arguments to be used in the
  *     substitution
  * @return true if the type is changed by type substitution.
  */
 private static boolean isChangedByTypeSubstitution(
     FieldElement baseField, InterfaceType definingType) {
   Type[] argumentTypes = definingType.getTypeArguments();
   if (baseField != null && argumentTypes.length != 0) {
     Type baseType = baseField.getType();
     Type[] parameterTypes = definingType.getElement().getType().getTypeArguments();
     if (baseType != null) {
       Type substitutedType = baseType.substitute(argumentTypes, parameterTypes);
       if (!baseType.equals(substitutedType)) {
         return true;
       }
     }
     // If the field has a propagated type, then we need to check whether the propagated type
     // needs substitution.
     Type basePropagatedType = baseField.getPropagatedType();
     if (basePropagatedType != null) {
       Type substitutedPropagatedType =
           basePropagatedType.substitute(argumentTypes, parameterTypes);
       if (!basePropagatedType.equals(substitutedPropagatedType)) {
         return true;
       }
     }
   }
   return false;
 }
  @Override
  public boolean isSubtypeOf(Type type) {
    // trivial base cases
    if (type == null) {
      return false;
    } else if (this == type || type.isDynamic() || type.isDartCoreFunction()) {
      return true;
    } else if (!(type instanceof FunctionType)) {
      return false;
    } else if (this.equals(type)) {
      return true;
    }
    FunctionType t = this;
    FunctionType s = (FunctionType) type;
    // normal parameter types
    if (t.getNormalParameterTypes().length != s.getNormalParameterTypes().length) {
      return false;
    } else if (t.getNormalParameterTypes().length > 0) {
      Type[] tTypes = t.getNormalParameterTypes();
      Type[] sTypes = s.getNormalParameterTypes();
      for (int i = 0; i < tTypes.length; i++) {
        if (!tTypes[i].isAssignableTo(sTypes[i])) {
          return false;
        }
      }
    }

    // optional parameter types
    if (t.getOptionalParameterTypes().length > 0) {
      Type[] tOpTypes = t.getOptionalParameterTypes();
      Type[] sOpTypes = s.getOptionalParameterTypes();
      // if k >= m is false, return false: the passed function type has more optional parameter
      // types than this
      if (tOpTypes.length < sOpTypes.length) {
        return false;
      }
      for (int i = 0; i < sOpTypes.length; i++) {
        if (!tOpTypes[i].isAssignableTo(sOpTypes[i])) {
          return false;
        }
      }
      if (t.getNamedParameterTypes().size() > 0 || s.getNamedParameterTypes().size() > 0) {
        return false;
      }
    } else if (s.getOptionalParameterTypes().length > 0) {
      return false;
    }

    // named parameter types
    if (t.getNamedParameterTypes().size() > 0) {
      Map<String, Type> namedTypesT = t.getNamedParameterTypes();
      Map<String, Type> namedTypesS = s.getNamedParameterTypes();
      // if k >= m is false, return false: the passed function type has more named parameter types
      // than this
      if (namedTypesT.size() < namedTypesS.size()) {
        return false;
      }
      // Loop through each element in S verifying that T has a matching parameter name and that the
      // corresponding type is assignable to the type in S.
      Iterator<Entry<String, Type>> iteratorS = namedTypesS.entrySet().iterator();
      while (iteratorS.hasNext()) {
        Entry<String, Type> entryS = iteratorS.next();
        Type typeT = namedTypesT.get(entryS.getKey());
        if (typeT == null) {
          return false;
        }
        if (!entryS.getValue().isAssignableTo(typeT)) {
          return false;
        }
      }
    } else if (s.getNamedParameterTypes().size() > 0) {
      return false;
    }
    return s.getReturnType().equals(VoidTypeImpl.getInstance())
        || t.getReturnType().isAssignableTo(s.getReturnType());
  }