From 73a7ac51a59c53843f6a8e8df29b6268cfac9e72 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 1/3] 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 --- .../src/tests/extendsCall.scala | 2 +- .../src/tests/optionalParams.scala | 23 +++++++++++++++++++ .../scaladoc/tasty/ClassLikeSupport.scala | 3 ++- .../tools/scaladoc/tasty/TypesSupport.scala | 4 ++-- .../TranslatableSignaturesTestCases.scala | 2 ++ 5 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 4514cb42c9c3..840ec0ac4c0b 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -443,12 +443,13 @@ trait ClassLikeSupport: val inlinePrefix = if argument.symbol.flags.is(Flags.Inline) then "inline " else "" val nameIfNotSynthetic = Option.when(!argument.symbol.flags.is(Flags.Synthetic))(argument.symbol.normalizedName) val name = argument.symbol.normalizedName + val defaultValue = Option.when(argument.symbol.flags.is(Flags.HasDefault))(Plain(" = ...")) api.TermParameter( argument.symbol.getAnnotations(), inlinePrefix + prefix(argument.symbol), nameIfNotSynthetic, argument.symbol.dri, - memberInfo.get(name).fold(argument.tpt.asSignature(classDef))(_.asSignature(classDef)), + memberInfo.get(name).fold(argument.tpt.asSignature(classDef))(_.asSignature(classDef)) :++ defaultValue, isExtendedSymbol, isGrouped ) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index 8cef8af12604..1d5aa4e3a43a 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -233,7 +233,7 @@ trait TypesSupport: tpe(tp.typeSymbol) case _: TermRef | _: ParamRef => val suffix = if tp.typeSymbol == Symbol.noSymbol then tpe(typeName).l else tpe(tp.typeSymbol) - inner(qual)(using skipTypeSuffix = true) ++ plain(".").l ++ suffix + inner(qual)(using indent = indent, skipTypeSuffix = true) ++ plain(".").l ++ suffix case ThisType(tr) => findSupertype(elideThis, tr.typeSymbol) match case Some((sym, AppliedType(tr2, args))) => @@ -250,7 +250,7 @@ trait TypesSupport: val sig = inParens(inner(qual)(using indent = indent, skipTypeSuffix = true), wrapping) sig ++ plain(".").l ++ tpe(tp.typeSymbol) case _ => - val sig = inParens(inner(qual, skipThisTypePrefix), wrapping) + val sig = inParens(inner(qual), wrapping) sig ++ keyword("#").l ++ tpe(tp.typeSymbol) } diff --git a/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala b/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala index 78de0ce67124..5469c06c8eb1 100644 --- a/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala +++ b/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala @@ -124,3 +124,5 @@ class InfixTypes extends SignatureTest("infixTypes", SignatureTest.all) class ExtendsCall extends SignatureTest("extendsCall", SignatureTest.all) class RightAssocExtension extends SignatureTest("rightAssocExtension", SignatureTest.all) + +class OptionalParams extends SignatureTest("optionalParams", SignatureTest.all) From 1e637e9356535ecb3db530c75eda326382d1891a Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Tue, 12 Aug 2025 20:34:45 +0200 Subject: [PATCH 2/3] 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][modified] From c7d96898163dcb717fe7e62e077dbc84c8527fde Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Tue, 12 Aug 2025 20:49:46 +0200 Subject: [PATCH 3/3] bugfix: Fix tests using newer given syntax --- tests/neg/i19414.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/neg/i19414.scala b/tests/neg/i19414.scala index 8843441e81f2..bb275ad943b7 100644 --- a/tests/neg/i19414.scala +++ b/tests/neg/i19414.scala @@ -9,7 +9,7 @@ class Printer given Writer[JsValue] = ??? given Writer[JsObject] = ??? -given [B: Writer] => (printer: Printer = new Printer) => BodySerializer[B] = ??? +given [B: Writer](using printer: Printer = new Printer): BodySerializer[B] = ??? def f: Unit = summon[BodySerializer[JsObject]] // error: Ambiguous given instances