Version 2.12.0-156.0.dev
Merge commit 'c272975f92acf954b546caf38ac7fe68a7fa90f1' into 'dev'
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 1a1df17..6239f0b 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
@@ -651,6 +651,12 @@
/// is currently promoted. Otherwise returns `null`.
Type? promotedType(Variable variable);
+ /// Retrieves the SSA node associated with [variable], or `null` if [variable]
+ /// is not associated with an SSA node because it is write captured. For
+ /// testing only.
+ @visibleForTesting
+ SsaNode? ssaNodeForTesting(Variable variable);
+
/// Call this method just before visiting one of the cases in the body of a
/// switch statement. See [switchStatement_expressionEnd] for details.
///
@@ -1139,6 +1145,13 @@
}
@override
+ SsaNode? ssaNodeForTesting(Variable variable) {
+ return _wrap('ssaNodeForTesting($variable)',
+ () => _wrapped.ssaNodeForTesting(variable),
+ isQuery: true);
+ }
+
+ @override
void switchStatement_beginCase(bool hasLabel, Node node) {
_wrap('switchStatement_beginCase($hasLabel, $node)',
() => _wrapped.switchStatement_beginCase(hasLabel, node));
@@ -1354,7 +1367,7 @@
(newVariableInfo ??=
new Map<Variable, VariableModel<Variable, Type>>.from(
variableInfo))[variable] = new VariableModel<Variable, Type>(
- null, const [], false, false, true);
+ null, const [], false, false, null);
} else if (!info.writeCaptured) {
(newVariableInfo ??=
new Map<Variable, VariableModel<Variable, Type>>.from(
@@ -1685,7 +1698,7 @@
: _updateVariableInfo(
variable,
new VariableModel<Variable, Type>(newPromotedTypes, newTested,
- info.assigned, info.unassigned, info.writeCaptured),
+ info.assigned, info.unassigned, info.ssaNode),
reachable: newReachable);
}
@@ -1949,6 +1962,29 @@
}
}
+/// Data structure representing a unique value that a variable might take on
+/// during execution of the code being analyzed. SSA nodes are immutable (so
+/// they can be safety shared among data structures) and have identity (so that
+/// it is possible to tell whether one SSA node is the same as another).
+///
+/// This is similar to the nodes used in traditional single assignment analysis
+/// (https://en.wikipedia.org/wiki/Static_single_assignment_form) except that it
+/// does not store a complete IR of the code being analyzed.
+@visibleForTesting
+class SsaNode {
+ /// Expando mapping SSA nodes to debug ids. Only used by `toString`.
+ static final Expando<int> _debugIds = new Expando<int>();
+
+ static int _nextDebugId = 0;
+
+ @override
+ String toString() {
+ SsaNode self = this; // Work around #44475
+ int id = _debugIds[self] ??= _nextDebugId++;
+ return 'ssa$id';
+ }
+}
+
/// Enum representing the different classifications of types that can be
/// returned by [TypeOperations.classifyType].
enum TypeClassification {
@@ -2043,11 +2079,17 @@
/// Indicates whether the variable is unassigned.
final bool unassigned;
- /// Indicates whether the variable has been write captured.
- final bool writeCaptured;
+ /// SSA node associated with this variable. Every time the variable's value
+ /// potentially changes (either through an explicit write or a join with a
+ /// control flow path that contains a write), this field is updated to point
+ /// to a fresh node. Thus, it can be used to detect whether a variable's
+ /// value has changed since a time in the past.
+ ///
+ /// `null` if the variable has been write captured.
+ final SsaNode? ssaNode;
VariableModel(this.promotedTypes, this.tested, this.assigned, this.unassigned,
- this.writeCaptured) {
+ this.ssaNode) {
assert(!(assigned && unassigned),
"Can't be both definitely assigned and unassigned");
assert(promotedTypes == null || promotedTypes!.isNotEmpty);
@@ -2065,16 +2107,19 @@
: promotedTypes = null,
tested = const [],
unassigned = !assigned,
- writeCaptured = false;
+ ssaNode = new SsaNode();
+
+ /// Indicates whether the variable has been write captured.
+ bool get writeCaptured => ssaNode == null;
/// Returns a new [VariableModel] in which any promotions present have been
/// dropped, and the variable has been marked as "not unassigned".
+ ///
+ /// Used by [conservativeJoin] to update the state of variables at the top of
+ /// loops whose bodies write to them.
VariableModel<Variable, Type> discardPromotionsAndMarkNotUnassigned() {
- if (promotedTypes == null && !unassigned) {
- return this;
- }
return new VariableModel<Variable, Type>(
- null, tested, assigned, false, writeCaptured);
+ null, tested, assigned, false, writeCaptured ? null : new SsaNode());
}
/// Returns an updated model reflect a control path that is known to have
@@ -2141,7 +2186,7 @@
}
}
return _identicalOrNew(this, otherModel, newPromotedTypes, tested,
- newAssigned, newUnassigned, newWriteCaptured);
+ newAssigned, newUnassigned, newWriteCaptured ? null : ssaNode);
}
@override
@@ -2171,7 +2216,7 @@
TypeOperations<Variable, Type> typeOperations) {
if (writeCaptured) {
return new VariableModel<Variable, Type>(
- promotedTypes, tested, true, false, writeCaptured);
+ promotedTypes, tested, true, false, null);
}
List<Type>? newPromotedTypes = _demoteViaAssignment(
@@ -2182,7 +2227,10 @@
Type declaredType = typeOperations.variableType(variable);
newPromotedTypes = _tryPromoteToTypeOfInterest(
typeOperations, declaredType, newPromotedTypes, writtenType);
- if (identical(promotedTypes, newPromotedTypes) && assigned) return this;
+ if (identical(promotedTypes, newPromotedTypes) && assigned) {
+ return new VariableModel<Variable, Type>(
+ promotedTypes, tested, assigned, unassigned, new SsaNode());
+ }
List<Type> newTested;
if (newPromotedTypes == null && promotedTypes != null) {
@@ -2192,14 +2240,14 @@
}
return new VariableModel<Variable, Type>(
- newPromotedTypes, newTested, true, false, writeCaptured);
+ newPromotedTypes, newTested, true, false, new SsaNode());
}
/// Returns a new [VariableModel] reflecting the fact that the variable has
/// been write-captured.
VariableModel<Variable, Type> writeCapture() {
return new VariableModel<Variable, Type>(
- null, const [], assigned, false, true);
+ null, const [], assigned, false, null);
}
List<Type>? _demoteViaAssignment(
@@ -2356,7 +2404,7 @@
List<Type> newTested = joinTested(tested, model.tested, typeOperations);
if (identical(newTested, model.tested)) return model;
return new VariableModel<Variable, Type>(model.promotedTypes, newTested,
- model.assigned, model.unassigned, model.writeCaptured);
+ model.assigned, model.unassigned, model.ssaNode);
}
/// Joins two variable models. See [FlowModel.join] for details.
@@ -2375,8 +2423,13 @@
List<Type> newTested = newWriteCaptured
? const []
: joinTested(first.tested, second.tested, typeOperations);
+ SsaNode? newSsaNode = newWriteCaptured
+ ? null
+ : first.ssaNode == second.ssaNode
+ ? first.ssaNode
+ : new SsaNode();
return _identicalOrNew(first, second, newPromotedTypes, newTested,
- newAssigned, newUnassigned, newWriteCaptured);
+ newAssigned, newUnassigned, newWriteCaptured ? null : newSsaNode);
}
/// Performs the portion of the "join" algorithm that applies to promotion
@@ -2487,22 +2540,22 @@
List<Type> newTested,
bool newAssigned,
bool newUnassigned,
- bool newWriteCaptured) {
+ SsaNode? newSsaNode) {
if (identical(first.promotedTypes, newPromotedTypes) &&
identical(first.tested, newTested) &&
first.assigned == newAssigned &&
first.unassigned == newUnassigned &&
- first.writeCaptured == newWriteCaptured) {
+ first.ssaNode == newSsaNode) {
return first;
} else if (identical(second.promotedTypes, newPromotedTypes) &&
identical(second.tested, newTested) &&
second.assigned == newAssigned &&
second.unassigned == newUnassigned &&
- second.writeCaptured == newWriteCaptured) {
+ second.ssaNode == newSsaNode) {
return second;
} else {
- return new VariableModel<Variable, Type>(newPromotedTypes, newTested,
- newAssigned, newUnassigned, newWriteCaptured);
+ return new VariableModel<Variable, Type>(
+ newPromotedTypes, newTested, newAssigned, newUnassigned, newSsaNode);
}
}
@@ -3138,6 +3191,10 @@
}
@override
+ SsaNode? ssaNodeForTesting(Variable variable) =>
+ _current.variableInfo[variable]?.ssaNode;
+
+ @override
void switchStatement_beginCase(bool hasLabel, Node node) {
AssignedVariablesNodeInfo<Variable> info =
_assignedVariables._getInfoForNode(node);
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 70f50e4..ac6b69b 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
@@ -67,8 +67,14 @@
Statement continue_(BranchTargetPlaceholder branchTargetPlaceholder) =>
new _Continue(branchTargetPlaceholder);
-Statement declare(Var variable, {required bool initialized}) =>
- new _Declare(variable, initialized);
+Statement declare(Var variable,
+ {required bool initialized, bool isFinal = false}) =>
+ new _Declare(
+ variable, initialized ? expr(variable.type.type) : null, isFinal);
+
+Statement declareInitialized(Var variable, Expression initializer,
+ {bool isFinal = false}) =>
+ new _Declare(variable, initializer, isFinal);
Statement do_(List<Statement> body, Expression condition) =>
_Do(body, condition);
@@ -126,6 +132,12 @@
return new _ForEach(variable, iterable, body, false);
}
+/// Creates a [Statement] that, when analyzed, will cause [callback] to be
+/// passed an [SsaNodeHarness] allowing the test to examine the values of
+/// variables' SSA nodes.
+Statement getSsaNodes(void Function(SsaNodeHarness) callback) =>
+ new _GetSsaNodes(callback);
+
Statement if_(Expression condition, List<Statement> ifTrue,
[List<Statement>? ifFalse]) =>
new _If(condition, ifTrue, ifFalse);
@@ -258,6 +270,8 @@
/// needed for testing.
class Harness extends TypeOperations<Var, Type> {
static const Map<String, bool> _coreSubtypes = const {
+ 'bool <: int': false,
+ 'bool <: Object': true,
'double <: Object': true,
'double <: num': true,
'double <: num?': true,
@@ -326,6 +340,7 @@
'String <: int': false,
'String <: int?': false,
'String <: num?': false,
+ 'String <: Object': true,
'String <: Object?': true,
};
@@ -335,7 +350,9 @@
'Object? - num?': Type('Object'),
'Object? - Object?': Type('Never?'),
'Object? - String': Type('Object?'),
+ 'Object - bool': Type('Object'),
'Object - int': Type('Object'),
+ 'Object - String': Type('Object'),
'int - Object': Type('Never'),
'int - String': Type('int'),
'int - int': Type('Never'),
@@ -489,6 +506,17 @@
Node._();
}
+/// Helper class allowing tests to examine the values of variables' SSA nodes.
+class SsaNodeHarness {
+ final FlowAnalysis<Node, Statement, Expression, Var, Type> _flow;
+
+ SsaNodeHarness(this._flow);
+
+ /// Gets the SSA node associated with [variable] at the current point in
+ /// control flow, or `null` if the variable has been write captured.
+ SsaNode? operator [](Var variable) => _flow.ssaNodeForTesting(variable);
+}
+
/// Representation of a statement in the pseudo-Dart language used for flow
/// analysis testing.
abstract class Statement extends Node implements _Visitable<void> {
@@ -812,20 +840,33 @@
class _Declare extends Statement {
final Var variable;
- final bool initialized;
+ final Expression? initializer;
+ final bool isFinal;
- _Declare(this.variable, this.initialized) : super._();
+ _Declare(this.variable, this.initializer, this.isFinal) : super._();
@override
- String toString() => '$variable${initialized ? ' = ...' : ''};';
+ String toString() {
+ var finalPart = isFinal ? 'final ' : '';
+ var initializerPart = initializer != null ? ' = $initializer' : '';
+ return '$finalPart$variable${initializerPart};';
+ }
@override
- void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
+ void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
+ initializer?._preVisit(assignedVariables);
+ }
@override
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
- flow.declare(variable, initialized);
+ var initializer = this.initializer;
+ if (initializer == null) {
+ flow.declare(variable, false);
+ } else {
+ initializer._visit(h, flow);
+ flow.declare(variable, true);
+ }
}
}
@@ -1006,6 +1047,21 @@
}
}
+class _GetSsaNodes extends Statement {
+ final void Function(SsaNodeHarness) callback;
+
+ _GetSsaNodes(this.callback) : super._();
+
+ @override
+ void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
+
+ @override
+ void _visit(
+ Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
+ callback(SsaNodeHarness(flow));
+ }
+}
+
class _If extends Statement {
final Expression condition;
final List<Statement> ifTrue;
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 35f7d10..f9dfea6 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
@@ -12,10 +12,13 @@
test('asExpression_end promotes variables', () {
var h = Harness();
var x = Var('x', 'int?');
+ late SsaNode ssaBeforePromotion;
h.run([
declare(x, initialized: true),
+ getSsaNodes((nodes) => ssaBeforePromotion = nodes[x]!),
x.read.as_('int').stmt,
checkPromoted(x, 'int'),
+ getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
]);
});
@@ -164,17 +167,33 @@
]);
});
+ test('declare() sets Ssa', () {
+ var h = Harness();
+ var x = Var('x', 'Object');
+ var y = Var('y', 'int?');
+ h.run([
+ declareInitialized(x, y.read.eq(nullLiteral)),
+ getSsaNodes((nodes) {
+ expect(nodes[x], isNotNull);
+ }),
+ ]);
+ });
+
test('equalityOp(x != null) promotes true branch', () {
var h = Harness();
var x = Var('x', 'int?');
+ late SsaNode ssaBeforePromotion;
h.run([
declare(x, initialized: true),
+ getSsaNodes((nodes) => ssaBeforePromotion = nodes[x]!),
if_(x.read.notEq(nullLiteral), [
checkReachable(true),
checkPromoted(x, 'int'),
+ getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
], [
checkReachable(true),
checkNotPromoted(x),
+ getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
]),
]);
});
@@ -232,14 +251,18 @@
test('equalityOp(x == null) promotes false branch', () {
var h = Harness();
var x = Var('x', 'int?');
+ late SsaNode ssaBeforePromotion;
h.run([
declare(x, initialized: true),
+ getSsaNodes((nodes) => ssaBeforePromotion = nodes[x]!),
if_(x.read.eq(nullLiteral), [
checkReachable(true),
checkNotPromoted(x),
+ getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
], [
checkReachable(true),
checkPromoted(x, 'int'),
+ getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
]),
]);
});
@@ -262,12 +285,16 @@
test('equalityOp(null != x) promotes true branch', () {
var h = Harness();
var x = Var('x', 'int?');
+ late SsaNode ssaBeforePromotion;
h.run([
declare(x, initialized: true),
+ getSsaNodes((nodes) => ssaBeforePromotion = nodes[x]!),
if_(nullLiteral.notEq(x.read), [
checkPromoted(x, 'int'),
+ getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
], [
checkNotPromoted(x),
+ getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
]),
]);
});
@@ -288,12 +315,16 @@
test('equalityOp(null == x) promotes false branch', () {
var h = Harness();
var x = Var('x', 'int?');
+ late SsaNode ssaBeforePromotion;
h.run([
declare(x, initialized: true),
+ getSsaNodes((nodes) => ssaBeforePromotion = nodes[x]!),
if_(nullLiteral.eq(x.read), [
checkNotPromoted(x),
+ getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
], [
checkPromoted(x, 'int'),
+ getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
]),
]);
});
@@ -381,14 +412,39 @@
]);
});
+ test('declare(initialized: false) assigns new SSA ids', () {
+ var h = Harness();
+ var x = Var('x', 'int?');
+ var y = Var('y', 'int?');
+ h.run([
+ declare(x, initialized: false),
+ declare(y, initialized: false),
+ getSsaNodes((nodes) => expect(nodes[y], isNot(nodes[x]))),
+ ]);
+ });
+
+ test('declare(initialized: true) assigns new SSA ids', () {
+ var h = Harness();
+ var x = Var('x', 'int?');
+ var y = Var('y', 'int?');
+ h.run([
+ declare(x, initialized: true),
+ declare(y, initialized: true),
+ getSsaNodes((nodes) => expect(nodes[y], isNot(nodes[x]))),
+ ]);
+ });
+
test('doStatement_bodyBegin() un-promotes', () {
var h = Harness();
var x = Var('x', 'int?');
+ late SsaNode ssaBeforeLoop;
h.run([
declare(x, initialized: true),
x.read.as_('int').stmt,
checkPromoted(x, 'int'),
+ getSsaNodes((nodes) => ssaBeforeLoop = nodes[x]!),
branchTarget((t) => do_([
+ getSsaNodes((nodes) => expect(nodes[x], isNot(ssaBeforeLoop))),
checkNotPromoted(x),
x.write(expr('Null')).stmt,
], expr('bool'))),
@@ -457,13 +513,22 @@
test('for_conditionBegin() un-promotes', () {
var h = Harness();
var x = Var('x', 'int?');
+ late SsaNode ssaBeforeLoop;
h.run([
declare(x, initialized: true),
x.read.as_('int').stmt,
checkPromoted(x, 'int'),
- for_(null, checkNotPromoted(x).thenExpr(expr('bool')), null, [
- x.write(expr('int?')).stmt,
- ]),
+ getSsaNodes((nodes) => ssaBeforeLoop = nodes[x]!),
+ for_(
+ null,
+ block([
+ checkNotPromoted(x),
+ getSsaNodes((nodes) => expect(nodes[x], isNot(ssaBeforeLoop))),
+ ]).thenExpr(expr('bool')),
+ null,
+ [
+ x.write(expr('int?')).stmt,
+ ]),
]);
});
@@ -587,15 +652,75 @@
]);
});
+ test('for_end() with break updates Ssa of modified vars', () {
+ var h = Harness();
+ var x = Var('x', 'int?');
+ var y = Var('x', 'int?');
+ late SsaNode xSsaInsideLoop;
+ late SsaNode ySsaInsideLoop;
+ h.run([
+ declare(x, initialized: true),
+ declare(y, initialized: true),
+ branchTarget((t) => for_(null, expr('bool'), null, [
+ x.write(expr('int?')).stmt,
+ if_(expr('bool'), [break_(t)]),
+ getSsaNodes((nodes) {
+ xSsaInsideLoop = nodes[x]!;
+ ySsaInsideLoop = nodes[y]!;
+ }),
+ ])),
+ getSsaNodes((nodes) {
+ // x's Ssa should have been changed because of the join at the end of
+ // of the loop. y's should not, since it retains the value it had
+ // prior to the loop.
+ expect(nodes[x], isNot(xSsaInsideLoop));
+ expect(nodes[y], same(ySsaInsideLoop));
+ }),
+ ]);
+ });
+
+ test(
+ 'for_end() with break updates Ssa of modified vars when types were '
+ 'tested', () {
+ var h = Harness();
+ var x = Var('x', 'int?');
+ var y = Var('x', 'int?');
+ late SsaNode xSsaInsideLoop;
+ late SsaNode ySsaInsideLoop;
+ h.run([
+ declare(x, initialized: true),
+ declare(y, initialized: true),
+ branchTarget((t) => for_(null, expr('bool'), null, [
+ x.write(expr('int?')).stmt,
+ if_(expr('bool'), [break_(t)]),
+ if_(x.read.is_('int'), []),
+ getSsaNodes((nodes) {
+ xSsaInsideLoop = nodes[x]!;
+ ySsaInsideLoop = nodes[y]!;
+ }),
+ ])),
+ getSsaNodes((nodes) {
+ // x's Ssa should have been changed because of the join at the end of
+ // the loop. y's should not, since it retains the value it had prior
+ // to the loop.
+ expect(nodes[x], isNot(xSsaInsideLoop));
+ expect(nodes[y], same(ySsaInsideLoop));
+ }),
+ ]);
+ });
+
test('forEach_bodyBegin() un-promotes', () {
var h = Harness();
var x = Var('x', 'int?');
+ late SsaNode ssaBeforeLoop;
h.run([
declare(x, initialized: true),
x.read.as_('int').stmt,
checkPromoted(x, 'int'),
+ getSsaNodes((nodes) => ssaBeforeLoop = nodes[x]!),
forEachWithNonVariable(expr('List<int?>'), [
checkNotPromoted(x),
+ getSsaNodes((nodes) => expect(nodes[x], isNot(ssaBeforeLoop))),
x.write(expr('int?')).stmt,
]),
]);
@@ -631,6 +756,20 @@
]);
});
+ test('forEach_bodyBegin() does not write capture loop variable', () {
+ var h = Harness();
+ var x = Var('x', 'int?');
+ h.run([
+ declare(x, initialized: false),
+ checkAssigned(x, false),
+ forEachWithVariableSet(x, expr('List<int?>'), [
+ checkAssigned(x, true),
+ if_(x.read.notEq(nullLiteral), [checkPromoted(x, 'int')]),
+ ]),
+ checkAssigned(x, false),
+ ]);
+ });
+
test('forEach_bodyBegin() pushes conservative join state', () {
var h = Harness();
var x = Var('x', 'int');
@@ -675,13 +814,25 @@
y.read.as_('int').stmt,
checkPromoted(x, 'int'),
checkPromoted(y, 'int'),
+ getSsaNodes((nodes) {
+ expect(nodes[x], isNotNull);
+ expect(nodes[y], isNotNull);
+ }),
localFunction([
// x is unpromoted within the local function
checkNotPromoted(x), checkPromoted(y, 'int'),
+ getSsaNodes((nodes) {
+ expect(nodes[x], isNull);
+ expect(nodes[y], isNotNull);
+ }),
x.write(expr('int?')).stmt, x.read.as_('int').stmt,
]),
// x is unpromoted after the local function too
checkNotPromoted(x), checkPromoted(y, 'int'),
+ getSsaNodes((nodes) {
+ expect(nodes[x], isNull);
+ expect(nodes[y], isNotNull);
+ }),
]);
});
@@ -721,21 +872,28 @@
var h = Harness();
var x = Var('x', 'int?');
var y = Var('y', 'int?');
+ late SsaNode ssaBeforeFunction;
h.run([
declare(x, initialized: true), declare(y, initialized: true),
x.read.as_('int').stmt, y.read.as_('int').stmt,
- checkPromoted(x, 'int'), checkPromoted(y, 'int'),
+ checkPromoted(x, 'int'),
+ getSsaNodes((nodes) => ssaBeforeFunction = nodes[x]!),
+ checkPromoted(y, 'int'),
localFunction([
// x is unpromoted within the local function, because the write
// might have happened by the time the local function executes.
- checkNotPromoted(x), checkPromoted(y, 'int'),
+ checkNotPromoted(x),
+ getSsaNodes((nodes) => expect(nodes[x], isNot(ssaBeforeFunction))),
+ checkPromoted(y, 'int'),
// But it can be re-promoted because the write isn't captured.
x.read.as_('int').stmt,
checkPromoted(x, 'int'), checkPromoted(y, 'int'),
]),
// x is still promoted after the local function, though, because the
// write hasn't occurred yet.
- checkPromoted(x, 'int'), checkPromoted(y, 'int'),
+ checkPromoted(x, 'int'),
+ getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforeFunction))),
+ checkPromoted(y, 'int'),
x.write(expr('int?')).stmt,
// x is unpromoted now.
checkNotPromoted(x), checkPromoted(y, 'int'),
@@ -762,7 +920,9 @@
h.run([
declare(y, initialized: true),
y.read.as_('int').stmt,
+ getSsaNodes((nodes) => expect(nodes[x], null)),
localFunction([
+ getSsaNodes((nodes) => expect(nodes[x], isNot(nodes[y]))),
x.read.as_('int').stmt,
// Promotion should not occur, because x might be write-captured by
// the time this code is reached.
@@ -967,19 +1127,96 @@
]);
});
+ test(
+ 'ifStatement_end() discards non-matching expression info from joined '
+ 'branches', () {
+ var h = Harness();
+ var w = Var('w', 'Object');
+ var x = Var('x', 'bool');
+ var y = Var('y', 'bool');
+ var z = Var('z', 'bool');
+ late SsaNode xSsaNodeBeforeIf;
+ h.run([
+ declare(w, initialized: true),
+ declare(x, initialized: true),
+ declare(y, initialized: true),
+ declare(z, initialized: true),
+ x.write(w.read.is_('int')).stmt,
+ getSsaNodes((nodes) {
+ xSsaNodeBeforeIf = nodes[x]!;
+ }),
+ if_(expr('bool'), [
+ y.write(w.read.is_('String')).stmt,
+ ], [
+ z.write(w.read.is_('bool')).stmt,
+ ]),
+ getSsaNodes((nodes) {
+ expect(nodes[x], same(xSsaNodeBeforeIf));
+ expect(nodes[y], isNotNull);
+ expect(nodes[z], isNotNull);
+ }),
+ ]);
+ });
+
+ test(
+ 'ifStatement_end() ignores non-matching SSA info from "then" path if '
+ 'unreachable', () {
+ var h = Harness();
+ var x = Var('x', 'Object');
+ late SsaNode xSsaNodeBeforeIf;
+ h.run([
+ declare(x, initialized: true),
+ getSsaNodes((nodes) {
+ xSsaNodeBeforeIf = nodes[x]!;
+ }),
+ if_(expr('bool'), [
+ x.write(expr('Object')).stmt,
+ return_(),
+ ]),
+ getSsaNodes((nodes) {
+ expect(nodes[x], same(xSsaNodeBeforeIf));
+ }),
+ ]);
+ });
+
+ test(
+ 'ifStatement_end() ignores non-matching SSA info from "else" path if '
+ 'unreachable', () {
+ var h = Harness();
+ var x = Var('x', 'Object');
+ late SsaNode xSsaNodeBeforeIf;
+ h.run([
+ declare(x, initialized: true),
+ getSsaNodes((nodes) {
+ xSsaNodeBeforeIf = nodes[x]!;
+ }),
+ if_(expr('bool'), [], [
+ x.write(expr('Object')).stmt,
+ return_(),
+ ]),
+ getSsaNodes((nodes) {
+ expect(nodes[x], same(xSsaNodeBeforeIf));
+ }),
+ ]);
+ });
+
void _checkIs(String declaredType, String tryPromoteType,
String? expectedPromotedTypeThen, String? expectedPromotedTypeElse,
{bool inverted = false}) {
var h = Harness();
var x = Var('x', declaredType);
+ late SsaNode ssaBeforePromotion;
h.run([
declare(x, initialized: true),
+ getSsaNodes((nodes) => ssaBeforePromotion = nodes[x]!),
if_(x.read.is_(tryPromoteType, isInverted: inverted), [
checkReachable(true),
checkPromoted(x, expectedPromotedTypeThen),
+ getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
], [
checkReachable(true),
checkPromoted(x, expectedPromotedTypeElse),
+ getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
])
]);
}
@@ -1176,25 +1413,33 @@
test('nonNullAssert_end(x) promotes', () {
var h = Harness();
var x = Var('x', 'int?');
+ late SsaNode ssaBeforePromotion;
h.run([
declare(x, initialized: true),
+ getSsaNodes((nodes) => ssaBeforePromotion = nodes[x]!),
x.read.nonNullAssert.stmt,
checkPromoted(x, 'int'),
+ getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
]);
});
test('nullAwareAccess temporarily promotes', () {
var h = Harness();
var x = Var('x', 'int?');
+ late SsaNode ssaBeforePromotion;
h.run([
declare(x, initialized: true),
+ getSsaNodes((nodes) => ssaBeforePromotion = nodes[x]!),
x.read
.nullAwareAccess(block([
checkReachable(true),
checkPromoted(x, 'int'),
+ getSsaNodes(
+ (nodes) => expect(nodes[x], same(ssaBeforePromotion))),
]).thenExpr(expr('Null')))
.stmt,
checkNotPromoted(x),
+ getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
]);
});
@@ -1380,14 +1625,20 @@
test('switchStatement_beginCase(true) un-promotes', () {
var h = Harness();
var x = Var('x', 'int?');
+ late SsaNode ssaBeforeSwitch;
h.run([
declare(x, initialized: true),
x.read.as_('int').stmt,
switch_(
- expr('Null'),
+ expr('Null').thenStmt(block([
+ checkPromoted(x, 'int'),
+ getSsaNodes((nodes) => ssaBeforeSwitch = nodes[x]!),
+ ])),
[
case_([
checkNotPromoted(x),
+ getSsaNodes(
+ (nodes) => expect(nodes[x], isNot(ssaBeforeSwitch))),
x.write(expr('int?')).stmt,
checkNotPromoted(x),
], hasLabel: true),
@@ -1527,6 +1778,7 @@
() {
var h = Harness();
var x = Var('x', 'int?');
+ late SsaNode ssaAfterTry;
h.run([
declare(x, initialized: true),
x.read.as_('int').stmt,
@@ -1535,9 +1787,11 @@
x.write(expr('int?')).stmt,
x.read.as_('int').stmt,
checkPromoted(x, 'int'),
+ getSsaNodes((nodes) => ssaAfterTry = nodes[x]!),
], [
catch_(body: [
checkNotPromoted(x),
+ getSsaNodes((nodes) => expect(nodes[x], isNot(ssaAfterTry))),
]),
]),
]);
@@ -1673,16 +1927,27 @@
'body', () {
var h = Harness();
var x = Var('x', 'int?');
+ late SsaNode ssaAtStartOfTry;
+ late SsaNode ssaAfterTry;
h.run([
declare(x, initialized: true),
x.read.as_('int').stmt,
checkPromoted(x, 'int'),
tryFinally([
+ getSsaNodes((nodes) => ssaAtStartOfTry = nodes[x]!),
x.write(expr('int?')).stmt,
x.read.as_('int').stmt,
checkPromoted(x, 'int'),
+ getSsaNodes((nodes) => ssaAfterTry = nodes[x]!),
], [
checkNotPromoted(x),
+ // The SSA node for X should be different from what it was at any time
+ // during the try block, because there is no telling at what point an
+ // exception might have occurred.
+ getSsaNodes((nodes) {
+ expect(nodes[x], isNot(ssaAtStartOfTry));
+ expect(nodes[x], isNot(ssaAfterTry));
+ }),
]),
]);
});
@@ -1733,6 +1998,8 @@
var h = Harness();
var x = Var('x', 'int?');
var y = Var('y', 'int?');
+ late SsaNode xSsaAtEndOfFinally;
+ late SsaNode ySsaAtEndOfFinally;
h.run([
declare(x, initialized: true), declare(y, initialized: true),
tryFinally([
@@ -1744,24 +2011,42 @@
y.write(expr('int?')).stmt,
y.read.as_('int').stmt,
checkPromoted(y, 'int'),
+ getSsaNodes((nodes) {
+ xSsaAtEndOfFinally = nodes[x]!;
+ ySsaAtEndOfFinally = nodes[y]!;
+ }),
]),
// x should not be re-promoted, because it might have been assigned a
// non-promoted value in the "finally" block. But y's promotion still
// stands, because y was promoted in the finally block.
checkNotPromoted(x), checkPromoted(y, 'int'),
+ // Both x and y should have the same SSA nodes they had at the end of
+ // the finally block, since the finally block is guaranteed to have
+ // executed.
+ getSsaNodes((nodes) {
+ expect(nodes[x], same(xSsaAtEndOfFinally));
+ expect(nodes[y], same(ySsaAtEndOfFinally));
+ }),
]);
});
test('whileStatement_conditionBegin() un-promotes', () {
var h = Harness();
var x = Var('x', 'int?');
+ late SsaNode ssaBeforeLoop;
h.run([
declare(x, initialized: true),
x.read.as_('int').stmt,
checkPromoted(x, 'int'),
- while_(checkNotPromoted(x).thenExpr(expr('bool')), [
- x.write(expr('Null')).stmt,
- ]),
+ getSsaNodes((nodes) => ssaBeforeLoop = nodes[x]!),
+ while_(
+ block([
+ checkNotPromoted(x),
+ getSsaNodes((nodes) => expect(nodes[x], isNot(ssaBeforeLoop))),
+ ]).thenExpr(expr('bool')),
+ [
+ x.write(expr('Null')).stmt,
+ ]),
]);
});
@@ -1835,6 +2120,98 @@
]);
});
+ test('whileStatement_end() with break updates Ssa of modified vars', () {
+ var h = Harness();
+ var x = Var('x', 'int?');
+ var y = Var('x', 'int?');
+ late SsaNode xSsaInsideLoop;
+ late SsaNode ySsaInsideLoop;
+ h.run([
+ declare(x, initialized: true),
+ declare(y, initialized: true),
+ branchTarget((t) => while_(expr('bool'), [
+ x.write(expr('int?')).stmt,
+ if_(expr('bool'), [break_(t)]),
+ getSsaNodes((nodes) {
+ xSsaInsideLoop = nodes[x]!;
+ ySsaInsideLoop = nodes[y]!;
+ }),
+ ])),
+ getSsaNodes((nodes) {
+ // x's Ssa should have been changed because of the join at the end of
+ // the loop. y's should not, since it retains the value it had prior
+ // to the loop.
+ expect(nodes[x], isNot(xSsaInsideLoop));
+ expect(nodes[y], same(ySsaInsideLoop));
+ }),
+ ]);
+ });
+
+ test(
+ 'whileStatement_end() with break updates Ssa of modified vars when '
+ 'types were tested', () {
+ var h = Harness();
+ var x = Var('x', 'int?');
+ var y = Var('x', 'int?');
+ late SsaNode xSsaInsideLoop;
+ late SsaNode ySsaInsideLoop;
+ h.run([
+ declare(x, initialized: true),
+ declare(y, initialized: true),
+ branchTarget((t) => while_(expr('bool'), [
+ x.write(expr('int?')).stmt,
+ if_(expr('bool'), [break_(t)]),
+ if_(x.read.is_('int'), []),
+ getSsaNodes((nodes) {
+ xSsaInsideLoop = nodes[x]!;
+ ySsaInsideLoop = nodes[y]!;
+ }),
+ ])),
+ getSsaNodes((nodes) {
+ // x's Ssa should have been changed because of the join at the end of
+ // the loop. y's should not, since it retains the value it had prior
+ // to the loop.
+ expect(nodes[x], isNot(xSsaInsideLoop));
+ expect(nodes[y], same(ySsaInsideLoop));
+ }),
+ ]);
+ });
+
+ test('write() de-promotes and updates Ssa of a promoted variable', () {
+ var h = Harness();
+ var x = Var('x', 'Object');
+ var y = Var('y', 'int?');
+ late SsaNode ssaBeforeWrite;
+ h.run([
+ declare(x, initialized: true),
+ declare(y, initialized: true),
+ x.read.as_('int').stmt,
+ checkPromoted(x, 'int'),
+ getSsaNodes((nodes) => ssaBeforeWrite = nodes[x]!),
+ x.write(y.read.eq(nullLiteral)).stmt,
+ checkNotPromoted(x),
+ getSsaNodes((nodes) {
+ expect(nodes[x], isNot(ssaBeforeWrite));
+ expect(nodes[x], isNotNull);
+ }),
+ ]);
+ });
+
+ test('write() updates Ssa', () {
+ var h = Harness();
+ var x = Var('x', 'Object');
+ var y = Var('y', 'int?');
+ late SsaNode ssaBeforeWrite;
+ h.run([
+ declare(x, initialized: true),
+ getSsaNodes((nodes) => ssaBeforeWrite = nodes[x]!),
+ x.write(y.read.eq(nullLiteral)).stmt,
+ getSsaNodes((nodes) {
+ expect(nodes[x], isNot(ssaBeforeWrite));
+ }),
+ ]);
+ });
+
test('Infinite loop does not implicitly assign variables', () {
var h = Harness();
var x = Var('x', 'int');
@@ -1868,8 +2245,10 @@
localFunction([
x.write(expr('Object')).stmt,
]),
+ getSsaNodes((nodes) => expect(nodes[x], isNull)),
x.read.as_('int').stmt,
checkNotPromoted(x),
+ getSsaNodes((nodes) => expect(nodes[x], isNull)),
]);
});
@@ -2153,7 +2532,15 @@
var s1 = FlowModel<Var, Type>(Reachability.initial)
.declare(objectQVar, true);
var s2 = s1.write(objectQVar, Type('Object?'), h);
- expect(s2, same(s1));
+ expect(s2, isNot(same(s1)));
+ expect(s2.reachable, same(s1.reachable));
+ expect(
+ s2.infoFor(objectQVar),
+ _matchVariableModel(
+ chain: null,
+ ofInterest: isEmpty,
+ assigned: true,
+ unassigned: false));
});
test('marks as assigned', () {
@@ -2260,7 +2647,14 @@
});
var s2 = s1.write(objectQVar, Type('num'), h);
expect(s2.reachable.overallReachable, true);
- expect(s2.variableInfo, same(s1.variableInfo));
+ expect(s2.variableInfo, isNot(same(s1.variableInfo)));
+ expect(s2.variableInfo, {
+ objectQVar: _matchVariableModel(
+ chain: ['num?', 'num'],
+ ofInterest: ['num?', 'num'],
+ assigned: true,
+ unassigned: false)
+ });
});
test('leaves promoted, when writing a subtype', () {
@@ -2280,7 +2674,14 @@
});
var s2 = s1.write(objectQVar, Type('int'), h);
expect(s2.reachable.overallReachable, true);
- expect(s2.variableInfo, same(s1.variableInfo));
+ expect(s2.variableInfo, isNot(same(s1.variableInfo)));
+ expect(s2.variableInfo, {
+ objectQVar: _matchVariableModel(
+ chain: ['num?', 'num'],
+ ofInterest: ['num?', 'num'],
+ assigned: true,
+ unassigned: false)
+ });
});
group('Promotes to NonNull of a type of interest', () {
@@ -2545,7 +2946,13 @@
var s2 = s1.write(objectQVar, Type('int'), h);
// It's ambiguous whether to promote to num? or num*, so we don't
// promote.
- expect(s2, same(s1));
+ expect(s2, isNot(same(s1)));
+ expect(s2.variableInfo, {
+ objectQVar: _matchVariableModel(
+ chain: ['Object'],
+ ofInterest: ['num?', 'num*'],
+ ),
+ });
});
});
@@ -2682,7 +3089,12 @@
.tryPromoteForTypeCheck(h, objectQVar, Type('int'))
.ifTrue;
var s2 = s1.conservativeJoin([intQVar], []);
- expect(s2, same(s1));
+ expect(s2, isNot(same(s1)));
+ expect(s2.reachable, same(s1.reachable));
+ expect(s2.variableInfo, {
+ objectQVar: _matchVariableModel(chain: ['int'], ofInterest: ['int']),
+ intQVar: _matchVariableModel(chain: null, ofInterest: [])
+ });
});
test('written', () {
@@ -3075,12 +3487,11 @@
VariableModel<Var, Type> model(List<Type>? promotionChain,
{List<Type>? typesOfInterest, bool assigned = false}) =>
VariableModel<Var, Type>(
- promotionChain,
- typesOfInterest ?? promotionChain ?? [],
- assigned,
- !assigned,
- false,
- );
+ promotionChain,
+ typesOfInterest ?? promotionChain ?? [],
+ assigned,
+ !assigned,
+ new SsaNode());
group('without input reuse', () {
test('promoted with unpromoted', () {
@@ -3259,18 +3670,12 @@
VariableModel<Var, Type> varModel(List<Type>? promotionChain,
{bool assigned = false}) =>
- VariableModel<Var, Type>(
- promotionChain,
- promotionChain ?? [],
- assigned,
- !assigned,
- false,
- );
+ VariableModel<Var, Type>(promotionChain, promotionChain ?? [], assigned,
+ !assigned, new SsaNode());
test('first is null', () {
var h = Harness();
- var s1 = FlowModel.withInfo(
- Reachability.initial.split(), emptyMap);
+ var s1 = FlowModel.withInfo(Reachability.initial.split(), emptyMap);
var result = FlowModel.merge(h, null, s1, emptyMap);
expect(result.reachable, same(Reachability.initial));
});
@@ -3353,7 +3758,8 @@
const emptyMap = const <Var, VariableModel<Var, Type>>{};
VariableModel<Var, Type> model(List<Type> typesOfInterest) =>
- VariableModel<Var, Type>(null, typesOfInterest, true, false, false);
+ VariableModel<Var, Type>(
+ null, typesOfInterest, true, false, new SsaNode());
test('inherits types of interest from other', () {
var h = Harness();
diff --git a/pkg/analysis_server/lib/src/lsp/mapping.dart b/pkg/analysis_server/lib/src/lsp/mapping.dart
index e9b77bf..5ab1bfa 100644
--- a/pkg/analysis_server/lib/src/lsp/mapping.dart
+++ b/pkg/analysis_server/lib/src/lsp/mapping.dart
@@ -1297,6 +1297,14 @@
var insertText = completion;
var insertTextFormat = lsp.InsertTextFormat.PlainText;
+ // SuggestionBuilder already does the equiv of completeFunctionCalls for
+ // some methods (for example Flutter's setState). If the completion already
+ // includes any `(` then disable our own insertion as the special-cased code
+ // will likely provide better code.
+ if (completion.contains('(')) {
+ completeFunctionCalls = false;
+ }
+
// If the client supports snippets, we can support completeFunctionCalls or
// setting a selection.
if (supportsSnippets) {
diff --git a/pkg/analysis_server/test/lsp/completion_dart_test.dart b/pkg/analysis_server/test/lsp/completion_dart_test.dart
index 66ec3ce..2214c75 100644
--- a/pkg/analysis_server/test/lsp/completion_dart_test.dart
+++ b/pkg/analysis_server/test/lsp/completion_dart_test.dart
@@ -31,6 +31,15 @@
);
}
+ @override
+ void setUp() {
+ super.setUp();
+ writePackageConfig(
+ projectFolderPath,
+ flutter: true,
+ );
+ }
+
Future<void> test_commitCharacter_completionItem() async {
await provideConfig(
() => initialize(
@@ -124,6 +133,55 @@
);
}
+ Future<void> test_completeFunctionCalls_flutterSetState() async {
+ // Flutter's setState method has special handling inside SuggestionBuilder
+ // that already adds in a selection (which overlaps with completeFunctionCalls).
+ // Ensure we don't end up with two sets of parens/placeholders in this case.
+ final content = '''
+import 'package:flutter/material.dart';
+
+class MyWidget extends StatefulWidget {
+ @override
+ _MyWidgetState createState() => _MyWidgetState();
+}
+
+class _MyWidgetState extends State<MyWidget> {
+ @override
+ Widget build(BuildContext context) {
+ [[setSt^]]
+ return Container();
+ }
+}
+ ''';
+
+ await provideConfig(
+ () => initialize(
+ textDocumentCapabilities: withCompletionItemSnippetSupport(
+ emptyTextDocumentClientCapabilities),
+ workspaceCapabilities:
+ withConfigurationSupport(emptyWorkspaceClientCapabilities),
+ ),
+ {'completeFunctionCalls': true},
+ );
+ await openFile(mainFileUri, withoutMarkers(content));
+ final res = await getCompletion(mainFileUri, positionFromMarker(content));
+ final item = res.singleWhere((c) => c.label.startsWith('setState('));
+
+ // Usually the label would be "setState(…)" but here it's slightly different
+ // to indicate a full statement is being inserted.
+ expect(item.label, equals('setState(() {});'));
+
+ // Ensure the snippet comes through in the expected format with the expected
+ // placeholders.
+ expect(item.insertTextFormat, equals(InsertTextFormat.Snippet));
+ expect(item.insertText, equals('setState(() {\n \${0:}\n \\});'));
+ expect(item.textEdit.newText, equals(item.insertText));
+ expect(
+ item.textEdit.range,
+ equals(rangeFromMarkers(content)),
+ );
+ }
+
Future<void> test_completeFunctionCalls_noRequiredParameters() async {
final content = '''
void myFunction({int a}) {}
diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart
index f782575..b4ca2b5 100644
--- a/pkg/analyzer/lib/error/error.dart
+++ b/pkg/analyzer/lib/error/error.dart
@@ -222,6 +222,7 @@
CompileTimeErrorCode.INSTANCE_MEMBER_ACCESS_FROM_STATIC,
CompileTimeErrorCode.INSTANTIATE_ABSTRACT_CLASS,
CompileTimeErrorCode.INSTANTIATE_ENUM,
+ CompileTimeErrorCode.INSTANTIATE_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER,
CompileTimeErrorCode.INTEGER_LITERAL_IMPRECISE_AS_DOUBLE,
CompileTimeErrorCode.INTEGER_LITERAL_OUT_OF_RANGE,
CompileTimeErrorCode.INVALID_ANNOTATION,
@@ -374,6 +375,7 @@
CompileTimeErrorCode.REDIRECT_TO_MISSING_CONSTRUCTOR,
CompileTimeErrorCode.REDIRECT_TO_NON_CLASS,
CompileTimeErrorCode.REDIRECT_TO_NON_CONST_CONSTRUCTOR,
+ CompileTimeErrorCode.REDIRECT_TO_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER,
CompileTimeErrorCode.REFERENCED_BEFORE_DECLARATION,
CompileTimeErrorCode.RETHROW_OUTSIDE_CATCH,
CompileTimeErrorCode.RETURN_IN_GENERATIVE_CONSTRUCTOR,
diff --git a/pkg/analyzer/lib/src/dart/resolver/type_name_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/type_name_resolver.dart
index eef2897..37497f3 100644
--- a/pkg/analyzer/lib/src/dart/resolver/type_name_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/type_name_resolver.dart
@@ -79,7 +79,7 @@
return;
}
- if (prefixElement is ClassElement) {
+ if (prefixElement is ClassElement || prefixElement is TypeAliasElement) {
_rewriteToConstructorName(node, typeIdentifier);
return;
}
@@ -206,7 +206,7 @@
nullabilitySuffix: nullability,
);
type = typeSystem.toLegacyType(type);
- return _verifyTypeAliasForContext(node, type);
+ return _verifyTypeAliasForContext(node, element, type);
} else if (_isInstanceCreation(node)) {
_ErrorHelper(errorReporter).reportNewWithNonType(node);
return dynamicType;
@@ -249,7 +249,7 @@
typeAliasElement: element,
nullabilitySuffix: nullability,
);
- return _verifyTypeAliasForContext(node, type);
+ return _verifyTypeAliasForContext(node, element, type);
} else if (_isInstanceCreation(node)) {
_ErrorHelper(errorReporter).reportNewWithNonType(node);
return dynamicType;
@@ -367,7 +367,19 @@
}
}
- DartType _verifyTypeAliasForContext(TypeName node, DartType type) {
+ DartType _verifyTypeAliasForContext(
+ TypeName node,
+ TypeAliasElement element,
+ DartType type,
+ ) {
+ if (element.aliasedType is TypeParameterType) {
+ var constructorName = node.parent;
+ if (constructorName is ConstructorName) {
+ _ErrorHelper(errorReporter)
+ .reportTypeAliasExpandsToTypeParameter(constructorName, element);
+ return dynamicType;
+ }
+ }
if (type is! InterfaceType && _isInstanceCreation(node)) {
_ErrorHelper(errorReporter).reportNewWithNonType(node);
return dynamicType;
@@ -522,6 +534,28 @@
);
}
+ void reportTypeAliasExpandsToTypeParameter(
+ ConstructorName constructorName,
+ TypeAliasElement element,
+ ) {
+ var errorNode = _getErrorNode(constructorName.type);
+ var constructorUsage = constructorName.parent;
+ if (constructorUsage is InstanceCreationExpression) {
+ errorReporter.reportErrorForNode(
+ CompileTimeErrorCode.INSTANTIATE_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER,
+ errorNode,
+ );
+ } else if (constructorUsage is ConstructorDeclaration &&
+ constructorUsage.redirectedConstructor == constructorName) {
+ errorReporter.reportErrorForNode(
+ CompileTimeErrorCode.REDIRECT_TO_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER,
+ errorNode,
+ );
+ } else {
+ throw UnimplementedError('${constructorUsage.runtimeType}');
+ }
+ }
+
/// Returns the simple identifier of the given (maybe prefixed) identifier.
static Identifier _getErrorNode(TypeName node) {
Identifier identifier = node.name;
diff --git a/pkg/analyzer/lib/src/error/codes.dart b/pkg/analyzer/lib/src/error/codes.dart
index 22fbfd8..2acc9c4 100644
--- a/pkg/analyzer/lib/src/error/codes.dart
+++ b/pkg/analyzer/lib/src/error/codes.dart
@@ -4922,6 +4922,18 @@
correction: "Try using one of the defined constants.");
/**
+ * It is a compile-time error for an instance creation `C<T1, .. Tk>(...)` or
+ * `C<T1, .. Tk>.name()` (where `k` may be zero, which means that the type
+ * argument list is absent) if `C` denotes a type alias that expands to a
+ * type variable.
+ */
+ static const CompileTimeErrorCode
+ INSTANTIATE_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER = CompileTimeErrorCode(
+ 'INSTANTIATE_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER',
+ "Type aliases that expand to a type parameter can't be instantiated.",
+ correction: "Try replacing it with a class.");
+
+ /**
* An integer literal with static type `double` and numeric value `i`
* evaluates to an instance of the `double` class representing the value `i`.
* It is a compile-time error if the value `i` cannot be represented
@@ -8999,6 +9011,19 @@
correction: "Try redirecting to a different constructor.");
/**
+ * It is a compile-time error for a redirecting factory constructor to have
+ * a body which is a type alias that expands to a type variable, or a body
+ * which is a parameterized type of the form `F<T1, .. Tk>`, where `F` is
+ * a type alias that expands to a type variable.
+ */
+ static const CompileTimeErrorCode
+ REDIRECT_TO_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER = CompileTimeErrorCode(
+ 'REDIRECT_TO_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER',
+ "Redirecting constructor can't redirect to a type alias "
+ "that expands to a type parameter.",
+ correction: "Try replacing it with a class.");
+
+ /**
* No parameters.
*/
// #### Description
diff --git a/pkg/analyzer/test/src/dart/resolution/type_name_test.dart b/pkg/analyzer/test/src/dart/resolution/type_name_test.dart
index f802202..f739c14 100644
--- a/pkg/analyzer/test/src/dart/resolution/type_name_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/type_name_test.dart
@@ -422,6 +422,19 @@
);
}
+ test_typeAlias_asParameterType_question() async {
+ await assertNoErrorsInCode(r'''
+typedef X<T> = T?;
+void f(X<int> a) {}
+''');
+
+ assertTypeName(
+ findNode.typeName('X<int>'),
+ findElement.typeAlias('X'),
+ 'int?',
+ );
+ }
+
test_typeAlias_asReturnType_interfaceType() async {
await assertNoErrorsInCode(r'''
typedef X<T> = Map<int, T>;
diff --git a/pkg/analyzer/test/src/diagnostics/instantiate_type_alias_expands_to_type_parameter_test.dart b/pkg/analyzer/test/src/diagnostics/instantiate_type_alias_expands_to_type_parameter_test.dart
new file mode 100644
index 0000000..0cb16f19
--- /dev/null
+++ b/pkg/analyzer/test/src/diagnostics/instantiate_type_alias_expands_to_type_parameter_test.dart
@@ -0,0 +1,114 @@
+// 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.
+
+import 'package:analyzer/src/error/codes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../dart/resolution/context_collection_resolution.dart';
+
+main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(InstantiateTypeAliasExpandsToTypeParameterTest);
+ });
+}
+
+@reflectiveTest
+class InstantiateTypeAliasExpandsToTypeParameterTest
+ extends PubPackageResolutionTest with WithNonFunctionTypeAliasesMixin {
+ CompileTimeErrorCode get _errorCode =>
+ CompileTimeErrorCode.INSTANTIATE_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER;
+
+ test_const_generic_noArguments_unnamed_typeParameter() async {
+ await assertErrorsInCode(r'''
+typedef A<T> = T;
+
+void f() {
+ const A();
+}
+''', [
+ error(_errorCode, 38, 1),
+ ]);
+ }
+
+ test_const_notGeneric_unnamed_class() async {
+ await assertNoErrorsInCode(r'''
+class A {
+ const A();
+}
+
+typedef X = A;
+
+void f() {
+ const X();
+}
+''');
+ }
+
+ test_new_generic_noArguments_unnamed_typeParameter() async {
+ await assertErrorsInCode(r'''
+typedef A<T> = T;
+
+void f() {
+ new A();
+}
+''', [
+ error(_errorCode, 36, 1),
+ ]);
+ }
+
+ test_new_generic_withArgument_named_typeParameter() async {
+ await assertErrorsInCode(r'''
+class A {
+ A.named();
+}
+
+typedef B<T> = T;
+
+void f() {
+ new B<A>.named();
+}
+''', [
+ error(_errorCode, 62, 1),
+ ]);
+ }
+
+ test_new_generic_withArgument_unnamed_typeParameter() async {
+ await assertErrorsInCode(r'''
+class A {}
+
+typedef B<T> = T;
+
+void f() {
+ new B<A>();
+}
+''', [
+ error(_errorCode, 48, 1),
+ ]);
+ }
+
+ test_new_notGeneric_unnamed_class() async {
+ await assertNoErrorsInCode(r'''
+class A {}
+
+typedef X = A;
+
+void f() {
+ new X();
+}
+''');
+ }
+
+ test_new_notGeneric_unnamed_typeParameter2() async {
+ await assertErrorsInCode(r'''
+typedef A<T> = T;
+typedef B<T> = A<T>;
+
+void f() {
+ new B();
+}
+''', [
+ error(_errorCode, 57, 1),
+ ]);
+ }
+}
diff --git a/pkg/analyzer/test/src/diagnostics/redirect_to_type_alias_expands_to_type_parameter_test.dart b/pkg/analyzer/test/src/diagnostics/redirect_to_type_alias_expands_to_type_parameter_test.dart
new file mode 100644
index 0000000..dbe1342
--- /dev/null
+++ b/pkg/analyzer/test/src/diagnostics/redirect_to_type_alias_expands_to_type_parameter_test.dart
@@ -0,0 +1,97 @@
+// 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.
+
+import 'package:analyzer/src/error/codes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../dart/resolution/context_collection_resolution.dart';
+
+main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(RedirectTypeAliasExpandsToTypeParameterTest);
+ });
+}
+
+@reflectiveTest
+class RedirectTypeAliasExpandsToTypeParameterTest
+ extends PubPackageResolutionTest with WithNonFunctionTypeAliasesMixin {
+ test_generic_typeParameter_withArgument_named() async {
+ await assertErrorsInCode(r'''
+class A implements C {
+ A.named();
+}
+
+typedef B<T> = T;
+
+class C {
+ factory C() = B<A>.named;
+}
+''', [
+ error(
+ CompileTimeErrorCode.REDIRECT_TO_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER,
+ 84,
+ 1),
+ ]);
+ }
+
+ test_generic_typeParameter_withArgument_unnamed() async {
+ await assertErrorsInCode(r'''
+class A implements C {}
+
+typedef B<T> = T;
+
+class C {
+ factory C() = B<A>;
+}
+''', [
+ error(
+ CompileTimeErrorCode.REDIRECT_TO_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER,
+ 70,
+ 1),
+ ]);
+ }
+
+ test_generic_typeParameter_withoutArgument_unnamed() async {
+ await assertErrorsInCode(r'''
+class A implements C {}
+
+typedef B<T> = T;
+
+class C {
+ factory C() = B;
+}
+''', [
+ error(
+ CompileTimeErrorCode.REDIRECT_TO_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER,
+ 70,
+ 1),
+ ]);
+ }
+
+ test_notGeneric_class_named() async {
+ await assertNoErrorsInCode(r'''
+class A implements C {
+ A.named();
+}
+
+typedef B = A;
+
+class C {
+ factory C() = B.named;
+}
+''');
+ }
+
+ test_notGeneric_class_unnamed() async {
+ await assertNoErrorsInCode(r'''
+class A implements C {}
+
+typedef B = A;
+
+class C {
+ factory C() = B;
+}
+''');
+ }
+}
diff --git a/pkg/analyzer/test/src/diagnostics/test_all.dart b/pkg/analyzer/test/src/diagnostics/test_all.dart
index 05d7fb0..d8c3e4e 100644
--- a/pkg/analyzer/test/src/diagnostics/test_all.dart
+++ b/pkg/analyzer/test/src/diagnostics/test_all.dart
@@ -267,6 +267,8 @@
as instance_member_access_from_static;
import 'instantiate_abstract_class_test.dart' as instantiate_abstract_class;
import 'instantiate_enum_test.dart' as instantiate_enum;
+import 'instantiate_type_alias_expands_to_type_parameter_test.dart'
+ as instantiate_type_alias_expands_to_type_parameter;
import 'integer_literal_imprecise_as_double_test.dart'
as integer_literal_imprecise_as_double;
import 'integer_literal_out_of_range_test.dart' as integer_literal_out_of_range;
@@ -527,6 +529,8 @@
import 'redirect_to_non_class_test.dart' as redirect_to_non_class;
import 'redirect_to_non_const_constructor_test.dart'
as redirect_to_non_const_constructor;
+import 'redirect_to_type_alias_expands_to_type_parameter_test.dart'
+ as redirect_to_type_alias_expands_to_type_parameter;
import 'referenced_before_declaration_test.dart'
as referenced_before_declaration;
import 'rethrow_outside_catch_test.dart' as rethrow_outside_catch;
@@ -833,6 +837,7 @@
instance_member_access_from_static.main();
instantiate_abstract_class.main();
instantiate_enum.main();
+ instantiate_type_alias_expands_to_type_parameter.main();
integer_literal_imprecise_as_double.main();
integer_literal_out_of_range.main();
invalid_annotation.main();
@@ -1005,6 +1010,7 @@
redirect_to_missing_constructor.main();
redirect_to_non_class.main();
redirect_to_non_const_constructor.main();
+ redirect_to_type_alias_expands_to_type_parameter.main();
referenced_before_declaration.main();
rethrow_outside_catch.main();
return_in_generative_constructor.main();
diff --git a/pkg/analyzer/test/src/summary/resynthesize_common.dart b/pkg/analyzer/test/src/summary/resynthesize_common.dart
index 99a5202..a7c9e90 100644
--- a/pkg/analyzer/test/src/summary/resynthesize_common.dart
+++ b/pkg/analyzer/test/src/summary/resynthesize_common.dart
@@ -5119,6 +5119,30 @@
''');
}
+ test_constructor_redirected_factory_named_generic_viaTypeAlias() async {
+ featureSet = FeatureSets.nonFunctionTypeAliases;
+ var library = await checkLibrary('''
+typedef A<T, U> = C<T, U>;
+class B<T, U> {
+ factory B() = A<U, T>.named;
+ B._();
+}
+class C<T, U> extends A<U, T> {
+ C.named() : super._();
+}
+''');
+ checkElementText(library, r'''
+typedef A<T, U> = C<T, U>;
+class B<T, U> {
+ factory B() = C<U, T>.named;
+ B._();
+}
+class C<T, U> extends C<U, T> {
+ C.named();
+}
+''');
+ }
+
test_constructor_redirected_factory_named_imported() async {
addLibrarySource('/foo.dart', '''
import 'test.dart';
@@ -5282,6 +5306,30 @@
''');
}
+ test_constructor_redirected_factory_unnamed_generic_viaTypeAlias() async {
+ featureSet = FeatureSets.nonFunctionTypeAliases;
+ var library = await checkLibrary('''
+typedef A<T, U> = C<T, U>;
+class B<T, U> {
+ factory B() = A<U, T>;
+ B_();
+}
+class C<T, U> extends B<U, T> {
+ C() : super._();
+}
+''');
+ checkElementText(library, r'''
+typedef A<T, U> = C<T, U>;
+class B<T, U> {
+ factory B() = C<U, T>;
+ dynamic B_();
+}
+class C<T, U> extends B<U, T> {
+ C();
+}
+''');
+ }
+
test_constructor_redirected_factory_unnamed_imported() async {
addLibrarySource('/foo.dart', '''
import 'test.dart';
@@ -5328,6 +5376,31 @@
''');
}
+ test_constructor_redirected_factory_unnamed_imported_viaTypeAlias() async {
+ featureSet = FeatureSets.nonFunctionTypeAliases;
+ addLibrarySource('/foo.dart', '''
+import 'test.dart';
+typedef A = B;
+class B extends C {
+ B() : super._();
+}
+''');
+ var library = await checkLibrary('''
+import 'foo.dart';
+class C {
+ factory C() = A;
+ C._();
+}
+''');
+ checkElementText(library, r'''
+import 'foo.dart';
+class C {
+ factory C() = B;
+ C._();
+}
+''');
+ }
+
test_constructor_redirected_factory_unnamed_prefixed() async {
addLibrarySource('/foo.dart', '''
import 'test.dart';
@@ -5374,6 +5447,31 @@
''');
}
+ test_constructor_redirected_factory_unnamed_prefixed_viaTypeAlias() async {
+ featureSet = FeatureSets.nonFunctionTypeAliases;
+ addLibrarySource('/foo.dart', '''
+import 'test.dart';
+typedef A = B;
+class B extends C {
+ B() : super._();
+}
+''');
+ var library = await checkLibrary('''
+import 'foo.dart' as foo;
+class C {
+ factory C() = foo.A;
+ C._();
+}
+''');
+ checkElementText(library, r'''
+import 'foo.dart' as foo;
+class C {
+ factory C() = B;
+ C._();
+}
+''');
+ }
+
test_constructor_redirected_factory_unnamed_unresolved() async {
var library = await checkLibrary('''
class C<E> {
@@ -5387,6 +5485,30 @@
''');
}
+ test_constructor_redirected_factory_unnamed_viaTypeAlias() async {
+ featureSet = FeatureSets.nonFunctionTypeAliases;
+ var library = await checkLibrary('''
+typedef A = C;
+class B {
+ factory B() = A;
+ B._();
+}
+class C extends B {
+ C() : super._();
+}
+''');
+ checkElementText(library, r'''
+typedef A = C;
+class B {
+ factory B() = C;
+ B._();
+}
+class C extends B {
+ C();
+}
+''');
+ }
+
test_constructor_redirected_thisInvocation_named() async {
var library = await checkLibrary('''
class C {
diff --git a/pkg/analyzer/test/src/summary/top_level_inference_test.dart b/pkg/analyzer/test/src/summary/top_level_inference_test.dart
index 11b223e..9fbb5f4 100644
--- a/pkg/analyzer/test/src/summary/top_level_inference_test.dart
+++ b/pkg/analyzer/test/src/summary/top_level_inference_test.dart
@@ -2711,8 +2711,10 @@
Future<LibraryElement> _encodeDecodeLibrary(String text) async {
newFile(testFilePath, content: text);
- var analysisSession = contextFor(testFilePath).currentSession;
- var result = await analysisSession.getUnitElement(testFilePath);
+
+ var path = convertPath(testFilePath);
+ var analysisSession = contextFor(path).currentSession;
+ var result = await analysisSession.getUnitElement(path);
return result.element.library /*!*/;
}
}
diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt
index 3d7c72e..4cbae00 100644
--- a/pkg/front_end/test/spell_checking_list_code.txt
+++ b/pkg/front_end/test/spell_checking_list_code.txt
@@ -381,6 +381,7 @@
exhausted
existentially
exp
+expando
expense
exportable
exportee
@@ -1057,6 +1058,7 @@
sra
srawlins
src
+ssa
st
stability
stacktrace
@@ -1171,6 +1173,7 @@
tops
tput
tracker
+traditional
transformers
transforming
translation
diff --git a/tools/VERSION b/tools/VERSION
index 0f9cb79..b9ae36a 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 12
PATCH 0
-PRERELEASE 155
+PRERELEASE 156
PRERELEASE_PATCH 0
\ No newline at end of file