Skip to content
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

Backport "Fix regression in exhausitivity of HK types" #18374

Merged
merged 3 commits into from
Aug 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 8 additions & 16 deletions compiler/src/dotty/tools/dotc/transform/patmat/Space.scala
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ object SpaceEngine {
* The types should be atomic (non-decomposable) and unrelated (neither
* should be a subtype of the other).
*/
def intersectUnrelatedAtomicTypes(tp1: Type, tp2: Type)(sp: Space)(using Context): Space = trace(i"atomic intersection: ${AndType(tp1, tp2)}", debug) {
def intersectUnrelatedAtomicTypes(tp1: Type, tp2: Type)(sp: Space)(using Context): Space = trace(i"atomic intersection: ${AndType(tp1, tp2)}", debug, show) {
// Precondition: !isSubType(tp1, tp2) && !isSubType(tp2, tp1).
if !ctx.mode.is(Mode.SafeNulls) && (tp1.isNullType || tp2.isNullType) then
// Since projections of types don't include null, intersection with null is empty.
Expand Down Expand Up @@ -468,17 +468,8 @@ object SpaceEngine {
WildcardType

case tp @ AppliedType(tycon, args) =>
val args2 =
if tycon.isRef(defn.ArrayClass) then
args.map(arg => erase(arg, inArray = true, isValue = false))
else tycon.typeParams.lazyZip(args).map { (tparam, arg) =>
if isValue && tparam.paramVarianceSign == 0 then
// when matching against a value,
// any type argument for an invariant type parameter will be unchecked,
// meaning it won't fail to match against anything; thus the wildcard replacement
WildcardType
else erase(arg, inArray = false, isValue = false)
}
val inArray = tycon.isRef(defn.ArrayClass)
val args2 = args.map(arg => erase(arg, inArray = inArray, isValue = false))
tp.derivedAppliedType(erase(tycon, inArray, isValue = false), args2)

case tp @ OrType(tp1, tp2) =>
Expand Down Expand Up @@ -642,7 +633,7 @@ object SpaceEngine {
// For instance, from i15029, `decompose((X | Y).Field[T]) = [X.Field[T], Y.Field[T]]`.
parts.map(tp.derivedAppliedType(_, targs))

case tp if tp.classSymbol.isDecomposableToChildren =>
case tp if tp.isDecomposableToChildren =>
def getChildren(sym: Symbol): List[Symbol] =
sym.children.flatMap { child =>
if child eq sym then List(sym) // i3145: sealed trait Baz, val x = new Baz {}, Baz.children returns Baz...
Expand Down Expand Up @@ -678,8 +669,8 @@ object SpaceEngine {
rec(tp, Nil)
}

extension (cls: Symbol)
/** A type is decomposable to children if it's sealed,
extension (tp: Type)
Copy link
Contributor

Choose a reason for hiding this comment

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

This change changes binary compatibility of our public, yet private API.
However, I don't think anyone would try to access any of this extension method

Copy link
Member

Choose a reason for hiding this comment

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

We have enough problems and difficulties. Let's not find ways to add new ones. None of this is supported public API, and is subject to any change.

/** A type is decomposable to children if it has a simple kind, it's sealed,
* abstract (or a trait) - so its not a sealed concrete class that can be instantiated on its own,
* has no anonymous children, which we wouldn't be able to name as counter-examples,
* but does have children.
Expand All @@ -688,7 +679,8 @@ object SpaceEngine {
* A sealed trait with subclasses that then get removed after `refineUsingParent`, decomposes to the empty list.
* So that's why we consider whether a type has children. */
def isDecomposableToChildren(using Context): Boolean =
cls.is(Sealed) && cls.isOneOf(AbstractOrTrait) && !cls.hasAnonymousChild && cls.children.nonEmpty
val cls = tp.classSymbol
tp.hasSimpleKind && cls.is(Sealed) && cls.isOneOf(AbstractOrTrait) && !cls.hasAnonymousChild && cls.children.nonEmpty

val ListOfNoType = List(NoType)
val ListOfTypNoType = ListOfNoType.map(Typ(_, decomposed = true))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ object Test {
def err2[A, B](value: Foo[A, B], a: A => Int): B = value match {
case b: Bar[B] => // spurious // error
b.x
case _ => ??? // avoid fatal inexhaustivity warnings suppressing the uncheckable warning
}

def fail[A, B](value: Foo[A, B], a: A => Int): B = value match {
case b: Bar[Int] => // error
b.x
case _ => ??? // avoid fatal inexhaustivity warnings suppressing the uncheckable warning
}
}
2 changes: 2 additions & 0 deletions tests/neg-custom-args/isInstanceOf/enum-approx2.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ case class Fun[A, B](f: Exp[A => B]) extends Exp[A => B]
class Test {
def eval(e: Fun[Int, Int]) = e match {
case Fun(x: Fun[Int, Double]) => ??? // error
case Fun(x: Exp[Int => String]) => ??? // error
case _ =>
}
}
1 change: 1 addition & 0 deletions tests/neg-custom-args/isInstanceOf/i11178.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ object Test1 {
def test[A](bar: Bar[A]) =
bar match {
case _: Bar[Boolean] => ??? // error
case _ => ???
}
}

Expand Down
1 change: 1 addition & 0 deletions tests/neg-custom-args/isInstanceOf/i8932.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class Dummy extends Bar[Nothing] with Foo[String]
def bugReport[A](foo: Foo[A]): Foo[A] =
foo match {
case bar: Bar[A] => bar // error
case dummy: Dummy => ???
}

def test = bugReport(new Dummy: Foo[String])
File renamed without changes.
4 changes: 0 additions & 4 deletions tests/neg/i16451.scala → tests/pending/neg/i16451.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
// scalac: -Werror
enum Color:
case Red, Green
//sealed trait Color
//object Color:
// case object Red extends Color
// case object Green extends Color

case class Wrapper[A](value: A)

Expand Down
16 changes: 16 additions & 0 deletions tests/pos/i17230.bootstrap.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
type Untyped = Type | Null

class Type
abstract class SearchFailureType extends Type

abstract class Tree[+T <: Untyped]:
def tpe: T = null.asInstanceOf[T]

class SearchFailureIdent[+T <: Untyped] extends Tree[T]

class Test_i17230_bootstrap:
def t1(arg: Tree[Type]) = arg match
case arg: SearchFailureIdent[?] => arg.tpe match
case x: SearchFailureType =>
case _ =>
case _ =>
15 changes: 15 additions & 0 deletions tests/pos/i17230.min1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// scalac: -Werror
trait Foo:
type Bar[_]

object Foo:
type Aux[B[_]] = Foo { type Bar[A] = B[A] }

class Test:
def t1[B[_]](self: Option[Foo.Aux[B]]) = self match
case Some(_) => 1
case None => 2

def t2[B[_]](self: Option[Foo.Aux[B]]) = self match
case Some(f) => 1
case None => 2
20 changes: 20 additions & 0 deletions tests/pos/i17230.orig.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// scalac: -Werror
import scala.util.*

trait Transaction {
type State[_]
}
object Transaction {
type of[S[_]] = Transaction { type State[A] = S[A] }
}
trait DynamicScope[State[_]]

case class ScopeSearch[State[_]](self: Either[Transaction.of[State], DynamicScope[State]]) {

def embedTransaction[T](f: Transaction.of[State] => T): T =
self match {
case Left(integrated) => ???
case Right(ds) => ???
}
}

Loading