/*
* 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.VariableDeclaration;
import com.google.dart.engine.ast.VariableDeclarationList;
import com.google.dart.engine.element.Element;
import com.google.dart.engine.element.PropertyAccessorElement;
import com.google.dart.engine.element.VariableElement;
import com.google.dart.engine.internal.type.UnionTypeImpl;
import com.google.dart.engine.type.Type;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Instances of the class {@code TypeOverrideManager} manage the ability to override the type of an
* element within a given context.
*/
public class TypeOverrideManager {
/**
* Instances of the class {@code TypeOverrideScope} represent a scope in which the types of
* elements can be overridden.
*/
private static class TypeOverrideScope {
/**
* The outer scope in which types might be overridden.
*/
private TypeOverrideScope outerScope;
/**
* A table mapping elements to the overridden type of that element.
*/
private Map<VariableElement, Type> overridenTypes = new HashMap<VariableElement, Type>();
/**
* Initialize a newly created scope to be an empty child of the given scope.
*
* @param outerScope the outer scope in which types might be overridden
*/
public TypeOverrideScope(TypeOverrideScope outerScope) {
this.outerScope = outerScope;
}
/**
* Apply a set of overrides that were previously captured.
*
* @param overrides the overrides to be applied
*/
public void applyOverrides(Map<VariableElement, Type> overrides) {
for (Map.Entry<VariableElement, Type> entry : overrides.entrySet()) {
overridenTypes.put(entry.getKey(), entry.getValue());
}
}
/**
* Return a table mapping the elements whose type is overridden in the current scope to the
* overriding type.
*
* @return the overrides in the current scope
*/
public Map<VariableElement, Type> captureLocalOverrides() {
return overridenTypes;
}
/**
* Return a map from the elements for the variables in the given list that have their types
* overridden to the overriding type.
*
* @param variableList the list of variables whose overriding types are to be captured
* @return a table mapping elements to their overriding types
*/
public Map<VariableElement, Type> captureOverrides(VariableDeclarationList variableList) {
Map<VariableElement, Type> overrides = new HashMap<VariableElement, Type>();
if (variableList.isConst() || variableList.isFinal()) {
for (VariableDeclaration variable : variableList.getVariables()) {
VariableElement element = variable.getElement();
if (element != null) {
Type type = overridenTypes.get(element);
if (type != null) {
overrides.put(element, type);
}
}
}
}
return overrides;
}
/**
* Return the overridden type of the given element, or {@code null} if the type of the element
* has not been overridden.
*
* @param element the element whose type might have been overridden
* @return the overridden type of the given element
*/
public Type getType(Element element) {
Type type = overridenTypes.get(element);
if (type == null && element instanceof PropertyAccessorElement) {
type = overridenTypes.get(((PropertyAccessorElement) element).getVariable());
}
if (type != null) {
return type;
} else if (outerScope != null) {
return outerScope.getType(element);
}
return null;
}
/**
* Set the overridden type of the given element to the given type
*
* @param element the element whose type might have been overridden
* @param type the overridden type of the given element
*/
public void setType(VariableElement element, Type type) {
overridenTypes.put(element, type);
}
}
/**
* The current override scope, or {@code null} if no scope has been entered.
*/
private TypeOverrideScope currentScope;
/**
* Initialize a newly created override manager to not be in any scope.
*/
public TypeOverrideManager() {
super();
}
/**
* Apply a set of overrides that were previously captured.
*
* @param overrides the overrides to be applied
*/
public void applyOverrides(Map<VariableElement, Type> overrides) {
if (currentScope == null) {
throw new IllegalStateException("Cannot apply overrides without a scope");
}
currentScope.applyOverrides(overrides);
}
/**
* Return a table mapping the elements whose type is overridden in the current scope to the
* overriding type.
*
* @return the overrides in the current scope
*/
public Map<VariableElement, Type> captureLocalOverrides() {
if (currentScope == null) {
throw new IllegalStateException("Cannot capture local overrides without a scope");
}
return currentScope.captureLocalOverrides();
}
/**
* Return a map from the elements for the variables in the given list that have their types
* overridden to the overriding type.
*
* @param variableList the list of variables whose overriding types are to be captured
* @return a table mapping elements to their overriding types
*/
public Map<VariableElement, Type> captureOverrides(VariableDeclarationList variableList) {
if (currentScope == null) {
throw new IllegalStateException("Cannot capture overrides without a scope");
}
return currentScope.captureOverrides(variableList);
}
/**
* Enter a new override scope.
*/
public void enterScope() {
currentScope = new TypeOverrideScope(currentScope);
}
/**
* Exit the current override scope.
*/
public void exitScope() {
if (currentScope == null) {
throw new IllegalStateException("No scope to exit");
}
currentScope = currentScope.outerScope;
}
/**
* Return the best type information available for the given element. If the type of the element
* has been overridden, then return the overriding type. Otherwise, return the static type.
*
* @param element the element for which type information is to be returned
* @return the best type information available for the given element
*/
public Type getBestType(VariableElement element) {
Type bestType = getType(element);
return bestType == null ? element.getType() : bestType;
}
/**
* Return the overridden type of the given element, or {@code null} if the type of the element has
* not been overridden.
*
* @param element the element whose type might have been overridden
* @return the overridden type of the given element
*/
public Type getType(Element element) {
if (currentScope == null) {
return null;
}
return currentScope.getType(element);
}
/**
* Update overrides assuming {@code perBranchOverrides} is the collection of per-branch overrides
* for *all* branches flowing into a join point. If a variable is updated in each per-branch
* override, then its type before the branching is ignored. Otherwise, its type before the
* branching is merged with all updates in the branches.
* <p>
* Although this method would do the right thing for a single set of overrides, we require there
* to be at least two override sets. Instead use {@code applyOverrides} for to apply a single set.
* <p>
* For example, for the code
*
* <pre>
* if (c) {
* ...
* } else {
* ...
* }
* </pre>
* the {@code perBranchOverrides} would include overrides for the then and else branches, and for
* the code
*
* <pre>
* ...
* while(c) {
* ...
* }
* </pre>
* the {@code perBranchOverrides} would include overrides for before the loop and for the loop
* body.
*
* @param perBranchOverrides one set of overrides for each (at least two) branch flowing into the
* join point
*/
public void joinOverrides(List<Map<VariableElement, Type>> perBranchOverrides) {
if (perBranchOverrides.size() < 2) {
throw new IllegalArgumentException("There is no point in joining zero or one override sets.");
}
Set<VariableElement> allElements = new HashSet<VariableElement>();
Set<VariableElement> commonElements = new HashSet<VariableElement>(
perBranchOverrides.get(0).keySet());
for (Map<VariableElement, Type> os : perBranchOverrides) {
// Union: elements updated in some branch.
allElements.addAll(os.keySet());
// Intersection: elements updated in all branches.
commonElements.retainAll(os.keySet());
}
Set<VariableElement> uncommonElements = allElements;
// Difference: elements updated in some but not all branches.
uncommonElements.removeAll(commonElements);
Map<VariableElement, Type> joinOverrides = new HashMap<VariableElement, Type>();
// The common elements were updated in all branches, so their type
// before branching can be ignored.
for (VariableElement e : commonElements) {
joinOverrides.put(e, perBranchOverrides.get(0).get(e));
for (Map<VariableElement, Type> os : perBranchOverrides) {
joinOverrides.put(e, UnionTypeImpl.union(joinOverrides.get(e), os.get(e)));
}
}
// The uncommon elements were updated in some but not all branches,
// so they may still have the type they had before branching.
for (VariableElement e : uncommonElements) {
joinOverrides.put(e, getBestType(e));
for (Map<VariableElement, Type> os : perBranchOverrides) {
if (os.containsKey(e)) {
joinOverrides.put(e, UnionTypeImpl.union(joinOverrides.get(e), os.get(e)));
}
}
}
applyOverrides(joinOverrides);
}
/**
* Set the overridden type of the given element to the given type
*
* @param element the element whose type might have been overridden
* @param type the overridden type of the given element
*/
public void setType(VariableElement element, Type type) {
if (currentScope == null) {
throw new IllegalStateException("Cannot override without a scope");
}
currentScope.setType(element, type);
}
}