Skip to content

Commit d8897a0

Browse files
committed
Simplify uncheckable local classes check
1 parent e1e3dbd commit d8897a0

File tree

4 files changed

+54
-25
lines changed

4 files changed

+54
-25
lines changed

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

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ object TypeTestsCasts {
3131
import typer.Inferencing.maximizeType
3232
import typer.ProtoTypes.constrained
3333

34-
/** Whether `(x:X).isInstanceOf[P]` can be checked at runtime?
34+
/** Whether `(x: X).isInstanceOf[P]` can be checked at runtime?
3535
*
3636
* First do the following substitution:
3737
* (a) replace `T @unchecked` and pattern binder types (e.g., `_$1`) in P with WildcardType
@@ -48,7 +48,8 @@ object TypeTestsCasts {
4848
* (c) maximize `pre.F[Xs]` and check `pre.F[Xs] <:< P`
4949
* 6. if `P = T1 | T2` or `P = T1 & T2`, checkable(X, T1) && checkable(X, T2).
5050
* 7. if `P` is a refinement type, FALSE
51-
* 8. otherwise, TRUE
51+
* 8. if `P` is a local class which is not statically reachable from the scope where `X` is defined, FALSE
52+
* 9. otherwise, TRUE
5253
*/
5354
def checkable(X: Type, P: Type, span: Span)(using Context): Boolean = atPhase(Phases.refchecksPhase.next) {
5455
// Run just before ElimOpaque transform (which follows RefChecks)
@@ -58,7 +59,7 @@ object TypeTestsCasts {
5859
def apply(tp: Type) = tp match {
5960
case tref: TypeRef if tref.typeSymbol.isPatternBound =>
6061
WildcardType
61-
case AnnotatedType(_, annot) if annot.symbol == defn.UncheckedAnnot =>
62+
case tp if tp.hasAnnotation(defn.UncheckedAnnot) =>
6263
WildcardType
6364
case _ => mapOver(tp)
6465
}
@@ -123,11 +124,6 @@ object TypeTestsCasts {
123124

124125
}
125126

126-
lazy val scrutineeIsUnchecked = X.widenTermRefExpr.existsPart {
127-
case AnnotatedType(_, annot) if annot.symbol == defn.UncheckedAnnot => true
128-
case _ => false
129-
}
130-
131127
def recur(X: Type, P: Type): Boolean = (X <:< P) || (P.dealias match {
132128
case _: SingletonType => true
133129
case _: TypeProxy
@@ -157,26 +153,13 @@ object TypeTestsCasts {
157153
case AnnotatedType(t, _) => recur(X, t)
158154
case tp2: RefinedType => recur(X, tp2.parent) && TypeComparer.hasMatchingMember(tp2.refinedName, X, tp2)
159155
case tp2: RecType => recur(X, tp2.parent)
160-
case tp2
161-
if tp2.typeSymbol.isLocal && !scrutineeIsUnchecked =>
162-
val sym = tp2.typeSymbol
163-
val methodSymbol = sym.owner
164-
val tpSyms = typer.ErrorReporting.substitutableTypeSymbolsInScope(sym).toSet
165-
def isAccessible(sym: Symbol): Boolean = sym == methodSymbol || sym.isType && isAccessible(sym.owner)
166-
val seen = scala.collection.mutable.Set.empty[Type]
167-
def hasPoison(tp: Type): Boolean =
168-
seen += tp
169-
tp.baseClasses.filter(isAccessible).exists { sym =>
170-
sym.info.decls.exists { sym =>
171-
sym.info.existsPart(tp => tpSyms.contains(tp.typeSymbol))
172-
|| !seen.contains(sym.info) && isAccessible(sym.info.typeSymbol.maybeOwner) && hasPoison(sym.info)
173-
}
174-
}
175-
!hasPoison(tp2)
156+
case _
157+
if P.classSymbol.isLocal && P.classSymbol.isInaccessibleChildOf(X.classSymbol) => // 8
158+
false
176159
case _ => true
177160
})
178161

179-
val res = recur(X.widen, replaceP(P))
162+
val res = X.widenTermRefExpr.hasAnnotation(defn.UncheckedAnnot) || recur(X.widen, replaceP(P))
180163

181164
debug.println(i"checking ${X.show} isInstanceOf ${P} = $res")
182165

tests/neg/i4812.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,7 @@
2222
60 | case prev: A => // error: the type test for A cannot be checked at runtime
2323
| ^
2424
| the type test for A cannot be checked at runtime
25+
-- Error: tests/neg/i4812.scala:96:11 ----------------------------------------------------------------------------------
26+
96 | case x: B => // error: the type test for B cannot be checked at runtime
27+
| ^
28+
| the type test for B cannot be checked at runtime

tests/neg/i4812.new.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// scalac: -Werror
2+
object Test:
3+
sealed class A
4+
var prevA: A = _
5+
def test9: A =
6+
val methodCallId = System.nanoTime()
7+
class B(val id: Long) extends A
8+
prevA match
9+
case x: B => // error: the type test for B cannot be checked at runtime
10+
x.ensuring(x.id == methodCallId, s"Method call id $methodCallId != ${x.id}")
11+
case _ =>
12+
val x = new B(methodCallId)
13+
prevA = x
14+
x

tests/neg/i4812.scala

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,34 @@ object Test:
8080
case prev: A => prev.elem
8181
case _ => prev = new A(x); x
8282

83+
def test9 =
84+
trait A
85+
class B extends A
86+
val x: A = new B
87+
x match
88+
case x: B => x
89+
90+
sealed class A
91+
var prevA: A = _
92+
def test10: A =
93+
val methodCallId = System.nanoTime()
94+
class B(val id: Long) extends A
95+
prevA match
96+
case x: B => // error: the type test for B cannot be checked at runtime
97+
x.ensuring(x.id == methodCallId, s"Method call id $methodCallId != ${x.id}")
98+
case _ =>
99+
val x = new B(methodCallId)
100+
prevA = x
101+
x
102+
103+
def test11 =
104+
trait A
105+
trait B
106+
class C extends A with B
107+
val x: A = new C
108+
x match
109+
case x: B => x
110+
83111
def main(args: Array[String]): Unit =
84112
test(1)
85113
val x: String = test("") // was: ClassCastException: java.lang.Integer cannot be cast to java.lang.String

0 commit comments

Comments
 (0)