From cd442cae66eaf762ac72af83ec83a81014badc2b Mon Sep 17 00:00:00 2001 From: katrinafyi <39479354+katrinafyi@users.noreply.github.com> Date: Tue, 12 Aug 2025 18:07:08 +1000 Subject: [PATCH] scaladoc: indicate optional parameters with `= ...` (#23676) currently, there is no indication in the scaladoc when parameters may be optional. this leads to [long and overwhelming signatures][1] which is not at all user-friendly. users are forced to first intuit that this method *might* have some optional parameters, then manually find [the source code][2] to learn which parameters are optional. [1]: https://javadoc.io/static/com.lihaoyi/os-lib_3/0.11.5/os/proc.html#call-fffff910 [2]: https://github.com/com-lihaoyi/os-lib/blob/0.11.5/os/src/ProcessOps.scala#L192-L205 this PR suffixes `= ...` after the type signature of optional parameters. this makes it possible to tell that these parameters are optional and may be omitted. this applies to both method parameters and class parameters. the format is intentionally similar to the definition of such optional parameters in code: ```scala // new scaladoc display: def f(x: Int, s: String = ...): Nothing // code: def f(x: Int, s: String = "a"): Nothing ``` of course, the `...` term is different. i think this is a reasonable choice because (1) ellipsis commonly represents something present but omitted, and (2) it is not valid Scala, so there is no risk someone will think this is denoting a literal default value of `...`. a proper ellipsis character (rather than 3 periods) could also be considered, but i found that looked out of place amongst the monospace signature. about displaying the default value itself, this PR does not display the default value. this is because of anticipated difficulties around displaying an expression. this could be re-visited in future, but i think it should not hold up this PR. i believe that this PR alone is already a substantial improvement for the documentation of optional parameters. finally, here is a screenshot of the scaladoc from the new optionalParams.scala test case: image this might be related to https://github.com/scala/scala3/issues/13424 [Cherry-picked d2404688091a6a40b80e83df72072efa4fea8f22] --- .../src/tests/extendsCall.scala | 2 +- .../src/tests/optionalParams.scala | 23 +++++++++++++++++++ .../scaladoc/tasty/ClassLikeSupport.scala | 7 +++--- .../TranslatableSignaturesTestCases.scala | 2 ++ 4 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 scaladoc-testcases/src/tests/optionalParams.scala diff --git a/scaladoc-testcases/src/tests/extendsCall.scala b/scaladoc-testcases/src/tests/extendsCall.scala index b90af8162e15..3ccd70de4216 100644 --- a/scaladoc-testcases/src/tests/extendsCall.scala +++ b/scaladoc-testcases/src/tests/extendsCall.scala @@ -3,4 +3,4 @@ package extendsCall class Impl() extends Base(Seq.empty, c = "-") //expected: class Impl() extends Base -class Base(val a: Seq[String], val b: String = "", val c: String = "") //expected: class Base(val a: Seq[String], val b: String, val c: String) +class Base(val a: Seq[String], val b: String = "", val c: String = "") //expected: class Base(val a: Seq[String], val b: String = ..., val c: String = ...) diff --git a/scaladoc-testcases/src/tests/optionalParams.scala b/scaladoc-testcases/src/tests/optionalParams.scala new file mode 100644 index 000000000000..551e14f7f811 --- /dev/null +++ b/scaladoc-testcases/src/tests/optionalParams.scala @@ -0,0 +1,23 @@ +package tests +package optionalParams + +class C(val a: Seq[String], val b: String = "", var c: String = "") //expected: class C(val a: Seq[String], val b: String = ..., var c: String = ...) +{ + def m(x: Int, s: String = "a"): Nothing //expected: def m(x: Int, s: String = ...): Nothing + = ??? +} + +def f(x: Int, s: String = "a"): Nothing //expected: def f(x: Int, s: String = ...): Nothing + = ??? + +extension (y: Int) + def ext(x: Int = 0): Int //expected: def ext(x: Int = ...): Int + = 0 + +def byname(s: => String = "a"): Int //expected: def byname(s: => String = ...): Int + = 0 + +enum E(val x: Int = 0) //expected: enum E(val x: Int = ...) +{ + case E1(y: Int = 10) extends E(y) //expected: final case class E1(y: Int = ...) extends E +} diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala index 690a19c6e78b..1a64625d491c 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -454,14 +454,15 @@ trait ClassLikeSupport: val inlinePrefix = if symbol.flags.is(Flags.Inline) then "inline " else "" val name = symbol.normalizedName val nameIfNotSynthetic = Option.when(!symbol.flags.is(Flags.Synthetic))(name) + val defaultValue = Option.when(symbol.flags.is(Flags.HasDefault))(Plain(" = ...")) api.TermParameter( symbol.getAnnotations(), inlinePrefix + prefix(symbol), nameIfNotSynthetic, symbol.dri, - argument.tpt.asSignature(classDef, symbol.owner), - isExtendedSymbol, - isGrouped + argument.tpt.asSignature(classDef, symbol.owner) :++ defaultValue, + isExtendedSymbol = isExtendedSymbol, + isGrouped = isGrouped ) def mkTypeArgument( diff --git a/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala b/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala index 34e9bc128402..98656d3b5c4a 100644 --- a/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala +++ b/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala @@ -130,3 +130,5 @@ class RightAssocExtension extends SignatureTest("rightAssocExtension", Signature class NamedTuples extends SignatureTest("namedTuples", SignatureTest.all) class InnerClasses extends SignatureTest("innerClasses", SignatureTest.all) + +class OptionalParams extends SignatureTest("optionalParams", SignatureTest.all)