diff --git a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart index d334dac1693..175f066e3fe 100644 --- a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart +++ b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart @@ -306,7 +306,12 @@ class ConstantsTransformer extends Transformer { // If this constant is inlined, remove it. if (!keepVariables && shouldInline(node.initializer)) { - return null; + if (constant is! UnevaluatedConstant) { + // If the constant is unevaluated we need to keep the expression, + // so that, in the case the constant contains error but the local + // is unused, the error will still be reported. + return null; + } } } else { node.initializer = node.initializer.accept(this)..parent = node; @@ -933,6 +938,12 @@ class ConstantEvaluator extends RecursiveVisitor { instanceBuilder.setFieldValue( fieldRef.asField, _evaluateSubexpression(value)); }); + node.unusedArguments.forEach((Expression value) { + Constant constant = _evaluateSubexpression(value); + if (constant is UnevaluatedConstant) { + instanceBuilder.unusedArguments.add(extract(constant)); + } + }); if (shouldBeUnevaluated) { return unevaluated(node, instanceBuilder.buildUnevaluatedInstance()); } @@ -1133,6 +1144,10 @@ class ConstantEvaluator extends RecursiveVisitor { '"${init.runtimeType}".'); } } + + for (UnevaluatedConstant constant in env.unevaluatedUnreadConstants) { + instanceBuilder.unusedArguments.add(extract(constant)); + } }); }); } @@ -1763,7 +1778,7 @@ class ConstantEvaluator extends RecursiveVisitor { final Constant constant = _evaluateSubexpression(node.operand); if (shouldBeUnevaluated) { return unevaluated(node, - new AsExpression(extract(constant), env.subsituteType(node.type))); + new AsExpression(extract(constant), env.substituteType(node.type))); } return ensureIsSubtype(constant, evaluateDartType(node, node.type), node); } @@ -1808,7 +1823,7 @@ class ConstantEvaluator extends RecursiveVisitor { return unevaluated( node, new Instantiation(extract(constant), - node.typeArguments.map((t) => env.subsituteType(t)).toList())); + node.typeArguments.map((t) => env.substituteType(t)).toList())); } if (constant is TearOffConstant) { if (node.typeArguments.length == @@ -1927,7 +1942,7 @@ class ConstantEvaluator extends RecursiveVisitor { } DartType evaluateDartType(TreeNode node, DartType type) { - final result = env.subsituteType(type); + final result = env.substituteType(type); if (!isInstantiated(result)) { return report( @@ -2118,6 +2133,8 @@ class InstanceBuilder { final List asserts = []; + final List unusedArguments = []; + InstanceBuilder(this.evaluator, this.klass, this.typeArguments); void setFieldValue(Field field, Constant constant) { @@ -2131,6 +2148,7 @@ class InstanceBuilder { assert(value is! UnevaluatedConstant); fieldValues[field.reference] = value; }); + assert(unusedArguments.isEmpty); return new InstanceConstant(klass.reference, typeArguments, fieldValues); } @@ -2139,9 +2157,8 @@ class InstanceBuilder { fields.forEach((Field field, Constant value) { fieldValues[field.reference] = evaluator.extract(value); }); - // TODO(askesc): Put actual unused arguments. return new InstanceCreation( - klass.reference, typeArguments, fieldValues, asserts, []); + klass.reference, typeArguments, fieldValues, asserts, unusedArguments); } } @@ -2155,6 +2172,13 @@ class EvaluationEnvironment { final Map _variables = {}; + /// The variables that hold unevaluated constants. + /// + /// Variables are removed from this set when looked up, leaving only the + /// unread variables at the end. + final Set _unreadUnevaluatedVariables = + new Set(); + /// Whether the current environment is empty. bool get isEmpty => _typeVariables.isEmpty && _variables.isEmpty; @@ -2165,6 +2189,9 @@ class EvaluationEnvironment { void addVariableValue(VariableDeclaration variable, Constant value) { _variables[variable] = value; + if (value is UnevaluatedConstant) { + _unreadUnevaluatedVariables.add(variable); + } } DartType lookupParameterValue(TypeParameter parameter) { @@ -2174,10 +2201,21 @@ class EvaluationEnvironment { } Constant lookupVariable(VariableDeclaration variable) { - return _variables[variable]; + Constant value = _variables[variable]; + if (value is UnevaluatedConstant) { + _unreadUnevaluatedVariables.remove(variable); + } + return value; } - DartType subsituteType(DartType type) { + /// The unevaluated constants of variables that were never read. + Iterable get unevaluatedUnreadConstants { + if (_unreadUnevaluatedVariables.isEmpty) return const []; + return _unreadUnevaluatedVariables.map( + (VariableDeclaration variable) => _variables[variable]); + } + + DartType substituteType(DartType type) { if (_typeVariables.isEmpty) return type; return substitute(type, _typeVariables); } diff --git a/tests/compiler/dart2js/model/cfe_constant_evaluation_test.dart b/tests/compiler/dart2js/model/cfe_constant_evaluation_test.dart index 1ed2501ecd7..8eb674687d1 100644 --- a/tests/compiler/dart2js/model/cfe_constant_evaluation_test.dart +++ b/tests/compiler/dart2js/model/cfe_constant_evaluation_test.dart @@ -493,7 +493,47 @@ class C2 { 'const C2(id)', 'ConstructedConstant(C2(a=' 'InstantiationConstant([A],FunctionConstant(id))))'), - ]) + ]), + const TestData('unused-arguments', ''' +class A { + const A(); + + A operator -() => this; +} +class B implements A { + const B(); + + B operator -() => this; +} +class C implements A { + const C(); + + C operator -() => this; +} +class Class { + const Class(T t); + const Class.redirect(dynamic t) : this(t); + const Class.method(T t) : this(-t); +} +class Subclass extends Class { + const Subclass(dynamic t) : super(t); +} +''', const [ + const ConstantData( + 'const Class(const A())', 'ConstructedConstant(Class())'), + const ConstantData('const Class.redirect(const B())', + 'ConstructedConstant(Class())'), + const ConstantData('const Class.redirect(const C())', 'NonConstant', + expectedErrors: 'ConstEvalInvalidType'), + const ConstantData('const Class.method(const A())', 'NonConstant', + expectedErrors: 'ConstEvalInvalidMethodInvocation'), + const ConstantData( + 'const Subclass(const A())', 'ConstructedConstant(Subclass())'), + const ConstantData( + 'const Subclass(const B())', 'ConstructedConstant(Subclass())'), + const ConstantData('const Subclass(const C())', 'NonConstant', + expectedErrors: 'ConstEvalInvalidType'), + ]), ]; main(List args) { diff --git a/tests/compiler/dart2js_extra/unused_local_const_test.dart b/tests/compiler/dart2js_extra/unused_local_const_test.dart new file mode 100644 index 00000000000..acd2076bf46 --- /dev/null +++ b/tests/compiler/dart2js_extra/unused_local_const_test.dart @@ -0,0 +1,17 @@ +// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// dart2jsOptions=--enable-experiment=constant-update-2018 + +class A { + const A(); + + A operator -() => this; +} + +main() { + const a = const A(); + const c = //# 01: compile-time error + const bool.fromEnvironment('foo') ? null : -a; //# 01: continued +}