Skip to content

Fix #7788: Add new syntax for conditional given instances #7794

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 7 commits into from
Dec 18, 2019
Merged
Show file tree
Hide file tree
Changes from 5 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
94 changes: 74 additions & 20 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,7 @@ object Parsers {

/** Are the next tokens a prefix of a formal parameter or given type?
* @pre: current token is LPAREN
* TODO: Drop once syntax has stabilized
*/
def followingIsParamOrGivenType() =
val lookahead = in.LookaheadScanner()
Expand All @@ -915,6 +916,22 @@ object Parsers {
else false
else false

/** Are the next tokens a prefix of a formal parameter?
* @pre: current token is LPAREN
*/
def followingIsParam() =
val lookahead = in.LookaheadScanner()
lookahead.nextToken()
if startParamTokens.contains(lookahead.token) then true
else if lookahead.token == IDENTIFIER then
if lookahead.name == nme.inline then
lookahead.nextToken()
if lookahead.token == IDENTIFIER then
lookahead.nextToken()
lookahead.token == COLON
else false
else false

/** Are the next token the "GivenSig" part of a given definition,
* i.e. an identifier followed by type and value parameters, followed by `:`?
* @pre The current token is an identifier
Expand Down Expand Up @@ -2766,15 +2783,20 @@ object Parsers {
def typeParamClauseOpt(ownerKind: ParamOwner.Value): List[TypeDef] =
if (in.token == LBRACKET) typeParamClause(ownerKind) else Nil

/** OLD: GivenTypes ::= AnnotType {‘,’ AnnotType}
* NEW: GivenTypes ::= Type {‘,’ Type}
*/
def givenTypes(nparams: Int, ofClass: Boolean): List[ValDef] =
val tps = commaSeparated(typ)
def typesToGivenParams(tps: List[Tree], ofClass: Boolean, nparams: Int): List[ValDef] =
var counter = nparams
def nextIdx = { counter += 1; counter }
val paramFlags = if ofClass then Private | Local | ParamAccessor else Param
tps.map(makeSyntheticParameter(nextIdx, _, paramFlags | Synthetic | Given))
val tps1 = tps match
case Tuple(tps1) :: Nil => tps1
case _ => tps
tps1.map(makeSyntheticParameter(nextIdx, _, paramFlags | Synthetic | Given))

/** OLD: GivenTypes ::= AnnotType {‘,’ AnnotType}
* NEW: GivenTypes ::= Type {‘,’ Type}
*/
def givenTypes(ofClass: Boolean, nparams: Int): List[ValDef] =
typesToGivenParams(commaSeparated(typ), ofClass, nparams)

/** ClsParamClause ::= ‘(’ [‘erased’] ClsParams ‘)’
* GivenClsParamClause::= ‘(’ ‘given’ [‘erased’] (ClsParams | GivenTypes) ‘)’
Expand Down Expand Up @@ -2873,7 +2895,7 @@ object Parsers {
|| startParamTokens.contains(in.token)
|| isIdent && (in.name == nme.inline || in.lookaheadIn(BitSet(COLON)))
if isParams then commaSeparated(() => param())
else givenTypes(nparams, ofClass)
else givenTypes(ofClass, nparams)
checkVarArgsRules(clause)
clause
}
Expand Down Expand Up @@ -3384,12 +3406,13 @@ object Parsers {
syntaxError(i"extension clause can only define methods", stat.span)
}

/** GivenDef ::= [GivenSig (‘:’ | <:)] Type ‘=’ Expr
* | [GivenSig ‘:’] ConstrApps [[‘with’] TemplateBody]
/** GivenDef ::= [GivenSig (‘:’ | <:)] {FunArgTypes ‘=>’} AnnotType ‘=’ Expr
* | [GivenSig ‘:’] {FunArgTypes ‘=>’} ConstrApps [[‘with’] TemplateBody]
* | [id ‘:’] ExtParamClause {GivenParamClause} ‘extended’ ‘with’ ExtMethods
* GivenSig ::= [id] [DefTypeParamClause] {GivenParamClause}
* ExtParamClause ::= [DefTypeParamClause] DefParamClause
* ExtMethods ::= [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’
* TODO: cleanup once syntax has stabilized
*/
def givenDef(start: Offset, mods: Modifiers, instanceMod: Mod) = atSpan(start, nameStart) {
var mods1 = addMod(mods, instanceMod)
Expand All @@ -3414,13 +3437,18 @@ object Parsers {
templ.body.foreach(checkExtensionMethod(tparams, _))
ModuleDef(name, templ)
else
val hasLabel = !name.isEmpty && in.token == COLON
if hasLabel then in.nextToken()
var hasLabel = false
def skipColon() =
if !hasLabel && in.token == COLON then
hasLabel = true
in.nextToken()
if !name.isEmpty then skipColon()
val tparams = typeParamClauseOpt(ParamOwner.Def)
if !tparams.isEmpty then skipColon()
val paramsStart = in.offset
val vparamss =
var vparamss =
if in.token == LPAREN && followingIsParamOrGivenType()
then paramClauses()
then paramClauses() // todo: ONLY admit a single paramClause
else Nil
val isExtension = isIdent(nme.extended)
def checkAllGivens(vparamss: List[List[ValDef]], what: String) =
Expand All @@ -3440,19 +3468,45 @@ object Parsers {
stats.foreach(checkExtensionMethod(tparams, _))
ModuleDef(name, Template(makeConstructor(tparams, vparamss), Nil, Nil, self, stats))
else
checkAllGivens(vparamss, "parameter of given instance")
def makeGiven(params: List[ValDef]): List[ValDef] =
params.map(param => param.withMods(param.mods | Given))
def conditionalParents(): List[Tree] =
Copy link
Member

Choose a reason for hiding this comment

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

The rules still accept given [A]: (given showA: Show[A]) => Show[List[A]] e.g. :

trait Show[-A] with
  def show(a:A): String

given Show[String] = x => x
given Show[Int] = _.toString

given [A,B]: (given sA: Show[A], sB: Show[B]) => Show[(A,B)] = (a,b) => s"(${sA.show(a)}, ${sB.show(b)})"
given [A]: (given Show[A]) => Show[List[A]] = as => as.map(summon[Show[A]].show).mkString(", ")
given showOption[A]: (given Show[A]) => Show[Option[A]] = o => o.map(summon[Show[A]].show).fold("Nothing")(s => s"Some($s)")
given showEither[A,B]: (given sA: Show[A], sb: Show[B]) => Show[Either[A,B]] = _.fold(a => s"Left(${summon[Show[A]].show(a)})", b => s"Right(${summon[Show[B]].show(b)})")

@main def ShowDemo =
  println(summon[Show[(Int, String)]].show(0 -> "hello"))
  println(summon[Show[List[Int]]].show(List(1,2,3)))
  println(summon[Show[Option[Int]]].show(Some(25)))
  println(summon[Show[Either[Int, String]]].show(Left(-1)))
  println(summon[Show[Either[Int, String]]].show(Right("success message")))

Copy link
Member

Choose a reason for hiding this comment

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

So this syntax is still ok to be alongside the other?

accept(ARROW)
if in.token == LPAREN && followingIsParam() then
vparamss = vparamss :+ makeGiven(paramClause(vparamss.flatten.length))
conditionalParents()
else
val constrs = constrApps(commaOK = true, templateCanFollow = true)
if in.token == ARROW && constrs.forall(_.isType) then
vparamss = vparamss
:+ typesToGivenParams(constrs, ofClass = false, vparamss.flatten.length)
conditionalParents()
else constrs

val isConditional =
in.token == ARROW
&& vparamss.length == 1
Copy link
Member

Choose a reason for hiding this comment

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

there should probably be a neg test for curried param lists:

trait Show[-A] with
  def show(a:A): String

given Show[String] = x => x
given Show[Int] = _.toString

given [A,B]: (sA: Show[A])(sB: Show[B]) => Show[(A,B)] = (a,b) => s"(${sA.show(a)}, ${sB.show(b)})"
given [A,B,C]: (Show[A])(Show[B])(Show[C]) => Show[(A,B,C)] = (a,b,c) => s"(${summon[Show[A]].show(a)}, ${summon[Show[B]].show(b)}, ${summon[Show[C]].show(c)})"
given showEither[A,B]: (sA: Show[A])(sb: Show[B]) => Show[Either[A,B]] = _.fold(a => s"Left(${summon[Show[A]].show(a)})", b => s"Right(${summon[Show[B]].show(b)})")

@main def ShowDemo =
  println(summon[Show[(Int, String)]].show(0 -> "hello"))

&& (hasLabel || name.isEmpty && tparams.isEmpty)
if !isConditional then checkAllGivens(vparamss, "parameter of given instance")
val parents =
if hasLabel then
constrApps(commaOK = true, templateCanFollow = true)
else if in.token == SUBTYPE then
if in.token == SUBTYPE && !hasLabel then
if !mods.is(Inline) then
syntaxError("`<:` is only allowed for given with `inline` modifier")
in.nextToken()
TypeBoundsTree(EmptyTree, toplevelTyp()) :: Nil
TypeBoundsTree(EmptyTree, annotType()) :: Nil
else if isConditional then
vparamss = vparamss.map(makeGiven)
conditionalParents()
else
if !(name.isEmpty && tparams.isEmpty && vparamss.isEmpty) then
if !hasLabel && !(name.isEmpty && tparams.isEmpty && vparamss.isEmpty) then
accept(COLON)
constrApps(commaOK = true, templateCanFollow = true)
val constrs = constrApps(commaOK = true, templateCanFollow = true)
if in.token == ARROW && vparamss.isEmpty && constrs.forall(_.isType) then
vparamss = typesToGivenParams(constrs, ofClass = false, 0) :: Nil
conditionalParents()
else
constrs

if in.token == EQUALS && parents.length == 1 && parents.head.isType then
in.nextToken()
mods1 |= Final
Expand Down
6 changes: 4 additions & 2 deletions docs/docs/internals/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -384,8 +384,10 @@ ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses
ConstrMods ::= {Annotation} [AccessModifier]
ObjectDef ::= id [Template] ModuleDef(mods, name, template) // no constructor
EnumDef ::= id ClassConstr InheritClauses [‘with’] EnumBody EnumDef(mods, name, tparams, template)
GivenDef ::= [GivenSig (‘:’ | <:)] Type ‘=’ Expr
| [GivenSig ‘:’] ConstrApps [[‘with’] TemplateBody]
GivenDef ::= [GivenSig (‘:’ | <:)] {FunArgTypes ‘=>’}
AnnotType ‘=’ Expr
| [GivenSig ‘:’] {FunArgTypes ‘=>’}
ConstrApps [[‘with’] TemplateBody]
| [id ‘:’] ExtParamClause {GivenParamClause}
‘extended’ ‘with’ ExtMethods
GivenSig ::= [id] [DefTypeParamClause] {GivenParamClause}
Expand Down
12 changes: 7 additions & 5 deletions docs/docs/reference/contextual/delegates.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ given intOrd: Ord[Int] {
if (x < y) -1 else if (x > y) +1 else 0
}

given listOrd[T](given ord: Ord[T]): Ord[List[T]] {
given listOrd[T]: (ord: Ord[T]) => Ord[List[T]] {

def compare(xs: List[T], ys: List[T]): Int = (xs, ys) match {
case (Nil, Nil) => 0
Expand All @@ -33,16 +33,18 @@ given listOrd[T](given ord: Ord[T]): Ord[List[T]] {
This code defines a trait `Ord` with two given instances. `intOrd` defines
a given for the type `Ord[Int]` whereas `listOrd[T]` defines givens
for `Ord[List[T]]` for all types `T` that come with a given instance for `Ord[T]` themselves.
The `(given ord: Ord[T])` clause in `listOrd` defines an implicit parameter.
Given clauses are further explained in the [next section](./given-clauses.md).
The `(ord: Ord[T]) =>` clause in `listOrd` defines a condition: There must be a
given instance of type `Ord[T]` so that a given instance of type `List[Ord[T]]` can
be synthesized. Such conditions are expanded by the compiler to implicit
parameters, which are explained in the [next section](./given-clauses.md).

## Anonymous Given Instances

The name of a given instance can be left out. So the definitions
of the last section can also be expressed like this:
```scala
given Ord[Int] { ... }
given [T](given Ord[T]): Ord[List[T]] { ... }
given [T]: Ord[T] => Ord[List[T]] { ... }
```
If the name of a given is missing, the compiler will synthesize a name from
the implemented type(s).
Expand All @@ -61,7 +63,7 @@ returned for this and all subsequent accesses to `global`.
Alias givens can be anonymous, e.g.
```scala
given Position = enclosingTree.position
given (given outer: Context): Context = outer.withOwner(currentOwner)
given (outer: Context) => Context = outer.withOwner(currentOwner)
```
An alias given can have type parameters and given clauses just like any other given instance, but it can only implement a single type.

Expand Down
10 changes: 5 additions & 5 deletions docs/docs/reference/contextual/derivation.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ we need to implement a method `Eq.derived` on the companion object of `Eq` that
a `Mirror[T]`. Here is a possible implementation,

```scala
inline given derived[T](given m: Mirror.Of[T]): Eq[T] = {
inline given derived[T]: (m: Mirror.Of[T]) => Eq[T] = {
val elemInstances = summonAll[m.MirroredElemTypes] // (1)
inline m match { // (2)
case s: Mirror.SumOf[T] => eqSum(s, elemInstances)
Expand Down Expand Up @@ -281,7 +281,7 @@ object Eq {
}
}

inline given derived[T](given m: Mirror.Of[T]): Eq[T] = {
inline given derived[T]: (m: Mirror.Of[T]) => Eq[T] = {
val elemInstances = summonAll[m.MirroredElemTypes]
inline m match {
case s: Mirror.SumOf[T] => eqSum(s, elemInstances)
Expand Down Expand Up @@ -312,7 +312,7 @@ In this case the code that is generated by the inline expansion for the derived
following, after a little polishing,

```scala
given derived$Eq[T](given eqT: Eq[T]): Eq[Opt[T]] =
given derived$Eq[T]: (eqT: Eq[T]) => Eq[Opt[T]] =
eqSum(summon[Mirror[Opt[T]]],
List(
eqProduct(summon[Mirror[Sm[T]]], List(summon[Eq[T]]))
Expand All @@ -329,13 +329,13 @@ As a third example, using a higher level library such as shapeless the type clas
`derived` method as,

```scala
given eqSum[A](given inst: => K0.CoproductInstances[Eq, A]): Eq[A] {
given eqSum[A] (inst: => K0.CoproductInstances[Eq, A]) => Eq[A] {
def eqv(x: A, y: A): Boolean = inst.fold2(x, y)(false)(
[t] => (eqt: Eq[t], t0: t, t1: t) => eqt.eqv(t0, t1)
)
}

given eqProduct[A](given inst: K0.ProductInstances[Eq, A]): Eq[A] {
given eqProduct[A] (inst: K0.ProductInstances[Eq, A]) => Eq[A] {
def eqv(x: A, y: A): Boolean = inst.foldLeft2(x, y)(true: Boolean)(
[t] => (acc: Boolean, eqt: Eq[t], t0: t, t1: t) => Complete(!eqt.eqv(t0, t1))(false)(true)
)
Expand Down
3 changes: 0 additions & 3 deletions docs/docs/reference/contextual/extension-methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ layout: doc-page
title: "Extension Methods"
---

**Note** The syntax described in this section is currently under revision.
[Here is the new version which will be implemented in Dotty 0.20](./extension-methods-new.html).

Extension methods allow one to add methods to a type after the type is defined. Example:

```scala
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ trait Codec[T] {

given intCodec: Codec[Int] = ???

given optionCodec[T](given ev: => Codec[T]): Codec[Option[T]] {
given optionCodec[T]: (ev: => Codec[T]) => Codec[Option[T]] {
def write(xo: Option[T]) = xo match {
case Some(x) => ev.write(x)
case None =>
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/reference/contextual/relationship-implicits.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Given instances can be mapped to combinations of implicit objects, classes and i
```
2. Parameterized given instances are mapped to combinations of classes and implicit methods. E.g.,
```scala
given listOrd[T](given ord: Ord[T]): Ord[List[T]] { ... }
given listOrd[T]: (ord: Ord[T]) => Ord[List[T]] { ... }
```
maps to
```scala
Expand Down
2 changes: 1 addition & 1 deletion library/src-bootstrapped/scala/quoted/Liftable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ object Liftable {
else '{ Array(${Expr(array(0))}, ${Expr(array.toSeq.tail)}: _*) }
}

given iArrayIsLiftable[T: Type](given ltArray: Liftable[Array[T]]): Liftable[IArray[T]] {
given iArrayIsLiftable[T: Type]: (ltArray: Liftable[Array[T]]) => Liftable[IArray[T]] {
def toExpr(iarray: IArray[T]): (given QuoteContext) => Expr[IArray[T]] =
'{ ${ltArray.toExpr(iarray.asInstanceOf[Array[T]])}.asInstanceOf[IArray[T]] }
}
Expand Down
2 changes: 1 addition & 1 deletion tests/neg/i7248.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
object Test extends App {
given f[H](given h: H): H = h
given f[H]: (h: H) => H = h
summon[Int] // error
}
2 changes: 1 addition & 1 deletion tests/neg/i7249.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ trait F[H, T]


object Test extends App {
given f[H, T](given h: H, t: T): F[H, T] = ???
given f[H, T]: (h: H, t: T) => F[H, T] = ???
summon[F[Int, Unit]] // error
}
2 changes: 1 addition & 1 deletion tests/neg/i7459.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ object Eq {
}
}

inline given derived[T](given m: Mirror.Of[T]): Eq[T] = {
inline given derived[T]: (m: Mirror.Of[T]) => Eq[T] = {
val elemInstances = summonAll[m.MirroredElemTypes]
inline m match {
case s: Mirror.SumOf[T] => eqSum(s, elemInstances)
Expand Down
2 changes: 1 addition & 1 deletion tests/neg/multi-param-derives.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ object Test extends App {
trait Show[T]
object Show {
given Show[Int] {}
given [T](given st: Show[T]): Show[Tuple1[T]] {}
given [T]: (st: Show[T]) => Show[Tuple1[T]] {}
given t2[T, U](given st: Show[T], su: Show[U]) : Show[(T, U)] {}
given t3[T, U, V](given st: Show[T], su: Show[U], sv: Show[V]) : Show[(T, U, V)] {}

Expand Down
2 changes: 1 addition & 1 deletion tests/pos/combine.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ trait Semigroup[A] {
def (x: A) combine (y: A): A
}
given Semigroup[Int] = ???
given [A, B](given Semigroup[A], Semigroup[B]): Semigroup[(A, B)] = ???
given [A, B]: Semigroup[A], Semigroup[B] => Semigroup[(A, B)] = ???
object Test extends App {
((1, 1)) combine ((2, 2)) // doesn't compile
((1, 1): (Int, Int)) combine (2, 2) // compiles
Expand Down
2 changes: 1 addition & 1 deletion tests/pos/i6914.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ object test1 {
class ToExpr[T](given Liftable[T]) extends Conversion[T, Expr[T]] {
def apply(x: T): Expr[T] = ???
}
given toExpr[T](given Liftable[T]): ToExpr[T]
given toExpr[T]: Liftable[T] => ToExpr[T]

given Liftable[Int] = ???
given Liftable[String] = ???
Expand Down
2 changes: 1 addition & 1 deletion tests/pos/multiversal.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
object Test {
import scala.Eql

given [X, Y](given Eql[X, Y]): Eql[List[X], List[Y]] = Eql.derived
given [X, Y]: Eql[X, Y] => Eql[List[X], List[Y]] = Eql.derived

val b: Byte = 1
val c: Char = 2
Expand Down
6 changes: 3 additions & 3 deletions tests/pos/reference/delegates.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ object Instances extends Common with
def (x: Int) compareTo (y: Int) =
if (x < y) -1 else if (x > y) +1 else 0

given listOrd[T](given Ord[T]): Ord[List[T]] with
given listOrd[T]: Ord[T] => Ord[List[T]] with
def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match
case (Nil, Nil) => 0
case (Nil, _) => -1
Expand Down Expand Up @@ -114,7 +114,7 @@ object Instances extends Common with
println(summon[D[Int]])
}
locally {
given (given Context): D[Int]
given Context => D[Int]
println(summon[D[Int]])
}
end C
Expand Down Expand Up @@ -161,7 +161,7 @@ object AnonymousInstances extends Common with
given [T](xs: List[T]) extended with
def second = xs.tail.head

given [From, To](given c: Convertible[From, To]) : Convertible[List[From], List[To]] with
given [From, To]: (c: Convertible[From, To]) => Convertible[List[From], List[To]] with
def (x: List[From]) convert: List[To] = x.map(c.convert)

given Monoid[String] with
Expand Down
19 changes: 19 additions & 0 deletions tests/pos/tupled-given.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class A
class B
class C
class D
class E
trait F

given A()
given B()
given (A, B) => C() // this one is equivalent to ...
given A, B => D(), F // ... the one without the parens
given ((A, B)) => E() // to demand a tuple, add an extra pair of parens

@main def Test =
summon[C]
summon[D]
summon[F]
given (A, B) = (summon[A], summon[B])
summon[E]
Loading