From f6168b8a4d661985b0fb4d6d3cbba256bfc69607 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 21 Jan 2013 10:45:36 +0100 Subject: [PATCH] SI-6231 Report unsupported free var capture by a trait. If a class nested in a trait captures a free variable from the enclosing scope of the trait, the transformation to add that variable to the `init` method of the trait implementation class happens *after* the abstract trait interface has been extracted. This would lead to a crash when trying to find the corresponding interface method. This commit detects this situation and reports an implementation restriction. The enclosed test case shows a workaround. To lift this restriction, LambdaLifter should add the getters and make sure they end up in the trait interface. Looks like Martin tried this once: // LambdaLift.scala // // Disabled attempt to to add getters to freeParams // this does not work yet. Problem is that local symbols need local names // and references to local symbols need to be transformed into // method calls to setters. // def paramGetter(param: Symbol): Tree = { // val getter = param.newGetter setFlag TRANS_FLAG resetFlag PARAMACCESSOR // mark because we have to add them to interface // sym.info.decls.enter(getter) // val rhs = Select(gen.mkAttributedThis(sym), param) setType param.tpe // DefDef(getter, rhs) setPos tree.pos setType NoType // } // val newDefs = if (sym.isTrait) freeParams ::: (ps map paramGetter) else freeParams --- .../tools/nsc/transform/LambdaLift.scala | 3 ++- .../scala/tools/nsc/transform/Mixin.scala | 19 +++++++++++++++++-- test/files/neg/t6231.check | 6 ++++++ test/files/neg/t6231.scala | 15 +++++++++++++++ 4 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 test/files/neg/t6231.check create mode 100644 test/files/neg/t6231.scala diff --git a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala index 4a23e65ad20a..cd2bffcfdefd 100644 --- a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala +++ b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala @@ -352,6 +352,7 @@ abstract class LambdaLift extends InfoTransform { copyDefDef(tree)(vparamss = List(vparams ++ freeParams)) case ClassDef(_, _, _, _) => + // SI-6231 // Disabled attempt to to add getters to freeParams // this does not work yet. Problem is that local symbols need local names // and references to local symbols need to be transformed into @@ -369,7 +370,7 @@ abstract class LambdaLift extends InfoTransform { tree } -/* Something like this will be necessary to eliminate the implementation +/* SI-6231: Something like this will be necessary to eliminate the implementation * restiction from paramGetter above: * We need to pass getters to the interface of an implementation class. private def fixTraitGetters(lifted: List[Tree]): List[Tree] = diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index 57bdaea17ade..3cd943aa7490 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -1215,9 +1215,24 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { // refer to fields in some implementation class via an abstract // getter in the interface. val iface = toInterface(sym.owner.tpe).typeSymbol - val getter = sym getter iface orElse abort("No getter for " + sym + " in " + iface) + val ifaceGetter = sym getter iface + + def si6231Restriction() { + // See SI-6231 comments in LamdaLift for ideas on how to lift the restriction. + val msg = sm"""Implementation restriction: local ${iface.fullLocationString} is unable to automatically capture the + |free variable ${sym} on behalf of ${currentClass}. You can manually assign it to a val inside the trait, + |and refer that that val in ${currentClass}. For more details, see SI-6231.""" + reporter.error(tree.pos, msg) + } - typedPos(tree.pos)((qual DOT getter)()) + if (ifaceGetter == NoSymbol) { + if (sym.isParamAccessor) { + si6231Restriction() + EmptyTree + } + else abort("No getter for " + sym + " in " + iface) + } + else typedPos(tree.pos)((qual DOT ifaceGetter)()) case Assign(Apply(lhs @ Select(qual, _), List()), rhs) => // assign to fields in some implementation class via an abstract diff --git a/test/files/neg/t6231.check b/test/files/neg/t6231.check new file mode 100644 index 000000000000..b27961d393c6 --- /dev/null +++ b/test/files/neg/t6231.check @@ -0,0 +1,6 @@ +t6231.scala:4: error: Implementation restriction: local trait Bug$X$1 is unable to automatically capture the +free variable value ev$1 on behalf of anonymous class anonfun$qux$1. You can manually assign it to a val inside the trait, +and refer that that val in anonymous class anonfun$qux$1. For more details, see SI-6231. + def qux = { () => ev } + ^ +one error found diff --git a/test/files/neg/t6231.scala b/test/files/neg/t6231.scala new file mode 100644 index 000000000000..1e5b4e0e1a16 --- /dev/null +++ b/test/files/neg/t6231.scala @@ -0,0 +1,15 @@ +object Bug { + def bar(ev: Any) = { + trait X { + def qux = { () => ev } + } + new X {}.qux() + + // workaround + trait Y { + val ev2 = ev // manually capture `ev` so that `ev2` is added to the trait interface. + def qux = { () => ev2 } + } + } +} +