Skip to content

Commit 89c107c

Browse files
committed
Preserve the intersection of disjoint numbers in match analysis
The use of provablyDisjoint is a way of simplifying an intersection of two types into the Empty space, using type comparing logic. However, for types like (42L: Long) and (it: Int) (a constant type and a term ref, of two different number types) the two types are provably disjoint, but that doesn't mean that a "case `it` =>" won't match a "42L" scrutinee. So we extend the logic to keep the intersection as it is when numeric value types are in play.
1 parent 48c65c4 commit 89c107c

File tree

2 files changed

+25
-12
lines changed

2 files changed

+25
-12
lines changed

compiler/src/dotty/tools/dotc/transform/patmat/Space.scala

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package patmat
44

55
import core._
66
import Types._
7+
import TypeUtils._
78
import Contexts._
89
import Flags._
910
import ast._
@@ -333,9 +334,11 @@ class SpaceEngine(using Context) extends SpaceLogic {
333334
// Since projections of types don't include null, intersection with null is empty.
334335
Empty
335336
else
336-
val res = TypeComparer.provablyDisjoint(tp1, tp2)
337-
if res then Empty
338-
else Typ(AndType(tp1, tp2), decomposed = true)
337+
val intersection = Typ(AndType(tp1, tp2), decomposed = true)
338+
// unrelated numeric value classes can equal each other, so let's not consider type space interection empty
339+
if tp1.classSymbol.isNumericValueClass && tp2.classSymbol.isNumericValueClass then intersection
340+
else if TypeComparer.provablyDisjoint(tp1, tp2) then Empty
341+
else intersection
339342
}
340343

341344
/** Return the space that represents the pattern `pat` */
@@ -407,7 +410,7 @@ class SpaceEngine(using Context) extends SpaceLogic {
407410
case tp => Typ(tp, decomposed = true)
408411
}
409412

410-
private def unapplySeqInfo(resTp: Type, pos: SrcPos)(using Context): (Int, Type, Type) = {
413+
private def unapplySeqInfo(resTp: Type, pos: SrcPos): (Int, Type, Type) = {
411414
var resultTp = resTp
412415
var elemTp = unapplySeqTypeElemTp(resultTp)
413416
var arity = productArity(resultTp, pos)
@@ -501,19 +504,20 @@ class SpaceEngine(using Context) extends SpaceLogic {
501504
}
502505

503506
/** Numeric literals, while being constant values of unrelated types (e.g. Char and Int),
504-
* when used in a case may end up matching at runtime, because their equals may returns true.
505-
* Because these are universally available, general purpose types, it would be good to avoid
506-
* returning false positive warnings, such as in `(c: Char) match { case 67 => ... }` emitting a
507+
* when used in a case may end up matching at runtime as their equals may returns true.
508+
* Because these are universally available, general purpose types, it would be good to avoid,
509+
* for example in `(c: Char) match { case 67 => ... }`, emitting a false positive
507510
* reachability warning on the case. So the type `ConstantType(Constant(67, IntTag))` is
508511
* converted to `ConstantType(Constant(67, CharTag))`. #12805 */
509-
def convertConstantType(tp: Type, pt: Type): Type = tp match
512+
def convertConstantType(tp: Type, pt: Type): Type = trace(i"convertConstantType($tp, $pt)", show = true)(tp match
510513
case tp @ ConstantType(const) =>
511514
val converted = const.convertTo(pt)
512515
if converted == null then tp else ConstantType(converted)
513516
case _ => tp
517+
)
514518

515-
def isPrimToBox(tp: Type, pt: Type) =
516-
tp.classSymbol.isPrimitiveValueClass && (defn.boxedType(tp).classSymbol eq pt.classSymbol)
519+
def isPrimToBox(tp: Type, pt: Type): Boolean =
520+
tp.isPrimitiveValueType && (defn.boxedType(tp).classSymbol eq pt.classSymbol)
517521

518522
/** Adapt types by performing primitive value unboxing or boxing, or numeric constant conversion. #12805
519523
*
@@ -539,7 +543,10 @@ class SpaceEngine(using Context) extends SpaceLogic {
539543
def isSubType(tp1: Type, tp2: Type): Boolean = trace(i"$tp1 <:< $tp2", debug, show = true) {
540544
if tp1 == constantNullType && !ctx.mode.is(Mode.SafeNulls)
541545
then tp2 == constantNullType
542-
else adaptType(tp1, tp2) <:< tp2
546+
else
547+
val tp1a = adaptType(tp1, tp2)
548+
if tp1a eq tp1 then tp1 <:< tp2
549+
else trace(i"$tp1a <:< $tp2 (adapted)", debug, show = true)(tp1a <:< tp2)
543550
}
544551

545552
def isSameUnapply(tp1: TermRef, tp2: TermRef): Boolean =
@@ -872,7 +879,7 @@ class SpaceEngine(using Context) extends SpaceLogic {
872879
/** Return the underlying type of non-module, non-constant, non-enum case singleton types.
873880
* Also widen ExprType to its result type, and rewrap any annotation wrappers.
874881
* For example, with `val opt = None`, widen `opt.type` to `None.type`. */
875-
def toUnderlying(tp: Type)(using Context): Type = trace(i"toUnderlying($tp)", show = true)(tp match {
882+
def toUnderlying(tp: Type): Type = trace(i"toUnderlying($tp)", show = true)(tp match {
876883
case _: ConstantType => tp
877884
case tp: TermRef if tp.symbol.is(Module) => tp
878885
case tp: TermRef if tp.symbol.isAllOf(EnumCase) => tp

tests/pos/i14407.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// scalac: -Werror
2+
@main def Test =
3+
val it: Int = 42
4+
42L match
5+
case `it` => println("pass")
6+
case _ => println("fail")

0 commit comments

Comments
 (0)