WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content
Draft
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
92 changes: 48 additions & 44 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Decorators.*
import Annotations.Annotation
import NameKinds.{UniqueName, ContextBoundParamName, ContextFunctionParamName, DefaultGetterName, WildcardParamName}
import typer.{Namer, Checking}
import util.{Property, SourceFile, SourcePosition, SrcPos, Chars}
import util.{Chars, NoSourcePosition, Property, SourceFile, SourcePosition, SrcPos}
import config.{Feature, Config}
import config.Feature.{sourceVersion, migrateTo3, enabled}
import config.SourceVersion.*
Expand Down Expand Up @@ -1185,55 +1185,59 @@ object desugar {
}

def extMethod(mdef: DefDef, extParamss: List[ParamClause])(using Context): DefDef =
def finish(problem: String = "", pos: SrcPos = NoSourcePosition) =
if !problem.isEmpty then
report.error(em"right-associative extension method $problem", pos)
extParamss ++ mdef.paramss
def rightAssocParams =
val (rightTyParams, paramss) = mdef.paramss.span(isTypeParamClause) // first extract type parameters

paramss match
case (rightParam @ ValDefs(vparam :: Nil)) :: paramss if !vparam.mods.is(Given) =>
// must be a single parameter without `given` flag for rassoc rewrite
// we merge the extension parameters with the method parameters,
// swapping the operator arguments:
// e.g.
// extension [A](using B)(c: C)(using D)
// def %:[E](f: F)(g: G)(using H): Res = ???
// will be encoded as
// def %:[A](using B)[E](f: F)(c: C)(using D)(g: G)(using H): Res = ???
//
// If you change the names in the clauses below, also change them in right-associative-extension-methods.md
val (leftTyParamsAndLeadingUsing, leftParamAndTrailingUsing) = extParamss.span(isUsingOrTypeParamClause)

val names = (for ps <- mdef.paramss; p <- ps yield p.name).toSet[Name]

val tt = new untpd.UntypedTreeTraverser:
def traverse(tree: Tree)(using Context): Unit = tree match
case tree: Ident if names.contains(tree.name) =>
finish(s"cannot have a forward reference to ${tree.name}", tree.srcPos)
case _ => traverseChildren(tree)

for ts <- leftParamAndTrailingUsing; t <- ts do
tt.traverse(t)

leftTyParamsAndLeadingUsing ::: rightTyParams ::: rightParam :: leftParamAndTrailingUsing ::: paramss
case ValDefs(vparam :: _) :: _ =>
if vparam.mods.is(Given) then
// no explicit value parameters, so not an infix operator.
finish()
else
finish("must start with a single parameter, consider a tupled parameter instead", mdef.srcPos)
case _ =>
// no value parameters, so not an infix operator.
finish()
end rightAssocParams

cpy.DefDef(mdef)(
name = normalizeName(mdef, mdef.tpt).asTermName,
paramss =
if mdef.name.isRightAssocOperatorName then
val (rightTyParams, paramss) = mdef.paramss.span(isTypeParamClause) // first extract type parameters

paramss match
case rightParam :: paramss1 => // `rightParam` must have a single parameter and without `given` flag

def badRightAssoc(problem: String, pos: SrcPos) =
report.error(em"right-associative extension method $problem", pos)
extParamss ++ mdef.paramss

rightParam match
case ValDefs(vparam :: Nil) =>
if !vparam.mods.is(Given) then
// we merge the extension parameters with the method parameters,
// swapping the operator arguments:
// e.g.
// extension [A](using B)(c: C)(using D)
// def %:[E](f: F)(g: G)(using H): Res = ???
// will be encoded as
// def %:[A](using B)[E](f: F)(c: C)(using D)(g: G)(using H): Res = ???
//
// If you change the names of the clauses below, also change them in right-associative-extension-methods.md
val (leftTyParamsAndLeadingUsing, leftParamAndTrailingUsing) = extParamss.span(isUsingOrTypeParamClause)

val names = (for ps <- mdef.paramss; p <- ps yield p.name).toSet[Name]

val tt = new untpd.UntypedTreeTraverser:
def traverse(tree: Tree)(using Context): Unit = tree match
case tree: Ident if names.contains(tree.name) =>
badRightAssoc(s"cannot have a forward reference to ${tree.name}", tree.srcPos)
case _ => traverseChildren(tree)

for ts <- leftParamAndTrailingUsing; t <- ts do
tt.traverse(t)

leftTyParamsAndLeadingUsing ::: rightTyParams ::: rightParam :: leftParamAndTrailingUsing ::: paramss1
else
badRightAssoc("cannot start with using clause", mdef.srcPos)
case _ =>
badRightAssoc("must start with a single parameter", mdef.srcPos)
case _ =>
// no value parameters, so not an infix operator.
extParamss ++ mdef.paramss
rightAssocParams
else
extParamss ++ mdef.paramss
finish()
).withMods(mdef.mods | ExtensionMethod)
end extMethod

/** Transform extension construct to list of extension methods */
def extMethods(ext: ExtMethods)(using Context): Tree = flatTree {
Expand Down
28 changes: 28 additions & 0 deletions tests/neg/i24745.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
extension (s: String)
def f_::(using DummyImplicit): String = s.reverse
def g_::(x: (suffix: String, n: Int)): String = s"$s${x.suffix * x.n}"
def ok_::(using suffix: String, n: Int): String = s"$s${suffix * n}"
def no_::(suffix: String, n: Int): String = s"$s${suffix * n}" // error
def huh_::(using DummyImplicit)(suffix: String, n: Int): String = s"$s${suffix * n}"

def local =
extension (s: String) def f_::(using DummyImplicit): String = s.reverse
"hello, world".f_::

@main def Test =
println:
"hello, world".f_::
println:
f_::
("hello, world")
(using DummyImplicit.dummyImplicit)
println:
(suffix = "s", n = 3).g_::("hello, world")
println:
"hello, world" g_:: ("s", 3)
println:
given String = "s"
given Int = 3
"hello, world".ok_::
println:
"hello, world".huh_::("s", 3)
6 changes: 1 addition & 5 deletions tests/neg/rightassoc-extmethod.check
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
-- Error: tests/neg/rightassoc-extmethod.scala:1:23 --------------------------------------------------------------------
1 |extension (x: Int) def +: (using String): Int = x // error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| right-associative extension method cannot start with using clause
-- Error: tests/neg/rightassoc-extmethod.scala:2:23 --------------------------------------------------------------------
2 |extension (x: Int) def *: (y: Int, z: Int) = x // error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
| right-associative extension method must start with a single parameter
| right-associative extension method must start with a single parameter, consider a tupled parameter instead
3 changes: 1 addition & 2 deletions tests/neg/rightassoc-extmethod.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
extension (x: Int) def +: (using String): Int = x // error
extension (x: Int) def +: (using String): Int = x
extension (x: Int) def *: (y: Int, z: Int) = x // error

Loading