Chromium Code Reviews| Index: lib/compiler/implementation/compile_time_constants.dart |
| diff --git a/lib/compiler/implementation/compile_time_constants.dart b/lib/compiler/implementation/compile_time_constants.dart |
| index 87792a842b6d10d6b339d87c06449debb8d094db..414bf63057ea5cea5c7792181af4cc8451b94470 100644 |
| --- a/lib/compiler/implementation/compile_time_constants.dart |
| +++ b/lib/compiler/implementation/compile_time_constants.dart |
| @@ -477,24 +477,32 @@ class ConstructedConstant extends ObjectConstant { |
| /** |
| * The [ConstantHandler] keeps track of compile-time constants, |
| - * initializations of global and static fields, and default values of |
| + * initializations of global and static const fields, and default values of |
| * optional parameters. |
| */ |
| class ConstantHandler extends CompilerTask { |
| - // Contains the initial value of fields. Must contain all static and global |
| - // initializations of used fields. May contain caches for instance fields. |
| + /** |
| + * Contains the initial value of fields. Must contain all static and global |
| + * initializations of const fields. May contain eagerly compiled values for |
| + * statics and instance fields. |
| + */ |
| final Map<VariableElement, Constant> initialVariableValues; |
| - // Map from compile-time constants to their JS name. |
| + /** Map from compile-time constants to their JS name.*/ |
|
kasperl
2012/08/16 14:41:37
Space between . and */
floitsch
2012/08/16 22:52:33
Done.
|
| final Map<Constant, String> compiledConstants; |
| - // The set of variable elements that are in the process of being computed. |
| + /** The set of variable elements that are in the process of being computed.*/ |
|
kasperl
2012/08/16 14:41:37
Space between . and */
floitsch
2012/08/16 22:52:33
Done.
|
| final Set<VariableElement> pendingVariables; |
| + /** Caches the statics where the initial value cannot be eagerly compiled. */ |
| + final Set<VariableElement> lazyStatics; |
| + |
| + |
| ConstantHandler(Compiler compiler) |
| : initialVariableValues = new Map<VariableElement, Dynamic>(), |
| compiledConstants = new Map<Constant, String>(), |
| pendingVariables = new Set<VariableElement>(), |
| + lazyStatics = new Set<VariableElement>(), |
| super(compiler); |
| String get name() => 'ConstantHandler'; |
| @@ -505,44 +513,78 @@ class ConstantHandler extends CompilerTask { |
| /** |
| * Compiles the initial value of the given field and stores it in an internal |
| - * map. |
| + * map. Returns the initial value (a constant) if it can be computed |
| + * statically. Returns `null` if the variable must be initialized lazily. |
|
kasperl
2012/08/16 14:41:37
`null` -- back ticks, really?
floitsch
2012/08/16 22:52:33
I find it nice to read and it's the code-tag in ma
|
| * |
| * [WorkItem] must contain a [VariableElement] refering to a global or |
| * static field. |
| */ |
| - void compileWorkItem(WorkItem work) { |
| - measure(() { |
| + Constant compileWorkItem(WorkItem work) { |
| + return measure(() { |
| assert(work.element.kind == ElementKind.FIELD |
| || work.element.kind == ElementKind.PARAMETER |
| || work.element.kind == ElementKind.FIELD_PARAMETER); |
| VariableElement element = work.element; |
| + Constant result; |
| // Shortcut if it has already been compiled. |
| - if (initialVariableValues.containsKey(element)) return; |
| - compileVariableWithDefinitions(element, work.resolutionTree); |
| + result = initialVariableValues[element]; |
| + if (result != null) return result; |
| + if (lazyStatics.contains(element)) return null; |
| + result = compileVariableWithDefinitions(element, work.resolutionTree); |
| assert(pendingVariables.isEmpty()); |
| + return result; |
| }); |
| } |
| - Constant compileVariable(VariableElement element) { |
| + /** |
| + * Returns a compile-time constant, or reports an error if the element is not |
| + * a compile-time constant. |
| + */ |
| + Constant compileConstant(VariableElement element) { |
| + return compileVariable(element, isConst: true); |
| + } |
| + |
| + /** |
| + * Returns the a compile-time constant if the variable could be compiled |
| + * eagerly. Otherwise returns `null`. |
| + */ |
| + Constant compileVariable(VariableElement element, [bool isConst = false]) { |
| return measure(() { |
| if (initialVariableValues.containsKey(element)) { |
| Constant result = initialVariableValues[element]; |
| return result; |
| } |
| TreeElements definitions = compiler.analyzeElement(element); |
| - Constant constant = compileVariableWithDefinitions(element, definitions); |
| + Constant constant = compileVariableWithDefinitions( |
| + element, definitions, isConst: isConst); |
| return constant; |
| }); |
| } |
| + /** |
| + * Returns the a compile-time constant if the variable could be compiled |
| + * eagerly. If the variable needs to be initialized lazily returns `null`. |
| + * If the variable is `const` but cannot be compiled eagerly reports an |
| + * error. |
| + */ |
| Constant compileVariableWithDefinitions(VariableElement element, |
| - TreeElements definitions) { |
| + TreeElements definitions, |
| + [bool isConst = false]) { |
| return measure(() { |
| + // Initializers for fields, or parameters must be const. |
| + isConst = isConst || !element.modifiers.isFinal(); |
| + if (!isConst && lazyStatics.contains(element)) return null; |
| + |
| Node node = element.parseNode(compiler); |
| if (pendingVariables.contains(element)) { |
| - MessageKind kind = MessageKind.CYCLIC_COMPILE_TIME_CONSTANTS; |
| - compiler.reportError(node, |
| - new CompileTimeConstantError(kind, const [])); |
| + if (isConst) { |
| + MessageKind kind = MessageKind.CYCLIC_COMPILE_TIME_CONSTANTS; |
| + compiler.reportError(node, |
| + new CompileTimeConstantError(kind, const [])); |
| + } else { |
| + lazyStatics.add(element); |
| + return null; |
| + } |
| } |
| pendingVariables.add(element); |
| @@ -553,19 +595,27 @@ class ConstantHandler extends CompilerTask { |
| value = new NullConstant(); |
| } else { |
| Node right = assignment.arguments.head; |
| - value = compileNodeWithDefinitions(right, definitions); |
| + value = |
| + compileNodeWithDefinitions(right, definitions, isConst: isConst); |
| + } |
| + if (value != null) { |
| + initialVariableValues[element] = value; |
| + } else { |
| + assert(!isConst); |
| + lazyStatics.add(element); |
| } |
| - initialVariableValues[element] = value; |
| pendingVariables.remove(element); |
| return value; |
| }); |
| } |
| - Constant compileNodeWithDefinitions(Node node, TreeElements definitions) { |
| + Constant compileNodeWithDefinitions(Node node, |
| + TreeElements definitions, |
| + [bool isConst]) { |
| return measure(() { |
| assert(node !== null); |
| CompileTimeConstantEvaluator evaluator = |
| - new CompileTimeConstantEvaluator(definitions, compiler); |
| + new CompileTimeConstantEvaluator(definitions, compiler, isConst); |
| return evaluator.evaluate(node); |
| }); |
| } |
| @@ -610,6 +660,10 @@ class ConstantHandler extends CompilerTask { |
| }); |
| } |
| + List<VariableElement> getLazilyInitializedFieldsForEmission() { |
| + return new List<VariableElement>.from(lazyStatics); |
| + } |
| + |
| List<Constant> getConstantsForEmission() { |
| // We must emit dependencies before their uses. |
| Set<Constant> seenConstants = new Set<Constant>(); |
| @@ -713,17 +767,29 @@ class ConstantHandler extends CompilerTask { |
| } |
| class CompileTimeConstantEvaluator extends AbstractVisitor { |
| + bool isEvaluatingConstant; |
| final TreeElements elements; |
| final Compiler compiler; |
| - CompileTimeConstantEvaluator(this.elements, this.compiler); |
| + CompileTimeConstantEvaluator(this.elements, this.compiler, [bool isConst]) |
| + : this.isEvaluatingConstant = isConst; |
| Constant evaluate(Node node) { |
| return node.accept(this); |
| } |
| - visitNode(Node node) { |
| - error(node); |
| + Constant evaluateConstant(Node node) { |
| + bool oldIsEvaluatingConstant = isEvaluatingConstant; |
| + isEvaluatingConstant = true; |
| + Constant result = node.accept(this); |
| + isEvaluatingConstant = oldIsEvaluatingConstant; |
| + assert(result != null); |
| + return result; |
| + } |
| + |
| + Constant visitNode(Node node) { |
| + signalNotACompileTimeConstant(node); |
| + return null; |
| } |
| Constant visitLiteralBool(LiteralBool node) { |
| @@ -739,12 +805,15 @@ class CompileTimeConstantEvaluator extends AbstractVisitor { |
| } |
| Constant visitLiteralList(LiteralList node) { |
| - if (!node.isConst()) error(node); |
| + if (!node.isConst()) { |
| + signalNotACompileTimeConstant(node); |
| + return null; |
| + } |
| List<Constant> arguments = <Constant>[]; |
| for (Link<Node> link = node.elements.nodes; |
| !link.isEmpty(); |
| link = link.tail) { |
| - arguments.add(evaluate(link.head)); |
| + arguments.add(evaluateConstant(link.head)); |
| } |
| // TODO(floitsch): get type from somewhere. |
| Type type = null; |
| @@ -754,21 +823,24 @@ class CompileTimeConstantEvaluator extends AbstractVisitor { |
| } |
| Constant visitLiteralMap(LiteralMap node) { |
| - if (!node.isConst()) error(node); |
| + if (!node.isConst()) { |
| + signalNotACompileTimeConstant(node); |
| + error(node); |
| + } |
| List<StringConstant> keys = <StringConstant>[]; |
| Map<StringConstant, Constant> map = new Map<StringConstant, Constant>(); |
| for (Link<Node> link = node.entries.nodes; |
| !link.isEmpty(); |
| link = link.tail) { |
| LiteralMapEntry entry = link.head; |
| - Constant key = evaluate(entry.key); |
| + Constant key = evaluateConstant(entry.key); |
| if (!key.isString() || entry.key.asStringNode() === null) { |
| MessageKind kind = MessageKind.KEY_NOT_A_STRING_LITERAL; |
| compiler.reportError(entry.key, new ResolutionError(kind, const [])); |
| } |
| StringConstant keyConstant = key; |
| if (!map.containsKey(key)) keys.add(key); |
| - map[key] = evaluate(entry.value); |
| + map[key] = evaluateConstant(entry.value); |
| } |
| List<Constant> values = <Constant>[]; |
| Constant protoValue = null; |
| @@ -808,12 +880,14 @@ class CompileTimeConstantEvaluator extends AbstractVisitor { |
| Constant visitStringJuxtaposition(StringJuxtaposition node) { |
| StringConstant left = evaluate(node.first); |
| StringConstant right = evaluate(node.second); |
| + if (left == null || right == null) return null; |
| return new StringConstant(new DartString.concat(left.value, right.value), |
| node); |
| } |
| Constant visitStringInterpolation(StringInterpolation node) { |
| StringConstant initialString = evaluate(node.string); |
| + if (initialString == null) return null; |
| DartString accumulator = initialString.value; |
| for (StringInterpolationPart part in node.parts) { |
| Constant expression = evaluate(part.expression); |
| @@ -825,10 +899,12 @@ class CompileTimeConstantEvaluator extends AbstractVisitor { |
| PrimitiveConstant primitive = expression; |
| expressionString = primitive.value; |
| } else { |
| - error(part.expression); |
| + signalNotACompileTimeConstant(part.expression); |
| + return null; |
| } |
| accumulator = new DartString.concat(accumulator, expressionString); |
| StringConstant partString = evaluate(part.string); |
| + if (partString == null) return null; |
| accumulator = new DartString.concat(accumulator, partString.value); |
| }; |
| return new StringConstant(accumulator, node); |
| @@ -838,14 +914,22 @@ class CompileTimeConstantEvaluator extends AbstractVisitor { |
| Constant visitSend(Send send) { |
| Element element = elements[send]; |
| if (Elements.isStaticOrTopLevelField(element)) { |
| - if (element.modifiers === null || |
| - !element.modifiers.isFinal()) { |
| - error(send); |
| + Constant result; |
| + if (element.modifiers !== null) { |
| + if (element.modifiers.isConst()) { |
| + result = compiler.compileConstant(element); |
| + } else if (element.modifiers.isFinal()) { |
| + // TODO(4516): remove support for final compile-time constants: if |
| + // isCompilingConstant is true don't compile the variable. |
| + result = compiler.compileVariable(element); |
| + } |
| } |
| - return compiler.compileVariable(element); |
| + if (result == null) signalNotACompileTimeConstant(send); |
| + return result; |
| } else if (send.isPrefix) { |
| assert(send.isOperator); |
| Constant receiverConstant = evaluate(send.receiver); |
| + if (receiverConstant == null) return null; |
| Operator op = send.selector; |
| Constant folded; |
| switch (op.source.stringValue) { |
| @@ -862,12 +946,13 @@ class CompileTimeConstantEvaluator extends AbstractVisitor { |
| compiler.internalError("Unexpected operator.", node: op); |
| break; |
| } |
| - if (folded === null) error(send); |
| + if (folded === null) signalNotACompileTimeConstant(send); |
| return folded; |
| } else if (send.isOperator && !send.isPostfix) { |
| assert(send.argumentCount() == 1); |
| Constant left = evaluate(send.receiver); |
| Constant right = evaluate(send.argumentsNode.nodes.head); |
| + if (left == null || right == null) return null; |
| Operator op = send.selector.asOperator(); |
| Constant folded = null; |
| switch (op.source.stringValue) { |
| @@ -954,14 +1039,16 @@ class CompileTimeConstantEvaluator extends AbstractVisitor { |
| } |
| break; |
| } |
| - if (folded === null) error(send); |
| + if (folded === null) signalNotACompileTimeConstant(send); |
| return folded; |
| } |
| - return super.visitSend(send); |
| + signalNotACompileTimeConstant(send); |
| + return null; |
| } |
| - visitSendSet(SendSet node) { |
| - error(node); |
| + Constant visitSendSet(SendSet node) { |
| + signalNotACompileTimeConstant(node); |
| + return null; |
| } |
| /** Returns the list of constants that are passed to the static function. */ |
| @@ -970,8 +1057,8 @@ class CompileTimeConstantEvaluator extends AbstractVisitor { |
| FunctionElement target) { |
| List<Constant> compiledArguments = <Constant>[]; |
| - Function compileArgument = evaluate; |
| - Function compileConstant = compiler.compileVariable; |
| + Function compileArgument = evaluateConstant; |
| + Function compileConstant = compiler.compileConstant; |
| bool succeeded = selector.addArgumentsToList(arguments, |
| compiledArguments, |
| target, |
| @@ -983,7 +1070,10 @@ class CompileTimeConstantEvaluator extends AbstractVisitor { |
| } |
| Constant visitNewExpression(NewExpression node) { |
| - if (!node.isConst()) error(node); |
| + if (!node.isConst()) { |
| + signalNotACompileTimeConstant(node); |
| + return null; |
| + } |
| Send send = node.send; |
| FunctionElement constructor = elements[send]; |
| @@ -1020,11 +1110,20 @@ class CompileTimeConstantEvaluator extends AbstractVisitor { |
| MessageKind kind = MessageKind.NOT_A_COMPILE_TIME_CONSTANT; |
| compiler.reportError(node, new CompileTimeConstantError(kind, const [])); |
| } |
| + |
| + void signalNotACompileTimeConstant(Node node) { |
| + if (isEvaluatingConstant) { |
| + error(node); |
| + } |
| + // Else we don't need to do anything. The final handler is only |
| + // optimistically trying to compile constants. So it is normal that we |
| + // sometimes see non-compile time constants. |
| + } |
| } |
| class TryCompileTimeConstantEvaluator extends CompileTimeConstantEvaluator { |
| TryCompileTimeConstantEvaluator(TreeElements elements, Compiler compiler): |
| - super(elements, compiler); |
| + super(elements, compiler, isConst: true); |
| error(Node node) { |
| // Just fail without reporting it anywhere. |
| @@ -1043,7 +1142,8 @@ class ConstructorEvaluator extends CompileTimeConstantEvaluator { |
| this.definitions = new Map<Element, Constant>(), |
| this.fieldValues = new Map<Element, Constant>(), |
| super(compiler.resolver.resolveMethodElement(constructor), |
| - compiler); |
| + compiler, |
| + isConst: true); |
| Constant visitSend(Send send) { |
| Element element = elements[send]; |
| @@ -1166,7 +1266,7 @@ class ConstructorEvaluator extends CompileTimeConstantEvaluator { |
| Constant fieldValue = fieldValues[field]; |
| if (fieldValue === null) { |
| // Use the default value. |
| - fieldValue = compiler.compileVariable(field); |
| + fieldValue = compiler.compileConstant(field); |
| } |
| jsNewArguments.add(fieldValue); |
| }); |