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 6b22ccbc0ae..890d581e74d 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 @@ -1482,9 +1482,8 @@ class FlowModel { VariableModel infoForVar = variableInfo[variable]; if (infoForVar == null) return this; - Type declaredType = typeOperations.variableType(variable); VariableModel newInfoForVar = - infoForVar.write(declaredType, writtenType, typeOperations); + infoForVar.write(variable, writtenType, typeOperations); if (identical(newInfoForVar, infoForVar)) return this; return _updateVariableInfo(variable, newInfoForVar); @@ -1655,6 +1654,11 @@ abstract class TypeOperations { /// consideration by an instance check. Type factor(Type from, Type what); + /// Return `true` if the [variable] is a local variable (not a formal + /// parameter), and it has no declared type (no explicit type, and not + /// initializer). + bool isLocalVariableWithoutDeclaredType(Variable variable); + /// Returns `true` if [type1] and [type2] are the same type. bool isSameType(Type type1, Type type2); @@ -1836,13 +1840,18 @@ class VariableModel { /// Returns a new [VariableModel] reflecting the fact that the variable was /// just written to. - VariableModel write(Type declaredType, Type writtenType, + VariableModel write(Variable variable, Type writtenType, TypeOperations typeOperations) { if (writeCaptured) { return new VariableModel( promotedTypes, tested, true, false, writeCaptured); } + if (_isPromotableViaInitialization(typeOperations, variable)) { + return new VariableModel( + [writtenType], tested, true, false, writeCaptured); + } + List newPromotedTypes; if (promotedTypes == null) { newPromotedTypes = null; @@ -1863,15 +1872,19 @@ class VariableModel { } } } + + Type declaredType = typeOperations.variableType(variable); newPromotedTypes = _tryPromoteToTypeOfInterest( typeOperations, declaredType, newPromotedTypes, writtenType); if (identical(promotedTypes, newPromotedTypes) && assigned) return this; + List newTested; if (newPromotedTypes == null && promotedTypes != null) { newTested = const []; } else { newTested = tested; } + return new VariableModel( newPromotedTypes, newTested, true, false, writeCaptured); } @@ -1883,6 +1896,26 @@ class VariableModel { null, const [], assigned, false, true); } + /// We say that a variable `x` is promotable via initialization given + /// variable model `VM` if `x` is a local variable (not a formal parameter) + /// and: + /// * VM = VariableModel(declared, promoted, tested, + /// assigned, unassigned, captured) + /// * and `captured` is false + /// * and `promoted` is empty + /// * and `x` is declared with no explicit type and no initializer + /// * and `assigned` is false and `unassigned` is true + bool _isPromotableViaInitialization( + TypeOperations typeOperations, + Variable variable, + ) { + return !writeCaptured && + !assigned && + unassigned && + promotedTypes == null && + typeOperations.isLocalVariableWithoutDeclaredType(variable); + } + /// Determines whether a variable with the given [promotedTypes] should be /// promoted to [writtenType] based on types of interest. If it should, /// returns an updated promotion chain; otherwise returns [promotedTypes] 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 89bd5fb4fe2..67bd777a852 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 @@ -2266,6 +2266,21 @@ main() { }); }); }); + + test('promote via initialization', () { + var h = _Harness(); + var x = _Var('x', null, isLocalVariableWithoutDeclaredType: true); + + var s1 = FlowModel<_Var, _Type>(true).declare(x, false); + expect(s1.variableInfo, { + x: _matchVariableModel(chain: null), + }); + + var s2 = s1.write(x, _Type('int'), h); + expect(s2.variableInfo, { + x: _matchVariableModel(chain: ['int']), + }); + }); }); group('declare', () { @@ -2832,9 +2847,14 @@ main() { const emptyMap = const <_Var, VariableModel<_Var, _Type>>{}; VariableModel<_Var, _Type> model(List<_Type> promotionChain, - [List<_Type> typesOfInterest]) => - VariableModel<_Var, _Type>(promotionChain, - typesOfInterest ?? promotionChain ?? [], false, true, false); + {List<_Type> typesOfInterest, bool assigned = false}) => + VariableModel<_Var, _Type>( + promotionChain, + typesOfInterest ?? promotionChain ?? [], + assigned, + !assigned, + false, + ); group('without input reuse', () { test('promoted with unpromoted', () { @@ -2963,18 +2983,18 @@ main() { test('assigned', () { var h = _Harness(); - var intQModel = model([intQType]); - var writtenModel = intQModel.write(intQType, _Type('Object?'), h); - var p1 = {x: writtenModel, y: writtenModel, z: intQModel, w: intQModel}; - var p2 = {x: writtenModel, y: intQModel, z: writtenModel, w: intQModel}; + var unassigned = model(null, assigned: false); + var assigned = model(null, assigned: true); + var p1 = {x: assigned, y: assigned, z: unassigned, w: unassigned}; + var p2 = {x: assigned, y: unassigned, z: assigned, w: unassigned}; var joined = FlowModel.joinVariableInfo(h, p1, p2, emptyMap); expect(joined, { - x: same(writtenModel), + x: same(assigned), y: _matchVariableModel( chain: null, assigned: false, unassigned: false), z: _matchVariableModel( chain: null, assigned: false, unassigned: false), - w: same(intQModel) + w: same(unassigned) }); }); @@ -3322,6 +3342,11 @@ class _Harness implements TypeOperations<_Var, _Type> { if_(isNotType(variableRead(variable), type), ifTrue); } + @override + bool isLocalVariableWithoutDeclaredType(_Var variable) { + return variable.isLocalVariableWithoutDeclaredType; + } + /// Creates a [LazyExpression] representing an `is!` check, checking whether /// [subExpression] has the given [type]. LazyExpression isNotType(LazyExpression subExpression, String type) { @@ -3475,10 +3500,14 @@ class _Type { class _Var { final String name; - final _Type type; + final bool isLocalVariableWithoutDeclaredType; - _Var(this.name, this.type); + _Var( + this.name, + this.type, { + this.isLocalVariableWithoutDeclaredType = false, + }); @override String toString() => '$type $name'; diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/declaration.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/declaration.dart new file mode 100644 index 00000000000..099a022a4c8 --- /dev/null +++ b/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/declaration.dart @@ -0,0 +1,62 @@ +// 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. + +// The tests in this file exercise various kinds of constructs that initialize a +// variable at its declaration site. We verify that the variable is not +// considered "written to" for purposes of defeating type promotions inside +// closures. + +parameter(Object a) { + if (a is int) { + () { + /*int*/ a; + }; + } +} + +localParameter() { + localFunction(Object a) { + if (a is int) { + () { + /*int*/ a; + }; + } + } + + (Object b) { + if (b is int) { + () { + /*int*/ b; + }; + } + }; +} + +localVariable() { + Object a = 1; + if (a is int) { + () { + /*int*/ a; + }; + } +} + +class MyStackTrace implements StackTrace { + noSuchMethod(invocation) => super.noSuchMethod(invocation); +} + +catchParameters() { + try {} on Object catch (e, st) { + if (e is int) { + () { + /*int*/ e; + }; + } + if (st is MyStackTrace) { + () { + /*MyStackTrace*/ st; + }; + } + } +} diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/initialization.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/initialization.dart index 099a022a4c8..6425744adf6 100644 --- a/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/initialization.dart +++ b/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/initialization.dart @@ -1,62 +1,95 @@ -// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file +// 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. -// The tests in this file exercise various kinds of constructs that initialize a -// variable at its declaration site. We verify that the variable is not -// considered "written to" for purposes of defeating type promotions inside -// closures. - -parameter(Object a) { - if (a is int) { - () { - /*int*/ a; - }; - } -} - -localParameter() { - localFunction(Object a) { - if (a is int) { - () { - /*int*/ a; - }; - } - } - - (Object b) { - if (b is int) { - () { - /*int*/ b; - }; - } - }; -} +// The tests in this file exercise "promotable via initialization" part of +// the flow analysis specification. localVariable() { - Object a = 1; - if (a is int) { - () { - /*int*/ a; - }; - } + var x; + x = 1; + /*int*/ x; + x = 2.3; + x; } -class MyStackTrace implements StackTrace { - noSuchMethod(invocation) => super.noSuchMethod(invocation); +localVariable_hasInitializer(num a) { + var x = a; + x = 1; + x; } -catchParameters() { - try {} on Object catch (e, st) { - if (e is int) { - () { - /*int*/ e; - }; - } - if (st is MyStackTrace) { - () { - /*MyStackTrace*/ st; - }; - } - } +localVariable_hasTypeAnnotation() { + num x; + x = 1; + x; +} + +localVariable_hasTypeAnnotation_dynamic() { + dynamic x; + x = 1; + x; +} + +localVariable_ifElse_differentTypes(bool a) { + var x; + if (a) { + x = 0; + /*int*/ x; + } else { + x = 1.2; + /*double*/ x; + } + x; +} + +localVariable_ifElse_sameTypes(bool a) { + var x; + if (a) { + x = 0; + /*int*/ x; + } else { + x = 1; + /*int*/ x; + } + /*int*/ x; +} + +localVariable_notDefinitelyUnassigned(bool a) { + var x; + if (a) { + x = 1.2; + } + x = 1; + x; +} + +localVariable_notDefinitelyUnassigned_hasLocalFunction() { + var x; + + void f() { + // Note, no assignment to 'x', but because 'x' is assigned somewhere in + // the enclosing function, it is not definitely unassigned in 'f'. + // So, when we join after 'f' declaration, we make 'x' not definitely + // unassigned in the enclosing function as well. + } + + f(); + + x = 1; + x; +} + +parameter(x) { + x = 1; + x; +} + +parameterLocal() { + void f(x) { + x = 1; + x; + } + + f(0); } diff --git a/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart b/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart index 19c607e1ccc..fad0ca3a65d 100644 --- a/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart +++ b/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart @@ -355,6 +355,13 @@ class TypeSystemTypeOperations return typeSystem.factor(from, what); } + @override + bool isLocalVariableWithoutDeclaredType(PromotableElement variable) { + return variable is LocalVariableElement && + variable.hasImplicitType && + variable.initializer == null; + } + @override bool isSameType(covariant TypeImpl type1, covariant TypeImpl type2) { return type1 == type2; diff --git a/pkg/front_end/lib/src/fasta/type_inference/type_inference_engine.dart b/pkg/front_end/lib/src/fasta/type_inference/type_inference_engine.dart index 018b07700a1..bbaff281f2b 100644 --- a/pkg/front_end/lib/src/fasta/type_inference/type_inference_engine.dart +++ b/pkg/front_end/lib/src/fasta/type_inference/type_inference_engine.dart @@ -16,6 +16,7 @@ import 'package:kernel/ast.dart' NamedType, NeverType, Nullability, + Statement, TreeNode, TypeParameter, TypeParameterType, @@ -35,6 +36,8 @@ import '../builder/constructor_builder.dart'; import '../kernel/forest.dart'; +import '../kernel/internal_ast.dart' show VariableDeclarationImpl; + import '../kernel/kernel_builder.dart' show ClassHierarchyBuilder, ImplicitFieldType; @@ -260,6 +263,14 @@ class TypeOperationsCfe return factorType(typeEnvironment, from, what); } + @override + bool isLocalVariableWithoutDeclaredType(VariableDeclaration variable) { + return variable is VariableDeclarationImpl && + variable.parent is Statement && + variable.isImplicitlyTyped && + !variable.hasDeclaredInitializer; + } + // TODO(dmitryas): Consider checking for mutual subtypes instead of ==. @override bool isSameType(DartType type1, DartType type2) => type1 == type2; diff --git a/pkg/nnbd_migration/lib/src/decorated_type_operations.dart b/pkg/nnbd_migration/lib/src/decorated_type_operations.dart index 323d5794a78..5de23f2d8ed 100644 --- a/pkg/nnbd_migration/lib/src/decorated_type_operations.dart +++ b/pkg/nnbd_migration/lib/src/decorated_type_operations.dart @@ -25,6 +25,13 @@ class DecoratedTypeOperations return from; } + @override + bool isLocalVariableWithoutDeclaredType(PromotableElement variable) { + return variable is LocalVariableElement && + variable.hasImplicitType && + variable.initializer == null; + } + @override bool isSameType(DecoratedType type1, DecoratedType type2) { return type1 == type2; diff --git a/sdk_nnbd/lib/html/dart2js/html_dart2js.dart b/sdk_nnbd/lib/html/dart2js/html_dart2js.dart index 7330bc630f4..64f31f0c89b 100644 --- a/sdk_nnbd/lib/html/dart2js/html_dart2js.dart +++ b/sdk_nnbd/lib/html/dart2js/html_dart2js.dart @@ -40391,7 +40391,7 @@ class KeyEvent extends _WrappedEvent implements KeyboardEvent { view = window; } - var eventObj; + dynamic eventObj; // Currently this works on everything but Safari. Safari throws an // "Attempting to change access mechanism for an unconfigurable property" diff --git a/tests/compiler/dart2js/analyses/api_allowed_nnbd.json b/tests/compiler/dart2js/analyses/api_allowed_nnbd.json index 6d84ab9549a..e32d5da0ab9 100644 --- a/tests/compiler/dart2js/analyses/api_allowed_nnbd.json +++ b/tests/compiler/dart2js/analyses/api_allowed_nnbd.json @@ -55,7 +55,6 @@ }, "org-dartlang-sdk:///sdk_nnbd/lib/_internal/js_runtime/lib/native_typed_data.dart": { "Dynamic invocation of '>'.": 1, - "Dynamic invocation of '|'.": 3, "Dynamic invocation of '>='.": 1 }, "org-dartlang-sdk:///sdk_nnbd/lib/_internal/js_runtime/lib/async_patch.dart": { @@ -82,7 +81,6 @@ "Dynamic invocation of 'remove'.": 2, "Dynamic update to 'dart.dom.html::_innerHtml'.": 1, "Dynamic access of 'firstChild'.": 2, - "Dynamic invocation of 'append'.": 1, "Dynamic access of 'tagName'.": 2, "Dynamic invocation of 'call'.": 2, "Dynamic invocation of 'dart.dom.html::_initKeyboardEvent'.": 1, @@ -100,11 +98,6 @@ "org-dartlang-sdk:///sdk_nnbd/lib/html/html_common/filtered_element_list.dart": { "Dynamic invocation of 'remove'.": 1 }, - "org-dartlang-sdk:///sdk_nnbd/lib/indexed_db/dart2js/indexed_db_dart2js.dart": { - "Dynamic access of 'onUpgradeNeeded'.": 1, - "Dynamic invocation of 'listen'.": 2, - "Dynamic access of 'onBlocked'.": 1 - }, "org-dartlang-sdk:///sdk_nnbd/lib/io/directory_impl.dart": { "Dynamic invocation of '[]'.": 10 },