[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:
Paul Berry
2026-02-21 10:59:44 -08:00
committed by Commit Queue
parent 5cbe70416f
commit d0278ea7b8
3 changed files with 91 additions and 3 deletions
@@ -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')), [
+13 -3
View File
@@ -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());
}