diff --git a/project/Build.scala b/project/Build.scala
index 112c591b433e..fa2d8ab49915 100644
--- a/project/Build.scala
+++ b/project/Build.scala
@@ -1267,7 +1267,7 @@ object Build {
scalaSrcLink(stdLibVersion, srcManaged(dottyNonBootstrappedVersion, "scala") + "="),
dottySrcLink(referenceVersion, srcManaged(dottyNonBootstrappedVersion, "dotty") + "=", "#library/src"),
dottySrcLink(referenceVersion),
- ) ++ scalacOptionsDocSettings ++ revision ++ params ++ targets
+ ) ++ scalacOptionsDocSettings ++ revision ++ params ++ targets ++ Seq("-Ygenerate-inkuire")
import _root_.scala.sys.process._
val escapedCmd = cmd.map(arg => if(arg.contains(" ")) s""""$arg"""" else arg)
Def.task {
diff --git a/scaladoc-js/resources/scaladoc-searchbar.css b/scaladoc-js/resources/scaladoc-searchbar.css
index fded722670b3..a248eeb5c328 100644
--- a/scaladoc-js/resources/scaladoc-searchbar.css
+++ b/scaladoc-js/resources/scaladoc-searchbar.css
@@ -50,6 +50,9 @@
box-shadow: 0 2px 16px 0 rgba(0, 42, 76, 0.15);
font-size: 13px;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Noto Sans, Ubuntu, Cantarell, Helvetica Neue, Arial, sans-serif;
+ background-color: var(--leftbar-bg);
+ color: var(--leftbar-fg);
+ box-shadow: 0 0 2px var(--shadow);
}
#scaladoc-searchbar-input {
@@ -58,6 +61,8 @@
border: none;
border-bottom: 1px solid #bbb;
padding: 10px;
+ background-color: var(--leftbar-bg);
+ color: var(--leftbar-fg);
}
#scaladoc-searchbar-input:focus {
@@ -65,7 +70,6 @@
}
#scaladoc-searchbar-results {
- background: var(--white);
display: flex;
flex-direction: column;
max-height: 500px;
@@ -73,7 +77,8 @@
}
.scaladoc-searchbar-result {
- background: var(--white);
+ background-color: var(--leftbar-bg);
+ color: var(--leftbar-fg);
line-height: 24px;
display: flex;
padding: 4px 10px 4px 10px;
@@ -90,11 +95,11 @@
}
.scaladoc-searchbar-result[selected] {
- background-color: var(--blue100);
+ background-color: var(--leftbar-hover-bg);
+ color: var(--leftbar-hover-fg);
}
.scaladoc-searchbar-result a {
- color: var(--grey900);
/* for some reason, with display:block if there's a wrap between the
* search result text and the location span, the dead space to the
* left of the location span doesn't get treated as part of the block,
@@ -107,10 +112,6 @@
padding-left: 20px;
}
-.scaladoc-searchbar-result .scaladoc-searchbar-location {
- color: gray;
-}
-
#searchBar {
display: inline-flex;
}
diff --git a/scaladoc-js/src/searchbar/Searchbar.scala b/scaladoc-js/src/searchbar/Searchbar.scala
index a3dce6bb3a09..687888cbae61 100644
--- a/scaladoc-js/src/searchbar/Searchbar.scala
+++ b/scaladoc-js/src/searchbar/Searchbar.scala
@@ -2,7 +2,8 @@ package dotty.tools.scaladoc
class Searchbar {
val pages = SearchbarGlobals.pages.toList.map(PageEntry.apply)
- val engine = SearchbarEngine(pages)
val parser = QueryParser()
- val component = SearchbarComponent(q => engine.query(parser.parse(q)))
-}
+ val searchEngine = SearchbarEngine(pages)
+ val inkuireEngine = InkuireJSSearchEngine()
+ val component = SearchbarComponent(searchEngine, inkuireEngine, parser)
+}
\ No newline at end of file
diff --git a/scaladoc-js/src/searchbar/SearchbarComponent.scala b/scaladoc-js/src/searchbar/SearchbarComponent.scala
index 4c710f876c8b..2ca7aa310985 100644
--- a/scaladoc-js/src/searchbar/SearchbarComponent.scala
+++ b/scaladoc-js/src/searchbar/SearchbarComponent.scala
@@ -2,11 +2,13 @@ package dotty.tools.scaladoc
import org.scalajs.dom._
import org.scalajs.dom.html.Input
+import scala.scalajs.js.timers._
+import scala.concurrent.duration._
-class SearchbarComponent(val callback: (String) => List[PageEntry]):
+class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearchEngine, parser: QueryParser):
val resultsChunkSize = 100
extension (p: PageEntry)
- def toHTML =
+ def toHTML(inkuire: Boolean = false) =
val wrapper = document.createElement("div").asInstanceOf[html.Div]
wrapper.classList.add("scaladoc-searchbar-result")
wrapper.classList.add("monospace")
@@ -16,7 +18,7 @@ class SearchbarComponent(val callback: (String) => List[PageEntry]):
icon.classList.add(p.kind.take(2))
val resultA = document.createElement("a").asInstanceOf[html.Anchor]
- resultA.href = Globals.pathToRoot + p.location
+ resultA.href = if inkuire then p.location else Globals.pathToRoot + p.location
resultA.text = s"${p.fullName}"
val location = document.createElement("span")
@@ -32,8 +34,8 @@ class SearchbarComponent(val callback: (String) => List[PageEntry]):
})
wrapper
- def handleNewQuery(query: String) =
- val result = callback(query).map(_.toHTML)
+ def handleNewFluffQuery(matchers: List[Matchers]) =
+ val result = engine.query(matchers).map(_.toHTML(inkuire = false))
resultsDiv.scrollTop = 0
while (resultsDiv.hasChildNodes()) resultsDiv.removeChild(resultsDiv.lastChild)
val fragment = document.createDocumentFragment()
@@ -41,17 +43,58 @@ class SearchbarComponent(val callback: (String) => List[PageEntry]):
resultsDiv.appendChild(fragment)
def loadMoreResults(result: List[raw.HTMLElement]): Unit = {
resultsDiv.onscroll = (event: Event) => {
- if (resultsDiv.scrollHeight - resultsDiv.scrollTop == resultsDiv.clientHeight)
- {
- val fragment = document.createDocumentFragment()
- result.take(resultsChunkSize).foreach(fragment.appendChild)
- resultsDiv.appendChild(fragment)
- loadMoreResults(result.drop(resultsChunkSize))
- }
+ if (resultsDiv.scrollHeight - resultsDiv.scrollTop == resultsDiv.clientHeight) {
+ val fragment = document.createDocumentFragment()
+ result.take(resultsChunkSize).foreach(fragment.appendChild)
+ resultsDiv.appendChild(fragment)
+ loadMoreResults(result.drop(resultsChunkSize))
+ }
}
}
loadMoreResults(result.drop(resultsChunkSize))
+ extension (s: String)
+ def toHTMLError =
+ val wrapper = document.createElement("div").asInstanceOf[html.Div]
+ wrapper.classList.add("scaladoc-searchbar-result")
+ wrapper.classList.add("monospace")
+
+ val errorSpan = document.createElement("span").asInstanceOf[html.Span]
+ errorSpan.classList.add("search-error")
+ errorSpan.textContent = s
+
+ wrapper.appendChild(errorSpan)
+ wrapper
+
+ var timeoutHandle: SetTimeoutHandle = null
+ def handleNewQuery(query: String) =
+ clearTimeout(timeoutHandle)
+ resultsDiv.scrollTop = 0
+ resultsDiv.onscroll = (event: Event) => { }
+ while (resultsDiv.hasChildNodes()) resultsDiv.removeChild(resultsDiv.lastChild)
+ val fragment = document.createDocumentFragment()
+ parser.parse(query) match {
+ case EngineMatchersQuery(matchers) =>
+ handleNewFluffQuery(matchers)
+ case BySignature(signature) =>
+ timeoutHandle = setTimeout(1.second) {
+ val properResultsDiv = document.createElement("div").asInstanceOf[html.Div]
+ resultsDiv.appendChild(properResultsDiv)
+ val loading = document.createElement("div").asInstanceOf[html.Div]
+ loading.classList.add("loading-wrapper")
+ val animation = document.createElement("div").asInstanceOf[html.Div]
+ animation.classList.add("loading")
+ loading.appendChild(animation)
+ properResultsDiv.appendChild(loading)
+ inkuireEngine.query(query) { (p: PageEntry) =>
+ properResultsDiv.appendChild(p.toHTML(inkuire = true))
+ } { (s: String) =>
+ animation.classList.remove("loading")
+ properResultsDiv.appendChild(s.toHTMLError)
+ }
+ }
+ }
+
private val searchIcon: html.Div =
val span = document.createElement("span").asInstanceOf[html.Span]
span.innerHTML = """"""
diff --git a/scaladoc-js/src/searchbar/engine/InkuireJSSearchEngine.scala b/scaladoc-js/src/searchbar/engine/InkuireJSSearchEngine.scala
new file mode 100644
index 000000000000..5b91512a46f7
--- /dev/null
+++ b/scaladoc-js/src/searchbar/engine/InkuireJSSearchEngine.scala
@@ -0,0 +1,50 @@
+package dotty.tools.scaladoc
+
+import scala.io.Source
+import dotty.tools.scaladoc.PageEntry
+import org.scalajs.dom.webworkers.Worker
+import org.scalajs.dom._
+import scala.scalajs.js.{ JSON, Dynamic }
+import scala.collection.mutable.ListBuffer
+import scala.scalajs.js
+import scala.scalajs.js.timers._
+import org.scalajs.dom.ext.Ajax
+import scala.scalajs.js.URIUtils
+
+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],
+ d.prettifiedSignature.asInstanceOf[String],
+ d.pageLocation.asInstanceOf[String],
+ d.functionName.asInstanceOf[String],
+ "def",
+ List.empty
+ )
+ }
+
+ def query(s: String)(callback: PageEntry => Unit)(endCallback: String => Unit): List[PageEntry] = {
+ worker.onmessage = _ => ()
+ val res = ListBuffer[PageEntry]()
+ val func = (msg: MessageEvent) => {
+ msg.data.asInstanceOf[String] match {
+ case "engine_ready" =>
+ case "new_query" =>
+ case endMsg if endMsg.startsWith("query_ended") =>
+ endCallback(endMsg.drop("query_ended".length))
+ case q =>
+ val matches = JSON.parse(q).matches
+ val actualMatches = matches.asInstanceOf[js.Array[Dynamic]].map(dynamicToPageEntry)
+ 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/Matchers.scala b/scaladoc-js/src/searchbar/engine/Matchers.scala
index ceca8afd357e..14b7eeea9653 100644
--- a/scaladoc-js/src/searchbar/engine/Matchers.scala
+++ b/scaladoc-js/src/searchbar/engine/Matchers.scala
@@ -1,5 +1,9 @@
package dotty.tools.scaladoc
+sealed trait EngineQuery
+case class EngineMatchersQuery(matchers: List[Matchers]) extends EngineQuery
+case class BySignature(signature: String) extends EngineQuery
+
sealed trait Matchers extends Function1[PageEntry, Int]
case class ByName(query: String) extends Matchers:
diff --git a/scaladoc-js/src/searchbar/engine/QueryParser.scala b/scaladoc-js/src/searchbar/engine/QueryParser.scala
index f5996484188c..56c40c0003ef 100644
--- a/scaladoc-js/src/searchbar/engine/QueryParser.scala
+++ b/scaladoc-js/src/searchbar/engine/QueryParser.scala
@@ -19,10 +19,16 @@ class QueryParser:
val kindRegex = ("(?i)" + kinds.mkString("(","|",")") + " (.*)").r
val restRegex = raw"(.*)".r
val escapedRegex = raw"`(.*)`".r
+ val signatureRegex = raw"([^=>]+=>.*)".r
- def parse(query: String): List[Matchers] = query match {
+ def parseMatchers(query: String): List[Matchers] = query match {
case escapedRegex(rest) => List(ByName(rest))
- case kindRegex(kind, rest) => List(ByKind(kind)) ++ parse(rest)
+ case kindRegex(kind, rest) => List(ByKind(kind)) ++ parseMatchers(rest)
case restRegex(name) => List(ByName(name))
case _ => List()
+ }
+
+ def parse(query: String): EngineQuery = query match {
+ case signatureRegex(signature) => BySignature(signature)
+ case other => EngineMatchersQuery(parseMatchers(other))
}
\ No newline at end of file
diff --git a/scaladoc-js/src/searchbar/engine/SearchbarEngine.scala b/scaladoc-js/src/searchbar/engine/SearchbarEngine.scala
index cbc43eaec051..c7b56b482047 100644
--- a/scaladoc-js/src/searchbar/engine/SearchbarEngine.scala
+++ b/scaladoc-js/src/searchbar/engine/SearchbarEngine.scala
@@ -1,6 +1,7 @@
package dotty.tools.scaladoc
import math.Ordering.Implicits.seqOrdering
+import org.scalajs.dom.Node
class SearchbarEngine(pages: List[PageEntry]):
def query(query: List[Matchers]): List[PageEntry] =
diff --git a/scaladoc-js/test/dotty/dokka/QueryParserTest.scala b/scaladoc-js/test/dotty/dokka/QueryParserTest.scala
index b89c7502714f..bd5ab8f9ea8a 100644
--- a/scaladoc-js/test/dotty/dokka/QueryParserTest.scala
+++ b/scaladoc-js/test/dotty/dokka/QueryParserTest.scala
@@ -17,7 +17,7 @@ class QueryParserTest:
"given",
"type"
)
- private def testCase(query: String, result: List[Matchers]) = {
+ private def testCase(query: String, result: EngineQuery) = {
val parsed = queryParser.parse(query)
assertEquals(
s"Query parser test error: for query: $query expected $result but found $parsed",
@@ -28,8 +28,8 @@ class QueryParserTest:
@Test
def queryParserTests() = {
- kinds.foreach(k => testCase(s"$k ", List(ByKind(k), ByName(""))))
- testCase("trait", List(ByName("trait")))
- testCase("trait A", List(ByKind("trait"), ByName("A")))
- testCase("`trait A`", List(ByName("trait A")))
+ kinds.foreach(k => testCase(s"$k ", EngineMatchersQuery(List(ByKind(k), ByName("")))))
+ testCase("trait", EngineMatchersQuery(List(ByName("trait"))))
+ testCase("trait A", EngineMatchersQuery(List(ByKind("trait"), ByName("A"))))
+ testCase("`trait A`", EngineMatchersQuery(List(ByName("trait A"))))
}
\ No newline at end of file
diff --git a/scaladoc-testcases/src/tests/inkuire.scala b/scaladoc-testcases/src/tests/inkuire.scala
new file mode 100644
index 000000000000..1f50fc63858c
--- /dev/null
+++ b/scaladoc-testcases/src/tests/inkuire.scala
@@ -0,0 +1,15 @@
+package tests.inkuire
+
+trait InType1
+trait InType2 extends InType1
+
+trait OutType1
+trait OutType2 extends OutType1
+
+class JustAClass {
+ def mathod(l: InType1): OutType1 = ???
+}
+
+class JustAnotherClass extends JustAClass {
+ def method(i: InType2): OutType2 = ???
+}
diff --git a/scaladoc/resources/dotty_res/images/scaladoc_logo_dark.svg b/scaladoc/resources/dotty_res/images/scaladoc_logo_dark.svg
new file mode 100644
index 000000000000..a203e185dd7a
--- /dev/null
+++ b/scaladoc/resources/dotty_res/images/scaladoc_logo_dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/scaladoc/resources/dotty_res/scripts/inkuire-worker.js b/scaladoc/resources/dotty_res/scripts/inkuire-worker.js
new file mode 100644
index 000000000000..0b37ba58335e
--- /dev/null
+++ b/scaladoc/resources/dotty_res/scripts/inkuire-worker.js
@@ -0,0 +1,2 @@
+importScripts("inkuire.js");
+WorkerMain.main();
diff --git a/scaladoc/resources/dotty_res/scripts/inkuire.md b/scaladoc/resources/dotty_res/scripts/inkuire.md
new file mode 100644
index 000000000000..387f58e788ef
--- /dev/null
+++ b/scaladoc/resources/dotty_res/scripts/inkuire.md
@@ -0,0 +1,59 @@
+# Inkuire
+
+Inkuire is a Hoogle-like search engine for Scala 3 (and Kotlin).
+
+# Usage
+
+To include Inkuire in scaladoc one should add `-Ygenerate-inkuire` flag. This will allow the usage of Inkuire for generated Scaladoc with project sources and available sources from external-mappings.
+
+The Inkuire worker works in Scaladoc searchbar. It is triggered once an input containing `=>` is detected in searchbar. There is 1s debounce on searches with Inkuire.
+
+## Generated Files
+
+When including `-Ygenerate-inkuire` flag database for project sources and a config should be generated. Which are namely files: `inkuire-db.json` and `scripts/inkuire-config.json`. Config file includes addresses of possible inkuire-db files. There always is at least one - generated one. But also links for external mappings are addes on relative path `../inkuire-db.json`.
+
+`inkuire-db.json` contains a json with Inkuire engine's representation of:
+- types (or rather classes and objects for now)
+- functions
+- implicit conversions
+
+
+## Code
+
+The source code is available [here](https://github.com/VirtusLab/Inkuire).
+
+Important parts are:
+- engineCommon - provides most of the logic for search engine
+- engineJs - provides the way of using Inkuire as a Web Worker
+
+## Integration with Scaladoc
+
+Since Inkuire has quite a lot of dependencies it's sources cannot be easily integrated into Scaladoc.
+That is why Inkuire is included as a resource, namely `inkuire.js` file and loaded as Web Worker.
+
+Web worker accepts String messages. Each message should be a requested signature.
+Web worker can send different messages:
+- engine_ready - sent after database has been loaded
+- new_query - sent once a new signature is accepted
+- query_ended(`msg`) - After processing a single signature has finished. `msg` is optional and contains an error to be displayed.
+- json of format [`ResultFormat`](https://github.com/VirtusLab/Inkuire/blob/68d1e0bb2732deda714de9cfca3fe45f75fb5239/engineCommon/shared/src/main/scala/org/virtuslab/inkuire/engine/common/model/OutputFormat.scala#L12) - resulting function found with some additional information, like package location and documentation link.
+
+## Input format
+
+Signature format accepted by Inkuire is pretty much a Scala curried function. With some minor changes:
+- Types with names as single letters or single letters with digits are considered by default type variables. But other type variables can be declared with [polymorphic function types syntax](https://dotty.epfl.ch/docs/reference/new-types/polymorphic-function-types.html).
+- `_` is treated as sort of wildcard, so matches to any type on any position. So searching for any one-argument function from `Int` can be done like this: `Int => _`.
+
+Some example signatures with expected (not exclusive)results:
+- `Seq[Int] => (Int => Long) => Seq[Long]` -> `IterableOps.map`
+- `(A, B) => A` -> `Product2._1`
+- `Set[Long] => Long => Boolean` -> `SetOps.contains`
+- `BigDecimal => Byte` -> `ScalaNumericAnyConversions.toByte`
+- `Int => Long => Int` -> `Function.const`
+- `String => Int => Char` -> `StringOps.apply`
+
+## Signature processing
+
+Some improvements done on engine side:
+- first argument can be interpreted as a receiver. More specifically there is no difference between a function on type `A` and a function with the first parameter as `A` (provided other parameters and return type are the same).
+- permutating arguments. This may have to be dropped/limited in the future. But for now every permutation of arguments can be matched, so for example: `Int => String => String` matches `Function.const`, even though the arguments are switched.
\ No newline at end of file
diff --git a/scaladoc/resources/dotty_res/styles/colors.css b/scaladoc/resources/dotty_res/styles/colors.css
index 5712bbf21474..d6de713b0cbe 100644
--- a/scaladoc/resources/dotty_res/styles/colors.css
+++ b/scaladoc/resources/dotty_res/styles/colors.css
@@ -80,10 +80,14 @@
--icon-color: var(--grey400);
--selected-fg: var(--blue900);
--selected-bg: var(--blue200);
+
+ --shadow: var(--black);
}
/* Dark Mode */
:root.theme-dark {
+ color-scheme: dark;
+
--border-light: var(--blue800);
--border-medium: var(--blue700);
@@ -124,4 +128,6 @@
--tab-selected: var(--white);
--tab-default: var(--grey300);
+
+ --shadow: var(--white);
}
diff --git a/scaladoc/resources/dotty_res/styles/scalastyle.css b/scaladoc/resources/dotty_res/styles/scalastyle.css
index 11bb67d7421e..679fbc846fb7 100644
--- a/scaladoc/resources/dotty_res/styles/scalastyle.css
+++ b/scaladoc/resources/dotty_res/styles/scalastyle.css
@@ -187,6 +187,20 @@ th {
margin-left: -16px;
}
+.theme-dark .scaladoc_logo {
+ display: none;
+}
+
+.scaladoc_logo_dark {
+ display: none;
+}
+
+.theme-dark .scaladoc_logo_dark {
+ width: 116px;
+ margin-left: -16px;
+ display: block;
+}
+
/* Navigation */
#sideMenu2 {
overflow: auto;
@@ -491,7 +505,7 @@ footer {
border-top: 1px solid var(--border-light);
font-size: 14px;
}
-.theme-dark footer img {
+.theme-dark footer .social-icon {
/* "Poor man's dark mode" for images.
* This works great with black images,
* and just-okay with colored images.
diff --git a/scaladoc/resources/dotty_res/styles/search-bar.css b/scaladoc/resources/dotty_res/styles/search-bar.css
index fc5c7ed45c15..cb0167d43503 100644
--- a/scaladoc/resources/dotty_res/styles/search-bar.css
+++ b/scaladoc/resources/dotty_res/styles/search-bar.css
@@ -63,3 +63,47 @@
width: auto !important;
}
}
+
+/* Loading */
+.loading-wrapper {
+ align-self: center;
+ padding: 4px;
+}
+
+.loading, .loading::before, .loading::after {
+ content: '';
+ width: 10px;
+ height: 10px;
+ border-radius: 5px;
+ background-color: white;
+ color: white;
+ animation: dotFlashing 1s infinite alternate;
+ display: inline-block;
+ position: absolute;
+ top: 0;
+}
+
+.loading {
+ left: 50%;
+ position: relative;
+ animation-delay: .5s;
+}
+
+.loading::before {
+ left: -15px;
+ animation-delay: 0s;
+}
+
+.loading::after {
+ left: 15px;
+ animation-delay: 1s;
+}
+
+@keyframes dotFlashing {
+ 0% {
+ background-color: var(--leftbar-bg);
+ }
+ 100% {
+ background-color: white;
+ }
+}
\ No newline at end of file
diff --git a/scaladoc/src/dotty/tools/scaladoc/Inkuire.scala b/scaladoc/src/dotty/tools/scaladoc/Inkuire.scala
new file mode 100644
index 000000000000..1d4da3af6928
--- /dev/null
+++ b/scaladoc/src/dotty/tools/scaladoc/Inkuire.scala
@@ -0,0 +1,246 @@
+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)
+
+ def generateInkuireConfig(externalMappings: Seq[String]): String = {
+ val paths = ("../inkuire-db.json" +: externalMappings.map(_ + "../inkuire-db.json")).map(jsonString)
+ jsonObject(("inkuirePaths", jsonList(paths))).toString
+ }
+
+ case class InkuireDb(
+ functions: Seq[ExternalSignature],
+ types: Map[ITID, (Type, Seq[Type])],
+ implicitConversions: Seq[(ITID, Type)]
+ )
+
+ case class ITID(uuid: String, isParsed: Boolean)
+
+ case class Signature(
+ receiver: Option[Contravariance],
+ arguments: Seq[Contravariance],
+ result: Covariance,
+ context: SignatureContext
+ ) {
+ def typesWithVariances: Seq[Variance] = receiver.toSeq ++ arguments ++ Seq(result)
+ }
+
+ object Signature {
+ def apply(receiver: Option[Type], arguments: Seq[Type], result: Type, context: SignatureContext): Signature =
+ Signature(receiver.map(Contravariance(_)), arguments.map(Contravariance(_)), Covariance(result), context)
+ }
+
+ case class ExternalSignature(
+ signature: Signature,
+ name: String,
+ packageName: String,
+ uri: String
+ )
+
+ case class Type(
+ name: TypeName,
+ params: Seq[Variance] = Seq.empty,
+ nullable: Boolean = false,
+ itid: Option[ITID] = None,
+ isVariable: Boolean = false,
+ isStarProjection: Boolean = false,
+ isUnresolved: Boolean = false
+ )
+
+ object Type {
+ def unresolved: Type =
+ Type(
+ name = TypeName(""),
+ itid = Some(
+ ITID(
+ uuid = "",
+ isParsed = false
+ )
+ )
+ )
+ }
+
+ case class TypeName(name: String) {
+ override def hashCode: Int = name.toLowerCase.hashCode
+
+ override def equals(obj: Any): Boolean = {
+ obj match {
+ case o: TypeName => this.name.toLowerCase == o.name.toLowerCase
+ case _ => false
+ }
+ }
+
+ override def toString: String = name
+ }
+
+ case class SignatureContext(
+ vars: Set[String],
+ constraints: Map[String, Seq[Type]]
+ ) {
+ override def hashCode: Int = vars.size.hashCode
+
+ override def equals(obj: Any): Boolean =
+ obj match {
+ case other: SignatureContext if this.vars.size == other.vars.size => true
+ case _ => false
+ }
+ }
+
+ object SignatureContext {
+ def empty: SignatureContext = SignatureContext(Set.empty, Map.empty)
+ }
+
+ sealed abstract class Variance {
+ val typ: Type
+ }
+
+ case class Covariance(typ: Type) extends Variance
+
+ case class Contravariance(typ: Type) extends Variance
+
+ case class Invariance(typ: Type) extends Variance
+
+ case class UnresolvedVariance(typ: Type) 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)))
+ )
+ }
+
+ private def serializeConversion(conversion: (ITID, Type)): JSON = {
+ jsonList(
+ Seq(
+ serialize(conversion._1),
+ serialize(conversion._2)
+ )
+ )
+ }
+
+ private def serialize(types: Map[ITID, (Type, Seq[Type])]): JSON = {
+ jsonObject((
+ types.toList.map {
+ case (itid, v) =>
+ (serializeAsKey(itid), serialize(v))
+ }
+ )*)
+ }
+
+ private def serializeAsKey(itid: ITID): String = {
+ s"""${itid.isParsed}=${itid.uuid}"""
+ }
+
+ private def serialize(v: (Type, Seq[Type])): JSON = {
+ jsonList(
+ Seq(
+ serialize(v._1),
+ jsonList(v._2.map(serialize))
+ )
+ )
+ }
+
+ 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(b: Boolean): JSON = {
+ if b then rawJSON("true") else rawJSON("false")
+ }
+
+ private def serialize(itid: ITID): JSON = {
+ jsonObject(
+ ("uuid", serialize(itid.uuid)),
+ ("isParsed", serialize(itid.isParsed))
+ )
+ }
+
+ private def serialize(s: TypeName): JSON = {
+ jsonObject(
+ ("name", serialize(s.name))
+ )
+ }
+
+ private def serialize(v: Variance): JSON = v match {
+ case _: Invariance =>
+ jsonObject(
+ ("typ", serialize(v.typ)),
+ ("variancekind", serialize("invariance"))
+ )
+ case _: Covariance =>
+ jsonObject(
+ ("typ", serialize(v.typ)),
+ ("variancekind", serialize("covariance"))
+ )
+ case _: Contravariance =>
+ jsonObject(
+ ("typ", serialize(v.typ)),
+ ("variancekind", serialize("contravariance"))
+ )
+ case _: UnresolvedVariance =>
+ jsonObject(
+ ("typ", serialize(v.typ)),
+ ("variancekind", serialize("unresolved"))
+ )
+ }
+
+ private def serialize(e: ExternalSignature): JSON = {
+ jsonObject(
+ ("signature", serialize(e.signature)),
+ ("name", serialize(e.name)),
+ ("packageName", serialize(e.packageName)),
+ ("uri", serialize(e.uri))
+ )
+ }
+
+ private def serialize(s: String): JSON = {
+ jsonString(s)
+ }
+
+ private def serialize(s: Signature): JSON = {
+ jsonObject(
+ ("receiver", serialize(s.receiver)),
+ ("arguments", jsonList(s.arguments.map(serialize))),
+ ("result", serialize(s.result)),
+ ("context", serialize(s.context))
+ )
+ }
+
+ private def serialize(o: Option[Contravariance]): JSON = {
+ o.fold(rawJSON("null")) { v =>
+ serialize(v)
+ }
+ }
+
+ private def serialize(c: SignatureContext): JSON = {
+ jsonObject(
+ ("vars", jsonList(c.vars.toSeq.map(serialize))),
+ ("constraints", serializeConstraints(c.constraints))
+ )
+ }
+
+ private def serializeConstraints(constraints: Map[String, Seq[Type]]): JSON = {
+ jsonObject((
+ constraints.toList.map {
+ case (name, vs) =>
+ (name, jsonList(vs.map(serialize)))
+ }
+ )*)
+ }
+ }
+
+}
diff --git a/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala b/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala
index 1c2610ca62d7..1b357d385172 100644
--- a/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala
+++ b/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala
@@ -2,17 +2,21 @@ package dotty.tools.scaladoc
import java.util.ServiceLoader
import java.io.File
+import java.io.FileWriter
import java.util.jar._
import collection.JavaConverters._
import collection.immutable.ArraySeq
-import java.nio.file.Files
+import java.nio.file.{ Files, Paths }
import dotty.tools.dotc.config.Settings._
import dotty.tools.dotc.config.{ CommonScalaSettings, AllScalaSettings }
import dotty.tools.dotc.reporting.Reporter
import dotty.tools.dotc.core.Contexts._
+import dotty.tools.scaladoc.Inkuire
+import dotty.tools.scaladoc.Inkuire._
+
object Scaladoc:
enum CommentSyntax:
case Wiki
@@ -53,7 +57,8 @@ object Scaladoc:
snippetCompiler: List[String] = Nil,
snippetCompilerDebug: Boolean = false,
noLinkWarnings: Boolean = false,
- versionsDictionaryUrl: Option[String] = None
+ versionsDictionaryUrl: Option[String] = None,
+ generateInkuire : Boolean = false
)
def run(args: Array[String], rootContext: CompilerContext): Reporter =
@@ -78,9 +83,26 @@ object Scaladoc:
report.inform("Done")
else report.error("Failure")
+ if parsedArgs.generateInkuire then dumpInkuireDB(parsedArgs.output.getAbsolutePath, parsedArgs)
}
+
ctx.reporter
+ def dumpInkuireDB(output: String, parsedArgs: Args) = {
+ val dbPath = Paths.get(output, "inkuire-db.json")
+ val dbFile = dbPath.toFile()
+ dbFile.createNewFile()
+ val dbWriter = new FileWriter(dbFile, false)
+ dbWriter.write(s"${EngineModelSerializers.serialize(Inkuire.db)}")
+ dbWriter.close()
+
+ val configPath = Paths.get(output, "scripts/inkuire-config.json")
+ val configFile = configPath.toFile()
+ configFile.createNewFile()
+ val configWriter = new FileWriter(configFile, false)
+ configWriter.write(Inkuire.generateInkuireConfig(parsedArgs.externalMappings.map(_.documentationUrl.toString)))
+ configWriter.close()
+ }
def extract(args: Array[String], rootCtx: CompilerContext): (Option[Scaladoc.Args], CompilerContext) =
val newContext = rootCtx.fresh
@@ -199,7 +221,8 @@ object Scaladoc:
snippetCompiler.get,
noLinkWarnings.get,
snippetCompilerDebug.get,
- versionsDictionaryUrl.nonDefault
+ versionsDictionaryUrl.nonDefault,
+ generateInkuire.get
)
(Some(docArgs), newContext)
}
diff --git a/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala b/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala
index 742d51ee8441..b5b725ffcca0 100644
--- a/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala
+++ b/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala
@@ -117,5 +117,8 @@ class ScaladocSettings extends SettingGroup with AllScalaSettings:
val snippetCompilerDebug: Setting[Boolean] =
BooleanSetting("-Ysnippet-compiler-debug", snippets.SnippetCompilerArgs.debugUsage, false)
+ val generateInkuire: Setting[Boolean] =
+ BooleanSetting("-Ygenerate-inkuire", "Generates InkuireDB and enables Hoogle-like searches", false)
+
def scaladocSpecificSettings: Set[Setting[_]] =
- Set(sourceLinks, syntax, revision, externalDocumentationMappings, socialLinks, skipById, skipByRegex, deprecatedSkipPackages, docRootContent, snippetCompiler, snippetCompilerDebug)
+ Set(sourceLinks, syntax, revision, externalDocumentationMappings, socialLinks, skipById, skipByRegex, deprecatedSkipPackages, docRootContent, snippetCompiler, snippetCompilerDebug, generateInkuire)
diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala
index e73d515c7de9..08e35dc3024c 100644
--- a/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala
+++ b/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala
@@ -289,6 +289,11 @@ class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member])(using ctx
src := resolveRoot(link.dri, "images/scaladoc_logo.svg"),
alt := "scaladoc",
cls := "scaladoc_logo"
+ ),
+ img(
+ src := resolveRoot(link.dri, "images/scaladoc_logo_dark.svg"),
+ alt := "scaladoc",
+ cls := "scaladoc_logo_dark"
)
)
),
diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala
index e170dda5394b..99051b20645d 100644
--- a/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala
+++ b/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala
@@ -21,6 +21,7 @@ enum Resource(val path: String):
case Classpath(override val path: String, name: String) extends Resource(path)
case File(override val path: String, file: Path) extends Resource(path)
case URL(url: String) extends Resource(url)
+ case URLToCopy(url: String, dest: String) extends Resource(url)
trait Resources(using ctx: DocContext) extends Locations, Writer:
private def dynamicJsData =
@@ -77,7 +78,8 @@ trait Resources(using ctx: DocContext) extends Locations, Writer:
"scripts/components/Input.js",
"scripts/components/FilterGroup.js",
"scripts/components/Filter.js",
- "scripts/searchbar.js"
+ "scripts/searchbar.js",
+ "scripts/inkuire-worker.js"
).map(dottyRes)
val urls = List(
@@ -87,7 +89,13 @@ trait Resources(using ctx: DocContext) extends Locations, Writer:
"https://cdnjs.cloudflare.com/ajax/libs/dagre-d3/0.6.1/dagre-d3.min.js",
).map(Resource.URL.apply)
- fromResources ++ urls ++ projectLogo ++ Seq(scaladocVersionFile, dynamicJsData)
+ val urlToPathMappings = List(
+ ("https://github.com/VirtusLab/Inkuire/releases/download/1.0.0-M1/inkuire.js", "scripts/inkuire.js"),
+ ).map { case (url, path) =>
+ Resource.URLToCopy(url, path)
+ }
+
+ fromResources ++ urls ++ urlToPathMappings ++ projectLogo ++ Seq(scaladocVersionFile, dynamicJsData)
val searchDataPath = "scripts/searchData.js"
val memberResourcesPaths = Seq(searchDataPath) ++ memberResources.map(_.path)
@@ -136,6 +144,7 @@ trait Resources(using ctx: DocContext) extends Locations, Writer:
dottyRes("fonts/dotty-icons.woff"),
dottyRes("fonts/dotty-icons.ttf"),
dottyRes("images/scaladoc_logo.svg"),
+ dottyRes("images/scaladoc_logo_dark.svg"),
dottyRes("images/class.svg"),
dottyRes("images/class_comp.svg"),
dottyRes("images/object.svg"),
@@ -176,3 +185,5 @@ trait Resources(using ctx: DocContext) extends Locations, Writer:
Seq(copy(file, path))
case Resource.URL(url) =>
Nil
+ case Resource.URLToCopy(url, dest) =>
+ Seq(copy(new URL(url).openStream(), dest))
diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala
index 27f023fecc39..53cf0ced6fbb 100644
--- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala
+++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala
@@ -3,6 +3,7 @@ package dotty.tools.scaladoc.tasty
import collection.JavaConverters._
import dotty.tools.scaladoc._
import dotty.tools.scaladoc.{Signature => DSignature}
+import dotty.tools.scaladoc.Inkuire
import scala.quoted._
@@ -93,6 +94,77 @@ trait ClassLikeSupport:
deprecated = classDef.symbol.isDeprecated()
)
+ if summon[DocContext].args.generateInkuire then {
+
+ val classType = classDef.asInkuire(Set.empty, true)
+ val variableNames = classType.params.map(_.typ.name.name).toSet
+
+ val parents = classDef.parents.map(_.asInkuire(variableNames, false))
+
+ 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.declaredMethods
+ .filter { (s: Symbol) =>
+ !s.flags.is(Flags.Private) &&
+ !s.flags.is(Flags.Protected) &&
+ !s.flags.is(Flags.Override)
+ }
+ .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 from = defdef.paramss.flatMap(_.params).collectFirst {
+ case v: ValDef => v.tpt.asInkuire(variableNames, false)
+ }
+ from match
+ case Some(from) => Inkuire.db = Inkuire.db.copy(implicitConversions = Inkuire.db.implicitConversions :+ (from.itid.get -> to))
+ case None =>
+
+ 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.Type] =
+ Some(classType)
+ .filter(_ => !isModule)
+ .orElse(methodSymbol.extendedSymbol.flatMap(s => partialAsInkuire(vars, false).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),
+ 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("")
+ )
+ Inkuire.db = Inkuire.db.copy(functions = Inkuire.db.functions :+ sgn)
+ }
+
+ }
+
if signatureOnly then baseMember else baseMember.copy(
members = classDef.extractPatchedMembers.sortBy(m => (m.name, m.kind.name)),
directParents = classDef.getParentsAsLinkToTypes,
@@ -341,13 +413,7 @@ trait ClassLikeSupport:
specificKind: (Kind.Def => Kind) = identity
): Member =
val method = methodSymbol.tree.asInstanceOf[DefDef]
- val paramLists: List[TermParamClause] =
- if emptyParamsList then Nil
- else if methodSymbol.isExtensionMethod then
- val params = method.termParamss
- if methodSymbol.isLeftAssoc || params.size == 1 then params.tail
- else params.head :: params.tail.drop(1)
- else method.termParamss
+ val paramLists: List[TermParamClause] = methodSymbol.nonExtensionParamLists
val genericTypes = if (methodSymbol.isClassConstructor) Nil else method.leadingTypeParams
val memberInfo = unwrapMemberInfo(c, methodSymbol)
diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/InkuireSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/InkuireSupport.scala
new file mode 100644
index 000000000000..fbb466ffc1e6
--- /dev/null
+++ b/scaladoc/src/dotty/tools/scaladoc/tasty/InkuireSupport.scala
@@ -0,0 +1,155 @@
+package dotty.tools.scaladoc.tasty
+
+import collection.JavaConverters._
+import dotty.tools.scaladoc._
+import dotty.tools.scaladoc.{Signature => DSignature}
+import dotty.tools.scaladoc.Inkuire
+
+import scala.quoted._
+
+import SymOps._
+import NameNormalizer._
+import SyntheticsSupport._
+
+trait InkuireSupport:
+ self: TastyParser =>
+ import qctx.reflect._
+
+ private given qctx.type = qctx
+
+ private def paramsForClass(classDef: ClassDef, vars: Set[String], isVariable: Boolean): Seq[Inkuire.Variance] =
+ classDef.getTypeParams.map(mkTypeArgumentInkuire(_, vars, isVariable))
+
+ given TreeSyntaxInkuire: AnyRef with
+ extension (tpeTree: Tree)
+ def asInkuire(vars: Set[String], isVariable: Boolean): Inkuire.Type =
+ partialAsInkuire(vars, isVariable)(tpeTree)
+
+ def partialAsInkuire(vars: Set[String], isVariable: Boolean): PartialFunction[Tree, Inkuire.Type] = {
+ 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)))
+ )
+ case tpeTree: TypeTree =>
+ inner(tpeTree.tpe, vars)
+ case term: Term => inner(term.tpe, vars)
+ case classDef: ClassDef => mkTypeFromClassDef(classDef, vars, isVariable)
+ case typeDef: TypeDef =>
+ Inkuire.Type(
+ name = Inkuire.TypeName(typeDef.name),
+ itid = typeDef.symbol.itid
+ )
+ }
+
+ def mkTypeFromClassDef(classDef: ClassDef, vars: Set[String], isVariable: Boolean): Inkuire.Type = {
+ Inkuire.Type(
+ name = Inkuire.TypeName(classDef.name),
+ itid = classDef.symbol.itid,
+ params = paramsForClass(classDef, vars, isVariable)
+ )
+ }
+
+ given SymbolSyntaxInkuire: AnyRef with
+ extension (symbol: Symbol)
+ def itid(using dctx: DocContext): Option[Inkuire.ITID] = Some(Inkuire.ITID(symbol.dri.symbolUUID, isParsed = false))
+
+ given TypeSyntaxInkuire: AnyRef with
+ extension (tpe: TypeRepr)
+ def asInkuire(vars: Set[String]): Inkuire.Type = inner(tpe, vars)
+
+ def mkTypeArgumentInkuire(argument: TypeDef, vars: Set[String] = Set.empty, isVariable: Boolean = false): Inkuire.Variance =
+ val name = argument.symbol.normalizedName
+ val normalizedName = if name.matches("_\\$\\d*") then "_" else name
+ val t = Inkuire.Type(
+ name = Inkuire.TypeName(normalizedName),
+ itid = argument.symbol.itid,
+ isVariable = vars.contains(normalizedName) || isVariable,
+ params = Seq.empty //TODO [Inkuire] Type Lambdas
+ )
+ 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)
+
+ private def isRepeatedAnnotation(term: Term) =
+ term.tpe match
+ case t: TypeRef => t.name == "Repeated" && t.qualifier.match
+ case ThisType(tref: TypeRef) if tref.name == "internal" => true
+ case _ => false
+ case _ => false
+
+ private def isRepeated(typeRepr: TypeRepr) =
+ typeRepr match
+ case t: TypeRef => t.name == "" && t.qualifier.match
+ case ThisType(tref: TypeRef) if tref.name == "scala" => true
+ 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
+ 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(params, paramBounds, resType) =>
+ inner(resType, vars) //TODO [Inkuire] Type lambdas
+ 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.isTupleType 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).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, _, resType), i) =>
+ Inkuire.Type(
+ name = Inkuire.TypeName(names(i)),
+ itid = Some(Inkuire.ITID(s"external-itid-${names(i)}", isParsed = false)),
+ isVariable = true
+ )
+ 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 72c8a8073d8a..17500897b238 100644
--- a/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala
+++ b/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala
@@ -141,6 +141,15 @@ object SymOps:
else termParamss(1).params(0)
}
+ def nonExtensionParamLists: List[reflect.TermParamClause] =
+ import reflect.*
+ val method = sym.tree.asInstanceOf[DefDef]
+ if sym.isExtensionMethod then
+ val params = method.termParamss
+ if sym.isLeftAssoc || params.size == 1 then params.tail
+ else params.head :: params.tail.drop(1)
+ else method.termParamss
+
end extension
end SymOps
diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala
index 69d83e126b36..12b1c013e4fd 100644
--- a/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala
+++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala
@@ -178,7 +178,7 @@ case class TastyParser(
isSkipped: qctx.reflect.Symbol => Boolean
)(
using val ctx: DocContext
-) extends BasicSupport with TypesSupport with ClassLikeSupport with PackageSupport:
+) extends BasicSupport with TypesSupport with ClassLikeSupport with PackageSupport with InkuireSupport:
import qctx.reflect._
private given qctx.type = qctx
diff --git a/scaladoc/src/dotty/tools/scaladoc/util/JSON.scala b/scaladoc/src/dotty/tools/scaladoc/util/JSON.scala
index d822bf4cf898..468300db6616 100644
--- a/scaladoc/src/dotty/tools/scaladoc/util/JSON.scala
+++ b/scaladoc/src/dotty/tools/scaladoc/util/JSON.scala
@@ -5,6 +5,8 @@ import scala.annotation.tailrec
opaque type JSON = String
+def rawJSON(s: String): JSON = s
+
def jsonList(elems: Seq[JSON]): JSON = elems.mkString("[", ",\n", "]")
def jsonObject(fields: (String, JSON)*): JSON =