// 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 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>(); c1q?.c?..id.expectStaticType>(); // 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>(); (c1q?.c).expectStaticType>(); c1q?.cq!.expectStaticType>(); (c1q?.cq!).expectStaticType>(); (c1q?.cq)!.expectStaticType>(); c1q?.cq!.c.expectStaticType>(); (c1q?.cq!.c).expectStaticType>(); c1q?[1].expectStaticType>(); (c1q?[1]).expectStaticType>(); c2q?[1].expectStaticType>(); (c2q?[1]).expectStaticType>(); } // TODO(lrn): Change Expect.fail to return Never. Never get unreachable => Expect.fail("Unreachable") as Never;