e933e91aaa
This is a reland of 6a1c54ec30
Original change's description:
> Flow analysis: promote to non-nullable on initialization
>
> When flow analysis encounters a variable declaration of the form `T? x
> = expr;`, if the type of `expr` is `T`, then the variable is
> immediately promoted to type `T`.
>
> Fixes #43099.
>
> Change-Id: Ia206fe0d50e2fdd9bdf637e13c85633d8490dbcc
> Bug: https://github.com/dart-lang/sdk/issues/43099
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/163841
> Commit-Queue: Paul Berry <paulberry@google.com>
> Reviewed-by: Bob Nystrom <rnystrom@google.com>
> Reviewed-by: Johnni Winther <johnniwinther@google.com>
> Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Bug: https://github.com/dart-lang/sdk/issues/43099
Change-Id: I7530bb0f7c24674a7b500558b89d50b35e045aca
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/166305
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
173 lines
4.0 KiB
Dart
173 lines
4.0 KiB
Dart
// 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;
|