From e464c18bfe5d0abe9a2c99806796a23d5e34adb4 Mon Sep 17 00:00:00 2001 From: Florian3k Date: Mon, 23 Dec 2024 22:29:38 +0100 Subject: [PATCH] Scaladoc: add support for named tuples --- .../src/tests/namedTuples.scala | 23 +++++++ .../tools/scaladoc/tasty/TypesSupport.scala | 65 ++++++++++--------- .../TranslatableSignaturesTestCases.scala | 2 + 3 files changed, 61 insertions(+), 29 deletions(-) create mode 100644 scaladoc-testcases/src/tests/namedTuples.scala diff --git a/scaladoc-testcases/src/tests/namedTuples.scala b/scaladoc-testcases/src/tests/namedTuples.scala new file mode 100644 index 000000000000..30a83e7e01b0 --- /dev/null +++ b/scaladoc-testcases/src/tests/namedTuples.scala @@ -0,0 +1,23 @@ +package tests.namedTuples + +import language.experimental.namedTuples +import NamedTuple.* + +type Person = (name: String, age: Int) + +type Person2 = NamedTuple[("age2", "name2"), (Int, String)] //expected: type Person2 = (age2: Int, name2: String) + +val x = (name = "Bob", age = 25) //expected: val x: (name: String, age: Int) + +def foo(p1: (age: Int, name: String), p2: (name: String, age: Int)): Nothing + = ??? + +def invalid1: NamedTuple[("age", String), (Int, Int)] + = ??? + +def invalid2: NamedTuple[("age", "name"), (Int, Int, Int)] + = ??? + +def invalid3: NamedTuple[("age", "name", "something"), (Int, Int)] + = ??? + diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index 855678a091d2..15c3071c38c9 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -204,36 +204,43 @@ trait TypesSupport: prefix ++ plain("{ ").l ++ refinedElems.flatMap(e => parseRefinedElem(e.name, e.info)) ++ plain(" }").l } } + + case AppliedType(tpe, args) if defn.isTupleClass(tpe.typeSymbol) && args.length > 1 => + inParens(commas(args.map(inner(_)))) + + case AppliedType(namedTuple, List(AppliedType(tuple1, names), AppliedType(tuple2, types))) + if namedTuple.typeSymbol == Symbol.requiredModule("scala.NamedTuple").typeMember("NamedTuple") + && defn.isTupleClass(tuple1.typeSymbol) && defn.isTupleClass(tuple2.typeSymbol) && names.length == types.length + && names.forall { case ConstantType(StringConstant(_)) => true case _ => false } => + val elems = names + .collect { case ConstantType(StringConstant(s)) => s } + .zip(types) + .map((name, tpe) => plain(name) +: plain(": ") +: inner(tpe)) + inParens(commas(elems)) + + case t @ AppliedType(tpe, List(lhs, rhs)) if isInfix(t) => + inParens(inner(lhs), shouldWrapInParens(lhs, t, true)) + ++ plain(" ").l + ++ inner(tpe) + ++ plain(" ").l + ++ inParens(inner(rhs), shouldWrapInParens(rhs, t, false)) + + case t @ AppliedType(tpe, args) if t.isFunctionType => + val arrow = if t.isContextFunctionType then " ?=> " else " => " + args match + case Nil => Nil + case List(rtpe) => plain("()").l ++ keyword(arrow).l ++ inner(rtpe) + case List(arg, rtpe) => + val wrapInParens = stripAnnotated(arg) match + case _: TermRef | _: TypeRef | _: ConstantType | _: ParamRef => false + case at: AppliedType if !isInfix(at) && !at.isFunctionType && !at.isTupleN => false + case _ => true + inParens(inner(arg), wrapInParens) ++ keyword(arrow).l ++ inner(rtpe) + case _ => + plain("(").l ++ commas(args.init.map(inner(_))) ++ plain(")").l ++ keyword(arrow).l ++ inner(args.last) + case t @ AppliedType(tpe, typeList) => - import dotty.tools.dotc.util.Chars._ - if defn.isTupleClass(tpe.typeSymbol) && typeList.length != 1 then - typeList match - case Nil => Nil - case args => inParens(commas(args.map(inner(_)))) - else if isInfix(t) then - val lhs = typeList.head - val rhs = typeList.last - inParens(inner(lhs), shouldWrapInParens(lhs, t, true)) - ++ plain(" ").l - ++ inner(tpe) - ++ plain(" ").l - ++ inParens(inner(rhs), shouldWrapInParens(rhs, t, false)) - else if t.isFunctionType then - val arrow = if t.isContextFunctionType then " ?=> " else " => " - typeList match - case Nil => - Nil - case Seq(rtpe) => - plain("()").l ++ keyword(arrow).l ++ inner(rtpe) - case Seq(arg, rtpe) => - val partOfSignature = stripAnnotated(arg) match - case _: TermRef | _: TypeRef | _: ConstantType | _: ParamRef => inner(arg) - case at: AppliedType if !isInfix(at) && !at.isFunctionType && !at.isTupleN => inner(arg) - case _ => inParens(inner(arg)) - partOfSignature ++ keyword(arrow).l ++ inner(rtpe) - case args => - plain("(").l ++ commas(args.init.map(inner(_))) ++ plain(")").l ++ keyword(arrow).l ++ inner(args.last) - else inner(tpe) ++ plain("[").l ++ commas(typeList.map { t => t match + inner(tpe) ++ plain("[").l ++ commas(typeList.map { t => t match case _: TypeBounds => keyword("_").l ++ inner(t) case _ => topLevelProcess(t) }) ++ plain("]").l diff --git a/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala b/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala index bfa2a372827a..4ed17662fff8 100644 --- a/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala +++ b/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala @@ -124,3 +124,5 @@ class ExtendsCall extends SignatureTest("extendsCall", SignatureTest.all) class RefinedFunctionTypes extends SignatureTest("refinedFunctionTypes", SignatureTest.all) class RightAssocExtension extends SignatureTest("rightAssocExtension", SignatureTest.all) + +class NamedTuples extends SignatureTest("namedTuples", SignatureTest.all)