Skip to content

Commit d1579e6

Browse files
committed
Fix #7788: Add new syntax for conditional given instances
1 parent b1eb3a8 commit d1579e6

23 files changed

+107
-56
lines changed

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

Lines changed: 68 additions & 19 deletions
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,16 +2783,18 @@ 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
27772790
tps.map(makeSyntheticParameter(nextIdx, _, paramFlags | Synthetic | Given))
27782791

2792+
/** OLD: GivenTypes ::= AnnotType {‘,’ AnnotType}
2793+
* NEW: GivenTypes ::= Type {‘,’ Type}
2794+
*/
2795+
def givenTypes(ofClass: Boolean, nparams: Int): List[ValDef] =
2796+
typesToGivenParams(commaSeparated(typ), ofClass, nparams)
2797+
27792798
/** ClsParamClause ::= ‘(’ [‘erased’] ClsParams ‘)’
27802799
* GivenClsParamClause::= ‘(’ ‘given’ [‘erased’] (ClsParams | GivenTypes) ‘)’
27812800
* ClsParams ::= ClsParam {‘,’ ClsParam}
@@ -2873,7 +2892,7 @@ object Parsers {
28732892
|| startParamTokens.contains(in.token)
28742893
|| isIdent && (in.name == nme.inline || in.lookaheadIn(BitSet(COLON)))
28752894
if isParams then commaSeparated(() => param())
2876-
else givenTypes(nparams, ofClass)
2895+
else givenTypes(ofClass, nparams)
28772896
checkVarArgsRules(clause)
28782897
clause
28792898
}
@@ -3384,12 +3403,13 @@ object Parsers {
33843403
syntaxError(i"extension clause can only define methods", stat.span)
33853404
}
33863405

3387-
/** GivenDef ::= [GivenSig (‘:’ | <:)] Type ‘=’ Expr
3388-
* | [GivenSig ‘:’] ConstrApps [[‘with’] TemplateBody]
3406+
/** GivenDef ::= [GivenSig (‘:’ | <:)] {FunArgTypes ‘=>’} AnnotType ‘=’ Expr
3407+
* | [GivenSig ‘:’] {FunArgTypes ‘=>’} ConstrApps [[‘with’] TemplateBody]
33893408
* | [id ‘:’] ExtParamClause {GivenParamClause} ‘extended’ ‘with’ ExtMethods
33903409
* GivenSig ::= [id] [DefTypeParamClause] {GivenParamClause}
33913410
* ExtParamClause ::= [DefTypeParamClause] DefParamClause
33923411
* ExtMethods ::= [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’
3412+
* TODO: cleanup once syntax has stabilized
33933413
*/
33943414
def givenDef(start: Offset, mods: Modifiers, instanceMod: Mod) = atSpan(start, nameStart) {
33953415
var mods1 = addMod(mods, instanceMod)
@@ -3414,13 +3434,18 @@ object Parsers {
34143434
templ.body.foreach(checkExtensionMethod(tparams, _))
34153435
ModuleDef(name, templ)
34163436
else
3417-
val hasLabel = !name.isEmpty && in.token == COLON
3418-
if hasLabel then in.nextToken()
3437+
var hasLabel = false
3438+
def skipColon() =
3439+
if !hasLabel && in.token == COLON then
3440+
hasLabel = true
3441+
in.nextToken()
3442+
if !name.isEmpty then skipColon()
34193443
val tparams = typeParamClauseOpt(ParamOwner.Def)
3444+
if !tparams.isEmpty then skipColon()
34203445
val paramsStart = in.offset
3421-
val vparamss =
3446+
var vparamss =
34223447
if in.token == LPAREN && followingIsParamOrGivenType()
3423-
then paramClauses()
3448+
then paramClauses() // todo: ONLY admit a single paramClause
34243449
else Nil
34253450
val isExtension = isIdent(nme.extended)
34263451
def checkAllGivens(vparamss: List[List[ValDef]], what: String) =
@@ -3440,19 +3465,43 @@ object Parsers {
34403465
stats.foreach(checkExtensionMethod(tparams, _))
34413466
ModuleDef(name, Template(makeConstructor(tparams, vparamss), Nil, Nil, self, stats))
34423467
else
3443-
checkAllGivens(vparamss, "parameter of given instance")
3468+
def conditionalParents(): List[Tree] =
3469+
accept(ARROW)
3470+
if in.token == LPAREN && followingIsParam() then
3471+
vparamss = vparamss :+ paramClause(vparamss.flatten.length)
3472+
conditionalParents()
3473+
else
3474+
val constrs = constrApps(commaOK = true, templateCanFollow = true)
3475+
if in.token == ARROW && constrs.forall(_.isType) then
3476+
vparamss = vparamss
3477+
:+ typesToGivenParams(constrs, ofClass = false, vparamss.flatten.length)
3478+
conditionalParents()
3479+
else constrs
3480+
3481+
val isConditional =
3482+
in.token == ARROW
3483+
&& vparamss.length == 1
3484+
&& (hasLabel || name.isEmpty && tparams.isEmpty)
3485+
if !isConditional then checkAllGivens(vparamss, "parameter of given instance")
34443486
val parents =
3445-
if hasLabel then
3446-
constrApps(commaOK = true, templateCanFollow = true)
3447-
else if in.token == SUBTYPE then
3487+
if in.token == SUBTYPE && !hasLabel then
34483488
if !mods.is(Inline) then
34493489
syntaxError("`<:` is only allowed for given with `inline` modifier")
34503490
in.nextToken()
3451-
TypeBoundsTree(EmptyTree, toplevelTyp()) :: Nil
3491+
TypeBoundsTree(EmptyTree, annotType()) :: Nil
3492+
else if isConditional then
3493+
vparamss = vparamss.head.map(param => param.withMods(param.mods | Given)) :: Nil
3494+
conditionalParents()
34523495
else
3453-
if !(name.isEmpty && tparams.isEmpty && vparamss.isEmpty) then
3496+
if !hasLabel && !(name.isEmpty && tparams.isEmpty && vparamss.isEmpty) then
34543497
accept(COLON)
3455-
constrApps(commaOK = true, templateCanFollow = true)
3498+
val constrs = constrApps(commaOK = true, templateCanFollow = true)
3499+
if in.token == ARROW && vparamss.isEmpty && constrs.forall(_.isType) then
3500+
vparamss = typesToGivenParams(constrs, ofClass = false, 0) :: Nil
3501+
conditionalParents()
3502+
else
3503+
constrs
3504+
34563505
if in.token == EQUALS && parents.length == 1 && parents.head.isType then
34573506
in.nextToken()
34583507
mods1 |= Final

docs/docs/internals/syntax.md

Lines changed: 4 additions & 2 deletions
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/derivation.md

Lines changed: 2 additions & 2 deletions
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)

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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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/i6914.scala

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 3 additions & 3 deletions
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/run/extmethods2.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ object Test extends App {
22

33
class TC
44

5-
given stringListOps(given TC): Object {
5+
given stringListOps: TC => Object {
66
type T = List[String]
77
def (x: T) foo (y: T) = (x ++ y, summon[TC])
88
def (x: T) bar (y: Int) = (x(0)(y), summon[TC])

tests/run/implicit-alias.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ object Test extends App {
5353

5454
locally {
5555
println("with given")
56-
given t(given TC1): TC = new TC
56+
given t: TC1 => TC = new TC
5757
summon[TC]
5858
summon[TC]
5959
}

tests/run/implicit-specifity.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ object Show {
99
class Generic
1010
object Generic {
1111
given gen : Generic = new Generic
12-
given showGen[T](given Generic): Show[T] = new Show[T](2)
12+
given showGen[T]: Generic => Show[T] = new Show[T](2)
1313
}
1414

1515
class Generic2
@@ -27,11 +27,11 @@ object Contextual {
2727

2828
given ctx: Context
2929

30-
given showGen[T](given Generic): Show[T] = new Show[T](2)
30+
given showGen[T]: Generic => Show[T] = new Show[T](2)
3131

32-
given showGen[T](given Generic, Context): Show[T] = new Show[T](3)
32+
given showGen[T]: Generic, Context => Show[T] = new Show[T](3)
3333

34-
given showGen[T](given SubGen): Show[T] = new Show[T](4)
34+
given showGen[T]: SubGen => Show[T] = new Show[T](4)
3535
}
3636

3737
object Test extends App {

tests/run/implied-divergence.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ given e : E(null)
66

77
object Test extends App {
88

9-
given f(given e: E): E(e)
9+
given f: (e: E) => E(e)
1010

1111
assert(summon[E].toString == "E(E(null))")
1212

tests/run/implied-for.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class Monoid[T]
3131
object Instances {
3232
given intOrd : Ordering[Int]
3333

34-
given listOrd[T](given Ordering[T]): Ordering[List[T]]
34+
given listOrd[T]: Ordering[T] => Ordering[List[T]]
3535
given ec : ExecutionContext
3636
given im : Monoid[Int]
3737
}

tests/run/implied-priority.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class LowPriorityImplicits {
1515
}
1616

1717
object NormalImplicits extends LowPriorityImplicits {
18-
given t2[T](given Arg[T]): E[T]("norm")
18+
given t2[T]: Arg[T] => E[T]("norm")
1919
}
2020

2121
def test1 = {
@@ -38,8 +38,8 @@ object Priority {
3838
}
3939

4040
object Impl2 {
41-
given t1[T](given Priority.Low): E[T]("low")
42-
given t2[T](given Priority.High)(given Arg[T]): E[T]("norm")
41+
given t1[T]: Priority.Low => E[T]("low")
42+
given t2[T]: Priority.High => Arg[T] => E[T]("norm")
4343
}
4444

4545
def test2 = {
@@ -103,7 +103,7 @@ def test3 = {
103103
object Impl4 {
104104
given t1 : E[String]("string")
105105

106-
given t2[T](given Arg[T]): E[T]("generic")
106+
given t2[T]: Arg[T] => E[T]("generic")
107107
}
108108

109109
object fallback4 {
@@ -134,7 +134,7 @@ object HigherPriority {
134134
}
135135

136136
object fallback5 {
137-
given [T](given ev: E[T] = new E[T]("fallback")): (E[T] & HigherPriority.Type) = HigherPriority.inject(ev)
137+
given [T]: (ev: E[T] = new E[T]("fallback")) => (E[T] & HigherPriority.Type) = HigherPriority.inject(ev)
138138
}
139139

140140
def test5 = {

tests/run/implied-specifity-2.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ object Foo {
2121
}
2222
class Bar[T]
2323
object Bar {
24-
given foobar[T](given Medium): Foo[Bar[T]](3)
25-
given foobarbaz(given Medium): Foo[Bar[Baz]](4)
24+
given foobar[T]: Medium => Foo[Bar[T]](3)
25+
given foobarbaz: Medium => Foo[Bar[Baz]](4)
2626
}
2727
class Baz
2828
object Baz {
29-
given baz(given High): Foo[Bar[Baz]](5)
29+
given baz: High => Foo[Bar[Baz]](5)
3030
}
3131

3232
class Arg

0 commit comments

Comments
 (0)