Skip to content

Test case for new typeclass derivation scheme #6531

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 35 commits into from
Jun 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
2b1ec55
New test case
odersky May 17, 2019
844c5d7
Fix widenAbstractTypes
odersky May 18, 2019
d5c5c11
Special treatment for Singletons
odersky May 18, 2019
6fda1f3
Fix printer bug
odersky May 19, 2019
c30af3c
Add desugar Printer
odersky May 20, 2019
3617cba
Add deriving.Mirror infrastructure
odersky May 20, 2019
42807da
Add isGenericProduct test
odersky May 20, 2019
5829928
Synthesis for mirror infrastructure
odersky May 20, 2019
c6c0f71
Rename SyntheticMethods -> SyntheticMembers
odersky May 20, 2019
5c924ca
Mirror infrastructure for generic sum types
odersky May 20, 2019
159c883
Refine isGenericSum condition
odersky May 21, 2019
88cec9f
Always generate companion objects for sealed classes
odersky May 21, 2019
4cccf41
Revert "Always generate companion objects for sealed classes"
odersky May 21, 2019
c031b30
Make enum cases implement Mirror.Singleton
odersky May 21, 2019
9120105
Add Mirror.Singleton to enum cases after typer
odersky May 21, 2019
ed79907
Refactor special case handling in implicit arguments
odersky May 21, 2019
f898fc1
Add new utility method: withAttachment
odersky May 22, 2019
934c4ad
Use attachment to mark singleton cases
odersky May 22, 2019
be1a063
Synthesize implicits for product and sum mirrors
odersky May 22, 2019
275a0a9
Generate sum mirrors for sealed traits that do not have a companion
odersky May 22, 2019
e50a032
Use Label field for sum and product mirrors
odersky May 22, 2019
eda4232
Synthesize mirrors also for Scala 2 defined classes and objects
odersky May 22, 2019
f24e247
Polishings
odersky May 22, 2019
7583adc
Avoid name clashes by prefixing all type members with Mirrored
odersky May 22, 2019
4784e2f
Update typeclass-scaling data using old Generic scheme
odersky May 23, 2019
a87b42b
Measure typeclass scaling using new scheme
odersky May 23, 2019
56c0f96
Drop old deriving infrastructure
odersky May 23, 2019
01d3ff7
Make scheme work also for nested classes and companion objects
odersky May 23, 2019
6d3c2f9
Don't constrain MirroredElemTypes to be a subtype of Tuple
odersky May 24, 2019
c02861f
Introduce MirroredTypeConstructor
odersky May 24, 2019
930ca64
Avoid widening derived instances too far
milessabin Jun 2, 2019
91df766
Generalized type class derivation for higher kinded type classes
milessabin Jun 2, 2019
ddb0c4c
Renamed elemTypes to elemsType
milessabin Jun 4, 2019
bf67516
MirroredMonoType -> MirroredType in comment
milessabin Jun 4, 2019
2afa89c
Removed redundant case and inlined RHSs
milessabin Jun 4, 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
8 changes: 4 additions & 4 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import reporting.diagnostic.messages._
import reporting.trace
import annotation.constructorOnly
import printing.Formatting.hl
import config.Printers

import scala.annotation.internal.sharable

Expand Down Expand Up @@ -51,7 +52,7 @@ object desugar {
private type VarInfo = (NameTree, Tree)

/** Is `name` the name of a method that can be invalidated as a compiler-generated
* case class method that clashes with a user-defined method?
* case class method if it clashes with a user-defined method?
*/
def isRetractableCaseClassMethodName(name: Name)(implicit ctx: Context): Boolean = name match {
case nme.apply | nme.unapply | nme.unapplySeq | nme.copy => true
Expand Down Expand Up @@ -765,7 +766,7 @@ object desugar {
}

flatTree(cdef1 :: companions ::: implicitWrappers)
}
}.reporting(res => i"desugared: $res", Printers.desugar)

