Skip to content

Commit 0b7710a

Browse files
committed
Merge IsInstanceOfChecker and TypeTestsCasts
It turns out it is difficult to find a good home for CutErasedDecls. Further tests revealed it has to run after the group of ExplicitOuter, since erased defs still need to be analyzed to see whether they require outer references to be generated. But it has also to run before checking whether InstanceOfs can be computed at runtime. The only way to accommodate that is to merge IsInstanceOfChecker with TypeTestsCasts, so that it is run at erasure. That's more coherent and shorter anyway.
1 parent 57853b0 commit 0b7710a

File tree

5 files changed

+124
-162
lines changed

5 files changed

+124
-162
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,15 +73,14 @@ class Compiler {
7373
new HoistSuperArgs, // Hoist complex arguments of supercalls to enclosing scope
7474
new ClassOf, // Expand `Predef.classOf` calls.
7575
new RefChecks) :: // Various checks mostly related to abstract members and overriding
76-
List(new CutErasedDecls, // Drop erased definitions from scopes and simplify erased expressions
77-
new TryCatchPatterns, // Compile cases in try/catch
76+
List(new TryCatchPatterns, // Compile cases in try/catch
7877
new PatternMatcher, // Compile pattern matches
7978
new ExplicitOuter, // Add accessors to outer classes from nested ones.
8079
new ExplicitSelf, // Make references to non-trivial self types explicit as casts
8180
new StringInterpolatorOpt, // Optimizes raw and s string interpolators by rewriting them to string concatentations
8281
new CrossCastAnd, // Normalize selections involving intersection types.
8382
new Splitter) :: // Expand selections involving union types into conditionals
84-
List(new IsInstanceOfChecker, // check runtime realisability for `isInstanceOf`
83+
List(new CutErasedDecls, // Drop erased definitions from scopes and simplify erased expressions
8584
new VCInlineMethods, // Inlines calls to value class methods
8685
new SeqLiterals, // Express vararg arguments as arrays
8786
new InterceptedMethods, // Special handling of `==`, `|=`, `getClass` methods

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class CutErasedDecls extends MiniPhase with SymTransformer { thisTransform =>
2525

2626
override def changesMembers = true // makes erased members private
2727

28-
override def runsAfterGroupsOf = Set(RefChecks.name)
28+
override def runsAfterGroupsOf = Set(RefChecks.name, ExplicitOuter.name)
2929

3030
override def transformSym(sym: SymDenotation)(implicit ctx: Context): SymDenotation =
3131
if (sym.is(Erased, butNot = Private) && sym.owner.isClass)

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

Lines changed: 0 additions & 151 deletions
This file was deleted.

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,8 @@ class ResolveSuper extends MiniPhase with IdentityDenotTransformer { thisPhase =
5151
override def phaseName: String = ResolveSuper.name
5252

5353
override def runsAfter = Set(ElimByName.name, // verified empirically, need to figure out what the reason is.
54-
AugmentScala2Traits.name)
55-
56-
override def runsAfterGroupsOf = Set(CutErasedDecls.name) // Erased decls make `isCurrent` work incorrectly
54+
AugmentScala2Traits.name,
55+
CutErasedDecls.name) // Erased decls make `isCurrent` work incorrectly
5756

5857
override def changesMembers = true // the phase adds super accessors and method forwarders
5958

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

Lines changed: 119 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package transform
44
import core._
55
import Contexts._, Symbols._, Types._, Constants._, StdNames._, Decorators._
66
import ast.Trees._
7+
import ast.untpd
78
import Erasure.Boxing._
89
import TypeErasure._
910
import ValueClasses._
@@ -12,7 +13,7 @@ import core.Flags._
1213
import util.Positions._
1314
import reporting.diagnostic.messages.TypeTestAlwaysSucceeds
1415
import reporting.trace
15-
16+
import config.Printers.{ transforms => debug }
1617

1718
/** This transform normalizes type tests and type casts,
1819
* also replacing type tests with singleton argument type with reference equality check
@@ -21,12 +22,122 @@ import reporting.trace
2122
* - have a reference type as receiver
2223
* - can be translated directly to machine instructions
2324
*
24-
*
2525
* Unfortunately this phase ended up being not Y-checkable unless types are erased. A cast to an ConstantType(3) or x.type
26-
* cannot be rewritten before erasure.
26+
* cannot be rewritten before erasure. That's why TypeTestsCasts is called from Erasure.
2727
*/
2828
object TypeTestsCasts {
2929
import ast.tpd._
30+
import typer.Inferencing.maximizeType
31+
import typer.ProtoTypes.constrained
32+
33+
/** Whether `(x:X).isInstanceOf[P]` can be checked at runtime?
34+
*
35+
* First do the following substitution:
36+
* (a) replace `T @unchecked` and pattern binder types (e.g., `_$1`) in P with WildcardType
37+
* (b) replace pattern binder types (e.g., `_$1`) in X:
38+
* - variance = 1 : hiBound
39+
* - variance = -1 : loBound
40+
* - variance = 0 : OrType(Any, Nothing) // TODO: use original type param bounds
41+
*
42+
* Then check:
43+
*
44+
* 1. if `X <:< P`, TRUE
45+
* 2. if `P` is a singleton type, TRUE
46+
* 3. if `P` refers to an abstract type member or type parameter, FALSE
47+
* 4. if `P = Array[T]`, checkable(E, T) where `E` is the element type of `X`, defaults to `Any`.
48+
* 5. if `P` is `pre.F[Ts]` and `pre.F` refers to a class which is not `Array`:
49+
* (a) replace `Ts` with fresh type variables `Xs`
50+
* (b) constrain `Xs` with `pre.F[Xs] <:< X`
51+
* (c) instantiate Xs and check `pre.F[Xs] <:< P`
52+
* 6. if `P = T1 | T2` or `P = T1 & T2`, checkable(X, T1) && checkable(X, T2).
53+
* 7. if `P` is a refinement type, FALSE
54+
* 8. otherwise, TRUE
55+
*/
56+
def checkable(X: Type, P: Type, pos: Position)(implicit ctx: Context): Boolean = {
57+
def isAbstract(P: Type) = !P.dealias.typeSymbol.isClass
58+
def isPatternTypeSymbol(sym: Symbol) = !sym.isClass && sym.is(Case)
59+
60+
def replaceP(tp: Type)(implicit ctx: Context) = new TypeMap {
61+
def apply(tp: Type) = tp match {
62+
case tref: TypeRef
63+
if isPatternTypeSymbol(tref.typeSymbol) => WildcardType
64+
case AnnotatedType(_, annot)
65+
if annot.symbol == defn.UncheckedAnnot => WildcardType
66+
case _ => mapOver(tp)
67+
}
68+
}.apply(tp)
69+
70+
def replaceX(tp: Type)(implicit ctx: Context) = new TypeMap {
71+
def apply(tp: Type) = tp match {
72+
case tref: TypeRef
73+
if isPatternTypeSymbol(tref.typeSymbol) =>
74+
if (variance == 1) tref.info.hiBound
75+
else if (variance == -1) tref.info.loBound
76+
else OrType(defn.AnyType, defn.NothingType)
77+
case _ => mapOver(tp)
78+
}
79+
}.apply(tp)
80+
81+
/** Approximate type parameters depending on variance */
82+
def stripTypeParam(tp: Type)(implicit ctx: Context) = new ApproximatingTypeMap {
83+
def apply(tp: Type): Type = tp match {
84+
case tp: TypeRef if tp.underlying.isInstanceOf[TypeBounds] =>
85+
val lo = apply(tp.info.loBound)
86+
val hi = apply(tp.info.hiBound)
87+
range(lo, hi)
88+
case _ =>
89+
mapOver(tp)
90+
}
91+
}.apply(tp)
92+
93+
def isClassDetermined(X: Type, P: AppliedType)(implicit ctx: Context) = {
94+
val AppliedType(tycon, _) = P
95+
val typeLambda = tycon.ensureLambdaSub.asInstanceOf[TypeLambda]
96+
val tvars = constrained(typeLambda, untpd.EmptyTree, alwaysAddTypeVars = true)._2.map(_.tpe)
97+
val P1 = tycon.appliedTo(tvars)
98+
99+
debug.println("P : " + P)
100+
debug.println("P1 : " + P1)
101+
debug.println("X : " + X)
102+
103+
P1 <:< X // constraint P1
104+
105+
// use fromScala2x to avoid generating pattern bound symbols
106+
maximizeType(P1, pos, fromScala2x = true)
107+
108+
val res = P1 <:< P
109+
debug.println("P1 : " + P1)
110+
debug.println("P1 <:< P = " + res)
111+
112+
res
113+
}
114+
115+
def recur(X: Type, P: Type): Boolean = (X <:< P) || (P match {
116+
case _: SingletonType => true
117+
case _: TypeProxy
118+
if isAbstract(P) => false
119+
case defn.ArrayOf(tpT) =>
120+
X match {
121+
case defn.ArrayOf(tpE) => recur(tpE, tpT)
122+
case _ => recur(defn.AnyType, tpT)
123+
}
124+
case tpe: AppliedType =>
125+
// first try withou striping type parameters for performance
126+
isClassDetermined(X, tpe)(ctx.fresh.setNewTyperState()) ||
127+
isClassDetermined(stripTypeParam(X), tpe)(ctx.fresh.setNewTyperState())
128+
case AndType(tp1, tp2) => recur(X, tp1) && recur(X, tp2)
129+
case OrType(tp1, tp2) => recur(X, tp1) && recur(X, tp2)
130+
case AnnotatedType(t, _) => recur(X, t)
131+
case _: RefinedType => false
132+
case _ => true
133+
})
134+
135+
val res = recur(replaceX(X.widen), replaceP(P))
136+
137+
debug.println(i"checking ${X.show} isInstanceOf ${P} = $res")
138+
139+
res
140+
}
30141

31142
def interceptTypeApply(tree: TypeApply)(implicit ctx: Context): Tree = trace(s"transforming ${tree.show}", show = true) {
32143
tree.fun match {
@@ -156,8 +267,12 @@ object TypeTestsCasts {
156267
transformIsInstanceOf(expr, erasure(testType), flagUnrelated)
157268
}
158269

159-
if (sym.isTypeTest)
270+
if (sym.isTypeTest) {
271+
val argType = tree.args.head.tpe
272+
if (!checkable(expr.tpe, argType, tree.pos))
273+
ctx.warning(s"the type test for $argType cannot be checked at runtime", tree.pos)
160274
transformTypeTest(expr, tree.args.head.tpe, flagUnrelated = true)
275+
}
161276
else if (sym eq defn.Any_asInstanceOf)
162277
transformAsInstanceOf(erasure(tree.args.head.tpe))
163278
else tree

0 commit comments

Comments
 (0)