diff --git a/scaladoc-testcases/src/tests/extensionParams.scala b/scaladoc-testcases/src/tests/extensionParams.scala new file mode 100644 index 000000000000..231a8a1fefbf --- /dev/null +++ b/scaladoc-testcases/src/tests/extensionParams.scala @@ -0,0 +1,22 @@ +package tests.extensionParams + +extension [A](thiz: A) + def toTuple2[B](that: B): (A, B) = thiz -> that + +extension [A](a: A)(using Int) + def f[B](b: B): (A, B) = ??? + +extension [A](a: A)(using Int) + def ff(b: A): (A, A) = ??? + +extension [A](a: A)(using Int) + def fff(using String)(b: A): (A, A) = ??? + +extension (a: Char)(using Int) + def ffff(using String)(b: Int): Unit = ??? + +extension (a: Char)(using Int) + def fffff[B](using String)(b: B): Unit = ??? + +extension [A <: List[Char]](a: A)(using Int) + def ffffff[B](b: B): (A, B) = ??? diff --git a/scaladoc/src/dotty/tools/scaladoc/api.scala b/scaladoc/src/dotty/tools/scaladoc/api.scala index 91f0eb3cc5b4..edb5bf056865 100644 --- a/scaladoc/src/dotty/tools/scaladoc/api.scala +++ b/scaladoc/src/dotty/tools/scaladoc/api.scala @@ -41,12 +41,12 @@ enum Modifier(val name: String, val prefix: Boolean): case Opaque extends Modifier("opaque", true) case Open extends Modifier("open", true) -case class ExtensionTarget(name: String, signature: Signature, dri: DRI, position: Long) +case class ExtensionTarget(name: String, typeParams: Seq[TypeParameter], argsLists: Seq[ParametersList], signature: Signature, dri: DRI, position: Long) case class ImplicitConversion(from: DRI, to: DRI) trait ImplicitConversionProvider { def conversion: Option[ImplicitConversion] } trait Classlike -enum Kind(val name: String){ +enum Kind(val name: String): case RootPackage extends Kind("") case Package extends Kind("package") case Class(typeParams: Seq[TypeParameter], argsLists: Seq[ParametersList]) @@ -70,7 +70,6 @@ enum Kind(val name: String){ case Implicit(kind: Kind.Def | Kind.Val.type, conversion: Option[ImplicitConversion]) extends Kind(kind.name) with ImplicitConversionProvider case Unknown extends Kind("Unknown") -} enum Origin: case ImplicitlyAddedBy(name: String, dri: DRI) diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala index fc7f4ae0601b..b99b8efe5301 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala @@ -280,7 +280,14 @@ class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) ext case _ => None }.collect { case (Some(on), members) => - val sig = Signature(s"extension (${on.name}: ") ++ on.signature ++ Signature(")") + val typeSig = InlineSignatureBuilder() + .text("extension ") + .generics(on.typeParams) + .asInstanceOf[InlineSignatureBuilder].names.reverse + val argsSig = InlineSignatureBuilder() + .functionParameters(on.argsLists) + .asInstanceOf[InlineSignatureBuilder].names.reverse + val sig = typeSig ++ Signature(s"(${on.name}: ") ++ on.signature ++ Signature(")") ++ argsSig MGroup(span(sig.map(renderElement)), members.sortBy(_.name).toSeq, on.name) }.toSeq diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala index bcc0d1a47039..f7906a281a7c 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -176,7 +176,7 @@ trait ClassLikeSupport: val sgn = Inkuire.ExternalSignature( signature = Inkuire.Signature( receiver = receiver, - arguments = methodSymbol.nonExtensionParamLists.collect { + arguments = methodSymbol.nonExtensionTermParamLists.collect { case tpc@TermParamClause(params) if !tpc.isImplicit && !tpc.isGiven => params //TODO [Inkuire] Implicit parameters }.flatten.map(_.tpt.asInkuire(vars)), result = defdef.returnTpt.asInkuire(vars), @@ -242,11 +242,18 @@ trait ClassLikeSupport: private def isDocumentableExtension(s: Symbol) = !s.isHiddenByVisibility && !s.isSyntheticFunc && s.isExtensionMethod - private def parseMember(c: ClassDef)(s: Tree): Option[Member] = processTreeOpt(s)(s match + private def parseMember(c: ClassDef)(s: Tree): Option[Member] = processTreeOpt(s) { s match case dd: DefDef if isDocumentableExtension(dd.symbol) => dd.symbol.extendedSymbol.map { extSym => + val memberInfo = unwrapMemberInfo(c, dd.symbol) + val typeParams = dd.symbol.extendedTypeParams.map(mkTypeArgument(_, memberInfo.genericTypes)) + val termParams = dd.symbol.extendedTermParamLists.zipWithIndex.map { case (paramList, index) => + ParametersList(paramList.params.map(mkParameter(_, memberInfo = memberInfo.paramLists(index))), paramListModifier(paramList.params)) + } val target = ExtensionTarget( extSym.symbol.normalizedName, + typeParams, + termParams, extSym.tpt.asSignature, extSym.tpt.symbol.dri, extSym.symbol.pos.get.start @@ -302,7 +309,7 @@ trait ClassLikeSupport: Some(parseClasslike(c)) case _ => None - ) + } private def parseGivenClasslike(c: ClassDef): Member = { val parsedClasslike = parseClasslike(c) @@ -470,8 +477,8 @@ trait ClassLikeSupport: specificKind: (Kind.Def => Kind) = identity ): Member = val method = methodSymbol.tree.asInstanceOf[DefDef] - val paramLists: List[TermParamClause] = methodSymbol.nonExtensionParamLists - val genericTypes = if (methodSymbol.isClassConstructor) Nil else method.leadingTypeParams + val paramLists: List[TermParamClause] = methodSymbol.nonExtensionTermParamLists + val genericTypes: List[TypeDef] = if (methodSymbol.isClassConstructor) Nil else methodSymbol.nonExtensionLeadingTypeParams val memberInfo = unwrapMemberInfo(c, methodSymbol) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala index 6563ba89f8b3..b98436a318c0 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala @@ -141,14 +141,52 @@ object SymOps: else termParamss(1).params(0) } - def nonExtensionParamLists: List[reflect.TermParamClause] = + def extendedTypeParams: List[reflect.TypeDef] = + import reflect.* + val method = sym.tree.asInstanceOf[DefDef] + method.leadingTypeParams + + def extendedTermParamLists: List[reflect.TermParamClause] = + import reflect.* + if sym.nonExtensionLeadingTypeParams.nonEmpty then + sym.nonExtensionParamLists.takeWhile { + case _: TypeParamClause => false + case _ => true + }.collect { + case tpc: TermParamClause => tpc + } + else + List.empty + + def nonExtensionTermParamLists: List[reflect.TermParamClause] = + import reflect.* + if sym.nonExtensionLeadingTypeParams.nonEmpty then + sym.nonExtensionParamLists.dropWhile { + case _: TypeParamClause => false + case _ => true + }.drop(1).collect { + case tpc: TermParamClause => tpc + } + else + sym.nonExtensionParamLists.collect { + case tpc: TermParamClause => tpc + } + + def nonExtensionParamLists: List[reflect.ParamClause] = import reflect.* val method = sym.tree.asInstanceOf[DefDef] if sym.isExtensionMethod then - val params = method.termParamss - if sym.isLeftAssoc || params.size == 1 then params.tail - else params.head :: params.tail.drop(1) - else method.termParamss + val params = method.paramss + val toDrop = if method.leadingTypeParams.nonEmpty then 2 else 1 + if sym.isLeftAssoc || params.size == 1 then params.drop(toDrop) + else params.head :: params.tail.drop(toDrop) + else method.paramss + + def nonExtensionLeadingTypeParams: List[reflect.TypeDef] = + import reflect.* + sym.nonExtensionParamLists.collectFirst { + case TypeParamClause(params) => params + }.toList.flatten end extension diff --git a/scaladoc/src/dotty/tools/scaladoc/transformers/ImplicitMembersExtensionTransformer.scala b/scaladoc/src/dotty/tools/scaladoc/transformers/ImplicitMembersExtensionTransformer.scala index 9d95f4462b78..0fa6b9c5d3cf 100644 --- a/scaladoc/src/dotty/tools/scaladoc/transformers/ImplicitMembersExtensionTransformer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/transformers/ImplicitMembersExtensionTransformer.scala @@ -26,7 +26,7 @@ class ImplicitMembersExtensionTransformer(using DocContext) extends(Module => Mo val MyDri = c.dri def collectApplicableMembers(source: Member): Seq[Member] = source.members.flatMap { - case m @ Member(_, _, _, Kind.Extension(ExtensionTarget(_, _, MyDri, _), _), Origin.RegularlyDefined) => + case m @ Member(_, _, _, Kind.Extension(ExtensionTarget(_, _, _, _, MyDri, _), _), Origin.RegularlyDefined) => val kind = m.kind match case d: Kind.Def => d case _ => Kind.Def(Nil, Nil) diff --git a/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureUtils.scala b/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureUtils.scala index 2973cd537354..8f67cb9ec9b9 100644 --- a/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureUtils.scala +++ b/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureUtils.scala @@ -24,75 +24,75 @@ trait SignatureBuilder extends ScalaSignatureUtils { def memberName(name: String, dri: DRI) = text(name) def list[E]( - elements: Seq[E], - prefix: String = "", - suffix: String = "", - separator: String = ", ", - forcePrefixAndSuffix: Boolean = false - )( - elemOp: (SignatureBuilder, E) => SignatureBuilder - ): SignatureBuilder = elements match { - case Nil => if forcePrefixAndSuffix then this.text(prefix + suffix) else this - case head :: tail => - tail.foldLeft(elemOp(text(prefix), head))((b, e) => elemOp(b.text(separator), e)).text(suffix) - } + elements: Seq[E], + prefix: String = "", + suffix: String = "", + separator: String = ", ", + forcePrefixAndSuffix: Boolean = false + )( + elemOp: (SignatureBuilder, E) => SignatureBuilder + ): SignatureBuilder = elements match { + case Nil => if forcePrefixAndSuffix then this.text(prefix + suffix) else this + case head :: tail => + tail.foldLeft(elemOp(text(prefix), head))((b, e) => elemOp(b.text(separator), e)).text(suffix) + } def annotationsBlock(d: Member): SignatureBuilder = - d.annotations.foldLeft(this){ (bdr, annotation) => bdr.buildAnnotation(annotation)} - - def annotationsInline(d: Parameter): SignatureBuilder = - d.annotations.foldLeft(this){ (bdr, annotation) => bdr.buildAnnotation(annotation) } + d.annotations.foldLeft(this){ (bdr, annotation) => bdr.buildAnnotation(annotation)} - def annotationsInline(t: TypeParameter): SignatureBuilder = - t.annotations.foldLeft(this){ (bdr, annotation) => bdr.buildAnnotation(annotation) } + def annotationsInline(d: Parameter): SignatureBuilder = + d.annotations.foldLeft(this){ (bdr, annotation) => bdr.buildAnnotation(annotation) } - private def buildAnnotation(a: Annotation): SignatureBuilder = - text("@").driLink(a.dri.location.split('.').last, a.dri).buildAnnotationParams(a).text(" ") + def annotationsInline(t: TypeParameter): SignatureBuilder = + t.annotations.foldLeft(this){ (bdr, annotation) => bdr.buildAnnotation(annotation) } - private def buildAnnotationParams(a: Annotation): SignatureBuilder = - if !a.params.isEmpty then - val params = a.params.filterNot { - case Annotation.LinkParameter(_, _, text) => text == "_" - case _ => false - } - list(params, "(", ")", ", "){ (bdr, param) => bdr.buildAnnotationParameter(param)} - else this + private def buildAnnotation(a: Annotation): SignatureBuilder = + text("@").driLink(a.dri.location.split('.').last, a.dri).buildAnnotationParams(a).text(" ") - private def addParameterName(txt: Option[String]): SignatureBuilder = txt match { - case Some(name) => this.text(s"$name = ") - case _ => this + private def buildAnnotationParams(a: Annotation): SignatureBuilder = + if !a.params.isEmpty then + val params = a.params.filterNot { + case Annotation.LinkParameter(_, _, text) => text == "_" + case _ => false } + list(params, "(", ")", ", "){ (bdr, param) => bdr.buildAnnotationParameter(param)} + else this - private def buildAnnotationParameter(a: Annotation.AnnotationParameter): SignatureBuilder = a match { - case Annotation.PrimitiveParameter(name, value) => - addParameterName(name).text(value) - case Annotation.LinkParameter(name, dri, text) => - addParameterName(name).driLink(text, dri) - case Annotation.UnresolvedParameter(name, value) => - addParameterName(name).text(value) - } + private def addParameterName(txt: Option[String]): SignatureBuilder = txt match { + case Some(name) => this.text(s"$name = ") + case _ => this + } - def modifiersAndVisibility(t: Member, kind: String) = - val (prefixMods, suffixMods) = t.modifiers.partition(_.prefix) - val all = prefixMods.map(_.name) ++ Seq(t.visibility.asSignature) ++ suffixMods.map(_.name) + private def buildAnnotationParameter(a: Annotation.AnnotationParameter): SignatureBuilder = a match { + case Annotation.PrimitiveParameter(name, value) => + addParameterName(name).text(value) + case Annotation.LinkParameter(name, dri, text) => + addParameterName(name).driLink(text, dri) + case Annotation.UnresolvedParameter(name, value) => + addParameterName(name).text(value) + } - text(all.toSignatureString()).text(kind + " ") + def modifiersAndVisibility(t: Member, kind: String) = + val (prefixMods, suffixMods) = t.modifiers.partition(_.prefix) + val all = prefixMods.map(_.name) ++ Seq(t.visibility.asSignature) ++ suffixMods.map(_.name) - def generics(on: Seq[TypeParameter]) = list(on.toList, "[", "]"){ (bdr, e) => - bdr.annotationsInline(e).text(e.variance).memberName(e.name, e.dri).signature(e.signature) - } + text(all.toSignatureString()).text(kind + " ") - def functionParameters(params: Seq[ParametersList]) = - if params.isEmpty then this.text("") - else if params.size == 1 && params(0).parameters == Nil then this.text("()") - else this.list(params, separator = ""){ (bld, pList) => - bld.list(pList.parameters, s"(${pList.modifiers}", ")", forcePrefixAndSuffix = true){ (bld, p) => - val annotationsAndModifiers = bld.annotationsInline(p) - .text(p.modifiers) - val name = p.name.fold(annotationsAndModifiers)(annotationsAndModifiers.memberName(_, p.dri).text(": ")) - name.signature(p.signature) - } + def generics(on: Seq[TypeParameter]) = list(on.toList, "[", "]"){ (bdr, e) => + bdr.annotationsInline(e).text(e.variance).memberName(e.name, e.dri).signature(e.signature) + } + + def functionParameters(params: Seq[ParametersList]) = + if params.isEmpty then this.text("") + else if params.size == 1 && params(0).parameters == Nil then this.text("()") + else this.list(params, separator = "") { (bld, pList) => + bld.list(pList.parameters, s"(${pList.modifiers}", ")", forcePrefixAndSuffix = true) { (bld, p) => + val annotationsAndModifiers = bld.annotationsInline(p) + .text(p.modifiers) + val name = p.name.fold(annotationsAndModifiers)(annotationsAndModifiers.memberName(_, p.dri).text(": ")) + name.signature(p.signature) } + } } trait ScalaSignatureUtils: