From 8f2b6cf0dc2d681be6c585572e7cf0680e627d2e Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 22 Feb 2024 13:59:04 +0000 Subject: [PATCH 1/7] Approximate MatchTypes with lub of case bodies, if non-recursive --- .../src/dotty/tools/dotc/core/TypeComparer.scala | 13 ++++++++++++- tests/pos/i19710.scala | 11 +++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i19710.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index b23bfe9fe14b..9373889b37d6 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -1030,6 +1030,17 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling if tp1 ne tp1norm then recur(tp1norm, tp2) else either(recur(tp11, tp2), recur(tp12, tp2)) case tp1: MatchType => + def compareUpper = + val lub1 = tp1.cases.foldLeft(defn.NothingType: Type): (acc, case1) => + if acc.exists then + val rhs = case1.resultType match { case defn.MatchCase(_, body) => body } + val isRecursive = rhs.existsPart(_.isInstanceOf[LazyRef]) + if isRecursive then NoType else lub(acc, rhs) + else acc + if lub1.exists then + recur(lub1, tp2) + else + recur(tp1.underlying, tp2) def compareMatch = tp2 match { case tp2: MatchType => // we allow a small number of scrutinee types to be widened: @@ -1047,7 +1058,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling tp1.cases.corresponds(tp2.cases)(isSubType) case _ => false } - (!caseLambda.exists || canWidenAbstract) && recur(tp1.underlying, tp2) || compareMatch + (!caseLambda.exists || canWidenAbstract) && compareUpper || compareMatch case tp1: AnnotatedType if tp1.isRefining => isNewSubType(tp1.parent) case JavaArrayType(elem1) => diff --git a/tests/pos/i19710.scala b/tests/pos/i19710.scala new file mode 100644 index 000000000000..03fd1e2d80b3 --- /dev/null +++ b/tests/pos/i19710.scala @@ -0,0 +1,11 @@ +import scala.util.NotGiven + +type HasName1 = [n] =>> [x] =>> x match { + case n => true + case _ => false + } +@main def Test = { + summon[HasName1["foo"]["foo"] =:= true] + summon[NotGiven[HasName1["foo"]["bar"] =:= true]] + summon[Tuple.Filter[(1, "foo", 2, "bar"), HasName1["foo"]] =:= Tuple1["foo"]] // error +} From c25eddec7f4340fc8fa39b9985d4f3e7929af067 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 26 Feb 2024 17:28:56 +0000 Subject: [PATCH 2/7] Lub MatchTypeTree case body types and store as the inferred bound --- .../src/dotty/tools/dotc/core/TypeComparer.scala | 13 +------------ compiler/src/dotty/tools/dotc/typer/Typer.scala | 10 +++++++++- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 9373889b37d6..b23bfe9fe14b 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -1030,17 +1030,6 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling if tp1 ne tp1norm then recur(tp1norm, tp2) else either(recur(tp11, tp2), recur(tp12, tp2)) case tp1: MatchType => - def compareUpper = - val lub1 = tp1.cases.foldLeft(defn.NothingType: Type): (acc, case1) => - if acc.exists then - val rhs = case1.resultType match { case defn.MatchCase(_, body) => body } - val isRecursive = rhs.existsPart(_.isInstanceOf[LazyRef]) - if isRecursive then NoType else lub(acc, rhs) - else acc - if lub1.exists then - recur(lub1, tp2) - else - recur(tp1.underlying, tp2) def compareMatch = tp2 match { case tp2: MatchType => // we allow a small number of scrutinee types to be widened: @@ -1058,7 +1047,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling tp1.cases.corresponds(tp2.cases)(isSubType) case _ => false } - (!caseLambda.exists || canWidenAbstract) && compareUpper || compareMatch + (!caseLambda.exists || canWidenAbstract) && recur(tp1.underlying, tp2) || compareMatch case tp1: AnnotatedType if tp1.isRefining => isNewSubType(tp1.parent) case JavaArrayType(elem1) => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index f1db302e958c..b96aa75b70c4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2373,7 +2373,15 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer report.error(MatchTypeScrutineeCannotBeHigherKinded(sel1Tpe), sel1.srcPos) val pt1 = if (bound1.isEmpty) pt else bound1.tpe val cases1 = tree.cases.mapconserve(typedTypeCase(_, sel1Tpe, pt1)) - assignType(cpy.MatchTypeTree(tree)(bound1, sel1, cases1), bound1, sel1, cases1) + val bound2 = if tree.bound.isEmpty then + val lub = cases1.foldLeft(defn.NothingType: Type): (acc, case1) => + if !acc.exists then NoType + else if case1.body.tpe.existsPart(_.isInstanceOf[LazyRef]) then NoType + else acc | TypeOps.avoid(case1.body.tpe, patVars(case1)) + if lub.exists then TypeTree(lub, inferred = true) + else bound1 + else bound1 + assignType(cpy.MatchTypeTree(tree)(bound2, sel1, cases1), bound2, sel1, cases1) } def typedByNameTypeTree(tree: untpd.ByNameTypeTree)(using Context): ByNameTypeTree = tree.result match From 55ecbfca4057908025b3551a29d3b6130647a725 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 27 Feb 2024 13:48:48 +0000 Subject: [PATCH 3/7] Use isProvisional instead of has LazyRef --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 4 ++-- tests/pos/Tuple.Drop.scala | 7 +++++++ tests/pos/Tuple.Elem.scala | 7 +++++++ 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 tests/pos/Tuple.Drop.scala create mode 100644 tests/pos/Tuple.Elem.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index b96aa75b70c4..2a8674df0155 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2376,8 +2376,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val bound2 = if tree.bound.isEmpty then val lub = cases1.foldLeft(defn.NothingType: Type): (acc, case1) => if !acc.exists then NoType - else if case1.body.tpe.existsPart(_.isInstanceOf[LazyRef]) then NoType - else acc | TypeOps.avoid(case1.body.tpe, patVars(case1)) + else if case1.body.tpe.isProvisional then NoType + else acc | case1.body.tpe if lub.exists then TypeTree(lub, inferred = true) else bound1 else bound1 diff --git a/tests/pos/Tuple.Drop.scala b/tests/pos/Tuple.Drop.scala new file mode 100644 index 000000000000..9b88cc227966 --- /dev/null +++ b/tests/pos/Tuple.Drop.scala @@ -0,0 +1,7 @@ +import compiletime.ops.int.* + +type Drop[T <: Tuple, N <: Int] <: Tuple = N match + case 0 => T + case S[n1] => T match + case EmptyTuple => EmptyTuple + case x *: xs => Drop[xs, n1] diff --git a/tests/pos/Tuple.Elem.scala b/tests/pos/Tuple.Elem.scala new file mode 100644 index 000000000000..81494485c321 --- /dev/null +++ b/tests/pos/Tuple.Elem.scala @@ -0,0 +1,7 @@ +import compiletime.ops.int.* + +type Elem[T <: Tuple, I <: Int] = T match + case h *: tail => + I match + case 0 => h + case S[j] => Elem[tail, j] From 3419aae7d548ac9a74c4139852bf23a66302696d Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 27 Feb 2024 15:09:38 +0000 Subject: [PATCH 4/7] Fix PlusTri in pos/13633 --- tests/pos/13633.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pos/13633.scala b/tests/pos/13633.scala index ca0f7e68e81e..8883ef98d0be 100644 --- a/tests/pos/13633.scala +++ b/tests/pos/13633.scala @@ -21,7 +21,7 @@ object Sums extends App: type Reverse[A] = ReverseLoop[A, EmptyTuple] - type PlusTri[A, B, C] = (A, B, C) match + type PlusTri[A, B, C] <: Tuple = (A, B, C) match case (false, false, false) => (false, false) case (true, false, false) | (false, true, false) | (false, false, true) => (false, true) case (true, true, false) | (true, false, true) | (false, true, true) => (true, false) From 2423198a8b68e870a42890bf325866e7acfa0942 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 27 Feb 2024 15:27:50 +0000 Subject: [PATCH 5/7] Handle NoType in disjointnessBoundary --- compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index b23bfe9fe14b..0be68b119d40 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -2863,6 +2863,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling disjointnessBoundary(tp.effectiveBounds.hi) case tp: ErrorType => defn.AnyType + case NoType => // ParamRef#superTypeNormalized can return NoType + defn.AnyType end disjointnessBoundary (disjointnessBoundary(tp1), disjointnessBoundary(tp2)) match From db1dcdc0e11516a61c4edc25776a6ea0d5dacf78 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 28 Feb 2024 10:26:46 +0000 Subject: [PATCH 6/7] Fix run-macros/type-show --- tests/run-macros/type-show/Test_2.scala | 30 +++++++++++++++++++------ 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/tests/run-macros/type-show/Test_2.scala b/tests/run-macros/type-show/Test_2.scala index ace303a6596e..de845f3e84dd 100644 --- a/tests/run-macros/type-show/Test_2.scala +++ b/tests/run-macros/type-show/Test_2.scala @@ -1,18 +1,34 @@ object Test { import TypeToolbox.* + + def assertEql[A](obt: A, exp: A): Unit = + assert(obt == exp, s"\nexpected: $exp\nobtained: $obt") + def main(args: Array[String]): Unit = { val x = 5 - assert(show[x.type] == "x.type") - assert(show[Nil.type] == "scala.Nil.type") - assert(show[Int] == "scala.Int") - assert(show[Int => Int] == "scala.Function1[scala.Int, scala.Int]") - assert(show[(Int, String)] == "scala.Tuple2[scala.Int, scala.Predef.String]") - assert(show[[X] =>> X match { case Int => Int }] == + assertEql(show[x.type], "x.type") + assertEql(show[Nil.type], "scala.Nil.type") + assertEql(show[Int], "scala.Int") + assertEql(show[Int => Int], "scala.Function1[scala.Int, scala.Int]") + assertEql(show[(Int, String)], "scala.Tuple2[scala.Int, scala.Predef.String]") + assertEql(show[[X] =>> X match { case Int => Int }], """[X >: scala.Nothing <: scala.Any] =>> X match { | case scala.Int => scala.Int |}""".stripMargin) - assert(showStructure[[X] =>> X match { case Int => Int }] == """TypeLambda(List(X), List(TypeBounds(TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Nothing"), TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Any"))), MatchType(TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Any"), ParamRef(binder, 0), List(MatchCase(TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "")), "scala"), "Int"), TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "")), "scala"), "Int")))))""") + assertEql(showStructure[[X] =>> X match { case Int => Int }], + """TypeLambda("""+ + """List(X), """+ + """List(TypeBounds("""+ + """TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Nothing"), """+ + """TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Any"))), """+ + """MatchType("""+ + """TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "")), "scala"), "Int"), """+ // match type bound + """ParamRef(binder, 0), """+ + """List("""+ + """MatchCase("""+ + """TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "")), "scala"), "Int"), """+ + """TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "")), "scala"), "Int")))))""") // TODO: more complex types: // - implicit function types From d687dee2edac1078526af7e14837c742d458bb4d Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 4 Mar 2024 13:14:28 +0000 Subject: [PATCH 7/7] Rework ParamRef#underlying handling in disjointnessBoundary --- compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 0be68b119d40..9128b1f11e45 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -2857,14 +2857,19 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling tp case tp: HKTypeLambda => tp + case tp: ParamRef => + val st = tp.superTypeNormalized + if st.exists then + disjointnessBoundary(st) + else + // workaround for when ParamRef#underlying returns NoType + defn.AnyType case tp: TypeProxy => disjointnessBoundary(tp.superTypeNormalized) case tp: WildcardType => disjointnessBoundary(tp.effectiveBounds.hi) case tp: ErrorType => defn.AnyType - case NoType => // ParamRef#superTypeNormalized can return NoType - defn.AnyType end disjointnessBoundary (disjointnessBoundary(tp1), disjointnessBoundary(tp2)) match