Skip to content

Commit 0c43efa

Browse files
committed
Fix #5006: Normalize prefixes of TypeApply in Erasure
1 parent a35d724 commit 0c43efa

File tree

5 files changed

+151
-138
lines changed

5 files changed

+151
-138
lines changed

compiler/src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1253,15 +1253,15 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
12531253
def desugarIdent(tree: Ident)(implicit ctx: Context): Tree = {
12541254
val qual = desugarIdentPrefix(tree)
12551255
if (qual.isEmpty) tree
1256-
else qual.select(tree.symbol)
1256+
else qual.select(tree.symbol).withSpan(tree.span)
12571257
}
12581258

12591259
/** Recover identifier prefix (e.g. this) if it exists */
12601260
def desugarIdentPrefix(tree: Ident)(implicit ctx: Context): Tree = tree.tpe match {
12611261
case TermRef(prefix: TermRef, _) =>
1262-
ref(prefix)
1262+
ref(prefix).withSpan(tree.span)
12631263
case TermRef(prefix: ThisType, _) =>
1264-
This(prefix.cls)
1264+
This(prefix.cls).withSpan(tree.span)
12651265
case _ =>
12661266
EmptyTree
12671267
}

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

Lines changed: 137 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -156,159 +156,161 @@ object TypeTestsCasts {
156156
}
157157

