|
| 1 | +package dotty.tools.dotc |
| 2 | +package transform |
| 3 | + |
| 4 | +import core._ |
| 5 | +import Contexts._, Symbols._, Types._, Flags._, Decorators._, StdNames._, Constants._ |
| 6 | +import SymDenotations.SymDenotation |
| 7 | +import TreeTransforms._ |
| 8 | +import SymUtils._ |
| 9 | +import ast.untpd |
| 10 | +import ast.Trees._ |
| 11 | + |
| 12 | +/** Expand SAM closures that cannot be represented by the JVM as lambdas to anonymous classes. |
| 13 | + * These fall into five categories |
| 14 | + * |
| 15 | + * 1. Partial function closures, we need to generate a isDefinedAt method for these. |
| 16 | + * 2. Closures implementing non-trait classes. |
| 17 | + * 3. Closures implementing classes that inherit from a class other than Object |
| 18 | + * (a lambda cannot not be a run-time subtype of such a class) |
| 19 | + * 4. Closures that implement traits which run initialization code. |
| 20 | + * 5. Closures that get synthesized abstract methods in the transformation pipeline. These methods can be |
| 21 | + * (1) superaccessors, (2) outer references, (3) accessors for fields. |
| 22 | + */ |
| 23 | +class ExpandSAMs extends MiniPhaseTransform { thisTransformer => |
| 24 | + override def phaseName = "expandSAMs" |
| 25 | + |
| 26 | + import ast.tpd._ |
| 27 | + |
| 28 | + def noJvmSam(cls: ClassSymbol)(implicit ctx: Context): Boolean = |
| 29 | + !cls.is(Trait) || |
| 30 | + cls.superClass != defn.ObjectClass || |
| 31 | + !cls.is(NoInits) || |
| 32 | + !cls.directlyInheritedTraits.forall(_.is(NoInits)) || |
| 33 | + ExplicitOuter.needsOuterIfReferenced(cls) || |
| 34 | + cls.typeRef.fields.nonEmpty // Superaccessors already show up as abstract methods here, so no test necessary |
| 35 | + |
| 36 | + |
| 37 | + override def transformBlock(tree: Block)(implicit ctx: Context, info: TransformerInfo): Tree = tree match { |
| 38 | + case Block(stats @ (fn: DefDef) :: Nil, Closure(_, fnRef, tpt)) if fnRef.symbol == fn.symbol => |
| 39 | + tpt.tpe match { |
| 40 | + case NoType => tree // it's a plain function |
| 41 | + case tpe @ SAMType(_) if !noJvmSam(tpe.classSymbol.asClass) => |
| 42 | + if (tpe isRef defn.PartialFunctionClass) toPartialFunction(tree) |
| 43 | + else tree |
| 44 | + case tpe => |
| 45 | + cpy.Block(tree)(stats, |
| 46 | + AnonClass(tpe :: Nil, fn.symbol.asTerm :: Nil, nme.apply :: Nil)) |
| 47 | + } |
| 48 | + case _ => |
| 49 | + tree |
| 50 | + } |
| 51 | + |
| 52 | + private def toPartialFunction(tree: Block)(implicit ctx: Context, info: TransformerInfo): Tree = { |
| 53 | + val Block( |
| 54 | + (applyDef @ DefDef(nme.ANON_FUN, Nil, List(List(param)), _, _)) :: Nil, |
| 55 | + Closure(_, _, tpt)) = tree |
| 56 | + val applyRhs: Tree = applyDef.rhs |
| 57 | + val applyFn = applyDef.symbol.asTerm |
| 58 | + |
| 59 | + val MethodType(paramNames, paramTypes) = applyFn.info |
| 60 | + val isDefinedAtFn = applyFn.copy( |
| 61 | + name = nme.isDefinedAtImpl, |
| 62 | + flags = Synthetic | Method, |
| 63 | + info = MethodType(paramNames, paramTypes, defn.BooleanType)).asTerm |
| 64 | + val tru = Literal(Constant(true)) |
| 65 | + def isDefinedAtRhs(paramRefss: List[List[Tree]]) = applyRhs match { |
| 66 | + case Match(selector, cases) => |
| 67 | + assert(selector.symbol == param.symbol) |
| 68 | + val paramRef = paramRefss.head.head |
| 69 | + // Again, the alternative |
| 70 | + // val List(List(paramRef)) = paramRefs |
| 71 | + // fails with a similar self instantiation error |
| 72 | + def translateCase(cdef: CaseDef): CaseDef = |
| 73 | + cpy.CaseDef(cdef)(body = tru).changeOwner(applyFn, isDefinedAtFn) |
| 74 | + val defaultSym = ctx.newSymbol(isDefinedAtFn, nme.WILDCARD, Synthetic, selector.tpe.widen) |
| 75 | + val defaultCase = |
| 76 | + CaseDef( |
| 77 | + Bind(defaultSym, untpd.Ident(nme.WILDCARD).withType(selector.tpe.widen)), |
| 78 | + EmptyTree, |
| 79 | + Literal(Constant(false))) |
| 80 | + cpy.Match(applyRhs)(paramRef, cases.map(translateCase) :+ defaultCase) |
| 81 | + case _ => |
| 82 | + tru |
| 83 | + } |
| 84 | + val isDefinedAtDef = transformFollowingDeep(DefDef(isDefinedAtFn, isDefinedAtRhs(_))) |
| 85 | + val anonCls = AnonClass(tpt.tpe :: Nil, List(applyFn, isDefinedAtFn), List(nme.apply, nme.isDefinedAt)) |
| 86 | + cpy.Block(tree)(List(applyDef, isDefinedAtDef), anonCls) |
| 87 | + } |
| 88 | +} |
0 commit comments