Skip to content

Commit f429504

Browse files
committed
FullParametrization: allow to have $this of ThisType.
TailRec methods remain members of enclosing class, it means that they can refer to methods that require this.type. It means that tailrec, unlike value classes is not allowed to widen type of $this to it's full self type. Fixes #1089
1 parent 13b6165 commit f429504

File tree

3 files changed

+61
-15
lines changed

3 files changed

+61
-15
lines changed

src/dotty/tools/dotc/transform/FullParameterization.scala

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import NameOps._
1212
import ast._
1313
import ast.Trees._
1414

15+
import scala.reflect.internal.util.Collections
16+
1517
/** Provides methods to produce fully parameterized versions of instance methods,
1618
* where the `this` of the enclosing class is abstracted out in an extra leading
1719
* `$this` parameter and type parameters of the class become additional type
@@ -86,9 +88,12 @@ trait FullParameterization {
8688
* }
8789
*
8890
* If a self type is present, $this has this self type as its type.
91+
*
8992
* @param abstractOverClass if true, include the type parameters of the class in the method's list of type parameters.
93+
* @param liftThisType if true, require created $this to be $this: (Foo[A] & Foo,this).
94+
* This is needed of created member stays inside scope of Foo(as in tailrec)
9095
*/
91-
def fullyParameterizedType(info: Type, clazz: ClassSymbol, abstractOverClass: Boolean = true)(implicit ctx: Context): Type = {
96+
def fullyParameterizedType(info: Type, clazz: ClassSymbol, abstractOverClass: Boolean = true, liftThisType: Boolean = false)(implicit ctx: Context): Type = {
9297
val (mtparamCount, origResult) = info match {
9398
case info @ PolyType(mtnames) => (mtnames.length, info.resultType)
9499
case info: ExprType => (0, info.resultType)
@@ -100,7 +105,8 @@ trait FullParameterization {
100105
/** The method result type */
101106
def resultType(mapClassParams: Type => Type) = {
102107
val thisParamType = mapClassParams(clazz.classInfo.selfType)
103-
MethodType(nme.SELF :: Nil, thisParamType :: Nil)(mt =>
108+
val firstArgType = if (liftThisType) thisParamType & clazz.thisType else thisParamType
109+
MethodType(nme.SELF :: Nil, firstArgType :: Nil)(mt =>
104110
mapClassParams(origResult).substThisUnlessStatic(clazz, MethodParam(mt, 0)))
105111
}
106112

@@ -217,12 +223,26 @@ trait FullParameterization {
217223
* - the `this` of the enclosing class,
218224
* - the value parameters of the original method `originalDef`.
219225
*/
220-
def forwarder(derived: TermSymbol, originalDef: DefDef, abstractOverClass: Boolean = true)(implicit ctx: Context): Tree =
226+
def forwarder(derived: TermSymbol, originalDef: DefDef, abstractOverClass: Boolean = true, liftThisType: Boolean = false)(implicit ctx: Context): Tree = {
227+
val fun =
221228
ref(derived.termRef)
222-
.appliedToTypes(allInstanceTypeParams(originalDef, abstractOverClass).map(_.typeRef))
223-
.appliedTo(This(originalDef.symbol.enclosingClass.asClass))
224-
.appliedToArgss(originalDef.vparamss.nestedMap(vparam => ref(vparam.symbol)))
225-
.withPos(originalDef.rhs.pos)
229+
.appliedToTypes(allInstanceTypeParams(originalDef, abstractOverClass).map(_.typeRef))
230+
.appliedTo(This(originalDef.symbol.enclosingClass.asClass))
231+
232+
(if (!liftThisType)
233+
fun.appliedToArgss(originalDef.vparamss.nestedMap(vparam => ref(vparam.symbol)))
234+
else {
235+
// this type could have changed on forwarding. Need to insert a cast.
236+
val args = Collections.map2(originalDef.vparamss, fun.tpe.paramTypess)((vparams, paramTypes) =>
237+
Collections.map2(vparams, paramTypes)((vparam, paramType) => {
238+
assert(vparam.tpe <:< paramType.widen) // type should still conform to widened type
239+
ref(vparam.symbol).ensureConforms(paramType)
240+
})
241+
)
242+
fun.appliedToArgss(args)
243+
244+
}).withPos(originalDef.rhs.pos)
245+
}
226246
}
227247

228248
object FullParameterization {

src/dotty/tools/dotc/transform/TailRec.scala

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class TailRec extends MiniPhaseTransform with DenotTransformer with FullParamete
7777
private def mkLabel(method: Symbol, abstractOverClass: Boolean)(implicit c: Context): TermSymbol = {
7878
val name = c.freshName(labelPrefix)
7979

80-
c.newSymbol(method, name.toTermName, labelFlags, fullyParameterizedType(method.info, method.enclosingClass.asClass, abstractOverClass))
80+
c.newSymbol(method, name.toTermName, labelFlags, fullyParameterizedType(method.info, method.enclosingClass.asClass, abstractOverClass, liftThisType = true))
8181
}
8282

8383
override def transformDefDef(tree: tpd.DefDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = {
@@ -112,7 +112,7 @@ class TailRec extends MiniPhaseTransform with DenotTransformer with FullParamete
112112
if (rewrote) {
113113
val dummyDefDef = cpy.DefDef(tree)(rhs = rhsSemiTransformed)
114114
val res = fullyParameterizedDef(label, dummyDefDef, abstractOverClass = defIsTopLevel)
115-
val call = forwarder(label, dd, abstractOverClass = defIsTopLevel)
115+
val call = forwarder(label, dd, abstractOverClass = defIsTopLevel, liftThisType = true)
116116
Block(List(res), call)
117117
} else {
118118
if (mandatory)
@@ -175,8 +175,8 @@ class TailRec extends MiniPhaseTransform with DenotTransformer with FullParamete
175175
case x => (x, x, accArgs, accT, x.symbol)
176176
}
177177

178-
val (reciever, call, arguments, typeArguments, symbol) = receiverArgumentsAndSymbol(tree)
179-
val recv = noTailTransform(reciever)
178+
val (prefix, call, arguments, typeArguments, symbol) = receiverArgumentsAndSymbol(tree)
179+
val recv = noTailTransform(prefix)
180180

181181
val targs = typeArguments.map(noTailTransform)
182182
val argumentss = arguments.map(noTailTransforms)
@@ -215,12 +215,12 @@ class TailRec extends MiniPhaseTransform with DenotTransformer with FullParamete
215215
targs ::: classTypeArgs.map(x => ref(x.typeSymbol))
216216
} else targs
217217

218-
val method = Apply(if (callTargs.nonEmpty) TypeApply(Ident(label.termRef), callTargs) else Ident(label.termRef),
219-
List(receiver))
218+
val method = if (callTargs.nonEmpty) TypeApply(Ident(label.termRef), callTargs) else Ident(label.termRef)
219+
val thisPassed = method appliedTo(receiver.ensureConforms(method.tpe.widen.firstParamTypes.head))
220220

221221
val res =
222-
if (method.tpe.widen.isParameterless) method
223-
else argumentss.foldLeft(method) {
222+
if (thisPassed.tpe.widen.isParameterless) thisPassed
223+
else argumentss.foldLeft(thisPassed) {
224224
(met, ar) => Apply(met, ar) // Dotty deviation no auto-detupling yet.
225225
}
226226
res

tests/pos/tailcall/i1089.scala

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package hello
2+
3+
import scala.annotation.tailrec
4+
5+
class Enclosing {
6+
class SomeData(val x: Int)
7+
8+
def localDef(): Unit = {
9+
def foo(data: SomeData): Int = data.x
10+
11+
@tailrec
12+
def test(i: Int, data: SomeData): Unit = {
13+
if (i != 0) {
14+
println(foo(data))
15+
test(i - 1, data)
16+
}
17+
}
18+
19+
test(3, new SomeData(42))
20+
}
21+
}
22+
23+
object world extends App {
24+
println("hello dotty!")
25+
new Enclosing().localDef()
26+
}

0 commit comments

Comments
 (0)