Skip to content

Restrict/generalize derives clauses #6961

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 4 commits into from
Aug 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
214 changes: 163 additions & 51 deletions compiler/src/dotty/tools/dotc/typer/Deriving.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,85 +61,197 @@ trait Deriving { this: Typer =>

/** Check derived type tree `derived` for the following well-formedness conditions:
* (1) It must be a class type with a stable prefix (@see checkClassTypeWithStablePrefix)
* (2) It must have exactly one type parameter
* If it passes the checks, enter a typeclass instance for it in the current scope.
* Given
*
* class C[Ts] .... derives ... D ...
*
* where `T_1, ..., T_n` are the first-kinded type parameters in `Ts`,
* the typeclass instance has the form
* (2) It must belong to one of the following three categories:
* (a) a single paramter type class with a parameter which matches the kind of
* the deriving ADT
* (b) a single parameter type class with a parameter of kind * and an ADT with
* one or more type parameter of kind *
* (c) the Eql type class
*
* implicit def derived$D(implicit ev_1: D[T_1], ..., ev_n: D[T_n]): D[C[Ts]] = D.derived
* See detailed descriptions in deriveSingleParameter and deriveEql below.
*
* See the body of this method for how to generalize this to typeclasses with more
* or less than one type parameter.
* If it passes the checks, enter a typeclass instance for it in the current scope.
*
* See test run/typeclass-derivation2 and run/derive-multi
* See test run/typeclass-derivation2, run/poly-kinded-derives and pos/derive-eq
* for examples that spell out what would be generated.
*
* Note that the name of the derived method contains the name in the derives clause, not
* the underlying class name. This allows one to disambiguate derivations of type classes
* that have the same name but different prefixes through selective aliasing.
*/
private def processDerivedInstance(derived: untpd.Tree): Unit = {
val originalType = typedAheadType(derived, AnyTypeConstructorProto).tpe
val underlyingType = underlyingClassRef(originalType)
val derivedType = checkClassType(underlyingType, derived.sourcePos, traitReq = false, stablePrefixReq = true)
val typeClass = derivedType.classSymbol
val nparams = typeClass.typeParams.length

lazy val clsTpe = cls.typeRef.EtaExpand(cls.typeParams)
if (nparams == 1 && clsTpe.hasSameKindAs(typeClass.typeParams.head.info)) {
// A "natural" type class instance ... the kind of the data type
// matches the kind of the unique type class type parameter

val resultType = derivedType.appliedTo(clsTpe)
val instanceInfo = ExprType(resultType)
addDerivedInstance(originalType.typeSymbol.name, instanceInfo, derived.sourcePos)
} else {
// A matrix of all parameter combinations of current class parameters
// and derived typeclass parameters.
// Rows: parameters of current class
// Columns: parameters of typeclass

// Running example: typeclass: class TC[X, Y, Z], deriving class: class A[T, U]
val originalTypeClassType = typedAheadType(derived, AnyTypeConstructorProto).tpe
val typeClassType = checkClassType(underlyingClassRef(originalTypeClassType), derived.sourcePos, traitReq = false, stablePrefixReq = true)
val typeClass = typeClassType.classSymbol
val typeClassParams = typeClass.typeParams
val typeClassArity = typeClassParams.length

def sameParamKinds(xs: List[ParamInfo], ys: List[ParamInfo]): Boolean =
xs.corresponds(ys)((x, y) => x.paramInfo.hasSameKindAs(y.paramInfo))

def cannotBeUnified =
ctx.error(i"${cls.name} cannot be unified with the type argument of ${typeClass.name}", derived.sourcePos)

def addInstance(derivedParams: List[TypeSymbol], evidenceParamInfos: List[List[Type]], instanceTypes: List[Type]): Unit = {
val resultType = typeClassType.appliedTo(instanceTypes)
val methodOrExpr =
if (evidenceParamInfos.isEmpty) ExprType(resultType)
else ImplicitMethodType(evidenceParamInfos.map(typeClassType.appliedTo), resultType)
val derivedInfo = if (derivedParams.isEmpty) methodOrExpr else PolyType.fromParams(derivedParams, methodOrExpr)
addDerivedInstance(originalTypeClassType.typeSymbol.name, derivedInfo, derived.sourcePos)
}

def deriveSingleParameter: Unit = {
// Single parameter type classes ... (a) and (b) above
//
// (a) ADT and type class parameters overlap on the right and have the
// same kinds at the overlap.
//
// Examples:
//
// Type class: TC[F[T, U]]
//
// ADT: C[A, B, C, D] (C, D have same kinds as T, U)
//
// given derived$TC[a, b]: TC[[t, u] =>> C[a, b, t, u]]
//
// ADT: C[A, B, C] (B, C have same kinds at T, U)
//
// given derived$TC [a]: TC[[t, u] =>> C[a, t, u]]
//
// ADT: C[A, B] (A, B have same kinds at T, U)
//
// given derived$TC : TC[ C ] // a "natural" instance
//
// ADT: C[A] (A has same kind as U)
//
// given derived$TC : TC[[t, u] =>> C[ u]]
//
// (b) The type class and all ADT type parameters are of kind *
//
// In this case the ADT has at least one type parameter of kind *,
// otherwise it would already have been covered as a "natural" case
// for a type class of the form F[_].
//
// The derived instance has a type parameter and a given for
// each of the type parameters of the ADT,
//
// Example:
//
// Type class: TC[T]
//
// ADT: C[A, B, C]
//
// given derived$TC[a, b, c] given TC[a], TC[b], TC[c]: TC[a, b, c]
//
// This, like the derivation for Eql, is a special case of the
// earlier more general multi-parameter type class model for which
// the heuristic is typically a good one.

val typeClassParamType = typeClassParams.head.info
val typeClassParamInfos = typeClassParamType.typeParams
val instanceArity = typeClassParamInfos.length
val clsType = cls.typeRef
val clsParams = cls.typeParams
val clsParamInfos = clsType.typeParams
val clsArity = clsParamInfos.length
val alignedClsParamInfos = clsParamInfos.takeRight(instanceArity)
val alignedTypeClassParamInfos = typeClassParamInfos.take(alignedClsParamInfos.length)


if ((instanceArity == clsArity || instanceArity > 0) && sameParamKinds(alignedClsParamInfos, alignedTypeClassParamInfos)) {
// case (a) ... see description above
val derivedParams = clsParams.dropRight(instanceArity)
val instanceType =
if (instanceArity == clsArity) clsType.EtaExpand(clsParams)
else {
val derivedParamTypes = derivedParams.map(_.typeRef)

HKTypeLambda(typeClassParamInfos.map(_.paramName))(
tl => typeClassParamInfos.map(_.paramInfo.bounds),
tl => clsType.appliedTo(derivedParamTypes ++ tl.paramRefs.takeRight(clsArity)))
}

addInstance(derivedParams, Nil, List(instanceType))
} else if (instanceArity == 0 && !clsParams.exists(_.info.isLambdaSub)) {
// case (b) ... see description above
val instanceType = clsType.appliedTo(clsParams.map(_.typeRef))
val evidenceParamInfos = clsParams.map(param => List(param.typeRef))
addInstance(clsParams, evidenceParamInfos, List(instanceType))
} else
cannotBeUnified
}

def deriveEql: Unit = {
// Specific derives rules for the Eql type class ... (c) above
//
// This has been extracted from the earlier more general multi-parameter
// type class model. Modulo the assumptions below, the implied semantics
// are reasonable defaults.
//
// Assumptions:
// 1. Type params of the deriving class correspond to all and only
// elements of the deriving class which are relevant to equality (but:
// type params could be phantom, or the deriving class might have an
// element of a non-Eql type non-parametrically).
//
// 2. Type params of kinds other than * can be assumed to be irrelevant to
// the derivation (but: eg. Foo[F[_]](fi: F[Int])).
//
// Are they reasonable? They cover some important cases (eg. Tuples of all
// arities). derives Eql is opt-in, so if the semantics don't match those
// appropriate for the deriving class the author of that class can provide
// their own instance in the normal way. That being so, the question turns
// on whether there are enough types which fit these semantics for the
// feature to pay its way.

// Procedure:
// We construct a two column matrix of the deriving class type parameters
// and the Eql typeclass parameters.
//
// Rows: parameters of the deriving class
// Columns: parameters of the Eql typeclass (L/R)
//
// Running example: typeclass: class Eql[L, R], deriving class: class A[T, U, V]
// clsParamss =
// T_X T_Y T_Z
// U_X U_Y U_Z
// T_L T_R
// U_L U_R
// V_L V_R
val clsParamss: List[List[TypeSymbol]] = cls.typeParams.map { tparam =>
if (nparams == 0) Nil
else if (nparams == 1) tparam :: Nil
else typeClass.typeParams.map(tcparam =>
typeClassParams.map(tcparam =>
tparam.copy(name = s"${tparam.name}_$$_${tcparam.name}".toTypeName)
.asInstanceOf[TypeSymbol])
}
// Retain only rows with L/R params of kind * which Eql can be applied to.
// No pairwise evidence will be required for params of other kinds.
val firstKindedParamss = clsParamss.filter {
case param :: _ => !param.info.isLambdaSub
case nil => false
case _ => false
}

// The types of the required evidence parameters. In the running example:
// TC[T_X, T_Y, T_Z], TC[U_X, U_Y, U_Z]
// Eql[T_L, T_R], Eql[U_L, U_R], Eql[V_L, V_R]
val evidenceParamInfos =
for (row <- firstKindedParamss)
yield derivedType.appliedTo(row.map(_.typeRef))
yield row.map(_.typeRef)

// The class instances in the result type. Running example:
// A[T_X, U_X], A[T_Y, U_Y], A[T_Z, U_Z]
val resultInstances =
for (n <- List.range(0, nparams))
// A[T_L, U_L, V_L], A[T_R, U_R, V_R]
val instanceTypes =
for (n <- List.range(0, typeClassArity))
yield cls.typeRef.appliedTo(clsParamss.map(row => row(n).typeRef))

// TC[A[T_X, U_X], A[T_Y, U_Y], A[T_Z, U_Z]]
val resultType = derivedType.appliedTo(resultInstances)

val clsParams: List[TypeSymbol] = clsParamss.flatten
val instanceInfo =
if (clsParams.isEmpty) ExprType(resultType)
else PolyType.fromParams(clsParams, ImplicitMethodType(evidenceParamInfos, resultType))
addDerivedInstance(originalType.typeSymbol.name, instanceInfo, derived.sourcePos)
// Eql[A[T_L, U_L, V_L], A[T_R, U_R, V_R]]
addInstance(clsParamss.flatten, evidenceParamInfos, instanceTypes)
}

if (typeClassArity == 1) deriveSingleParameter
else if (typeClass == defn.EqlClass) deriveEql
else if (typeClassArity == 0)
ctx.error(i"type ${typeClass.name} in derives clause of ${cls.name} has no type parameters", derived.sourcePos)
else
cannotBeUnified
}

/** Create symbols for derived instances and infrastructure,
Expand Down
18 changes: 12 additions & 6 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,16 @@ trait Implicits { self: Typer =>
loop(formal)
}

private def mkMirroredMonoType(mirroredType: HKTypeLambda)(implicit ctx: Context): Type = {
val monoMap = new TypeMap {
def apply(t: Type) = t match {
case TypeParamRef(lambda, n) if lambda eq mirroredType => mirroredType.paramInfos(n)
case t => mapOver(t)
}
}
monoMap(mirroredType.resultType)
}

/** An implied instance for a type of the form `Mirror.Product { type MirroredType = T }`
* where `T` is a generic product type or a case object or an enum case.
*/
Expand Down Expand Up @@ -945,9 +955,7 @@ trait Implicits { self: Typer =>
mirroredType.derivedLambdaType(
resType = TypeOps.nestedPairs(accessors.map(mirroredType.memberInfo(_).widenExpr))
)
val AppliedType(tycon, _) = mirroredType.resultType
val monoType = AppliedType(tycon, mirroredType.paramInfos)
(monoType, elems)
(mkMirroredMonoType(mirroredType), elems)
case _ =>
val elems = TypeOps.nestedPairs(accessors.map(mirroredType.memberInfo(_).widenExpr))
(mirroredType, elems)
Expand Down Expand Up @@ -1029,9 +1037,7 @@ trait Implicits { self: Typer =>
val elems = mirroredType.derivedLambdaType(
resType = TypeOps.nestedPairs(cls.children.map(solve))
)
val AppliedType(tycon, _) = mirroredType.resultType
val monoType = AppliedType(tycon, mirroredType.paramInfos)
(monoType, elems)
(mkMirroredMonoType(mirroredType), elems)
case _ =>
val elems = TypeOps.nestedPairs(cls.children.map(solve))
(mirroredType, elems)
Expand Down
74 changes: 74 additions & 0 deletions tests/neg/multi-param-derives.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import scala.deriving._

object Test extends App {
{
trait Show[T]
object Show {
given as Show[Int] {}
given [T] as Show[Tuple1[T]] given (st: Show[T]) {}
given t2 [T, U] as Show[(T, U)] given (st: Show[T], su: Show[U]) {}
given t3 [T, U, V] as Show[(T, U, V)] given (st: Show[T], su: Show[U], sv: Show[V]) {}

def derived[T] given (m: Mirror.Of[T], r: Show[m.MirroredElemTypes]): Show[T] = new Show[T] {}
}

case class Mono(i: Int) derives Show
case class Poly[A](a: A) derives Show
case class Poly11[F[_]](fi: F[Int]) derives Show // error
case class Poly2[A, B](a: A, b: B) derives Show
case class Poly3[A, B, C](a: A, b: B, c: C) derives Show
}

{
trait Functor[F[_]]
object Functor {
given [C] as Functor[[T] =>> C] {}
given as Functor[[T] =>> Tuple1[T]] {}
given t2 [T] as Functor[[U] =>> (T, U)] {}
given t3 [T, U] as Functor[[V] =>> (T, U, V)] {}

def derived[F[_]] given (m: Mirror { type MirroredType = F ; type MirroredElemTypes[_] }, r: Functor[m.MirroredElemTypes]): Functor[F] = new Functor[F] {}
}

case class Mono(i: Int) derives Functor
case class Poly[A](a: A) derives Functor
case class Poly11[F[_]](fi: F[Int]) derives Functor // error
case class Poly2[A, B](a: A, b: B) derives Functor
case class Poly3[A, B, C](a: A, b: B, c: C) derives Functor
}

{
trait FunctorK[F[_[_]]]
object FunctorK {
given [C] as FunctorK[[F[_]] =>> C] {}
given [T] as FunctorK[[F[_]] =>> Tuple1[F[T]]]

def derived[F[_[_]]] given (m: Mirror { type MirroredType = F ; type MirroredElemTypes[_[_]] }, r: FunctorK[m.MirroredElemTypes]): FunctorK[F] = new FunctorK[F] {}
}

case class Mono(i: Int) derives FunctorK
case class Poly[A](a: A) derives FunctorK // error
case class Poly11[F[_]](fi: F[Int]) derives FunctorK
case class Poly2[A, B](a: A, b: B) derives FunctorK // error
case class Poly3[A, B, C](a: A, b: B, c: C) derives FunctorK // error
}

{
trait Bifunctor[F[_, _]]
object Bifunctor {
given [C] as Bifunctor[[T, U] =>> C] {}
given as Bifunctor[[T, U] =>> Tuple1[U]] {}
given t2 as Bifunctor[[T, U] =>> (T, U)] {}
given t3 [T] as Bifunctor[[U, V] =>> (T, U, V)] {}

def derived[F[_, _]] given (m: Mirror { type MirroredType = F ; type MirroredElemTypes[_, _] }, r: Bifunctor[m.MirroredElemTypes]): Bifunctor[F] = ???
}

case class Mono(i: Int) derives Bifunctor
case class Poly[A](a: A) derives Bifunctor
case class Poly11[F[_]](fi: F[Int]) derives Bifunctor // error
case class Poly2[A, B](a: A, b: B) derives Bifunctor
case class Poly3[A, B, C](a: A, b: B, c: C) derives Bifunctor
}
}

Loading