Skip to content

Commit d0f7300

Browse files
committed
Add new heuristic for extension methods
Uses the assumption that there is the following "pos hierachy": extension paramss < DefDef < extMethod paramss
1 parent 5671d26 commit d0f7300

File tree

1 file changed

+48
-1
lines changed

1 file changed

+48
-1
lines changed

scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,15 @@ object SymOps:
142142
import reflect._
143143
sym.flags.is(Flags.Artifact)
144144

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
146154

147155
def extendedSymbol: Option[reflect.ValDef] =
148156
import reflect.*
@@ -152,6 +160,45 @@ object SymOps:
152160
else termParamss(1).params(0)
153161
}
154162

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
155202
def extendedTypeParams: List[reflect.TypeDef] =
156203
import reflect.*
157204
val method = sym.tree.asInstanceOf[DefDef]

0 commit comments

Comments
 (0)