Skip to content

Commit 0b4e96c

Browse files
authored
Merge pull request #14715 from dotty-staging/fix-14705-v2
Don't optimize explicitly written isInstanceOf tests away.
2 parents 0aafca1 + 9648c63 commit 0b4e96c

File tree

8 files changed

+68
-25
lines changed

8 files changed

+68
-25
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID]:
131131
DoubleDefinitionID,
132132
MatchCaseOnlyNullWarningID,
133133
ImportRenamedTwiceID,
134-
TypeTestAlwaysSucceedsID,
134+
TypeTestAlwaysDivergesID,
135135
TermMemberNeedsNeedsResultTypeForImplicitSearchID,
136136
ClassCannotExtendEnumID,
137137
ValueClassParameterMayNotBeCallByNameID,

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

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2177,13 +2177,9 @@ import transform.SymUtils._
21772177
def explain = ""
21782178
}
21792179

2180-
class TypeTestAlwaysSucceeds(scrutTp: Type, testTp: Type)(using Context) extends SyntaxMsg(TypeTestAlwaysSucceedsID) {
2181-
def msg = {
2182-
val addendum =
2183-
if (scrutTp != testTp) s" is a subtype of ${testTp.show}"
2184-
else " is the same as the tested type"
2185-
s"The highlighted type test will always succeed since the scrutinee type ${scrutTp.show}" + addendum
2186-
}
2180+
class TypeTestAlwaysDiverges(scrutTp: Type, testTp: Type)(using Context) extends SyntaxMsg(TypeTestAlwaysDivergesID) {
2181+
def msg =
2182+
s"This type test will never return a result since the scrutinee type ${scrutTp.show} does not contain any value."
21872183
def explain = ""
21882184
}
21892185

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

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,16 @@ package transform
33

44
import ast.{TreeTypeMap, tpd}
55
import config.Printers.tailrec
6-
import core.Contexts._
7-
import core.Constants.Constant
8-
import core.Flags._
9-
import core.NameKinds.{TailLabelName, TailLocalName, TailTempName}
10-
import core.StdNames.nme
11-
import core.Symbols._
12-
import reporting._
6+
import core.*
7+
import Contexts.*, Flags.*, Symbols.*
8+
import Constants.Constant
9+
import NameKinds.{TailLabelName, TailLocalName, TailTempName}
10+
import StdNames.nme
11+
import reporting.*
1312
import transform.MegaPhase.MiniPhase
1413
import util.LinearSet
1514
import dotty.tools.uncheckedNN
1615

17-
1816
/** A Tail Rec Transformer.
1917
*
2018
* What it does:
@@ -161,15 +159,26 @@ class TailRec extends MiniPhase {
161159
val rhsFullyTransformed = varForRewrittenThis match {
162160
case Some(localThisSym) =>
163161
val thisRef = localThisSym.termRef
164-
new TreeTypeMap(
162+
val substitute = new TreeTypeMap(
165163
typeMap = _.substThisUnlessStatic(enclosingClass, thisRef)
166164
.subst(rewrittenParamSyms, varsForRewrittenParamSyms.map(_.termRef)),
167165
treeMap = {
168166
case tree: This if tree.symbol == enclosingClass => Ident(thisRef)
169167
case tree => tree
170168
}
171-
).transform(rhsSemiTransformed)
172-
169+
)
170+
// The previous map will map `This` references to `Ident`s even under `Super`.
171+
// This violates super's contract. We fix this by cleaning up `Ident`s under
172+
// super, mapping them back to the original `This` reference. This is not
173+
// very elegant, but I did not manage to find a cleaner way to handle this.
174+
// See pos/tailrec-super.scala for a test case.
175+
val cleanup = new TreeMap:
176+
override def transform(t: Tree)(using Context) = t match
177+
case Super(qual: Ident, mix) if !qual.tpe.isInstanceOf[Types.ThisType] =>
178+
cpy.Super(t)(This(enclosingClass), mix)
179+
case _ =>
180+
super.transform(t)
181+
cleanup.transform(substitute.transform(rhsSemiTransformed))
173182
case none =>
174183
new TreeTypeMap(
175184
typeMap = _.subst(rewrittenParamSyms, varsForRewrittenParamSyms.map(_.termRef))

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ class TreeChecker extends Phase with SymTransformer {
418418
}
419419

420420
override def typedSuper(tree: untpd.Super, pt: Type)(using Context): Tree =
421-
assert(tree.qual.tpe.isInstanceOf[ThisType], i"expect prefix of Super to be This, actual = ${tree.qual}")
421+
assert(tree.qual.typeOpt.isInstanceOf[ThisType], i"expect prefix of Super to be This, actual = ${tree.qual}")
422422
super.typedSuper(tree, pt)
423423

424424
override def typedTyped(tree: untpd.Typed, pt: Type)(using Context): Tree =

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -243,13 +243,12 @@ object TypeTestsCasts {
243243
else foundClasses.exists(check)
244244
end checkSensical
245245

246-
if (expr.tpe <:< testType)
247-
if (expr.tpe.isNotNull) {
248-
if (!inMatch) report.warning(TypeTestAlwaysSucceeds(expr.tpe, testType), tree.srcPos)
249-
constant(expr, Literal(Constant(true)))
250-
}
246+
if (expr.tpe <:< testType) && inMatch then
247+
if expr.tpe.isNotNull then constant(expr, Literal(Constant(true)))
251248
else expr.testNotNull
252249
else {
250+
if expr.tpe.isBottomType then
251+
report.warning(TypeTestAlwaysDiverges(expr.tpe, testType), tree.srcPos)
253252
val nestedCtx = ctx.fresh.setNewTyperState()
254253
val foundClsSyms = foundClasses(expr.tpe.widen, Nil)
255254
val sensical = checkSensical(foundClsSyms)(using nestedCtx)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
val n = Nil
2+
val b = n.head.isInstanceOf[String] // error
3+

tests/pos/tailrec-super.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
class Tree
2+
case class Inlined(call: Tree, bindings: List[String], expr: Tree) extends Tree
3+
case object EmptyTree extends Tree
4+
class Context
5+
6+
class Transform:
7+
def transform(tree: Tree)(using Context): Tree = tree
8+
9+
class Inliner:
10+
var enclosingInlineds: List[String] = Nil
11+
private def expandMacro(using Context) =
12+
val inlinedNormalizer = new Transform:
13+
override def transform(tree: Tree)(using Context) = tree match
14+
case Inlined(EmptyTree, Nil, expr) if enclosingInlineds.isEmpty => transform(expr)
15+
case _ => super.transform(tree)
16+
17+
object Inliner

tests/run/i14705.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
trait Fruit
2+
case class Apple() extends Fruit
3+
case class Orange() extends Fruit
4+
5+
case class Box[C](fruit: C) extends Fruit
6+
7+
val apple = Box(fruit = Apple())
8+
val orange = Box(fruit = Orange())
9+
10+
11+
val result = List(apple, orange).map {
12+
case appleBox: Box[Apple] @unchecked if appleBox.fruit.isInstanceOf[Apple] => //contains apple
13+
"apple"
14+
case _ =>
15+
"orange"
16+
}
17+
18+
@main def Test =
19+
assert(result == List("apple", "orange"))

0 commit comments

Comments
 (0)