158158
def interceptTypeApply(tree: TypeApply)(implicit ctx: Context): Tree = trace(s"transforming ${tree.show}", show = true) {
159-
tree.fun match {
160-
case fun @ Select(expr, selector) =>
161-
val sym = tree.symbol
159+
val expr = tree.fun match {
160+
case Select(expr, _) => expr
161+
case i: Ident => desugarIdentPrefix(i)
162+
case _ => EmptyTree
163+
}
164+
if (expr.isEmpty) tree
165+
else {
166+
val sym = tree.symbol
167+
168+
def isPrimitive(tp: Type) = tp.classSymbol.isPrimitiveValueClass
162169

163-
def isPrimitive(tp: Type) = tp.classSymbol.isPrimitiveValueClass
170+
def derivedTree(expr1: Tree, sym: Symbol, tp: Type) =
171+
cpy.TypeApply(tree)(expr1.select(sym).withSpan(expr.span), List(TypeTree(tp)))
164172

165-
def derivedTree(expr1: Tree, sym: Symbol, tp: Type) =
166-
cpy.TypeApply(tree)(expr1.select(sym).withSpan(expr.span), List(TypeTree(tp)))
173+
def effectiveClass(tp: Type): Symbol =
174+
if (tp.isRef(defn.PairClass)) effectiveClass(erasure(tp))
175+
else tp.classSymbol
167176

168-
def effectiveClass(tp: Type): Symbol =
169-
if (tp.isRef(defn.PairClass)) effectiveClass(erasure(tp))
170-
else tp.classSymbol
177+
def foundCls = effectiveClass(expr.tpe.widen)
171178

172-
def foundCls = effectiveClass(expr.tpe.widen)
179+
def inMatch =
180+
tree.fun.symbol == defn.Any_typeTest || // new scheme
181+
expr.symbol.is(Case) // old scheme
173182

174-
def inMatch =
175-
fun.symbol == defn.Any_typeTest || // new scheme
176-
expr.symbol.is(Case) // old scheme
183+
def transformIsInstanceOf(expr: Tree, testType: Type, flagUnrelated: Boolean): Tree = {
184+
def testCls = effectiveClass(testType.widen)
177185

178-
def transformIsInstanceOf(expr: Tree, testType: Type, flagUnrelated: Boolean): Tree = {
179-
def testCls = effectiveClass(testType.widen)
186+
def unreachable(why: => String): Boolean = {
187+
if (flagUnrelated)
188+
if (inMatch) ctx.error(em"this case is unreachable since $why", expr.sourcePos)
189+
else ctx.warning(em"this will always yield false since $why", expr.sourcePos)
190+
false
191+
}
180192

181-
def unreachable(why: => String): Boolean = {
182-
if (flagUnrelated)
183-
if (inMatch) ctx.error(em"this case is unreachable since $why", expr.sourcePos)
184-
else ctx.warning(em"this will always yield false since $why", expr.sourcePos)
193+
/** Are `foundCls` and `testCls` classes that allow checks
194+
* whether a test would be always false?
195+
*/
196+
def isCheckable =
197+
foundCls.isClass && testCls.isClass &&
198+
!(testCls.isPrimitiveValueClass && !foundCls.isPrimitiveValueClass) &&
199+
// if `test` is primitive but `found` is not, we might have a case like
200+
// found = java.lang.Integer, test = Int, which could be true
201+
// (not sure why that is so, but scalac behaves the same way)
202+
!isDerivedValueClass(foundCls) && !isDerivedValueClass(testCls)
203+
// we don't have the logic to handle derived value classes
204+
205+
/** Check whether a runtime test that a value of `foundCls` can be a `testCls`
206+
* can be true in some cases. Issues a warning or an error otherwise.
207+
*/
208+
def checkSensical: Boolean =
209+
if (!isCheckable) true
210+
else if (foundCls.isPrimitiveValueClass && !testCls.isPrimitiveValueClass) {
211+
ctx.error("cannot test if value types are references", tree.sourcePos)
185212
false
186213
}
187-
188-
/** Are `foundCls` and `testCls` classes that allow checks
189-
* whether a test would be always false?
190-
*/
191-
def isCheckable =
192-
foundCls.isClass && testCls.isClass &&
193-
!(testCls.isPrimitiveValueClass && !foundCls.isPrimitiveValueClass) &&
194-
// if `test` is primitive but `found` is not, we might have a case like
195-
// found = java.lang.Integer, test = Int, which could be true
196-
// (not sure why that is so, but scalac behaves the same way)
197-
!isDerivedValueClass(foundCls) && !isDerivedValueClass(testCls)
198-
// we don't have the logic to handle derived value classes
199-
200-
/** Check whether a runtime test that a value of `foundCls` can be a `testCls`
201-
* can be true in some cases. Issues a warning or an error otherwise.
202-
*/
203-
def checkSensical: Boolean =
204-
if (!isCheckable) true
205-
else if (foundCls.isPrimitiveValueClass && !testCls.isPrimitiveValueClass) {
206-
ctx.error("cannot test if value types are references", tree.sourcePos)
207-
false
208-
}
209-
else if (!foundCls.derivesFrom(testCls)) {
210-
val unrelated = !testCls.derivesFrom(foundCls) && (
211-
testCls.is(Final) || !testCls.is(Trait) && !foundCls.is(Trait)
212-
)
213-
if (foundCls.is(Final))
214-
unreachable(i"$foundCls is not a subclass of $testCls")
215-
else if (unrelated)
216-
unreachable(i"$foundCls and $testCls are unrelated")
217-
else true
218-
}
214+
else if (!foundCls.derivesFrom(testCls)) {
215+
val unrelated = !testCls.derivesFrom(foundCls) && (
216+
testCls.is(Final) || !testCls.is(Trait) && !foundCls.is(Trait)
217+
)
218+
if (foundCls.is(Final))
219+
unreachable(i"$foundCls is not a subclass of $testCls")
220+
else if (unrelated)
221+
unreachable(i"$foundCls and $testCls are unrelated")
219222
else true
223+
}
224+
else true
220225

221-
if (expr.tpe <:< testType)
222-
if (expr.tpe.isNotNull) {
223-
if (!inMatch) ctx.warning(TypeTestAlwaysSucceeds(foundCls, testCls), tree.sourcePos)
224-
constant(expr, Literal(Constant(true)))
225-
}
226-
else expr.testNotNull
227-
else if (!checkSensical)
228-
constant(expr, Literal(Constant(false)))
229-
else if (testCls.isPrimitiveValueClass)
230-
if (foundCls.isPrimitiveValueClass)
231-
constant(expr, Literal(Constant(foundCls == testCls)))
232-
else
233-
transformIsInstanceOf(expr, defn.boxedType(testCls.typeRef), flagUnrelated)
226+
if (expr.tpe <:< testType)
227+
if (expr.tpe.isNotNull) {
228+
if (!inMatch) ctx.warning(TypeTestAlwaysSucceeds(foundCls, testCls), tree.sourcePos)
229+
constant(expr, Literal(Constant(true)))
230+
}
231+
else expr.testNotNull
232+
else if (!checkSensical)
233+
constant(expr, Literal(Constant(false)))
234+
else if (testCls.isPrimitiveValueClass)
235+
if (foundCls.isPrimitiveValueClass)
236+
constant(expr, Literal(Constant(foundCls == testCls)))
234237
else
235-
derivedTree(expr, defn.Any_isInstanceOf, testType)
238+
transformIsInstanceOf(expr, defn.boxedType(testCls.typeRef), flagUnrelated)
239+
else
240+
derivedTree(expr, defn.Any_isInstanceOf, testType)
241+
}
242+
243+
def transformAsInstanceOf(testType: Type): Tree = {
244+
def testCls = testType.widen.classSymbol
245+
if (expr.tpe <:< testType)
246+
Typed(expr, tree.args.head)
247+
else if (testCls eq defn.BoxedUnitClass) {
248+
// as a special case, casting to Unit always successfully returns Unit
249+
Block(expr :: Nil, Literal(Constant(()))).withSpan(expr.span)
250+
}
251+
else if (foundCls.isPrimitiveValueClass) {
252+
if (testCls.isPrimitiveValueClass) primitiveConversion(expr, testCls)
253+
else derivedTree(box(expr), defn.Any_asInstanceOf, testType)
236254
}
255+
else if (testCls.isPrimitiveValueClass)
256+
unbox(expr.ensureConforms(defn.ObjectType), testType)
257+
else if (isDerivedValueClass(testCls)) {
258+
expr // adaptToType in Erasure will do the necessary type adaptation
259+
}
260+
else if (testCls eq defn.NothingClass) {
261+
// In the JVM `x.asInstanceOf[Nothing]` would throw a class cast exception except when `x eq null`.
262+
// To avoid this loophole we execute `x` and then regardless of the result throw a `ClassCastException`
263+
val throwCCE = Throw(New(defn.ClassCastExceptionClass.typeRef, defn.ClassCastExceptionClass_stringConstructor,
264+
Literal(Constant("Cannot cast to scala.Nothing")) :: Nil))
265+
Block(expr :: Nil, throwCCE).withSpan(expr.span)
266+
}
267+
else
268+
derivedTree(expr, defn.Any_asInstanceOf, testType)
269+
}
237270

238-
def transformAsInstanceOf(testType: Type): Tree = {
239-
def testCls = testType.widen.classSymbol
240-
if (expr.tpe <:< testType)
241-
Typed(expr, tree.args.head)
242-
else if (testCls eq defn.BoxedUnitClass) {
243-
// as a special case, casting to Unit always successfully returns Unit
244-
Block(expr :: Nil, Literal(Constant(()))).withSpan(expr.span)
271+
/** Transform isInstanceOf OrType
272+
*
273+
* expr.isInstanceOf[A | B] ~~> expr.isInstanceOf[A] | expr.isInstanceOf[B]
274+
* expr.isInstanceOf[A & B] ~~> expr.isInstanceOf[A] & expr.isInstanceOf[B]
275+
*
276+
* The transform happens before erasure of `testType`, thus cannot be merged
277+
* with `transformIsInstanceOf`, which depends on erased type of `testType`.
278+
*/
279+
def transformTypeTest(expr: Tree, testType: Type, flagUnrelated: Boolean): Tree = testType.dealias match {
280+
case _: SingletonType =>
281+
expr.isInstance(testType).withSpan(tree.span)
282+
case OrType(tp1, tp2) =>
283+
evalOnce(expr) { e =>
284+
transformTypeTest(e, tp1, flagUnrelated = false)
285+
.or(transformTypeTest(e, tp2, flagUnrelated = false))
245286
}
246-
else if (foundCls.isPrimitiveValueClass) {
247-
if (testCls.isPrimitiveValueClass) primitiveConversion(expr, testCls)
248-
else derivedTree(box(expr), defn.Any_asInstanceOf, testType)
287+
case AndType(tp1, tp2) =>
288+
evalOnce(expr) { e =>
289+
transformTypeTest(e, tp1, flagUnrelated)
290+
.and(transformTypeTest(e, tp2, flagUnrelated))
249291
}
250-
else if (testCls.isPrimitiveValueClass)
251-
unbox(expr.ensureConforms(defn.ObjectType), testType)
252-
else if (isDerivedValueClass(testCls)) {
253-
expr // adaptToType in Erasure will do the necessary type adaptation
292+
case defn.MultiArrayOf(elem, ndims) if isUnboundedGeneric(elem) =>
293+
def isArrayTest(arg: Tree) =
294+
ref(defn.runtimeMethodRef(nme.isArray)).appliedTo(arg, Literal(Constant(ndims)))
295+
if (ndims == 1) isArrayTest(expr)
296+
else evalOnce(expr) { e =>
297+
derivedTree(e, defn.Any_isInstanceOf, e.tpe)
298+
.and(isArrayTest(e))
254299
}
255-
else if (testCls eq defn.NothingClass) {
256-
// In the JVM `x.asInstanceOf[Nothing]` would throw a class cast exception except when `x eq null`.
257-
// To avoid this loophole we execute `x` and then regardless of the result throw a `ClassCastException`
258-
val throwCCE = Throw(New(defn.ClassCastExceptionClass.typeRef, defn.ClassCastExceptionClass_stringConstructor,
259-
Literal(Constant("Cannot cast to scala.Nothing")) :: Nil))
260-
Block(expr :: Nil, throwCCE).withSpan(expr.span)
261-
}
262-
else
263-
derivedTree(expr, defn.Any_asInstanceOf, testType)
264-
}
265-
266-
/** Transform isInstanceOf OrType
267-
*
268-
* expr.isInstanceOf[A | B] ~~> expr.isInstanceOf[A] | expr.isInstanceOf[B]
269-
* expr.isInstanceOf[A & B] ~~> expr.isInstanceOf[A] & expr.isInstanceOf[B]
270-
*
271-
* The transform happens before erasure of `testType`, thus cannot be merged
272-
* with `transformIsInstanceOf`, which depends on erased type of `testType`.
273-
*/
274-
def transformTypeTest(expr: Tree, testType: Type, flagUnrelated: Boolean): Tree = testType.dealias match {
275-
case _: SingletonType =>
276-
expr.isInstance(testType).withSpan(tree.span)
277-
case OrType(tp1, tp2) =>
278-
evalOnce(expr) { e =>
279-
transformTypeTest(e, tp1, flagUnrelated = false)
280-
.or(transformTypeTest(e, tp2, flagUnrelated = false))
281-
}
282-
case AndType(tp1, tp2) =>
283-
evalOnce(expr) { e =>
284-
transformTypeTest(e, tp1, flagUnrelated)
285-
.and(transformTypeTest(e, tp2, flagUnrelated))
286-
}
287-
case defn.MultiArrayOf(elem, ndims) if isUnboundedGeneric(elem) =>
288-
def isArrayTest(arg: Tree) =
289-
ref(defn.runtimeMethodRef(nme.isArray)).appliedTo(arg, Literal(Constant(ndims)))
290-
if (ndims == 1) isArrayTest(expr)
291-
else evalOnce(expr) { e =>
292-
derivedTree(e, defn.Any_isInstanceOf, e.tpe)
293-
.and(isArrayTest(e))
294-
}
295-
case _ =>
296-
transformIsInstanceOf(expr, erasure(testType), flagUnrelated)
297-
}
298-
299-
if (sym.isTypeTest) {
300-
val argType = tree.args.head.tpe
301-
val isTrusted = tree.hasAttachment(PatternMatcher.TrustedTypeTestKey)
302-
if (!isTrusted && !checkable(expr.tpe, argType, tree.span))
303-
ctx.warning(i"the type test for $argType cannot be checked at runtime", tree.sourcePos)
304-
transformTypeTest(expr, tree.args.head.tpe, flagUnrelated = true)
305-
}
306-
else if (sym.isTypeCast)
307-
transformAsInstanceOf(erasure(tree.args.head.tpe))
308-
else tree
300+
case _ =>
301+
transformIsInstanceOf(expr, erasure(testType), flagUnrelated)
302+
}
309303

310-
case _ =>
311-
tree
304+
if (sym.isTypeTest) {
305+
val argType = tree.args.head.tpe
306+
val isTrusted = tree.hasAttachment(PatternMatcher.TrustedTypeTestKey)
307+
if (!isTrusted && !checkable(expr.tpe, argType, tree.span))
308+
ctx.warning(i"the type test for $argType cannot be checked at runtime", tree.sourcePos)
309+
transformTypeTest(expr, tree.args.head.tpe, flagUnrelated = true)
310+
}
311+
else if (sym.isTypeCast)
312+
transformAsInstanceOf(erasure(tree.args.head.tpe))
313+
else tree
312314
}
313315
}
314316
}

tests/pos/i5006.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
object i0 {
2+
def f: Int = asInstanceOf[Int].toInt
3+
}
4+
5+
class i2 {
6+
def f: Int = asInstanceOf[Int].toInt
7+
}
8+
9+
trait i3 {
10+
def f: Int = asInstanceOf[Int].toInt
11+
}

0 commit comments

Comments
 (0)