diff --git a/scaladoc-js/resources/scaladoc-searchbar.css b/scaladoc-js/resources/scaladoc-searchbar.css index a248eeb5c328..7c81c3fa75b6 100644 --- a/scaladoc-js/resources/scaladoc-searchbar.css +++ b/scaladoc-js/resources/scaladoc-searchbar.css @@ -80,10 +80,13 @@ background-color: var(--leftbar-bg); color: var(--leftbar-fg); line-height: 24px; - display: flex; padding: 4px 10px 4px 10px; } +.scaladoc-searchbar-result-row { + display: flex; +} + .scaladoc-searchbar-result .micon { height: 16px; width: 16px; diff --git a/scaladoc-js/src/searchbar/PageEntry.scala b/scaladoc-js/src/searchbar/PageEntry.scala index 8caaf8c9d806..4429965a2060 100644 --- a/scaladoc-js/src/searchbar/PageEntry.scala +++ b/scaladoc-js/src/searchbar/PageEntry.scala @@ -20,6 +20,14 @@ case class PageEntry( tokens: List[String] ) +case class InkuireMatch( + prettifiedSignature: String, + functionName: String, + packageLocation: String, + pageLocation: String, + entryType: String +) + object PageEntry { def apply(jsObj: PageEntryJS): PageEntry = PageEntry( jsObj.t, diff --git a/scaladoc-js/src/searchbar/SearchbarComponent.scala b/scaladoc-js/src/searchbar/SearchbarComponent.scala index 16fe6a22f662..d45352df3f54 100644 --- a/scaladoc-js/src/searchbar/SearchbarComponent.scala +++ b/scaladoc-js/src/searchbar/SearchbarComponent.scala @@ -8,9 +8,10 @@ import scala.concurrent.duration._ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearchEngine, parser: QueryParser): val resultsChunkSize = 100 extension (p: PageEntry) - def toHTML(inkuire: Boolean = false) = + def toHTML = val wrapper = document.createElement("div").asInstanceOf[html.Div] wrapper.classList.add("scaladoc-searchbar-result") + wrapper.classList.add("scaladoc-searchbar-result-row") wrapper.classList.add("monospace") val icon = document.createElement("span").asInstanceOf[html.Span] @@ -18,7 +19,7 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch icon.classList.add(p.kind.take(2)) val resultA = document.createElement("a").asInstanceOf[html.Anchor] - resultA.href = if inkuire then p.location else Globals.pathToRoot + p.location + resultA.href = Globals.pathToRoot + p.location resultA.text = s"${p.fullName}" val location = document.createElement("span") @@ -34,8 +35,52 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch }) wrapper + extension (m: InkuireMatch) + def toHTML = + val wrapper = document.createElement("div").asInstanceOf[html.Div] + wrapper.classList.add("scaladoc-searchbar-result") + wrapper.classList.add("monospace") + + val resultDiv = document.createElement("div").asInstanceOf[html.Div] + resultDiv.classList.add("scaladoc-searchbar-result-row") + + val icon = document.createElement("span").asInstanceOf[html.Span] + icon.classList.add("micon") + icon.classList.add(m.entryType.take(2)) + + val resultA = document.createElement("a").asInstanceOf[html.Anchor] + resultA.href = m.pageLocation + resultA.text = m.functionName + + val packageDiv = document.createElement("div").asInstanceOf[html.Div] + packageDiv.classList.add("scaladoc-searchbar-inkuire-package") + + val packageIcon = document.createElement("span").asInstanceOf[html.Span] + packageIcon.classList.add("micon") + packageIcon.classList.add("pa") + + val packageSpan = document.createElement("span").asInstanceOf[html.Span] + packageSpan.textContent = m.packageLocation + + val signature = document.createElement("span") + signature.classList.add("pull-right") + signature.classList.add("scaladoc-searchbar-inkuire-signature") + signature.textContent = m.prettifiedSignature + + wrapper.appendChild(resultDiv) + resultDiv.appendChild(icon) + resultDiv.appendChild(resultA) + resultA.appendChild(signature) + wrapper.appendChild(packageDiv) + packageDiv.appendChild(packageIcon) + packageDiv.appendChild(packageSpan) + wrapper.addEventListener("mouseover", { + case e: MouseEvent => handleHover(wrapper) + }) + wrapper + def handleNewFluffQuery(matchers: List[Matchers]) = - val result = engine.query(matchers).map(_.toHTML(inkuire = false)) + val result = engine.query(matchers).map(_.toHTML) resultsDiv.scrollTop = 0 while (resultsDiv.hasChildNodes()) resultsDiv.removeChild(resultsDiv.lastChild) val fragment = document.createDocumentFragment() @@ -86,8 +131,8 @@ class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearch animation.classList.add("loading") loading.appendChild(animation) properResultsDiv.appendChild(loading) - inkuireEngine.query(query) { (p: PageEntry) => - properResultsDiv.appendChild(p.toHTML(inkuire = true)) + inkuireEngine.query(query) { (m: InkuireMatch) => + properResultsDiv.appendChild(m.toHTML) } { (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 5b91512a46f7..9a1c75947307 100644 --- a/scaladoc-js/src/searchbar/engine/InkuireJSSearchEngine.scala +++ b/scaladoc-js/src/searchbar/engine/InkuireJSSearchEngine.scala @@ -16,20 +16,18 @@ class InkuireJSSearchEngine { val scriptPath = Globals.pathToRoot + "scripts/" val worker: Worker = new Worker(scriptPath + "inkuire-worker.js") - def dynamicToPageEntry(d: Dynamic): PageEntry = { - PageEntry( - d.functionName.asInstanceOf[String], + def dynamicToMatch(d: Dynamic): InkuireMatch = { + InkuireMatch( d.prettifiedSignature.asInstanceOf[String], - d.pageLocation.asInstanceOf[String], d.functionName.asInstanceOf[String], - "def", - List.empty + d.packageLocation.asInstanceOf[String], + d.pageLocation.asInstanceOf[String], + d.entryType.asInstanceOf[String] ) } - def query(s: String)(callback: PageEntry => Unit)(endCallback: String => Unit): List[PageEntry] = { + def query(s: String)(callback: InkuireMatch => Unit)(endCallback: String => Unit): Unit = { worker.onmessage = _ => () - val res = ListBuffer[PageEntry]() val func = (msg: MessageEvent) => { msg.data.asInstanceOf[String] match { case "engine_ready" => @@ -38,13 +36,12 @@ class InkuireJSSearchEngine { endCallback(endMsg.drop("query_ended".length)) case q => val matches = JSON.parse(q).matches - val actualMatches = matches.asInstanceOf[js.Array[Dynamic]].map(dynamicToPageEntry) + val actualMatches = matches.asInstanceOf[js.Array[Dynamic]].map(dynamicToMatch) actualMatches.foreach(callback) } } worker.onmessage = func worker.postMessage(s) - res.toList } } \ No newline at end of file diff --git a/scaladoc-js/src/searchbar/engine/QueryParser.scala b/scaladoc-js/src/searchbar/engine/QueryParser.scala index 56c40c0003ef..c03becb0026e 100644 --- a/scaladoc-js/src/searchbar/engine/QueryParser.scala +++ b/scaladoc-js/src/searchbar/engine/QueryParser.scala @@ -19,7 +19,7 @@ class QueryParser: val kindRegex = ("(?i)" + kinds.mkString("(","|",")") + " (.*)").r val restRegex = raw"(.*)".r val escapedRegex = raw"`(.*)`".r - val signatureRegex = raw"([^=>]+=>.*)".r + val signatureRegex = raw"(.*=>.*)".r def parseMatchers(query: String): List[Matchers] = query match { case escapedRegex(rest) => List(ByName(rest)) diff --git a/scaladoc-testcases/src/tests/inkuire.scala b/scaladoc-testcases/src/tests/inkuire.scala index 1f50fc63858c..2d70a70fb6f5 100644 --- a/scaladoc-testcases/src/tests/inkuire.scala +++ b/scaladoc-testcases/src/tests/inkuire.scala @@ -13,3 +13,8 @@ class JustAClass { class JustAnotherClass extends JustAClass { def method(i: InType2): OutType2 = ??? } + +object InkuireObject { + def function(i: InType1): OutType1 = ??? + val value: InType1 => OutType1 = ??? +} \ No newline at end of file diff --git a/scaladoc/resources/dotty_res/styles/search-bar.css b/scaladoc/resources/dotty_res/styles/search-bar.css index f778bf43c186..9662db3f90e7 100644 --- a/scaladoc/resources/dotty_res/styles/search-bar.css +++ b/scaladoc/resources/dotty_res/styles/search-bar.css @@ -66,7 +66,7 @@ /* Loading */ .loading-wrapper { - align-self: center; + text-align: center; padding: 4px; } @@ -75,18 +75,21 @@ width: 10px; height: 10px; border-radius: 5px; - background-color: var(--shadow); - color: var(--shadow); - animation: dotFlashing 1s infinite alternate; + background-color: var(--leftbar-bg); + color: var(--leftbar-bg); + animation-name: dotFlashing; + animation-duration: 1.6s; + animation-iteration-count: infinite; + animation-direction: normal; + animation-timing-function: ease-in-out; display: inline-block; position: absolute; top: 0; } .loading { - left: 50%; position: relative; - animation-delay: .5s; + animation-delay: .2s; } .loading::before { @@ -96,14 +99,31 @@ .loading::after { left: 15px; - animation-delay: 1s; + animation-delay: .4s; } @keyframes dotFlashing { 0% { background-color: var(--leftbar-bg); } - 100% { + 25% { background-color: var(--shadow); } -} \ No newline at end of file + 50% { + background-color: var(--leftbar-bg); + } +} + +.scaladoc-searchbar-inkuire-package { + display: none; + color: var(--symbol-fg) +} + +div[selected] > .scaladoc-searchbar-inkuire-package { + display: flex; +} + +.scaladoc-searchbar-inkuire-package > .micon { + float: right; + margin-left: auto !important; +} diff --git a/scaladoc/src/dotty/tools/scaladoc/Inkuire.scala b/scaladoc/src/dotty/tools/scaladoc/Inkuire.scala index 2a96a0b8cc85..fa663d5af5f9 100644 --- a/scaladoc/src/dotty/tools/scaladoc/Inkuire.scala +++ b/scaladoc/src/dotty/tools/scaladoc/Inkuire.scala @@ -1,11 +1,10 @@ package dotty.tools.scaladoc import dotty.tools.scaladoc.util._ -import scala.collection.mutable.{ Map => MMap} object Inkuire { - var db = InkuireDb(Seq.empty, Map.empty, Seq.empty) + var db = InkuireDb(Seq.empty, Map.empty, Seq.empty, Map.empty) def beforeSave(): Unit = { db = db.copy( @@ -20,10 +19,23 @@ object Inkuire { jsonObject(("inkuirePaths", jsonList(paths))).toString } + def curry(e: Signature): Signature = { + e.result.typ match + case t: Type if t.name.name == s"Function${t.params.size-1}" => + curry( + e.copy( + arguments = e.arguments ++ t.params.init.map(_.typ).map(Contravariance(_)), + result = Covariance(t.params.last.typ) + ) + ) + case _ => e + } + case class InkuireDb( functions: Seq[ExternalSignature], types: Map[ITID, (Type, Seq[Type])], - implicitConversions: Seq[(ITID, Type)] + implicitConversions: Seq[(ITID, Type)], + typeAliases: Map[ITID, TypeLike] ) case class ITID(uuid: String, isParsed: Boolean) @@ -38,7 +50,7 @@ object Inkuire { } object Signature { - def apply(receiver: Option[Type], arguments: Seq[Type], result: Type, context: SignatureContext): Signature = + def apply(receiver: Option[TypeLike], arguments: Seq[TypeLike], result: TypeLike, context: SignatureContext): Signature = Signature(receiver.map(Contravariance(_)), arguments.map(Contravariance(_)), Covariance(result), context) } @@ -46,18 +58,36 @@ object Inkuire { signature: Signature, name: String, packageName: String, - uri: String + uri: String, + entryType: String ) + sealed trait TypeLike + case class Type( - name: TypeName, - params: Seq[Variance] = Seq.empty, - nullable: Boolean = false, - itid: Option[ITID] = None, - isVariable: Boolean = false, + name: TypeName, + params: Seq[Variance] = Seq.empty, + nullable: Boolean = false, + itid: Option[ITID] = None, + isVariable: Boolean = false, isStarProjection: Boolean = false, - isUnresolved: Boolean = false - ) + isUnresolved: Boolean = false + ) extends TypeLike + + case class AndType(left: TypeLike, right: TypeLike) extends TypeLike + case class OrType(left: TypeLike, right: TypeLike) extends TypeLike + + case class TypeLambda(args: Seq[Type], result: TypeLike) extends TypeLike + + object TypeLambda { + def argument(name: String): Type = + val uuid = s"external-type-lambda-arg-$name" + Inkuire.Type( + name = Inkuire.TypeName(name), + itid = Some(Inkuire.ITID(uuid, isParsed = false)), + isVariable = true + ) + } object Type { def unresolved: Type = @@ -70,6 +100,13 @@ object Inkuire { ) ) ) + + def StarProjection: Type = + Type( + name = TypeName("_"), + itid = Some(ITID("_", isParsed = false)), + isStarProjection = true + ) } case class TypeName(name: String) { @@ -87,7 +124,7 @@ object Inkuire { case class SignatureContext( vars: Set[String], - constraints: Map[String, Seq[Type]] + constraints: Map[String, Seq[TypeLike]] ) { override def hashCode: Int = vars.size.hashCode @@ -103,23 +140,24 @@ object Inkuire { } sealed abstract class Variance { - val typ: Type + val typ: TypeLike } - case class Covariance(typ: Type) extends Variance + case class Covariance(typ: TypeLike) extends Variance - case class Contravariance(typ: Type) extends Variance + case class Contravariance(typ: TypeLike) extends Variance - case class Invariance(typ: Type) extends Variance + case class Invariance(typ: TypeLike) extends Variance - case class UnresolvedVariance(typ: Type) extends Variance + case class UnresolvedVariance(typ: TypeLike) extends Variance object EngineModelSerializers { def serialize(db: InkuireDb): JSON = { jsonObject( ("types", serialize(db.types)), ("functions", jsonList(db.functions.map(serialize))), - ("implicitConversions", jsonList(db.implicitConversions.map(serializeConversion))) + ("implicitConversions", jsonList(db.implicitConversions.map(serializeConversion))), + ("typeAliases", serializeTypeAliases(db.typeAliases)) ) } @@ -141,6 +179,15 @@ object Inkuire { )*) } + private def serializeTypeAliases(types: Map[ITID, TypeLike]): JSON = { + jsonObject(( + types.toList.map { + case (itid, v) => + (serializeAsKey(itid), serialize(v)) + } + )*) + } + private def serializeAsKey(itid: ITID): String = { s"""${itid.isParsed}=${itid.uuid}""" } @@ -154,16 +201,36 @@ object Inkuire { ) } - private def serialize(t: Type): JSON = { - jsonObject( - ("name", serialize(t.name)), - ("params", jsonList(t.params.map(serialize))), - ("nullable", serialize(t.nullable)), - ("itid", serialize(t.itid.get)), - ("isVariable", serialize(t.isVariable)), - ("isStarProjection", serialize(t.isStarProjection)), - ("isUnresolved", serialize(t.isUnresolved)) - ) + private def serialize(t: TypeLike): JSON = t match { + case t: Type => + jsonObject( + ("name", serialize(t.name)), + ("params", jsonList(t.params.map(serialize))), + ("nullable", serialize(t.nullable)), + ("itid", serialize(t.itid.get)), + ("isVariable", serialize(t.isVariable)), + ("isStarProjection", serialize(t.isStarProjection)), + ("isUnresolved", serialize(t.isUnresolved)), + ("typelikekind", serialize("type")) + ) + case t: OrType => + jsonObject( + ("left", serialize(t.left)), + ("right", serialize(t.right)), + ("typelikekind", serialize("ortype")) + ) + case t: AndType => + jsonObject( + ("left", serialize(t.left)), + ("right", serialize(t.right)), + ("typelikekind", serialize("andtype")) + ) + case t: TypeLambda => + jsonObject( + ("args", jsonList(t.args.map(serialize))), + ("result", serialize(t.result)), + ("typelikekind", serialize("typelambda")) + ) } private def serialize(b: Boolean): JSON = { @@ -211,7 +278,8 @@ object Inkuire { ("signature", serialize(e.signature)), ("name", serialize(e.name)), ("packageName", serialize(e.packageName)), - ("uri", serialize(e.uri)) + ("uri", serialize(e.uri)), + ("entryType", serialize(e.entryType)) ) } @@ -241,7 +309,7 @@ object Inkuire { ) } - private def serializeConstraints(constraints: Map[String, Seq[Type]]): JSON = { + private def serializeConstraints(constraints: Map[String, Seq[TypeLike]]): JSON = { jsonObject(( constraints.toList.map { case (name, vs) => diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala index 99051b20645d..6b38dd5f4f57 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala @@ -90,7 +90,7 @@ trait Resources(using ctx: DocContext) extends Locations, Writer: ).map(Resource.URL.apply) val urlToPathMappings = List( - ("https://github.com/VirtusLab/Inkuire/releases/download/1.0.0-M1/inkuire.js", "scripts/inkuire.js"), + ("https://github.com/VirtusLab/Inkuire/releases/download/1.0.0-M2/inkuire.js", "scripts/inkuire.js"), ).map { case (url, path) => Resource.URLToCopy(url, path) } diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala index 53cf0ced6fbb..bcc0d1a47039 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -96,43 +96,72 @@ trait ClassLikeSupport: if summon[DocContext].args.generateInkuire then { - val classType = classDef.asInkuire(Set.empty, true) - val variableNames = classType.params.map(_.typ.name.name).toSet + val classType: Inkuire.Type = classDef.asInkuire(Set.empty).asInstanceOf[Inkuire.Type] - val parents = classDef.parents.map(_.asInkuire(variableNames, false)) + 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.foreach { - case typeSymbol: Symbol => - val typeDef = typeSymbol.tree.asInstanceOf[TypeDef] - if typeDef.rhs.symbol.fullName.contains("java") then - val t = typeSymbol.tree.asInkuire(variableNames, false) // TODO [Inkuire] Hack until type aliases are supported - val tJava = typeDef.rhs.symbol.tree.asInkuire(variableNames, false) - Inkuire.db = Inkuire.db.copy(types = Inkuire.db.types.updated(t.itid.get, (t, Seq.empty))) // TODO [Inkuire] Hack until type aliases are supported - Inkuire.db = Inkuire.db.copy(types = Inkuire.db.types.updated(tJava.itid.get, (tJava, Seq.empty))) + 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 { (s: Symbol) => - !s.flags.is(Flags.Private) && - !s.flags.is(Flags.Protected) && - !s.flags.is(Flags.Override) - } + .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, false) + val to = defdef.returnTpt.asInkuire(variableNames) val from = defdef.paramss.flatMap(_.params).collectFirst { - case v: ValDef => v.tpt.asInkuire(variableNames, false) + case v: ValDef => v.tpt.asInkuire(variableNames) } - from match - case Some(from) => Inkuire.db = Inkuire.db.copy(implicitConversions = Inkuire.db.implicitConversions :+ (from.itid.get -> to)) - case None => + (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] @@ -140,17 +169,17 @@ trait ClassLikeSupport: case TypeDef(name, _) => name } val vars = variableNames ++ methodVars - val receiver: Option[Inkuire.Type] = + val receiver: Option[Inkuire.TypeLike] = Some(classType) .filter(_ => !isModule) - .orElse(methodSymbol.extendedSymbol.flatMap(s => partialAsInkuire(vars, false).lift(s.tpt))) + .orElse(methodSymbol.extendedSymbol.flatMap(s => partialAsInkuire(vars).lift(s.tpt))) val sgn = Inkuire.ExternalSignature( signature = Inkuire.Signature( receiver = receiver, - arguments = methodSymbol.nonExtensionParamLists.flatMap(_.params).collect { - case ValDef(_, tpe, _) => tpe.asInkuire(vars, false) - }, - result = defdef.returnTpt.asInkuire(vars, false), + arguments = methodSymbol.nonExtensionParamLists.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 @@ -158,11 +187,39 @@ trait ClassLikeSupport: ), name = methodSymbol.name, packageName = methodSymbol.dri.location, - uri = methodSymbol.dri.externalLink.getOrElse("") + uri = methodSymbol.dri.externalLink.getOrElse(""), + entryType = "def" ) - Inkuire.db = Inkuire.db.copy(functions = Inkuire.db.functions :+ sgn) + 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 signatureOnly then baseMember else baseMember.copy( diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/InkuireSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/InkuireSupport.scala index fbb466ffc1e6..2b34ba633b0e 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/InkuireSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/InkuireSupport.scala @@ -5,6 +5,7 @@ import dotty.tools.scaladoc._ import dotty.tools.scaladoc.{Signature => DSignature} import dotty.tools.scaladoc.Inkuire +import scala.util.Random import scala.quoted._ import SymOps._ @@ -17,36 +18,47 @@ trait InkuireSupport: private given qctx.type = qctx - private def paramsForClass(classDef: ClassDef, vars: Set[String], isVariable: Boolean): Seq[Inkuire.Variance] = - classDef.getTypeParams.map(mkTypeArgumentInkuire(_, vars, isVariable)) + private def paramsForClass(classDef: ClassDef, vars: Set[String]): Seq[Inkuire.Variance] = + classDef.getTypeParams.map(mkTypeArgumentInkuire) given TreeSyntaxInkuire: AnyRef with extension (tpeTree: Tree) - def asInkuire(vars: Set[String], isVariable: Boolean): Inkuire.Type = - partialAsInkuire(vars, isVariable)(tpeTree) + def asInkuire(vars: Set[String]): Inkuire.TypeLike = + partialAsInkuire(vars)(tpeTree) - def partialAsInkuire(vars: Set[String], isVariable: Boolean): PartialFunction[Tree, Inkuire.Type] = { + def partialAsInkuire(vars: Set[String]): PartialFunction[Tree, Inkuire.TypeLike] = { case TypeBoundsTree(low, high) => inner(low.tpe, vars) //TODO [Inkuire] Type bounds case tpeTree: Applied => - inner(tpeTree.tpe, vars).copy( - params = tpeTree.args.map(p => Inkuire.Invariance(p.asInkuire(vars, isVariable))) - ) + inner(tpeTree.tpe, vars) case tpeTree: TypeTree => inner(tpeTree.tpe, vars) case term: Term => inner(term.tpe, vars) - case classDef: ClassDef => mkTypeFromClassDef(classDef, vars, isVariable) - case typeDef: TypeDef => + case classDef: ClassDef => mkTypeFromClassDef(classDef, vars) + case typeDef: TypeDef => mkTypeDef(typeDef) + } + + 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 + val params = paramsDefs.map(_.name).map(Inkuire.TypeLambda.argument) + Inkuire.Type( + name = Inkuire.TypeName(normalizedName), + itid = typeDef.symbol.itid, + params = params.map(Inkuire.Invariance(_)) + ) + case _ => Inkuire.Type( name = Inkuire.TypeName(typeDef.name), itid = typeDef.symbol.itid ) } - def mkTypeFromClassDef(classDef: ClassDef, vars: Set[String], isVariable: Boolean): Inkuire.Type = { + def mkTypeFromClassDef(classDef: ClassDef, vars: Set[String]): Inkuire.Type = { Inkuire.Type( name = Inkuire.TypeName(classDef.name), itid = classDef.symbol.itid, - params = paramsForClass(classDef, vars, isVariable) + params = paramsForClass(classDef, vars) ) } @@ -56,21 +68,44 @@ trait InkuireSupport: given TypeSyntaxInkuire: AnyRef with extension (tpe: TypeRepr) - def asInkuire(vars: Set[String]): Inkuire.Type = inner(tpe, vars) + def asInkuire(vars: Set[String]): Inkuire.TypeLike = inner(tpe, vars) + + private def genSyntheticTypeArgs(n: Int, resSymbol: Symbol) = + 1.to(n).map { i => + val uuid = s"synthetic-arg$i${resSymbol.hashCode}" + val name = s"X$i" + Inkuire.Type( + name = Inkuire.TypeName(name), + itid = Some(Inkuire.ITID(uuid, isParsed = false)), + isVariable = true + ) + } - def mkTypeArgumentInkuire(argument: TypeDef, vars: Set[String] = Set.empty, isVariable: Boolean = false): Inkuire.Variance = + 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 - val t = Inkuire.Type( + val params = genSyntheticTypeArgs(typeVariableDeclarationParamsNo(argument), argument.symbol) + val res = Inkuire.Type( name = Inkuire.TypeName(normalizedName), itid = argument.symbol.itid, - isVariable = vars.contains(normalizedName) || isVariable, - params = Seq.empty //TODO [Inkuire] Type Lambdas + isVariable = true, + params = params.map(Inkuire.Invariance(_)) ) + val t = params.toList match + case Nil => res + case _ => Inkuire.TypeLambda(params, res) if argument.symbol.flags.is(Flags.Covariant) then Inkuire.Covariance(t) else if argument.symbol.flags.is(Flags.Contravariant) then Inkuire.Contravariance(t) else Inkuire.Invariance(t) + def typeVariableDeclarationParamsNo(argument: TypeDef): Int = + argument.rhs match + case t: TypeTree => t.tpe match + case TypeBounds(_, TypeLambda(names, _, _)) => names.size + case _ => 0 + case _ => 0 + private def isRepeatedAnnotation(term: Term) = term.tpe match case t: TypeRef => t.name == "Repeated" && t.qualifier.match @@ -85,9 +120,9 @@ trait InkuireSupport: case _ => false case _ => false - private def inner(tp: TypeRepr, vars: Set[String]): Inkuire.Type = tp match - case OrType(left, right) => inner(left, vars) //TODO [Inkuire] Or/AndTypes - case AndType(left, right) => inner(left, vars) //TODO [Inkuire] Or/AndTypes + 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( @@ -102,8 +137,8 @@ trait InkuireSupport: inner(tpe, vars) //TODO [Inkuire] Repeated types case AnnotatedType(tpe, _) => inner(tpe, vars) - case tl @ TypeLambda(params, paramBounds, resType) => - inner(resType, vars) //TODO [Inkuire] Type lambdas + 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) => @@ -123,7 +158,7 @@ trait InkuireSupport: itid = Some(Inkuire.ITID(s"${name}scala.${name}//[]", isParsed = false)) ) else - inner(tpe, vars).copy( + inner(tpe, vars).asInstanceOf[Inkuire.Type].copy( params = typeList.map(p => Inkuire.Invariance(inner(p, vars))) ) case tp: TypeRef => @@ -141,12 +176,8 @@ trait InkuireSupport: 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, _, resType), i) => - Inkuire.Type( - name = Inkuire.TypeName(names(i)), - itid = Some(Inkuire.ITID(s"external-itid-${names(i)}", isParsed = false)), - isVariable = true - ) + case ParamRef(TypeLambda(names, _, _), i) => + Inkuire.TypeLambda.argument(names(i)) case ParamRef(m: MethodType, i) => inner(m.paramTypes(i), vars) case RecursiveType(tp) =>