1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
package dotty.tools.dotc
package transform
import core._
import Contexts._, Symbols._, Types._, Flags._, Decorators._, StdNames._, Constants._
import SymDenotations.SymDenotation
import TreeTransforms._
import ast.untpd
import ast.Trees._
/** Expand SAM closures that cannot be represented by the JVM to anonymous classes.
* These fall into three categories
*
* 1. Partial function closures, we need to generate a isDefinedAt method for these.
* 2. Closures implementaing non-trait classes.
* 3. Closures that get synthesized abstract methods in the transformation pipeline. These methods can be
* (1) superaccessors, (2) outer references, (3) accessors for fields.
*/
class ExpandSAMs extends MiniPhaseTransform { thisTransformer =>
override def phaseName = "expandSAMs"
import ast.tpd._
def noJvmSam(cls: ClassSymbol)(implicit ctx: Context): Boolean =
!cls.is(Trait) || ExplicitOuter.needsOuterIfReferenced(cls) || cls.typeRef.fields.nonEmpty
override def transformBlock(tree: Block)(implicit ctx: Context, info: TransformerInfo): Tree = tree match {
case Block(stats @ (fn: DefDef) :: Nil, Closure(_, fnRef, tpt)) if fnRef.symbol == fn.symbol =>
tpt.tpe match {
case NoType => tree // it's a plain function
case tpe @ SAMType(_) if !noJvmSam(tpe.classSymbol.asClass) =>
if (tpe isRef defn.PartialFunctionClass) toPartialFunction(tree)
else tree
case tpe =>
cpy.Block(tree)(stats,
AnonClass(tpe, fn.symbol.asTerm :: Nil, nme.apply :: Nil))
}
case _ =>
tree
}
private def toPartialFunction(tree: Block)(implicit ctx: Context, info: TransformerInfo): Tree = {
val Block(
(applyDef @ DefDef(nme.ANON_FUN, Nil, List(params), _, _)) :: Nil,
Closure(_, _, tpt)) = tree
val List(param) = params
// Dotty problem: If we match instead List(List(param)) directly,
// we get:
// Exception in thread "main" java.lang.AssertionError: assertion failed: self instantiation of (A?
// ...
// at scala.Predef$.assert(Predef.scala:165)
// at dotty.tools.dotc.core.Types$TypeVar.instantiateWith(Types.scala:2308)
// at dotty.tools.dotc.core.Types$TypeVar.instantiate(Types.scala:2363)
// at dotty.tools.dotc.typer.Inferencing$$anonfun$interpolate$1$1$$anonfun$apply$mcV$sp$4.apply(Inferencing.scala:198)
// at dotty.tools.dotc.typer.Inferencing$$anonfun$interpolate$1$1$$anonfun$apply$mcV$sp$4.apply(Inferencing.scala:195)
//
// I think it has to do with the double :: (or List) pattern to extract `param`.
val applyRhs: Tree = applyDef.rhs
val applyFn = applyDef.symbol.asTerm
val MethodType(paramNames, paramTypes) = applyFn.info
val isDefinedAtFn = applyFn.copy(
name = nme.isDefinedAtImpl,
flags = Synthetic | Method,
info = MethodType(paramNames, paramTypes, defn.BooleanType)).asTerm
val tru = Literal(Constant(true))
def isDefinedAtRhs(paramRefss: List[List[Tree]]) = applyRhs match {
case Match(selector, cases) =>
assert(selector.symbol == param.symbol)
val paramRef = paramRefss.head.head
// Again, the alternative
// val List(List(paramRef)) = paramRefs
// fails with a similar self instantiation error
def translateCase(cdef: CaseDef): CaseDef =
cpy.CaseDef(cdef)(body = tru).changeOwner(applyFn, isDefinedAtFn)
val defaultSym = ctx.newSymbol(isDefinedAtFn, nme.WILDCARD, Synthetic, selector.tpe.widen)
val defaultCase =
CaseDef(
Bind(defaultSym, untpd.Ident(nme.WILDCARD).withType(selector.tpe.widen)),
EmptyTree,
Literal(Constant(false)))
cpy.Match(applyRhs)(paramRef, cases.map(translateCase) :+ defaultCase)
case _ =>
tru
}
val isDefinedAtDef = transformFollowingDeep(DefDef(isDefinedAtFn, isDefinedAtRhs(_)))
val anonCls = AnonClass(tpt.tpe, List(applyFn, isDefinedAtFn), List(nme.apply, nme.isDefinedAt))
cpy.Block(tree)(List(applyDef, isDefinedAtDef), anonCls)
}
}
|