Skip to content

Add typeclass derivation #5540

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 56 commits into from
Jan 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
0dc0191
Allow `,` in parents of template
odersky Nov 26, 2018
6bf25d2
Syntax and parsing for `derives` clauses
odersky Nov 26, 2018
2e72be4
Make `derives` a soft keyword
odersky Nov 26, 2018
ae79a8a
Clarify regular and soft keywords keywords in syntax.md
odersky Nov 26, 2018
fb09e82
Represent derives clauses in Template trees
odersky Nov 28, 2018
c8357b2
Make Case a FromStartFlag
odersky Nov 29, 2018
b3c48d3
Reorganize children handling
odersky Nov 29, 2018
fe9f40a
Improve error diagnostics for implicits not found
odersky Nov 29, 2018
4642070
Flesh out test
odersky Nov 29, 2018
1d58888
Fix tests
odersky Nov 29, 2018
7eda566
Deriving infrastructure in typelevel
odersky Nov 30, 2018
7adc4c4
Partial Implementation of Deriving functionality
odersky Nov 30, 2018
a1fbe5f
Auto-generated derived typeclass instances
odersky Nov 30, 2018
ceb5f2e
Flesh out derived$shaped
odersky Nov 30, 2018
83bc248
More blacklisting and test fixing
odersky Dec 1, 2018
6e48ee1
Complete first version of derivation scheme
odersky Dec 3, 2018
9063951
Allow derivation for classes with no companions
odersky Dec 3, 2018
0377cab
Handle derived typeclasses with non-trivial prefixes
odersky Dec 3, 2018
27f96dc
Scaling test
odersky Dec 3, 2018
17b0fee
Move typelevel classes to src-bootstrapped
odersky Dec 4, 2018
34f9c84
Rename typelevel package to compiletime
odersky Dec 4, 2018
e36c5e2
Packaging reorgs
odersky Dec 7, 2018
0db9e9d
Add docs
odersky Dec 7, 2018
944eca0
blacklist new test for pickling
odersky Dec 7, 2018
98fcbc1
Shaped -> Generic
odersky Dec 7, 2018
0e8e7ce
Change Shaped -> Generic
odersky Dec 8, 2018
04c3ae7
Fix order of Child annotations.
odersky Dec 8, 2018
d81ce91
Synthesize Generic instances on the fly
odersky Dec 9, 2018
f1467f3
Handle duplicate children
odersky Dec 9, 2018
dffa71b
Allow to derive directly from Generic
odersky Dec 9, 2018
5eca939
Improve robustness in the face of errors
odersky Dec 9, 2018
12609e4
Drop redundant check
odersky Dec 9, 2018
4d52930
Fixes to positions
odersky Dec 9, 2018
f3d901e
Revert changes to CheckRealizable
odersky Dec 9, 2018
465ffcc
Fix patmat exhaustivity check files
odersky Dec 9, 2018
d4025d6
Polishings
odersky Dec 9, 2018
1a9c5c0
Tweaks to docs
odersky Dec 9, 2018
1a58222
Fix typo
odersky Dec 10, 2018
7fccb61
Revert to standard implicit syntax in docs
odersky Dec 10, 2018
46e4b19
Polishings
odersky Dec 10, 2018
5182b8b
Fix erasure of *: and NonEmptyTuple
odersky Dec 11, 2018
efb3e5f
Make NonEmptyTuple `sealed`.
odersky Dec 13, 2018
4e594ed
More tests
odersky Dec 14, 2018
8e98772
Fix typos
odersky Dec 18, 2018
683b93b
Add derived field to Tasty reflect
odersky Jan 6, 2019
569f918
Drop failing assertion
odersky Jan 6, 2019
e3f74e3
Add missing comma and update checkfiles
nicolasstucki Jan 6, 2019
d9be6e2
Update another check file
odersky Jan 7, 2019
caece0e
Fix rebase breakage
odersky Jan 7, 2019
9d87241
Fix test
odersky Jan 7, 2019
248971b
Another check file fix
odersky Jan 7, 2019
49901fe
Fix typos in documentation
OlivierBlanvillain Jan 15, 2019
4cd81ba
Fix spacing and add return types
OlivierBlanvillain Jan 15, 2019
296958e
Fix failing fuzzy test
odersky Jan 16, 2019
bbb54cb
Fix rebase breakage
odersky Jan 19, 2019
5e4fa24
Fix indent
odersky Jan 19, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 30 additions & 14 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Symbols._, StdNames._, Trees._
import Decorators._, transform.SymUtils._
import NameKinds.{UniqueName, EvidenceParamName, DefaultGetterName}
import typer.FrontEnd
import util.{Property, SourceFile}
import util.{Property, SourceFile, SourcePosition}
import collection.mutable.ListBuffer
import reporting.diagnostic.messages._
import reporting.trace
Expand All @@ -23,7 +23,13 @@ object desugar {
/** If a Select node carries this attachment, suppress the check
* that its type refers to an acessible symbol.
*/
val SuppressAccessCheck = new Property.Key[Unit]
val SuppressAccessCheck: Property.Key[Unit] = new Property.Key

/** An attachment for companion modules of classes that have a `derives` clause.
* The position value indicates the start position of the template of the
* deriving class.
*/
val DerivingCompanion: Property.Key[SourcePosition] = new Property.Key

/** Info of a variable in a pattern: The named tree and its type */
private type VarInfo = (NameTree, Tree)
Expand Down Expand Up @@ -297,7 +303,8 @@ object desugar {
/** The expansion of a class definition. See inline comments for what is involved */
def classDef(cdef: TypeDef)(implicit ctx: Context): Tree = {
val className = checkNotReservedName(cdef).asTypeName
val impl @ Template(constr0, parents, self, _) = cdef.rhs
val impl @ Template(_, _, self, _) = cdef.rhs
val parents = impl.parents
val mods = cdef.mods
val companionMods = mods
.withFlags((mods.flags & (AccessFlags | Final)).toCommonFlags)
Expand All @@ -312,7 +319,7 @@ object desugar {
meth
}

val constr1 = decompose(defDef(constr0, isPrimaryConstructor = true))
val constr1 = decompose(defDef(impl.constr, isPrimaryConstructor = true))

// The original type and value parameters in the constructor already have the flags
// needed to be type members (i.e. param, and possibly also private and local unless
Expand Down Expand Up @@ -557,14 +564,23 @@ object desugar {
}
def eqInstances = if (isEnum) eqInstance :: Nil else Nil

// derived type classes of non-module classes go to their companions
val (clsDerived, companionDerived) =
if (mods.is(Module)) (impl.derived, Nil) else (Nil, impl.derived)

// The thicket which is the desugared version of the companion object
// synthetic object C extends parentTpt { defs }
def companionDefs(parentTpt: Tree, defs: List[Tree]) =
moduleDef(
// synthetic object C extends parentTpt derives class-derived { defs }
def companionDefs(parentTpt: Tree, defs: List[Tree]) = {
val mdefs = moduleDef(
ModuleDef(
className.toTermName, Template(emptyConstructor, parentTpt :: Nil, EmptyValDef, defs))
className.toTermName, Template(emptyConstructor, parentTpt :: Nil, companionDerived, EmptyValDef, defs))
.withMods(companionMods | Synthetic))
.withSpan(cdef.span).toList
.withSpan(cdef.span).toList
if (companionDerived.nonEmpty)
for (modClsDef @ TypeDef(_, _) <- mdefs)
modClsDef.putAttachment(DerivingCompanion, impl.sourcePos.startPos)
mdefs
}

val companionMembers = defaultGetters ::: eqInstances ::: enumCases

Expand Down Expand Up @@ -613,10 +629,10 @@ object desugar {
}
companionDefs(companionParent, applyMeths ::: unapplyMeth :: companionMembers)
}
else if (companionMembers.nonEmpty)
else if (companionMembers.nonEmpty || companionDerived.nonEmpty)
companionDefs(anyRef, companionMembers)
else if (isValueClass) {
constr0.vparamss match {
impl.constr.vparamss match {
case (_ :: Nil) :: _ => companionDefs(anyRef, Nil)
case _ => Nil // error will be emitted in typer
}
Expand Down Expand Up @@ -675,7 +691,7 @@ object desugar {
}
cpy.TypeDef(cdef: TypeDef)(
name = className,
rhs = cpy.Template(impl)(constr, parents1, self1,
rhs = cpy.Template(impl)(constr, parents1, clsDerived, self1,
tparamAccessors ::: vparamAccessors ::: normalizedBody ::: caseClassMeths)): TypeDef
}

Expand Down Expand Up @@ -772,7 +788,7 @@ object desugar {
val localType = tdef.withMods(Modifiers(Synthetic | Opaque).withPrivateWithin(tdef.name))

val companions = moduleDef(ModuleDef(
moduleName, Template(emptyConstructor, Nil, EmptyValDef, localType :: Nil))
moduleName, Template(emptyConstructor, Nil, Nil, EmptyValDef, localType :: Nil))
.withFlags(Synthetic | Opaque))
Thicket(aliasType :: companions.toList)
}
Expand Down Expand Up @@ -1335,7 +1351,7 @@ object desugar {
val (classParents, self) =
if (parentCores.length == 1 && (parent.tpe eq parentCores.head)) (untpdParent :: Nil, EmptyValDef)
else (parentCores map TypeTree, ValDef(nme.WILDCARD, untpdParent, EmptyTree))
val impl = Template(emptyConstructor, classParents, self, refinements)
val impl = Template(emptyConstructor, classParents, Nil, self, refinements)
TypeDef(tpnme.REFINE_CLASS, impl).withFlags(Trait)
}

Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ object DesugarEnums {
val toStringDef =
DefDef(nme.toString_, Nil, Nil, TypeTree(), Ident(nme.name))
.withFlags(Override)
def creator = New(Template(emptyConstructor, enumClassRef :: Nil, EmptyValDef,
def creator = New(Template(emptyConstructor, enumClassRef :: Nil, Nil, EmptyValDef,
List(enumTagDef, toStringDef) ++ registerCall))
DefDef(nme.DOLLAR_NEW, Nil,
List(List(param(nme.tag, defn.IntType), param(nme.name, defn.StringType))),
Expand Down Expand Up @@ -216,7 +216,7 @@ object DesugarEnums {
if (!enumClass.exists) EmptyTree
else if (enumClass.typeParams.nonEmpty) {
val parent = interpolatedEnumParent(span)
val impl = Template(emptyConstructor, parent :: Nil, EmptyValDef, Nil)
val impl = Template(emptyConstructor, parent :: Nil, Nil, EmptyValDef, Nil)
expandEnumModule(name, impl, mods, span)
}
else {
Expand Down
29 changes: 18 additions & 11 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,6 @@ object Trees {
/** selector match { cases } */
case class Match[-T >: Untyped] private[ast] (selector: Tree[T], cases: List[CaseDef[T]])(implicit @constructorOnly src: SourceFile)
extends TermTree[T] {
assert(cases.nonEmpty)
type ThisTree[-T >: Untyped] = Match[T]
def isInline = false
}
Expand Down Expand Up @@ -739,16 +738,24 @@ object Trees {
def isClassDef: Boolean = rhs.isInstanceOf[Template[_]]
}

/** extends parents { self => body } */
case class Template[-T >: Untyped] private[ast] (constr: DefDef[T], parents: List[Tree[T]], self: ValDef[T], private var preBody: LazyTreeList)(implicit @constructorOnly src: SourceFile)
/** extends parents { self => body }
* @param parentsOrDerived A list of parents followed by a list of derived classes,
* if this is of class untpd.DerivingTemplate.
* Typed templates only have parents.
*/
case class Template[-T >: Untyped] private[ast] (constr: DefDef[T], parentsOrDerived: List[Tree[T]], self: ValDef[T], private var preBody: LazyTreeList)(implicit @constructorOnly src: SourceFile)
extends DefTree[T] with WithLazyField[List[Tree[T]]] {
type ThisTree[-T >: Untyped] = Template[T]
def unforcedBody: LazyTreeList = unforced
def unforced: LazyTreeList = preBody
protected def force(x: AnyRef): Unit = preBody = x
def body(implicit ctx: Context): List[Tree[T]] = forceIfLazy

def parents: List[Tree[T]] = parentsOrDerived // overridden by DerivingTemplate
def derived: List[untpd.Tree] = Nil // overridden by DerivingTemplate
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be Tree[T] here? Then I think the two .derived.asInstanceOf[List[TypeTree]] casts in TreeOpsImpl.scala wouldn't be needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

derived only exist as untyped trees. Typed trees contain derived$... members instead. It would be pointless to keep typed derived trees around, as the info in them is never relevant.

}


/** import expr.selectors
* where a selector is either an untyped `Ident`, `name` or
* an untyped thicket consisting of `name` and `rename`.
Expand Down Expand Up @@ -1143,9 +1150,9 @@ object Trees {
case tree: TypeDef if (name == tree.name) && (rhs eq tree.rhs) => tree
case _ => finalize(tree, untpd.TypeDef(name, rhs)(tree.source))
}
def Template(tree: Tree)(constr: DefDef, parents: List[Tree], self: ValDef, body: LazyTreeList)(implicit ctx: Context): Template = tree match {
case tree: Template if (constr eq tree.constr) && (parents eq tree.parents) && (self eq tree.self) && (body eq tree.unforcedBody) => tree
case _ => finalize(tree, untpd.Template(constr, parents, self, body)(tree.source))
def Template(tree: Tree)(constr: DefDef, parents: List[Tree], derived: List[untpd.Tree], self: ValDef, body: LazyTreeList)(implicit ctx: Context): Template = tree match {
case tree: Template if (constr eq tree.constr) && (parents eq tree.parents) && (derived eq tree.derived) && (self eq tree.self) && (body eq tree.unforcedBody) => tree
case tree => finalize(tree, untpd.Template(constr, parents, derived, self, body)(tree.source))
}
def Import(tree: Tree)(expr: Tree, selectors: List[untpd.Tree])(implicit ctx: Context): Import = tree match {
case tree: Import if (expr eq tree.expr) && (selectors eq tree.selectors) => tree
Expand Down Expand Up @@ -1182,8 +1189,8 @@ object Trees {
DefDef(tree: Tree)(name, tparams, vparamss, tpt, rhs)
def TypeDef(tree: TypeDef)(name: TypeName = tree.name, rhs: Tree = tree.rhs)(implicit ctx: Context): TypeDef =
TypeDef(tree: Tree)(name, rhs)
def Template(tree: Template)(constr: DefDef = tree.constr, parents: List[Tree] = tree.parents, self: ValDef = tree.self, body: LazyTreeList = tree.unforcedBody)(implicit ctx: Context): Template =
Template(tree: Tree)(constr, parents, self, body)
def Template(tree: Template)(constr: DefDef = tree.constr, parents: List[Tree] = tree.parents, derived: List[untpd.Tree] = tree.derived, self: ValDef = tree.self, body: LazyTreeList = tree.unforcedBody)(implicit ctx: Context): Template =
Template(tree: Tree)(constr, parents, derived, self, body)
}

/** Hook to indicate that a transform of some subtree should be skipped */
Expand Down Expand Up @@ -1292,8 +1299,8 @@ object Trees {
case tree @ TypeDef(name, rhs) =>
implicit val ctx = localCtx
cpy.TypeDef(tree)(name, transform(rhs))
case tree @ Template(constr, parents, self, _) =>
cpy.Template(tree)(transformSub(constr), transform(parents), transformSub(self), transformStats(tree.body))
case tree @ Template(constr, parents, self, _) if tree.derived.isEmpty =>
cpy.Template(tree)(transformSub(constr), transform(tree.parents), Nil, transformSub(self), transformStats(tree.body))
case Import(expr, selectors) =>
cpy.Import(tree)(transform(expr), selectors)
case PackageDef(pid, stats) =>
Expand Down Expand Up @@ -1416,7 +1423,7 @@ object Trees {
case TypeDef(name, rhs) =>
implicit val ctx = localCtx
this(x, rhs)
case tree @ Template(constr, parents, self, _) =>
case tree @ Template(constr, parents, self, _) if tree.derived.isEmpty =>
this(this(this(this(x, constr), parents), self), tree.body)
case Import(expr, selectors) =>
this(x, expr)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
val findLocalDummy = new FindLocalDummyAccumulator(cls)
val localDummy = ((NoSymbol: Symbol) /: body)(findLocalDummy.apply)
.orElse(ctx.newLocalDummy(cls))
val impl = untpd.Template(constr, parents, selfType, newTypeParams ++ body)
val impl = untpd.Template(constr, parents, Nil, selfType, newTypeParams ++ body)
.withType(localDummy.termRef)
ta.assignType(untpd.TypeDef(cls.name, impl), cls)
}
Expand Down
25 changes: 21 additions & 4 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,19 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
def withName(name: Name)(implicit ctx: Context): ModuleDef = cpy.ModuleDef(this)(name.toTermName, impl)
}

case class ParsedTry(expr: Tree, handler: Tree, finalizer: Tree)(implicit @constructorOnly src: SourceFile) extends Tree with TermTree
/** An untyped template with a derives clause. Derived parents are added to the end
* of the `parents` list. `derivedCount` keeps track of how many there are.
* This representation was chosen because it balances two concerns:
* - maximize overlap between DerivingTemplate and Template for code streamlining
* - keep invariant that elements of untyped trees align with source positions
*/
class DerivingTemplate(constr: DefDef, parentsOrDerived: List[Tree], self: ValDef, preBody: LazyTreeList, derivedCount: Int)(implicit @constructorOnly src: SourceFile)
extends Template(constr, parentsOrDerived, self, preBody) {
override val parents = parentsOrDerived.dropRight(derivedCount)
override val derived = parentsOrDerived.takeRight(derivedCount)
}

case class ParsedTry(expr: Tree, handler: Tree, finalizer: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree

case class SymbolLit(str: String)(implicit @constructorOnly src: SourceFile) extends TermTree

Expand Down Expand Up @@ -303,7 +315,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
def ValDef(name: TermName, tpt: Tree, rhs: LazyTree)(implicit src: SourceFile): ValDef = new ValDef(name, tpt, rhs)
def DefDef(name: TermName, tparams: List[TypeDef], vparamss: List[List[ValDef]], tpt: Tree, rhs: LazyTree)(implicit src: SourceFile): DefDef = new DefDef(name, tparams, vparamss, tpt, rhs)
def TypeDef(name: TypeName, rhs: Tree)(implicit src: SourceFile): TypeDef = new TypeDef(name, rhs)
def Template(constr: DefDef, parents: List[Tree], self: ValDef, body: LazyTreeList)(implicit src: SourceFile): Template = new Template(constr, parents, self, body)
def Template(constr: DefDef, parents: List[Tree], derived: List[Tree], self: ValDef, body: LazyTreeList)(implicit src: SourceFile): Template =
if (derived.isEmpty) new Template(constr, parents, self, body)
else new DerivingTemplate(constr, parents ++ derived, self, body, derived.length)
def Import(expr: Tree, selectors: List[Tree])(implicit src: SourceFile): Import = new Import(expr, selectors)
def PackageDef(pid: RefTree, stats: List[Tree])(implicit src: SourceFile): PackageDef = new PackageDef(pid, stats)
def Annotated(arg: Tree, annot: Tree)(implicit src: SourceFile): Annotated = new Annotated(arg, annot)
Expand Down Expand Up @@ -431,8 +445,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
case _ => finalize(tree, untpd.ModuleDef(name, impl)(tree.source))
}
def ParsedTry(tree: Tree)(expr: Tree, handler: Tree, finalizer: Tree)(implicit ctx: Context): TermTree = tree match {
case tree: ParsedTry
if (expr eq tree.expr) && (handler eq tree.handler) && (finalizer eq tree.finalizer) => tree
case tree: ParsedTry if (expr eq tree.expr) && (handler eq tree.handler) && (finalizer eq tree.finalizer) => tree
case _ => finalize(tree, untpd.ParsedTry(expr, handler, finalizer)(tree.source))
}
def SymbolLit(tree: Tree)(str: String)(implicit ctx: Context): TermTree = tree match {
Expand Down Expand Up @@ -513,6 +526,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
override def transformMoreCases(tree: Tree)(implicit ctx: Context): Tree = tree match {
case ModuleDef(name, impl) =>
cpy.ModuleDef(tree)(name, transformSub(impl))
case tree: DerivingTemplate =>
cpy.Template(tree)(transformSub(tree.constr), transform(tree.parents), transform(tree.derived), transformSub(tree.self), transformStats(tree.body))
case ParsedTry(expr, handler, finalizer) =>
cpy.ParsedTry(tree)(transform(expr), transform(handler), transform(finalizer))
case SymbolLit(str) =>
Expand Down Expand Up @@ -560,6 +575,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
override def foldMoreCases(x: X, tree: Tree)(implicit ctx: Context): X = tree match {
case ModuleDef(name, impl) =>
this(x, impl)
case tree: DerivingTemplate =>
this(this(this(this(this(x, tree.constr), tree.parents), tree.derived), tree.self), tree.body)
case ParsedTry(expr, handler, finalizer) =>
this(this(this(x, expr), handler), finalizer)
case SymbolLit(str) =>
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/config/Printers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ object Printers {
val config: Printer = noPrinter
val cyclicErrors: Printer = noPrinter
val debug = noPrinter
val derive: Printer = noPrinter
val dottydoc: Printer = noPrinter
val exhaustivity: Printer = noPrinter
val gadts: Printer = noPrinter
Expand Down
Loading