1
0
mirror of https://github.com/dart-lang/sdk synced 2024-07-03 00:08:46 +00:00

Flow analysis: account for initializers of implicitly typed variables.

Previously, initializers of implicitly typed variables did not
contribute to the SSA tracking performed by flow analysis (see
https://github.com/dart-lang/language/issues/1785).  This change fixes
the bug, however the fix is conditioned on the "constructor tearoffs"
language feature to avoid compatibility issues (we don't want someone
to publish a package taking advantage of the fix, without realizing
that it makes their package unusable on older SDKs).

TEST=standard trybots
Bug: https://github.com/dart-lang/language/issues/1785
Change-Id: I1143440c7a9795b059e8f4b84e3f4125cd80732c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/211306
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
Paul Berry 2021-08-27 15:01:15 +00:00 committed by commit-bot@chromium.org
parent 1b4f067e0b
commit 1b18e8f5ac
15 changed files with 346 additions and 34 deletions

View File

@ -21,6 +21,30 @@
This brings the implementation behavior in line with the spec.
- Initializer expressions on implicitly typed condition variables can now
contribute to type promotion. For example, this program no longer produces a
compile-time error:
```dart
f(int? i) {
var iIsNull = i == null;
if (!iIsNull) {
print(i + 1); // OK, because `i` is known to be non-null
}
}
```
Previously, the above program had a compile-time error because type promotion
due to a bug ([#1785][]) which prevented the initializer expression (`i ==
null`) from being accounted for when the variable in question (`iIsNull`)
lacked an explicit type.
To avoid causing problems for packages that are intended to work with older
versions of Dart, the fix only takes effect when the "constructor tearoffs"
language feature is enabled.
[#1785]: https://github.com/dart-lang/language/issues/1785
### Tools
#### Dart command line

View File

@ -368,8 +368,11 @@ class ExpressionInfo<Variable extends Object, Type extends Object> {
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) {
return new _FlowAnalysisImpl(typeOperations, assignedVariables);
AssignedVariables<Node, Variable> assignedVariables,
{required bool respectImplicitlyTypedVarInitializers}) {
return new _FlowAnalysisImpl(typeOperations, assignedVariables,
respectImplicitlyTypedVarInitializers:
respectImplicitlyTypedVarInitializers);
}
factory FlowAnalysis.legacy(TypeOperations<Variable, Type> typeOperations,
@ -983,10 +986,13 @@ class FlowAnalysisDebug<Node extends Object, Statement extends Node,
bool _exceptionOccurred = false;
factory FlowAnalysisDebug(TypeOperations<Variable, Type> typeOperations,
AssignedVariables<Node, Variable> assignedVariables) {
AssignedVariables<Node, Variable> assignedVariables,
{required bool respectImplicitlyTypedVarInitializers}) {
print('FlowAnalysisDebug()');
return new FlowAnalysisDebug._(
new _FlowAnalysisImpl(typeOperations, assignedVariables));
return new FlowAnalysisDebug._(new _FlowAnalysisImpl(
typeOperations, assignedVariables,
respectImplicitlyTypedVarInitializers:
respectImplicitlyTypedVarInitializers));
}
factory FlowAnalysisDebug.legacy(
@ -3454,7 +3460,15 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
final AssignedVariables<Node, Variable> _assignedVariables;
_FlowAnalysisImpl(this.typeOperations, this._assignedVariables);
/// Indicates whether initializers of implicitly typed variables should be
/// accounted for by SSA analysis. (In an ideal world, they always would be,
/// but due to https://github.com/dart-lang/language/issues/1785, they weren't
/// always, and we need to be able to replicate the old behavior when
/// analyzing old language versions).
final bool respectImplicitlyTypedVarInitializers;
_FlowAnalysisImpl(this.typeOperations, this._assignedVariables,
{required this.respectImplicitlyTypedVarInitializers});
@override
bool get isReachable => _current.reachable.overallReachable;
@ -3810,10 +3824,11 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
if (isLate) {
// Don't get expression info for late variables, since we don't know when
// they'll be initialized.
} else if (isImplicitlyTyped) {
// We don't get expression info for implicitly typed variables yet (bug
// https://github.com/dart-lang/language/issues/1785).
// TODO(paulberry): fix this.
} else if (isImplicitlyTyped && !respectImplicitlyTypedVarInitializers) {
// If the language version is too old, SSA analysis has to ignore
// initializer expressions for implicitly typed variables, in order to
// preserve the buggy behavior of
// https://github.com/dart-lang/language/issues/1785.
} else {
expressionInfo = _getExpressionInfo(initializerExpression);
}

View File

@ -594,7 +594,8 @@ main() {
var e = expr('Null');
var s = if_(e, []);
var flow = FlowAnalysis<Node, Statement, Expression, Var, Type>(
h, AssignedVariables<Node, Var>());
h, AssignedVariables<Node, Var>(),
respectImplicitlyTypedVarInitializers: true);
flow.ifStatement_conditionBegin();
flow.ifStatement_thenBegin(e, s);
expect(() => flow.finish(), _asserts);
@ -1407,9 +1408,10 @@ main() {
]);
});
test('initialize() does not store expressionInfo for implicitly typed vars',
() {
var h = Harness();
test(
'initialize() does not store expressionInfo for implicitly typed '
'vars, pre-bug fix', () {
var h = Harness(respectImplicitlyTypedVarInitializers: false);
var x = Var('x', 'Object', isImplicitlyTyped: true);
var y = Var('y', 'int?');
h.run([
@ -1420,6 +1422,34 @@ main() {
]);
});
test(
'initialize() stores expressionInfo for implicitly typed '
'vars, post-bug fix', () {
var h = Harness(respectImplicitlyTypedVarInitializers: true);
var x = Var('x', 'Object', isImplicitlyTyped: true);
var y = Var('y', 'int?');
h.run([
declareInitialized(x, y.expr.eq(nullLiteral)),
getSsaNodes((nodes) {
expect(nodes[x]!.expressionInfo, isNotNull);
}),
]);
});
test(
'initialize() stores expressionInfo for explicitly typed '
'vars, pre-bug fix', () {
var h = Harness(respectImplicitlyTypedVarInitializers: false);
var x = Var('x', 'Object');
var y = Var('y', 'int?');
h.run([
declareInitialized(x, y.expr.eq(nullLiteral)),
getSsaNodes((nodes) {
expect(nodes[x]!.expressionInfo, isNotNull);
}),
]);
});
test('initialize() does not store expressionInfo for trivial expressions',
() {
var h = Harness();

View File

@ -2,6 +2,8 @@
// 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.
// @dart=2.13
finalLocalBool(int? x) {
final bool b = x == null;
if (!b) {

View File

@ -388,7 +388,17 @@ class Harness extends TypeOperations<Var, Type> {
late final _typeAnalyzer = _MiniAstTypeAnalyzer(this);
Harness({this.legacy = false, String? thisType})
/// Indicates whether initializers of implicitly typed variables should be
/// accounted for by SSA analysis. (In an ideal world, they always would be,
/// but due to https://github.com/dart-lang/language/issues/1785, they weren't
/// always, and we need to be able to replicate the old behavior when
/// analyzing old language versions).
final bool respectImplicitlyTypedVarInitializers;
Harness(
{this.legacy = false,
String? thisType,
this.respectImplicitlyTypedVarInitializers = true})
: thisType = thisType == null ? null : Type(thisType);
MiniIrBuilder get _irBuilder => _typeAnalyzer._irBuilder;
@ -483,7 +493,9 @@ class Harness extends TypeOperations<Var, Type> {
? FlowAnalysis<Node, Statement, Expression, Var, Type>.legacy(
this, assignedVariables)
: FlowAnalysis<Node, Statement, Expression, Var, Type>(
this, assignedVariables);
this, assignedVariables,
respectImplicitlyTypedVarInitializers:
respectImplicitlyTypedVarInitializers);
_typeAnalyzer.dispatchStatement(b);
_typeAnalyzer.finish();
}

View File

@ -13,6 +13,7 @@ import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_system.dart' show TypeSystemImpl;
import 'package:analyzer/src/generated/migration.dart';
import 'package:analyzer/src/generated/resolver.dart';
import 'package:analyzer/src/generated/variable_type_provider.dart';
/// Data gathered by flow analysis, retained for testing purposes.
@ -222,7 +223,8 @@ class FlowAnalysisHelper {
flow!.labeledStatement_end();
}
void topLevelDeclaration_enter(AstNode node, FormalParameterList? parameters,
void topLevelDeclaration_enter(
ResolverVisitor resolver, AstNode node, FormalParameterList? parameters,
{void Function(AstVisitor<Object?> visitor)? visit}) {
assert(flow == null);
assignedVariables = computeAssignedVariables(node, parameters,
@ -233,7 +235,9 @@ class FlowAnalysisHelper {
}
flow = isNonNullableByDefault
? FlowAnalysis<AstNode, Statement, Expression, PromotableElement,
DartType>(_typeOperations, assignedVariables!)
DartType>(_typeOperations, assignedVariables!,
respectImplicitlyTypedVarInitializers:
resolver.isConstructorTearoffsEnabled)
: FlowAnalysis<AstNode, Statement, Expression, PromotableElement,
DartType>.legacy(_typeOperations, assignedVariables!);
}
@ -349,9 +353,10 @@ class FlowAnalysisHelperForMigration extends FlowAnalysisHelper {
: super(typeSystem, false, isNonNullableByDefault);
@override
void topLevelDeclaration_enter(AstNode node, FormalParameterList? parameters,
void topLevelDeclaration_enter(
ResolverVisitor resolver, AstNode node, FormalParameterList? parameters,
{void Function(AstVisitor<Object?> visitor)? visit}) {
super.topLevelDeclaration_enter(node, parameters, visit: visit);
super.topLevelDeclaration_enter(resolver, node, parameters, visit: visit);
migrationResolutionHooks.setFlowAnalysis(flow);
}

View File

@ -45,7 +45,7 @@ class VariableDeclarationResolver {
InferenceContext.setTypeFromNode(initializer, node);
if (isTopLevel) {
_resolver.flowAnalysis.topLevelDeclaration_enter(node, null);
_resolver.flowAnalysis.topLevelDeclaration_enter(_resolver, node, null);
} else if (element.isLate) {
_resolver.flowAnalysis.flow?.lateInitializer_begin(node);
}

View File

@ -1185,7 +1185,7 @@ class ResolverVisitor extends ResolverBase with ErrorDetectionHelpers {
@override
void visitConstructorDeclaration(ConstructorDeclaration node) {
flowAnalysis.topLevelDeclaration_enter(node, node.parameters);
flowAnalysis.topLevelDeclaration_enter(this, node, node.parameters);
flowAnalysis.executableDeclaration_enter(node, node.parameters, false);
var returnType = node.declaredElement!.type.returnType;
@ -1409,7 +1409,7 @@ class ResolverVisitor extends ResolverBase with ErrorDetectionHelpers {
flowAnalysis.flow!.functionExpression_begin(node);
} else {
flowAnalysis.topLevelDeclaration_enter(
node, node.functionExpression.parameters);
this, node, node.functionExpression.parameters);
}
flowAnalysis.executableDeclaration_enter(
node,
@ -1638,7 +1638,7 @@ class ResolverVisitor extends ResolverBase with ErrorDetectionHelpers {
@override
void visitMethodDeclaration(MethodDeclaration node) {
flowAnalysis.topLevelDeclaration_enter(node, node.parameters);
flowAnalysis.topLevelDeclaration_enter(this, node, node.parameters);
flowAnalysis.executableDeclaration_enter(node, node.parameters, false);
DartType returnType = node.declaredElement!.returnType;

View File

@ -58,7 +58,7 @@ class AstResolver {
node.accept(_resolutionVisitor);
node.accept(_scopeResolverVisitor);
_prepareEnclosingDeclarations();
_flowAnalysis.topLevelDeclaration_enter(node, null);
_flowAnalysis.topLevelDeclaration_enter(_resolverVisitor, node, null);
node.accept(_resolverVisitor);
_flowAnalysis.topLevelDeclaration_exit();
}
@ -76,7 +76,8 @@ class AstResolver {
visit(_scopeResolverVisitor);
_prepareEnclosingDeclarations();
_flowAnalysis.topLevelDeclaration_enter(node, node.parameters,
_flowAnalysis.topLevelDeclaration_enter(
_resolverVisitor, node, node.parameters,
visit: visit);
visit(_resolverVisitor);
_flowAnalysis.topLevelDeclaration_exit();
@ -95,7 +96,8 @@ class AstResolver {
node.accept(_scopeResolverVisitor);
}
_prepareEnclosingDeclarations();
_flowAnalysis.topLevelDeclaration_enter(node.parent!, null);
_flowAnalysis.topLevelDeclaration_enter(
_resolverVisitor, node.parent!, null);
node.accept(_resolverVisitor);
_flowAnalysis.topLevelDeclaration_exit();
}

View File

@ -79,12 +79,14 @@ void main() {
}
test_ifElementWithElse_mayBeConst() async {
await assertNoErrorsInCode('''
await assertErrorsInCode('''
void main() {
const isTrue = true;
const {1: null, if (isTrue) null: null else null: null};
}
''');
''', [
error(HintCode.DEAD_CODE, 83, 12),
]);
}
test_spreadElement_mayBeConst() async {

View File

@ -224,7 +224,9 @@ class TypeInferrerImpl implements TypeInferrer {
flowAnalysis = library.isNonNullableByDefault
? new FlowAnalysis(
new TypeOperationsCfe(engine.typeSchemaEnvironment),
assignedVariables)
assignedVariables,
respectImplicitlyTypedVarInitializers:
library.enableConstructorTearOffsInLibrary)
: new FlowAnalysis.legacy(
new TypeOperationsCfe(engine.typeSchemaEnvironment),
assignedVariables) {}

View File

@ -142,6 +142,7 @@ bs
bsd
bslash
buffered
buggy
builder`s
bulk
bump
@ -559,6 +560,7 @@ i'll
i2b
ic
id
ideal
identifies
identifying
ideographic

View File

@ -377,10 +377,14 @@ class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType>
var previousAssignedVariables = _assignedVariables;
if (_flowAnalysis == null) {
_assignedVariables = AssignedVariables();
// Note: we are using flow analysis to help us track true nullabilities;
// it's not necessary to replicate old bugs. So we pass `true` for
// `respectImplicitlyTypedVarInitializers`.
_flowAnalysis = FlowAnalysis<AstNode, Statement, Expression,
PromotableElement, DecoratedType>(
DecoratedTypeOperations(_typeSystem, _variables, _graph),
_assignedVariables!);
_assignedVariables!,
respectImplicitlyTypedVarInitializers: true);
}
try {
_dispatch(node.name);
@ -2110,10 +2114,14 @@ class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType>
assert(_assignedVariables == null);
_assignedVariables =
FlowAnalysisHelper.computeAssignedVariables(node, parameters);
// Note: we are using flow analysis to help us track true nullabilities;
// it's not necessary to replicate old bugs. So we pass `true` for
// `respectImplicitlyTypedVarInitializers`.
_flowAnalysis = FlowAnalysis<AstNode, Statement, Expression,
PromotableElement, DecoratedType>(
DecoratedTypeOperations(_typeSystem, _variables, _graph),
_assignedVariables!);
_assignedVariables!,
respectImplicitlyTypedVarInitializers: true);
if (parameters != null) {
for (var parameter in parameters.parameters) {
_flowAnalysis!.declare(parameter.declaredElement!, true);

View File

@ -315,9 +315,15 @@ class AnnotateKernel extends RecursiveVisitor {
Constant? constantValue;
bool isInt = false;
final nullable = type is NullableType;
// Note: the explicit type `bool` is needed because the checked-in version
// of the CFE that we use for bootstrapping doesn't yet have constructor
// tearoffs enabled, and the fix for bug
// https://github.com/dart-lang/language/issues/1785 only takes effect when
// constructor tearoffs are enabled. TODO(paulberry): remove the type after
// the bootstrap CFE enables constructor tearoffs.
final bool nullable = type is NullableType;
if (nullable) {
type = (type as NullableType).baseType;
type = type.baseType;
}
if (nullable && type == const EmptyType()) {

View File

@ -0,0 +1,202 @@
// 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.
// SharedOptions=--enable-experiment=constructor-tearoffs
import '../../static_type_helper.dart';
// This test checks whether a local boolean variable can be used to perform type
// promotion, if that variable is implicitly typed.
//
// This test confirms that once the "constructor tearoffs" language feature is
// enabled, initializer expressions on implicitly typed variables are no longer
// ignored for the purposes of type promotion
// (i.e. https://github.com/dart-lang/language/issues/1785 is fixed).
parameterUnmodified(int? x) {
{
late final b = x != null;
// We don't promote based on the initializers of late locals anyhow, because
// we don't know when they execute.
if (b) x.expectStaticType<Exactly<int?>>();
}
{
late final b;
b = x != null;
if (b) x.expectStaticType<Exactly<int>>();
}
{
late var b = x != null;
// We don't promote based on the initializers of late locals anyhow, because
// we don't know when they execute.
if (b) x.expectStaticType<Exactly<int?>>();
}
{
late var b;
b = x != null;
if (b) x.expectStaticType<Exactly<int>>();
}
{
final b = x != null;
if (b) x.expectStaticType<Exactly<int>>();
}
{
final b;
b = x != null;
if (b) x.expectStaticType<Exactly<int>>();
}
{
var b = x != null;
if (b) x.expectStaticType<Exactly<int>>();
}
{
var b;
b = x != null;
if (b) x.expectStaticType<Exactly<int>>();
}
}
parameterModifiedLater(int? x, int? y) {
x = y;
{
late final b = x != null;
// We don't promote based on the initializers of late locals anyhow, because
// we don't know when they execute.
if (b) x.expectStaticType<Exactly<int?>>();
}
{
late final b;
b = x != null;
if (b) x.expectStaticType<Exactly<int>>();
}
{
late var b = x != null;
// We don't promote based on the initializers of late locals anyhow, because
// we don't know when they execute.
if (b) x.expectStaticType<Exactly<int?>>();
}
{
late var b;
b = x != null;
if (b) x.expectStaticType<Exactly<int>>();
}
{
final b = x != null;
if (b) x.expectStaticType<Exactly<int>>();
}
{
final b;
b = x != null;
if (b) x.expectStaticType<Exactly<int>>();
}
{
var b = x != null;
if (b) x.expectStaticType<Exactly<int>>();
}
{
var b;
b = x != null;
if (b) x.expectStaticType<Exactly<int>>();
}
}
localVariableInitialized(int? y) {
int? x = y;
{
late final b = x != null;
// We don't promote based on the initializers of late locals anyhow, because
// we don't know when they execute.
if (b) x.expectStaticType<Exactly<int?>>();
}
{
late final b;
b = x != null;
if (b) x.expectStaticType<Exactly<int>>();
}
{
late var b = x != null;
// We don't promote based on the initializers of late locals anyhow, because
// we don't know when they execute.
if (b) x.expectStaticType<Exactly<int?>>();
}
{
late var b;
b = x != null;
if (b) x.expectStaticType<Exactly<int>>();
}
{
final b = x != null;
if (b) x.expectStaticType<Exactly<int>>();
}
{
final b;
b = x != null;
if (b) x.expectStaticType<Exactly<int>>();
}
{
var b = x != null;
if (b) x.expectStaticType<Exactly<int>>();
}
{
var b;
b = x != null;
if (b) x.expectStaticType<Exactly<int>>();
}
}
localVariableModifiedLater(int? y) {
int? x;
x = y;
{
late final b = x != null;
// We don't promote based on the initializers of late locals anyhow, because
// we don't know when they execute.
if (b) x.expectStaticType<Exactly<int?>>();
}
{
late final b;
b = x != null;
if (b) x.expectStaticType<Exactly<int>>();
}
{
late var b = x != null;
// We don't promote based on the initializers of late locals anyhow, because
// we don't know when they execute.
if (b) x.expectStaticType<Exactly<int?>>();
}
{
late var b;
b = x != null;
if (b) x.expectStaticType<Exactly<int>>();
}
{
final b = x != null;
if (b) x.expectStaticType<Exactly<int>>();
}
{
final b;
b = x != null;
if (b) x.expectStaticType<Exactly<int>>();
}
{
var b = x != null;
if (b) x.expectStaticType<Exactly<int>>();
}
{
var b;
b = x != null;
if (b) x.expectStaticType<Exactly<int>>();
}
}
main() {
parameterUnmodified(null);
parameterUnmodified(0);
parameterModifiedLater(null, null);
parameterModifiedLater(null, 0);
localVariableInitialized(null);
localVariableInitialized(0);
localVariableModifiedLater(null);
localVariableModifiedLater(0);
}