Skip to content

Commit 161c583

Browse files
committed
Revised given syntax
Update given syntax to latest discussed variant in the SIP tests/pos/given-syntax.scala shows a semi-systematic list of possible syntax forms.
1 parent a212ff0 commit 161c583

15 files changed

+300
-80
lines changed

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

Lines changed: 112 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -972,18 +972,16 @@ object Parsers {
972972
followedByToken(LARROW) // `<-` comes before possible statement starts
973973
}
974974

975-
/** Are the next token the "GivenSig" part of a given definition,
976-
* i.e. an identifier followed by type and value parameters, followed by `:`?
975+
/** Are the next tokens a valid continuation of a named given def?
976+
* i.e. an identifier, possibly followed by type and value parameters, followed by `:`?
977977
* @pre The current token is an identifier
978978
*/
979-
def followingIsOldStyleGivenSig() =
979+
def followingIsGivenDefWithColon() =
980980
val lookahead = in.LookaheadScanner()
981981
if lookahead.isIdent then
982982
lookahead.nextToken()
983-
var paramsSeen = false
984983
def skipParams(): Unit =
985984
if lookahead.token == LPAREN || lookahead.token == LBRACKET then
986-
paramsSeen = true
987985
lookahead.skipParens()
988986
skipParams()
989987
else if lookahead.isNewLine then
@@ -1002,6 +1000,11 @@ object Parsers {
10021000
}
10031001
}
10041002

