Skip to content

Commit fd76c38

Browse files
committed
Merge pull request #88 from dotty-staging/try/hygienic-desugaring
Try/hygienic desugaring
2 parents b5864b4 + e50646c commit fd76c38

33 files changed

+458
-56
lines changed

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

Lines changed: 124 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._
88
import Decorators._
99
import language.higherKinds
1010
import collection.mutable.ListBuffer
11+
import config.Printers._
1112
import typer.ErrorReporting.InfoString
1213
import typer.Mode
1314

@@ -21,6 +22,67 @@ object desugar {
2122
/** Info of a variable in a pattern: The named tree and its type */
2223
private type VarInfo = (NameTree, Tree)
2324

25+
// ----- DerivedTypeTrees -----------------------------------
26+
27+
class SetterParamTree extends DerivedTypeTree {
28+
def derivedType(sym: Symbol)(implicit ctx: Context) = sym.info.resultType
29+
}
30+
31+
class TypeRefTree extends DerivedTypeTree {
32+
def derivedType(sym: Symbol)(implicit ctx: Context) = sym.typeRef
33+
}
34+
35+
class DerivedFromParamTree extends DerivedTypeTree {
36+
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+
}
72+
}
73+
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)
83+
84+
// ----- Desugar methods -------------------------------------------------
85+
2486
/** var x: Int = expr
2587
* ==>
2688
* def x: Int = expr
@@ -35,7 +97,7 @@ object desugar {
3597
// val getter = ValDef(mods, name, tpt, rhs) withPos vdef.pos ?
3698
// right now vdef maps via expandedTree to a thicket which concerns itself.
3799
// I don't see a problem with that but if there is one we can avoid it by making a copy here.
38-
val setterParam = makeSyntheticParameter(tpt = TypeTree())
100+
val setterParam = makeSyntheticParameter(tpt = (new SetterParamTree).watching(vdef))
39101
val setterRhs = if (vdef.rhs.isEmpty) EmptyTree else unitLiteral
40102
val setter = cpy.DefDef(vdef,
41103
mods | Accessor, name.setterName, Nil, (setterParam :: Nil) :: Nil,
@@ -151,6 +213,9 @@ object desugar {
151213
private def toDefParam(tparam: TypeDef) =
152214
cpy.TypeDef(tparam, Modifiers(Param), tparam.name, tparam.rhs, tparam.tparams)
153215

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

169-
val tparams = constr1.tparams map toDefParam
170-
val vparamss =
234+
val constrTparams = constr1.tparams map toDefParam
235+
val constrVparamss =
171236
if (constr1.vparamss.isEmpty) { // ensure parameter list is non-empty
172237
if (mods is Case)
173238
ctx.error("case class needs to have at least one parameter list", cdef.pos)
174239
ListOfNil
175-
} else
176-
constr1.vparamss.nestedMap(vparam => cpy.ValDef(vparam,
177-
Modifiers(Param | vparam.mods.flags & Implicit), vparam.name, vparam.tpt, vparam.rhs))
178-
240+
}
241+
else constr1.vparamss.nestedMap(toDefParam)
179242
val constr = cpy.DefDef(constr1,
180-
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
181250

182251
// a reference to the class type, with all parameters given.
183252
val classTypeRef/*: Tree*/ = {
184253
// -language:keepUnions difference: classTypeRef needs type annotation, otherwise
185254
// infers Ident | AppliedTypeTree, which
186255
// renders the :\ in companions below untypable.
187-
val tycon = Ident(cdef.name) withPos cdef.pos.startPos
256+
classTycon = (new TypeRefTree) withPos cdef.pos.startPos // watching is set at end of method
188257
val tparams = impl.constr.tparams
189-
if (tparams.isEmpty) tycon else AppliedTypeTree(tycon, tparams map refOfDef)
258+
if (tparams.isEmpty) classTycon else AppliedTypeTree(classTycon, tparams map refOfDef)
190259
}
191260

192261
// new C[Ts](paramss)
193-
lazy val creatorExpr = New(classTypeRef, vparamss nestedMap refOfDef)
262+
lazy val creatorExpr = New(classTypeRef, constrVparamss nestedMap refOfDef)
194263

