Skip to content

Commit 85ad51a

Browse files
committed
Change extension method syntax.
Use (this: T) parameter instead of prefix parameter list.
1 parent 907f516 commit 85ad51a

File tree

17 files changed

+893
-103
lines changed

17 files changed

+893
-103
lines changed

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,8 @@ object desugar {
212212
* inline def f(x: Boolean): Any = if (x) 1 else ""
213213
* ==>
214214
* inline def f(x: Boolean): Any = (if (x) 1 else ""): Any
215+
*
216+
* 5. Wrap body of extension methods in { import this._ ; [] }
215217
*/
216218
private def defDef(meth0: DefDef, isPrimaryConstructor: Boolean = false)(implicit ctx: Context): Tree = {
217219
val meth @ DefDef(_, tparams, vparamss, tpt, rhs) = transformQuotedPatternName(meth0)
@@ -287,12 +289,21 @@ object desugar {
287289
Nil
288290
}
289291

292+
val meth2 =
293+
if (mods.is(Extension) && vparamss.exists(isThisParamClause(_)) && !meth1.rhs.isEmpty)
294+
cpy.DefDef(meth1)(
295+
rhs = Block(
296+
Import(importDelegate = false, Ident(nme.this_), Ident(nme.WILDCARD) :: Nil) :: Nil,
297+
meth1.rhs
298+
))
299+
else meth1
300+
290301
val defGetters = defaultGetters(vparamss, 0)
291-
if (defGetters.isEmpty) meth1
302+
if (defGetters.isEmpty) meth2
292303
else {
293-
val meth2 = cpy.DefDef(meth1)(vparamss = normalizedVparamss)
304+
val meth3 = cpy.DefDef(meth2)(vparamss = normalizedVparamss)
294305
.withMods(meth1.mods | DefaultParameterized)
295-
Thicket(meth2 :: defGetters)
306+
Thicket(meth3 :: defGetters)
296307
}
297308
}
298309

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

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -209,19 +209,31 @@ abstract class Positioned(implicit @constructorOnly src: SourceFile) extends Pro
209209
check(tree.mods)
210210
check(tree.vparamss)
211211
case tree: DefDef if tree.mods.is(Extension) =>
212-
tree.vparamss match {
213-
case vparams1 :: vparams2 :: rest if !isLeftAssoc(tree.name) =>
214-
check(vparams2)
215-
check(tree.tparams)
216-
check(vparams1)
217-
check(rest)
218-
case vparams1 :: rest =>
219-
check(vparams1)
220-
check(tree.tparams)
221-
check(rest)
222-
case _ =>
223-
check(tree.tparams)
212+
if (tree.vparamss.exists(untpd.isThisParamClause)) {
213+
check(tree.tparams)
214+
tree.vparamss match {
215+
case vparams1 :: vparams2 :: rest if !isLeftAssoc(tree.name) =>
216+
check(vparams2)
217+
check(vparams1)
218+
check(rest)
219+
case _ =>
220+
check(tree.vparamss)
221+
}
224222
}
223+
else
224+
tree.vparamss match {
225+
case vparams1 :: vparams2 :: rest if !isLeftAssoc(tree.name) =>
226+
check(vparams2)
227+
check(tree.tparams)
228+
check(vparams1)
229+
check(rest)
230+
case vparams1 :: rest =>
231+
check(vparams1)
232+
check(tree.tparams)
233+
check(rest)
234+
case _ =>
235+
check(tree.tparams)
236+
}
225237
check(tree.tpt)
226238
check(tree.rhs)
227239
case _ =>

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,16 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] =>
143143
/** Is tree a backquoted identifier or definition */
144144
def isBackquoted(tree: Tree): Boolean = tree.hasAttachment(Backquoted)
145145

146+
/** Is param of the form `this: T` ? */
147+
def isThisParam(param: ValDef): Boolean =
148+
param.name == nme.this_ && !isBackquoted(param)
149+
150+
/** Is params of the form `(this: T)` ? */
151+
def isThisParamClause(params: List[ValDef]): Boolean = params match {
152+
case param :: Nil => isThisParam(param)
153+
case _ => false
154+
}
155+
146156
/** Is tree a variable pattern? */
147157
def isVarPattern(pat: Tree): Boolean = unsplice(pat) match {
148158
case x: Ident => x.name.isVariableName && !isBackquoted(x)

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

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2205,13 +2205,15 @@ object Parsers {
22052205

22062206
/** ClsParamClause ::= [‘erased’] (‘(’ ClsParams ‘)’
22072207
* GivenClsParamClause::= ‘given’ [‘erased’] (‘(’ ClsParams ‘)’ | GivenTypes)
2208+
* ThisParamClause ::= [nl] ‘(’ {Annotation} ThisParam ‘)’
22082209
* ClsParams ::= ClsParam {‘,’ ClsParam}
22092210
* ClsParam ::= {Annotation}
22102211
*
22112212
* DefParamClause ::= [‘erased’] (‘(’ DefParams ‘)’
22122213
* GivenParamClause ::= ‘given’ [‘erased’] (‘(’ DefParams ‘)’ | GivenTypes)
22132214
* DefParams ::= DefParam {‘,’ DefParam}
22142215
* DefParam ::= {Annotation} [‘inline’] Param
2216+
* ThisParam ::= {Annotation} [‘inline’] ‘this’ ‘:’ ParamType
22152217
*
22162218
* Param ::= id `:' ParamType [`=' Expr]
22172219
*
@@ -2221,10 +2223,11 @@ object Parsers {
22212223
ofCaseClass: Boolean = false, // owner is a case class
22222224
prefix: Boolean = false, // clause precedes name of an extension method
22232225
firstClause: Boolean = false, // clause is the first in regular list of clauses
2226+
thisOK: Boolean = false, // can be a this param clause
22242227
initialMods: Modifiers = EmptyModifiers): List[ValDef] = {
22252228
var impliedMods: Modifiers = initialMods
22262229

2227-
def param(): ValDef = {
2230+
def param(thisOK: Boolean): ValDef = {
22282231
val start = in.offset
22292232
var mods = impliedMods.withAnnotations(annotations())
22302233
if (ofClass) {
@@ -2251,18 +2254,27 @@ object Parsers {
22512254
mods |= Param
22522255
}
22532256
atSpan(start, nameStart) {
2254-
val name = ident()
2257+
val isBackquoted = in.token == BACKQUOTED_IDENT
2258+
val name =
2259+
if (thisOK && in.token == THIS) {
2260+
in.nextToken()
2261+
nme.this_
2262+
}
2263+
else ident()
22552264
accept(COLON)
22562265
if (in.token == ARROW && ofClass && !mods.is(Local))
22572266
syntaxError(VarValParametersMayNotBeCallByName(name, mods.is(Mutable)))
22582267
val tpt = paramType()
2268+
def isThisParam = name == nme.this_ && !isBackquoted
22592269
val default =
2260-
if (in.token == EQUALS) { in.nextToken(); expr() }
2270+
if (in.token == EQUALS && !isThisParam) { in.nextToken(); expr() }
22612271
else EmptyTree
22622272
if (impliedMods.mods.nonEmpty) {
22632273
impliedMods = impliedMods.withMods(Nil) // keep only flags, so that parameter positions don't overlap
22642274
}
2265-
ValDef(name, tpt, default).withMods(mods)
2275+
val result = ValDef(name, tpt, default).withMods(mods)
2276+
if (isBackquoted) result.pushAttachment(Backquoted, ())
2277+
result
22662278
}
22672279
}
22682280

@@ -2284,9 +2296,19 @@ object Parsers {
22842296
else {
22852297
if (in.token == IMPLICIT && !impliedMods.isOneOf(Given | Erased))
22862298
impliedMods = addMod(impliedMods, atSpan(accept(IMPLICIT)) { Mod.Implicit() })
2287-
val clause =
2288-
if (prefix) param() :: Nil
2289-
else commaSeparated(() => param())
2299+
val clause = {
2300+
val leading = param(thisOK)
2301+
if (prefix || isThisParam(leading))
2302+
leading :: Nil
2303+
else {
2304+
val ts = new ListBuffer[ValDef] += leading
2305+
while (in.token == COMMA) {
2306+
in.nextToken()
2307+
ts += param(thisOK = false)
2308+
}
2309+
ts.toList
2310+
}
2311+
}
22902312
checkVarArgsRules(clause)
22912313
clause
22922314
}
@@ -2302,8 +2324,9 @@ object Parsers {
23022324
*/
23032325
def paramClauses(ofClass: Boolean = false,
23042326
ofCaseClass: Boolean = false,
2305-
ofInstance: Boolean = false): List[List[ValDef]] = {
2306-
def recur(firstClause: Boolean, nparams: Int, contextualOnly: Boolean): List[List[ValDef]] = {
2327+
ofInstance: Boolean = false,
2328+
thisOK: Boolean = false): List[List[ValDef]] = {
2329+
def recur(firstClause: Boolean, nparams: Int, givenOnly: Boolean): List[List[ValDef]] = {
23072330
var initialMods = EmptyModifiers
23082331
if (in.token == GIVEN) {
23092332
in.nextToken()
@@ -2313,10 +2336,10 @@ object Parsers {
23132336
in.nextToken()
23142337
initialMods |= Erased
23152338
}
2316-
val isContextual = initialMods.is(Given)
2339+
val isGiven = initialMods.is(Given)
23172340
newLineOptWhenFollowedBy(LPAREN)
23182341
def isParamClause: Boolean =
2319-
!isContextual || {
2342+
!isGiven || {
23202343
val lookahead = in.lookaheadScanner
23212344
lookahead.nextToken()
23222345
paramIntroTokens.contains(lookahead.token) && {
@@ -2328,28 +2351,29 @@ object Parsers {
23282351
}
23292352
}
23302353
if (in.token == LPAREN && isParamClause) {
2331-
if (contextualOnly && !isContextual)
2354+
if (givenOnly && !isGiven)
23322355
if (ofInstance) syntaxError(em"parameters of instance definitions must come after `given'")
23332356
else syntaxError(em"normal parameters cannot come after `given' clauses")
23342357
val params = paramClause(
23352358
ofClass = ofClass,
23362359
ofCaseClass = ofCaseClass,
23372360
firstClause = firstClause,
2361+
thisOK = thisOK && firstClause,
23382362
initialMods = initialMods)
23392363
val lastClause = params.nonEmpty && params.head.mods.flags.is(Implicit)
2340-
params :: (if (lastClause) Nil else recur(firstClause = false, nparams + params.length, isContextual))
2364+
params :: (if (lastClause) Nil else recur(firstClause = false, nparams + params.length, isGiven))
23412365
}
2342-
else if (isContextual) {
2366+
else if (isGiven) {
23432367
val tps = commaSeparated(() => annotType())
23442368
var counter = nparams
23452369
def nextIdx = { counter += 1; counter }
23462370
val paramFlags = if (ofClass) Private | Local | ParamAccessor else Param
23472371
val params = tps.map(makeSyntheticParameter(nextIdx, _, paramFlags | Synthetic | Given))
2348-
params :: recur(firstClause = false, nparams + params.length, isContextual)
2372+
params :: recur(firstClause = false, nparams + params.length, isGiven)
23492373
}
23502374
else Nil
23512375
}
2352-
recur(firstClause = true, 0, ofInstance)
2376+
recur(firstClause = true, nparams = 0, givenOnly = ofInstance)
23532377
}
23542378

23552379
/* -------- DEFS ------------------------------------------- */
@@ -2538,6 +2562,7 @@ object Parsers {
25382562
* | this ParamClause ParamClauses `=' ConstrExpr
25392563
* DefDcl ::= DefSig `:' Type
25402564
* DefSig ::= [‘(’ DefParam ‘)’ [nl]] id [DefTypeParamClause] ParamClauses
2565+
* | id [DefTypeParamClause] [ThisParamClause] DefParamClauses
25412566
*/
25422567
def defDefOrDcl(start: Offset, mods: Modifiers): Tree = atSpan(start, nameStart) {
25432568
def scala2ProcedureSyntax(resultTypeStr: String) = {
@@ -2565,20 +2590,26 @@ object Parsers {
25652590
}
25662591
makeConstructor(Nil, vparamss, rhs).withMods(mods).setComment(in.getDocComment(start))
25672592
} else {
2568-
val (leadingParamss, flags) =
2593+
val leadingParamss =
25692594
if (in.token == LPAREN)
2570-
try (paramClause(prefix = true) :: Nil, Method | Extension)
2595+
try paramClause(prefix = true) :: Nil
25712596
finally newLineOpt()
2572-
else
2573-
(Nil, Method)
2574-
val mods1 = addFlag(mods, flags)
2597+
else Nil
2598+
var mods1 = addFlag(mods, Method)
25752599
val ident = termIdent()
25762600
val tparams = typeParamClauseOpt(ParamOwner.Def)
2577-
val vparamss = paramClauses() match {
2578-
case rparams :: rparamss if leadingParamss.nonEmpty && !isLeftAssoc(ident.name) =>
2579-
rparams :: leadingParamss ::: rparamss
2580-
case rparamss =>
2581-
leadingParamss ::: rparamss
2601+
val vparamssAsGiven = leadingParamss ::: paramClauses(thisOK = leadingParamss.isEmpty)
2602+
vparamssAsGiven match {
2603+
case (vparam :: Nil) :: _ if leadingParamss.nonEmpty || isThisParam(vparam) =>
2604+
mods1 = addFlag(mods, Extension)
2605+
case _ =>
2606+
}
2607+
// swap first two parameter lists of right-associative extension operators
2608+
val vparamss = vparamssAsGiven match {
2609+
case rparams1 :: rparams2 :: rparamss if mods1.is(Extension) && !isLeftAssoc(ident.name) =>
2610+
rparams2 :: rparams1 :: rparamss
2611+
case _ =>
2612+
vparamssAsGiven
25822613
}
25832614
var tpt = fromWithinReturnType {
25842615
if (in.token == SUBTYPE && mods.is(Inline)) {

compiler/src/dotty/tools/dotc/transform/Erasure.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ object Erasure {
491491
checkNotErased(recur(qual1))
492492
}
493493

494-
override def typedThis(tree: untpd.This)(implicit ctx: Context): Tree =
494+
override def typedThis(tree: untpd.This, pt: Type)(implicit ctx: Context): Tree =
495495
if (tree.symbol == ctx.owner.lexicallyEnclosingClass || tree.symbol.isStaticOwner) promote(tree)
496496
else {
497497
ctx.log(i"computing outer path from ${ctx.owner.ownersIterator.toList}%, % to ${tree.symbol}, encl class = ${ctx.owner.enclosingClass}")

compiler/src/dotty/tools/dotc/transform/TreeChecker.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,8 +341,8 @@ class TreeChecker extends Phase with SymTransformer {
341341
checkNotRepeated(super.typedSelect(tree, pt))
342342
}
343343

344-
override def typedThis(tree: untpd.This)(implicit ctx: Context): Tree = {
345-
val res = super.typedThis(tree)
344+
override def typedThis(tree: untpd.This, pt: Type)(implicit ctx: Context): Tree = {
345+
val res = super.typedThis(tree, pt)
346346
val cls = res.symbol
347347
assert(cls.isStaticOwner || ctx.owner.isContainedIn(cls), i"error while typing $tree, ${ctx.owner} is not contained in $cls")
348348
res

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class ReTyper extends Typer with ReChecking {
4545
override def typedLiteral(tree: untpd.Literal)(implicit ctc: Context): Tree =
4646
promote(tree)
4747

48-
override def typedThis(tree: untpd.This)(implicit ctx: Context): Tree =
48+
override def typedThis(tree: untpd.This, pt: Type)(implicit ctx: Context): Tree =
4949
promote(tree)
5050

5151
override def typedSuper(tree: untpd.Super, pt: Type)(implicit ctx: Context): Tree =

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ class Typer extends Namer
195195

196196
// Pass refctx so that any errors are reported in the context of the
197197
// reference instead of the
198-
if (reallyExists(denot)) pre.select(name, denot) else NoType
198+
if (qualifies(denot)) pre.select(name, denot) else NoType
199199
}
200200

201201
/** The type representing a named import with enclosing name when imported
@@ -491,8 +491,11 @@ class Typer extends Namer
491491
typeSelectOnTerm(ctx)
492492
}
493493

494-
def typedThis(tree: untpd.This)(implicit ctx: Context): Tree = track("typedThis") {
495-
assignType(tree)
494+
def typedThis(tree: untpd.This, pt: Type)(implicit ctx: Context): Tree = track("typedThis") {
495+
def isExtensionThis(owner: Symbol): Boolean =
496+
owner.is(Extension) || owner.isTerm && isExtensionThis(owner.owner)
497+
if (isExtensionThis(ctx.owner) && tree.qual.isEmpty) typedIdent(cpy.Ident(tree)(nme.this_), pt)
498+
else assignType(tree)
496499
}
497500

498501
def typedSuper(tree: untpd.Super, pt: Type)(implicit ctx: Context): Tree = track("typedSuper") {
@@ -2145,7 +2148,7 @@ class Typer extends Namer
21452148
def typedUnnamed(tree: untpd.Tree): Tree = tree match {
21462149
case tree: untpd.Apply =>
21472150
if (ctx.mode is Mode.Pattern) typedUnApply(tree, pt) else typedApply(tree, pt)
2148-
case tree: untpd.This => typedThis(tree)
2151+
case tree: untpd.This => typedThis(tree, pt)
21492152
case tree: untpd.Literal => typedLiteral(tree)
21502153
case tree: untpd.New => typedNew(tree, pt)
21512154
case tree: untpd.Typed => typedTyped(tree, pt)

compiler/test/dotty/tools/repl/ReplCompilerTests.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,8 @@ class ReplCompilerTests extends ReplTest {
147147
run("""
148148
|trait Ord[T] {
149149
| def compare(x: T, y: T): Int
150-
| def (x: T) < (y: T) = compare(x, y) < 0
151-
| def (x: T) > (y: T) = compare(x, y) > 0
150+
| def < (this: T)(that: T) = compare(this, that) < 0
151+
| def > (this: T)(that: T) = compare(this, that) > 0
152152
|}
153153
|
154154
|delegate IntOrd for Ord[Int] {

docs/docs/internals/syntax.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,8 +310,10 @@ DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [‘implicit’] DefParams
310310
| {DefParamClause} {GivenParamClause}
311311
DefParamClause ::= ‘(’ DefParams ‘)’
312312
GivenParamClause ::= ‘given’ (‘(’ DefParams ‘)’ | GivenTypes)
313+
ThisParamClause ::= ‘(’ ThisParam ‘)’
313314
DefParams ::= DefParam {‘,’ DefParam}
314315
DefParam ::= {Annotation} [‘inline’] Param ValDef(mods, id, tpe, expr) -- point of mods at id.
316+
ThisParam ::= {Annotation} [‘inline’] ‘this’ ‘:’ ParamType
315317
GivenTypes ::= AnnotType {‘,’ AnnotType}
316318
ClosureMods ::= { ‘implicit’ | ‘given’}
317319
```
@@ -357,8 +359,7 @@ Dcl ::= RefineDcl
357359
ValDcl ::= ids ‘:’ Type PatDef(_, ids, tpe, EmptyTree)
358360
VarDcl ::= ids ‘:’ Type PatDef(_, ids, tpe, EmptyTree)
359361
DefDcl ::= DefSig [‘:’ Type] DefDef(_, name, tparams, vparamss, tpe, EmptyTree)
360-
DefSig ::= [‘(’ DefParam ‘)’ [nl]] id
361-
[DefTypeParamClause] DefParamClauses
362+
DefSig ::= id [DefTypeParamClause] [ThisParamClause] DefParamClauses
362363
TypeDcl ::= id [TypeParamClause] SubtypeBounds [‘=’ Type] TypeDefTree(_, name, tparams, bound
363364
364365
Def ::= ‘val’ PatDef

docs/docs/reference/changed-features/operators.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ The purpose of the `@infix` annotation is to achieve consistency across a code b
113113
@infix def op[T](x: T)(y: S): R // ok
114114
@infix def op[T](x: T, y: S): R // error: two parameters
115115

116-
@infix def (x: A) op (y: B): R // ok
117-
@infix def (x: A) op (y1: B, y2: B): R // error: two parameters
116+
@infix def op(this: A)(y: B): R // ok
117+
@infix def op(this A)(y1: B, y2: B): R // error: two parameters
118118
```
119119

120120
4. @infix annotations can also be given to type, trait or class definitions that have exactly two type parameters. An infix type like

docs/docs/reference/contextual/delegates.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ that serve for synthesizing arguments to [given clauses](./given-clauses.html).
99
```scala
1010
trait Ord[T] {
1111
def compare(x: T, y: T): Int
12-
def (x: T) < (y: T) = compare(x, y) < 0
13-
def (x: T) > (y: T) = compare(x, y) > 0
12+
def < (this: T)(that: T) = compare(this, that) < 0
13+
def > (this: T)(that: T) = compare(this, that) > 0
1414
}
1515

1616
delegate IntOrd for Ord[Int] {

0 commit comments

Comments
 (0)