mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 03:36:59 +00:00
Implement promotion via initialization in flow analysis.
Change-Id: I10a5f6bfeb5a05dd144db5212c9f31796f5a2385 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/145502 Reviewed-by: Johnni Winther <johnniwinther@google.com> Reviewed-by: Paul Berry <paulberry@google.com> Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
parent
a144fedbba
commit
cf3a034210
|
@ -1482,9 +1482,8 @@ class FlowModel<Variable, Type> {
|
|||
VariableModel<Variable, Type> infoForVar = variableInfo[variable];
|
||||
if (infoForVar == null) return this;
|
||||
|
||||
Type declaredType = typeOperations.variableType(variable);
|
||||
VariableModel<Variable, Type> 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<Variable, Type> {
|
|||
/// 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<Variable, Type> {
|
|||
|
||||
/// Returns a new [VariableModel] reflecting the fact that the variable was
|
||||
/// just written to.
|
||||
VariableModel<Variable, Type> write(Type declaredType, Type writtenType,
|
||||
VariableModel<Variable, Type> write(Variable variable, Type writtenType,
|
||||
TypeOperations<Variable, Type> typeOperations) {
|
||||
if (writeCaptured) {
|
||||
return new VariableModel<Variable, Type>(
|
||||
promotedTypes, tested, true, false, writeCaptured);
|
||||
}
|
||||
|
||||
if (_isPromotableViaInitialization(typeOperations, variable)) {
|
||||
return new VariableModel<Variable, Type>(
|
||||
[writtenType], tested, true, false, writeCaptured);
|
||||
}
|
||||
|
||||
List<Type> newPromotedTypes;
|
||||
if (promotedTypes == null) {
|
||||
newPromotedTypes = null;
|
||||
|
@ -1863,15 +1872,19 @@ class VariableModel<Variable, Type> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Type declaredType = typeOperations.variableType(variable);
|
||||
newPromotedTypes = _tryPromoteToTypeOfInterest(
|
||||
typeOperations, declaredType, newPromotedTypes, writtenType);
|
||||
if (identical(promotedTypes, newPromotedTypes) && assigned) return this;
|
||||
|
||||
List<Type> newTested;
|
||||
if (newPromotedTypes == null && promotedTypes != null) {
|
||||
newTested = const [];
|
||||
} else {
|
||||
newTested = tested;
|
||||
}
|
||||
|
||||
return new VariableModel<Variable, Type>(
|
||||
newPromotedTypes, newTested, true, false, writeCaptured);
|
||||
}
|
||||
|
@ -1883,6 +1896,26 @@ class VariableModel<Variable, Type> {
|
|||
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<Variable>(
|
||||
TypeOperations<Variable, Type> 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]
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue