Skip to content

Commit 401931e

Browse files
committed
Extension methods with leading parameter list
1 parent dd1db7a commit 401931e

File tree

5 files changed

+107
-83
lines changed

5 files changed

+107
-83
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,9 +345,9 @@ object Trees {
345345

346346
def withFlags(flags: FlagSet): ThisTree[Untyped] = withMods(untpd.Modifiers(flags))
347347

348-
def setComment(comment: Option[Comment]): ThisTree[Untyped] = {
348+
def setComment(comment: Option[Comment]): this.type = {
349349
comment.map(putAttachment(DocComment, _))
350-
asInstanceOf[ThisTree[Untyped]]
350+
this
351351
}
352352

353353
/** Destructively update modifiers. To be used with care. */

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

Lines changed: 98 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1968,15 +1968,21 @@ object Parsers {
19681968
* DefParams ::= DefParam {`,' DefParam}
19691969
* DefParam ::= {Annotation} [`inline'] Param
19701970
* Param ::= id `:' ParamType [`=' Expr]
1971-
*/
1972-
def paramClauses(owner: Name, ofCaseClass: Boolean = false): List[List[ValDef]] = {
1973-
var imods: Modifiers = EmptyModifiers
1971+
*
1972+
* @return the list of parameter definitions
1973+
*/
1974+
def paramClause(ofClass: Boolean = false, // owner is a class
1975+
ofCaseClass: Boolean = false, // owner is a case class
1976+
ofMethod: Boolean = false, // owner is a method or constructor
1977+
prefix: Boolean = false, // clause precedes name of an extension method
1978+
firstClause: Boolean = false) // clause is the first in regular list of clauses
1979+
: List[ValDef] = {
19741980
var implicitOffset = -1 // use once
1975-
var firstClauseOfCaseClass = ofCaseClass
1976-
def param(): ValDef = {
1981+
1982+
def param(impliedMods: Modifiers): ValDef = {
19771983
val start = in.offset
1978-
var mods = annotsAsMods()
1979-
if (owner.isTypeName) {
1984+
var mods = impliedMods.withAnnotations(annotations())
1985+
if (ofClass) {
19801986
mods = addFlag(modifiers(start = mods), ParamAccessor)
19811987
mods =
19821988
atPos(start, in.offset) {
@@ -1989,9 +1995,9 @@ object Parsers {
19891995
addMod(mods, mod)
19901996
}
19911997
else {
1992-
if (!(mods.flags &~ (ParamAccessor | Inline)).isEmpty)
1998+
if (!(mods.flags &~ (ParamAccessor | Inline | impliedMods.flags)).isEmpty)
19931999
syntaxError("`val' or `var' expected")
1994-
if (firstClauseOfCaseClass) mods
2000+
if (firstClause && ofCaseClass) mods
19952001
else mods | PrivateLocal
19962002
}
19972003
}
@@ -2004,7 +2010,7 @@ object Parsers {
20042010
atPos(start, nameStart) {
20052011
val name = ident()
20062012
accept(COLON)
2007-
if (in.token == ARROW && owner.isTypeName && !(mods is Local))
2013+
if (in.token == ARROW && ofClass && !(mods is Local))
20082014
syntaxError(VarValParametersMayNotBeCallByName(name, mods is Mutable))
20092015
val tpt = paramType()
20102016
val default =
@@ -2014,56 +2020,75 @@ object Parsers {
20142020
mods = mods.withPos(mods.pos.union(Position(implicitOffset, implicitOffset)))
20152021
implicitOffset = -1
20162022
}
2017-
for (imod <- imods.mods) mods = addMod(mods, imod)
20182023
ValDef(name, tpt, default).withMods(mods)
20192024
}
20202025
}
2021-
def paramClause(): List[ValDef] = inParens {
2022-
if (in.token == RPAREN) Nil
2026+
2027+
def checkVarArgsRules(vparams: List[ValDef]): Unit = vparams match {
2028+
case Nil =>
2029+
case _ :: Nil if !prefix =>
2030+
case vparam :: rest =>
2031+
vparam.tpt match {
2032+
case PostfixOp(_, op) if op.name == tpnme.raw.STAR =>
2033+
syntaxError(VarArgsParamMustComeLast(), vparam.tpt.pos)
2034+
case _ =>
2035+
}
2036+
checkVarArgsRules(rest)
2037+
}
2038+
2039+
// begin paramClause
2040+
inParens {
2041+
if (in.token == RPAREN && !prefix) Nil
20232042
else {
2024-
def funArgMods(): Unit = {
2043+
def funArgMods(mods: Modifiers): Modifiers =
20252044
if (in.token == IMPLICIT) {
20262045
implicitOffset = in.offset
2027-
imods = addMod(imods, atPos(accept(IMPLICIT)) { Mod.Implicit() })
2028-
funArgMods()
2029-
} else if (in.token == ERASED) {
2030-
imods = addMod(imods, atPos(accept(ERASED)) { Mod.Erased() })
2031-
funArgMods()
2046+
funArgMods(addMod(mods, atPos(accept(IMPLICIT)) { Mod.Implicit() }))
20322047
}
2033-
}
2034-
funArgMods()
2035-
2036-
commaSeparated(() => param())
2048+
else if (in.token == ERASED)
2049+
funArgMods(addMod(mods, atPos(accept(ERASED)) { Mod.Erased() }))
2050+
else mods
2051+
2052+
val paramMods = funArgMods(EmptyModifiers)
2053+
val clause =
2054+
if (prefix) param(paramMods) :: Nil
2055+
else commaSeparated(() => param(paramMods))
2056+
checkVarArgsRules(clause)
2057+
clause
20372058
}
20382059
}
2039-
def clauses(): List[List[ValDef]] = {
2060+
}
2061+
2062+
/** ClsParamClauses ::= {ClsParamClause}
2063+
* DefParamClauses ::= {DefParamClause}
2064+
*
2065+
* @return The parameter definitions
2066+
*/
2067+
def paramClauses(ofClass: Boolean = false,
2068+
ofCaseClass: Boolean = false,
2069+
ofMethod: Boolean = false): (List[List[ValDef]]) = {
2070+
def recur(firstClause: Boolean): List[List[ValDef]] = {
20402071
newLineOptWhenFollowedBy(LPAREN)
20412072
if (in.token == LPAREN) {
2042-
imods = EmptyModifiers
2043-
paramClause() :: {
2044-
firstClauseOfCaseClass = false
2045-
if (imods is Implicit) Nil else clauses()
2046-
}
2047-
} else Nil
2048-
}
2049-
val start = in.offset
2050-
val result = clauses()
2051-
if (owner == nme.CONSTRUCTOR && (result.isEmpty || (result.head take 1 exists (_.mods is Implicit)))) {
2052-
in.token match {
2053-
case LBRACKET => syntaxError("no type parameters allowed here")
2054-
case EOF => incompleteInputError(AuxConstructorNeedsNonImplicitParameter())
2055-
case _ => syntaxError(AuxConstructorNeedsNonImplicitParameter(), start)
2073+
val params = paramClause(
2074+
ofClass = ofClass,
2075+
ofCaseClass = ofCaseClass,
2076+
ofMethod = ofMethod,
2077+
firstClause = firstClause)
2078+
val lastClause =
2079+
params.nonEmpty && params.head.mods.flags.is(Implicit)
2080+
params :: (if (lastClause) Nil else recur(firstClause = false))
20562081
}
2082+
else Nil
20572083
}
2058-
val listOfErrors = checkVarArgsRules(result)
2059-
listOfErrors.foreach { vparam =>
2060-
syntaxError(VarArgsParamMustComeLast(), vparam.tpt.pos)
2061-
}
2062-
result
2084+
recur(firstClause = true)
20632085
}
20642086

20652087
/* -------- DEFS ------------------------------------------- */
20662088

2089+
def finalizeDef(md: MemberDef, mods: Modifiers, start: Int): md.ThisTree[Untyped] =
2090+
md.withMods(mods).setComment(in.getDocComment(start))
2091+
20672092
/** Import ::= import ImportExpr {`,' ImportExpr}
20682093
*/
20692094
def importClause(): List[Tree] = {
@@ -2189,29 +2214,16 @@ object Parsers {
21892214
} else EmptyTree
21902215
lhs match {
21912216
case (id @ Ident(name: TermName)) :: Nil => {
2192-
ValDef(name, tpt, rhs).withMods(mods).setComment(in.getDocComment(start))
2217+
finalizeDef(ValDef(name, tpt, rhs), mods, start)
21932218
} case _ =>
21942219
PatDef(mods, lhs, tpt, rhs)
21952220
}
21962221
}
21972222

2198-
private def checkVarArgsRules(vparamss: List[List[ValDef]]): List[ValDef] = {
2199-
def isVarArgs(tpt: Trees.Tree[Untyped]): Boolean = tpt match {
2200-
case PostfixOp(_, op) if op.name == tpnme.raw.STAR => true
2201-
case _ => false
2202-
}
2203-
2204-
vparamss.flatMap { params =>
2205-
if (params.nonEmpty) {
2206-
params.init.filter(valDef => isVarArgs(valDef.tpt))
2207-
} else List()
2208-
}
2209-
}
2210-
22112223
/** DefDef ::= DefSig [(‘:’ | ‘<:’) Type] ‘=’ Expr
22122224
* | this ParamClause ParamClauses `=' ConstrExpr
22132225
* DefDcl ::= DefSig `:' Type
2214-
* DefSig ::= id [DefTypeParamClause] ParamClauses
2226+
* DefSig ::= ‘(’ DefParam ‘)’ [nl] id [DefTypeParamClause] ParamClauses
22152227
*/
22162228
def defDefOrDcl(start: Offset, mods: Modifiers): Tree = atPos(start, nameStart) {
22172229
def scala2ProcedureSyntax(resultTypeStr: String) = {
@@ -2225,18 +2237,29 @@ object Parsers {
22252237
}
22262238
if (in.token == THIS) {
22272239
in.nextToken()
2228-
val vparamss = paramClauses(nme.CONSTRUCTOR)
2240+
val vparamss = paramClauses()
2241+
if (vparamss.isEmpty || vparamss.head.take(1).exists(_.mods.is(Implicit)))
2242+
in.token match {
2243+
case LBRACKET => syntaxError("no type parameters allowed here")
2244+
case EOF => incompleteInputError(AuxConstructorNeedsNonImplicitParameter())
2245+
case _ => syntaxError(AuxConstructorNeedsNonImplicitParameter(), nameStart)
2246+
}
22292247
if (in.isScala2Mode) newLineOptWhenFollowedBy(LBRACE)
22302248
val rhs = {
22312249
if (!(in.token == LBRACE && scala2ProcedureSyntax(""))) accept(EQUALS)
22322250
atPos(in.offset) { constrExpr() }
22332251
}
22342252
makeConstructor(Nil, vparamss, rhs).withMods(mods).setComment(in.getDocComment(start))
22352253
} else {
2236-
val mods1 = addFlag(mods, Method)
2254+
val (leadingParamss: List[List[ValDef]], flags: FlagSet) =
2255+
if (in.token == LPAREN)
2256+
(paramClause(ofMethod = true, prefix = true) :: Nil, Method | Extension)
2257+
else
2258+
(Nil, Method)
2259+
val mods1 = addFlag(mods, flags)
22372260
val name = ident()
22382261
val tparams = typeParamClauseOpt(ParamOwner.Def)
2239-
val vparamss = paramClauses(name)
2262+
val vparamss = leadingParamss ::: paramClauses(ofMethod = true)
22402263
var tpt = fromWithinReturnType {
22412264
if (in.token == SUBTYPE && mods.is(Inline)) {
22422265
in.nextToken()
@@ -2262,7 +2285,7 @@ object Parsers {
22622285
accept(EQUALS)
22632286
expr()
22642287
}
2265-
DefDef(name, tparams, vparamss, tpt, rhs).withMods(mods1).setComment(in.getDocComment(start))
2288+
finalizeDef(DefDef(name, tparams, vparamss, tpt, rhs), mods1, start)
22662289
}
22672290
}
22682291

@@ -2302,7 +2325,7 @@ object Parsers {
23022325
val name = ident().toTypeName
23032326
val tparams = typeParamClauseOpt(ParamOwner.Type)
23042327
def makeTypeDef(rhs: Tree): Tree =
2305-
TypeDef(name, lambdaAbstract(tparams, rhs)).withMods(mods).setComment(in.getDocComment(start))
2328+
finalizeDef(TypeDef(name, lambdaAbstract(tparams, rhs)), mods, start)
23062329
in.token match {
23072330
case EQUALS =>
23082331
in.nextToken()
@@ -2355,23 +2378,23 @@ object Parsers {
23552378
}
23562379

23572380
def classDefRest(start: Offset, mods: Modifiers, name: TypeName): TypeDef = {
2358-
val constr = classConstr(name, isCaseClass = mods is Case)
2381+
val constr = classConstr(isCaseClass = mods.is(Case))
23592382
val templ = templateOpt(constr)
2360-
TypeDef(name, templ).withMods(mods).setComment(in.getDocComment(start))
2383+
finalizeDef(TypeDef(name, templ), mods, start)
23612384
}
23622385

23632386
/** ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses
23642387
*/
2365-
def classConstr(owner: TypeName, isCaseClass: Boolean = false): DefDef = atPos(in.lastOffset) {
2388+
def classConstr(isCaseClass: Boolean = false): DefDef = atPos(in.lastOffset) {
23662389
val tparams = typeParamClauseOpt(ParamOwner.Class)
2367-
val cmods = fromWithinClassConstr(constrModsOpt(owner))
2368-
val vparamss = paramClauses(owner, isCaseClass)
2390+
val cmods = fromWithinClassConstr(constrModsOpt())
2391+
val vparamss = paramClauses(ofClass = true, ofCaseClass = isCaseClass)
23692392
makeConstructor(tparams, vparamss).withMods(cmods)
23702393
}
23712394

23722395
/** ConstrMods ::= {Annotation} [AccessModifier]
23732396
*/
2374-
def constrModsOpt(owner: Name): Modifiers =
2397+
def constrModsOpt(): Modifiers =
23752398
modifiers(accessModifierTokens, annotsAsMods())
23762399

23772400
/** ObjectDef ::= id TemplateOpt
@@ -2382,17 +2405,17 @@ object Parsers {
23822405

23832406
def objectDefRest(start: Offset, mods: Modifiers, name: TermName): ModuleDef = {
23842407
val template = templateOpt(emptyConstructor)
2385-
ModuleDef(name, template).withMods(mods).setComment(in.getDocComment(start))
2408+
finalizeDef(ModuleDef(name, template), mods, start)
23862409
}
23872410

23882411
/** EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumBody
23892412
*/
23902413
def enumDef(start: Offset, mods: Modifiers, enumMod: Mod): TypeDef = atPos(start, nameStart) {
23912414
val modName = ident()
23922415
val clsName = modName.toTypeName
2393-
val constr = classConstr(clsName)
2416+
val constr = classConstr()
23942417
val impl = templateOpt(constr, isEnum = true)
2395-
TypeDef(clsName, impl).withMods(addMod(mods, enumMod)).setComment(in.getDocComment(start))
2418+
finalizeDef(TypeDef(clsName, impl), addMod(mods, enumMod), start)
23962419
}
23972420

23982421
/** EnumCase = `case' (id ClassConstr [`extends' ConstrApps] | ids)
@@ -2416,12 +2439,12 @@ object Parsers {
24162439
val caseDef =
24172440
if (in.token == LBRACKET || in.token == LPAREN || in.token == AT || isModifier) {
24182441
val clsName = id.name.toTypeName
2419-
val constr = classConstr(clsName, isCaseClass = true)
2442+
val constr = classConstr(isCaseClass = true)
24202443
TypeDef(clsName, caseTemplate(constr))
24212444
}
24222445
else
24232446
ModuleDef(id.name.toTermName, caseTemplate(emptyConstructor))
2424-
caseDef.withMods(mods1).setComment(in.getDocComment(start))
2447+
finalizeDef(caseDef, mods1, start)
24252448
}
24262449
}
24272450
}
@@ -2564,7 +2587,7 @@ object Parsers {
25642587
case Typed(tree @ This(EmptyTypeIdent), tpt) =>
25652588
self = makeSelfDef(nme.WILDCARD, tpt).withPos(first.pos)
25662589
case _ =>
2567-
val ValDef(name, tpt, _) = convertToParam(first, expected = "self type clause")
2590+
val ValDef(name, tpt, _) = convertToParam(first, EmptyModifiers, "self type clause")
25682591
if (name != nme.ERROR)
25692592
self = makeSelfDef(name, tpt).withPos(first.pos)
25702593
}

docs/docs/internals/syntax.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ Param ::= id ‘:’ ParamType [‘=’ Expr]
278278
DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [FunArgMods] DefParams ‘)’]
279279
DefParamClause ::= [nl] ‘(’ [DefParams] ‘)’
280280
DefParams ::= DefParam {‘,’ DefParam}
281-
DefParam ::= {Annotation} [‘transparent’] Param ValDef(mods, id, tpe, expr) -- point of mods at id.
281+
DefParam ::= {Annotation} [‘inline’] Param ValDef(mods, id, tpe, expr) -- point of mods at id.
282282
```
283283

284284
### Bindings and Imports
@@ -319,7 +319,8 @@ Dcl ::= RefineDcl
319319
ValDcl ::= ids ‘:’ Type PatDef(_, ids, tpe, EmptyTree)
320320
VarDcl ::= ids ‘:’ Type PatDef(_, ids, tpe, EmptyTree)
321321
DefDcl ::= DefSig [‘:’ Type] DefDef(_, name, tparams, vparamss, tpe, EmptyTree)
322-
DefSig ::= id [DefTypeParamClause] DefParamClauses
322+
DefSig ::= ‘(’ DefParam ‘)’ [nl] id
323+
[DefTypeParamClause] DefParamClauses
323324
TypeDcl ::= id [TypeParamClause] (TypeBounds | ‘=’ Type) TypeDefTree(_, name, tparams, bounds)
324325
| id [TypeParamClause] <: Type = MatchType
325326

docs/docs/reference/extension-methods.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ The required syntax extension just adds one clause for extension methods relativ
272272
to the [current syntax](https://github.com/lampepfl/dotty/blob/master/docs/docs/internals/syntax.md).
273273
```
274274
DefSig ::= ...
275-
| ‘(’ DefParam ‘)’ id [DefTypeParamClause] DefParamClauses
275+
| ‘(’ DefParam ‘)’ [nl] id [DefTypeParamClause] DefParamClauses
276276
```
277277

278278

tests/pos/opaque-xm.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ object opaquetypes {
1616
// Extension methods define opaque types' public APIs
1717

1818
// This is the second way to unlift the logarithm type
19-
def toDouble(this x: Logarithm): Double = math.exp(x)
20-
def +(this x: Logarithm)(y: Logarithm) = Logarithm(math.exp(x) + math.exp(y))
21-
def *(this x: Logarithm)(y: Logarithm): Logarithm = Logarithm(x + y)
19+
def (x: Logarithm) toDouble: Double = math.exp(x)
20+
def (x: Logarithm) + (y: Logarithm) = Logarithm(math.exp(x) + math.exp(y))
21+
def (x: Logarithm) * (y: Logarithm): Logarithm = Logarithm(x + y)
2222
}
2323
}
2424
object usesites {

0 commit comments

Comments
 (0)