From 208a728ee2aaf9083f69b240a54799f8af462dc6 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 12 Dec 2023 18:40:33 +0000 Subject: [PATCH 1/4] Move checkMatch into SpaceEngine ... makes it easier to disable one of them. [Cherry-picked dbb97ca1d65bd49dc04db9087c9b3a7671e1b041] --- .../dotty/tools/dotc/transform/PatternMatcher.scala | 3 +-- .../src/dotty/tools/dotc/transform/patmat/Space.scala | 10 +++++++--- scaladoc-js/common/src/utils/html.scala | 4 ++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 28a6482fe112..3f79de8dc7a4 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -55,8 +55,7 @@ class PatternMatcher extends MiniPhase { if !inInlinedCode then // check exhaustivity and unreachability - SpaceEngine.checkExhaustivity(tree) - SpaceEngine.checkRedundancy(tree) + SpaceEngine.checkMatch(tree) translated.ensureConforms(matchType) } diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 977fcfb703d8..d4d77af35247 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -882,7 +882,7 @@ object SpaceEngine { case _ => tp }) - def checkExhaustivity(m: Match)(using Context): Unit = if exhaustivityCheckable(m.selector) then trace(i"checkExhaustivity($m)", debug) { + def checkExhaustivity(m: Match)(using Context): Unit = trace(i"checkExhaustivity($m)", debug) { val selTyp = toUnderlying(m.selector.tpe).dealias debug.println(i"selTyp = $selTyp") @@ -905,7 +905,7 @@ object SpaceEngine { report.warning(PatternMatchExhaustivity(showSpaces(deduped), m), m.selector) } - private def redundancyCheckable(sel: Tree)(using Context): Boolean = + private def reachabilityCheckable(sel: Tree)(using Context): Boolean = // Ignore Expr[T] and Type[T] for unreachability as a special case. // Quote patterns produce repeated calls to the same unapply method, but with different implicit parameters. // Since we assume that repeated calls to the same unapply method overlap @@ -915,7 +915,7 @@ object SpaceEngine { && !sel.tpe.widen.isRef(defn.QuotedExprClass) && !sel.tpe.widen.isRef(defn.QuotedTypeClass) - def checkRedundancy(m: Match)(using Context): Unit = if redundancyCheckable(m.selector) then trace(i"checkRedundancy($m)", debug) { + def checkReachability(m: Match)(using Context): Unit = trace(i"checkReachability($m)", debug) { val cases = m.cases.toIndexedSeq val selTyp = toUnderlying(m.selector.tpe).dealias @@ -967,4 +967,8 @@ object SpaceEngine { i += 1 } } + + def checkMatch(m: Match)(using Context): Unit = + if exhaustivityCheckable(m.selector) then checkExhaustivity(m) + if reachabilityCheckable(m.selector) then checkReachability(m) } diff --git a/scaladoc-js/common/src/utils/html.scala b/scaladoc-js/common/src/utils/html.scala index 1a7f108a3555..3743dfd11b74 100644 --- a/scaladoc-js/common/src/utils/html.scala +++ b/scaladoc-js/common/src/utils/html.scala @@ -26,7 +26,7 @@ object HTML { case ("id", id) => elem.id = id case ("class", value) => value.split("\\s+").foreach(cls => elem.classList.add(cls)) case (attr, value) => elem.setAttribute(attr, value) - case s: Seq[AppliedAttr] => unpackAttributes(s*) + case s: Seq[AppliedAttr @unchecked] => unpackAttributes(s*) } unpackTags(tags:_*) @@ -118,4 +118,4 @@ object HTML { val titleAttr =Attr("title") val onkeyup = Attr("onkeyup") -} \ No newline at end of file +} From c1dfcef0a851a95361d0e6047445a75b634179f6 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 6 Dec 2023 18:04:27 +0000 Subject: [PATCH 2/4] Remove unnecessary and recursive Space decomposition Space decomposition recently learnt to decompose prefixes. Given a nested definition like in i19031, aggressively trying to decompose while intersecting can lead to recursive decompositions (building bigger and bigger nested prefixes). Turns out the decomposition isn't necessary. [Cherry-picked 5439e98d5a17219365166f171b7ca23ea64dafb4] --- .../src/dotty/tools/dotc/transform/patmat/Space.scala | 4 ---- tests/pos/i19031.scala | 9 +++++++++ 2 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 tests/pos/i19031.scala diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index d4d77af35247..5280b63b20d3 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -209,17 +209,13 @@ object SpaceEngine { case (a @ Typ(tp1, _), b @ Typ(tp2, _)) => if isSubType(tp1, tp2) then a else if isSubType(tp2, tp1) then b - else if canDecompose(a) then intersect(Or(decompose(a)), b) - else if canDecompose(b) then intersect(a, Or(decompose(b))) else intersectUnrelatedAtomicTypes(tp1, tp2)(a) case (a @ Typ(tp1, _), Prod(tp2, fun, ss)) => if isSubType(tp2, tp1) then b - else if canDecompose(a) then intersect(Or(decompose(a)), b) else if isSubType(tp1, tp2) then a // problematic corner case: inheriting a case class else intersectUnrelatedAtomicTypes(tp1, tp2)(b) case (Prod(tp1, fun, ss), b @ Typ(tp2, _)) => if isSubType(tp1, tp2) then a - else if canDecompose(b) then intersect(a, Or(decompose(b))) else if isSubType(tp2, tp1) then a // problematic corner case: inheriting a case class else intersectUnrelatedAtomicTypes(tp1, tp2)(a) case (a @ Prod(tp1, fun1, ss1), Prod(tp2, fun2, ss2)) => diff --git a/tests/pos/i19031.scala b/tests/pos/i19031.scala new file mode 100644 index 000000000000..e56744017255 --- /dev/null +++ b/tests/pos/i19031.scala @@ -0,0 +1,9 @@ +//> using options -Werror + +sealed trait A: + class B extends A + +class Test: + def t1(a: A): Boolean = + a match + case b: A#B => true From f87a634a6e88777dad6b808d2813289d703330c8 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 7 Dec 2023 14:02:33 +0000 Subject: [PATCH 3/4] Fix provablyDisjoint handling of HK types If refineUsingParent/instantiateToSubType is passed a HK then it's not possible to instantiate a class to that type (at the moment and perhaps ever). So it's important we guard against that. This came up while trying to see if Mark[?] and Foo[Int] (from pos/i19031.ci-reg1.scala) are provably disjoint - which they should be reported not to be. Because they're not applied types of the same type constructor we end up trying to prove that HK type Mark is disjoint from HK type Foo. Because we don't know how to instantiate Foo's subclasses (e.g Bar2) such that it's a subtype of higher-kinded type "Mark", we end up discarding all of Foo's subclasses, which implies that Foo & Mark is uninhabited, thus they are provably disjoint - which is incorrect. We originally didn't encounter this because we eagerly decomposed in Space intersection, while now we've dispatched it to provablyDisjoint. (edit) We allow for some kindness in provablyDisjoint. [Cherry-picked 7e472945c7c186140431675fb5b6d46712f1e1ae] --- .../src/dotty/tools/dotc/core/TypeComparer.scala | 8 +++++--- tests/pos/i19031.ci-reg1.scala | 12 ++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 tests/pos/i19031.ci-reg1.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 7687263c0e36..5d500642effe 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -2758,10 +2758,12 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling case (tp1: TypeRef, tp2: TypeRef) if tp1.symbol.isClass && tp2.symbol.isClass => val cls1 = tp1.classSymbol val cls2 = tp2.classSymbol - def isDecomposable(tp: Symbol): Boolean = - tp.is(Sealed) && !tp.hasAnonymousChild + val sameKind = tp1.hasSameKindAs(tp2) + def isDecomposable(sym: Symbol): Boolean = + sameKind && sym.is(Sealed) && !sym.hasAnonymousChild def decompose(sym: Symbol, tp: Type): List[Type] = - sym.children.map(x => refineUsingParent(tp, x)).filter(_.exists) + val tpSimple = tp.applyIfParameterized(tp.typeParams.map(_ => WildcardType)) + sym.children.map(x => refineUsingParent(tpSimple, x)).filter(_.exists) if (cls1.derivesFrom(cls2) || cls2.derivesFrom(cls1)) false else diff --git a/tests/pos/i19031.ci-reg1.scala b/tests/pos/i19031.ci-reg1.scala new file mode 100644 index 000000000000..be2ed914c42b --- /dev/null +++ b/tests/pos/i19031.ci-reg1.scala @@ -0,0 +1,12 @@ +//> using options -Werror + +sealed trait Mark[T] + +trait Foo[T] +class Bar1[T] extends Foo[T] +class Bar2[T] extends Foo[T] with Mark[T] + +class Test: + def t1(foo: Foo[Int]): Unit = foo match + case _: Mark[t] => + case _ => From 8c29c1f321294c2747eb3c38216ef1a77a4daab6 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 11 Dec 2023 14:43:29 +0000 Subject: [PATCH 4/4] Fix instantiating subtypes with outer references Minimising from the `case test.Generic =>` in ParallelTesting, the anonymous pattern match is expanded, wrapping the match with `applyOrElse`, which has a type parameter A1 as the scrutinee type, with an upper bound of the original element type (out.Foo for us). During reachability analysis the pattern type, e.g. out.Bar3.type, is intersected with the scrutinee type, A1 - giving out.Bar3.type & A1. Then that we attempt to decompose that type. Previously the abstract A1 in that type lead to 3 WildcardTypes, for the 3 subclasses, which are a subtype of previous cases. The fix that by generalising how we recognise the singleton types in the scrutinee type, so instead of the ownership chain we use the parameter type info, and we also match term parameters. For extra correctness we consider the failure to be a subtype of a mixin as a failure for instantiating. Also, make sure to handle and avoid recursion in traverseTp2. [Cherry-picked 0931c43a29d7d4ca5ac63449c749d6c4a25b5da1] --- .../src/dotty/tools/dotc/core/TypeOps.scala | 45 ++++++++++--------- tests/pos/i19031.ci-reg1.scala | 4 ++ tests/pos/i19031.ci-reg2.scala | 15 +++++++ 3 files changed, 44 insertions(+), 20 deletions(-) create mode 100644 tests/pos/i19031.ci-reg2.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 7e3afb35b2b6..6f323c1c0e4d 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -838,33 +838,34 @@ object TypeOps: } } - /** Gather GADT symbols and `ThisType`s found in `tp2`, ie. the scrutinee. */ + /** Gather GADT symbols and singletons found in `tp2`, ie. the scrutinee. */ object TraverseTp2 extends TypeTraverser: - val thisTypes = util.HashSet[ThisType]() - val gadtSyms = new mutable.ListBuffer[Symbol] + val singletons = util.HashMap[Symbol, SingletonType]() + val gadtSyms = new mutable.ListBuffer[Symbol] - def traverse(tp: Type) = { + def traverse(tp: Type) = try val tpd = tp.dealias if tpd ne tp then traverse(tpd) else tp match - case tp: ThisType if !tp.tref.symbol.isStaticOwner && !thisTypes.contains(tp) => - thisTypes += tp + case tp: ThisType if !singletons.contains(tp.tref.symbol) && !tp.tref.symbol.isStaticOwner => + singletons(tp.tref.symbol) = tp traverseChildren(tp.tref) - case tp: TypeRef if tp.symbol.isAbstractOrParamType => + case tp: TermRef if tp.symbol.is(Param) => + singletons(tp.typeSymbol) = tp + traverseChildren(tp) + case tp: TypeRef if !gadtSyms.contains(tp.symbol) && tp.symbol.isAbstractOrParamType => gadtSyms += tp.symbol traverseChildren(tp) - val owners = Iterator.iterate(tp.symbol)(_.maybeOwner).takeWhile(_.exists) - for sym <- owners do - // add ThisType's for the classes symbols in the ownership of `tp` - // for example, i16451.CanForward.scala, add `Namer.this`, as one of the owners of the type parameter `A1` - if sym.isClass && !sym.isAnonymousClass && !sym.isStaticOwner then - traverse(sym.thisType) + // traverse abstract type infos, to add any singletons + // for example, i16451.CanForward.scala, add `Namer.this`, from the info of the type parameter `A1` + // also, i19031.ci-reg2.scala, add `out`, from the info of the type parameter `A1` (from synthetic applyOrElse) + traverseChildren(tp.info) case _ => traverseChildren(tp) - } + catch case ex: Throwable => handleRecursive("traverseTp2", tp.show, ex) TraverseTp2.traverse(tp2) - val thisTypes = TraverseTp2.thisTypes - val gadtSyms = TraverseTp2.gadtSyms.toList + val singletons = TraverseTp2.singletons + val gadtSyms = TraverseTp2.gadtSyms.toList // Prefix inference, given `p.C.this.Child`: // 1. return it as is, if `C.this` is found in `tp`, i.e. the scrutinee; or @@ -874,10 +875,13 @@ object TypeOps: class InferPrefixMap extends TypeMap { var prefixTVar: Type | Null = null def apply(tp: Type): Type = tp match { - case tp @ ThisType(tref) if !tref.symbol.isStaticOwner => + case tp: TermRef if singletons.contains(tp.symbol) => + prefixTVar = singletons(tp.symbol) // e.g. tests/pos/i19031.ci-reg2.scala, keep out + prefixTVar.uncheckedNN + case ThisType(tref) if !tref.symbol.isStaticOwner => val symbol = tref.symbol - if thisTypes.contains(tp) then - prefixTVar = tp // e.g. tests/pos/i16785.scala, keep Outer.this + if singletons.contains(symbol) then + prefixTVar = singletons(symbol) // e.g. tests/pos/i16785.scala, keep Outer.this prefixTVar.uncheckedNN else if symbol.is(Module) then TermRef(this(tref.prefix), symbol.sourceModule) @@ -912,7 +916,8 @@ object TypeOps: } def instantiate(): Type = { - for tp <- mixins.reverseIterator do protoTp1 <:< tp + for tp <- mixins.reverseIterator do + protoTp1 <:< tp maximizeType(protoTp1, NoSpan) wildApprox(protoTp1) } diff --git a/tests/pos/i19031.ci-reg1.scala b/tests/pos/i19031.ci-reg1.scala index be2ed914c42b..3c15a3eb9afc 100644 --- a/tests/pos/i19031.ci-reg1.scala +++ b/tests/pos/i19031.ci-reg1.scala @@ -10,3 +10,7 @@ class Test: def t1(foo: Foo[Int]): Unit = foo match case _: Mark[t] => case _ => + + def t2[F <: Foo[Int]](foo: F): Unit = foo match + case _: Mark[t] => + case _ => diff --git a/tests/pos/i19031.ci-reg2.scala b/tests/pos/i19031.ci-reg2.scala new file mode 100644 index 000000000000..e5b12cc17655 --- /dev/null +++ b/tests/pos/i19031.ci-reg2.scala @@ -0,0 +1,15 @@ +//> using options -Werror + +trait Outer: + sealed trait Foo + case class Bar1() extends Foo + case class Bar2() extends Foo + case object Bar3 extends Foo + + def foos: List[Foo] + +class Test: + def t1(out: Outer) = out.foos.collect: + case out.Bar1() => 1 + case out.Bar2() => 2 + case out.Bar3 => 3