From 1a6cfc6c0ee4d4f69d288c551c980cf3e84fff2e Mon Sep 17 00:00:00 2001 From: Florian3k Date: Thu, 5 Dec 2024 20:48:26 +0100 Subject: [PATCH 1/4] scaladoc: fixes and improvements to context bounds and extension methods --- .../src/tests/classSignatureTestSource.scala | 2 + .../src/tests/contextBounds.scala | 14 +- scaladoc-testcases/src/tests/exports1.scala | 2 +- .../src/tests/extensionMethodSignatures.scala | 21 +- .../scaladoc/tasty/ClassLikeSupport.scala | 216 +++++------------- .../tools/scaladoc/tasty/TypesSupport.scala | 9 +- 6 files changed, 101 insertions(+), 163 deletions(-) diff --git a/scaladoc-testcases/src/tests/classSignatureTestSource.scala b/scaladoc-testcases/src/tests/classSignatureTestSource.scala index 4d4ebf9578ec..0f501be16909 100644 --- a/scaladoc-testcases/src/tests/classSignatureTestSource.scala +++ b/scaladoc-testcases/src/tests/classSignatureTestSource.scala @@ -17,6 +17,8 @@ abstract class Documentation[T, A <: Int, B >: String, -X, +Y](c1: String, val c def this(x: T) = this() + //expected: def toArray[B >: T : ClassTag]: Array[B] + class innerDocumentationClass { diff --git a/scaladoc-testcases/src/tests/contextBounds.scala b/scaladoc-testcases/src/tests/contextBounds.scala index 794af0b8b8f8..7d20ba9d8216 100644 --- a/scaladoc-testcases/src/tests/contextBounds.scala +++ b/scaladoc-testcases/src/tests/contextBounds.scala @@ -4,6 +4,16 @@ package contextBounds import scala.reflect.ClassTag class A: + type :+:[X, Y] = [Z] =>> Map[Z, (X, Y)] + + extension [T : ([X] =>> String) : ([X] =>> Int)](x: Int) + def foo[U : ([X] =>> String)](y: Int): Nothing + = ??? + def bar[W : T match { case String => List case Int => Option } : Set]: Nothing + = ??? + def baz[V : Int :+: String : Option]: Nothing + = ??? + def basic[A : ClassTag]: A = ??? @@ -35,5 +45,5 @@ class A: // = 1 class Outer[A]: - def falsePositiveInner[T](implicit evidence$3: ClassTag[A]): Int - = 1 \ No newline at end of file + def falsePositiveInner[T](implicit evidence$3: ClassTag[A]): Int //expected: def falsePositiveInner[T]: Int + = 1 diff --git a/scaladoc-testcases/src/tests/exports1.scala b/scaladoc-testcases/src/tests/exports1.scala index f719bca35eb1..a2f94731260d 100644 --- a/scaladoc-testcases/src/tests/exports1.scala +++ b/scaladoc-testcases/src/tests/exports1.scala @@ -14,7 +14,7 @@ class A: //unexpected = 1 var aVar1: 1 = 1 - type HKT[T[_], X] //expected: final type HKT = [T[_], X] =>> a.HKT[T, X] + type HKT[T[_], X] //expected: final type HKT = a.HKT = T[X] type SomeRandomType = (List[?] | Seq[?]) & String //expected: final type SomeRandomType = a.SomeRandomType def x[T[_], X](x: X): HKT[T, X] //expected: def x[T[_], X](x: X): A.this.HKT[T, X] diff --git a/scaladoc-testcases/src/tests/extensionMethodSignatures.scala b/scaladoc-testcases/src/tests/extensionMethodSignatures.scala index 34237da32983..fd8312f78d00 100644 --- a/scaladoc-testcases/src/tests/extensionMethodSignatures.scala +++ b/scaladoc-testcases/src/tests/extensionMethodSignatures.scala @@ -44,4 +44,23 @@ case class ClassTwo(a: String, b: String) } -class ClassOneTwo extends ClassOne \ No newline at end of file +class ClassOneTwo extends ClassOne + +trait C[T] +trait Equiv[T]: + extension [U : C](x: U) + def ><[V](y: V): Nothing + = ??? + +trait Monoid[T]: + extension (a: T) + def \:[U](b: U): Nothing + = ??? + extension [U](a: T) + def \\:(b: U): Nothing + = ??? + +class Clazz[U]: + extension [T : ([X] =>> String) : ([X] =>> String)](x: Int) + def bar[U : ([X] =>> String) : List](y: Int): Nothing + = ??? diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala index ee12755c7f98..96c110712e10 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -1,9 +1,7 @@ package dotty.tools.scaladoc.tasty -import scala.jdk.CollectionConverters._ import dotty.tools.scaladoc._ import dotty.tools.scaladoc.{Signature => DSignature} -import dotty.tools.scaladoc.Inkuire import scala.quoted._ @@ -148,18 +146,44 @@ trait ClassLikeSupport: private def isDocumentableExtension(s: Symbol) = !s.isHiddenByVisibility && !s.isSyntheticFunc && s.isExtensionMethod + private def isEvidence(tpc: TermParamClause) = + (tpc.isGiven || tpc.isImplicit) && tpc.params.forall(_.name.startsWith(NameKinds.ContextBoundParamName.separator)) + + private def extractEvidences(tpcs: List[TermParamClause]): (Map[Symbol, List[TypeRepr]], List[TermParamClause]) = + val (evidenceParams, termParams) = tpcs.partition(isEvidence) + val evidenceMap = evidenceParams.flatMap(_.params).map(p => (p.tpt, p.tpt.tpe)).collect { + case (Applied(bound, List(arg: TypeTree)), _) => (arg.tpe.typeSymbol, bound.tpe) + case (_, AppliedType(bound, List(arg))) => (arg.typeSymbol, bound) + // It seems like here we could do: + // (...).map(_.tpt.tpe).collect { + // case AppliedType(bound, List(arg)) => (arg.typeSymbol, bound) + // or: + // (...).map(_.tpt).collect { + // case Applied(bound, List(arg: TypeTree)) => (arg.tpe.typeSymbol, bound.tpe) + // + // First one doesn't always work because .tpe in some cases causes type lambda reductions, eg: + // def foo[T : ([X] =>> String)] + // after desugaring: + // def foo[T](implicit ecidence$1 : ([X] =>> String)[T]) + // tree for this evidence looks like: ([X] =>> String)[T] + // but type repr looks like: String + // (see scaladoc-testcases/src/tests/contextBounds.scala) + // + // Second one doesn't always work, because the tree is sometimes `Inferred` + // (see toArray inherited in scaladoc-testcases/src/tests/classSignatureTestSource.scala) + // + // TODO: check if those two cases can occur at the same time + }.groupMap(_._1)(_._2).withDefaultValue(Nil) + (evidenceMap, termParams) + 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(_, c, memberInfo.genericTypes)) - val termParams = dd.symbol.extendedTermParamLists.zipWithIndex.flatMap { case (termParamList, index) => - memberInfo.termParamLists(index) match - case MemberInfo.EvidenceOnlyParameterList => None - case MemberInfo.RegularParameterList(info) => - Some(api.TermParameterList(termParamList.params.map(mkParameter(_, c, memberInfo = info)), paramListModifier(termParamList.params))) - case _ => assert(false, "memberInfo.termParamLists contains a type parameter list !") - } + val (evidenceMap, termParamClauses) = extractEvidences(dd.symbol.extendedTermParamLists) + val termParams = termParamClauses.map: tpc => + api.TermParameterList(tpc.params.map(mkParameter(_, c)), paramListModifier(tpc.params)) + val typeParams = dd.symbol.extendedTypeParams.map(td => mkTypeArgument(td, c, evidenceMap(td.symbol))) + val target = ExtensionTarget( extSym.symbol.normalizedName, typeParams, @@ -351,45 +375,20 @@ trait ClassLikeSupport: specificKind: (Kind.Def => Kind) = identity ): Member = val method = methodSymbol.tree.asInstanceOf[DefDef] - val paramLists = methodSymbol.nonExtensionParamLists - - val memberInfo = unwrapMemberInfo(c, methodSymbol) - - val unshuffledMemberInfoParamLists = - if methodSymbol.isExtensionMethod && methodSymbol.isRightAssoc then - // Taken from RefinedPrinter.scala - // If you change the names of the clauses below, also change them in right-associative-extension-methods.md - val (leftTyParams, rest1) = memberInfo.paramLists match - case fst :: tail if fst.isType => (List(fst), tail) - case other => (List(), other) - val (leadingUsing, rest2) = rest1.span(_.isUsing) - val (rightTyParams, rest3) = rest2.span(_.isType) - val (rightParam, rest4) = rest3.splitAt(1) - val (leftParam, rest5) = rest4.splitAt(1) - val (trailingUsing, rest6) = rest5.span(_.isUsing) - if leftParam.nonEmpty then - // leftTyParams ::: leadingUsing ::: leftParam ::: trailingUsing ::: rightTyParams ::: rightParam ::: rest6 - // because of takeRight after, this is equivalent to the following: - rightTyParams ::: rightParam ::: rest6 - else - memberInfo.paramLists // it wasn't a binary operator, after all. - else - memberInfo.paramLists - - val croppedUnshuffledMemberInfoParamLists = unshuffledMemberInfoParamLists.takeRight(paramLists.length) - - val basicDefKind: Kind.Def = Kind.Def( - paramLists.zip(croppedUnshuffledMemberInfoParamLists).flatMap{ - case (_: TermParamClause, MemberInfo.EvidenceOnlyParameterList) => Nil - case (pList: TermParamClause, MemberInfo.RegularParameterList(info)) => - Some(Left(api.TermParameterList(pList.params.map( - mkParameter(_, c, paramPrefix, memberInfo = info)), paramListModifier(pList.params) - ))) - case (TypeParamClause(genericTypeList), MemberInfo.TypeParameterList(memInfoTypes)) => - Some(Right(genericTypeList.map(mkTypeArgument(_, c, memInfoTypes, memberInfo.contextBounds)))) - case (_,_) => - assert(false, s"croppedUnshuffledMemberInfoParamLists and SymOps.nonExtensionParamLists disagree on whether this clause is a type or term one") - } + val paramLists = methodSymbol.nonExtensionParamLists.filter: + case TypeParamClause(_) => true + case tpc@TermParamClause(_) => !isEvidence(tpc) + + val evidenceMap = extractEvidences(method.termParamss)._1 + + val basicDefKind: Kind.Def = Kind.Def(paramLists.map: + case TermParamClause(vds) => + Left(api.TermParameterList( + vds.map(mkParameter(_, c, paramPrefix)), + paramListModifier(vds) + )) + case TypeParamClause(genericTypeList) => + Right(genericTypeList.map(td => mkTypeArgument(td, c, evidenceMap(td.symbol)))) ) val methodKind = @@ -456,8 +455,7 @@ trait ClassLikeSupport: def mkTypeArgument( argument: TypeDef, classDef: ClassDef, - memberInfo: Map[String, TypeBounds] = Map.empty, - contextBounds: Map[String, DSignature] = Map.empty, + contextBounds: List[TypeRepr] = Nil, ): TypeParameter = val variancePrefix: "+" | "-" | "" = if argument.symbol.flags.is(Flags.Covariant) then "+" @@ -466,11 +464,13 @@ trait ClassLikeSupport: val name = argument.symbol.normalizedName val normalizedName = if name.matches("_\\$\\d*") then "_" else name - val boundsSignature = memberInfo.get(name).fold(argument.rhs.asSignature(classDef))(_.asSignature(classDef)) - val signature = contextBounds.get(name) match - case None => boundsSignature - case Some(contextBoundsSignature) => - boundsSignature ++ DSignature(Plain(" : ")) ++ contextBoundsSignature + val boundsSignature = argument.rhs.asSignature(classDef) + val signature = boundsSignature ++ contextBounds.flatMap(tr => + val wrap = tr match + case _: TypeLambda => true + case _ => false + Plain(" : ") +: inParens(tr.asSignature(classDef), wrap) + ) TypeParameter( argument.symbol.getAnnotations(), @@ -511,9 +511,9 @@ trait ClassLikeSupport: def parseValDef(c: ClassDef, valDef: ValDef): Member = def defaultKind = if valDef.symbol.flags.is(Flags.Mutable) then Kind.Var else Kind.Val - val memberInfo = unwrapMemberInfo(c, valDef.symbol) + val sig = valDef.tpt.tpe.asSignature(c) val kind = if valDef.symbol.flags.is(Flags.Implicit) then Kind.Implicit(Kind.Val, extractImplicitConversion(valDef.tpt.tpe)) - else if valDef.symbol.flags.is(Flags.Given) then Kind.Given(Kind.Val, Some(memberInfo.res.asSignature(c)), extractImplicitConversion(valDef.tpt.tpe)) + else if valDef.symbol.flags.is(Flags.Given) then Kind.Given(Kind.Val, Some(sig), extractImplicitConversion(valDef.tpt.tpe)) else if valDef.symbol.flags.is(Flags.Enum) then Kind.EnumCase(Kind.Val) else defaultKind @@ -523,7 +523,7 @@ trait ClassLikeSupport: .filterNot(m => m == Modifier.Lazy || m == Modifier.Final) case _ => valDef.symbol.getExtraModifiers() - mkMember(valDef.symbol, kind, memberInfo.res.asSignature(c))( + mkMember(valDef.symbol, kind, sig)( modifiers = modifiers, deprecated = valDef.symbol.isDeprecated(), experimental = valDef.symbol.isExperimental() @@ -554,102 +554,6 @@ trait ClassLikeSupport: experimental = experimental ) - - case class MemberInfo( - paramLists: List[MemberInfo.ParameterList], - res: TypeRepr, - contextBounds: Map[String, DSignature] = Map.empty, - ){ - val genericTypes: Map[String, TypeBounds] = paramLists.collect{ case MemberInfo.TypeParameterList(types) => types }.headOption.getOrElse(Map()) - - val termParamLists: List[MemberInfo.ParameterList] = paramLists.filter(_.isTerm) - } - - object MemberInfo: - enum ParameterList(val isTerm: Boolean, val isUsing: Boolean): - inline def isType = !isTerm - case EvidenceOnlyParameterList extends ParameterList(isTerm = true, isUsing = false) - case RegularParameterList(m: Map[String, TypeRepr])(isUsing: Boolean) extends ParameterList(isTerm = true, isUsing) - case TypeParameterList(m: Map[String, TypeBounds]) extends ParameterList(isTerm = false, isUsing = false) - - export ParameterList.{RegularParameterList, EvidenceOnlyParameterList, TypeParameterList} - - - - def unwrapMemberInfo(c: ClassDef, symbol: Symbol): MemberInfo = - val qualTypeRepr = if c.symbol.isClassDef then This(c.symbol).tpe else typeForClass(c) - val baseTypeRepr = qualTypeRepr.memberType(symbol) - - def isSyntheticEvidence(name: String) = - if !name.startsWith(NameKinds.ContextBoundParamName.separator) then false else - // This assumes that every parameter that starts with `evidence$` and is implicit is generated by compiler to desugar context bound. - // Howrever, this is just a heuristic, so - // `def foo[A](evidence$1: ClassTag[A]) = 1` - // will be documented as - // `def foo[A: ClassTag] = 1`. - // Scala spec states that `$` should not be used in names and behaviour may be undefiend in such case. - // Documenting method slightly different then its definition is withing the 'undefiend behaviour'. - symbol.paramSymss.flatten.find(_.name == name).exists(p => - p.flags.is(Flags.Given) || p.flags.is(Flags.Implicit)) - - def handlePolyType(memberInfo: MemberInfo, polyType: PolyType): MemberInfo = - val typeParamList = MemberInfo.TypeParameterList(polyType.paramNames.zip(polyType.paramBounds).toMap) - MemberInfo(memberInfo.paramLists :+ typeParamList, polyType.resType) - - def handleMethodType(memberInfo: MemberInfo, methodType: MethodType): MemberInfo = - val rawParams = methodType.paramNames.zip(methodType.paramTypes).toMap - val isUsing = methodType.isImplicit - val (evidences, notEvidences) = rawParams.partition(e => isSyntheticEvidence(e._1)) - - def findParamRefs(t: TypeRepr): Seq[ParamRef] = t match - case paramRef: ParamRef => Seq(paramRef) - case AppliedType(_, args) => args.flatMap(findParamRefs) - case MatchType(bound, scrutinee, cases) => - findParamRefs(bound) ++ findParamRefs(scrutinee) - case _ => Nil - - def nameForRef(ref: ParamRef): String = - val PolyType(names, _, _) = ref.binder: @unchecked - names(ref.paramNum) - - val (paramsThatLookLikeContextBounds, contextBounds) = - evidences.partitionMap { - case (_, AppliedType(tpe, List(typeParam: ParamRef))) => - Right(nameForRef(typeParam) -> tpe.asSignature(c)) - case (name, original) => - findParamRefs(original) match - case Nil => Left((name, original)) - case typeParam :: _ => - val name = nameForRef(typeParam) - val signature = Seq( - Plain("(["), - dotty.tools.scaladoc.Type(name, None), - Plain("]"), - Keyword(" =>> "), - ) ++ original.asSignature(c) ++ Seq(Plain(")")) - Right(name -> signature.toList) - } - - val newParams = notEvidences ++ paramsThatLookLikeContextBounds - - val termParamList = if newParams.isEmpty && contextBounds.nonEmpty - then MemberInfo.EvidenceOnlyParameterList - else MemberInfo.RegularParameterList(newParams)(isUsing) - - - MemberInfo(memberInfo.paramLists :+ termParamList, methodType.resType, contextBounds.toMap) - - def handleByNameType(memberInfo: MemberInfo, byNameType: ByNameType): MemberInfo = - MemberInfo(memberInfo.paramLists, byNameType.underlying) - - def recursivelyCalculateMemberInfo(memberInfo: MemberInfo): MemberInfo = memberInfo.res match - case p: PolyType => recursivelyCalculateMemberInfo(handlePolyType(memberInfo, p)) - case m: MethodType => recursivelyCalculateMemberInfo(handleMethodType(memberInfo, m)) - case b: ByNameType => handleByNameType(memberInfo, b) - case _ => memberInfo - - recursivelyCalculateMemberInfo(MemberInfo(List.empty, baseTypeRepr)) - private def paramListModifier(parameters: Seq[ValDef]): String = if parameters.size > 0 then if parameters(0).symbol.flags.is(Flags.Given) then "using " diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index 110ee498a3ac..51007d2b61da 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -37,7 +37,7 @@ trait TypesSupport: private def tpe(str: String): SignaturePart = dotty.tools.scaladoc.Type(str, None) - private def inParens(s: SSignature, wrap: Boolean = true) = + protected def inParens(s: SSignature, wrap: Boolean = true) = if wrap then plain("(").l ++ s ++ plain(")").l else s extension (on: SignaturePart) def l: List[SignaturePart] = List(on) @@ -115,8 +115,11 @@ trait TypesSupport: case AnnotatedType(tpe, _) => inner(tpe) case tl @ TypeLambda(params, paramBounds, AppliedType(tpe, args)) - if paramBounds.map(inner).forall(_.isEmpty) && params.zip(args.map(inner).flatten.map(_.name)).forall(_ == _) => - inner(tpe) + if paramBounds.forall { case TypeBounds(low, hi) => low.typeSymbol == defn.NothingClass && hi.typeSymbol == defn.AnyClass } + && params.length == args.length + && args.zipWithIndex.forall(_ == tl.param(_)) => + // simplify type lambdas such as [X, Y] =>> Map[X, Y] to just Map + inner(tpe) case tl @ TypeLambda(params, paramBounds, resType) => plain("[").l ++ commas(params.zip(paramBounds).map { (name, typ) => val normalizedName = if name.matches("_\\$\\d*") then "_" else name From 86c689492a8b460efeb970ccb4c0bf14212569a0 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Wed, 14 May 2025 16:18:55 +0200 Subject: [PATCH 2/4] Improve this-type rendering Co-authored-by: Florian3k --- .../src/tests/classSignatureTestSource.scala | 4 +- scaladoc-testcases/src/tests/exports1.scala | 4 +- .../src/tests/innerClasses.scala | 25 +++ scaladoc-testcases/src/tests/thisType.scala | 29 ++- .../scaladoc/tasty/ClassLikeSupport.scala | 84 ++++---- .../tools/scaladoc/tasty/TypesSupport.scala | 185 ++++++++++-------- .../TranslatableSignaturesTestCases.scala | 2 + 7 files changed, 203 insertions(+), 130 deletions(-) create mode 100644 scaladoc-testcases/src/tests/innerClasses.scala diff --git a/scaladoc-testcases/src/tests/classSignatureTestSource.scala b/scaladoc-testcases/src/tests/classSignatureTestSource.scala index 0f501be16909..1ddfff4aeb28 100644 --- a/scaladoc-testcases/src/tests/classSignatureTestSource.scala +++ b/scaladoc-testcases/src/tests/classSignatureTestSource.scala @@ -26,7 +26,9 @@ abstract class Documentation[T, A <: Int, B >: String, -X, +Y](c1: String, val c sealed trait CaseImplementThis(id: Int) - case class IAmACaseClass(x: T, id: Int) extends CaseImplementThis/*<-*/(id)/*->*/ + case class IAmACaseClass(x: Documentation.this.T, id: Int) extends CaseImplementThis/*<-*/(id)/*->*/ + + case class IAmACaseClassWithParam[T](x: Documentation.this.T, id: T) case object IAmACaseObject extends CaseImplementThis/*<-*/(0)/*->*/ diff --git a/scaladoc-testcases/src/tests/exports1.scala b/scaladoc-testcases/src/tests/exports1.scala index a2f94731260d..2c71b42e8da3 100644 --- a/scaladoc-testcases/src/tests/exports1.scala +++ b/scaladoc-testcases/src/tests/exports1.scala @@ -17,12 +17,12 @@ class A: //unexpected type HKT[T[_], X] //expected: final type HKT = a.HKT = T[X] type SomeRandomType = (List[?] | Seq[?]) & String //expected: final type SomeRandomType = a.SomeRandomType - def x[T[_], X](x: X): HKT[T, X] //expected: def x[T[_], X](x: X): A.this.HKT[T, X] + def x[T[_], X](x: X): HKT[T, X] = ??? def fn[T, U]: T => U = ??? object Object //expected: val Obj: Object.type - val x: HKT[List, Int] //expected: val x: A.this.HKT[List, Int] + val x: HKT[List, Int] = ??? class Class(val a: Int, val b: Int) extends Serializable //expected: final type Class = a.Class enum Enum: //expected: final type Enum = a.Enum diff --git a/scaladoc-testcases/src/tests/innerClasses.scala b/scaladoc-testcases/src/tests/innerClasses.scala new file mode 100644 index 000000000000..50b2df41a826 --- /dev/null +++ b/scaladoc-testcases/src/tests/innerClasses.scala @@ -0,0 +1,25 @@ +package tests +package innerClasses + +trait A: + def baz: B + = baz2 + def baz2: A.this.B //expected: def baz2: B + = baz + type B + class C extends A: + def foo: A.this.B + = ??? + def foo2: B + = ??? + def bar: B + = ??? + +class T1: + trait T + class T2: + trait T + class Impl extends T1.this.T //expected: class Impl extends T + // we get rid of the this-type above, + // as ambiguity created by removing this-types is alleviated by links + // (but this can be changed if needed) diff --git a/scaladoc-testcases/src/tests/thisType.scala b/scaladoc-testcases/src/tests/thisType.scala index 28cb55fcc49e..2d68e70c43ac 100644 --- a/scaladoc-testcases/src/tests/thisType.scala +++ b/scaladoc-testcases/src/tests/thisType.scala @@ -1,8 +1,35 @@ package tests package thisType -// issue 16024 class X[Map[_, _[_]]]: + // issue 16024 inline def map[F[_]](f: [t] => t => F[t]): Map[this.type, F] = ??? + +sealed trait Tuple[Y[_]]: + def ++[This >: this.type <: Tuple[Y]](that: Y[Tuple[Y]]): Any + = ??? + +sealed trait NonEmptyTuple extends Tuple[Option] +//expected: def ++[This >: this.type <: Tuple[Option]](that: Option[Tuple[Option]]): Any + +trait Foo[X]: + def foo0[T <: Foo.this.type](x: X): Foo.this.type //expected: def foo0[T <: this.type](x: X): this.type + = bar0[T](x) + def bar0[T <: this.type](x: X): this.type + = foo0[T](x) + + sealed abstract class Nested[+H, +T <: (Tuple), A <: Tuple[List]] extends NonEmptyTuple, Foo[Int]: + // ^^^^^^^ TODO fix + //expected: def ++[This >: this.type <: Tuple[Option]](that: Option[Tuple[Option]]): Any + + //expected: def foo0[T <: this.type](x: Int): this.type + + //expected: def bar0[T <: this.type](x: Int): this.type + + def foo1[T <: Foo.this.type]: Nothing + = ??? + + def foo2[T <: this.type]: Nothing + = ??? diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala index 96c110712e10..99aac7010d8b 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -87,7 +87,7 @@ trait ClassLikeSupport: def getSupertypesGraph(link: LinkToType, to: Seq[Tree]): Seq[(LinkToType, LinkToType)] = to.flatMap { case tree => val symbol = if tree.symbol.isClassConstructor then tree.symbol.owner else tree.symbol - val signature = signatureWithName(tree.asSignature(classDef)) + val signature = signatureWithName(tree.asSignature(classDef, classDef.symbol)) val superLink = LinkToType(signature, symbol.dri, bareClasslikeKind(symbol)) val nextTo = unpackTreeToClassDef(tree).parents if symbol.isHiddenByVisibility then getSupertypesGraph(link, nextTo) @@ -98,16 +98,17 @@ trait ClassLikeSupport: .filterNot((s, t) => s.isHiddenByVisibility) .map { case (symbol, tpe) => - val signature = signatureWithName(tpe.asSignature(classDef)) + val signature = signatureWithName(tpe.asSignature(classDef, classDef.symbol)) LinkToType(signature, symbol.dri, bareClasslikeKind(symbol)) } val selfType = classDef.self.map { (valdef: ValDef) => val symbol = valdef.symbol val tpe = valdef.tpt.tpe - val signature = signatureWithName(tpe.asSignature(classDef)) + val owner = if symbol.exists then symbol.owner else Symbol.noSymbol + val signature = signatureWithName(tpe.asSignature(classDef, owner)) LinkToType(signature, symbol.dri, Kind.Type(false, false, Seq.empty)) } - val selfSignature: DSignature = signatureWithName(typeForClass(classDef).asSignature(classDef)) + val selfSignature: DSignature = signatureWithName(typeForClass(classDef).asSignature(classDef, classDef.symbol)) val graph = HierarchyGraph.withEdges( getSupertypesGraph(LinkToType(selfSignature, classDef.symbol.dri, bareClasslikeKind(classDef.symbol)), unpackTreeToClassDef(classDef).parents) @@ -188,7 +189,7 @@ trait ClassLikeSupport: extSym.symbol.normalizedName, typeParams, termParams, - extSym.tpt.asSignature(c), + extSym.tpt.asSignature(c, extSym.symbol.owner), extSym.tpt.symbol.dri, extSym.symbol.pos.get.start ) @@ -296,7 +297,7 @@ trait ClassLikeSupport: def getParentsAsLinkToTypes: List[LinkToType] = c.getParentsAsTreeSymbolTuples.map { - (tree, symbol) => LinkToType(tree.asSignature(c), symbol.dri, bareClasslikeKind(symbol)) + (tree, symbol) => LinkToType(tree.asSignature(c, c.symbol, skipThisTypePrefix = true), symbol.dri, bareClasslikeKind(symbol)) } def getParentsAsTreeSymbolTuples: List[(Tree, Symbol)] = @@ -407,7 +408,7 @@ trait ClassLikeSupport: )) case _ => Kind.Implicit(basicDefKind, None) - else if methodSymbol.flags.is(Flags.Given) then Kind.Given(basicDefKind, Some(method.returnTpt.tpe.asSignature(c)), extractImplicitConversion(method.returnTpt.tpe)) + else if methodSymbol.flags.is(Flags.Given) then Kind.Given(basicDefKind, Some(method.returnTpt.tpe.asSignature(c, methodSymbol.owner)), extractImplicitConversion(method.returnTpt.tpe)) else specificKind(basicDefKind) val origin = if !methodSymbol.isOverridden then Origin.RegularlyDefined else @@ -423,7 +424,7 @@ trait ClassLikeSupport: mkMember( methodSymbol, methodKind, - method.returnTpt.tpe.asSignature(c), + method.returnTpt.tpe.asSignature(c, methodSymbol), )( modifiers = modifiers, origin = origin, @@ -437,17 +438,17 @@ trait ClassLikeSupport: prefix: Symbol => String = _ => "", isExtendedSymbol: Boolean = false, isGrouped: Boolean = false, - memberInfo: Map[String, TypeRepr] = Map.empty, ) = - 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 symbol = argument.symbol + 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) api.TermParameter( - argument.symbol.getAnnotations(), - inlinePrefix + prefix(argument.symbol), + symbol.getAnnotations(), + inlinePrefix + prefix(symbol), nameIfNotSynthetic, - argument.symbol.dri, - memberInfo.get(name).fold(argument.tpt.asSignature(classDef))(_.asSignature(classDef)), + symbol.dri, + argument.tpt.asSignature(classDef, symbol.owner), isExtendedSymbol, isGrouped ) @@ -457,30 +458,32 @@ trait ClassLikeSupport: classDef: ClassDef, contextBounds: List[TypeRepr] = Nil, ): TypeParameter = + val symbol = argument.symbol val variancePrefix: "+" | "-" | "" = - if argument.symbol.flags.is(Flags.Covariant) then "+" - else if argument.symbol.flags.is(Flags.Contravariant) then "-" + if symbol.flags.is(Flags.Covariant) then "+" + else if symbol.flags.is(Flags.Contravariant) then "-" else "" - val name = argument.symbol.normalizedName + val name = symbol.normalizedName val normalizedName = if name.matches("_\\$\\d*") then "_" else name - val boundsSignature = argument.rhs.asSignature(classDef) + val boundsSignature = argument.rhs.asSignature(classDef, symbol.owner) val signature = boundsSignature ++ contextBounds.flatMap(tr => val wrap = tr match case _: TypeLambda => true case _ => false - Plain(" : ") +: inParens(tr.asSignature(classDef), wrap) + Plain(" : ") +: inParens(tr.asSignature(classDef, symbol.owner), wrap) ) TypeParameter( - argument.symbol.getAnnotations(), + symbol.getAnnotations(), variancePrefix, normalizedName, - argument.symbol.dri, + symbol.dri, signature ) def parseTypeDef(typeDef: TypeDef, classDef: ClassDef): Member = + val symbol = typeDef.symbol def isTreeAbstract(typ: Tree): Boolean = typ match { case TypeBoundsTree(_, _) => true case LambdaTypeTree(params, body) => isTreeAbstract(body) @@ -490,43 +493,44 @@ trait ClassLikeSupport: case LambdaTypeTree(params, body) => (params.map(mkTypeArgument(_, classDef)), body) case tpe => (Nil, tpe) - val defaultKind = Kind.Type(!isTreeAbstract(typeDef.rhs), typeDef.symbol.isOpaque, generics).asInstanceOf[Kind.Type] - val kind = if typeDef.symbol.flags.is(Flags.Enum) then Kind.EnumCase(defaultKind) + val defaultKind = Kind.Type(!isTreeAbstract(typeDef.rhs), symbol.isOpaque, generics).asInstanceOf[Kind.Type] + val kind = if symbol.flags.is(Flags.Enum) then Kind.EnumCase(defaultKind) else defaultKind - if typeDef.symbol.flags.is(Flags.Exported) + if symbol.flags.is(Flags.Exported) then { val origin = Some(tpeTree).flatMap { case TypeBoundsTree(l: TypeTree, h: TypeTree) if l.tpe == h.tpe => Some(Link(l.tpe.typeSymbol.owner.name, l.tpe.typeSymbol.owner.dri)) case _ => None } - mkMember(typeDef.symbol, Kind.Exported(kind), tpeTree.asSignature(classDef))( - deprecated = typeDef.symbol.isDeprecated(), + mkMember(symbol, Kind.Exported(kind), tpeTree.asSignature(classDef, symbol.owner))( + deprecated = symbol.isDeprecated(), origin = Origin.ExportedFrom(origin), - experimental = typeDef.symbol.isExperimental() + experimental = symbol.isExperimental() ) } - else mkMember(typeDef.symbol, kind, tpeTree.asSignature(classDef))(deprecated = typeDef.symbol.isDeprecated()) + else mkMember(symbol, kind, tpeTree.asSignature(classDef, symbol.owner))(deprecated = symbol.isDeprecated()) def parseValDef(c: ClassDef, valDef: ValDef): Member = - def defaultKind = if valDef.symbol.flags.is(Flags.Mutable) then Kind.Var else Kind.Val - val sig = valDef.tpt.tpe.asSignature(c) - val kind = if valDef.symbol.flags.is(Flags.Implicit) then Kind.Implicit(Kind.Val, extractImplicitConversion(valDef.tpt.tpe)) - else if valDef.symbol.flags.is(Flags.Given) then Kind.Given(Kind.Val, Some(sig), extractImplicitConversion(valDef.tpt.tpe)) - else if valDef.symbol.flags.is(Flags.Enum) then Kind.EnumCase(Kind.Val) + val symbol = valDef.symbol + def defaultKind = if symbol.flags.is(Flags.Mutable) then Kind.Var else Kind.Val + val sig = valDef.tpt.tpe.asSignature(c, symbol.owner) + val kind = if symbol.flags.is(Flags.Implicit) then Kind.Implicit(Kind.Val, extractImplicitConversion(valDef.tpt.tpe)) + else if symbol.flags.is(Flags.Given) then Kind.Given(Kind.Val, Some(sig), extractImplicitConversion(valDef.tpt.tpe)) + else if symbol.flags.is(Flags.Enum) then Kind.EnumCase(Kind.Val) else defaultKind val modifiers = kind match - case _: Kind.Given => valDef.symbol + case _: Kind.Given => symbol .getExtraModifiers() .filterNot(m => m == Modifier.Lazy || m == Modifier.Final) - case _ => valDef.symbol.getExtraModifiers() + case _ => symbol.getExtraModifiers() - mkMember(valDef.symbol, kind, sig)( + mkMember(symbol, kind, sig)( modifiers = modifiers, - deprecated = valDef.symbol.isDeprecated(), - experimental = valDef.symbol.isExperimental() + deprecated = symbol.isDeprecated(), + experimental = symbol.isExperimental() ) def mkMember(symbol: Symbol, kind: Kind, signature: DSignature)( diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index 51007d2b61da..80981573f01e 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -16,17 +16,21 @@ trait TypesSupport: given TreeSyntax: AnyRef with extension (using Quotes)(tpeTree: reflect.Tree) - def asSignature(elideThis: reflect.ClassDef): SSignature = + def asSignature(elideThis: reflect.ClassDef, originalOwner: reflect.Symbol, skipThisTypePrefix: Boolean): SSignature = import reflect._ tpeTree match - case TypeBoundsTree(low, high) => typeBoundsTreeOfHigherKindedType(low.tpe, high.tpe)(using elideThis) - case tpeTree: TypeTree => topLevelProcess(tpeTree.tpe)(using elideThis) - case term: Term => topLevelProcess(term.tpe)(using elideThis) + case TypeBoundsTree(low, high) => typeBoundsTreeOfHigherKindedType(low.tpe, high.tpe, skipThisTypePrefix)(using elideThis, originalOwner) + case tpeTree: TypeTree => topLevelProcess(tpeTree.tpe, skipThisTypePrefix)(using elideThis, originalOwner) + case term: Term => topLevelProcess(term.tpe, skipThisTypePrefix)(using elideThis, originalOwner) + def asSignature(elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = + tpeTree.asSignature(elideThis, originalOwner, skipThisTypePrefix = false) given TypeSyntax: AnyRef with extension (using Quotes)(tpe: reflect.TypeRepr) - def asSignature(elideThis: reflect.ClassDef): SSignature = - topLevelProcess(tpe)(using elideThis) + def asSignature(elideThis: reflect.ClassDef, originalOwner: reflect.Symbol, skipThisTypePrefix: Boolean): SSignature = + topLevelProcess(tpe, skipThisTypePrefix)(using elideThis, originalOwner) + def asSignature(elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = + tpe.asSignature(elideThis, originalOwner, skipThisTypePrefix = false) private def plain(str: String): SignaturePart = Plain(str) @@ -67,22 +71,24 @@ trait TypesSupport: case _ => false case _ => false - private def topLevelProcess(using Quotes)(tp: reflect.TypeRepr)(using elideThis: reflect.ClassDef): SSignature = + private def topLevelProcess(using Quotes)(tp: reflect.TypeRepr, skipThisTypePrefix: Boolean)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = import reflect._ tp match case ThisType(tpe) => val suffix = List(keyword("this"), plain("."), keyword("type")) - if skipPrefix(tp, elideThis) then suffix - else inner(tpe) ++ plain(".").l ++ suffix - case tpe => inner(tpe) + if skipPrefix(tp, elideThis, originalOwner, skipThisTypePrefix) then suffix + else inner(tpe, skipThisTypePrefix) ++ plain(".").l ++ suffix + case tpe => inner(tpe, skipThisTypePrefix) // TODO #23 add support for all types signatures that make sense private def inner( using Quotes, )( tp: reflect.TypeRepr, + skipThisTypePrefix: Boolean )(using elideThis: reflect.ClassDef, + originalOwner: reflect.Symbol, indent: Int = 0, skipTypeSuffix: Boolean = false, ): SSignature = @@ -92,47 +98,45 @@ trait TypesSupport: plain(s"Unsupported[$name]").l tp match case OrType(left, right) => - inParens(inner(left), shouldWrapInParens(left, tp, true)) + inParens(inner(left, skipThisTypePrefix), shouldWrapInParens(left, tp, true)) ++ keyword(" | ").l - ++ inParens(inner(right), shouldWrapInParens(right, tp, false)) + ++ inParens(inner(right, skipThisTypePrefix), shouldWrapInParens(right, tp, false)) case AndType(left, right) => - inParens(inner(left), shouldWrapInParens(left, tp, true)) + inParens(inner(left, skipThisTypePrefix), shouldWrapInParens(left, tp, true)) ++ keyword(" & ").l - ++ inParens(inner(right), shouldWrapInParens(right, tp, false)) - case ByNameType(tpe) => keyword("=> ") :: inner(tpe) + ++ inParens(inner(right, skipThisTypePrefix), shouldWrapInParens(right, tp, false)) + case ByNameType(tpe) => keyword("=> ") :: inner(tpe, skipThisTypePrefix) case ConstantType(constant) => plain(constant.show).l case ThisType(tpe) => - val prefix = findSupertype(elideThis, tpe.typeSymbol) match - case Some(_) => Nil - case None => inner(tpe) ++ plain(".").l + val prefix = if skipPrefix(tp, elideThis, originalOwner, skipThisTypePrefix) then Nil else inner(tpe, skipThisTypePrefix) ++ plain(".").l val suffix = if skipTypeSuffix then Nil else List(plain("."), keyword("type")) prefix ++ keyword("this").l ++ suffix case AnnotatedType(AppliedType(_, Seq(tpe)), annotation) if isRepeatedAnnotation(annotation) => - inner(tpe) :+ plain("*") + inner(tpe, skipThisTypePrefix) :+ plain("*") case AppliedType(repeatedClass, Seq(tpe)) if isRepeated(repeatedClass) => - inner(tpe) :+ plain("*") + inner(tpe, skipThisTypePrefix) :+ plain("*") case AnnotatedType(tpe, _) => - inner(tpe) + inner(tpe, skipThisTypePrefix) case tl @ TypeLambda(params, paramBounds, AppliedType(tpe, args)) if paramBounds.forall { case TypeBounds(low, hi) => low.typeSymbol == defn.NothingClass && hi.typeSymbol == defn.AnyClass } && params.length == args.length && args.zipWithIndex.forall(_ == tl.param(_)) => // simplify type lambdas such as [X, Y] =>> Map[X, Y] to just Map - inner(tpe) + inner(tpe, skipThisTypePrefix) case tl @ TypeLambda(params, paramBounds, resType) => plain("[").l ++ commas(params.zip(paramBounds).map { (name, typ) => val normalizedName = if name.matches("_\\$\\d*") then "_" else name - tpe(normalizedName).l ++ inner(typ) + tpe(normalizedName).l ++ inner(typ, skipThisTypePrefix) }) ++ plain("]").l ++ keyword(" =>> ").l - ++ inner(resType) + ++ inner(resType, skipThisTypePrefix) case Refinement(parent, "apply", mt : MethodType) if isPolyOrEreased(parent) => val isCtx = isContextualMethod(mt) val sym = defn.FunctionClass(mt.paramTypes.length, isCtx) val at = sym.typeRef.appliedTo(mt.paramTypes :+ mt.resType) - inner(Refinement(at, "apply", mt)) + inner(Refinement(at, "apply", mt), skipThisTypePrefix) case r: Refinement => { //(parent, name, info) def getRefinementInformation(t: TypeRepr): List[TypeRepr] = t match { @@ -141,20 +145,20 @@ trait TypesSupport: } def getParamBounds(t: PolyType): SSignature = commas( - t.paramNames.zip(t.paramBounds.map(inner(_))) + t.paramNames.zip(t.paramBounds.map(inner(_, skipThisTypePrefix))) .map(b => tpe(b(0)).l ++ b(1)) ) def getParamList(m: MethodType): SSignature = plain("(").l - ++ m.paramNames.zip(m.paramTypes).map{ case (name, tp) => plain(s"$name: ").l ++ inner(tp)} + ++ m.paramNames.zip(m.paramTypes).map{ case (name, tp) => plain(s"$name: ").l ++ inner(tp, skipThisTypePrefix)} .reduceLeftOption((acc: SSignature, elem: SSignature) => acc ++ plain(", ").l ++ elem).getOrElse(List()) ++ plain(")").l def parseRefinedElem(name: String, info: TypeRepr, polyTyped: SSignature = Nil): SSignature = ( info match { case m: MethodType => { val paramList = getParamList(m) - keyword("def ").l ++ plain(name).l ++ polyTyped ++ paramList ++ plain(": ").l ++ inner(m.resType) + keyword("def ").l ++ plain(name).l ++ polyTyped ++ paramList ++ plain(": ").l ++ inner(m.resType, skipThisTypePrefix) } case t: PolyType => { val paramBounds = getParamBounds(t) @@ -163,10 +167,10 @@ trait TypesSupport: parseRefinedElem(name, t.resType, plain("[").l ++ paramBounds ++ plain("]").l) } else parseRefinedElem(name, t.resType) } - case ByNameType(tp) => keyword("def ").l ++ plain(s"$name: ").l ++ inner(tp) - case t: TypeBounds => keyword("type ").l ++ plain(name).l ++ inner(t) - case t: TypeRef => keyword("val ").l ++ plain(s"$name: ").l ++ inner(t) - case t: TermRef => keyword("val ").l ++ plain(s"$name: ").l ++ inner(t) + case ByNameType(tp) => keyword("def ").l ++ plain(s"$name: ").l ++ inner(tp, skipThisTypePrefix) + case t: TypeBounds => keyword("type ").l ++ plain(name).l ++ inner(t, skipThisTypePrefix) + case t: TypeRef => keyword("val ").l ++ plain(s"$name: ").l ++ inner(t, skipThisTypePrefix) + case t: TermRef => keyword("val ").l ++ plain(s"$name: ").l ++ inner(t, skipThisTypePrefix) case other => noSupported(s"Not supported type in refinement $info") } ) ++ plain("; ").l @@ -185,18 +189,18 @@ trait TypesSupport: if isDependentMethod(m) then val paramList = getParamList(m) val arrow = keyword(if isCtx then " ?=> " else " => ").l - val resType = inner(m.resType) + val resType = inner(m.resType, skipThisTypePrefix) paramList ++ arrow ++ resType else val sym = defn.FunctionClass(m.paramTypes.length, isCtx) - inner(sym.typeRef.appliedTo(m.paramTypes :+ m.resType)) + inner(sym.typeRef.appliedTo(m.paramTypes :+ m.resType), skipThisTypePrefix) case other => noSupported("Dependent function type without MethodType refinement") } val refinementInfo = getRefinementInformation(r) val refinedType = refinementInfo.head val refinedElems = refinementInfo.tail.collect{ case r: Refinement => r }.toList - val prefix = if refinedType.typeSymbol != defn.ObjectClass then inner(refinedType) ++ plain(" ").l else Nil + val prefix = if refinedType.typeSymbol != defn.ObjectClass then inner(refinedType, skipThisTypePrefix) ++ plain(" ").l else Nil if (refinedType.typeSymbol.fullName == "scala.PolyFunction" && refinedElems.size == 1) { parsePolyFunction(refinedElems.head.info) } @@ -209,7 +213,7 @@ trait TypesSupport: } case AppliedType(tpe, args) if defn.isTupleClass(tpe.typeSymbol) && args.length > 1 => - inParens(commas(args.map(inner(_)))) + inParens(commas(args.map(inner(_, skipThisTypePrefix)))) case AppliedType(namedTuple, List(AppliedType(tuple1, names), AppliedType(tuple2, types))) if namedTuple.typeSymbol == Symbol.requiredModule("scala.NamedTuple").typeMember("NamedTuple") @@ -218,78 +222,80 @@ trait TypesSupport: val elems = names .collect { case ConstantType(StringConstant(s)) => s } .zip(types) - .map((name, tpe) => plain(name) +: plain(": ") +: inner(tpe)) + .map((name, tpe) => plain(name) +: plain(": ") +: inner(tpe, skipThisTypePrefix)) inParens(commas(elems)) case t @ AppliedType(tpe, List(lhs, rhs)) if isInfix(t) => - inParens(inner(lhs), shouldWrapInParens(lhs, t, true)) + inParens(inner(lhs, skipThisTypePrefix), shouldWrapInParens(lhs, t, true)) ++ plain(" ").l - ++ inner(tpe) + ++ inner(tpe, skipThisTypePrefix) ++ plain(" ").l - ++ inParens(inner(rhs), shouldWrapInParens(rhs, t, false)) + ++ inParens(inner(rhs, skipThisTypePrefix), 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(rtpe) => plain("()").l ++ keyword(arrow).l ++ inner(rtpe, skipThisTypePrefix) 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) + inParens(inner(arg, skipThisTypePrefix), wrapInParens) ++ keyword(arrow).l ++ inner(rtpe, skipThisTypePrefix) case _ => - plain("(").l ++ commas(args.init.map(inner(_))) ++ plain(")").l ++ keyword(arrow).l ++ inner(args.last) + plain("(").l ++ commas(args.init.map(inner(_, skipThisTypePrefix))) ++ plain(")").l ++ keyword(arrow).l ++ inner(args.last, skipThisTypePrefix) case t @ AppliedType(tpe, typeList) => - inner(tpe) ++ plain("[").l ++ commas(typeList.map { t => t match - case _: TypeBounds => keyword("_").l ++ inner(t) - case _ => topLevelProcess(t) + inner(tpe, skipThisTypePrefix) ++ plain("[").l ++ commas(typeList.map { t => t match + case _: TypeBounds => keyword("_").l ++ inner(t, skipThisTypePrefix) + case _ => topLevelProcess(t, skipThisTypePrefix) }) ++ plain("]").l case tp @ TypeRef(qual, typeName) => qual match { case r: RecursiveThis => tpe(s"this.$typeName").l - case t if skipPrefix(t, elideThis) => - 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 case ThisType(tr) => - findSupertype(elideThis, tr.typeSymbol) match + val typeFromSupertypeConstructor = findSupertype(elideThis, tr.typeSymbol) match case Some((sym, AppliedType(tr2, args))) => sym.tree.asInstanceOf[ClassDef].constructor.paramss.headOption match case Some(TypeParamClause(tpc)) => tpc.zip(args).collectFirst { case (TypeDef(name, _), arg) if name == typeName => arg - } match - case Some(tr) => inner(tr) - case None => tpe(tp.typeSymbol) - case _ => tpe(tp.typeSymbol) - case Some(_) => tpe(tp.typeSymbol) - case None => - val sig = inParens(inner(qual)(using skipTypeSuffix = true), shouldWrapInParens(qual, tp, true)) + }.map(inner(_, skipThisTypePrefix)) + case _ => None + case _ => None + typeFromSupertypeConstructor.getOrElse: + if skipPrefix(qual, elideThis, originalOwner, skipThisTypePrefix) then + tpe(tp.typeSymbol) + else + val sig = inParens(inner(qual, skipThisTypePrefix)(using skipTypeSuffix = true), shouldWrapInParens(qual, tp, true)) sig ++ plain(".").l ++ tpe(tp.typeSymbol) + + case t if skipPrefix(t, elideThis, originalOwner, skipThisTypePrefix) => + tpe(tp.typeSymbol) + case _: TermRef | _: ParamRef => + val suffix = if tp.typeSymbol == Symbol.noSymbol then tpe(typeName).l else tpe(tp.typeSymbol) + inner(qual, skipThisTypePrefix)(using skipTypeSuffix = true) ++ plain(".").l ++ suffix case _ => - val sig = inParens(inner(qual), shouldWrapInParens(qual, tp, true)) + val sig = inParens(inner(qual, skipThisTypePrefix), shouldWrapInParens(qual, tp, true)) sig ++ keyword("#").l ++ tpe(tp.typeSymbol) } case tr @ TermRef(qual, typeName) => val prefix = qual match - case t if skipPrefix(t, elideThis) => Nil - case tp => inner(tp)(using skipTypeSuffix = true) ++ plain(".").l + case t if skipPrefix(t, elideThis, originalOwner, skipThisTypePrefix) => Nil + case tp => inner(tp, skipThisTypePrefix)(using skipTypeSuffix = true) ++ plain(".").l val suffix = if skipTypeSuffix then Nil else List(plain("."), keyword("type")) val typeSig = tr.termSymbol.tree match case vd: ValDef if tr.termSymbol.flags.is(Flags.Module) => - inner(vd.tpt.tpe) + inner(vd.tpt.tpe, skipThisTypePrefix) case _ => plain(typeName).l prefix ++ typeSig ++ suffix case TypeBounds(low, hi) => - if(low == hi) keyword(" = ").l ++ inner(low) - else typeBoundsTreeOfHigherKindedType(low, hi) + if(low == hi) keyword(" = ").l ++ inner(low, skipThisTypePrefix) + else typeBoundsTreeOfHigherKindedType(low, hi, skipThisTypePrefix) case NoPrefix() => Nil @@ -298,11 +304,11 @@ trait TypesSupport: val spaces = " " * (indent) val casesTexts = cases.flatMap { case MatchCase(from, to) => - keyword(caseSpaces + "case ").l ++ inner(from) ++ keyword(" => ").l ++ inner(to)(using indent = indent + 2) ++ plain("\n").l + keyword(caseSpaces + "case ").l ++ inner(from, skipThisTypePrefix) ++ keyword(" => ").l ++ inner(to, skipThisTypePrefix)(using indent = indent + 2) ++ plain("\n").l case TypeLambda(_, _, MatchCase(from, to)) => - keyword(caseSpaces + "case ").l ++ inner(from) ++ keyword(" => ").l ++ inner(to)(using indent = indent + 2) ++ plain("\n").l + keyword(caseSpaces + "case ").l ++ inner(from, skipThisTypePrefix) ++ keyword(" => ").l ++ inner(to, skipThisTypePrefix)(using indent = indent + 2) ++ plain("\n").l } - inner(sc) ++ keyword(" match ").l ++ plain("{\n").l ++ casesTexts ++ plain(spaces + "}").l + inner(sc, skipThisTypePrefix) ++ keyword(" match ").l ++ plain("{\n").l ++ casesTexts ++ plain(spaces + "}").l case ParamRef(m: MethodType, i) => val suffix = if skipTypeSuffix then Nil else List(plain("."), keyword("type")) @@ -310,13 +316,13 @@ trait TypesSupport: case ParamRef(binder: LambdaType, i) => tpe(binder.paramNames(i)).l - case RecursiveType(tp) => inner(tp) + case RecursiveType(tp) => inner(tp, skipThisTypePrefix) case MatchCase(pattern, rhs) => - keyword("case ").l ++ inner(pattern) ++ keyword(" => ").l ++ inner(rhs) + keyword("case ").l ++ inner(pattern, skipThisTypePrefix) ++ keyword(" => ").l ++ inner(rhs, skipThisTypePrefix) case t: dotty.tools.dotc.core.Types.LazyRef => try { - inner(t.ref(using ctx.compilerContext).asInstanceOf[TypeRepr]) + inner(t.ref(using ctx.compilerContext).asInstanceOf[TypeRepr], skipThisTypePrefix) } catch { case e: AssertionError => tpe("LazyRef(...)").l } @@ -326,28 +332,30 @@ trait TypesSupport: s"${tpe.show(using Printer.TypeReprStructure)}" throw MatchError(msg) - private def typeBound(using Quotes)(t: reflect.TypeRepr, low: Boolean)(using elideThis: reflect.ClassDef) = + private def typeBound(using Quotes)(t: reflect.TypeRepr, low: Boolean, skipThisTypePrefix: Boolean)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol) = import reflect._ val ignore = if (low) t.typeSymbol == defn.NothingClass else t.typeSymbol == defn.AnyClass val prefix = keyword(if low then " >: " else " <: ") t match { - case l: TypeLambda => prefix :: inParens(inner(l)(using elideThis)) - case p: ParamRef => prefix :: inner(p)(using elideThis) - case other if !ignore => prefix :: topLevelProcess(other)(using elideThis) + case l: TypeLambda => prefix :: inParens(inner(l, skipThisTypePrefix)(using elideThis, originalOwner)) + case p: ParamRef => prefix :: inner(p, skipThisTypePrefix)(using elideThis, originalOwner) + case other if !ignore => prefix :: topLevelProcess(other, skipThisTypePrefix)(using elideThis, originalOwner) case _ => Nil } - private def typeBoundsTreeOfHigherKindedType(using Quotes)(low: reflect.TypeRepr, high: reflect.TypeRepr)(using elideThis: reflect.ClassDef) = + private def typeBoundsTreeOfHigherKindedType(using Quotes)(low: reflect.TypeRepr, high: reflect.TypeRepr, skipThisTypePrefix: Boolean)( + using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol + ) = import reflect._ def regularTypeBounds(low: TypeRepr, high: TypeRepr) = - if low == high then keyword(" = ").l ++ inner(low)(using elideThis) - else typeBound(low, low = true)(using elideThis) ++ typeBound(high, low = false)(using elideThis) + if low == high then keyword(" = ").l ++ inner(low, skipThisTypePrefix)(using elideThis, originalOwner) + else typeBound(low, low = true, skipThisTypePrefix)(using elideThis, originalOwner) ++ typeBound(high, low = false, skipThisTypePrefix)(using elideThis, originalOwner) high.match case TypeLambda(params, paramBounds, resType) => if resType.typeSymbol == defn.AnyClass then plain("[").l ++ commas(params.zip(paramBounds).map { (name, typ) => val normalizedName = if name.matches("_\\$\\d*") then "_" else name - tpe(normalizedName).l ++ inner(typ)(using elideThis) + tpe(normalizedName).l ++ inner(typ, skipThisTypePrefix)(using elideThis, originalOwner) }) ++ plain("]").l else regularTypeBounds(low, high) @@ -356,18 +364,23 @@ trait TypesSupport: private def findSupertype(using Quotes)(c: reflect.ClassDef, sym: reflect.Symbol) = getSupertypes(c).find((s, t) => s == sym) - private def skipPrefix(using Quotes)(tr: reflect.TypeRepr, elideThis: reflect.ClassDef) = + private def skipPrefix(using Quotes)(tr: reflect.TypeRepr, elideThis: reflect.ClassDef, originalOwner: reflect.Symbol, skipThisTypePrefix: Boolean) = import reflect._ - def collectOwners(owners: Set[Symbol], sym: Symbol): Set[Symbol] = - if sym.flags.is(Flags.Package) then owners - else collectOwners(owners + sym, sym.owner) - val owners = collectOwners(Set.empty, elideThis.symbol) + def findClassOwner(s: Symbol): Symbol = + if s.isClassDef then s + else if s.exists then findClassOwner(s.owner) + else Symbol.noSymbol + + val classOwner = findClassOwner(originalOwner) tr match case NoPrefix() => true - case ThisType(tp) if owners(tp.typeSymbol) => true - case tp if owners(tp.typeSymbol) => true + case ThisType(tp) if tp.typeSymbol == classOwner || tp.typeSymbol == elideThis.symbol => true + case ThisType(_) if skipThisTypePrefix => true + // case ThisType(_) if elideThis.symbol == classOwner => + // Thread.dumpStack() + // true case _ => val flags = tr.typeSymbol.flags flags.is(Flags.Module) || flags.is(Flags.Package) diff --git a/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala b/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala index adb9397f1bcd..34e9bc128402 100644 --- a/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala +++ b/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala @@ -128,3 +128,5 @@ class RefinedFunctionTypes extends SignatureTest("refinedFunctionTypes", Signatu class RightAssocExtension extends SignatureTest("rightAssocExtension", SignatureTest.all) class NamedTuples extends SignatureTest("namedTuples", SignatureTest.all) + +class InnerClasses extends SignatureTest("innerClasses", SignatureTest.all) From 55ee74fe03062ea3f04975e20f47246b0fe0299e Mon Sep 17 00:00:00 2001 From: Florian3k Date: Sun, 29 Jun 2025 23:43:09 +0200 Subject: [PATCH 3/4] adjust test case --- scaladoc-testcases/src/tests/classSignatureTestSource.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scaladoc-testcases/src/tests/classSignatureTestSource.scala b/scaladoc-testcases/src/tests/classSignatureTestSource.scala index 1ddfff4aeb28..a176a3592f5e 100644 --- a/scaladoc-testcases/src/tests/classSignatureTestSource.scala +++ b/scaladoc-testcases/src/tests/classSignatureTestSource.scala @@ -26,7 +26,7 @@ abstract class Documentation[T, A <: Int, B >: String, -X, +Y](c1: String, val c sealed trait CaseImplementThis(id: Int) - case class IAmACaseClass(x: Documentation.this.T, id: Int) extends CaseImplementThis/*<-*/(id)/*->*/ + case class IAmACaseClass(x: T, id: Int) extends CaseImplementThis(id) //expected: case class IAmACaseClass(x: Documentation.this.T, id: Int) extends CaseImplementThis case class IAmACaseClassWithParam[T](x: Documentation.this.T, id: T) From 0f89ae9d012dc33e2fd20a9775b72cc96fc3bd95 Mon Sep 17 00:00:00 2001 From: Florian3k Date: Mon, 30 Jun 2025 16:48:15 +0200 Subject: [PATCH 4/4] remove debugging code --- scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala | 3 --- 1 file changed, 3 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index 80981573f01e..30a5ac22be0d 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -378,9 +378,6 @@ trait TypesSupport: case NoPrefix() => true case ThisType(tp) if tp.typeSymbol == classOwner || tp.typeSymbol == elideThis.symbol => true case ThisType(_) if skipThisTypePrefix => true - // case ThisType(_) if elideThis.symbol == classOwner => - // Thread.dumpStack() - // true case _ => val flags = tr.typeSymbol.flags flags.is(Flags.Module) || flags.is(Flags.Package)