Flow analysis: implement "why not promoted" logic for assignments.

This CL handles ordinary assignment expressions, variable declaration
initializers, and constructor field initializers.

Bug: https://github.com/dart-lang/sdk/issues/44898
Change-Id: I06bae2c7d57213bd7769c931a03080d148af3dbe
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/192724
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Paul Berry 2021-03-25 02:01:33 +00:00 committed by commit-bot@chromium.org
parent 6929718456
commit f161e228e4
5 changed files with 139 additions and 18 deletions

View file

@ -290,3 +290,102 @@ whileCondition(C20 c) {
while (/*analyzer.notPromoted(propertyNotPromoted(target: member:C20.bad, type: bool?))*/ c
. /*cfe.notPromoted(propertyNotPromoted(target: member:C20.bad, type: bool?))*/ bad) {}
}
class C21 {
int? bad;
}
assignmentRhs(C21 c, int i) {
if (c.bad == null) return;
i =
/*analyzer.notPromoted(propertyNotPromoted(target: member:C21.bad, type: int?))*/ c
. /*cfe.notPromoted(propertyNotPromoted(target: member:C21.bad, type: int?))*/ bad;
}
class C22 {
int? bad;
}
variableInitializer(C22 c) {
if (c.bad == null) return;
int i =
/*analyzer.notPromoted(propertyNotPromoted(target: member:C22.bad, type: int?))*/ c
. /*cfe.notPromoted(propertyNotPromoted(target: member:C22.bad, type: int?))*/ bad;
}
class C23 {
int? bad;
final int x;
final int y;
C23.constructorInitializer(C23 c)
: x = c.bad!,
y =
/*analyzer.notPromoted(propertyNotPromoted(target: member:C23.bad, type: int?))*/ c
. /*cfe.notPromoted(propertyNotPromoted(target: member:C23.bad, type: int?))*/ bad;
}
class C24 {
int? bad;
}
forVariableInitializer(C24 c) {
if (c.bad == null) return;
for (int i =
/*analyzer.notPromoted(propertyNotPromoted(target: member:C24.bad, type: int?))*/ c
. /*cfe.notPromoted(propertyNotPromoted(target: member:C24.bad, type: int?))*/ bad;
false;) {}
[
for (int i =
/*analyzer.notPromoted(propertyNotPromoted(target: member:C24.bad, type: int?))*/ c
. /*cfe.notPromoted(propertyNotPromoted(target: member:C24.bad, type: int?))*/ bad;
false;)
null
];
({
for (int i =
/*analyzer.notPromoted(propertyNotPromoted(target: member:C24.bad, type: int?))*/ c
. /*cfe.notPromoted(propertyNotPromoted(target: member:C24.bad, type: int?))*/ bad;
false;)
null
});
({
for (int i =
/*analyzer.notPromoted(propertyNotPromoted(target: member:C24.bad, type: int?))*/ c
. /*cfe.notPromoted(propertyNotPromoted(target: member:C24.bad, type: int?))*/ bad;
false;)
null: null
});
}
class C25 {
int? bad;
}
forAssignmentInitializer(C25 c, int i) {
if (c.bad == null) return;
for (i =
/*analyzer.notPromoted(propertyNotPromoted(target: member:C25.bad, type: int?))*/ c
. /*cfe.notPromoted(propertyNotPromoted(target: member:C25.bad, type: int?))*/ bad;
false;) {}
[
for (i =
/*analyzer.notPromoted(propertyNotPromoted(target: member:C25.bad, type: int?))*/ c
. /*cfe.notPromoted(propertyNotPromoted(target: member:C25.bad, type: int?))*/ bad;
false;)
null
];
({
for (i =
/*analyzer.notPromoted(propertyNotPromoted(target: member:C25.bad, type: int?))*/ c
. /*cfe.notPromoted(propertyNotPromoted(target: member:C25.bad, type: int?))*/ bad;
false;)
null
});
({
for (i =
/*analyzer.notPromoted(propertyNotPromoted(target: member:C25.bad, type: int?))*/ c
. /*cfe.notPromoted(propertyNotPromoted(target: member:C25.bad, type: int?))*/ bad;
false;)
null: null
});
}

