Add test for null shortening.
Change-Id: Ica4a36114f487247b1e7a5bf1cd4a8584b3f36df
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/164326
Commit-Queue: Lasse R.H. Nielsen <lrn@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/tests/language/null_aware/null_shortening_test.dart b/tests/language/null_aware/null_shortening_test.dart
new file mode 100644
index 0000000..f4ce95c
--- /dev/null
+++ b/tests/language/null_aware/null_shortening_test.dart
@@ -0,0 +1,172 @@
+// 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();
+ C1? c1q = c1;
+ C1? c1n = null;
+
+ C2 c2 = C2();
+ C2? c2q = 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?.cn!.c.expectStaticType<Exactly<C1>>();
+ (c1q?.cn!.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;