@@ -142,7 +142,15 @@ object SymOps:
142
142
import reflect ._
143
143
sym.flags.is(Flags .Artifact )
144
144
145
- def isLeftAssoc : Boolean = ! sym.name.endsWith(" :" )
145
+ /**
146
+ * note that this is not the right criterion:
147
+ * An extension method is treated as a right-associative operator (as in SLS §6.12.3)
148
+ * if it has a name ending in : and is immediately followed by a single parameter.
149
+ * https://docs.scala-lang.org/scala3/reference/contextual/right-associative-extension-methods.html
150
+ */
151
+ def isRightAssoc : Boolean = sym.name.endsWith(" :" )
152
+
153
+ def isLeftAssoc : Boolean = ! sym.isRightAssoc
146
154
147
155
def extendedSymbol : Option [reflect.ValDef ] =
148
156
import reflect .*
@@ -152,6 +160,45 @@ object SymOps:
152
160
else termParamss(1 ).params(0 )
153
161
}
154
162
163
+ def splitExtensionParamLists : (List [reflect.ParamClause ], List [reflect.ParamClause ]) =
164
+ if sym.isRightAssoc && sym.isExtensionMethod then
165
+ val unswapped @ (extPart, defPart) = sym.splitExtensionParamListsAssumingLeftAssoc
166
+ def nonUsingClauses (clauses : List [reflect.ParamClause ]) = clauses.zipWithIndex.collect{case (terms : reflect.TermParamClause , i) if ! terms.isGiven => (terms, i)}
167
+ val extNonUsingClause = nonUsingClauses(extPart)
168
+ val defNonUsingClauses = nonUsingClauses(defPart)
169
+ assert(extNonUsingClause.size == 1 )
170
+
171
+ if defNonUsingClauses.lift(0 ).map(_._1.params.size != 1 ).getOrElse(true ) // was not really right associative, see comment of isRightAssoc
172
+ then unswapped
173
+ else
174
+ val (first, i1) = extNonUsingClause(0 )
175
+ val (second, i2) = defNonUsingClauses(0 ) // since cond is false, we know lift(0) returned Some(_)
176
+ (extPart.updated(i1, second), defPart.updated(i2, first))
177
+ else
178
+ sym.splitExtensionParamListsAssumingLeftAssoc
179
+
180
+ /**
181
+ * This uses the assumption that there is the following "pos hierachy": extension paramss < DefDef < extMethod paramss
182
+ * /!\ where DefDef is the tree containing the paramss
183
+ * It wouldn't really make sense for the Def's position not to be either the "def" or the method name,
184
+ * but is not enforced
185
+ */
186
+ def splitExtensionParamListsAssumingLeftAssoc : (List [reflect.ParamClause ], List [reflect.ParamClause ]) =
187
+ val method = sym.tree.asInstanceOf [reflect.DefDef ]
188
+ val paramss = method.paramss // List[ParamClause[T]] //ParamClause[T] = List[ValDef[T]] | List[TypeDef[T]]
189
+ val defCoord = method.symbol.pos.get.start // .span.point
190
+
191
+ val res = paramss.span{
192
+ case reflect.TypeParamClause (params) => params.head.symbol.pos.get.start < defCoord // .span.start
193
+ case reflect.TermParamClause (params) =>
194
+ params.headOption
195
+ .map(_.symbol.pos.get.start < defCoord) // .span.start
196
+ .getOrElse(false ) // () is only allowed on the RHS of extensions
197
+ }
198
+ // println(method.name)
199
+ // println(res._1.map(_.params.map(_.show)).mkString("ExtensionPart:\n","\n","\n"))
200
+ // println(res._2.map(_.params.map(_.show)).mkString("NonExtensionPart:\n","\n","\n"))
201
+ res
155
202
def extendedTypeParams : List [reflect.TypeDef ] =
156
203
import reflect .*
157
204
val method = sym.tree.asInstanceOf [DefDef ]
0 commit comments