diff options
-rw-r--r-- | src/dotty/tools/dotc/Compiler.scala | 1 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/StdNames.scala | 1 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/Types.scala | 10 | ||||
-rw-r--r-- | src/dotty/tools/dotc/transform/ExpandSAMs.scala | 91 | ||||
-rw-r--r-- | test/dotc/tests.scala | 3 | ||||
-rw-r--r-- | tests/neg/sammy_poly.scala | 7 | ||||
-rw-r--r-- | tests/pos/sams.scala | 63 |
7 files changed, 166 insertions, 10 deletions
diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index 44e7ac450..a9aa65201 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -45,6 +45,7 @@ class Compiler { new ElimRepeated, new NormalizeFlags, new ExtensionMethods, + new ExpandSAMs, new TailRec), List(new PatternMatcher, new ExplicitOuter, diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index 74a121b47..826cbe2c6 100644 --- a/src/dotty/tools/dotc/core/StdNames.scala +++ b/src/dotty/tools/dotc/core/StdNames.scala @@ -418,6 +418,7 @@ object StdNames { val isArray: N = "isArray" val isDefined: N = "isDefined" val isDefinedAt: N = "isDefinedAt" + val isDefinedAtImpl: N = "$isDefinedAt" val isEmpty: N = "isEmpty" val isInstanceOf_ : N = "isInstanceOf" val java: N = "java" diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 595732b37..80cceed38 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -545,6 +545,11 @@ object Types { (name, buf) => buf ++= member(name).altsWith(x => x.isClass)) } + final def fields(implicit ctx: Context): Seq[SingleDenotation] = track("fields") { + memberDenots(fieldFilter, + (name, buf) => buf ++= member(name).altsWith(x => !x.is(Method))) + } + /** The set of members of this type having at least one of `requiredFlags` but none of `excludedFlags` set */ final def membersBasedOnFlags(requiredFlags: FlagSet, excludedFlags: FlagSet)(implicit ctx: Context): Seq[SingleDenotation] = track("implicitMembers") { memberDenots(takeAllFilter, @@ -3049,6 +3054,11 @@ object Types { def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean = name.isTypeName } + object fieldFilter extends NameFilter { + def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean = + name.isTermName && (pre member name).hasAltWith(!_.symbol.is(Method)) + } + object takeAllFilter extends NameFilter { def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean = true } diff --git a/src/dotty/tools/dotc/transform/ExpandSAMs.scala b/src/dotty/tools/dotc/transform/ExpandSAMs.scala new file mode 100644 index 000000000..1340a2067 --- /dev/null +++ b/src/dotty/tools/dotc/transform/ExpandSAMs.scala @@ -0,0 +1,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) + } +} diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 7761589ad..0e86303a2 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -106,9 +106,6 @@ class tests extends CompilerTest { @Test def neg_i50_volatile = compileFile(negDir, "i50-volatile", xerrors = 6) @Test def neg_t0273_doubledefs = compileFile(negDir, "t0273", xerrors = 1) @Test def neg_zoo = compileFile(negDir, "zoo", xerrors = 12) - @Test def neg_sam = compileFile(negDir, "sammy_poly", xerrors = 1) - // TODO: this test file doesn't exist (anymore?), remove? - // @Test def neg_t1192_legalPrefix = compileFile(negDir, "t1192", xerrors = 1) val negTailcallDir = negDir + "tailcall/" @Test def neg_tailcall_t1672b = compileFile(negTailcallDir, "t1672b", xerrors = 6) diff --git a/tests/neg/sammy_poly.scala b/tests/neg/sammy_poly.scala deleted file mode 100644 index 8d0236496..000000000 --- a/tests/neg/sammy_poly.scala +++ /dev/null @@ -1,7 +0,0 @@ -// test synthesizeSAMFunction where the sam type is not fully defined -class T { - trait F[T, U] { def apply(x: T): U } - // this is an inner trait, that will recieve an abstract $outer pointer. Not a SAM. - def app[T, U](x: T)(f: F[T, U]): U = f(x) - app(1)(x => List(x)) -} diff --git a/tests/pos/sams.scala b/tests/pos/sams.scala new file mode 100644 index 000000000..671213fef --- /dev/null +++ b/tests/pos/sams.scala @@ -0,0 +1,63 @@ +object test { + + trait X { def foo(x: Int): Int; def bar = foo(2) } + + val x: X = (x: Int) => 2 // should be a closure + + trait T { + var f = 2 + def foo(x: Int): Int + } + + val t: T = (x: Int) => 2 // needs to be an anonymous class because of defined field + + trait U extends T + + val u: U = (x: Int) => 2 // needs to be an anonymous class because of inherited field + + trait Y extends X { + def baz = super.bar + } + + val y: Y = (x: Int) => 2 // needs to be an anonymous class because of super accessor + + abstract class C { + def foo(x: Int): Int + + trait I { def foo(x: Int): Int } + + } + + val c: C = (x: Int) => 2 // needs to be an anonymous class because C is not a trait + + val ci: c.I = (x: Int) => 2 // needs to be an anonymous class because it needs an outer pointer + + + val pf: PartialFunction[Int, Int] = { + case 1 => 1 + case 2 => 2 + } + + val qf: PartialFunction[(Int, String), Int] = { + case (1, "abc") => 1 + case _ => 2 + } + + val rf: PartialFunction[(Int, AnyRef), Int] = { + case (_: Int, _: String) => 1 + case _ => 2 + } + + val sf: PartialFunction[Any, Int] = { + case x: String if x == "abc" => 1 + } +} + +// From: neg/sammy_poly +// synthesizeSAMFunction where the sam type is not fully defined +class T { + trait F[T, U] { def apply(x: T): U } + // this is an inner trait, that will recieve an abstract $outer pointer. Not a SAM. + def app[T, U](x: T)(f: F[T, U]): U = f(x) + app(1)(x => List(x)) +} |