blob: 82336a1d8efd9053f7d26d79181ba0fd7ce5a117 [file] [log] [blame]
// Copyright (c) 2022, 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.
// There are a few circumstances where implicit tear-off of `call` methods does
// not occur; this test exercises the user-visible static analysis behaviors
// arising from those circumstances.
// NOTICE: This test checks the currently implemented behavior, even though the
// implemented behavior does not match the language specification. Until an
// official decision has been made about whether to change the implementation to
// match the specification, or vice versa, this regression test is intended to
// protect against inadvertent implementation changes.
// A note on how the tests work: in several places we use the pattern
// `context<C>(b ? d : (E..expectStaticType<Exactly<T>>()))` (where `b` has type
// `bool` and `d` has type `dynamic`). This pattern ensures that `E` will be
// type analyzed with a context of `C`, and tests that the resulting expression
// has a type of `T`. However, the presence of `b ? d :` at the beginning
// ensures that the overall expression has type `dynamic`, so no assignability
// error will occur if types `C` and `T` are different.
import "package:expect/expect.dart";
import '../static_type_helper.dart';
class A {}
class C extends A {
void call() {}
void m() {}
}
// These are top level getters rather than local variables to avoid triggering
// flow analysis.
bool get bTrue => true;
bool get bFalse => false;
void testCascadeTarget() {
C c = C();
// Even though the subexpression `c` has type `C` and context `void
// Function()`, we don't tear off `.call` for subexpressions that are the
// target of a cascade; instead, we tear-off `.call` on the full cascade
// expression. So `c..m()` is equivalent to `(c..m()).call` (which is valid)
// rather than `(c.call)..m()` (which is not).
context<void Function()>(c..m());
// Same as above, but confirm that extra parens around `c` don't change the
// behavior.
context<void Function()>((c)..m());
context<void Function()>(((c))..m());
}
void testConditional() {
A a = A();
C c = C();
dynamic d = null;
// Even though the subexpression `c` has type `C` and context `void
// Function()`, we don't tear off `.call` for the `then` or `else`
// subexpressions of a conditional expression; instead, we tear off `.call`
// for the conditional expression as a whole (if appropriate). So, in
// `(bTrue ? c : a)..expectStaticType<...>()`, no implicit tearoff of `c`
// occurs, and the subexpression `bTrue ? c : a` gets assigned a static type
// of `A`.
Expect.throws(() => context<void Function()>(
bFalse ? d : ((bTrue ? c : a)..expectStaticType<Exactly<A>>())));
Expect.throws(() => context<void Function()>(
bFalse ? d : ((bFalse ? a : c)..expectStaticType<Exactly<A>>())));
// Same as above, but confirm that extra parens around `c` don't change the
// behavior.
Expect.throws(() => context<void Function()>(
bFalse ? d : ((bTrue ? (c) : a)..expectStaticType<Exactly<A>>())));
Expect.throws(() => context<void Function()>(
bFalse ? d : ((bFalse ? a : (c))..expectStaticType<Exactly<A>>())));
Expect.throws(() => context<void Function()>(
bFalse ? d : ((bTrue ? ((c)) : a)..expectStaticType<Exactly<A>>())));
Expect.throws(() => context<void Function()>(
bFalse ? d : ((bFalse ? a : ((c)))..expectStaticType<Exactly<A>>())));
}
void testIfNull() {
A a = A();
A? aq = null;
C c = C();
dynamic d = null;
// Even though the subexpression `c` has type `C` and context `void
// Function()?`, we don't tear off `.call` for the LHS of a `??` expression;
// instead, we tear off `.call` for the `??` expression as a whole (if
// appropriate). So, in
// `(c ?? a)..expectStaticType<...>()`, no implicit tearoff of `c` occurs, and
// the subexpression `c ?? a` gets assigned a static type of `A`.
Expect.throws(() => context<void Function()>(bFalse
? d
: ((c ?? a) // ignore: dead_null_aware_expression
..expectStaticType<Exactly<A>>())));
// In `(aq ?? c)..expectStaticType<...>()`, the situation is similar, but the
// context for `c` is (non-nullable) `void Function()`.
Expect.throws(() => context<void Function()>(
bFalse ? d : ((aq ?? c)..expectStaticType<Exactly<A>>())));
// Same as above, but confirm that extra parens around `c` don't change the
// behavior.
Expect.throws(() => context<void Function()>(bFalse
? d
: (((c) ?? a) // ignore: dead_null_aware_expression
..expectStaticType<Exactly<A>>())));
Expect.throws(() => context<void Function()>(
bFalse ? d : ((aq ?? (c))..expectStaticType<Exactly<A>>())));
Expect.throws(() => context<void Function()>(bFalse
? d
: ((((c)) ?? a) // ignore: dead_null_aware_expression
..expectStaticType<Exactly<A>>())));
Expect.throws(() => context<void Function()>(
bFalse ? d : ((aq ?? ((c)))..expectStaticType<Exactly<A>>())));
}
main() {
testCascadeTarget();
testConditional();
testIfNull();
}