Skip to content

Commit 8884ec0

Browse files
authored
Merge pull request #8722 from dotty-staging/fix-8681
Fix #8681: handle scrutinee of union types
2 parents 1a0a5a2 + acc2b6c commit 8884ec0

File tree

6 files changed

+75
-24
lines changed

6 files changed

+75
-24
lines changed

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2062,12 +2062,12 @@ object messages {
20622062
def explain = ""
20632063
}
20642064

2065-
class TypeTestAlwaysSucceeds(foundCls: Symbol, testCls: Symbol)(implicit ctx: Context) extends SyntaxMsg(TypeTestAlwaysSucceedsID) {
2065+
class TypeTestAlwaysSucceeds(scrutTp: Type, testTp: Type)(implicit ctx: Context) extends SyntaxMsg(TypeTestAlwaysSucceedsID) {
20662066
def msg = {
20672067
val addendum =
2068-
if (foundCls != testCls) s" is a subtype of $testCls"
2068+
if (scrutTp != testTp) s" is a subtype of ${testTp.show}"
20692069
else " is the same as the tested type"
2070-
s"The highlighted type test will always succeed since the scrutinee type ($foundCls)" + addendum
2070+
s"The highlighted type test will always succeed since the scrutinee type ($scrutTp.show)" + addendum
20712071
}
20722072
def explain = ""
20732073
}

compiler/src/dotty/tools/dotc/transform/Erasure.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,7 @@ object Erasure {
722722
}
723723

724724
override def typedTypeApply(tree: untpd.TypeApply, pt: Type)(using Context): Tree = {
725-
val ntree = interceptTypeApply(tree.asInstanceOf[TypeApply])(ctx.withPhase(ctx.erasurePhase)).withSpan(tree.span)
725+
val ntree = interceptTypeApply(tree.asInstanceOf[TypeApply])(using ctx.withPhase(ctx.erasurePhase)).withSpan(tree.span)
726726

727727
ntree match {
728728
case TypeApply(fun, args) =>

compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ object TypeTestsCasts {
5353
* 7. if `P` is a refinement type, FALSE
5454
* 8. otherwise, TRUE
5555
*/
56-
def checkable(X: Type, P: Type, span: Span)(implicit ctx: Context): Boolean = {
56+
def checkable(X: Type, P: Type, span: Span)(using Context): Boolean = {
5757
def isAbstract(P: Type) = !P.dealias.typeSymbol.isClass
5858
def isPatternTypeSymbol(sym: Symbol) = !sym.isClass && sym.is(Case)
5959

@@ -155,7 +155,7 @@ object TypeTestsCasts {
155155
res
156156
}
157157

158-
def interceptTypeApply(tree: TypeApply)(implicit ctx: Context): Tree = trace(s"transforming ${tree.show}", show = true) {
158+
def interceptTypeApply(tree: TypeApply)(using Context): Tree = trace(s"transforming ${tree.show}", show = true) {
159159
/** Intercept `expr.xyz[XYZ]` */
160160
def interceptWith(expr: Tree): Tree =
161161
if (expr.isEmpty) tree
@@ -172,7 +172,9 @@ object TypeTestsCasts {
172172
else if tp.isRef(defn.AnyValClass) then defn.AnyClass
173173
else tp.classSymbol
174174

175-
def foundCls = effectiveClass(expr.tpe.widen)
175+
def foundClasses(tp: Type, acc: List[Symbol]): List[Symbol] = tp match
176+
case OrType(tp1, tp2) => foundClasses(tp2, foundClasses(tp1, acc))
177+
case _ => effectiveClass(tp) :: acc
176178

177179
def inMatch =
178180
tree.fun.symbol == defn.Any_typeTest || // new scheme
@@ -181,7 +183,7 @@ object TypeTestsCasts {
181183
def transformIsInstanceOf(expr: Tree, testType: Type, flagUnrelated: Boolean): Tree = {
182184
def testCls = effectiveClass(testType.widen)
183185

184-
def unreachable(why: => String): Boolean = {
186+
def unreachable(why: => String)(using ctx: Context): Boolean = {
185187
if (flagUnrelated)
186188
if (inMatch) ctx.error(em"this case is unreachable since $why", expr.sourcePos)
187189
else ctx.warning(em"this will always yield false since $why", expr.sourcePos)
@@ -191,7 +193,7 @@ object TypeTestsCasts {
191193
/** Are `foundCls` and `testCls` classes that allow checks
192194
* whether a test would be always false?
193195
*/
194-
def isCheckable =
196+
def isCheckable(foundCls: Symbol) =
195197
foundCls.isClass && testCls.isClass &&
196198
!(testCls.isPrimitiveValueClass && !foundCls.isPrimitiveValueClass) &&
197199
// if `test` is primitive but `found` is not, we might have a case like
@@ -203,8 +205,8 @@ object TypeTestsCasts {
203205
/** Check whether a runtime test that a value of `foundCls` can be a `testCls`
204206
* can be true in some cases. Issues a warning or an error otherwise.
205207
*/
206-
def checkSensical: Boolean =
207-
if (!isCheckable) true
208+
def checkSensical(foundCls: Symbol)(using Context): Boolean =
209+
if (!isCheckable(foundCls)) true
208210
else if (foundCls.isPrimitiveValueClass && !testCls.isPrimitiveValueClass) {
209211
ctx.error("cannot test if value types are references", tree.sourcePos)
210212
false
@@ -214,38 +216,50 @@ object TypeTestsCasts {
214216
testCls.is(Final) || !testCls.is(Trait) && !foundCls.is(Trait)
215217
)
216218
if (foundCls.is(Final))
217-
unreachable(i"$foundCls is not a subclass of $testCls")
219+
unreachable(i"type ${expr.tpe.widen} is not a subclass of $testCls")
218220
else if (unrelated)
219-
unreachable(i"$foundCls and $testCls are unrelated")
221+
unreachable(i"type ${expr.tpe.widen} and $testCls are unrelated")
220222
else true
221223
}
222224
else true
223225

224226
if (expr.tpe <:< testType)
225227
if (expr.tpe.isNotNull) {
226-
if (!inMatch) ctx.warning(TypeTestAlwaysSucceeds(foundCls, testCls), tree.sourcePos)
228+
if (!inMatch) ctx.warning(TypeTestAlwaysSucceeds(expr.tpe, testType), tree.sourcePos)
227229
constant(expr, Literal(Constant(true)))
228230
}
229231
else expr.testNotNull
230-
else if (!checkSensical)
231-
constant(expr, Literal(Constant(false)))
232-
else if (testCls.isPrimitiveValueClass)
233-
if (foundCls.isPrimitiveValueClass)
234-
constant(expr, Literal(Constant(foundCls == testCls)))
232+
else {
233+
val nestedCtx = ctx.fresh.setNewTyperState()
234+
val foundClsSyms = foundClasses(expr.tpe.widen, Nil)
235+
val sensical = foundClsSyms.exists(sym => checkSensical(sym)(using nestedCtx))
236+
if (!sensical) {
237+
nestedCtx.typerState.commit()
238+
constant(expr, Literal(Constant(false)))
239+
}
240+
else if (testCls.isPrimitiveValueClass)
241+
foundClsSyms match
242+
case List(cls) if cls.isPrimitiveValueClass =>
243+
constant(expr, Literal(Constant(foundClsSyms.head == testCls)))
244+
case _ =>
245+
transformIsInstanceOf(expr, defn.boxedType(testCls.typeRef), flagUnrelated)
235246
else
236-
transformIsInstanceOf(expr, defn.boxedType(testCls.typeRef), flagUnrelated)
237-
else
238-
derivedTree(expr, defn.Any_isInstanceOf, testType)
247+
derivedTree(expr, defn.Any_isInstanceOf, testType)
248+
}
239249
}
240250

241251
def transformAsInstanceOf(testType: Type): Tree = {
242-
def testCls = testType.widen.classSymbol
252+
def testCls = effectiveClass(testType.widen)
253+
def foundClsSymPrimitive = {
254+
val foundClsSyms = foundClasses(expr.tpe.widen, Nil)
255+
foundClsSyms.size == 1 && foundClsSyms.head.isPrimitiveValueClass
256+
}
243257
if (erasure(expr.tpe) <:< testType)
244258
Typed(expr, tree.args.head) // Replace cast by type ascription (which does not generate any bytecode)
245259
else if (testCls eq defn.BoxedUnitClass)
246260
// as a special case, casting to Unit always successfully returns Unit
247261
Block(expr :: Nil, Literal(Constant(()))).withSpan(expr.span)
248-
else if (foundCls.isPrimitiveValueClass)
262+
else if (foundClsSymPrimitive)
249263
if (testCls.isPrimitiveValueClass) primitiveConversion(expr, testCls)
250264
else derivedTree(box(expr), defn.Any_asInstanceOf, testType)
251265
else if (testCls.isPrimitiveValueClass)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
case class A(a: Int)
2+
case class B(b: Int)
3+
case class C(c: Int)
4+
5+
val a = (A(1): A) match {
6+
case A(_) => "OK"
7+
case B(_) => "NOT OK" // error: this case is unreachable since class A and class B are unrelated
8+
}
9+
10+
val b = (A(1): A | B) match {
11+
case A(_) => "OK"
12+
case B(_) => "OK"
13+
case C(_) => "NOT OK" // error
14+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-- Error: tests/neg-custom-args/fatal-warnings/i8711.scala:7:9 ---------------------------------------------------------
2+
7 | case x: B => x // error: this case is unreachable since class A is not a subclass of class B
3+
| ^
4+
| this case is unreachable since type A and class B are unrelated
5+
-- Error: tests/neg-custom-args/fatal-warnings/i8711.scala:12:9 --------------------------------------------------------
6+
12 | case x: C => x // error
7+
| ^
8+
| this case is unreachable since type A | B and class C are unrelated
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class A
2+
class B
3+
class C
4+
5+
object Test {
6+
def foo(x: A) = x match {
7+
case x: B => x // error: this case is unreachable since class A is not a subclass of class B
8+
case _ =>
9+
}
10+
11+
def bar(x: A | B) = x match {
12+
case x: C => x // error
13+
case _ =>
14+
}
15+
}

0 commit comments

Comments
 (0)