/** Expand
*
Expand Down Expand Up @@ -848,8 +849,7 @@ object desugar {
fwd
}
val moduleName = tdef.name.toTermName
val localRef = Select(Ident(moduleName), tdef.name)
localRef.pushAttachment(SuppressAccessCheck, ())
val localRef = Select(Ident(moduleName), tdef.name).withAttachment(SuppressAccessCheck, ())
val aliasType = cpy.TypeDef(tdef)(rhs = completeForwarder(localRef)).withSpan(tdef.span.startPos)
val localType = tdef.withMods(Modifiers(Synthetic | Opaque).withPrivateWithin(tdef.name))

Expand Down
13 changes: 10 additions & 3 deletions compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Symbols._, StdNames._, Trees._
import Decorators._
import util.{Property, SourceFile}
import typer.ErrorReporting._
import transform.SyntheticMembers.ExtendsSingletonMirror

import scala.annotation.internal.sharable

Expand Down Expand Up @@ -115,11 +116,16 @@ object DesugarEnums {
val toStringDef =
DefDef(nme.toString_, Nil, Nil, TypeTree(), Ident(nme.name))
.withFlags(Override)
def creator = New(Template(emptyConstructor, enumClassRef :: Nil, Nil, EmptyValDef,
List(enumTagDef, toStringDef) ++ registerCall))
val creator = New(Template(
constr = emptyConstructor,
parents = enumClassRef :: Nil,
derived = Nil,
self = EmptyValDef,
body = List(enumTagDef, toStringDef) ++ registerCall
).withAttachment(ExtendsSingletonMirror, ()))
DefDef(nme.DOLLAR_NEW, Nil,
List(List(param(nme.tag, defn.IntType), param(nme.name, defn.StringType))),
TypeTree(), creator)
TypeTree(), creator).withFlags(Private | Synthetic)
}

/** The return type of an enum case apply method and any widening methods in which
Expand Down Expand Up @@ -259,6 +265,7 @@ object DesugarEnums {
.withFlags(Override)
val (tagMeth, scaffolding) = enumTagMeth(CaseKind.Object)
val impl1 = cpy.Template(impl)(body = List(tagMeth, toStringMeth) ++ registerCall)
.withAttachment(ExtendsSingletonMirror, ())
val vdef = ValDef(name, TypeTree(), New(impl1)).withMods(mods | Final)
flatTree(scaffolding ::: vdef :: Nil).withSpan(span)
}
Expand Down
20 changes: 20 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,26 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
case ConstantType(value) => Literal(value)
}

/** A path that corresponds to the given type `tp`. Error if `tp` is not a refinement
* of an addressable singleton type.
*/
def pathFor(tp: Type)(implicit ctx: Context): Tree = {
def recur(tp: Type): Tree = tp match {
case tp: NamedType =>
tp.info match {
case TypeAlias(alias) => recur(alias)
case _: TypeBounds => EmptyTree
case _ => singleton(tp)
}
case tp: TypeProxy => recur(tp.superType)
case _ => EmptyTree
}
recur(tp).orElse {
ctx.error(em"$tp is not an addressable singleton type")
TypeTree(tp)
}
}

/** A tree representing a `newXYZArray` operation of the right
* kind for the given element type in `elemTpe`. No type arguments or
* `length` arguments are given.
Expand Down
5 changes: 1 addition & 4 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -263,10 +263,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
}

/** Install the derived type tree as a dependency on `sym` */
def watching(sym: Symbol): this.type = {
pushAttachment(OriginalSymbol, sym)
this
}
def watching(sym: Symbol): this.type = withAttachment(OriginalSymbol, sym)

/** A hook to ensure that all necessary symbols are completed so that
* OriginalSymbol attachments are propagated to this tree
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 @@ -19,6 +19,7 @@ object Printers {
val cyclicErrors: Printer = noPrinter
val debug = noPrinter // no type annotation here to force inlining
val derive: Printer = noPrinter
val desugar: Printer = noPrinter
val dottydoc: Printer = noPrinter
val exhaustivity: Printer = noPrinter
val gadts: Printer = noPrinter
Expand Down
27 changes: 17 additions & 10 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -690,16 +690,23 @@ class Definitions {
lazy val ModuleSerializationProxyConstructor: TermSymbol =
ModuleSerializationProxyClass.requiredMethod(nme.CONSTRUCTOR, List(ClassType(TypeBounds.empty)))

lazy val GenericType: TypeRef = ctx.requiredClassRef("scala.reflect.Generic")
def GenericClass(implicit ctx: Context): ClassSymbol = GenericType.symbol.asClass
lazy val ShapeType: TypeRef = ctx.requiredClassRef("scala.compiletime.Shape")
def ShapeClass(implicit ctx: Context): ClassSymbol = ShapeType.symbol.asClass
lazy val ShapeCaseType: TypeRef = ctx.requiredClassRef("scala.compiletime.Shape.Case")
def ShapeCaseClass(implicit ctx: Context): ClassSymbol = ShapeCaseType.symbol.asClass
lazy val ShapeCasesType: TypeRef = ctx.requiredClassRef("scala.compiletime.Shape.Cases")
def ShapeCasesClass(implicit ctx: Context): ClassSymbol = ShapeCasesType.symbol.asClass
lazy val MirrorType: TypeRef = ctx.requiredClassRef("scala.reflect.Mirror")
lazy val GenericClassType: TypeRef = ctx.requiredClassRef("scala.reflect.GenericClass")
lazy val MirrorType: TypeRef = ctx.requiredClassRef("scala.deriving.Mirror")
def MirrorClass(implicit ctx: Context): ClassSymbol = MirrorType.symbol.asClass

lazy val Mirror_ProductType: TypeRef = ctx.requiredClassRef("scala.deriving.Mirror.Product")
def Mirror_ProductClass(implicit ctx: Context): ClassSymbol = Mirror_ProductType.symbol.asClass

lazy val Mirror_Product_fromProductR: TermRef = Mirror_ProductClass.requiredMethodRef(nme.fromProduct)
def Mirror_Product_fromProduct(implicit ctx: Context): Symbol = Mirror_Product_fromProductR.symbol

lazy val Mirror_SumType: TypeRef = ctx.requiredClassRef("scala.deriving.Mirror.Sum")
def Mirror_SumClass(implicit ctx: Context): ClassSymbol = Mirror_SumType.symbol.asClass

lazy val Mirror_SingletonType: TypeRef = ctx.requiredClassRef("scala.deriving.Mirror.Singleton")
def Mirror_SingletonClass(implicit ctx: Context): ClassSymbol = Mirror_SingletonType.symbol.asClass

lazy val Mirror_SingletonProxyType: TypeRef = ctx.requiredClassRef("scala.deriving.Mirror.SingletonProxy")
def Mirror_SingletonProxyClass(implicit ctx: Context): ClassSymbol = Mirror_SingletonProxyType.symbol.asClass

lazy val LanguageModuleRef: TermSymbol = ctx.requiredModule("scala.language")
def LanguageModuleClass(implicit ctx: Context): ClassSymbol = LanguageModuleRef.moduleClass.asClass
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,7 @@ object Flags {

/** An enum case */
final val EnumCase: FlagConjunction = allOf(Enum, Case)
final val EnumCaseVal: FlagConjunction = allOf(Enum, CaseVal)

