[flow analysis] Test post increment/decrement demotion.
Adds a language test and a flow analysis unit test to cover a flow analysis behavior of post-increment and post-decrement operators that wasn't previously covered. The tests verify that the expressions `x++` and `x--` demote `x` in the same way that `x = x + 1` and `x = x - 1` would. This demotion is only user-visible if the type of `x` is a user-defined type. In the process of writing these tests, I noticed that the "mini-AST" implementation of post-increment (which is used solely for flow analysis unit testing) was not correct; it presumed that the type read from the target, the type written to it, and the type of the whole expression were all the same. This is not correct; the type written to the target is determined by the return type of the `+` operator. I've fixed this as part of this CL so that the unit test properly exercises flow analysis. I will follow this up with some refactoring of how flow analysis handles post increment/decrement operations. Landing the test first allows us to be confident that the refactor won't change the tested behavior. Change-Id: I6a6a6964417b48db0c1681c06d7418bd79e96357 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/482342 Commit-Queue: Paul Berry <paulberry@google.com> Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
@@ -2090,6 +2090,24 @@ main() {
|
||||
]);
|
||||
});
|
||||
|
||||
test('postIncDec() demotes to the written type', () {
|
||||
// If `x` has type B, but is promoted to subtype C and then D, and D
|
||||
// has a `+` operator that returns C, then after `x++`, `x` should be
|
||||
// demoted to C.
|
||||
var x = Var('x');
|
||||
h.addSuperInterfaces('B', (_) => [Type('Object')]);
|
||||
h.addSuperInterfaces('C', (_) => [Type('B'), Type('Object')]);
|
||||
h.addSuperInterfaces('D', (_) => [Type('C'), Type('B'), Type('Object')]);
|
||||
h.addMember('D', '+', 'C Function(int)');
|
||||
h.run([
|
||||
declare(x, initializer: expr('B')),
|
||||
x.as_('C'),
|
||||
x.as_('D'),
|
||||
x.postIncDec(),
|
||||
checkPromoted(x, 'C'),
|
||||
]);
|
||||
});
|
||||
|
||||
test('switchExpression throw in scrutinee makes all cases unreachable', () {
|
||||
h.run([
|
||||
switchExpr(throw_(expr('C')), [
|
||||
|
||||
@@ -1790,6 +1790,7 @@ class Harness {
|
||||
'int.>=': Type('bool Function(num)'),
|
||||
'int.abs': Type('int Function()'),
|
||||
'int.isEven': Type('bool'),
|
||||
'num.+': Type('num Function(num)'),
|
||||
'num.sign': Type('num'),
|
||||
'Object.toString': Type('String Function()'),
|
||||
};
|
||||
@@ -4368,12 +4369,21 @@ class PostIncDec extends Expression {
|
||||
|
||||
@override
|
||||
ExpressionTypeAnalysisResult visit(Harness h, SharedTypeSchemaView schema) {
|
||||
Type type = h.typeAnalyzer
|
||||
Type operandType = h.typeAnalyzer
|
||||
.analyzeExpression(lhs, h.operations.unknownType)
|
||||
.type
|
||||
.unwrapTypeView();
|
||||
lhs._visitPostIncDec(h, this, type);
|
||||
return new ExpressionTypeAnalysisResult(type: SharedTypeView(type));
|
||||
var member = h.getMember(operandType, '+');
|
||||
if (member == null) {
|
||||
fail('No + operator found for $operandType');
|
||||
}
|
||||
var memberType = member._type;
|
||||
if (memberType is! FunctionType) {
|
||||
fail('Expected function type for + operator, got $memberType');
|
||||
}
|
||||
var writtenType = memberType.returnType;
|
||||
lhs._visitPostIncDec(h, this, writtenType);
|
||||
return new ExpressionTypeAnalysisResult(type: SharedTypeView(operandType));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2026, 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.
|
||||
|
||||
import 'package:expect/static_type_helper.dart';
|
||||
|
||||
// This test checks that `x++` and `x--` demote `x` to the type returned by the
|
||||
// `+` or `-` operator defined on the previously-promoted type of `x`.
|
||||
//
|
||||
// It further verifies that when `x` is `int`, `x++` and `x--` do not demote to
|
||||
// `num`, because of the special type inference rule that adding or subtracting
|
||||
// two ints yields an int.
|
||||
|
||||
increment_int(num x) {
|
||||
if (x is int) {
|
||||
x++;
|
||||
x.expectStaticType<Exactly<int>>();
|
||||
}
|
||||
}
|
||||
|
||||
decrement_int(num x) {
|
||||
if (x is int) {
|
||||
x--;
|
||||
x.expectStaticType<Exactly<int>>();
|
||||
}
|
||||
}
|
||||
|
||||
increment_userDefinedType(B x) {
|
||||
if (x is C) {
|
||||
if (x is D) {
|
||||
x++;
|
||||
x.expectStaticType<Exactly<C>>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decrement_userDefinedType(B x) {
|
||||
if (x is C) {
|
||||
if (x is D) {
|
||||
x++;
|
||||
x.expectStaticType<Exactly<C>>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class B {}
|
||||
|
||||
class C extends B {}
|
||||
|
||||
class D extends C {
|
||||
C operator +(int i) => this;
|
||||
C operator -(int i) => this;
|
||||
}
|
||||
|
||||
main() {
|
||||
increment_int(0);
|
||||
decrement_int(0);
|
||||
increment_userDefinedType(D());
|
||||
decrement_userDefinedType(D());
|
||||
}
|
||||
Reference in New Issue
Block a user