From 018f3234be3f1ec342463ec283054e92b6b3fbf4 Mon Sep 17 00:00:00 2001 From: kasiaMarek Date: Tue, 3 Dec 2024 14:35:18 +0100 Subject: [PATCH] fix: use alternative with stripped synthetic `filename$package.` --- .../internal/mtags/MtagsEnrichments.scala | 7 +-- .../meta/internal/metals/Docstrings.scala | 30 ++++++++++++- .../mtags/DefinitionAlternatives.scala | 44 ++++++++++++++----- .../mtags/ScalametaCommonEnrichments.scala | 3 ++ .../internal/mtags/SymbolIndexBucket.scala | 32 +++++++++----- .../tests/hover/HoverScala3TypeSuite.scala | 16 +++++++ 6 files changed, 106 insertions(+), 26 deletions(-) diff --git a/mtags/src/main/scala-3/scala/meta/internal/mtags/MtagsEnrichments.scala b/mtags/src/main/scala-3/scala/meta/internal/mtags/MtagsEnrichments.scala index 316aef28277..4431ade44fe 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/mtags/MtagsEnrichments.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/mtags/MtagsEnrichments.scala @@ -230,9 +230,10 @@ object MtagsEnrichments extends ScalametaCommonEnrichments: def stripBackticks: String = s.stripPrefix("`").stripSuffix("`") extension (search: SymbolSearch) - def symbolDocumentation(symbol: Symbol, contentType: ContentType = ContentType.MARKDOWN)(using - Context - ): Option[SymbolDocumentation] = + def symbolDocumentation( + symbol: Symbol, + contentType: ContentType = ContentType.MARKDOWN + )(using Context): Option[SymbolDocumentation] = def toSemanticdbSymbol(symbol: Symbol) = SemanticdbSymbols.symbolName( if !symbol.is(JavaDefined) && symbol.isPrimaryConstructor then diff --git a/mtags/src/main/scala/scala/meta/internal/metals/Docstrings.scala b/mtags/src/main/scala/scala/meta/internal/metals/Docstrings.scala index 434a511c1d6..b35352200ce 100644 --- a/mtags/src/main/scala/scala/meta/internal/metals/Docstrings.scala +++ b/mtags/src/main/scala/scala/meta/internal/metals/Docstrings.scala @@ -33,6 +33,7 @@ import scala.meta.pc.SymbolDocumentation */ class Docstrings(index: GlobalSymbolIndex) { val cache = new TrieMap[Content, SymbolDocumentation]() + val alternativesMap = new TrieMap[String, Set[String]] private val logger = Logger.getLogger(classOf[Docstrings].getName) def documentation( @@ -130,6 +131,7 @@ class Docstrings(index: GlobalSymbolIndex) { case Some(defn) => try { indexSymbolDefinition(defn, contentType) + maybeCacheAlternative(defn, contentType) } catch { case NonFatal(e) => logger.log(Level.SEVERE, defn.path.toURI.toString, e) @@ -138,6 +140,29 @@ class Docstrings(index: GlobalSymbolIndex) { } } + private def maybeCacheAlternative( + defn: SymbolDefinition, + contentType: ContentType + ) = { + val defSymbol = defn.definitionSymbol.value + val querySymbol = defn.querySymbol.value + lazy val queryContent = Content.from(querySymbol, contentType) + + if (defSymbol != querySymbol && !cache.contains(queryContent)) { + cache + .get(Content.from(defSymbol, contentType)) + .foreach(cache.put(queryContent, _)) + + alternativesMap.synchronized { + val prev = alternativesMap.get(defSymbol) + alternativesMap.put( + defSymbol, + prev.map(_ + querySymbol).getOrElse(Set(querySymbol)) + ) + } + } + } + private def indexSymbolDefinition( defn: SymbolDefinition, contentType: ContentType @@ -166,7 +191,10 @@ class Docstrings(index: GlobalSymbolIndex) { ): Unit = { for { contentType <- ContentType.values() - } cache.remove(Content.from(occ.symbol, contentType)) + symbol <- alternativesMap + .remove(occ.symbol) + .getOrElse(Set.empty) + occ.symbol + } cache.remove(Content.from(symbol, contentType)) } } diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/DefinitionAlternatives.scala b/mtags/src/main/scala/scala/meta/internal/mtags/DefinitionAlternatives.scala index c8e54f2b915..d848b404892 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/DefinitionAlternatives.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/DefinitionAlternatives.scala @@ -1,22 +1,25 @@ package scala.meta.internal.mtags import scala.meta.internal.semanticdb.Scala._ +import MtagsEnrichments._ object DefinitionAlternatives { + type ValidateLocation = SymbolLocation => Boolean /** * Returns a list of fallback symbols that can act instead of given symbol. */ - def apply(symbol: Symbol): List[Symbol] = { - List( - caseClassCompanionToType(symbol), - caseClassApplyOrCopy(symbol), - caseClassApplyOrCopyParams(symbol), - initParamToValue(symbol), - varGetter(symbol), - methodOwner(symbol), - objectInsteadOfAny(symbol) - ).flatten + def apply(symbol: Symbol): List[(Symbol, Option[ValidateLocation])] = { + stripSyntheticPackageObjectFromObject(symbol).toList ++ + List( + caseClassCompanionToType(symbol), + caseClassApplyOrCopy(symbol), + caseClassApplyOrCopyParams(symbol), + initParamToValue(symbol), + varGetter(symbol), + methodOwner(symbol), + objectInsteadOfAny(symbol) + ).flatten.map((_, None)) } object GlobalSymbol { @@ -26,6 +29,27 @@ object DefinitionAlternatives { Some(sym.owner -> sym.value.desc) } + private def stripSyntheticPackageObjectFromObject( + symbol: Symbol + ): Option[(Symbol, Option[ValidateLocation])] = { + val toplevel = symbol.toplevel + Option(toplevel).flatMap { + case GlobalSymbol(owner, Descriptor.Term(pkgName)) + if pkgName.endsWith("$package") => + val newSymbol = + Symbol(s"${owner.value}${symbol.value.stripPrefix(toplevel.value)}") + if (newSymbol.toplevel.isTerm) { + Some( + ( + newSymbol, + Some(loc => s"${loc.path.scalaFileName}$$package" == pkgName) + ) + ) + } else None + case _ => None + } + } + /** * If `case class A(a: Int)` and there is no companion object, resolve * `A` in `A(1)` to the class definition. diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/ScalametaCommonEnrichments.scala b/mtags/src/main/scala/scala/meta/internal/mtags/ScalametaCommonEnrichments.scala index cfb11bad68d..ee139e1b6a5 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/ScalametaCommonEnrichments.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/ScalametaCommonEnrichments.scala @@ -363,6 +363,9 @@ trait ScalametaCommonEnrichments extends CommonMtagsEnrichments { def filename: String = path.toNIO.filename + def scalaFileName: String = + path.filename.stripSuffix(".scala").stripSuffix(".sc") + def toIdeallyRelativeURI(sourceItemOpt: Option[AbsolutePath]): String = sourceItemOpt match { case Some(sourceItem) => diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/SymbolIndexBucket.scala b/mtags/src/main/scala/scala/meta/internal/mtags/SymbolIndexBucket.scala index cdd97cc8f27..e3125d3cdf7 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/SymbolIndexBucket.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/SymbolIndexBucket.scala @@ -16,6 +16,7 @@ import scala.meta.internal.mtags.ScalametaCommonEnrichments._ import scala.meta.internal.semanticdb.Scala._ import scala.meta.internal.{semanticdb => s} import scala.meta.io.AbsolutePath +import scala.meta.internal.mtags.DefinitionAlternatives.ValidateLocation final case class SymbolLocation( path: AbsolutePath, @@ -190,7 +191,8 @@ class SymbolIndexBucket( */ private def query0( querySymbol: Symbol, - symbol: Symbol + symbol: Symbol, + validate: Option[ValidateLocation] = None ): List[SymbolDefinition] = { removeOldEntries(symbol) @@ -224,21 +226,27 @@ class SymbolIndexBucket( if (!definitions.contains(symbol.value)) { // Fallback 3: guess related symbols from the enclosing class. DefinitionAlternatives(symbol) - .flatMap(alternative => query0(querySymbol, alternative)) + .flatMap { case (alternative, validate) => + query0(querySymbol, alternative, validate) + } } else { definitions .get(symbol.value) .map { paths => - paths.map { location => - SymbolDefinition( - querySymbol = querySymbol, - definitionSymbol = symbol, - path = location.path, - dialect = dialect, - range = location.range, - kind = None, - properties = 0 - ) + paths.flatMap { location => + if (validate.forall(_(location))) { + Some( + SymbolDefinition( + querySymbol = querySymbol, + definitionSymbol = symbol, + path = location.path, + dialect = dialect, + range = location.range, + kind = None, + properties = 0 + ) + ) + } else None }.toList } .getOrElse(List.empty) diff --git a/tests/cross/src/test/scala/tests/hover/HoverScala3TypeSuite.scala b/tests/cross/src/test/scala/tests/hover/HoverScala3TypeSuite.scala index 917da63e000..25a16537918 100644 --- a/tests/cross/src/test/scala/tests/hover/HoverScala3TypeSuite.scala +++ b/tests/cross/src/test/scala/tests/hover/HoverScala3TypeSuite.scala @@ -595,4 +595,20 @@ class HoverScala3TypeSuite extends BaseHoverSuite { |""".stripMargin ) + check( + "i6852", + """ + |type Foo = String + | + |/** some doc */ + |object Foo: + | /** doc doc */ + | val fo@@o = "" + |""".stripMargin, + """|```scala + |val foo: String + |``` + |doc doc""".stripMargin + ) + }