Skip to content

Commit 68656d4

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 46b30cc commit 68656d4

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
@@ -4343,23 +4343,37 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
43434343
mapOver(tp)
43444344
}
43454345

4346-
// Is it certain that a value of `tree.tpe` is never a subtype of `pt`?
4347-
// It is true if either
4348-
// - the class of `tree.tpe` and class of `pt` cannot have common subclass, or
4349-
// - `tree` is an object or enum value, which cannot possibly be a subtype of `pt`
4350-
val isDefiniteNotSubtype = {
4351-
val clsA = tree.tpe.widenDealias.classSymbol
4352-
val clsB = pt.dealias.classSymbol
4353-
clsA.exists && clsB.exists
4354-
&& clsA != defn.NullClass
4355-
&& (!clsA.isNumericValueClass && !clsB.isNumericValueClass) // approximation for numeric conversion and boxing
4356-
&& !clsA.asClass.mayHaveCommonChild(clsB.asClass)
4357-
|| tree.symbol.isOneOf(Module | Enum)
4358-
&& !(tree.tpe frozen_<:< pt) // fast track
4359-
&& !(tree.tpe frozen_<:< approx(pt))
4360-
}
4361-
4362-
if isDefiniteNotSubtype then
4346+
// Is it possible that a value of `clsA` is equal to a value of `clsB`?
4347+
// This ignores user-defined equals methods, but makes an exception
4348+
// for numeric classes.
4349+
def canOverlap(clsA: ClassSymbol, clsB: ClassSymbol): Boolean =
4350+
clsA.mayHaveCommonChild(clsB)
4351+
|| clsA.isNumericValueClass // this is quite coarse, but matches to what was done before
4352+
|| clsB.isNumericValueClass
4353+
4354+
// Can type `a` possiblly have a common instance with type `b`?
4355+
def canEqual(a: Type, b: Type): Boolean = trace(i"canEqual $a $b"):
4356+
b match
4357+
case _: TypeRef | _: AppliedType if b.typeSymbol.isClass =>
4358+
a match
4359+
case a: TermRef if a.symbol.isOneOf(Module | Enum) =>
4360+
(a frozen_<:< b) // fast track
4361+
|| (a frozen_<:< approx(b))
4362+
case _: TypeRef | _: AppliedType if a.typeSymbol.isClass =>
4363+
if a.isNullType then !b.isNotNull
4364+
else canOverlap(a.typeSymbol.asClass, b.typeSymbol.asClass)
4365+
case a: TypeProxy =>
4366+
canEqual(a.superType, b)
4367+
case a: AndOrType =>
4368+
canEqual(a.tp1, b) || canEqual(a.tp2, b)
4369+
case b: TypeProxy =>
4370+
canEqual(a, b.superType)
4371+
case b: AndOrType =>
4372+
// we lose precision with and/or types, but it's hard to do better and
4373+
// still compute `canEqual(A & B, B & A) = true`.
4374+
canEqual(a, b.tp1) || canEqual(a, b.tp2)
4375+
4376+
if !canEqual(tree.tpe, pt) then
43634377
// We could check whether `equals` is overridden.
43644378
// Reasons for not doing so:
43654379
// - 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)