195264
// Methods to add to a case class C[..](p1: T1, ..., pN: Tn)(moreParams)
196265
// def isDefined = true
@@ -201,23 +270,23 @@ object desugar {
201270
// def copy(p1: T1 = p1, ..., pN: TN = pN)(moreParams) = new C[...](p1, ..., pN)(moreParams)
202271
val caseClassMeths =
203272
if (mods is Case) {
204-
val caseParams = vparamss.head.toArray
205273
def syntheticProperty(name: TermName, rhs: Tree) =
206274
DefDef(synthetic, name, Nil, Nil, TypeTree(), rhs)
207275
val isDefinedMeth = syntheticProperty(nme.isDefined, Literal(Constant(true)))
208-
val productArityMeth = syntheticProperty(nme.productArity, Literal(Constant(caseParams.length)))
276+
val productArityMeth = syntheticProperty(nme.productArity, Literal(Constant(arity)))
209277
def selectorName(n: Int) =
210-
if (caseParams.length == 1) nme.get else nme.selectorName(n)
211-
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
212281
syntheticProperty(selectorName(i), Select(This(EmptyTypeName), caseParams(i).name))
213282
val copyMeths =
214283
if (mods is Abstract) Nil
215284
else {
216-
val copyFirstParams = vparamss.head.map(vparam =>
285+
val copyFirstParams = derivedVparamss.head.map(vparam =>
217286
cpy.ValDef(vparam, vparam.mods, vparam.name, vparam.tpt, refOfDef(vparam)))
218-
val copyRestParamss = vparamss.tail.nestedMap(vparam =>
287+
val copyRestParamss = derivedVparamss.tail.nestedMap(vparam =>
219288
cpy.ValDef(vparam, vparam.mods, vparam.name, vparam.tpt, EmptyTree))
220-
DefDef(synthetic, nme.copy, tparams, copyFirstParams :: copyRestParamss, TypeTree(), creatorExpr) :: Nil
289+
DefDef(synthetic, nme.copy, derivedTparams, copyFirstParams :: copyRestParamss, TypeTree(), creatorExpr) :: Nil
221290
}
222291
copyMeths ::: isDefinedMeth :: productArityMeth :: productElemMeths.toList
223292
}
@@ -226,15 +295,14 @@ object desugar {
226295
def anyRef = ref(defn.AnyRefAlias.typeRef)
227296
def productConstr(n: Int) = {
228297
val tycon = ref(defn.ProductNClass(n).typeRef)
229-
val targs = vparamss.head map (_.tpt)
298+
val targs = constrVparamss.head map (_.tpt)
230299
AppliedTypeTree(tycon, targs)
231300
}
232301

233302
// Case classes get a ProductN parent
234303
var parents1 = parents
235-
val n = vparamss.head.length
236-
if ((mods is Case) && 2 <= n && n <= Definitions.MaxTupleArity)
237-
parents1 = parents1 :+ productConstr(n)
304+
if ((mods is Case) && 2 <= arity && arity <= Definitions.MaxTupleArity)
305+
parents1 = parents1 :+ productConstr(arity)
238306

239307
// The thicket which is the desugared version of the companion object
240308
// synthetic object C extends parentTpt { defs }
@@ -256,17 +324,18 @@ object desugar {
256324
val companions =
257325
if (mods is Case) {
258326
val parent =
259-
if (tparams.nonEmpty) anyRef
260-
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))
261329
val applyMeths =
262330
if (mods is Abstract) Nil
263-
else DefDef(
331+
else
332+
DefDef(
264333
synthetic | (constr1.mods.flags & DefaultParameterized), nme.apply,
265-
tparams, vparamss, TypeTree(), creatorExpr) :: Nil
334+
derivedTparams, derivedVparamss, TypeTree(), creatorExpr) :: Nil
266335
val unapplyMeth = {
267336
val unapplyParam = makeSyntheticParameter(tpt = classTypeRef)
268-
val unapplyRHS = if (n == 0) Literal(Constant(true)) else Ident(unapplyParam.name)
269-
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)
270339
}
271340
companionDefs(parent, applyMeths ::: unapplyMeth :: defaultGetters)
272341
}
@@ -283,19 +352,40 @@ object desugar {
283352
ctx.error("implicit classes may not be toplevel", cdef.pos)
284353
if (mods is Case)
285354
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.
286358
DefDef(Modifiers(Synthetic | Implicit), name.toTermName,
287-
tparams, vparamss, classTypeRef, creatorExpr) :: Nil
359+
constrTparams, constrVparamss, classTypeRef, creatorExpr) :: Nil
288360
}
289361
else Nil
290362

291-
val selfType = if (self.tpt.isEmpty) classTypeRef else self.tpt
292-
val self1 =
363+
val self1 = {
364+
val selfType = if (self.tpt.isEmpty) classTypeRef else self.tpt
293365
if (self.isEmpty) self
294366
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+
}
295388

296-
val cdef1 = cpy.TypeDef(cdef, mods, name,
297-
cpy.Template(impl, constr, parents1, self1,
298-
constr1.tparams ::: constr1.vparamss.flatten ::: body ::: caseClassMeths))
299389
flatTree(cdef1 :: companions ::: implicitWrappers)
300390
}
301391

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: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ 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 TypeTree(original) if original.isEmpty =>
87+
case tpt: untpd.DerivedTypeTree =>
88+
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)
9091
case _ =>

0 commit comments

Comments
 (0)