Skip to content

Commit 2640412

Browse files
Merge pull request #7794 from dotty-staging/fix-#7788
Fix #7788: Add new syntax for conditional given instances
2 parents e036f46 + 93258da commit 2640412

30 files changed

+163
-72
lines changed

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

+74-20
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,7 @@ object Parsers {
901901

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

919+
/** Are the next tokens a prefix of a formal parameter?
920+
* @pre: current token is LPAREN
921+
*/
922+
def followingIsParam() =
923+
val lookahead = in.LookaheadScanner()
924+
lookahead.nextToken()
925+
if startParamTokens.contains(lookahead.token) then true
926+
else if lookahead.token == IDENTIFIER then
927+
if lookahead.name == nme.inline then
928+
lookahead.nextToken()
929+
if lookahead.token == IDENTIFIER then
930+
lookahead.nextToken()
931+
lookahead.token == COLON
932+
else false
933+
else false
934+
918935
/** Are the next token the "GivenSig" part of a given definition,
919936
* i.e. an identifier followed by type and value parameters, followed by `:`?
920937
* @pre The current token is an identifier
@@ -2766,15 +2783,20 @@ object Parsers {
27662783
def typeParamClauseOpt(ownerKind: ParamOwner.Value): List[TypeDef] =
27672784
if (in.token == LBRACKET) typeParamClause(ownerKind) else Nil
27682785

2769-
/** OLD: GivenTypes ::= AnnotType {‘,’ AnnotType}
2770-
* NEW: GivenTypes ::= Type {‘,’ Type}
2771-
*/
2772-
def givenTypes(nparams: Int, ofClass: Boolean): List[ValDef] =
2773-
val tps = commaSeparated(typ)
2786+
def typesToGivenParams(tps: List[Tree], ofClass: Boolean, nparams: Int): List[ValDef] =
27742787
var counter = nparams
27752788
def nextIdx = { counter += 1; counter }
27762789
val paramFlags = if ofClass then Private | Local | ParamAccessor else Param
2777-
tps.map(makeSyntheticParameter(nextIdx, _, paramFlags | Synthetic | Given))
2790+
val tps1 = tps match
2791+
case Tuple(tps1) :: Nil => tps1
2792+
case _ => tps
2793+
tps1.map(makeSyntheticParameter(nextIdx, _, paramFlags | Synthetic | Given))
2794+
2795+
/** OLD: GivenTypes ::= AnnotType {‘,’ AnnotType}
2796+
* NEW: GivenTypes ::= Type {‘,’ Type}
2797+
*/
2798+
def givenTypes(ofClass: Boolean, nparams: Int): List[ValDef] =
2799+
typesToGivenParams(commaSeparated(typ), ofClass, nparams)
27782800

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

3387-
/** GivenDef ::= [GivenSig (‘:’ | <:)] Type ‘=’ Expr
3388-
* | [GivenSig ‘:’] ConstrApps [[‘with’] TemplateBody]
3409+
/** GivenDef ::= [GivenSig (‘:’ | <:)] {FunArgTypes ‘=>’} AnnotType ‘=’ Expr
3410+
* | [GivenSig ‘:’] {FunArgTypes ‘=>’} ConstrApps [[‘with’] TemplateBody]
33893411
* | [id ‘:’] ExtParamClause {GivenParamClause} ‘extended’ ‘with’ ExtMethods
33903412
* GivenSig ::= [id] [DefTypeParamClause] {GivenParamClause}
33913413
* ExtParamClause ::= [DefTypeParamClause] DefParamClause
33923414
* ExtMethods ::= [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’
3415+
* TODO: cleanup once syntax has stabilized
33933416
*/
33943417
def givenDef(start: Offset, mods: Modifiers, instanceMod: Mod) = atSpan(start, nameStart) {
33953418
var mods1 = addMod(mods, instanceMod)
@@ -3414,13 +3437,18 @@ object Parsers {
34143437
templ.body.foreach(checkExtensionMethod(tparams, _))
34153438
ModuleDef(name, templ)
34163439
else
3417-
val hasLabel = !name.isEmpty && in.token == COLON
3418-
if hasLabel then in.nextToken()
3440+
var hasLabel = false
3441+
def skipColon() =
3442+
if !hasLabel && in.token == COLON then
3443+
hasLabel = true
3444+
in.nextToken()
3445+
if !name.isEmpty then skipColon()
34193446
val tparams = typeParamClauseOpt(ParamOwner.Def)
3447+
if !tparams.isEmpty then skipColon()
34203448
val paramsStart = in.offset
3421-
val vparamss =
3449+
var vparamss =
34223450
if in.token == LPAREN && followingIsParamOrGivenType()
3423-
then paramClauses()
3451+
then paramClauses() // todo: ONLY admit a single paramClause
34243452
else Nil
34253453
val isExtension = isIdent(nme.extended)
34263454
def checkAllGivens(vparamss: List[List[ValDef]], what: String) =
@@ -3440,19 +3468,45 @@ object Parsers {
34403468
stats.foreach(checkExtensionMethod(tparams, _))
34413469
ModuleDef(name, Template(makeConstructor(tparams, vparamss), Nil, Nil, self, stats))
34423470
else
3443-
checkAllGivens(vparamss, "parameter of given instance")
3471+
def makeGiven(params: List[ValDef]): List[ValDef] =
3472+
params.map(param => param.withMods(param.mods | Given))
3473+
def conditionalParents(): List[Tree] =
3474+
accept(ARROW)
3475+
if in.token == LPAREN && followingIsParam() then
3476+
vparamss = vparamss :+ makeGiven(paramClause(vparamss.flatten.length))
3477+
conditionalParents()
3478+
else
3479+
val constrs = constrApps(commaOK = true, templateCanFollow = true)
3480+
if in.token == ARROW && constrs.forall(_.isType) then
3481+
vparamss = vparamss
3482+
:+ typesToGivenParams(constrs, ofClass = false, vparamss.flatten.length)
3483+
conditionalParents()
3484+
else constrs
3485+
3486+
val isConditional =
3487+
in.token == ARROW
3488+
&& vparamss.length == 1
3489+
&& (hasLabel || name.isEmpty && tparams.isEmpty)
3490+
if !isConditional then checkAllGivens(vparamss, "parameter of given instance")
34443491
val parents =
3445-
if hasLabel then
3446-
constrApps(commaOK = true, templateCanFollow = true)
3447-
else if in.token == SUBTYPE then
3492+
if in.token == SUBTYPE && !hasLabel then
34483493
if !mods.is(Inline) then
34493494
syntaxError("`<:` is only allowed for given with `inline` modifier")
34503495
in.nextToken()
3451-
TypeBoundsTree(EmptyTree, toplevelTyp()) :: Nil
3496+
TypeBoundsTree(EmptyTree, annotType()) :: Nil
3497+
else if isConditional then
3498+
vparamss = vparamss.map(makeGiven)
3499+
conditionalParents()
34523500
else
3453-
if !(name.isEmpty && tparams.isEmpty && vparamss.isEmpty) then
3501+
if !hasLabel && !(name.isEmpty && tparams.isEmpty && vparamss.isEmpty) then
34543502
accept(COLON)
3455-
constrApps(commaOK = true, templateCanFollow = true)
3503+
val constrs = constrApps(commaOK = true, templateCanFollow = true)
3504+
if in.token == ARROW && vparamss.isEmpty && constrs.forall(_.isType) then
3505+
vparamss = typesToGivenParams(constrs, ofClass = false, 0) :: Nil
3506+
conditionalParents()
3507+
else
3508+
constrs
3509+
34563510
if in.token == EQUALS && parents.length == 1 && parents.head.isType then
34573511
in.nextToken()
34583512
mods1 |= Final

docs/docs/internals/syntax.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -384,8 +384,10 @@ ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses
384384
ConstrMods ::= {Annotation} [AccessModifier]
385385
ObjectDef ::= id [Template] ModuleDef(mods, name, template) // no constructor
386386
EnumDef ::= id ClassConstr InheritClauses [‘with’] EnumBody EnumDef(mods, name, tparams, template)
387-
GivenDef ::= [GivenSig (‘:’ | <:)] Type ‘=’ Expr
388-
| [GivenSig ‘:’] ConstrApps [[‘with’] TemplateBody]
387+
GivenDef ::= [GivenSig (‘:’ | <:)] {FunArgTypes ‘=>’}
388+
AnnotType ‘=’ Expr
389+
| [GivenSig ‘:’] {FunArgTypes ‘=>’}
390+
ConstrApps [[‘with’] TemplateBody]
389391
| [id ‘:’] ExtParamClause {GivenParamClause}
390392
‘extended’ ‘with’ ExtMethods
391393
GivenSig ::= [id] [DefTypeParamClause] {GivenParamClause}

docs/docs/reference/contextual/delegates.md

+7-5
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ given intOrd: Ord[Int] {
1818
if (x < y) -1 else if (x > y) +1 else 0
1919
}
2020

21-
given listOrd[T](given ord: Ord[T]): Ord[List[T]] {
21+
given listOrd[T]: (ord: Ord[T]) => Ord[List[T]] {
2222

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

3941
## Anonymous Given Instances
4042

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

docs/docs/reference/contextual/derivation.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ we need to implement a method `Eq.derived` on the companion object of `Eq` that
175175
a `Mirror[T]`. Here is a possible implementation,
176176

177177
```scala
178-
inline given derived[T](given m: Mirror.Of[T]): Eq[T] = {
178+
inline given derived[T]: (m: Mirror.Of[T]) => Eq[T] = {
179179
val elemInstances = summonAll[m.MirroredElemTypes] // (1)
180180
inline m match { // (2)
181181
case s: Mirror.SumOf[T] => eqSum(s, elemInstances)
@@ -281,7 +281,7 @@ object Eq {
281281
}
282282
}
283283

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

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

331331
```scala
332-
given eqSum[A](given inst: => K0.CoproductInstances[Eq, A]): Eq[A] {
332+
given eqSum[A] (inst: => K0.CoproductInstances[Eq, A]) => Eq[A] {
333333
def eqv(x: A, y: A): Boolean = inst.fold2(x, y)(false)(
334334
[t] => (eqt: Eq[t], t0: t, t1: t) => eqt.eqv(t0, t1)
335335
)
336336
}
337337

338-
given eqProduct[A](given inst: K0.ProductInstances[Eq, A]): Eq[A] {
338+
given eqProduct[A] (inst: K0.ProductInstances[Eq, A]) => Eq[A] {
339339
def eqv(x: A, y: A): Boolean = inst.foldLeft2(x, y)(true: Boolean)(
340340
[t] => (acc: Boolean, eqt: Eq[t], t0: t, t1: t) => Complete(!eqt.eqv(t0, t1))(false)(true)
341341
)

docs/docs/reference/contextual/extension-methods.md

-3
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@ layout: doc-page
33
title: "Extension Methods"
44
---
55

6-
**Note** The syntax described in this section is currently under revision.
7-
[Here is the new version which will be implemented in Dotty 0.20](./extension-methods-new.html).
8-
96
Extension methods allow one to add methods to a type after the type is defined. Example:
107

118
```scala

docs/docs/reference/contextual/implicit-by-name-parameters.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ trait Codec[T] {
1212

1313
given intCodec: Codec[Int] = ???
1414

15-
given optionCodec[T](given ev: => Codec[T]): Codec[Option[T]] {
15+
given optionCodec[T]: (ev: => Codec[T]) => Codec[Option[T]] {
1616
def write(xo: Option[T]) = xo match {
1717
case Some(x) => ev.write(x)
1818
case None =>

docs/docs/reference/contextual/relationship-implicits.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Given instances can be mapped to combinations of implicit objects, classes and i
2121
```
2222
2. Parameterized given instances are mapped to combinations of classes and implicit methods. E.g.,
2323
```scala
24-
given listOrd[T](given ord: Ord[T]): Ord[List[T]] { ... }
24+
given listOrd[T]: (ord: Ord[T]) => Ord[List[T]] { ... }
2525
```
2626
maps to
2727
```scala

library/src-bootstrapped/scala/quoted/Liftable.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ object Liftable {
103103
else '{ Array(${Expr(array(0))}, ${Expr(array.toSeq.tail)}: _*) }
104104
}
105105

106-
given iArrayIsLiftable[T: Type](given ltArray: Liftable[Array[T]]): Liftable[IArray[T]] {
106+
given iArrayIsLiftable[T: Type]: (ltArray: Liftable[Array[T]]) => Liftable[IArray[T]] {
107107
def toExpr(iarray: IArray[T]): (given QuoteContext) => Expr[IArray[T]] =
108108
'{ ${ltArray.toExpr(iarray.asInstanceOf[Array[T]])}.asInstanceOf[IArray[T]] }
109109
}

tests/neg/i7248.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
object Test extends App {
2-
given f[H](given h: H): H = h
2+
given f[H]: (h: H) => H = h
33
summon[Int] // error
44
}

tests/neg/i7249.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ trait F[H, T]
44

55

66
object Test extends App {
7-
given f[H, T](given h: H, t: T): F[H, T] = ???
7+
given f[H, T]: (h: H, t: T) => F[H, T] = ???
88
summon[F[Int, Unit]] // error
99
}

tests/neg/i7459.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ object Eq {
4747
}
4848
}
4949

50-
inline given derived[T](given m: Mirror.Of[T]): Eq[T] = {
50+
inline given derived[T]: (m: Mirror.Of[T]) => Eq[T] = {
5151
val elemInstances = summonAll[m.MirroredElemTypes]
5252
inline m match {
5353
case s: Mirror.SumOf[T] => eqSum(s, elemInstances)

tests/neg/multi-param-derives.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ object Test extends App {
55
trait Show[T]
66
object Show {
77
given Show[Int] {}
8-
given [T](given st: Show[T]): Show[Tuple1[T]] {}
8+
given [T]: (st: Show[T]) => Show[Tuple1[T]] {}
99
given t2[T, U](given st: Show[T], su: Show[U]) : Show[(T, U)] {}
1010
given t3[T, U, V](given st: Show[T], su: Show[U], sv: Show[V]) : Show[(T, U, V)] {}
1111

tests/pos/combine.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ trait Semigroup[A] {
22
def (x: A) combine (y: A): A
33
}
44
given Semigroup[Int] = ???
5-
given [A, B](given Semigroup[A], Semigroup[B]): Semigroup[(A, B)] = ???
5+
given [A, B]: Semigroup[A], Semigroup[B] => Semigroup[(A, B)] = ???
66
object Test extends App {
77
((1, 1)) combine ((2, 2)) // doesn't compile
88
((1, 1): (Int, Int)) combine (2, 2) // compiles

tests/pos/i6914.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ object test1 {
55
class ToExpr[T](given Liftable[T]) extends Conversion[T, Expr[T]] {
66
def apply(x: T): Expr[T] = ???
77
}
8-
given toExpr[T](given Liftable[T]): ToExpr[T]
8+
given toExpr[T]: Liftable[T] => ToExpr[T]
99

1010
given Liftable[Int] = ???
1111
given Liftable[String] = ???

tests/pos/multiversal.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
object Test {
22
import scala.Eql
33

4-
given [X, Y](given Eql[X, Y]): Eql[List[X], List[Y]] = Eql.derived
4+
given [X, Y]: Eql[X, Y] => Eql[List[X], List[Y]] = Eql.derived
55

66
val b: Byte = 1
77
val c: Char = 2

tests/pos/reference/delegates.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ object Instances extends Common with
2929
def (x: Int) compareTo (y: Int) =
3030
if (x < y) -1 else if (x > y) +1 else 0
3131

32-
given listOrd[T](given Ord[T]): Ord[List[T]] with
32+
given listOrd[T]: Ord[T] => Ord[List[T]] with
3333
def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match
3434
case (Nil, Nil) => 0
3535
case (Nil, _) => -1
@@ -114,7 +114,7 @@ object Instances extends Common with
114114
println(summon[D[Int]])
115115
}
116116
locally {
117-
given (given Context): D[Int]
117+
given Context => D[Int]
118118
println(summon[D[Int]])
119119
}
120120
end C
@@ -161,7 +161,7 @@ object AnonymousInstances extends Common with
161161
given [T](xs: List[T]) extended with
162162
def second = xs.tail.head
163163

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

167167
given Monoid[String] with

tests/pos/tupled-given.scala

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
class A
2+
class B
3+
class C
4+
class D
5+
class E
6+
trait F
7+
8+
given A()
9+
given B()
10+
given (A, B) => C() // this one is equivalent to ...
11+
given A, B => D(), F // ... the one without the parens
12+
given ((A, B)) => E() // to demand a tuple, add an extra pair of parens
13+
14+
@main def Test =
15+
summon[C]
16+
summon[D]
17+
summon[F]
18+
given (A, B) = (summon[A], summon[B])
19+
summon[E]

0 commit comments

Comments
 (0)