Skip to content

Commit 924bcd9

Browse files
authored
Merge pull request #7917 from dotty-staging/change-extmethods3
New syntax for collective extension methods
2 parents 90f7b0f + ab3de89 commit 924bcd9

File tree

79 files changed

+491
-266
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+491
-266
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

+21-26
Original file line numberDiff line numberDiff line change
@@ -954,18 +954,14 @@ object desugar {
954954
else tree
955955
}
956956

957-
/** Invent a name for an anonympus given of type or template `impl`. */
958-
def inventGivenName(impl: Tree)(implicit ctx: Context): SimpleName =
959-
s"given_${inventName(impl)}".toTermName.asSimpleName
960-
961957
/** The normalized name of `mdef`. This means
962958
* 1. Check that the name does not redefine a Scala core class.
963959
* If it does redefine, issue an error and return a mangled name instead of the original one.
964960
* 2. If the name is missing (this can be the case for instance definitions), invent one instead.
965961
*/
966962
def normalizeName(mdef: MemberDef, impl: Tree)(implicit ctx: Context): Name = {
967963
var name = mdef.name
968-
if (name.isEmpty) name = name.likeSpaced(inventGivenName(impl))
964+
if (name.isEmpty) name = name.likeSpaced(inventGivenOrExtensionName(impl))
969965
if (ctx.owner == defn.ScalaPackageClass && defn.reservedScalaClassNames.contains(name.toTypeName)) {
970966
def kind = if (name.isTypeName) "class" else "object"
971967
ctx.error(em"illegal redefinition of standard $kind $name", mdef.sourcePos)
@@ -974,27 +970,26 @@ object desugar {
974970
name
975971
}
976972

977-
/** Invent a name for an anonymous instance with template `impl`.
978-
*/
979-
private def inventName(impl: Tree)(implicit ctx: Context): String = impl match {
980-
case impl: Template =>
981-
if (impl.parents.isEmpty)
982-
impl.body.find {
983-
case dd: DefDef if dd.mods.is(Extension) => true
984-
case _ => false
985-
}
986-
match {
987-
case Some(DefDef(name, _, (vparam :: _) :: _, _, _)) =>
988-
s"${name}_of_${inventTypeName(vparam.tpt)}"
989-
case _ =>
990-
ctx.error(i"anonymous instance must implement a type or have at least one extension method", impl.sourcePos)
991-
nme.ERROR.toString
992-
}
993-
else
994-
impl.parents.map(inventTypeName(_)).mkString("_")
995-
case impl: Tree =>
996-
inventTypeName(impl)
997-
}
973+
/** Invent a name for an anonympus given or extension of type or template `impl`. */
974+
def inventGivenOrExtensionName(impl: Tree)(given ctx: Context): SimpleName =
975+
val str = impl match
976+
case impl: Template =>
977+
if impl.parents.isEmpty then
978+
impl.body.find {
979+
case dd: DefDef if dd.mods.is(Extension) => true
980+
case _ => false
981+
}
982+
match
983+
case Some(DefDef(name, _, (vparam :: _) :: _, _, _)) =>
984+
s"extension_${name}_${inventTypeName(vparam.tpt)}"
985+
case _ =>
986+
ctx.error(i"anonymous instance must implement a type or have at least one extension method", impl.sourcePos)
987+
nme.ERROR.toString
988+
else
989+
impl.parents.map(inventTypeName(_)).mkString("given_", "_", "")
990+
case impl: Tree =>
991+
"given_" ++ inventTypeName(impl)
992+
str.toTermName.asSimpleName
998993

999994
private class NameExtractor(followArgs: Boolean) extends UntypedTreeAccumulator[String] {
1000995
private def extractArgs(args: List[Tree])(implicit ctx: Context): String =

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

+1
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,7 @@ object StdNames {
530530
val null_ : N = "null"
531531
val nullExpr: N = "nullExpr"
532532
val ofDim: N = "ofDim"
533+
val on: N = "on"
533534
val opaque: N = "opaque"
534535
val open: N = "open"
535536
val ordinal: N = "ordinal"

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

+45-12
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ object Parsers {
188188
/* -------------- TOKEN CLASSES ------------------------------------------- */
189189

190190
def isIdent = in.isIdent
191-
def isIdent(name: Name) = in.token == IDENTIFIER && in.name == name
191+
def isIdent(name: Name) = in.isIdent(name)
192192
def isSimpleLiteral = simpleLiteralTokens contains in.token
193193
def isLiteral = literalTokens contains in.token
194194
def isNumericLit = numericLitTokens contains in.token
@@ -216,10 +216,11 @@ object Parsers {
216216
in.canStartExprTokens.contains(in.token) && !in.isSoftModifierInModifierPosition
217217

218218
def isDefIntro(allowedMods: BitSet, excludedSoftModifiers: Set[TermName] = Set.empty): Boolean =
219-
in.token == AT ||
220-
(defIntroTokens `contains` in.token) ||
221-
(allowedMods `contains` in.token) ||
222-
in.isSoftModifierInModifierPosition && !excludedSoftModifiers.contains(in.name)
219+
in.token == AT
220+
|| defIntroTokens.contains(in.token)
221+
|| allowedMods.contains(in.token)
222+
|| in.isSoftModifierInModifierPosition && !excludedSoftModifiers.contains(in.name)
223+
|| isIdent(nme.extension) && followingIsExtension()
223224

224225
def isStatSep: Boolean = in.isNewLine || in.token == SEMI
225226

@@ -944,6 +945,13 @@ object Parsers {
944945
lookahead.skipParens()
945946
lookahead.token == COLON || lookahead.token == SUBTYPE
946947

948+
def followingIsExtension() =
949+
val lookahead = in.LookaheadScanner()
950+
lookahead.nextToken()
951+
if lookahead.isIdent && !lookahead.isIdent(nme.on) then
952+
lookahead.nextToken()
953+
lookahead.isIdent(nme.on)
954+
947955
/* --------- OPERAND/OPERATOR STACK --------------------------------------- */
948956

949957
var opStack: List[OpInfo] = Nil
@@ -3105,7 +3113,7 @@ object Parsers {
31053113
* | this ParamClause ParamClauses `=' ConstrExpr
31063114
* DefDcl ::= DefSig `:' Type
31073115
* DefSig ::= id [DefTypeParamClause] DefParamClauses
3108-
* | ExtParamClause [nl] id DefParamClauses
3116+
* | ExtParamClause [nl] [‘.’] id DefParamClauses
31093117
*/
31103118
def defDefOrDcl(start: Offset, mods: Modifiers): Tree = atSpan(start, nameStart) {
31113119
def scala2ProcedureSyntax(resultTypeStr: String) = {
@@ -3134,7 +3142,11 @@ object Parsers {
31343142
makeConstructor(Nil, vparamss, rhs).withMods(mods).setComment(in.getDocComment(start))
31353143
}
31363144
else {
3137-
def extParamss() = try paramClause(0, prefix = true) :: Nil finally newLineOpt()
3145+
def extParamss() =
3146+
try paramClause(0, prefix = true) :: Nil
3147+
finally
3148+
if in.token == DOT then in.nextToken()
3149+
else newLineOpt()
31383150
val (leadingTparams, leadingVparamss, flags) =
31393151
if in.token == LBRACKET then
31403152
(typeParamClause(ParamOwner.Def), extParamss(), Method | Extension)
@@ -3271,6 +3283,7 @@ object Parsers {
32713283
* | [‘case’] ‘object’ ObjectDef
32723284
* | ‘enum’ EnumDef
32733285
* | ‘given’ GivenDef
3286+
* | ‘extension’ ExtensionDef
32743287
*/
32753288
def tmplDef(start: Int, mods: Modifiers): Tree =
32763289
in.token match {
@@ -3289,8 +3302,11 @@ object Parsers {
32893302
case GIVEN =>
32903303
givenDef(start, mods, atSpan(in.skipToken()) { Mod.Given() })
32913304
case _ =>
3292-
syntaxErrorOrIncomplete(ExpectedStartOfTopLevelDefinition())
3293-
EmptyTree
3305+
if isIdent(nme.extension) && followingIsExtension() then
3306+
extensionDef(start, mods)
3307+
else
3308+
syntaxErrorOrIncomplete(ExpectedStartOfTopLevelDefinition())
3309+
EmptyTree
32943310
}
32953311

32963312
/** ClassDef ::= id ClassConstr TemplateOpt
@@ -3518,6 +3534,23 @@ object Parsers {
35183534
finalizeDef(gdef, mods1, start)
35193535
}
35203536

3537+
/** ExtensionDef ::= [id] ‘of’ ExtParamClause {GivenParamClause} ‘with’ ExtMethods
3538+
*/
3539+
def extensionDef(start: Offset, mods: Modifiers): ModuleDef =
3540+
in.nextToken()
3541+
val name = if isIdent && !isIdent(nme.on) then ident() else EmptyTermName
3542+
if !isIdent(nme.on) then syntaxErrorOrIncomplete("`on` expected")
3543+
if isIdent(nme.on) then in.nextToken()
3544+
val tparams = typeParamClauseOpt(ParamOwner.Def)
3545+
val extParams = paramClause(0, prefix = true)
3546+
val givenParamss = paramClauses(givenOnly = true)
3547+
possibleTemplateStart()
3548+
if !in.isNestedStart then syntaxError("Extension without extension methods")
3549+
val templ = templateBodyOpt(makeConstructor(tparams, extParams :: givenParamss), Nil, Nil)
3550+
templ.body.foreach(checkExtensionMethod(tparams, _))
3551+
val edef = ModuleDef(name, templ)
3552+
finalizeDef(edef, addFlag(mods, Given), start)
3553+
35213554
/* -------- TEMPLATES ------------------------------------------- */
35223555

35233556
/** SimpleConstrApp ::= AnnotType {ParArgumentExprs}
@@ -3677,7 +3710,7 @@ object Parsers {
36773710
def templateStatSeq(): (ValDef, List[Tree]) = checkNoEscapingPlaceholders {
36783711
var self: ValDef = EmptyValDef
36793712
val stats = new ListBuffer[Tree]
3680-
if (isExprIntro) {
3713+
if (isExprIntro && !isDefIntro(modifierTokens)) {
36813714
val first = expr1()
36823715
if (in.token == ARROW) {
36833716
first match {
@@ -3703,10 +3736,10 @@ object Parsers {
37033736
stats ++= importClause(IMPORT, Import)
37043737
else if (in.token == EXPORT)
37053738
stats ++= importClause(EXPORT, Export.apply)
3706-
else if (isExprIntro)
3707-
stats += expr1()
37083739
else if (isDefIntro(modifierTokensOrCase))
37093740
stats +++= defOrDcl(in.offset, defAnnotsMods(modifierTokens))
3741+
else if (isExprIntro)
3742+
stats += expr1()
37103743
else if (!isStatSep) {
37113744
exitOnError = mustStartStat
37123745
syntaxErrorOrIncomplete("illegal start of definition")

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

+1
Original file line numberDiff line numberDiff line change
@@ -1017,6 +1017,7 @@ object Scanners {
10171017

10181018
def isNewLine = token == NEWLINE || token == NEWLINES
10191019
def isIdent = token == IDENTIFIER || token == BACKQUOTED_IDENT
1020+
def isIdent(name: Name) = token == IDENTIFIER && this.name == name
10201021

10211022
def isNestedStart = token == LBRACE || token == INDENT
10221023
def isNestedEnd = token == RBRACE || token == OUTDENT

compiler/src/dotty/tools/dotc/typer/Typer.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1541,7 +1541,7 @@ class Typer extends Namer
15411541
var name = tree.name
15421542
if (name == nme.WILDCARD && tree.mods.is(Given)) {
15431543
val Typed(_, tpt): @unchecked = tree.body
1544-
name = desugar.inventGivenName(tpt)
1544+
name = desugar.inventGivenOrExtensionName(tpt)
15451545
}
15461546
if (name == nme.WILDCARD) body1
15471547
else {

docs/blog/_posts/2019-12-20-21th-dotty-milestone-release.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ given extension (s: String) { ... }
183183
```
184184
or
185185
```scala
186-
given listOps: [T](xs: List[T]) extended with { ... }
186+
extension listOps of [T](xs: List[T]) with { ... }
187187

188188
given (s: String) extended with { ... }
189189
```

docs/docs/contributing/debugging.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ But you can also do:
8888
assertPositioned(tree.reporting(s"Tree is: $result"))
8989
```
9090

91-
`def (a: A) reporting(f: given WrappedResult[T] => String, p: Printer = Printers.default): A` is defined on all types. The function `f` can be written without the argument since the argument is `given`. The `result` variable is a part of the `WrapperResult` – a tiny framework powering the `reporting` function. Basically, whenever you are using `reporting` on an object `A`, you can use the `result: A` variable from this function and it will be equal to the object you are calling `reporting` on.
91+
`def (a: A).reporting(f: given WrappedResult[T] => String, p: Printer = Printers.default): A` is defined on all types. The function `f` can be written without the argument since the argument is `given`. The `result` variable is a part of the `WrapperResult` – a tiny framework powering the `reporting` function. Basically, whenever you are using `reporting` on an object `A`, you can use the `result: A` variable from this function and it will be equal to the object you are calling `reporting` on.
9292

9393
## Printing out trees after phases
9494
To print out the trees you are compiling after the FrontEnd (scanner, parser, namer, typer) phases:

docs/docs/internals/syntax.md

+5-6
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ yield
103103
### Soft keywords
104104

105105
```
106-
as derives inline opaque open
106+
derives extension inline opaque open
107107
~ * | & + -
108108
```
109109

@@ -358,7 +358,7 @@ ValDcl ::= ids ‘:’ Type
358358
VarDcl ::= ids ‘:’ Type PatDef(_, ids, tpe, EmptyTree)
359359
DefDcl ::= DefSig ‘:’ Type DefDef(_, name, tparams, vparamss, tpe, EmptyTree)
360360
DefSig ::= id [DefTypeParamClause] DefParamClauses
361-
| ExtParamClause [nl] id DefParamClauses
361+
| ExtParamClause [nl] [‘.’] id DefParamClauses
362362
TypeDcl ::= id [TypeParamClause] SubtypeBounds [‘=’ Type] TypeDefTree(_, name, tparams, bound
363363
364364
Def ::= ‘val’ PatDef
@@ -378,7 +378,7 @@ TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef
378378
| [‘case’] ‘object’ ObjectDef
379379
| ‘enum’ EnumDef
380380
| ‘given’ GivenDef
381-
| Export
381+
| ‘extension’ ExtensionDef
382382
ClassDef ::= id ClassConstr [Template] ClassDef(mods, name, tparams, templ)
383383
ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses with DefDef(_, <init>, Nil, vparamss, EmptyTree, EmptyTree) as first stat
384384
ConstrMods ::= {Annotation} [AccessModifier]
@@ -388,11 +388,10 @@ GivenDef ::= [GivenSig (‘:’ | <:)] {FunArgTypes ‘=>’}
388388
AnnotType ‘=’ Expr
389389
| [GivenSig ‘:’] {FunArgTypes ‘=>’}
390390
ConstrApps [[‘with’] TemplateBody]
391-
| [id ‘:’] ExtParamClause {GivenParamClause}
392-
‘extended’ ‘with’ ExtMethods
393391
GivenSig ::= [id] [DefTypeParamClause] {GivenParamClause}
394-
ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’
392+
ExtensionDef ::= [id] ‘of’ ExtParamClause {GivenParamClause} ExtMethods
395393
ExtMethods ::= [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’
394+
ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’
396395
Template ::= InheritClauses [[‘with’] TemplateBody] Template(constr, parents, self, stats)
397396
InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}]
398397
ConstrApps ::= ConstrApp {(‘,’ | ‘with’) ConstrApp}

0 commit comments

Comments
 (0)