From e555ee0777e80dc42018a12193903d022fec34da Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 7 May 2021 19:56:06 +0200 Subject: [PATCH 1/4] Fix #12337: Enable exhaustivity check for case classes with checkable components This aligns with Scala 2 behavior. --- .../dotty/tools/dotc/transform/patmat/Space.scala | 8 +++++--- tests/patmat/i12337.check | 1 + tests/patmat/i12337.scala | 12 ++++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 tests/patmat/i12337.check create mode 100644 tests/patmat/i12337.scala diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index bcb021af7b76..b6993735ac89 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -791,16 +791,18 @@ class SpaceEngine(using Context) extends SpaceLogic { def isCheckable(tp: Type): Boolean = !tp.hasAnnotation(defn.UncheckedAnnot) && { val tpw = tp.widen.dealias + val classSym = tpw.classSymbol ctx.settings.YcheckAllPatmat.value || - tpw.typeSymbol.is(Sealed) || + classSym.is(Sealed) || tpw.isInstanceOf[OrType] || (tpw.isInstanceOf[AndType] && { val and = tpw.asInstanceOf[AndType] isCheckable(and.tp1) || isCheckable(and.tp2) }) || tpw.isRef(defn.BooleanClass) || - tpw.typeSymbol.isAllOf(JavaEnumTrait) || - (defn.isTupleType(tpw) && tpw.argInfos.exists(isCheckable(_))) + classSym.isAllOf(JavaEnumTrait) || + (defn.isProductSubType(tpw) && classSym.is(Case) + && productSelectorTypes(tpw, sel.srcPos).exists(isCheckable(_))) } val res = isCheckable(sel.tpe) diff --git a/tests/patmat/i12337.check b/tests/patmat/i12337.check new file mode 100644 index 000000000000..995f8b892239 --- /dev/null +++ b/tests/patmat/i12337.check @@ -0,0 +1 @@ +8: Pattern Match Exhaustivity: Foo(Inactive) diff --git a/tests/patmat/i12337.scala b/tests/patmat/i12337.scala new file mode 100644 index 000000000000..fac329d8ac37 --- /dev/null +++ b/tests/patmat/i12337.scala @@ -0,0 +1,12 @@ +sealed trait Status +object Status { + case object Active extends Status + case object Inactive extends Status +} + +case class Foo(status: Status) +def bar(user: Foo): Unit = user match { + case Foo(Status.Active) => + println("active") + // no compile-time warning for missing Status.Inactive case +} From 52ac0e6cfafb280ffbf060b3776c3876741c28e1 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 7 May 2021 20:09:33 +0200 Subject: [PATCH 2/4] Fix bootstrap --- compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala | 4 ++-- compiler/src/dotty/tools/backend/jvm/BCodeSyncAndTry.scala | 2 +- .../tools/dotc/transform/localopt/StringInterpolatorOpt.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Applications.scala | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala index 156551519cb9..47b2d3a69a10 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala @@ -228,7 +228,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { resKind } - def genPrimitiveOp(tree: Apply, expectedType: BType): BType = tree match { + def genPrimitiveOp(tree: Apply, expectedType: BType): BType = (tree: @unchecked) match { case Apply(fun @ DesugaredSelect(receiver, _), _) => val sym = tree.symbol @@ -610,7 +610,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { } } - def genTypeApply(t: TypeApply): BType = t match { + def genTypeApply(t: TypeApply): BType = (t: @unchecked) match { case TypeApply(fun@DesugaredSelect(obj, _), targs) => val sym = fun.symbol diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSyncAndTry.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSyncAndTry.scala index 04425b524b93..d326bcfa242a 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSyncAndTry.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSyncAndTry.scala @@ -27,7 +27,7 @@ trait BCodeSyncAndTry extends BCodeBodyBuilder { */ abstract class SyncAndTryBuilder(cunit: CompilationUnit) extends PlainBodyBuilder(cunit) { - def genSynchronized(tree: Apply, expectedType: BType): BType = tree match { + def genSynchronized(tree: Apply, expectedType: BType): BType = (tree: @unchecked) match { case Apply(TypeApply(fun, _), args) => val monitor = locals.makeLocal(ObjectReference, "monitor", defn.ObjectType, tree.span) val monCleanup = new asm.Label diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/StringInterpolatorOpt.scala b/compiler/src/dotty/tools/dotc/transform/localopt/StringInterpolatorOpt.scala index 8f4aa6af8783..90b2b4f4cabf 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/StringInterpolatorOpt.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/StringInterpolatorOpt.scala @@ -121,7 +121,7 @@ class StringInterpolatorOpt extends MiniPhase { (sym.name == nme.f && sym.eq(defn.StringContext_f)) || (sym.name == nme.s && sym.eq(defn.StringContext_s)) if (isInterpolatedMethod) - tree match { + (tree: @unchecked) match { case StringContextIntrinsic(strs: List[Literal], elems: List[Tree]) => val stri = strs.iterator val elemi = elems.iterator diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 2d30b925a7eb..f8e9143c1fc0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -990,7 +990,7 @@ trait Applications extends Compatibility { * { val xs = es; e' = e' + args } */ def typedOpAssign(using Context): Tree = { - val (lhs1, name, rhss) = tree match + val (lhs1, name, rhss) = (tree: @unchecked) match case Apply(Select(lhs, name), rhss) => (typedExpr(lhs), name, rhss) case Apply(untpd.TypedSplice(Select(lhs1, name)), rhss) => (lhs1, name, rhss) val liftedDefs = new mutable.ListBuffer[Tree] From a84e970fe57e5c88082b52da17747090e3e987e6 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 7 May 2021 23:17:31 +0200 Subject: [PATCH 3/4] Fix num of warnings in test --- tests/neg-custom-args/isInstanceOf/enum-approx2.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/neg-custom-args/isInstanceOf/enum-approx2.scala b/tests/neg-custom-args/isInstanceOf/enum-approx2.scala index 8350f9cf4b9c..516b765ec64b 100644 --- a/tests/neg-custom-args/isInstanceOf/enum-approx2.scala +++ b/tests/neg-custom-args/isInstanceOf/enum-approx2.scala @@ -5,5 +5,6 @@ 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 _ => } } \ No newline at end of file From 682a596454d9a0069e29b65b22c1616755e5c318 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Mon, 10 May 2021 10:25:29 +0200 Subject: [PATCH 4/4] Update test (thanks to @Kevin-Lee) --- tests/patmat/i12337.check | 1 + tests/patmat/i12337.scala | 24 +++++++++++++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/tests/patmat/i12337.check b/tests/patmat/i12337.check index 995f8b892239..2b314017a6dd 100644 --- a/tests/patmat/i12337.check +++ b/tests/patmat/i12337.check @@ -1 +1,2 @@ 8: Pattern Match Exhaustivity: Foo(Inactive) +17: Pattern Match Exhaustivity: Foo(Status.Active(_)) diff --git a/tests/patmat/i12337.scala b/tests/patmat/i12337.scala index fac329d8ac37..efa3e04168d6 100644 --- a/tests/patmat/i12337.scala +++ b/tests/patmat/i12337.scala @@ -1,12 +1,26 @@ sealed trait Status object Status { - case object Active extends Status + case class Active(since: Int) extends Status case object Inactive extends Status } case class Foo(status: Status) -def bar(user: Foo): Unit = user match { - case Foo(Status.Active) => - println("active") - // no compile-time warning for missing Status.Inactive case +def bar(foo: Foo): Unit = foo match { + case Foo(Status.Active(since)) => + println(s"active since $since") } +// Expected: +// warning: match may not be exhaustive. +// It would fail on the following input: Foo(Inactive) +// def bar(foo: Foo): Unit = foo match { + +def baz(foo: Foo): Unit = foo match { + case Foo(Status.Active(2000)) => + println("active since 2000") + case Foo(Status.Inactive) => + println("inactive") +} +// Expected: +// warning: match may not be exhaustive. +// It would fail on the following input: Foo(Active((x: Int forSome x not in 2000))) +// def baz(foo: Foo): Unit = foo match { \ No newline at end of file