Skip to content

Commit fa0d0d2

Browse files
committed
Change to new extension method syntax
It's given extension { ... } or given extension ops: { ... }
1 parent 421d2e9 commit fa0d0d2

File tree

16 files changed

+125
-82
lines changed

16 files changed

+125
-82
lines changed

compiler/src/dotty/tools/dotc/core/StdNames.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,7 @@ object StdNames {
434434
val eval: N = "eval"
435435
val eqlAny: N = "eqlAny"
436436
val ex: N = "ex"
437+
val extension: N = "extension"
437438
val experimental: N = "experimental"
438439
val f: N = "f"
439440
val false_ : N = "false"

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

Lines changed: 63 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ object Parsers {
3333
import reporting.diagnostic.Message
3434
import reporting.diagnostic.messages._
3535

36+
private inline val allowOldGiven = true
37+
private inline val allowOldExtension = true
38+
3639
case class OpInfo(operand: Tree, operator: Ident, offset: Offset)
3740

3841
class ParensCounters {
@@ -795,7 +798,7 @@ object Parsers {
795798
* - continue a statement (e.g. `else`, catch`)?
796799
*/
797800
def followedByToken(query: Token): Boolean = {
798-
val lookahead = in.lookaheadScanner
801+
val lookahead = in.LookaheadScanner()
799802
var braces = 0
800803
while (true) {
801804
val token = lookahead.token
@@ -815,7 +818,7 @@ object Parsers {
815818

816819
/** A the generators of a for-expression enclosed in (...)? */
817820
def parensEncloseGenerators: Boolean = {
818-
val lookahead = in.lookaheadScanner
821+
val lookahead = in.LookaheadScanner()
819822
var parens = 1
820823
lookahead.nextToken()
821824
while (parens != 0 && lookahead.token != EOF) {
@@ -891,7 +894,7 @@ object Parsers {
891894
}
892895
else recur(operand())
893896
}
894-
else if (in.token == GIVEN && !isType) {
897+
else if (in.token == GIVEN && allowOldGiven && !isType) {
895898
val top1 = reduceStack(base, top, minInfixPrec, leftAssoc = true, nme.WITHkw, isType)
896899
assert(opStack `eq` base)
897900
recur(applyGiven(top1, operand))
@@ -2122,7 +2125,7 @@ object Parsers {
21222125
*/
21232126
def parArgumentExprss(fn: Tree): Tree = {
21242127
def isLegalAnnotArg: Boolean = {
2125-
val lookahead = in.lookaheadScanner
2128+
val lookahead = in.LookaheadScanner()
21262129
(lookahead.token == LPAREN) && {
21272130
lookahead.nextToken()
21282131
if (lookahead.token == RPAREN)
@@ -2580,8 +2583,13 @@ object Parsers {
25802583
/** ClosureMods ::= { ‘implicit’ | ‘erased’ | ‘given’}
25812584
* FunTypeMods ::= { ‘erased’ | ‘given’}
25822585
*/
2583-
val closureMods: BitSet = BitSet(GIVEN, IMPLICIT, ERASED)
2584-
val funTypeMods: BitSet = BitSet(GIVEN, ERASED)
2586+
val closureMods: BitSet =
2587+
if allowOldGiven then BitSet(GIVEN, IMPLICIT, ERASED)
2588+
else BitSet(IMPLICIT)
2589+
2590+
val funTypeMods: BitSet =
2591+
if allowOldGiven then BitSet(GIVEN, ERASED)
2592+
else BitSet()
25852593

25862594
/** Wrap annotation or constructor in New(...).<init> */
25872595
def wrapNew(tpt: Tree): Select = Select(New(tpt), nme.CONSTRUCTOR)
@@ -2765,7 +2773,7 @@ object Parsers {
27652773
ofInstance: Boolean = false): List[List[ValDef]] = {
27662774

27672775
def followingIsParamClause: Boolean = {
2768-
val lookahead = in.lookaheadScanner
2776+
val lookahead = in.LookaheadScanner()
27692777
lookahead.nextToken()
27702778
paramIntroTokens.contains(lookahead.token) && {
27712779
lookahead.token != IDENTIFIER ||
@@ -2792,7 +2800,7 @@ object Parsers {
27922800
*/
27932801
def followingIsInstanceDef =
27942802
(ofClass || ofInstance) && {
2795-
val lookahead = in.lookaheadScanner // skips newline on startup
2803+
val lookahead = in.LookaheadScanner() // skips newline on startup
27962804
lookahead.nextToken() // skip the `given`
27972805
if (lookahead.token == LBRACKET) true
27982806
else {
@@ -2818,9 +2826,9 @@ object Parsers {
28182826
var initialMods = EmptyModifiers
28192827
val isNewLine = in.token == NEWLINE
28202828
newLineOptWhenFollowedBy(LPAREN)
2821-
if (in.token == NEWLINE && in.next.token == GIVEN && !followingIsInstanceDef)
2829+
if (in.token == NEWLINE && in.next.token == GIVEN && allowOldGiven && !followingIsInstanceDef)
28222830
in.nextToken()
2823-
if (in.token == GIVEN) {
2831+
if (in.token == GIVEN && allowOldGiven) {
28242832
in.nextToken()
28252833
initialMods |= Given
28262834
}
@@ -3322,31 +3330,52 @@ object Parsers {
33223330
* NEW:
33233331
* GivenDef ::= [GivenSig (‘:’ | <:)] Type ‘=’ Expr
33243332
* | [GivenSig ‘:’] [ConstrApp {‘,’ ConstrApp }] [TemplateBody]
3325-
* // | [id ‘:’] [ExtParamClause] TemplateBody (not yet implemented)
3333+
* | ‘extension’ [id ‘:’] [ExtParamClause] TemplateBody
33263334
* ExtParamClause ::= [DefTypeParamClause] DefParamClause {GivenParamClause}
33273335
* GivenSig ::= [id] [DefTypeParamClause] {GivenParamClause}
33283336
*/
33293337
def instanceDef(newStyle: Boolean, start: Offset, mods: Modifiers, instanceMod: Mod) = atSpan(start, nameStart) {
33303338
var mods1 = addMod(mods, instanceMod)
3331-
val name = if (isIdent && !(newStyle && isIdent(nme.as))) ident() else EmptyTermName
3332-
indentRegion(name) {
3333-
val tparams = typeParamClauseOpt(ParamOwner.Def)
3334-
var leadingParamss =
3335-
if (in.token == LPAREN)
3336-
try paramClause(prefix = true) :: Nil
3337-
finally {
3338-
possibleTemplateStart()
3339-
if (!in.isNestedStart) syntaxErrorOrIncomplete("`{' expected")
3339+
3340+
def extensionDef() = {
3341+
in.nextToken()
3342+
val name =
3343+
if isIdent then
3344+
val id = ident()
3345+
accept(COLON)
3346+
id
3347+
else EmptyTermName
3348+
indentRegion(name) {
3349+
// TODO: accept type params here
3350+
val leadingParamss = if in.token == LPAREN then paramClause(prefix = true) :: Nil else Nil
3351+
// TODO: accept given params here
3352+
possibleTemplateStart()
3353+
val templ = templateBodyOpt(makeConstructor(Nil, leadingParamss), Nil, Nil)
3354+
ModuleDef(name, templ)
3355+
}
3356+
}
3357+
3358+
def regularDef() = {
3359+
val name =
3360+
if isIdent && !(newStyle && allowOldGiven && isIdent(nme.as)) then ident()
3361+
else EmptyTermName
3362+
indentRegion(name) {
3363+
val tparams = typeParamClauseOpt(ParamOwner.Def)
3364+
var leadingParamss =
3365+
if (in.token == LPAREN && allowOldExtension)
3366+
try paramClause(prefix = true) :: Nil
3367+
finally {
3368+
possibleTemplateStart()
3369+
if (!in.isNestedStart) syntaxErrorOrIncomplete("`{' expected")
3370+
}
3371+
else Nil
3372+
val parents =
3373+
if (!newStyle && in.token == FOR || isIdent(nme.as)) { // for the moment, accept both `given for` and `given as`
3374+
in.nextToken()
3375+
tokenSeparated(COMMA, constrApp)
33403376
}
3341-
else Nil
3342-
val parents =
3343-
if (!newStyle && in.token == FOR || isIdent(nme.as)) { // for the moment, accept both `given for` and `given as`
3344-
in.nextToken()
3345-
tokenSeparated(COMMA, constrApp)
3346-
}
3347-
else Nil
3348-
val vparamss = paramClauses(ofInstance = true)
3349-
val instDef =
3377+
else Nil
3378+
val vparamss = paramClauses(ofInstance = true)
33503379
if (in.token == EQUALS && parents.length == 1 && parents.head.isType) {
33513380
in.nextToken()
33523381
mods1 |= Final
@@ -3365,15 +3394,16 @@ object Parsers {
33653394
if (tparams.isEmpty && vparamss1.isEmpty || leadingParamss.nonEmpty) ModuleDef(name, templ)
33663395
else TypeDef(name.toTypeName, templ)
33673396
}
3368-
finalizeDef(instDef, mods1, start)
3397+
}
33693398
}
3399+
3400+
val gdef = if isIdent(nme.extension) then extensionDef() else regularDef()
3401+
finalizeDef(gdef, mods1, start)
33703402
}
33713403

33723404
/* -------- TEMPLATES ------------------------------------------- */
33733405

33743406
/** SimpleConstrApp ::= AnnotType {ParArgumentExprs}
3375-
* ConstrApp ::= SimpleConstrApp
3376-
* | ‘(’ SimpleConstrApp {‘given’ (PrefixExpr | ParArgumentExprs)} ‘)’
33773407
*/
33783408
val constrApp: () => Tree = () => {
33793409

@@ -3393,7 +3423,7 @@ object Parsers {
33933423
def givenArgs(t: Tree): Tree =
33943424
if (in.token == GIVEN) givenArgs(applyGiven(t, prefixExpr)) else t
33953425

3396-
if (in.token == LPAREN)
3426+
if (allowOldGiven && in.token == LPAREN)
33973427
inParens {
33983428
val t = toplevelTyp()
33993429
if (isAnnotType(t))

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

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ object Scanners {
140140
val keepComments = !ctx.settings.YdropComments.value
141141

142142
/** A switch whether operators at the start of lines can be infix operators */
143-
private var allowLeadingInfixOperators = true
143+
private[Scanners] var allowLeadingInfixOperators = true
144144

145145
val isScala2Mode: Boolean = ctx.scala2Setting
146146

@@ -326,7 +326,7 @@ object Scanners {
326326
*/
327327
private def handleEndMarkers(width: IndentWidth): Unit =
328328
if (next.token == IDENTIFIER && next.name == nme.end && width == currentRegion.indentWidth) {
329-
val lookahead = lookaheadScanner
329+
val lookahead = LookaheadScanner()
330330
lookahead.nextToken() // skip the `end`
331331

332332
def handle(tag: EndMarkerTag) = {
@@ -378,7 +378,7 @@ object Scanners {
378378
&& ch == ' '
379379
&& !pastBlankLine
380380
&& {
381-
val lookahead = lookaheadScanner
381+
val lookahead = LookaheadScanner()
382382
lookahead.allowLeadingInfixOperators = false
383383
// force a NEWLINE a after current token if it is on its own line
384384
lookahead.nextToken()
@@ -865,18 +865,25 @@ object Scanners {
865865

866866
// Lookahead ---------------------------------------------------------------
867867

868-
/** A new Scanner that starts at the current token offset */
869-
def lookaheadScanner: Scanner = new Scanner(source, offset) {
868+
class LookaheadScanner extends Scanner(source, offset) {
870869
override val indentSyntax = false
871870
override protected def printState() = {
872871
print("la:")
873872
super.printState()
874873
}
874+
875+
final def skipParens(opening: Token): Unit = {
876+
nextToken()
877+
while token != EOF && token != opening + 1 do
878+
if (token == opening) skipParens(opening)
879+
else nextToken()
880+
nextToken()
881+
}
875882
}
876883

877884
/** Is the token following the current one in `tokens`? */
878885
def lookaheadIn(tokens: BitSet): Boolean = {
879-
val lookahead = lookaheadScanner
886+
val lookahead = LookaheadScanner()
880887
while ({
881888
lookahead.nextToken()
882889
lookahead.token == NEWLINE || lookahead.token == NEWLINES
@@ -887,7 +894,7 @@ object Scanners {
887894

888895
/** Is the current token in a position where a modifier is allowed? */
889896
def inModifierPosition(): Boolean = {
890-
val lookahead = lookaheadScanner
897+
val lookahead = LookaheadScanner()
891898
while ({
892899
lookahead.nextToken()
893900
lookahead.token == NEWLINE || lookahead.token == NEWLINES || lookahead.isSoftModifier

docs/docs/internals/syntax.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,13 +389,14 @@ ObjectDef ::= id [Template]
389389
EnumDef ::= id ClassConstr InheritClauses EnumBody EnumDef(mods, name, tparams, template)
390390
GivenDef ::= [GivenSig (‘:’ | <:)] Type ‘=’ Expr
391391
| [GivenSig ‘:’] [ConstrApp {‘,’ ConstrApp }] [TemplateBody]
392-
| [GivenSig ‘:’] [DefTypeParamClause] DefParamClause TemplateBody
392+
| ‘extension’ [id ‘:’] [ExtParamClause] TemplateBody
393393
GivenSig ::= [id] [DefTypeParamClause] {GivenParamClause}
394+
ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’ {GivenParamClause}
394395
Template ::= InheritClauses [TemplateBody] Template(constr, parents, self, stats)
395396
InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}]
396397
ConstrApps ::= ConstrApp {‘with’ ConstrApp}
397398
| ConstrApp {‘,’ ConstrApp}
398-
ConstrApp ::= AnnotType {ArgumentExprs} Apply(tp, args)
399+
ConstrApp ::= AnnotType {ParArgumentExprs} Apply(tp, args)
399400
ConstrExpr ::= SelfInvocation
400401
| ‘{’ SelfInvocation {semi BlockStat} ‘}’
401402
SelfInvocation ::= ‘this’ ArgumentExprs {ArgumentExprs}

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

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -84,36 +84,40 @@ So `circle.circumference` translates to `CircleOps.circumference(circle)`, provi
8484

8585
### Given Instances for Extension Methods
8686

87-
Given instances that define extension methods can also be defined without an `as` clause. E.g.,
87+
Given instances that define extension methods can also be defined without a parent clause. In this case the `given` is followed by
88+
`extension`. E.g.,
8889

8990
```scala
90-
given StringOps {
91+
given extension stringOps: {
9192
def (xs: Seq[String]) longestStrings: Seq[String] = {
9293
val maxLength = xs.map(_.length).max
9394
xs.filter(_.length == maxLength)
9495
}
9596
}
9697

97-
given {
98+
given extension {
9899
def (xs: List[T]) second[T] = xs.tail.head
99100
}
100101
```
101-
If such given instances are anonymous (as in the second clause), their name is synthesized from the name of the first defined extension method.
102+
If given extensions are anonymous (as in the second clause), their name is synthesized from the name of the first defined extension method.
102103

103-
### Given Instances with Collective Parameters
104+
Note: `extension` is a soft keyword, it can be used elsewhere
105+
as a normal identifier.
104106

105-
If a given instance has several extension methods one can pull out the left parameter section
107+
### Given Extensions with Collective Parameters
108+
109+
If a given extension defines several extension methods one can pull out the left parameter section
106110
as well as any type parameters of these extension methods into the given instance itself.
107111
For instance, here is a given instance with two extension methods.
108112
```scala
109-
given ListOps {
113+
given extension listOps: {
110114
def (xs: List[T]) second[T]: T = xs.tail.head
111115
def (xs: List[T]) third[T]: T = xs.tail.tail.head
112116
}
113117
```
114-
The repetition in the parameters can be avoided by moving the parameters into the given instance itself. The following version is a shorthand for the code above.
118+
The repetition in the parameters can be avoided by moving the parameters in front of the opening brace. The following version is a shorthand for the code above.
115119
```scala
116-
given ListOps[T](xs: List[T]) {
120+
given extension listOps: [T](xs: List[T]) {
117121
def second: T = xs.tail.head
118122
def third: T = xs.tail.tail.head
119123
}
@@ -170,8 +174,8 @@ As usual, type parameters of the extension method follow the defined method name
170174
The required syntax extension just adds one clause for extension methods relative
171175
to the [current syntax](../../internals/syntax.md).
172176
```
173-
DefSig ::= ...
174-
| ‘(’ DefParam ‘)’ [nl] id [DefTypeParamClause] DefParamClauses
175-
GivenBody ::= ...
177+
GivenDef ::= ...
178+
| ‘extension’ [id ‘:’] ExtParamClause TemplateBody
179+
ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’ {GivenParamClause}
176180
| ‘(’ DefParam ‘)’ TemplateBody
177181
```

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ The synthesized type names are formed from
6666

6767
Anonymous given instances that define extension methods without also implementing a type
6868
get their name from the name of the first extension method and the toplevel type
69-
constructor of its first parameter. For example, the given instance
69+
constructor of its first parameter. For example, the given extension
7070
```scala
71-
given {
71+
given extension {
7272
def (xs: List[T]) second[T] = ...
7373
}
7474
```

docs/docs/reference/metaprogramming/macros.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ The `toExpr` extension method is defined in package `quoted`:
251251
```scala
252252
package quoted
253253

254-
given ExprOps {
254+
given extension {
255255
def (x: T) toExpr[T: Liftable] given QuoteContext: Expr[T] = summon[Liftable[T]].toExpr(x)
256256
...
257257
}

docs/docs/reference/other-new-features/opaques.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ object Logarithms {
2020
}
2121

2222
// Extension methods define opaque types' public APIs
23-
given LogarithmOps {
23+
given extension logarithmOps: {
2424
def (x: Logarithm) toDouble: Double = math.exp(x)
2525
def (x: Logarithm) + (y: Logarithm): Logarithm = Logarithm(math.exp(x) + math.exp(y))
2626
def (x: Logarithm) * (y: Logarithm): Logarithm = Logarithm(x + y)
@@ -57,7 +57,7 @@ But the following operations would lead to type errors:
5757
l / l2 // error: `/` is not a member fo Logarithm
5858
```
5959

60-
Aside: the `any2stringadd => _` import suppression is necessary since otherwise the universal `+` operation in `Predef` would take precedence over the `+` extension method in `LogarithmOps`. We plan to resolve this wart by eliminating `any2stringadd`.
60+
Aside: the `any2stringadd => _` import suppression is necessary since otherwise the universal `+` operation in `Predef` would take precedence over the `+` extension method in `logarithmOps`. We plan to resolve this wart by eliminating `any2stringadd`.
6161

6262
### Bounds For Opaque Type Aliases
6363

library/src-bootstrapped/scala/IArray.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ object opaques {
88
opaque type IArray[+T] = Array[_ <: T]
99

1010
/** Defines extension methods for immutable arrays */
11-
given arrayOps {
11+
given extension arrayOps: {
1212

1313
/** The selection operation on an immutable array.
1414
*

0 commit comments

Comments
 (0)