Skip to content

SIP-56-extended: Refined provablyDisjoint test. #18416

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
62dda65
Update the perspective community build project to the latest upstream.
sjrd Aug 9, 2023
9f5aa6f
Update `perspective` to be compatible with the current state of `main`.
sjrd Aug 9, 2023
a377544
Introduce MatchTypeCaseSpec to categorize match type cases.
sjrd Jul 20, 2023
c61674b
Use new specced match types for class type constructors.
sjrd Aug 3, 2023
223be76
Use new specced match types for `scala.compiletime.int.S[n]`.
sjrd Aug 4, 2023
3f17d5a
Short-circuit match type cases with missing captures in their patterns.
sjrd Aug 4, 2023
628e50f
Use the specced match types for abstract tycons in patterns.
sjrd Aug 4, 2023
9c19ada
Make sure in CI that we do not unexpectedly fall back on legacy match…
sjrd Aug 7, 2023
048951c
Handle type member extractors as specced match types.
sjrd Aug 8, 2023
ccf7416
Introduce the SourceVersions 3.4 and 3.4-migration; make 3.4 the defa…
sjrd Aug 8, 2023
ff710e4
Disable legacy match types under source version >= 3.4.
sjrd Aug 8, 2023
bff2de6
Allow to reduce type member extractors when the member is a class.
sjrd Aug 14, 2023
aea28b9
Simplify the type member extractor logic.
sjrd Aug 15, 2023
12e74ac
SIP-56-extended: Simpler provablyDisjoint test.
sjrd Aug 17, 2023
a84fbb1
Use a `pending` set to detect infinite recursion.
sjrd Aug 28, 2023
4086ada
Remove disjointness of invariant type params based on isSameType.
sjrd Aug 28, 2023
f876971
Be more specific about higher-kinded types in provablyDisjoint.
sjrd Aug 29, 2023
cb94367
Do not use provablyEmpty anymore; use S <: T + provablyDisjoint(S, T)…
sjrd Aug 30, 2023
980c9e7
Add regression tests for old issues fixed with the new match types.
sjrd Aug 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
370 changes: 245 additions & 125 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions tests/neg/6314-1.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
object G {
final class X
final class Y
trait X
class Y
class Z

trait FooSig {
type Type
Expand All @@ -13,14 +14,14 @@ object G {
type Foo = Foo.Type

type Bar[A] = A match {
case X & Y => String
case X & Z => String
case Y => Int
}

def main(args: Array[String]): Unit = {
val a: Bar[X & Y] = "hello" // error
val i: Bar[Y & Foo] = Foo.apply[Bar](a)
val b: Int = i // error
val b: Int = i
println(b + 1)
}
}
16 changes: 16 additions & 0 deletions tests/neg/6314-6.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-- Error: tests/neg/6314-6.scala:26:3 ----------------------------------------------------------------------------------
26 | (new YY {}).boom // error: object creation impossible
| ^
|object creation impossible, since def apply(fa: String): Int in trait XX in object Test3 is not defined
|(Note that
| parameter String in def apply(fa: String): Int in trait XX in object Test3 does not match
| parameter Test3.Bar[X & Object with Test3.YY {...}#Foo] in def apply(fa: Test3.Bar[X & YY.this.Foo]): Test3.Bar[Y & YY.this.Foo] in trait YY in object Test3
| )
-- Error: tests/neg/6314-6.scala:52:3 ----------------------------------------------------------------------------------
52 | (new YY {}).boom // error: object creation impossible
| ^
|object creation impossible, since def apply(fa: String): Int in trait XX in object Test4 is not defined
|(Note that
| parameter String in def apply(fa: String): Int in trait XX in object Test4 does not match
| parameter Test4.Bar[X & Object with Test4.YY {...}#FooAlias] in def apply(fa: Test4.Bar[X & YY.this.FooAlias]): Test4.Bar[Y & YY.this.FooAlias] in trait YY in object Test4
| )
12 changes: 4 additions & 8 deletions tests/neg/6314-6.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,9 @@ object Test3 {
trait YY extends XX {
type Foo = X & Y

def apply(fa: Bar[X & Foo]): Bar[Y & Foo] = fa // error
// overriding method apply in trait XX of type (fa: String): Int;
// method apply of type (fa: String): String has incompatible type
def apply(fa: Bar[X & Foo]): Bar[Y & Foo] = fa
}
(new YY {}).boom
(new YY {}).boom // error: object creation impossible
}

object Test4 {
Expand All @@ -49,9 +47,7 @@ object Test4 {
trait YY extends XX {
type Foo = X & Y

def apply(fa: Bar[X & FooAlias]): Bar[Y & FooAlias] = fa // error
// overriding method apply in trait XX of type (fa: String): Int;
// method apply of type (fa: String): String has incompatible type
def apply(fa: Bar[X & FooAlias]): Bar[Y & FooAlias] = fa
}
(new YY {}).boom
(new YY {}).boom // error: object creation impossible
}
44 changes: 44 additions & 0 deletions tests/neg/6314.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
-- [E007] Type Mismatch Error: tests/neg/6314.scala:28:27 --------------------------------------------------------------
28 | val i: Bar[Y | Type] = 1 // error
| ^
| Found: (1 : Int)
| Required: Test1Bis.Bar[Test1Bis.Y | Test.this.Type]
|
| Note: a match type could not be fully reduced:
|
| trying to reduce Test1Bis.Bar[Test1Bis.Y | Test.this.Type]
| failed since selector Test1Bis.Y | Test.this.Type
| does not match case Test1Bis.X & Test1Bis.Y => String
| and cannot be shown to be disjoint from it either.
| Therefore, reduction cannot advance to the remaining case
|
| case Any => Int
|
| longer explanation available when compiling with `-explain`
-- [E007] Type Mismatch Error: tests/neg/6314.scala:45:33 --------------------------------------------------------------
45 | def right(fa: Bar[L]): Int = fa // error
| ^^
| Found: (fa : Wizzle.this.Bar[L])
| Required: Int
|
| where: L is a type in trait Wizzle with bounds <: Int & Singleton
|
| longer explanation available when compiling with `-explain`
-- [E007] Type Mismatch Error: tests/neg/6314.scala:55:33 --------------------------------------------------------------
55 | def right(fa: Bar[L]): Int = fa // error
| ^^
| Found: (fa : Wazzlo.this.Bar[L])
| Required: Int
|
| where: L is a type in trait Wazzlo with bounds <: Int & AnyVal
|
| longer explanation available when compiling with `-explain`
-- [E007] Type Mismatch Error: tests/neg/6314.scala:65:33 --------------------------------------------------------------
65 | def right(fa: Bar[L]): Int = fa // error
| ^^
| Found: (fa : Wuzzlu.this.Bar[L])
| Required: Int
|
| where: L is a type in trait Wuzzlu with bounds <: String & AnyRef
|
| longer explanation available when compiling with `-explain`
31 changes: 25 additions & 6 deletions tests/neg/6314.scala
Original file line number Diff line number Diff line change
@@ -1,20 +1,39 @@
final class X
final class Y

object Test1 {
// X, Y and Z are unrelated, Y is provably disjoint from Z, but X is not provably disjoint with either
trait X
class Y
class Z

trait Test {
type Type
// This is testing that both permutations of the types in a &
// are taken into account by the intersection test
val i: Bar[Y & Type] = 1 // error
// are taken into account by the provablyDisjoint test
val i: Bar[Y & Type] = 1 // ok, disjoint from X & Z because Y and Z are disjoint
}

type Bar[A] = A match {
case X & Y => String
case X & Z => String
case Y => Int
}
}

object Test1Bis {
final class X
final class Y

trait Test {
type Type
// This is testing that both permutations of the types in a |
// are taken into account by the provablyDisjoint test
val i: Bar[Y | Type] = 1 // error
}

type Bar[A] = A match {
case X & Y => String
case Any => Int
}
}

object Test2 {
trait Wizzle[L <: Int with Singleton] {
type Bar[A] = A match {
Expand Down
15 changes: 15 additions & 0 deletions tests/neg/i13190.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

-- [E172] Type Error: tests/neg/i13190/B_2.scala:14:38 -----------------------------------------------------------------
14 | summon[FindField[R, "B"] =:= Double] // error
| ^
| Cannot prove that Test.FindField[Test.R, ("B" : String)] =:= Double.
|
| Note: a match type could not be fully reduced:
|
| trying to reduce Test.FindField[Test.R, ("B" : String)]
| failed since selector Test.R
| does not match case Opaque.FieldType[("B" : String), f] *: t => f
| and cannot be shown to be disjoint from it either.
| Therefore, reduction cannot advance to the remaining case
|
| case _ *: t => Test.FindField[t, ("B" : String)]
2 changes: 1 addition & 1 deletion tests/pos/i13190/A_1.scala → tests/neg/i13190/A_1.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
object Opaque {
opaque type FieldType[K, +V] <: V = V
}
}
2 changes: 1 addition & 1 deletion tests/pos/i13190/B_2.scala → tests/neg/i13190/B_2.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ object Test {
//val f2: Int = f

type R = FieldType["A", Int] *: FieldType["B", Double] *: FieldType["C", String] *: FieldType["D", Boolean] *: EmptyTuple
summon[FindField[R, "B"] =:= Double]
summon[FindField[R, "B"] =:= Double] // error
}
14 changes: 14 additions & 0 deletions tests/neg/i13190b.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- [E172] Type Error: tests/neg/i13190b.scala:18:38 --------------------------------------------------------------------
18 | summon[FindField[R, "B"] =:= Double] // error
| ^
| Cannot prove that Test.FindField[Test.R, ("B" : String)] =:= Double.
|
| Note: a match type could not be fully reduced:
|
| trying to reduce Test.FindField[Test.R, ("B" : String)]
| failed since selector Test.R
| does not match case Opaque.FieldType[("B" : String), f] *: t => f
| and cannot be shown to be disjoint from it either.
| Therefore, reduction cannot advance to the remaining case
|
| case _ *: t => Test.FindField[t, ("B" : String)]
19 changes: 19 additions & 0 deletions tests/neg/i13190b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
object Opaque {
opaque type FieldType[K, +V] <: V = V
}

import Opaque.*

object Test {
type FindField[R <: scala.Tuple, K] = R match {
case FieldType[K, f] *: t => f
case _ *: t => FindField[t, K]
}

val f: FieldType["A", Int] = ???
val f1: Int = f
//val f2: Int = f

type R = FieldType["A", Int] *: FieldType["B", Double] *: FieldType["C", String] *: FieldType["D", Boolean] *: EmptyTuple
Copy link
Member

@bishabosha bishabosha Aug 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've observed that a lot of people want to use this kind of pattern for type level records, what is the solution?

use class not opaque type?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you have to use classes. In fact this test was very obviously wrong. FieldType["A", Int] is clearly not disjoint from FieldType["B", Int] since they both map to Int! The fact that it passed before was definitely a bug, not a feature.

summon[FindField[R, "B"] =:= Double] // error
}
17 changes: 17 additions & 0 deletions tests/neg/i15312.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- [E007] Type Mismatch Error: tests/neg/i15312.scala:7:27 -------------------------------------------------------------
7 |val b: F[{type A = Int}] = "asd" // error
| ^^^^^
| Found: ("asd" : String)
| Required: F[Object{type A = Int}]
|
| Note: a match type could not be fully reduced:
|
| trying to reduce F[Object{type A = Int}]
| failed since selector Object{type A = Int}
| does not match case Object{type A = Float} => Int
| and cannot be shown to be disjoint from it either.
| Therefore, reduction cannot advance to the remaining case
|
| case Object{type A = Int} => String
|
| longer explanation available when compiling with `-explain`
2 changes: 1 addition & 1 deletion tests/pos/i15312.scala → tests/neg/i15312.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ type F[t] =
case {type A = Int} => String

val a: F[{type A = Float}] = 10
val b: F[{type A = Int}] = "asd" // Found:("asd" : String) Required: F[Object{A = Int}]
val b: F[{type A = Int}] = "asd" // error
2 changes: 2 additions & 0 deletions tests/pos/i15677.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// scalac: -Yno-deep-subtypes:false

class Inv[A]

class Foo[B, M[_]]
Expand Down
56 changes: 56 additions & 0 deletions tests/pos/match-type-disjoint-transitivity.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* Tests that the following property holds for a chosen set of types (S, T, U):
*
* If S <: T and T provably disjoint from U, then S provably disjoint from U.
*/

class Parent[T]
class Child[T] extends Parent[T]
trait ChildTrait[T] extends Parent[T]

class OtherClass

trait Common[A]
trait Left[A] extends Common[A]
trait Right[A] extends Common[A]

// Since Parent[Boolean] disjoint from Parent[Int], we must have Child[Boolean] also disjoint from Parent[Int]
object Test1:
type MT[X] = X match
case Parent[Int] => Int
case Parent[Boolean] => Boolean

def test(): Unit =
summon[MT[Parent[Int]] =:= Int]
summon[MT[Parent[Boolean]] =:= Boolean]

summon[MT[Child[Int]] =:= Int]
summon[MT[Child[Boolean]] =:= Boolean]
end test
end Test1

// Since Parent[Int] disjoint from OtherClass, we must have Child[Int] and ChildTrait[T] also disjoint from OtherClass
object Test2:
type MT[X] = X match
case OtherClass => Int
case Parent[Int] => Boolean

def test(): Unit =
summon[MT[OtherClass] =:= Int]
summon[MT[Parent[Int]] =:= Boolean]

summon[MT[Child[Int]] =:= Boolean]
summon[MT[ChildTrait[Int]] =:= Boolean]
end test
end Test2

// Since Common[Int] is disjoint from Right[Boolean], we must have Left[Int] disjoint from Right[Boolean]
object Test3:
type MT[X] = X match
case Right[Boolean] => Int
case Any => Boolean

def test(): Unit =
summon[MT[Common[Int]] =:= Boolean]
summon[MT[Left[Int]] =:= Boolean]
end test
end Test3