[CFE] Preserve errors in unevaluated and unused arguments and locals
Change-Id: If8c23ac2f374ee863fd8df4c241195c0a115e69d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/104980
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Aske Simon Christensen <askesc@google.com>
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 d334dac..175f066 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 @@
// 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 @@
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 @@
'"${init.runtimeType}".');
}
}
+
+ for (UnevaluatedConstant constant in env.unevaluatedUnreadConstants) {
+ instanceBuilder.unusedArguments.add(extract(constant));
+ }
});
});
}
@@ -1763,7 +1778,7 @@
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 @@
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 @@
}
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 @@
final List<AssertStatement> asserts = <AssertStatement>[];
+ final List<Expression> unusedArguments = <Expression>[];
+
InstanceBuilder(this.evaluator, this.klass, this.typeArguments);
void setFieldValue(Field field, Constant constant) {
@@ -2131,6 +2148,7 @@
assert(value is! UnevaluatedConstant);
fieldValues[field.reference] = value;
});
+ assert(unusedArguments.isEmpty);
return new InstanceConstant(klass.reference, typeArguments, fieldValues);
}
@@ -2139,9 +2157,8 @@
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 @@
final Map<VariableDeclaration, Constant> _variables =
<VariableDeclaration, Constant>{};
+ /// 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<VariableDeclaration> _unreadUnevaluatedVariables =
+ new Set<VariableDeclaration>();
+
/// Whether the current environment is empty.
bool get isEmpty => _typeVariables.isEmpty && _variables.isEmpty;
@@ -2165,6 +2189,9 @@
void addVariableValue(VariableDeclaration variable, Constant value) {
_variables[variable] = value;
+ if (value is UnevaluatedConstant) {
+ _unreadUnevaluatedVariables.add(variable);
+ }
}
DartType lookupParameterValue(TypeParameter parameter) {
@@ -2174,10 +2201,21 @@
}
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<UnevaluatedConstant> get unevaluatedUnreadConstants {
+ if (_unreadUnevaluatedVariables.isEmpty) return const [];
+ return _unreadUnevaluatedVariables.map<UnevaluatedConstant>(
+ (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 1ed2501..8eb6746 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 @@
'const C2<A>(id)',
'ConstructedConstant(C2<A>(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<T extends A> {
+ const Class(T t);
+ const Class.redirect(dynamic t) : this(t);
+ const Class.method(T t) : this(-t);
+}
+class Subclass<T extends A> extends Class<T> {
+ const Subclass(dynamic t) : super(t);
+}
+''', const <ConstantData>[
+ const ConstantData(
+ 'const Class<A>(const A())', 'ConstructedConstant(Class<A>())'),
+ const ConstantData('const Class<B>.redirect(const B())',
+ 'ConstructedConstant(Class<B>())'),
+ const ConstantData('const Class<B>.redirect(const C())', 'NonConstant',
+ expectedErrors: 'ConstEvalInvalidType'),
+ const ConstantData('const Class<A>.method(const A())', 'NonConstant',
+ expectedErrors: 'ConstEvalInvalidMethodInvocation'),
+ const ConstantData(
+ 'const Subclass<A>(const A())', 'ConstructedConstant(Subclass<A>())'),
+ const ConstantData(
+ 'const Subclass<B>(const B())', 'ConstructedConstant(Subclass<B>())'),
+ const ConstantData('const Subclass<B>(const C())', 'NonConstant',
+ expectedErrors: 'ConstEvalInvalidType'),
+ ]),
];
main(List<String> 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 0000000..acd2076
--- /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
+}