Version 2.13.0-87.0.dev
Merge commit '46434442051eb8c112da536cb4e485ba8b745f09' into 'dev'
diff --git a/DEPS b/DEPS
index 31d2194..35ba597 100644
--- a/DEPS
+++ b/DEPS
@@ -143,13 +143,13 @@
"shelf_static_rev": "bafde9eaddb5d02040a614e41deddd971b4d67e6",
"shelf_packages_handler_rev": "78302e67c035047e6348e692b0c1182131f0fe35",
"shelf_proxy_tag": "0.1.0+7",
- "shelf_rev": "fa5afaa38bd51dedeeaa25b7bfd8822cabbcc57f",
- "shelf_web_socket_rev": "091d2ed2105827b7f376668fafc88b2b8c845d71",
+ "shelf_rev": "e9294125f0c1fc2049c958577cd586ca2395168f",
+ "shelf_web_socket_rev": "aa312d3cdeef96fb64bc3e602bc354a05a995624",
"source_map_stack_trace_rev": "1c3026f69d9771acf2f8c176a1ab750463309cce",
"source_maps-0.9.4_rev": "38524",
"source_maps_rev": "53eb92ccfe6e64924054f83038a534b959b12b3e",
"source_span_rev": "1be3c44045a06dff840d2ed3a13e6082d7a03a23",
- "sse_tag": "8add37c0ec0419ce28153a03f299d44ce006a975",
+ "sse_tag": "5da8fedcdc56f306933d202e2d204753eecefd36",
"stack_trace_tag": "6788afc61875079b71b3d1c3e65aeaa6a25cbc2f",
"stagehand_rev": "e64ac90cac508981011299c4ceb819149e71f1bd",
"stream_channel_tag": "d7251e61253ec389ee6e045ee1042311bced8f1d",
@@ -229,6 +229,13 @@
}],
"dep_type": "cipd",
},
+ Var("dart_root") + "/third_party/devtools": {
+ "packages": [{
+ "package": "dart/third_party/flutter/devtools",
+ "version": "revision:6729ec62c3548839018c32fa711756202431ccf7",
+ }],
+ "dep_type": "cipd",
+ },
Var("dart_root") + "/tests/co19/src": {
"packages": [{
"package": "dart/third_party/co19",
diff --git a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
index f46c760..2f8d1e5 100644
--- a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
@@ -4,16 +4,6 @@
import 'package:meta/meta.dart';
-/// Set this boolean to `true` to permanently enable the feature of allowing
-/// local boolean variables to influence promotion (see
-/// https://github.com/dart-lang/language/issues/1274). While this boolean is
-/// `false`, the feature remains experimental and can be activated via an
-/// optional boolean parameter to the [FlowAnalysis] constructor.
-///
-/// Changing this value to `true` will cause some dead code warnings to appear
-/// for code that only exists to support the old behavior.
-const bool allowLocalBooleanVarsToPromoteByDefault = true;
-
/// [AssignedVariables] is a helper class capable of computing the set of
/// variables that are potentially written to, and potentially captured by
/// closures, at various locations inside the code being analyzed. This class
@@ -242,6 +232,11 @@
'{${_info.keys.map((k) => '$k (${k.hashCode})').join(',')}}'));
}
+ /// Indicates whether information is stored for the given [node].
+ bool _hasInfoForNode(Node node) {
+ return _info[node] != null;
+ }
+
void _printOn(StringBuffer sb) {
sb.write('_info=$_info,');
sb.write('_stack=$_stack,');
@@ -402,10 +397,8 @@
abstract class FlowAnalysis<Node extends Object, Statement extends Node,
Expression extends Object, Variable extends Object, Type extends Object> {
factory FlowAnalysis(TypeOperations<Variable, Type> typeOperations,
- AssignedVariables<Node, Variable> assignedVariables,
- {bool allowLocalBooleanVarsToPromote = false}) {
- return new _FlowAnalysisImpl(typeOperations, assignedVariables,
- allowLocalBooleanVarsToPromote: allowLocalBooleanVarsToPromote);
+ AssignedVariables<Node, Variable> assignedVariables) {
+ return new _FlowAnalysisImpl(typeOperations, assignedVariables);
}
factory FlowAnalysis.legacy(TypeOperations<Variable, Type> typeOperations,
@@ -935,6 +928,8 @@
/// promotion, to retrieve information about why [target] was not promoted.
/// This call must be made right after visiting [target].
///
+ /// If [target] is `null` it is assumed to be an implicit reference to `this`.
+ ///
/// The returned value is a map whose keys are types that the user might have
/// been expecting the target to be promoted to, and whose values are reasons
/// why the corresponding promotion did not occur. The caller is expected to
@@ -943,7 +938,7 @@
/// occurs due to the target having a nullable type, the caller should report
/// a non-promotion reason associated with non-promotion to a non-nullable
/// type).
- Map<Type, NonPromotionReason> whyNotPromoted(Expression target);
+ Map<Type, NonPromotionReason> whyNotPromoted(Expression? target);
/// Register write of the given [variable] in the current state.
/// [writtenType] should be the type of the value that was written.
@@ -974,12 +969,10 @@
bool _exceptionOccurred = false;
factory FlowAnalysisDebug(TypeOperations<Variable, Type> typeOperations,
- AssignedVariables<Node, Variable> assignedVariables,
- {bool allowLocalBooleanVarsToPromote = false}) {
+ AssignedVariables<Node, Variable> assignedVariables) {
print('FlowAnalysisDebug()');
- return new FlowAnalysisDebug._(new _FlowAnalysisImpl(
- typeOperations, assignedVariables,
- allowLocalBooleanVarsToPromote: allowLocalBooleanVarsToPromote));
+ return new FlowAnalysisDebug._(
+ new _FlowAnalysisImpl(typeOperations, assignedVariables));
}
factory FlowAnalysisDebug.legacy(
@@ -1460,7 +1453,7 @@
}
@override
- Map<Type, NonPromotionReason> whyNotPromoted(Expression target) {
+ Map<Type, NonPromotionReason> whyNotPromoted(Expression? target) {
return _wrap(
'whyNotPromoted($target)', () => _wrapped.whyNotPromoted(target),
isQuery: true);
@@ -1879,79 +1872,6 @@
return _identicalOrNew(this, base, newReachable, newVariableInfo);
}
- /// Updates the state to reflect a control path that is known to have
- /// previously passed through some [other] state.
- ///
- /// Approximately, this method forms the union of the definite assignments and
- /// promotions in `this` state and the [other] state. More precisely:
- ///
- /// The control flow path is considered reachable if both this state and the
- /// other state are reachable. Variables are considered definitely assigned
- /// if they were definitely assigned in either this state or the other state.
- /// Variable type promotions are taken from this state, unless the promotion
- /// in the other state is more specific, and the variable is "safe". A
- /// variable is considered safe if there is no chance that it was assigned
- /// more recently than the "other" state.
- ///
- /// This is used after a `try/finally` statement to combine the promotions and
- /// definite assignments that occurred in the `try` and `finally` blocks
- /// (where `this` is the state from the `finally` block and `other` is the
- /// state from the `try` block). Variables that are assigned in the `finally`
- /// block are considered "unsafe" because the assignment might have cancelled
- /// the effect of any promotion that occurred inside the `try` block.
- FlowModel<Variable, Type> restrict(
- TypeOperations<Variable, Type> typeOperations,
- FlowModel<Variable, Type> other,
- Set<Variable> unsafe) {
- if (allowLocalBooleanVarsToPromoteByDefault) {
- // TODO(paulberry): when we hardcode
- // allowLocalBooleanVarsToPromoteByDefault to `true`, we should remove
- // this method entirely.
- throw new StateError('This method should not be called anymore');
- }
- Reachability newReachable =
- Reachability.restrict(reachable, other.reachable);
-
- Map<Variable?, VariableModel<Variable, Type>> newVariableInfo =
- <Variable?, VariableModel<Variable, Type>>{};
- bool variableInfoMatchesThis = true;
- bool variableInfoMatchesOther = true;
- for (MapEntry<Variable?, VariableModel<Variable, Type>> entry
- in variableInfo.entries) {
- Variable? variable = entry.key;
- VariableModel<Variable, Type> thisModel = entry.value;
- VariableModel<Variable, Type>? otherModel = other.variableInfo[variable];
- if (otherModel == null) {
- variableInfoMatchesThis = false;
- continue;
- }
- VariableModel<Variable, Type> restricted = thisModel.restrict(
- typeOperations, otherModel, unsafe.contains(variable));
- newVariableInfo[variable] = restricted;
- if (!identical(restricted, thisModel)) variableInfoMatchesThis = false;
- if (!identical(restricted, otherModel)) variableInfoMatchesOther = false;
- }
- if (variableInfoMatchesOther) {
- for (Variable? variable in other.variableInfo.keys) {
- if (!variableInfo.containsKey(variable)) {
- variableInfoMatchesOther = false;
- break;
- }
- }
- }
- assert(variableInfoMatchesThis ==
- _variableInfosEqual(newVariableInfo, variableInfo));
- assert(variableInfoMatchesOther ==
- _variableInfosEqual(newVariableInfo, other.variableInfo));
- if (variableInfoMatchesThis) {
- newVariableInfo = variableInfo;
- } else if (variableInfoMatchesOther) {
- newVariableInfo = other.variableInfo;
- }
-
- return _identicalOrNew(this, other, newReachable, newVariableInfo);
- }
-
/// Updates the state to indicate that the control flow path is unreachable.
FlowModel<Variable, Type> setUnreachable() {
if (!reachable.locallyReachable) return this;
@@ -2797,59 +2717,6 @@
ssaNode: writeCaptured ? null : new SsaNode<Variable, Type>(null));
}
- /// Returns an updated model reflect a control path that is known to have
- /// previously passed through some [other] state. See [FlowModel.restrict]
- /// for details.
- VariableModel<Variable, Type> restrict(
- TypeOperations<Variable, Type> typeOperations,
- VariableModel<Variable, Type> otherModel,
- bool unsafe) {
- if (allowLocalBooleanVarsToPromoteByDefault) {
- // TODO(paulberry): when we hardcode
- // allowLocalBooleanVarsToPromoteByDefault to `true`, we should remove
- // this method entirely.
- throw new StateError('This method should not be called anymore');
- }
- List<Type>? thisPromotedTypes = promotedTypes;
- List<Type>? otherPromotedTypes = otherModel.promotedTypes;
- bool newAssigned = assigned || otherModel.assigned;
- // The variable can only be unassigned in this state if it was also
- // unassigned in the other state or if the other state didn't complete
- // normally. For the latter case the resulting state is unreachable but to
- // avoid creating a variable model that is both assigned and unassigned we
- // take the intersection below.
- //
- // This situation can occur in try-finally like:
- //
- // method() {
- // var local;
- // try {
- // local = 0;
- // return; // assigned
- // } finally {
- // local; // unassigned
- // }
- // local; // unreachable state
- // }
- //
- bool newUnassigned = unassigned && otherModel.unassigned;
- bool newWriteCaptured = writeCaptured || otherModel.writeCaptured;
- List<Type>? newPromotedTypes;
- if (newWriteCaptured) {
- // Write-captured variables can't be promoted
- newPromotedTypes = null;
- } else if (unsafe) {
- // There was an assignment to the variable in the "this" path, so none of
- // the promotions from the "other" path can be used.
- newPromotedTypes = thisPromotedTypes;
- } else {
- newPromotedTypes = rebasePromotedTypes(
- typeOperations, thisPromotedTypes, otherPromotedTypes);
- }
- return _identicalOrNew(this, otherModel, newPromotedTypes, tested,
- newAssigned, newUnassigned, newWriteCaptured ? null : ssaNode);
- }
-
/// Updates `this` with a new set of properties.
VariableModel<Variable, Type> setProperties(
Map<String, VariableModel<Variable, Type>> newProperties) =>
@@ -3524,18 +3391,7 @@
final AssignedVariables<Node, Variable> _assignedVariables;
- /// Set this boolean to `true` to temporarily enable the feature of allowing
- /// local boolean variables to influence promotion, for this flow analysis
- /// session (see https://github.com/dart-lang/language/issues/1274). Once the
- /// top level const [allowLocalBooleanVarsToPromoteByDefault] is changed to
- /// `true`, this field will always be `true`, so it can be safely removed.
- final bool allowLocalBooleanVarsToPromote;
-
- _FlowAnalysisImpl(this.typeOperations, this._assignedVariables,
- {bool allowLocalBooleanVarsToPromote = false})
- : allowLocalBooleanVarsToPromote =
- allowLocalBooleanVarsToPromoteByDefault ||
- allowLocalBooleanVarsToPromote;
+ _FlowAnalysisImpl(this.typeOperations, this._assignedVariables);
@override
bool get isReachable => _current.reachable.overallReachable;
@@ -4186,17 +4042,13 @@
@override
void tryFinallyStatement_end(Node finallyBlock) {
- AssignedVariablesNodeInfo<Variable> info =
- _assignedVariables._getInfoForNode(finallyBlock);
+ // We used to need info for `finally` blocks but we don't anymore.
+ assert(!_assignedVariables._hasInfoForNode(finallyBlock),
+ 'No assigned variables info should have been stored for $finallyBlock');
_TryFinallyContext<Variable, Type> context =
_stack.removeLast() as _TryFinallyContext<Variable, Type>;
- if (allowLocalBooleanVarsToPromote) {
- _current = context._afterBodyAndCatches
- .attachFinally(typeOperations, context._beforeFinally, _current);
- } else {
- _current = _current.restrict(
- typeOperations, context._afterBodyAndCatches, info._written);
- }
+ _current = context._afterBodyAndCatches
+ .attachFinally(typeOperations, context._beforeFinally, _current);
}
@override
@@ -4218,13 +4070,11 @@
_storeExpressionReference(expression, variableReference);
VariableModel<Variable, Type> variableModel =
variableReference.getInfo(_current.variableInfo);
- if (allowLocalBooleanVarsToPromote) {
- ExpressionInfo<Variable, Type>? expressionInfo = variableModel
- .ssaNode?.expressionInfo
- ?.rebaseForward(typeOperations, _current);
- if (expressionInfo != null) {
- _storeExpressionInfo(expression, expressionInfo);
- }
+ ExpressionInfo<Variable, Type>? expressionInfo = variableModel
+ .ssaNode?.expressionInfo
+ ?.rebaseForward(typeOperations, _current);
+ if (expressionInfo != null) {
+ _storeExpressionInfo(expression, expressionInfo);
}
return variableModel.promotedTypes?.last;
}
@@ -4257,13 +4107,16 @@
}
@override
- Map<Type, NonPromotionReason> whyNotPromoted(Expression target) {
- if (identical(target, _expressionWithReference)) {
- Reference<Variable, Type>? reference = _expressionReference;
- if (reference != null) {
- return reference.getNonPromotionReasons(
- _current.variableInfo, typeOperations);
- }
+ Map<Type, NonPromotionReason> whyNotPromoted(Expression? target) {
+ Reference<Variable, Type>? reference;
+ if (target == null) {
+ reference = new _ThisReference<Variable, Type>();
+ } else if (identical(target, _expressionWithReference)) {
+ reference = _expressionReference;
+ }
+ if (reference != null) {
+ return reference.getNonPromotionReasons(
+ _current.variableInfo, typeOperations);
}
return {};
}
@@ -4850,7 +4703,7 @@
void whileStatement_end() {}
@override
- Map<Type, NonPromotionReason> whyNotPromoted(Expression target) {
+ Map<Type, NonPromotionReason> whyNotPromoted(Expression? target) {
return {};
}
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/assigned_variables/data/tryFinally.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/assigned_variables/data/tryFinally.dart
index 331b152..f9922ae 100644
--- a/pkg/_fe_analyzer_shared/test/flow_analysis/assigned_variables/data/tryFinally.dart
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/assigned_variables/data/tryFinally.dart
@@ -2,18 +2,18 @@
// 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.
-/*member: tryFinally:declared={a, b}, assigned={a, b}*/
+/*member: tryFinally:declared={a, b, d}, assigned={a, b}*/
tryFinally(int a, int b) {
/*assigned={a}*/ try /*declared={c}, assigned={a}*/ {
a = 0;
var c;
- } finally /*declared={d}, assigned={b}*/ {
+ } finally {
b = 0;
var d;
}
}
-/*member: tryCatchFinally:declared={a, b, c}, assigned={a, b, c}*/
+/*member: tryCatchFinally:declared={a, b, c, f}, assigned={a, b, c}*/
tryCatchFinally(int a, int b, int c) {
// Note: try/catch/finally is desugared into try/catch nested inside
// try/finally. The comment preceding the "try" refers to the outer
@@ -25,7 +25,7 @@
} on String {
b = 0;
var e;
- } finally /*declared={f}, assigned={c}*/ {
+ } finally {
c = 0;
var f;
}
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart
index 0c02855..d1a4d13 100644
--- a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart
@@ -9,7 +9,7 @@
import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart';
import 'package:test/test.dart';
-const Expression nullLiteral = const _NullLiteral();
+Expression get nullLiteral => new _NullLiteral();
Statement assert_(Expression condition, [Expression? message]) =>
new _Assert(condition, message);
@@ -144,6 +144,10 @@
[List<Statement>? ifFalse]) =>
new _If(condition, ifTrue, ifFalse);
+Statement implicitThis_whyNotPromoted(
+ void Function(Map<Type, NonPromotionReason>) callback) =>
+ new _WhyNotPromoted_ImplicitThis(callback);
+
Statement labeled(Statement body) => new _LabeledStatement(body);
Statement localFunction(List<Statement> body) => _LocalFunction(body);
@@ -215,7 +219,7 @@
/// analysis testing. Methods in this class may be used to create more complex
/// expressions based on this one.
abstract class Expression extends Node implements _Visitable<Type> {
- const Expression() : super._();
+ Expression() : super._();
/// If `this` is an expression `x`, creates the expression `x!`.
Expression get nonNullAssert => new _NonNullAssert(this);
@@ -419,8 +423,6 @@
'num* - Object': Type('Never'),
};
- final bool allowLocalBooleanVarsToPromote;
-
final bool legacy;
final Map<String, bool> _subtypes = Map.of(_coreSubtypes);
@@ -431,7 +433,7 @@
Map<String, Map<String, String>> _promotionExceptions = {};
- Harness({this.allowLocalBooleanVarsToPromote = false, this.legacy = false});
+ Harness({this.legacy = false});
@override
Type get topType => Type('Object?');
@@ -508,8 +510,7 @@
? FlowAnalysis<Node, Statement, Expression, Var, Type>.legacy(
this, assignedVariables)
: FlowAnalysis<Node, Statement, Expression, Var, Type>(
- this, assignedVariables,
- allowLocalBooleanVarsToPromote: allowLocalBooleanVarsToPromote);
+ this, assignedVariables);
statements._visit(this, flow);
flow.finish();
}
@@ -570,7 +571,13 @@
/// Representation of an expression or statement in the pseudo-Dart language
/// used for flow analysis testing.
class Node {
- const Node._();
+ static int _nextId = 0;
+
+ final int id;
+
+ Node._() : id = _nextId++;
+
+ String toString() => 'Node#$id';
}
/// Helper class allowing tests to examine the values of variables' SSA nodes.
@@ -1410,7 +1417,7 @@
}
class _NullLiteral extends Expression {
- const _NullLiteral();
+ _NullLiteral();
@override
String toString() => 'null';
@@ -1648,9 +1655,7 @@
assignedVariables.beginNode();
body._preVisit(assignedVariables);
assignedVariables.endNode(_bodyNode);
- assignedVariables.beginNode();
finally_._preVisit(assignedVariables);
- assignedVariables.endNode(_finallyNode);
}
@override
@@ -1754,6 +1759,30 @@
}
}
+class _WhyNotPromoted_ImplicitThis extends Statement {
+ final void Function(Map<Type, NonPromotionReason>) callback;
+
+ _WhyNotPromoted_ImplicitThis(this.callback) : super._();
+
+ @override
+ String toString() => 'implicit this (whyNotPromoted)';
+
+ @override
+ void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
+
+ @override
+ void _visit(
+ Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
+ assert(!Type._allowComparisons);
+ Type._allowComparisons = true;
+ try {
+ callback(flow.whyNotPromoted(null));
+ } finally {
+ Type._allowComparisons = false;
+ }
+ }
+}
+
class _WrappedExpression extends Expression {
final Statement? before;
final Expression expr;
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart
index a344d9e..f594559 100644
--- a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart
@@ -2275,7 +2275,7 @@
test(
'tryFinallyStatement_end() restores SSA nodes from try block when it'
'is sound to do so', () {
- var h = Harness(allowLocalBooleanVarsToPromote: true);
+ var h = Harness();
var x = Var('x', 'int?');
var y = Var('y', 'int?');
late SsaNode<Var, Type> xSsaAtEndOfTry;
@@ -2322,7 +2322,7 @@
test(
'tryFinallyStatement_end() sets unreachable if end of try block '
'unreachable', () {
- var h = Harness(allowLocalBooleanVarsToPromote: true);
+ var h = Harness();
h.run([
tryFinally([
return_(),
@@ -2337,7 +2337,7 @@
test(
'tryFinallyStatement_end() sets unreachable if end of finally block '
'unreachable', () {
- var h = Harness(allowLocalBooleanVarsToPromote: true);
+ var h = Harness();
h.run([
tryFinally([
checkReachable(true),
@@ -2352,7 +2352,7 @@
test(
'tryFinallyStatement_end() handles a variable declared only in the '
'try block', () {
- var h = Harness(allowLocalBooleanVarsToPromote: true);
+ var h = Harness();
var x = Var('x', 'int?');
h.run([
tryFinally([
@@ -2364,7 +2364,7 @@
test(
'tryFinallyStatement_end() handles a variable declared only in the '
'finally block', () {
- var h = Harness(allowLocalBooleanVarsToPromote: true);
+ var h = Harness();
var x = Var('x', 'int?');
h.run([
tryFinally([], [
@@ -2376,7 +2376,7 @@
test(
'tryFinallyStatement_end() handles a variable that was write '
'captured in the try block', () {
- var h = Harness(allowLocalBooleanVarsToPromote: true);
+ var h = Harness();
var x = Var('x', 'int?');
h.run([
declare(x, initialized: true),
@@ -2394,7 +2394,7 @@
test(
'tryFinallyStatement_end() handles a variable that was write '
'captured in the finally block', () {
- var h = Harness(allowLocalBooleanVarsToPromote: true);
+ var h = Harness();
var x = Var('x', 'int?');
h.run([
declare(x, initialized: true),
@@ -2412,7 +2412,7 @@
test(
'tryFinallyStatement_end() handles a variable that was promoted in '
'the try block and write captured in the finally block', () {
- var h = Harness(allowLocalBooleanVarsToPromote: true);
+ var h = Harness();
var x = Var('x', 'int?');
h.run([
declare(x, initialized: true),
@@ -2438,7 +2438,7 @@
test(
'tryFinallyStatement_end() keeps promotions from both try and '
'finally blocks when there is no write in the finally block', () {
- var h = Harness(allowLocalBooleanVarsToPromote: true);
+ var h = Harness();
var x = Var('x', 'Object');
h.run([
declare(x, initialized: true),
@@ -2462,7 +2462,7 @@
test(
'tryFinallyStatement_end() keeps promotions from the finally block '
'when there is a write in the finally block', () {
- var h = Harness(allowLocalBooleanVarsToPromote: true);
+ var h = Harness();
var x = Var('x', 'Object');
h.run([
declare(x, initialized: true),
@@ -2484,7 +2484,7 @@
test(
'tryFinallyStatement_end() keeps tests from both the try and finally '
'blocks', () {
- var h = Harness(allowLocalBooleanVarsToPromote: true);
+ var h = Harness();
var x = Var('x', 'Object');
h.run([
declare(x, initialized: true),
@@ -2509,7 +2509,7 @@
test(
'tryFinallyStatement_end() handles variables not definitely assigned '
'in either the try or finally block', () {
- var h = Harness(allowLocalBooleanVarsToPromote: true);
+ var h = Harness();
var x = Var('x', 'Object');
h.run([
declare(x, initialized: false),
@@ -2532,7 +2532,7 @@
test(
'tryFinallyStatement_end() handles variables definitely assigned in '
'the try block', () {
- var h = Harness(allowLocalBooleanVarsToPromote: true);
+ var h = Harness();
var x = Var('x', 'Object');
h.run([
declare(x, initialized: false),
@@ -2553,7 +2553,7 @@
test(
'tryFinallyStatement_end() handles variables definitely assigned in '
'the finally block', () {
- var h = Harness(allowLocalBooleanVarsToPromote: true);
+ var h = Harness();
var x = Var('x', 'Object');
h.run([
declare(x, initialized: false),
@@ -2574,7 +2574,7 @@
test(
'tryFinallyStatement_end() handles variables definitely unassigned '
'in both the try and finally blocks', () {
- var h = Harness(allowLocalBooleanVarsToPromote: true);
+ var h = Harness();
var x = Var('x', 'Object');
h.run([
declare(x, initialized: false),
@@ -2591,7 +2591,7 @@
test(
'tryFinallyStatement_end() handles variables definitely unassigned '
'in the try but not the finally block', () {
- var h = Harness(allowLocalBooleanVarsToPromote: true);
+ var h = Harness();
var x = Var('x', 'Object');
h.run([
declare(x, initialized: false),
@@ -2611,7 +2611,7 @@
test(
'tryFinallyStatement_end() handles variables definitely unassigned '
'in the finally but not the try block', () {
- var h = Harness(allowLocalBooleanVarsToPromote: true);
+ var h = Harness();
var x = Var('x', 'Object');
h.run([
declare(x, initialized: false),
@@ -2630,7 +2630,7 @@
});
test('variableRead() restores promotions from previous write()', () {
- var h = Harness(allowLocalBooleanVarsToPromote: true);
+ var h = Harness();
var x = Var('x', 'int?');
var y = Var('y', 'int?');
var z = Var('z', 'bool');
@@ -2664,7 +2664,7 @@
});
test('variableRead() restores promotions from previous initialization', () {
- var h = Harness(allowLocalBooleanVarsToPromote: true);
+ var h = Harness();
var x = Var('x', 'int?');
var y = Var('y', 'int?');
var z = Var('z', 'bool');
@@ -2697,7 +2697,7 @@
});
test('variableRead() rebases old promotions', () {
- var h = Harness(allowLocalBooleanVarsToPromote: true);
+ var h = Harness();
var w = Var('w', 'int?');
var x = Var('x', 'int?');
var y = Var('y', 'int?');
@@ -2739,7 +2739,7 @@
// Note: we have the available infrastructure to do this if we want, but
// we think it will give an inconsistent feel because comparisons like
// `if (i == null)` *don't* promote.
- var h = Harness(allowLocalBooleanVarsToPromote: true);
+ var h = Harness();
var x = Var('x', 'int?');
var y = Var('y', 'int?');
h.run([
@@ -2967,7 +2967,7 @@
// if (b) { /* x promoted again */ }
// But there are a lot of corner cases to test and it's not clear how much
// the benefit will be, so for now we're not doing it.
- var h = Harness(allowLocalBooleanVarsToPromote: true);
+ var h = Harness();
var x = Var('x', 'int?');
var y = Var('y', 'int?');
late SsaNode<Var, Type> xSsaBeforeWrite;
@@ -5683,6 +5683,40 @@
]);
});
});
+
+ group('because this', () {
+ test('explicit', () {
+ var h = Harness()
+ ..addSubtype('D', 'Object?', true)
+ ..addFactor('Object?', 'D', 'Object?');
+ h.run([
+ if_(this_('C').isNot('D'), [
+ return_(),
+ ]),
+ this_('C').whyNotPromoted((reasons) {
+ expect(reasons.keys, unorderedEquals([Type('D')]));
+ var nonPromotionReason = reasons.values.single;
+ expect(nonPromotionReason, TypeMatcher<ThisNotPromoted>());
+ }).stmt,
+ ]);
+ });
+
+ test('implicit', () {
+ var h = Harness()
+ ..addSubtype('D', 'Object?', true)
+ ..addFactor('Object?', 'D', 'Object?');
+ h.run([
+ if_(this_('C').isNot('D'), [
+ return_(),
+ ]),
+ implicitThis_whyNotPromoted((reasons) {
+ expect(reasons.keys, unorderedEquals([Type('D')]));
+ var nonPromotionReason = reasons.values.single;
+ expect(nonPromotionReason, TypeMatcher<ThisNotPromoted>());
+ }),
+ ]);
+ });
+ });
});
}
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/this.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/this.dart
new file mode 100644
index 0000000..47012b6
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/this.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2020, 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.
+
+// This test validates integration of "why not promoted" when the user tries to
+// promote `this`.
+
+// TODO(paulberry): once we support adding "why not promoted" information to
+// errors that aren't related to null safety, test references to `this` in
+// classes and mixins.
+
+extension on int? {
+ extension_explicit_this() {
+ if (this == null) return;
+ this. /*notPromoted(thisNotPromoted)*/ isEven;
+ }
+
+ extension_implicit_this() {
+ if (this == null) return;
+ /*notPromoted(thisNotPromoted)*/ isEven;
+ }
+}
diff --git a/pkg/analysis_server/tool/code_completion/completion_metrics.dart b/pkg/analysis_server/tool/code_completion/completion_metrics.dart
index fbb23b7..38e22d5 100644
--- a/pkg/analysis_server/tool/code_completion/completion_metrics.dart
+++ b/pkg/analysis_server/tool/code_completion/completion_metrics.dart
@@ -2,11 +2,13 @@
// 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.
+import 'dart:convert';
import 'dart:io' as io;
import 'dart:math' as math;
import 'package:_fe_analyzer_shared/src/base/syntactic_entity.dart';
import 'package:analysis_server/src/domains/completion/available_suggestions.dart';
+import 'package:analysis_server/src/protocol/protocol_internal.dart';
import 'package:analysis_server/src/protocol_server.dart' as protocol;
import 'package:analysis_server/src/services/completion/completion_core.dart';
import 'package:analysis_server/src/services/completion/completion_performance.dart';
@@ -36,6 +38,7 @@
VariableElement;
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/error/error.dart' as err;
+import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/file_system/overlay_file_system.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:analyzer/src/dart/analysis/byte_store.dart';
@@ -50,6 +53,7 @@
import 'metrics_util.dart';
import 'output_utilities.dart';
+import 'relevance_table_generator.dart';
import 'visitors.dart';
Future<void> main(List<String> args) async {
@@ -61,17 +65,75 @@
}
var options = CompletionMetricsOptions(result);
- var root = result.rest[0];
- print('Analyzing root: "$root"');
+ var provider = PhysicalResourceProvider.INSTANCE;
+ if (result.wasParsed('reduceDir')) {
+ var targetMetrics = <CompletionMetrics>[];
+ var dir = provider.getFolder(result['reduceDir']);
+ var computer = CompletionMetricsComputer('', options);
+ for (var child in dir.getChildren()) {
+ if (child is File) {
+ var metricsList =
+ (json.decode(child.readAsStringSync()) as List<dynamic>)
+ .map((map) =>
+ CompletionMetrics.fromJson(map as Map<String, dynamic>))
+ .toList();
+ if (targetMetrics.isEmpty) {
+ targetMetrics.addAll(metricsList);
+ } else if (targetMetrics.length != metricsList.length) {
+ throw StateError('metrics lengths differ');
+ } else {
+ for (var i = 0; i < targetMetrics.length; i++) {
+ targetMetrics[i].addData(metricsList[i]);
+ }
+ }
+ }
+ }
+ computer.targetMetrics.addAll(targetMetrics);
+ computer.printResults();
+ return;
+ }
+
+ var rootPath = result.rest[0];
+ print('Analyzing root: "$rootPath"');
var stopwatch = Stopwatch()..start();
- var computer = CompletionMetricsComputer(root, options);
+ var computer = CompletionMetricsComputer(rootPath, options);
var code = await computer.computeMetrics();
stopwatch.stop();
var duration = Duration(milliseconds: stopwatch.elapsedMilliseconds);
print('');
print('Metrics computed in $duration');
- computer.printResults();
+
+ File uniqueDataFile() {
+ var dataDir = result['mapDir'];
+ var baseFileName = provider.pathContext.basename(rootPath);
+ var index = 1;
+ while (index < 10000) {
+ var suffix = (index++).toString();
+ suffix = '0000'.substring(suffix.length) + suffix + '.json';
+ var fileName = baseFileName + suffix;
+ var filePath = provider.pathContext.join(dataDir, fileName);
+ var file = provider.getFile(filePath);
+ if (!file.exists) {
+ return file;
+ }
+ }
+
+ /// If there are more than 10000 directories with the same name, just
+ /// overwrite a previously generated file.
+ var fileName = baseFileName + '9999';
+ var filePath = provider.pathContext.join(dataDir, fileName);
+ return provider.getFile(filePath);
+ }
+
+ if (result.wasParsed('mapDir')) {
+ var dataFile = uniqueDataFile();
+ var map =
+ computer.targetMetrics.map((metrics) => metrics.toJson()).toList();
+ dataFile.writeAsStringSync(json.encode(map));
+ } else {
+ computer.printResults();
+ }
return io.exit(code);
}
@@ -148,7 +210,18 @@
defaultsTo: false,
help: 'Print information about the completion requests that had the '
'worst mrr scores.',
- negatable: false);
+ negatable: false)
+ ..addOption(
+ 'mapDir',
+ help: 'The absolute path of the directory to which the completion '
+ 'metrics data will be written. Using this option will prevent the '
+ 'completion results from being written in a textual form.',
+ )
+ ..addOption(
+ 'reduceDir',
+ help: 'The absolute path of the directory from which the completion '
+ 'metrics data will be read.',
+ );
}
/// Print usage information for this tool.
@@ -170,16 +243,16 @@
if (result.wasParsed('help')) {
printUsage(parser);
return false;
+ } else if (result.wasParsed('reduceDir')) {
+ return validateDir(parser, result['reduceDir']);
} else if (result.rest.length != 1) {
printUsage(parser, error: 'No package path specified.');
return false;
}
- var rootPath = result.rest[0];
- if (!io.Directory(rootPath).existsSync()) {
- printUsage(parser, error: 'The directory "$rootPath" does not exist.');
- return false;
+ if (result.wasParsed('mapDir')) {
+ return validateDir(parser, result['mapDir']);
}
- return true;
+ return validateDir(parser, result.rest[0]);
}
/// An indication of the group in which the completion falls for the purposes of
@@ -233,56 +306,153 @@
/// The function to be executed when this metrics collector is disabled.
final void Function() disableFunction;
- Counter completionCounter = Counter('all completions');
+ final Counter completionCounter = Counter('all completions');
- Counter completionMissedTokenCounter =
+ final Counter completionMissedTokenCounter =
Counter('unsuccessful completion token counter');
- Counter completionKindCounter =
+ final Counter completionKindCounter =
Counter('unsuccessful completion kind counter');
- Counter completionElementKindCounter =
+ final Counter completionElementKindCounter =
Counter('unsuccessful completion element kind counter');
- ArithmeticMeanComputer meanCompletionMS =
+ final ArithmeticMeanComputer meanCompletionMS =
ArithmeticMeanComputer('ms per completion');
- MeanReciprocalRankComputer mrrComputer =
+ final MeanReciprocalRankComputer mrrComputer =
MeanReciprocalRankComputer('all completions');
- MeanReciprocalRankComputer successfulMrrComputer =
+ final MeanReciprocalRankComputer successfulMrrComputer =
MeanReciprocalRankComputer('successful completions');
/// A table mapping completion groups to the mrr computer used to track the
/// quality of suggestions for those groups.
- Map<CompletionGroup, MeanReciprocalRankComputer> groupMrrComputers = {};
+ final Map<CompletionGroup, MeanReciprocalRankComputer> groupMrrComputers = {};
/// A table mapping locations to the mrr computer used to track the quality of
/// suggestions for those locations.
- Map<String, MeanReciprocalRankComputer> locationMrrComputers = {};
+ final Map<String, MeanReciprocalRankComputer> locationMrrComputers = {};
- ArithmeticMeanComputer charsBeforeTop =
+ final ArithmeticMeanComputer charsBeforeTop =
ArithmeticMeanComputer('chars_before_top');
- ArithmeticMeanComputer charsBeforeTopFive =
+ final ArithmeticMeanComputer charsBeforeTopFive =
ArithmeticMeanComputer('chars_before_top_five');
- ArithmeticMeanComputer insertionLengthTheoretical =
+ final ArithmeticMeanComputer insertionLengthTheoretical =
ArithmeticMeanComputer('insertion_length_theoretical');
/// The places in which a completion location was requested when none was
/// available.
- Set<String> missingCompletionLocations = {};
+ final Set<String> missingCompletionLocations = {};
/// The completion locations for which no relevance table was available.
- Set<String> missingCompletionLocationTables = {};
+ final Set<String> missingCompletionLocationTables = {};
- Map<CompletionGroup, List<CompletionResult>> slowestResults = {};
+ final Map<CompletionGroup, List<CompletionResult>> slowestResults = {};
- Map<CompletionGroup, List<CompletionResult>> worstResults = {};
+ final Map<CompletionGroup, List<CompletionResult>> worstResults = {};
CompletionMetrics(this.name, {this.enableFunction, this.disableFunction});
+ /// Return an instance extracted from the decoded JSON [map].
+ factory CompletionMetrics.fromJson(Map<String, dynamic> map) {
+ var metrics = CompletionMetrics(map['name'] as String);
+ metrics.completionCounter
+ .fromJson(map['completionCounter'] as Map<String, dynamic>);
+ metrics.completionMissedTokenCounter
+ .fromJson(map['completionMissedTokenCounter'] as Map<String, dynamic>);
+ metrics.completionKindCounter
+ .fromJson(map['completionKindCounter'] as Map<String, dynamic>);
+ metrics.completionElementKindCounter
+ .fromJson(map['completionElementKindCounter'] as Map<String, dynamic>);
+ metrics.meanCompletionMS
+ .fromJson(map['meanCompletionMS'] as Map<String, dynamic>);
+ metrics.mrrComputer.fromJson(map['mrrComputer'] as Map<String, dynamic>);
+ metrics.successfulMrrComputer
+ .fromJson(map['successfulMrrComputer'] as Map<String, dynamic>);
+ for (var entry
+ in (map['groupMrrComputers'] as Map<String, dynamic>).entries) {
+ var group = CompletionGroup.values[int.parse(entry.key)];
+ metrics.groupMrrComputers[group] = MeanReciprocalRankComputer(group.name)
+ ..fromJson(entry.value);
+ }
+ for (var entry
+ in (map['locationMrrComputers'] as Map<String, dynamic>).entries) {
+ var location = entry.key;
+ metrics.locationMrrComputers[location] =
+ MeanReciprocalRankComputer(location)..fromJson(entry.value);
+ }
+ metrics.charsBeforeTop
+ .fromJson(map['charsBeforeTop'] as Map<String, dynamic>);
+ metrics.charsBeforeTopFive
+ .fromJson(map['charsBeforeTopFive'] as Map<String, dynamic>);
+ metrics.insertionLengthTheoretical
+ .fromJson(map['insertionLengthTheoretical'] as Map<String, dynamic>);
+ for (var element in map['missingCompletionLocations'] as List<dynamic>) {
+ metrics.missingCompletionLocations.add(element);
+ }
+ for (var element
+ in map['missingCompletionLocationTables'] as List<dynamic>) {
+ metrics.missingCompletionLocationTables.add(element);
+ }
+ for (var entry in (map['slowestResults'] as Map<String, dynamic>).entries) {
+ var group = CompletionGroup.values[int.parse(entry.key)];
+ var results = (entry.value as List<dynamic>)
+ .map((map) => CompletionResult.fromJson(map as Map<String, dynamic>))
+ .toList();
+ metrics.slowestResults[group] = results;
+ }
+ for (var entry in (map['worstResults'] as Map<String, dynamic>).entries) {
+ var group = CompletionGroup.values[int.parse(entry.key)];
+ var results = (entry.value as List<dynamic>)
+ .map((map) => CompletionResult.fromJson(map as Map<String, dynamic>))
+ .toList();
+ metrics.worstResults[group] = results;
+ }
+ return metrics;
+ }
+
+ /// Add the data from the given [metrics] to this metrics.
+ void addData(CompletionMetrics metrics) {
+ completionCounter.addData(metrics.completionCounter);
+ completionMissedTokenCounter.addData(metrics.completionMissedTokenCounter);
+ completionKindCounter.addData(metrics.completionKindCounter);
+ completionElementKindCounter.addData(metrics.completionElementKindCounter);
+ meanCompletionMS.addData(metrics.meanCompletionMS);
+ mrrComputer.addData(metrics.mrrComputer);
+ successfulMrrComputer.addData(metrics.successfulMrrComputer);
+ for (var entry in metrics.groupMrrComputers.entries) {
+ var group = entry.key;
+ groupMrrComputers
+ .putIfAbsent(group, () => MeanReciprocalRankComputer(group.name))
+ .addData(entry.value);
+ }
+ for (var entry in metrics.locationMrrComputers.entries) {
+ var location = entry.key;
+ locationMrrComputers
+ .putIfAbsent(location, () => MeanReciprocalRankComputer(location))
+ .addData(entry.value);
+ }
+ charsBeforeTop.addData(metrics.charsBeforeTop);
+ charsBeforeTopFive.addData(metrics.charsBeforeTopFive);
+ insertionLengthTheoretical.addData(metrics.insertionLengthTheoretical);
+ missingCompletionLocations.addAll(metrics.missingCompletionLocations);
+ missingCompletionLocationTables
+ .addAll(metrics.missingCompletionLocationTables);
+ for (var resultList in metrics.slowestResults.values) {
+ for (var result in resultList) {
+ _recordSlowestResult(result);
+ }
+ }
+ for (var resultList in metrics.worstResults.values) {
+ for (var result in resultList) {
+ _recordWorstResult(result);
+ }
+ }
+ }
+
/// Perform any operations required in order to revert computing the kind of
/// completions represented by this metrics collector.
void disable() {
@@ -310,6 +480,35 @@
_recordMissingInformation(listener);
}
+ Map<String, dynamic> toJson() {
+ return {
+ 'name': name,
+ 'completionCounter': completionCounter.toJson(),
+ 'completionMissedTokenCounter': completionMissedTokenCounter.toJson(),
+ 'completionKindCounter': completionKindCounter.toJson(),
+ 'completionElementKindCounter': completionElementKindCounter.toJson(),
+ 'meanCompletionMS': meanCompletionMS.toJson(),
+ 'mrrComputer': mrrComputer.toJson(),
+ 'successfulMrrComputer': successfulMrrComputer.toJson(),
+ 'groupMrrComputers': groupMrrComputers
+ .map((key, value) => MapEntry(key.index.toString(), value.toJson())),
+ 'locationMrrComputers': locationMrrComputers
+ .map((key, value) => MapEntry(key, value.toJson())),
+ 'charsBeforeTop': charsBeforeTop.toJson(),
+ 'charsBeforeTopFive': charsBeforeTopFive.toJson(),
+ 'insertionLengthTheoretical': insertionLengthTheoretical.toJson(),
+ 'missingCompletionLocations': missingCompletionLocations.toList(),
+ 'missingCompletionLocationTables':
+ missingCompletionLocationTables.toList(),
+ 'slowestResults': slowestResults.map((key, value) => MapEntry(
+ key.index.toString(),
+ value.map((result) => result.toJson()).toList())),
+ 'worstResults': worstResults.map((key, value) => MapEntry(
+ key.index.toString(),
+ value.map((result) => result.toJson()).toList())),
+ };
+ }
+
/// If the completion location was requested but missing when computing the
/// [result], then record where that happened.
void _recordMissingInformation(MetricsSuggestionListener listener) {
@@ -1325,6 +1524,32 @@
this.completionLocation,
this.elapsedMS);
+ /// Return an instance extracted from the decoded JSON [map].
+ factory CompletionResult.fromJson(Map<String, dynamic> map) {
+ var place = Place.fromJson(map['place'] as Map<String, dynamic>);
+ var actualSuggestion = SuggestionData.fromJson(
+ map['actualSuggestion'] as Map<String, dynamic>);
+ var topSuggestions = (map['topSuggestions'] as List<dynamic>)
+ ?.map((map) => SuggestionData.fromJson(map as Map<String, dynamic>))
+ ?.toList();
+ var precedingRelevanceCounts =
+ (map['precedingRelevanceCounts'] as Map<String, dynamic>)
+ ?.map((key, value) => MapEntry(int.parse(key), value as int));
+ var expectedCompletion = ExpectedCompletion.fromJson(
+ map['expectedCompletion'] as Map<String, dynamic>);
+ var completionLocation = map['completionLocation'] as String;
+ var elapsedMS = map['elapsedMS'] as int;
+ return CompletionResult(
+ place,
+ null,
+ actualSuggestion,
+ topSuggestions,
+ precedingRelevanceCounts,
+ expectedCompletion,
+ completionLocation,
+ elapsedMS);
+ }
+
/// Return the completion group for the location at which completion was
/// requested.
CompletionGroup get group {
@@ -1381,6 +1606,23 @@
return CompletionGroup.unknown;
}
+ /// Return a map used to represent this completion result in a JSON structure.
+ Map<String, dynamic> toJson() {
+ return {
+ 'place': place.toJson(),
+ 'actualSuggestion': actualSuggestion.toJson(),
+ if (topSuggestions != null)
+ 'topSuggestions':
+ topSuggestions.map((suggestion) => suggestion.toJson()).toList(),
+ if (precedingRelevanceCounts != null)
+ 'precedingRelevanceCounts': precedingRelevanceCounts
+ .map((key, value) => MapEntry(key.toString(), value)),
+ 'expectedCompletion': expectedCompletion.toJson(),
+ 'completionLocation': completionLocation,
+ 'elapsedMS': elapsedMS,
+ };
+ }
+
/// Return the element associated with the syntactic [entity], or `null` if
/// there is no such element.
Element _getElement(SyntacticEntity entity) {
@@ -1527,6 +1769,22 @@
List<double> features;
SuggestionData(this.suggestion, this.features);
+
+ /// Return an instance extracted from the decoded JSON [map].
+ factory SuggestionData.fromJson(Map<String, dynamic> map) {
+ return SuggestionData(
+ protocol.CompletionSuggestion.fromJson(ResponseDecoder(null), '',
+ map['suggestion'] as Map<String, dynamic>),
+ (map['features'] as List<dynamic>).cast<double>());
+ }
+
+ /// Return a map used to represent this suggestion data in a JSON structure.
+ Map<String, dynamic> toJson() {
+ return {
+ 'suggestion': suggestion.toJson(),
+ 'features': features,
+ };
+ }
}
extension on CompletionGroup {
diff --git a/pkg/analysis_server/tool/code_completion/metrics_util.dart b/pkg/analysis_server/tool/code_completion/metrics_util.dart
index 98acfce..f5c385e 100644
--- a/pkg/analysis_server/tool/code_completion/metrics_util.dart
+++ b/pkg/analysis_server/tool/code_completion/metrics_util.dart
@@ -17,6 +17,12 @@
num get mean => sum / count;
+ /// Add the data from the given [computer] to this computer.
+ void addData(ArithmeticMeanComputer computer) {
+ sum += computer.sum;
+ count += computer.count;
+ }
+
void addValue(num val) {
sum += val;
count++;
@@ -27,12 +33,27 @@
count = 0;
}
+ /// Set the state of this computer to the state recorded in the decoded JSON
+ /// [map].
+ void fromJson(Map<String, dynamic> map) {
+ sum = map['sum'] as num;
+ count = map['count'] as int;
+ }
+
void printMean() {
print('Mean \'$name\' ${mean.toStringAsFixed(6)} (total = $count)');
}
+
+ /// Return a map used to represent this computer in a JSON structure.
+ Map<String, dynamic> toJson() {
+ return {
+ 'sum': sum,
+ 'count': count,
+ };
+ }
}
-/// A simple counter class. A [String] name is passed to name the counter. Each
+/// A simple counter class. A [String] name is passed to name the counter. Each
/// time something is counted, a non-null, non-empty [String] key is passed to
/// [count] to increment the amount from zero. [printCounterValues] is provided
/// to have a [String] summary of the generated counts, example:
@@ -58,6 +79,15 @@
int get totalCount => _totalCount;
+ /// Add the data from the given [counter] to this counter.
+ void addData(Counter counter) {
+ for (var entry in counter._buckets.entries) {
+ var bucket = entry.key;
+ _buckets[bucket] = (_buckets[bucket] ?? 0) + entry.value;
+ }
+ _totalCount += counter._totalCount;
+ }
+
void clear() {
_buckets.clear();
_totalCount = 0;
@@ -73,6 +103,15 @@
_totalCount += countNumber;
}
+ /// Set the state of this counter to the state recorded in the decoded JSON
+ /// [map].
+ void fromJson(Map<String, dynamic> map) {
+ for (var entry in (map['buckets'] as Map<String, dynamic>).entries) {
+ _buckets[entry.key] = entry.value as int;
+ }
+ _totalCount = map['totalCount'] as int;
+ }
+
int getCountOf(String id) => _buckets[id] ?? 0;
void printCounterValues() {
@@ -93,6 +132,14 @@
print('<no counts>');
}
}
+
+ /// Return a map used to represent this counter in a JSON structure.
+ Map<String, dynamic> toJson() {
+ return {
+ 'buckets': _buckets,
+ 'totalCount': _totalCount,
+ };
+ }
}
/// A computer for the mean reciprocal rank. The MRR as well as the MRR only
@@ -125,6 +172,13 @@
return _sum_5 / count;
}
+ /// Add the data from the given [computer] to this computer.
+ void addData(MeanReciprocalRankComputer computer) {
+ _sum += computer._sum;
+ _sum_5 += computer._sum_5;
+ _count += computer._count;
+ }
+
void addRank(int rank) {
if (rank != 0) {
_sum += 1 / rank;
@@ -141,6 +195,14 @@
_count = 0;
}
+ /// Set the state of this computer to the state recorded in the decoded JSON
+ /// [map].
+ void fromJson(Map<String, dynamic> map) {
+ _sum = map['sum'] as double;
+ _sum_5 = map['sum_5'] as double;
+ _count = map['count'] as int;
+ }
+
void printMean() {
print('Mean Reciprocal Rank \'$name\' (total = $count)');
print('mrr = ${mrr.toStringAsFixed(6)} '
@@ -149,6 +211,15 @@
print('mrr_5 = ${mrr_5.toStringAsFixed(6)} '
'(inverse = ${(1 / mrr_5).toStringAsFixed(3)})');
}
+
+ /// Return a map used to represent this computer in a JSON structure.
+ Map<String, dynamic> toJson() {
+ return {
+ 'sum': _sum,
+ 'sum_5': _sum_5,
+ 'count': _count,
+ };
+ }
}
/// An immutable class to represent the placement in some list, for example '2nd
@@ -164,6 +235,11 @@
: assert(_numerator > 0),
assert(_denominator >= _numerator);
+ /// Return an instance extracted from the decoded JSON [map].
+ factory Place.fromJson(Map<String, dynamic> map) {
+ return Place(map['numerator'] as int, map['denominator'] as int);
+ }
+
const Place.none()
: _numerator = 0,
_denominator = 0;
@@ -182,4 +258,12 @@
other is Place &&
_numerator == other._numerator &&
_denominator == other._denominator;
+
+ /// Return a map used to represent this place in a JSON structure.
+ Map<String, dynamic> toJson() {
+ return {
+ 'numerator': _numerator,
+ 'denominator': _denominator,
+ };
+ }
}
diff --git a/pkg/analysis_server/tool/code_completion/visitors.dart b/pkg/analysis_server/tool/code_completion/visitors.dart
index db428f9..69d68eb 100644
--- a/pkg/analysis_server/tool/code_completion/visitors.dart
+++ b/pkg/analysis_server/tool/code_completion/visitors.dart
@@ -2,6 +2,7 @@
// 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.
+import 'package:analysis_server/src/protocol/protocol_internal.dart';
import 'package:analysis_server/src/protocol_server.dart' as protocol;
import 'package:analysis_server/src/services/completion/dart/keyword_contributor.dart';
import 'package:analyzer/dart/ast/ast.dart';
@@ -32,6 +33,32 @@
this._columnNumber, this._kind, this._elementKind)
: _completionString = null;
+ /// Return an instance extracted from the decoded JSON [map].
+ factory ExpectedCompletion.fromJson(Map<String, dynamic> map) {
+ var jsonDecoder = ResponseDecoder(null);
+ var filePath = map['filePath'] as String;
+ var offset = map['offset'] as int;
+ var lineNumber = map['lineNumber'] as int;
+ var columnNumber = map['columnNumber'] as int;
+ var completionString = map['completionString'] as String;
+ var kind = map['kind'] != null
+ ? protocol.CompletionSuggestionKind.fromJson(
+ jsonDecoder, '', map['kind'] as String)
+ : null;
+ var elementKind = map['elementKind'] != null
+ ? protocol.ElementKind.fromJson(
+ jsonDecoder, '', map['elementKind'] as String)
+ : null;
+ return ExpectedCompletion.specialCompletionString(
+ filePath,
+ _SyntacticEntity(offset),
+ lineNumber,
+ columnNumber,
+ completionString,
+ kind,
+ elementKind);
+ }
+
ExpectedCompletion.specialCompletionString(
this._filePath,
this._entity,
@@ -76,6 +103,19 @@
return false;
}
+ /// Return a map used to represent this expected completion in a JSON structure.
+ Map<String, dynamic> toJson() {
+ return {
+ 'filePath': _filePath,
+ 'offset': _entity.offset,
+ 'lineNumber': _lineNumber,
+ 'columnNumber': _columnNumber,
+ 'completionString': _completionString,
+ if (_kind != null) 'kind': _kind.toJson(),
+ if (_elementKind != null) 'elementKind': _elementKind.toJson(),
+ };
+ }
+
@override
String toString() =>
"'$completion', kind = $kind, elementKind = $elementKind, $location";
@@ -757,3 +797,16 @@
return true;
}
}
+
+class _SyntacticEntity implements SyntacticEntity {
+ @override
+ final int offset;
+
+ _SyntacticEntity(this.offset);
+
+ @override
+ int get end => offset + length;
+
+ @override
+ int get length => 0;
+}
diff --git a/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart b/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart
index 3775ad7..40726d1 100644
--- a/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart
@@ -592,11 +592,7 @@
node.catchClauses.accept(this);
assignedVariables.endNode(node);
- if (finallyBlock != null) {
- assignedVariables.beginNode();
- finallyBlock.accept(this);
- assignedVariables.endNode(finallyBlock);
- }
+ finallyBlock?.accept(this);
}
@override
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index d65859f..fcdafed 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -516,29 +516,27 @@
List<DiagnosticMessage> computeWhyNotPromotedMessages(
Expression? receiver, SyntacticEntity errorEntity) {
List<DiagnosticMessage> messages = [];
- if (receiver != null) {
- var whyNotPromoted = flowAnalysis?.flow?.whyNotPromoted(receiver);
- if (whyNotPromoted != null) {
- for (var entry in whyNotPromoted.entries) {
- var whyNotPromotedVisitor = _WhyNotPromotedVisitor(
- source, receiver, flowAnalysis!.dataForTesting);
- if (typeSystem.isPotentiallyNullable(entry.key)) continue;
- var message = entry.value.accept(whyNotPromotedVisitor);
- if (message != null) {
- if (flowAnalysis!.dataForTesting != null) {
- var nonPromotionReasonText = entry.value.shortName;
- if (whyNotPromotedVisitor.propertyReference != null) {
- var id =
- computeMemberId(whyNotPromotedVisitor.propertyReference!);
- nonPromotionReasonText += '($id)';
- }
- flowAnalysis!.dataForTesting!.nonPromotionReasons[errorEntity] =
- nonPromotionReasonText;
+ var whyNotPromoted = flowAnalysis?.flow?.whyNotPromoted(receiver);
+ if (whyNotPromoted != null) {
+ for (var entry in whyNotPromoted.entries) {
+ var whyNotPromotedVisitor = _WhyNotPromotedVisitor(
+ source, receiver, errorEntity, flowAnalysis!.dataForTesting);
+ if (typeSystem.isPotentiallyNullable(entry.key)) continue;
+ var message = entry.value.accept(whyNotPromotedVisitor);
+ if (message != null) {
+ if (flowAnalysis!.dataForTesting != null) {
+ var nonPromotionReasonText = entry.value.shortName;
+ if (whyNotPromotedVisitor.propertyReference != null) {
+ var id =
+ computeMemberId(whyNotPromotedVisitor.propertyReference!);
+ nonPromotionReasonText += '($id)';
}
- messages = [message];
+ flowAnalysis!.dataForTesting!.nonPromotionReasons[errorEntity] =
+ nonPromotionReasonText;
}
- break;
+ messages = [message];
}
+ break;
}
}
return messages;
@@ -3343,13 +3341,16 @@
PromotableElement> {
final Source source;
- final Expression _receiver;
+ final Expression? _receiver;
+
+ final SyntacticEntity _errorEntity;
final FlowAnalysisDataForTesting? _dataForTesting;
PropertyAccessorElement? propertyReference;
- _WhyNotPromotedVisitor(this.source, this._receiver, this._dataForTesting);
+ _WhyNotPromotedVisitor(
+ this.source, this._receiver, this._errorEntity, this._dataForTesting);
@override
DiagnosticMessage? visitDemoteViaExplicitWrite(
@@ -3420,8 +3421,8 @@
return DiagnosticMessageImpl(
filePath: source.fullName,
message: "'this' can't be promoted.",
- offset: _receiver.offset,
- length: _receiver.length);
+ offset: _errorEntity.offset,
+ length: _errorEntity.length);
}
DiagnosticMessageImpl _contextMessageForProperty(
diff --git a/pkg/dartdev/doc/dart-fix.md b/pkg/dartdev/doc/dart-fix.md
index 95dbf1a..2fd0f73 100644
--- a/pkg/dartdev/doc/dart-fix.md
+++ b/pkg/dartdev/doc/dart-fix.md
@@ -2,19 +2,18 @@
## What is it?
-`dart fix` is a command line tool and part of the regular `dart` tool. It is used
-to batch apply fixes for analysis issues.
+`dart fix` is a command line tool and part of the regular `dart` tool. It is
+used to batch apply fixes for analysis issues.
## How does it work?
-`dart fix` runs over your project looking for analysis issues. For each issue
-it checks whether there is an automated fix that can be applied. These fixes
-are generaly either in response to a lint or hint in your code, or part of
-upgrading your source to newer package APIs.
+`dart fix` runs over your project looking for analysis issues. For each issue it
+checks whether there is an automated fix that can be applied. These fixes are
+generaly either in response to a lint or hint in your code, or part of upgrading
+your source to newer package APIs.
-For the first type of change, the fixes are generally in response to the set
-of lints and analysis configuration specified in your [analysis_options.yaml]
-file.
+For the first type of change, the fixes are generally in response to the set of
+lints and analysis configuration specified in your [analysis_options.yaml] file.
The second type of change - upgrading to newer package APIs - is performed
based on API changes defined for specific packages. This declarative definition
@@ -24,18 +23,16 @@
## Command line usage
```
-Fix Dart source code.
+Apply automated fixes to Dart source code.
-This tool looks for and fixes analysis issues that have associated automated
-fixes or issues that have associated package API migration information.
+This tool looks for and fixes analysis issues that have associated automated fixes.
-To use the tool, run one of:
-- 'dart fix --dry-run' for a preview of the proposed changes for a project
-- 'dart fix --apply' to apply the changes
+To use the tool, run either 'dart fix --dry-run' for a preview of the proposed changes for a project, or 'dart fix --apply' to
+apply the changes.
Usage: dart fix [arguments]
-h, --help Print this usage information.
--n, --dry-run Show which files would be modified but make no changes.
+-n, --dry-run Preview the proposed changes but make no changes.
--apply Apply the proposed changes.
Run "dart help" to see global options.
diff --git a/pkg/front_end/lib/src/fasta/kernel/body_builder.dart b/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
index e5da386..06e8346 100644
--- a/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
@@ -2757,9 +2757,6 @@
// This is matched by the call to [beginNode] in [beginTryStatement].
tryStatementInfoStack = tryStatementInfoStack
.prepend(typeInferrer?.assignedVariables?.deferNode());
-
- // This is matched by the call to [endNode] in [endTryStatement].
- typeInferrer?.assignedVariables?.beginNode();
}
super.beginBlock(token, blockKind);
}
@@ -3918,8 +3915,6 @@
Statement finallyBlock;
if (finallyKeyword != null) {
finallyBlock = pop();
- // This is matched by the call to [beginNode] in [beginBlock].
- typeInferrer?.assignedVariables?.endNode(finallyBlock);
} else {
// This is matched by the call to [beginNode] in [beginTryStatement].
tryStatementInfoStack = tryStatementInfoStack
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 4987b10..4c4e74d 100644
--- a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
@@ -2598,6 +2598,17 @@
Constant visitStaticInvocation(StaticInvocation node) {
final Procedure target = node.target;
final Arguments arguments = node.arguments;
+ List<DartType> types = _evaluateTypeArguments(node, arguments);
+ if (types == null && _gotError != null) {
+ AbortConstant error = _gotError;
+ _gotError = null;
+ return error;
+ }
+ assert(_gotError == null);
+ assert(types != null);
+
+ final List<DartType> typeArguments = convertTypes(types);
+
final List<Constant> positionals = _evaluatePositionalArguments(arguments);
if (positionals == null && _gotError != null) {
AbortConstant error = _gotError;
@@ -2684,6 +2695,8 @@
}
} else if (target.isExtensionMember) {
return createErrorConstant(node, messageConstEvalExtension);
+ } else if (enableConstFunctions && target.kind == ProcedureKind.Method) {
+ return _handleStaticInvocation(node, typeArguments, positionals, named);
}
String name = target.name.text;
@@ -2697,6 +2710,47 @@
return createInvalidExpressionConstant(node, "Invocation of $name");
}
+ Constant _handleStaticInvocation(
+ StaticInvocation node,
+ List<DartType> typeArguments,
+ List<Constant> positionalArguments,
+ Map<String, Constant> namedArguments) {
+ return withNewEnvironment(() {
+ final FunctionNode function = node.target.function;
+
+ // Map arguments from caller to callee.
+ for (int i = 0; i < function.typeParameters.length; i++) {
+ env.addTypeParameterValue(function.typeParameters[i], typeArguments[i]);
+ }
+ for (int i = 0; i < function.positionalParameters.length; i++) {
+ final VariableDeclaration parameter = function.positionalParameters[i];
+ final Constant value = (i < positionalArguments.length)
+ ? positionalArguments[i]
+ // TODO(johnniwinther): This should call [_evaluateSubexpression].
+ : _evaluateNullableSubexpression(parameter.initializer);
+ if (value is AbortConstant) return value;
+ env.addVariableValue(parameter, value);
+ }
+ for (final VariableDeclaration parameter in function.namedParameters) {
+ final Constant value = namedArguments[parameter.name] ??
+ // TODO(johnniwinther): This should call [_evaluateSubexpression].
+ _evaluateNullableSubexpression(parameter.initializer);
+ if (value is AbortConstant) return value;
+ env.addVariableValue(parameter, value);
+ }
+ return function.body.accept(this);
+ });
+ }
+
+ @override
+ Constant visitReturnStatement(ReturnStatement node) {
+ if (!enableConstFunctions) {
+ return createInvalidExpressionConstant(
+ node, "Return statements are not supported.");
+ }
+ return node.expression.accept(this);
+ }
+
@override
Constant visitAsExpression(AsExpression node) {
final Constant constant = _evaluateSubexpression(node.operand);
diff --git a/pkg/front_end/lib/src/fasta/kernel/expression_generator.dart b/pkg/front_end/lib/src/fasta/kernel/expression_generator.dart
index 3208247..4e59fb4 100644
--- a/pkg/front_end/lib/src/fasta/kernel/expression_generator.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/expression_generator.dart
@@ -1487,7 +1487,8 @@
int offset, List<UnresolvedType> typeArguments, Arguments arguments,
{bool isTypeArgumentsInForest = false}) {
if (_helper.constantContext != ConstantContext.none &&
- !_helper.isIdentical(readTarget)) {
+ !_helper.isIdentical(readTarget) &&
+ !_helper.enableConstFunctionsInLibrary) {
return _helper.buildProblem(
templateNotConstantExpression.withArguments('Method invocation'),
offset,
diff --git a/pkg/test_runner/lib/src/compiler_configuration.dart b/pkg/test_runner/lib/src/compiler_configuration.dart
index 502eaf3..55bf077 100644
--- a/pkg/test_runner/lib/src/compiler_configuration.dart
+++ b/pkg/test_runner/lib/src/compiler_configuration.dart
@@ -558,7 +558,7 @@
args.addAll(options);
args.addAll(_configuration.sharedOptions);
- bool d8Runtime = _configuration.runtime == Runtime.d8;
+ var d8Runtime = _configuration.runtime == Runtime.d8;
args.addAll([
"--ignore-unrecognized-flags",
diff --git a/tests/language/const_functions/const_functions_disabled_simple_invocations.dart b/tests/language/const_functions/const_functions_disabled_simple_invocations.dart
new file mode 100644
index 0000000..522f4fa
--- /dev/null
+++ b/tests/language/const_functions/const_functions_disabled_simple_invocations.dart
@@ -0,0 +1,67 @@
+// Copyright (c) 2021, 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.
+
+// Tests 'const-function' flag disabled for simple function invocations.
+
+const binary = binaryFn(2, 1);
+// ^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+// [cfe] Method invocation is not a constant expression.
+int binaryFn(int a, int b) => a - b;
+
+const optional = optionalFn(2);
+// ^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+// [cfe] Method invocation is not a constant expression.
+const optional1 = optionalFn(2, 1);
+// ^^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+// [cfe] Method invocation is not a constant expression.
+int optionalFn(int c, [int d = 0]) => c + d;
+
+const named = namedFn(2, f: 2);
+// ^^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+// [cfe] Method invocation is not a constant expression.
+const named1 = namedFn(2);
+// ^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+// [cfe] Method invocation is not a constant expression.
+int namedFn(int e, {int f = 3}) => e + f;
+
+const type = typeFn(6);
+// ^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+// [cfe] Method invocation is not a constant expression.
+T typeFn<T>(T x) => x;
+
+const str = stringFn("str");
+// ^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+// [cfe] Method invocation is not a constant expression.
+String stringFn(String s) => s + "ing";
+
+const eq = equalFn(2, 2);
+// ^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+// [cfe] Method invocation is not a constant expression.
+bool equalFn(int a, int b) => a == b;
+
+const neg = unary(2);
+// ^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+// [cfe] Method invocation is not a constant expression.
+int unary(int a) => -a;
+
+const boolean = boolFn(true, false);
+// ^^^^^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+// [cfe] Method invocation is not a constant expression.
+bool boolFn(bool a, bool b) => a || b;
+
+const doub = doubleFn(2.2, 2);
+// ^^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+// [cfe] Method invocation is not a constant expression.
+double doubleFn(double a, double b) => a * b;
diff --git a/tests/language/const_functions/const_functions_simple_invocations.dart b/tests/language/const_functions/const_functions_simple_invocations.dart
new file mode 100644
index 0000000..2280197
--- /dev/null
+++ b/tests/language/const_functions/const_functions_simple_invocations.dart
@@ -0,0 +1,74 @@
+// Copyright (c) 2021, 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.
+
+// Tests function invocations that immediately return simple expressions.
+
+// SharedOptions=--enable-experiment=const-functions
+
+import "package:expect/expect.dart";
+
+const binary = binaryFn(2, 1);
+// ^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+int binaryFn(int a, int b) => a - b;
+
+const optional = optionalFn(2);
+// ^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+const optional1 = optionalFn(2, 1);
+// ^^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+int optionalFn(int c, [int d = 0]) => c + d;
+
+const named = namedFn(2, f: 2);
+// ^^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+const named1 = namedFn(2);
+// ^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+int namedFn(int e, {int f = 3}) => e + f;
+
+const type = typeFn(6);
+// ^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+T typeFn<T>(T x) => x;
+
+const str = stringFn("str");
+// ^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+String stringFn(String s) => s + "ing";
+
+const eq = equalFn(2, 2);
+// ^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+bool equalFn(int a, int b) => a == b;
+
+const neg = unary(2);
+// ^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+int unary(int a) => -a;
+
+const boolean = boolFn(true, false);
+// ^^^^^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+bool boolFn(bool a, bool b) => a || b;
+
+const doub = doubleFn(2.2, 2);
+// ^^^^^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+double doubleFn(double a, double b) => a * b;
+
+void main() {
+ Expect.equals(binary, 1);
+ Expect.equals(optional, 2);
+ Expect.equals(optional1, 3);
+ Expect.equals(named, 4);
+ Expect.equals(named1, 5);
+ Expect.equals(type, 6);
+ Expect.equals(str, "string");
+ Expect.equals(eq, true);
+ Expect.equals(neg, -2);
+ Expect.equals(boolean, true);
+ Expect.equals(doub, 4.4);
+}
diff --git a/tests/language/metadata/metadata_location_test.dart b/tests/language/metadata/metadata_location_test.dart
index 8e7c225..2702eda 100644
--- a/tests/language/metadata/metadata_location_test.dart
+++ b/tests/language/metadata/metadata_location_test.dart
@@ -159,7 +159,16 @@
typedef void F1<@m X>();
@m
-typedef F2<@m X>= void Function();
+typedef F2<@m X> = void Function();
+
+@m
+typedef F3 = void Function<@m X, @m Y extends X>(@m X, {@m List<X> name});
+
+@m
+typedef F4 = F3 Function<@m X>([@m X, @m int]);
+
+@m
+const int Function<@m X extends Y, @m Y>(@m void, [@m List<X>])? c = null;
@m
void main() {
diff --git a/third_party/devtools/README b/third_party/devtools/README
new file mode 100644
index 0000000..d35a323
--- /dev/null
+++ b/third_party/devtools/README
@@ -0,0 +1,14 @@
+This folder contains a pre-built Dart DevTools instance which can be served
+as a web application, as well as the package:devtools_server and package:devtools_shared
+from the same revision, used to host and launch DevTools from a Dart process.
+
+First, ensure Flutter is installed and on your path (see https://flutter.dev/docs/get-started/install/linux#install-flutter-manually).
+
+With `flutter` on your path, do the following:
+
+- Run ./update.sh <revision> to build DevTools at a given revision and upload it
+ to CIPD. The uploaded CIPD entry will be tagged with `revision:<revision>`
+- Update DEPS to point to the newly updated DevTools by providing "revision:<revision>"
+ as the version entry for "third_party/devtools"
+
+DevTools CIPD packages are located at https://chrome-infra-packages.appspot.com/p/dart/third_party/flutter/devtools/+/.
diff --git a/third_party/devtools/update.sh b/third_party/devtools/update.sh
new file mode 100755
index 0000000..a69f6ee
--- /dev/null
+++ b/third_party/devtools/update.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+
+# Copyright 2021 The Dart Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+set -ex #echo on
+
+if [ -z "$1" ]; then
+ echo "Usage: update.sh revision"
+ exit 1
+fi
+
+tmpdir=$(mktemp -d)
+cleanup() {
+ rm -rf "$tmpdir"
+}
+
+trap cleanup EXIT HUP INT QUIT TERM PIPE
+cd "$tmpdir"
+
+# Clone DevTools and build.
+git clone git@github.com:flutter/devtools.git
+cd devtools
+git checkout -b cipd_release $1
+
+./tool/build_release.sh
+
+# Copy the build output as well as the devtools packages needed
+# to serve from DDS.
+mkdir cipd_package
+cp -R packages/devtools/build/ cipd_package/web
+cp -r packages/devtools_server cipd_package
+cp -r packages/devtools_shared cipd_package
+
+cipd create \
+ -name dart/third_party/flutter/devtools \
+ -in cipd_package \
+ -install-mode copy \
+ -tag revision:$1
+
diff --git a/tools/VERSION b/tools/VERSION
index 8789cc9..06f4903 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 13
PATCH 0
-PRERELEASE 86
+PRERELEASE 87
PRERELEASE_PATCH 0
\ No newline at end of file