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] = ???

0 commit comments

Comments
 (0)