blob: d185aa66e1e960e57b493cd8f606e2192b615892 [file] [log] [blame]
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/resolver/flow_analysis.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'driver_resolution.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(DefiniteAssignmentFlowTest);
defineReflectiveTests(TypePromotionFlowTest);
});
}
@reflectiveTest
class DefiniteAssignmentFlowTest extends DriverResolutionTest {
FlowAnalysis flow;
/// Assert that only local variables with the given names are marked as read
/// before being written. All the other local variables are implicitly
/// considered definitely assigned.
void assertReadBeforeWritten(
[String name1, String name2, String name3, String name4]) {
var expected = [name1, name2, name3, name4]
.where((i) => i != null)
.map((name) => findElement.localVar(name))
.toList();
expect(flow.readBeforeWritten, unorderedEquals(expected));
}
test_binaryExpression_logicalAnd_left() async {
await trackCode(r'''
main(bool c) {
int v;
((v = 0) >= 0) && c;
v;
}
''');
assertReadBeforeWritten();
}
test_binaryExpression_logicalAnd_right() async {
await trackCode(r'''
main(bool c) {
int v;
c && ((v = 0) >= 0);
v;
}
''');
assertReadBeforeWritten('v');
}
test_binaryExpression_logicalOr_left() async {
await trackCode(r'''
main(bool c) {
int v;
((v = 0) >= 0) || c;
v;
}
''');
assertReadBeforeWritten();
}
test_binaryExpression_logicalOr_right() async {
await trackCode(r'''
main(bool c) {
int v;
c || ((v = 0) >= 0);
v;
}
''');
assertReadBeforeWritten('v');
}
test_binaryExpression_plus_left() async {
await trackCode(r'''
main() {
int v;
(v = 0) + 1;
v;
}
''');
assertReadBeforeWritten();
}
test_binaryExpression_plus_right() async {
await trackCode(r'''
main() {
int v;
1 + (v = 0);
v;
}
''');
assertReadBeforeWritten();
}
test_if_condition() async {
await trackCode(r'''
main() {
int v;
if ((v = 0) >= 0) {
v;
} else {
v;
}
v;
}
''');
assertReadBeforeWritten();
}
test_if_condition_false() async {
// new test
await trackCode(r'''
void f() {
int v;
if (false) {
// not assigned
} else {
v = 0;
}
v;
}
''');
assertReadBeforeWritten();
}
test_if_condition_logicalAnd_else() async {
// new test
await trackCode(r'''
void f(bool b, int i) {
int v;
if (b && (v = i) > 0) {
} else {
v;
}
}
''');
assertReadBeforeWritten('v');
}
test_if_condition_logicalAnd_then() async {
// new test
await trackCode(r'''
void f(bool b, int i) {
int v;
if (b && (v = i) > 0) {
v;
}
}
''');
assertReadBeforeWritten();
}
test_if_condition_logicalOr_else() async {
// new test
await trackCode(r'''
void f(bool b, int i) {
int v;
if (b || (v = i) > 0) {
} else {
v;
}
}
''');
assertReadBeforeWritten();
}
test_if_condition_logicalOr_then() async {
// new test
await trackCode(r'''
void f(bool b, int i) {
int v;
if (b || (v = i) > 0) {
v;
} else {
}
}
''');
assertReadBeforeWritten('v');
}
test_if_condition_notFalse() async {
// new test
await trackCode(r'''
void f() {
int v;
if (!false) {
v = 0;
}
v;
}
''');
assertReadBeforeWritten();
}
test_if_condition_notTrue() async {
// new test
await trackCode(r'''
void f() {
int v;
if (!true) {
// not assigned
} else {
v = 0;
}
v;
}
''');
assertReadBeforeWritten();
}
test_if_condition_true() async {
// new test
await trackCode(r'''
void f() {
int v;
if (true) {
v = 0;
}
v;
}
''');
assertReadBeforeWritten();
}
test_if_then() async {
await trackCode(r'''
main(bool c) {
int v;
if (c) {
v = 0;
}
v;
}
''');
assertReadBeforeWritten('v');
}
test_if_thenElse_all() async {
await trackCode(r'''
main(bool c) {
int v;
if (c) {
v = 0;
v;
} else {
v = 0;
v;
}
v;
}
''');
assertReadBeforeWritten();
}
test_if_thenElse_else() async {
await trackCode(r'''
main(bool c) {
int v;
if (c) {
// not assigned
} else {
v = 0;
}
v;
}
''');
assertReadBeforeWritten('v');
}
test_if_thenElse_then() async {
await trackCode(r'''
main(bool c) {
int v;
if (c) {
v = 0;
} else {
// not assigned
}
v;
}
''');
assertReadBeforeWritten('v');
}
/// Resolve the given [code] and track assignments in the unit.
Future<void> trackCode(String code) async {
addTestFile(code);
await resolveTestFile();
var typeSystem = result.unit.declaredElement.context.typeSystem;
flow = FlowAnalysis(typeSystem);
var visitor = _AstVisitor(flow, {});
result.unit.accept(visitor);
}
}
@reflectiveTest
class TypePromotionFlowTest extends DriverResolutionTest {
Map<AstNode, DartType> promotedTypes = {};
FlowAnalysis flow;
void assertNotPromoted(String search) {
var node = findNode.simple(search);
var actualType = promotedTypes[node];
expect(actualType, isNull);
}
void assertPromoted(String search, String expectedType) {
var node = findNode.simple(search);
var actualType = promotedTypes[node];
if (actualType == null) {
fail('$expectedType expected, but actually not promoted');
}
assertElementTypeString(actualType, expectedType);
}
test_if_combine_empty() async {
// new test
await trackCode(r'''
main(bool b, Object v) {
if (b) {
v is int || (throw 1);
} else {
v is String || (throw 2);
}
v; // 3
}
''');
assertNotPromoted('v; // 3');
}
test_if_isNotType() async {
// new test
await trackCode(r'''
main(v) {
if (v is! String) {
v; // 1
} else {
v; // 2
}
v; // 3
}
''');
assertNotPromoted('v; // 1');
assertPromoted('v; // 2', 'String');
assertNotPromoted('v; // 3');
}
test_if_isNotType_return() async {
// new test
await trackCode(r'''
main(v) {
if (v is! String) return;
v; // ref
}
''');
assertPromoted('v; // ref', 'String');
}
test_if_isType() async {
await trackCode(r'''
main(v) {
if (v is String) {
v; // 1
} else {
v; // 2
}
v; // 3
}
''');
assertPromoted('v; // 1', 'String');
assertNotPromoted('v; // 2');
assertNotPromoted('v; // 3');
}
test_if_isType_thenNonBoolean() async {
await trackCode(r'''
f(Object x) {
if ((x is String) != 3) {
x; // 1
}
}
''');
assertNotPromoted('x; // 1');
}
test_if_logicalNot_isType() async {
// new test
await trackCode(r'''
main(v) {
if (!(v is String)) {
v; // 1
} else {
v; // 2
}
v; // 3
}
''');
assertNotPromoted('v; // 1');
assertPromoted('v; // 2', 'String');
assertNotPromoted('v; // 3');
}
test_logicalOr_throw() async {
// new test
await trackCode(r'''
main(v) {
v is String || (throw 42);
v; // ref
}
''');
assertPromoted('v; // ref', 'String');
}
/// Resolve the given [code] and track assignments in the unit.
Future<void> trackCode(String code) async {
addTestFile(code);
await resolveTestFile();
var typeSystem = result.unit.declaredElement.context.typeSystem;
flow = FlowAnalysis(typeSystem);
var visitor = _AstVisitor(flow, promotedTypes);
result.unit.accept(visitor);
}
}
/// [AstVisitor] that drives the [flow] in the way we expect the resolver
/// will do in production.
class _AstVisitor extends RecursiveAstVisitor<void> {
final FlowAnalysis flow;
final Map<AstNode, DartType> promotedTypes;
_AstVisitor(this.flow, this.promotedTypes);
@override
void visitAssignmentExpression(AssignmentExpression node) {
var left = node.leftHandSide;
var right = node.rightHandSide;
LocalVariableElement localElement;
if (left is SimpleIdentifier) {
var element = left.staticElement;
if (element is LocalVariableElement) {
localElement = element;
}
}
if (localElement != null) {
var isPure = node.operator.type == TokenType.EQ;
if (!isPure) {
flow.read(localElement);
}
right.accept(this);
flow.write(localElement);
} else {
left.accept(this);
right.accept(this);
}
}
@override
void visitBinaryExpression(BinaryExpression node) {
var left = node.leftOperand;
var right = node.rightOperand;
var operator = node.operator.type;
if (operator == TokenType.AMPERSAND_AMPERSAND) {
left.accept(this);
flow.logicalAnd_rightBegin(node);
right.accept(this);
flow.logicalAnd_end(node);
} else if (operator == TokenType.BAR_BAR) {
left.accept(this);
flow.logicalOr_rightBegin(node);
right.accept(this);
flow.logicalOr_end(node);
} else {
left.accept(this);
right.accept(this);
}
// var isLogical = operator == TokenType.AMPERSAND_AMPERSAND ||
// operator == TokenType.BAR_BAR ||
// operator == TokenType.QUESTION_QUESTION;
//
// left.accept(this);
//
// if (isLogical) {
// tracker.beginBinaryExpressionLogicalRight();
// }
//
// right.accept(this);
//
// if (isLogical) {
// tracker.endBinaryExpressionLogicalRight();
// }
}
@override
void visitBlockFunctionBody(BlockFunctionBody node) {
super.visitBlockFunctionBody(node);
flow.verifyStackEmpty();
}
@override
void visitBooleanLiteral(BooleanLiteral node) {
super.visitBooleanLiteral(node);
if (_isFalseLiteral(node)) {
flow.falseLiteral(node);
}
if (_isTrueLiteral(node)) {
flow.trueLiteral(node);
}
}
@override
void visitExpressionFunctionBody(ExpressionFunctionBody node) {
super.visitExpressionFunctionBody(node);
flow.verifyStackEmpty();
}
@override
void visitIfStatement(IfStatement node) {
var condition = node.condition;
var thenStatement = node.thenStatement;
var elseStatement = node.elseStatement;
condition.accept(this);
flow.ifStatement_thenBegin(node);
thenStatement.accept(this);
if (elseStatement != null) {
flow.ifStatement_elseBegin();
elseStatement.accept(this);
}
flow.ifStatement_end(elseStatement != null);
}
@override
void visitIsExpression(IsExpression node) {
super.visitIsExpression(node);
var expression = node.expression;
var typeAnnotation = node.type;
if (expression is SimpleIdentifier) {
var element = expression.staticElement;
if (element is LocalElement) {
flow.isExpression_end(node, element, typeAnnotation.type);
}
}
}
@override
void visitPrefixExpression(PrefixExpression node) {
var operand = node.operand;
var operator = node.operator.type;
if (operator == TokenType.BANG) {
operand.accept(this);
flow.logicalNot_end(node);
} else {
operand.accept(this);
}
}
@override
void visitReturnStatement(ReturnStatement node) {
super.visitReturnStatement(node);
flow.handleExit();
}
@override
void visitSimpleIdentifier(SimpleIdentifier node) {
var element = node.staticElement;
var isLocalVariable = element is LocalVariableElement;
if (isLocalVariable || element is ParameterElement) {
if (node.inGetterContext()) {
if (isLocalVariable) {
flow.read(element);
}
var promotedType = flow.promotedType(element);
if (promotedType != null) {
promotedTypes[node] = promotedType;
}
}
}
super.visitSimpleIdentifier(node);
}
@override
void visitThrowExpression(ThrowExpression node) {
super.visitThrowExpression(node);
flow.handleExit();
}
@override
void visitVariableDeclarationStatement(VariableDeclarationStatement node) {
var variables = node.variables.variables;
for (var i = 0; i < variables.length; ++i) {
var variable = variables[i];
flow.add(variable.declaredElement,
assigned: variable.initializer != null);
}
super.visitVariableDeclarationStatement(node);
}
static bool _isFalseLiteral(AstNode node) {
return node is BooleanLiteral && !node.value;
}
static bool _isTrueLiteral(AstNode node) {
return node is BooleanLiteral && node.value;
}
}