Skip to content

Commit eeb8340

Browse files
authored
Merge pull request #11691 from dotty-staging/fix-right-assoc-extension
fix param desugar in right-associative extensions
2 parents 9522b0f + 8505258 commit eeb8340

File tree

5 files changed

+95
-30
lines changed

5 files changed

+95
-30
lines changed

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

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -902,28 +902,37 @@ object desugar {
902902
defDef(
903903
cpy.DefDef(mdef)(
904904
name = normalizeName(mdef, ext).asTermName,
905-
paramss = mdef.paramss match
906-
case params1 :: paramss1 if mdef.name.isRightAssocOperatorName =>
907-
def badRightAssoc(problem: String) =
908-
report.error(i"right-associative extension method $problem", mdef.srcPos)
909-
ext.paramss ++ mdef.paramss
910-
def noVParam = badRightAssoc("must start with a single parameter")
911-
def checkVparam(params: ParamClause) = params match
912-
case ValDefs(vparam :: Nil) =>
913-
if !vparam.mods.is(Given) then
914-
val (leadingUsing, otherExtParamss) = ext.paramss.span(isUsingOrTypeParamClause)
915-
leadingUsing ::: params1 :: otherExtParamss ::: paramss1
916-
else badRightAssoc("cannot start with using clause")
905+
paramss =
906+
if mdef.name.isRightAssocOperatorName then
907+
val (typaramss, paramss) = mdef.paramss.span(isTypeParamClause) // first extract type parameters
908+
909+
paramss match
910+
case params :: paramss1 => // `params` must have a single parameter and without `given` flag
911+
912+
def badRightAssoc(problem: String) =
913+
report.error(i"right-associative extension method $problem", mdef.srcPos)
914+
ext.paramss ++ mdef.paramss
915+
916+
params match
917+
case ValDefs(vparam :: Nil) =>
918+
if !vparam.mods.is(Given) then
919+
// we merge the extension parameters with the method parameters,
920+
// swapping the operator arguments:
921+
// e.g.
922+
// extension [A](using B)(c: C)(using D)
923+
// def %:[E](f: F)(g: G)(using H): Res = ???
924+
// will be encoded as
925+
// def %:[A](using B)[E](f: F)(c: C)(using D)(g: G)(using H): Res = ???
926+
val (leadingUsing, otherExtParamss) = ext.paramss.span(isUsingOrTypeParamClause)
927+
leadingUsing ::: typaramss ::: params :: otherExtParamss ::: paramss1
928+
else
929+
badRightAssoc("cannot start with using clause")
930+
case _ =>
931+
badRightAssoc("must start with a single parameter")
917932
case _ =>
918-
noVParam
919-
params1 match
920-
case TypeDefs(_) => paramss1 match
921-
case params2 :: _ => checkVparam(params2)
922-
case _ => noVParam
923-
case _ =>
924-
checkVparam(params1)
925-
926-
case _ =>
933+
// no value parameters, so not an infix operator.
934+
ext.paramss ++ mdef.paramss
935+
else
927936
ext.paramss ++ mdef.paramss
928937
).withMods(mdef.mods | ExtensionMethod)
929938
)

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,10 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] =>
251251
case TypeDefs(_) => true
252252
case _ => isUsingClause(params)
253253

254+
def isTypeParamClause(params: ParamClause)(using Context): Boolean = params match
255+
case TypeDefs(_) => true
256+
case _ => false
257+
254258
private val languageSubCategories = Set(nme.experimental, nme.deprecated)
255259

