diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index ab77350be26c..0979e09d7e17 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -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.* @@ -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 { diff --git a/tests/neg/i24745.scala b/tests/neg/i24745.scala new file mode 100644 index 000000000000..855bad5e7b0f --- /dev/null +++ b/tests/neg/i24745.scala @@ -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) diff --git a/tests/neg/rightassoc-extmethod.check b/tests/neg/rightassoc-extmethod.check index a1d2328ed2ff..80f02851474c 100644 --- a/tests/neg/rightassoc-extmethod.check +++ b/tests/neg/rightassoc-extmethod.check @@ -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 diff --git a/tests/neg/rightassoc-extmethod.scala b/tests/neg/rightassoc-extmethod.scala index 4a136ca6eac3..9fbea9d4a04a 100644 --- a/tests/neg/rightassoc-extmethod.scala +++ b/tests/neg/rightassoc-extmethod.scala @@ -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 -