Skip to content

Commit b866f79

Browse files
abgruszeckiromanowski
authored andcommitted
Scaladoc: further definition lookup fixes
Lookup supports DFS now, and duplicates more of Scaladoc's old behaviour. Fixes majority of remaining lookup warnings in stdlib.
1 parent 80befc3 commit b866f79

File tree

3 files changed

+85
-31
lines changed

3 files changed

+85
-31
lines changed

scaladoc-testcases/src/tests/tests.scala

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,11 @@ class A {
8585
}
8686

8787
/** Companion object to test linking */
88-
object A
88+
object A {
89+
/** Apparently this should work: [[A.bar]]. */
90+
def foo() = 0
91+
def bar() = 0
92+
}
8993

9094
/** = An important Wiki test class =
9195
*
@@ -150,6 +154,14 @@ class C extends A {
150154
class D[T]
151155
class E[T] extends D[T]
152156

157+
package inner {
158+
object A
159+
class B {
160+
/** This resolves: [[A]]. */
161+
def foo() = ()
162+
}
163+
}
164+
153165
/** A class with a semi-non-trivial constructor.
154166
*
155167
* @param a Hello!

scaladoc/src/dotty/tools/scaladoc/tasty/comments/MemberLookup.scala

Lines changed: 66 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -26,39 +26,52 @@ trait MemberLookup {
2626
def nearestMembered(sym: Symbol): Symbol =
2727
if sym.isClassDef || sym.flags.is(Flags.Package) then sym else nearestMembered(sym.owner)
2828

29-
val res =
29+
val res: Option[(Symbol, String)] = {
3030
def toplevelLookup(querystrings: List[String]) =
3131
downwardLookup(querystrings, defn.PredefModule.moduleClass)
3232
.orElse(downwardLookup(querystrings, defn.ScalaPackage))
3333
.orElse(downwardLookup(querystrings, defn.RootPackage))
34+
.orElse(downwardLookup(querystrings, defn.EmptyPackageClass))
3435

3536
ownerOpt match {
3637
case Some(owner) =>
3738
val nearest = nearestMembered(owner)
3839
val nearestCls = nearestClass(owner)
3940
val nearestPkg = nearestPackage(owner)
40-
def relativeLookup(querystrings: List[String]) =
41-
// TODO walk the owner chain?
42-
downwardLookup(querystrings, nearestPkg).orElse(toplevelLookup(querystrings))
41+
def relativeLookup(querystrings: List[String], owner: Symbol): Option[Symbol] = {
42+
val isMeaningful =
43+
owner.exists
44+
// those are just an optimisation, they can be dropped if problems show up
45+
&& owner.ne(defn.ScalaPackage)
46+
&& owner.ne(defn.RootClass)
47+
&& owner.ne(defn.EmptyPackageClass)
48+
49+
if !isMeaningful then None else {
50+
downwardLookup(querystrings, owner) match {
51+
case None => relativeLookup(querystrings, owner.owner)
52+
case some => some
53+
}
54+
}
55+
}
56+
4357
query match {
44-
case Query.StrictMemberId(id) => localLookup(id, nearest).map(_ -> id)
45-
case Query.Id(id) =>
46-
(localLookup(id, nearest) orElse relativeLookup(List(id))).map(_ -> id)
58+
case Query.StrictMemberId(id) =>
59+
localLookup(id, nearest).nextOption.map(_ -> id)
4760
case Query.QualifiedId(Query.Qual.This, _, rest) =>
4861
downwardLookup(rest.asList, nearestCls).map(_ -> rest.join)
4962
case Query.QualifiedId(Query.Qual.Package, _, rest) =>
5063
downwardLookup(rest.asList, nearestPkg).map(_ -> rest.join)
51-
case Query.QualifiedId(Query.Qual.Id(id), _, rest) if id == nearestCls.name =>
52-
downwardLookup(rest.asList, nearestCls).map(_ -> rest.join)
53-
case Query.QualifiedId(Query.Qual.Id(id), _, rest) if id == nearestPkg.name =>
54-
downwardLookup(rest.asList, nearestPkg).map(_ -> rest.join)
55-
case query: Query.QualifiedId =>
56-
relativeLookup(query.asList).map(_ -> query.join)
64+
case query =>
65+
val ql = query.asList
66+
toplevelLookup(ql)
67+
.orElse(relativeLookup(ql, nearest))
68+
.map(_ -> query.join)
5769
}
5870

5971
case None =>
6072
toplevelLookup(query.asList).map(_ -> query.join)
6173
}
74+
}
6275

6376
// println(s"looked up `$query` in ${owner.show}[${owner.flags.show}] as ${res.map(_.show)}")
6477

@@ -67,14 +80,15 @@ trait MemberLookup {
6780
case e: Exception =>
6881
// TODO (https://github.com/lampepfl/scala3doc/issues/238): proper reporting
6982
println(s"[WARN] Unable to find a link for ${query} ${ownerOpt.fold("")(o => "in " + o.name)}")
83+
e.printStackTrace()
7084
None
7185

7286
private def hackMembersOf(using Quotes)(rsym: quotes.reflect.Symbol) = {
7387
import quotes.reflect._
7488
import dotty.tools.dotc
7589
given dotc.core.Contexts.Context = quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
7690
val sym = rsym.asInstanceOf[dotc.core.Symbols.Symbol]
77-
val members = sym.info.decls.iterator.filter(_.isCompleted)
91+
val members = sym.info.decls.iterator.filter(s => hackIsNotAbsent(s.asInstanceOf[Symbol]))
7892
// println(s"members of ${sym.show} : ${members.map(_.show).mkString(", ")}")
7993
members.asInstanceOf[Iterator[Symbol]]
8094
}
@@ -83,16 +97,14 @@ trait MemberLookup {
8397
import dotty.tools.dotc
8498
given dotc.core.Contexts.Context = quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
8599
val sym = rsym.asInstanceOf[dotc.core.Symbols.Symbol]
86-
sym.isCompleted
100+
// note: Predef has .info = NoType for some reason
101+
sym.isCompleted && sym.info.exists
87102
}
88103

89-
private def localLookup(using Quotes)(query: String, owner: quotes.reflect.Symbol): Option[quotes.reflect.Symbol] = {
104+
private def localLookup(using Quotes)(query: String, owner: quotes.reflect.Symbol): Iterator[quotes.reflect.Symbol] = {
90105
import quotes.reflect._
91106

92-
inline def whenExists(s: Symbol)(otherwise: => Option[Symbol]): Option[Symbol] =
93-
if s.exists then Some(s) else otherwise
94-
95-
def findMatch(syms: Iterator[Symbol]): Option[Symbol] = {
107+
def findMatch(syms: Iterator[Symbol]): Iterator[Symbol] = {
96108
// Scaladoc overloading support allows terminal * (and they're meaningless)
97109
val cleanQuery = query.stripSuffix("*")
98110
val (q, forceTerm, forceType) =
@@ -114,23 +126,24 @@ trait MemberLookup {
114126
if s.flags.is(Flags.Module) then s.moduleClass else s
115127

116128
// val syms0 = syms.toList
117-
// val matched0 = syms0.find(matches)
129+
// val matched0 = syms0.filter(matches)
118130
// if matched0.isEmpty then
119131
// println(s"Failed to look up $q in $owner; all members below:")
120132
// syms0.foreach { s => println(s"\t$s") }
121-
// val matched = matched0
133+
// val matched = matched0.iterator
122134

123135
// def showMatched() = matched.foreach { s =>
124-
// println(s">>> ${s.show}")
136+
// println(s">>> $s")
125137
// println(s">>> ${s.pos}")
126138
// println(s">>> [${s.flags.show}]")
127139
// println(s">>> {${if s.isTerm then "isterm" else ""};${if s.isType then "istype" else ""}}")
128140
// println(s">>> moduleClass = ${if hackResolveModule(s) == s then hackResolveModule(s).show else "none"}")
129141
// }
130-
// println(s"localLookup for class ${owner.show} of `$q`{forceTerm=$forceTerm}")
142+
// println(s"localLookup in class ${owner} for `$q`{forceTerm=$forceTerm}")
143+
// println(s"\t${matched0.mkString(", ")}")
131144
// showMatched()
132145

133-
val matched = syms.find(matches)
146+
val matched = syms.filter(matches)
134147
matched.map(hackResolveModule)
135148
}
136149

@@ -147,20 +160,43 @@ trait MemberLookup {
147160
case tpt: TypeTree => tpt.tpe
148161
}
149162

150-
tpe.classSymbol.flatMap { s =>
151-
findMatch(hackMembersOf(s))
163+
tpe.classSymbol match {
164+
case Some(s) => findMatch(hackMembersOf(s))
165+
case None => Iterator.empty
152166
}
153167
case _ =>
154168
findMatch(hackMembersOf(owner))
155169
}
156170
}
157171

158-
private def downwardLookup(using Quotes)(query: List[String], owner: quotes.reflect.Symbol): Option[quotes.reflect.Symbol] =
172+
private def downwardLookup(using Quotes)(query: List[String], owner: quotes.reflect.Symbol): Option[quotes.reflect.Symbol] = {
173+
import quotes.reflect._
159174
query match {
160175
case Nil => None
161-
case q :: Nil => localLookup(q, owner)
162-
case q :: qs => localLookup(q, owner).flatMap(downwardLookup(qs, _))
176+
case q :: Nil => localLookup(q, owner).nextOption
177+
case q :: qs =>
178+
val lookedUp =
179+
localLookup(q, owner).toSeq
180+
181+
if lookedUp.isEmpty then None else {
182+
// tm/tp - term/type symbols which we looked up and which allow further lookup
183+
// pk - package symbol
184+
// Note: packages collide with both term and type definitions
185+
// Note: classes and types collide
186+
var pk: Option[Symbol] = None
187+
var tp: Option[Symbol] = None
188+
var tm: Option[Symbol] = None
189+
lookedUp.foreach { s =>
190+
if s.isPackageDef then pk = Some(s)
191+
else if s.flags.is(Flags.Module) then tm = Some(s)
192+
else if s.isClassDef || s.isTypeDef then tp = Some(s)
193+
}
194+
pk.flatMap(downwardLookup(qs, _))
195+
.orElse(tp.flatMap(downwardLookup(qs, _)))
196+
.orElse(tm.flatMap(downwardLookup(qs, _)))
197+
}
163198
}
199+
}
164200
}
165201

166202
object MemberLookup extends MemberLookup

scaladoc/test/dotty/tools/scaladoc/tasty/comments/MemberLookupTests.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ class LookupTestCases[Q <: Quotes](val q: Quotes) {
1919
val cases = List[(String, Sym)](
2020
"Array" -> cls("scala.Array"),
2121
"Option" -> cls("scala.Option"),
22+
"Predef$" -> cls("scala.Predef$"),
2223
"Predef$.identity" -> cls("scala.Predef$").fun("identity"),
24+
"Predef.identity" -> cls("scala.Predef$").fun("identity"),
2325
"Array$.from" -> cls("scala.Array$").fun("from"),
2426
"???" -> cls("scala.Predef$").fun("???"),
2527
"tests.A" -> cls("tests.A"),
@@ -75,6 +77,10 @@ class LookupTestCases[Q <: Quotes](val q: Quotes) {
7577
/*sanity*/ cls("tests.A") -> "this.Y" -> cls("tests.A").tpe("Y"),
7678
cls("tests.A") -> "this.X.method" -> cls("tests.B").fun("method"),
7779
cls("tests.A") -> "this.Y.method" -> cls("tests.B").fun("method"),
80+
81+
cls("tests.A") -> "A.foo" -> cls("tests.A$").fun("foo"),
82+
83+
cls("tests.inner.B") -> "A" -> cls("tests.inner.A$"),
7884
)
7985

8086
cases.foreach { case ((Sym(owner), query), Sym(target)) =>

0 commit comments

Comments
 (0)