From 87a6f70fa8ad618e3272f6618cfe6819655cf0a9 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 29 Sep 2021 17:39:44 +0000 Subject: [PATCH 1/6] Adjust elem type --- compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala index 429c0e7445b2..71d814bb0a62 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala @@ -15,6 +15,7 @@ import Decorators._ import Denotations._, SymDenotations._ import TypeErasure.erasure import DenotTransformers._ +import NullOpsDecorator._ object ElimRepeated { val name: String = "elimRepeated" @@ -335,6 +336,9 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => val array = tp.translateFromRepeated(toArray = true) // Array[? <: T] val element = array.elemType.hiBound // T - if element <:< defn.AnyRefType || element.typeSymbol.isPrimitiveValueClass then array + + if element <:< defn.AnyRefType + || ctx.mode.is(Mode.SafeNulls) && element.stripNull <:< defn.AnyRefType + || element.typeSymbol.isPrimitiveValueClass then array else defn.ArrayOf(TypeBounds.upper(AndType(element, defn.AnyRefType))) // Array[? <: T & AnyRef] } From b0a3bb835f3fddbec8f7abb5e16dc1d337de5de5 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 1 Oct 2021 00:16:18 +0000 Subject: [PATCH 2/6] Fix overriing check with FromJavaObject --- .../dotty/tools/dotc/core/TypeComparer.scala | 23 ++++++++++++++++++- .../src/dotty/tools/dotc/core/Types.scala | 7 ++++++ .../tools/dotc/transform/ResolveSuper.scala | 9 +++++++- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 49cfb2b6191c..33ceb3bdbba0 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -1505,12 +1505,33 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling false } + // This & will try to preserve the FromJavaObjects type in upper bounds + // For example, (? <: FromJavaObjects | Null) & (? <: Any), + // we want to get (? <: FromJavaObjects | Null) intead of (? <: Any), + // because we may check FromJavaObjects | Null <:< Object | Null later. + def bounds_&(tp1: TypeBounds, tp2: TypeBounds) = + if tp1.hi.containsFromJavaObject + && (tp1.hi frozen_<:< tp2.hi) + && (tp2.lo frozen_<:< tp1.lo) then + // FromJavaObject in tp1.hi guarantees tp2.hi <:< tp1.hi + // prefer tp1 if FromJavaObject is in its hi + tp1 + else if tp2.hi.containsFromJavaObject + && (tp2.hi frozen_<:< tp1.hi) + && (tp1.lo frozen_<:< tp2.lo) then + // Similarly, prefer tp2 if FromJavaObject is in its hi + tp2 + else + // Use regular & to solve other cases + tp1 & tp2 + def isSubArg(arg1: Type, arg2: Type): Boolean = arg2 match { case arg2: TypeBounds => val arg1norm = arg1 match { case arg1: TypeBounds => tparam match { - case tparam: Symbol => arg1 & paramBounds(tparam) + case tparam: Symbol => + bounds_&(arg1, paramBounds(tparam)) case _ => arg1 // This case can only arise when a hk-type is illegally instantiated with a wildcard } case _ => arg1 diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index d3c0eeab73d9..457b8c4c94f9 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -301,6 +301,11 @@ object Types { def isFromJavaObject(using Context): Boolean = typeSymbol eq defn.FromJavaObjectSymbol + def containsFromJavaObject(using Context): Boolean = this match + case tp: OrType => tp.tp1.containsFromJavaObject || tp.tp2.containsFromJavaObject + case tp: AndType => tp.tp1.containsFromJavaObject && tp.tp2.containsFromJavaObject + case _ => isFromJavaObject + /** True iff `symd` is a denotation of a class type parameter and the reference * `
 . ` is an actual argument reference, i.e. `pre` is not the
      *  ThisType of `symd`'s owner, or a reference to `symd`'s owner.'
@@ -4933,6 +4938,8 @@ object Types {
     }
 
     def & (that: TypeBounds)(using Context): TypeBounds =
+      // if ((that.lo frozen_<:< this.lo) && (this.hi frozen_<:< that.hi)) this
+      // else if ((this.lo frozen_<:< that.lo) && (that.hi frozen_<:< this.hi)) that
       if ((this.lo frozen_<:< that.lo) && (that.hi frozen_<:< this.hi)) that
       else if ((that.lo frozen_<:< this.lo) && (this.hi frozen_<:< that.hi)) this
       else TypeBounds(this.lo | that.lo, this.hi & that.hi)
diff --git a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala
index 30ccc69e37ca..40c860bf3bdc 100644
--- a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala
+++ b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala
@@ -107,7 +107,14 @@ object ResolveSuper {
         // of the superaccessor's type, see i5433.scala for an example where this matters
         val otherTp = other.asSeenFrom(base.typeRef).info
         val accTp = acc.asSeenFrom(base.typeRef).info
-        if (!(otherTp.overrides(accTp, matchLoosely = true)))
+        // Since the super class can be Java defined,
+        // we use releaxed overriding check for explicit nulls if one of the symbols is Java defined.
+        // This forces `Null` being a subtype of reference types during override checking.
+        val relaxedCtxForNulls =
+        if ctx.explicitNulls && (sym.is(JavaDefined) || acc.is(JavaDefined)) then
+          ctx.retractMode(Mode.SafeNulls)
+        else ctx
+        if (!(otherTp.overrides(accTp, matchLoosely = true)(using relaxedCtxForNulls)))
           report.error(IllegalSuperAccessor(base, memberName, targetName, acc, accTp, other.symbol, otherTp), base.srcPos)
 
       bcs = bcs.tail

From b6eddaacfee78a2263b7d7a2cdb8a3895e4da91a Mon Sep 17 00:00:00 2001
From: noti0na1 
Date: Fri, 1 Oct 2021 00:23:04 +0000
Subject: [PATCH 3/6] Add tests

---
 compiler/src/dotty/tools/dotc/core/Types.scala | 2 --
 tests/explicit-nulls/pos/i13040/Impl.scala     | 5 +++++
 tests/explicit-nulls/pos/i13040/Intf.java      | 3 +++
 tests/explicit-nulls/pos/i13486.scala          | 7 +++++++
 tests/explicit-nulls/pos/i13608.scala          | 3 +++
 5 files changed, 18 insertions(+), 2 deletions(-)
 create mode 100644 tests/explicit-nulls/pos/i13040/Impl.scala
 create mode 100644 tests/explicit-nulls/pos/i13040/Intf.java
 create mode 100644 tests/explicit-nulls/pos/i13486.scala
 create mode 100644 tests/explicit-nulls/pos/i13608.scala

diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala
index 457b8c4c94f9..5aa57d155e10 100644
--- a/compiler/src/dotty/tools/dotc/core/Types.scala
+++ b/compiler/src/dotty/tools/dotc/core/Types.scala
@@ -4938,8 +4938,6 @@ object Types {
     }
 
     def & (that: TypeBounds)(using Context): TypeBounds =
-      // if ((that.lo frozen_<:< this.lo) && (this.hi frozen_<:< that.hi)) this
-      // else if ((this.lo frozen_<:< that.lo) && (that.hi frozen_<:< this.hi)) that
       if ((this.lo frozen_<:< that.lo) && (that.hi frozen_<:< this.hi)) that
       else if ((that.lo frozen_<:< this.lo) && (this.hi frozen_<:< that.hi)) this
       else TypeBounds(this.lo | that.lo, this.hi & that.hi)
diff --git a/tests/explicit-nulls/pos/i13040/Impl.scala b/tests/explicit-nulls/pos/i13040/Impl.scala
new file mode 100644
index 000000000000..d26927db2e94
--- /dev/null
+++ b/tests/explicit-nulls/pos/i13040/Impl.scala
@@ -0,0 +1,5 @@
+class Impl extends Intf:
+  override def test(x: Object | Null*): Unit = ???
+
+class Impl2 extends Intf:
+  override def test(x: Object*): Unit = ???
diff --git a/tests/explicit-nulls/pos/i13040/Intf.java b/tests/explicit-nulls/pos/i13040/Intf.java
new file mode 100644
index 000000000000..816c94485cc4
--- /dev/null
+++ b/tests/explicit-nulls/pos/i13040/Intf.java
@@ -0,0 +1,3 @@
+interface Intf {
+    void test(Object... x);
+}
diff --git a/tests/explicit-nulls/pos/i13486.scala b/tests/explicit-nulls/pos/i13486.scala
new file mode 100644
index 000000000000..d9fafc157d4c
--- /dev/null
+++ b/tests/explicit-nulls/pos/i13486.scala
@@ -0,0 +1,7 @@
+class MyPrintStream extends java.io.PrintStream(??? : java.io.OutputStream):
+  override def printf(format: String | Null, args: Array[? <: Object | Null])
+  : java.io.PrintStream | Null = ???
+
+class MyPrintStream2 extends java.io.PrintStream(??? : java.io.OutputStream):
+  override def printf(format: String, args: Array[? <: Object])
+  : java.io.PrintStream = ???
diff --git a/tests/explicit-nulls/pos/i13608.scala b/tests/explicit-nulls/pos/i13608.scala
new file mode 100644
index 000000000000..29f9f8692086
--- /dev/null
+++ b/tests/explicit-nulls/pos/i13608.scala
@@ -0,0 +1,3 @@
+import scala.util.control.NoStackTrace
+
+case class ParseException(line: Int, character: Int, message: String) extends NoStackTrace

From 9729df4f3f6a08f7505d7c81f5904d45f2af8899 Mon Sep 17 00:00:00 2001
From: noti0na1 
Date: Tue, 5 Oct 2021 20:47:03 +0000
Subject: [PATCH 4/6] Move logic, handle FromJavaObject in glb and lub as well

---
 .../dotty/tools/dotc/core/TypeComparer.scala  | 27 ++++---------------
 .../src/dotty/tools/dotc/core/Types.scala     | 19 +++++++++++--
 2 files changed, 22 insertions(+), 24 deletions(-)

diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala
index 33ceb3bdbba0..b169478e5dbc 100644
--- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala
+++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala
@@ -1505,33 +1505,12 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
             false
         }
 
-        // This & will try to preserve the FromJavaObjects type in upper bounds
-        // For example, (? <: FromJavaObjects | Null) & (? <: Any),
-        // we want to get (? <: FromJavaObjects | Null) intead of (? <: Any),
-        // because we may check FromJavaObjects | Null <:< Object | Null later.
-        def bounds_&(tp1: TypeBounds, tp2: TypeBounds) =
-          if tp1.hi.containsFromJavaObject
-            && (tp1.hi frozen_<:< tp2.hi)
-            && (tp2.lo frozen_<:< tp1.lo) then
-            // FromJavaObject in tp1.hi guarantees tp2.hi <:< tp1.hi
-            // prefer tp1 if FromJavaObject is in its hi
-            tp1
-          else if tp2.hi.containsFromJavaObject
-            && (tp2.hi frozen_<:< tp1.hi)
-            && (tp1.lo frozen_<:< tp2.lo) then
-            // Similarly, prefer tp2 if FromJavaObject is in its hi
-            tp2
-          else
-            // Use regular & to solve other cases
-            tp1 & tp2
-
         def isSubArg(arg1: Type, arg2: Type): Boolean = arg2 match {
           case arg2: TypeBounds =>
             val arg1norm = arg1 match {
               case arg1: TypeBounds =>
                 tparam match {
-                  case tparam: Symbol =>
-                    bounds_&(arg1, paramBounds(tparam))
+                  case tparam: Symbol => arg1 & paramBounds(tparam)
                   case _ => arg1 // This case can only arise when a hk-type is illegally instantiated with a wildcard
                 }
               case _ => arg1
@@ -2062,6 +2041,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
     else if (!tp2.exists) tp1
     else if tp1.isAny && !tp2.isLambdaSub || tp1.isAnyKind || isBottom(tp2) then tp2
     else if tp2.isAny && !tp1.isLambdaSub || tp2.isAnyKind || isBottom(tp1) then tp1
+    else if tp1.isFromJavaObject && !tp2.isLambdaSub then tp2
+    else if tp2.isFromJavaObject && !tp1.isLambdaSub then tp1
     else tp2 match
       case tp2: LazyRef =>
         glb(tp1, tp2.ref)
@@ -2110,6 +2091,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
     if (tp1 eq tp2) tp1
     else if (!tp1.exists) tp1
     else if (!tp2.exists) tp2
+    else if tp1.isFromJavaObject && !tp2.isLambdaSub then tp1
+    else if tp2.isFromJavaObject && !tp1.isLambdaSub then tp2
     else if tp1.isAny && !tp2.isLambdaSub || tp1.isAnyKind || isBottom(tp2) then tp1
     else if tp2.isAny && !tp1.isLambdaSub || tp2.isAnyKind || isBottom(tp1) then tp2
     else
diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala
index 5aa57d155e10..cd5df4f78614 100644
--- a/compiler/src/dotty/tools/dotc/core/Types.scala
+++ b/compiler/src/dotty/tools/dotc/core/Types.scala
@@ -4938,8 +4938,23 @@ object Types {
     }
 
     def & (that: TypeBounds)(using Context): TypeBounds =
-      if ((this.lo frozen_<:< that.lo) && (that.hi frozen_<:< this.hi)) that
-      else if ((that.lo frozen_<:< this.lo) && (this.hi frozen_<:< that.hi)) this
+      // This will try to preserve the FromJavaObjects type in upper bounds.
+      // For example, (? <: FromJavaObjects | Null) & (? <: Any),
+      // we want to get (? <: FromJavaObjects | Null) intead of (? <: Any),
+      // because we may check the result <:< (? <: Object | Null) later.
+      if this.hi.containsFromJavaObject
+        && (this.hi frozen_<:< that.hi)
+        && (that.lo frozen_<:< this.lo) then
+        // FromJavaObject in tp1.hi guarantees tp2.hi <:< tp1.hi
+        // prefer tp1 if FromJavaObject is in its hi
+        this
+      else if that.hi.containsFromJavaObject
+        && (that.hi frozen_<:< this.hi)
+        && (this.lo frozen_<:< that.lo) then
+        // Similarly, prefer tp2 if FromJavaObject is in its hi
+        that
+      else if (this.lo frozen_<:< that.lo) && (that.hi frozen_<:< this.hi) then that
+      else if (that.lo frozen_<:< this.lo) && (this.hi frozen_<:< that.hi) then this
       else TypeBounds(this.lo | that.lo, this.hi & that.hi)
 
     def | (that: TypeBounds)(using Context): TypeBounds =

From 644319dde64a5dbee86b3dd4aabab9bac314b27f Mon Sep 17 00:00:00 2001
From: noti0na1 
Date: Tue, 5 Oct 2021 23:10:47 +0000
Subject: [PATCH 5/6] Handle FromJavaObject union

---
 .../src/dotty/tools/dotc/core/TypeComparer.scala     | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala
index b169478e5dbc..6e69ae4239b5 100644
--- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala
+++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala
@@ -2041,8 +2041,10 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
     else if (!tp2.exists) tp1
     else if tp1.isAny && !tp2.isLambdaSub || tp1.isAnyKind || isBottom(tp2) then tp2
     else if tp2.isAny && !tp1.isLambdaSub || tp2.isAnyKind || isBottom(tp1) then tp1
-    else if tp1.isFromJavaObject && !tp2.isLambdaSub then tp2
-    else if tp2.isFromJavaObject && !tp1.isLambdaSub then tp1
+    else if tp1.containsFromJavaObject && !tp2.isLambdaSub then
+      tp2 // If tp1 is FromJavaObject or a union containing FromJavaObject, tp2 <:< tp1
+    else if tp2.containsFromJavaObject && !tp1.isLambdaSub then
+      tp1 // Similarly, tp1 <:< tp2
     else tp2 match
       case tp2: LazyRef =>
         glb(tp1, tp2.ref)
@@ -2091,8 +2093,10 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
     if (tp1 eq tp2) tp1
     else if (!tp1.exists) tp1
     else if (!tp2.exists) tp2
-    else if tp1.isFromJavaObject && !tp2.isLambdaSub then tp1
-    else if tp2.isFromJavaObject && !tp1.isLambdaSub then tp2
+    else if tp1.containsFromJavaObject && !tp2.isLambdaSub then
+      tp1 // If tp1 is FromJavaObject or a union containing FromJavaObject, tp2 <:< tp1
+    else if tp2.containsFromJavaObject && !tp1.isLambdaSub then
+      tp2 // Similarly, tp1 <:< tp2
     else if tp1.isAny && !tp2.isLambdaSub || tp1.isAnyKind || isBottom(tp2) then tp1
     else if tp2.isAny && !tp1.isLambdaSub || tp2.isAnyKind || isBottom(tp1) then tp2
     else

From 79931f33aee586ae74af93c16c77f398e623fc2f Mon Sep 17 00:00:00 2001
From: noti0na1 
Date: Wed, 6 Oct 2021 14:13:57 +0000
Subject: [PATCH 6/6] Remove unnecessary logic in glb and lub

---
 compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 8 --------
 1 file changed, 8 deletions(-)

diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala
index 6e69ae4239b5..49cfb2b6191c 100644
--- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala
+++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala
@@ -2041,10 +2041,6 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
     else if (!tp2.exists) tp1
     else if tp1.isAny && !tp2.isLambdaSub || tp1.isAnyKind || isBottom(tp2) then tp2
     else if tp2.isAny && !tp1.isLambdaSub || tp2.isAnyKind || isBottom(tp1) then tp1
-    else if tp1.containsFromJavaObject && !tp2.isLambdaSub then
-      tp2 // If tp1 is FromJavaObject or a union containing FromJavaObject, tp2 <:< tp1
-    else if tp2.containsFromJavaObject && !tp1.isLambdaSub then
-      tp1 // Similarly, tp1 <:< tp2
     else tp2 match
       case tp2: LazyRef =>
         glb(tp1, tp2.ref)
@@ -2093,10 +2089,6 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
     if (tp1 eq tp2) tp1
     else if (!tp1.exists) tp1
     else if (!tp2.exists) tp2
-    else if tp1.containsFromJavaObject && !tp2.isLambdaSub then
-      tp1 // If tp1 is FromJavaObject or a union containing FromJavaObject, tp2 <:< tp1
-    else if tp2.containsFromJavaObject && !tp1.isLambdaSub then
-      tp2 // Similarly, tp1 <:< tp2
     else if tp1.isAny && !tp2.isLambdaSub || tp1.isAnyKind || isBottom(tp2) then tp1
     else if tp2.isAny && !tp1.isLambdaSub || tp2.isAnyKind || isBottom(tp1) then tp2
     else