@@ -36,25 +36,32 @@ class SimpleUnreachableCodeElimination extends RemovingTransformer {
36
36
return result;
37
37
}
38
38
39
- bool _isBoolConstant (Expression node) =>
40
- node is BoolLiteral ||
41
- (node is ConstantExpression && node.constant is BoolConstant );
42
-
43
- bool _getBoolConstantValue (Expression node) {
44
- if (node is BoolLiteral ) {
45
- return node.value;
46
- }
47
- if (node is ConstantExpression ) {
48
- final constant = node.constant;
49
- if (constant is BoolConstant ) {
50
- return constant.value;
51
- }
39
+ bool ? _getBoolConstantValue (Expression node) {
40
+ if (node is BoolLiteral ) return node.value;
41
+ if (node is ! ConstantExpression ) return null ;
42
+ final constant = node.constant;
43
+ return constant is BoolConstant ? constant.value : null ;
44
+ }
45
+
46
+ Expression _makeConstantExpression (Constant constant, Expression node) {
47
+ if (constant is UnevaluatedConstant &&
48
+ constant.expression is InvalidExpression ) {
49
+ return constant.expression;
50
+ }
51
+ ConstantExpression constantExpression = new ConstantExpression (
52
+ constant, node.getStaticType (_staticTypeContext! ))
53
+ ..fileOffset = node.fileOffset;
54
+ if (node is FileUriExpression ) {
55
+ return new FileUriConstantExpression (constantExpression.constant,
56
+ type: constantExpression.type, fileUri: node.fileUri)
57
+ ..fileOffset = node.fileOffset;
52
58
}
53
- throw 'Expected bool constant: $ node ' ;
59
+ return constantExpression ;
54
60
}
55
61
56
- Expression _createBoolLiteral (bool value, int fileOffset) =>
57
- new BoolLiteral (value)..fileOffset = fileOffset;
62
+ Expression _createBoolConstantExpression (bool value, Expression node) =>
63
+ _makeConstantExpression (
64
+ constantEvaluator.canonicalize (BoolConstant (value)), node);
58
65
59
66
Statement _makeEmptyBlockIfEmptyStatement (Statement node, TreeNode parent) =>
60
67
node is EmptyStatement ? (Block (< Statement > [])..parent = parent) : node;
@@ -63,8 +70,8 @@ class SimpleUnreachableCodeElimination extends RemovingTransformer {
63
70
TreeNode visitIfStatement (IfStatement node, TreeNode ? removalSentinel) {
64
71
node.transformOrRemoveChildren (this );
65
72
final condition = node.condition;
66
- if ( _isBoolConstant ( condition)) {
67
- final value = _getBoolConstantValue (condition);
73
+ final value = _getBoolConstantValue ( condition);
74
+ if ( value != null ) {
68
75
return value
69
76
? node.then
70
77
: (node.otherwise ?? removalSentinel ?? new EmptyStatement ());
@@ -78,8 +85,8 @@ class SimpleUnreachableCodeElimination extends RemovingTransformer {
78
85
ConditionalExpression node, TreeNode ? removalSentinel) {
79
86
node.transformOrRemoveChildren (this );
80
87
final condition = node.condition;
81
- if ( _isBoolConstant ( condition)) {
82
- final value = _getBoolConstantValue (condition);
88
+ final value = _getBoolConstantValue ( condition);
89
+ if ( value != null ) {
83
90
return value ? node.then : node.otherwise;
84
91
}
85
92
return node;
@@ -89,9 +96,9 @@ class SimpleUnreachableCodeElimination extends RemovingTransformer {
89
96
TreeNode visitNot (Not node, TreeNode ? removalSentinel) {
90
97
node.transformOrRemoveChildren (this );
91
98
final operand = node.operand;
92
- if ( _isBoolConstant ( operand)) {
93
- return _createBoolLiteral (
94
- ! _getBoolConstantValue (operand) , node.fileOffset );
99
+ final value = _getBoolConstantValue ( operand);
100
+ if (value != null ) {
101
+ return _createBoolConstantExpression ( ! value , node);
95
102
}
96
103
return node;
97
104
}
@@ -100,47 +107,89 @@ class SimpleUnreachableCodeElimination extends RemovingTransformer {
100
107
TreeNode visitLogicalExpression (
101
108
LogicalExpression node, TreeNode ? removalSentinel) {
102
109
node.transformOrRemoveChildren (this );
103
- final left = node.left;
104
- final right = node.right;
105
- final operatorEnum = node.operatorEnum;
106
- if (_isBoolConstant (left)) {
107
- final leftValue = _getBoolConstantValue (left);
108
- if (_isBoolConstant (right)) {
109
- final rightValue = _getBoolConstantValue (right);
110
- if (operatorEnum == LogicalExpressionOperator .OR ) {
111
- return _createBoolLiteral (leftValue || rightValue, node.fileOffset);
112
- } else if (operatorEnum == LogicalExpressionOperator .AND ) {
113
- return _createBoolLiteral (leftValue && rightValue, node.fileOffset);
114
- } else {
115
- throw 'Unexpected LogicalExpression operator ${operatorEnum }: $node ' ;
116
- }
117
- } else {
118
- if (leftValue && operatorEnum == LogicalExpressionOperator .OR ) {
119
- return _createBoolLiteral (true , node.fileOffset);
120
- } else if (! leftValue &&
121
- operatorEnum == LogicalExpressionOperator .AND ) {
122
- return _createBoolLiteral (false , node.fileOffset);
110
+ bool ? value = _getBoolConstantValue (node.left);
111
+ // Because of short-circuiting, these operators cannot be treated as
112
+ // symmetric, so a non-constant left and a constant right is left as-is.
113
+ if (value == null ) return node;
114
+ switch (node.operatorEnum) {
115
+ case LogicalExpressionOperator .OR :
116
+ return value ? node.left : node.right;
117
+ case LogicalExpressionOperator .AND :
118
+ return value ? node.right : node.left;
119
+ }
120
+ }
121
+
122
+ @override
123
+ TreeNode visitSwitchStatement (
124
+ SwitchStatement node, TreeNode ? removalSentinel) {
125
+ node.transformOrRemoveChildren (this );
126
+ final tested = node.expression;
127
+ if (tested is ! ConstantExpression ) return node;
128
+
129
+ // Set of SwitchCases that should be retained, either because they are
130
+ // guaranteed to match, may match and no case before them is guaranteed
131
+ // to match, or because they are the target of a case that may match or
132
+ // is guaranteed to match.
133
+ final toKeep = < SwitchCase > {};
134
+ // Whether an expression from a previous case is guaranteed to match.
135
+ bool foundMatchingCase = false ;
136
+ for (final c in node.cases) {
137
+ // Trim any constant expressions that don't match, or any expression
138
+ // that follows a guaranteed-to-match case. Perform this trimming even
139
+ // on cases that follow a guaranteed to match case and thus are not
140
+ // in the initial set to retain, since they may be the target of continue
141
+ // statements and thus added to the set to retain later.
142
+ bool containsMatchingCase = false ;
143
+ c.expressions.retainWhere ((e) {
144
+ if (foundMatchingCase) return false ;
145
+ if (e is ! ConstantExpression ) return true ;
146
+ containsMatchingCase = e.constant == tested.constant;
147
+ return containsMatchingCase;
148
+ });
149
+ if (foundMatchingCase) continue ;
150
+ foundMatchingCase = containsMatchingCase;
151
+ if (c.isDefault || c.expressions.isNotEmpty) {
152
+ toKeep.add (c);
153
+ }
154
+ }
155
+
156
+ if (toKeep.isEmpty) {
157
+ throw 'Expected at least one possibly matching case: $node ' ;
158
+ }
159
+
160
+ // Now iteratively keep cases that are targets of continue statements
161
+ // within the cases we've already marked to keep.
162
+ final worklist = [...toKeep];
163
+ final collector = ContinueSwitchStatementTargetCollector (node);
164
+ while (worklist.isNotEmpty) {
165
+ final next = worklist.removeLast ();
166
+ final targets = collector.collectTargets (next);
167
+ for (final target in targets) {
168
+ if (toKeep.add (target)) {
169
+ worklist.add (target);
123
170
}
124
171
}
125
172
}
173
+
174
+ node.cases.retainWhere ((c) => toKeep.contains (c));
175
+ if (node.cases.length == 1 ) {
176
+ return node.cases.first.body;
177
+ }
126
178
return node;
127
179
}
128
180
129
181
@override
130
- visitStaticGet (StaticGet node, TreeNode ? removalSentinel) {
182
+ TreeNode visitStaticGet (StaticGet node, TreeNode ? removalSentinel) {
131
183
node.transformOrRemoveChildren (this );
132
184
final target = node.target;
133
185
if (target is Field && target.isConst) {
134
186
throw 'StaticGet from const field $target should be evaluated by front-end: $node ' ;
135
187
}
136
- if (constantEvaluator.transformerShouldEvaluateExpression (node)) {
137
- final context = _staticTypeContext! ;
138
- final result = constantEvaluator.evaluate (context, node);
139
- assert (result is ! UnevaluatedConstant );
140
- return new ConstantExpression (result, node.getStaticType (context))
141
- ..fileOffset = node.fileOffset;
188
+ if (! constantEvaluator.transformerShouldEvaluateExpression (node)) {
189
+ return node;
142
190
}
143
- return node;
191
+ final result = constantEvaluator.evaluate (_staticTypeContext! , node);
192
+ return _makeConstantExpression (result, node);
144
193
}
145
194
146
195
@override
@@ -235,3 +284,25 @@ class SimpleUnreachableCodeElimination extends RemovingTransformer {
235
284
return node;
236
285
}
237
286
}
287
+
288
+ class ContinueSwitchStatementTargetCollector extends RecursiveVisitor {
289
+ final SwitchStatement parent;
290
+ late Set <SwitchCase > collected;
291
+
292
+ ContinueSwitchStatementTargetCollector (this .parent);
293
+
294
+ Set <SwitchCase > collectTargets (SwitchCase node) {
295
+ collected = {};
296
+ node.accept (this );
297
+ return collected;
298
+ }
299
+
300
+ @override
301
+ void visitContinueSwitchStatement (ContinueSwitchStatement node) {
302
+ node.visitChildren (this );
303
+ // Only keep targets that are within the original node being checked.
304
+ if (node.target.parent == parent) {
305
+ collected.add (node.target);
306
+ }
307
+ }
308
+ }
0 commit comments