Index: pkg/analyzer/lib/src/generated/constant.dart |
diff --git a/pkg/analyzer/lib/src/generated/constant.dart b/pkg/analyzer/lib/src/generated/constant.dart |
index 79e29da912065a90c9fab38cb99dd61809c85d87..ef4fd68d0605e31dd219aa041666f58f3e5c6c14 100644 |
--- a/pkg/analyzer/lib/src/generated/constant.dart |
+++ b/pkg/analyzer/lib/src/generated/constant.dart |
@@ -112,6 +112,9 @@ class BoolState extends InstanceState { |
bool get isBoolNumStringOrNull => true; |
@override |
+ bool get isUnknown => value == null; |
+ |
+ @override |
BoolState logicalAnd(InstanceState rightOperand) { |
assertBool(rightOperand); |
if (value == null) { |
@@ -204,7 +207,7 @@ class ConstantEvaluator { |
ConstantEvaluator(this._source, this._typeProvider); |
EvaluationResult evaluate(Expression expression) { |
- EvaluationResultImpl result = expression.accept(new ConstantVisitor(_typeProvider)); |
+ EvaluationResultImpl result = expression.accept(new ConstantVisitor.con1(_typeProvider)); |
if (result is ValidResult) { |
return EvaluationResult.forValue(result.value); |
} |
@@ -228,6 +231,37 @@ class ConstantFinder extends RecursiveAstVisitor<Object> { |
*/ |
final Map<VariableElement, VariableDeclaration> variableMap = new Map<VariableElement, VariableDeclaration>(); |
+ /** |
+ * A table mapping constant constructors to the declarations of those constructors. |
+ */ |
+ final Map<ConstructorElement, ConstructorDeclaration> constructorMap = new Map<ConstructorElement, ConstructorDeclaration>(); |
+ |
+ /** |
+ * A collection of constant constructor invocations. |
+ */ |
+ final List<InstanceCreationExpression> constructorInvocations = new List<InstanceCreationExpression>(); |
+ |
+ @override |
+ Object visitConstructorDeclaration(ConstructorDeclaration node) { |
+ super.visitConstructorDeclaration(node); |
+ if (node.constKeyword != null) { |
+ ConstructorElement element = node.element; |
+ if (element != null) { |
+ constructorMap[element] = node; |
+ } |
+ } |
+ return null; |
+ } |
+ |
+ @override |
+ Object visitInstanceCreationExpression(InstanceCreationExpression node) { |
+ super.visitInstanceCreationExpression(node); |
+ if (node.isConst) { |
+ constructorInvocations.add(node); |
+ } |
+ return null; |
+ } |
+ |
@override |
Object visitVariableDeclaration(VariableDeclaration node) { |
super.visitVariableDeclaration(node); |
@@ -243,124 +277,430 @@ class ConstantFinder extends RecursiveAstVisitor<Object> { |
} |
/** |
- * Instances of the class `ConstantValueComputer` compute the values of constant variables in |
- * one or more compilation units. The expected usage pattern is for the compilation units to be |
- * added to this computer using the method [add] and then for the method |
- * [computeValues] to be invoked exactly once. Any use of an instance after invoking the |
- * method [computeValues] will result in unpredictable behavior. |
+ * Instances of the class `ConstantValueComputer` compute the values of constant variables and |
+ * constant constructor invocations in one or more compilation units. The expected usage pattern is |
+ * for the compilation units to be added to this computer using the method |
+ * [add] and then for the method [computeValues] to be invoked |
+ * exactly once. Any use of an instance after invoking the method [computeValues] will |
+ * result in unpredictable behavior. |
*/ |
class ConstantValueComputer { |
/** |
* The type provider used to access the known types. |
*/ |
- final TypeProvider _typeProvider; |
+ TypeProvider typeProvider; |
/** |
- * The object used to find constant variables in the compilation units that were added. |
+ * The object used to find constant variables and constant constructor invocations in the |
+ * compilation units that were added. |
*/ |
ConstantFinder _constantFinder = new ConstantFinder(); |
/** |
- * A graph in which the nodes are the constant variables and the edges are from each variable to |
- * the other constant variables that are referenced in the head's initializer. |
+ * A graph in which the nodes are the constants, and the edges are from each constant to the other |
+ * constants that are referenced by it. |
*/ |
- DirectedGraph<VariableElement> _referenceGraph = new DirectedGraph<VariableElement>(); |
+ DirectedGraph<AstNode> referenceGraph = new DirectedGraph<AstNode>(); |
/** |
* A table mapping constant variables to the declarations of those variables. |
*/ |
- Map<VariableElement, VariableDeclaration> _declarationMap; |
+ Map<VariableElement, VariableDeclaration> _variableDeclarationMap; |
+ |
+ /** |
+ * A table mapping constant constructors to the declarations of those constructors. |
+ */ |
+ Map<ConstructorElement, ConstructorDeclaration> constructorDeclarationMap; |
+ |
+ /** |
+ * A collection of constant constructor invocations. |
+ */ |
+ List<InstanceCreationExpression> _constructorInvocations; |
/** |
* Initialize a newly created constant value computer. |
* |
* @param typeProvider the type provider used to access known types |
*/ |
- ConstantValueComputer(this._typeProvider); |
+ ConstantValueComputer(TypeProvider typeProvider) { |
+ this.typeProvider = typeProvider; |
+ } |
/** |
- * Add the constant variables in the given compilation unit to the list of constant variables |
- * whose value needs to be computed. |
+ * Add the constants in the given compilation unit to the list of constants whose value needs to |
+ * be computed. |
* |
- * @param unit the compilation unit defining the constant variables to be added |
+ * @param unit the compilation unit defining the constants to be added |
*/ |
void add(CompilationUnit unit) { |
unit.accept(_constantFinder); |
} |
/** |
- * Compute values for all of the constant variables in the compilation units that were added. |
+ * Compute values for all of the constants in the compilation units that were added. |
*/ |
void computeValues() { |
- _declarationMap = _constantFinder.variableMap; |
- for (MapEntry<VariableElement, VariableDeclaration> entry in getMapEntrySet(_declarationMap)) { |
- VariableElement element = entry.getKey(); |
- ReferenceFinder referenceFinder = new ReferenceFinder(element, _referenceGraph); |
- _referenceGraph.addNode(element); |
- entry.getValue().initializer.accept(referenceFinder); |
- } |
- while (!_referenceGraph.isEmpty) { |
- VariableElement element = _referenceGraph.removeSink(); |
- while (element != null) { |
- _computeValueFor(element); |
- element = _referenceGraph.removeSink(); |
+ _variableDeclarationMap = _constantFinder.variableMap; |
+ constructorDeclarationMap = _constantFinder.constructorMap; |
+ _constructorInvocations = _constantFinder.constructorInvocations; |
+ for (MapEntry<VariableElement, VariableDeclaration> entry in getMapEntrySet(_variableDeclarationMap)) { |
+ VariableDeclaration declaration = entry.getValue(); |
+ ReferenceFinder referenceFinder = new ReferenceFinder(declaration, referenceGraph, _variableDeclarationMap, constructorDeclarationMap); |
+ referenceGraph.addNode(declaration); |
+ declaration.initializer.accept(referenceFinder); |
+ } |
+ for (MapEntry<ConstructorElement, ConstructorDeclaration> entry in getMapEntrySet(constructorDeclarationMap)) { |
+ ConstructorDeclaration declaration = entry.getValue(); |
+ ReferenceFinder referenceFinder = new ReferenceFinder(declaration, referenceGraph, _variableDeclarationMap, constructorDeclarationMap); |
+ referenceGraph.addNode(declaration); |
+ bool superInvocationFound = false; |
+ NodeList<ConstructorInitializer> initializers = declaration.initializers; |
+ for (ConstructorInitializer initializer in initializers) { |
+ if (initializer is SuperConstructorInvocation) { |
+ superInvocationFound = true; |
+ } |
+ initializer.accept(referenceFinder); |
} |
- if (!_referenceGraph.isEmpty) { |
- List<VariableElement> variablesInCycle = _referenceGraph.findCycle(); |
- if (variablesInCycle == null) { |
- // |
- // This should not happen. Either the graph should be empty, or there should be at least |
- // one sink, or there should be a cycle. If this does happen we exit to prevent an |
- // infinite loop. |
- // |
- AnalysisEngine.instance.logger.logError("Exiting constant value computer with ${_referenceGraph.nodeCount} variables that are neither sinks nor in a cycle"); |
- return; |
+ if (!superInvocationFound) { |
+ // No explicit superconstructor invocation found, so we need to manually insert |
+ // a reference to the implicit superconstructor. |
+ InterfaceType superclass = (entry.getKey().returnType as InterfaceType).superclass; |
+ if (superclass != null && !superclass.isObject) { |
+ ConstructorElement unnamedConstructor = superclass.element.unnamedConstructor; |
+ ConstructorDeclaration superConstructorDeclaration = findConstructorDeclaration(unnamedConstructor); |
+ if (superConstructorDeclaration != null) { |
+ referenceGraph.addEdge(declaration, superConstructorDeclaration); |
+ } |
} |
- for (VariableElement variable in variablesInCycle) { |
- _generateCycleError(variablesInCycle, variable); |
+ } |
+ for (FormalParameter parameter in declaration.parameters.parameters) { |
+ referenceGraph.addNode(parameter); |
+ referenceGraph.addEdge(declaration, parameter); |
+ if (parameter is DefaultFormalParameter) { |
+ Expression defaultValue = parameter.defaultValue; |
+ if (defaultValue != null) { |
+ ReferenceFinder parameterReferenceFinder = new ReferenceFinder(parameter, referenceGraph, _variableDeclarationMap, constructorDeclarationMap); |
+ defaultValue.accept(parameterReferenceFinder); |
+ } |
+ } |
+ } |
+ } |
+ for (InstanceCreationExpression expression in _constructorInvocations) { |
+ referenceGraph.addNode(expression); |
+ ConstructorElement constructor = expression.staticElement; |
+ if (constructor == null) { |
+ break; |
+ } |
+ constructor = _followConstantRedirectionChain(constructor); |
+ ConstructorDeclaration declaration = findConstructorDeclaration(constructor); |
+ // An instance creation expression depends both on the constructor and the arguments passed |
+ // to it. |
+ ReferenceFinder referenceFinder = new ReferenceFinder(expression, referenceGraph, _variableDeclarationMap, constructorDeclarationMap); |
+ if (declaration != null) { |
+ referenceGraph.addEdge(expression, declaration); |
+ } |
+ expression.argumentList.accept(referenceFinder); |
+ } |
+ List<List<AstNode>> topologicalSort = referenceGraph.computeTopologicalSort(); |
+ for (List<AstNode> constantsInCycle in topologicalSort) { |
+ if (constantsInCycle.length == 1) { |
+ _computeValueFor(constantsInCycle[0]); |
+ } else { |
+ for (AstNode constant in constantsInCycle) { |
+ _generateCycleError(constantsInCycle, constant); |
} |
- _referenceGraph.removeAllNodes(variablesInCycle); |
} |
} |
} |
/** |
- * Compute a value for the given variable. |
+ * This method is called just before computing the constant value associated with an AST node. |
+ * Unit tests will override this method to introduce additional error checking. |
+ */ |
+ void beforeComputeValue(AstNode constNode) { |
+ } |
+ |
+ /** |
+ * This method is called just before getting the constant initializers associated with a |
+ * constructor AST node. Unit tests will override this method to introduce additional error |
+ * checking. |
+ */ |
+ void beforeGetConstantInitializers(ConstructorElement constructor) { |
+ } |
+ |
+ /** |
+ * This method is called just before getting a parameter's default value. Unit tests will override |
+ * this method to introduce additional error checking. |
+ */ |
+ void beforeGetParameterDefault(ParameterElement parameter) { |
+ } |
+ |
+ /** |
+ * Create the ConstantVisitor used to evaluate constants. Unit tests will override this method to |
+ * introduce additional error checking. |
+ */ |
+ ConstantVisitor createConstantVisitor() => new ConstantVisitor.con1(typeProvider); |
+ |
+ ConstructorDeclaration findConstructorDeclaration(ConstructorElement constructor) => constructorDeclarationMap[_getConstructorBase(constructor)]; |
+ |
+ /** |
+ * Compute a value for the given constant. |
* |
- * @param variable the variable for which a value is to be computed |
- */ |
- void _computeValueFor(VariableElement variable) { |
- VariableDeclaration declaration = _declarationMap[variable]; |
- if (declaration == null) { |
- // |
- // The declaration will be null when the variable was added to the graph as a result of being |
- // referenced by another variable but is not defined in the compilation units that were added |
- // to this computer. In such cases, the variable should already have a value associated with |
- // it, but we don't bother to check because there's nothing we can do about it at this point. |
- // |
+ * @param constNode the constant for which a value is to be computed |
+ */ |
+ void _computeValueFor(AstNode constNode) { |
+ beforeComputeValue(constNode); |
+ if (constNode is VariableDeclaration) { |
+ VariableDeclaration declaration = constNode; |
+ Element element = declaration.element; |
+ EvaluationResultImpl result = declaration.initializer.accept(createConstantVisitor()); |
+ (element as VariableElementImpl).evaluationResult = result; |
+ } else if (constNode is InstanceCreationExpression) { |
+ InstanceCreationExpression expression = constNode; |
+ ConstructorElement constructor = expression.staticElement; |
+ if (constructor == null) { |
+ // Couldn't resolve the constructor so we can't compute a value. No problem--the error |
+ // has already been reported. |
+ return; |
+ } |
+ ConstantVisitor constantVisitor = createConstantVisitor(); |
+ ValidResult result = _evaluateConstructorCall(expression.argumentList.arguments, constructor, constantVisitor); |
+ expression.evaluationResult = result; |
+ } else if (constNode is ConstructorDeclaration) { |
+ ConstructorDeclaration declaration = constNode; |
+ NodeList<ConstructorInitializer> initializers = declaration.initializers; |
+ ConstructorElementImpl constructor = declaration.element as ConstructorElementImpl; |
+ constructor.constantInitializers = new ConstantValueComputer_InitializerCloner().cloneNodeList(initializers); |
+ } else if (constNode is FormalParameter) { |
+ if (constNode is DefaultFormalParameter) { |
+ DefaultFormalParameter parameter = constNode; |
+ ParameterElement element = parameter.element; |
+ Expression defaultValue = parameter.defaultValue; |
+ if (defaultValue != null) { |
+ EvaluationResultImpl result = defaultValue.accept(createConstantVisitor()); |
+ (element as ParameterElementImpl).evaluationResult = result; |
+ } |
+ } |
+ } else { |
+ // Should not happen. |
+ AnalysisEngine.instance.logger.logError("Constant value computer trying to compute the value of a node which is not a VariableDeclaration, InstanceCreationExpression, FormalParameter, or ConstructorDeclaration"); |
return; |
} |
- EvaluationResultImpl result = declaration.initializer.accept(new ConstantVisitor(_typeProvider)); |
- (variable as VariableElementImpl).evaluationResult = result; |
- if (result is ErrorResult) { |
- List<AnalysisError> errors = new List<AnalysisError>(); |
- for (ErrorResult_ErrorData data in result.errorData) { |
- AstNode node = data.node; |
- Source source = variable.getAncestor((element) => element is CompilationUnitElement).source; |
- errors.add(new AnalysisError.con2(source, node.offset, node.length, data.errorCode, [])); |
+ } |
+ |
+ ValidResult _evaluateConstructorCall(NodeList<Expression> arguments, ConstructorElement constructor, ConstantVisitor constantVisitor) { |
+ int argumentCount = arguments.length; |
+ List<DartObjectImpl> argumentValues = new List<DartObjectImpl>(argumentCount); |
+ Map<String, DartObjectImpl> namedArgumentValues = new Map<String, DartObjectImpl>(); |
+ for (int i = 0; i < argumentCount; i++) { |
+ Expression argument = arguments[i]; |
+ if (argument is NamedExpression) { |
+ NamedExpression namedExpression = argument; |
+ String name = namedExpression.name.label.name; |
+ namedArgumentValues[name] = constantVisitor._valueOf(namedExpression.expression); |
+ argumentValues[i] = constantVisitor.null2; |
+ } else { |
+ argumentValues[i] = constantVisitor._valueOf(argument); |
} |
} |
+ constructor = _followConstantRedirectionChain(constructor); |
+ InterfaceType definingClass = constructor.returnType as InterfaceType; |
+ if (constructor.isFactory) { |
+ // We couldn't find a non-factory constructor. See if it's because we reached an external |
+ // const factory constructor that we can emulate. |
+ // TODO(paulberry): if the constructor is one of {bool,int,String}.fromEnvironment(), |
+ // we may be able to infer the value based on -D flags provided to the analyzer (see |
+ // dartbug.com/17234). |
+ if (identical(definingClass, typeProvider.symbolType) && argumentCount == 1) { |
+ String argumentValue = argumentValues[0].stringValue; |
+ if (argumentValue != null) { |
+ return constantVisitor._valid(definingClass, new SymbolState(argumentValue)); |
+ } |
+ } |
+ // Either it's an external const factory constructor that we can't emulate, or an error |
+ // occurred (a cycle, or a const constructor trying to delegate to a non-const constructor). |
+ // In the former case, the best we can do is consider it an unknown value. In the latter |
+ // case, the error has already been reported, so considering it an unknown value will |
+ // suppress further errors. |
+ return constantVisitor._validWithUnknownValue(definingClass); |
+ } |
+ beforeGetConstantInitializers(constructor); |
+ ConstructorElementImpl constructorBase = _getConstructorBase(constructor) as ConstructorElementImpl; |
+ List<ConstructorInitializer> initializers = constructorBase.constantInitializers; |
+ if (initializers == null) { |
+ // This can happen in some cases where there are compile errors in the code being analyzed |
+ // (for example if the code is trying to create a const instance using a non-const |
+ // constructor, or the node we're visiting is involved in a cycle). The error has already |
+ // been reported, so consider it an unknown value to suppress further errors. |
+ return constantVisitor._validWithUnknownValue(definingClass); |
+ } |
+ Map<String, DartObjectImpl> fieldMap = new Map<String, DartObjectImpl>(); |
+ Map<String, DartObjectImpl> parameterMap = new Map<String, DartObjectImpl>(); |
+ List<ParameterElement> parameters = constructorBase.parameters; |
+ int parameterCount = parameters.length; |
+ for (int i = 0; i < parameterCount; i++) { |
+ ParameterElement parameter = parameters[i]; |
+ while (parameter is ParameterMember) { |
+ parameter = (parameter as ParameterMember).baseElement; |
+ } |
+ DartObjectImpl argumentValue = null; |
+ if (parameter.parameterKind == ParameterKind.NAMED) { |
+ argumentValue = namedArgumentValues[parameter.name]; |
+ } else if (i < argumentCount) { |
+ argumentValue = argumentValues[i]; |
+ } |
+ if (argumentValue == null && parameter is ParameterElementImpl) { |
+ // The parameter is an optional positional parameter for which no value was provided, so |
+ // use the default value. |
+ beforeGetParameterDefault(parameter); |
+ EvaluationResultImpl evaluationResult = (parameter as ParameterElementImpl).evaluationResult; |
+ if (evaluationResult is ValidResult) { |
+ argumentValue = evaluationResult.value; |
+ } else if (evaluationResult == null) { |
+ // No default was provided, so the default value is null. |
+ argumentValue = constantVisitor.null2; |
+ } |
+ } |
+ if (argumentValue != null) { |
+ if (parameter.isInitializingFormal) { |
+ FieldElement field = (parameter as FieldFormalParameterElement).field; |
+ if (field != null) { |
+ String fieldName = field.name; |
+ fieldMap[fieldName] = argumentValue; |
+ } |
+ } else { |
+ String name = parameter.name; |
+ parameterMap[name] = argumentValue; |
+ } |
+ } |
+ } |
+ ConstantVisitor initializerVisitor = new ConstantVisitor.con2(typeProvider, parameterMap); |
+ String superName = null; |
+ NodeList<Expression> superArguments = null; |
+ for (ConstructorInitializer initializer in initializers) { |
+ if (initializer is ConstructorFieldInitializer) { |
+ ConstructorFieldInitializer constructorFieldInitializer = initializer; |
+ Expression initializerExpression = constructorFieldInitializer.expression; |
+ EvaluationResultImpl evaluationResult = initializerExpression.accept(initializerVisitor); |
+ if (evaluationResult is ValidResult) { |
+ DartObjectImpl value = evaluationResult.value; |
+ String fieldName = constructorFieldInitializer.fieldName.name; |
+ fieldMap[fieldName] = value; |
+ } |
+ } else if (initializer is SuperConstructorInvocation) { |
+ SuperConstructorInvocation superConstructorInvocation = initializer; |
+ SimpleIdentifier name = superConstructorInvocation.constructorName; |
+ if (name != null) { |
+ superName = name.name; |
+ } |
+ superArguments = superConstructorInvocation.argumentList.arguments; |
+ } |
+ } |
+ // Evaluate explicit or implicit call to super(). |
+ InterfaceType superclass = definingClass.superclass; |
+ if (superclass != null && !superclass.isObject) { |
+ ConstructorElement superConstructor = superclass.lookUpConstructor(superName, constructor.library); |
+ if (superConstructor != null) { |
+ if (superArguments == null) { |
+ superArguments = new NodeList<Expression>(null); |
+ } |
+ _evaluateSuperConstructorCall(fieldMap, superConstructor, superArguments, initializerVisitor); |
+ } |
+ } |
+ return constantVisitor._valid(definingClass, new GenericState(fieldMap)); |
+ } |
+ |
+ void _evaluateSuperConstructorCall(Map<String, DartObjectImpl> fieldMap, ConstructorElement superConstructor, NodeList<Expression> superArguments, ConstantVisitor initializerVisitor) { |
+ if (superConstructor != null && superConstructor.isConst) { |
+ ValidResult evaluationResult = _evaluateConstructorCall(superArguments, superConstructor, initializerVisitor); |
+ fieldMap[GenericState.SUPERCLASS_FIELD] = evaluationResult.value; |
+ } |
+ } |
+ |
+ /** |
+ * Attempt to follow the chain of factory redirections until a constructor is reached which is not |
+ * a const factory constructor. |
+ * |
+ * @return the constant constructor which terminates the chain of factory redirections, if the |
+ * chain terminates. If there is a problem (e.g. a redirection can't be found, or a cycle |
+ * is encountered), the chain will be followed as far as possible and then a const factory |
+ * constructor will be returned. |
+ */ |
+ ConstructorElement _followConstantRedirectionChain(ConstructorElement constructor) { |
+ Set<ConstructorElement> constructorsVisited = new Set<ConstructorElement>(); |
+ while (constructor.isFactory) { |
+ if (identical(constructor.enclosingElement.type, typeProvider.symbolType)) { |
+ // The dart:core.Symbol has a const factory constructor that redirects to |
+ // dart:_internal.Symbol. That in turn redirects to an external const constructor, which |
+ // we won't be able to evaluate. So stop following the chain of redirections at |
+ // dart:core.Symbol, and let [evaluateInstanceCreationExpression] handle it specially. |
+ break; |
+ } |
+ constructorsVisited.add(constructor); |
+ ConstructorElement redirectedConstructor = constructor.redirectedConstructor; |
+ if (redirectedConstructor == null) { |
+ // This can happen if constructor is an external factory constructor. |
+ break; |
+ } |
+ if (!redirectedConstructor.isConst) { |
+ // Delegating to a non-const constructor--this is not allowed (and |
+ // is checked elsewhere--see [ErrorVerifier.checkForRedirectToNonConstConstructor()]). |
+ break; |
+ } |
+ if (constructorsVisited.contains(redirectedConstructor)) { |
+ // Cycle in redirecting factory constructors--this is not allowed |
+ // and is checked elsewhere--see [ErrorVerifier.checkForRecursiveFactoryRedirect()]). |
+ break; |
+ } |
+ constructor = redirectedConstructor; |
+ } |
+ return constructor; |
} |
/** |
- * Generate an error indicating that the given variable is not a valid compile-time constant |
- * because it references at least one of the variables in the given cycle, each of which directly |
- * or indirectly references the variable. |
+ * Generate an error indicating that the given constant is not a valid compile-time constant |
+ * because it references at least one of the constants in the given cycle, each of which directly |
+ * or indirectly references the constant. |
* |
- * @param variablesInCycle the variables in the cycle that includes the given variable |
- * @param variable the variable that is not a valid compile-time constant |
+ * @param constantsInCycle the constants in the cycle that includes the given constant |
+ * @param constant the constant that is not a valid compile-time constant |
*/ |
- void _generateCycleError(List<VariableElement> variablesInCycle, VariableElement variable) { |
+ void _generateCycleError(List<AstNode> constantsInCycle, AstNode constant) { |
+ } |
+ |
+ ConstructorElement _getConstructorBase(ConstructorElement constructor) { |
+ while (constructor is ConstructorMember) { |
+ constructor = (constructor as ConstructorMember).baseElement; |
+ } |
+ return constructor; |
+ } |
+} |
+ |
+/** |
+ * [AstCloner] that copies the necessary information from the AST to allow const constructor |
+ * initializers to be evaluated. |
+ */ |
+class ConstantValueComputer_InitializerCloner extends AstCloner { |
+ @override |
+ InstanceCreationExpression visitInstanceCreationExpression(InstanceCreationExpression node) { |
+ // All we need is the evaluation result, and the keyword so that we know whether it's const. |
+ InstanceCreationExpression expression = new InstanceCreationExpression(node.keyword, null, null); |
+ expression.evaluationResult = node.evaluationResult; |
+ return expression; |
+ } |
+ |
+ @override |
+ SimpleIdentifier visitSimpleIdentifier(SimpleIdentifier node) { |
+ SimpleIdentifier identifier = super.visitSimpleIdentifier(node); |
+ identifier.staticElement = node.staticElement; |
+ return identifier; |
+ } |
+ |
+ @override |
+ SuperConstructorInvocation visitSuperConstructorInvocation(SuperConstructorInvocation node) { |
+ SuperConstructorInvocation invocation = super.visitSuperConstructorInvocation(node); |
+ invocation.staticElement = node.staticElement; |
+ return invocation; |
} |
} |
@@ -418,12 +758,29 @@ class ConstantVisitor extends UnifyingAstVisitor<EvaluationResultImpl> { |
*/ |
DartObjectImpl _nullObject; |
+ Map<String, DartObjectImpl> _lexicalEnvironment; |
+ |
/** |
* Initialize a newly created constant visitor. |
* |
* @param typeProvider the type provider used to access known types |
+ * @param lexicalEnvironment values which should override simpleIdentifiers, or null if no |
+ * overriding is necessary. |
*/ |
- ConstantVisitor(this._typeProvider); |
+ ConstantVisitor.con1(this._typeProvider) { |
+ this._lexicalEnvironment = null; |
+ } |
+ |
+ /** |
+ * Initialize a newly created constant visitor. |
+ * |
+ * @param typeProvider the type provider used to access known types |
+ * @param lexicalEnvironment values which should override simpleIdentifiers, or null if no |
+ * overriding is necessary. |
+ */ |
+ ConstantVisitor.con2(this._typeProvider, Map<String, DartObjectImpl> lexicalEnvironment) { |
+ this._lexicalEnvironment = lexicalEnvironment; |
+ } |
@override |
EvaluationResultImpl visitAdjacentStrings(AdjacentStrings node) { |
@@ -539,84 +896,10 @@ class ConstantVisitor extends UnifyingAstVisitor<EvaluationResultImpl> { |
// TODO(brianwilkerson) Figure out which error to report. |
return _error(node, null); |
} |
- ConstructorElement constructor = node.staticElement; |
- if (constructor != null && constructor.isConst) { |
- NodeList<Expression> arguments = node.argumentList.arguments; |
- int argumentCount = arguments.length; |
- List<DartObjectImpl> argumentValues = new List<DartObjectImpl>(argumentCount); |
- Map<String, DartObjectImpl> namedArgumentValues = new Map<String, DartObjectImpl>(); |
- for (int i = 0; i < argumentCount; i++) { |
- Expression argument = arguments[i]; |
- if (argument is NamedExpression) { |
- NamedExpression namedExpression = argument; |
- String name = namedExpression.name.label.name; |
- namedArgumentValues[name] = _valueOf(namedExpression.expression); |
- argumentValues[i] = null2; |
- } else { |
- argumentValues[i] = _valueOf(argument); |
- } |
- } |
- Set<ConstructorElement> constructorsVisited = new Set<ConstructorElement>(); |
- InterfaceType definingClass = constructor.returnType as InterfaceType; |
- while (constructor.isFactory) { |
- if (definingClass.element.library.isDartCore) { |
- String className = definingClass.name; |
- if (className == "Symbol" && argumentCount == 1) { |
- String argumentValue = argumentValues[0].stringValue; |
- if (argumentValue != null) { |
- return _valid(definingClass, new SymbolState(argumentValue)); |
- } |
- } |
- } |
- constructorsVisited.add(constructor); |
- ConstructorElement redirectedConstructor = constructor.redirectedConstructor; |
- if (redirectedConstructor == null) { |
- // This can happen if constructor is an external factory constructor. Since there is no |
- // constructor to delegate to, we currently can't evaluate the constant. |
- // TODO(paulberry): if the constructor is one of {bool,int,String}.fromEnvironment(), |
- // we may be able to infer the value based on -D flags provided to the analyzer (see |
- // dartbug.com/17234). |
- return _error(node, null); |
- } |
- if (!redirectedConstructor.isConst) { |
- // Delegating to a non-const constructor--this is not allowed (and |
- // is checked elsewhere--see [ErrorVerifier.checkForRedirectToNonConstConstructor()]). |
- // So if we encounter it just error out. |
- return _error(node, null); |
- } |
- if (constructorsVisited.contains(redirectedConstructor)) { |
- // Cycle in redirecting factory constructors--this is not allowed |
- // and is checked elsewhere--see [ErrorVerifier.checkForRecursiveFactoryRedirect()]). |
- // So if we encounter it just error out. |
- return _error(node, null); |
- } |
- constructor = redirectedConstructor; |
- definingClass = constructor.returnType as InterfaceType; |
- } |
- Map<String, DartObjectImpl> fieldMap = new Map<String, DartObjectImpl>(); |
- List<ParameterElement> parameters = constructor.parameters; |
- int parameterCount = parameters.length; |
- for (int i = 0; i < parameterCount; i++) { |
- ParameterElement parameter = parameters[i]; |
- if (parameter.isInitializingFormal) { |
- FieldElement field = (parameter as FieldFormalParameterElement).field; |
- if (field != null) { |
- String fieldName = field.name; |
- if (parameter.parameterKind == ParameterKind.NAMED) { |
- DartObjectImpl argumentValue = namedArgumentValues[parameter.name]; |
- if (argumentValue != null) { |
- fieldMap[fieldName] = argumentValue; |
- } |
- } else if (i < argumentCount) { |
- fieldMap[fieldName] = argumentValues[i]; |
- } |
- } |
- } |
- } |
- // TODO(brianwilkerson) This doesn't handle fields initialized in an initializer. We should be |
- // able to handle fields initialized by the superclass' constructor fairly easily, but other |
- // initializers will be harder. |
- return _valid(definingClass, new GenericState(fieldMap)); |
+ beforeGetEvaluationResult(node); |
+ EvaluationResultImpl result = node.evaluationResult; |
+ if (result != null) { |
+ return result; |
} |
// TODO(brianwilkerson) Figure out which error to report. |
return _error(node, null); |
@@ -755,7 +1038,12 @@ class ConstantVisitor extends UnifyingAstVisitor<EvaluationResultImpl> { |
EvaluationResultImpl visitPropertyAccess(PropertyAccess node) => _getConstantValue(node, node.propertyName.staticElement); |
@override |
- EvaluationResultImpl visitSimpleIdentifier(SimpleIdentifier node) => _getConstantValue(node, node.staticElement); |
+ EvaluationResultImpl visitSimpleIdentifier(SimpleIdentifier node) { |
+ if (_lexicalEnvironment != null && _lexicalEnvironment.containsKey(node.name)) { |
+ return new ValidResult(_lexicalEnvironment[node.name]); |
+ } |
+ return _getConstantValue(node, node.staticElement); |
+ } |
@override |
EvaluationResultImpl visitSimpleStringLiteral(SimpleStringLiteral node) => _valid(_typeProvider.stringType, new StringState(node.value)); |
@@ -787,6 +1075,58 @@ class ConstantVisitor extends UnifyingAstVisitor<EvaluationResultImpl> { |
} |
/** |
+ * This method is called just before retrieving an evaluation result from an AST node. Unit tests |
+ * will override it to introduce additional error checking. |
+ */ |
+ void beforeGetEvaluationResult(AstNode node) { |
+ } |
+ |
+ /** |
+ * Return an object representing the value 'null'. |
+ * |
+ * @return an object representing the value 'null' |
+ */ |
+ DartObjectImpl get null2 { |
+ if (_nullObject == null) { |
+ _nullObject = new DartObjectImpl(_typeProvider.nullType, NullState.NULL_STATE); |
+ } |
+ return _nullObject; |
+ } |
+ |
+ ValidResult _valid(InterfaceType type, InstanceState state) => new ValidResult(new DartObjectImpl(type, state)); |
+ |
+ ValidResult _validWithUnknownValue(InterfaceType type) { |
+ if (type.element.library.isDartCore) { |
+ String typeName = type.name; |
+ if (typeName == "bool") { |
+ return _valid(type, BoolState.UNKNOWN_VALUE); |
+ } else if (typeName == "double") { |
+ return _valid(type, DoubleState.UNKNOWN_VALUE); |
+ } else if (typeName == "int") { |
+ return _valid(type, IntState.UNKNOWN_VALUE); |
+ } else if (typeName == "String") { |
+ return _valid(type, StringState.UNKNOWN_VALUE); |
+ } |
+ } |
+ return _valid(type, GenericState.UNKNOWN_VALUE); |
+ } |
+ |
+ /** |
+ * Return the value of the given expression, or a representation of 'null' if the expression |
+ * cannot be evaluated. |
+ * |
+ * @param expression the expression whose value is to be returned |
+ * @return the value of the given expression |
+ */ |
+ DartObjectImpl _valueOf(Expression expression) { |
+ EvaluationResultImpl expressionValue = expression.accept(this); |
+ if (expressionValue is ValidResult) { |
+ return expressionValue.value; |
+ } |
+ return null2; |
+ } |
+ |
+ /** |
* Return a result object representing an error associated with the given node. |
* |
* @param node the AST node associated with the error |
@@ -808,6 +1148,7 @@ class ConstantVisitor extends UnifyingAstVisitor<EvaluationResultImpl> { |
} |
if (element is VariableElementImpl) { |
VariableElementImpl variableElementImpl = element; |
+ beforeGetEvaluationResult(node); |
EvaluationResultImpl value = variableElementImpl.evaluationResult; |
if (variableElementImpl.isConst && value != null) { |
return value; |
@@ -825,18 +1166,6 @@ class ConstantVisitor extends UnifyingAstVisitor<EvaluationResultImpl> { |
} |
/** |
- * Return an object representing the value 'null'. |
- * |
- * @return an object representing the value 'null' |
- */ |
- DartObjectImpl get null2 { |
- if (_nullObject == null) { |
- _nullObject = new DartObjectImpl(_typeProvider.nullType, NullState.NULL_STATE); |
- } |
- return _nullObject; |
- } |
- |
- /** |
* Return the union of the errors encoded in the given results. |
* |
* @param leftResult the first set of errors, or `null` if there was no previous collection |
@@ -855,39 +1184,6 @@ class ConstantVisitor extends UnifyingAstVisitor<EvaluationResultImpl> { |
} |
return leftResult; |
} |
- |
- ValidResult _valid(InterfaceType type, InstanceState state) => new ValidResult(new DartObjectImpl(type, state)); |
- |
- ValidResult _validWithUnknownValue(InterfaceType type) { |
- if (type.element.library.isDartCore) { |
- String typeName = type.name; |
- if (typeName == "bool") { |
- return _valid(type, BoolState.UNKNOWN_VALUE); |
- } else if (typeName == "double") { |
- return _valid(type, DoubleState.UNKNOWN_VALUE); |
- } else if (typeName == "int") { |
- return _valid(type, IntState.UNKNOWN_VALUE); |
- } else if (typeName == "String") { |
- return _valid(type, StringState.UNKNOWN_VALUE); |
- } |
- } |
- return _valid(type, GenericState.UNKNOWN_VALUE); |
- } |
- |
- /** |
- * Return the value of the given expression, or a representation of 'null' if the expression |
- * cannot be evaluated. |
- * |
- * @param expression the expression whose value is to be returned |
- * @return the value of the given expression |
- */ |
- DartObjectImpl _valueOf(Expression expression) { |
- EvaluationResultImpl expressionValue = expression.accept(this); |
- if (expressionValue is ValidResult) { |
- return expressionValue.value; |
- } |
- return null2; |
- } |
} |
/** |
@@ -1143,6 +1439,8 @@ class DartObjectImpl implements DartObject { |
return null; |
} |
+ Map<String, DartObjectImpl> get fields => _state.fields; |
+ |
@override |
int get intValue { |
if (_state is IntState) { |
@@ -1223,6 +1521,11 @@ class DartObjectImpl implements DartObject { |
bool get isTrue => _state is BoolState && identical((_state as BoolState).value, true); |
/** |
+ * Return true if this object represents an unknown value. |
+ */ |
+ bool get isUnknown => _state.isUnknown; |
+ |
+ /** |
* Return `true` if this object represents an instance of a user-defined class. |
* |
* @return `true` if this object represents an instance of a user-defined class |
@@ -2449,6 +2752,11 @@ class GenericState extends InstanceState { |
final Map<String, DartObjectImpl> _fieldMap; |
/** |
+ * Pseudo-field that we use to represent fields in the superclass. |
+ */ |
+ static String SUPERCLASS_FIELD = "(super)"; |
+ |
+ /** |
* A state that can be used to represent an object whose state is not known. |
*/ |
static GenericState UNKNOWN_VALUE = new GenericState(new Map<String, DartObjectImpl>()); |
@@ -2464,6 +2772,15 @@ class GenericState extends InstanceState { |
StringState convertToString() => StringState.UNKNOWN_VALUE; |
@override |
+ BoolState equalEqual(InstanceState rightOperand) { |
+ assertBoolNumStringOrNull(rightOperand); |
+ if (rightOperand is DynamicState) { |
+ return BoolState.UNKNOWN_VALUE; |
+ } |
+ return BoolState.from(this == rightOperand); |
+ } |
+ |
+ @override |
bool operator ==(Object object) { |
if (object is! GenericState) { |
return false; |
@@ -2485,13 +2802,7 @@ class GenericState extends InstanceState { |
} |
@override |
- BoolState equalEqual(InstanceState rightOperand) { |
- assertBoolNumStringOrNull(rightOperand); |
- if (rightOperand is DynamicState) { |
- return BoolState.UNKNOWN_VALUE; |
- } |
- return BoolState.from(this == rightOperand); |
- } |
+ Map<String, DartObjectImpl> get fields => _fieldMap; |
@override |
String get typeName => "user defined type"; |
@@ -2504,6 +2815,9 @@ class GenericState extends InstanceState { |
} |
return hashCode; |
} |
+ |
+ @override |
+ bool get isUnknown => identical(this, UNKNOWN_VALUE); |
} |
/** |
@@ -2625,6 +2939,12 @@ abstract class InstanceState { |
BoolState equalEqual(InstanceState rightOperand); |
/** |
+ * If this represents a generic dart object, return a map from its fieldnames to their values. |
+ * Otherwise return null. |
+ */ |
+ Map<String, DartObjectImpl> get fields => null; |
+ |
+ /** |
* Return the name of the type of this value. |
* |
* @return the name of the type of this value |
@@ -2702,6 +3022,11 @@ abstract class InstanceState { |
bool get isBoolNumStringOrNull => false; |
/** |
+ * Return true if this object represents an unknown value. |
+ */ |
+ bool get isUnknown => false; |
+ |
+ /** |
* Return the result of invoking the '<' operator on this object with the given argument. |
* |
* @param rightOperand the right-hand operand of the operation |
@@ -3602,15 +3927,15 @@ class NumState extends InstanceState { |
} |
@override |
- bool operator ==(Object object) => object is NumState; |
- |
- @override |
BoolState equalEqual(InstanceState rightOperand) { |
assertBoolNumStringOrNull(rightOperand); |
return BoolState.UNKNOWN_VALUE; |
} |
@override |
+ bool operator ==(Object object) => object is NumState; |
+ |
+ @override |
String get typeName => "num"; |
@override |
@@ -3648,6 +3973,9 @@ class NumState extends InstanceState { |
bool get isBoolNumStringOrNull => true; |
@override |
+ bool get isUnknown => identical(this, UNKNOWN_VALUE); |
+ |
+ @override |
BoolState lessThan(InstanceState rightOperand) { |
assertNumOrNull(rightOperand); |
return BoolState.UNKNOWN_VALUE; |
@@ -3690,15 +4018,25 @@ class NumState extends InstanceState { |
*/ |
class ReferenceFinder extends RecursiveAstVisitor<Object> { |
/** |
- * The element representing the variable whose initializer will be visited. |
+ * The element representing the construct that will be visited. |
*/ |
- final VariableElement _source; |
+ final AstNode _source; |
/** |
* A graph in which the nodes are the constant variables and the edges are from each variable to |
* the other constant variables that are referenced in the head's initializer. |
*/ |
- final DirectedGraph<VariableElement> _referenceGraph; |
+ final DirectedGraph<AstNode> _referenceGraph; |
+ |
+ /** |
+ * A table mapping constant variables to the declarations of those variables. |
+ */ |
+ final Map<VariableElement, VariableDeclaration> _variableDeclarationMap; |
+ |
+ /** |
+ * A table mapping constant constructors to the declarations of those constructors. |
+ */ |
+ final Map<ConstructorElement, ConstructorDeclaration> _constructorDeclarationMap; |
/** |
* Initialize a newly created reference finder to find references from the given variable to other |
@@ -3707,8 +4045,20 @@ class ReferenceFinder extends RecursiveAstVisitor<Object> { |
* @param source the element representing the variable whose initializer will be visited |
* @param referenceGraph a graph recording which variables (heads) reference which other variables |
* (tails) in their initializers |
+ * @param variableDeclarationMap A table mapping constant variables to the declarations of those |
+ * variables. |
+ * @param constructorDeclarationMap A table mapping constant constructors to the declarations of |
+ * those constructors. |
*/ |
- ReferenceFinder(this._source, this._referenceGraph); |
+ ReferenceFinder(this._source, this._referenceGraph, this._variableDeclarationMap, this._constructorDeclarationMap); |
+ |
+ @override |
+ Object visitInstanceCreationExpression(InstanceCreationExpression node) { |
+ if (node.isConst) { |
+ _referenceGraph.addEdge(_source, node); |
+ } |
+ return null; |
+ } |
@override |
Object visitSimpleIdentifier(SimpleIdentifier node) { |
@@ -3719,7 +4069,31 @@ class ReferenceFinder extends RecursiveAstVisitor<Object> { |
if (element is VariableElement) { |
VariableElement variable = element as VariableElement; |
if (variable.isConst) { |
- _referenceGraph.addEdge(_source, variable); |
+ VariableDeclaration variableDeclaration = _variableDeclarationMap[variable]; |
+ // The declaration will be null when the variable is not defined in the compilation units |
+ // that were used to produce the variableDeclarationMap. In such cases, the variable should |
+ // already have a value associated with it, but we don't bother to check because there's |
+ // nothing we can do about it at this point. |
+ if (variableDeclaration != null) { |
+ _referenceGraph.addEdge(_source, variableDeclaration); |
+ } |
+ } |
+ } |
+ return null; |
+ } |
+ |
+ @override |
+ Object visitSuperConstructorInvocation(SuperConstructorInvocation node) { |
+ super.visitSuperConstructorInvocation(node); |
+ ConstructorElement constructor = node.staticElement; |
+ if (constructor != null && constructor.isConst) { |
+ ConstructorDeclaration constructorDeclaration = _constructorDeclarationMap[constructor]; |
+ // The declaration will be null when the constructor is not defined in the compilation |
+ // units that were used to produce the constructorDeclarationMap. In such cases, the |
+ // constructor should already have its initializer AST's stored in it, but we don't bother |
+ // to check because there's nothing we can do about it at this point. |
+ if (constructorDeclaration != null) { |
+ _referenceGraph.addEdge(_source, constructorDeclaration); |
} |
} |
return null; |
@@ -3802,6 +4176,9 @@ class StringState extends InstanceState { |
bool get isBoolNumStringOrNull => true; |
@override |
+ bool get isUnknown => value == null; |
+ |
+ @override |
String toString() => value == null ? "-unknown-" : "'${value}'"; |
} |