Modify property promotion: only this

https://dart-review.googlesource.com/c/sdk/+/498840 added support for
promotion of properties (private, final instance variables with a name
which isn't used much for other purposes) in the context of anonymous
methods.

This CL reduces the set of situations where this feature is enabled such
that only `this` will allow property promotions to be carried in (such
that `this._x` is promoted in `v.=> this._x` when `v` is such that
`v._x` has been promoted before the anonymous method occurs). It also
generalizes the mechanism such that property promotions are carried out
(so we can do `if (v.=> _x is int) v._x.isEven;`).

Tests has been adjusted accordingly.

Change-Id: Ibe70713d3d9c89a6d95f9c3dd28df8f147cb518d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/502660
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Erik Ernst <eernst@google.com>
This commit is contained in:
Erik Ernst
2026-05-13 00:08:24 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent 3f66adee09
commit 96a4dd4c19
8 changed files with 54 additions and 29 deletions
@@ -12938,7 +12938,8 @@ main() {
]);
});
test('Parameterized anonymous method parameter inherits promotion', () {
test('Parameterized anonymous method parameter '
'does not inherit promotion', () {
var x = Var('x');
var p = Var('p');
h.addMember('A', '_field', 'Object', promotable: true);
@@ -12947,15 +12948,15 @@ main() {
declare(x, type: 'A', initializer: expr('A')),
x.property('_field').as_('num'),
x.invokeAnonymousMethod(isParameterless: false, parameter: p, [
checkPromoted(p.property('_field'), 'num'),
checkNotPromoted(p.property('_field')),
p.property('_field').as_('int'),
], returnType: 'void'),
checkPromoted(x.property('_field'), 'int'),
checkPromoted(x.property('_field'), 'num'),
]);
});
test('Parameterized anonymous method promotes parameter._field '
'from instanceVariable._field and vice versa', () {
test('Parameterized anonymous method does not promote parameter._field '
'from instanceVariable._field or vice versa', () {
var p = Var('p');
h.addMember('A', '_field', 'B', promotable: true);
h.addMember('B', '_subField', 'Object', promotable: true);
@@ -12967,10 +12968,10 @@ main() {
this_
.property('_field')
.invokeAnonymousMethod(isParameterless: false, parameter: p, [
checkPromoted(p.property('_subField'), 'num'),
checkNotPromoted(p.property('_subField')),
p.property('_subField').as_('int'),
], returnType: 'void'),
checkPromoted(this_.property('_field').property('_subField'), 'int'),
checkPromoted(this_.property('_field').property('_subField'), 'num'),
]);
});
+1 -1
View File
@@ -2517,7 +2517,7 @@ class InvokeAnonymousMethod extends Expression {
isFinal: false,
isLate: false,
isImplicitlyTyped: isImplicitlyTyped,
inheritPromotableProperties: true,
inheritPromotableProperties: isParameterless,
);
}
// Analyze the block, and generate its IR.
+9 -2
View File
@@ -1993,7 +1993,7 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
isFinal: false,
isLate: false,
isImplicitlyTyped: parameter.type == null,
inheritPromotableProperties: true,
inheritPromotableProperties: false,
);
} else {
// An error will occur because there are multiple parameters, but
@@ -2017,13 +2017,20 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
var targetInfo = target != null
? flowAnalysis.flow?.getExpressionInfo(target)
: null;
var body = node.body;
flowAnalysis.flow?.thisBinding_begin(targetInfo);
try {
returnedType = node.body.resolve(this, contextType);
returnedType = body.resolve(this, contextType);
} finally {
flowAnalysis.flow?.thisBinding_end();
_thisType = oldThisType;
}
if (body is AnonymousExpressionBodyImpl) {
flowAnalysis.flow?.storeExpressionInfo(
node,
flowAnalysis.flow?.getExpressionInfo(body.expression),
);
}
} else {
returnedType = node.body.resolve(this, contextType);
}
@@ -516,9 +516,10 @@ class Cascade extends InternalExpression {
class AnonymousMethodExpression extends InternalExpression {
VariableDeclaration variable;
Expression body;
final bool isCascade;
final bool isImplicitlyTyped;
final bool isNullAware;
final bool isCascade;
final bool isParameterless;
final int typeOffset;
AnonymousMethodExpression(
@@ -528,7 +529,7 @@ class AnonymousMethodExpression extends InternalExpression {
required this.isNullAware,
required this.isCascade,
required this.typeOffset,
}) {
}) : isParameterless = variable.isSynthesized {
variable.parent = this;
body.parent = this;
}
@@ -11881,7 +11881,7 @@ class InferenceVisitorImpl extends InferenceVisitorBase
isFinal: false,
isLate: false,
isImplicitlyTyped: node.isImplicitlyTyped,
inheritPromotableProperties: true,
inheritPromotableProperties: node.isParameterless,
);
if (node.isNullAware) {
flow.nullAwareAccess_rightBegin(
@@ -11891,8 +11891,7 @@ class InferenceVisitorImpl extends InferenceVisitorBase
);
}
bool isParameterless = node.variable.isSynthesized;
if (isParameterless) {
if (node.isParameterless) {
flow.thisBinding_begin(
flowAnalysis.getExpressionInfo(node.variable.initializer!),
);
@@ -11902,7 +11901,7 @@ class InferenceVisitorImpl extends InferenceVisitorBase
typeContext,
isVoidAllowed: true,
);
if (isParameterless) {
if (node.isParameterless) {
flow.thisBinding_end();
}
@@ -11966,6 +11965,13 @@ class InferenceVisitorImpl extends InferenceVisitorBase
replacement = new Let(node.variable, body)..fileOffset = node.fileOffset;
}
if (node.isParameterless) {
flowAnalysis.storeExpressionInfo(
replacement,
flowAnalysis.getExpressionInfo(node.body),
);
}
return new ExpressionInferenceResult(inferredType, replacement);
}
@@ -56,19 +56,20 @@ void testParameterlessPromotionsCarriedIn(C c) {
}
}
void testParameterfulPromotionsCarriedIn(C c) {
void testParameterfulPromotionsNotCarriedIn(C c) {
if (c._x != null) {
c._x.expectStaticType<Exactly<Object>>;
c.(p) {
// `p` refers to the same object as `c`, so `p._x` is promoted.
p._x.expectStaticType<Exactly<Object>>;
// `p` refers to the same object as `c`, but `p._x` is not promoted
// because `p` is treated like a normal local variable.
p._x.expectStaticType<Exactly<Object?>>;
};
}
}
main() {
testParameterlessPromotionsCarriedIn(C(0));
testParameterfulPromotionsCarriedIn(C(0));
C(0).testParameterlessRebindsThis();
C(0).testParameterfulDoesNotRebindThis();
testParameterlessPromotionsCarriedIn(C(0));
testParameterfulPromotionsNotCarriedIn(C(0));
}
@@ -9,5 +9,5 @@ import 'package:expect/expect.dart';
void main() {
final int? x = 2;
Expect.isTrue((x != null).=> this ? x.isEven : false);
Expect.isTrue((x != null).(b) => b ? x.isEven : false);
Expect.isTrue((x != null).(p) => p ? x.isEven : false);
}
@@ -43,7 +43,7 @@ class C {
}
}
void testParameterlessPromotionsCarriedIn(C c) {
void testParameterlessPromotionsCarriedInAndOut(C c) {
if (c._x != null) {
c._x.expectStaticType<Exactly<Object>>;
// Inside the anonymous method, `this` refers to the same object as `c`, so
@@ -51,20 +51,29 @@ void testParameterlessPromotionsCarriedIn(C c) {
c.=> _x.expectStaticType<Exactly<Object>>;
c.=> this._x.expectStaticType<Exactly<Object>>;
}
// The promotion is carried out again.
c._x.expectStaticType<Exactly<Object?>>;
if (c.=> _x is Object) {
c._x.expectStaticType<Exactly<Object>>;
}
if (c.=> this._x is Object) {
c._x.expectStaticType<Exactly<Object>>;
}
}
void testParameterfulPromotionsCarriedIn(C c) {
void testParameterfulPromotionsNotCarriedIn(C c) {
if (c._x != null) {
c._x.expectStaticType<Exactly<Object>>;
// Inside the anonymous method, `p` refers to the same object as `c`, so
// `p._x` is promoted.
c.(p) => p._x.expectStaticType<Exactly<Object>>;
// Inside the anonymous method, `p` refers to the same object as `c`, but
// `p._x` is not promoted because `p` is treated like a normal local
// variable.
c.(p) => p._x.expectStaticType<Exactly<Object?>>;
}
}
main() {
testParameterlessPromotionsCarriedIn(C(0));
testParameterfulPromotionsCarriedIn(C(0));
C(0).testParameterlessRebindsThis();
C(0).testParameterfulDoesNotRebindThis();
testParameterlessPromotionsCarriedInAndOut(C(0));
testParameterfulPromotionsNotCarriedIn(C(0));
}