From 531bf4daa56d4856b6bce3758a6dc970ef5a0312 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 26 Jul 2022 14:02:57 +0100 Subject: [PATCH 1/3] Allow refineUsingParent to infer GADT bounds Not doing so makes the type var interpolation between typer and the match space analysis different, which can result in false positive and negative exhaustivity and reachability warnings --- .../src/dotty/tools/dotc/core/TypeOps.scala | 18 +++++++++++++++++- .../dotty/tools/vulpix/ParallelTesting.scala | 2 +- .../suppressed-type-test-warnings.scala | 2 ++ tests/pos/i15289.scala | 6 ++++++ 4 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 tests/pos/i15289.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 25e43efb7885..6fbf6f17724e 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -712,7 +712,16 @@ object TypeOps: val childTp = if (child.isTerm) child.termRef else child.typeRef - inContext(ctx.fresh.setExploreTyperState().setFreshGADTBounds) { + val ctx1 = ctx.fresh.setExploreTyperState().setFreshGADTBounds + val ctx2 = parent match + case _: RefinedType => + ctx1 + // patmat/t9657 + // When running Bicycle.type <:< Vehicle { A = P } + // TypeComparer is happy to infer GADT bounds P >: Pedal.type <: Petrol.type & Pedal.type + // Despite the fact that Bicycle is an object, and thus final, so its type A can only be Pedal.type. + case _ => ctx1.addMode(Mode.GadtConstraintInference) + inContext(ctx2) { instantiateToSubType(childTp, parent).dealias } } @@ -829,6 +838,13 @@ object TypeOps: val tvars = tp1.typeParams.map { tparam => newTypeVar(tparam.paramInfo.bounds) } val protoTp1 = inferThisMap.apply(tp1).appliedTo(tvars) + val getAbstractSymbols = new TypeAccumulator[List[Symbol]]: + def apply(xs: List[Symbol], tp: Type) = tp.dealias match + case tp: TypeRef if !tp.symbol.isClass => foldOver(tp.symbol :: xs, tp) + case tp => foldOver(xs, tp) + val syms2 = getAbstractSymbols(Nil, tp2).reverse + if syms2.nonEmpty then ctx.gadt.addToConstraint(syms2) + // If parent contains a reference to an abstract type, then we should // refine subtype checking to eliminate abstract types according to // variance. As this logic is only needed in exhaustivity check, diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 9e898a68a1e8..fb60d98ea5cf 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -168,7 +168,7 @@ trait ParallelTesting extends RunnerOrchestration { self => ) extends TestSource { def sourceFiles: Array[JFile] = files.filter(isSourceFile) - override def toString() = outDir.toString + override def toString() = sourceFiles match { case Array(f) => f.getPath case _ => outDir.getPath } } /** A test source whose files will be compiled separately according to their diff --git a/tests/neg-custom-args/fatal-warnings/suppressed-type-test-warnings.scala b/tests/neg-custom-args/fatal-warnings/suppressed-type-test-warnings.scala index 175096fc6b21..92d86b3307e5 100644 --- a/tests/neg-custom-args/fatal-warnings/suppressed-type-test-warnings.scala +++ b/tests/neg-custom-args/fatal-warnings/suppressed-type-test-warnings.scala @@ -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 } } diff --git a/tests/pos/i15289.scala b/tests/pos/i15289.scala new file mode 100644 index 000000000000..5eea2fe9f91f --- /dev/null +++ b/tests/pos/i15289.scala @@ -0,0 +1,6 @@ +// scalac: -Werror +sealed abstract class Foo[A, B] +final case class Bar[C](baz: C) extends Foo[C, C] + +class Test: + def m1[X](f1: Foo[X, String]): String = f1 match { case Bar(_) => "" } From b5bb7cebf34be03f759f8b95521614159efdf826 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 26 Jul 2022 16:05:00 +0100 Subject: [PATCH 2/3] Another case fixed by inferring GADT bounds --- tests/pos/i13548.scala | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 tests/pos/i13548.scala diff --git a/tests/pos/i13548.scala b/tests/pos/i13548.scala new file mode 100644 index 000000000000..2f2f62e17878 --- /dev/null +++ b/tests/pos/i13548.scala @@ -0,0 +1,6 @@ +// scalac: -Werror +sealed abstract class Foo[N, A] +final case class Bar[B](foo: Foo[B, B]) extends Foo[B, B] +class Test: + def pmat[P, C](scr: Foo[P, C]): C = scr match + case Bar(foo) => pmat(foo) From c4f85a4111013b2594262c66563c36d4e2fc39fc Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 26 Jul 2022 23:34:56 +0100 Subject: [PATCH 3/3] Rollback on failure to narrow GADT bounds All credit to Linyxus! --- compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 7 +++++-- compiler/src/dotty/tools/dotc/core/TypeOps.scala | 11 +---------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index e9e7f1b3e52d..6a1f79efb277 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -1945,8 +1945,11 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling val tparam = tr.symbol gadts.println(i"narrow gadt bound of $tparam: ${tparam.info} from ${if (isUpper) "above" else "below"} to $bound ${bound.toString} ${bound.isRef(tparam)}") if (bound.isRef(tparam)) false - else if (isUpper) gadtAddUpperBound(tparam, bound) - else gadtAddLowerBound(tparam, bound) + else + val savedGadt = ctx.gadt.fresh + val success = if isUpper then gadtAddUpperBound(tparam, bound) else gadtAddLowerBound(tparam, bound) + if !success then ctx.gadt.restore(savedGadt) + success } } diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 6fbf6f17724e..0174fb2f3da9 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -712,16 +712,7 @@ object TypeOps: val childTp = if (child.isTerm) child.termRef else child.typeRef - val ctx1 = ctx.fresh.setExploreTyperState().setFreshGADTBounds - val ctx2 = parent match - case _: RefinedType => - ctx1 - // patmat/t9657 - // When running Bicycle.type <:< Vehicle { A = P } - // TypeComparer is happy to infer GADT bounds P >: Pedal.type <: Petrol.type & Pedal.type - // Despite the fact that Bicycle is an object, and thus final, so its type A can only be Pedal.type. - case _ => ctx1.addMode(Mode.GadtConstraintInference) - inContext(ctx2) { + inContext(ctx.fresh.setExploreTyperState().setFreshGADTBounds.addMode(Mode.GadtConstraintInference)) { instantiateToSubType(childTp, parent).dealias } }