Skip to content

Add Enum Eq #2552

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 18 commits into from
May 29, 2017
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
63 changes: 47 additions & 16 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,12 @@ object desugar {
val defctx = ctx.outersIterator.dropWhile(_.scope eq ctx.scope).next
var local = defctx.denotNamed(tp.name).suchThat(_ is ParamOrAccessor).symbol
if (local.exists) (defctx.owner.thisType select local).dealias
else throw new java.lang.Error(
s"no matching symbol for ${tp.symbol.showLocated} in ${defctx.owner} / ${defctx.effectiveScope}"
)
else {
def msg =
s"no matching symbol for ${tp.symbol.showLocated} in ${defctx.owner} / ${defctx.effectiveScope}"
if (ctx.reporter.errorsReported) new ErrorType(msg)
else throw new java.lang.Error(msg)
}
case _ =>
mapOver(tp)
}
Expand Down Expand Up @@ -124,7 +127,7 @@ object desugar {
else vdef
}

def makeImplicitParameters(tpts: List[Tree], forPrimaryConstructor: Boolean)(implicit ctx: Context) =
def makeImplicitParameters(tpts: List[Tree], forPrimaryConstructor: Boolean = false)(implicit ctx: Context) =
for (tpt <- tpts) yield {
val paramFlags: FlagSet = if (forPrimaryConstructor) PrivateLocalParamAccessor else Param
val epname = EvidenceParamName.fresh()
Expand Down Expand Up @@ -265,7 +268,7 @@ object desugar {
val mods = cdef.mods
val companionMods = mods
.withFlags((mods.flags & AccessFlags).toCommonFlags)
.withMods(mods.mods.filter(!_.isInstanceOf[Mod.EnumCase]))
.withMods(Nil)

val (constr1, defaultGetters) = defDef(constr0, isPrimaryConstructor = true) match {
case meth: DefDef => (meth, Nil)
Expand All @@ -291,7 +294,7 @@ object desugar {

val isCaseClass = mods.is(Case) && !mods.is(Module)
val isCaseObject = mods.is(Case) && mods.is(Module)
val isEnum = mods.hasMod[Mod.Enum]
val isEnum = mods.hasMod[Mod.Enum] && !mods.is(Module)
val isEnumCase = isLegalEnumCase(cdef)
val isValueClass = parents.nonEmpty && isAnyVal(parents.head)
// This is not watertight, but `extends AnyVal` will be replaced by `inline` later.
Expand Down Expand Up @@ -326,10 +329,12 @@ object desugar {

val classTycon: Tree = new TypeRefTree // watching is set at end of method

def appliedRef(tycon: Tree) =
(if (constrTparams.isEmpty) tycon
else AppliedTypeTree(tycon, constrTparams map refOfDef))
.withPos(cdef.pos.startPos)
def appliedTypeTree(tycon: Tree, args: List[Tree]) =
(if (args.isEmpty) tycon else AppliedTypeTree(tycon, args))
.withPos(cdef.pos.startPos)

def appliedRef(tycon: Tree, tparams: List[TypeDef] = constrTparams) =
appliedTypeTree(tycon, tparams map refOfDef)

// a reference to the class type bound by `cdef`, with type parameters coming from the constructor
val classTypeRef = appliedRef(classTycon)
Expand All @@ -344,8 +349,7 @@ object desugar {
else {
ctx.error(i"explicit extends clause needed because type parameters of case and enum class differ"
, cdef.pos.startPos)
AppliedTypeTree(enumClassRef, constrTparams map (_ => anyRef))
.withPos(cdef.pos.startPos)
appliedTypeTree(enumClassRef, constrTparams map (_ => anyRef))
}
case _ =>
enumClassRef
Expand Down Expand Up @@ -411,6 +415,31 @@ object desugar {
if (isEnum)
parents1 = parents1 :+ ref(defn.EnumType)

// The Eq instance for an Enum class. For an enum class
//
// enum class C[T1, ..., Tn]
//
// we generate:
//
// implicit def eqInstance[T1$1, ..., Tn$1, T1$2, ..., Tn$2](implicit
// ev1: Eq[T1$1, T1$2], ..., evn: Eq[Tn$1, Tn$2]])
// : Eq[C[T1$1, ..., Tn$1], C[T1$2, ..., Tn$2]] = Eq
def eqInstance = {
def append(tdef: TypeDef, str: String) = cpy.TypeDef(tdef)(name = tdef.name ++ str)
val leftParams = derivedTparams.map(append(_, "$1"))
Copy link
Member

Choose a reason for hiding this comment

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

Any reason to not use semantic names 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.

Yes, we could do that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

On the other hand, maybe it's not worth the hassle. The names literally don't matter.

val rightParams = derivedTparams.map(append(_, "$2"))
val subInstances = (leftParams, rightParams).zipped.map((param1, param2) =>
appliedRef(ref(defn.EqType), List(param1, param2)))
DefDef(
name = nme.eqInstance,
tparams = leftParams ++ rightParams,
vparamss = List(makeImplicitParameters(subInstances)),
tpt = appliedTypeTree(ref(defn.EqType),
appliedRef(classTycon, leftParams) :: appliedRef(classTycon, rightParams) :: Nil),
rhs = ref(defn.EqModule.termRef)).withFlags(Synthetic | Implicit)
}
def eqInstances = if (isEnum) eqInstance :: Nil else Nil

// The thicket which is the desugared version of the companion object
// synthetic object C extends parentTpt { defs }
def companionDefs(parentTpt: Tree, defs: List[Tree]) =
Expand All @@ -420,6 +449,8 @@ object desugar {
.withMods(companionMods | Synthetic))
.withPos(cdef.pos).toList

val companionMeths = defaultGetters ::: eqInstances

// The companion object definitions, if a companion is needed, Nil otherwise.
// companion definitions include:
// 1. If class is a case class case class C[Ts](p1: T1, ..., pN: TN)(moreParams):
Expand Down Expand Up @@ -465,10 +496,10 @@ object desugar {
DefDef(nme.unapply, derivedTparams, (unapplyParam :: Nil) :: Nil, TypeTree(), unapplyRHS)
.withMods(synthetic)
}
companionDefs(parent, applyMeths ::: unapplyMeth :: defaultGetters)
companionDefs(parent, applyMeths ::: unapplyMeth :: companionMeths)
}
else if (defaultGetters.nonEmpty)
companionDefs(anyRef, defaultGetters)
else if (companionMeths.nonEmpty)
companionDefs(anyRef, companionMeths)
else if (isValueClass) {
constr0.vparamss match {
case List(_ :: Nil) => companionDefs(anyRef, Nil)
Expand Down Expand Up @@ -739,7 +770,7 @@ object desugar {
}

def makeImplicitFunction(formals: List[Type], body: Tree)(implicit ctx: Context): Tree = {
val params = makeImplicitParameters(formals.map(TypeTree), forPrimaryConstructor = false)
val params = makeImplicitParameters(formals.map(TypeTree))
new ImplicitFunction(params, body)
}

Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,7 @@ class Definitions {

lazy val EqType = ctx.requiredClassRef("scala.Eq")
def EqClass(implicit ctx: Context) = EqType.symbol.asClass
def EqModule(implicit ctx: Context) = EqClass.companionModule

lazy val XMLTopScopeModuleRef = ctx.requiredModuleRef("scala.xml.TopScope")

Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ object StdNames {
val ensureAccessible : N = "ensureAccessible"
val enumTag: N = "enumTag"
val eq: N = "eq"
val eqInstance: N = "eqInstance"
val equalsNumChar : N = "equalsNumChar"
val equalsNumNum : N = "equalsNumNum"
val equalsNumObject : N = "equalsNumObject"
Expand Down
36 changes: 23 additions & 13 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -656,7 +656,7 @@ trait Implicits { self: Typer =>
if (!ctx.isAfterTyper && !assumedCanEqual(ltp, rtp)) {
val res = inferImplicitArg(
defn.EqType.appliedTo(ltp, rtp), msgFun => ctx.error(msgFun(""), pos), pos)
implicits.println(i"Eq witness found: $res: ${res.tpe}")
implicits.println(i"Eq witness found for $ltp / $rtp: $res: ${res.tpe}")
}

/** Find an implicit parameter or conversion.
Expand All @@ -676,7 +676,7 @@ trait Implicits { self: Typer =>
val isearch =
if (ctx.settings.explainImplicits.value) new ExplainedImplicitSearch(pt, argument, pos)
else new ImplicitSearch(pt, argument, pos)
val result = isearch.bestImplicit
val result = isearch.bestImplicit(contextual = true)
result match {
case result: SearchSuccess =>
result.tstate.commit()
Expand Down Expand Up @@ -743,7 +743,7 @@ trait Implicits { self: Typer =>
def typedImplicit(cand: Candidate)(implicit ctx: Context): SearchResult = track("typedImplicit") { ctx.traceIndented(i"typed implicit ${cand.ref}, pt = $pt, implicitsEnabled == ${ctx.mode is ImplicitsEnabled}", implicits, show = true) {
assert(constr eq ctx.typerState.constraint)
val ref = cand.ref
var generated: Tree = tpd.ref(ref).withPos(pos.startPos)
var generated: Tree = tpd.ref(ref).withPos(pos)
if (!argument.isEmpty)
generated = typedUnadapted(
untpd.Apply(untpd.TypedSplice(generated), untpd.TypedSplice(argument) :: Nil),
Expand All @@ -759,13 +759,20 @@ trait Implicits { self: Typer =>
case _ => false
}
}
// Does there exist an implicit value of type `Eq[tp, tp]`?
def hasEq(tp: Type): Boolean =
new ImplicitSearch(defn.EqType.appliedTo(tp, tp), EmptyTree, pos).bestImplicit match {
case result: SearchSuccess => result.ref.symbol != defn.Predef_eqAny
case result: AmbiguousImplicits => true
case _ => false
}
// Does there exist an implicit value of type `Eq[tp, tp]`
// which is different from `eqAny`?
def hasEq(tp: Type): Boolean = {
def search(contextual: Boolean): Boolean =
new ImplicitSearch(defn.EqType.appliedTo(tp, tp), EmptyTree, pos)
.bestImplicit(contextual) match {
case result: SearchSuccess =>
result.ref.symbol != defn.Predef_eqAny ||
contextual && search(contextual = false)
case result: AmbiguousImplicits => true
case _ => false
}
search(contextual = true)
}

def validEqAnyArgs(tp1: Type, tp2: Type) = {
List(tp1, tp2).foreach(fullyDefinedType(_, "eqAny argument", pos))
Expand Down Expand Up @@ -872,12 +879,15 @@ trait Implicits { self: Typer =>
}

/** Find a unique best implicit reference */
def bestImplicit: SearchResult = {
searchImplicits(ctx.implicits.eligible(wildProto), contextual = true) match {
def bestImplicit(contextual: Boolean): SearchResult = {
val eligible =
if (contextual) ctx.implicits.eligible(wildProto)
else implicitScope(wildProto).eligible
searchImplicits(eligible, contextual) match {
case result: SearchSuccess => result
case result: AmbiguousImplicits => result
case result: SearchFailure =>
searchImplicits(implicitScope(wildProto).eligible, contextual = false)
if (contextual) bestImplicit(contextual = false) else result
}
}

Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1963,7 +1963,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
case _: RefTree | _: Literal
if !isVarPattern(tree) &&
!(tree.tpe <:< pt)(ctx.addMode(Mode.GADTflexible)) =>
checkCanEqual(pt, wtp, tree.pos)(ctx.retractMode(Mode.Pattern))
val tp1 :: tp2 :: Nil = harmonizeTypes(pt :: wtp :: Nil)
checkCanEqual(tp1, tp2, tree.pos)(ctx.retractMode(Mode.Pattern))
case _ =>
}
tree
Expand Down
33 changes: 33 additions & 0 deletions docs/docs/reference/auto-parameter-tupling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
layout: doc-page
title: "Automatic Tupling of Function Parameters"
---

Say you have a list of pairs

val xs: List[(Int, Int)]

and you want to map `xs` to a list of `Int`s so that eich pair of numbers is mapped to
their sum. Previously, the best way to do this was with a pattern-matching decomposition:

xs map {
case (x, y) => x + y
}

While correct, this is also inconvenient. Dotty now also allows:

xs.map {
(x, y) => x + y
}

or, equivalently:

xs.map(_ + _)

Generally, a function value with `n > 1` parameters is converted to a
pattern-matching closure using `case` if the expected type is a unary
function type of the form `((T_1, ..., T_n)) => U`.




11 changes: 10 additions & 1 deletion docs/docs/reference/desugarEnums.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ comma separated simple cases into a sequence of cases.

case C <params> ...

expands analogous to a case class:
expands analogous to a final case class:

final case class C <params> ...

Expand Down Expand Up @@ -138,6 +138,15 @@ comma separated simple cases into a sequence of cases.
Any modifiers or annotations on the original case extend to all expanded
cases.

## Equality

An `enum` type contains a `scala.Eq` instance that restricts values of the `enum` type to
be compared only to other values of the same enum type. Furtermore, generic
`enum` types are comparable only if their type arguments are. For instance the
`Option` enum type will get the following definition in its companion object:

implicit def eqOption[T, U](implicit ev1: Eq[T, U]): Eq[Option[T], Option[U]] = Eq

## Translation of Enumerations

Non-generic enum classes `E` that define one or more singleton cases
Expand Down
99 changes: 99 additions & 0 deletions docs/docs/reference/implicit-function-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
---
layout: doc-page
title: "Implicit Function Types"
---

An implicit funciton type describes functions with implicit parameters. Example:

type Contextual[T] = implicit Context => T

A value of implicit function type is applied to implicit arguments, in
the same way a method with implicit parameters is applied. For instance:

implicit ctx: Context = ...

def f(x: Int): Contextual[Int] = ...

f(2) // is expanded to f(2)(ctx)

Conversely, if the expected type of an expression `E` is an implicit
function type `implicit (T_1, ..., T_n) => U` and `E` is not already an
implicit function value, `E` is converted to an implicit function value
by rewriting to

implicit (x_1: T1, ..., x_n: Tn) => E

where the names `x_1`, ..., `x_n` are arbitrary. For example, continuing
with the previous definitions,

def g(arg: Contextual[Int]) = ...

g(22) // is expanded to g { implicit ctx => 22 }

g(f(2)) // is expanded to g { implicit ctx => f(2)(ctx) }

g(implicit ctx => f(22)(ctx)) // is left as it is

Implicit function types have considerable expressive power. For
instance, here is how they can support the "builder pattern", where
the aim is to construct tables like this:

table {
row {
cell("top left")
cell("top right")
}
row {
cell("botttom left")
cell("bottom right")
}
}

The idea is to define classes for `Table` and `Row` that allow
addition of elements via `add`:

class Table {
val rows = new ArrayBuffer[Row]
def add(r: Row): Unit = rows += r
override def toString = rows.mkString("Table(", ", ", ")")
}

class Row {
val cells = new ArrayBuffer[Cell]
def add(c: Cell): Unit = cells += c
override def toString = cells.mkString("Row(", ", ", ")")
}

case class Cell(elem: String)

Then, the `table`, `row` and `cell` constructor methods can be defined
in terms of implicit function types to avoid the plumbing boilerplate
that would otherwise be necessary.

def table(init: implicit Table => Unit) = {
implicit val t = new Table
init
t
}

def row(init: implicit Row => Unit)(implicit t: Table) = {
implicit val r = new Row
init
t.add(r)
}

def cell(str: String)(implicit r: Row) =
r.add(new Cell(str))

With that setup, the table construction code above compiles and expands to:

table { implicit $t: Table =>
row { implicit $r: Row =>
cell("top left")($r)
cell("top right")($r)
}($t)
row { implicit $r: Row =>
cell("botttom left")($r)
cell("bottom right")($r)
}($t)
}
Loading