Skip to content

Commit 8f94144

Browse files
osa1Commit Queue
authored andcommitted
[tfa] Propagate int and string constants in conditionals
When comparing a value against a literal or constant: if (x == literal) { ... } if (x == constant) { ... } Allow propagating the value if the LHS type is `int` or `String`, as those types don't have identity. Previously we would propagate the values if the RHS is a literal (not constant). Constants would only be propagated if the LHS type does not override `operator ==`. Because `int` and `String` don't have identity, they can be propagated when they're constants even though they override `operator ==`. Tested: Added new TFA transformer test. Change-Id: I3c150ffae39bfbc06992f666ffec7bab1d1c52d7 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/403987 Commit-Queue: Ömer Ağacan <[email protected]> Reviewed-by: Martin Kustermann <[email protected]> Reviewed-by: Alexander Markov <[email protected]>
1 parent 655cdcb commit 8f94144

File tree

3 files changed

+250
-2
lines changed

3 files changed

+250
-2
lines changed

pkg/vm/lib/transformations/type_flow/summary_collector.dart

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1578,10 +1578,20 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
15781578
} else if (node is EqualsCall && node.left is VariableGet) {
15791579
final lhs = node.left as VariableGet;
15801580
final rhs = node.right;
1581-
if ((rhs is IntLiteral &&
1581+
bool isIntConstant(Expression expr) => switch (expr) {
1582+
IntLiteral() => true,
1583+
ConstantExpression(constant: IntConstant()) => true,
1584+
_ => false
1585+
};
1586+
bool isStringConstant(Expression expr) => switch (expr) {
1587+
StringLiteral() => true,
1588+
ConstantExpression(constant: StringConstant()) => true,
1589+
_ => false
1590+
};
1591+
if ((isIntConstant(rhs) &&
15821592
_isSubtype(lhs.variable.type,
15831593
_environment.coreTypes.intNullableRawType)) ||
1584-
(rhs is StringLiteral &&
1594+
(isStringConstant(rhs) &&
15851595
target.canInferStringClassAfterEqualityComparison &&
15861596
_isSubtype(lhs.variable.type,
15871597
_environment.coreTypes.stringNullableRawType)) ||
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// Tests constant propagation inside considitonals.
6+
7+
void testStringLiteral(String s) {
8+
print(s);
9+
}
10+
11+
void testStringConstant(String s) {
12+
print(s);
13+
}
14+
15+
void testIntLiteral(int i) {
16+
print(i);
17+
}
18+
19+
void testIntConstant(int i) {
20+
print(i);
21+
}
22+
23+
void testDefaultEq1(DefaultEq c) {
24+
print(c);
25+
}
26+
27+
void testDefaultEq2(DefaultEq c) {
28+
print(c);
29+
}
30+
31+
void testOverriddenEq1(OverriddenEq c) {
32+
print(c);
33+
}
34+
35+
void testOverriddenEq2(OverriddenEq c) {
36+
print(c);
37+
}
38+
39+
bool get runtimeTrue => int.parse('1') == 1;
40+
41+
class DefaultEq {
42+
final int i;
43+
44+
const DefaultEq(this.i);
45+
46+
@override
47+
String toString() => 'DefaultEq(i=$i)';
48+
}
49+
50+
class OverriddenEq {
51+
final int i;
52+
53+
const OverriddenEq(this.i);
54+
55+
@override
56+
bool operator ==(Object other) {
57+
return true;
58+
}
59+
60+
@override
61+
String toString() => 'OverriddenEq(i=$i)';
62+
}
63+
64+
void main() {
65+
final s1 = runtimeTrue ? "foo" : "bar";
66+
if (s1 == "foo") {
67+
testStringLiteral(s1);
68+
}
69+
70+
final s2 = runtimeTrue ? "1234" : "asdf";
71+
const sConst = "1234";
72+
if (s2 == sConst) {
73+
testStringConstant(s2);
74+
}
75+
76+
final i1 = runtimeTrue ? 123 : 456;
77+
if (i1 == 123) {
78+
testIntLiteral(i1);
79+
}
80+
81+
final i2 = runtimeTrue ? 456 : 789;
82+
const iConst = 456;
83+
if (i2 == iConst) {
84+
testIntConstant(i2);
85+
}
86+
87+
final c1 = runtimeTrue ? DefaultEq(1) : DefaultEq(2);
88+
if (c1 == const DefaultEq(1)) {
89+
testDefaultEq1(c1); // should propagate
90+
}
91+
92+
if (c1 == sConst) {
93+
testDefaultEq2(c1); // OK to propagate as the branch won't be taken
94+
}
95+
96+
final c2 = runtimeTrue ? OverriddenEq(1) : OverriddenEq(2);
97+
if (c2 == const OverriddenEq(1)) {
98+
testOverriddenEq1(c2); // should not propagate
99+
}
100+
101+
if (c2 == sConst) {
102+
testOverriddenEq2(c2); // should not propagate
103+
}
104+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
library #lib;
2+
import self as self;
3+
import "dart:core" as core;
4+
5+
class DefaultEq extends core::Object /*hasConstConstructor*/ {
6+
7+
[@vm.inferred-type.metadata=dart.core::_Smi]
8+
[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:1]
9+
[@vm.unboxing-info.metadata=()->i]
10+
final field core::int i;
11+
12+
[@vm.unboxing-info.metadata=(i)->b]
13+
const constructor •([@vm.inferred-arg-type.metadata=dart.core::_Smi] core::int i) → self::DefaultEq
14+
: self::DefaultEq::i = i, super core::Object::•()
15+
;
16+
17+
[@vm.inferred-return-type.metadata=!]
18+
[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:2,getterSelectorId:3]
19+
[@vm.unboxing-info.metadata=[!regcc]]
20+
method toString() → core::String
21+
return "DefaultEq(i=${[@vm.direct-call.metadata=#lib::DefaultEq.i] this.{self::DefaultEq::i}{core::int}})";
22+
}
23+
class OverriddenEq extends core::Object /*hasConstConstructor*/ {
24+
25+
[@vm.inferred-type.metadata=dart.core::_Smi]
26+
[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:4]
27+
[@vm.unboxing-info.metadata=()->i]
28+
final field core::int i;
29+
30+
[@vm.unboxing-info.metadata=(i)->b]
31+
const constructor •([@vm.inferred-arg-type.metadata=dart.core::_Smi] core::int i) → self::OverriddenEq
32+
: self::OverriddenEq::i = i, super core::Object::•()
33+
;
34+
35+
[@vm.inferred-return-type.metadata=dart.core::bool]
36+
[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:5]
37+
[@vm.unboxing-info.metadata=[!regcc]]
38+
operator ==(core::Object other) → core::bool {
39+
return true;
40+
}
41+
42+
[@vm.inferred-return-type.metadata=!]
43+
[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:2,getterSelectorId:3]
44+
[@vm.unboxing-info.metadata=[!regcc]]
45+
method toString() → core::String
46+
return "OverriddenEq(i=${[@vm.direct-call.metadata=#lib::OverriddenEq.i] this.{self::OverriddenEq::i}{core::int}})";
47+
}
48+
49+
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
50+
static method testStringLiteral() → void {
51+
core::print(#C1);
52+
}
53+
54+
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
55+
static method testStringConstant() → void {
56+
core::print(#C2);
57+
}
58+
59+
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
60+
static method testIntLiteral() → void {
61+
core::print(#C3);
62+
}
63+
64+
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
65+
static method testIntConstant() → void {
66+
core::print(#C4);
67+
}
68+
69+
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
70+
static method testDefaultEq1() → void {
71+
core::print(#C6);
72+
}
73+
74+
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
75+
static method testDefaultEq2() → void {
76+
throw "Attempt to execute code removed by Dart AOT compiler (TFA)";
77+
}
78+
79+
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
80+
static method testOverriddenEq1([@vm.inferred-arg-type.metadata=#lib::OverriddenEq] self::OverriddenEq c) → void {
81+
core::print(c);
82+
}
83+
84+
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
85+
static method testOverriddenEq2([@vm.inferred-arg-type.metadata=#lib::OverriddenEq] self::OverriddenEq c) → void {
86+
core::print(c);
87+
}
88+
89+
[@vm.inferred-return-type.metadata=dart.core::bool]
90+
static get runtimeTrue() → core::bool
91+
return [@vm.direct-call.metadata=dart.core::_IntegerImplementation.==] [@vm.inferred-type.metadata=dart.core::bool (skip check)] [@vm.inferred-type.metadata=int] core::int::parse("1") =={core::num::==}{(core::Object) → core::bool} 1;
92+
93+
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
94+
static method main() → void {
95+
final core::String s1 = [@vm.inferred-type.metadata=dart.core::bool] self::runtimeTrue ?{core::String} "foo" : "bar";
96+
if([@vm.inferred-type.metadata=dart.core::bool (receiver not int)] s1 =={core::String::==}{(core::Object) → core::bool} "foo") {
97+
self::testStringLiteral();
98+
}
99+
final core::String s2 = [@vm.inferred-type.metadata=dart.core::bool] self::runtimeTrue ?{core::String} "1234" : "asdf";
100+
if([@vm.inferred-type.metadata=dart.core::bool (receiver not int)] s2 =={core::String::==}{(core::Object) → core::bool} #C2) {
101+
self::testStringConstant();
102+
}
103+
final core::int i1 = [@vm.inferred-type.metadata=dart.core::bool] self::runtimeTrue ?{core::int} 123 : 456;
104+
if([@vm.direct-call.metadata=dart.core::_IntegerImplementation.==] [@vm.inferred-type.metadata=dart.core::bool (skip check)] i1 =={core::num::==}{(core::Object) → core::bool} 123) {
105+
self::testIntLiteral();
106+
}
107+
final core::int i2 = [@vm.inferred-type.metadata=dart.core::bool] self::runtimeTrue ?{core::int} 456 : 789;
108+
if([@vm.direct-call.metadata=dart.core::_IntegerImplementation.==] [@vm.inferred-type.metadata=dart.core::bool (skip check)] i2 =={core::num::==}{(core::Object) → core::bool} #C4) {
109+
self::testIntConstant();
110+
}
111+
final self::DefaultEq c1 = [@vm.inferred-type.metadata=dart.core::bool] self::runtimeTrue ?{self::DefaultEq} new self::DefaultEq::•(1) : new self::DefaultEq::•(2);
112+
if([@vm.direct-call.metadata=dart.core::Object.==] [@vm.inferred-type.metadata=dart.core::bool (skip check) (receiver not int)] c1 =={core::Object::==}{(core::Object) → core::bool} #C6) {
113+
self::testDefaultEq1();
114+
}
115+
if([@vm.direct-call.metadata=dart.core::Object.==] [@vm.inferred-type.metadata=dart.core::bool (skip check) (receiver not int)] c1 =={core::Object::==}{(core::Object) → core::bool} #C2) {
116+
self::testDefaultEq2();
117+
}
118+
final self::OverriddenEq c2 = [@vm.inferred-type.metadata=dart.core::bool] self::runtimeTrue ?{self::OverriddenEq} new self::OverriddenEq::•(1) : new self::OverriddenEq::•(2);
119+
if([@vm.direct-call.metadata=#lib::OverriddenEq.==] [@vm.inferred-type.metadata=dart.core::bool (skip check) (receiver not int)] c2 =={self::OverriddenEq::==}{(core::Object) → core::bool} #C7) {
120+
self::testOverriddenEq1(c2);
121+
}
122+
if([@vm.direct-call.metadata=#lib::OverriddenEq.==] [@vm.inferred-type.metadata=dart.core::bool (skip check) (receiver not int)] c2 =={self::OverriddenEq::==}{(core::Object) → core::bool} #C2) {
123+
self::testOverriddenEq2(c2);
124+
}
125+
}
126+
constants {
127+
#C1 = "foo"
128+
#C2 = "1234"
129+
#C3 = 123
130+
#C4 = 456
131+
#C5 = 1
132+
#C6 = self::DefaultEq {i:#C5}
133+
#C7 = self::OverriddenEq {i:#C5}
134+
}

0 commit comments

Comments
 (0)