From ebf51577b5757b8c7318b6ffb69a505b367e3ae6 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Thu, 31 Aug 2023 14:20:30 +0200 Subject: [PATCH 01/26] feature: index overridden symbols in top level --- .../meta/internal/mtags/MtagsIndexer.scala | 17 +++--- .../internal/mtags/ScalaToplevelMtags.scala | 54 ++++++++++++++++--- .../mtags/UnresolvedOverriddenSymbol.scala | 12 +++++ .../test/scala/tests/ScalaToplevelSuite.scala | 17 ++++-- 4 files changed, 83 insertions(+), 17 deletions(-) create mode 100644 mtags/src/main/scala/scala/meta/internal/mtags/UnresolvedOverriddenSymbol.scala diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/MtagsIndexer.scala b/mtags/src/main/scala/scala/meta/internal/mtags/MtagsIndexer.scala index 590a7b3a1f1..730feaab2e1 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/MtagsIndexer.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/MtagsIndexer.scala @@ -64,8 +64,8 @@ trait MtagsIndexer { ) } } - def term(name: String, pos: m.Position, kind: Kind, properties: Int): String = - addSignature(Descriptor.Term(name), pos, kind, properties) + def term(name: String, pos: m.Position, kind: Kind, properties: Int, overriddenSymbols: List[(String, m.Position)] = List.empty): String = + addSignature(Descriptor.Term(name), pos, kind, properties, overriddenSymbols) def term(name: Term.Name, kind: Kind, properties: Int): String = addSignature(Descriptor.Term(name.value), name.pos, kind, properties) def tparam(name: Name, kind: Kind, properties: Int): String = @@ -122,8 +122,8 @@ trait MtagsIndexer { properties ) } - def tpe(name: String, pos: m.Position, kind: Kind, properties: Int): String = - addSignature(Descriptor.Type(name), pos, kind, properties) + def tpe(name: String, pos: m.Position, kind: Kind, properties: Int, overriddenSymbols: List[(String, m.Position)] = List.empty): String = + addSignature(Descriptor.Type(name), pos, kind, properties, overriddenSymbols) def tpe(name: Name, kind: Kind, properties: Int): String = addSignature(Descriptor.Type(name.value), name.pos, kind, properties) def pkg(name: String, pos: m.Position): String = { @@ -141,7 +141,8 @@ trait MtagsIndexer { signature: Descriptor, definition: m.Position, kind: s.SymbolInformation.Kind, - properties: Int + properties: Int, + overriddenSymbols: List[(String, m.Position)] = List.empty ): String = { val previousOwner = currentOwner currentOwner = symbol(signature) @@ -155,12 +156,16 @@ trait MtagsIndexer { syntax, role ) + val encodedOverriddenSymbols = overriddenSymbols.map{ + case (simpleName, pos) => UnresolvedOverriddenSymbol(simpleName, pos.start) + } val info = s.SymbolInformation( symbol = syntax, language = language, kind = kind, properties = properties, - displayName = signature.name.value + displayName = signature.name.value, + overriddenSymbols = encodedOverriddenSymbols ) visitOccurrence(occ, info, previousOwner) syntax diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala index c6cafdbb439..401d4da03dd 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala @@ -177,13 +177,13 @@ class ScalaToplevelMtags( newExpectExtensionTemplate(nextOwner) ) case CLASS | TRAIT | OBJECT | ENUM if needEmitMember(currRegion) => - emitMember(false, currRegion.owner) + val maybeNewIdent = emitMember(false, currRegion.owner) val template = expectTemplate match { case Some(expect) if expect.isCaseClassConstructor => newExpectCaseClassTemplate case _ => newExpectClassTemplate } - loop(indent, isAfterNewline = false, currRegion, template) + loop(maybeNewIdent.getOrElse(indent), isAfterNewline = maybeNewIdent.isDefined, currRegion, template) // also covers extension methods because of `def` inside case DEF // extension group @@ -485,30 +485,59 @@ class ScalaToplevelMtags( buf.result() } + @tailrec + private def acceptAllAfterOverriddenIdentifier(): Unit = { + scanner.curr.token match { + case LPAREN => + acceptBalancedDelimeters(LPAREN, RPAREN) + acceptAllAfterOverriddenIdentifier() + case LBRACKET => + acceptBalancedDelimeters(LBRACKET, RBRACKET) + acceptAllAfterOverriddenIdentifier() + case _ => + } + + } + + @tailrec + private def findOverridden(acc : List[Identifier]): (List[Identifier], Option[Int]) = { + val maybeNewIdent = acceptTrivia() + scanner.curr.token match { + case EXTENDS | WITH => + acceptTrivia() + val curr = newIdentifier.toList + acceptAllAfterOverriddenIdentifier() + findOverridden(curr ++ acc) + case _ => (acc, maybeNewIdent) + } + } + /** * Enters a toplevel symbol such as class, trait or object */ - def emitMember(isPackageObject: Boolean, owner: String): Unit = { + def emitMember(isPackageObject: Boolean, owner: String): Option[Int] = { val kind = scanner.curr.token acceptTrivia() val maybeName = newIdentifier currentOwner = owner + val (overridden0, maybeNewIdent) = findOverridden(List.empty) + val overridden = overridden0.map(id => (id.name, id.pos)) maybeName.foreach { name => kind match { case CLASS | ENUM => - tpe(name.name, name.pos, Kind.CLASS, 0) + tpe(name.name, name.pos, Kind.CLASS, 0, overridden) case TRAIT => - tpe(name.name, name.pos, Kind.TRAIT, 0) + tpe(name.name, name.pos, Kind.TRAIT, 0, overridden) case OBJECT => if (isPackageObject) { currentOwner = symbol(Scala.Descriptor.Package(name.name)) term("package", name.pos, Kind.OBJECT, 0) } else { - term(name.name, name.pos, Kind.OBJECT, 0) + term(name.name, name.pos, Kind.OBJECT, 0, overridden) } } } - scanner.nextToken() + maybeNewIdent } /** @@ -668,7 +697,9 @@ class ScalaToplevelMtags( } } - private def acceptTrivia(): Unit = { + private def acceptTrivia(): Option[Int] = { + var includedNewline = false + var ident = 0 scanner.nextToken() while ( !isDone && @@ -677,8 +708,15 @@ class ScalaToplevelMtags( case _ => false }) ) { + if(isNewline){ + includedNewline = true + ident = 0 + } else if(scanner.curr.token == WHITESPACE) { + ident += 1 + } scanner.nextToken() } + if(includedNewline) Some(ident) else None } private def nextIsNL(): Boolean = { diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/UnresolvedOverriddenSymbol.scala b/mtags/src/main/scala/scala/meta/internal/mtags/UnresolvedOverriddenSymbol.scala new file mode 100644 index 00000000000..791bad1e680 --- /dev/null +++ b/mtags/src/main/scala/scala/meta/internal/mtags/UnresolvedOverriddenSymbol.scala @@ -0,0 +1,12 @@ +package scala.meta.internal.mtags + +object UnresolvedOverriddenSymbol { + def apply(name: String, pos: Int): String = + s"unresolved::$name::$pos" + + def unapply(unresolved: String): Option[(String, Int)] = + unresolved match { + case s"unresolved::$name::$pos" => pos.toIntOption.map((name, _)) + case _ => None + } +} diff --git a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala index 312fa346954..86a049abc1c 100644 --- a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala +++ b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala @@ -4,6 +4,7 @@ import scala.meta.Dialect import scala.meta.dialects import scala.meta.inputs.Input import scala.meta.internal.mtags.Mtags +import scala.meta.internal.mtags.UnresolvedOverriddenSymbol import munit.TestOptions @@ -530,7 +531,7 @@ class ScalaToplevelSuite extends BaseSuite { | } |} |""".stripMargin, - List("a/", "a/TypeProxy#"), + List("a/", "a/TypeProxy# -> Type"), dialect = dialects.Scala3, mode = ToplevelWithInner, ) @@ -603,6 +604,7 @@ class ScalaToplevelSuite extends BaseSuite { expected: List[String], mode: Mode = Toplevel, dialect: Dialect = dialects.Scala3, + includeOverridden: Boolean = true )(implicit location: munit.Location): Unit = { test(options) { val input = Input.VirtualFile("Test.scala", code) @@ -612,8 +614,17 @@ class ScalaToplevelSuite extends BaseSuite { val includeMembers = mode == All Mtags .allToplevels(input, dialect, includeMembers) - .occurrences - .map(_.symbol) + .symbols + .map{ si => + if(!includeOverridden || si.overriddenSymbols.isEmpty) si.symbol + else { + val overridden = + si.overriddenSymbols.collect{ + case UnresolvedOverriddenSymbol(name, _) => name + }.mkString(", ") + s"${si.symbol} -> $overridden" + } + } .toList case Toplevel => Mtags.toplevels(input, dialect) } From 7b95abaf3aabeeccd1750d1abfb97a78864269aa Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Thu, 31 Aug 2023 17:22:06 +0200 Subject: [PATCH 02/26] feature: collect overridden symbols in Scala toplevel mtags --- .../scala/meta/internal/mtags/Mtags.scala | 18 ++- .../meta/internal/mtags/MtagsIndexer.scala | 48 ++++--- .../internal/mtags/OverriddenSymbol.scala | 6 + .../internal/mtags/ScalaToplevelMtags.scala | 128 ++++++++++++------ .../mtags/UnresolvedOverriddenSymbol.scala | 12 -- .../test/scala/tests/ScalaToplevelSuite.scala | 75 ++++++---- .../scala/tests/ToplevelLibrarySuite.scala | 2 +- 7 files changed, 187 insertions(+), 102 deletions(-) create mode 100644 mtags/src/main/scala/scala/meta/internal/mtags/OverriddenSymbol.scala delete mode 100644 mtags/src/main/scala/scala/meta/internal/mtags/UnresolvedOverriddenSymbol.scala diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/Mtags.scala b/mtags/src/main/scala/scala/meta/internal/mtags/Mtags.scala index af9233fbbb5..805e88eced0 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/Mtags.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/Mtags.scala @@ -34,6 +34,7 @@ final class Mtags(implicit rc: ReportContext) { addLines(language, input.text) mtags .index() + .textDocument .occurrences .iterator .filterNot(_.symbol.isPackage) @@ -55,8 +56,9 @@ final class Mtags(implicit rc: ReportContext) { JavaMtags .index(input, includeMembers = true) .index() + .textDocument } else if (language.isScala) { - ScalaMtags.index(input, dialect).index() + ScalaMtags.index(input, dialect).index().textDocument } else { TextDocument() } @@ -91,11 +93,11 @@ object Mtags { .toList } - def allToplevels( + def allToplevelsEnriched( input: Input.VirtualFile, dialect: Dialect, includeMembers: Boolean = true - )(implicit rc: ReportContext = EmptyReportContext): TextDocument = { + )(implicit rc: ReportContext = EmptyReportContext): EnrichedTextDocument = { input.toLanguage match { case Language.JAVA => new JavaMtags(input, includeMembers = true).index() @@ -104,9 +106,17 @@ object Mtags { new ScalaToplevelMtags(input, true, includeMembers, dialect) mtags.index() case _ => - TextDocument() + JustDocument(TextDocument()) } } + + def allToplevels( + input: Input.VirtualFile, + dialect: Dialect, + includeMembers: Boolean = true + )(implicit rc: ReportContext = EmptyReportContext): TextDocument = + allToplevelsEnriched(input, dialect, includeMembers).textDocument + def toplevels( input: Input.VirtualFile, dialect: Dialect = dialects.Scala213 diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/MtagsIndexer.scala b/mtags/src/main/scala/scala/meta/internal/mtags/MtagsIndexer.scala index 730feaab2e1..09113451a7c 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/MtagsIndexer.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/MtagsIndexer.scala @@ -11,18 +11,21 @@ import scala.meta.internal.semanticdb.Scala._ import scala.meta.internal.semanticdb.SymbolInformation.Kind import scala.meta.internal.{semanticdb => s} -trait MtagsIndexer { +trait GenericMtagsIndexer[T <: EnrichedTextDocument] { def language: Language def indexRoot(): Unit def input: Input.VirtualFile - def index(): s.TextDocument = { + protected def documentToResult(doc: s.TextDocument): T + def index(): T = { indexRoot() - s.TextDocument( - uri = input.path, - text = input.text, - language = language, - occurrences = names.result(), - symbols = symbols.result() + documentToResult( + s.TextDocument( + uri = input.path, + text = input.text, + language = language, + occurrences = names.result(), + symbols = symbols.result() + ) ) } // This method is intentionally non-final to allow accessing this stream directly without building a s.TextDocument. @@ -64,8 +67,8 @@ trait MtagsIndexer { ) } } - def term(name: String, pos: m.Position, kind: Kind, properties: Int, overriddenSymbols: List[(String, m.Position)] = List.empty): String = - addSignature(Descriptor.Term(name), pos, kind, properties, overriddenSymbols) + def term(name: String, pos: m.Position, kind: Kind, properties: Int): String = + addSignature(Descriptor.Term(name), pos, kind, properties) def term(name: Term.Name, kind: Kind, properties: Int): String = addSignature(Descriptor.Term(name.value), name.pos, kind, properties) def tparam(name: Name, kind: Kind, properties: Int): String = @@ -122,8 +125,8 @@ trait MtagsIndexer { properties ) } - def tpe(name: String, pos: m.Position, kind: Kind, properties: Int, overriddenSymbols: List[(String, m.Position)] = List.empty): String = - addSignature(Descriptor.Type(name), pos, kind, properties, overriddenSymbols) + def tpe(name: String, pos: m.Position, kind: Kind, properties: Int): String = + addSignature(Descriptor.Type(name), pos, kind, properties) def tpe(name: Name, kind: Kind, properties: Int): String = addSignature(Descriptor.Type(name.value), name.pos, kind, properties) def pkg(name: String, pos: m.Position): String = { @@ -141,8 +144,7 @@ trait MtagsIndexer { signature: Descriptor, definition: m.Position, kind: s.SymbolInformation.Kind, - properties: Int, - overriddenSymbols: List[(String, m.Position)] = List.empty + properties: Int ): String = { val previousOwner = currentOwner currentOwner = symbol(signature) @@ -156,16 +158,12 @@ trait MtagsIndexer { syntax, role ) - val encodedOverriddenSymbols = overriddenSymbols.map{ - case (simpleName, pos) => UnresolvedOverriddenSymbol(simpleName, pos.start) - } val info = s.SymbolInformation( symbol = syntax, language = language, kind = kind, properties = properties, - displayName = signature.name.value, - overriddenSymbols = encodedOverriddenSymbols + displayName = signature.name.value ) visitOccurrence(occ, info, previousOwner) syntax @@ -175,3 +173,15 @@ trait MtagsIndexer { Symbols.Global(Symbols.RootPackage, signature) else Symbols.Global(currentOwner, signature) } + +trait EnrichedTextDocument { + def textDocument: s.TextDocument +} + +case class JustDocument(textDocument: s.TextDocument) + extends EnrichedTextDocument + +trait MtagsIndexer extends GenericMtagsIndexer[JustDocument] { + protected def documentToResult(doc: s.TextDocument): JustDocument = + JustDocument(doc) +} diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/OverriddenSymbol.scala b/mtags/src/main/scala/scala/meta/internal/mtags/OverriddenSymbol.scala new file mode 100644 index 00000000000..259f1c961bf --- /dev/null +++ b/mtags/src/main/scala/scala/meta/internal/mtags/OverriddenSymbol.scala @@ -0,0 +1,6 @@ +package scala.meta.internal.mtags + +sealed trait OverriddenSymbol +case class UnresolvedOverriddenSymbol(name: String, pos: Int) + extends OverriddenSymbol +case class ResolvedOverriddenSymbol(symbol: String) extends OverriddenSymbol diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala index 401d4da03dd..4b5eb4c621b 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala @@ -14,6 +14,7 @@ import scala.meta.internal.semanticdb.Scala import scala.meta.internal.semanticdb.Scala._ import scala.meta.internal.semanticdb.SymbolInformation import scala.meta.internal.semanticdb.SymbolInformation.Kind +import scala.meta.internal.semanticdb.TextDocument import scala.meta.internal.tokenizers.LegacyScanner import scala.meta.internal.tokenizers.LegacyToken._ import scala.meta.tokenizers.TokenizeException @@ -46,7 +47,17 @@ class ScalaToplevelMtags( includeMembers: Boolean, dialect: Dialect )(implicit rc: ReportContext) - extends MtagsIndexer { + extends GenericMtagsIndexer[TextDocumentWithOverridden] { + + override protected def documentToResult( + doc: TextDocument + ): TextDocumentWithOverridden = + new TextDocumentWithOverridden(doc, overridden.result) + + private val overridden = List.newBuilder[(String, List[OverriddenSymbol])] + + private def addOverridden(symbols: List[OverriddenSymbol]) = + overridden += ((currentOwner, symbols)) import ScalaToplevelMtags._ @@ -177,13 +188,13 @@ class ScalaToplevelMtags( newExpectExtensionTemplate(nextOwner) ) case CLASS | TRAIT | OBJECT | ENUM if needEmitMember(currRegion) => - val maybeNewIdent = emitMember(false, currRegion.owner) + emitMember(false, currRegion.owner) val template = expectTemplate match { case Some(expect) if expect.isCaseClassConstructor => newExpectCaseClassTemplate case _ => newExpectClassTemplate } - loop(maybeNewIdent.getOrElse(indent), isAfterNewline = maybeNewIdent.isDefined, currRegion, template) + loop(indent, isAfterNewline = false, currRegion, template) // also covers extension methods because of `def` inside case DEF // extension group @@ -402,6 +413,23 @@ class ScalaToplevelMtags( currRegion.changeCaseClassState(true), nextExpectTemplate ) + case EXTENDS => + val (overridden, maybeNewIdent) = findOverridden(List.empty) + expectTemplate.map(tmpl => + withOwner(tmpl.owner) { + addOverridden( + overridden.reverse.map(id => + UnresolvedOverriddenSymbol(id.name, id.pos.start) + ) + ) + } + ) + loop( + maybeNewIdent.getOrElse(indent), + isAfterNewline = maybeNewIdent.isDefined, + currRegion, + expectTemplate + ) case IDENTIFIER if currRegion.emitIdentifier && includeMembers => withOwner(currRegion.owner) { term( @@ -419,17 +447,14 @@ class ScalaToplevelMtags( ) case CASE => val nextIsNewLine = nextIsNL() - val (shouldCreateClassTemplate, isAfterNewline) = + val isAfterNewline = emitEnumCases(region, nextIsNewLine) - val nextExpectTemplate = - if (shouldCreateClassTemplate) newExpectClassTemplate - else expectTemplate.filter(!_.isPackageBody) loop( indent, isAfterNewline, currRegion, if (scanner.curr.token == CLASS) newExpectCaseClassTemplate - else nextExpectTemplate + else newExpectClassTemplate ) case t => val nextExpectTemplate = expectTemplate.filter(!_.isPackageBody) @@ -486,7 +511,8 @@ class ScalaToplevelMtags( } @tailrec - private def acceptAllAfterOverriddenIdentifier(): Unit = { + private def acceptAllAfterOverriddenIdentifier(): Option[Int] = { + val maybeNewIdent = acceptTrivia() scanner.curr.token match { case LPAREN => acceptBalancedDelimeters(LPAREN, RPAREN) @@ -494,50 +520,59 @@ class ScalaToplevelMtags( case LBRACKET => acceptBalancedDelimeters(LBRACKET, RBRACKET) acceptAllAfterOverriddenIdentifier() - case _ => + case _ => maybeNewIdent } } @tailrec - private def findOverridden(acc : List[Identifier]): (List[Identifier], Option[Int]) = { + private def findOverridden( + acc0: List[Identifier] + ): (List[Identifier], Option[Int]) = { val maybeNewIdent = acceptTrivia() - scanner.curr.token match { - case EXTENDS | WITH => - acceptTrivia() - val curr = newIdentifier.toList - acceptAllAfterOverriddenIdentifier() - findOverridden(curr ++ acc) - case _ => (acc, maybeNewIdent) + val (shouldGoOn, acc) = scanner.curr.token match { + case IDENTIFIER => + (true, newIdentifier.toList ++ acc0) + case LBRACE => + acceptBalancedDelimeters(LBRACE, RBRACE) + (true, acc0) + case _ => (false, acc0) } + + if (shouldGoOn) { + val maybeNewIdent = acceptAllAfterOverriddenIdentifier() + scanner.curr.token match { + case WITH => findOverridden(acc) + case _ => (acc, maybeNewIdent) + } + } else (acc, maybeNewIdent) + } /** * Enters a toplevel symbol such as class, trait or object */ - def emitMember(isPackageObject: Boolean, owner: String): Option[Int] = { + def emitMember(isPackageObject: Boolean, owner: String): Unit = { val kind = scanner.curr.token acceptTrivia() val maybeName = newIdentifier currentOwner = owner - val (overridden0, maybeNewIdent) = findOverridden(List.empty) - val overridden = overridden0.map(id => (id.name, id.pos)) maybeName.foreach { name => kind match { case CLASS | ENUM => - tpe(name.name, name.pos, Kind.CLASS, 0, overridden) + tpe(name.name, name.pos, Kind.CLASS, 0) case TRAIT => - tpe(name.name, name.pos, Kind.TRAIT, 0, overridden) + tpe(name.name, name.pos, Kind.TRAIT, 0) case OBJECT => if (isPackageObject) { currentOwner = symbol(Scala.Descriptor.Package(name.name)) term("package", name.pos, Kind.OBJECT, 0) } else { - term(name.name, name.pos, Kind.OBJECT, 0, overridden) + term(name.name, name.pos, Kind.OBJECT, 0) } } } - maybeNewIdent + scanner.nextToken() } /** @@ -597,7 +632,7 @@ class ScalaToplevelMtags( private def emitEnumCases( region: Region, nextIsNewLine: Boolean - ): (Boolean, Boolean) = { + ): Boolean = { def ownerCompanionObject = if (currentOwner.endsWith("#")) s"${currentOwner.stripSuffix("#")}." @@ -607,19 +642,22 @@ class ScalaToplevelMtags( val pos = newPosition val name = scanner.curr.name def emitEnumCaseObject() = { - withOwner(ownerCompanionObject) { - term( - name, - pos, - Kind.METHOD, - SymbolInformation.Property.VAL.value - ) - } + currentOwner = ownerCompanionObject + term( + name, + pos, + Kind.METHOD, + SymbolInformation.Property.VAL.value + ) } + def emitOverridden() = addOverridden( + List(ResolvedOverriddenSymbol(region.owner)) + ) val nextIsNewLine0 = nextIsNL() scanner.curr.token match { case COMMA => emitEnumCaseObject() + emitOverridden() resetRegion(region) val nextIsNewLine1 = nextIsNL() emitEnumCases(region, nextIsNewLine1) @@ -631,12 +669,15 @@ class ScalaToplevelMtags( Kind.CLASS, SymbolInformation.Property.VAL.value ) - (true, false) - case _ => + false + case tok => emitEnumCaseObject() - (false, nextIsNewLine0) + if (tok != EXTENDS) { + emitOverridden() + } + nextIsNewLine0 } - case _ => (false, nextIsNewLine) + case _ => nextIsNewLine } } @@ -708,15 +749,15 @@ class ScalaToplevelMtags( case _ => false }) ) { - if(isNewline){ + if (isNewline) { includedNewline = true ident = 0 - } else if(scanner.curr.token == WHITESPACE) { + } else if (scanner.curr.token == WHITESPACE) { ident += 1 } scanner.nextToken() } - if(includedNewline) Some(ident) else None + if (includedNewline) Some(ident) else None } private def nextIsNL(): Boolean = { @@ -992,3 +1033,8 @@ object ScalaToplevelMtags { } } } + +case class TextDocumentWithOverridden( + textDocument: TextDocument, + overridden: List[(String, List[OverriddenSymbol])] +) extends EnrichedTextDocument diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/UnresolvedOverriddenSymbol.scala b/mtags/src/main/scala/scala/meta/internal/mtags/UnresolvedOverriddenSymbol.scala deleted file mode 100644 index 791bad1e680..00000000000 --- a/mtags/src/main/scala/scala/meta/internal/mtags/UnresolvedOverriddenSymbol.scala +++ /dev/null @@ -1,12 +0,0 @@ -package scala.meta.internal.mtags - -object UnresolvedOverriddenSymbol { - def apply(name: String, pos: Int): String = - s"unresolved::$name::$pos" - - def unapply(unresolved: String): Option[(String, Int)] = - unresolved match { - case s"unresolved::$name::$pos" => pos.toIntOption.map((name, _)) - case _ => None - } -} diff --git a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala index 86a049abc1c..c8ede54b6cf 100644 --- a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala +++ b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala @@ -4,6 +4,8 @@ import scala.meta.Dialect import scala.meta.dialects import scala.meta.inputs.Input import scala.meta.internal.mtags.Mtags +import scala.meta.internal.mtags.ResolvedOverriddenSymbol +import scala.meta.internal.mtags.TextDocumentWithOverridden import scala.meta.internal.mtags.UnresolvedOverriddenSymbol import munit.TestOptions @@ -52,7 +54,7 @@ class ScalaToplevelSuite extends BaseSuite { List( "_empty_/A.", "_empty_/A.foo().", "_empty_/A.Z#", "_empty_/B#", "_empty_/B#X#", "_empty_/B#foo().", "_empty_/B#v.", "_empty_/C#", - "_empty_/C#i.", "_empty_/D#", "_empty_/D.Da.", "_empty_/D.Db.", + "_empty_/C#i.", "_empty_/D#", "_empty_/D.Da. -> D", "_empty_/D.Db. -> D", "_empty_/D#getI().", "_empty_/D#i.", ), mode = All, @@ -100,7 +102,7 @@ class ScalaToplevelSuite extends BaseSuite { List( "_empty_/A.", "_empty_/A.foo().", "_empty_/A.Z#", "_empty_/B#", "_empty_/B#X#", "_empty_/B#foo().", "_empty_/C#", "_empty_/D#", - "_empty_/D.Da.", "_empty_/D.Db.", + "_empty_/D.Da. -> _empty_/D#", "_empty_/D.Db. -> _empty_/D#", ), mode = All, ) @@ -472,9 +474,10 @@ class ScalaToplevelSuite extends BaseSuite { | |enum NotPlanets{ case Vase } |""".stripMargin, - List("a/", "a/Planets#", "a/Planets.Earth.", "a/Planets.Mercury.", - "a/Planets#num.", "a/Planets.Venus.", "a/NotPlanets#", - "a/NotPlanets.Vase."), + List("a/", "a/Planets#", "a/Planets.Earth. -> Planets", + "a/Planets.Mercury. -> Planets", "a/Planets#num.", + "a/Planets.Venus. -> Planets", "a/NotPlanets#", + "a/NotPlanets.Vase. -> a/NotPlanets#"), dialect = dialects.Scala3, mode = All, ) @@ -495,9 +498,10 @@ class ScalaToplevelSuite extends BaseSuite { |enum NotPlanets: | case Vase |""".stripMargin, - List("a/", "a/Planets#", "a/Planets.Earth.", "a/Planets.Mercury.", - "a/Planets#num.", "a/Planets.Venus.", "a/NotPlanets#", - "a/NotPlanets.Vase."), + List("a/", "a/Planets#", "a/Planets.Earth. -> Planets", + "a/Planets.Mercury. -> Planets", "a/Planets#num.", + "a/Planets.Venus. -> Planets", "a/NotPlanets#", + "a/NotPlanets.Vase. -> a/NotPlanets#"), dialect = dialects.Scala3, mode = All, ) @@ -514,9 +518,10 @@ class ScalaToplevelSuite extends BaseSuite { |enum NotPlanets: | case Vase |""".stripMargin, - List("a/", "a/Planets#", "a/Planets#mmm().", "a/Planets.Earth#", - "a/Planets.Earth#v.", "a/Planets.Mercury#", "a/Planets#num.", - "a/Planets.Venus#", "a/NotPlanets#", "a/NotPlanets.Vase."), + List("a/", "a/Planets#", "a/Planets#mmm().", "a/Planets.Earth# -> Planets", + "a/Planets.Earth#v.", "a/Planets.Mercury# -> Planets", "a/Planets#num.", + "a/Planets.Venus# -> Planets", "a/NotPlanets#", + "a/NotPlanets.Vase. -> a/NotPlanets#"), dialect = dialects.Scala3, mode = All, ) @@ -598,13 +603,24 @@ class ScalaToplevelSuite extends BaseSuite { mode = All, ) + check( + "overridden", + """|package a + |case class A[T](v: Int)(using Context) extends B[Int](2) with C: + | object O extends H + |class M(ctx: Context) extends W(1)(ctx) + |""".stripMargin, + List("a/", "a/A# -> B, C", "a/A#v.", "a/A#O. -> H", "a/M# -> W"), + dialect = dialects.Scala3, + mode = All, + ) + def check( options: TestOptions, code: String, expected: List[String], mode: Mode = Toplevel, dialect: Dialect = dialects.Scala3, - includeOverridden: Boolean = true )(implicit location: munit.Location): Unit = { test(options) { val input = Input.VirtualFile("Test.scala", code) @@ -612,20 +628,29 @@ class ScalaToplevelSuite extends BaseSuite { mode match { case All | ToplevelWithInner => val includeMembers = mode == All - Mtags - .allToplevels(input, dialect, includeMembers) - .symbols - .map{ si => - if(!includeOverridden || si.overriddenSymbols.isEmpty) si.symbol - else { - val overridden = - si.overriddenSymbols.collect{ - case UnresolvedOverriddenSymbol(name, _) => name - }.mkString(", ") - s"${si.symbol} -> $overridden" + val enrichedDoc = + Mtags.allToplevelsEnriched(input, dialect, includeMembers) + val symbols = + enrichedDoc.textDocument.occurrences.map(_.symbol).toList + enrichedDoc match { + case doc: TextDocumentWithOverridden => + val overriddenMap = doc.overridden.toMap + symbols.map { symbol => + overriddenMap.get(symbol) match { + case None => symbol + case Some(symbols) => + val overridden = + symbols + .map { + case ResolvedOverriddenSymbol(symbol) => symbol + case UnresolvedOverriddenSymbol(name, _) => name + } + .mkString(", ") + s"$symbol -> $overridden" + } } - } - .toList + case _ => symbols + } case Toplevel => Mtags.toplevels(input, dialect) } assertNoDiff( diff --git a/tests/unit/src/test/scala/tests/ToplevelLibrarySuite.scala b/tests/unit/src/test/scala/tests/ToplevelLibrarySuite.scala index f5c0786c22f..39bfb2d542f 100644 --- a/tests/unit/src/test/scala/tests/ToplevelLibrarySuite.scala +++ b/tests/unit/src/test/scala/tests/ToplevelLibrarySuite.scala @@ -77,7 +77,7 @@ class ToplevelLibrarySuite extends BaseSuite { assertNoDiff( obtained.mkString("\n"), expected.mkString("\n"), - s"${input.path}", + s"${input.text}", ) } From 950aa0447c43648901c29cf941b77addf6195794 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Mon, 4 Sep 2023 16:24:33 +0200 Subject: [PATCH 03/26] feature: index overriden symbols --- .../ImplementationProvider.scala | 48 ++++++++++++++ .../scala/meta/internal/metals/Indexer.scala | 43 +++++++++++-- .../meta/internal/metals/JarOverridden.scala | 19 ++++++ .../internal/metals/MetalsLspService.scala | 1 + .../scala/meta/internal/metals/Tables.scala | 1 + .../internal/mtags/GlobalSymbolIndex.scala | 6 +- .../scala/meta/internal/mtags/Mtags.scala | 40 ++++++++---- .../meta/internal/mtags/MtagsIndexer.scala | 43 +++++++++---- .../internal/mtags/OnDemandSymbolIndex.scala | 8 +-- .../internal/mtags/ScalaToplevelMtags.scala | 16 +++-- .../internal/mtags/SymbolIndexBucket.scala | 62 +++++++++++-------- .../tests/DelegatingGlobalSymbolIndex.scala | 6 +- .../test/scala/tests/ScalaToplevelSuite.scala | 13 ++-- 13 files changed, 226 insertions(+), 80 deletions(-) create mode 100644 metals/src/main/scala/scala/meta/internal/metals/JarOverridden.scala diff --git a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala index f36257b9ebb..342781350ef 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala @@ -18,8 +18,12 @@ import scala.meta.internal.metals.ScalaVersionSelector import scala.meta.internal.metals.SemanticdbFeatureProvider import scala.meta.internal.mtags.GlobalSymbolIndex import scala.meta.internal.mtags.Mtags +import scala.meta.internal.mtags.OverriddenSymbol +import scala.meta.internal.mtags.ResolvedOverriddenSymbol +import scala.meta.internal.mtags.SemanticdbPath import scala.meta.internal.mtags.Semanticdbs import scala.meta.internal.mtags.SymbolDefinition +import scala.meta.internal.mtags.UnresolvedOverriddenSymbol import scala.meta.internal.mtags.{Symbol => MSymbol} import scala.meta.internal.parsing.Trees import scala.meta.internal.semanticdb.ClassSignature @@ -53,6 +57,8 @@ final class ImplementationProvider( private val globalTable = new GlobalClassTable(buildTargets) private val implementationsInPath = new ConcurrentHashMap[Path, Map[String, Set[ClassLocation]]] + private val implementationsInDependencySources = + new ConcurrentHashMap[String, Set[Implementation]] override def reset(): Unit = { implementationsInPath.clear() @@ -70,6 +76,39 @@ final class ImplementationProvider( ) } + def addImplementationInDependenciesInfo( + overriddenInfo: List[ + (AbsolutePath, List[(String, List[OverriddenSymbol])]) + ] + ): Unit = { + overriddenInfo.foreach { case (path, overridden) => + addImplementationInDependenciesInfo(path, overridden) + } + } + + private def addImplementationInDependenciesInfo( + path: AbsolutePath, + newOverridden: List[(String, List[OverriddenSymbol])], + ): Unit = { + def createUpdate( + newSymbol: Implementation + ): (String, Set[Implementation]) => Set[Implementation] = { + case (_, null) => Set(newSymbol) + case (_, previous) => previous + newSymbol + } + newOverridden.foreach { case (clazz, list) => + list.foreach { + case ResolvedOverriddenSymbol(symbol) => + val update = createUpdate(ImplementationForResolved(clazz)) + implementationsInDependencySources.compute(symbol, update(_, _)) + case UnresolvedOverriddenSymbol(name, pos) => + val update = + createUpdate(ImplementationForUnresolved(clazz, path, pos)) + implementationsInDependencySources.compute(name, update(_, _)) + } + } + } + private def computeInheritance( documents: TextDocuments ): Map[String, Set[ClassLocation]] = { @@ -530,3 +569,12 @@ object ImplementationProvider { info.isObject || info.isClass || info.isTrait || info.isType || info.isInterface } + +sealed trait Implementation +case class ImplementationForResolved(implementationSymbol: String) + extends Implementation +case class ImplementationForUnresolved( + implementationSymbol: String, + usagePath: AbsolutePath, + usagePosition: Int, +) extends Implementation diff --git a/metals/src/main/scala/scala/meta/internal/metals/Indexer.scala b/metals/src/main/scala/scala/meta/internal/metals/Indexer.scala index e6396948819..53702271990 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Indexer.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Indexer.scala @@ -23,12 +23,16 @@ import scala.meta.internal.builds.BuildTool import scala.meta.internal.builds.BuildTools import scala.meta.internal.builds.Digest.Status import scala.meta.internal.builds.WorkspaceReload +import scala.meta.internal.implementation.ImplementationProvider import scala.meta.internal.metals.MetalsEnrichments._ import scala.meta.internal.metals.clients.language.DelegatingLanguageClient import scala.meta.internal.metals.clients.language.ForwardingMetalsBuildClient import scala.meta.internal.metals.debug.BuildTargetClasses import scala.meta.internal.metals.watcher.FileWatcher +import scala.meta.internal.mtags.EnrichedTextDocument import scala.meta.internal.mtags.OnDemandSymbolIndex +import scala.meta.internal.mtags.OverriddenSymbol +import scala.meta.internal.mtags.OverriddenSymbolsEnrichment import scala.meta.internal.semanticdb.Scala._ import scala.meta.internal.tvp.FolderTreeViewProvider import scala.meta.internal.worksheets.WorksheetProvider @@ -79,6 +83,7 @@ final case class Indexer( scalaVersionSelector: ScalaVersionSelector, sourceMapper: SourceMapper, workspaceFolder: AbsolutePath, + implementationProvider: ImplementationProvider, )(implicit rc: ReportContext) { private implicit def ec: ExecutionContextExecutorService = executionContext @@ -465,7 +470,12 @@ final case class Indexer( ) ) .getOrElse(Scala213) - definitionIndex.addSourceDirectory(path, dialect) + + val documents = definitionIndex.addSourceDirectory(path, dialect) + val overriddenInfo = collectOverriddenInfo(documents) + implementationProvider.addImplementationInDependenciesInfo( + overriddenInfo + ) } else { scribe.warn(s"unexpected dependency: $path") } @@ -477,6 +487,17 @@ final case class Indexer( usedJars.toSet } + private def collectOverriddenInfo( + documents: List[(AbsolutePath, EnrichedTextDocument)] + ): List[(AbsolutePath, List[(String, List[OverriddenSymbol])])] = + documents.flatMap { case (path, document) => + document.enrichment match { + case OverriddenSymbolsEnrichment(overridden) => + Some((path, overridden)) + case _ => None + } + } + private def indexJdkSources( data: TargetData, dependencySources: b.DependencySourcesResult, @@ -593,14 +614,26 @@ final case class Indexer( * @param path JAR path */ private def addSourceJarSymbols(path: AbsolutePath): Unit = { - tables.jarSymbols.getTopLevels(path) match { - case Some(toplevels) => + ( + tables.jarSymbols.getTopLevels(path), + tables.jarOverriddenSymbols.getOverriddenInfo(path), + ) match { + case (Some(toplevels), Some(overridden)) => val dialect = ScalaVersions.dialectForDependencyJar(path.filename) definitionIndex.addIndexedSourceJar(path, toplevels, dialect) - case None => + implementationProvider.addImplementationInDependenciesInfo(overridden) + case _ => val dialect = ScalaVersions.dialectForDependencyJar(path.filename) - val toplevels = definitionIndex.addSourceJar(path, dialect) + val documents = definitionIndex.addSourceJar(path, dialect) + val toplevels = documents.flatMap { case (path, document) => + document.topLevels.map((_, path)) + } + val overriddenInfo = collectOverriddenInfo(documents) + implementationProvider.addImplementationInDependenciesInfo( + overriddenInfo + ) tables.jarSymbols.putTopLevels(path, toplevels) + tables.jarOverriddenSymbols.putOverriddenInfo(path, overriddenInfo) } } diff --git a/metals/src/main/scala/scala/meta/internal/metals/JarOverridden.scala b/metals/src/main/scala/scala/meta/internal/metals/JarOverridden.scala new file mode 100644 index 00000000000..6e2c63a846c --- /dev/null +++ b/metals/src/main/scala/scala/meta/internal/metals/JarOverridden.scala @@ -0,0 +1,19 @@ +package scala.meta.internal.metals + +import java.sql.Connection + +import scala.meta.internal.mtags.OverriddenSymbol +import scala.meta.io.AbsolutePath + +//TODO:: implement +final class JarOverridden(conn: () => Connection) { + + def getOverriddenInfo( + path: AbsolutePath + ): Option[List[(AbsolutePath, List[(String, List[OverriddenSymbol])])]] = None + + def putOverriddenInfo( + path: AbsolutePath, + overridden: List[(AbsolutePath, List[(String, List[OverriddenSymbol])])], + ): Int = 0 +} diff --git a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala index 2a2d149cd50..e1a10e8ebba 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala @@ -2387,6 +2387,7 @@ class MetalsLspService( scalaVersionSelector, sourceMapper, folder, + implementationProvider, ) private def checkRunningBloopVersion(bspServerVersion: String) = { diff --git a/metals/src/main/scala/scala/meta/internal/metals/Tables.scala b/metals/src/main/scala/scala/meta/internal/metals/Tables.scala index 61e6accb1da..14c0804c957 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Tables.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Tables.scala @@ -23,6 +23,7 @@ final class Tables( import Tables.ConnectionState val jarSymbols = new JarTopLevels(() => connection) + val jarOverriddenSymbols = new JarOverridden(() => connection) val digests = new Digests(() => connection, time) val dependencySources = diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/GlobalSymbolIndex.scala b/mtags/src/main/scala/scala/meta/internal/mtags/GlobalSymbolIndex.scala index cd94563fcf7..57cb2fe054b 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/GlobalSymbolIndex.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/GlobalSymbolIndex.scala @@ -55,7 +55,7 @@ trait GlobalSymbolIndex { file: AbsolutePath, sourceDirectory: Option[AbsolutePath], dialect: Dialect - ): List[String] + ): Option[EnrichedTextDocument] /** * Index a jar or zip file containing Scala and Java source files. @@ -88,7 +88,7 @@ trait GlobalSymbolIndex { def addSourceJar( jar: AbsolutePath, dialect: Dialect - ): List[(String, AbsolutePath)] + ): List[(AbsolutePath, EnrichedTextDocument)] /** * The same as `addSourceJar` except for directories @@ -96,7 +96,7 @@ trait GlobalSymbolIndex { def addSourceDirectory( dir: AbsolutePath, dialect: Dialect - ): List[(String, AbsolutePath)] + ): List[(AbsolutePath, EnrichedTextDocument)] } diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/Mtags.scala b/mtags/src/main/scala/scala/meta/internal/mtags/Mtags.scala index 805e88eced0..ef5b7411422 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/Mtags.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/Mtags.scala @@ -45,6 +45,28 @@ final class Mtags(implicit rc: ReportContext) { } } + def enrichedTextDocument( + input: Input.VirtualFile, + dialect: Dialect = dialects.Scala213, + includeMembers: Boolean = false + ): Option[EnrichedTextDocument] = { + val language = input.toLanguage + if (language.isJava || language.isScala) { + val mtags = + if (language.isJava) + new JavaToplevelMtags(input) + else + new ScalaToplevelMtags( + input, + includeInnerClasses = true, + includeMembers, + dialect + ) + addLines(language, input.text) + Some(mtags.index()) + } else None + } + def index( language: Language, input: Input.VirtualFile, @@ -93,29 +115,21 @@ object Mtags { .toList } - def allToplevelsEnriched( + def allToplevels( input: Input.VirtualFile, dialect: Dialect, includeMembers: Boolean = true - )(implicit rc: ReportContext = EmptyReportContext): EnrichedTextDocument = { + )(implicit rc: ReportContext = EmptyReportContext): TextDocument = input.toLanguage match { case Language.JAVA => - new JavaMtags(input, includeMembers = true).index() + new JavaMtags(input, includeMembers = true).index().textDocument case Language.SCALA => val mtags = new ScalaToplevelMtags(input, true, includeMembers, dialect) - mtags.index() + mtags.index().textDocument case _ => - JustDocument(TextDocument()) + TextDocument() } - } - - def allToplevels( - input: Input.VirtualFile, - dialect: Dialect, - includeMembers: Boolean = true - )(implicit rc: ReportContext = EmptyReportContext): TextDocument = - allToplevelsEnriched(input, dialect, includeMembers).textDocument def toplevels( input: Input.VirtualFile, diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/MtagsIndexer.scala b/mtags/src/main/scala/scala/meta/internal/mtags/MtagsIndexer.scala index 09113451a7c..e7c2708b071 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/MtagsIndexer.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/MtagsIndexer.scala @@ -11,14 +11,14 @@ import scala.meta.internal.semanticdb.Scala._ import scala.meta.internal.semanticdb.SymbolInformation.Kind import scala.meta.internal.{semanticdb => s} -trait GenericMtagsIndexer[T <: EnrichedTextDocument] { +trait GenericMtagsIndexer[E <: TextDocumentEnrichment] { def language: Language def indexRoot(): Unit def input: Input.VirtualFile - protected def documentToResult(doc: s.TextDocument): T - def index(): T = { + protected def enrich(doc: s.TextDocument): EnrichedTextDocument + def index(): EnrichedTextDocument = { indexRoot() - documentToResult( + enrich( s.TextDocument( uri = input.path, text = input.text, @@ -174,14 +174,35 @@ trait GenericMtagsIndexer[T <: EnrichedTextDocument] { else Symbols.Global(currentOwner, signature) } -trait EnrichedTextDocument { - def textDocument: s.TextDocument +class EnrichedTextDocument( + val textDocument: s.TextDocument, + val enrichment: TextDocumentEnrichment, + topLevelFiler: List[String] => List[String] = identity +) { + lazy val topLevels: List[String] = { + val topLevelSymbols = + textDocument.occurrences.iterator + .filterNot(_.symbol.isPackage) + .map(_.symbol) + .toList + topLevelFiler(topLevelSymbols) + } + + def withFilter(newFilter: List[String] => List[String]) = + new EnrichedTextDocument( + textDocument, + enrichment, + topLevelFiler.compose(newFilter) + ) } -case class JustDocument(textDocument: s.TextDocument) - extends EnrichedTextDocument +sealed trait TextDocumentEnrichment +case object NoEnrichment extends TextDocumentEnrichment +case class OverriddenSymbolsEnrichment( + overridden: List[(String, List[OverriddenSymbol])] +) extends TextDocumentEnrichment -trait MtagsIndexer extends GenericMtagsIndexer[JustDocument] { - protected def documentToResult(doc: s.TextDocument): JustDocument = - JustDocument(doc) +trait MtagsIndexer extends GenericMtagsIndexer[NoEnrichment.type] { + protected def enrich(doc: s.TextDocument): EnrichedTextDocument = + new EnrichedTextDocument(doc, NoEnrichment) } diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/OnDemandSymbolIndex.scala b/mtags/src/main/scala/scala/meta/internal/mtags/OnDemandSymbolIndex.scala index 8f5beeca2c2..b9935b9e25e 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/OnDemandSymbolIndex.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/OnDemandSymbolIndex.scala @@ -65,7 +65,7 @@ final class OnDemandSymbolIndex( override def addSourceDirectory( dir: AbsolutePath, dialect: Dialect - ): List[(String, AbsolutePath)] = + ): List[(AbsolutePath, EnrichedTextDocument)] = tryRun( dir, List.empty, @@ -77,7 +77,7 @@ final class OnDemandSymbolIndex( override def addSourceJar( jar: AbsolutePath, dialect: Dialect - ): List[(String, AbsolutePath)] = + ): List[(AbsolutePath, EnrichedTextDocument)] = tryRun( jar, List.empty, { @@ -109,10 +109,10 @@ final class OnDemandSymbolIndex( source: AbsolutePath, sourceDirectory: Option[AbsolutePath], dialect: Dialect - ): List[String] = + ): Option[EnrichedTextDocument] = tryRun( source, - List.empty, { + None, { indexedSources += 1 getOrCreateBucket(dialect).addSourceFile(source, sourceDirectory) } diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala index 4b5eb4c621b..d2ed3152fe5 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala @@ -47,12 +47,15 @@ class ScalaToplevelMtags( includeMembers: Boolean, dialect: Dialect )(implicit rc: ReportContext) - extends GenericMtagsIndexer[TextDocumentWithOverridden] { + extends GenericMtagsIndexer[OverriddenSymbolsEnrichment] { - override protected def documentToResult( + override protected def enrich( doc: TextDocument - ): TextDocumentWithOverridden = - new TextDocumentWithOverridden(doc, overridden.result) + ): EnrichedTextDocument = + new EnrichedTextDocument( + doc, + OverriddenSymbolsEnrichment(overridden.result) + ) private val overridden = List.newBuilder[(String, List[OverriddenSymbol])] @@ -1033,8 +1036,3 @@ object ScalaToplevelMtags { } } } - -case class TextDocumentWithOverridden( - textDocument: TextDocument, - overridden: List[(String, List[OverriddenSymbol])] -) extends EnrichedTextDocument 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 ce0431b38a6..f62f7ff1db6 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/SymbolIndexBucket.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/SymbolIndexBucket.scala @@ -51,27 +51,30 @@ class SymbolIndexBucket( def close(): Unit = sourceJars.close() - def addSourceDirectory(dir: AbsolutePath): List[(String, AbsolutePath)] = { + def addSourceDirectory( + dir: AbsolutePath + ): List[(AbsolutePath, EnrichedTextDocument)] = { if (sourceJars.addEntry(dir.toNIO)) { dir.listRecursive.toList.flatMap { case source if source.isScala => - addSourceFile(source, Some(dir)).map(sym => (sym, source)) + addSourceFile(source, Some(dir)).map((source, _)) case _ => - List.empty + None } - } else - List.empty + } else List.empty } - def addSourceJar(jar: AbsolutePath): List[(String, AbsolutePath)] = { + def addSourceJar( + jar: AbsolutePath + ): List[(AbsolutePath, EnrichedTextDocument)] = { if (sourceJars.addEntry(jar.toNIO)) { FileIO.withJarFileSystem(jar, create = false) { root => try { root.listRecursive.toList.flatMap { case source if source.isScala => - addSourceFile(source, None, Some(jar)).map(sym => (sym, source)) + addSourceFile(source, None, Some(jar)).map((source, _)) case _ => - List.empty + None } } catch { // this happens in broken jars since file from FileWalker should exists @@ -105,36 +108,41 @@ class SymbolIndexBucket( source: AbsolutePath, sourceDirectory: Option[AbsolutePath], fromSourceJar: Option[AbsolutePath] = None - ): List[String] = { + ): Option[EnrichedTextDocument] = { val uri = source.toIdeallyRelativeURI(sourceDirectory) - val symbols = indexSource(source, uri, dialect) - - val patched = - fromSourceJar match { - case Some(jar) if stdLibPatches.isScala3Library(jar) => - symbols.map(stdLibPatches.patchSymbol) - case _ => symbols + for { + doc <- indexSource(source, uri, dialect) + } yield { + val enrichedDocument = doc.withFilter { symbols => + fromSourceJar match { + case Some(jar) if stdLibPatches.isScala3Library(jar) => + symbols.map(stdLibPatches.patchSymbol) + case _ => symbols + } } - - patched.foreach { symbol => - val acc = toplevels.getOrElse(symbol, Set.empty) - toplevels(symbol) = acc + source + enrichedDocument.topLevels.foreach { symbol => + val acc = toplevels.getOrElse(symbol, Set.empty) + toplevels(symbol) = acc + source + } + enrichedDocument } - symbols } private def indexSource( source: AbsolutePath, uri: String, dialect: Dialect - ): List[String] = { + ): Option[EnrichedTextDocument] = { val text = FileIO.slurp(source, StandardCharsets.UTF_8) val input = Input.VirtualFile(uri, text) - val sourceToplevels = mtags.toplevels(input, dialect) - if (source.isAmmoniteScript) - sourceToplevels - else - sourceToplevels.filter(sym => !isTrivialToplevelSymbol(uri, sym)) + mtags + .enrichedTextDocument(input, dialect) + .map(_.withFilter { sourceToplevels => + if (source.isAmmoniteScript) + sourceToplevels + else + sourceToplevels.filter(sym => !isTrivialToplevelSymbol(uri, sym)) + }) } // Returns true if symbol is com/foo/Bar# and path is /com/foo/Bar.scala diff --git a/tests/mtest/src/main/scala/tests/DelegatingGlobalSymbolIndex.scala b/tests/mtest/src/main/scala/tests/DelegatingGlobalSymbolIndex.scala index 1f29a1f1c57..520f98fe89d 100644 --- a/tests/mtest/src/main/scala/tests/DelegatingGlobalSymbolIndex.scala +++ b/tests/mtest/src/main/scala/tests/DelegatingGlobalSymbolIndex.scala @@ -26,19 +26,19 @@ class DelegatingGlobalSymbolIndex( file: AbsolutePath, sourceDirectory: Option[AbsolutePath], dialect: Dialect, - ): List[String] = { + ): Option[mtags.EnrichedTextDocument] = { underlying.addSourceFile(file, sourceDirectory, dialect) } def addSourceJar( jar: AbsolutePath, dialect: Dialect, - ): List[(String, AbsolutePath)] = { + ): List[(AbsolutePath, mtags.EnrichedTextDocument)] = { underlying.addSourceJar(jar, dialect) } def addSourceDirectory( dir: AbsolutePath, dialect: Dialect, - ): List[(String, AbsolutePath)] = { + ): List[(AbsolutePath, mtags.EnrichedTextDocument)] = { underlying.addSourceDirectory(dir, dialect) } } diff --git a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala index c8ede54b6cf..910798aaf2d 100644 --- a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala +++ b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala @@ -3,9 +3,10 @@ package tests import scala.meta.Dialect import scala.meta.dialects import scala.meta.inputs.Input +import scala.meta.internal.metals.EmptyReportContext import scala.meta.internal.mtags.Mtags +import scala.meta.internal.mtags.OverriddenSymbolsEnrichment import scala.meta.internal.mtags.ResolvedOverriddenSymbol -import scala.meta.internal.mtags.TextDocumentWithOverridden import scala.meta.internal.mtags.UnresolvedOverriddenSymbol import munit.TestOptions @@ -629,12 +630,14 @@ class ScalaToplevelSuite extends BaseSuite { case All | ToplevelWithInner => val includeMembers = mode == All val enrichedDoc = - Mtags.allToplevelsEnriched(input, dialect, includeMembers) + new Mtags()(EmptyReportContext) + .enrichedTextDocument(input, dialect, includeMembers) + .get val symbols = enrichedDoc.textDocument.occurrences.map(_.symbol).toList - enrichedDoc match { - case doc: TextDocumentWithOverridden => - val overriddenMap = doc.overridden.toMap + enrichedDoc.enrichment match { + case OverriddenSymbolsEnrichment(overridden) => + val overriddenMap = overridden.toMap symbols.map { symbol => overriddenMap.get(symbol) match { case None => symbol From 1d4dadabe7f355834c9688f018afdc981b578dc2 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Tue, 5 Sep 2023 13:59:10 +0200 Subject: [PATCH 04/26] feature: use overridden index information for finding implementations in dependencies --- .../implementation/ClassLocation.scala | 3 + .../implementation/GlobalClassTable.scala | 8 +- .../ImplementationProvider.scala | 129 +++++++++++------- .../implementation/InheritanceContext.scala | 69 ++++++++-- .../internal/metals/DefinitionProvider.scala | 19 +++ .../src/main/scala/tests/TestingServer.scala | 13 +- .../scala/tests/ImplementationLspSuite.scala | 27 ++++ 7 files changed, 208 insertions(+), 60 deletions(-) diff --git a/metals/src/main/scala/scala/meta/internal/implementation/ClassLocation.scala b/metals/src/main/scala/scala/meta/internal/implementation/ClassLocation.scala index 6d6dd1e6d9b..c1d9efbb27f 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/ClassLocation.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/ClassLocation.scala @@ -5,4 +5,7 @@ import java.nio.file.Path case class ClassLocation( symbol: String, file: Option[Path], + // position of the reference to the overridden symbol, e.g. + // class AClass extends @@BClass + pos: Option[Int] = None, ) diff --git a/metals/src/main/scala/scala/meta/internal/implementation/GlobalClassTable.scala b/metals/src/main/scala/scala/meta/internal/implementation/GlobalClassTable.scala index b50c2c074d0..33fd9f42eac 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/GlobalClassTable.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/GlobalClassTable.scala @@ -5,6 +5,7 @@ import scala.collection.concurrent.TrieMap import scala.collection.mutable import scala.meta.internal.metals.BuildTargets +import scala.meta.internal.metals.DefinitionProvider import scala.meta.internal.semanticdb.SymbolInformation import scala.meta.internal.symtab.GlobalSymbolTable import scala.meta.io.AbsolutePath @@ -26,11 +27,16 @@ final class GlobalClassTable( def globalContextFor( source: AbsolutePath, implementationsInPath: ImplementationCache, + definitionProvider: DefinitionProvider, + implementationsInDependencySources: Map[String, Set[ClassLocation]], ): Option[InheritanceContext] = { for { symtab <- globalSymbolTableFor(source) } yield { - calculateIndex(symtab, implementationsInPath) + calculateIndex(symtab, implementationsInPath).toGlobal( + definitionProvider, + implementationsInDependencySources, + ) } } diff --git a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala index 342781350ef..4b8df269356 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala @@ -1,5 +1,6 @@ package scala.meta.internal.implementation +import java.nio.charset.StandardCharsets import java.nio.file.Path import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentLinkedQueue @@ -9,12 +10,15 @@ import scala.concurrent.ExecutionContext import scala.concurrent.Future import scala.util.control.NonFatal +import scala.meta.inputs.Input +import scala.meta.internal.io.FileIO import scala.meta.internal.metals.Buffers import scala.meta.internal.metals.BuildTargets import scala.meta.internal.metals.DefinitionProvider import scala.meta.internal.metals.MetalsEnrichments._ import scala.meta.internal.metals.ReportContext import scala.meta.internal.metals.ScalaVersionSelector +import scala.meta.internal.metals.ScalaVersions import scala.meta.internal.metals.SemanticdbFeatureProvider import scala.meta.internal.mtags.GlobalSymbolIndex import scala.meta.internal.mtags.Mtags @@ -58,7 +62,7 @@ final class ImplementationProvider( private val implementationsInPath = new ConcurrentHashMap[Path, Map[String, Set[ClassLocation]]] private val implementationsInDependencySources = - new ConcurrentHashMap[String, Set[Implementation]] + new ConcurrentHashMap[String, Set[ClassLocation]] override def reset(): Unit = { implementationsInPath.clear() @@ -91,19 +95,19 @@ final class ImplementationProvider( newOverridden: List[(String, List[OverriddenSymbol])], ): Unit = { def createUpdate( - newSymbol: Implementation - ): (String, Set[Implementation]) => Set[Implementation] = { + newSymbol: ClassLocation + ): (String, Set[ClassLocation]) => Set[ClassLocation] = { case (_, null) => Set(newSymbol) case (_, previous) => previous + newSymbol } newOverridden.foreach { case (clazz, list) => list.foreach { case ResolvedOverriddenSymbol(symbol) => - val update = createUpdate(ImplementationForResolved(clazz)) + val update = createUpdate(ClassLocation(clazz, Some(path.toNIO))) implementationsInDependencySources.compute(symbol, update(_, _)) case UnresolvedOverriddenSymbol(name, pos) => val update = - createUpdate(ImplementationForUnresolved(clazz, path, pos)) + createUpdate(ClassLocation(clazz, Some(path.toNIO), Some(pos))) implementationsInDependencySources.compute(name, update(_, _)) } } @@ -179,6 +183,8 @@ final class ImplementationProvider( globalTable.globalContextFor( source, implementationsInPath.asScala.toMap, + definitionProvider, + implementationsInDependencySources.asScala.toMap, ) // symbol is in workspace, // we might need to search different places for related symbols @@ -333,12 +339,7 @@ final class ImplementationProvider( file <- files locations = locationsByFile(file) implPath = AbsolutePath(file) - implDocument <- findSemanticdb(implPath).toIterable - distance = buffer.tokenEditDistance( - implPath, - implDocument.text, - trees, - ) + implDocument <- findSemanticdb(implPath, handleJars = true) implLocation <- locations implSymbol <- findImplementationSymbol( parentSymbol, @@ -354,7 +355,17 @@ final class ImplementationProvider( source, ) range <- implOccurrence.range - revised <- distance.toRevised(range.toLsp) + revised <- + if (implPath.isJarFileSystem) { + Some(range.toLsp) + } else { + val distance = buffer.tokenEditDistance( + implPath, + implDocument.text, + trees, + ) + distance.toRevised(range.toLsp) + } } { allLocations.add(new Location(file.toUri.toString, revised)) } } @@ -363,55 +374,86 @@ final class ImplementationProvider( classContext <- inheritanceContext.toIterable parentSymbol <- classContext.findSymbol(symbol).toIterable symbolClass <- classFromSymbol(parentSymbol, classContext.findSymbol) - locationsByFile = findImplementation( - symbolClass.symbol, - classContext, - source.toNIO, - ) - files <- locationsByFile.keySet.grouped( - Math.max(locationsByFile.size / cores, 1) - ) - } yield findImplementationLocations(files, locationsByFile, parentSymbol) + } yield { + for { + locationsByFile <- findImplementation( + symbolClass.symbol, + classContext, + source.toNIO, + ) + files = locationsByFile.keySet.grouped( + Math.max(locationsByFile.size / cores, 1) + ) + collected <- + Future.sequence( + files.map( + findImplementationLocations(_, locationsByFile, parentSymbol) + ) + ) + } yield collected + } Future.sequence(splitJobs).map { _ => allLocations.asScala.toSeq } } - private def findSemanticdb(fileSource: AbsolutePath): Option[TextDocument] = { + private def findSemanticdb( + fileSource: AbsolutePath, + handleJars: Boolean = false, + ): Option[TextDocument] = { if (fileSource.isJarFileSystem) - None + if (!handleJars) None + else Some(semanticdbForJarFile(fileSource)) else semanticdbs .textDocument(fileSource) .documentIncludingStale } + private def semanticdbForJarFile(fileSource: AbsolutePath) = { + val dialect = ScalaVersions.dialectForDependencyJar(fileSource.filename) + val text = FileIO.slurp(fileSource, StandardCharsets.UTF_8) + val input = Input.VirtualFile(fileSource.toURI.toString(), text) + val textDocument = Mtags.index(input, dialect) + textDocument + } + private def findImplementation( symbol: String, classContext: InheritanceContext, file: Path, - ): Map[Path, Set[ClassLocation]] = { + ): Future[Map[Path, Set[ClassLocation]]] = { - def loop(symbol: String, currentPath: Option[Path]): Set[ClassLocation] = { + def loop( + symbol: String, + currentPath: Option[Path], + ): Future[Set[ClassLocation]] = { val directImplementations = - classContext.getLocations(symbol).filterNot { loc => - // we are not interested in local symbols from outside the workspace - (loc.symbol.isLocal && loc.file.isEmpty) || - // for local symbols, inheritance should only be picked up in the same file - // otherwise there can be a name collision between files - // local1' is from file A, local2 extends local1'' - // but both local2 and local1'' are from file B - // clearly, local2 shouldn't be considered for local1' - (symbol.isLocal && loc.symbol.isLocal && loc.file != currentPath) - } - directImplementations ++ directImplementations - .flatMap { loc => loop(loc.symbol, loc.file) } + classContext + .getLocations(symbol) + .map(_.filterNot { loc => + // we are not interested in local symbols from outside the workspace + (loc.symbol.isLocal && loc.file.isEmpty) || + // for local symbols, inheritance should only be picked up in the same file + // otherwise there can be a name collision between files + // local1' is from file A, local2 extends local1'' + // but both local2 and local1'' are from file B + // clearly, local2 shouldn't be considered for local1' + (symbol.isLocal && loc.symbol.isLocal && loc.file != currentPath) + }) + directImplementations.flatMap { directImplementations => + Future + .sequence(directImplementations.map { loc => + loop(loc.symbol, loc.file) + }) + .map(rec => directImplementations ++ rec.flatten) + } } - loop(symbol, Some(file)).groupBy(_.file).collect { + loop(symbol, Some(file)).map(_.groupBy(_.file).collect { case (Some(path), locs) => path -> locs - } + }) } private def findSymbolInformation( @@ -569,12 +611,3 @@ object ImplementationProvider { info.isObject || info.isClass || info.isTrait || info.isType || info.isInterface } - -sealed trait Implementation -case class ImplementationForResolved(implementationSymbol: String) - extends Implementation -case class ImplementationForUnresolved( - implementationSymbol: String, - usagePath: AbsolutePath, - usagePosition: Int, -) extends Implementation diff --git a/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala b/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala index 0b112fe7a62..4cb4ac8ba0e 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala @@ -3,19 +3,28 @@ package scala.meta.internal.implementation import java.nio.file.Path import scala.collection.mutable +import scala.concurrent.ExecutionContext +import scala.concurrent.Future +import scala.meta.internal.metals.DefinitionProvider +import scala.meta.internal.semanticdb.Scala._ import scala.meta.internal.semanticdb.SymbolInformation +import scala.meta.io.AbsolutePath -case class InheritanceContext( - findSymbol: String => Option[SymbolInformation], - private val inheritance: Map[String, Set[ClassLocation]], +class InheritanceContext( + val findSymbol: String => Option[SymbolInformation], + inheritance: Map[String, Set[ClassLocation]], ) { def allClassSymbols = inheritance.keySet - def getLocations(symbol: String): Set[ClassLocation] = { + def getLocations(symbol: String)(implicit + ec: ExecutionContext + ): Future[Set[ClassLocation]] = + Future.successful(getWorkspaceLocations(symbol)) + + protected def getWorkspaceLocations(symbol: String): Set[ClassLocation] = inheritance.getOrElse(symbol, Set.empty) - } def withClasspathContext( classpathInheritance: Map[String, Set[ClassLocation]] @@ -26,10 +35,54 @@ case class InheritanceContext( newInheritance.getOrElse(symbol, Set.empty) ++ locations newInheritance += symbol -> newLocations } - this.copy( - inheritance = newInheritance.toMap + new InheritanceContext( + findSymbol, + newInheritance.toMap, ) } + + def toGlobal( + definitionProvider: DefinitionProvider, + implementationsInDependencySources: Map[String, Set[ClassLocation]], + ) = new GlobalInheritanceContext( + findSymbol, + definitionProvider, + implementationsInDependencySources, + inheritance, + ) +} + +class GlobalInheritanceContext( + override val findSymbol: String => Option[SymbolInformation], + definitionProvider: DefinitionProvider, + implementationsInDependencySources: Map[String, Set[ClassLocation]], + localInheritance: Map[String, Set[ClassLocation]], +) extends InheritanceContext(findSymbol, localInheritance) { + override def getLocations( + symbol: String + )(implicit ec: ExecutionContext): Future[Set[ClassLocation]] = { + val workspaceImplementations = getWorkspaceLocations(symbol) + val enumCasesImplementations = + implementationsInDependencySources.getOrElse(symbol, Set.empty) + val shortName = symbol.desc.name.value + val resolveGlobal = + implementationsInDependencySources + .getOrElse(shortName, Set.empty) + .collect { case loc @ ClassLocation(_, Some(file), Some(pos)) => + definitionProvider.definition(AbsolutePath(file), pos).map { + definition => + def dealiased = ImplementationProvider + .dealiasClass(definition.symbol, findSymbol) + if (definition.symbol == symbol || dealiased == symbol) Some(loc) + else None + } + } + Future + .sequence(resolveGlobal) + .map { globalImplementations => + workspaceImplementations ++ globalImplementations.flatten ++ enumCasesImplementations + } + } } object InheritanceContext { @@ -47,6 +100,6 @@ object InheritanceContext { val updated = inheritance.getOrElse(symbol, Set.empty) ++ locations inheritance += symbol -> updated } - InheritanceContext(findSymbol, inheritance.toMap) + new InheritanceContext(findSymbol, inheritance.toMap) } } diff --git a/metals/src/main/scala/scala/meta/internal/metals/DefinitionProvider.scala b/metals/src/main/scala/scala/meta/internal/metals/DefinitionProvider.scala index 3ea03c2ebee..8e897ebb384 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/DefinitionProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/DefinitionProvider.scala @@ -8,6 +8,7 @@ import scala.concurrent.Future import scala.meta.Term import scala.meta.Type import scala.meta.inputs.Input +import scala.meta.inputs.Position.Range import scala.meta.internal.metals.MetalsEnrichments._ import scala.meta.internal.mtags.GlobalSymbolIndex import scala.meta.internal.mtags.Mtags @@ -34,6 +35,7 @@ import org.eclipse.lsp4j.Location import org.eclipse.lsp4j.Position import org.eclipse.lsp4j.SymbolInformation import org.eclipse.lsp4j.SymbolKind +import org.eclipse.lsp4j.TextDocumentIdentifier import org.eclipse.lsp4j.TextDocumentPositionParams /** @@ -129,6 +131,23 @@ final class DefinitionProvider( } } + def definition( + path: AbsolutePath, + pos: Int, + ): Future[DefinitionResult] = { + val text = path.readText + val input = new Input.VirtualFile(path.toURI.toString(), text) + val range = Range(input, pos, pos) + definition( + path, + new TextDocumentPositionParams( + new TextDocumentIdentifier(path.toURI.toString()), + range.toLsp.getStart(), + ), + EmptyCancelToken, + ) + } + /** * Tries to find an identifier token at the current position * to use it for symbol search. This is the last possibility for diff --git a/tests/unit/src/main/scala/tests/TestingServer.scala b/tests/unit/src/main/scala/tests/TestingServer.scala index f9d60750e9b..df08b8a6e60 100644 --- a/tests/unit/src/main/scala/tests/TestingServer.scala +++ b/tests/unit/src/main/scala/tests/TestingServer.scala @@ -1532,12 +1532,19 @@ final case class TestingServer( base: Map[String, String], ): Future[Map[String, String]] = { Debug.printEnclosing() + implementation(filename, query).map( + TestRanges.renderLocationsAsString(base, _) + ) + } + + def implementation( + filename: String, + query: String, + ): Future[List[Location]] = { for { (_, params) <- offsetParams(filename, query, workspace) implementations <- fullServer.implementation(params).asScala - } yield { - TestRanges.renderLocationsAsString(base, implementations.asScala.toList) - } + } yield implementations.asScala.toList } def getReferenceLocations( diff --git a/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala b/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala index c55f8c2a820..dd9eb949bab 100644 --- a/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala +++ b/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala @@ -571,6 +571,33 @@ class ImplementationLspSuite extends BaseRangesSuite("implementation") { |""".stripMargin, ) + test("global-implementations") { + cleanWorkspace() + val fileName = "a/src/main/scala/a/Main.scala" + val fileContent = + """|package a + |class MyException extends Excep@@tion + |""".stripMargin + for { + _ <- initialize( + s"""/metals.json + |{"a": + | { + | "scalaVersion" : "${BuildInfo.scalaVersion}", + | "libraryDependencies": ${toJsonArray(libraryDependencies)} + | } + |} + |/$fileName + |${fileContent.replace("@@", "")} + """.stripMargin + ) + _ <- server.didOpen("a/src/main/scala/a/Main.scala") + locations <- server.implementation(fileName, fileContent) + _ = assert(locations.length > 1) + _ <- server.shutdown() + } yield () + } + override protected def libraryDependencies: List[String] = List("org.scalatest::scalatest:3.2.16", "io.circe::circe-generic:0.12.0") From 5fa2a663e4e1519667965ceea00eaecafa5c530d Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Wed, 6 Sep 2023 18:06:10 +0200 Subject: [PATCH 05/26] store type hierarchy information --- .../db/migration/V5__Jar_type_hierarchy.sql | 15 ++ .../ImplementationProvider.scala | 41 +++-- .../scala/meta/internal/metals/Indexer.scala | 51 ++++--- .../meta/internal/metals/JarOverridden.scala | 19 --- .../meta/internal/metals/JarTopLevels.scala | 142 ++++++++++++++++-- .../scala/meta/internal/metals/Tables.scala | 1 - .../test/scala/tests/JarTopLevelsSuite.scala | 60 +++++++- 7 files changed, 255 insertions(+), 74 deletions(-) create mode 100644 metals/src/main/resources/db/migration/V5__Jar_type_hierarchy.sql delete mode 100644 metals/src/main/scala/scala/meta/internal/metals/JarOverridden.scala diff --git a/metals/src/main/resources/db/migration/V5__Jar_type_hierarchy.sql b/metals/src/main/resources/db/migration/V5__Jar_type_hierarchy.sql new file mode 100644 index 00000000000..2ee57eecd35 --- /dev/null +++ b/metals/src/main/resources/db/migration/V5__Jar_type_hierarchy.sql @@ -0,0 +1,15 @@ +-- Type hierarchy information, e.g. symbol: "a/MyException#", extended_name: "Exception" +create table type_hierarchy( + symbol varchar not null, + extended_name varchar not null, + extended_name_offset int, + path varchar not null, + jar int, + foreign key (jar) references indexed_jar (id) on delete cascade, + primary key (jar, path, symbol, extended_name, extended_name_offset) +); + +create index type_hierarchy_jar on type_hierarchy(jar); + +alter table indexed_jar +add type_hierarchy_indexed bit diff --git a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala index 4b8df269356..d4268c0b4fe 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala @@ -80,19 +80,28 @@ final class ImplementationProvider( ) } - def addImplementationInDependenciesInfo( + def addTypeHierarchy( overriddenInfo: List[ (AbsolutePath, List[(String, List[OverriddenSymbol])]) ] ): Unit = { - overriddenInfo.foreach { case (path, overridden) => - addImplementationInDependenciesInfo(path, overridden) + overriddenInfo.foreach { case (path, list) => + list.foreach { case (overridesSymbol, overridden) => + overridden.foreach(addTypeHierarchyElement(path, overridesSymbol, _)) + } } } - private def addImplementationInDependenciesInfo( + def addTypeHierarchyElements( + elements: List[(AbsolutePath, String, OverriddenSymbol)] + ): Unit = elements.foreach { case (path, overridesSymbol, overridden) => + addTypeHierarchyElement(path, overridesSymbol, overridden) + } + + private def addTypeHierarchyElement( path: AbsolutePath, - newOverridden: List[(String, List[OverriddenSymbol])], + overridesSymbol: String, + overridden: OverriddenSymbol, ): Unit = { def createUpdate( newSymbol: ClassLocation @@ -100,16 +109,18 @@ final class ImplementationProvider( case (_, null) => Set(newSymbol) case (_, previous) => previous + newSymbol } - newOverridden.foreach { case (clazz, list) => - list.foreach { - case ResolvedOverriddenSymbol(symbol) => - val update = createUpdate(ClassLocation(clazz, Some(path.toNIO))) - implementationsInDependencySources.compute(symbol, update(_, _)) - case UnresolvedOverriddenSymbol(name, pos) => - val update = - createUpdate(ClassLocation(clazz, Some(path.toNIO), Some(pos))) - implementationsInDependencySources.compute(name, update(_, _)) - } + overridden match { + case ResolvedOverriddenSymbol(symbol) => + val update = createUpdate( + ClassLocation(overridesSymbol, Some(path.toNIO)) + ) + implementationsInDependencySources.compute(symbol, update(_, _)) + case UnresolvedOverriddenSymbol(name, pos) => + val update = + createUpdate( + ClassLocation(overridesSymbol, Some(path.toNIO), Some(pos)) + ) + implementationsInDependencySources.compute(name, update(_, _)) } } diff --git a/metals/src/main/scala/scala/meta/internal/metals/Indexer.scala b/metals/src/main/scala/scala/meta/internal/metals/Indexer.scala index 53702271990..26f213380ec 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Indexer.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Indexer.scala @@ -473,9 +473,7 @@ final case class Indexer( val documents = definitionIndex.addSourceDirectory(path, dialect) val overriddenInfo = collectOverriddenInfo(documents) - implementationProvider.addImplementationInDependenciesInfo( - overriddenInfo - ) + implementationProvider.addTypeHierarchy(overriddenInfo) } else { scribe.warn(s"unexpected dependency: $path") } @@ -614,26 +612,35 @@ final case class Indexer( * @param path JAR path */ private def addSourceJarSymbols(path: AbsolutePath): Unit = { - ( - tables.jarSymbols.getTopLevels(path), - tables.jarOverriddenSymbols.getOverriddenInfo(path), - ) match { - case (Some(toplevels), Some(overridden)) => - val dialect = ScalaVersions.dialectForDependencyJar(path.filename) - definitionIndex.addIndexedSourceJar(path, toplevels, dialect) - implementationProvider.addImplementationInDependenciesInfo(overridden) - case _ => - val dialect = ScalaVersions.dialectForDependencyJar(path.filename) - val documents = definitionIndex.addSourceJar(path, dialect) - val toplevels = documents.flatMap { case (path, document) => - document.topLevels.map((_, path)) + val dialect = ScalaVersions.dialectForDependencyJar(path.filename) + def indexJar() = { + val documents = definitionIndex.addSourceJar(path, dialect) + val toplevels = documents.flatMap { case (path, document) => + document.topLevels.map((_, path)) + } + val overrides = collectOverriddenInfo(documents).flatMap { + case (path, list) => + list.flatMap { case (symbol, overridden) => + overridden.map((path, symbol, _)) + } + } + implementationProvider.addTypeHierarchyElements(overrides) + (toplevels, overrides) + } + + tables.jarSymbols.getTopLevels(path) match { + case Some(toplevels) => + tables.jarSymbols.getTypeHierarchy(path) match { + case Some(overrides) => + definitionIndex.addIndexedSourceJar(path, toplevels, dialect) + implementationProvider.addTypeHierarchyElements(overrides) + case None => + val (_, overrides) = indexJar() + tables.jarSymbols.addTypeHierarchyInfo(path, overrides) } - val overriddenInfo = collectOverriddenInfo(documents) - implementationProvider.addImplementationInDependenciesInfo( - overriddenInfo - ) - tables.jarSymbols.putTopLevels(path, toplevels) - tables.jarOverriddenSymbols.putOverriddenInfo(path, overriddenInfo) + case None => + val (toplevels, overrides) = indexJar() + tables.jarSymbols.putJarIndexingInfo(path, toplevels, overrides) } } diff --git a/metals/src/main/scala/scala/meta/internal/metals/JarOverridden.scala b/metals/src/main/scala/scala/meta/internal/metals/JarOverridden.scala deleted file mode 100644 index 6e2c63a846c..00000000000 --- a/metals/src/main/scala/scala/meta/internal/metals/JarOverridden.scala +++ /dev/null @@ -1,19 +0,0 @@ -package scala.meta.internal.metals - -import java.sql.Connection - -import scala.meta.internal.mtags.OverriddenSymbol -import scala.meta.io.AbsolutePath - -//TODO:: implement -final class JarOverridden(conn: () => Connection) { - - def getOverriddenInfo( - path: AbsolutePath - ): Option[List[(AbsolutePath, List[(String, List[OverriddenSymbol])])]] = None - - def putOverriddenInfo( - path: AbsolutePath, - overridden: List[(AbsolutePath, List[(String, List[OverriddenSymbol])])], - ): Int = 0 -} diff --git a/metals/src/main/scala/scala/meta/internal/metals/JarTopLevels.scala b/metals/src/main/scala/scala/meta/internal/metals/JarTopLevels.scala index 6fae84713fd..1502ceb6a82 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/JarTopLevels.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/JarTopLevels.scala @@ -12,6 +12,9 @@ import scala.meta.internal.io.PlatformFileIO import scala.meta.internal.metals.JdbcEnrichments._ import scala.meta.internal.metals.MetalsEnrichments._ import scala.meta.internal.mtags.MD5 +import scala.meta.internal.mtags.OverriddenSymbol +import scala.meta.internal.mtags.ResolvedOverriddenSymbol +import scala.meta.internal.mtags.UnresolvedOverriddenSymbol import scala.meta.io.AbsolutePath /** @@ -61,6 +64,48 @@ final class JarTopLevels(conn: () => Connection) { None } + def getTypeHierarchy( + path: AbsolutePath + ): Option[List[(AbsolutePath, String, OverriddenSymbol)]] = + try { + val fs = path.jarPath + .map(jarPath => + PlatformFileIO.newFileSystem( + jarPath.toURI, + new java.util.HashMap[String, String](), + ) + ) + .getOrElse(PlatformFileIO.newJarFileSystem(path, create = false)) + val toplevels = List.newBuilder[(AbsolutePath, String, OverriddenSymbol)] + conn() + .query( + """select th.symbol, th.extended_name, th.extended_name_offset, th.path + |from indexed_jar ij + |left join type_hierarchy th + |on ij.id=th.jar + |where ij.type_hierarchy_indexed=true and ij.md5=?""".stripMargin + ) { _.setString(1, getMD5Digest(path)) } { rs => + if ( + rs.getString(1) != null && rs + .getString(2) != null && rs.getString(4) != null + ) { + val symbol = rs.getString(1) + val extendedName = rs.getString(2) + val extendedOffset = rs.getInt(3) + val path = AbsolutePath(fs.getPath(rs.getString(4))) + val overridden = + if (extendedOffset < 0) ResolvedOverriddenSymbol(extendedName) + else UnresolvedOverriddenSymbol(extendedName, extendedOffset) + toplevels += ((path, symbol, overridden)) + } + } + .headOption + .map(_ => toplevels.result) + } catch { + case _: ZipError | _: ZipException => + None + } + /** * Stores the top level symbols for the Jar * @@ -68,21 +113,23 @@ final class JarTopLevels(conn: () => Connection) { * @param toplevels toplevel symbols in the jar * @return the number of toplevel symbols inserted */ - def putTopLevels( + def putJarIndexingInfo( path: AbsolutePath, toplevels: List[(String, AbsolutePath)], + type_hierarchy: List[(AbsolutePath, String, OverriddenSymbol)], ): Int = { - if (toplevels.isEmpty) 0 + if (toplevels.isEmpty && type_hierarchy.isEmpty) 0 else { // Add jar to H2 var jarStmt: PreparedStatement = null val jar = try { jarStmt = conn().prepareStatement( - s"insert into indexed_jar (md5) values (?)", + s"insert into indexed_jar (md5, type_hierarchy_indexed) values (?, ?)", Statement.RETURN_GENERATED_KEYS, ) jarStmt.setString(1, getMD5Digest(path)) + jarStmt.setBoolean(2, true) jarStmt.executeUpdate() val rs = jarStmt.getGeneratedKeys rs.next() @@ -90,7 +137,42 @@ final class JarTopLevels(conn: () => Connection) { } finally { if (jarStmt != null) jarStmt.close() } + putToplevels(jar, toplevels) + putTypeHierarchyInfo(jar, type_hierarchy) + } + } + + def addTypeHierarchyInfo( + path: AbsolutePath, + type_hierarchy: List[(AbsolutePath, String, OverriddenSymbol)], + ): Int = { + var jarStmt: PreparedStatement = null + val jar = + try { + val digest = getMD5Digest(path) + jarStmt = conn().prepareStatement( + s"update indexed_jar set type_hierarchy_indexed = true where (md5) = (?)" + ) + jarStmt.setString(1, digest) + jarStmt.executeUpdate() + conn() + .query( + """select id + |from indexed_jar + |where md5=?""".stripMargin + ) { _.setString(1, digest) } { _.getInt(1) } + .head + } finally { + if (jarStmt != null) jarStmt.close() + } + putTypeHierarchyInfo(jar, type_hierarchy) + } + + def putToplevels( + jar: Int, + toplevels: List[(String, AbsolutePath)], + ): Int = + if (toplevels.nonEmpty) { // Add symbols for jar to H2 var symbolStmt: PreparedStatement = null try { @@ -108,8 +190,39 @@ final class JarTopLevels(conn: () => Connection) { } finally { if (symbolStmt != null) symbolStmt.close() } - } - } + } else 0 + + private def putTypeHierarchyInfo( + jar: Int, + type_hierarchy: List[(AbsolutePath, String, OverriddenSymbol)], + ): Int = + if (type_hierarchy.nonEmpty) { + // Add symbols for jar to H2 + var symbolStmt: PreparedStatement = null + try { + symbolStmt = conn().prepareStatement( + s"insert into type_hierarchy (symbol, extended_name, extended_name_offset, path, jar) values (?, ?, ?, ?, ?)" + ) + type_hierarchy.foreach { case (path, symbol, overridden) => + symbolStmt.setString(1, symbol) + overridden match { + case ResolvedOverriddenSymbol(name) => + symbolStmt.setString(2, name) + symbolStmt.setInt(3, -1) + case UnresolvedOverriddenSymbol(name, pos) => + symbolStmt.setString(2, name) + symbolStmt.setInt(3, pos) + } + symbolStmt.setString(4, path.toString()) + symbolStmt.setInt(5, jar) + symbolStmt.addBatch() + } + // Return number of rows inserted + symbolStmt.executeBatch().sum + } finally { + if (symbolStmt != null) symbolStmt.close() + } + } else 0 /** * Delete the jars that are not used and their top level symbols @@ -124,7 +237,17 @@ final class JarTopLevels(conn: () => Connection) { } { _ => () } } - private def getMD5Digest(path: AbsolutePath) = { + def clearAll(): Unit = { + val statement1 = conn().prepareStatement("truncate table toplevel_symbol") + statement1.execute() + val statement2 = + conn().prepareStatement("truncate table type_hierarchy_jar") + statement2.execute() + val statement3 = conn().prepareStatement("delete from indexed_jar") + statement3.execute() + } + + def getMD5Digest(path: AbsolutePath): String = { val attributes = Files .getFileAttributeView(path.toNIO, classOf[BasicFileAttributeView]) .readAttributes() @@ -134,11 +257,4 @@ final class JarTopLevels(conn: () => Connection) { .toMillis + ":" + attributes.size() ) } - - def clearAll(): Unit = { - val statement1 = conn().prepareStatement("truncate table toplevel_symbol") - statement1.execute() - val statement2 = conn().prepareStatement("delete from indexed_jar") - statement2.execute() - } } diff --git a/metals/src/main/scala/scala/meta/internal/metals/Tables.scala b/metals/src/main/scala/scala/meta/internal/metals/Tables.scala index 14c0804c957..61e6accb1da 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Tables.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Tables.scala @@ -23,7 +23,6 @@ final class Tables( import Tables.ConnectionState val jarSymbols = new JarTopLevels(() => connection) - val jarOverriddenSymbols = new JarOverridden(() => connection) val digests = new Digests(() => connection, time) val dependencySources = diff --git a/tests/unit/src/test/scala/tests/JarTopLevelsSuite.scala b/tests/unit/src/test/scala/tests/JarTopLevelsSuite.scala index d67afa3d4ed..447a051f69b 100644 --- a/tests/unit/src/test/scala/tests/JarTopLevelsSuite.scala +++ b/tests/unit/src/test/scala/tests/JarTopLevelsSuite.scala @@ -2,10 +2,13 @@ package tests import java.nio.file.Files import java.nio.file.Path +import java.sql.PreparedStatement +import java.sql.Statement import scala.meta.internal.io.FileIO import scala.meta.internal.io.PlatformFileIO import scala.meta.internal.metals.JarTopLevels +import scala.meta.internal.mtags.UnresolvedOverriddenSymbol import scala.meta.io.AbsolutePath class JarTopLevelsSuite extends BaseTablesSuite { @@ -19,7 +22,7 @@ class JarTopLevelsSuite extends BaseTablesSuite { FileIO.withJarFileSystem(zip, create = true, close = true) { root => FileLayout.fromString( """|/foo.scala - |object Hello { + |case class Hello(i: Int) extends AnyVal { |}""".stripMargin, root, ) @@ -31,13 +34,20 @@ class JarTopLevelsSuite extends BaseTablesSuite { val fs = PlatformFileIO.newJarFileSystem(jar1, create = false) val filePath = AbsolutePath(fs.getPath("/foo.scala")) val toplevels = List("foo" -> filePath) - jarSymbols.putTopLevels(jar1, toplevels) + val overrides = + List((filePath, "foo/Hello#", UnresolvedOverriddenSymbol("AnyVal", 32))) + jarSymbols.putJarIndexingInfo(jar1, toplevels, overrides) val resultOption = jarSymbols.getTopLevels(jar1) assert(resultOption.isDefined) val result = resultOption.get assert(toplevels == result) + val resultOption1 = jarSymbols.getTypeHierarchy(jar1) + assert(resultOption1.isDefined) + val result1 = resultOption1.get + assert(overrides == result1) val noOption = jarSymbols.getTopLevels(jar2) assert(noOption.isEmpty) + assert(jarSymbols.getTypeHierarchy(jar2).isEmpty) } test("deleteNotUsed") { @@ -45,16 +55,58 @@ class JarTopLevelsSuite extends BaseTablesSuite { val fs = PlatformFileIO.newJarFileSystem(jar, create = false) val filePath = AbsolutePath(fs.getPath("/foo.scala")) val toplevels = List("foo" -> filePath) - jarSymbols.putTopLevels(jar, toplevels) + val overrides = + List((filePath, "foo/Hello#", UnresolvedOverriddenSymbol("AnyVal", 32))) + jarSymbols.putJarIndexingInfo(jar, toplevels, overrides) } jarSymbols.deleteNotUsedTopLevels(Array(jar1, jar1)) assert(jarSymbols.getTopLevels(jar1).isDefined) + assert(jarSymbols.getTypeHierarchy(jar1).isDefined) assert(jarSymbols.getTopLevels(jar2).isEmpty) + assert(jarSymbols.getTypeHierarchy(jar2).isEmpty) } test("noSymbols") { - jarSymbols.putTopLevels(jar1, List.empty) + jarSymbols.putJarIndexingInfo(jar1, List.empty, List.empty) val result = jarSymbols.getTopLevels(jar1) assert(result.isEmpty) } + + test("addTypeHierarchy") { + val fs = PlatformFileIO.newJarFileSystem(jar1, create = false) + val filePath = AbsolutePath(fs.getPath("/foo.scala")) + val toplevels = List("foo" -> filePath) + val overrides = + List((filePath, "foo/Hello#", UnresolvedOverriddenSymbol("AnyVal", 32))) + + var jarStmt: PreparedStatement = null + val jar = + try { + jarStmt = tables + .connect() + .prepareStatement( + s"insert into indexed_jar (md5) values (?)", + Statement.RETURN_GENERATED_KEYS, + ) + jarStmt.setString(1, tables.jarSymbols.getMD5Digest(jar1)) + jarStmt.executeUpdate() + val rs = jarStmt.getGeneratedKeys + rs.next() + rs.getInt("id") + } finally { + if (jarStmt != null) jarStmt.close() + } + tables.jarSymbols.putToplevels(jar, toplevels) + + assert(jarSymbols.getTopLevels(jar1).nonEmpty) + assert(jarSymbols.getTypeHierarchy(jar1).isEmpty) + + jarSymbols.addTypeHierarchyInfo(jar1, overrides) + val obtainedTopLevels = jarSymbols.getTopLevels(jar1) + assert(obtainedTopLevels.nonEmpty) + assert(obtainedTopLevels.get == toplevels) + val obtainedTypeHierarchy = jarSymbols.getTypeHierarchy(jar1) + assert(obtainedTypeHierarchy.nonEmpty) + assert(obtainedTypeHierarchy.get == overrides) + } } From 57694a82c5ba6c912f75c64ab7265a21aaedc0d4 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Thu, 7 Sep 2023 14:54:57 +0200 Subject: [PATCH 06/26] bugfix: cover `class A extends b.B` when indexing type hierarchy --- .../internal/mtags/ScalaToplevelMtags.scala | 39 ++++++++++++------- .../test/scala/tests/ScalaToplevelSuite.scala | 9 +++++ 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala index d2ed3152fe5..2d32cac53fc 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala @@ -532,24 +532,35 @@ class ScalaToplevelMtags( private def findOverridden( acc0: List[Identifier] ): (List[Identifier], Option[Int]) = { - val maybeNewIdent = acceptTrivia() - val (shouldGoOn, acc) = scanner.curr.token match { + val maybeNewIdent0 = acceptTrivia() + scanner.curr.token match { case IDENTIFIER => - (true, newIdentifier.toList ++ acc0) + @tailrec + def getIdentifier(): (Option[Identifier], Option[Int]) = { + val currentIdentifier = newIdentifier + val maybeNewIdent = acceptAllAfterOverriddenIdentifier() + scanner.curr.token match { + case DOT => + scanner.nextToken() + getIdentifier() + case _ => (currentIdentifier, maybeNewIdent) + } + } + val (identifier, maybeNewIdent) = getIdentifier() + val acc = identifier.toList ++ acc0 + scanner.curr.token match { + case WITH => findOverridden(acc) + case _ => (acc, maybeNewIdent) + } case LBRACE => acceptBalancedDelimeters(LBRACE, RBRACE) - (true, acc0) - case _ => (false, acc0) + val maybeNewIdent = acceptTrivia() + scanner.curr.token match { + case WITH => findOverridden(acc0) + case _ => (acc0, maybeNewIdent) + } + case _ => (acc0, maybeNewIdent0) } - - if (shouldGoOn) { - val maybeNewIdent = acceptAllAfterOverriddenIdentifier() - scanner.curr.token match { - case WITH => findOverridden(acc) - case _ => (acc, maybeNewIdent) - } - } else (acc, maybeNewIdent) - } /** diff --git a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala index 910798aaf2d..2e388acaa34 100644 --- a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala +++ b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala @@ -616,6 +616,15 @@ class ScalaToplevelSuite extends BaseSuite { mode = All, ) + check( + "overridden2", + """|package a + |class A extends b.B + |""".stripMargin, + List("a/", "a/A# -> B"), + mode = All, + ) + def check( options: TestOptions, code: String, From 8a6c7b6587a27bc7af38f02a3be3eeee1334864f Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Thu, 7 Sep 2023 16:17:22 +0200 Subject: [PATCH 07/26] tests --- .../internal/metals/MetalsEnrichments.scala | 8 ++ .../scala/tests/ImplementationLspSuite.scala | 118 ++++++++++++++---- 2 files changed, 100 insertions(+), 26 deletions(-) diff --git a/metals/src/main/scala/scala/meta/internal/metals/MetalsEnrichments.scala b/metals/src/main/scala/scala/meta/internal/metals/MetalsEnrichments.scala index 2eed2086b75..2e4aac6ed00 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsEnrichments.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsEnrichments.scala @@ -1186,6 +1186,14 @@ object MetalsEnrichments } } + implicit class XtensionLocation(location: l.Location) { + def toTextDocumentPositionParams = + new l.TextDocumentPositionParams( + new l.TextDocumentIdentifier(location.getUri()), + location.getRange().getStart(), + ) + } + /** * Strips ANSI colors. * As long as the color codes are valid this should correctly strip diff --git a/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala b/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala index dd9eb949bab..d8c7928dbf7 100644 --- a/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala +++ b/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala @@ -1,6 +1,8 @@ package tests import scala.concurrent.Future +import scala.meta.internal.metals.MetalsEnrichments._ + class ImplementationLspSuite extends BaseRangesSuite("implementation") { check( @@ -571,32 +573,61 @@ class ImplementationLspSuite extends BaseRangesSuite("implementation") { |""".stripMargin, ) - test("global-implementations") { - cleanWorkspace() - val fileName = "a/src/main/scala/a/Main.scala" - val fileContent = - """|package a - |class MyException extends Excep@@tion - |""".stripMargin - for { - _ <- initialize( - s"""/metals.json - |{"a": - | { - | "scalaVersion" : "${BuildInfo.scalaVersion}", - | "libraryDependencies": ${toJsonArray(libraryDependencies)} - | } - |} - |/$fileName - |${fileContent.replace("@@", "")} - """.stripMargin - ) - _ <- server.didOpen("a/src/main/scala/a/Main.scala") - locations <- server.implementation(fileName, fileContent) - _ = assert(locations.length > 1) - _ <- server.shutdown() - } yield () - } + checkSymbols( + "set", + """|package a + |class MySet[A] extends S@@et[A] { + | override def iterator: Iterator[A] = ??? + | override def contains(elem: A): Boolean = ??? + | override def incl(elem: A): Set[A] = ??? + | override def excl(elem: A): Set[A] = ??? + |} + |""".stripMargin, + """|a/MySet# + |scala/Enumeration#ValueSet# + |scala/Enumeration#ValueSet# + |scala/collection/immutable/AbstractSet# + |scala/collection/immutable/BitSet# + |scala/collection/immutable/BitSet# + |scala/collection/immutable/BitSet.BitSet1# + |scala/collection/immutable/BitSet.BitSet2# + |scala/collection/immutable/BitSet.BitSetN# + |scala/collection/immutable/HashMap#HashKeySet# + |scala/collection/immutable/HashSet# + |scala/collection/immutable/ListSet# + |scala/collection/immutable/ListSet#Node# + |scala/collection/immutable/ListSet.EmptyListSet. + |scala/collection/immutable/MapOps#ImmutableKeySet# + |scala/collection/immutable/Set.EmptySet. + |scala/collection/immutable/Set.Set1# + |scala/collection/immutable/Set.Set2# + |scala/collection/immutable/Set.Set3# + |scala/collection/immutable/Set.Set4# + |scala/collection/immutable/SortedMapOps#ImmutableKeySortedSet# + |scala/collection/immutable/SortedMapOps#ImmutableKeySortedSet# + |scala/collection/immutable/SortedSet# + |scala/collection/immutable/TreeSet# + |scala/collection/immutable/TreeSet# + |""".stripMargin + ) + + checkSymbols( + "exception", + """package a + |class MyException extends Excep@@tion + |""".stripMargin, + """|a/MyException# + |scala/ScalaReflectionException# + |scala/reflect/internal/FatalError# + |scala/reflect/internal/MissingRequirementError# + |scala/reflect/internal/Positions#ValidateException# + |scala/reflect/macros/Enclosures#EnclosureException# + |scala/reflect/macros/ParseException# + |scala/reflect/macros/ReificationException# + |scala/reflect/macros/TypecheckException# + |scala/reflect/macros/UnexpectedReificationException# + |""".stripMargin + ) override protected def libraryDependencies: List[String] = List("org.scalatest::scalatest:3.2.16", "io.circe::circe-generic:0.12.0") @@ -614,4 +645,39 @@ class ImplementationLspSuite extends BaseRangesSuite("implementation") { base.toMap, ) } + + def checkSymbols( + name: String, + fileContents: String, + expectedSymbols: String + ): Unit = + test(name) { + val fileName = "a/src/main/scala/a/Main.scala" + cleanWorkspace() + for { + _ <- initialize( + s"""/metals.json + |{"a": + | { + | "scalaVersion" : "${BuildInfo.scalaVersion}" + | } + |} + |/$fileName + |${fileContents.replace("@@", "")} + """.stripMargin + ) + _ <- server.didOpen(fileName) + locations <- server.implementation(fileName, fileContents) + definitions <- + Future.sequence( + locations.map( + location => + server.server.definitionResult(location.toTextDocumentPositionParams) + ) + ) + symbols = definitions.map(_.symbol).sorted + _ = assertNoDiff(symbols.mkString("\n"), expectedSymbols) + _ <- server.shutdown() + } yield () + } } From ed822f6ab298b0a0a3c99f9844586a1a3a537dae Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Thu, 7 Sep 2023 17:16:53 +0200 Subject: [PATCH 08/26] add benchmark --- .../src/main/scala/bench/MetalsBench.scala | 8 ++++ .../scala/meta/internal/mtags/Mtags.scala | 10 +++++ .../scala/tests/ImplementationLspSuite.scala | 45 ++++++++++--------- .../test/scala/tests/ScalaToplevelSuite.scala | 5 +-- 4 files changed, 42 insertions(+), 26 deletions(-) diff --git a/metals-bench/src/main/scala/bench/MetalsBench.scala b/metals-bench/src/main/scala/bench/MetalsBench.scala index 031d88a32e8..d5c9c5599df 100644 --- a/metals-bench/src/main/scala/bench/MetalsBench.scala +++ b/metals-bench/src/main/scala/bench/MetalsBench.scala @@ -87,6 +87,14 @@ class MetalsBench { scalaDependencySources.inputs.foreach { input => Mtags.toplevels(input) } } + @Benchmark + @BenchmarkMode(Array(Mode.SingleShotTime)) + def typeHierarchyIndex(): Unit = { + scalaDependencySources.inputs.foreach { input => + Mtags.enrichedTextDocument(input) + } + } + @Benchmark @BenchmarkMode(Array(Mode.SingleShotTime)) def scalaTokenize(): Unit = { diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/Mtags.scala b/mtags/src/main/scala/scala/meta/internal/mtags/Mtags.scala index ef5b7411422..dfb52e0d8aa 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/Mtags.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/Mtags.scala @@ -138,4 +138,14 @@ object Mtags { new Mtags().toplevels(input, dialect) } + def enrichedTextDocument( + input: Input.VirtualFile, + dialect: Dialect = dialects.Scala213, + includeMembers: Boolean = false + )(implicit + rc: ReportContext = EmptyReportContext + ): Option[EnrichedTextDocument] = { + new Mtags().enrichedTextDocument(input, dialect, includeMembers) + } + } diff --git a/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala b/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala index d8c7928dbf7..52929fa444e 100644 --- a/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala +++ b/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala @@ -576,13 +576,13 @@ class ImplementationLspSuite extends BaseRangesSuite("implementation") { checkSymbols( "set", """|package a - |class MySet[A] extends S@@et[A] { - | override def iterator: Iterator[A] = ??? - | override def contains(elem: A): Boolean = ??? - | override def incl(elem: A): Set[A] = ??? - | override def excl(elem: A): Set[A] = ??? - |} - |""".stripMargin, + |class MySet[A] extends S@@et[A] { + | override def iterator: Iterator[A] = ??? + | override def contains(elem: A): Boolean = ??? + | override def incl(elem: A): Set[A] = ??? + | override def excl(elem: A): Set[A] = ??? + |} + |""".stripMargin, """|a/MySet# |scala/Enumeration#ValueSet# |scala/Enumeration#ValueSet# @@ -608,7 +608,7 @@ class ImplementationLspSuite extends BaseRangesSuite("implementation") { |scala/collection/immutable/SortedSet# |scala/collection/immutable/TreeSet# |scala/collection/immutable/TreeSet# - |""".stripMargin + |""".stripMargin, ) checkSymbols( @@ -626,7 +626,7 @@ class ImplementationLspSuite extends BaseRangesSuite("implementation") { |scala/reflect/macros/ReificationException# |scala/reflect/macros/TypecheckException# |scala/reflect/macros/UnexpectedReificationException# - |""".stripMargin + |""".stripMargin, ) override protected def libraryDependencies: List[String] = @@ -647,9 +647,9 @@ class ImplementationLspSuite extends BaseRangesSuite("implementation") { } def checkSymbols( - name: String, - fileContents: String, - expectedSymbols: String + name: String, + fileContents: String, + expectedSymbols: String, ): Unit = test(name) { val fileName = "a/src/main/scala/a/Main.scala" @@ -657,22 +657,23 @@ class ImplementationLspSuite extends BaseRangesSuite("implementation") { for { _ <- initialize( s"""/metals.json - |{"a": - | { - | "scalaVersion" : "${BuildInfo.scalaVersion}" - | } - |} - |/$fileName - |${fileContents.replace("@@", "")} + |{"a": + | { + | "scalaVersion" : "${BuildInfo.scalaVersion}" + | } + |} + |/$fileName + |${fileContents.replace("@@", "")} """.stripMargin ) _ <- server.didOpen(fileName) locations <- server.implementation(fileName, fileContents) definitions <- Future.sequence( - locations.map( - location => - server.server.definitionResult(location.toTextDocumentPositionParams) + locations.map(location => + server.server.definitionResult( + location.toTextDocumentPositionParams + ) ) ) symbols = definitions.map(_.symbol).sorted diff --git a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala index 2e388acaa34..460f007e916 100644 --- a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala +++ b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala @@ -3,7 +3,6 @@ package tests import scala.meta.Dialect import scala.meta.dialects import scala.meta.inputs.Input -import scala.meta.internal.metals.EmptyReportContext import scala.meta.internal.mtags.Mtags import scala.meta.internal.mtags.OverriddenSymbolsEnrichment import scala.meta.internal.mtags.ResolvedOverriddenSymbol @@ -639,9 +638,7 @@ class ScalaToplevelSuite extends BaseSuite { case All | ToplevelWithInner => val includeMembers = mode == All val enrichedDoc = - new Mtags()(EmptyReportContext) - .enrichedTextDocument(input, dialect, includeMembers) - .get + Mtags.enrichedTextDocument(input, dialect, includeMembers).get val symbols = enrichedDoc.textDocument.occurrences.map(_.symbol).toList enrichedDoc.enrichment match { From 97bca825c754868f7814903d6205f068137aac1d Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Fri, 15 Sep 2023 11:31:20 +0200 Subject: [PATCH 09/26] review fixes --- .../ImplementationProvider.scala | 12 +++---- .../implementation/InheritanceContext.scala | 1 + .../internal/mtags/ScalaToplevelMtags.scala | 35 ++++++++++--------- .../test/scala/tests/ScalaToplevelSuite.scala | 9 +++++ 4 files changed, 33 insertions(+), 24 deletions(-) diff --git a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala index d4268c0b4fe..0085fdc51fa 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala @@ -84,13 +84,11 @@ final class ImplementationProvider( overriddenInfo: List[ (AbsolutePath, List[(String, List[OverriddenSymbol])]) ] - ): Unit = { - overriddenInfo.foreach { case (path, list) => - list.foreach { case (overridesSymbol, overridden) => - overridden.foreach(addTypeHierarchyElement(path, overridesSymbol, _)) - } - } - } + ): Unit = for { + (path, list) <- overriddenInfo + (overridesSymbol, overriddenSymbols) <- list + overridden <- overriddenSymbols + } addTypeHierarchyElement(path, overridesSymbol, overridden) def addTypeHierarchyElements( elements: List[(AbsolutePath, String, OverriddenSymbol)] diff --git a/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala b/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala index 4cb4ac8ba0e..0214ce4ff43 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala @@ -62,6 +62,7 @@ class GlobalInheritanceContext( symbol: String )(implicit ec: ExecutionContext): Future[Set[ClassLocation]] = { val workspaceImplementations = getWorkspaceLocations(symbol) + // for enum class we resolve all cases as implementations while indexing val enumCasesImplementations = implementationsInDependencySources.getOrElse(symbol, Set.empty) val shortName = symbol.desc.name.value diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala index 2d32cac53fc..410edad2289 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala @@ -417,7 +417,7 @@ class ScalaToplevelMtags( nextExpectTemplate ) case EXTENDS => - val (overridden, maybeNewIdent) = findOverridden(List.empty) + val (overridden, maybeNewIndent) = findOverridden(List.empty) expectTemplate.map(tmpl => withOwner(tmpl.owner) { addOverridden( @@ -428,8 +428,8 @@ class ScalaToplevelMtags( } ) loop( - maybeNewIdent.getOrElse(indent), - isAfterNewline = maybeNewIdent.isDefined, + maybeNewIndent.getOrElse(indent), + isAfterNewline = maybeNewIndent.isDefined, currRegion, expectTemplate ) @@ -515,7 +515,7 @@ class ScalaToplevelMtags( @tailrec private def acceptAllAfterOverriddenIdentifier(): Option[Int] = { - val maybeNewIdent = acceptTrivia() + val maybeNewIndent = acceptTrivia() scanner.curr.token match { case LPAREN => acceptBalancedDelimeters(LPAREN, RPAREN) @@ -523,7 +523,7 @@ class ScalaToplevelMtags( case LBRACKET => acceptBalancedDelimeters(LBRACKET, RBRACKET) acceptAllAfterOverriddenIdentifier() - case _ => maybeNewIdent + case _ => maybeNewIndent } } @@ -532,34 +532,35 @@ class ScalaToplevelMtags( private def findOverridden( acc0: List[Identifier] ): (List[Identifier], Option[Int]) = { - val maybeNewIdent0 = acceptTrivia() + val maybeNewIndent0 = acceptTrivia() scanner.curr.token match { case IDENTIFIER => @tailrec def getIdentifier(): (Option[Identifier], Option[Int]) = { val currentIdentifier = newIdentifier - val maybeNewIdent = acceptAllAfterOverriddenIdentifier() + val maybeNewIndent = acceptAllAfterOverriddenIdentifier() scanner.curr.token match { case DOT => scanner.nextToken() getIdentifier() - case _ => (currentIdentifier, maybeNewIdent) + case _ => (currentIdentifier, maybeNewIndent) } } - val (identifier, maybeNewIdent) = getIdentifier() + val (identifier, maybeNewIndent) = getIdentifier() val acc = identifier.toList ++ acc0 scanner.curr.token match { case WITH => findOverridden(acc) - case _ => (acc, maybeNewIdent) + case COMMA => findOverridden(acc) + case _ => (acc, maybeNewIndent) } case LBRACE => acceptBalancedDelimeters(LBRACE, RBRACE) - val maybeNewIdent = acceptTrivia() + val maybeNewIndent = acceptTrivia() scanner.curr.token match { case WITH => findOverridden(acc0) - case _ => (acc0, maybeNewIdent) + case _ => (acc0, maybeNewIndent) } - case _ => (acc0, maybeNewIdent0) + case _ => (acc0, maybeNewIndent0) } } @@ -754,7 +755,7 @@ class ScalaToplevelMtags( private def acceptTrivia(): Option[Int] = { var includedNewline = false - var ident = 0 + var indent = 0 scanner.nextToken() while ( !isDone && @@ -765,13 +766,13 @@ class ScalaToplevelMtags( ) { if (isNewline) { includedNewline = true - ident = 0 + indent = 0 } else if (scanner.curr.token == WHITESPACE) { - ident += 1 + indent += 1 } scanner.nextToken() } - if (includedNewline) Some(ident) else None + if (includedNewline) Some(indent) else None } private def nextIsNL(): Boolean = { diff --git a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala index 460f007e916..dfba3af08a9 100644 --- a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala +++ b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala @@ -624,6 +624,15 @@ class ScalaToplevelSuite extends BaseSuite { mode = All, ) + check( + "overridden3", + """|package a + |class A extends B, C + |""".stripMargin, + List("a/", "a/A# -> B, C"), + mode = All, + ) + def check( options: TestOptions, code: String, From 46fb233a41cb688a93c2e659f2bb0e900ccc0b57 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Fri, 27 Oct 2023 16:33:11 +0200 Subject: [PATCH 10/26] review fixes --- .../db/migration/V5__Jar_type_hierarchy.sql | 7 ++++--- .../internal/implementation/ClassLocation.scala | 2 +- .../implementation/ImplementationProvider.scala | 1 - .../meta/internal/metals/JarTopLevels.scala | 17 ++++++++++------- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/metals/src/main/resources/db/migration/V5__Jar_type_hierarchy.sql b/metals/src/main/resources/db/migration/V5__Jar_type_hierarchy.sql index 2ee57eecd35..030f7c9b89b 100644 --- a/metals/src/main/resources/db/migration/V5__Jar_type_hierarchy.sql +++ b/metals/src/main/resources/db/migration/V5__Jar_type_hierarchy.sql @@ -1,12 +1,13 @@ -- Type hierarchy information, e.g. symbol: "a/MyException#", extended_name: "Exception" create table type_hierarchy( symbol varchar not null, - extended_name varchar not null, - extended_name_offset int, + parent_name varchar not null, + parent_name_offset int, path varchar not null, jar int, + is_resolved bit, foreign key (jar) references indexed_jar (id) on delete cascade, - primary key (jar, path, symbol, extended_name, extended_name_offset) + primary key (jar, path, symbol, parent_name, parent_name_offset) ); create index type_hierarchy_jar on type_hierarchy(jar); diff --git a/metals/src/main/scala/scala/meta/internal/implementation/ClassLocation.scala b/metals/src/main/scala/scala/meta/internal/implementation/ClassLocation.scala index c1d9efbb27f..932198384d2 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/ClassLocation.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/ClassLocation.scala @@ -5,7 +5,7 @@ import java.nio.file.Path case class ClassLocation( symbol: String, file: Option[Path], - // position of the reference to the overridden symbol, e.g. + // position of the reference to the overridden symbol for lazy resolution, e.g. // class AClass extends @@BClass pos: Option[Int] = None, ) diff --git a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala index 0085fdc51fa..59780a472b6 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala @@ -24,7 +24,6 @@ import scala.meta.internal.mtags.GlobalSymbolIndex import scala.meta.internal.mtags.Mtags import scala.meta.internal.mtags.OverriddenSymbol import scala.meta.internal.mtags.ResolvedOverriddenSymbol -import scala.meta.internal.mtags.SemanticdbPath import scala.meta.internal.mtags.Semanticdbs import scala.meta.internal.mtags.SymbolDefinition import scala.meta.internal.mtags.UnresolvedOverriddenSymbol diff --git a/metals/src/main/scala/scala/meta/internal/metals/JarTopLevels.scala b/metals/src/main/scala/scala/meta/internal/metals/JarTopLevels.scala index 1502ceb6a82..0e9d4b480e2 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/JarTopLevels.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/JarTopLevels.scala @@ -79,7 +79,7 @@ final class JarTopLevels(conn: () => Connection) { val toplevels = List.newBuilder[(AbsolutePath, String, OverriddenSymbol)] conn() .query( - """select th.symbol, th.extended_name, th.extended_name_offset, th.path + """select th.symbol, th.parent_name, th.parent_name_offset, th.path, th.is_resolved |from indexed_jar ij |left join type_hierarchy th |on ij.id=th.jar @@ -90,12 +90,13 @@ final class JarTopLevels(conn: () => Connection) { .getString(2) != null && rs.getString(4) != null ) { val symbol = rs.getString(1) - val extendedName = rs.getString(2) - val extendedOffset = rs.getInt(3) + val parentName = rs.getString(2) + val parentOffset = rs.getInt(3) val path = AbsolutePath(fs.getPath(rs.getString(4))) + val isResolved = rs.getBoolean(5) val overridden = - if (extendedOffset < 0) ResolvedOverriddenSymbol(extendedName) - else UnresolvedOverriddenSymbol(extendedName, extendedOffset) + if (isResolved) ResolvedOverriddenSymbol(parentName) + else UnresolvedOverriddenSymbol(parentName, parentOffset) toplevels += ((path, symbol, overridden)) } } @@ -201,17 +202,19 @@ final class JarTopLevels(conn: () => Connection) { var symbolStmt: PreparedStatement = null try { symbolStmt = conn().prepareStatement( - s"insert into type_hierarchy (symbol, extended_name, extended_name_offset, path, jar) values (?, ?, ?, ?, ?)" + s"insert into type_hierarchy (symbol, parent_name, parent_name_offset, path, jar, is_resolved) values (?, ?, ?, ?, ?, ?)" ) type_hierarchy.foreach { case (path, symbol, overridden) => symbolStmt.setString(1, symbol) overridden match { case ResolvedOverriddenSymbol(name) => symbolStmt.setString(2, name) - symbolStmt.setInt(3, -1) + symbolStmt.setInt(3, 0) + symbolStmt.setBoolean(6, true) case UnresolvedOverriddenSymbol(name, pos) => symbolStmt.setString(2, name) symbolStmt.setInt(3, pos) + symbolStmt.setBoolean(6, false) } symbolStmt.setString(4, path.toString()) symbolStmt.setInt(5, jar) From ede5b9ae8141fb4e9328c4668054e612fc2ecbb8 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Mon, 30 Oct 2023 18:16:46 +0100 Subject: [PATCH 11/26] get rid of enriched document --- .../src/main/scala/bench/MetalsBench.scala | 4 +- .../ImplementationProvider.scala | 11 ++-- .../scala/meta/internal/metals/Indexer.scala | 30 +++------- .../internal/mtags/GlobalSymbolIndex.scala | 12 +++- .../scala/meta/internal/mtags/Mtags.scala | 24 ++++---- .../meta/internal/mtags/MtagsIndexer.scala | 54 ++++-------------- .../internal/mtags/OnDemandSymbolIndex.scala | 6 +- .../internal/mtags/ScalaToplevelMtags.scala | 12 +--- .../internal/mtags/SymbolIndexBucket.scala | 55 +++++++++---------- .../tests/DelegatingGlobalSymbolIndex.scala | 6 +- .../test/scala/tests/ScalaToplevelSuite.scala | 40 ++++++-------- 11 files changed, 99 insertions(+), 155 deletions(-) diff --git a/metals-bench/src/main/scala/bench/MetalsBench.scala b/metals-bench/src/main/scala/bench/MetalsBench.scala index d5c9c5599df..cdc2c05aa61 100644 --- a/metals-bench/src/main/scala/bench/MetalsBench.scala +++ b/metals-bench/src/main/scala/bench/MetalsBench.scala @@ -90,9 +90,7 @@ class MetalsBench { @Benchmark @BenchmarkMode(Array(Mode.SingleShotTime)) def typeHierarchyIndex(): Unit = { - scalaDependencySources.inputs.foreach { input => - Mtags.enrichedTextDocument(input) - } + scalaDependencySources.inputs.foreach { Mtags.indexWithOverrides(_) } } @Benchmark diff --git a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala index 59780a472b6..7c86ab3ae6d 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala @@ -21,6 +21,7 @@ import scala.meta.internal.metals.ScalaVersionSelector import scala.meta.internal.metals.ScalaVersions import scala.meta.internal.metals.SemanticdbFeatureProvider import scala.meta.internal.mtags.GlobalSymbolIndex +import scala.meta.internal.mtags.IndexingResult import scala.meta.internal.mtags.Mtags import scala.meta.internal.mtags.OverriddenSymbol import scala.meta.internal.mtags.ResolvedOverriddenSymbol @@ -79,13 +80,9 @@ final class ImplementationProvider( ) } - def addTypeHierarchy( - overriddenInfo: List[ - (AbsolutePath, List[(String, List[OverriddenSymbol])]) - ] - ): Unit = for { - (path, list) <- overriddenInfo - (overridesSymbol, overriddenSymbols) <- list + def addTypeHierarchy(results: List[IndexingResult]): Unit = for { + IndexingResult(path, _, overrides) <- results + (overridesSymbol, overriddenSymbols) <- overrides overridden <- overriddenSymbols } addTypeHierarchyElement(path, overridesSymbol, overridden) diff --git a/metals/src/main/scala/scala/meta/internal/metals/Indexer.scala b/metals/src/main/scala/scala/meta/internal/metals/Indexer.scala index 26f213380ec..8c1252a042a 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Indexer.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Indexer.scala @@ -29,10 +29,8 @@ import scala.meta.internal.metals.clients.language.DelegatingLanguageClient import scala.meta.internal.metals.clients.language.ForwardingMetalsBuildClient import scala.meta.internal.metals.debug.BuildTargetClasses import scala.meta.internal.metals.watcher.FileWatcher -import scala.meta.internal.mtags.EnrichedTextDocument +import scala.meta.internal.mtags.IndexingResult import scala.meta.internal.mtags.OnDemandSymbolIndex -import scala.meta.internal.mtags.OverriddenSymbol -import scala.meta.internal.mtags.OverriddenSymbolsEnrichment import scala.meta.internal.semanticdb.Scala._ import scala.meta.internal.tvp.FolderTreeViewProvider import scala.meta.internal.worksheets.WorksheetProvider @@ -471,9 +469,7 @@ final case class Indexer( ) .getOrElse(Scala213) - val documents = definitionIndex.addSourceDirectory(path, dialect) - val overriddenInfo = collectOverriddenInfo(documents) - implementationProvider.addTypeHierarchy(overriddenInfo) + definitionIndex.addSourceDirectory(path, dialect) } else { scribe.warn(s"unexpected dependency: $path") } @@ -485,17 +481,6 @@ final case class Indexer( usedJars.toSet } - private def collectOverriddenInfo( - documents: List[(AbsolutePath, EnrichedTextDocument)] - ): List[(AbsolutePath, List[(String, List[OverriddenSymbol])])] = - documents.flatMap { case (path, document) => - document.enrichment match { - case OverriddenSymbolsEnrichment(overridden) => - Some((path, overridden)) - case _ => None - } - } - private def indexJdkSources( data: TargetData, dependencySources: b.DependencySourcesResult, @@ -614,12 +599,13 @@ final case class Indexer( private def addSourceJarSymbols(path: AbsolutePath): Unit = { val dialect = ScalaVersions.dialectForDependencyJar(path.filename) def indexJar() = { - val documents = definitionIndex.addSourceJar(path, dialect) - val toplevels = documents.flatMap { case (path, document) => - document.topLevels.map((_, path)) + val indexResult = definitionIndex.addSourceJar(path, dialect) + val toplevels = indexResult.flatMap { + case IndexingResult(path, toplevels, _) => + toplevels.map((_, path)) } - val overrides = collectOverriddenInfo(documents).flatMap { - case (path, list) => + val overrides = indexResult.flatMap { + case IndexingResult(path, _, list) => list.flatMap { case (symbol, overridden) => overridden.map((path, symbol, _)) } diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/GlobalSymbolIndex.scala b/mtags/src/main/scala/scala/meta/internal/mtags/GlobalSymbolIndex.scala index 57cb2fe054b..5bde20d49ce 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/GlobalSymbolIndex.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/GlobalSymbolIndex.scala @@ -55,7 +55,7 @@ trait GlobalSymbolIndex { file: AbsolutePath, sourceDirectory: Option[AbsolutePath], dialect: Dialect - ): Option[EnrichedTextDocument] + ): Option[IndexingResult] /** * Index a jar or zip file containing Scala and Java source files. @@ -88,7 +88,7 @@ trait GlobalSymbolIndex { def addSourceJar( jar: AbsolutePath, dialect: Dialect - ): List[(AbsolutePath, EnrichedTextDocument)] + ): List[IndexingResult] /** * The same as `addSourceJar` except for directories @@ -96,7 +96,7 @@ trait GlobalSymbolIndex { def addSourceDirectory( dir: AbsolutePath, dialect: Dialect - ): List[(AbsolutePath, EnrichedTextDocument)] + ): List[IndexingResult] } @@ -110,3 +110,9 @@ case class SymbolDefinition( def isExact: Boolean = querySymbol == definitionSymbol } + +case class IndexingResult( + path: AbsolutePath, + topLevels: List[String], + overrides: List[(String, List[OverriddenSymbol])] +) diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/Mtags.scala b/mtags/src/main/scala/scala/meta/internal/mtags/Mtags.scala index dfb52e0d8aa..2c0b07e78c1 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/Mtags.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/Mtags.scala @@ -34,7 +34,6 @@ final class Mtags(implicit rc: ReportContext) { addLines(language, input.text) mtags .index() - .textDocument .occurrences .iterator .filterNot(_.symbol.isPackage) @@ -45,11 +44,11 @@ final class Mtags(implicit rc: ReportContext) { } } - def enrichedTextDocument( + def indexWithOverrides( input: Input.VirtualFile, dialect: Dialect = dialects.Scala213, includeMembers: Boolean = false - ): Option[EnrichedTextDocument] = { + ): (TextDocument, MtagsIndexer.AllOverrides) = { val language = input.toLanguage if (language.isJava || language.isScala) { val mtags = @@ -63,8 +62,10 @@ final class Mtags(implicit rc: ReportContext) { dialect ) addLines(language, input.text) - Some(mtags.index()) - } else None + val doc = mtags.index() + val overrides = mtags.overrides() + (doc, overrides) + } else (TextDocument(), Nil) } def index( @@ -78,9 +79,8 @@ final class Mtags(implicit rc: ReportContext) { JavaMtags .index(input, includeMembers = true) .index() - .textDocument } else if (language.isScala) { - ScalaMtags.index(input, dialect).index().textDocument + ScalaMtags.index(input, dialect).index() } else { TextDocument() } @@ -122,11 +122,11 @@ object Mtags { )(implicit rc: ReportContext = EmptyReportContext): TextDocument = input.toLanguage match { case Language.JAVA => - new JavaMtags(input, includeMembers = true).index().textDocument + new JavaMtags(input, includeMembers = true).index() case Language.SCALA => val mtags = new ScalaToplevelMtags(input, true, includeMembers, dialect) - mtags.index().textDocument + mtags.index() case _ => TextDocument() } @@ -138,14 +138,14 @@ object Mtags { new Mtags().toplevels(input, dialect) } - def enrichedTextDocument( + def indexWithOverrides( input: Input.VirtualFile, dialect: Dialect = dialects.Scala213, includeMembers: Boolean = false )(implicit rc: ReportContext = EmptyReportContext - ): Option[EnrichedTextDocument] = { - new Mtags().enrichedTextDocument(input, dialect, includeMembers) + ): (TextDocument, MtagsIndexer.AllOverrides) = { + new Mtags().indexWithOverrides(input, dialect, includeMembers) } } diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/MtagsIndexer.scala b/mtags/src/main/scala/scala/meta/internal/mtags/MtagsIndexer.scala index e7c2708b071..92694a7e703 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/MtagsIndexer.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/MtagsIndexer.scala @@ -11,21 +11,20 @@ import scala.meta.internal.semanticdb.Scala._ import scala.meta.internal.semanticdb.SymbolInformation.Kind import scala.meta.internal.{semanticdb => s} -trait GenericMtagsIndexer[E <: TextDocumentEnrichment] { +trait MtagsIndexer { def language: Language def indexRoot(): Unit def input: Input.VirtualFile - protected def enrich(doc: s.TextDocument): EnrichedTextDocument - def index(): EnrichedTextDocument = { + // should only be called after `index`/`indexRoot` + def overrides(): MtagsIndexer.AllOverrides = Nil + def index(): s.TextDocument = { indexRoot() - enrich( - s.TextDocument( - uri = input.path, - text = input.text, - language = language, - occurrences = names.result(), - symbols = symbols.result() - ) + s.TextDocument( + uri = input.path, + text = input.text, + language = language, + occurrences = names.result(), + symbols = symbols.result() ) } // This method is intentionally non-final to allow accessing this stream directly without building a s.TextDocument. @@ -174,35 +173,6 @@ trait GenericMtagsIndexer[E <: TextDocumentEnrichment] { else Symbols.Global(currentOwner, signature) } -class EnrichedTextDocument( - val textDocument: s.TextDocument, - val enrichment: TextDocumentEnrichment, - topLevelFiler: List[String] => List[String] = identity -) { - lazy val topLevels: List[String] = { - val topLevelSymbols = - textDocument.occurrences.iterator - .filterNot(_.symbol.isPackage) - .map(_.symbol) - .toList - topLevelFiler(topLevelSymbols) - } - - def withFilter(newFilter: List[String] => List[String]) = - new EnrichedTextDocument( - textDocument, - enrichment, - topLevelFiler.compose(newFilter) - ) -} - -sealed trait TextDocumentEnrichment -case object NoEnrichment extends TextDocumentEnrichment -case class OverriddenSymbolsEnrichment( - overridden: List[(String, List[OverriddenSymbol])] -) extends TextDocumentEnrichment - -trait MtagsIndexer extends GenericMtagsIndexer[NoEnrichment.type] { - protected def enrich(doc: s.TextDocument): EnrichedTextDocument = - new EnrichedTextDocument(doc, NoEnrichment) +object MtagsIndexer { + type AllOverrides = List[(String, List[OverriddenSymbol])] } diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/OnDemandSymbolIndex.scala b/mtags/src/main/scala/scala/meta/internal/mtags/OnDemandSymbolIndex.scala index b9935b9e25e..a7b47aa3ba3 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/OnDemandSymbolIndex.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/OnDemandSymbolIndex.scala @@ -65,7 +65,7 @@ final class OnDemandSymbolIndex( override def addSourceDirectory( dir: AbsolutePath, dialect: Dialect - ): List[(AbsolutePath, EnrichedTextDocument)] = + ): List[IndexingResult] = tryRun( dir, List.empty, @@ -77,7 +77,7 @@ final class OnDemandSymbolIndex( override def addSourceJar( jar: AbsolutePath, dialect: Dialect - ): List[(AbsolutePath, EnrichedTextDocument)] = + ): List[IndexingResult] = tryRun( jar, List.empty, { @@ -109,7 +109,7 @@ final class OnDemandSymbolIndex( source: AbsolutePath, sourceDirectory: Option[AbsolutePath], dialect: Dialect - ): Option[EnrichedTextDocument] = + ): Option[IndexingResult] = tryRun( source, None, { diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala index 410edad2289..a245c633264 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala @@ -14,7 +14,6 @@ import scala.meta.internal.semanticdb.Scala import scala.meta.internal.semanticdb.Scala._ import scala.meta.internal.semanticdb.SymbolInformation import scala.meta.internal.semanticdb.SymbolInformation.Kind -import scala.meta.internal.semanticdb.TextDocument import scala.meta.internal.tokenizers.LegacyScanner import scala.meta.internal.tokenizers.LegacyToken._ import scala.meta.tokenizers.TokenizeException @@ -47,15 +46,10 @@ class ScalaToplevelMtags( includeMembers: Boolean, dialect: Dialect )(implicit rc: ReportContext) - extends GenericMtagsIndexer[OverriddenSymbolsEnrichment] { + extends MtagsIndexer { - override protected def enrich( - doc: TextDocument - ): EnrichedTextDocument = - new EnrichedTextDocument( - doc, - OverriddenSymbolsEnrichment(overridden.result) - ) + override def overrides(): List[(String, List[OverriddenSymbol])] = + overridden.result private val overridden = List.newBuilder[(String, List[OverriddenSymbol])] 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 f62f7ff1db6..ff5c98f2294 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/SymbolIndexBucket.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/SymbolIndexBucket.scala @@ -53,11 +53,11 @@ class SymbolIndexBucket( def addSourceDirectory( dir: AbsolutePath - ): List[(AbsolutePath, EnrichedTextDocument)] = { + ): List[IndexingResult] = { if (sourceJars.addEntry(dir.toNIO)) { dir.listRecursive.toList.flatMap { case source if source.isScala => - addSourceFile(source, Some(dir)).map((source, _)) + addSourceFile(source, Some(dir)) case _ => None } @@ -66,13 +66,13 @@ class SymbolIndexBucket( def addSourceJar( jar: AbsolutePath - ): List[(AbsolutePath, EnrichedTextDocument)] = { + ): List[IndexingResult] = { if (sourceJars.addEntry(jar.toNIO)) { FileIO.withJarFileSystem(jar, create = false) { root => try { root.listRecursive.toList.flatMap { case source if source.isScala => - addSourceFile(source, None, Some(jar)).map((source, _)) + addSourceFile(source, None, Some(jar)) case _ => None } @@ -108,41 +108,40 @@ class SymbolIndexBucket( source: AbsolutePath, sourceDirectory: Option[AbsolutePath], fromSourceJar: Option[AbsolutePath] = None - ): Option[EnrichedTextDocument] = { + ): Option[IndexingResult] = { val uri = source.toIdeallyRelativeURI(sourceDirectory) - for { - doc <- indexSource(source, uri, dialect) - } yield { - val enrichedDocument = doc.withFilter { symbols => - fromSourceJar match { - case Some(jar) if stdLibPatches.isScala3Library(jar) => - symbols.map(stdLibPatches.patchSymbol) - case _ => symbols - } - } - enrichedDocument.topLevels.foreach { symbol => - val acc = toplevels.getOrElse(symbol, Set.empty) - toplevels(symbol) = acc + source + val IndexingResult(path, topLevels, overrides) = + indexSource(source, uri, dialect) + val symbols = + fromSourceJar match { + case Some(jar) if stdLibPatches.isScala3Library(jar) => + topLevels.map(stdLibPatches.patchSymbol) + case _ => topLevels } - enrichedDocument + symbols.foreach { symbol => + val acc = toplevels.getOrElse(symbol, Set.empty) + toplevels(symbol) = acc + source } + Some(IndexingResult(path, symbols, overrides)) } private def indexSource( source: AbsolutePath, uri: String, dialect: Dialect - ): Option[EnrichedTextDocument] = { + ): IndexingResult = { val text = FileIO.slurp(source, StandardCharsets.UTF_8) val input = Input.VirtualFile(uri, text) - mtags - .enrichedTextDocument(input, dialect) - .map(_.withFilter { sourceToplevels => - if (source.isAmmoniteScript) - sourceToplevels - else - sourceToplevels.filter(sym => !isTrivialToplevelSymbol(uri, sym)) - }) + val (doc, overrides) = mtags.indexWithOverrides(input, dialect) + val sourceTopLevels = + doc.occurrences.iterator + .filterNot(_.symbol.isPackage) + .map(_.symbol) + val topLevels = + if (source.isAmmoniteScript) sourceTopLevels.toList + else + sourceTopLevels.filter(sym => !isTrivialToplevelSymbol(uri, sym)).toList + IndexingResult(source, topLevels, overrides) } // Returns true if symbol is com/foo/Bar# and path is /com/foo/Bar.scala diff --git a/tests/mtest/src/main/scala/tests/DelegatingGlobalSymbolIndex.scala b/tests/mtest/src/main/scala/tests/DelegatingGlobalSymbolIndex.scala index 520f98fe89d..383a276f29b 100644 --- a/tests/mtest/src/main/scala/tests/DelegatingGlobalSymbolIndex.scala +++ b/tests/mtest/src/main/scala/tests/DelegatingGlobalSymbolIndex.scala @@ -26,19 +26,19 @@ class DelegatingGlobalSymbolIndex( file: AbsolutePath, sourceDirectory: Option[AbsolutePath], dialect: Dialect, - ): Option[mtags.EnrichedTextDocument] = { + ): Option[mtags.IndexingResult] = { underlying.addSourceFile(file, sourceDirectory, dialect) } def addSourceJar( jar: AbsolutePath, dialect: Dialect, - ): List[(AbsolutePath, mtags.EnrichedTextDocument)] = { + ): List[mtags.IndexingResult] = { underlying.addSourceJar(jar, dialect) } def addSourceDirectory( dir: AbsolutePath, dialect: Dialect, - ): List[(AbsolutePath, mtags.EnrichedTextDocument)] = { + ): List[mtags.IndexingResult] = { underlying.addSourceDirectory(dir, dialect) } } diff --git a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala index dfba3af08a9..f85c5732f16 100644 --- a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala +++ b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala @@ -4,7 +4,6 @@ import scala.meta.Dialect import scala.meta.dialects import scala.meta.inputs.Input import scala.meta.internal.mtags.Mtags -import scala.meta.internal.mtags.OverriddenSymbolsEnrichment import scala.meta.internal.mtags.ResolvedOverriddenSymbol import scala.meta.internal.mtags.UnresolvedOverriddenSymbol @@ -646,28 +645,23 @@ class ScalaToplevelSuite extends BaseSuite { mode match { case All | ToplevelWithInner => val includeMembers = mode == All - val enrichedDoc = - Mtags.enrichedTextDocument(input, dialect, includeMembers).get - val symbols = - enrichedDoc.textDocument.occurrences.map(_.symbol).toList - enrichedDoc.enrichment match { - case OverriddenSymbolsEnrichment(overridden) => - val overriddenMap = overridden.toMap - symbols.map { symbol => - overriddenMap.get(symbol) match { - case None => symbol - case Some(symbols) => - val overridden = - symbols - .map { - case ResolvedOverriddenSymbol(symbol) => symbol - case UnresolvedOverriddenSymbol(name, _) => name - } - .mkString(", ") - s"$symbol -> $overridden" - } - } - case _ => symbols + val (doc, overrides) = + Mtags.indexWithOverrides(input, dialect, includeMembers) + val symbols = doc.occurrences.map(_.symbol).toList + val overriddenMap = overrides.toMap + symbols.map { symbol => + overriddenMap.get(symbol) match { + case None => symbol + case Some(symbols) => + val overridden = + symbols + .map { + case ResolvedOverriddenSymbol(symbol) => symbol + case UnresolvedOverriddenSymbol(name, _) => name + } + .mkString(", ") + s"$symbol -> $overridden" + } } case Toplevel => Mtags.toplevels(input, dialect) } From 25bf32101215a01d3e37bc906cf451079632424d Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Mon, 30 Oct 2023 18:17:21 +0100 Subject: [PATCH 12/26] use `toAbsolutePath` in JarTopLevels --- .../meta/internal/metals/JarTopLevels.scala | 38 +++++++------------ 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/metals/src/main/scala/scala/meta/internal/metals/JarTopLevels.scala b/metals/src/main/scala/scala/meta/internal/metals/JarTopLevels.scala index 0e9d4b480e2..c388e0e8fda 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/JarTopLevels.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/JarTopLevels.scala @@ -8,7 +8,6 @@ import java.sql.Statement import java.util.zip.ZipError import java.util.zip.ZipException -import scala.meta.internal.io.PlatformFileIO import scala.meta.internal.metals.JdbcEnrichments._ import scala.meta.internal.metals.MetalsEnrichments._ import scala.meta.internal.mtags.MD5 @@ -31,17 +30,9 @@ final class JarTopLevels(conn: () => Connection) { * @return the top level Scala symbols in the jar */ def getTopLevels( - path: AbsolutePath + jar: AbsolutePath ): Option[List[(String, AbsolutePath)]] = try { - val fs = path.jarPath - .map(jarPath => - PlatformFileIO.newFileSystem( - jarPath.toURI, - new java.util.HashMap[String, String](), - ) - ) - .getOrElse(PlatformFileIO.newJarFileSystem(path, create = false)) val toplevels = List.newBuilder[(String, AbsolutePath)] conn() .query( @@ -50,32 +41,25 @@ final class JarTopLevels(conn: () => Connection) { |left join toplevel_symbol ts |on ij.id=ts.jar |where ij.md5=?""".stripMargin - ) { _.setString(1, getMD5Digest(path)) } { rs => + ) { _.setString(1, getMD5Digest(jar)) } { rs => if (rs.getString(1) != null && rs.getString(2) != null) { val symbol = rs.getString(1) - val path = AbsolutePath(fs.getPath(rs.getString(2))) + val path = toPath(jar, rs.getString(2)) toplevels += (symbol -> path) } } .headOption .map(_ => toplevels.result) } catch { - case _: ZipError | _: ZipException => + case error @ (_: ZipError | _: ZipException) => + scribe.warn(s"corrupted jar $jar: $error") None } def getTypeHierarchy( - path: AbsolutePath + jar: AbsolutePath ): Option[List[(AbsolutePath, String, OverriddenSymbol)]] = try { - val fs = path.jarPath - .map(jarPath => - PlatformFileIO.newFileSystem( - jarPath.toURI, - new java.util.HashMap[String, String](), - ) - ) - .getOrElse(PlatformFileIO.newJarFileSystem(path, create = false)) val toplevels = List.newBuilder[(AbsolutePath, String, OverriddenSymbol)] conn() .query( @@ -84,7 +68,7 @@ final class JarTopLevels(conn: () => Connection) { |left join type_hierarchy th |on ij.id=th.jar |where ij.type_hierarchy_indexed=true and ij.md5=?""".stripMargin - ) { _.setString(1, getMD5Digest(path)) } { rs => + ) { _.setString(1, getMD5Digest(jar)) } { rs => if ( rs.getString(1) != null && rs .getString(2) != null && rs.getString(4) != null @@ -92,7 +76,7 @@ final class JarTopLevels(conn: () => Connection) { val symbol = rs.getString(1) val parentName = rs.getString(2) val parentOffset = rs.getInt(3) - val path = AbsolutePath(fs.getPath(rs.getString(4))) + val path = toPath(jar, rs.getString(4)) val isResolved = rs.getBoolean(5) val overridden = if (isResolved) ResolvedOverriddenSymbol(parentName) @@ -103,10 +87,14 @@ final class JarTopLevels(conn: () => Connection) { .headOption .map(_ => toplevels.result) } catch { - case _: ZipError | _: ZipException => + case error @ (_: ZipError | _: ZipException) => + scribe.warn(s"corrupted jar $jar: $error") None } + private def toPath(jar: AbsolutePath, path: String) = + ("jar:" ++ jar.toNIO.toUri.toString() ++ "!" ++ path).toAbsolutePath + /** * Stores the top level symbols for the Jar * From 83436adfc3c8772f86f84b22ef7724776f0640db Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Tue, 21 Nov 2023 21:21:22 +0100 Subject: [PATCH 13/26] fixes after merge --- metals-bench/src/main/scala/bench/MetalsBench.scala | 10 +++++++++- .../implementation/ImplementationProvider.scala | 6 ++---- .../scala/meta/internal/mtags/SymbolIndexBucket.scala | 7 ++++--- .../main/scala/tests/DelegatingGlobalSymbolIndex.scala | 6 +++--- .../src/test/scala/tests/ToplevelLibrarySuite.scala | 2 +- 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/metals-bench/src/main/scala/bench/MetalsBench.scala b/metals-bench/src/main/scala/bench/MetalsBench.scala index 2726ab262a8..5715745a5de 100644 --- a/metals-bench/src/main/scala/bench/MetalsBench.scala +++ b/metals-bench/src/main/scala/bench/MetalsBench.scala @@ -102,7 +102,15 @@ class MetalsBench { @Benchmark @BenchmarkMode(Array(Mode.SingleShotTime)) def typeHierarchyIndex(): Unit = { - scalaDependencySources.inputs.foreach { Mtags.indexWithOverrides(_) } + scalaDependencySources.inputs.foreach { input => + implicit val rc: ReportContext = EmptyReportContext + new ScalaToplevelMtags( + input, + includeInnerClasses = true, + includeMembers = false, + dialects.Scala213, + ).index() + } } @Benchmark diff --git a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala index 7c86ab3ae6d..2adf1e3cb84 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala @@ -10,7 +10,6 @@ import scala.concurrent.ExecutionContext import scala.concurrent.Future import scala.util.control.NonFatal -import scala.meta.inputs.Input import scala.meta.internal.io.FileIO import scala.meta.internal.metals.Buffers import scala.meta.internal.metals.BuildTargets @@ -417,9 +416,8 @@ final class ImplementationProvider( private def semanticdbForJarFile(fileSource: AbsolutePath) = { val dialect = ScalaVersions.dialectForDependencyJar(fileSource.filename) - val text = FileIO.slurp(fileSource, StandardCharsets.UTF_8) - val input = Input.VirtualFile(fileSource.toURI.toString(), text) - val textDocument = Mtags.index(input, dialect) + FileIO.slurp(fileSource, StandardCharsets.UTF_8) + val textDocument = Mtags.index(fileSource, dialect) textDocument } 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 70fe1d5c191..7a122356e6b 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/SymbolIndexBucket.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/SymbolIndexBucket.scala @@ -93,10 +93,11 @@ class SymbolIndexBucket( } def addSourceFile( - source: AbsolutePath, - sourceDirectory: Option[AbsolutePath], + source: AbsolutePath, + sourceDirectory: Option[AbsolutePath] ): Option[IndexingResult] = { - val IndexingResult(path, topLevels, overrides) = indexSource(source, dialect, sourceDirectory) + val IndexingResult(path, topLevels, overrides) = + indexSource(source, dialect, sourceDirectory) topLevels.foreach { symbol => val acc = toplevels.getOrElse(symbol, Set.empty) toplevels(symbol) = acc + source diff --git a/tests/mtest/src/main/scala/tests/DelegatingGlobalSymbolIndex.scala b/tests/mtest/src/main/scala/tests/DelegatingGlobalSymbolIndex.scala index 383a276f29b..782503ac6ce 100644 --- a/tests/mtest/src/main/scala/tests/DelegatingGlobalSymbolIndex.scala +++ b/tests/mtest/src/main/scala/tests/DelegatingGlobalSymbolIndex.scala @@ -25,19 +25,19 @@ class DelegatingGlobalSymbolIndex( def addSourceFile( file: AbsolutePath, sourceDirectory: Option[AbsolutePath], - dialect: Dialect, + dialect: Dialect ): Option[mtags.IndexingResult] = { underlying.addSourceFile(file, sourceDirectory, dialect) } def addSourceJar( jar: AbsolutePath, - dialect: Dialect, + dialect: Dialect ): List[mtags.IndexingResult] = { underlying.addSourceJar(jar, dialect) } def addSourceDirectory( dir: AbsolutePath, - dialect: Dialect, + dialect: Dialect ): List[mtags.IndexingResult] = { underlying.addSourceDirectory(dir, dialect) } diff --git a/tests/unit/src/test/scala/tests/ToplevelLibrarySuite.scala b/tests/unit/src/test/scala/tests/ToplevelLibrarySuite.scala index f54e25d4751..6db5ed60082 100644 --- a/tests/unit/src/test/scala/tests/ToplevelLibrarySuite.scala +++ b/tests/unit/src/test/scala/tests/ToplevelLibrarySuite.scala @@ -77,7 +77,7 @@ class ToplevelLibrarySuite extends BaseSuite { assertNoDiff( obtained.mkString("\n"), expected.mkString("\n"), - s"${input.text}", + s"${input.path}", ) } From 357b3c05eb6ac0239f1b52cda2060b68ccedee57 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Thu, 23 Nov 2023 16:16:27 +0100 Subject: [PATCH 14/26] fix resolving if symbol is defined in the workspace --- .../ImplementationProvider.scala | 59 +++++++++++-------- .../metals/InteractiveSemanticdbs.scala | 8 ++- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala index 2adf1e3cb84..b96533c605f 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala @@ -174,31 +174,29 @@ final class ImplementationProvider( val dealiased = if (sym.desc.isType) dealiasClass(sym, symbolSearch) else sym - val definitionDocument = - if (currentDocument.definesSymbol(dealiased)) { - Some(currentDocument) - } else { - findSemanticDbForSymbol(dealiased) - } + val isWorkspaceSymbol = + (source.isWorkspaceSource(workspace) && + currentDocument.definesSymbol(dealiased)) || + findSymbolDefinition(dealiased).exists( + _.path.isWorkspaceSource(workspace) + ) - val inheritanceContext = definitionDocument match { - // symbol is not in workspace, we only search classpath for it - case None => - globalTable.globalContextFor( - source, + val inheritanceContext = if (!isWorkspaceSymbol) { + // symbol is not in workspace, we search both workspace and dependencies for it + globalTable.globalContextFor( + source, + implementationsInPath.asScala.toMap, + definitionProvider, + implementationsInDependencySources.asScala.toMap, + ) + } else { + // symbol in workspace we search the workspace for it only + Some( + InheritanceContext.fromDefinitions( + symbolSearch, implementationsInPath.asScala.toMap, - definitionProvider, - implementationsInDependencySources.asScala.toMap, - ) - // symbol is in workspace, - // we might need to search different places for related symbols - case Some(_) => - Some( - InheritanceContext.fromDefinitions( - symbolSearch, - implementationsInPath.asScala.toMap, - ) ) + ) } symbolLocationsFromContext( dealiased, @@ -426,11 +424,14 @@ final class ImplementationProvider( classContext: InheritanceContext, file: Path, ): Future[Map[Path, Set[ClassLocation]]] = { + val visited = mutable.Set.empty[String] def loop( symbol: String, currentPath: Option[Path], ): Future[Set[ClassLocation]] = { + visited.add(symbol) + scribe.debug(s"searching for implementations for symbol $symbol") val directImplementations = classContext .getLocations(symbol) @@ -446,9 +447,17 @@ final class ImplementationProvider( }) directImplementations.flatMap { directImplementations => Future - .sequence(directImplementations.map { loc => - loop(loc.symbol, loc.file) - }) + .sequence( + directImplementations + .withFilter(loc => + (!visited( + loc.symbol + ) && loc.symbol.desc.isType) || loc.symbol.isLocal + ) + .map { loc => + loop(loc.symbol, loc.file) + } + ) .map(rec => directImplementations ++ rec.flatten) } } diff --git a/metals/src/main/scala/scala/meta/internal/metals/InteractiveSemanticdbs.scala b/metals/src/main/scala/scala/meta/internal/metals/InteractiveSemanticdbs.scala index 6e2631634f9..944c54db7e1 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/InteractiveSemanticdbs.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/InteractiveSemanticdbs.scala @@ -201,8 +201,12 @@ final class InteractiveSemanticdbs( .getOrElse(compilers().fallbackCompiler(source)) val (prependedLinesSize, modifiedText) = - buildTargets - .sbtAutoImports(source) + Option + .when(source.isSbt)( + buildTargets + .sbtAutoImports(source) + ) + .flatten .fold((0, text))(imports => (imports.size, SbtBuildTool.prependAutoImports(text, imports)) ) From b56da77aed41cc68ade486b59aa395f44db19dbd Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Thu, 14 Dec 2023 14:59:59 +0100 Subject: [PATCH 15/26] add find parents in pc --- .../implementation/GlobalClassTable.scala | 6 +- .../ImplementationProvider.scala | 4 +- .../implementation/InheritanceContext.scala | 23 ++--- .../meta/internal/metals/Compilers.scala | 9 ++ .../internal/metals/MetalsLspService.scala | 99 ++++++++++--------- .../scala/meta/pc/PresentationCompiler.java | 4 + .../pc/ScalaPresentationCompiler.scala | 11 +++ .../internal/pc/WorkspaceSymbolSearch.scala | 28 ++++++ .../meta/internal/pc/ParentProvider.scala | 53 ++++++++++ .../pc/ScalaPresentationCompiler.scala | 8 ++ 10 files changed, 181 insertions(+), 64 deletions(-) create mode 100644 mtags/src/main/scala-3/scala/meta/internal/pc/ParentProvider.scala diff --git a/metals/src/main/scala/scala/meta/internal/implementation/GlobalClassTable.scala b/metals/src/main/scala/scala/meta/internal/implementation/GlobalClassTable.scala index 33fd9f42eac..57cd19f50a9 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/GlobalClassTable.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/GlobalClassTable.scala @@ -5,7 +5,7 @@ import scala.collection.concurrent.TrieMap import scala.collection.mutable import scala.meta.internal.metals.BuildTargets -import scala.meta.internal.metals.DefinitionProvider +import scala.meta.internal.metals.Compilers import scala.meta.internal.semanticdb.SymbolInformation import scala.meta.internal.symtab.GlobalSymbolTable import scala.meta.io.AbsolutePath @@ -27,14 +27,14 @@ final class GlobalClassTable( def globalContextFor( source: AbsolutePath, implementationsInPath: ImplementationCache, - definitionProvider: DefinitionProvider, + compilers: Compilers, implementationsInDependencySources: Map[String, Set[ClassLocation]], ): Option[InheritanceContext] = { for { symtab <- globalSymbolTableFor(source) } yield { calculateIndex(symtab, implementationsInPath).toGlobal( - definitionProvider, + compilers, implementationsInDependencySources, ) } diff --git a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala index b96533c605f..2e89a955c0b 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala @@ -13,6 +13,7 @@ import scala.util.control.NonFatal import scala.meta.internal.io.FileIO import scala.meta.internal.metals.Buffers import scala.meta.internal.metals.BuildTargets +import scala.meta.internal.metals.Compilers import scala.meta.internal.metals.DefinitionProvider import scala.meta.internal.metals.MetalsEnrichments._ import scala.meta.internal.metals.ReportContext @@ -53,6 +54,7 @@ final class ImplementationProvider( definitionProvider: DefinitionProvider, trees: Trees, scalaVersionSelector: ScalaVersionSelector, + compilers: Compilers )(implicit ec: ExecutionContext, rc: ReportContext) extends SemanticdbFeatureProvider { import ImplementationProvider._ @@ -186,7 +188,7 @@ final class ImplementationProvider( globalTable.globalContextFor( source, implementationsInPath.asScala.toMap, - definitionProvider, + compilers, implementationsInDependencySources.asScala.toMap, ) } else { diff --git a/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala b/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala index 0214ce4ff43..fb8e69654d9 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala @@ -6,7 +6,7 @@ import scala.collection.mutable import scala.concurrent.ExecutionContext import scala.concurrent.Future -import scala.meta.internal.metals.DefinitionProvider +import scala.meta.internal.metals.Compilers import scala.meta.internal.semanticdb.Scala._ import scala.meta.internal.semanticdb.SymbolInformation import scala.meta.io.AbsolutePath @@ -42,11 +42,11 @@ class InheritanceContext( } def toGlobal( - definitionProvider: DefinitionProvider, + compilers: Compilers, implementationsInDependencySources: Map[String, Set[ClassLocation]], ) = new GlobalInheritanceContext( findSymbol, - definitionProvider, + compilers, implementationsInDependencySources, inheritance, ) @@ -54,7 +54,7 @@ class InheritanceContext( class GlobalInheritanceContext( override val findSymbol: String => Option[SymbolInformation], - definitionProvider: DefinitionProvider, + compilers: Compilers, implementationsInDependencySources: Map[String, Set[ClassLocation]], localInheritance: Map[String, Set[ClassLocation]], ) extends InheritanceContext(findSymbol, localInheritance) { @@ -69,13 +69,14 @@ class GlobalInheritanceContext( val resolveGlobal = implementationsInDependencySources .getOrElse(shortName, Set.empty) - .collect { case loc @ ClassLocation(_, Some(file), Some(pos)) => - definitionProvider.definition(AbsolutePath(file), pos).map { - definition => - def dealiased = ImplementationProvider - .dealiasClass(definition.symbol, findSymbol) - if (definition.symbol == symbol || dealiased == symbol) Some(loc) - else None + .collect { case loc @ ClassLocation(sym, Some(file), _) => + compilers.findParents(AbsolutePath(file), sym).map{ res => + Option.when( + res.exists{ sym => + def dealiased = ImplementationProvider.dealiasClass(sym, findSymbol) + sym == symbol || dealiased == symbol + } + )(loc) } } Future diff --git a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala index 832f9f01d58..4c187df24a4 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala @@ -784,6 +784,15 @@ class Compilers( definition(params = params, token = token, findTypeDef = true) } + def findParents( + path: AbsolutePath, + symbol: String + ): Future[List[String]] = { + loadCompiler(path).map{ + _.findParents(symbol).asScala.map(_.asScala.toList) + }.getOrElse(Future.successful(Nil)) + } + private def definition( params: TextDocumentPositionParams, token: CancelToken, diff --git a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala index e2d605c7cdc..26936c0eb94 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala @@ -533,24 +533,6 @@ class MetalsLspService( ) } - private val implementationProvider: ImplementationProvider = - new ImplementationProvider( - semanticdbs, - folder, - definitionIndex, - buildTargets, - buffers, - definitionProvider, - trees, - scalaVersionSelector, - ) - - private val supermethods: Supermethods = new Supermethods( - languageClient, - definitionProvider, - implementationProvider, - ) - private val referencesProvider: ReferenceProvider = new ReferenceProvider( folder, semanticdbs, @@ -577,17 +559,6 @@ class MetalsLspService( isStatisticsEnabled = clientConfig.initialConfig.statistics.isTreeView ) - private val semanticDBIndexer: SemanticdbIndexer = new SemanticdbIndexer( - List( - referencesProvider, - implementationProvider, - testProvider, - classpathTreeIndex, - ), - buildTargets, - folder, - ) - private val formattingProvider: FormattingProvider = new FormattingProvider( folder, buffers, @@ -600,26 +571,6 @@ class MetalsLspService( buildTargets, ) - private val javaFormattingProvider: JavaFormattingProvider = - new JavaFormattingProvider( - buffers, - () => userConfig, - buildTargets, - ) - - private val callHierarchyProvider: CallHierarchyProvider = - new CallHierarchyProvider( - folder, - semanticdbs, - definitionProvider, - referencesProvider, - clientConfig.icons, - () => compilers, - trees, - buildTargets, - supermethods, - ) - private val javaHighlightProvider: JavaDocumentHighlightProvider = new JavaDocumentHighlightProvider( definitionProvider, @@ -695,6 +646,56 @@ class MetalsLspService( ) ) + private val javaFormattingProvider: JavaFormattingProvider = + new JavaFormattingProvider( + buffers, + () => userConfig, + buildTargets, + ) + + private val implementationProvider: ImplementationProvider = + new ImplementationProvider( + semanticdbs, + folder, + definitionIndex, + buildTargets, + buffers, + definitionProvider, + trees, + scalaVersionSelector, + compilers, + ) + + private val supermethods: Supermethods = new Supermethods( + languageClient, + definitionProvider, + implementationProvider, + ) + + private val semanticDBIndexer: SemanticdbIndexer = new SemanticdbIndexer( + List( + referencesProvider, + implementationProvider, + testProvider, + classpathTreeIndex, + ), + buildTargets, + folder, + ) + + private val callHierarchyProvider: CallHierarchyProvider = + new CallHierarchyProvider( + folder, + semanticdbs, + definitionProvider, + referencesProvider, + clientConfig.icons, + () => compilers, + trees, + buildTargets, + supermethods, + ) + private val renameProvider: RenameProvider = new RenameProvider( referencesProvider, implementationProvider, diff --git a/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java b/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java index af82134fa32..190b1b3197b 100644 --- a/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java +++ b/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java @@ -171,6 +171,10 @@ public CompletableFuture> syntheticDecorations(Synthet return CompletableFuture.completedFuture(Collections.emptyList()); } + public CompletableFuture> findParents(String symbol) { + return CompletableFuture.completedFuture(Collections.emptyList()); + } + /** * File was closed. */ diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala index 88e09c157d2..52984895f61 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala @@ -339,6 +339,17 @@ case class ScalaPresentationCompiler( ) { pc => new PcDefinitionProvider(pc.compiler(), params).definition() } } + override def findParents( + symbol: String + ): CompletableFuture[util.List[String]] = { + compilerAccess.withNonInterruptableCompiler(None)( + List.empty[String].asJava, + EmptyCancelToken + ) { pc => + pc.compiler().findParents(symbol).asJava + } + } + override def typeDefinition( params: OffsetParams ): CompletableFuture[DefinitionResult] = { diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/WorkspaceSymbolSearch.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/WorkspaceSymbolSearch.scala index 97a5a26d234..955565221de 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/WorkspaceSymbolSearch.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/WorkspaceSymbolSearch.scala @@ -10,10 +10,38 @@ import org.eclipse.{lsp4j => l} trait WorkspaceSymbolSearch { this: MetalsGlobal => + def findParents(symbol: String): List[String] = { + val index = symbol.lastIndexOf("/") + val pkgString = symbol.take(index + 1) + val pkg = packageSymbolFromString(pkgString).getOrElse( + throw new NoSuchElementException(pkgString) + ) + + def loop( + symbol: String, + acc: List[(String, Boolean)] + ): List[(String, Boolean)] = + if (symbol.isEmpty()) acc.reverse + else { + val newSymbol = symbol.takeWhile(c => c != '.' && c != '#') + val rest = symbol.drop(newSymbol.size) + loop(rest.drop(1), (newSymbol, rest.headOption.exists(_ == '#')) :: acc) + } + val names = + loop(symbol.drop(index + 1), List.empty) + val compilerSymbol = names.foldLeft(pkg) { case (sym, (name, isClass)) => + if (isClass) sym.info.member(TypeName(name)) + else sym.info.member(TermName(name)) + } + + compilerSymbol.parentSymbols.map(semanticdbSymbol) + } + class CompilerSearchVisitor( context: Context, visitMember: Symbol => Boolean ) extends SymbolSearchVisitor { + def visit(top: SymbolSearchCandidate): Int = { var added = 0 for { diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/ParentProvider.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/ParentProvider.scala new file mode 100644 index 00000000000..c02a3fbc340 --- /dev/null +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/ParentProvider.scala @@ -0,0 +1,53 @@ +package scala.meta.internal.pc + +import scala.util.control.NonFatal + +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Names.* +import dotty.tools.dotc.core.Symbols.* + +class ParentProvider(using Context): + private def toSymbols( + pkg: String, + parts: List[(String, Boolean)], + ): Option[Symbol] = + def loop(owner: Symbol, parts: List[(String, Boolean)]): Option[Symbol] = + parts match + case (head, isClass) :: tl => + val next = + if(isClass) owner.info.member(typeName(head)) + else owner.info.member(termName(head)) + if(next.exists) loop(next.symbol, tl) + else None + case Nil => Some(owner) + + val pkgSym = requiredPackage(pkg) + loop(pkgSym, parts) + end toSymbols + + def parents(symbol: String): List[String] = + val index = symbol.lastIndexOf("/") + val pkg = normalizePackage(symbol.take(index + 1)) + + def loop( + symbol: String, + acc: List[(String, Boolean)] + ): List[(String, Boolean)] = + if (symbol.isEmpty()) acc.reverse + else { + val newSymbol = symbol.takeWhile(c => c != '.' && c != '#') + val rest = symbol.drop(newSymbol.size) + loop(rest.drop(1), (newSymbol, rest.headOption.exists(_ == '#')) :: acc) + } + val names = + loop(symbol.drop(index + 1), List.empty) + + val foundSym = + try toSymbols(pkg, names) + catch + case NonFatal(e) => None + foundSym.toList.flatMap(_.ownersIterator).map(SemanticdbSymbols.symbolName) + end parents + + private def normalizePackage(pkg: String): String = + pkg.replace("/", ".").stripSuffix(".") diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala index 082eae1aab3..972415222d0 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala @@ -177,6 +177,14 @@ case class ScalaPresentationCompiler( def diagnosticsForDebuggingPurposes(): ju.List[String] = List[String]().asJava + override def findParents(symbol: String): ju.concurrent.CompletableFuture[ju.List[String]] = + compilerAccess.withNonInterruptableCompiler(None)( + List.empty[String].asJava, + EmptyCancelToken, + ) { access => + ParentProvider(using access.compiler().currentCtx).parents(symbol).asJava + } + def semanticdbTextDocument( filename: URI, code: String, From 1285672571e5dcd056e249cc93eee3183dbfdbbb Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Thu, 14 Dec 2023 15:23:30 +0100 Subject: [PATCH 16/26] delete position from classLocation --- .../db/migration/V5__Jar_type_hierarchy.sql | 3 +-- .../implementation/ClassLocation.scala | 3 --- .../ImplementationProvider.scala | 6 ++--- .../implementation/InheritanceContext.scala | 9 ++++---- .../meta/internal/metals/Compilers.scala | 12 +++++----- .../meta/internal/metals/JarTopLevels.scala | 22 +++++++++---------- .../scala/meta/internal/metals/Tables.scala | 3 ++- .../meta/internal/pc/ParentProvider.scala | 15 ++++++------- .../pc/ScalaPresentationCompiler.scala | 4 +++- .../internal/mtags/OverriddenSymbol.scala | 3 +-- .../internal/mtags/ScalaToplevelMtags.scala | 2 +- .../scala/tests/ImplementationLspSuite.scala | 4 ---- .../test/scala/tests/JarTopLevelsSuite.scala | 6 ++--- .../test/scala/tests/ScalaToplevelSuite.scala | 2 +- 14 files changed, 44 insertions(+), 50 deletions(-) diff --git a/metals/src/main/resources/db/migration/V5__Jar_type_hierarchy.sql b/metals/src/main/resources/db/migration/V5__Jar_type_hierarchy.sql index 030f7c9b89b..3e5561cc4d9 100644 --- a/metals/src/main/resources/db/migration/V5__Jar_type_hierarchy.sql +++ b/metals/src/main/resources/db/migration/V5__Jar_type_hierarchy.sql @@ -2,12 +2,11 @@ create table type_hierarchy( symbol varchar not null, parent_name varchar not null, - parent_name_offset int, path varchar not null, jar int, is_resolved bit, foreign key (jar) references indexed_jar (id) on delete cascade, - primary key (jar, path, symbol, parent_name, parent_name_offset) + primary key (jar, path, symbol, parent_name) ); create index type_hierarchy_jar on type_hierarchy(jar); diff --git a/metals/src/main/scala/scala/meta/internal/implementation/ClassLocation.scala b/metals/src/main/scala/scala/meta/internal/implementation/ClassLocation.scala index 932198384d2..6d6dd1e6d9b 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/ClassLocation.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/ClassLocation.scala @@ -5,7 +5,4 @@ import java.nio.file.Path case class ClassLocation( symbol: String, file: Option[Path], - // position of the reference to the overridden symbol for lazy resolution, e.g. - // class AClass extends @@BClass - pos: Option[Int] = None, ) diff --git a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala index 2e89a955c0b..f30b4df1b1a 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala @@ -54,7 +54,7 @@ final class ImplementationProvider( definitionProvider: DefinitionProvider, trees: Trees, scalaVersionSelector: ScalaVersionSelector, - compilers: Compilers + compilers: Compilers, )(implicit ec: ExecutionContext, rc: ReportContext) extends SemanticdbFeatureProvider { import ImplementationProvider._ @@ -110,10 +110,10 @@ final class ImplementationProvider( ClassLocation(overridesSymbol, Some(path.toNIO)) ) implementationsInDependencySources.compute(symbol, update(_, _)) - case UnresolvedOverriddenSymbol(name, pos) => + case UnresolvedOverriddenSymbol(name) => val update = createUpdate( - ClassLocation(overridesSymbol, Some(path.toNIO), Some(pos)) + ClassLocation(overridesSymbol, Some(path.toNIO)) ) implementationsInDependencySources.compute(name, update(_, _)) } diff --git a/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala b/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala index fb8e69654d9..fdda7e87df5 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala @@ -69,11 +69,12 @@ class GlobalInheritanceContext( val resolveGlobal = implementationsInDependencySources .getOrElse(shortName, Set.empty) - .collect { case loc @ ClassLocation(sym, Some(file), _) => - compilers.findParents(AbsolutePath(file), sym).map{ res => + .collect { case loc @ ClassLocation(sym, Some(file)) => + compilers.findParents(AbsolutePath(file), sym).map { res => Option.when( - res.exists{ sym => - def dealiased = ImplementationProvider.dealiasClass(sym, findSymbol) + res.exists { sym => + def dealiased = + ImplementationProvider.dealiasClass(sym, findSymbol) sym == symbol || dealiased == symbol } )(loc) diff --git a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala index 4c187df24a4..4eddd7c67eb 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala @@ -785,12 +785,14 @@ class Compilers( } def findParents( - path: AbsolutePath, - symbol: String + path: AbsolutePath, + symbol: String, ): Future[List[String]] = { - loadCompiler(path).map{ - _.findParents(symbol).asScala.map(_.asScala.toList) - }.getOrElse(Future.successful(Nil)) + loadCompiler(path) + .map { + _.findParents(symbol).asScala.map(_.asScala.toList) + } + .getOrElse(Future.successful(Nil)) } private def definition( diff --git a/metals/src/main/scala/scala/meta/internal/metals/JarTopLevels.scala b/metals/src/main/scala/scala/meta/internal/metals/JarTopLevels.scala index c388e0e8fda..bdecc52aec6 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/JarTopLevels.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/JarTopLevels.scala @@ -63,7 +63,7 @@ final class JarTopLevels(conn: () => Connection) { val toplevels = List.newBuilder[(AbsolutePath, String, OverriddenSymbol)] conn() .query( - """select th.symbol, th.parent_name, th.parent_name_offset, th.path, th.is_resolved + """select th.symbol, th.parent_name, th.path, th.is_resolved |from indexed_jar ij |left join type_hierarchy th |on ij.id=th.jar @@ -75,12 +75,11 @@ final class JarTopLevels(conn: () => Connection) { ) { val symbol = rs.getString(1) val parentName = rs.getString(2) - val parentOffset = rs.getInt(3) - val path = toPath(jar, rs.getString(4)) - val isResolved = rs.getBoolean(5) + val path = toPath(jar, rs.getString(3)) + val isResolved = rs.getBoolean(4) val overridden = if (isResolved) ResolvedOverriddenSymbol(parentName) - else UnresolvedOverriddenSymbol(parentName, parentOffset) + else UnresolvedOverriddenSymbol(parentName) toplevels += ((path, symbol, overridden)) } } @@ -190,7 +189,7 @@ final class JarTopLevels(conn: () => Connection) { var symbolStmt: PreparedStatement = null try { symbolStmt = conn().prepareStatement( - s"insert into type_hierarchy (symbol, parent_name, parent_name_offset, path, jar, is_resolved) values (?, ?, ?, ?, ?, ?)" + s"insert into type_hierarchy (symbol, parent_name, path, jar, is_resolved) values (?, ?, ?, ?, ?)" ) type_hierarchy.foreach { case (path, symbol, overridden) => symbolStmt.setString(1, symbol) @@ -198,14 +197,13 @@ final class JarTopLevels(conn: () => Connection) { case ResolvedOverriddenSymbol(name) => symbolStmt.setString(2, name) symbolStmt.setInt(3, 0) - symbolStmt.setBoolean(6, true) - case UnresolvedOverriddenSymbol(name, pos) => + symbolStmt.setBoolean(5, true) + case UnresolvedOverriddenSymbol(name) => symbolStmt.setString(2, name) - symbolStmt.setInt(3, pos) - symbolStmt.setBoolean(6, false) + symbolStmt.setBoolean(5, false) } - symbolStmt.setString(4, path.toString()) - symbolStmt.setInt(5, jar) + symbolStmt.setString(3, path.toString()) + symbolStmt.setInt(4, jar) symbolStmt.addBatch() } // Return number of rows inserted diff --git a/metals/src/main/scala/scala/meta/internal/metals/Tables.scala b/metals/src/main/scala/scala/meta/internal/metals/Tables.scala index 61e6accb1da..0864ff727d7 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Tables.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Tables.scala @@ -143,7 +143,8 @@ final class Tables( private def tryUrl(url: String): Connection = { val user = "sa" - val flyway = Flyway.configure.dataSource(url, user, null).load() + val flyway = + Flyway.configure.dataSource(url, user, null).cleanDisabled(false).load() migrateOrRestart(flyway) DriverManager.getConnection(url, user, null) } diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/ParentProvider.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/ParentProvider.scala index c02a3fbc340..da6af8b7d71 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/ParentProvider.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/ParentProvider.scala @@ -15,9 +15,9 @@ class ParentProvider(using Context): parts match case (head, isClass) :: tl => val next = - if(isClass) owner.info.member(typeName(head)) + if isClass then owner.info.member(typeName(head)) else owner.info.member(termName(head)) - if(next.exists) loop(next.symbol, tl) + if next.exists then loop(next.symbol, tl) else None case Nil => Some(owner) @@ -31,23 +31,22 @@ class ParentProvider(using Context): def loop( symbol: String, - acc: List[(String, Boolean)] + acc: List[(String, Boolean)], ): List[(String, Boolean)] = - if (symbol.isEmpty()) acc.reverse - else { + if symbol.isEmpty() then acc.reverse + else val newSymbol = symbol.takeWhile(c => c != '.' && c != '#') val rest = symbol.drop(newSymbol.size) loop(rest.drop(1), (newSymbol, rest.headOption.exists(_ == '#')) :: acc) - } val names = loop(symbol.drop(index + 1), List.empty) val foundSym = try toSymbols(pkg, names) - catch - case NonFatal(e) => None + catch case NonFatal(e) => None foundSym.toList.flatMap(_.ownersIterator).map(SemanticdbSymbols.symbolName) end parents private def normalizePackage(pkg: String): String = pkg.replace("/", ".").stripSuffix(".") +end ParentProvider diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala index 972415222d0..bd6550a3fb5 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala @@ -177,7 +177,9 @@ case class ScalaPresentationCompiler( def diagnosticsForDebuggingPurposes(): ju.List[String] = List[String]().asJava - override def findParents(symbol: String): ju.concurrent.CompletableFuture[ju.List[String]] = + override def findParents( + symbol: String + ): ju.concurrent.CompletableFuture[ju.List[String]] = compilerAccess.withNonInterruptableCompiler(None)( List.empty[String].asJava, EmptyCancelToken, diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/OverriddenSymbol.scala b/mtags/src/main/scala/scala/meta/internal/mtags/OverriddenSymbol.scala index 259f1c961bf..9f75858840c 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/OverriddenSymbol.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/OverriddenSymbol.scala @@ -1,6 +1,5 @@ package scala.meta.internal.mtags sealed trait OverriddenSymbol -case class UnresolvedOverriddenSymbol(name: String, pos: Int) - extends OverriddenSymbol +case class UnresolvedOverriddenSymbol(name: String) extends OverriddenSymbol case class ResolvedOverriddenSymbol(symbol: String) extends OverriddenSymbol diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala index a245c633264..fed7ad000ab 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala @@ -416,7 +416,7 @@ class ScalaToplevelMtags( withOwner(tmpl.owner) { addOverridden( overridden.reverse.map(id => - UnresolvedOverriddenSymbol(id.name, id.pos.start) + UnresolvedOverriddenSymbol(id.name) ) ) } diff --git a/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala b/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala index 52929fa444e..50e2437af67 100644 --- a/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala +++ b/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala @@ -584,11 +584,9 @@ class ImplementationLspSuite extends BaseRangesSuite("implementation") { |} |""".stripMargin, """|a/MySet# - |scala/Enumeration#ValueSet# |scala/Enumeration#ValueSet# |scala/collection/immutable/AbstractSet# |scala/collection/immutable/BitSet# - |scala/collection/immutable/BitSet# |scala/collection/immutable/BitSet.BitSet1# |scala/collection/immutable/BitSet.BitSet2# |scala/collection/immutable/BitSet.BitSetN# @@ -604,10 +602,8 @@ class ImplementationLspSuite extends BaseRangesSuite("implementation") { |scala/collection/immutable/Set.Set3# |scala/collection/immutable/Set.Set4# |scala/collection/immutable/SortedMapOps#ImmutableKeySortedSet# - |scala/collection/immutable/SortedMapOps#ImmutableKeySortedSet# |scala/collection/immutable/SortedSet# |scala/collection/immutable/TreeSet# - |scala/collection/immutable/TreeSet# |""".stripMargin, ) diff --git a/tests/unit/src/test/scala/tests/JarTopLevelsSuite.scala b/tests/unit/src/test/scala/tests/JarTopLevelsSuite.scala index 447a051f69b..c6444aee90d 100644 --- a/tests/unit/src/test/scala/tests/JarTopLevelsSuite.scala +++ b/tests/unit/src/test/scala/tests/JarTopLevelsSuite.scala @@ -35,7 +35,7 @@ class JarTopLevelsSuite extends BaseTablesSuite { val filePath = AbsolutePath(fs.getPath("/foo.scala")) val toplevels = List("foo" -> filePath) val overrides = - List((filePath, "foo/Hello#", UnresolvedOverriddenSymbol("AnyVal", 32))) + List((filePath, "foo/Hello#", UnresolvedOverriddenSymbol("AnyVal"))) jarSymbols.putJarIndexingInfo(jar1, toplevels, overrides) val resultOption = jarSymbols.getTopLevels(jar1) assert(resultOption.isDefined) @@ -56,7 +56,7 @@ class JarTopLevelsSuite extends BaseTablesSuite { val filePath = AbsolutePath(fs.getPath("/foo.scala")) val toplevels = List("foo" -> filePath) val overrides = - List((filePath, "foo/Hello#", UnresolvedOverriddenSymbol("AnyVal", 32))) + List((filePath, "foo/Hello#", UnresolvedOverriddenSymbol("AnyVal"))) jarSymbols.putJarIndexingInfo(jar, toplevels, overrides) } jarSymbols.deleteNotUsedTopLevels(Array(jar1, jar1)) @@ -77,7 +77,7 @@ class JarTopLevelsSuite extends BaseTablesSuite { val filePath = AbsolutePath(fs.getPath("/foo.scala")) val toplevels = List("foo" -> filePath) val overrides = - List((filePath, "foo/Hello#", UnresolvedOverriddenSymbol("AnyVal", 32))) + List((filePath, "foo/Hello#", UnresolvedOverriddenSymbol("AnyVal"))) var jarStmt: PreparedStatement = null val jar = diff --git a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala index d1cb1164103..3eab6aef238 100644 --- a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala +++ b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala @@ -662,7 +662,7 @@ class ScalaToplevelSuite extends BaseSuite { symbols .map { case ResolvedOverriddenSymbol(symbol) => symbol - case UnresolvedOverriddenSymbol(name, _) => name + case UnresolvedOverriddenSymbol(name) => name } .mkString(", ") s"$symbol -> $overridden" From f280dd69cf826773c9f58df899b462459c888e49 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Tue, 2 Jan 2024 15:52:24 +0100 Subject: [PATCH 17/26] delete primary key in type hierarchy --- .../main/resources/db/migration/V5__Jar_type_hierarchy.sql | 3 +-- .../scala/meta/internal/mtags/ScalaToplevelMtags.scala | 7 ++++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/metals/src/main/resources/db/migration/V5__Jar_type_hierarchy.sql b/metals/src/main/resources/db/migration/V5__Jar_type_hierarchy.sql index 3e5561cc4d9..080bd9dcff5 100644 --- a/metals/src/main/resources/db/migration/V5__Jar_type_hierarchy.sql +++ b/metals/src/main/resources/db/migration/V5__Jar_type_hierarchy.sql @@ -5,8 +5,7 @@ create table type_hierarchy( path varchar not null, jar int, is_resolved bit, - foreign key (jar) references indexed_jar (id) on delete cascade, - primary key (jar, path, symbol, parent_name) + foreign key (jar) references indexed_jar (id) on delete cascade ); create index type_hierarchy_jar on type_hierarchy(jar); diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala index 8337aea66df..377d1796b51 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala @@ -476,9 +476,10 @@ class ScalaToplevelMtags( expectTemplate.map(tmpl => withOwner(tmpl.owner) { addOverridden( - overridden.reverse.map(id => - UnresolvedOverriddenSymbol(id.name) - ) + overridden.reverse + .map(_.name) + .distinct + .map(UnresolvedOverriddenSymbol(_)) ) } ) From 5a903ad7984439185d83a7c8503fe659d263ad6d Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Tue, 2 Jan 2024 16:06:22 +0100 Subject: [PATCH 18/26] fix find parents for Scala 3 --- .../meta/internal/pc/ParentProvider.scala | 8 ++- .../feature/ImplementationCrossLspSuite.scala | 56 +++++++++++++++++ .../scala/tests/BaseImplementationSuite.scala | 60 +++++++++++++++++++ .../scala/tests/ImplementationLspSuite.scala | 54 +---------------- 4 files changed, 124 insertions(+), 54 deletions(-) create mode 100644 tests/slow/src/test/scala/tests/feature/ImplementationCrossLspSuite.scala create mode 100644 tests/unit/src/main/scala/tests/BaseImplementationSuite.scala diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/ParentProvider.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/ParentProvider.scala index da6af8b7d71..1af98c0f7a4 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/ParentProvider.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/ParentProvider.scala @@ -44,7 +44,13 @@ class ParentProvider(using Context): val foundSym = try toSymbols(pkg, names) catch case NonFatal(e) => None - foundSym.toList.flatMap(_.ownersIterator).map(SemanticdbSymbols.symbolName) + + for + sym <- foundSym.toList + classSym = if sym.isClass then sym else sym.moduleClass + if classSym.isClass + parent <- classSym.asClass.parentSyms + yield SemanticdbSymbols.symbolName(parent) end parents private def normalizePackage(pkg: String): String = diff --git a/tests/slow/src/test/scala/tests/feature/ImplementationCrossLspSuite.scala b/tests/slow/src/test/scala/tests/feature/ImplementationCrossLspSuite.scala new file mode 100644 index 00000000000..37fc95ad9f9 --- /dev/null +++ b/tests/slow/src/test/scala/tests/feature/ImplementationCrossLspSuite.scala @@ -0,0 +1,56 @@ +package tests.feature + +import scala.meta.internal.metals.{BuildInfo => V} + +import tests.BaseImplementationSuite + +class ImplementationCrossLspSuite + extends BaseImplementationSuite("implementation-cross") { + + checkSymbols( + "seqFactory", + """package a + |import scala.collection.SeqFactory + |import scala.collection.mutable + | + |object A extends Seq@@Factory[List] { + | + | override def from[A](source: IterableOnce[A]): List[A] = ??? + | + | override def empty[A]: List[A] = ??? + | + | override def newBuilder[A]: mutable.Builder[A, List[A]] = ??? + | + | + |} + |""".stripMargin, + """|a/A. + |scala/collection/ClassTagSeqFactory.AnySeqDelegate# + |scala/collection/IndexedSeq. + |scala/collection/LinearSeq. + |scala/collection/Seq. + |scala/collection/SeqFactory.Delegate# + |scala/collection/StrictOptimizedSeqFactory# + |scala/collection/immutable/IndexedSeq. + |scala/collection/immutable/LazyList. + |scala/collection/immutable/LinearSeq. + |scala/collection/immutable/List. + |scala/collection/immutable/Queue. + |scala/collection/immutable/Seq. + |scala/collection/immutable/Stream. + |scala/collection/immutable/Vector. + |scala/collection/mutable/ArrayBuffer. + |scala/collection/mutable/ArrayDeque. + |scala/collection/mutable/Buffer. + |scala/collection/mutable/IndexedBuffer. + |scala/collection/mutable/IndexedSeq. + |scala/collection/mutable/ListBuffer. + |scala/collection/mutable/Queue. + |scala/collection/mutable/Seq. + |scala/collection/mutable/Stack. + |scala/jdk/AnyAccumulator. + |""".stripMargin, + scalaVersion = V.scala3, + ) + +} diff --git a/tests/unit/src/main/scala/tests/BaseImplementationSuite.scala b/tests/unit/src/main/scala/tests/BaseImplementationSuite.scala new file mode 100644 index 00000000000..3626bbcb383 --- /dev/null +++ b/tests/unit/src/main/scala/tests/BaseImplementationSuite.scala @@ -0,0 +1,60 @@ +package tests + +import scala.concurrent.Future + +import scala.meta.internal.metals.MetalsEnrichments._ + +abstract class BaseImplementationSuite(name: String) + extends BaseRangesSuite(name) { + + override def assertCheck( + filename: String, + edit: String, + expected: Map[String, String], + base: Map[String, String], + ): Future[Unit] = { + server.assertImplementation( + filename, + edit, + expected.toMap, + base.toMap, + ) + } + + def checkSymbols( + name: String, + fileContents: String, + expectedSymbols: String, + scalaVersion: String = BuildInfo.scalaVersion, + ): Unit = + test(name) { + val fileName = "a/src/main/scala/a/Main.scala" + cleanWorkspace() + for { + _ <- initialize( + s"""/metals.json + |{"a": + | { + | "scalaVersion" : "$scalaVersion" + | } + |} + |/$fileName + |${fileContents.replace("@@", "")} + """.stripMargin + ) + _ <- server.didOpen(fileName) + locations <- server.implementation(fileName, fileContents) + definitions <- + Future.sequence( + locations.map(location => + server.server.definitionResult( + location.toTextDocumentPositionParams + ) + ) + ) + symbols = definitions.map(_.symbol).sorted + _ = assertNoDiff(symbols.mkString("\n"), expectedSymbols) + _ <- server.shutdown() + } yield () + } +} diff --git a/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala b/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala index 9894057cae9..09a3bd780f2 100644 --- a/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala +++ b/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala @@ -1,9 +1,6 @@ package tests -import scala.concurrent.Future -import scala.meta.internal.metals.MetalsEnrichments._ - -class ImplementationLspSuite extends BaseRangesSuite("implementation") { +class ImplementationLspSuite extends BaseImplementationSuite("implementation") { check( "basic", @@ -642,53 +639,4 @@ class ImplementationLspSuite extends BaseRangesSuite("implementation") { override protected def libraryDependencies: List[String] = List("org.scalatest::scalatest:3.2.16", "io.circe::circe-generic:0.12.0") - override def assertCheck( - filename: String, - edit: String, - expected: Map[String, String], - base: Map[String, String], - ): Future[Unit] = { - server.assertImplementation( - filename, - edit, - expected.toMap, - base.toMap, - ) - } - - def checkSymbols( - name: String, - fileContents: String, - expectedSymbols: String, - ): Unit = - test(name) { - val fileName = "a/src/main/scala/a/Main.scala" - cleanWorkspace() - for { - _ <- initialize( - s"""/metals.json - |{"a": - | { - | "scalaVersion" : "${BuildInfo.scalaVersion}" - | } - |} - |/$fileName - |${fileContents.replace("@@", "")} - """.stripMargin - ) - _ <- server.didOpen(fileName) - locations <- server.implementation(fileName, fileContents) - definitions <- - Future.sequence( - locations.map(location => - server.server.definitionResult( - location.toTextDocumentPositionParams - ) - ) - ) - symbols = definitions.map(_.symbol).sorted - _ = assertNoDiff(symbols.mkString("\n"), expectedSymbols) - _ <- server.shutdown() - } yield () - } } From c0299e5e378a9ccc78d5a3ede766660f19c1a022 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Tue, 16 Jan 2024 12:40:05 +0100 Subject: [PATCH 19/26] remove usage of global symbol table from go to implementation --- .../implementation/GlobalClassTable.scala | 68 ---- .../ImplementationProvider.scala | 371 ++++++++++-------- .../implementation/InheritanceContext.scala | 44 +-- .../meta/internal/metals/Compilers.scala | 22 +- .../scala/meta/pc/PcSymbolInformation.java | 13 + .../scala/meta/pc/PresentationCompiler.java | 2 +- .../meta/internal/metals/ReportContext.scala | 5 +- .../internal/pc/PcSymbolInformation.scala | 63 +++ .../pc/ScalaPresentationCompiler.scala | 14 +- .../internal/pc/WorkspaceSymbolSearch.scala | 45 ++- .../internal/pc/completions/Completions.scala | 26 +- .../meta/internal/pc/ParentProvider.scala | 58 --- .../pc/ScalaPresentationCompiler.scala | 13 +- .../pc/SymbolInformationProvider.scala | 109 +++++ .../internal/mtags/ScalaToplevelMtags.scala | 79 +++- .../feature/ImplementationCrossLspSuite.scala | 32 ++ .../main/scala/tests/BaseRangesSuite.scala | 7 +- .../scala/tests/ImplementationLspSuite.scala | 22 +- .../test/scala/tests/ScalaToplevelSuite.scala | 21 +- 19 files changed, 650 insertions(+), 364 deletions(-) create mode 100644 mtags-interfaces/src/main/java/scala/meta/pc/PcSymbolInformation.java create mode 100644 mtags-shared/src/main/scala/scala/meta/internal/pc/PcSymbolInformation.scala delete mode 100644 mtags/src/main/scala-3/scala/meta/internal/pc/ParentProvider.scala create mode 100644 mtags/src/main/scala-3/scala/meta/internal/pc/SymbolInformationProvider.scala diff --git a/metals/src/main/scala/scala/meta/internal/implementation/GlobalClassTable.scala b/metals/src/main/scala/scala/meta/internal/implementation/GlobalClassTable.scala index 57cd19f50a9..6295bda9f1e 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/GlobalClassTable.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/GlobalClassTable.scala @@ -2,11 +2,8 @@ package scala.meta.internal.implementation import java.nio.file.Path import scala.collection.concurrent.TrieMap -import scala.collection.mutable import scala.meta.internal.metals.BuildTargets -import scala.meta.internal.metals.Compilers -import scala.meta.internal.semanticdb.SymbolInformation import scala.meta.internal.symtab.GlobalSymbolTable import scala.meta.io.AbsolutePath import scala.meta.io.Classpath @@ -16,30 +13,11 @@ import ch.epfl.scala.bsp4j.BuildTargetIdentifier final class GlobalClassTable( buildTargets: BuildTargets ) { - - import ImplementationProvider._ - type ImplementationCache = Map[Path, Map[String, Set[ClassLocation]]] private val buildTargetsIndexes = TrieMap.empty[BuildTargetIdentifier, GlobalSymbolTable] - def globalContextFor( - source: AbsolutePath, - implementationsInPath: ImplementationCache, - compilers: Compilers, - implementationsInDependencySources: Map[String, Set[ClassLocation]], - ): Option[InheritanceContext] = { - for { - symtab <- globalSymbolTableFor(source) - } yield { - calculateIndex(symtab, implementationsInPath).toGlobal( - compilers, - implementationsInDependencySources, - ) - } - } - def globalSymbolTableFor( source: AbsolutePath ): Option[GlobalSymbolTable] = @@ -56,50 +34,4 @@ final class GlobalClassTable( } } - private def calculateIndex( - symTab: GlobalSymbolTable, - implementationsInPath: ImplementationCache, - ): InheritanceContext = { - val context = InheritanceContext.fromDefinitions( - symTab.safeInfo, - implementationsInPath.toMap, - ) - val symbolsInformation = for { - classSymbol <- context.allClassSymbols - classInfo <- symTab.safeInfo(classSymbol) - } yield classInfo - - calculateInheritance(symbolsInformation, context, symTab) - - } - - private def calculateInheritance( - classpathClassInfos: Set[SymbolInformation], - context: InheritanceContext, - symTab: GlobalSymbolTable, - ): InheritanceContext = { - val results = new mutable.ListBuffer[(String, ClassLocation)] - val calculated = mutable.Set.empty[String] - var infos = classpathClassInfos - - while (infos.nonEmpty) { - calculated ++= infos.map(_.symbol) - - val allParents = infos.flatMap { info => - ImplementationProvider.parentsFromSignature( - info.symbol, - info.signature, - None, - ) - } - results ++= allParents - infos = (allParents.map(_._1) -- calculated).flatMap(symTab.safeInfo) - } - - val inheritance = results.groupBy(_._1).map { case (symbol, locations) => - symbol -> locations.map(_._2).toSet - } - context.withClasspathContext(inheritance) - } - } diff --git a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala index d1d48f6aa71..03df44f2403 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala @@ -30,6 +30,9 @@ import scala.meta.internal.mtags.SymbolDefinition import scala.meta.internal.mtags.UnresolvedOverriddenSymbol import scala.meta.internal.mtags.{Symbol => MSymbol} import scala.meta.internal.parsing.Trees +import scala.meta.internal.pc.PcSymbolInformation +import scala.meta.internal.pc.PcSymbolKind +import scala.meta.internal.pc.PcSymbolProperty import scala.meta.internal.semanticdb.ClassSignature import scala.meta.internal.semanticdb.Scala._ import scala.meta.internal.semanticdb.Signature @@ -171,43 +174,45 @@ final class ImplementationProvider( // 1. Search locally for symbol // 2. Search inside workspace // 3. Search classpath via GlobalSymbolTable - val symbolSearch = defaultSymbolSearch(source, currentDocument) val sym = symbolOccurrence.symbol - val dealiased = - if (sym.desc.isType) - dealiasClass(sym, symbolSearch) - else sym - - val isWorkspaceSymbol = - (source.isWorkspaceSource(workspace) && - currentDocument.definesSymbol(dealiased)) || - findSymbolDefinition(dealiased).exists( - _.path.isWorkspaceSource(workspace) + val dealised = + if (sym.desc.isType) { + symbolInfo(currentDocument, source, sym).map( + _.map(_.dealisedSymbol).getOrElse(sym) ) + } else Future.successful(sym) + dealised.flatMap { dealisedSymbol => + val isWorkspaceSymbol = + (source.isWorkspaceSource(workspace) && + currentDocument.definesSymbol(dealisedSymbol)) || + findSymbolDefinition(dealisedSymbol).exists( + _.path.isWorkspaceSource(workspace) + ) - val inheritanceContext = if (!isWorkspaceSymbol) { - // symbol is not in workspace, we search both workspace and dependencies for it - globalTable.globalContextFor( + val inheritanceContext: InheritanceContext = + if (!isWorkspaceSymbol) { + // symbol is not in workspace, we search both workspace and dependencies for it + InheritanceContext + .fromDefinitions(implementationsInPath.asScala.toMap) + .toGlobal( + compilers, + implementationsInDependencySources.asScala.toMap, + ) + } else { + // symbol in workspace we search the workspace for it only + InheritanceContext.fromDefinitions( + implementationsInPath.asScala.toMap + ) + } + symbolLocationsFromContext( + dealisedSymbol, + currentDocument, source, - implementationsInPath.asScala.toMap, - compilers, - implementationsInDependencySources.asScala.toMap, - ) - } else { - // symbol in workspace we search the workspace for it only - Some( - InheritanceContext.fromDefinitions( - symbolSearch, - implementationsInPath.asScala.toMap, - ) + inheritanceContext, ) } - symbolLocationsFromContext( - dealiased, - source, - inheritanceContext, - ) } + Future.sequence(locations).map { _.flatten.toList } @@ -311,25 +316,57 @@ final class ImplementationProvider( } private def symbolLocationsFromContext( - symbol: String, + dealised: String, + textDocument: TextDocument, source: AbsolutePath, - inheritanceContext: Option[InheritanceContext], + inheritanceContext: InheritanceContext, ): Future[Seq[Location]] = { def findImplementationSymbol( - parentSymbolInfo: SymbolInformation, - implDocument: TextDocument, + info: PcSymbolInformation, + classSymbol: String, + textDocument: TextDocument, implReal: ClassLocation, - ): Option[String] = { - if (isClassLike(parentSymbolInfo)) - Some(implReal.symbol) + ): Option[Future[String]] = { + def findSymbolInDoc(symbol: String) = + textDocument.symbols.find(_.symbol == symbol) + if (classLikeKinds(info.kind)) Some(Future(implReal.symbol)) else { - val symbolSearch = defaultSymbolSearch(source, implDocument) - MethodImplementation.findInherited( - parentSymbolInfo, - implReal, - symbolSearch, - ) + def tryFromDoc = + for { + classInfo <- findSymbolInDoc(implReal.symbol) + declarations <- classInfo.signature match { + case ClassSignature(_, _, _, declarations) => declarations + case _ => None + } + found <- declarations.symlinks.collectFirst { sym => + findSymbolInDoc(sym) match { + case Some(implInfo) + if implInfo.overriddenSymbols.contains(info.symbol) => + sym + } + } + } yield Future.successful(found) + def pcSearch = { + val symbol = + s"${implReal.symbol}${info.symbol.stripPrefix(classSymbol)}" + implReal.file + .map(path => + compilers + .infoAll(AbsolutePath(path), symbol) + .map { allFound => + allFound + .find(implInfo => implInfo.overridden.contains(info.symbol)) + .map(_.symbol) + .getOrElse(symbol) + } + ) + .getOrElse(Future.successful(symbol)) + } + tryFromDoc.orElse { + if (implReal.symbol.isLocal) None + else Some(pcSearch) + } } } @@ -338,70 +375,84 @@ final class ImplementationProvider( def findImplementationLocations( files: Set[Path], locationsByFile: Map[Path, Set[ClassLocation]], - parentSymbol: SymbolInformation, - ) = - Future { + parentSymbol: PcSymbolInformation, + classSymbol: String, + ) = Future.sequence({ + for { + file <- files + locations = locationsByFile(file) + implPath = AbsolutePath(file) + implDocument <- findSemanticdb(implPath, handleJars = true).toList + } yield { for { - file <- files - locations = locationsByFile(file) - implPath = AbsolutePath(file) - implDocument <- findSemanticdb(implPath, handleJars = true) - implLocation <- locations - implSymbol <- findImplementationSymbol( - parentSymbol, - implDocument, - implLocation, - ) - if !findSymbol(implDocument, implSymbol).exists( - // we should not show types if we are looking for a class implementations - sym => sym.isType && !parentSymbol.isType - ) - implOccurrence <- findDefOccurrence( - implDocument, - implSymbol, - source, - ) - range <- implOccurrence.range - revised <- - if (implPath.isJarFileSystem) { - Some(range.toLsp) - } else { - val distance = buffer.tokenEditDistance( - implPath, - implDocument.text, - trees, + symbols <- Future.sequence( + locations.flatMap( + findImplementationSymbol( + parentSymbol, + classSymbol, + implDocument, + _, ) - distance.toRevised(range.toLsp) - } - } { allLocations.add(new Location(file.toUri.toString, revised)) } + ) + ) + } yield { + for { + sym <- symbols + symInfo <- implDocument.symbols.find(_.symbol == sym) + if (!symInfo.isType || parentSymbol.kind == PcSymbolKind.TYPE) + implOccurrence <- findDefOccurrence( + implDocument, + sym, + implPath, + ).toList + range <- implOccurrence.range + revised <- + if (implPath.isJarFileSystem) { + Some(range.toLsp) + } else { + val distance = buffer.tokenEditDistance( + implPath, + implDocument.text, + trees, + ) + distance.toRevised(range.toLsp) + } + } { allLocations.add(new Location(file.toUri.toString, revised)) } + } } + }) lazy val cores = Runtime.getRuntime().availableProcessors() - val splitJobs = for { - classContext <- inheritanceContext.toIterable - parentSymbol <- classContext.findSymbol(symbol).toIterable - symbolClass <- classFromSymbol(parentSymbol, classContext.findSymbol) - } yield { - for { - locationsByFile <- findImplementation( - symbolClass.symbol, - classContext, - source.toNIO, - ) - files = locationsByFile.keySet.grouped( - Math.max(locationsByFile.size / cores, 1) - ) - collected <- - Future.sequence( - files.map( - findImplementationLocations(_, locationsByFile, parentSymbol) + val splitJobs = + symbolInfo(textDocument, source, dealised).flatMap { optSymbolInfo => + (for { + symbolInfo <- optSymbolInfo + symbolClass <- classFromSymbol(symbolInfo) + } yield { + for { + locationsByFile <- findImplementation( + symbolClass, + inheritanceContext, + source.toNIO, ) - ) - } yield collected - } - Future.sequence(splitJobs).map { _ => - allLocations.asScala.toSeq - } + files = locationsByFile.keySet.grouped( + Math.max(locationsByFile.size / cores, 1) + ) + collected <- + Future.sequence( + files.map( + findImplementationLocations( + _, + locationsByFile, + symbolInfo, + symbolClass, + ) + ) + ) + } yield collected + }).getOrElse(Future.successful(Iterator.empty)) + } + splitJobs.map(_ => allLocations.asScala.toSeq) } private def findSemanticdb( @@ -501,18 +552,9 @@ final class ImplementationProvider( } } - private def classFromSymbol( - info: SymbolInformation, - findSymbol: String => Option[SymbolInformation], - ): Iterable[SymbolInformation] = { - val classInfo = if (isClassLike(info)) { - Some(info) - } else { - findSymbol(info.symbol.owner) - .filter(info => isClassLike(info)) - } - classInfo.map(inf => dealiasClass(inf, findSymbol)) - } + private def classFromSymbol(info: PcSymbolInformation): Option[String] = + if (classLikeKinds(info.kind)) Some(info.dealisedSymbol) + else info.classOwner private def findDefOccurrence( semanticDb: TextDocument, @@ -531,6 +573,61 @@ final class ImplementationProvider( .find(isDefinitionOccurrence) ) } + + private def symbolInfo( + textDocument: TextDocument, + source: AbsolutePath, + symbol: String, + ): Future[Option[PcSymbolInformation]] = + if (symbol.isLocal) { + (for { + info <- textDocument.symbols.find(_.symbol == symbol) + } yield { + info.signature match { + case typeSig: TypeSignature => + typeSig.upperBound match { + case tr: TypeRef => + symbolInfo(textDocument, source, tr.symbol).map( + _.map(_.copy(symbol = symbol)) + ) + case _ => Future.successful(None) + } + case _ => Future.successful(Some(toPcSymbolInfo(textDocument, info))) + } + }).getOrElse(Future.successful(None)) + } else compilers.info(source, symbol) + + private def toPcSymbolInfo( + textDocument: TextDocument, + info: SymbolInformation, + ): PcSymbolInformation = { + val parents = + info.signature match { + case ClassSignature(_, parents, _, _) => + parents.collect { case t: TypeRef => t.symbol }.toList + case _ => Nil + } + val classOwner = + textDocument.symbols.collectFirst { classInfo => + classInfo.signature match { + case ClassSignature(_, _, _, declarations) + if declarations.exists(_.symlinks.contains(info.symbol)) => + classInfo.symbol + } + } + + PcSymbolInformation( + symbol = info.symbol, + kind = PcSymbolKind.values + .find(_.id == info.kind.value) + .getOrElse(PcSymbolKind.UNKNOWN_KIND), + parents = parents, + dealisedSymbol = info.symbol, + classOwner = classOwner, + overridden = info.overriddenSymbols.toList, + properties = if (info.isAbstract) List(PcSymbolProperty.ABSTRACT) else Nil, + ) + } } object ImplementationProvider { @@ -544,47 +641,6 @@ object ImplementationProvider { } } - def dealiasClass( - symbol: String, - findSymbol: String => Option[SymbolInformation], - ): String = { - if (symbol.desc.isType) { - findSymbol(symbol) - .map { inf => - val isAbstractType = inf.isAbstract && inf.isType - // abstract type will always have Any as upper bound - if (isAbstractType) symbol - else dealiasClass(inf, findSymbol).symbol - - } - .getOrElse(symbol) - } else { - symbol - } - } - - def dealiasClass( - info: SymbolInformation, - findSymbol: String => Option[SymbolInformation], - ): SymbolInformation = { - if (info.isType) { - info.signature match { - case ts: TypeSignature => - ts.upperBound match { - case tr: TypeRef => - findSymbol(tr.symbol) - .map(dealiasClass(_, findSymbol)) - .getOrElse(info) - case _ => - info - } - case _ => info - } - } else { - info - } - } - private def findSymbol( semanticDb: TextDocument, symbol: String, @@ -633,4 +689,11 @@ object ImplementationProvider { def isClassLike(info: SymbolInformation): Boolean = info.isObject || info.isClass || info.isTrait || info.isInterface + val classLikeKinds: Set[PcSymbolKind.Value] = Set( + PcSymbolKind.OBJECT, + PcSymbolKind.CLASS, + PcSymbolKind.TRAIT, + PcSymbolKind.INTERFACE, + ) + } diff --git a/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala b/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala index fdda7e87df5..b696fab0b97 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala @@ -8,13 +8,9 @@ import scala.concurrent.Future import scala.meta.internal.metals.Compilers import scala.meta.internal.semanticdb.Scala._ -import scala.meta.internal.semanticdb.SymbolInformation import scala.meta.io.AbsolutePath -class InheritanceContext( - val findSymbol: String => Option[SymbolInformation], - inheritance: Map[String, Set[ClassLocation]], -) { +class InheritanceContext(inheritance: Map[String, Set[ClassLocation]]) { def allClassSymbols = inheritance.keySet @@ -26,26 +22,10 @@ class InheritanceContext( protected def getWorkspaceLocations(symbol: String): Set[ClassLocation] = inheritance.getOrElse(symbol, Set.empty) - def withClasspathContext( - classpathInheritance: Map[String, Set[ClassLocation]] - ): InheritanceContext = { - val newInheritance = mutable.Map(inheritance.toSeq: _*) - for { (symbol, locations) <- classpathInheritance } { - val newLocations = - newInheritance.getOrElse(symbol, Set.empty) ++ locations - newInheritance += symbol -> newLocations - } - new InheritanceContext( - findSymbol, - newInheritance.toMap, - ) - } - def toGlobal( compilers: Compilers, implementationsInDependencySources: Map[String, Set[ClassLocation]], ) = new GlobalInheritanceContext( - findSymbol, compilers, implementationsInDependencySources, inheritance, @@ -53,11 +33,10 @@ class InheritanceContext( } class GlobalInheritanceContext( - override val findSymbol: String => Option[SymbolInformation], compilers: Compilers, implementationsInDependencySources: Map[String, Set[ClassLocation]], localInheritance: Map[String, Set[ClassLocation]], -) extends InheritanceContext(findSymbol, localInheritance) { +) extends InheritanceContext(localInheritance) { override def getLocations( symbol: String )(implicit ec: ExecutionContext): Future[Set[ClassLocation]] = { @@ -70,14 +49,10 @@ class GlobalInheritanceContext( implementationsInDependencySources .getOrElse(shortName, Set.empty) .collect { case loc @ ClassLocation(sym, Some(file)) => - compilers.findParents(AbsolutePath(file), sym).map { res => - Option.when( - res.exists { sym => - def dealiased = - ImplementationProvider.dealiasClass(sym, findSymbol) - sym == symbol || dealiased == symbol - } - )(loc) + compilers.info(AbsolutePath(file), sym).map { + case Some(symInfo) if symInfo.parents.contains(symbol) => Some(loc) + case Some(symInfo) if symInfo.dealisedSymbol == symbol && symInfo.symbol != symbol => Some(loc) + case _ => None } } Future @@ -90,10 +65,7 @@ class GlobalInheritanceContext( object InheritanceContext { - def fromDefinitions( - findSymbol: String => Option[SymbolInformation], - localDefinitions: Map[Path, Map[String, Set[ClassLocation]]], - ): InheritanceContext = { + def fromDefinitions(localDefinitions: Map[Path, Map[String, Set[ClassLocation]]]): InheritanceContext = { val inheritance = mutable.Map .empty[String, Set[ClassLocation]] for { @@ -103,6 +75,6 @@ object InheritanceContext { val updated = inheritance.getOrElse(symbol, Set.empty) ++ locations inheritance += symbol -> updated } - new InheritanceContext(findSymbol, inheritance.toMap) + new InheritanceContext(inheritance.toMap) } } diff --git a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala index 04eb00bdba5..b179928c4a8 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala @@ -25,6 +25,7 @@ import scala.meta.internal.parsing.Trees import scala.meta.internal.pc.EmptySymbolSearch import scala.meta.internal.pc.JavaPresentationCompiler import scala.meta.internal.pc.LogMessages +import scala.meta.internal.pc.PcSymbolInformation import scala.meta.internal.pc.ScalaPresentationCompiler import scala.meta.internal.worksheets.WorksheetPcData import scala.meta.internal.worksheets.WorksheetProvider @@ -784,17 +785,20 @@ class Compilers( definition(params = params, token = token, findTypeDef = true) } - def findParents( + def infoAll( path: AbsolutePath, symbol: String, - ): Future[List[String]] = { - loadCompiler(path) - .map { - _.findParents(symbol).asScala.map(_.asScala.toList) - } - .getOrElse(Future.successful(Nil)) + ): Future[Seq[PcSymbolInformation]] = { + loadCompiler(path, forceScala = true) + .map(_.info(symbol).asScala.map(_.asScala.toSeq.map(PcSymbolInformation.from))) + .getOrElse(Future(Nil)) } + def info( + path: AbsolutePath, + symbol: String, + ): Future[Option[PcSymbolInformation]] = infoAll(path, symbol).map(_.find(_.symbol == symbol)) + private def definition( params: TextDocumentPositionParams, token: CancelToken, @@ -851,7 +855,8 @@ class Compilers( } def loadCompiler( - path: AbsolutePath + path: AbsolutePath, + forceScala: Boolean = false ): Option[PresentationCompiler] = { def fromBuildTarget: Option[PresentationCompiler] = { @@ -862,6 +867,7 @@ class Compilers( case None => Some(fallbackCompiler(path)) case Some(value) => if (path.isScalaFilename) loadCompiler(value) + else if (path.isJavaFilename && forceScala) loadCompiler(value).orElse(loadJavaCompiler(value)) else if (path.isJavaFilename) loadJavaCompiler(value) else None } diff --git a/mtags-interfaces/src/main/java/scala/meta/pc/PcSymbolInformation.java b/mtags-interfaces/src/main/java/scala/meta/pc/PcSymbolInformation.java new file mode 100644 index 00000000000..e8a67b19ef2 --- /dev/null +++ b/mtags-interfaces/src/main/java/scala/meta/pc/PcSymbolInformation.java @@ -0,0 +1,13 @@ +package scala.meta.pc; + +import java.util.List; + +public interface PcSymbolInformation { + String symbol(); + String kindString(); + List parentsList(); + String dealisedSymbol(); + String classOwnerString(); + List overriddenList(); + List propertiesList(); +} diff --git a/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java b/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java index 190b1b3197b..d63bc78433e 100644 --- a/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java +++ b/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java @@ -171,7 +171,7 @@ public CompletableFuture> syntheticDecorations(Synthet return CompletableFuture.completedFuture(Collections.emptyList()); } - public CompletableFuture> findParents(String symbol) { + public CompletableFuture> info(String symbol) { return CompletableFuture.completedFuture(Collections.emptyList()); } diff --git a/mtags-shared/src/main/scala/scala/meta/internal/metals/ReportContext.scala b/mtags-shared/src/main/scala/scala/meta/internal/metals/ReportContext.scala index cba45550521..daea359efff 100644 --- a/mtags-shared/src/main/scala/scala/meta/internal/metals/ReportContext.scala +++ b/mtags-shared/src/main/scala/scala/meta/internal/metals/ReportContext.scala @@ -6,6 +6,7 @@ import java.nio.file.Paths import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicReference +import scala.util.Try import scala.util.matching.Regex import scala.meta.internal.metals.utils.LimitedFilesManager @@ -139,9 +140,11 @@ class StdReporter( } yield duplicate optDuplicate.orElse { + Try { path.createDirectories() path.writeText(sanitize(report.fullText(withIdAndSummary = true))) - Some(path) + path + }.toOption } } diff --git a/mtags-shared/src/main/scala/scala/meta/internal/pc/PcSymbolInformation.scala b/mtags-shared/src/main/scala/scala/meta/internal/pc/PcSymbolInformation.scala new file mode 100644 index 00000000000..995735e118d --- /dev/null +++ b/mtags-shared/src/main/scala/scala/meta/internal/pc/PcSymbolInformation.scala @@ -0,0 +1,63 @@ +package scala.meta.internal.pc + +import java.{util => ju} + +import scala.util.Try + +import scala.meta.internal.jdk.CollectionConverters._ +import scala.meta.pc.{PcSymbolInformation => IPcSymbolInformation} + +case class PcSymbolInformation( + symbol: String, + kind: PcSymbolKind.PcSymbolKind, + parents: List[String], + dealisedSymbol: String, + classOwner: Option[String], + overridden: List[String], + properties: List[PcSymbolProperty.PcSymbolProperty] +) extends IPcSymbolInformation { + + def overriddenList(): ju.List[String] = overridden.asJava + def classOwnerString(): String = classOwner.getOrElse("") + def kindString(): String = kind.toString() + def parentsList(): ju.List[String] = parents.asJava + def propertiesList(): ju.List[String] = properties.map(_.toString()).asJava +} + +object PcSymbolInformation { + def from(info: IPcSymbolInformation): PcSymbolInformation = + PcSymbolInformation( + info.symbol(), + Try(PcSymbolKind.withName(info.kindString())).getOrElse(PcSymbolKind.UNKNOWN_KIND), + info.parentsList().asScala.toList, + info.dealisedSymbol(), + if(info.classOwnerString().nonEmpty) Some(info.classOwnerString()) else None, + info.overriddenList().asScala.toList, + info.propertiesList().asScala.toList.flatMap(name => Try(PcSymbolProperty.withName(name)).toOption) + ) +} + +object PcSymbolKind extends Enumeration { + type PcSymbolKind = Value + val UNKNOWN_KIND: Value = Value(0) + val METHOD: Value = Value(3) + val MACRO: Value = Value(6) + val TYPE: Value = Value(7) + val PARAMETER: Value = Value(8) + val TYPE_PARAMETER: Value = Value(9) + val OBJECT: Value = Value(10) + val PACKAGE: Value = Value(11) + val PACKAGE_OBJECT: Value = Value(12) + val CLASS: Value = Value(13) + val TRAIT: Value = Value(14) + val SELF_PARAMETER: Value = Value(17) + val INTERFACE: Value = Value(18) + val LOCAL: Value = Value(19) + val FIELD: Value = Value(20) + val CONSTRUCTOR: Value = Value(21) +} + +object PcSymbolProperty extends Enumeration { + type PcSymbolProperty = Value + val ABSTRACT: Value = Value(4) +} diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala index 52984895f61..1c026338493 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala @@ -40,6 +40,7 @@ import scala.meta.pc.SymbolSearch import scala.meta.pc.SyntheticDecoration import scala.meta.pc.SyntheticDecorationsParams import scala.meta.pc.VirtualFileParams +import scala.meta.pc.{PcSymbolInformation => IPcSymbolInformation} import org.eclipse.lsp4j.CompletionItem import org.eclipse.lsp4j.CompletionList @@ -339,14 +340,17 @@ case class ScalaPresentationCompiler( ) { pc => new PcDefinitionProvider(pc.compiler(), params).definition() } } - override def findParents( + override def info( symbol: String - ): CompletableFuture[util.List[String]] = { - compilerAccess.withNonInterruptableCompiler(None)( - List.empty[String].asJava, + ): CompletableFuture[ju.List[IPcSymbolInformation]] = { + compilerAccess.withNonInterruptableCompiler[ju.List[IPcSymbolInformation]]( + None + )( + Nil.asJava, EmptyCancelToken ) { pc => - pc.compiler().findParents(symbol).asJava + val result: List[IPcSymbolInformation] = pc.compiler().info(symbol) + result.asJava } } diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/WorkspaceSymbolSearch.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/WorkspaceSymbolSearch.scala index 955565221de..cf0a76db2ba 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/WorkspaceSymbolSearch.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/WorkspaceSymbolSearch.scala @@ -10,7 +10,7 @@ import org.eclipse.{lsp4j => l} trait WorkspaceSymbolSearch { this: MetalsGlobal => - def findParents(symbol: String): List[String] = { + def info(symbol: String): List[PcSymbolInformation] = { val index = symbol.lastIndexOf("/") val pkgString = symbol.take(index + 1) val pkg = packageSymbolFromString(pkgString).getOrElse( @@ -27,16 +27,49 @@ trait WorkspaceSymbolSearch { this: MetalsGlobal => val rest = symbol.drop(newSymbol.size) loop(rest.drop(1), (newSymbol, rest.headOption.exists(_ == '#')) :: acc) } + val names = - loop(symbol.drop(index + 1), List.empty) - val compilerSymbol = names.foldLeft(pkg) { case (sym, (name, isClass)) => - if (isClass) sym.info.member(TypeName(name)) - else sym.info.member(TermName(name)) + loop(symbol.drop(index + 1).takeWhile(_ != '('), List.empty) + val compilerSymbols = names.foldLeft(List(pkg)) { case (owners, (name, isClass)) => + owners.flatMap{ owner => + val foundChild = + if (isClass) owner.info.member(TypeName(name)) + else owner.info.member(TermName(name)) + foundChild.info match { + case OverloadedType(_, alts) => alts + case _ => List(foundChild) + } + } } - compilerSymbol.parentSymbols.map(semanticdbSymbol) + compilerSymbols.map(compilerSymbol => + PcSymbolInformation( + symbol = semanticdbSymbol(compilerSymbol), + kind = getSymbolKind(compilerSymbol), + parents = compilerSymbol.parentSymbols.map(semanticdbSymbol), + dealisedSymbol = semanticdbSymbol(compilerSymbol.dealiased), + classOwner = compilerSymbol.ownerChain.find(c => c.isClass || c.isModule).map(semanticdbSymbol), + overridden = compilerSymbol.overrides.map(semanticdbSymbol), + properties = if(compilerSymbol.isAbstractClass || compilerSymbol.isAbstractType) List(PcSymbolProperty.ABSTRACT) else Nil + ) + ) } + private def getSymbolKind(sym: Symbol): PcSymbolKind.PcSymbolKind = + if(sym.isJavaInterface) PcSymbolKind.INTERFACE + else if(sym.isTrait) PcSymbolKind.TRAIT + else if (sym.isConstructor) PcSymbolKind.CONSTRUCTOR + else if (sym.isPackageObject) PcSymbolKind.PACKAGE_OBJECT + else if (sym.isClass) PcSymbolKind.CLASS + else if (sym.isMacro) PcSymbolKind.MACRO + else if (sym.isLocalToBlock) PcSymbolKind.LOCAL + else if (sym.isMethod) PcSymbolKind.METHOD + else if (sym.isParameter) PcSymbolKind.PARAMETER + else if (sym.hasPackageFlag) PcSymbolKind.PACKAGE + else if (sym.isTypeParameter) PcSymbolKind.TYPE_PARAMETER + else if (sym.isType) PcSymbolKind.TYPE + else PcSymbolKind.UNKNOWN_KIND + class CompilerSearchVisitor( context: Context, visitMember: Symbol => Boolean diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/completions/Completions.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/completions/Completions.scala index 3a586d82c0c..06412cbbcf7 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/completions/Completions.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/completions/Completions.scala @@ -76,19 +76,21 @@ trait Completions { this: MetalsGlobal => val packageSymbols: mutable.Map[String, Option[Symbol]] = mutable.Map.empty[String, Option[Symbol]] - def packageSymbolFromString(symbol: String): Option[Symbol] = { - packageSymbols.getOrElseUpdate( - symbol, { - val fqn = symbol.stripSuffix("/").replace('/', '.') - try { - Some(rootMirror.staticPackage(fqn)) - } catch { - case NonFatal(_) => - None + def packageSymbolFromString(symbol: String): Option[Symbol] = + if(symbol == "_empty_/") Some(rootMirror.EmptyPackage) + else { + packageSymbols.getOrElseUpdate( + symbol, { + val fqn = symbol.stripSuffix("/").replace('/', '.') + try { + Some(rootMirror.staticPackage(fqn)) + } catch { + case NonFatal(_) => + None + } } - } - ) - } + ) + } /** * Returns a high number for less relevant symbols and low number for relevant numbers. diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/ParentProvider.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/ParentProvider.scala deleted file mode 100644 index 1af98c0f7a4..00000000000 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/ParentProvider.scala +++ /dev/null @@ -1,58 +0,0 @@ -package scala.meta.internal.pc - -import scala.util.control.NonFatal - -import dotty.tools.dotc.core.Contexts.Context -import dotty.tools.dotc.core.Names.* -import dotty.tools.dotc.core.Symbols.* - -class ParentProvider(using Context): - private def toSymbols( - pkg: String, - parts: List[(String, Boolean)], - ): Option[Symbol] = - def loop(owner: Symbol, parts: List[(String, Boolean)]): Option[Symbol] = - parts match - case (head, isClass) :: tl => - val next = - if isClass then owner.info.member(typeName(head)) - else owner.info.member(termName(head)) - if next.exists then loop(next.symbol, tl) - else None - case Nil => Some(owner) - - val pkgSym = requiredPackage(pkg) - loop(pkgSym, parts) - end toSymbols - - def parents(symbol: String): List[String] = - val index = symbol.lastIndexOf("/") - val pkg = normalizePackage(symbol.take(index + 1)) - - def loop( - symbol: String, - acc: List[(String, Boolean)], - ): List[(String, Boolean)] = - if symbol.isEmpty() then acc.reverse - else - val newSymbol = symbol.takeWhile(c => c != '.' && c != '#') - val rest = symbol.drop(newSymbol.size) - loop(rest.drop(1), (newSymbol, rest.headOption.exists(_ == '#')) :: acc) - val names = - loop(symbol.drop(index + 1), List.empty) - - val foundSym = - try toSymbols(pkg, names) - catch case NonFatal(e) => None - - for - sym <- foundSym.toList - classSym = if sym.isClass then sym else sym.moduleClass - if classSym.isClass - parent <- classSym.asClass.parentSyms - yield SemanticdbSymbols.symbolName(parent) - end parents - - private def normalizePackage(pkg: String): String = - pkg.replace("/", ".").stripSuffix(".") -end ParentProvider diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala index bd6550a3fb5..f0c67ca0320 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala @@ -23,6 +23,7 @@ import scala.meta.internal.mtags.BuildInfo import scala.meta.internal.pc.completions.CompletionProvider import scala.meta.internal.pc.completions.OverrideCompletions import scala.meta.pc.* +import scala.meta.pc.{PcSymbolInformation => IPcSymbolInformation} import dotty.tools.dotc.reporting.StoreReporter import org.eclipse.lsp4j.DocumentHighlight @@ -177,14 +178,14 @@ case class ScalaPresentationCompiler( def diagnosticsForDebuggingPurposes(): ju.List[String] = List[String]().asJava - override def findParents( + override def info( symbol: String - ): ju.concurrent.CompletableFuture[ju.List[String]] = - compilerAccess.withNonInterruptableCompiler(None)( - List.empty[String].asJava, - EmptyCancelToken, + ): CompletableFuture[ju.List[IPcSymbolInformation]] = + compilerAccess.withNonInterruptableCompiler[ju.List[IPcSymbolInformation]](None)( + Nil.asJava, + EmptyCancelToken ) { access => - ParentProvider(using access.compiler().currentCtx).parents(symbol).asJava + SymbolInformationProvider(using access.compiler().currentCtx).info(symbol).asJava } def semanticdbTextDocument( diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/SymbolInformationProvider.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/SymbolInformationProvider.scala new file mode 100644 index 00000000000..6e30ecf6de5 --- /dev/null +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/SymbolInformationProvider.scala @@ -0,0 +1,109 @@ +package scala.meta.internal.pc + +import scala.util.control.NonFatal + +import scala.meta.internal.mtags.MtagsEnrichments.metalsDealias + +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Denotations.Denotation +import dotty.tools.dotc.core.Denotations.MultiDenotation +import dotty.tools.dotc.core.Flags +import dotty.tools.dotc.core.Names.* +import dotty.tools.dotc.core.StdNames.nme +import dotty.tools.dotc.core.Symbols.* + +class SymbolInformationProvider(using Context): + private def toSymbols( + pkg: String, + parts: List[(String, Boolean)], + ): List[Symbol] = + def collectSymbols(denotation: Denotation): List[Symbol] = + denotation match + case MultiDenotation(denot1, denot2) => collectSymbols(denot1) ++ collectSymbols(denot2) + case denot => List(denot.symbol) + + def loop(owners: List[Symbol], parts: List[(String, Boolean)]): List[Symbol] = + parts match + case (head, isClass) :: tl => + val foundSymbols = + owners.flatMap { owner => + val next = + if isClass then owner.info.member(typeName(head)) + else owner.info.member(termName(head)) + collectSymbols(next).filter(_.exists) + } + if foundSymbols.nonEmpty then loop(foundSymbols, tl) + else Nil + case Nil => owners + + val pkgSym = + if pkg == "_empty_" then requiredPackage(nme.EMPTY_PACKAGE) + else requiredPackage(pkg) + loop(List(pkgSym), parts) + end toSymbols + + def info(symbol: String): List[PcSymbolInformation] = + val index = symbol.lastIndexOf("/") + val pkg = normalizePackage(symbol.take(index + 1)) + + def loop( + symbol: String, + acc: List[(String, Boolean)], + ): List[(String, Boolean)] = + if symbol.isEmpty() then acc.reverse + else + val newSymbol = symbol.takeWhile(c => c != '.' && c != '#') + val rest = symbol.drop(newSymbol.size) + loop(rest.drop(1), (newSymbol, rest.headOption.exists(_ == '#')) :: acc) + val names = + loop(symbol.drop(index + 1).takeWhile(_ != '('), List.empty) + + val foundSymbols = + try toSymbols(pkg, names) + catch case NonFatal(e) => + pprint.log(e) + Nil + + for + sym <- foundSymbols + yield { + val classSym = if sym.isClass then sym else sym.moduleClass + val parents = + if(classSym.isClass) + then classSym.asClass.parentSyms.map(SemanticdbSymbols.symbolName) + else Nil + val dealisedSymbol = + if(sym.isAliasType) sym.info.metalsDealias.typeSymbol else sym + val classOwner = sym.ownersIterator.drop(1).find(s => s.isClass || s.is(Flags.Module)) + val overridden = sym.denot.allOverriddenSymbols.toList + + PcSymbolInformation( + symbol = SemanticdbSymbols.symbolName(sym), + kind = getSymbolKind(sym), + parents = parents, + dealisedSymbol = SemanticdbSymbols.symbolName(dealisedSymbol), + classOwner = classOwner.map(SemanticdbSymbols.symbolName), + overridden = overridden.map(SemanticdbSymbols.symbolName), + properties = if sym.is(Flags.Abstract) then List(PcSymbolProperty.ABSTRACT) else Nil + ) + } + end info + + private def getSymbolKind(sym: Symbol): PcSymbolKind.PcSymbolKind = + if sym.isAllOf(Flags.JavaInterface) then PcSymbolKind.INTERFACE + else if sym.is(Flags.Trait) then PcSymbolKind.TRAIT + else if sym.isConstructor then PcSymbolKind.CONSTRUCTOR + else if sym.isPackageObject then PcSymbolKind.PACKAGE_OBJECT + else if sym.isClass then PcSymbolKind.CLASS + else if sym.is(Flags.Macro) then PcSymbolKind.MACRO + else if sym.is(Flags.Local) then PcSymbolKind.LOCAL + else if sym.is(Flags.Method) then PcSymbolKind.METHOD + else if sym.is(Flags.Param) then PcSymbolKind.PARAMETER + else if sym.is(Flags.Package) then PcSymbolKind.PACKAGE + else if sym.is(Flags.TypeParam) then PcSymbolKind.TYPE_PARAMETER + else if sym.isType then PcSymbolKind.TYPE + else PcSymbolKind.UNKNOWN_KIND + + private def normalizePackage(pkg: String): String = + pkg.replace("/", ".").stripSuffix(".") +end SymbolInformationProvider diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala index 377d1796b51..b27656b17a4 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala @@ -288,7 +288,7 @@ class ScalaToplevelMtags( currRegion.withTermOwner(owner), expectTemplate ) - case DEF | VAL | VAR | GIVEN | TYPE + case DEF | VAL | VAR | GIVEN if expectTemplate.map(!_.isExtension).getOrElse(true) => if (needEmitTermMember()) { withOwner(currRegion.termOwner) { @@ -296,6 +296,13 @@ class ScalaToplevelMtags( } } else scanner.nextToken() loop(indent, isAfterNewline = false, currRegion, newExpectIgnoreBody) + case TYPE if expectTemplate.map(!_.isExtension).getOrElse(true) => + if (needEmitMember(currRegion)) { + withOwner(currRegion.termOwner) { + emitType(needEmitTermMember()) + } + } else scanner.nextToken() + loop(indent, isAfterNewline = false, currRegion, newExpectIgnoreBody) case IMPORT | EXPORT => // skip imports because they might have `given` kw acceptToStatSep() @@ -655,8 +662,55 @@ class ScalaToplevelMtags( scanner.nextToken() } + def emitType(emitTermMember: Boolean): Option[Unit] = { + acceptTrivia() + newIdentifier + .map { ident => + val typeSymbol = symbol(Descriptor.Type(ident.name)) + if (emitTermMember) { + tpe(ident.name, ident.pos, Kind.TYPE, 0) + } + nextIsNL() + @tailrec + def loop(name: Option[String], isAfterEq: Boolean = false): Option[String] = { + scanner.curr.token match { + case SEMI => name + case _ if isNewline | isDone => name + case EQUALS => + scanner.nextToken() + loop(name, isAfterEq = true) + case TYPELAMBDAARROW | WHITESPACE => + scanner.nextToken() + loop(name, isAfterEq) + case LBRACKET => + acceptBalancedDelimeters(LBRACKET, RBRACKET) + scanner.nextToken() + loop(name, isAfterEq) + case LBRACE => + acceptBalancedDelimeters(LBRACE, RBRACE) + scanner.nextToken() + loop(name, isAfterEq) + case IDENTIFIER if isAfterEq && scanner.curr.name != "|" && scanner.curr.name != "&" => + val optName = selectName() + loop(optName, isAfterEq) + case _ if isAfterEq => None + case _ => + scanner.nextToken() + loop(name) + } + } + + loop(name = None).foreach { rhsName => + overridden += (( + typeSymbol, + List(UnresolvedOverriddenSymbol(rhsName)) + )) + } + } + } + /** - * Enters a global element (def/val/var/type) + * Enters a global element (def/val/var/given) */ def emitTerm(region: Region): Unit = { val kind = scanner.curr.token @@ -682,10 +736,6 @@ class ScalaToplevelMtags( ) resetRegion(region) }) - case TYPE => - newIdentifier.foreach { name => - tpe(name.name, name.pos, Kind.TYPE, 0) - } case DEF => methodIdentifier.foreach(name => method( @@ -865,7 +915,24 @@ class ScalaToplevelMtags( reportError("identifier") None } + } + def selectName(): Option[String] = { + @tailrec + def loop(last: Option[String]): Option[String] = { + scanner.curr.token match { + case IDENTIFIER => + val name = scanner.curr.name + scanner.nextToken() + loop(Some(name)) + case DOT => + scanner.nextToken() + loop(last) + case _ => + last + } + } + loop(last = None) } /** diff --git a/tests/slow/src/test/scala/tests/feature/ImplementationCrossLspSuite.scala b/tests/slow/src/test/scala/tests/feature/ImplementationCrossLspSuite.scala index 37fc95ad9f9..8953492c41c 100644 --- a/tests/slow/src/test/scala/tests/feature/ImplementationCrossLspSuite.scala +++ b/tests/slow/src/test/scala/tests/feature/ImplementationCrossLspSuite.scala @@ -53,4 +53,36 @@ class ImplementationCrossLspSuite scalaVersion = V.scala3, ) + check( + "basic-method-params", + """|/a/src/main/scala/a/Main.scala + |package a + |trait LivingBeing{ + | def sound: Int + | def s@@ound(times : Int): Int = 1 + | def sound(start : Long): Int = 1 + |} + |abstract class Animal extends LivingBeing{} + |class Dog extends Animal{ + | def sound = 1 + | override def sound(times : Long) = 1 + | override def <>(times : Int) = 1 + |} + |class Cat extends Animal{ + | override def <>(times : Int) = 1 + | override def sound = 1 + |} + |""".stripMargin, + scalaVersion = Some(V.scala3), + ) + + check( + "empty-pkg", + """|/a/src/main/scala/a/Main.scala + |trait A@@A + |class <> extends A@@A + |""".stripMargin, + scalaVersion = Some(V.scala3), + ) + } diff --git a/tests/unit/src/main/scala/tests/BaseRangesSuite.scala b/tests/unit/src/main/scala/tests/BaseRangesSuite.scala index 8304e59eabb..2cd714dece7 100644 --- a/tests/unit/src/main/scala/tests/BaseRangesSuite.scala +++ b/tests/unit/src/main/scala/tests/BaseRangesSuite.scala @@ -5,6 +5,7 @@ import scala.concurrent.Future import munit.Location import munit.TestOptions + abstract class BaseRangesSuite(name: String) extends BaseLspSuite(name) { protected def libraryDependencies: List[String] = Nil @@ -20,6 +21,8 @@ abstract class BaseRangesSuite(name: String) extends BaseLspSuite(name) { name: TestOptions, input: String, scalaVersion: Option[String] = None, + additionalLibraryDependencies: List[String] = Nil, + scalacOptions: List[String] = Nil )(implicit loc: Location ): Unit = { @@ -51,7 +54,8 @@ abstract class BaseRangesSuite(name: String) extends BaseLspSuite(name) { |{"a": | { | "scalaVersion" : "$actualScalaVersion", - | "libraryDependencies": ${toJsonArray(libraryDependencies)} + | "libraryDependencies": ${toJsonArray(libraryDependencies ++ additionalLibraryDependencies)}, + | "scalacOptions": ${toJsonArray(scalacOptions)} | } |} |${input @@ -60,6 +64,7 @@ abstract class BaseRangesSuite(name: String) extends BaseLspSuite(name) { _ <- Future.sequence( files.map(file => server.didOpen(s"${file._1}")) ) + _ = pprint.log(server.client.diagnostics) _ <- assertCheck(filename, edit, expected, base) _ <- server.shutdown() } yield () diff --git a/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala b/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala index 09a3bd780f2..e27507fed36 100644 --- a/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala +++ b/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala @@ -254,8 +254,9 @@ class ImplementationLspSuite extends BaseImplementationSuite("implementation") { |""".stripMargin, ) + // we have to collect information about overridden symbols in JavaMtags for this to work check( - "java-classes", + "java-classes".ignore, """|/a/src/main/scala/a/Main.scala |package a |class <> extends Exce@@ption @@ -520,6 +521,8 @@ class ImplementationLspSuite extends BaseImplementationSuite("implementation") { | case object <> extends Animal |} |""".stripMargin, + additionalLibraryDependencies = List("io.circe::circe-generic-extras:0.14.0"), + scalacOptions = List("-Ymacro-annotations") ) check( @@ -535,6 +538,23 @@ class ImplementationLspSuite extends BaseImplementationSuite("implementation") { |} |""".stripMargin, ) + + check( + "local-methods", + """|/a/src/main/scala/a/Main.scala + |object Test { + | def main { + | trait A { + | def f@@oo(): Int + | } + | class B extends A { + | def <>(): Int = 1 + | } + | } + |} + |""".stripMargin, + ) + check( "type-implementation", """|/a/src/main/scala/a/Main.scala diff --git a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala index 652c75cc585..4f8d800eb93 100644 --- a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala +++ b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala @@ -599,7 +599,7 @@ class ScalaToplevelSuite extends BaseSuite { // It is easier to work around this inconstancy in `SemanticdbSymbols.inverseSemanticdbSymbol` // than to change symbols emitted by `ScalaTopLevelMtags`, // since the object could be placed before type definition. - List("s/", "s/Test$package.", "s/Test$package.Cow#", "s/Cow.", + List("s/", "s/Test$package.", "s/Test$package.Cow# -> Long", "s/Cow.", "s/Cow.apply()."), dialect = dialects.Scala3, mode = All, @@ -635,6 +635,25 @@ class ScalaToplevelSuite extends BaseSuite { mode = All, ) + check( + "overridden-type-alias", + """|package a + |object O { + | type A[X] = Set[X] + | type W[X] = mutable.Set[X] + | type H = [X] =>> List[X] + | type R = Set[Int] { def a: Int } + | opaque type L <: mutable.List[Int] = mutable.List[Int] + | type Elem[X] = X match + | case String => Char + | case Array[t] => t + | case Iterable[t] => t + |} + |""".stripMargin, + List("a/", "a/O.", "a/O.A# -> Set", "a/O.H# -> List", "a/O.W# -> Set", "a/O.R# -> Set", "a/O.L# -> List", "a/O.Elem#"), + mode = All, + ) + def check( options: TestOptions, code: String, From 22b41d3d1f31765912f67d1d793df9e1b6374fa6 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Tue, 16 Jan 2024 13:21:08 +0100 Subject: [PATCH 20/26] move unused methods from implementation provider --- .../ImplementationProvider.scala | 203 +--------------- .../implementation/Supermethods.scala | 7 +- .../internal/metals/MetalsLspService.scala | 16 +- .../meta/internal/rename/RenameProvider.scala | 6 +- .../GlobalClassTable.scala | 6 +- .../internal/search/SymbolHierarchyOps.scala | 227 ++++++++++++++++++ 6 files changed, 259 insertions(+), 206 deletions(-) rename metals/src/main/scala/scala/meta/internal/{implementation => search}/GlobalClassTable.scala (86%) create mode 100644 metals/src/main/scala/scala/meta/internal/search/SymbolHierarchyOps.scala diff --git a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala index 03df44f2403..8bb676311df 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala @@ -8,11 +8,9 @@ import java.util.concurrent.ConcurrentLinkedQueue import scala.collection.mutable import scala.concurrent.ExecutionContext import scala.concurrent.Future -import scala.util.control.NonFatal import scala.meta.internal.io.FileIO import scala.meta.internal.metals.Buffers -import scala.meta.internal.metals.BuildTargets import scala.meta.internal.metals.Compilers import scala.meta.internal.metals.DefinitionProvider import scala.meta.internal.metals.MetalsEnrichments._ @@ -33,26 +31,24 @@ import scala.meta.internal.parsing.Trees import scala.meta.internal.pc.PcSymbolInformation import scala.meta.internal.pc.PcSymbolKind import scala.meta.internal.pc.PcSymbolProperty +import scala.meta.internal.search.SymbolHierarchyOps._ import scala.meta.internal.semanticdb.ClassSignature import scala.meta.internal.semanticdb.Scala._ import scala.meta.internal.semanticdb.Signature import scala.meta.internal.semanticdb.SymbolInformation -import scala.meta.internal.semanticdb.SymbolOccurrence import scala.meta.internal.semanticdb.TextDocument import scala.meta.internal.semanticdb.TextDocuments import scala.meta.internal.semanticdb.TypeRef import scala.meta.internal.semanticdb.TypeSignature -import scala.meta.internal.symtab.GlobalSymbolTable import scala.meta.io.AbsolutePath import org.eclipse.lsp4j.Location import org.eclipse.lsp4j.TextDocumentPositionParams final class ImplementationProvider( - semanticdbs: Semanticdbs, + semanticdbs: Semanticdbs, workspace: AbsolutePath, index: GlobalSymbolIndex, - buildTargets: BuildTargets, buffer: Buffers, definitionProvider: DefinitionProvider, trees: Trees, @@ -61,8 +57,6 @@ final class ImplementationProvider( )(implicit ec: ExecutionContext, rc: ReportContext) extends SemanticdbFeatureProvider { import ImplementationProvider._ - - private val globalTable = new GlobalClassTable(buildTargets) private val implementationsInPath = new ConcurrentHashMap[Path, Map[String, Set[ClassLocation]]] private val implementationsInDependencySources = @@ -144,20 +138,6 @@ final class ImplementationProvider( } } - def defaultSymbolSearch( - anyWorkspacePath: AbsolutePath, - textDocument: TextDocument, - ): String => Option[SymbolInformation] = { - lazy val global = - globalTable.globalSymbolTableFor(anyWorkspacePath) - symbol => { - textDocument.symbols - .find(_.symbol == symbol) - .orElse(findSymbolInformation(symbol)) - .orElse(global.flatMap(_.safeInfo(symbol))) - } - } - def implementations( params: TextDocumentPositionParams ): Future[List[Location]] = { @@ -218,103 +198,6 @@ final class ImplementationProvider( } } - def topMethodParents( - symbol: String, - textDocument: TextDocument, - ): Seq[Location] = { - - def findClassInfo(owner: String) = { - if (owner.nonEmpty) { - findSymbol(textDocument, owner) - } else { - textDocument.symbols.find { sym => - sym.signature match { - case sig: ClassSignature => - sig.declarations.exists(_.symlinks.contains(symbol)) - case _ => false - } - } - } - } - - val results = for { - currentInfo <- findSymbol(textDocument, symbol) - if !isClassLike(currentInfo) - classInfo <- findClassInfo(symbol.owner) - } yield { - classInfo.signature match { - case sig: ClassSignature => - methodInParentSignature(sig, currentInfo, sig) - case _ => Nil - } - } - results.getOrElse(Seq.empty) - } - - private def methodInParentSignature( - currentClassSig: ClassSignature, - bottomSymbol: SymbolInformation, - bottomClassSig: ClassSignature, - ): Seq[Location] = { - currentClassSig.parents.flatMap { - case parentSym: TypeRef => - val parentTextDocument = findSemanticDbForSymbol(parentSym.symbol) - def search(symbol: String) = - parentTextDocument.flatMap(findSymbol(_, symbol)) - search(parentSym.symbol).map(_.signature) match { - case Some(parenClassSig: ClassSignature) => - val fromParent = methodInParentSignature( - parenClassSig, - bottomSymbol, - bottomClassSig, - ) - if (fromParent.isEmpty) { - locationFromClass( - bottomSymbol, - parenClassSig, - search, - parentTextDocument, - ) - } else { - fromParent - } - case _ => Nil - } - - case _ => Nil - } - } - - private def locationFromClass( - bottomSymbolInformation: SymbolInformation, - parentClassSig: ClassSignature, - search: String => Option[SymbolInformation], - parentTextDocument: Option[TextDocument], - ): Option[Location] = { - val matchingSymbol = MethodImplementation.findParentSymbol( - bottomSymbolInformation, - parentClassSig, - search, - ) - for { - symbol <- matchingSymbol - parentDoc <- parentTextDocument - source = workspace.resolve(parentDoc.uri) - implOccurrence <- findDefOccurrence( - parentDoc, - symbol, - source, - ) - range <- implOccurrence.range - distance = buffer.tokenEditDistance( - source, - parentDoc.text, - trees, - ) - revised <- distance.toRevised(range.toLsp) - } yield new Location(source.toNIO.toUri().toString(), revised) - } - private def symbolLocationsFromContext( dealised: String, textDocument: TextDocument, @@ -328,19 +211,17 @@ final class ImplementationProvider( textDocument: TextDocument, implReal: ClassLocation, ): Option[Future[String]] = { - def findSymbolInDoc(symbol: String) = - textDocument.symbols.find(_.symbol == symbol) if (classLikeKinds(info.kind)) Some(Future(implReal.symbol)) else { def tryFromDoc = for { - classInfo <- findSymbolInDoc(implReal.symbol) + classInfo <- findSymbol(textDocument, implReal.symbol) declarations <- classInfo.signature match { case ClassSignature(_, _, _, declarations) => declarations case _ => None } found <- declarations.symlinks.collectFirst { sym => - findSymbolInDoc(sym) match { + findSymbol(textDocument, sym) match { case Some(implInfo) if implInfo.overriddenSymbols.contains(info.symbol) => sym @@ -382,7 +263,7 @@ final class ImplementationProvider( file <- files locations = locationsByFile(file) implPath = AbsolutePath(file) - implDocument <- findSemanticdb(implPath, handleJars = true).toList + implDocument <- findSemanticdb(implPath).toList } yield { for { symbols <- Future.sequence( @@ -404,6 +285,7 @@ final class ImplementationProvider( implDocument, sym, implPath, + scalaVersionSelector ).toList range <- implOccurrence.range revised <- @@ -455,13 +337,9 @@ final class ImplementationProvider( splitJobs.map(_ => allLocations.asScala.toSeq) } - private def findSemanticdb( - fileSource: AbsolutePath, - handleJars: Boolean = false, - ): Option[TextDocument] = { + private def findSemanticdb(fileSource: AbsolutePath): Option[TextDocument] = { if (fileSource.isJarFileSystem) - if (!handleJars) None - else Some(semanticdbForJarFile(fileSource)) + Some(semanticdbForJarFile(fileSource)) else semanticdbs .textDocument(fileSource) @@ -524,56 +402,14 @@ final class ImplementationProvider( }) } - private def findSymbolInformation( - symbol: String - ): Option[SymbolInformation] = { - findSemanticDbForSymbol(symbol).flatMap(findSymbol(_, symbol)) - } - - def findSemanticDbWithPathForSymbol( - symbol: String - ): Option[TextDocumentWithPath] = { - for { - symbolDefinition <- findSymbolDefinition(symbol) - document <- findSemanticdb(symbolDefinition.path) - } yield TextDocumentWithPath(document, symbolDefinition.path) - } - private def findSymbolDefinition(symbol: String): Option[SymbolDefinition] = { index.definition(MSymbol(symbol)) } - private def findSemanticDbForSymbol(symbol: String): Option[TextDocument] = { - for { - symbolDefinition <- findSymbolDefinition(symbol) - document <- findSemanticdb(symbolDefinition.path) - } yield { - document - } - } - private def classFromSymbol(info: PcSymbolInformation): Option[String] = if (classLikeKinds(info.kind)) Some(info.dealisedSymbol) else info.classOwner - private def findDefOccurrence( - semanticDb: TextDocument, - symbol: String, - source: AbsolutePath, - ): Option[SymbolOccurrence] = { - def isDefinitionOccurrence(occ: SymbolOccurrence) = - occ.role.isDefinition && occ.symbol == symbol - - semanticDb.occurrences - .find(isDefinitionOccurrence) - .orElse( - Mtags - .allToplevels(source.toInput, scalaVersionSelector.getDialect(source)) - .occurrences - .find(isDefinitionOccurrence) - ) - } - private def symbolInfo( textDocument: TextDocument, source: AbsolutePath, @@ -581,7 +417,7 @@ final class ImplementationProvider( ): Future[Option[PcSymbolInformation]] = if (symbol.isLocal) { (for { - info <- textDocument.symbols.find(_.symbol == symbol) + info <- findSymbol(textDocument, symbol) } yield { info.signature match { case typeSig: TypeSignature => @@ -631,24 +467,6 @@ final class ImplementationProvider( } object ImplementationProvider { - - implicit class XtensionGlobalSymbolTable(symtab: GlobalSymbolTable) { - def safeInfo(symbol: String): Option[SymbolInformation] = - try { - symtab.info(symbol) - } catch { - case NonFatal(_) => None - } - } - - private def findSymbol( - semanticDb: TextDocument, - symbol: String, - ): Option[SymbolInformation] = { - semanticDb.symbols - .find(sym => sym.symbol == symbol) - } - def parentsFromSignature( symbol: String, signature: Signature, @@ -686,9 +504,6 @@ object ImplementationProvider { } } - def isClassLike(info: SymbolInformation): Boolean = - info.isObject || info.isClass || info.isTrait || info.isInterface - val classLikeKinds: Set[PcSymbolKind.Value] = Set( PcSymbolKind.OBJECT, PcSymbolKind.CLASS, diff --git a/metals/src/main/scala/scala/meta/internal/implementation/Supermethods.scala b/metals/src/main/scala/scala/meta/internal/implementation/Supermethods.scala index 94d1dce1154..2bef1344c08 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/Supermethods.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/Supermethods.scala @@ -11,6 +11,7 @@ import scala.meta.internal.metals.ReportContext import scala.meta.internal.metals.clients.language.MetalsLanguageClient import scala.meta.internal.metals.clients.language.MetalsQuickPickItem import scala.meta.internal.metals.clients.language.MetalsQuickPickParams +import scala.meta.internal.search.SymbolHierarchyOps import scala.meta.internal.semanticdb.SymbolInformation import scala.meta.io.AbsolutePath @@ -22,7 +23,7 @@ import org.eclipse.lsp4j.TextDocumentPositionParams class Supermethods( client: MetalsLanguageClient, definitionProvider: DefinitionProvider, - implementationProvider: ImplementationProvider, + symbolHierarchyOps: SymbolHierarchyOps, )(implicit ec: ExecutionContext, reports: ReportContext, @@ -76,7 +77,7 @@ class Supermethods( filePath, params.getPosition(), ) - findSymbol = implementationProvider.defaultSymbolSearch( + findSymbol = symbolHierarchyOps.defaultSymbolSearch( filePath, textDocument, ) @@ -131,7 +132,7 @@ class Supermethods( filePath, position, ) - findSymbol = implementationProvider.defaultSymbolSearch( + findSymbol = symbolHierarchyOps.defaultSymbolSearch( filePath, textDocument, ) diff --git a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala index 9648cad0980..e93eaf65c57 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala @@ -79,6 +79,7 @@ import scala.meta.internal.parsing.FoldingRangeProvider import scala.meta.internal.parsing.TokenEditDistance import scala.meta.internal.parsing.Trees import scala.meta.internal.rename.RenameProvider +import scala.meta.internal.search.SymbolHierarchyOps import scala.meta.internal.semver.SemVer import scala.meta.internal.tvp._ import scala.meta.internal.worksheets.DecorationWorksheetPublisher @@ -649,7 +650,6 @@ class MetalsLspService( semanticdbs, folder, definitionIndex, - buildTargets, buffers, definitionProvider, trees, @@ -657,10 +657,21 @@ class MetalsLspService( compilers, ) + private val symbolHierarchyOps: SymbolHierarchyOps = + new SymbolHierarchyOps( + folder, + buildTargets, + semanticdbs, + definitionIndex, + scalaVersionSelector, + buffers, + trees + ) + private val supermethods: Supermethods = new Supermethods( languageClient, definitionProvider, - implementationProvider, + symbolHierarchyOps, ) private val semanticDBIndexer: SemanticdbIndexer = new SemanticdbIndexer( @@ -690,6 +701,7 @@ class MetalsLspService( private val renameProvider: RenameProvider = new RenameProvider( referencesProvider, implementationProvider, + symbolHierarchyOps, definitionProvider, folder, languageClient, diff --git a/metals/src/main/scala/scala/meta/internal/rename/RenameProvider.scala b/metals/src/main/scala/scala/meta/internal/rename/RenameProvider.scala index 50ca3f5e634..dd4159e291d 100644 --- a/metals/src/main/scala/scala/meta/internal/rename/RenameProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/rename/RenameProvider.scala @@ -20,6 +20,7 @@ import scala.meta.internal.metals.TextEdits import scala.meta.internal.metals.clients.language.MetalsLanguageClient import scala.meta.internal.parsing.Trees import scala.meta.internal.pc.Identifier +import scala.meta.internal.search.SymbolHierarchyOps import scala.meta.internal.semanticdb.Scala._ import scala.meta.internal.semanticdb.SelectTree import scala.meta.internal.semanticdb.SymbolOccurrence @@ -50,6 +51,7 @@ import org.eclipse.lsp4j.{Range => LSPRange} final class RenameProvider( referenceProvider: ReferenceProvider, implementationProvider: ImplementationProvider, + symbolHierarchyOps: SymbolHierarchyOps, definitionProvider: DefinitionProvider, workspace: AbsolutePath, client: MetalsLanguageClient, @@ -177,7 +179,7 @@ final class RenameProvider( path: AbsolutePath, textDocument: TextDocument, ) = - !symbol.desc.isType && !(symbol.isLocal && implementationProvider + !symbol.desc.isType && !(symbol.isLocal && symbolHierarchyOps .defaultSymbolSearch(path, textDocument)(symbol) .exists(info => info.isTrait || info.isClass)) @@ -192,7 +194,7 @@ final class RenameProvider( isWorkspaceSymbol(occurence.symbol, definitionPath) && isNotRenamedSymbol(semanticDb, occurence) parentSymbols = - implementationProvider + symbolHierarchyOps .topMethodParents(occurence.symbol, defSemanticdb) txtParams <- { if (parentSymbols.nonEmpty) parentSymbols.map(toTextParams) diff --git a/metals/src/main/scala/scala/meta/internal/implementation/GlobalClassTable.scala b/metals/src/main/scala/scala/meta/internal/search/GlobalClassTable.scala similarity index 86% rename from metals/src/main/scala/scala/meta/internal/implementation/GlobalClassTable.scala rename to metals/src/main/scala/scala/meta/internal/search/GlobalClassTable.scala index 6295bda9f1e..f74aad8e946 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/GlobalClassTable.scala +++ b/metals/src/main/scala/scala/meta/internal/search/GlobalClassTable.scala @@ -1,6 +1,4 @@ -package scala.meta.internal.implementation -import java.nio.file.Path - +package scala.meta.internal.search import scala.collection.concurrent.TrieMap import scala.meta.internal.metals.BuildTargets @@ -13,8 +11,6 @@ import ch.epfl.scala.bsp4j.BuildTargetIdentifier final class GlobalClassTable( buildTargets: BuildTargets ) { - type ImplementationCache = Map[Path, Map[String, Set[ClassLocation]]] - private val buildTargetsIndexes = TrieMap.empty[BuildTargetIdentifier, GlobalSymbolTable] diff --git a/metals/src/main/scala/scala/meta/internal/search/SymbolHierarchyOps.scala b/metals/src/main/scala/scala/meta/internal/search/SymbolHierarchyOps.scala new file mode 100644 index 00000000000..4e83dbd7979 --- /dev/null +++ b/metals/src/main/scala/scala/meta/internal/search/SymbolHierarchyOps.scala @@ -0,0 +1,227 @@ +package scala.meta.internal.search + +import scala.util.control.NonFatal + +import scala.meta.internal.implementation.MethodImplementation +import scala.meta.internal.implementation.TextDocumentWithPath +import scala.meta.internal.metals.Buffers +import scala.meta.internal.metals.BuildTargets +import scala.meta.internal.metals.MetalsEnrichments._ +import scala.meta.internal.metals.ScalaVersionSelector +import scala.meta.internal.mtags.GlobalSymbolIndex +import scala.meta.internal.mtags.Mtags +import scala.meta.internal.mtags.Semanticdbs +import scala.meta.internal.mtags.SymbolDefinition +import scala.meta.internal.mtags.{Symbol => MSymbol} +import scala.meta.internal.parsing.Trees +import scala.meta.internal.search.SymbolHierarchyOps._ +import scala.meta.internal.semanticdb.ClassSignature +import scala.meta.internal.semanticdb.Scala._ +import scala.meta.internal.semanticdb.SymbolInformation +import scala.meta.internal.semanticdb.SymbolOccurrence +import scala.meta.internal.semanticdb.TextDocument +import scala.meta.internal.semanticdb.TypeRef +import scala.meta.internal.symtab.GlobalSymbolTable +import scala.meta.io.AbsolutePath + +import org.eclipse.lsp4j.Location + +class SymbolHierarchyOps( + workspace: AbsolutePath, + buildTargets: BuildTargets, + semanticdbs: Semanticdbs, + index: GlobalSymbolIndex, + scalaVersionSelector: ScalaVersionSelector, + buffer: Buffers, + trees: Trees, +) { + private val globalTable = new GlobalClassTable(buildTargets) + def defaultSymbolSearch( + anyWorkspacePath: AbsolutePath, + textDocument: TextDocument, + ): String => Option[SymbolInformation] = { + lazy val global = + globalTable.globalSymbolTableFor(anyWorkspacePath) + symbol => { + textDocument.symbols + .find(_.symbol == symbol) + .orElse(findSymbolInformation(symbol)) + .orElse(global.flatMap(_.safeInfo(symbol))) + } + } + + private def findSymbolInformation( + symbol: String + ): Option[SymbolInformation] = { + findSemanticDbForSymbol(symbol).flatMap(findSymbol(_, symbol)) + } + + def findSemanticDbWithPathForSymbol( + symbol: String + ): Option[TextDocumentWithPath] = { + for { + symbolDefinition <- findSymbolDefinition(symbol) + document <- findSemanticdb(symbolDefinition.path) + } yield TextDocumentWithPath(document, symbolDefinition.path) + } + + private def findSemanticdb(fileSource: AbsolutePath): Option[TextDocument] = { + if (fileSource.isJarFileSystem) None + else + semanticdbs + .textDocument(fileSource) + .documentIncludingStale + } + + private def findSymbolDefinition(symbol: String): Option[SymbolDefinition] = { + index.definition(MSymbol(symbol)) + } + + private def findSemanticDbForSymbol(symbol: String): Option[TextDocument] = { + for { + symbolDefinition <- findSymbolDefinition(symbol) + document <- findSemanticdb(symbolDefinition.path) + } yield { + document + } + } + + def topMethodParents( + symbol: String, + textDocument: TextDocument, + ): Seq[Location] = { + + def findClassInfo(owner: String) = { + if (owner.nonEmpty) { + findSymbol(textDocument, owner) + } else { + textDocument.symbols.find { sym => + sym.signature match { + case sig: ClassSignature => + sig.declarations.exists(_.symlinks.contains(symbol)) + case _ => false + } + } + } + } + + val results = for { + currentInfo <- findSymbol(textDocument, symbol) + if !isClassLike(currentInfo) + classInfo <- findClassInfo(symbol.owner) + } yield { + classInfo.signature match { + case sig: ClassSignature => + methodInParentSignature(sig, currentInfo, sig) + case _ => Nil + } + } + results.getOrElse(Seq.empty) + } + + private def methodInParentSignature( + currentClassSig: ClassSignature, + bottomSymbol: SymbolInformation, + bottomClassSig: ClassSignature, + ): Seq[Location] = { + currentClassSig.parents.flatMap { + case parentSym: TypeRef => + val parentTextDocument = findSemanticDbForSymbol(parentSym.symbol) + def search(symbol: String) = + parentTextDocument.flatMap(findSymbol(_, symbol)) + search(parentSym.symbol).map(_.signature) match { + case Some(parenClassSig: ClassSignature) => + val fromParent = methodInParentSignature( + parenClassSig, + bottomSymbol, + bottomClassSig, + ) + if (fromParent.isEmpty) { + locationFromClass( + bottomSymbol, + parenClassSig, + search, + parentTextDocument, + ) + } else { + fromParent + } + case _ => Nil + } + + case _ => Nil + } + } + + private def locationFromClass( + bottomSymbolInformation: SymbolInformation, + parentClassSig: ClassSignature, + search: String => Option[SymbolInformation], + parentTextDocument: Option[TextDocument], + ): Option[Location] = { + val matchingSymbol = MethodImplementation.findParentSymbol( + bottomSymbolInformation, + parentClassSig, + search, + ) + for { + symbol <- matchingSymbol + parentDoc <- parentTextDocument + source = workspace.resolve(parentDoc.uri) + implOccurrence <- findDefOccurrence( + parentDoc, + symbol, + source, + scalaVersionSelector + ) + range <- implOccurrence.range + distance = buffer.tokenEditDistance( + source, + parentDoc.text, + trees, + ) + revised <- distance.toRevised(range.toLsp) + } yield new Location(source.toNIO.toUri().toString(), revised) + } +} + +object SymbolHierarchyOps { + def findSymbol( + semanticDb: TextDocument, + symbol: String, + ): Option[SymbolInformation] = { + semanticDb.symbols + .find(sym => sym.symbol == symbol) + } + + implicit class XtensionGlobalSymbolTable(symtab: GlobalSymbolTable) { + def safeInfo(symbol: String): Option[SymbolInformation] = + try { + symtab.info(symbol) + } catch { + case NonFatal(_) => None + } + } + + def isClassLike(info: SymbolInformation): Boolean = + info.isObject || info.isClass || info.isTrait || info.isInterface + + def findDefOccurrence( + semanticDb: TextDocument, + symbol: String, + source: AbsolutePath, + scalaVersionSelector: ScalaVersionSelector + ): Option[SymbolOccurrence] = { + def isDefinitionOccurrence(occ: SymbolOccurrence) = + occ.role.isDefinition && occ.symbol == symbol + + semanticDb.occurrences + .find(isDefinitionOccurrence) + .orElse( + Mtags + .allToplevels(source.toInput, scalaVersionSelector.getDialect(source)) + .occurrences + .find(isDefinitionOccurrence) + ) + } +} From ecdc1668af5e81bde3158687e7d4f07bd698f15c Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Tue, 16 Jan 2024 14:03:18 +0100 Subject: [PATCH 21/26] don't use scala pc for java --- .../main/scala/scala/meta/internal/metals/Compilers.scala | 8 ++------ tests/unit/src/main/scala/tests/BaseRangesSuite.scala | 1 - .../src/test/scala/tests/ImplementationLspSuite.scala | 7 ++++--- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala index b179928c4a8..13ebf9f8a27 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala @@ -789,7 +789,7 @@ class Compilers( path: AbsolutePath, symbol: String, ): Future[Seq[PcSymbolInformation]] = { - loadCompiler(path, forceScala = true) + loadCompiler(path) .map(_.info(symbol).asScala.map(_.asScala.toSeq.map(PcSymbolInformation.from))) .getOrElse(Future(Nil)) } @@ -854,10 +854,7 @@ class Compilers( }.getOrElse(Future.successful(Nil.asJava)) } - def loadCompiler( - path: AbsolutePath, - forceScala: Boolean = false - ): Option[PresentationCompiler] = { + def loadCompiler(path: AbsolutePath): Option[PresentationCompiler] = { def fromBuildTarget: Option[PresentationCompiler] = { val target = buildTargets @@ -867,7 +864,6 @@ class Compilers( case None => Some(fallbackCompiler(path)) case Some(value) => if (path.isScalaFilename) loadCompiler(value) - else if (path.isJavaFilename && forceScala) loadCompiler(value).orElse(loadJavaCompiler(value)) else if (path.isJavaFilename) loadJavaCompiler(value) else None } diff --git a/tests/unit/src/main/scala/tests/BaseRangesSuite.scala b/tests/unit/src/main/scala/tests/BaseRangesSuite.scala index 2cd714dece7..4fb25c4ac66 100644 --- a/tests/unit/src/main/scala/tests/BaseRangesSuite.scala +++ b/tests/unit/src/main/scala/tests/BaseRangesSuite.scala @@ -64,7 +64,6 @@ abstract class BaseRangesSuite(name: String) extends BaseLspSuite(name) { _ <- Future.sequence( files.map(file => server.didOpen(s"${file._1}")) ) - _ = pprint.log(server.client.diagnostics) _ <- assertCheck(filename, edit, expected, base) _ <- server.shutdown() } yield () diff --git a/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala b/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala index e27507fed36..79b5f9ba1a5 100644 --- a/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala +++ b/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala @@ -254,7 +254,7 @@ class ImplementationLspSuite extends BaseImplementationSuite("implementation") { |""".stripMargin, ) - // we have to collect information about overridden symbols in JavaMtags for this to work + // we currently don't collect information about overridden symbols in JavaMtags check( "java-classes".ignore, """|/a/src/main/scala/a/Main.scala @@ -570,8 +570,9 @@ class ImplementationLspSuite extends BaseImplementationSuite("implementation") { |""".stripMargin, ) + // missing implementation of `info` for java pc check( - "java-implementation", + "java-implementation".ignore, """|/a/src/main/scala/a/Main.java |package a; |public class Main { @@ -593,7 +594,7 @@ class ImplementationLspSuite extends BaseImplementationSuite("implementation") { ) check( - "java-scala-implementation", + "java-scala-implementation".ignore, """|/a/src/main/scala/a/Test.scala |package a |class <> extends TestJava with TestScala {} From 1d86b8e586b4f5a6f19ed306b39a200000193f72 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Tue, 16 Jan 2024 16:19:14 +0100 Subject: [PATCH 22/26] make sure found implementations are within build target --- .../ImplementationProvider.scala | 54 ++++++++++--------- .../implementation/InheritanceContext.scala | 7 ++- .../meta/internal/metals/BuildTargets.scala | 34 +++++++++--- .../internal/metals/MetalsLspService.scala | 1 + 4 files changed, 64 insertions(+), 32 deletions(-) diff --git a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala index 8bb676311df..e74611304ac 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala @@ -11,6 +11,7 @@ import scala.concurrent.Future import scala.meta.internal.io.FileIO import scala.meta.internal.metals.Buffers +import scala.meta.internal.metals.BuildTargets import scala.meta.internal.metals.Compilers import scala.meta.internal.metals.DefinitionProvider import scala.meta.internal.metals.MetalsEnrichments._ @@ -42,11 +43,12 @@ import scala.meta.internal.semanticdb.TypeRef import scala.meta.internal.semanticdb.TypeSignature import scala.meta.io.AbsolutePath +import ch.epfl.scala.bsp4j.BuildTargetIdentifier import org.eclipse.lsp4j.Location import org.eclipse.lsp4j.TextDocumentPositionParams final class ImplementationProvider( - semanticdbs: Semanticdbs, + semanticdbs: Semanticdbs, workspace: AbsolutePath, index: GlobalSymbolIndex, buffer: Buffers, @@ -54,6 +56,7 @@ final class ImplementationProvider( trees: Trees, scalaVersionSelector: ScalaVersionSelector, compilers: Compilers, + buildTargets: BuildTargets )(implicit ec: ExecutionContext, rc: ReportContext) extends SemanticdbFeatureProvider { import ImplementationProvider._ @@ -169,21 +172,22 @@ final class ImplementationProvider( _.path.isWorkspaceSource(workspace) ) + val workspaceInheritanceContext: InheritanceContext = + InheritanceContext.fromDefinitions( + implementationsInPath.asScala.toMap + ) + val inheritanceContext: InheritanceContext = - if (!isWorkspaceSymbol) { - // symbol is not in workspace, we search both workspace and dependencies for it - InheritanceContext - .fromDefinitions(implementationsInPath.asScala.toMap) + if (isWorkspaceSymbol) workspaceInheritanceContext + else + // symbol is not defined in the workspace, we search both workspace and dependencies for it + workspaceInheritanceContext .toGlobal( compilers, implementationsInDependencySources.asScala.toMap, + source, ) - } else { - // symbol in workspace we search the workspace for it only - InheritanceContext.fromDefinitions( - implementationsInPath.asScala.toMap - ) - } + symbolLocationsFromContext( dealisedSymbol, currentDocument, @@ -210,6 +214,7 @@ final class ImplementationProvider( classSymbol: String, textDocument: TextDocument, implReal: ClassLocation, + source: AbsolutePath, ): Option[Future[String]] = { if (classLikeKinds(info.kind)) Some(Future(implReal.symbol)) else { @@ -231,18 +236,14 @@ final class ImplementationProvider( def pcSearch = { val symbol = s"${implReal.symbol}${info.symbol.stripPrefix(classSymbol)}" - implReal.file - .map(path => - compilers - .infoAll(AbsolutePath(path), symbol) - .map { allFound => - allFound - .find(implInfo => implInfo.overridden.contains(info.symbol)) - .map(_.symbol) - .getOrElse(symbol) - } - ) - .getOrElse(Future.successful(symbol)) + compilers + .infoAll(source, symbol) + .map { allFound => + allFound + .find(implInfo => implInfo.overridden.contains(info.symbol)) + .map(_.symbol) + .getOrElse(symbol) + } } tryFromDoc.orElse { if (implReal.symbol.isLocal) None @@ -258,11 +259,13 @@ final class ImplementationProvider( locationsByFile: Map[Path, Set[ClassLocation]], parentSymbol: PcSymbolInformation, classSymbol: String, + buildTarget: BuildTargetIdentifier ) = Future.sequence({ for { file <- files locations = locationsByFile(file) implPath = AbsolutePath(file) + if(buildTargets.belongsToBuildTarget(buildTarget, implPath)) implDocument <- findSemanticdb(implPath).toList } yield { for { @@ -273,6 +276,7 @@ final class ImplementationProvider( classSymbol, implDocument, _, + source, ) ) ) @@ -285,7 +289,7 @@ final class ImplementationProvider( implDocument, sym, implPath, - scalaVersionSelector + scalaVersionSelector, ).toList range <- implOccurrence.range revised <- @@ -310,6 +314,7 @@ final class ImplementationProvider( (for { symbolInfo <- optSymbolInfo symbolClass <- classFromSymbol(symbolInfo) + target <- buildTargets.inverseSources(source) } yield { for { locationsByFile <- findImplementation( @@ -328,6 +333,7 @@ final class ImplementationProvider( locationsByFile, symbolInfo, symbolClass, + target ) ) ) diff --git a/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala b/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala index b696fab0b97..17a49857e63 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala @@ -25,10 +25,12 @@ class InheritanceContext(inheritance: Map[String, Set[ClassLocation]]) { def toGlobal( compilers: Compilers, implementationsInDependencySources: Map[String, Set[ClassLocation]], + source: AbsolutePath ) = new GlobalInheritanceContext( compilers, implementationsInDependencySources, inheritance, + source ) } @@ -36,6 +38,7 @@ class GlobalInheritanceContext( compilers: Compilers, implementationsInDependencySources: Map[String, Set[ClassLocation]], localInheritance: Map[String, Set[ClassLocation]], + source: AbsolutePath ) extends InheritanceContext(localInheritance) { override def getLocations( symbol: String @@ -48,8 +51,8 @@ class GlobalInheritanceContext( val resolveGlobal = implementationsInDependencySources .getOrElse(shortName, Set.empty) - .collect { case loc @ ClassLocation(sym, Some(file)) => - compilers.info(AbsolutePath(file), sym).map { + .collect { case loc @ ClassLocation(sym, _) => + compilers.info(source, sym).map { case Some(symInfo) if symInfo.parents.contains(symbol) => Some(loc) case Some(symInfo) if symInfo.dealisedSymbol == symbol && symInfo.symbol != symbol => Some(loc) case _ => None diff --git a/metals/src/main/scala/scala/meta/internal/metals/BuildTargets.scala b/metals/src/main/scala/scala/meta/internal/metals/BuildTargets.scala index 114553c5103..6998abc10a7 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/BuildTargets.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/BuildTargets.scala @@ -240,6 +240,16 @@ final class BuildTargets private ( } } + def inverseSourcesAll( + source: AbsolutePath + ): Iterable[BuildTargetIdentifier] = { + val buildTargets = sourceBuildTargets(source) + val orSbtBuildTarget = + buildTargets.getOrElse(sbtBuildScalaTarget(source).toIterable) + if (orSbtBuildTarget.isEmpty) inferBuildTargets(source) + else orSbtBuildTarget + } + def inverseSourcesBsp( source: AbsolutePath )(implicit ec: ExecutionContext): Future[Option[BuildTargetIdentifier]] = { @@ -308,12 +318,16 @@ final class BuildTargets private ( */ def inferBuildTarget( source: AbsolutePath - ): Option[BuildTargetIdentifier] = { + ): Option[BuildTargetIdentifier] = inferBuildTargets(source).headOption + + def inferBuildTargets( + source: AbsolutePath + ): Iterable[BuildTargetIdentifier] = { if (source.isJarFileSystem) { for { - jarName <- source.jarPath.map(_.filename) - sourceJarFile <- sourceJarFile(jarName) - buildTargetId <- inverseDependencySource(sourceJarFile).headOption + jarName <- source.jarPath.map(_.filename).toIterable + sourceJarFile <- sourceJarFile(jarName).toIterable + buildTargetId <- inverseDependencySource(sourceJarFile) } yield buildTargetId } else { val readonly = workspace.resolve(Directories.readonly) @@ -323,8 +337,8 @@ final class BuildTargets private ( names match { case Directories.dependenciesName :: jarName :: _ => // match build target by source jar name - sourceJarFile(jarName) - .flatMap(inverseDependencySource(_).headOption) + sourceJarFile(jarName).toList + .flatMap(inverseDependencySource(_)) case _ => None } case None => @@ -343,6 +357,14 @@ final class BuildTargets private ( } } + def belongsToBuildTarget( + target: BuildTargetIdentifier, + path: AbsolutePath + ): Boolean = { + val possibleBuildTargets = target :: buildTargetTransitiveDependencies(target).toList + inverseSourcesAll(path).exists(possibleBuildTargets.contains) + } + def findByDisplayName(name: String): Option[BuildTarget] = { data .fromIterators(_.buildTargetInfo.valuesIterator) diff --git a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala index e93eaf65c57..f205247ed3e 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala @@ -655,6 +655,7 @@ class MetalsLspService( trees, scalaVersionSelector, compilers, + buildTargets ) private val symbolHierarchyOps: SymbolHierarchyOps = From 8c6fdbcbe12db1ace7eebc140301301428904c70 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Tue, 16 Jan 2024 16:19:37 +0100 Subject: [PATCH 23/26] fix oom + format --- .../ImplementationProvider.scala | 8 ++--- .../implementation/InheritanceContext.scala | 16 +++++---- .../meta/internal/metals/BuildTargets.scala | 7 ++-- .../meta/internal/metals/Compilers.scala | 8 +++-- .../internal/metals/MetalsLspService.scala | 4 +-- .../internal/search/SymbolHierarchyOps.scala | 4 +-- .../meta/internal/metals/ReportContext.scala | 6 ++-- .../internal/pc/PcSymbolInformation.scala | 14 +++++--- .../internal/pc/WorkspaceSymbolSearch.scala | 35 ++++++++++-------- .../internal/pc/completions/Completions.scala | 4 +-- .../pc/ScalaPresentationCompiler.scala | 12 ++++--- .../pc/SymbolInformationProvider.scala | 36 ++++++++++--------- .../internal/mtags/ScalaToplevelMtags.scala | 10 ++++-- .../main/scala/tests/BaseRangesSuite.scala | 3 +- .../scala/tests/ImplementationLspSuite.scala | 7 ++-- .../test/scala/tests/ScalaToplevelSuite.scala | 5 +-- 16 files changed, 107 insertions(+), 72 deletions(-) diff --git a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala index e74611304ac..28f9c8dd510 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala @@ -56,7 +56,7 @@ final class ImplementationProvider( trees: Trees, scalaVersionSelector: ScalaVersionSelector, compilers: Compilers, - buildTargets: BuildTargets + buildTargets: BuildTargets, )(implicit ec: ExecutionContext, rc: ReportContext) extends SemanticdbFeatureProvider { import ImplementationProvider._ @@ -259,13 +259,13 @@ final class ImplementationProvider( locationsByFile: Map[Path, Set[ClassLocation]], parentSymbol: PcSymbolInformation, classSymbol: String, - buildTarget: BuildTargetIdentifier + buildTarget: BuildTargetIdentifier, ) = Future.sequence({ for { file <- files locations = locationsByFile(file) implPath = AbsolutePath(file) - if(buildTargets.belongsToBuildTarget(buildTarget, implPath)) + if (buildTargets.belongsToBuildTarget(buildTarget, implPath)) implDocument <- findSemanticdb(implPath).toList } yield { for { @@ -333,7 +333,7 @@ final class ImplementationProvider( locationsByFile, symbolInfo, symbolClass, - target + target, ) ) ) diff --git a/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala b/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala index 17a49857e63..acfa9cf54be 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala @@ -25,12 +25,12 @@ class InheritanceContext(inheritance: Map[String, Set[ClassLocation]]) { def toGlobal( compilers: Compilers, implementationsInDependencySources: Map[String, Set[ClassLocation]], - source: AbsolutePath + source: AbsolutePath, ) = new GlobalInheritanceContext( compilers, implementationsInDependencySources, inheritance, - source + source, ) } @@ -38,7 +38,7 @@ class GlobalInheritanceContext( compilers: Compilers, implementationsInDependencySources: Map[String, Set[ClassLocation]], localInheritance: Map[String, Set[ClassLocation]], - source: AbsolutePath + source: AbsolutePath, ) extends InheritanceContext(localInheritance) { override def getLocations( symbol: String @@ -52,9 +52,11 @@ class GlobalInheritanceContext( implementationsInDependencySources .getOrElse(shortName, Set.empty) .collect { case loc @ ClassLocation(sym, _) => - compilers.info(source, sym).map { + compilers.info(source, sym).map { case Some(symInfo) if symInfo.parents.contains(symbol) => Some(loc) - case Some(symInfo) if symInfo.dealisedSymbol == symbol && symInfo.symbol != symbol => Some(loc) + case Some(symInfo) + if symInfo.dealisedSymbol == symbol && symInfo.symbol != symbol => + Some(loc) case _ => None } } @@ -68,7 +70,9 @@ class GlobalInheritanceContext( object InheritanceContext { - def fromDefinitions(localDefinitions: Map[Path, Map[String, Set[ClassLocation]]]): InheritanceContext = { + def fromDefinitions( + localDefinitions: Map[Path, Map[String, Set[ClassLocation]]] + ): InheritanceContext = { val inheritance = mutable.Map .empty[String, Set[ClassLocation]] for { diff --git a/metals/src/main/scala/scala/meta/internal/metals/BuildTargets.scala b/metals/src/main/scala/scala/meta/internal/metals/BuildTargets.scala index 6998abc10a7..246df86b141 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/BuildTargets.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/BuildTargets.scala @@ -358,10 +358,11 @@ final class BuildTargets private ( } def belongsToBuildTarget( - target: BuildTargetIdentifier, - path: AbsolutePath + target: BuildTargetIdentifier, + path: AbsolutePath, ): Boolean = { - val possibleBuildTargets = target :: buildTargetTransitiveDependencies(target).toList + val possibleBuildTargets = + target :: buildTargetTransitiveDependencies(target).toList inverseSourcesAll(path).exists(possibleBuildTargets.contains) } diff --git a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala index 13ebf9f8a27..db6bb01380c 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala @@ -790,14 +790,18 @@ class Compilers( symbol: String, ): Future[Seq[PcSymbolInformation]] = { loadCompiler(path) - .map(_.info(symbol).asScala.map(_.asScala.toSeq.map(PcSymbolInformation.from))) + .map( + _.info(symbol).asScala + .map(_.asScala.toSeq.map(PcSymbolInformation.from)) + ) .getOrElse(Future(Nil)) } def info( path: AbsolutePath, symbol: String, - ): Future[Option[PcSymbolInformation]] = infoAll(path, symbol).map(_.find(_.symbol == symbol)) + ): Future[Option[PcSymbolInformation]] = + infoAll(path, symbol).map(_.find(_.symbol == symbol)) private def definition( params: TextDocumentPositionParams, diff --git a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala index f205247ed3e..364c92dcee6 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala @@ -655,7 +655,7 @@ class MetalsLspService( trees, scalaVersionSelector, compilers, - buildTargets + buildTargets, ) private val symbolHierarchyOps: SymbolHierarchyOps = @@ -666,7 +666,7 @@ class MetalsLspService( definitionIndex, scalaVersionSelector, buffers, - trees + trees, ) private val supermethods: Supermethods = new Supermethods( diff --git a/metals/src/main/scala/scala/meta/internal/search/SymbolHierarchyOps.scala b/metals/src/main/scala/scala/meta/internal/search/SymbolHierarchyOps.scala index 4e83dbd7979..dd797ffe8cb 100644 --- a/metals/src/main/scala/scala/meta/internal/search/SymbolHierarchyOps.scala +++ b/metals/src/main/scala/scala/meta/internal/search/SymbolHierarchyOps.scala @@ -172,7 +172,7 @@ class SymbolHierarchyOps( parentDoc, symbol, source, - scalaVersionSelector + scalaVersionSelector, ) range <- implOccurrence.range distance = buffer.tokenEditDistance( @@ -210,7 +210,7 @@ object SymbolHierarchyOps { semanticDb: TextDocument, symbol: String, source: AbsolutePath, - scalaVersionSelector: ScalaVersionSelector + scalaVersionSelector: ScalaVersionSelector, ): Option[SymbolOccurrence] = { def isDefinitionOccurrence(occ: SymbolOccurrence) = occ.role.isDefinition && occ.symbol == symbol diff --git a/mtags-shared/src/main/scala/scala/meta/internal/metals/ReportContext.scala b/mtags-shared/src/main/scala/scala/meta/internal/metals/ReportContext.scala index daea359efff..0f8131a1c29 100644 --- a/mtags-shared/src/main/scala/scala/meta/internal/metals/ReportContext.scala +++ b/mtags-shared/src/main/scala/scala/meta/internal/metals/ReportContext.scala @@ -141,9 +141,9 @@ class StdReporter( optDuplicate.orElse { Try { - path.createDirectories() - path.writeText(sanitize(report.fullText(withIdAndSummary = true))) - path + path.createDirectories() + path.writeText(sanitize(report.fullText(withIdAndSummary = true))) + path }.toOption } } diff --git a/mtags-shared/src/main/scala/scala/meta/internal/pc/PcSymbolInformation.scala b/mtags-shared/src/main/scala/scala/meta/internal/pc/PcSymbolInformation.scala index 995735e118d..2678512d416 100644 --- a/mtags-shared/src/main/scala/scala/meta/internal/pc/PcSymbolInformation.scala +++ b/mtags-shared/src/main/scala/scala/meta/internal/pc/PcSymbolInformation.scala @@ -25,15 +25,21 @@ case class PcSymbolInformation( } object PcSymbolInformation { - def from(info: IPcSymbolInformation): PcSymbolInformation = + def from(info: IPcSymbolInformation): PcSymbolInformation = PcSymbolInformation( info.symbol(), - Try(PcSymbolKind.withName(info.kindString())).getOrElse(PcSymbolKind.UNKNOWN_KIND), + Try(PcSymbolKind.withName(info.kindString())) + .getOrElse(PcSymbolKind.UNKNOWN_KIND), info.parentsList().asScala.toList, info.dealisedSymbol(), - if(info.classOwnerString().nonEmpty) Some(info.classOwnerString()) else None, + if (info.classOwnerString().nonEmpty) Some(info.classOwnerString()) + else None, info.overriddenList().asScala.toList, - info.propertiesList().asScala.toList.flatMap(name => Try(PcSymbolProperty.withName(name)).toOption) + info + .propertiesList() + .asScala + .toList + .flatMap(name => Try(PcSymbolProperty.withName(name)).toOption) ) } diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/WorkspaceSymbolSearch.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/WorkspaceSymbolSearch.scala index cf0a76db2ba..30e54d5c693 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/WorkspaceSymbolSearch.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/WorkspaceSymbolSearch.scala @@ -13,9 +13,7 @@ trait WorkspaceSymbolSearch { this: MetalsGlobal => def info(symbol: String): List[PcSymbolInformation] = { val index = symbol.lastIndexOf("/") val pkgString = symbol.take(index + 1) - val pkg = packageSymbolFromString(pkgString).getOrElse( - throw new NoSuchElementException(pkgString) - ) + val pkg = packageSymbolFromString(pkgString) def loop( symbol: String, @@ -27,18 +25,22 @@ trait WorkspaceSymbolSearch { this: MetalsGlobal => val rest = symbol.drop(newSymbol.size) loop(rest.drop(1), (newSymbol, rest.headOption.exists(_ == '#')) :: acc) } - + val names = loop(symbol.drop(index + 1).takeWhile(_ != '('), List.empty) - val compilerSymbols = names.foldLeft(List(pkg)) { case (owners, (name, isClass)) => - owners.flatMap{ owner => + + val compilerSymbols = names.foldLeft(pkg.toList) { + case (owners, (name, isClass)) => + owners.flatMap { owner => val foundChild = if (isClass) owner.info.member(TypeName(name)) else owner.info.member(TermName(name)) - foundChild.info match { - case OverloadedType(_, alts) => alts - case _ => List(foundChild) - } + if (foundChild.exists) { + foundChild.info match { + case OverloadedType(_, alts) => alts + case _ => List(foundChild) + } + } else Nil } } @@ -48,16 +50,21 @@ trait WorkspaceSymbolSearch { this: MetalsGlobal => kind = getSymbolKind(compilerSymbol), parents = compilerSymbol.parentSymbols.map(semanticdbSymbol), dealisedSymbol = semanticdbSymbol(compilerSymbol.dealiased), - classOwner = compilerSymbol.ownerChain.find(c => c.isClass || c.isModule).map(semanticdbSymbol), + classOwner = compilerSymbol.ownerChain + .find(c => c.isClass || c.isModule) + .map(semanticdbSymbol), overridden = compilerSymbol.overrides.map(semanticdbSymbol), - properties = if(compilerSymbol.isAbstractClass || compilerSymbol.isAbstractType) List(PcSymbolProperty.ABSTRACT) else Nil + properties = + if (compilerSymbol.isAbstractClass || compilerSymbol.isAbstractType) + List(PcSymbolProperty.ABSTRACT) + else Nil ) ) } private def getSymbolKind(sym: Symbol): PcSymbolKind.PcSymbolKind = - if(sym.isJavaInterface) PcSymbolKind.INTERFACE - else if(sym.isTrait) PcSymbolKind.TRAIT + if (sym.isJavaInterface) PcSymbolKind.INTERFACE + else if (sym.isTrait) PcSymbolKind.TRAIT else if (sym.isConstructor) PcSymbolKind.CONSTRUCTOR else if (sym.isPackageObject) PcSymbolKind.PACKAGE_OBJECT else if (sym.isClass) PcSymbolKind.CLASS diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/completions/Completions.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/completions/Completions.scala index 06412cbbcf7..a0671887dcb 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/completions/Completions.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/completions/Completions.scala @@ -76,8 +76,8 @@ trait Completions { this: MetalsGlobal => val packageSymbols: mutable.Map[String, Option[Symbol]] = mutable.Map.empty[String, Option[Symbol]] - def packageSymbolFromString(symbol: String): Option[Symbol] = - if(symbol == "_empty_/") Some(rootMirror.EmptyPackage) + def packageSymbolFromString(symbol: String): Option[Symbol] = + if (symbol == "_empty_/") Some(rootMirror.EmptyPackage) else { packageSymbols.getOrElseUpdate( symbol, { diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala index f0c67ca0320..8085adb45f1 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala @@ -23,7 +23,7 @@ import scala.meta.internal.mtags.BuildInfo import scala.meta.internal.pc.completions.CompletionProvider import scala.meta.internal.pc.completions.OverrideCompletions import scala.meta.pc.* -import scala.meta.pc.{PcSymbolInformation => IPcSymbolInformation} +import scala.meta.pc.{PcSymbolInformation as IPcSymbolInformation} import dotty.tools.dotc.reporting.StoreReporter import org.eclipse.lsp4j.DocumentHighlight @@ -181,11 +181,15 @@ case class ScalaPresentationCompiler( override def info( symbol: String ): CompletableFuture[ju.List[IPcSymbolInformation]] = - compilerAccess.withNonInterruptableCompiler[ju.List[IPcSymbolInformation]](None)( + compilerAccess.withNonInterruptableCompiler[ju.List[IPcSymbolInformation]]( + None + )( Nil.asJava, - EmptyCancelToken + EmptyCancelToken, ) { access => - SymbolInformationProvider(using access.compiler().currentCtx).info(symbol).asJava + SymbolInformationProvider(using access.compiler().currentCtx) + .info(symbol) + .asJava } def semanticdbTextDocument( diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/SymbolInformationProvider.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/SymbolInformationProvider.scala index 6e30ecf6de5..f23bc7c1647 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/SymbolInformationProvider.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/SymbolInformationProvider.scala @@ -19,13 +19,17 @@ class SymbolInformationProvider(using Context): ): List[Symbol] = def collectSymbols(denotation: Denotation): List[Symbol] = denotation match - case MultiDenotation(denot1, denot2) => collectSymbols(denot1) ++ collectSymbols(denot2) + case MultiDenotation(denot1, denot2) => + collectSymbols(denot1) ++ collectSymbols(denot2) case denot => List(denot.symbol) - - def loop(owners: List[Symbol], parts: List[(String, Boolean)]): List[Symbol] = + + def loop( + owners: List[Symbol], + parts: List[(String, Boolean)], + ): List[Symbol] = parts match case (head, isClass) :: tl => - val foundSymbols = + val foundSymbols = owners.flatMap { owner => val next = if isClass then owner.info.member(typeName(head)) @@ -60,21 +64,19 @@ class SymbolInformationProvider(using Context): val foundSymbols = try toSymbols(pkg, names) - catch case NonFatal(e) => - pprint.log(e) - Nil + catch case NonFatal(e) => Nil - for - sym <- foundSymbols - yield { + for sym <- foundSymbols + yield val classSym = if sym.isClass then sym else sym.moduleClass val parents = - if(classSym.isClass) + if classSym.isClass then classSym.asClass.parentSyms.map(SemanticdbSymbols.symbolName) else Nil - val dealisedSymbol = - if(sym.isAliasType) sym.info.metalsDealias.typeSymbol else sym - val classOwner = sym.ownersIterator.drop(1).find(s => s.isClass || s.is(Flags.Module)) + val dealisedSymbol = + if sym.isAliasType then sym.info.metalsDealias.typeSymbol else sym + val classOwner = + sym.ownersIterator.drop(1).find(s => s.isClass || s.is(Flags.Module)) val overridden = sym.denot.allOverriddenSymbols.toList PcSymbolInformation( @@ -84,9 +86,11 @@ class SymbolInformationProvider(using Context): dealisedSymbol = SemanticdbSymbols.symbolName(dealisedSymbol), classOwner = classOwner.map(SemanticdbSymbols.symbolName), overridden = overridden.map(SemanticdbSymbols.symbolName), - properties = if sym.is(Flags.Abstract) then List(PcSymbolProperty.ABSTRACT) else Nil + properties = + if sym.is(Flags.Abstract) then List(PcSymbolProperty.ABSTRACT) + else Nil, ) - } + end for end info private def getSymbolKind(sym: Symbol): PcSymbolKind.PcSymbolKind = diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala index b27656b17a4..2e7484e05ed 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala @@ -297,7 +297,7 @@ class ScalaToplevelMtags( } else scanner.nextToken() loop(indent, isAfterNewline = false, currRegion, newExpectIgnoreBody) case TYPE if expectTemplate.map(!_.isExtension).getOrElse(true) => - if (needEmitMember(currRegion)) { + if (needEmitMember(currRegion) && !prevWasDot) { withOwner(currRegion.termOwner) { emitType(needEmitTermMember()) } @@ -672,7 +672,10 @@ class ScalaToplevelMtags( } nextIsNL() @tailrec - def loop(name: Option[String], isAfterEq: Boolean = false): Option[String] = { + def loop( + name: Option[String], + isAfterEq: Boolean = false + ): Option[String] = { scanner.curr.token match { case SEMI => name case _ if isNewline | isDone => name @@ -690,7 +693,8 @@ class ScalaToplevelMtags( acceptBalancedDelimeters(LBRACE, RBRACE) scanner.nextToken() loop(name, isAfterEq) - case IDENTIFIER if isAfterEq && scanner.curr.name != "|" && scanner.curr.name != "&" => + case IDENTIFIER + if isAfterEq && scanner.curr.name != "|" && scanner.curr.name != "&" => val optName = selectName() loop(optName, isAfterEq) case _ if isAfterEq => None diff --git a/tests/unit/src/main/scala/tests/BaseRangesSuite.scala b/tests/unit/src/main/scala/tests/BaseRangesSuite.scala index 4fb25c4ac66..7b96b99f25f 100644 --- a/tests/unit/src/main/scala/tests/BaseRangesSuite.scala +++ b/tests/unit/src/main/scala/tests/BaseRangesSuite.scala @@ -5,7 +5,6 @@ import scala.concurrent.Future import munit.Location import munit.TestOptions - abstract class BaseRangesSuite(name: String) extends BaseLspSuite(name) { protected def libraryDependencies: List[String] = Nil @@ -22,7 +21,7 @@ abstract class BaseRangesSuite(name: String) extends BaseLspSuite(name) { input: String, scalaVersion: Option[String] = None, additionalLibraryDependencies: List[String] = Nil, - scalacOptions: List[String] = Nil + scalacOptions: List[String] = Nil, )(implicit loc: Location ): Unit = { diff --git a/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala b/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala index 79b5f9ba1a5..b6d80e8e72d 100644 --- a/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala +++ b/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala @@ -521,8 +521,9 @@ class ImplementationLspSuite extends BaseImplementationSuite("implementation") { | case object <> extends Animal |} |""".stripMargin, - additionalLibraryDependencies = List("io.circe::circe-generic-extras:0.14.0"), - scalacOptions = List("-Ymacro-annotations") + additionalLibraryDependencies = + List("io.circe::circe-generic-extras:0.14.0"), + scalacOptions = List("-Ymacro-annotations"), ) check( @@ -539,7 +540,7 @@ class ImplementationLspSuite extends BaseImplementationSuite("implementation") { |""".stripMargin, ) - check( + check( "local-methods", """|/a/src/main/scala/a/Main.scala |object Test { diff --git a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala index 4f8d800eb93..90bf52ca242 100644 --- a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala +++ b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala @@ -635,7 +635,7 @@ class ScalaToplevelSuite extends BaseSuite { mode = All, ) - check( + check( "overridden-type-alias", """|package a |object O { @@ -650,7 +650,8 @@ class ScalaToplevelSuite extends BaseSuite { | case Iterable[t] => t |} |""".stripMargin, - List("a/", "a/O.", "a/O.A# -> Set", "a/O.H# -> List", "a/O.W# -> Set", "a/O.R# -> Set", "a/O.L# -> List", "a/O.Elem#"), + List("a/", "a/O.", "a/O.A# -> Set", "a/O.H# -> List", "a/O.W# -> Set", + "a/O.R# -> Set", "a/O.L# -> List", "a/O.Elem#"), mode = All, ) From 3542435a7fc3b8fb548b50d6785151e0fd0405c6 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Fri, 9 Feb 2024 12:26:07 +0100 Subject: [PATCH 24/26] review changes --- .../ImplementationProvider.scala | 28 ++++--- .../implementation/InheritanceContext.scala | 2 +- .../internal/metals/MetalsLspService.scala | 11 --- .../scala/meta/pc/PcSymbolInformation.java | 12 +-- .../main/java/scala/meta/pc/PcSymbolKind.java | 28 +++++++ .../java/scala/meta/pc/PcSymbolProperty.java | 5 ++ .../internal/pc/PcSymbolInformation.scala | 82 ++++++++----------- .../pc/ScalaPresentationCompiler.scala | 3 +- .../internal/pc/WorkspaceSymbolSearch.scala | 8 +- .../pc/ScalaPresentationCompiler.scala | 1 + .../pc/SymbolInformationProvider.scala | 8 +- .../internal/mtags/SymbolIndexBucket.scala | 3 +- 12 files changed, 103 insertions(+), 88 deletions(-) create mode 100644 mtags-interfaces/src/main/java/scala/meta/pc/PcSymbolKind.java create mode 100644 mtags-interfaces/src/main/java/scala/meta/pc/PcSymbolProperty.java diff --git a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala index 28f9c8dd510..1064b600d8e 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala @@ -30,8 +30,6 @@ import scala.meta.internal.mtags.UnresolvedOverriddenSymbol import scala.meta.internal.mtags.{Symbol => MSymbol} import scala.meta.internal.parsing.Trees import scala.meta.internal.pc.PcSymbolInformation -import scala.meta.internal.pc.PcSymbolKind -import scala.meta.internal.pc.PcSymbolProperty import scala.meta.internal.search.SymbolHierarchyOps._ import scala.meta.internal.semanticdb.ClassSignature import scala.meta.internal.semanticdb.Scala._ @@ -42,6 +40,8 @@ import scala.meta.internal.semanticdb.TextDocuments import scala.meta.internal.semanticdb.TypeRef import scala.meta.internal.semanticdb.TypeSignature import scala.meta.io.AbsolutePath +import scala.meta.pc.PcSymbolKind +import scala.meta.pc.PcSymbolProperty import ch.epfl.scala.bsp4j.BuildTargetIdentifier import org.eclipse.lsp4j.Location @@ -158,13 +158,13 @@ final class ImplementationProvider( // 2. Search inside workspace // 3. Search classpath via GlobalSymbolTable val sym = symbolOccurrence.symbol - val dealised = + val dealiased = if (sym.desc.isType) { symbolInfo(currentDocument, source, sym).map( - _.map(_.dealisedSymbol).getOrElse(sym) + _.map(_.dealiasedSymbol).getOrElse(sym) ) } else Future.successful(sym) - dealised.flatMap { dealisedSymbol => + dealiased.flatMap { dealisedSymbol => val isWorkspaceSymbol = (source.isWorkspaceSource(workspace) && currentDocument.definesSymbol(dealisedSymbol)) || @@ -203,7 +203,7 @@ final class ImplementationProvider( } private def symbolLocationsFromContext( - dealised: String, + dealiased: String, textDocument: TextDocument, source: AbsolutePath, inheritanceContext: InheritanceContext, @@ -240,7 +240,9 @@ final class ImplementationProvider( .infoAll(source, symbol) .map { allFound => allFound - .find(implInfo => implInfo.overridden.contains(info.symbol)) + .find(implInfo => + implInfo.overriddenSymbols.contains(info.symbol) + ) .map(_.symbol) .getOrElse(symbol) } @@ -310,7 +312,7 @@ final class ImplementationProvider( lazy val cores = Runtime.getRuntime().availableProcessors() val splitJobs = - symbolInfo(textDocument, source, dealised).flatMap { optSymbolInfo => + symbolInfo(textDocument, source, dealiased).flatMap { optSymbolInfo => (for { symbolInfo <- optSymbolInfo symbolClass <- classFromSymbol(symbolInfo) @@ -413,7 +415,7 @@ final class ImplementationProvider( } private def classFromSymbol(info: PcSymbolInformation): Option[String] = - if (classLikeKinds(info.kind)) Some(info.dealisedSymbol) + if (classLikeKinds(info.kind)) Some(info.dealiasedSymbol) else info.classOwner private def symbolInfo( @@ -461,12 +463,12 @@ final class ImplementationProvider( PcSymbolInformation( symbol = info.symbol, kind = PcSymbolKind.values - .find(_.id == info.kind.value) + .find(_.getValue == info.kind.value) .getOrElse(PcSymbolKind.UNKNOWN_KIND), parents = parents, - dealisedSymbol = info.symbol, + dealiasedSymbol = info.symbol, classOwner = classOwner, - overridden = info.overriddenSymbols.toList, + overriddenSymbols = info.overriddenSymbols.toList, properties = if (info.isAbstract) List(PcSymbolProperty.ABSTRACT) else Nil, ) } @@ -510,7 +512,7 @@ object ImplementationProvider { } } - val classLikeKinds: Set[PcSymbolKind.Value] = Set( + val classLikeKinds: Set[PcSymbolKind] = Set( PcSymbolKind.OBJECT, PcSymbolKind.CLASS, PcSymbolKind.TRAIT, diff --git a/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala b/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala index acfa9cf54be..5b8b14d088e 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/InheritanceContext.scala @@ -55,7 +55,7 @@ class GlobalInheritanceContext( compilers.info(source, sym).map { case Some(symInfo) if symInfo.parents.contains(symbol) => Some(loc) case Some(symInfo) - if symInfo.dealisedSymbol == symbol && symInfo.symbol != symbol => + if symInfo.dealiasedSymbol == symbol && symInfo.symbol != symbol => Some(loc) case _ => None } diff --git a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala index b6c307be8ce..84cb7e3dce0 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala @@ -541,16 +541,6 @@ class MetalsLspService( trees, ) - private val semanticDBIndexer: SemanticdbIndexer = new SemanticdbIndexer( - List( - referencesProvider, - implementationProvider, - testProvider, - ), - buildTargets, - folder, - ) - private val formattingProvider: FormattingProvider = new FormattingProvider( folder, buffers, @@ -684,7 +674,6 @@ class MetalsLspService( referencesProvider, implementationProvider, testProvider, - classpathTreeIndex, ), buildTargets, folder, diff --git a/mtags-interfaces/src/main/java/scala/meta/pc/PcSymbolInformation.java b/mtags-interfaces/src/main/java/scala/meta/pc/PcSymbolInformation.java index e8a67b19ef2..8189c1e6f18 100644 --- a/mtags-interfaces/src/main/java/scala/meta/pc/PcSymbolInformation.java +++ b/mtags-interfaces/src/main/java/scala/meta/pc/PcSymbolInformation.java @@ -4,10 +4,10 @@ public interface PcSymbolInformation { String symbol(); - String kindString(); - List parentsList(); - String dealisedSymbol(); - String classOwnerString(); - List overriddenList(); - List propertiesList(); + PcSymbolKind kind(); + List parents(); + String dealiasedSymbol(); + String classOwner(); + List overriddenSymbols(); + List properties(); } diff --git a/mtags-interfaces/src/main/java/scala/meta/pc/PcSymbolKind.java b/mtags-interfaces/src/main/java/scala/meta/pc/PcSymbolKind.java new file mode 100644 index 00000000000..345234a108b --- /dev/null +++ b/mtags-interfaces/src/main/java/scala/meta/pc/PcSymbolKind.java @@ -0,0 +1,28 @@ +package scala.meta.pc; + +public enum PcSymbolKind { + UNKNOWN_KIND(0), + METHOD(3), + MACRO(6), + TYPE(7), + PARAMETER(8), + TYPE_PARAMETER(9), + OBJECT(10), + PACKAGE(11), + PACKAGE_OBJECT(12), + CLASS(13), + TRAIT(14), + SELF_PARAMETER(17), + INTERFACE(18), + LOCAL(19), + FIELD(20), + CONSTRUCTOR(21); + + private int value; + + public int getValue(){return value;} + + private PcSymbolKind (int value) { + this.value = value; + } +} diff --git a/mtags-interfaces/src/main/java/scala/meta/pc/PcSymbolProperty.java b/mtags-interfaces/src/main/java/scala/meta/pc/PcSymbolProperty.java new file mode 100644 index 00000000000..0c85d0043ce --- /dev/null +++ b/mtags-interfaces/src/main/java/scala/meta/pc/PcSymbolProperty.java @@ -0,0 +1,5 @@ +package scala.meta.pc; + +public enum PcSymbolProperty { + ABSTRACT; +} diff --git a/mtags-shared/src/main/scala/scala/meta/internal/pc/PcSymbolInformation.scala b/mtags-shared/src/main/scala/scala/meta/internal/pc/PcSymbolInformation.scala index 2678512d416..e7560ffc6df 100644 --- a/mtags-shared/src/main/scala/scala/meta/internal/pc/PcSymbolInformation.scala +++ b/mtags-shared/src/main/scala/scala/meta/internal/pc/PcSymbolInformation.scala @@ -2,68 +2,52 @@ package scala.meta.internal.pc import java.{util => ju} -import scala.util.Try - import scala.meta.internal.jdk.CollectionConverters._ +import scala.meta.pc +import scala.meta.pc.PcSymbolProperty import scala.meta.pc.{PcSymbolInformation => IPcSymbolInformation} case class PcSymbolInformation( symbol: String, - kind: PcSymbolKind.PcSymbolKind, + kind: pc.PcSymbolKind, parents: List[String], - dealisedSymbol: String, + dealiasedSymbol: String, classOwner: Option[String], - overridden: List[String], - properties: List[PcSymbolProperty.PcSymbolProperty] -) extends IPcSymbolInformation { - - def overriddenList(): ju.List[String] = overridden.asJava - def classOwnerString(): String = classOwner.getOrElse("") - def kindString(): String = kind.toString() - def parentsList(): ju.List[String] = parents.asJava - def propertiesList(): ju.List[String] = properties.map(_.toString()).asJava + overriddenSymbols: List[String], + properties: List[PcSymbolProperty] +) { + def asJava: PcSymbolInformationJava = + PcSymbolInformationJava( + symbol, + kind, + parents.asJava, + dealiasedSymbol, + classOwner.getOrElse(""), + overriddenSymbols.asJava, + properties.asJava + ) } +case class PcSymbolInformationJava( + symbol: String, + kind: pc.PcSymbolKind, + parents: ju.List[String], + dealiasedSymbol: String, + classOwner: String, + overriddenSymbols: ju.List[String], + properties: ju.List[PcSymbolProperty] +) extends IPcSymbolInformation + object PcSymbolInformation { def from(info: IPcSymbolInformation): PcSymbolInformation = PcSymbolInformation( info.symbol(), - Try(PcSymbolKind.withName(info.kindString())) - .getOrElse(PcSymbolKind.UNKNOWN_KIND), - info.parentsList().asScala.toList, - info.dealisedSymbol(), - if (info.classOwnerString().nonEmpty) Some(info.classOwnerString()) + info.kind(), + info.parents().asScala.toList, + info.dealiasedSymbol(), + if (info.classOwner().nonEmpty) Some(info.classOwner()) else None, - info.overriddenList().asScala.toList, - info - .propertiesList() - .asScala - .toList - .flatMap(name => Try(PcSymbolProperty.withName(name)).toOption) + info.overriddenSymbols().asScala.toList, + info.properties().asScala.toList ) } - -object PcSymbolKind extends Enumeration { - type PcSymbolKind = Value - val UNKNOWN_KIND: Value = Value(0) - val METHOD: Value = Value(3) - val MACRO: Value = Value(6) - val TYPE: Value = Value(7) - val PARAMETER: Value = Value(8) - val TYPE_PARAMETER: Value = Value(9) - val OBJECT: Value = Value(10) - val PACKAGE: Value = Value(11) - val PACKAGE_OBJECT: Value = Value(12) - val CLASS: Value = Value(13) - val TRAIT: Value = Value(14) - val SELF_PARAMETER: Value = Value(17) - val INTERFACE: Value = Value(18) - val LOCAL: Value = Value(19) - val FIELD: Value = Value(20) - val CONSTRUCTOR: Value = Value(21) -} - -object PcSymbolProperty extends Enumeration { - type PcSymbolProperty = Value - val ABSTRACT: Value = Value(4) -} diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala index a37122a61b7..b39aae48a03 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala @@ -359,7 +359,8 @@ case class ScalaPresentationCompiler( Nil.asJava, EmptyCancelToken ) { pc => - val result: List[IPcSymbolInformation] = pc.compiler().info(symbol) + val result: List[IPcSymbolInformation] = + pc.compiler().info(symbol).map(_.asJava) result.asJava } } diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/WorkspaceSymbolSearch.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/WorkspaceSymbolSearch.scala index 30e54d5c693..b49c75c4982 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/WorkspaceSymbolSearch.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/WorkspaceSymbolSearch.scala @@ -4,6 +4,8 @@ import java.nio.file.Path import scala.util.control.NonFatal +import scala.meta.pc.PcSymbolKind +import scala.meta.pc.PcSymbolProperty import scala.meta.pc.SymbolSearchVisitor import org.eclipse.{lsp4j => l} @@ -49,11 +51,11 @@ trait WorkspaceSymbolSearch { this: MetalsGlobal => symbol = semanticdbSymbol(compilerSymbol), kind = getSymbolKind(compilerSymbol), parents = compilerSymbol.parentSymbols.map(semanticdbSymbol), - dealisedSymbol = semanticdbSymbol(compilerSymbol.dealiased), + dealiasedSymbol = semanticdbSymbol(compilerSymbol.dealiased), classOwner = compilerSymbol.ownerChain .find(c => c.isClass || c.isModule) .map(semanticdbSymbol), - overridden = compilerSymbol.overrides.map(semanticdbSymbol), + overriddenSymbols = compilerSymbol.overrides.map(semanticdbSymbol), properties = if (compilerSymbol.isAbstractClass || compilerSymbol.isAbstractType) List(PcSymbolProperty.ABSTRACT) @@ -62,7 +64,7 @@ trait WorkspaceSymbolSearch { this: MetalsGlobal => ) } - private def getSymbolKind(sym: Symbol): PcSymbolKind.PcSymbolKind = + private def getSymbolKind(sym: Symbol): PcSymbolKind = if (sym.isJavaInterface) PcSymbolKind.INTERFACE else if (sym.isTrait) PcSymbolKind.TRAIT else if (sym.isConstructor) PcSymbolKind.CONSTRUCTOR diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala index f765b333643..696973518a5 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala @@ -199,6 +199,7 @@ case class ScalaPresentationCompiler( ) { access => SymbolInformationProvider(using access.compiler().currentCtx) .info(symbol) + .map(_.asJava) .asJava } diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/SymbolInformationProvider.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/SymbolInformationProvider.scala index f23bc7c1647..41a281e9e1a 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/SymbolInformationProvider.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/SymbolInformationProvider.scala @@ -3,6 +3,8 @@ package scala.meta.internal.pc import scala.util.control.NonFatal import scala.meta.internal.mtags.MtagsEnrichments.metalsDealias +import scala.meta.pc.PcSymbolKind +import scala.meta.pc.PcSymbolProperty import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.core.Denotations.Denotation @@ -83,9 +85,9 @@ class SymbolInformationProvider(using Context): symbol = SemanticdbSymbols.symbolName(sym), kind = getSymbolKind(sym), parents = parents, - dealisedSymbol = SemanticdbSymbols.symbolName(dealisedSymbol), + dealiasedSymbol = SemanticdbSymbols.symbolName(dealisedSymbol), classOwner = classOwner.map(SemanticdbSymbols.symbolName), - overridden = overridden.map(SemanticdbSymbols.symbolName), + overriddenSymbols = overridden.map(SemanticdbSymbols.symbolName), properties = if sym.is(Flags.Abstract) then List(PcSymbolProperty.ABSTRACT) else Nil, @@ -93,7 +95,7 @@ class SymbolInformationProvider(using Context): end for end info - private def getSymbolKind(sym: Symbol): PcSymbolKind.PcSymbolKind = + private def getSymbolKind(sym: Symbol): PcSymbolKind = if sym.isAllOf(Flags.JavaInterface) then PcSymbolKind.INTERFACE else if sym.is(Flags.Trait) then PcSymbolKind.TRAIT else if sym.isConstructor then PcSymbolKind.CONSTRUCTOR 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 34733cc1f9e..ffac79cce78 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/SymbolIndexBucket.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/SymbolIndexBucket.scala @@ -72,7 +72,8 @@ class SymbolIndexBucket( case source if source.isJava => addJavaSourceFile(source) match { case Nil => None - case topLevels => Some(IndexingResult(source, topLevels, overrides = Nil)) + case topLevels => + Some(IndexingResult(source, topLevels, overrides = Nil)) } case _ => None From 0c22ffc4c7f3d5cefd8a97cb17e6aa6bdf633d7b Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Fri, 9 Feb 2024 12:38:41 +0100 Subject: [PATCH 25/26] use scala pc for java files --- .../scala/scala/meta/internal/metals/Compilers.scala | 9 +++++++-- .../src/test/scala/tests/ImplementationLspSuite.scala | 9 +++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala index db6bb01380c..bb99ebac4af 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala @@ -789,7 +789,7 @@ class Compilers( path: AbsolutePath, symbol: String, ): Future[Seq[PcSymbolInformation]] = { - loadCompiler(path) + loadCompiler(path, forceScala = true) .map( _.info(symbol).asScala .map(_.asScala.toSeq.map(PcSymbolInformation.from)) @@ -858,7 +858,10 @@ class Compilers( }.getOrElse(Future.successful(Nil.asJava)) } - def loadCompiler(path: AbsolutePath): Option[PresentationCompiler] = { + def loadCompiler( + path: AbsolutePath, + forceScala: Boolean = false, + ): Option[PresentationCompiler] = { def fromBuildTarget: Option[PresentationCompiler] = { val target = buildTargets @@ -868,6 +871,8 @@ class Compilers( case None => Some(fallbackCompiler(path)) case Some(value) => if (path.isScalaFilename) loadCompiler(value) + else if (path.isJavaFilename && forceScala) + loadCompiler(value).orElse(loadJavaCompiler(value)) else if (path.isJavaFilename) loadJavaCompiler(value) else None } diff --git a/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala b/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala index b6d80e8e72d..0411b7c22e2 100644 --- a/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala +++ b/tests/unit/src/test/scala/tests/ImplementationLspSuite.scala @@ -571,10 +571,11 @@ class ImplementationLspSuite extends BaseImplementationSuite("implementation") { |""".stripMargin, ) - // missing implementation of `info` for java pc check( - "java-implementation".ignore, - """|/a/src/main/scala/a/Main.java + "java-implementation", + """|/a/src/main/scala/a/Main.scala + |// empty scala file, so Scala pc is loaded + |/a/src/main/scala/a/Main.java |package a; |public class Main { | abstract class A { @@ -595,7 +596,7 @@ class ImplementationLspSuite extends BaseImplementationSuite("implementation") { ) check( - "java-scala-implementation".ignore, + "java-scala-implementation", """|/a/src/main/scala/a/Test.scala |package a |class <> extends TestJava with TestScala {} From 80d64f91dae5d3252d3a506107fb0b769c058816 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Fri, 23 Feb 2024 15:39:39 +0100 Subject: [PATCH 26/26] make `info` return result exactly for searched symbol --- .../ImplementationProvider.scala | 62 ++++++++++++++----- .../meta/internal/metals/BuildTargets.scala | 4 +- .../meta/internal/metals/Compilers.scala | 21 ++++--- .../scala/meta/pc/PcSymbolInformation.java | 2 + .../scala/meta/pc/PresentationCompiler.java | 4 +- .../internal/pc/PcSymbolInformation.scala | 4 ++ .../pc/ScalaPresentationCompiler.scala | 8 +-- .../internal/pc/WorkspaceSymbolSearch.scala | 44 ++++++++----- .../pc/ScalaPresentationCompiler.scala | 6 +- .../pc/SymbolInformationProvider.scala | 61 ++++++++++-------- 10 files changed, 140 insertions(+), 76 deletions(-) diff --git a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala index 1064b600d8e..5c4a9d40648 100644 --- a/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/implementation/ImplementationProvider.scala @@ -32,6 +32,7 @@ import scala.meta.internal.parsing.Trees import scala.meta.internal.pc.PcSymbolInformation import scala.meta.internal.search.SymbolHierarchyOps._ import scala.meta.internal.semanticdb.ClassSignature +import scala.meta.internal.semanticdb.Scala.Descriptor.Method import scala.meta.internal.semanticdb.Scala._ import scala.meta.internal.semanticdb.Signature import scala.meta.internal.semanticdb.SymbolInformation @@ -234,18 +235,32 @@ final class ImplementationProvider( } } yield Future.successful(found) def pcSearch = { - val symbol = - s"${implReal.symbol}${info.symbol.stripPrefix(classSymbol)}" - compilers - .infoAll(source, symbol) - .map { allFound => - allFound - .find(implInfo => - implInfo.overriddenSymbols.contains(info.symbol) - ) - .map(_.symbol) - .getOrElse(symbol) + val symbol = { + val inferredSymbol = + s"${implReal.symbol}${info.symbol.stripPrefix(classSymbol)}" + inferredSymbol.desc match { + case Method(value, _) => new Method(value, "()").toString() + case _ => inferredSymbol } + } + def overridesSym(info: PcSymbolInformation) = + info.overriddenSymbols.contains(info.symbol) + compilers.info(source, symbol).flatMap { + case Some(info) if overridesSym(info) => + Future.successful(info.symbol) + case Some(info) => + // look if one of the alternatives overrides `info.symbol` + Future + .sequence( + info.alternativeSymbols.map(compilers.info(source, _)) + ) + .map { + _.collectFirst { + case Some(info) if overridesSym(info) => info.symbol + }.getOrElse(symbol) + } + case None => Future.successful(symbol) + } } tryFromDoc.orElse { if (implReal.symbol.isLocal) None @@ -451,15 +466,33 @@ final class ImplementationProvider( parents.collect { case t: TypeRef => t.symbol }.toList case _ => Nil } - val classOwner = + + val classOwnerInfoOpt = textDocument.symbols.collectFirst { classInfo => classInfo.signature match { case ClassSignature(_, _, _, declarations) if declarations.exists(_.symlinks.contains(info.symbol)) => - classInfo.symbol + classInfo } } + def getMethodPrefix(symbol: String) = symbol.desc match { + case Method(searchedSym, _) => Some(searchedSym) + case _ => None + } + + val alternativeSymbols = + for { + classOwnerInfo <- classOwnerInfoOpt.toList + searchedSym <- getMethodPrefix(info.symbol).toList + decl <- classOwnerInfo.signature match { + case ClassSignature(_, _, _, declarations) => declarations + case _ => Nil + } + sym <- decl.symlinks + if (sym != searchedSym && getMethodPrefix(sym).contains(searchedSym)) + } yield sym + PcSymbolInformation( symbol = info.symbol, kind = PcSymbolKind.values @@ -467,7 +500,8 @@ final class ImplementationProvider( .getOrElse(PcSymbolKind.UNKNOWN_KIND), parents = parents, dealiasedSymbol = info.symbol, - classOwner = classOwner, + classOwner = classOwnerInfoOpt.map(_.symbol), + alternativeSymbols = alternativeSymbols.toList, overriddenSymbols = info.overriddenSymbols.toList, properties = if (info.isAbstract) List(PcSymbolProperty.ABSTRACT) else Nil, ) diff --git a/metals/src/main/scala/scala/meta/internal/metals/BuildTargets.scala b/metals/src/main/scala/scala/meta/internal/metals/BuildTargets.scala index 6f4b73f5b56..3ef24181868 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/BuildTargets.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/BuildTargets.scala @@ -380,8 +380,8 @@ final class BuildTargets private ( path: AbsolutePath, ): Boolean = { val possibleBuildTargets = - target :: buildTargetTransitiveDependencies(target).toList - inverseSourcesAll(path).exists(possibleBuildTargets.contains) + buildTargetTransitiveDependencies(target).toSet + target + inverseSourcesAll(path).exists(possibleBuildTargets(_)) } def inferBuildTarget( diff --git a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala index dfcec31ee44..127cff2df38 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala @@ -775,24 +775,18 @@ class Compilers( definition(params = params, token = token, findTypeDef = true) } - def infoAll( + def info( path: AbsolutePath, symbol: String, - ): Future[Seq[PcSymbolInformation]] = { + ): Future[Option[PcSymbolInformation]] = { loadCompiler(path, forceScala = true) .map( _.info(symbol).asScala - .map(_.asScala.toSeq.map(PcSymbolInformation.from)) + .map(_.asScala.map(PcSymbolInformation.from)) ) - .getOrElse(Future(Nil)) + .getOrElse(Future(None)) } - def info( - path: AbsolutePath, - symbol: String, - ): Future[Option[PcSymbolInformation]] = - infoAll(path, symbol).map(_.find(_.symbol == symbol)) - private def definition( params: TextDocumentPositionParams, token: CancelToken, @@ -848,6 +842,13 @@ class Compilers( }.getOrElse(Future.successful(Nil.asJava)) } + /** + * Gets presentation compiler for a file. + * @param path for which presentation compiler should be loaded, + * resolves build target based on this file + * @param forceScala if should use Scala pc for `.java` files that are in a Scala build target, + * useful when Scala pc can handle Java files and Java pc implementation of a feature is missing + */ def loadCompiler( path: AbsolutePath, forceScala: Boolean = false, diff --git a/mtags-interfaces/src/main/java/scala/meta/pc/PcSymbolInformation.java b/mtags-interfaces/src/main/java/scala/meta/pc/PcSymbolInformation.java index 8189c1e6f18..43e57233420 100644 --- a/mtags-interfaces/src/main/java/scala/meta/pc/PcSymbolInformation.java +++ b/mtags-interfaces/src/main/java/scala/meta/pc/PcSymbolInformation.java @@ -9,5 +9,7 @@ public interface PcSymbolInformation { String dealiasedSymbol(); String classOwner(); List overriddenSymbols(); + // overloaded methods + List alternativeSymbols(); List properties(); } diff --git a/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java b/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java index 4f736adf2c7..734cfc26e91 100644 --- a/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java +++ b/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java @@ -179,8 +179,8 @@ public CompletableFuture> syntheticDecorations(Synthet return CompletableFuture.completedFuture(Collections.emptyList()); } - public CompletableFuture> info(String symbol) { - return CompletableFuture.completedFuture(Collections.emptyList()); + public CompletableFuture> info(String symbol) { + return CompletableFuture.completedFuture(Optional.empty()); } /** diff --git a/mtags-shared/src/main/scala/scala/meta/internal/pc/PcSymbolInformation.scala b/mtags-shared/src/main/scala/scala/meta/internal/pc/PcSymbolInformation.scala index e7560ffc6df..aa35cbabc47 100644 --- a/mtags-shared/src/main/scala/scala/meta/internal/pc/PcSymbolInformation.scala +++ b/mtags-shared/src/main/scala/scala/meta/internal/pc/PcSymbolInformation.scala @@ -14,6 +14,7 @@ case class PcSymbolInformation( dealiasedSymbol: String, classOwner: Option[String], overriddenSymbols: List[String], + alternativeSymbols: List[String], properties: List[PcSymbolProperty] ) { def asJava: PcSymbolInformationJava = @@ -24,6 +25,7 @@ case class PcSymbolInformation( dealiasedSymbol, classOwner.getOrElse(""), overriddenSymbols.asJava, + alternativeSymbols.asJava, properties.asJava ) } @@ -35,6 +37,7 @@ case class PcSymbolInformationJava( dealiasedSymbol: String, classOwner: String, overriddenSymbols: ju.List[String], + alternativeSymbols: ju.List[String], properties: ju.List[PcSymbolProperty] ) extends IPcSymbolInformation @@ -48,6 +51,7 @@ object PcSymbolInformation { if (info.classOwner().nonEmpty) Some(info.classOwner()) else None, info.overriddenSymbols().asScala.toList, + info.alternativeSymbols().asScala.toList, info.properties().asScala.toList ) } diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala index bb853666c9f..7fad8e3fc10 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala @@ -352,14 +352,14 @@ case class ScalaPresentationCompiler( override def info( symbol: String - ): CompletableFuture[ju.List[IPcSymbolInformation]] = { - compilerAccess.withNonInterruptableCompiler[ju.List[IPcSymbolInformation]]( + ): CompletableFuture[Optional[IPcSymbolInformation]] = { + compilerAccess.withNonInterruptableCompiler[Optional[IPcSymbolInformation]]( None )( - Nil.asJava, + Optional.empty(), EmptyCancelToken ) { pc => - val result: List[IPcSymbolInformation] = + val result: Option[IPcSymbolInformation] = pc.compiler().info(symbol).map(_.asJava) result.asJava } diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/WorkspaceSymbolSearch.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/WorkspaceSymbolSearch.scala index b49c75c4982..9f4ab5e53bb 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/WorkspaceSymbolSearch.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/WorkspaceSymbolSearch.scala @@ -12,7 +12,7 @@ import org.eclipse.{lsp4j => l} trait WorkspaceSymbolSearch { this: MetalsGlobal => - def info(symbol: String): List[PcSymbolInformation] = { + def info(symbol: String): Option[PcSymbolInformation] = { val index = symbol.lastIndexOf("/") val pkgString = symbol.take(index + 1) val pkg = packageSymbolFromString(pkgString) @@ -46,22 +46,34 @@ trait WorkspaceSymbolSearch { this: MetalsGlobal => } } - compilerSymbols.map(compilerSymbol => - PcSymbolInformation( - symbol = semanticdbSymbol(compilerSymbol), - kind = getSymbolKind(compilerSymbol), - parents = compilerSymbol.parentSymbols.map(semanticdbSymbol), - dealiasedSymbol = semanticdbSymbol(compilerSymbol.dealiased), - classOwner = compilerSymbol.ownerChain - .find(c => c.isClass || c.isModule) - .map(semanticdbSymbol), - overriddenSymbols = compilerSymbol.overrides.map(semanticdbSymbol), - properties = - if (compilerSymbol.isAbstractClass || compilerSymbol.isAbstractType) - List(PcSymbolProperty.ABSTRACT) - else Nil + val (searchedSymbol, alternativeSymbols) = + compilerSymbols.partition(compilerSymbol => + semanticdbSymbol(compilerSymbol) == symbol ) - ) + + searchedSymbol match { + case compilerSymbol :: _ => + Some( + PcSymbolInformation( + symbol = symbol, + kind = getSymbolKind(compilerSymbol), + parents = compilerSymbol.parentSymbols.map(semanticdbSymbol), + dealiasedSymbol = semanticdbSymbol(compilerSymbol.dealiased), + classOwner = compilerSymbol.ownerChain + .find(c => c.isClass || c.isModule) + .map(semanticdbSymbol), + overriddenSymbols = compilerSymbol.overrides.map(semanticdbSymbol), + alternativeSymbols = alternativeSymbols.map(semanticdbSymbol), + properties = + if ( + compilerSymbol.isAbstractClass || compilerSymbol.isAbstractType + ) + List(PcSymbolProperty.ABSTRACT) + else Nil + ) + ) + case _ => None + } } private def getSymbolKind(sym: Symbol): PcSymbolKind = diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala index 2d062779ccd..0fe3963e477 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala @@ -190,11 +190,11 @@ case class ScalaPresentationCompiler( override def info( symbol: String - ): CompletableFuture[ju.List[IPcSymbolInformation]] = - compilerAccess.withNonInterruptableCompiler[ju.List[IPcSymbolInformation]]( + ): CompletableFuture[Optional[IPcSymbolInformation]] = + compilerAccess.withNonInterruptableCompiler[Optional[IPcSymbolInformation]]( None )( - Nil.asJava, + Optional.empty(), EmptyCancelToken, ) { access => SymbolInformationProvider(using access.compiler().currentCtx) diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/SymbolInformationProvider.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/SymbolInformationProvider.scala index 41a281e9e1a..f45eb12c98f 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/SymbolInformationProvider.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/SymbolInformationProvider.scala @@ -48,7 +48,7 @@ class SymbolInformationProvider(using Context): loop(List(pkgSym), parts) end toSymbols - def info(symbol: String): List[PcSymbolInformation] = + def info(symbol: String): Option[PcSymbolInformation] = val index = symbol.lastIndexOf("/") val pkg = normalizePackage(symbol.take(index + 1)) @@ -68,31 +68,42 @@ class SymbolInformationProvider(using Context): try toSymbols(pkg, names) catch case NonFatal(e) => Nil - for sym <- foundSymbols - yield - val classSym = if sym.isClass then sym else sym.moduleClass - val parents = - if classSym.isClass - then classSym.asClass.parentSyms.map(SemanticdbSymbols.symbolName) - else Nil - val dealisedSymbol = - if sym.isAliasType then sym.info.metalsDealias.typeSymbol else sym - val classOwner = - sym.ownersIterator.drop(1).find(s => s.isClass || s.is(Flags.Module)) - val overridden = sym.denot.allOverriddenSymbols.toList - - PcSymbolInformation( - symbol = SemanticdbSymbols.symbolName(sym), - kind = getSymbolKind(sym), - parents = parents, - dealiasedSymbol = SemanticdbSymbols.symbolName(dealisedSymbol), - classOwner = classOwner.map(SemanticdbSymbols.symbolName), - overriddenSymbols = overridden.map(SemanticdbSymbols.symbolName), - properties = - if sym.is(Flags.Abstract) then List(PcSymbolProperty.ABSTRACT) - else Nil, + val (searchedSymbol, alternativeSymbols) = + foundSymbols.partition(compilerSymbol => + SemanticdbSymbols.symbolName(compilerSymbol) == symbol ) - end for + + searchedSymbol match + case Nil => None + case sym :: _ => + val classSym = if sym.isClass then sym else sym.moduleClass + val parents = + if classSym.isClass + then classSym.asClass.parentSyms.map(SemanticdbSymbols.symbolName) + else Nil + val dealisedSymbol = + if sym.isAliasType then sym.info.metalsDealias.typeSymbol else sym + val classOwner = + sym.ownersIterator.drop(1).find(s => s.isClass || s.is(Flags.Module)) + val overridden = sym.denot.allOverriddenSymbols.toList + + val pcSymbolInformation = + PcSymbolInformation( + symbol = SemanticdbSymbols.symbolName(sym), + kind = getSymbolKind(sym), + parents = parents, + dealiasedSymbol = SemanticdbSymbols.symbolName(dealisedSymbol), + classOwner = classOwner.map(SemanticdbSymbols.symbolName), + overriddenSymbols = overridden.map(SemanticdbSymbols.symbolName), + alternativeSymbols = + alternativeSymbols.map(SemanticdbSymbols.symbolName), + properties = + if sym.is(Flags.Abstract) then List(PcSymbolProperty.ABSTRACT) + else Nil, + ) + + Some(pcSymbolInformation) + end match end info private def getSymbolKind(sym: Symbol): PcSymbolKind =