From b1c07b4f16880691d0cd3499aee4b07c91e13c01 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 26 Jul 2022 14:02:57 +0100 Subject: [PATCH] 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 | 17 +++++++++++++++++ 4 files changed, 37 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..23bcff08bf87 --- /dev/null +++ b/tests/pos/i15289.scala @@ -0,0 +1,17 @@ +// 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(_) => "" } // "" +//def m2[X](f2: Foo[X, String]): X = f2 match { case Bar(x) => x } // x: X +//def m3[X](f3: Foo[X, String]): String = f3 match { case Bar(x) => x } // x.asInstanceOf[x.type & String]: String +//def m4[X](f4: Foo[X, String]): X = f4 match { case Bar(_) => "" } // "".asInstanceOf[("" & String & X) @uncheckedStable] + +// ptc: Bar[C] aka Foo[C, C] vs Foo[X, String] +// ptc: C >: X ~> cstr: C >: X +// ptc: C <: X ~> cstr: C := X +// ptc: C >: String ~> gadt: X >: String +// ptc: C <: String ~> gadt: X := String +// ptc: Bar[X] +// max: Bar[X]