[front end] Perform hoisting after coercion.
Coercion from `dynamic` to a non-top type has observable effects,
since it may throw an exception. Therefore it's important that any
tree manipulations that are performed as part of type inference (such
as hoisting of named arguments that precede unnamed ones) preserve the
order of coercions relative to expression evaluation.
Prior to this change, the following code:
f(n: e1, e2)
Would get transformed into:
let tmp = e1 in f(coerce(e2), n: coerce(tmp))
And so the coercion of e1 would not happen until after the evaluation
of e2.
With this change, the code is transformed into:
let tmp = coerce(e1) in f(coerce(e2), n: tmp)
Which (correctly) coerces e1 before evaluating e2.
Fixes https://github.com/dart-lang/sdk/issues/63150.
Change-Id: Iee33c162c7cc3a9b0a8b03bde211bf926a6a6964
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/498980
Reviewed-by: Lasse Nielsen <lrn@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
committed by
dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent
dfba7e0c61
commit
07b9707d96
@@ -0,0 +1,130 @@
|
||||
// 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.
|
||||
|
||||
// Since dynamic downcasts can fail, their evaluation order is observable. This
|
||||
// test verifies that in an invocation with multiple arguments, each argument is
|
||||
// evaluated and then (if necessary) downcast prior to evaluating the next
|
||||
// argument.
|
||||
|
||||
// The test pays special attention to the situation where a named argument
|
||||
// precedes a positional argument, since the front end is known to re-order such
|
||||
// arguments when converting to the kernel representation.
|
||||
|
||||
// This test only makes sense to run on platforms that check implicit downcasts.
|
||||
// Requirements=checked-implicit-downcasts
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
final List<String> log = [];
|
||||
|
||||
void f(int x, int y) {
|
||||
log.add('f($x, $y) called');
|
||||
}
|
||||
|
||||
void g(int x, {required int y}) {
|
||||
log.add('g($x, y: $y) called');
|
||||
}
|
||||
|
||||
void h({required int x, required int y}) {
|
||||
log.add('h(x: $x, y: $y) called');
|
||||
}
|
||||
|
||||
T compute<T>(T t) {
|
||||
log.add('computed $t');
|
||||
return t;
|
||||
}
|
||||
|
||||
void testTwoPositionalArgs() {
|
||||
log.clear();
|
||||
Expect.throws<TypeError>(() => f(compute<dynamic>('bad'), compute<int>(1)));
|
||||
Expect.listEquals(['computed bad'], log);
|
||||
|
||||
log.clear();
|
||||
Expect.throws<TypeError>(() => f(compute<int>(0), compute<dynamic>('bad')));
|
||||
Expect.listEquals(['computed 0', 'computed bad'], log);
|
||||
|
||||
log.clear();
|
||||
f(compute<int>(0), compute<int>(1));
|
||||
Expect.listEquals(['computed 0', 'computed 1', 'f(0, 1) called'], log);
|
||||
}
|
||||
|
||||
void testPositionalThenNamedArg() {
|
||||
log.clear();
|
||||
Expect.throws<TypeError>(
|
||||
() => g(compute<dynamic>('bad'), y: compute<int>(1)),
|
||||
);
|
||||
Expect.listEquals(['computed bad'], log);
|
||||
|
||||
log.clear();
|
||||
Expect.throws<TypeError>(
|
||||
() => g(compute<int>(0), y: compute<dynamic>('bad')),
|
||||
);
|
||||
Expect.listEquals(['computed 0', 'computed bad'], log);
|
||||
|
||||
log.clear();
|
||||
g(compute<int>(0), y: compute<int>(1));
|
||||
Expect.listEquals(['computed 0', 'computed 1', 'g(0, y: 1) called'], log);
|
||||
}
|
||||
|
||||
void testNamedThenPositionalArg() {
|
||||
log.clear();
|
||||
Expect.throws<TypeError>(
|
||||
() => g(y: compute<dynamic>('bad'), compute<int>(1)),
|
||||
);
|
||||
Expect.listEquals(['computed bad'], log);
|
||||
|
||||
log.clear();
|
||||
Expect.throws<TypeError>(
|
||||
() => g(y: compute<int>(0), compute<dynamic>('bad')),
|
||||
);
|
||||
Expect.listEquals(['computed 0', 'computed bad'], log);
|
||||
|
||||
log.clear();
|
||||
g(y: compute<int>(0), compute<int>(1));
|
||||
Expect.listEquals(['computed 0', 'computed 1', 'g(1, y: 0) called'], log);
|
||||
}
|
||||
|
||||
void testTwoNamedArgsInOrder() {
|
||||
log.clear();
|
||||
Expect.throws<TypeError>(
|
||||
() => h(x: compute<dynamic>('bad'), y: compute<int>(1)),
|
||||
);
|
||||
Expect.listEquals(['computed bad'], log);
|
||||
|
||||
log.clear();
|
||||
Expect.throws<TypeError>(
|
||||
() => h(x: compute<int>(0), y: compute<dynamic>('bad')),
|
||||
);
|
||||
Expect.listEquals(['computed 0', 'computed bad'], log);
|
||||
|
||||
log.clear();
|
||||
h(x: compute<int>(0), y: compute<int>(1));
|
||||
Expect.listEquals(['computed 0', 'computed 1', 'h(x: 0, y: 1) called'], log);
|
||||
}
|
||||
|
||||
void testTwoNamedArgsOutOfOrder() {
|
||||
log.clear();
|
||||
Expect.throws<TypeError>(
|
||||
() => h(y: compute<dynamic>('bad'), x: compute<int>(1)),
|
||||
);
|
||||
Expect.listEquals(['computed bad'], log);
|
||||
|
||||
log.clear();
|
||||
Expect.throws<TypeError>(
|
||||
() => h(y: compute<int>(0), x: compute<dynamic>('bad')),
|
||||
);
|
||||
Expect.listEquals(['computed 0', 'computed bad'], log);
|
||||
|
||||
log.clear();
|
||||
h(y: compute<int>(0), x: compute<int>(1));
|
||||
Expect.listEquals(['computed 0', 'computed 1', 'h(x: 1, y: 0) called'], log);
|
||||
}
|
||||
|
||||
void main() {
|
||||
testTwoPositionalArgs();
|
||||
testPositionalThenNamedArg();
|
||||
testNamedThenPositionalArg();
|
||||
testTwoNamedArgsInOrder();
|
||||
testTwoNamedArgsOutOfOrder();
|
||||
}
|
||||
Reference in New Issue
Block a user