View file

@ -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:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
@ -84,8 +85,10 @@ class AssignmentExpressionResolver {
}
right.accept(_resolver);
right = node.rightHandSide;
var whyNotPromoted = flow?.whyNotPromoted(right);
_resolveTypes(node);
_resolveTypes(node, whyNotPromoted: whyNotPromoted);
if (flow != null) {
if (writeElement is PromotableElement) {
@ -103,8 +106,9 @@ class AssignmentExpressionResolver {
void _checkForInvalidAssignment(
DartType writeType,
Expression right,
DartType rightType,
) {
DartType rightType, {
required Map<DartType, NonPromotionReason> Function()? whyNotPromoted,
}) {
if (!writeType.isVoid && _checkForUseOfVoidResult(right)) {
return;
}
@ -117,6 +121,8 @@ class AssignmentExpressionResolver {
CompileTimeErrorCode.INVALID_ASSIGNMENT,
right,
[rightType, writeType],
_resolver.computeWhyNotPromotedMessages(
right, right, whyNotPromoted?.call()),
);
}
@ -192,7 +198,8 @@ class AssignmentExpressionResolver {
}
}
void _resolveTypes(AssignmentExpressionImpl node) {
void _resolveTypes(AssignmentExpressionImpl node,
{required Map<DartType, NonPromotionReason> Function()? whyNotPromoted}) {
DartType assignedType;
DartType nodeType;
@ -239,6 +246,7 @@ class AssignmentExpressionResolver {
node.writeType!,
node.rightHandSide,
assignedType,
whyNotPromoted: operator == TokenType.EQ ? whyNotPromoted : null,
);
}

View file

@ -52,10 +52,12 @@ class VariableDeclarationResolver {
}
initializer.accept(_resolver);
initializer = node.initializer;
initializer = node.initializer!;
var whyNotPromoted =
_resolver.flowAnalysis?.flow?.whyNotPromoted(initializer);
if (parent.type == null) {
_setInferredType(element, initializer!.typeOrThrow);
_setInferredType(element, initializer.typeOrThrow);
}
if (isTopLevel) {
@ -72,6 +74,8 @@ class VariableDeclarationResolver {
(element as ConstVariableElement).constantInitializer =
ConstantAstCloner().cloneNullableNode(initializer);
}
_resolver.checkForInvalidAssignment(node.name, initializer,
whyNotPromoted: whyNotPromoted);
}
void _setInferredType(VariableElement element, DartType initializerType) {

View file

@ -41,7 +41,7 @@ mixin ErrorDetectionHelpers {
_checkForAssignableExpressionAtType(
expression, actualStaticType, expectedStaticType, errorCode,
whyNotPromotedInfo: whyNotPromotedInfo);
whyNotPromoted: whyNotPromotedInfo);
}
}
@ -89,7 +89,8 @@ mixin ErrorDetectionHelpers {
/// [StaticWarningCode.FIELD_INITIALIZER_NOT_ASSIGNABLE].
void checkForFieldInitializerNotAssignable(
ConstructorFieldInitializer initializer, FieldElement fieldElement,
{required bool isConstConstructor}) {
{required bool isConstConstructor,
required Map<DartType, NonPromotionReason> Function()? whyNotPromoted}) {
// prepare field type
DartType fieldType = fieldElement.type;
// prepare expression type
@ -102,6 +103,8 @@ mixin ErrorDetectionHelpers {
}
return;
}
var messages = computeWhyNotPromotedMessages(
expression, expression, whyNotPromoted?.call());
// report problem
if (isConstConstructor) {
// TODO(paulberry): this error should be based on the actual type of the
@ -109,12 +112,14 @@ mixin ErrorDetectionHelpers {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.CONST_FIELD_INITIALIZER_NOT_ASSIGNABLE,
expression,
[staticType, fieldType]);
[staticType, fieldType],
messages);
}
errorReporter.reportErrorForNode(
CompileTimeErrorCode.FIELD_INITIALIZER_NOT_ASSIGNABLE,
expression,
[staticType, fieldType]);
[staticType, fieldType],
messages);
// TODO(brianwilkerson) Define a hint corresponding to these errors and
// report it if appropriate.
// // test the propagated type of the expression
@ -143,7 +148,8 @@ mixin ErrorDetectionHelpers {
/// represent a valid assignment.
///
/// See [CompileTimeErrorCode.INVALID_ASSIGNMENT].
void checkForInvalidAssignment(Expression? lhs, Expression? rhs) {
void checkForInvalidAssignment(Expression? lhs, Expression? rhs,
{Map<DartType, NonPromotionReason> Function()? whyNotPromoted}) {
if (lhs == null || rhs == null) {
return;
}
@ -173,7 +179,8 @@ mixin ErrorDetectionHelpers {
}
_checkForAssignableExpression(
rhs, leftType, CompileTimeErrorCode.INVALID_ASSIGNMENT);
rhs, leftType, CompileTimeErrorCode.INVALID_ASSIGNMENT,
whyNotPromoted: whyNotPromoted);
}
/// Check for situations where the result of a method or function is used,
@ -247,10 +254,12 @@ mixin ErrorDetectionHelpers {
}
bool _checkForAssignableExpression(
Expression expression, DartType expectedStaticType, ErrorCode errorCode) {
Expression expression, DartType expectedStaticType, ErrorCode errorCode,
{required Map<DartType, NonPromotionReason> Function()? whyNotPromoted}) {
DartType actualStaticType = expression.typeOrThrow;
return _checkForAssignableExpressionAtType(
expression, actualStaticType, expectedStaticType, errorCode);
expression, actualStaticType, expectedStaticType, errorCode,
whyNotPromoted: whyNotPromoted);
}
bool _checkForAssignableExpressionAtType(
@ -258,7 +267,7 @@ mixin ErrorDetectionHelpers {
DartType actualStaticType,
DartType expectedStaticType,
ErrorCode errorCode,
{Map<DartType, NonPromotionReason> Function()? whyNotPromotedInfo}) {
{Map<DartType, NonPromotionReason> Function()? whyNotPromoted}) {
if (!typeSystem.isAssignableTo(actualStaticType, expectedStaticType)) {
AstNode getErrorNode(AstNode node) {
if (node is CascadeExpression) {
@ -275,7 +284,7 @@ mixin ErrorDetectionHelpers {
getErrorNode(expression),
[actualStaticType, expectedStaticType],
computeWhyNotPromotedMessages(
expression, expression, whyNotPromotedInfo?.call()),
expression, expression, whyNotPromoted?.call()),
);
return false;
}

View file

@ -1297,12 +1297,14 @@ class ResolverVisitor extends ScopedVisitor with ErrorDetectionHelpers {
var fieldElement = enclosingClass!.getField(node.fieldName.name);
InferenceContext.setType(node.expression, fieldElement?.type);
node.expression.accept(this);
var whyNotPromoted = flowAnalysis?.flow?.whyNotPromoted(node.expression);
node.accept(elementResolver);
node.accept(typeAnalyzer);
var enclosingConstructor = enclosingFunction as ConstructorElement;
if (fieldElement != null) {
checkForFieldInitializerNotAssignable(node, fieldElement,
isConstConstructor: enclosingConstructor.isConst);
isConstConstructor: enclosingConstructor.isConst,
whyNotPromoted: whyNotPromoted);
}
}
@ -2160,7 +2162,6 @@ class ResolverVisitor extends ScopedVisitor with ErrorDetectionHelpers {
isFinal: parent.isFinal, isLate: parent.isLate);
}
}
checkForInvalidAssignment(node.name, node.initializer);
}
@override