Skip to content

Commit a4516ea

Browse files
oderskygzm0
authored andcommitted
Hygienic desugaring
Made desugaring hygienic. Trees that are derived from some other tree are no longer stored as simple untyped Ident trees, but as TypeTrees that know how to derive their types from some other type. Test cases in pos: hygiene.scala, t0054.scala and t0085.scala. The comment in hygiene.scala points to the difficulties we are facing. In particular, we need type trees that can rebind some references of a source type to local occurrences with the same name. t0054.scala is similar to hygiene.scala. t0085.scala is trickier, but also related. Essentially the problem there is that we have a class that inherits its outer class. In this case it is important to resolve an identifier in the right context. The identifier added to the copy method of a case class must be resolved outside the class (just like the same identifier in the constructor of that case class).
1 parent 5b687fb commit a4516ea

32 files changed

+447
-77
lines changed

src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 115 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import core._
66
import util.Positions._, Types._, Contexts._, Constants._, Names._, NameOps._, Flags._
77
import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._
88
import Decorators._
9-
import util.Attachment
109
import language.higherKinds
1110
import collection.mutable.ListBuffer
11+
import config.Printers._
1212
import typer.ErrorReporting.InfoString
1313
import typer.Mode
1414

@@ -22,34 +22,64 @@ object desugar {
2222
/** Info of a variable in a pattern: The named tree and its type */
2323
private type VarInfo = (NameTree, Tree)
2424

25-
// ----- TypeTrees that refer to other tree's symbols -------------------
25+
// ----- DerivedTypeTrees -----------------------------------
2626

27-
/** Attachment key containing TypeTrees whose type is computed
28-
* from the symbol in this type. These type trees have marker trees
29-
* TypeRefOfSym or InfoOfSym as their originals.
30-
*/
31-
val References = new Attachment.Key[List[Tree]]
27+
class SetterParamTree extends DerivedTypeTree {
28+
def derivedType(sym: Symbol)(implicit ctx: Context) = sym.info.resultType
29+
}
3230

33-
/** Attachment key for TypeTrees marked with TypeRefOfSym or InfoOfSym
34-
* which contains the symbol of the original tree from which this
35-
* TypeTree is derived.
36-
*/
37-
val OriginalSymbol = new Attachment.Key[Symbol]
31+
class TypeRefTree extends DerivedTypeTree {
32+
def derivedType(sym: Symbol)(implicit ctx: Context) = sym.typeRef
33+
}
3834

39-
/** A type tree that gets its type from some other tree's symbol. Enters the
40-
* type tree in the References attachment of the `from` tree as a side effect.
41-
*/
42-
abstract class DerivedTypeTree(from: Tree) extends TypeTree(EmptyTree) {
43-
val existing = from.attachmentOrElse(References, Nil)
44-
from.putAttachment(References, this :: existing)
35+
class DerivedFromParamTree extends DerivedTypeTree {
4536

46-
/** The method that computes the type of this tree */
47-
def derivedType(originalSym: Symbol)(implicit ctx: Context): Type
37+
/** Make sure that for all enclosing module classes their companion lasses
38+
* are completed. Reason: We need the constructor of such companion classes to
39+
* be completed so that OriginalSymbol attachments are pushed to DerivedTypeTrees
40+
* in appy/unapply methods.
41+
*/
42+
override def ensureCompletions(implicit ctx: Context) =
43+
if (!(ctx.owner is Package))
44+
if (ctx.owner is ModuleClass) ctx.owner.linkedClass.ensureCompleted()
45+
else ensureCompletions(ctx.outer)
46+
47+
/** Return info of original symbol, where all references to siblings of the
48+
* original symbol (i.e. sibling and original symbol have the same owner)
49+
* are rewired to same-named parameters or accessors in the scope enclosing
50+
* the current scope. The current scope is the scope owned by the defined symbol
51+
* itself, that's why we have to look one scope further out. If the resulting
52+
* type is an alias type, dealias it. This is necessary because the
53+
* accessor of a type parameter is a private type alias that cannot be accessed
54+
* from subclasses.
55+
*/
56+
def derivedType(sym: Symbol)(implicit ctx: Context) = {
57+
val relocate = new TypeMap {
58+
val originalOwner = sym.owner
59+
def apply(tp: Type) = tp match {
60+
case tp: NamedType if tp.symbol.owner eq originalOwner =>
61+
val defctx = ctx.outersIterator.dropWhile(_.scope eq ctx.scope).next
62+
var local = defctx.denotNamed(tp.name).suchThat(_ is ParamOrAccessor).symbol
63+
typr.println(s"rewiring ${tp.symbol} from ${originalOwner.showLocated} to ${local.showLocated}, current owner = ${ctx.owner.showLocated}")
64+
if (local.exists) (defctx.owner.thisType select local).dealias
65+
else throw new Error(s"no matching symbol for ${sym.showLocated} in ${defctx.owner} / ${defctx.effectiveScope}")
66+
case _ =>
67+
mapOver(tp)
68+
}
69+
}
70+
relocate(sym.info)
71+
}
4872
}
4973

50-
class SetterParam(vdef: ValDef) extends DerivedTypeTree(vdef) {
51-
def derivedType(vsym: Symbol)(implicit ctx: Context) = vsym.info.resultType
52-
}
74+
/** A type definition copied from `tdef` with a rhs typetree derived from it */
75+
def derivedTypeParam(tdef: TypeDef) =
76+
cpy.TypeDef(tdef, tdef.mods, tdef.name,
77+
new DerivedFromParamTree() withPos tdef.rhs.pos watching tdef, tdef.tparams) // todo: copy type params
78+
79+
/** A value definition copied from `vdef` with a tpt typetree derived from it */
80+
def derivedTermParam(vdef: ValDef) =
81+
cpy.ValDef(vdef, vdef.mods, vdef.name,
82+
new DerivedFromParamTree() withPos vdef.tpt.pos watching vdef, vdef.rhs)
5383

5484
// ----- Desugar methods -------------------------------------------------
5585

@@ -67,7 +97,7 @@ object desugar {
6797
// val getter = ValDef(mods, name, tpt, rhs) withPos vdef.pos ?
6898
// right now vdef maps via expandedTree to a thicket which concerns itself.
6999
// I don't see a problem with that but if there is one we can avoid it by making a copy here.
70-
val setterParam = makeSyntheticParameter(tpt = new SetterParam(vdef))
100+
val setterParam = makeSyntheticParameter(tpt = (new SetterParamTree).watching(vdef))
71101
val setterRhs = if (vdef.rhs.isEmpty) EmptyTree else unitLiteral
72102
val setter = cpy.DefDef(vdef,
73103
mods | Accessor, name.setterName, Nil, (setterParam :: Nil) :: Nil,
@@ -183,6 +213,9 @@ object desugar {
183213
private def toDefParam(tparam: TypeDef) =
184214
cpy.TypeDef(tparam, Modifiers(Param), tparam.name, tparam.rhs, tparam.tparams)
185215

216+
private def toDefParam(vparam: ValDef) =
217+
cpy.ValDef(vparam, Modifiers(Param | vparam.mods.flags & Implicit), vparam.name, vparam.tpt, vparam.rhs)
218+
186219
/** The expansion of a class definition. See inline comments for what is involved */
187220
def classDef(cdef: TypeDef)(implicit ctx: Context): Tree = {
188221
val TypeDef(
@@ -198,31 +231,35 @@ object desugar {
198231
// prefixed by type or val). `tparams` and `vparamss` are the type parameters that
199232
// go in `constr`, the constructor after desugaring.
200233

201-
val tparams = constr1.tparams map toDefParam
202-
val vparamss =
234+
val constrTparams = constr1.tparams map toDefParam
235+
val constrVparamss =
203236
if (constr1.vparamss.isEmpty) { // ensure parameter list is non-empty
204237
if (mods is Case)
205238
ctx.error("case class needs to have at least one parameter list", cdef.pos)
206239
ListOfNil
207-
} else
208-
constr1.vparamss.nestedMap(vparam => cpy.ValDef(vparam,
209-
Modifiers(Param | vparam.mods.flags & Implicit), vparam.name, vparam.tpt, vparam.rhs))
210-
240+
}
241+
else constr1.vparamss.nestedMap(toDefParam)
211242
val constr = cpy.DefDef(constr1,
212-
constr1.mods, constr1.name, tparams, vparamss, constr1.tpt, constr1.rhs)
243+
constr1.mods, constr1.name, constrTparams, constrVparamss, constr1.tpt, constr1.rhs)
244+
245+
val derivedTparams = constrTparams map derivedTypeParam
246+
val derivedVparamss = constrVparamss nestedMap derivedTermParam
247+
val arity = constrVparamss.head.length
248+
249+
var classTycon: Tree = EmptyTree
213250

214251
// a reference to the class type, with all parameters given.
215252
val classTypeRef/*: Tree*/ = {
216253
// -language:keepUnions difference: classTypeRef needs type annotation, otherwise
217254
// infers Ident | AppliedTypeTree, which
218255
// renders the :\ in companions below untypable.
219-
val tycon = Ident(cdef.name) withPos cdef.pos.startPos
256+
classTycon = (new TypeRefTree) withPos cdef.pos.startPos // watching is set at end of method
220257
val tparams = impl.constr.tparams
221-
if (tparams.isEmpty) tycon else AppliedTypeTree(tycon, tparams map refOfDef)
258+
if (tparams.isEmpty) classTycon else AppliedTypeTree(classTycon, tparams map refOfDef)
222259
}
223260

224261
// new C[Ts](paramss)
225-
lazy val creatorExpr = New(classTypeRef, vparamss nestedMap refOfDef)
262+
lazy val creatorExpr = New(classTypeRef, constrVparamss nestedMap refOfDef)
226263

227264
// Methods to add to a case class C[..](p1: T1, ..., pN: Tn)(moreParams)
228265
// def isDefined = true
@@ -233,23 +270,23 @@ object desugar {
233270
// def copy(p1: T1 = p1, ..., pN: TN = pN)(moreParams) = new C[...](p1, ..., pN)(moreParams)
234271
val caseClassMeths =
235272
if (mods is Case) {
236-
val caseParams = vparamss.head.toArray
237273
def syntheticProperty(name: TermName, rhs: Tree) =
238274
DefDef(synthetic, name, Nil, Nil, TypeTree(), rhs)
239275
val isDefinedMeth = syntheticProperty(nme.isDefined, Literal(Constant(true)))
240-
val productArityMeth = syntheticProperty(nme.productArity, Literal(Constant(caseParams.length)))
276+
val productArityMeth = syntheticProperty(nme.productArity, Literal(Constant(arity)))
241277
def selectorName(n: Int) =
242-
if (caseParams.length == 1) nme.get else nme.selectorName(n)
243-
val productElemMeths = for (i <- 0 until caseParams.length) yield
278+
if (arity == 1) nme.get else nme.selectorName(n)
279+
val caseParams = constrVparamss.head.toArray
280+
val productElemMeths = for (i <- 0 until arity) yield
244281
syntheticProperty(selectorName(i), Select(This(EmptyTypeName), caseParams(i).name))
245282
val copyMeths =
246283
if (mods is Abstract) Nil
247284
else {
248-
val copyFirstParams = vparamss.head.map(vparam =>
285+
val copyFirstParams = derivedVparamss.head.map(vparam =>
249286
cpy.ValDef(vparam, vparam.mods, vparam.name, vparam.tpt, refOfDef(vparam)))
250-
val copyRestParamss = vparamss.tail.nestedMap(vparam =>
287+
val copyRestParamss = derivedVparamss.tail.nestedMap(vparam =>
251288
cpy.ValDef(vparam, vparam.mods, vparam.name, vparam.tpt, EmptyTree))
252-
DefDef(synthetic, nme.copy, tparams, copyFirstParams :: copyRestParamss, TypeTree(), creatorExpr) :: Nil
289+
DefDef(synthetic, nme.copy, derivedTparams, copyFirstParams :: copyRestParamss, TypeTree(), creatorExpr) :: Nil
253290
}
254291
copyMeths ::: isDefinedMeth :: productArityMeth :: productElemMeths.toList
255292
}
@@ -258,15 +295,14 @@ object desugar {
258295
def anyRef = ref(defn.AnyRefAlias.typeRef)
259296
def productConstr(n: Int) = {
260297
val tycon = ref(defn.ProductNClass(n).typeRef)
261-
val targs = vparamss.head map (_.tpt)
298+
val targs = constrVparamss.head map (_.tpt)
262299
AppliedTypeTree(tycon, targs)
263300
}
264301

265302
// Case classes get a ProductN parent
266303
var parents1 = parents
267-
val n = vparamss.head.length
268-
if ((mods is Case) && 2 <= n && n <= Definitions.MaxTupleArity)
269-
parents1 = parents1 :+ productConstr(n)
304+
if ((mods is Case) && 2 <= arity && arity <= Definitions.MaxTupleArity)
305+
parents1 = parents1 :+ productConstr(arity)
270306

271307
// The thicket which is the desugared version of the companion object
272308
// synthetic object C extends parentTpt { defs }
@@ -288,17 +324,18 @@ object desugar {
288324
val companions =
289325
if (mods is Case) {
290326
val parent =
291-
if (tparams.nonEmpty) anyRef
292-
else (vparamss :\ classTypeRef) ((vparams, restpe) => Function(vparams map (_.tpt), restpe))
327+
if (constrTparams.nonEmpty) anyRef // todo: also use anyRef if constructor has a dependent method type (or rule that out)!
328+
else (constrVparamss :\ classTypeRef) ((vparams, restpe) => Function(vparams map (_.tpt), restpe))
293329
val applyMeths =
294330
if (mods is Abstract) Nil
295-
else DefDef(
331+
else
332+
DefDef(
296333
synthetic | (constr1.mods.flags & DefaultParameterized), nme.apply,
297-
tparams, vparamss, TypeTree(), creatorExpr) :: Nil
334+
derivedTparams, derivedVparamss, TypeTree(), creatorExpr) :: Nil
298335
val unapplyMeth = {
299336
val unapplyParam = makeSyntheticParameter(tpt = classTypeRef)
300-
val unapplyRHS = if (n == 0) Literal(Constant(true)) else Ident(unapplyParam.name)
301-
DefDef(synthetic, nme.unapply, tparams, (unapplyParam :: Nil) :: Nil, TypeTree(), unapplyRHS)
337+
val unapplyRHS = if (arity == 0) Literal(Constant(true)) else Ident(unapplyParam.name)
338+
DefDef(synthetic, nme.unapply, derivedTparams, (unapplyParam :: Nil) :: Nil, TypeTree(), unapplyRHS)
302339
}
303340
companionDefs(parent, applyMeths ::: unapplyMeth :: defaultGetters)
304341
}
@@ -315,19 +352,40 @@ object desugar {
315352
ctx.error("implicit classes may not be toplevel", cdef.pos)
316353
if (mods is Case)
317354
ctx.error("implicit classes may not case classes", cdef.pos)
355+
356+
// implicit wrapper is typechecked in same scope as constructor, so
357+
// we can reuse the constructor parameters; no derived params are needed.
318358
DefDef(Modifiers(Synthetic | Implicit), name.toTermName,
319-
tparams, vparamss, classTypeRef, creatorExpr) :: Nil
359+
constrTparams, constrVparamss, classTypeRef, creatorExpr) :: Nil
320360
}
321361
else Nil
322362

323-
val selfType = if (self.tpt.isEmpty) classTypeRef else self.tpt
324-
val self1 =
363+
val self1 = {
364+
val selfType = if (self.tpt.isEmpty) classTypeRef else self.tpt
325365
if (self.isEmpty) self
326366
else cpy.ValDef(self, self.mods | SelfName, self.name, selfType, self.rhs)
367+
}
368+
369+
val cdef1 = {
370+
val originalTparams = constr1.tparams.toIterator
371+
val originalVparams = constr1.vparamss.toIterator.flatten
372+
val tparamAccessors = derivedTparams map { tdef =>
373+
cpy.TypeDef(tdef, originalTparams.next.mods, tdef.name, tdef.rhs, tdef.tparams)
374+
}
375+
val vparamAccessors = derivedVparamss.flatten map { vdef =>
376+
cpy.ValDef(vdef, originalVparams.next.mods, vdef.name, vdef.tpt, vdef.rhs)
377+
}
378+
cpy.TypeDef(cdef, mods, name,
379+
cpy.Template(impl, constr, parents1, self1,
380+
tparamAccessors ::: vparamAccessors ::: body ::: caseClassMeths))
381+
}
382+
383+
// install the watch on classTycon
384+
classTycon match {
385+
case tycon: DerivedTypeTree => tycon.watching(cdef1)
386+
case _ =>
387+
}
327388

328-
val cdef1 = cpy.TypeDef(cdef, mods, name,
329-
cpy.Template(impl, constr, parents1, self1,
330-
constr1.tparams ::: constr1.vparamss.flatten ::: body ::: caseClassMeths))
331389
flatTree(cdef1 :: companions ::: implicitWrappers)
332390
}
333391

src/dotty/tools/dotc/ast/untpd.scala

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ import core._
66
import util.Positions._, Types._, Contexts._, Constants._, Names._, NameOps._, Flags._
77
import Denotations._, SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._
88
import Decorators._
9+
import util.Attachment
910
import language.higherKinds
1011
import collection.mutable.ListBuffer
1112

1213
object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
1314

14-
// ----- Tree cases that exist in untyped form only ------------------
15+
// ----- Tree cases that exist in untyped form only ------------------
1516

1617
trait OpTree extends Tree {
1718
def op: Name
@@ -61,6 +62,47 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
6162
override def withName(name: Name)(implicit ctx: Context) = cpy.PolyTypeDef(this, mods, name.toTypeName, tparams, rhs)
6263
}
6364

65+
// ----- TypeTrees that refer to other tree's symbols -------------------
66+
67+
/** A type tree that gets its type from some other tree's symbol. Enters the
68+
* type tree in the References attachment of the `from` tree as a side effect.
69+
*/
70+
abstract class DerivedTypeTree extends TypeTree(EmptyTree) {
71+
72+
private var myWatched: Tree = EmptyTree
73+
74+
/** The watched tree; used only for printing */
75+
def watched: Tree = myWatched
76+
77+
/** Install the derived type tree as a dependency on `original` */
78+
def watching(original: DefTree): this.type = {
79+
myWatched = original
80+
val existing = original.attachmentOrElse(References, Nil)
81+
original.putAttachment(References, this :: existing)
82+
this
83+
}
84+
85+
/** A hook to ensure that all necessary symbols are completed so that
86+
* OriginalSymbol attachments are propagated to this tree
87+
*/
88+
def ensureCompletions(implicit ctx: Context): Unit = ()
89+
90+
/** The method that computes the type of this tree */
91+
def derivedType(originalSym: Symbol)(implicit ctx: Context): Type
92+
}
93+
94+
/** Attachment key containing TypeTrees whose type is computed
95+
* from the symbol in this type. These type trees have marker trees
96+
* TypeRefOfSym or InfoOfSym as their originals.
97+
*/
98+
val References = new Attachment.Key[List[Tree]]
99+
100+
/** Attachment key for TypeTrees marked with TypeRefOfSym or InfoOfSym
101+
* which contains the symbol of the original tree from which this
102+
* TypeTree is derived.
103+
*/
104+
val OriginalSymbol = new Attachment.Key[Symbol]
105+
64106
// ------ Creation methods for untyped only -----------------
65107

66108
def Ident(name: Name): Ident = new Ident(name)

src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
241241
}
242242
case SeqLiteral(elems) =>
243243
"[" ~ toTextGlobal(elems, ",") ~ "]"
244+
case tpt: untpd.DerivedTypeTree =>
245+
"<derived typetree watching " ~ toText(tpt.watched) ~ ">"
244246
case TypeTree(orig) =>
245247
if (tree.hasType) toText(tree.typeOpt) else toText(orig)
246248
case SingletonTypeTree(ref) =>

src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ trait Checking extends NoChecking {
8484

8585
/** Check that (return) type of implicit definition is not empty */
8686
override def checkImplicitTptNonEmpty(defTree: untpd.ValOrDefDef)(implicit ctx: Context): Unit = defTree.tpt match {
87+
case tpt: untpd.DerivedTypeTree =>
8788
case TypeTree(untpd.EmptyTree) =>
8889
val resStr = if (defTree.isInstanceOf[untpd.DefDef]) "result " else ""
8990
ctx.error(i"${resStr}type of implicit definition needs to be given explicitly", defTree.pos)

0 commit comments

Comments
 (0)