| 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}'";
|
| }
|
|
|
|
|