256260
/** If `path` looks like a language import, `Some(name)` where name

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -815,14 +815,30 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
815815
if isExtension then
816816
val paramss =
817817
if tree.name.isRightAssocOperatorName then
818-
// we have the encoding: leadingUsingOrTypeParams rightParam trailingUsing leftParam
819-
// we need to swap rightParam and leftParam
820-
val (leadingUsing, rest1) = tree.paramss.span(isUsingOrTypeParamClause)
821-
val (rightParamss, rest2) = rest1.splitAt(1)
822-
val (trailingUsing, rest3) = rest2.span(isUsingClause)
823-
val (leftParamss, rest4) = rest3.splitAt(1)
818+
// we have the following encoding of tree.paramss:
819+
// (leadingTyParamss ++ leadingUsing
820+
// ++ rightTyParamss ++ rightParamss
821+
// ++ leftParamss ++ trailingUsing ++ rest)
822+
// e.g.
823+
// extension [A](using B)(c: C)(using D)
824+
// def %:[E](f: F)(g: G)(using H): Res = ???
825+
// will have the following values:
826+
// - leadingTyParamss = List(`[A]`)
827+
// - leadingUsing = List(`(using B)`)
828+
// - rightTyParamss = List(`[E]`)
829+
// - rightParamss = List(`(f: F)`)
830+
// - leftParamss = List(`(c: C)`)
831+
// - trailingUsing = List(`(using D)`)
832+
// - rest = List(`(g: G)`, `(using H)`)
833+
// we need to swap (rightTyParams ++ rightParamss) with (leftParamss ++ trailingUsing)
834+
val (leadingTyParamss, rest1) = tree.paramss.span(isTypeParamClause)
835+
val (leadingUsing, rest2) = rest1.span(isUsingClause)
836+
val (rightTyParamss, rest3) = rest2.span(isTypeParamClause)
837+
val (rightParamss, rest4) = rest3.splitAt(1)
838+
val (leftParamss, rest5) = rest4.splitAt(1)
839+
val (trailingUsing, rest6) = rest5.span(isUsingClause)
824840
if leftParamss.nonEmpty then
825-
leadingUsing ::: leftParamss ::: trailingUsing ::: rightParamss ::: rest4
841+
leadingTyParamss ::: leadingUsing ::: leftParamss ::: trailingUsing ::: rightTyParamss ::: rightParamss ::: rest6
826842
else
827843
tree.paramss // it wasn't a binary operator, after all.
828844
else

tests/pos/i11583.scala

Lines changed: 0 additions & 2 deletions
This file was deleted.

tests/run/i11583.scala

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
class Context:
2+
type Type
3+
type Term
4+
5+
class Env:
6+
type Extra
7+
8+
// TODO: enable after https://github.com/lampepfl/dotty/issues/11700 is fixed
9+
// extension [Ctx <: Context](using ctx: Ctx)(tpe: ctx.Type)(using env: Env)
10+
// /** essentially: `extension (s: String) def &&:(b: Boolean)(i: Int)`
11+
// * but exercises the RefinedPrinter and safety of reordering parameters
12+
// */
13+
// def &&:[T <: ctx.Term](trm: T)(ext: env.Extra): (ctx.Type, T, env.Extra) = (tpe, trm, ext)
14+
15+
extension [Ctx <: Context](using ctx: Ctx)(tpe: String)(using env: Env)
16+
def :#:[T <: Boolean](trm: T)(ext: env.Extra): (String, T, env.Extra) = (tpe, trm, ext)
17+
18+
extension [A](a: A)
19+
def :*:[T <: Tuple](t: T): A *: T = a *: t
20+
21+
@main def Test =
22+
23+
given Context with
24+
type Type = String
25+
type Term = Boolean
26+
27+
given Env with
28+
type Extra = Int
29+
30+
val t1: (String, Boolean, Int) = true.:#:("hello")(23)
31+
// val t2: (String, Boolean, Int) = true.&&:("hello")(23)
32+
val t3: (String, Boolean, Int) = "hello" :*: (true, 23)
33+
val t4: (String, Boolean, Int) = (true, 23).:*:("hello")
34+
35+
assert(t1 == ("hello", true, 23))
36+
// assert(t2 == ("hello", true, 23))
37+
assert(t3 == ("hello", true, 23))
38+
assert(t4 == ("hello", true, 23))

0 commit comments

Comments
 (0)