/** A term parameter or parameter accessor */
final val TermParamOrAccessor: FlagSet = Param | ParamAccessor
Expand Down
6 changes: 6 additions & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,11 @@ object StdNames {
val LiteralAnnotArg: N = "LiteralAnnotArg"
val longHash: N = "longHash"
val MatchCase: N = "MatchCase"
val MirroredElemTypes: N = "MirroredElemTypes"
val MirroredElemLabels: N = "MirroredElemLabels"
val MirroredLabel: N = "MirroredLabel"
val MirroredMonoType: N = "MirroredMonoType"
val MirroredType: N = "MirroredType"
val Modifiers: N = "Modifiers"
val NestedAnnotArg: N = "NestedAnnotArg"
val NoFlags: N = "NoFlags"
Expand Down Expand Up @@ -432,6 +437,7 @@ object StdNames {
val flagsFromBits : N = "flagsFromBits"
val flatMap: N = "flatMap"
val foreach: N = "foreach"
val fromProduct: N = "fromProduct"
val genericArrayOps: N = "genericArrayOps"
val genericClass: N = "genericClass"
val get: N = "get"
Expand Down
29 changes: 20 additions & 9 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2313,19 +2313,30 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
cas
}
def widenAbstractTypes(tp: Type): Type = new TypeMap {
var seen = Set[TypeParamRef]()
def apply(tp: Type) = tp match {
case tp: TypeRef =>
if (tp.symbol.isAbstractOrParamType | tp.symbol.isOpaqueAlias)
WildcardType
else tp.info match {
case TypeAlias(alias) =>
val alias1 = widenAbstractTypes(alias)
if (alias1 ne alias) alias1 else tp
case _ => mapOver(tp)
tp.info match {
case info: MatchAlias =>
mapOver(tp)
// TODO: We should follow the alias in this case, but doing so
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this just about error reporting (proper cyclic reference instead of a stack overflow), or do you actually have a type correct program that makes the compiler loop here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The second: if we would follow the alias here instead of just doing a mapOver every recursive match type would loop.

// risks infinite recursion
case TypeBounds(lo, hi) =>
if (hi frozen_<:< lo) {
val alias = apply(lo)
if (alias ne lo) alias else mapOver(tp)
}
else WildcardType
case _ =>
mapOver(tp)
}

case tp: TypeLambda =>
val saved = seen
seen ++= tp.paramRefs
try mapOver(tp)
finally seen = saved
case tp: TypeVar if !tp.isInstantiated => WildcardType
case _: TypeParamRef => WildcardType
case tp: TypeParamRef if !seen.contains(tp) => WildcardType
case _ => mapOver(tp)
}
}.apply(tp)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
val saved = maxSummarized
maxSummarized = ctx.base.toTextRecursions + depth
try op
finally maxSummarized = depth
finally maxSummarized = saved
}

def summarized[T](op: => T): T = summarized(summarizeDepth)(op)
Expand Down
8 changes: 4 additions & 4 deletions compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ object PostTyper {
* field (corresponding = super class field is initialized with subclass field)
* (@see ForwardParamAccessors)
*
* (3) Add synthetic methods (@see SyntheticMethods)
* (3) Add synthetic members (@see SyntheticMembers)
*
* (4) Check that `New` nodes can be instantiated, and that annotations are valid
*
Expand Down Expand Up @@ -64,7 +64,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
case _ =>
}

override def changesMembers: Boolean = true // the phase adds super accessors and synthetic methods
override def changesMembers: Boolean = true // the phase adds super accessors and synthetic members

override def transformPhase(implicit ctx: Context): Phase = thisPhase.next

Expand All @@ -73,7 +73,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase

val superAcc: SuperAccessors = new SuperAccessors(thisPhase)
val paramFwd: ParamForwarding = new ParamForwarding(thisPhase)
val synthMth: SyntheticMethods = new SyntheticMethods(thisPhase)
val synthMbr: SyntheticMembers = new SyntheticMembers(thisPhase)

private def newPart(tree: Tree): Option[New] = methPart(tree) match {
case Select(nu: New, _) => Some(nu)
Expand Down Expand Up @@ -230,7 +230,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
case tree: Template =>
withNoCheckNews(tree.parents.flatMap(newPart)) {
val templ1 = paramFwd.forwardParamAccessors(tree)
synthMth.addSyntheticMethods(
synthMbr.addSyntheticMembers(
superAcc.wrapTemplate(templ1)(
super.transform(_).asInstanceOf[Template]))
}
Expand Down
54 changes: 53 additions & 1 deletion compiler/src/dotty/tools/dotc/transform/SymUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import StdNames._
import NameKinds._
import Flags._
import Annotations._
import ValueClasses.isDerivedValueClass
import Decorators._

import language.implicitConversions
import scala.annotation.tailrec
Expand Down Expand Up @@ -59,10 +61,56 @@ class SymUtils(val self: Symbol) extends AnyVal {

def isSuperAccessor(implicit ctx: Context): Boolean = self.name.is(SuperAccessorName)

/** A type or term parameter or a term parameter accessor */
/** Is this a type or term parameter or a term parameter accessor? */
def isParamOrAccessor(implicit ctx: Context): Boolean =
self.is(Param) || self.is(ParamAccessor)

/** Is this a case class for which a product mirror is generated?
* Excluded are value classes, abstract classes and case classes with more than one
* parameter section.
*/
def whyNotGenericProduct(implicit ctx: Context): String =
if (!self.is(CaseClass)) "it is not a case class"
else if (self.is(Abstract)) "it is an abstract class"
else if (self.primaryConstructor.info.paramInfoss.length != 1) "it takes more than one parameter list"
else if (isDerivedValueClass(self)) "it is a value class"
else ""

def isGenericProduct(implicit ctx: Context): Boolean = whyNotGenericProduct.isEmpty

/** Is this a sealed class or trait for which a sum mirror is generated?
* It must satisfy the following conditions:
* - it has at least one child class or object
* - none of its children are anonymous classes
* - all of its children are addressable through a path from its companion object
* - all of its children are generic products or singletons
*/
def whyNotGenericSum(implicit ctx: Context): String =
if (!self.is(Sealed))
s"it is not a sealed ${if (self.is(Trait)) "trait" else "class"}"
else {
val children = self.children
val companion = self.linkedClass
def problem(child: Symbol) = {

def isAccessible(sym: Symbol): Boolean =
companion.isContainedIn(sym) || sym.is(Module) && isAccessible(sym.owner)

if (child == self) "it has anonymous or inaccessible subclasses"
else if (!isAccessible(child.owner)) i"its child $child is not accessible"
else if (!child.isClass) ""
else {
val s = child.whyNotGenericProduct
if (s.isEmpty) s
else i"its child $child is not a generic product because $s"
}
}
if (children.isEmpty) "it does not have subclasses"
else children.map(problem).find(!_.isEmpty).getOrElse("")
}

def isGenericSum(implicit ctx: Context): Boolean = whyNotGenericSum.isEmpty

/** If this is a constructor, its owner: otherwise this. */
final def skipConstructor(implicit ctx: Context): Symbol =
if (self.isConstructor) self.owner else self
Expand Down Expand Up @@ -151,6 +199,10 @@ class SymUtils(val self: Symbol) extends AnyVal {
else owner.isLocal
}

/** The typeRef with wildcard arguments for each type parameter */
def rawTypeRef(implicit ctx: Context) =
self.typeRef.appliedTo(self.typeParams.map(_ => TypeBounds.empty))

/** Is symbol a quote operation? */
def isQuote(implicit ctx: Context): Boolean =
self == defn.InternalQuoted_exprQuote || self == defn.InternalQuoted_typeQuote
Expand Down
Loading