/* * Copyright (c) 2012, the Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.dart.engine.internal.type; import com.google.dart.engine.element.ClassElement; import com.google.dart.engine.element.Element; import com.google.dart.engine.element.ExecutableElement; import com.google.dart.engine.element.FunctionTypeAliasElement; import com.google.dart.engine.element.ParameterElement; import com.google.dart.engine.element.TypeParameterElement; import com.google.dart.engine.internal.element.ElementPair; import com.google.dart.engine.internal.element.TypeParameterElementImpl; import com.google.dart.engine.internal.element.member.ParameterMember; import com.google.dart.engine.type.FunctionType; import com.google.dart.engine.type.Type; import com.google.dart.engine.type.UnionType; import com.google.dart.engine.utilities.dart.ParameterKind; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; /** * Instances of the class {@code FunctionTypeImpl} defines the behavior common to objects * representing the type of a function, method, constructor, getter, or setter. * * @coverage dart.engine.type */ public class FunctionTypeImpl extends TypeImpl implements FunctionType { /** * Return {@code true} if all of the name/type pairs in the first map are equal to the * corresponding name/type pairs in the second map. The maps are expected to iterate over their * entries in the same order in which those entries were added to the map. * * @param firstTypes the first map of name/type pairs being compared * @param secondTypes the second map of name/type pairs being compared * @param visitedElementPairs a set of visited element pairs * @return {@code true} if all of the name/type pairs in the first map are equal to the * corresponding name/type pairs in the second map */ private static boolean equals(Map<String, Type> firstTypes, Map<String, Type> secondTypes, Set<ElementPair> visitedElementPairs) { if (secondTypes.size() != firstTypes.size()) { return false; } Iterator<Map.Entry<String, Type>> firstIterator = firstTypes.entrySet().iterator(); Iterator<Map.Entry<String, Type>> secondIterator = secondTypes.entrySet().iterator(); while (firstIterator.hasNext()) { Map.Entry<String, Type> firstEntry = firstIterator.next(); Map.Entry<String, Type> secondEntry = secondIterator.next(); if (!firstEntry.getKey().equals(secondEntry.getKey()) || !((TypeImpl) firstEntry.getValue()).internalEquals( secondEntry.getValue(), visitedElementPairs)) { return false; } } return true; } /** * An array containing the actual types of the type arguments. */ private Type[] typeArguments = TypeImpl.EMPTY_ARRAY; /** * Initialize a newly created function type to be declared by the given element and to have the * given name. * * @param element the element representing the declaration of the function type */ public FunctionTypeImpl(ExecutableElement element) { super(element, null); } /** * Initialize a newly created function type to be declared by the given element and to have the * given name. * * @param element the element representing the declaration of the function type */ public FunctionTypeImpl(FunctionTypeAliasElement element) { // Here we use [element.getName()] as [name] to avoid infinite loops in [getDisplayName] // when displaying self-referential typedefs. super(element, element == null ? null : element.getName()); } @Override public boolean equals(Object object) { return internalEquals(object, new HashSet<ElementPair>()); } @Override public String getDisplayName() { String name = getName(); if (name == null || name.length() == 0) { // Function types have an empty name when they are defined implicitly by either a closure or // as part of a parameter declaration. Type[] normalParameterTypes = getNormalParameterTypes(); Type[] optionalParameterTypes = getOptionalParameterTypes(); Map<String, Type> namedParameterTypes = getNamedParameterTypes(); Type returnType = getReturnType(); StringBuilder builder = new StringBuilder(); builder.append("("); boolean needsComma = false; if (normalParameterTypes.length > 0) { for (Type type : normalParameterTypes) { if (needsComma) { builder.append(", "); } else { needsComma = true; } builder.append(type.getDisplayName()); } } if (optionalParameterTypes.length > 0) { if (needsComma) { builder.append(", "); needsComma = false; } builder.append("["); for (Type type : optionalParameterTypes) { if (needsComma) { builder.append(", "); } else { needsComma = true; } builder.append(type.getDisplayName()); } builder.append("]"); needsComma = true; } if (namedParameterTypes.size() > 0) { if (needsComma) { builder.append(", "); needsComma = false; } builder.append("{"); for (Map.Entry<String, Type> entry : namedParameterTypes.entrySet()) { if (needsComma) { builder.append(", "); } else { needsComma = true; } builder.append(entry.getKey()); builder.append(": "); builder.append(entry.getValue().getDisplayName()); } builder.append("}"); needsComma = true; } builder.append(")"); builder.append(Element.RIGHT_ARROW); if (returnType == null) { builder.append("null"); } else { builder.append(returnType.getDisplayName()); } name = builder.toString(); } return name; } @Override public Map<String, Type> getNamedParameterTypes() { LinkedHashMap<String, Type> namedParameterTypes = new LinkedHashMap<String, Type>(); ParameterElement[] parameters = getBaseParameters(); if (parameters.length == 0) { return namedParameterTypes; } Type[] typeParameters = TypeParameterTypeImpl.getTypes(getTypeParameters()); for (ParameterElement parameter : parameters) { if (parameter.getParameterKind() == ParameterKind.NAMED) { Type type = parameter.getType(); if (typeArguments.length != 0 && typeArguments.length == typeParameters.length) { type = type.substitute(typeArguments, typeParameters); } namedParameterTypes.put(parameter.getName(), type); } } return namedParameterTypes; } @Override public Type[] getNormalParameterTypes() { ParameterElement[] parameters = getBaseParameters(); if (parameters.length == 0) { return TypeImpl.EMPTY_ARRAY; } Type[] typeParameters = TypeParameterTypeImpl.getTypes(getTypeParameters()); ArrayList<Type> types = new ArrayList<Type>(); for (ParameterElement parameter : parameters) { if (parameter.getParameterKind() == ParameterKind.REQUIRED) { Type type = parameter.getType(); if (typeArguments.length != 0 && typeArguments.length == typeParameters.length) { type = type.substitute(typeArguments, typeParameters); } types.add(type); } } return types.toArray(new Type[types.size()]); } @Override public Type[] getOptionalParameterTypes() { ParameterElement[] parameters = getBaseParameters(); if (parameters.length == 0) { return TypeImpl.EMPTY_ARRAY; } Type[] typeParameters = TypeParameterTypeImpl.getTypes(getTypeParameters()); ArrayList<Type> types = new ArrayList<Type>(); for (ParameterElement parameter : parameters) { if (parameter.getParameterKind() == ParameterKind.POSITIONAL) { Type type = parameter.getType(); if (typeArguments.length != 0 && typeArguments.length == typeParameters.length) { type = type.substitute(typeArguments, typeParameters); } types.add(type); } } return types.toArray(new Type[types.size()]); } @Override public ParameterElement[] getParameters() { ParameterElement[] baseParameters = getBaseParameters(); // no parameters, quick return int parameterCount = baseParameters.length; if (parameterCount == 0) { return baseParameters; } // create specialized parameters ParameterElement[] specializedParameters = new ParameterElement[parameterCount]; for (int i = 0; i < parameterCount; i++) { specializedParameters[i] = ParameterMember.from(baseParameters[i], this); } return specializedParameters; } @Override public Type getReturnType() { Type baseReturnType = getBaseReturnType(); if (baseReturnType == null) { // TODO(brianwilkerson) This is a patch. The return type should never be null and we need to // understand why it is and fix it. return DynamicTypeImpl.getInstance(); } // If there are no arguments to substitute, or if the arguments size doesn't match the parameter // size, return the base return type. if (typeArguments.length == 0 || typeArguments.length != getTypeParameters().length) { return baseReturnType; } return baseReturnType.substitute( typeArguments, TypeParameterTypeImpl.getTypes(getTypeParameters())); } @Override public Type[] getTypeArguments() { return typeArguments; } @Override public TypeParameterElement[] getTypeParameters() { Element element = getElement(); if (element instanceof FunctionTypeAliasElement) { return ((FunctionTypeAliasElement) element).getTypeParameters(); } ClassElement definingClass = element.getAncestor(ClassElement.class); if (definingClass != null) { return definingClass.getTypeParameters(); } return TypeParameterElementImpl.EMPTY_ARRAY; } @Override public int hashCode() { if (getElement() == null) { return 0; } // Reference the arrays of parameters Type[] normalParameterTypes = getNormalParameterTypes(); Type[] optionalParameterTypes = getOptionalParameterTypes(); Collection<Type> namedParameterTypes = getNamedParameterTypes().values(); // Generate the hashCode int hashCode = getReturnType().hashCode(); for (int i = 0; i < normalParameterTypes.length; i++) { hashCode = (hashCode << 1) + normalParameterTypes[i].hashCode(); } for (int i = 0; i < optionalParameterTypes.length; i++) { hashCode = (hashCode << 1) + optionalParameterTypes[i].hashCode(); } for (Type type : namedParameterTypes) { hashCode = (hashCode << 1) + type.hashCode(); } return hashCode; } @Override public boolean internalIsMoreSpecificThan(Type type, boolean withDynamic, Set<TypePair> visitedTypePairs) { // trivial base cases if (type == null) { return false; } else if (this == type || type.isDynamic() || type.isDartCoreFunction() || type.isObject()) { return true; } else if ((type instanceof UnionType)) { return ((UnionTypeImpl) type).internalUnionTypeIsLessSpecificThan( this, withDynamic, visitedTypePairs); } else if (!(type instanceof FunctionType)) { return false; } else if (this.equals(type)) { return true; } FunctionType t = this; FunctionType s = (FunctionType) type; Type[] tTypes = t.getNormalParameterTypes(); Type[] tOpTypes = t.getOptionalParameterTypes(); Type[] sTypes = s.getNormalParameterTypes(); Type[] sOpTypes = s.getOptionalParameterTypes(); // If one function has positional and the other has named parameters, return false. if ((sOpTypes.length > 0 && t.getNamedParameterTypes().size() > 0) || (tOpTypes.length > 0 && s.getNamedParameterTypes().size() > 0)) { return false; } // named parameters case if (t.getNamedParameterTypes().size() > 0) { // check that the number of required parameters are equal, and check that every t_i is // more specific than every s_i if (t.getNormalParameterTypes().length != s.getNormalParameterTypes().length) { return false; } else if (t.getNormalParameterTypes().length > 0) { for (int i = 0; i < tTypes.length; i++) { if (!((TypeImpl) tTypes[i]).isMoreSpecificThan(sTypes[i], withDynamic, visitedTypePairs)) { return false; } } } 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 more specific then 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 (!((TypeImpl) typeT).isMoreSpecificThan(entryS.getValue(), withDynamic, visitedTypePairs)) { return false; } } } else if (s.getNamedParameterTypes().size() > 0) { return false; } else { // positional parameter case int tArgLength = tTypes.length + tOpTypes.length; int sArgLength = sTypes.length + sOpTypes.length; // Check that the total number of parameters in t is greater than or equal to the number of // parameters in s and that the number of required parameters in s is greater than or equal to // the number of required parameters in t. if (tArgLength < sArgLength || sTypes.length < tTypes.length) { return false; } if (tOpTypes.length == 0 && sOpTypes.length == 0) { // No positional arguments, don't copy contents to new array for (int i = 0; i < sTypes.length; i++) { if (!((TypeImpl) tTypes[i]).isMoreSpecificThan(sTypes[i], withDynamic, visitedTypePairs)) { return false; } } } else { // Else, we do have positional parameters, copy required and positional parameter types into // arrays to do the compare (for loop below). Type[] tAllTypes = new Type[sArgLength]; for (int i = 0; i < tTypes.length; i++) { tAllTypes[i] = tTypes[i]; } for (int i = tTypes.length, j = 0; i < sArgLength; i++, j++) { tAllTypes[i] = tOpTypes[j]; } Type[] sAllTypes = new Type[sArgLength]; for (int i = 0; i < sTypes.length; i++) { sAllTypes[i] = sTypes[i]; } for (int i = sTypes.length, j = 0; i < sArgLength; i++, j++) { sAllTypes[i] = sOpTypes[j]; } for (int i = 0; i < sAllTypes.length; i++) { if (!((TypeImpl) tAllTypes[i]).isMoreSpecificThan( sAllTypes[i], withDynamic, visitedTypePairs)) { return false; } } } } Type tRetType = t.getReturnType(); Type sRetType = s.getReturnType(); return sRetType.isVoid() || ((TypeImpl) tRetType).isMoreSpecificThan(sRetType, withDynamic, visitedTypePairs); } /** * Return {@code true} if this type is assignable to the given type. A function type <i>T</i> may * be assigned to a function type <i>S</i>, written <i>T</i> ⇔ <i>S</i>, iff <i>T</i> <: * <i>S</i> (Function Types section of spec). Note that this is more restrictive than the * "may be assigned to" rule for interface types. * <p> * * @param type the type being compared with this type * @return {@code true} if this type is assignable to the given type */ @Override public boolean isAssignableTo(Type type) { return isSubtypeOf(type, new HashSet<TypePair>()); } /** * Set the actual types of the type arguments to the given types. * * @param typeArguments the actual types of the type arguments */ public void setTypeArguments(Type[] typeArguments) { this.typeArguments = typeArguments; } @Override public FunctionTypeImpl substitute(Type[] argumentTypes) { return substitute(argumentTypes, getTypeArguments()); } @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.setTypeArguments(substitute(typeArguments, argumentTypes, parameterTypes)); return newType; } @Override protected void appendTo(StringBuilder builder) { Type[] normalParameterTypes = getNormalParameterTypes(); Type[] optionalParameterTypes = getOptionalParameterTypes(); Map<String, Type> namedParameterTypes = getNamedParameterTypes(); Type returnType = getReturnType(); builder.append("("); boolean needsComma = false; if (normalParameterTypes.length > 0) { for (Type type : normalParameterTypes) { if (needsComma) { builder.append(", "); } else { needsComma = true; } ((TypeImpl) type).appendTo(builder); } } if (optionalParameterTypes.length > 0) { if (needsComma) { builder.append(", "); needsComma = false; } builder.append("["); for (Type type : optionalParameterTypes) { if (needsComma) { builder.append(", "); } else { needsComma = true; } ((TypeImpl) type).appendTo(builder); } builder.append("]"); needsComma = true; } if (namedParameterTypes.size() > 0) { if (needsComma) { builder.append(", "); needsComma = false; } builder.append("{"); for (Map.Entry<String, Type> entry : namedParameterTypes.entrySet()) { if (needsComma) { builder.append(", "); } else { needsComma = true; } builder.append(entry.getKey()); builder.append(": "); ((TypeImpl) entry.getValue()).appendTo(builder); } builder.append("}"); needsComma = true; } builder.append(")"); builder.append(Element.RIGHT_ARROW); if (returnType == null) { builder.append("null"); } else { ((TypeImpl) returnType).appendTo(builder); } } /** * @return the base parameter elements of this function element, not {@code null}. */ protected ParameterElement[] getBaseParameters() { Element element = getElement(); if (element instanceof ExecutableElement) { return ((ExecutableElement) element).getParameters(); } else { return ((FunctionTypeAliasElement) element).getParameters(); } } @Override protected boolean internalEquals(Object object, Set<ElementPair> visitedElementPairs) { if (!(object instanceof FunctionTypeImpl)) { return false; } FunctionTypeImpl otherType = (FunctionTypeImpl) object; // If the visitedTypePairs already has the pair (this, type), use the elements to determine equality ElementPair elementPair = new ElementPair(getElement(), otherType.getElement()); if (!visitedElementPairs.add(elementPair)) { return elementPair.getFirstElt().equals(elementPair.getSecondElt()); } // Compute the result boolean result = TypeImpl.equalArrays( getNormalParameterTypes(), otherType.getNormalParameterTypes(), visitedElementPairs) && TypeImpl.equalArrays( getOptionalParameterTypes(), otherType.getOptionalParameterTypes(), visitedElementPairs) && equals(getNamedParameterTypes(), otherType.getNamedParameterTypes(), visitedElementPairs) && ((TypeImpl) getReturnType()).internalEquals( otherType.getReturnType(), visitedElementPairs); // Remove the pair from our visited pairs list visitedElementPairs.remove(elementPair); // Return the result return result; } @Override protected boolean internalIsSubtypeOf(Type type, Set<TypePair> visitedTypePairs) { // trivial base cases if (type == null) { return false; } else if (this == type || type.isDynamic() || type.isDartCoreFunction() || type.isObject()) { return true; } else if (type instanceof UnionType) { return ((UnionTypeImpl) type).internalUnionTypeIsSuperTypeOf(this, visitedTypePairs); } else if (!(type instanceof FunctionType)) { return false; } else if (this.equals(type)) { return true; } FunctionType t = this; FunctionType s = (FunctionType) type; Type[] tTypes = t.getNormalParameterTypes(); Type[] tOpTypes = t.getOptionalParameterTypes(); Type[] sTypes = s.getNormalParameterTypes(); Type[] sOpTypes = s.getOptionalParameterTypes(); // If one function has positional and the other has named parameters, return false. if ((sOpTypes.length > 0 && t.getNamedParameterTypes().size() > 0) || (tOpTypes.length > 0 && s.getNamedParameterTypes().size() > 0)) { return false; } // named parameters case if (t.getNamedParameterTypes().size() > 0) { // check that the number of required parameters are equal, and check that every t_i is // assignable to every s_i if (t.getNormalParameterTypes().length != s.getNormalParameterTypes().length) { return false; } else if (t.getNormalParameterTypes().length > 0) { for (int i = 0; i < tTypes.length; i++) { if (!((TypeImpl) tTypes[i]).isAssignableTo(sTypes[i], visitedTypePairs)) { return false; } } } 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 (!((TypeImpl) typeT).isAssignableTo(entryS.getValue(), visitedTypePairs)) { return false; } } } else if (s.getNamedParameterTypes().size() > 0) { return false; } else { // positional parameter case int tArgLength = tTypes.length + tOpTypes.length; int sArgLength = sTypes.length + sOpTypes.length; // Check that the total number of parameters in t is greater than or equal to the number of // parameters in s and that the number of required parameters in s is greater than or equal to // the number of required parameters in t. if (tArgLength < sArgLength || sTypes.length < tTypes.length) { return false; } if (tOpTypes.length == 0 && sOpTypes.length == 0) { // No positional arguments, don't copy contents to new array for (int i = 0; i < sTypes.length; i++) { if (!((TypeImpl) tTypes[i]).isAssignableTo(sTypes[i], visitedTypePairs)) { return false; } } } else { // Else, we do have positional parameters, copy required and positional parameter types into // arrays to do the compare (for loop below). Type[] tAllTypes = new Type[sArgLength]; for (int i = 0; i < tTypes.length; i++) { tAllTypes[i] = tTypes[i]; } for (int i = tTypes.length, j = 0; i < sArgLength; i++, j++) { tAllTypes[i] = tOpTypes[j]; } Type[] sAllTypes = new Type[sArgLength]; for (int i = 0; i < sTypes.length; i++) { sAllTypes[i] = sTypes[i]; } for (int i = sTypes.length, j = 0; i < sArgLength; i++, j++) { sAllTypes[i] = sOpTypes[j]; } for (int i = 0; i < sAllTypes.length; i++) { if (!((TypeImpl) tAllTypes[i]).isAssignableTo(sAllTypes[i], visitedTypePairs)) { return false; } } } } Type tRetType = t.getReturnType(); Type sRetType = s.getReturnType(); return sRetType.isVoid() || ((TypeImpl) tRetType).isAssignableTo(sRetType, visitedTypePairs); } /** * Return the return type defined by this function's element. * * @return the return type defined by this function's element */ private Type getBaseReturnType() { Element element = getElement(); if (element instanceof ExecutableElement) { return ((ExecutableElement) element).getReturnType(); } else { return ((FunctionTypeAliasElement) element).getReturnType(); } } }