Package com.google.dart.engine.internal.resolver

Source Code of com.google.dart.engine.internal.resolver.InheritanceManager

/*
* 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>, &hellip;, 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;
  }
}
TOP

Related Classes of com.google.dart.engine.internal.resolver.InheritanceManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.