[cfe] Update both sides of intersection types in GUB

In computation of GUB both the left-hand and the right-hand sides of
the intersection should be updated as nullable when the other operator
to GUB is Null.

Closes https://github.com/dart-lang/sdk/issues/50431

Change-Id: I4b616a94a3e7bf149205ba1b90732453c19ace47
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/311845
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Chloe Stefantsova <cstefantsova@google.com>
This commit is contained in:
Chloe Stefantsova 2023-07-10 09:51:56 +00:00 committed by Commit Queue
parent 44230dce94
commit b36fbaef2c
11 changed files with 88 additions and 6 deletions

View file

@ -1357,12 +1357,12 @@ class TypeSchemaEnvironmentTest extends TypeSchemaEnvironmentTestBase {
//
// resulting in
//
// (T & Object)? = T? & Object
// (T & Object)? = T? & Object?
//
checkUpperBound(
type1: "T",
type2: "Null",
upperBound: "T? & Object",
upperBound: "T? & Object?",
typeParameters: "T extends Object?",
nonNull1: true);
@ -1372,12 +1372,12 @@ class TypeSchemaEnvironmentTest extends TypeSchemaEnvironmentTestBase {
//
// resulting in
//
// (T & bool)? = T? & bool
// (T & bool)? = T? & bool?
//
checkUpperBound(
type1: "T",
type2: "Null",
upperBound: "T? & bool",
upperBound: "T? & bool?",
typeParameters: "T extends bool?",
nonNull1: true);

View file

@ -0,0 +1,8 @@
// Copyright (c) 2023, 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.
test<T>(T? t) {
var x1 = t != null ? t : null;
var x2 = t == null ? null : t;
}

View file

@ -0,0 +1,8 @@
library;
import self as self;
import "dart:core" as core;
static method test<T extends core::Object? = dynamic>(self::test::T? t) → dynamic {
self::test::T? x1 = !(t == null) ?{self::test::T? & core::Object? /* '?' & '?' = '?' */} t{self::test::T% & core::Object /* '%' & '!' = '!' */} : null;
self::test::T? x2 = t == null ?{self::test::T? & core::Object? /* '?' & '?' = '?' */} null : t{self::test::T% & core::Object /* '%' & '!' = '!' */};
}

View file

@ -0,0 +1,8 @@
library;
import self as self;
import "dart:core" as core;
static method test<T extends core::Object? = dynamic>(self::test::T? t) → dynamic {
self::test::T? x1 = !(t == null) ?{self::test::T? & core::Object? /* '?' & '?' = '?' */} t{self::test::T% & core::Object /* '%' & '!' = '!' */} : null;
self::test::T? x2 = t == null ?{self::test::T? & core::Object? /* '?' & '?' = '?' */} null : t{self::test::T% & core::Object /* '%' & '!' = '!' */};
}

View file

@ -0,0 +1 @@
test<T>(T? t) {}

View file

@ -0,0 +1 @@
test<T>(T? t) {}

View file

@ -0,0 +1,8 @@
library;
import self as self;
import "dart:core" as core;
static method test<T extends core::Object? = dynamic>(self::test::T? t) → dynamic {
self::test::T? x1 = !(t == null) ?{self::test::T? & core::Object? /* '?' & '?' = '?' */} t{self::test::T% & core::Object /* '%' & '!' = '!' */} : null;
self::test::T? x2 = t == null ?{self::test::T? & core::Object? /* '?' & '?' = '?' */} null : t{self::test::T% & core::Object /* '%' & '!' = '!' */};
}

View file

@ -0,0 +1,8 @@
library;
import self as self;
import "dart:core" as core;
static method test<T extends core::Object? = dynamic>(self::test::T? t) → dynamic {
self::test::T? x1 = !(t == null) ?{self::test::T? & core::Object? /* '?' & '?' = '?' */} t{self::test::T% & core::Object /* '%' & '!' = '!' */} : null;
self::test::T? x2 = t == null ?{self::test::T? & core::Object? /* '?' & '?' = '?' */} null : t{self::test::T% & core::Object /* '%' & '!' = '!' */};
}

View file

@ -0,0 +1,6 @@
library;
import self as self;
import "dart:core" as core;
static method test<T extends core::Object? = dynamic>(self::test::T? t) → dynamic
;

View file

@ -0,0 +1,8 @@
library;
import self as self;
import "dart:core" as core;
static method test<T extends core::Object? = dynamic>(self::test::T? t) → dynamic {
self::test::T? x1 = !(t == null) ?{self::test::T? & core::Object? /* '?' & '?' = '?' */} t{self::test::T% & core::Object /* '%' & '!' = '!' */} : null;
self::test::T? x2 = t == null ?{self::test::T? & core::Object? /* '?' & '?' = '?' */} null : t{self::test::T% & core::Object /* '%' & '!' = '!' */};
}

View file

@ -689,9 +689,35 @@ mixin StandardBounds {
if (coreTypes.isNull(type2)) {
return morebottom(type1, type2) ? type2 : type1;
}
return type2.withDeclaredNullability(Nullability.nullable);
if (type2 is IntersectionType) {
// Intersection types are treated specially because of the semantics of
// the declared nullability and the fact that the point of declaration
// of the intersection type is taken as the point of declaration of the
// corresponding type-parameter type.
//
// In case of the upper bound, both the left-hand side and the
// right-hand side should be updated.
return new IntersectionType(
type2.left.withDeclaredNullability(Nullability.nullable),
type2.right.withDeclaredNullability(Nullability.nullable));
} else {
return type2.withDeclaredNullability(Nullability.nullable);
}
} else if (coreTypes.isNull(type2)) {
return type1.withDeclaredNullability(Nullability.nullable);
if (type1 is IntersectionType) {
// Intersection types are treated specially because of the semantics of
// the declared nullability and the fact that the point of declaration
// of the intersection type is taken as the point of declaration of the
// corresponding type-parameter type.
//
// In case of the upper bound, both the left-hand side and the
// right-hand side should be updated.
return new IntersectionType(
type1.left.withDeclaredNullability(Nullability.nullable),
type1.right.withDeclaredNullability(Nullability.nullable));
} else {
return type1.withDeclaredNullability(Nullability.nullable);
}
}
// UP(T1, T2) where OBJECT(T1) and OBJECT(T2) =