Skip to content

Commit 55d9a46

Browse files
committed
Improve logic when to emit pattern type error
Improve logic when to emit "pattern type is incompatible with expected type" error. Fixes #18083 The whole thing is not very satisfactory. We have an approximation which has both false positives and false negatives. False positives: We are too lenient for numeric types and and/or types False negatives: We ignore user-generated equals methods The new approximation seems to be somewhat better than the old, but it's still an approximation. It begs the question why we attempt to do this at all.
1 parent 0a21ecf commit 55d9a46

File tree

4 files changed

+46
-21
lines changed

4 files changed

+46
-21
lines changed

compiler/src/dotty/tools/dotc/core/SymDenotations.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2020,8 +2020,10 @@ object SymDenotations {
20202020
* @return The result may contain false positives, but never false negatives.
20212021
*/
20222022
final def mayHaveCommonChild(that: ClassSymbol)(using Context): Boolean =
2023-
!this.is(Final) && !that.is(Final) && (this.is(Trait) || that.is(Trait)) ||
2024-
this.derivesFrom(that) || that.derivesFrom(this.symbol)
2023+
this.is(Trait) && !that.isEffectivelyFinal
2024+
|| that.is(Trait) && !this.isEffectivelyFinal
2025+
|| this.derivesFrom(that)
2026+
|| that.derivesFrom(this.symbol)
20252027

20262028
final override def typeParamCreationFlags: FlagSet = ClassTypeParamCreationFlags
20272029

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4380,23 +4380,37 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
43804380
mapOver(tp)
43814381
}
43824382

4383-
// Is it certain that a value of `tree.tpe` is never a subtype of `pt`?
4384-
// It is true if either
4385-
// - the class of `tree.tpe` and class of `pt` cannot have common subclass, or
4386-
// - `tree` is an object or enum value, which cannot possibly be a subtype of `pt`
4387-
val isDefiniteNotSubtype = {
4388-
val clsA = tree.tpe.widenDealias.classSymbol
4389-
val clsB = pt.dealias.classSymbol
4390-
clsA.exists && clsB.exists
4391-
&& clsA != defn.NullClass
4392-
&& (!clsA.isNumericValueClass && !clsB.isNumericValueClass) // approximation for numeric conversion and boxing
4393-
&& !clsA.asClass.mayHaveCommonChild(clsB.asClass)
4394-
|| tree.symbol.isOneOf(Module | Enum)
4395-
&& !(tree.tpe frozen_<:< pt) // fast track
4396-
&& !(tree.tpe frozen_<:< approx(pt))
4397-
}
4398-
4399-
if isDefiniteNotSubtype then
4383+
// Is it possible that a value of `clsA` is equal to a value of `clsB`?
4384+
// This ignores user-defined equals methods, but makes an exception
4385+
// for numeric classes.
4386+
def canOverlap(clsA: ClassSymbol, clsB: ClassSymbol): Boolean =
4387+
clsA.mayHaveCommonChild(clsB)
4388+
|| clsA.isNumericValueClass // this is quite coarse, but matches to what was done before
4389+
|| clsB.isNumericValueClass
4390+
4391+
// Can type `a` possiblly have a common instance with type `b`?
4392+
def canEqual(a: Type, b: Type): Boolean = trace(i"canEqual $a $b"):
4393+
b match
4394+
case _: TypeRef | _: AppliedType if b.typeSymbol.isClass =>
4395+
a match
4396+
case a: TermRef if a.symbol.isOneOf(Module | Enum) =>
4397+
(a frozen_<:< b) // fast track
4398+
|| (a frozen_<:< approx(b))
4399+
case _: TypeRef | _: AppliedType if a.typeSymbol.isClass =>
4400+
if a.isNullType then !b.isNotNull
4401+
else canOverlap(a.typeSymbol.asClass, b.typeSymbol.asClass)
4402+
case a: TypeProxy =>
4403+
canEqual(a.superType, b)
4404+
case a: AndOrType =>
4405+
canEqual(a.tp1, b) || canEqual(a.tp2, b)
4406+
case b: TypeProxy =>
4407+
canEqual(a, b.superType)
4408+
case b: AndOrType =>
4409+
// we lose precision with and/or types, but it's hard to do better and
4410+
// still compute `canEqual(A & B, B & A) = true`.
4411+
canEqual(a, b.tp1) || canEqual(a, b.tp2)
4412+
4413+
if !canEqual(tree.tpe, pt) then
44004414
// We could check whether `equals` is overridden.
44014415
// Reasons for not doing so:
44024416
// - it complicates the protocol

tests/neg/i5004.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
object i0 {
22
1 match {
33
def this(): Int // error
4-
def this() // error
5-
}
4+
def this()
5+
} // error
66
}

tests/pos/i18083.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
sealed trait A
2+
case class Sub1() extends A
3+
case object Sub2 extends A
4+
5+
def test(x: A | Null): Int =
6+
if x == null then return 0
7+
x match
8+
case Sub1() => 1
9+
case Sub2 => 2

0 commit comments

Comments
 (0)