[vm/aot] Keep static type of a conditional expression when removing it in tree shaker

In order to make sure static types in kernel AST are still correct
after tree shaking, tree shaker needs to insert no-op unsafeCast calls
when removing conditional expressions, as ConditionalExpression nodes
have explicit 'staticType' which may be different from static types of
sub-expressions.

Otherwise we might end up with an untyped function call
(FunctionInvocation with FunctionAccessKind.Function)
performed from a receiver with a known function type.
That would violate VM's assumptions about static type of receiver
being checked by the front-end and trigger assertion in
pkg/vm/lib/transformations/call_site_annotator.dart.

TEST=runtime/tests/vm/dart/untyped_function_invocation_with_known_function_type_test.dart
TEST=pkg/vm/testcases/transformations/type_flow/transformer/null_test_elimination2_nnbd_strong.dart

Change-Id: I6bf201a979d1b71eb48ed04f154adf2b62dac922
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/226741
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Alexander Markov
2022-01-07 14:22:26 +00:00
committed by Commit Bot
parent d095700f13
commit a09c585772
4 changed files with 78 additions and 2 deletions
@@ -1583,12 +1583,22 @@ class _TreeShakerPass1 extends RemovingTransformer {
if (_isExtendedBoolLiteral(condition)) {
final bool value = _getExtendedBoolLiteralValue(condition);
final Expression expr = transform(value ? node.then : node.otherwise);
Expression result;
if (condition is BlockExpression) {
condition.value = expr;
expr.parent = condition;
return condition;
result = condition;
} else {
return expr;
result = expr;
}
if (node.staticType != result.getStaticType(staticTypeContext)) {
return StaticInvocation(
unsafeCast,
Arguments([result],
types: [visitDartType(node.staticType, cannotRemoveSentinel)]))
..fileOffset = node.fileOffset;
} else {
return result;
}
}
node.condition = condition..parent = node;
@@ -0,0 +1,20 @@
// Copyright (c) 2022, 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.
// Tests elimination of null test in a conditional expression of
// a different static type.
bool _defaultCheck([dynamic _]) => true;
void testStaticTypeOfConditional<T>(bool Function(T error)? check, Object e) {
// Verify that null test elimination leaves unsafeCast here to
// keep static type of 'check ?? _defaultCheck' expression.
if (e is T && (check ?? _defaultCheck)(e)) {
print('ok');
}
}
void main() {
testStaticTypeOfConditional<String>((_) => true, 'hi');
}
@@ -0,0 +1,19 @@
library #lib /*isNonNullableByDefault*/;
import self as self;
import "dart:core" as core;
import "dart:_internal" as _in;
static method _defaultCheck([dynamic _ = #C1]) → core::bool
return true;
static method testStaticTypeOfConditional<T extends core::Object? = dynamic>([@vm.inferred-type.metadata=!] (self::testStaticTypeOfConditional::T%) →? core::bool check) → void {
if(#C2 is{ForNonNullableByDefault} self::testStaticTypeOfConditional::T% && (let final (self::testStaticTypeOfConditional::T%) →? core::bool #t1 = check in _in::unsafeCast<core::Function>(#t1{(self::testStaticTypeOfConditional::T%) → core::bool}))(#C2) as{TypeError,ForDynamic,ForNonNullableByDefault} core::bool) {
core::print("ok");
}
}
static method main() → void {
self::testStaticTypeOfConditional<core::String>((core::String _) → core::bool => true);
}
constants {
#C1 = null
#C2 = "hi"
}
@@ -0,0 +1,27 @@
// Copyright (c) 2022, 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.
// Verifies that compiler doesn't crash when it sees untyped function
// invocation (FunctionAccessKind.Function) but receiver has a known
// function type.
import "package:expect/expect.dart";
bool ok = false;
bool _defaultCheck([dynamic _]) => true;
void foo<T>(bool Function(T error)? check, Object e) {
// Function call on the result of 'check ?? _defaultCheck' is untyped
// (assumes static type Function). However, AOT compiler can eliminate
// null test and might be able to reduce the expression to 'check' with
// known function type.
if (e is T && (check ?? _defaultCheck)(e)) {
ok = true;
}
}
void main() {
foo<String>((_) => true, 'hi');
Expect.isTrue(ok);
}