diff --git a/project/Build.scala b/project/Build.scala index 9f2e1bc973d7..02ef660048fd 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1327,7 +1327,7 @@ object Build { import _root_.scala.sys.process._ import _root_.scala.concurrent._ import ExecutionContext.Implicits.global - val inkuireVersion = "1.0.0-M2" + val inkuireVersion = "1.0.0-M3" val inkuireLink = s"https://github.com/VirtusLab/Inkuire/releases/download/$inkuireVersion/inkuire.js" val inkuireDestinationFile = (Compile / resourceManaged).value / "dotty_res" / "scripts" / "inkuire.js" sbt.IO.touch(inkuireDestinationFile) diff --git a/scaladoc-js/src/searchbar/PageEntry.scala b/scaladoc-js/src/searchbar/PageEntry.scala index 4429965a2060..7b0837e87a95 100644 --- a/scaladoc-js/src/searchbar/PageEntry.scala +++ b/scaladoc-js/src/searchbar/PageEntry.scala @@ -25,7 +25,8 @@ case class InkuireMatch( functionName: String, packageLocation: String, pageLocation: String, - entryType: String + entryType: String, + mq: Int ) object PageEntry { diff --git a/scaladoc-js/src/searchbar/SearchbarComponent.scala b/scaladoc-js/src/searchbar/SearchbarComponent.scala index d45352df3f54..c77c8b6b9d83 100644 --- a/scaladoc-js/src/searchbar/SearchbarComponent.scala +++ b/scaladoc-js/src/searchbar/SearchbarComponent.scala @@ -1,6 +1,7 @@ package dotty.tools.scaladoc import org.scalajs.dom._ +import org.scalajs.dom.ext._ import org.scalajs.dom.html.Input import scala.scalajs.js.timers._ import scala.concurrent.duration._ @@ -40,6 +41,7 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch val wrapper = document.createElement("div").asInstanceOf[html.Div] wrapper.classList.add("scaladoc-searchbar-result") wrapper.classList.add("monospace") + wrapper.setAttribute("mq", m.mq.toString) val resultDiv = document.createElement("div").asInstanceOf[html.Div] resultDiv.classList.add("scaladoc-searchbar-result-row") @@ -132,7 +134,16 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch loading.appendChild(animation) properResultsDiv.appendChild(loading) inkuireEngine.query(query) { (m: InkuireMatch) => - properResultsDiv.appendChild(m.toHTML) + val next = properResultsDiv.children.foldLeft[Option[Element]](None) { + case (acc, child) if !acc.isEmpty => acc + case (_, child) => + Option.when(child.hasAttribute("mq") && Integer.parseInt(child.getAttribute("mq")) > m.mq)(child) + } + next.fold { + properResultsDiv.appendChild(m.toHTML) + } { next => + properResultsDiv.insertBefore(m.toHTML, next) + } } { (s: String) => animation.classList.remove("loading") properResultsDiv.appendChild(s.toHTMLError) diff --git a/scaladoc-js/src/searchbar/engine/InkuireJSSearchEngine.scala b/scaladoc-js/src/searchbar/engine/InkuireJSSearchEngine.scala index 9a1c75947307..f3e7cfd52417 100644 --- a/scaladoc-js/src/searchbar/engine/InkuireJSSearchEngine.scala +++ b/scaladoc-js/src/searchbar/engine/InkuireJSSearchEngine.scala @@ -22,7 +22,8 @@ class InkuireJSSearchEngine { d.functionName.asInstanceOf[String], d.packageLocation.asInstanceOf[String], d.pageLocation.asInstanceOf[String], - d.entryType.asInstanceOf[String] + d.entryType.asInstanceOf[String], + d.mq.asInstanceOf[Int] ) } diff --git a/scaladoc-testcases/src/tests/anImplicitClass.scala b/scaladoc-testcases/src/tests/anImplicitClass.scala new file mode 100644 index 000000000000..3653e867302c --- /dev/null +++ b/scaladoc-testcases/src/tests/anImplicitClass.scala @@ -0,0 +1,4 @@ +package tests.anImplicitClass + +implicit class AddingOps(n: Int): + def inc: Int = n + 1 diff --git a/scaladoc-testcases/src/tests/caseClassesWithVars.scala b/scaladoc-testcases/src/tests/caseClassesWithVars.scala new file mode 100644 index 000000000000..8782846665ed --- /dev/null +++ b/scaladoc-testcases/src/tests/caseClassesWithVars.scala @@ -0,0 +1,6 @@ +package tests.caseClassesWithVars + +case class Sheep(var numberOfLegs: Int): + var name: String = "Lawrence" + +class Goat(var numberOfLegs: Int) diff --git a/scaladoc-testcases/src/tests/justmethods.scala b/scaladoc-testcases/src/tests/justmethods.scala new file mode 100644 index 000000000000..13c957305aa8 --- /dev/null +++ b/scaladoc-testcases/src/tests/justmethods.scala @@ -0,0 +1,5 @@ +package tests.justmethods + +trait Animal + +def whatSoundDoIMake(animal: Animal): String = "IoIo" diff --git a/scaladoc-testcases/src/tests/syntaxes.scala b/scaladoc-testcases/src/tests/syntaxes.scala new file mode 100644 index 000000000000..51c929bf0ae3 --- /dev/null +++ b/scaladoc-testcases/src/tests/syntaxes.scala @@ -0,0 +1,11 @@ +package test.syntaxes + +class DoingStuffOps[A](a: A): + def doStuff: Unit = () + +trait DoingStuffSyntax: + implicit def toDoingStuffOps[A](a: A): DoingStuffOps[A] = DoingStuffOps(a) + +trait AllSyntaxes extends DoingStuffSyntax + +object doingstuff extends AllSyntaxes diff --git a/scaladoc/src/dotty/tools/scaladoc/Inkuire.scala b/scaladoc/src/dotty/tools/scaladoc/Inkuire.scala index 85624e8b15d7..f1b9414c9835 100644 --- a/scaladoc/src/dotty/tools/scaladoc/Inkuire.scala +++ b/scaladoc/src/dotty/tools/scaladoc/Inkuire.scala @@ -5,12 +5,15 @@ import dotty.tools.scaladoc.util._ object Inkuire { var db = InkuireDb(Seq.empty, Map.empty, Seq.empty, Map.empty) + var implicitConversions: Seq[(Option[TypeLike], Type)] = Seq.empty def beforeSave(): Unit = { db = db.copy( - functions = db.functions.sortBy(_.hashCode), + functions = functions.sortBy(_.hashCode), types = db.types.toSeq.sortBy(_._1.uuid).toMap, - implicitConversions = db.implicitConversions.sortBy(_._1.uuid) + implicitConversions = implicitConversions.collect { + case (Some(from), to) => from -> to + }.sortBy(_._1.hashCode) ) } @@ -34,10 +37,59 @@ object Inkuire { case _ => e } + def functions: Seq[ExternalSignature] = Inkuire.db.functions.flatMap { func => + val fromConversions = Inkuire.implicitConversions.filter { ic => + func.signature.receiver.nonEmpty && matchingICTypes(ic._2, func.signature.receiver.get.typ) + }.map { + case (Some(from), to) => + func.copy( + signature = func.signature.copy( + receiver = func.signature.receiver.map { rcvr => + Contravariance( + newReceiver(rcvr.typ, to, from) + ) + } + ) + ) + case (None, to) => + func.copy( + signature = func.signature.copy( + receiver = None + ) + ) + } + Seq(func) ++ fromConversions + } + .distinct + + def matchingICTypes(a: TypeLike, b: TypeLike): Boolean = (a, b) match { + case (a: Type, b: Type) if a.params.size == 0 && b.params.size == 0 && a.itid == b.itid => true + case (a: Type, b: Type) if a.params.size == 1 && b.params.size == 1 && a.itid == b.itid => + a.params.head.typ.isInstanceOf[Type] && a.params.head.typ.asInstanceOf[Type].isVariable && + b.params.head.typ.isInstanceOf[Type] && b.params.head.typ.asInstanceOf[Type].isVariable + case _ => false + } + + def newReceiver(old: TypeLike, to: TypeLike, from: TypeLike): TypeLike = (old, to) match { + case (a: Type, b: Type) if a.params.size == 0 && b.params.size == 0 && a.itid == b.itid => from + case (a: Type, b: Type) if a.params.size == 1 && b.params.size == 1 && a.itid == b.itid + && a.params.head.typ.isInstanceOf[Type] && a.params.head.typ.asInstanceOf[Type].isVariable && + b.params.head.typ.isInstanceOf[Type] && b.params.head.typ.asInstanceOf[Type].isVariable => + if from.isInstanceOf[Type] && from.asInstanceOf[Type].params.size == 1 && from.asInstanceOf[Type].params.head.typ.isInstanceOf[Type] && from.asInstanceOf[Type].params.head.typ.asInstanceOf[Type].isVariable then + from.asInstanceOf[Type].copy( + params = Seq(Contravariance(a.params.head.typ.asInstanceOf[Type])) + ) + else if from.isInstanceOf[Type] && from.asInstanceOf[Type].isVariable then + a.params.head.typ.asInstanceOf[Type] + else + from + case _ => old + } + case class InkuireDb( functions: Seq[ExternalSignature], types: Map[ITID, (Type, Seq[Type])], - implicitConversions: Seq[(ITID, Type)], + implicitConversions: Seq[(TypeLike, Type)], typeAliases: Map[ITID, TypeLike] ) @@ -164,7 +216,7 @@ object Inkuire { ) } - private def serializeConversion(conversion: (ITID, Type)): JSON = { + private def serializeConversion(conversion: (TypeLike, Type)): JSON = { jsonList( Seq( serialize(conversion._1), diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala index bbf737464eff..6cdca1590eae 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -100,133 +100,7 @@ trait ClassLikeSupport: deprecated = classDef.symbol.isDeprecated() ) - if summon[DocContext].args.generateInkuire then { - - val classType: Inkuire.Type = classDef.asInkuire(Set.empty).asInstanceOf[Inkuire.Type] - - def varName(t: Inkuire.TypeLike): Option[String] = t match { - case tpe: Inkuire.Type => Some(tpe.name.name) - case tl: Inkuire.TypeLambda => varName(tl.result) - case _ => None - } - - val variableNames: Set[String] = classType.params.map(_.typ) - .flatMap(varName(_).toList).toSet - - val parents: Seq[Inkuire.Type] = classDef.parents.map(_.asInkuire(variableNames).asInstanceOf[Inkuire.Type]) - - val isModule = classDef.symbol.flags.is(Flags.Module) - - if !isModule then Inkuire.db = Inkuire.db.copy(types = Inkuire.db.types.updated(classType.itid.get, (classType, parents))) - - classDef.symbol.declaredTypes - .filter(viableSymbol) - .foreach { - case typeSymbol: Symbol if typeSymbol.flags.is(Flags.Opaque) => - val typ = typeSymbol.tree.asInkuire(variableNames) - if typ.isInstanceOf[Inkuire.Type] then { - val t = typ.asInstanceOf[Inkuire.Type] - Inkuire.db = Inkuire.db.copy(types = Inkuire.db.types.updated(t.itid.get, (t, Seq.empty))) - } - case typeSymbol: Symbol if !typeSymbol.isClassDef => - val typeDef = typeSymbol.tree.asInstanceOf[TypeDef] - val typ = typeSymbol.tree.asInkuire(variableNames) - if typ.isInstanceOf[Inkuire.Type] then { - val t = typ.asInstanceOf[Inkuire.Type] - val rhsTypeLike = typeDef.rhs.asInkuire(variableNames) - Inkuire.db = Inkuire.db.copy( - typeAliases = Inkuire.db.typeAliases.updated(t.itid.get, rhsTypeLike), - types = Inkuire.db.types.updated(t.itid.get, (t, Seq.empty)) - ) - } - if typeDef.rhs.symbol.flags.is(Flags.JavaDefined) then - val typJava = typeDef.rhs.asInkuire(variableNames) - if typJava.isInstanceOf[Inkuire.Type] then { - val tJava = typJava.asInstanceOf[Inkuire.Type] - Inkuire.db = Inkuire.db.copy(types = Inkuire.db.types.updated(tJava.itid.get, (tJava, Seq.empty))) - } - case _ => - } - - def viableSymbol(s: Symbol): Boolean = - !s.flags.is(Flags.Private) && - !s.flags.is(Flags.Protected) && - !s.flags.is(Flags.Override) && - !s.flags.is(Flags.Synthetic) - - classDef.symbol.declaredMethods - .filter(viableSymbol) - .foreach { - case implicitConversion: Symbol if implicitConversion.flags.is(Flags.Implicit) - && classDef.symbol.flags.is(Flags.Module) - && implicitConversion.owner.fullName == ("scala.Predef$") => - val defdef = implicitConversion.tree.asInstanceOf[DefDef] - val to = defdef.returnTpt.asInkuire(variableNames) - val from = defdef.paramss.flatMap(_.params).collectFirst { - case v: ValDef => v.tpt.asInkuire(variableNames) - } - (from, to) match - case (Some(from: Inkuire.Type), to: Inkuire.Type) => Inkuire.db = Inkuire.db.copy(implicitConversions = Inkuire.db.implicitConversions :+ (from.itid.get -> to)) - case _ => - - case methodSymbol: Symbol => - val defdef = methodSymbol.tree.asInstanceOf[DefDef] - val methodVars = defdef.paramss.flatMap(_.params).collect { - case TypeDef(name, _) => name - } - val vars = variableNames ++ methodVars - val receiver: Option[Inkuire.TypeLike] = - Some(classType) - .filter(_ => !isModule) - .orElse(methodSymbol.extendedSymbol.flatMap(s => partialAsInkuire(vars).lift(s.tpt))) - val sgn = Inkuire.ExternalSignature( - signature = Inkuire.Signature( - receiver = receiver, - 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), - context = Inkuire.SignatureContext( - vars = vars.toSet, - constraints = Map.empty //TODO [Inkuire] Type bounds - ) - ), - name = methodSymbol.name, - packageName = methodSymbol.dri.location, - uri = methodSymbol.dri.externalLink.getOrElse(""), - entryType = "def" - ) - val curriedSgn = sgn.copy(signature = Inkuire.curry(sgn.signature)) - Inkuire.db = Inkuire.db.copy(functions = Inkuire.db.functions :+ curriedSgn) - } - - classDef.symbol.declaredFields - .filter(viableSymbol) - .foreach { - case valSymbol: Symbol => - val valdef = valSymbol.tree.asInstanceOf[ValDef] - val receiver: Option[Inkuire.TypeLike] = - Some(classType) - .filter(_ => !isModule) - val sgn = Inkuire.ExternalSignature( - signature = Inkuire.Signature( - receiver = receiver, - arguments = Seq.empty, - result = valdef.tpt.asInkuire(variableNames), - context = Inkuire.SignatureContext( - vars = variableNames.toSet, - constraints = Map.empty //TODO [Inkuire] Type bounds - ) - ), - name = valSymbol.name, - packageName = valSymbol.dri.location, - uri = valSymbol.dri.externalLink.getOrElse(""), - entryType = "val" - ) - val curriedSgn = sgn.copy(signature = Inkuire.curry(sgn.signature)) - Inkuire.db = Inkuire.db.copy(functions = Inkuire.db.functions :+ curriedSgn) - } - } + if summon[DocContext].args.generateInkuire then doInkuireStuff(classDef) if signatureOnly then baseMember else baseMember.copy( members = classDef.extractPatchedMembers.sortBy(m => (m.name, m.kind.name)), diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/InkuireSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/InkuireSupport.scala index a5103c914732..3e1a09574b16 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/InkuireSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/InkuireSupport.scala @@ -7,6 +7,7 @@ import dotty.tools.scaladoc.Inkuire import scala.util.Random import scala.quoted._ +import scala.util.chaining._ import SymOps._ import NameNormalizer._ @@ -18,6 +19,176 @@ trait InkuireSupport: private given qctx.type = qctx + def doInkuireStuff(classDef: ClassDef): Unit = { + val classType: Inkuire.Type = classDef.asInkuire(Set.empty).asInstanceOf[Inkuire.Type] + + val variableNames: Set[String] = classType.params.map(_.typ) + .flatMap(varName(_).toList).toSet + + val parents: Seq[Inkuire.Type] = classDef.parents.map(_.asInkuire(variableNames).asInstanceOf[Inkuire.Type]) + + val isModule = classDef.symbol.flags.is(Flags.Module) + + if !isModule then Inkuire.db = Inkuire.db.copy(types = Inkuire.db.types.updated(classType.itid.get, (classType, parents))) + + classDef.symbol.declaredTypes + .filter(viableSymbol) + .foreach { + case typeSymbol: Symbol if typeSymbol.flags.is(Flags.Opaque) => + val typ = typeSymbol.tree.asInkuire(variableNames) + if typ.isInstanceOf[Inkuire.Type] then { + val t = typ.asInstanceOf[Inkuire.Type] + Inkuire.db = Inkuire.db.copy(types = Inkuire.db.types.updated(t.itid.get, (t, Seq.empty))) + } + case typeSymbol: Symbol if !typeSymbol.isClassDef => + val typeDef = typeSymbol.tree.asInstanceOf[TypeDef] + val typ = typeSymbol.tree.asInkuire(variableNames) + if typ.isInstanceOf[Inkuire.Type] then { + val t = typ.asInstanceOf[Inkuire.Type] + val rhsTypeLike = typeDef.rhs.asInkuire(variableNames) + val withType = Inkuire.db.copy( + types = Inkuire.db.types.updated(t.itid.get, (t, Seq.empty)) + ) + typeDef.rhs match { + case TypeBoundsTree(_, r) if r.asInkuire(variableNames).isInstanceOf[Inkuire.Type] => + Inkuire.db = Inkuire.db.copy( + types = Inkuire.db.types.updated(t.itid.get, (t, Seq(r.asInkuire(variableNames).asInstanceOf[Inkuire.Type]))) + ) + case _ => + Inkuire.db = withType.copy( + types = Inkuire.db.types.updated(t.itid.get, (t, Seq.empty)), + typeAliases = Inkuire.db.typeAliases.updated(t.itid.get, rhsTypeLike) + ) + } + } + if typeDef.rhs.symbol.flags.is(Flags.JavaDefined) then + val typJava = typeDef.rhs.asInkuire(variableNames) + if typJava.isInstanceOf[Inkuire.Type] then { + val tJava = typJava.asInstanceOf[Inkuire.Type] + Inkuire.db = Inkuire.db.copy(types = Inkuire.db.types.updated(tJava.itid.get, (tJava, Seq.empty))) + } + case _ => + } + + if classDef.symbol.isImplicitClass then // Implicit classes <- synthetic method with the same name + classDef.symbol.maybeOwner.declarations + .filter { methodSymbol => + methodSymbol.name == classDef.symbol.name && methodSymbol.flags.is(Flags.Implicit) && methodSymbol.flags.is(Flags.Method) + } + .foreach(handleImplicitConversion(_, variableNames)) + + classDef.symbol.declaredMethods + .filter(viableSymbol) + .tap { _.foreach { // Loop for implicit conversions + case implicitConversion: Symbol if implicitConversion.flags.is(Flags.Implicit) || implicitConversion.flags.is(Flags.Given) => + handleImplicitConversion(implicitConversion, variableNames) + case _ => + }} + .tap { _.foreach { // Loop for functions and vals + case methodSymbol: Symbol => + val defdef = methodSymbol.tree.asInstanceOf[DefDef] + val methodVars = defdef.paramss.flatMap(_.params).collect { + case TypeDef(name, _) => name + } + val vars = variableNames ++ methodVars + val (receiver, preArgs): (Option[Inkuire.TypeLike], Seq[Inkuire.TypeLike]) = Some(classType).filter(_ => !isModule) match { + case None => (methodSymbol.extendedSymbol.flatMap(s => partialAsInkuire(vars).lift(s.tpt)), Seq.empty) + case rcvr => (rcvr, methodSymbol.extendedSymbol.flatMap(s => partialAsInkuire(vars).lift(s.tpt)).toSeq) + } + val (name, ownerName) = nameAndOwnerName(classDef, methodSymbol) + val sgn = Inkuire.ExternalSignature( + signature = Inkuire.Signature( + receiver = receiver, + arguments = preArgs ++ 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), + context = Inkuire.SignatureContext( + vars = vars.toSet, + constraints = Map.empty //TODO [Inkuire] Type bounds + ) + ), + name = name, + packageName = ownerName, + uri = methodSymbol.dri.externalLink.getOrElse(""), + entryType = "def" + ) + val curriedSgn = sgn.copy(signature = Inkuire.curry(sgn.signature)) + Inkuire.db = Inkuire.db.copy(functions = Inkuire.db.functions :+ curriedSgn) + }} + + classDef.symbol.declaredFields + .filter(viableSymbol) + .foreach { + case valSymbol: Symbol => + val valdef = valSymbol.tree.asInstanceOf[ValDef] + val receiver: Option[Inkuire.TypeLike] = + Some(classType) + .filter(_ => !isModule) + val (name, ownerName) = nameAndOwnerName(classDef, valSymbol) + val sgn = Inkuire.ExternalSignature( + signature = Inkuire.Signature( + receiver = receiver, + arguments = Seq.empty, + result = valdef.tpt.asInkuire(variableNames), + context = Inkuire.SignatureContext( + vars = variableNames.toSet, + constraints = Map.empty //TODO [Inkuire] Type bounds + ) + ), + name = name, + packageName = ownerName, + uri = valSymbol.dri.externalLink.getOrElse(""), + entryType = "val" + ) + val curriedSgn = sgn.copy(signature = Inkuire.curry(sgn.signature)) + Inkuire.db = Inkuire.db.copy(functions = Inkuire.db.functions :+ curriedSgn) + } + } + + private def handleImplicitConversion(implicitConversion: Symbol, variableNames: Set[String]) = { + val defdef = implicitConversion.tree.asInstanceOf[DefDef] + val methodVars = defdef.paramss.flatMap(_.params).collect { + case TypeDef(name, _) => name + } + val vars = variableNames ++ methodVars + val to = defdef.returnTpt.asInkuire(vars) + val from = defdef.paramss.flatMap(_.params).collectFirst { + case v: ValDef => v.tpt.asInkuire(vars) + } + (from, to) match + case (from, to: Inkuire.Type) => Inkuire.implicitConversions = Inkuire.implicitConversions :+ (from -> to) + case _ => + } + + private def nameAndOwnerName(classDef: ClassDef, symbol: Symbol): (String, String) = + if classDef.symbol.flags.is(Flags.Module) || Seq("apply", "unapply").contains(symbol.name) then + symbol.maybeOwner.normalizedName + "." + symbol.name -> + ownerNameChain(classDef.symbol.maybeOwner).mkString(".") + else + symbol.name -> + ownerNameChain(classDef.symbol).mkString(".") + + private def ownerNameChain(sym: Symbol): List[String] = + if sym.isNoSymbol then List.empty + else if sym == defn.EmptyPackageClass then List.empty + else if sym == defn.RootPackage then List.empty + else if sym == defn.RootClass then List.empty + else if sym.normalizedName.contains("$package") then ownerNameChain(sym.owner) + else ownerNameChain(sym.owner) :+ sym.normalizedName + + private def viableSymbol(s: Symbol): Boolean = + !s.flags.is(Flags.Private) && + !s.flags.is(Flags.Protected) && + !s.flags.is(Flags.Override) && + !s.flags.is(Flags.Synthetic) + + private def varName(t: Inkuire.TypeLike): Option[String] = t match { + case tpe: Inkuire.Type => Some(tpe.name.name) + case tl: Inkuire.TypeLambda => varName(tl.result) + case _ => None + } + private def paramsForClass(classDef: ClassDef, vars: Set[String]): Seq[Inkuire.Variance] = classDef.getTypeParams.map(mkTypeArgumentInkuire) @@ -26,8 +197,8 @@ trait InkuireSupport: def asInkuire(vars: Set[String]): Inkuire.TypeLike = partialAsInkuire(vars)(tpeTree) - def partialAsInkuire(vars: Set[String]): PartialFunction[Tree, Inkuire.TypeLike] = { - case TypeBoundsTree(low, high) => inner(low.tpe, vars) //TODO [Inkuire] Type bounds + private def partialAsInkuire(vars: Set[String]): PartialFunction[Tree, Inkuire.TypeLike] = { + case tpeTree: TypeBoundsTree => inner(tpeTree.tpe, vars) case tpeTree: Applied => inner(tpeTree.tpe, vars) case tpeTree: TypeTree => @@ -37,7 +208,7 @@ trait InkuireSupport: case typeDef: TypeDef => mkTypeDef(typeDef) } - def mkTypeDef(typeDef: TypeDef): Inkuire.Type = typeDef.rhs match { + private def mkTypeDef(typeDef: TypeDef): Inkuire.Type = typeDef.rhs match { case LambdaTypeTree(paramsDefs, _) => val name = typeDef.symbol.normalizedName val normalizedName = if name.matches("_\\$\\d*") then "_" else name @@ -54,7 +225,7 @@ trait InkuireSupport: ) } - def mkTypeFromClassDef(classDef: ClassDef, vars: Set[String]): Inkuire.Type = { + private def mkTypeFromClassDef(classDef: ClassDef, vars: Set[String]): Inkuire.Type = { Inkuire.Type( name = Inkuire.TypeName(classDef.name), itid = classDef.symbol.itid, @@ -81,7 +252,7 @@ trait InkuireSupport: ) } - def mkTypeArgumentInkuire(argument: TypeDef): Inkuire.Variance = + private def mkTypeArgumentInkuire(argument: TypeDef): Inkuire.Variance = //TODO [Inkuire] Type bounds (other than just HKTs) val name = argument.symbol.normalizedName val normalizedName = if name.matches("_\\$\\d*") then "_" else name @@ -99,7 +270,7 @@ trait InkuireSupport: else if argument.symbol.flags.is(Flags.Contravariant) then Inkuire.Contravariance(t) else Inkuire.Invariance(t) - def typeVariableDeclarationParamsNo(argument: TypeDef): Int = + private def typeVariableDeclarationParamsNo(argument: TypeDef): Int = argument.rhs match case t: TypeTree => t.tpe match case TypeBounds(_, TypeLambda(names, _, _)) => names.size @@ -120,67 +291,76 @@ trait InkuireSupport: case _ => false case _ => false - private def inner(tp: TypeRepr, vars: Set[String]): Inkuire.TypeLike = tp match - case OrType(left, right) => Inkuire.OrType(inner(left, vars), inner(right, vars)) - case AndType(left, right) => Inkuire.AndType(inner(left, vars), inner(right, vars)) - case ByNameType(tpe) => inner(tpe, vars) - case ConstantType(constant) => - Inkuire.Type( - name = Inkuire.TypeName(constant.toString), - params = Seq.empty, - itid = Some(Inkuire.ITID(constant.toString, isParsed = false)) - ) - case ThisType(tpe) => inner(tpe, vars) - case AnnotatedType(AppliedType(_, Seq(tpe)), annotation) if isRepeatedAnnotation(annotation) => - inner(tpe, vars) //TODO [Inkuire] Repeated types - case AppliedType(repeatedClass, Seq(tpe)) if isRepeated(repeatedClass) => - inner(tpe, vars) //TODO [Inkuire] Repeated types - case AnnotatedType(tpe, _) => - inner(tpe, vars) - case tl @ TypeLambda(paramNames, _, resType) => - Inkuire.TypeLambda(paramNames.map(Inkuire.TypeLambda.argument), inner(resType, vars)) //TODO [Inkuire] Type bounds - case r: Refinement => - inner(r.info, vars) //TODO [Inkuire] Refinements - case t @ AppliedType(tpe, typeList) => - import dotty.tools.dotc.util.Chars._ - if t.isFunctionType then - val name = s"Function${typeList.size-1}" + private def inner(tp: TypeRepr, vars: Set[String]): Inkuire.TypeLike = + tp match + case OrType(left, right) => Inkuire.OrType(inner(left, vars), inner(right, vars)) + case AndType(left, right) => Inkuire.AndType(inner(left, vars), inner(right, vars)) + case ByNameType(tpe) => inner(tpe, vars) + case ConstantType(constant) => Inkuire.Type( - name = Inkuire.TypeName(name), - params = typeList.init.map(p => Inkuire.Contravariance(inner(p, vars))) :+ Inkuire.Covariance(inner(typeList.last, vars)), - itid = Some(Inkuire.ITID(s"${name}scala.${name}//[]", isParsed = false)) + name = Inkuire.TypeName(constant.toString), + params = Seq.empty, + itid = Some(Inkuire.ITID(constant.toString, isParsed = false)) + ) + case ThisType(tpe) => inner(tpe, vars) + case AnnotatedType(AppliedType(_, Seq(tpe)), annotation) if isRepeatedAnnotation(annotation) => + inner(tpe, vars) //TODO [Inkuire] Repeated types + case AppliedType(repeatedClass, Seq(tpe)) if isRepeated(repeatedClass) => + inner(tpe, vars) //TODO [Inkuire] Repeated types + case AnnotatedType(tpe, _) => + inner(tpe, vars) + case tl @ TypeLambda(paramNames, _, resType) => + Inkuire.TypeLambda(paramNames.map(Inkuire.TypeLambda.argument), inner(resType, vars)) //TODO [Inkuire] Type bounds + case r: Refinement => + inner(r.info, vars) //TODO [Inkuire] Refinements + case t @ AppliedType(tpe, typeList) => + import dotty.tools.dotc.util.Chars._ + if t.isFunctionType then + val name = s"Function${typeList.size-1}" + Inkuire.Type( + name = Inkuire.TypeName(name), + params = typeList.init.map(p => Inkuire.Contravariance(inner(p, vars))) :+ Inkuire.Covariance(inner(typeList.last, vars)), + itid = Some(Inkuire.ITID(s"${name}scala.${name}//[]", isParsed = false)) + ) + else if t.isTupleN then + val name = s"Tuple${typeList.size}" + Inkuire.Type( + name = Inkuire.TypeName(name), + params = typeList.map(p => Inkuire.Covariance(inner(p, vars))), + itid = Some(Inkuire.ITID(s"${name}scala.${name}//[]", isParsed = false)) + ) + else + inner(tpe, vars).asInstanceOf[Inkuire.Type].copy( + params = typeList.map(p => Inkuire.Invariance(inner(p, vars))) + ) + case tp: TypeRef => + Inkuire.Type( + name = Inkuire.TypeName(tp.name), + itid = tp.typeSymbol.itid, + params = Seq.empty, + isVariable = vars.contains(tp.name) ) - else if t.isTupleN then - val name = s"Tuple${typeList.size}" + case tr @ TermRef(qual, typeName) => + inner(qual, vars) + case tb@TypeBounds(low, hi) => + if low.typeSymbol != defn.NothingClass || hi.typeSymbol == defn.AnyClass then + inner(low, vars) //TODO [Inkuire] Type bounds + else + inner(hi, vars) + case NoPrefix() => + Inkuire.Type.unresolved //TODO [Inkuire] <- should be handled by Singleton case, but didn't work + case MatchType(bond, sc, cases) => + inner(sc, vars) + case ParamRef(TypeLambda(names, _, _), i) => + Inkuire.TypeLambda.argument(names(i)) + case ParamRef(m: MethodType, i) => + inner(m.paramTypes(i), vars) + case RecursiveType(tp) => + inner(tp, vars) + case m@MethodType(_, typeList, resType) => + val name = s"Function${typeList.size-1}" Inkuire.Type( name = Inkuire.TypeName(name), - params = typeList.map(p => Inkuire.Covariance(inner(p, vars))), + params = typeList.map(p => Inkuire.Contravariance(inner(p, vars))) :+ Inkuire.Covariance(inner(resType, vars)), itid = Some(Inkuire.ITID(s"${name}scala.${name}//[]", isParsed = false)) ) - else - inner(tpe, vars).asInstanceOf[Inkuire.Type].copy( - params = typeList.map(p => Inkuire.Invariance(inner(p, vars))) - ) - case tp: TypeRef => - Inkuire.Type( - name = Inkuire.TypeName(tp.name), - itid = tp.typeSymbol.itid, - params = Seq.empty, - isVariable = vars.contains(tp.name) - ) - case tr @ TermRef(qual, typeName) => - inner(qual, vars) - case TypeBounds(low, hi) => - inner(low, vars) //TODO [Inkuire] Type bounds - case NoPrefix() => - Inkuire.Type.unresolved //TODO [Inkuire] <- should be handled by Singleton case, but didn't work - case MatchType(bond, sc, cases) => - inner(sc, vars) - case ParamRef(TypeLambda(names, _, _), i) => - Inkuire.TypeLambda.argument(names(i)) - case ParamRef(m: MethodType, i) => - inner(m.paramTypes(i), vars) - case RecursiveType(tp) => - inner(tp, vars) - case MethodType(_, params, resType) => - inner(resType, vars) //TODO [Inkuire] Method type diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala index defbdd5bbee7..cd079e8f237a 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala @@ -12,6 +12,13 @@ object SymOps: extension (using Quotes)(sym: reflect.Symbol) + def isImplicitClass: Boolean = + import reflect._ + sym.isClassDef && sym.maybeOwner != Symbol.noSymbol + && sym.maybeOwner.declaredMethods.exists { methodSymbol => + methodSymbol.name == sym.name && methodSymbol.flags.is(Flags.Implicit) && methodSymbol.flags.is(Flags.Method) + } + def packageName: String = if (sym.isPackageDef) sym.fullName else sym.maybeOwner.packageName @@ -91,7 +98,9 @@ object SymOps: Flags.Open -> Modifier.Open, Flags.Override -> Modifier.Override, Flags.Case -> Modifier.Case, - ).collect { case (flag, mod) if sym.flags.is(flag) => mod } + ).collect { + case (flag, mod) if sym.flags.is(flag) => mod + } ++ Seq(Modifier.Implicit).filter(_ => sym.isImplicitClass) def isHiddenByVisibility(using dctx: DocContext): Boolean = import VisibilityScope._