1003+
def followingIsArrow() =
1004+
val lookahead = in.LookaheadScanner()
1005+
lookahead.skipParens()
1006+
lookahead.token == ARROW
1007+
10051008
def followingIsExtension() =
10061009
val next = in.lookahead.token
10071010
next == LBRACKET || next == LPAREN
@@ -3432,7 +3435,11 @@ object Parsers {
34323435
/** ContextTypes ::= FunArgType {‘,’ FunArgType}
34333436
*/
34343437
def contextTypes(paramOwner: ParamOwner, numLeadParams: Int, impliedMods: Modifiers): List[ValDef] =
3435-
val tps = commaSeparated(() => paramTypeOf(() => toplevelTyp()))
3438+
typesToParams(
3439+
commaSeparated(() => paramTypeOf(() => toplevelTyp())),
3440+
paramOwner, numLeadParams, impliedMods)
3441+
3442+
def typesToParams(tps: List[Tree], paramOwner: ParamOwner, numLeadParams: Int, impliedMods: Modifiers): List[ValDef] =
34363443
var counter = numLeadParams
34373444
def nextIdx = { counter += 1; counter }
34383445
val paramFlags = if paramOwner.isClass then LocalParamAccessor else Param
@@ -3459,18 +3466,20 @@ object Parsers {
34593466
def termParamClause(
34603467
paramOwner: ParamOwner,
34613468
numLeadParams: Int, // number of parameters preceding this clause
3462-
firstClause: Boolean = false // clause is the first in regular list of clauses
3469+
firstClause: Boolean = false, // clause is the first in regular list of clauses
3470+
initialMods: Modifiers = EmptyModifiers
34633471
): List[ValDef] = {
3464-
var impliedMods: Modifiers = EmptyModifiers
3472+
var impliedMods: Modifiers = initialMods
34653473

34663474
def addParamMod(mod: () => Mod) = impliedMods = addMod(impliedMods, atSpan(in.skipToken()) { mod() })
34673475

34683476
def paramMods() =
34693477
if in.token == IMPLICIT then
34703478
addParamMod(() => Mod.Implicit())
3471-
else
3472-
if isIdent(nme.using) then
3473-
addParamMod(() => Mod.Given())
3479+
else if isIdent(nme.using) then
3480+
if initialMods.is(Given) then
3481+
syntaxError(em"`using` is already implied here, should not be given explicitly", in.offset)
3482+
addParamMod(() => Mod.Given())
34743483

34753484
def param(): ValDef = {
34763485
val start = in.offset
@@ -4135,18 +4144,67 @@ object Parsers {
41354144
* OldGivenSig ::= [id] [DefTypeParamClause] {UsingParamClauses} ‘:’
41364145
* StructuralInstance ::= ConstrApp {‘with’ ConstrApp} [‘with’ WithTemplateBody]
41374146
*
4138-
* NewGivenDef ::= [GivenConditional '=>'] NewGivenSig
4139-
* GivenConditional ::= [DefTypeParamClause | UsingParamClause] {UsingParamClause}
4140-
* NewGivenSig ::= GivenType ['as' id] ([‘=’ Expr] | TemplateBody)
4141-
* | ConstrApps ['as' id] TemplateBody
4142-
*
4147+
* NewGivenDef ::= [id ':'] GivenSig
4148+
* GivenSig ::= GivenImpl
4149+
* | '(' ')' '=>' GivenImpl
4150+
* | GivenConditional '=>' GivenSig
4151+
* GivenImpl ::= GivenType ([‘=’ Expr] | TemplateBody)
4152+
* | ConstrApps TemplateBody
4153+
* GivenConditional ::= DefTypeParamClause
4154+
* | DefTermParamClause
4155+
* | '(' FunArgTypes ')'
4156+
* | GivenType
41434157
* GivenType ::= AnnotType1 {id [nl] AnnotType1}
41444158
*/
41454159
def givenDef(start: Offset, mods: Modifiers, givenMod: Mod) = atSpan(start, nameStart) {
41464160
var mods1 = addMod(mods, givenMod)
41474161
val nameStart = in.offset
4148-
var name = if isIdent && followingIsOldStyleGivenSig() then ident() else EmptyTermName
41494162
var newSyntaxAllowed = in.featureEnabled(Feature.modularity)
4163+
val hasEmbeddedColon = !in.isColon && followingIsGivenDefWithColon()
4164+
val name = if isIdent && hasEmbeddedColon then ident() else EmptyTermName
4165+
4166+
def implemented(): List[Tree] =
4167+
if isSimpleLiteral then
4168+
rejectWildcardType(annotType()) :: Nil
4169+
else constrApp() match
4170+
case parent: Apply => parent :: moreConstrApps()
4171+
case parent if in.isIdent && newSyntaxAllowed =>
4172+
infixTypeRest(parent, _ => annotType1()) :: Nil
4173+
case parent => parent :: moreConstrApps()
4174+
4175+
// The term parameters and parent references */
4176+
def newTermParamssAndParents(numLeadParams: Int): (List[List[ValDef]], List[Tree]) =
4177+
if in.token == LPAREN && followingIsArrow() then
4178+
val params =
4179+
if in.lookahead.token == RPAREN && numLeadParams == 0 then
4180+
in.nextToken()
4181+
in.nextToken()
4182+
Nil
4183+
else
4184+
termParamClause(
4185+
ParamOwner.Given, numLeadParams, firstClause = true, initialMods = Modifiers(Given))
4186+
accept(ARROW)
4187+
if params.isEmpty then (params :: Nil, implemented())
4188+
else
4189+
val (paramss, parents) = newTermParamssAndParents(numLeadParams + params.length)
4190+
(params :: paramss, parents)
4191+
else
4192+
val parents = implemented()
4193+
if in.token == ARROW && parents.length == 1 && parents.head.isType then
4194+
in.nextToken()
4195+
val (paramss, parents1) = newTermParamssAndParents(numLeadParams + parents.length)
4196+
(typesToParams(parents, ParamOwner.Given, numLeadParams, Modifiers(Given)) :: paramss, parents1)
4197+
else
4198+
(Nil, parents)
4199+
4200+
/** Type parameters, term parameters and parent clauses */
4201+
def newSignature(): (List[TypeDef], (List[List[ValDef]], List[Tree])) =
4202+
val tparams =
4203+
if in.token == LBRACKET then
4204+
try typeParamClause(ParamOwner.Given)
4205+
finally accept(ARROW)
4206+
else Nil
4207+
(tparams, newTermParamssAndParents(numLeadParams = 0))
41504208

41514209
def moreConstrApps() =
41524210
if newSyntaxAllowed && in.token == COMMA then
@@ -4167,47 +4225,49 @@ object Parsers {
41674225
.asInstanceOf[List[ParamClause]]
41684226

41694227
val gdef =
4170-
val tparams = typeParamClauseOpt(ParamOwner.Given)
4171-
newLineOpt()
4172-
val vparamss =
4173-
if in.token == LPAREN && (in.lookahead.isIdent(nme.using) || name != EmptyTermName)
4174-
then termParamClauses(ParamOwner.Given)
4175-
else Nil
4176-
newLinesOpt()
4177-
val noParams = tparams.isEmpty && vparamss.isEmpty
4178-
val hasParamsOrId = !name.isEmpty || !noParams
4179-
if hasParamsOrId then
4180-
if in.isColon then
4181-
newSyntaxAllowed = false
4228+
val (tparams, (vparamss0, parents)) =
4229+
if in.isColon && !name.isEmpty then
41824230
in.nextToken()
4183-
else if newSyntaxAllowed then accept(ARROW)
4184-
else acceptColon()
4185-
val parents =
4186-
if isSimpleLiteral then
4187-
rejectWildcardType(annotType()) :: Nil
4188-
else constrApp() match
4189-
case parent: Apply => parent :: moreConstrApps()
4190-
case parent if in.isIdent && newSyntaxAllowed =>
4191-
infixTypeRest(parent, _ => annotType1()) :: Nil
4192-
case parent => parent :: moreConstrApps()
4193-
if newSyntaxAllowed && in.isIdent(nme.as) then
4194-
in.nextToken()
4195-
name = ident()
4196-
4231+
newSignature()
4232+
else if hasEmbeddedColon then
4233+
newSyntaxAllowed = false
4234+
val tparamsOld = typeParamClauseOpt(ParamOwner.Given)
4235+
newLineOpt()
4236+
val vparamssOld =
4237+
if in.token == LPAREN && (in.lookahead.isIdent(nme.using) || name != EmptyTermName)
4238+
then termParamClauses(ParamOwner.Given)
4239+
else Nil
4240+
acceptColon()
4241+
(tparamsOld, (vparamssOld, implemented()))
4242+
else
4243+
newSignature()
4244+
val hasParams = tparams.nonEmpty || vparamss0.nonEmpty
4245+
val vparamss = vparamss0 match
4246+
case Nil :: Nil => Nil
4247+
case _ => vparamss0
41974248
val parentsIsType = parents.length == 1 && parents.head.isType
41984249
if in.token == EQUALS && parentsIsType then
41994250
// given alias
42004251
accept(EQUALS)
42014252
mods1 |= Final
4202-
if noParams && !mods.is(Inline) then
4253+
if !hasParams && !mods.is(Inline) then
42034254
mods1 |= Lazy
42044255
ValDef(name, parents.head, subExpr())
42054256
else
42064257
DefDef(name, adjustDefParams(joinParams(tparams, vparamss)), parents.head, subExpr())
4207-
else if (isStatSep || isStatSeqEnd) && parentsIsType && !newSyntaxAllowed then
4258+
else if (isStatSep || isStatSeqEnd) && parentsIsType
4259+
&& !(name.isEmpty && newSyntaxAllowed)
4260+
// under new syntax, anonymous givens are translated to concrete classes,
4261+
// so it's treated as a structural instance.
4262+
then
42084263
// old-style abstract given
42094264
if name.isEmpty then
4210-
syntaxError(em"anonymous given cannot be abstract")
4265+
syntaxError(em"Anonymous given cannot be abstract, or maybe you want to define a concrete given and are missing a `()` argument?", in.lastOffset)
4266+
if newSyntaxAllowed then
4267+
warning(
4268+
em"""This defines an abstract given, which is deprecated. Use a `deferred` given instead.
4269+
|Or, if you intend to define a concrete given, follow the type with `()` arguments.""",
4270+
in.lastOffset)
42114271
DefDef(name, adjustDefParams(joinParams(tparams, vparamss)), parents.head, EmptyTree)
42124272
else
42134273
// structural instance
@@ -4219,12 +4279,16 @@ object Parsers {
42194279
val templ =
42204280
if isStatSep || isStatSeqEnd then
42214281
Template(constr, parents, Nil, EmptyValDef, Nil)
4222-
else if !newSyntaxAllowed || in.token == WITH then
4282+
else if !newSyntaxAllowed
4283+
|| in.token == WITH && tparams.isEmpty && vparamss.isEmpty
4284+
// if new syntax is still allowed and there are parameters, they mist be new style conditions,
4285+
// so old with-style syntax would not be allowed.
4286+
then
42234287
withTemplate(constr, parents)
42244288
else
42254289
possibleTemplateStart()
42264290
templateBodyOpt(constr, parents, Nil)
4227-
if noParams && !mods.is(Inline) then ModuleDef(name, templ)
4291+
if !hasParams && !mods.is(Inline) then ModuleDef(name, templ)
42284292
else TypeDef(name.toTypeName, templ)
42294293
end gdef
42304294
finalizeDef(gdef, mods1, start)

docs/_docs/internals/syntax.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -471,10 +471,16 @@ ConstrMods ::= {Annotation} [AccessModifier]
471471
ObjectDef ::= id [Template] ModuleDef(mods, name, template) // no constructor
472472
EnumDef ::= id ClassConstr InheritClauses EnumBody
473473
474-
GivenDef ::= [GivenConditional '=>'] GivenSig
475-
GivenConditional ::= [DefTypeParamClause | UsingParamClause] {UsingParamClause}
476-
GivenSig ::= GivenType ['as' id] ([‘=’ Expr] | TemplateBody)
477-
| ConstrApps ['as' id] TemplateBody
474+
GivenDef ::= [id ':'] GivenSig
475+
GivenSig ::= GivenImpl
476+
| '(' ')' '=>' GivenImpl
477+
| GivenConditional '=>' GivenSig
478+
GivenImpl ::= GivenType ([‘=’ Expr] | TemplateBody)
479+
| ConstrApps TemplateBody
480+
GivenConditional ::= DefTypeParamClause
481+
| DefTermParamClause
482+
| '(' FunArgTypes ')'
483+
| GivenType
478484
GivenType ::= AnnotType1 {id [nl] AnnotType1}
479485
480486
Extension ::= ‘extension’ [DefTypeParamClause] {UsingParamClause}

tests/neg/deferred-givens-2.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ object Scoped:
1212

1313
class SortedIntCorrect2 extends Sorted:
1414
type Element = Int
15-
override given (Int is Ord)() as given_Ord_Element
15+
override given given_Ord_Element: (Int is Ord)()
1616

1717
class SortedIntWrong1 extends Sorted: // error
1818
type Element = Int

tests/neg/deferred-givens.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ class Ctx
55
class Ctx2
66

77
trait A:
8-
given Ctx as ctx = deferred
8+
given ctx: Ctx = deferred
99
given Ctx2 = deferred
1010

1111
class B extends A // error
1212

1313
abstract class C extends A // error
1414

1515
class D extends A:
16-
given Ctx as ctx = Ctx() // ok, was implemented
16+
given ctx: Ctx = Ctx() // ok, was implemented
1717
given Ctx2 = Ctx2() // ok
1818

1919
class Ctx3[T]

tests/neg/i13580.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//> using options -language:experimental.modularity -source future
2+
trait IntWidth:
3+
type Out
4+
given IntWidth:
5+
type Out = 155
6+
7+
trait IntCandidate:
8+
type Out
9+
given (using tracked val w: IntWidth) => IntCandidate: // error
10+
type Out = w.Out
11+
12+
val x = summon[IntCandidate]
13+
val xx = summon[x.Out =:= 155]

tests/neg/i8150.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
trait A
22
trait B
3-
type T = {given(using a: A) as B} // error: refinement cannot be `given`
3+
type T = {given x(using a: A): B} // error: refinement cannot be `given`

tests/pos/deferred-givens.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//> using options -language:experimental.modularity -source future
22
import compiletime.*
33
class Ord[Elem]
4-
given Ord[Double]
4+
given Ord[Double]()
55

66
trait A:
77
type Elem : Ord

0 commit comments

Comments
 (0)