Skip to content

Change extension method syntax. #6760

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,8 @@ object desugar {
* inline def f(x: Boolean): Any = if (x) 1 else ""
* ==>
* inline def f(x: Boolean): Any = (if (x) 1 else ""): Any
*
* 5. Wrap body of extension methods in { import this._ ; [] }
*/
private def defDef(meth0: DefDef, isPrimaryConstructor: Boolean = false)(implicit ctx: Context): Tree = {
val meth @ DefDef(_, tparams, vparamss, tpt, rhs) = transformQuotedPatternName(meth0)
Expand Down Expand Up @@ -287,12 +289,21 @@ object desugar {
Nil
}

val meth2 =
if (mods.is(Extension) && vparamss.exists(isThisParamClause(_)) && !meth1.rhs.isEmpty)
cpy.DefDef(meth1)(
rhs = Block(
Import(importDelegate = false, Ident(nme.this_), Ident(nme.WILDCARD) :: Nil) :: Nil,
meth1.rhs
))
else meth1

val defGetters = defaultGetters(vparamss, 0)
if (defGetters.isEmpty) meth1
if (defGetters.isEmpty) meth2
else {
val meth2 = cpy.DefDef(meth1)(vparamss = normalizedVparamss)
val meth3 = cpy.DefDef(meth2)(vparamss = normalizedVparamss)
.withMods(meth1.mods | DefaultParameterized)
Thicket(meth2 :: defGetters)
Thicket(meth3 :: defGetters)
}
}

Expand Down
36 changes: 24 additions & 12 deletions compiler/src/dotty/tools/dotc/ast/Positioned.scala
Original file line number Diff line number Diff line change
Expand Up @@ -209,19 +209,31 @@ abstract class Positioned(implicit @constructorOnly src: SourceFile) extends Pro
check(tree.mods)
check(tree.vparamss)
case tree: DefDef if tree.mods.is(Extension) =>
tree.vparamss match {
case vparams1 :: vparams2 :: rest if !isLeftAssoc(tree.name) =>
check(vparams2)
check(tree.tparams)
check(vparams1)
check(rest)
case vparams1 :: rest =>
check(vparams1)
check(tree.tparams)
check(rest)
case _ =>
check(tree.tparams)
if (tree.vparamss.exists(untpd.isThisParamClause)) {
check(tree.tparams)
tree.vparamss match {
case vparams1 :: vparams2 :: rest if !isLeftAssoc(tree.name) =>
check(vparams2)
check(vparams1)
check(rest)
case _ =>
check(tree.vparamss)
}
}
else
tree.vparamss match {
case vparams1 :: vparams2 :: rest if !isLeftAssoc(tree.name) =>
check(vparams2)
check(tree.tparams)
check(vparams1)
check(rest)
case vparams1 :: rest =>
check(vparams1)
check(tree.tparams)
check(rest)
case _ =>
check(tree.tparams)
}
check(tree.tpt)
check(tree.rhs)
case _ =>
Expand Down
10 changes: 10 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,16 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] =>
/** Is tree a backquoted identifier or definition */
def isBackquoted(tree: Tree): Boolean = tree.hasAttachment(Backquoted)

/** Is param of the form `this: T` ? */
def isThisParam(param: ValDef): Boolean =
param.name == nme.this_ && !isBackquoted(param)

/** Is params of the form `(this: T)` ? */
def isThisParamClause(params: List[ValDef]): Boolean = params match {
case param :: Nil => isThisParam(param)
case _ => false
}

/** Is tree a variable pattern? */
def isVarPattern(pat: Tree): Boolean = unsplice(pat) match {
case x: Ident => x.name.isVariableName && !isBackquoted(x)
Expand Down
83 changes: 57 additions & 26 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2205,13 +2205,15 @@ object Parsers {

/** ClsParamClause ::= [‘erased’] (‘(’ ClsParams ‘)’
* GivenClsParamClause::= ‘given’ [‘erased’] (‘(’ ClsParams ‘)’ | GivenTypes)
* ThisParamClause ::= [nl] ‘(’ {Annotation} ThisParam ‘)’
* ClsParams ::= ClsParam {‘,’ ClsParam}
* ClsParam ::= {Annotation}
*
* DefParamClause ::= [‘erased’] (‘(’ DefParams ‘)’
* GivenParamClause ::= ‘given’ [‘erased’] (‘(’ DefParams ‘)’ | GivenTypes)
* DefParams ::= DefParam {‘,’ DefParam}
* DefParam ::= {Annotation} [‘inline’] Param
* ThisParam ::= {Annotation} [‘inline’] ‘this’ ‘:’ ParamType
*
* Param ::= id `:' ParamType [`=' Expr]
*
Expand All @@ -2221,10 +2223,11 @@ object Parsers {
ofCaseClass: Boolean = false, // owner is a case class
prefix: Boolean = false, // clause precedes name of an extension method
firstClause: Boolean = false, // clause is the first in regular list of clauses
thisOK: Boolean = false, // can be a this param clause
initialMods: Modifiers = EmptyModifiers): List[ValDef] = {
var impliedMods: Modifiers = initialMods

def param(): ValDef = {
def param(thisOK: Boolean): ValDef = {
val start = in.offset
var mods = impliedMods.withAnnotations(annotations())
if (ofClass) {
Expand All @@ -2251,18 +2254,27 @@ object Parsers {
mods |= Param
}
atSpan(start, nameStart) {
val name = ident()
val isBackquoted = in.token == BACKQUOTED_IDENT
val name =
if (thisOK && in.token == THIS) {
in.nextToken()
nme.this_
}
else ident()
accept(COLON)
if (in.token == ARROW && ofClass && !mods.is(Local))
syntaxError(VarValParametersMayNotBeCallByName(name, mods.is(Mutable)))
val tpt = paramType()
def isThisParam = name == nme.this_ && !isBackquoted
val default =
if (in.token == EQUALS) { in.nextToken(); expr() }
if (in.token == EQUALS && !isThisParam) { in.nextToken(); expr() }
else EmptyTree
if (impliedMods.mods.nonEmpty) {
impliedMods = impliedMods.withMods(Nil) // keep only flags, so that parameter positions don't overlap
}
ValDef(name, tpt, default).withMods(mods)
val result = ValDef(name, tpt, default).withMods(mods)
if (isBackquoted) result.pushAttachment(Backquoted, ())
result
}
}

Expand All @@ -2284,9 +2296,19 @@ object Parsers {
else {
if (in.token == IMPLICIT && !impliedMods.isOneOf(Given | Erased))
impliedMods = addMod(impliedMods, atSpan(accept(IMPLICIT)) { Mod.Implicit() })
val clause =
if (prefix) param() :: Nil
else commaSeparated(() => param())
val clause = {
val leading = param(thisOK)
if (prefix || isThisParam(leading))
leading :: Nil
else {
val ts = new ListBuffer[ValDef] += leading
while (in.token == COMMA) {
in.nextToken()
ts += param(thisOK = false)
}
ts.toList
}
}
checkVarArgsRules(clause)
clause
}
Expand All @@ -2302,8 +2324,9 @@ object Parsers {
*/
def paramClauses(ofClass: Boolean = false,
ofCaseClass: Boolean = false,
ofInstance: Boolean = false): List[List[ValDef]] = {
def recur(firstClause: Boolean, nparams: Int, contextualOnly: Boolean): List[List[ValDef]] = {
ofInstance: Boolean = false,
thisOK: Boolean = false): List[List[ValDef]] = {
def recur(firstClause: Boolean, nparams: Int, givenOnly: Boolean): List[List[ValDef]] = {
var initialMods = EmptyModifiers
if (in.token == GIVEN) {
in.nextToken()
Expand All @@ -2313,10 +2336,10 @@ object Parsers {
in.nextToken()
initialMods |= Erased
}
val isContextual = initialMods.is(Given)
val isGiven = initialMods.is(Given)
newLineOptWhenFollowedBy(LPAREN)
def isParamClause: Boolean =
!isContextual || {
!isGiven || {
val lookahead = in.lookaheadScanner
lookahead.nextToken()
paramIntroTokens.contains(lookahead.token) && {
Expand All @@ -2328,28 +2351,29 @@ object Parsers {
}
}
if (in.token == LPAREN && isParamClause) {
if (contextualOnly && !isContextual)
if (givenOnly && !isGiven)
if (ofInstance) syntaxError(em"parameters of instance definitions must come after `given'")
else syntaxError(em"normal parameters cannot come after `given' clauses")
val params = paramClause(
ofClass = ofClass,
ofCaseClass = ofCaseClass,
firstClause = firstClause,
thisOK = thisOK && firstClause,
initialMods = initialMods)
val lastClause = params.nonEmpty && params.head.mods.flags.is(Implicit)
params :: (if (lastClause) Nil else recur(firstClause = false, nparams + params.length, isContextual))
params :: (if (lastClause) Nil else recur(firstClause = false, nparams + params.length, isGiven))
}
else if (isContextual) {
else if (isGiven) {
val tps = commaSeparated(() => annotType())
var counter = nparams
def nextIdx = { counter += 1; counter }
val paramFlags = if (ofClass) Private | Local | ParamAccessor else Param
val params = tps.map(makeSyntheticParameter(nextIdx, _, paramFlags | Synthetic | Given))
params :: recur(firstClause = false, nparams + params.length, isContextual)
params :: recur(firstClause = false, nparams + params.length, isGiven)
}
else Nil
}
recur(firstClause = true, 0, ofInstance)
recur(firstClause = true, nparams = 0, givenOnly = ofInstance)
}

/* -------- DEFS ------------------------------------------- */
Expand Down Expand Up @@ -2538,6 +2562,7 @@ object Parsers {
* | this ParamClause ParamClauses `=' ConstrExpr
* DefDcl ::= DefSig `:' Type
* DefSig ::= [‘(’ DefParam ‘)’ [nl]] id [DefTypeParamClause] ParamClauses
* | id [DefTypeParamClause] [ThisParamClause] DefParamClauses
*/
def defDefOrDcl(start: Offset, mods: Modifiers): Tree = atSpan(start, nameStart) {
def scala2ProcedureSyntax(resultTypeStr: String) = {
Expand Down Expand Up @@ -2565,20 +2590,26 @@ object Parsers {
}
makeConstructor(Nil, vparamss, rhs).withMods(mods).setComment(in.getDocComment(start))
} else {
val (leadingParamss, flags) =
val leadingParamss =
if (in.token == LPAREN)
try (paramClause(prefix = true) :: Nil, Method | Extension)
try paramClause(prefix = true) :: Nil
finally newLineOpt()
else
(Nil, Method)
val mods1 = addFlag(mods, flags)
else Nil
var mods1 = addFlag(mods, Method)
val ident = termIdent()
val tparams = typeParamClauseOpt(ParamOwner.Def)
val vparamss = paramClauses() match {
case rparams :: rparamss if leadingParamss.nonEmpty && !isLeftAssoc(ident.name) =>
rparams :: leadingParamss ::: rparamss
case rparamss =>
leadingParamss ::: rparamss
val vparamssAsGiven = leadingParamss ::: paramClauses(thisOK = leadingParamss.isEmpty)
vparamssAsGiven match {
case (vparam :: Nil) :: _ if leadingParamss.nonEmpty || isThisParam(vparam) =>
mods1 = addFlag(mods, Extension)
case _ =>
}
// swap first two parameter lists of right-associative extension operators
val vparamss = vparamssAsGiven match {
case rparams1 :: rparams2 :: rparamss if mods1.is(Extension) && !isLeftAssoc(ident.name) =>
rparams2 :: rparams1 :: rparamss
case _ =>
vparamssAsGiven
}
var tpt = fromWithinReturnType {
if (in.token == SUBTYPE && mods.is(Inline)) {
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ object Erasure {
checkNotErased(recur(qual1))
}

override def typedThis(tree: untpd.This)(implicit ctx: Context): Tree =
override def typedThis(tree: untpd.This, pt: Type)(implicit ctx: Context): Tree =
if (tree.symbol == ctx.owner.lexicallyEnclosingClass || tree.symbol.isStaticOwner) promote(tree)
else {
ctx.log(i"computing outer path from ${ctx.owner.ownersIterator.toList}%, % to ${tree.symbol}, encl class = ${ctx.owner.enclosingClass}")
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/TreeChecker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -341,8 +341,8 @@ class TreeChecker extends Phase with SymTransformer {
checkNotRepeated(super.typedSelect(tree, pt))
}

override def typedThis(tree: untpd.This)(implicit ctx: Context): Tree = {
val res = super.typedThis(tree)
override def typedThis(tree: untpd.This, pt: Type)(implicit ctx: Context): Tree = {
val res = super.typedThis(tree, pt)
val cls = res.symbol
assert(cls.isStaticOwner || ctx.owner.isContainedIn(cls), i"error while typing $tree, ${ctx.owner} is not contained in $cls")
res
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/ReTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class ReTyper extends Typer with ReChecking {
override def typedLiteral(tree: untpd.Literal)(implicit ctc: Context): Tree =
promote(tree)

override def typedThis(tree: untpd.This)(implicit ctx: Context): Tree =
override def typedThis(tree: untpd.This, pt: Type)(implicit ctx: Context): Tree =
promote(tree)

override def typedSuper(tree: untpd.Super, pt: Type)(implicit ctx: Context): Tree =
Expand Down
11 changes: 7 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ class Typer extends Namer

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

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

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

def typedSuper(tree: untpd.Super, pt: Type)(implicit ctx: Context): Tree = track("typedSuper") {
Expand Down Expand Up @@ -2145,7 +2148,7 @@ class Typer extends Namer
def typedUnnamed(tree: untpd.Tree): Tree = tree match {
case tree: untpd.Apply =>
if (ctx.mode is Mode.Pattern) typedUnApply(tree, pt) else typedApply(tree, pt)
case tree: untpd.This => typedThis(tree)
case tree: untpd.This => typedThis(tree, pt)
case tree: untpd.Literal => typedLiteral(tree)
case tree: untpd.New => typedNew(tree, pt)
case tree: untpd.Typed => typedTyped(tree, pt)
Expand Down
4 changes: 2 additions & 2 deletions compiler/test/dotty/tools/repl/ReplCompilerTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,8 @@ class ReplCompilerTests extends ReplTest {
run("""
|trait Ord[T] {
| def compare(x: T, y: T): Int
| def (x: T) < (y: T) = compare(x, y) < 0
| def (x: T) > (y: T) = compare(x, y) > 0
| def < (this: T)(that: T) = compare(this, that) < 0
| def > (this: T)(that: T) = compare(this, that) > 0
|}
|
|delegate IntOrd for Ord[Int] {
Expand Down
5 changes: 3 additions & 2 deletions docs/docs/internals/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,10 @@ DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [‘implicit’] DefParams
| {DefParamClause} {GivenParamClause}
DefParamClause ::= ‘(’ DefParams ‘)’
GivenParamClause ::= ‘given’ (‘(’ DefParams ‘)’ | GivenTypes)
ThisParamClause ::= ‘(’ ThisParam ‘)’
DefParams ::= DefParam {‘,’ DefParam}
DefParam ::= {Annotation} [‘inline’] Param ValDef(mods, id, tpe, expr) -- point of mods at id.
ThisParam ::= {Annotation} [‘inline’] ‘this’ ‘:’ ParamType
GivenTypes ::= AnnotType {‘,’ AnnotType}
ClosureMods ::= { ‘implicit’ | ‘given’}
```
Expand Down Expand Up @@ -357,8 +359,7 @@ Dcl ::= RefineDcl
ValDcl ::= ids ‘:’ Type PatDef(_, ids, tpe, EmptyTree)
VarDcl ::= ids ‘:’ Type PatDef(_, ids, tpe, EmptyTree)
DefDcl ::= DefSig [‘:’ Type] DefDef(_, name, tparams, vparamss, tpe, EmptyTree)
DefSig ::= [‘(’ DefParam ‘)’ [nl]] id
[DefTypeParamClause] DefParamClauses
DefSig ::= id [DefTypeParamClause] [ThisParamClause] DefParamClauses
TypeDcl ::= id [TypeParamClause] SubtypeBounds [‘=’ Type] TypeDefTree(_, name, tparams, bound

Def ::= ‘val’ PatDef
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/reference/changed-features/operators.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ The purpose of the `@infix` annotation is to achieve consistency across a code b
@infix def op[T](x: T)(y: S): R // ok
@infix def op[T](x: T, y: S): R // error: two parameters

@infix def (x: A) op (y: B): R // ok
@infix def (x: A) op (y1: B, y2: B): R // error: two parameters
@infix def op(this: A)(y: B): R // ok
@infix def op(this: A)(y1: B, y2: B): R // error: two parameters
```

4. @infix annotations can also be given to type, trait or class definitions that have exactly two type parameters. An infix type like
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/reference/contextual/delegates.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ that serve for synthesizing arguments to [given clauses](./given-clauses.html).
```scala
trait Ord[T] {
def compare(x: T, y: T): Int
def (x: T) < (y: T) = compare(x, y) < 0
def (x: T) > (y: T) = compare(x, y) > 0
def < (this: T)(that: T) = compare(this, that) < 0
def > (this: T)(that: T) = compare(this, that) > 0
}

delegate IntOrd for Ord[Int] {
Expand Down
Loading