From 836db8def414804b658f4ae41d944f46b3303d22 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 17 Jan 2023 20:45:37 +0100 Subject: [PATCH 1/3] Throw a type error iwhen using hk-types in unions or intersections Throw a type error instead of crashing when using higher-kinded types in unions or intersections. We check kindedness only in PostTyper (to avoid cycles), which means we might get into situations where we coming a higher-kinded type in a union or intersection, which is illegal. In this case we now diagnose the problem with a TypeError instead of failing an assert. However, after Typer we do fail since by then such situations should have been checked by then. Fixeds #16696 --- .../src/dotty/tools/dotc/core/Types.scala | 23 +++++++++++-------- tests/neg/i16696.check | 12 ++++++++++ tests/neg/i16696.scala | 20 ++++++++++++++++ 3 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 tests/neg/i16696.check create mode 100644 tests/neg/i16696.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 0c5614e29a60..5d06d4b4aae6 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1071,7 +1071,7 @@ object Types { * @param relaxedCheck if true type `Null` becomes a subtype of non-primitive value types in TypeComparer. * @param matchLoosely if true the types `=> T` and `()T` are seen as overriding each other. * @param checkClassInfo if true we check that ClassInfos are within bounds of abstract types - * + * * @param isSubType a function used for checking subtype relationships. */ final def overrides(that: Type, relaxedCheck: Boolean, matchLoosely: => Boolean, checkClassInfo: Boolean = true, @@ -3322,11 +3322,11 @@ object Types { final class CachedAndType(tp1: Type, tp2: Type) extends AndType(tp1, tp2) object AndType { - def apply(tp1: Type, tp2: Type)(using Context): AndType = { - assert(tp1.isValueTypeOrWildcard && - tp2.isValueTypeOrWildcard, i"$tp1 & $tp2 / " + s"$tp1 & $tp2") + def apply(tp1: Type, tp2: Type)(using Context): AndType = + def where = i"in intersection $tp1 & $tp2" + expectValueTypeOrWildcard(tp1, where) + expectValueTypeOrWildcard(tp2, where) unchecked(tp1, tp2) - } def balanced(tp1: Type, tp2: Type)(using Context): AndType = tp1 match @@ -3366,7 +3366,7 @@ object Types { TypeComparer.liftIfHK(tp1, tp2, AndType.make(_, _, checkValid = false), makeHk, _ | _) } - abstract case class OrType(tp1: Type, tp2: Type) extends AndOrType { + abstract case class OrType protected(tp1: Type, tp2: Type) extends AndOrType { def isAnd: Boolean = false def isSoft: Boolean private var myBaseClassesPeriod: Period = Nowhere @@ -3399,9 +3399,6 @@ object Types { myFactorCount else 1 - assert(tp1.isValueTypeOrWildcard && - tp2.isValueTypeOrWildcard, s"$tp1 $tp2") - private var myJoin: Type = _ private var myJoinPeriod: Period = Nowhere @@ -3476,6 +3473,9 @@ object Types { object OrType { def apply(tp1: Type, tp2: Type, soft: Boolean)(using Context): OrType = { + def where = i"in union $tp1 | $tp2" + expectValueTypeOrWildcard(tp1, where) + expectValueTypeOrWildcard(tp2, where) assertUnerased() unique(new CachedOrType(tp1, tp2, soft)) } @@ -3506,6 +3506,11 @@ object Types { TypeComparer.liftIfHK(tp1, tp2, OrType(_, _, soft = true), makeHk, _ & _) } + def expectValueTypeOrWildcard(tp: Type, where: => String)(using Context): Unit = + if !tp.isValueTypeOrWildcard then + assert(!ctx.isAfterTyper, where) // we check correct kinds at PostTyper + throw TypeError(em"$tp is not a value type, cannot be used in $where") + /** An extractor object to pattern match against a nullable union. * e.g. * diff --git a/tests/neg/i16696.check b/tests/neg/i16696.check new file mode 100644 index 000000000000..c140a18ae720 --- /dev/null +++ b/tests/neg/i16696.check @@ -0,0 +1,12 @@ +-- Error: tests/neg/i16696.scala:7:29 ---------------------------------------------------------------------------------- +7 | val boom1 = BoxMaker[Some].make1 // error + | ^ + | Some is not a value type, cannot be used in in intersection Some & Int +-- Error: tests/neg/i16696.scala:8:29 ---------------------------------------------------------------------------------- +8 | val boom2 = BoxMaker[Some].make2 // error + | ^ + | Some is not a value type, cannot be used in in union Some | Int +-- Error: tests/neg/i16696.scala:20:27 --------------------------------------------------------------------------------- +20 | val boom = BoxMaker[Foo].make(_.foo) // error + | ^ + | test2.Foo is not a value type, cannot be used in in intersection R & test2.Foo diff --git a/tests/neg/i16696.scala b/tests/neg/i16696.scala new file mode 100644 index 000000000000..f54b884960fa --- /dev/null +++ b/tests/neg/i16696.scala @@ -0,0 +1,20 @@ +object test1: + class BoxMaker[T] { + def make1: T & Int = ??? + def make2: T | Int = ??? + } + + val boom1 = BoxMaker[Some].make1 // error + val boom2 = BoxMaker[Some].make2 // error + +object test2: + class Box[R] + + class BoxMaker[T] { + def make[R <: T](f: T => Box[R]): Box[R & T] = ??? + } + + trait Foo[A]{ + def foo: Box[Foo[Unit]] + } + val boom = BoxMaker[Foo].make(_.foo) // error From 1094b5831d9369c534397962ebbee39be7c46ee7 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 17 Jan 2023 22:59:17 +0100 Subject: [PATCH 2/3] Fix test --- compiler/test/dotty/tools/repl/TabcompleteTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/test/dotty/tools/repl/TabcompleteTests.scala b/compiler/test/dotty/tools/repl/TabcompleteTests.scala index ab735a07b092..910584a9b5e7 100644 --- a/compiler/test/dotty/tools/repl/TabcompleteTests.scala +++ b/compiler/test/dotty/tools/repl/TabcompleteTests.scala @@ -235,6 +235,6 @@ class TabcompleteTests extends ReplTest { } @Test def i9334 = initially { - assertEquals(Nil, tabComplete("class Foo[T]; classOf[Foo].")) + assert(tabComplete("class Foo[T]; classOf[Foo].").contains("getName")) } } From b1ae4bbb00ec371ba32543971e3e7e7fc250408e Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 17 Jan 2023 23:31:56 +0100 Subject: [PATCH 3/3] Fix error message --- compiler/src/dotty/tools/dotc/core/Types.scala | 2 +- tests/neg/i16696.check | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 5d06d4b4aae6..b8b6b7248ee2 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3509,7 +3509,7 @@ object Types { def expectValueTypeOrWildcard(tp: Type, where: => String)(using Context): Unit = if !tp.isValueTypeOrWildcard then assert(!ctx.isAfterTyper, where) // we check correct kinds at PostTyper - throw TypeError(em"$tp is not a value type, cannot be used in $where") + throw TypeError(em"$tp is not a value type, cannot be used $where") /** An extractor object to pattern match against a nullable union. * e.g. diff --git a/tests/neg/i16696.check b/tests/neg/i16696.check index c140a18ae720..2cac6a9c595a 100644 --- a/tests/neg/i16696.check +++ b/tests/neg/i16696.check @@ -1,12 +1,12 @@ -- Error: tests/neg/i16696.scala:7:29 ---------------------------------------------------------------------------------- 7 | val boom1 = BoxMaker[Some].make1 // error | ^ - | Some is not a value type, cannot be used in in intersection Some & Int + | Some is not a value type, cannot be used in intersection Some & Int -- Error: tests/neg/i16696.scala:8:29 ---------------------------------------------------------------------------------- 8 | val boom2 = BoxMaker[Some].make2 // error | ^ - | Some is not a value type, cannot be used in in union Some | Int + | Some is not a value type, cannot be used in union Some | Int -- Error: tests/neg/i16696.scala:20:27 --------------------------------------------------------------------------------- 20 | val boom = BoxMaker[Foo].make(_.foo) // error | ^ - | test2.Foo is not a value type, cannot be used in in intersection R & test2.Foo + | test2.Foo is not a value type, cannot be used in intersection R & test2.Foo