blob: 9f3f788fef934d85bbc4bfd5c235de8592fd75c5 [file] [log] [blame]
// Copyright (c) 2024, 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.
// Tests the absence of the functionality proposed in
// https://github.com/dart-lang/language/issues/1618#issuecomment-1507241494
// when the `inference-update-3` language feature is not enabled, using if-null
// assignments whose target is a null-aware index expression that refers to
// operators defined in an extension.
// @dart=3.3
import '../static_type_helper.dart';
/// Ensures a context type of `Iterable<T>?` for the operand, or `Iterable<_>?`
/// if no type argument is supplied.
Object? contextIterableQuestion<T>(Iterable<T>? x) => x;
class A {}
class B1<T> implements A {}
class B2<T> implements A {}
class C1<T> implements B1<T>, B2<T> {}
class C2<T> implements B1<T>, B2<T> {}
class CallableClass<T> {
T call() => throw '';
}
/// Class that can be the target of `[]` and `[]=` operations. [ReadType] and
/// [WriteType] are the read and write types of the `[]` and `[]=` operators,
/// respectively.
///
/// Note that the `[]` and `[]=` operators are defined in an extension.
class Indexable<ReadType, WriteType> {
final ReadType _value;
Indexable(this._value);
}
extension<ReadType, WriteType> on Indexable<ReadType, WriteType> {
ReadType operator [](int index) => _value;
operator []=(int index, WriteType value) {}
}
Indexable<ReadType, WriteType>? maybeIndexable<ReadType, WriteType>(
ReadType value) =>
Indexable<ReadType, WriteType>(value);
main() {
// - An if-null assignment `e` of the form `e1 ??= e2` with context type K is
// analyzed as follows:
//
// - Let T1 be the read type of `e1`. This is the static type that `e1`
// would have as an expression with a context type schema of `_`.
// - Let T2 be the type of `e2` inferred with context type J, where:
// - If the lvalue is a local variable, J is the current (possibly
// promoted) type of the variable.
// - Otherwise, J is the write type `e1`. This is the type schema that the
// setter associated with `e1` imposes on its single argument (or, for
// the case of indexed assignment, the type schema that `operator[]=`
// imposes on its second argument).
{
// Check the context type of `e`.
// ignore: dead_null_aware_expression
maybeIndexable<String, Object?>('')?[0] ??= contextType('')
..expectStaticType<Exactly<Object?>>();
maybeIndexable<String?, String?>(null)?[0] ??= contextType('')
..expectStaticType<Exactly<String?>>();
}
// - Let J' be the unpromoted write type of `e1`, defined as follows:
// - If `e1` is a local variable, J' is the declared (unpromoted) type of
// `e1`.
// - Otherwise J' = J.
// - Let T2' be the coerced type of `e2`, defined as follows:
// - If T2 is a subtype of J', then T2' = T2 (no coercion is needed).
// - Otherwise, if T2 can be coerced to a some other type which *is* a
// subtype of J', then apply that coercion and let T2' be the type
// resulting from the coercion.
// - Otherwise, it is a compile-time error.
// - Let T be UP(NonNull(T1), T2').
// - Let S be the greatest closure of K.
// - If T <: S, then the type of `e` is T.
// (Testing this case here. Otherwise continued below.)
{
// This example has:
// - K = Object?
// - T1 = int?
// - T2' = double
// Which implies:
// - T = num
// - S = Object?
// We have:
// - T <: S
// Therefore the type of `e` is T = num.
// (Which becomes num? after null shorting completes.)
var d = 2.0;
context<Object?>((maybeIndexable<int?, Object?>(null)?[0] ??= d)
..expectStaticType<Exactly<num?>>());
// This example has:
// - K = Iterable<_>?
// - T1 = Iterable<int>?
// - T2' = Iterable<double>
// Which implies:
// - T = Iterable<num>
// - S = Iterable<Object?>?
// We have:
// - T <: S
// Therefore the type of `e` is T = Iterable<num>.
// (Which becomes Iterable<num>? after null shorting completes.)
var iterableDouble = <double>[] as Iterable<double>;
contextIterableQuestion(
(maybeIndexable<Iterable<int>?, Object?>(null)?[0] ??= iterableDouble)
..expectStaticType<Exactly<Iterable<num>?>>());
// This example has:
// - K = Function?
// - T1 = Function?
// - T2' = int Function()
// (coerced from T2=CallableClass<int>)
// Which implies:
// - T = Function
// - S = Function?
// We have:
// - T <: S
// Therefore the type of `e` is T = Function.
// (Which becomes Function? after null shorting completes.)
var callableClassInt = CallableClass<int>();
context<Function?>((maybeIndexable<Function?, Function?>(null)?[0] ??=
callableClassInt)
..expectStaticType<Exactly<Function?>>());
}
// - Otherwise, if NonNull(T1) <: S and T2' <: S, and `inference-update-3`
// is enabled, then the type of `e` is S.
{
// This example has:
// - K = Iterable<num>?
// - T1 = Iterable<int>?
// - T2' = List<num>
// Which implies:
// - T = Object
// - S = Iterable<num>?
// We have:
// - T <!: S
// - NonNull(T1) <: S
// - T2' <: S
// However, inference-update-3 is not enabled.
// Therefore the type of `e` is T = Object.
// (Which becomes Object? after null shorting completes.)
var listNum = <num>[];
Object? o;
o = [0] as Object?;
if (o is Iterable<num>?) {
// We avoid having a compile-time error because `o` can be demoted.
o = (maybeIndexable<Iterable<int>?, Object?>(null)?[0] ??= listNum)
..expectStaticType<Exactly<Object?>>();
}
// This example has:
// - K = B1<int> Function()?
// - T1 = C1<int> Function()?
// - T2' = C2<int> Function()
// (coerced from T2=CallableClass<C2<int>>)
// Which implies:
// - T = A Function()
// - S = B1<int> Function()?
// We have:
// - T <!: S
// - NonNull(T1) <: S
// - T2' <: S
// However, inference-update-3 is not enabled.
// Therefore the type of `e` is T = A Function().
// (Which becomes A Function()? after null shorting completes.)
var callableClassC2Int = CallableClass<C2<int>>();
o = (() => B1<int>()) as Object?;
if (o is B1<int> Function()?) {
// We avoid having a compile-time error because `o` can be demoted.
o = (maybeIndexable<C1<int> Function()?, Function?>(null)?[0] ??=
callableClassC2Int)
..expectStaticType<Exactly<A Function()?>>();
}
}
// - Otherwise, the type of `e` is T.
{
var d = 2.0;
Object? o;
var intQuestion = null as int?;
o = 0 as Object?;
if (o is int?) {
// This example has:
// - K = int?
// - T1 = int?
// - T2' = double
// Which implies:
// - T = num
// - S = int?
// We have:
// - T <!: S
// - NonNull(T1) <: S
// - T2' <!: S
// The fact that T2' <!: S precludes using S as static type.
// Therefore the type of `e` is T = num.
// (Which becomes num? after null shorting completes.)
// We avoid having a compile-time error because `o` can be demoted.
o = (maybeIndexable<int?, Object?>(null)?[0] ??= d)
..expectStaticType<Exactly<num?>>();
}
o = 0 as Object?;
if (o is int?) {
// This example has:
// - K = int?
// - T1 = double?
// - T2' = int?
// Which implies:
// - T = num?
// - S = int?
// We have:
// - T <!: S
// - NonNull(T1) <!: S
// - T2' <: S
// The fact that NonNull(T1) <!: S precludes using S as static type.
// Therefore the type of `e` is T = num?.
// We avoid having a compile-time error because `o` can be demoted.
o = (maybeIndexable<double?, Object?>(null)?[0] ??= intQuestion)
..expectStaticType<Exactly<num?>>();
}
o = '' as Object?;
if (o is String?) {
// This example has:
// - K = String?
// - T1 = int?
// - T2' = double
// Which implies:
// - T = num
// - S = String?
// We have:
// - T <!: S
// - NonNull(T1) <!: S
// - T2' <!: S
// The fact that NonNull(T1) <!: S and T2' <!: S precludes using S as
// static type.
// Therefore the type of `e` is T = num.
// (Which becomes num? after null shorting completes.)
// We avoid having a compile-time error because `o` can be demoted.
o = (maybeIndexable<int?, Object?>(null)?[0] ??= d)
..expectStaticType<Exactly<num?>>();
}
var callableClassC2Int = CallableClass<C2<int>>();
o = (() => C1<int>()) as Object?;
if (o is C1<int> Function()?) {
// This example has:
// - K = C1<int> Function()?
// - T1 = C1<int> Function()?
// - T2' = C2<int> Function()
// (coerced from T2=CallableClass<C2<int>>)
// Which implies:
// - T = A Function()
// - S = C1<int> Function()?
// We have:
// - T <!: S
// - NonNull(T1) <: S
// - T2' <!: S
// The fact that T2' <!: S precludes using S as static type.
// Therefore the type of `e` is T = A Function().
// (Which becomes A Function()? after null shorting completes.)
// We avoid having a compile-time error because `o` can be demoted.
o = (maybeIndexable<C1<int> Function()?, Function?>(null)?[0] ??=
callableClassC2Int)
..expectStaticType<Exactly<A Function()?>>();
}
o = (() => C2<int>()) as Object?;
if (o is C2<int> Function()?) {
// This example has:
// - K = C2<int> Function()?
// - T1 = C1<int> Function()?
// - T2' = C2<int> Function()
// (coerced from T2=CallableClass<C2<int>>)
// Which implies:
// - T = A Function()
// - S = C2<int> Function()?
// We have:
// - T <!: S
// - NonNull(T1) <!: S
// - T2' <: S
// The fact that NonNull(T1) <!: S precludes using S as static type.
// Therefore the type of `e` is T = A Function().
// (Which becomes A Function()? after null shorting completes.)
// We avoid having a compile-time error because `o` can be demoted.
o = (maybeIndexable<C1<int> Function()?, Function?>(null)?[0] ??=
callableClassC2Int)
..expectStaticType<Exactly<A Function()?>>();
}
o = 0 as Object?;
if (o is int?) {
// This example has:
// - K = int?
// - T1 = C1<int> Function()?
// - T2' = C2<int> Function()
// (coerced from T2=CallableClass<C2<int>>)
// Which implies:
// - T = A Function()
// - S = int?
// We have:
// - T <!: S
// - NonNull(T1) <!: S
// - T2' <: S
// The fact that NonNull(T1) <!: S precludes using S as static type.
// Therefore the type of `e` is T = A Function().
// (Which becomes A Function()? after null shorting completes.)
// We avoid having a compile-time error because `o` can be demoted.
o = (maybeIndexable<C1<int> Function()?, Function?>(null)?[0] ??=
callableClassC2Int)
..expectStaticType<Exactly<A Function()?>>();
}
}
}