blob: 625bc08df05c9d62b0c6579fbd20a6c05aa44768 [file] [log] [blame]
// Copyright (c) 2020, 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.
// Check null-shortening semantics of `?.` and `?[]`
import "package:expect/expect.dart";
import "../static_type_helper.dart";
class C1 {
C1 get c => this;
C1? get cq => this;
C1? get cn => null;
C1 get getterThrows => unreachable;
void method(Object? v) {}
void methodThrows() => unreachable;
set setter(Object? v) {}
set setterThrows(Object? v) {
unreachable;
}
C1 operator [](Object? _) => this;
operator []=(Object? _, Object? __) {}
int n = 0;
}
extension<T> on T {
T get id => this;
}
extension on C1 {
C1 get extThrows => unreachable;
// ignore: unused_element
C1 operator +(Object? other) => unreachable;
// ignore: unused_element
C1 operator ~() => unreachable;
}
extension on C1? {
C1? operator +(Object? other) => this;
C1? operator ~() => this;
}
class C2 {
C2? operator [](int n) => n.isEven ? this : null;
operator []=(int n, C2? value) {}
}
main() {
C1 c1 = C1();
var c1q = c1 as C1?;
C1? c1n = null;
C2 c2 = C2();
var c2q = c2 as C2?;
// All selector operations short on null.
// .foo
// ?.foo
// .foo=
// ?.foo=
// .foo()
// ?.foo()
// []
// ?[]
// []=
// ?[]=
// !
// !.foo
// ![]
c1n?.getterThrows;
c1n?.setterThrows = 0;
c1n?.setter = unreachable;
c1n?.method(unreachable);
c1n?.methodThrows();
c1n?[unreachable];
c1n?[unreachable] = 0;
c1n?[0] = unreachable;
c1n?.extThrows;
c1n?.c.getterThrows;
c1n?.c.setterThrows = 0;
c1n?.c.setter = unreachable;
c1n?.c.method(unreachable);
c1n?.c.methodThrows();
c1n?.c[unreachable];
c1n?.c[unreachable] = 0;
c1n?.c[0] = unreachable;
c1n?.c.extThrows;
c1n?.cn!;
c1n?.cn!.getterThrows;
c1n?.cn!.setterThrows = 0;
c1n?.cn!.setter = unreachable;
c1n?.cn!.method(unreachable);
c1n?.cn!.methodThrows();
c1n?.cn![unreachable];
c1n?.cn![unreachable] = 0;
c1n?.cn![0] = unreachable;
c1n?.cn!.extThrows;
// Binary and prefix operators in general
// do not participate in null shortening.
var i = 0;
c1q?.c + (i++);
Expect.equals(i, 1);
~c1q?.c; // Would throw if hitting extension on C1
// ++ and -- participates in null shortening!
c1q?.n++;
++c1q?.n;
c1n?.n++;
++c1n?.n;
// Cascades do not participate in null shortening.
c1q?.c..id.expectStaticType<Exactly<C1?>>();
c1q?.c?..id.expectStaticType<Exactly<C1>>();
// Null shortening works the same below cascades
c1..cn?.getterThrows;
c1..cn?.setterThrows = 0;
c1..cn?.setter = unreachable;
c1..cn?.method(unreachable);
c1..cn?.methodThrows();
c1..cn?[unreachable];
c1..cn?[unreachable] = 0;
c1..cn?[0] = unreachable;
c1..cn?.extThrows;
c1..cn?.c.getterThrows;
c1..cn?.c.setterThrows = 0;
c1..cn?.c.setter = unreachable;
c1..cn?.c.method(unreachable);
c1..cn?.c.methodThrows();
c1..cn?.c[unreachable];
c1..cn?.c[unreachable] = 0;
c1..cn?.c[0] = unreachable;
c1..cn?.c.extThrows;
c1..cn?.cn!;
c1..cn?.cn!.getterThrows;
c1..cn?.cn!.setterThrows = 0;
c1..cn?.cn!.setter = unreachable;
c1..cn?.cn!.method(unreachable);
c1..cn?.cn!.methodThrows();
c1..cn?.cn![unreachable];
c1..cn?.cn![unreachable] = 0;
c1..cn?.cn![0] = unreachable;
c1..cn?.cn!.extThrows;
// The static types depend on whether the check is part of the
// null shortening.
c1q?.c.expectStaticType<Exactly<C1>>();
(c1q?.c).expectStaticType<Exactly<C1?>>();
c1q?.cq!.expectStaticType<Exactly<C1>>();
(c1q?.cq!).expectStaticType<Exactly<C1?>>();
(c1q?.cq)!.expectStaticType<Exactly<C1>>();
c1q?.cq!.c.expectStaticType<Exactly<C1>>();
(c1q?.cq!.c).expectStaticType<Exactly<C1?>>();
c1q?[1].expectStaticType<Exactly<C1>>();
(c1q?[1]).expectStaticType<Exactly<C1?>>();
c2q?[1].expectStaticType<Exactly<C2?>>();
(c2q?[1]).expectStaticType<Exactly<C2?>>();
}
// TODO(lrn): Change Expect.fail to return Never.
Never get unreachable => Expect.fail("Unreachable") as Never;