/* * Copyright (c) 2013, 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.resolver; import com.google.dart.engine.ast.SimpleIdentifier; import com.google.dart.engine.element.ClassElement; import com.google.dart.engine.element.ExecutableElement; import com.google.dart.engine.element.LibraryElement; import com.google.dart.engine.element.MethodElement; import com.google.dart.engine.element.MultiplyInheritedExecutableElement; import com.google.dart.engine.element.ParameterElement; import com.google.dart.engine.element.PropertyAccessorElement; import com.google.dart.engine.error.AnalysisError; import com.google.dart.engine.error.ErrorCode; import com.google.dart.engine.error.StaticTypeWarningCode; import com.google.dart.engine.error.StaticWarningCode; import com.google.dart.engine.internal.element.ExecutableElementImpl; import com.google.dart.engine.internal.element.MultiplyInheritedMethodElementImpl; import com.google.dart.engine.internal.element.MultiplyInheritedPropertyAccessorElementImpl; import com.google.dart.engine.internal.element.ParameterElementImpl; import com.google.dart.engine.internal.element.member.MethodMember; import com.google.dart.engine.internal.element.member.PropertyAccessorMember; import com.google.dart.engine.internal.type.DynamicTypeImpl; import com.google.dart.engine.internal.type.FunctionTypeImpl; import com.google.dart.engine.internal.verifier.ErrorVerifier; import com.google.dart.engine.scanner.StringToken; import com.google.dart.engine.scanner.TokenType; import com.google.dart.engine.type.FunctionType; import com.google.dart.engine.type.InterfaceType; import com.google.dart.engine.type.Type; import com.google.dart.engine.utilities.dart.ParameterKind; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map.Entry; import java.util.Set; /** * Instances of the class {@code InheritanceManager} manage the knowledge of where class members * (methods, getters & setters) are inherited from. * * @coverage dart.engine.resolver */ public class InheritanceManager { /** * Given some array of {@link ExecutableElement}s, this method creates a synthetic element as * described in 8.1.1: * <p> * Let <i>numberOfPositionals</i>(<i>f</i>) denote the number of positional parameters of a * function <i>f</i>, and let <i>numberOfRequiredParams</i>(<i>f</i>) denote the number of * required parameters of a function <i>f</i>. Furthermore, let <i>s</i> denote the set of all * named parameters of the <i>m<sub>1</sub>, …, m<sub>k</sub></i>. Then let * <ul> * <li><i>h = max(numberOfPositionals(m<sub>i</sub>)),</i></li> * <li><i>r = min(numberOfRequiredParams(m<sub>i</sub>)), for all <i>i</i>, 1 <= i <= k.</i></li> * </ul> * Then <i>I</i> has a method named <i>n</i>, with <i>r</i> required parameters of type * <b>dynamic</b>, <i>h</i> positional parameters of type <b>dynamic</b>, named parameters * <i>s</i> of type <b>dynamic</b> and return type <b>dynamic</b>. * <p> */ // TODO (jwren) Associate a propagated type to the synthetic method element using least upper // bounds instead of dynamic // // TODO (collinsn) @jwren's above TODO could maybe be addressed using the union-type method merge // code I'm adding: [ElementResolver.computeMergedExecutableElement]. // The difference is that I don't plan to handle unioning of methods with // different shapes very well: I'm just going to fall back to [dynamic] in that case. private static ExecutableElement computeMergedExecutableElement( ExecutableElement[] elementArrayToMerge) { int h = getNumOfPositionalParameters(elementArrayToMerge[0]); int r = getNumOfRequiredParameters(elementArrayToMerge[0]); Set<String> namedParametersList = new HashSet<String>(); for (int i = 1; i < elementArrayToMerge.length; i++) { ExecutableElement element = elementArrayToMerge[i]; int numOfPositionalParams = getNumOfPositionalParameters(element); if (h < numOfPositionalParams) { h = numOfPositionalParams; } int numOfRequiredParams = getNumOfRequiredParameters(element); if (r > numOfRequiredParams) { r = numOfRequiredParams; } namedParametersList.addAll(getNamedParameterNames(element)); } return createSyntheticExecutableElement( elementArrayToMerge, elementArrayToMerge[0].getDisplayName(), r, h - r, namedParametersList.toArray(new String[namedParametersList.size()])); } /** * Used by {@link #computeMergedExecutableElement(ExecutableElement[])} to actually create the * synthetic element. * * @param elementArrayToMerge the array used to create the synthetic element * @param name the name of the method, getter or setter * @param numOfRequiredParameters the number of required parameters * @param numOfPositionalParameters the number of positional parameters * @param namedParameters the list of {@link String}s that are the named parameters * @return the created synthetic element */ private static ExecutableElement createSyntheticExecutableElement( ExecutableElement[] elementArrayToMerge, String name, int numOfRequiredParameters, int numOfPositionalParameters, String... namedParameters) { DynamicTypeImpl dynamicType = DynamicTypeImpl.getInstance(); SimpleIdentifier nameIdentifier = new SimpleIdentifier(new StringToken( TokenType.IDENTIFIER, name, 0)); ExecutableElementImpl executable; if (elementArrayToMerge[0] instanceof MethodElement) { MultiplyInheritedMethodElementImpl unionedMethod = new MultiplyInheritedMethodElementImpl( nameIdentifier); unionedMethod.setInheritedElements(elementArrayToMerge); executable = unionedMethod; } else { MultiplyInheritedPropertyAccessorElementImpl unionedPropertyAccessor = new MultiplyInheritedPropertyAccessorElementImpl( nameIdentifier); unionedPropertyAccessor.setGetter(((PropertyAccessorElement) elementArrayToMerge[0]).isGetter()); unionedPropertyAccessor.setSetter(((PropertyAccessorElement) elementArrayToMerge[0]).isSetter()); unionedPropertyAccessor.setInheritedElements(elementArrayToMerge); executable = unionedPropertyAccessor; } int numOfParameters = numOfRequiredParameters + numOfPositionalParameters + namedParameters.length; ParameterElement[] parameters = new ParameterElement[numOfParameters]; int i = 0; for (int j = 0; j < numOfRequiredParameters; j++, i++) { ParameterElementImpl parameter = new ParameterElementImpl("", 0); parameter.setType(dynamicType); parameter.setParameterKind(ParameterKind.REQUIRED); parameters[i] = parameter; } for (int k = 0; k < numOfPositionalParameters; k++, i++) { ParameterElementImpl parameter = new ParameterElementImpl("", 0); parameter.setType(dynamicType); parameter.setParameterKind(ParameterKind.POSITIONAL); parameters[i] = parameter; } for (int m = 0; m < namedParameters.length; m++, i++) { ParameterElementImpl parameter = new ParameterElementImpl(namedParameters[m], 0); parameter.setType(dynamicType); parameter.setParameterKind(ParameterKind.NAMED); parameters[i] = parameter; } executable.setReturnType(dynamicType); executable.setParameters(parameters); FunctionTypeImpl methodType = new FunctionTypeImpl(executable); executable.setType(methodType); return executable; } /** * Given some {@link ExecutableElement}, return the list of named parameters. */ private static List<String> getNamedParameterNames(ExecutableElement executableElement) { ArrayList<String> namedParameterNames = new ArrayList<String>(); ParameterElement[] parameters = executableElement.getParameters(); for (int i = 0; i < parameters.length; i++) { ParameterElement parameterElement = parameters[i]; if (parameterElement.getParameterKind() == ParameterKind.NAMED) { namedParameterNames.add(parameterElement.getName()); } } return namedParameterNames; } /** * Given some {@link ExecutableElement} return the number of parameters of the specified kind. */ private static int getNumOfParameters(ExecutableElement executableElement, ParameterKind parameterKind) { int parameterCount = 0; ParameterElement[] parameters = executableElement.getParameters(); for (int i = 0; i < parameters.length; i++) { ParameterElement parameterElement = parameters[i]; if (parameterElement.getParameterKind() == parameterKind) { parameterCount++; } } return parameterCount; } /** * Given some {@link ExecutableElement} return the number of positional parameters. * <p> * Note: by positional we mean {@link ParameterKind#REQUIRED} or {@link ParameterKind#POSITIONAL}. */ private static int getNumOfPositionalParameters(ExecutableElement executableElement) { // For this portion of the spec "positional" references both the required and the positional // parameters. return getNumOfParameters(executableElement, ParameterKind.REQUIRED) + getNumOfParameters(executableElement, ParameterKind.POSITIONAL); } /** * Given some {@link ExecutableElement} return the number of required parameters. */ private static int getNumOfRequiredParameters(ExecutableElement executableElement) { return getNumOfParameters(executableElement, ParameterKind.REQUIRED); } /** * Given some {@link ExecutableElement} returns {@code true} if it is an abstract member of a * class. * * @param executableElement some {@link ExecutableElement} to evaluate * @return {@code true} if the given element is an abstract member of a class */ private static boolean isAbstract(ExecutableElement executableElement) { if (executableElement instanceof MethodElement) { return ((MethodElement) executableElement).isAbstract(); } else if (executableElement instanceof PropertyAccessorElement) { return ((PropertyAccessorElement) executableElement).isAbstract(); } return false; } /** * The {@link LibraryElement} that is managed by this manager. */ private LibraryElement library; /** * This is a mapping between each {@link ClassElement} and a map between the {@link String} member * names and the associated {@link ExecutableElement} in the mixin and superclass chain. */ private HashMap<ClassElement, MemberMap> classLookup; /** * This is a mapping between each {@link ClassElement} and a map between the {@link String} member * names and the associated {@link ExecutableElement} in the interface set. */ private HashMap<ClassElement, MemberMap> interfaceLookup; /** * A map between each visited {@link ClassElement} and the set of {@link AnalysisError}s found on * the class element. */ private HashMap<ClassElement, HashSet<AnalysisError>> errorsInClassElement = new HashMap<ClassElement, HashSet<AnalysisError>>(); /** * Initialize a newly created inheritance manager. * * @param library the library element context that the inheritance mappings are being generated */ public InheritanceManager(LibraryElement library) { this.library = library; classLookup = new HashMap<ClassElement, MemberMap>(); interfaceLookup = new HashMap<ClassElement, MemberMap>(); } /** * Return the set of {@link AnalysisError}s found on the passed {@link ClassElement}, or * {@code null} if there are none. * * @param classElt the class element to query * @return the set of {@link AnalysisError}s found on the passed {@link ClassElement}, or * {@code null} if there are none */ public HashSet<AnalysisError> getErrors(ClassElement classElt) { return errorsInClassElement.get(classElt); } /** * Get and return a mapping between the set of all string names of the members inherited from the * passed {@link ClassElement} superclass hierarchy, and the associated {@link ExecutableElement}. * * @param classElt the class element to query * @return a mapping between the set of all members inherited from the passed {@link ClassElement} * superclass hierarchy, and the associated {@link ExecutableElement} */ public MemberMap getMapOfMembersInheritedFromClasses(ClassElement classElt) { return computeClassChainLookupMap(classElt, new HashSet<ClassElement>()); } /** * Get and return a mapping between the set of all string names of the members inherited from the * passed {@link ClassElement} interface hierarchy, and the associated {@link ExecutableElement}. * * @param classElt the class element to query * @return a mapping between the set of all string names of the members inherited from the passed * {@link ClassElement} interface hierarchy, and the associated {@link ExecutableElement}. */ public MemberMap getMapOfMembersInheritedFromInterfaces(ClassElement classElt) { return computeInterfaceLookupMap(classElt, new HashSet<ClassElement>()); } /** * Given some {@link ClassElement class element} and some member name, this returns the * {@link ExecutableElement executable element} that the class inherits from the mixins, * superclasses or interfaces, that has the member name, if no member is inherited {@code null} is * returned. * * @param classElt the class element to query * @param memberName the name of the executable element to find and return * @return the inherited executable element with the member name, or {@code null} if no such * member exists */ public ExecutableElement lookupInheritance(ClassElement classElt, String memberName) { if (memberName == null || memberName.isEmpty()) { return null; } ExecutableElement executable = computeClassChainLookupMap(classElt, new HashSet<ClassElement>()).get( memberName); if (executable == null) { return computeInterfaceLookupMap(classElt, new HashSet<ClassElement>()).get(memberName); } return executable; } /** * Given some {@link ClassElement class element} and some member name, this returns the * {@link ExecutableElement executable element} that the class either declares itself, or * inherits, that has the member name, if no member is inherited {@code null} is returned. * * @param classElt the class element to query * @param memberName the name of the executable element to find and return * @return the inherited executable element with the member name, or {@code null} if no such * member exists */ public ExecutableElement lookupMember(ClassElement classElt, String memberName) { ExecutableElement element = lookupMemberInClass(classElt, memberName); if (element != null) { return element; } return lookupInheritance(classElt, memberName); } /** * Given some {@link InterfaceType interface type} and some member name, this returns the * {@link FunctionType function type} of the {@link ExecutableElement executable element} that the * class either declares itself, or inherits, that has the member name, if no member is inherited * {@code null} is returned. The returned {@link FunctionType function type} has all type * parameters substituted with corresponding type arguments from the given {@link InterfaceType}. * * @param interfaceType the interface type to query * @param memberName the name of the executable element to find and return * @return the member's function type, or {@code null} if no such member exists */ public FunctionType lookupMemberType(InterfaceType interfaceType, String memberName) { ExecutableElement iteratorMember = lookupMember(interfaceType.getElement(), memberName); if (iteratorMember == null) { return null; } return substituteTypeArgumentsInMemberFromInheritance( iteratorMember.getType(), memberName, interfaceType); } /** * Determine the set of methods which is overridden by the given class member. If no member is * inherited, an empty list is returned. If one of the inherited members is a * {@link MultiplyInheritedExecutableElement}, then it is expanded into its constituent inherited * elements. * * @param classElt the class to query * @param memberName the name of the class member to query * @return a list of overridden methods */ public ArrayList<ExecutableElement> lookupOverrides(ClassElement classElt, String memberName) { ArrayList<ExecutableElement> result = new ArrayList<ExecutableElement>(); if (memberName == null || memberName.isEmpty()) { return result; } ArrayList<MemberMap> interfaceMaps = gatherInterfaceLookupMaps( classElt, new HashSet<ClassElement>()); if (interfaceMaps != null) { for (MemberMap interfaceMap : interfaceMaps) { ExecutableElement overriddenElement = interfaceMap.get(memberName); if (overriddenElement != null) { if (overriddenElement instanceof MultiplyInheritedExecutableElement) { MultiplyInheritedExecutableElement multiplyInheritedElement = (MultiplyInheritedExecutableElement) overriddenElement; for (ExecutableElement element : multiplyInheritedElement.getInheritedElements()) { result.add(element); } } else { result.add(overriddenElement); } } } } return result; } /** * Set the new library element context. * * @param library the new library element */ public void setLibraryElement(LibraryElement library) { this.library = library; } /** * This method takes some inherited {@link FunctionType}, and resolves all the parameterized types * in the function type, dependent on the class in which it is being overridden. * * @param baseFunctionType the function type that is being overridden * @param memberName the name of the member, this is used to lookup the inheritance path of the * override * @param definingType the type that is overriding the member * @return the passed function type with any parameterized types substituted */ public FunctionType substituteTypeArgumentsInMemberFromInheritance(FunctionType baseFunctionType, String memberName, InterfaceType definingType) { // if the baseFunctionType is null, or does not have any parameters, return it. if (baseFunctionType == null || baseFunctionType.getTypeArguments().length == 0) { return baseFunctionType; } // First, generate the path from the defining type to the overridden member LinkedList<InterfaceType> inheritancePath = new LinkedList<InterfaceType>(); computeInheritancePath(inheritancePath, definingType, memberName); if (inheritancePath == null || inheritancePath.isEmpty()) { // TODO(jwren) log analysis engine error return baseFunctionType; } FunctionType functionTypeToReturn = baseFunctionType; // loop backward through the list substituting as we go: while (!inheritancePath.isEmpty()) { InterfaceType lastType = inheritancePath.removeLast(); Type[] parameterTypes = lastType.getElement().getType().getTypeArguments(); Type[] argumentTypes = lastType.getTypeArguments(); functionTypeToReturn = functionTypeToReturn.substitute(argumentTypes, parameterTypes); } return functionTypeToReturn; } /** * Compute and return a mapping between the set of all string names of the members inherited from * the passed {@link ClassElement} superclass hierarchy, and the associated * {@link ExecutableElement}. * * @param classElt the class element to query * @param visitedClasses a set of visited classes passed back into this method when it calls * itself recursively * @return a mapping between the set of all string names of the members inherited from the passed * {@link ClassElement} superclass hierarchy, and the associated {@link ExecutableElement} */ private MemberMap computeClassChainLookupMap(ClassElement classElt, HashSet<ClassElement> visitedClasses) { MemberMap resultMap = classLookup.get(classElt); if (resultMap != null) { return resultMap; } else { resultMap = new MemberMap(); } ClassElement superclassElt = null; InterfaceType supertype = classElt.getSupertype(); if (supertype != null) { superclassElt = supertype.getElement(); } else { // classElt is Object classLookup.put(classElt, resultMap); return resultMap; } if (superclassElt != null) { if (!visitedClasses.contains(superclassElt)) { visitedClasses.add(superclassElt); try { resultMap = new MemberMap(computeClassChainLookupMap(superclassElt, visitedClasses)); // // Substitute the super types down the hierarchy. // substituteTypeParametersDownHierarchy(supertype, resultMap); // // Include the members from the superclass in the resultMap. // recordMapWithClassMembers(resultMap, supertype, false); } finally { visitedClasses.remove(superclassElt); } } else { // This case happens only when the superclass was previously visited and not in the lookup, // meaning this is meant to shorten the compute for recursive cases. classLookup.put(superclassElt, resultMap); return resultMap; } } // // Include the members from the mixins in the resultMap // InterfaceType[] mixins = classElt.getMixins(); for (int i = mixins.length - 1; i >= 0; i--) { ClassElement mixinElement = mixins[i].getElement(); if (mixinElement != null) { if (!visitedClasses.contains(mixinElement)) { visitedClasses.add(mixinElement); try { MemberMap map = new MemberMap(computeClassChainLookupMap(mixinElement, visitedClasses)); // // Substitute the super types down the hierarchy. // substituteTypeParametersDownHierarchy(mixins[i], map); // // Include the members from the superclass in the resultMap. // recordMapWithClassMembers(map, mixins[i], false); // // Add the members from map into result map. // for (int j = 0; j < map.getSize(); j++) { String key = map.getKey(j); ExecutableElement value = map.getValue(j); if (key != null) { if (resultMap.get(key) == null || (resultMap.get(key) != null && !isAbstract(value))) { resultMap.put(key, value); } } } } finally { visitedClasses.remove(mixinElement); } } else { // This case happens only when the superclass was previously visited and not in the lookup, // meaning this is meant to shorten the compute for recursive cases. classLookup.put(mixinElement, resultMap); return resultMap; } } } classLookup.put(classElt, resultMap); return resultMap; } /** * Compute and return the inheritance path given the context of a type and a member that is * overridden in the inheritance path (for which the type is in the path). * * @param chain the inheritance path that is built up as this method calls itself recursively, * when this method is called an empty {@link LinkedList} should be provided * @param currentType the current type in the inheritance path * @param memberName the name of the member that is being looked up the inheritance path */ private void computeInheritancePath(LinkedList<InterfaceType> chain, InterfaceType currentType, String memberName) { // TODO (jwren) create a public version of this method which doesn't require the initial chain // to be provided, then provided tests for this functionality in InheritanceManagerTest chain.add(currentType); ClassElement classElt = currentType.getElement(); InterfaceType supertype = classElt.getSupertype(); // Base case- reached Object if (supertype == null) { // Looked up the chain all the way to Object, return null. // This should never happen. return; } // If we are done, return the chain // We are not done if this is the first recursive call on this method. if (chain.size() != 1) { // We are done however if the member is in this classElt if (lookupMemberInClass(classElt, memberName) != null) { return; } } // Otherwise, determine the next type (up the inheritance graph) to search for our member, start // with the mixins, followed by the superclass, and finally the interfaces: // Mixins- note that mixins call lookupMemberInClass, not lookupMember InterfaceType[] mixins = classElt.getMixins(); for (int i = mixins.length - 1; i >= 0; i--) { ClassElement mixinElement = mixins[i].getElement(); if (mixinElement != null) { ExecutableElement elt = lookupMemberInClass(mixinElement, memberName); if (elt != null) { // this is equivalent (but faster than) calling this method recursively // (return computeInheritancePath(chain, mixins[i], memberName);) chain.add(mixins[i]); return; } } } // Superclass ClassElement superclassElt = supertype.getElement(); if (lookupMember(superclassElt, memberName) != null) { computeInheritancePath(chain, supertype, memberName); return; } // Interfaces InterfaceType[] interfaces = classElt.getInterfaces(); for (InterfaceType interfaceType : interfaces) { ClassElement interfaceElement = interfaceType.getElement(); if (interfaceElement != null && lookupMember(interfaceElement, memberName) != null) { computeInheritancePath(chain, interfaceType, memberName); return; } } } /** * Compute and return a mapping between the set of all string names of the members inherited from * the passed {@link ClassElement} interface hierarchy, and the associated * {@link ExecutableElement}. * * @param classElt the class element to query * @param visitedInterfaces a set of visited classes passed back into this method when it calls * itself recursively * @return a mapping between the set of all string names of the members inherited from the passed * {@link ClassElement} interface hierarchy, and the associated {@link ExecutableElement} */ private MemberMap computeInterfaceLookupMap(ClassElement classElt, HashSet<ClassElement> visitedInterfaces) { MemberMap resultMap = interfaceLookup.get(classElt); if (resultMap != null) { return resultMap; } ArrayList<MemberMap> lookupMaps = gatherInterfaceLookupMaps(classElt, visitedInterfaces); if (lookupMaps == null) { resultMap = new MemberMap(); } else { HashMap<String, ArrayList<ExecutableElement>> unionMap = unionInterfaceLookupMaps(lookupMaps); resultMap = resolveInheritanceLookup(classElt, unionMap); } interfaceLookup.put(classElt, resultMap); return resultMap; } /** * Collect a list of interface lookup maps whose elements correspond to all of the classes * directly above {@link classElt} in the class hierarchy (the direct superclass if any, all * mixins, and all direct superinterfaces). Each item in the list is the interface lookup map * returned by {@link computeInterfaceLookupMap} for the corresponding super, except with type * parameters appropriately substituted. * * @param classElt the class element to query * @param visitedInterfaces a set of visited classes passed back into this method when it calls * itself recursively * @return {@code null} if there was a problem (such as a loop in the class hierarchy) or if there * are no classes above this one in the class hierarchy. Otherwise, a list of interface * lookup maps. */ private ArrayList<MemberMap> gatherInterfaceLookupMaps(ClassElement classElt, HashSet<ClassElement> visitedInterfaces) { InterfaceType supertype = classElt.getSupertype(); ClassElement superclassElement = supertype != null ? supertype.getElement() : null; InterfaceType[] mixins = classElt.getMixins(); InterfaceType[] interfaces = classElt.getInterfaces(); // Recursively collect the list of mappings from all of the interface types ArrayList<MemberMap> lookupMaps = new ArrayList<MemberMap>(interfaces.length + mixins.length + 1); // // Superclass element // if (superclassElement != null) { if (!visitedInterfaces.contains(superclassElement)) { try { visitedInterfaces.add(superclassElement); // // Recursively compute the map for the super type. // MemberMap map = computeInterfaceLookupMap(superclassElement, visitedInterfaces); map = new MemberMap(map); // // Substitute the super type down the hierarchy. // substituteTypeParametersDownHierarchy(supertype, map); // // Add any members from the super type into the map as well. // recordMapWithClassMembers(map, supertype, true); lookupMaps.add(map); } finally { visitedInterfaces.remove(superclassElement); } } else { return null; } } // // Mixin elements // for (int i = mixins.length - 1; i >= 0; i--) { InterfaceType mixinType = mixins[i]; ClassElement mixinElement = mixinType.getElement(); if (mixinElement != null) { if (!visitedInterfaces.contains(mixinElement)) { try { visitedInterfaces.add(mixinElement); // // Recursively compute the map for the mixin. // MemberMap map = computeInterfaceLookupMap(mixinElement, visitedInterfaces); map = new MemberMap(map); // // Substitute the mixin type down the hierarchy. // substituteTypeParametersDownHierarchy(mixinType, map); // // Add any members from the mixin type into the map as well. // recordMapWithClassMembers(map, mixinType, true); lookupMaps.add(map); } finally { visitedInterfaces.remove(mixinElement); } } else { return null; } } } // // Interface elements // for (InterfaceType interfaceType : interfaces) { ClassElement interfaceElement = interfaceType.getElement(); if (interfaceElement != null) { if (!visitedInterfaces.contains(interfaceElement)) { try { visitedInterfaces.add(interfaceElement); // // Recursively compute the map for the interfaces. // MemberMap map = computeInterfaceLookupMap(interfaceElement, visitedInterfaces); map = new MemberMap(map); // // Substitute the supertypes down the hierarchy // substituteTypeParametersDownHierarchy(interfaceType, map); // // And add any members from the interface into the map as well. // recordMapWithClassMembers(map, interfaceType, true); lookupMaps.add(map); } finally { visitedInterfaces.remove(interfaceElement); } } else { return null; } } } if (lookupMaps.size() == 0) { return null; } return lookupMaps; } /** * Given some {@link ClassElement}, this method finds and returns the {@link ExecutableElement} of * the passed name in the class element. Static members, members in super types and members not * accessible from the current library are not considered. * * @param classElt the class element to query * @param memberName the name of the member to lookup in the class * @return the found {@link ExecutableElement}, or {@code null} if no such member was found */ private ExecutableElement lookupMemberInClass(ClassElement classElt, String memberName) { MethodElement[] methods = classElt.getMethods(); for (MethodElement method : methods) { if (memberName.equals(method.getName()) && method.isAccessibleIn(library) && !method.isStatic()) { return method; } } PropertyAccessorElement[] accessors = classElt.getAccessors(); for (PropertyAccessorElement accessor : accessors) { if (memberName.equals(accessor.getName()) && accessor.isAccessibleIn(library) && !accessor.isStatic()) { return accessor; } } return null; } /** * Record the passed map with the set of all members (methods, getters and setters) in the type * into the passed map. * * @param map some non-{@code null} map to put the methods and accessors from the passed * {@link ClassElement} into * @param type the type that will be recorded into the passed map * @param doIncludeAbstract {@code true} if abstract members will be put into the map */ private void recordMapWithClassMembers(MemberMap map, InterfaceType type, boolean doIncludeAbstract) { MethodElement[] methods = type.getMethods(); for (MethodElement method : methods) { if (method.isAccessibleIn(library) && !method.isStatic() && (doIncludeAbstract || !method.isAbstract())) { map.put(method.getName(), method); } } PropertyAccessorElement[] accessors = type.getAccessors(); for (PropertyAccessorElement accessor : accessors) { if (accessor.isAccessibleIn(library) && !accessor.isStatic() && (doIncludeAbstract || !accessor.isAbstract())) { map.put(accessor.getName(), accessor); } } } /** * This method is used to report errors on when they are found computing inheritance information. * See {@link ErrorVerifier#checkForInconsistentMethodInheritance()} to see where these generated * error codes are reported back into the analysis engine. * * @param classElt the location of the source for which the exception occurred * @param offset the offset of the location of the error * @param length the length of the location of the error * @param errorCode the error code to be associated with this error * @param arguments the arguments used to build the error message */ private void reportError(ClassElement classElt, int offset, int length, ErrorCode errorCode, Object... arguments) { HashSet<AnalysisError> errorSet = errorsInClassElement.get(classElt); if (errorSet == null) { errorSet = new HashSet<AnalysisError>(); errorsInClassElement.put(classElt, errorSet); } errorSet.add(new AnalysisError(classElt.getSource(), offset, length, errorCode, arguments)); } /** * Given the set of methods defined by classes above {@link classElt} in the class hierarchy, * apply the appropriate inheritance rules to determine those methods inherited by or overridden * by {@link classElt}. Also report static warnings * {@link StaticTypeWarningCode.INCONSISTENT_METHOD_INHERITANCE} and * {@link StaticWarningCode.INCONSISTENT_METHOD_INHERITANCE_GETTER_AND_METHOD} if appropriate. * * @param classElt the class element to query. * @param unionMap a mapping from method name to the set of unique (in terms of signature) methods * defined in superclasses of {@link classElt}. * @return the inheritance lookup map for {@link classElt}. */ private MemberMap resolveInheritanceLookup(ClassElement classElt, HashMap<String, ArrayList<ExecutableElement>> unionMap) { MemberMap resultMap = new MemberMap(); for (Entry<String, ArrayList<ExecutableElement>> entry : unionMap.entrySet()) { String key = entry.getKey(); ArrayList<ExecutableElement> list = entry.getValue(); int numOfEltsWithMatchingNames = list.size(); if (numOfEltsWithMatchingNames == 1) { // // Example: class A inherits only 1 method named 'm'. Since it is the only such method, it // is inherited. // Another example: class A inherits 2 methods named 'm' from 2 different interfaces, but // they both have the same signature, so it is the method inherited. // resultMap.put(key, list.get(0)); } else { // // Then numOfEltsWithMatchingNames > 1, check for the warning cases. // boolean allMethods = true; boolean allSetters = true; boolean allGetters = true; for (ExecutableElement executableElement : list) { if (executableElement instanceof PropertyAccessorElement) { allMethods = false; if (((PropertyAccessorElement) executableElement).isSetter()) { allGetters = false; } else { allSetters = false; } } else { allGetters = false; allSetters = false; } } // // If there isn't a mixture of methods with getters, then continue, otherwise create a // warning. // if (allMethods || allGetters || allSetters) { // // Compute the element whose type is the subtype of all of the other types. // ExecutableElement[] elements = list.toArray(new ExecutableElement[numOfEltsWithMatchingNames]); FunctionType[] executableElementTypes = new FunctionType[numOfEltsWithMatchingNames]; for (int i = 0; i < numOfEltsWithMatchingNames; i++) { executableElementTypes[i] = elements[i].getType(); } ArrayList<Integer> subtypesOfAllOtherTypesIndexes = new ArrayList<Integer>(1); for (int i = 0; i < numOfEltsWithMatchingNames; i++) { FunctionType subtype = executableElementTypes[i]; if (subtype == null) { continue; } boolean subtypeOfAllTypes = true; for (int j = 0; j < numOfEltsWithMatchingNames && subtypeOfAllTypes; j++) { if (i != j) { if (!subtype.isSubtypeOf(executableElementTypes[j])) { subtypeOfAllTypes = false; break; } } } if (subtypeOfAllTypes) { subtypesOfAllOtherTypesIndexes.add(i); } } // // The following is split into three cases determined by the number of elements in subtypesOfAllOtherTypes // if (subtypesOfAllOtherTypesIndexes.size() == 1) { // // Example: class A inherited only 2 method named 'm'. One has the function type // '() -> dynamic' and one has the function type '([int]) -> dynamic'. Since the second // method is a subtype of all the others, it is the inherited method. // Tests: InheritanceManagerTest.test_getMapOfMembersInheritedFromInterfaces_union_oneSubtype_* // resultMap.put(key, elements[subtypesOfAllOtherTypesIndexes.get(0)]); } else { if (subtypesOfAllOtherTypesIndexes.isEmpty()) { // // Determine if the current class has a method or accessor with the member name, if it // does then then this class does not "inherit" from any of the supertypes. // See issue 16134. // boolean classHasMember = false; if (allMethods) { classHasMember = classElt.getMethod(key) != null; } else { PropertyAccessorElement[] accessors = classElt.getAccessors(); for (int i = 0; i < accessors.length; i++) { if (accessors[i].getName().equals(key)) { classHasMember = true; } } } // // Example: class A inherited only 2 method named 'm'. One has the function type // '() -> int' and one has the function type '() -> String'. Since neither is a subtype // of the other, we create a warning, and have this class inherit nothing. // if (!classHasMember) { String firstTwoFuntionTypesStr = executableElementTypes[0].toString() + ", " + executableElementTypes[1].toString(); reportError( classElt, classElt.getNameOffset(), classElt.getDisplayName().length(), StaticTypeWarningCode.INCONSISTENT_METHOD_INHERITANCE, key, firstTwoFuntionTypesStr); } } else { // // Example: class A inherits 2 methods named 'm'. One has the function type // '(int) -> dynamic' and one has the function type '(num) -> dynamic'. Since they are // both a subtype of the other, a synthetic function '(dynamic) -> dynamic' is // inherited. // Tests: test_getMapOfMembersInheritedFromInterfaces_union_multipleSubtypes_* // ExecutableElement[] elementArrayToMerge = new ExecutableElement[subtypesOfAllOtherTypesIndexes.size()]; for (int i = 0; i < elementArrayToMerge.length; i++) { elementArrayToMerge[i] = elements[subtypesOfAllOtherTypesIndexes.get(i)]; } ExecutableElement mergedExecutableElement = computeMergedExecutableElement(elementArrayToMerge); resultMap.put(key, mergedExecutableElement); } } } else { reportError( classElt, classElt.getNameOffset(), classElt.getDisplayName().length(), StaticWarningCode.INCONSISTENT_METHOD_INHERITANCE_GETTER_AND_METHOD, key); } } } return resultMap; } /** * Loop through all of the members in some {@link MemberMap}, performing type parameter * substitutions using a passed supertype. * * @param superType the supertype to substitute into the members of the {@link MemberMap} * @param map the MemberMap to perform the substitutions on */ private void substituteTypeParametersDownHierarchy(InterfaceType superType, MemberMap map) { for (int i = 0; i < map.getSize(); i++) { ExecutableElement executableElement = map.getValue(i); if (executableElement instanceof MethodMember) { executableElement = MethodMember.from((MethodMember) executableElement, superType); map.setValue(i, executableElement); } else if (executableElement instanceof PropertyAccessorMember) { executableElement = PropertyAccessorMember.from( (PropertyAccessorMember) executableElement, superType); map.setValue(i, executableElement); } } } /** * Union all of the {@link lookupMaps} together into a single map, grouping the ExecutableElements * into a list where none of the elements are equal where equality is determined by having equal * function types. (We also take note too of the kind of the element: ()->int and () -> int may * not be equal if one is a getter and the other is a method.) * * @param lookupMaps the maps to be unioned together. * @return the resulting union map. */ private HashMap<String, ArrayList<ExecutableElement>> unionInterfaceLookupMaps( ArrayList<MemberMap> lookupMaps) { HashMap<String, ArrayList<ExecutableElement>> unionMap = new HashMap<String, ArrayList<ExecutableElement>>(); for (MemberMap lookupMap : lookupMaps) { int lookupMapSize = lookupMap.getSize(); for (int i = 0; i < lookupMapSize; i++) { // Get the string key, if null, break. String key = lookupMap.getKey(i); if (key == null) { break; } // Get the list value out of the unionMap ArrayList<ExecutableElement> list = unionMap.get(key); // If we haven't created such a map for this key yet, do create it and put the list entry // into the unionMap. if (list == null) { list = new ArrayList<ExecutableElement>(4); unionMap.put(key, list); } // Fetch the entry out of this lookupMap ExecutableElement newExecutableElementEntry = lookupMap.getValue(i); if (list.isEmpty()) { // If the list is empty, just the new value list.add(newExecutableElementEntry); } else { // Otherwise, only add the newExecutableElementEntry if it isn't already in the list, this // covers situation where a class inherits two methods (or two getters) that are // identical. boolean alreadyInList = false; boolean isMethod1 = newExecutableElementEntry instanceof MethodElement; for (ExecutableElement executableElementInList : list) { boolean isMethod2 = executableElementInList instanceof MethodElement; if (isMethod1 == isMethod2 && executableElementInList.getType().equals(newExecutableElementEntry.getType())) { alreadyInList = true; break; } } if (!alreadyInList) { list.add(newExecutableElementEntry); } } } } return unionMap; } }