From a7b79975792fa4055d033247faaf181f77116183 Mon Sep 17 00:00:00 2001 From: kasiaMarek Date: Wed, 14 Aug 2024 15:29:45 +0200 Subject: [PATCH] feat: Allow user to choose what symbol to search for in go to references --- .../scala/meta/internal/metals/Messages.scala | 3 + .../internal/metals/MetalsLspService.scala | 4 +- .../internal/metals/ReferenceProvider.scala | 288 +++++++++++++----- .../meta/internal/rename/RenameProvider.scala | 4 + .../scala/meta/internal/pc/MetalsGlobal.scala | 8 +- .../scala/meta/internal/pc/PcCollector.scala | 27 +- .../internal/pc/PcReferencesProvider.scala | 1 - .../internal/pc/WithCompilationUnit.scala | 34 ++- .../internal/pc/PcReferencesProvider.scala | 2 +- .../internal/pc/WithCompilationUnit.scala | 8 +- .../scala/meta/internal/mtags/Symbol.scala | 15 + .../highlight/DocumentHighlightSuite.scala | 2 +- .../src/main/scala/tests/TestingClient.scala | 5 + .../test/scala/tests/ReferenceLspSuite.scala | 6 +- 14 files changed, 302 insertions(+), 105 deletions(-) diff --git a/metals/src/main/scala/scala/meta/internal/metals/Messages.scala b/metals/src/main/scala/scala/meta/internal/metals/Messages.scala index c5caab57513..c3e6ddb7fec 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Messages.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Messages.scala @@ -1082,6 +1082,9 @@ object Messages { } } + final val PickSymbolForReferenceSearch = + "Choose symbol to search references for." + } object FileOutOfScalaCliBspScope { 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 dc3cbb0f0ac..9291c5b78a2 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala @@ -465,6 +465,8 @@ abstract class MetalsLspService( buildTargets, compilers, scalaVersionSelector, + languageClient, + clientConfig.isQuickPickProvider(), ) protected val packageProvider: PackageProvider = @@ -1126,7 +1128,7 @@ abstract class MetalsLspService( params: ReferenceParams ): Future[List[ReferencesResult]] = { val timer = new Timer(time) - referencesProvider.references(params).map { results => + referencesProvider.references(params, isForRename = false).map { results => if (clientConfig.initialConfig.statistics.isReferences) { if (results.forall(_.symbol.isEmpty)) { scribe.info(s"time: found 0 references in $timer") diff --git a/metals/src/main/scala/scala/meta/internal/metals/ReferenceProvider.scala b/metals/src/main/scala/scala/meta/internal/metals/ReferenceProvider.scala index 6ca106c9466..4251684d6c2 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/ReferenceProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/ReferenceProvider.scala @@ -15,6 +15,9 @@ import scala.util.matching.Regex import scala.meta.Importee import scala.meta.internal.metals.MetalsEnrichments._ import scala.meta.internal.metals.ResolvedSymbolOccurrence +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.mtags.DefinitionAlternatives.GlobalSymbol import scala.meta.internal.mtags.Semanticdbs import scala.meta.internal.mtags.Symbol @@ -46,6 +49,8 @@ final class ReferenceProvider( buildTargets: BuildTargets, compilers: Compilers, scalaVersionSelector: ScalaVersionSelector, + languageClient: MetalsLanguageClient, + isMetalsQuickPickProvider: Boolean, )(implicit ec: ExecutionContext) extends SemanticdbFeatureProvider with CompletionItemPriority { @@ -186,6 +191,7 @@ final class ReferenceProvider( */ def references( params: ReferenceParams, + isForRename: Boolean, findRealRange: AdjustRange = noAdjustRange, includeSynthetics: Synthetic => Boolean = _ => true, )(implicit report: ReportContext): Future[List[ReferencesResult]] = { @@ -197,7 +203,7 @@ final class ReferenceProvider( if (supportsPcRefs) textDoc.toOption else textDoc.documentIncludingStale textDocOpt match { case Some(doc) => - val results: List[ResolvedSymbolOccurrence] = { + val occurences: List[ResolvedSymbolOccurrence] = { val posOccurrences = definition.positionOccurrences(source, params.getPosition, doc) if (posOccurrences.isEmpty) @@ -205,78 +211,97 @@ final class ReferenceProvider( occurrencesForRenamedImport(source, params, doc) else posOccurrences } - if (results.isEmpty) { + if (occurences.isEmpty) { scribe.debug( s"No symbol found at ${params.getPosition()} for $source" ) } - val semanticdbResult = Future.sequence { - results.map { result => + val resultWithAlt = + occurences.zipWithIndex.flatMap { case (result, idx) => val occurrence = result.occurrence.get - val distance = result.distance - val alternatives = - referenceAlternatives(occurrence.symbol, source, doc) - references( + (referenceAlternatives( + occurrence.symbol, source, - params, doc, - distance, - occurrence, - alternatives, - params.getContext.isIncludeDeclaration, - findRealRange, - includeSynthetics, - supportsPcRefs, - ).map { locations => - // It's possible to return nothing is we exclude declaration - if ( - locations.isEmpty && params.getContext().isIncludeDeclaration() - ) { - val fileInIndex = - if (index.contains(source.toNIO)) - s"Current file ${source} is present" - else s"Missing current file ${source}" - scribe.debug( - s"No references found, index size ${index.size}\n" + fileInIndex - ) - report.unsanitized.create( - Report( - "empty-references", - index - .map { case (path, entry) => - s"$path -> ${entry.bloom.approximateElementCount()}" - } - .mkString("\n"), - s"Could not find any locations for ${result.occurrence}, printing index state", - Some(source.toString()), - Some( - source.toString() + ":" + result.occurrence.getOrElse("") - ), - ) - ) - } - ReferencesResult(occurrence.symbol, locations) - } + ) + occurrence.symbol).map((_, idx)) } - } - val pcResult = - pcReferences( - source, - results.flatMap(_.occurrence).map(_.symbol), - params.getContext().isIncludeDeclaration(), - findRealRange, - ) - Future - .sequence(List(semanticdbResult, pcResult)) - .map( - _.flatten - .groupBy(_.symbol) - .collect { case (symbol, refs) => - ReferencesResult(symbol, refs.flatMap(_.locations)) + quickPickSymbolGroup(resultWithAlt.map(_._1).toSet, isForRename) + .map { chosen => + resultWithAlt.filter(x => chosen(x._1)).groupMap(_._2)(_._1).map { + case (idx, syms) => occurences(idx) -> syms.toSet + } + } + .flatMap { results => + val semanticdbResult = Future.sequence { + results.map { case (result, alternatives) => + val occurrence = result.occurrence.get + val distance = result.distance + references( + source, + params, + doc, + distance, + occurrence, + alternatives, + params.getContext.isIncludeDeclaration, + findRealRange, + includeSynthetics, + supportsPcRefs, + ).map { locations => + // It's possible to return nothing is we exclude declaration + if ( + locations.isEmpty && params + .getContext() + .isIncludeDeclaration() + ) { + val fileInIndex = + if (index.contains(source.toNIO)) + s"Current file ${source} is present" + else s"Missing current file ${source}" + scribe.debug( + s"No references found, index size ${index.size}\n" + fileInIndex + ) + report.unsanitized.create( + Report( + "empty-references", + index + .map { case (path, entry) => + s"$path -> ${entry.bloom.approximateElementCount()}" + } + .mkString("\n"), + s"Could not find any locations for ${result.occurrence}, printing index state", + Some(source.toString()), + Some( + source.toString() + ":" + result.occurrence + .getOrElse("") + ), + ) + ) + } + ReferencesResult(occurrence.symbol, locations) + } } - .toList - ) + } + val pcResult = + pcReferences( + source, + results.flatMap(_._2).toList, + params.getContext().isIncludeDeclaration(), + findRealRange, + ) + + Future + .sequence(List(semanticdbResult, pcResult)) + .map( + _.flatten + .groupBy(_.symbol) + .collect { case (symbol, refs) => + ReferencesResult(symbol, refs.flatMap(_.locations)) + } + .toList + ) + } case None => if (textDoc.documentIncludingStale.isEmpty) scribe.debug(s"No semanticDB for $source") @@ -289,30 +314,34 @@ final class ReferenceProvider( findRealRange, ) symbols = foundRefs.map(_.symbol).filterNot(_.isLocal) + chosen <- quickPickSymbolGroup(symbols.toSet, isForRename) + fileteredFoundRefs = foundRefs.filter(ref => + chosen(ref.symbol) || ref.symbol.isLocal + ) fromPc <- - if (symbols.isEmpty) Future.successful(Nil) + if (chosen.isEmpty) Future.successful(Nil) else { pcReferences( source, - symbols, + chosen.toList, includeDeclaration, findRealRange, ) } } yield { - if (symbols.isEmpty) foundRefs + if (chosen.isEmpty) fileteredFoundRefs else { val fromWorkspace = workspaceReferences( source, - symbols.toSet, + chosen.toSet, includeDeclaration, findRealRange, includeSynthetics, ) val results = ReferencesResult( - symbols.head, + chosen.head, fromWorkspace, - ) :: (fromPc ++ foundRefs) + ) :: (fromPc ++ fileteredFoundRefs) results .groupBy(_.symbol) @@ -325,6 +354,58 @@ final class ReferenceProvider( } } + private def quickPickSymbolGroup( + symbols: Set[String], + forRename: Boolean, + ): Future[Set[String]] = { + lazy val symsContainType = symbols.exists(sym => Symbol(sym).isType) + if (forRename) { + Future.successful { + if (symsContainType) { + symbols + .filter { sym => + Symbol(sym) match { + case sym if sym.isConstructor || sym.isApply => false + case _ => true + } + } + } else symbols + } + } else { + lazy val symsMap = symbols + .map { sym => + val tpe = Symbol(sym) match { + case sym if sym.isType => "Type" + case sym if sym.isConstructor || (sym.isApply && symsContainType) => + "Constructor / Synthetic apply" + case sym if sym.isApply => "Apply" + case _ => "Term" + } + tpe -> sym + } + .groupMap(_._1)(_._2) + + if (symsMap.size > 1 && isMetalsQuickPickProvider) { + val allElem = MetalsQuickPickItem("All", "All") + languageClient + .metalsQuickPick( + MetalsQuickPickParams( + (symsMap.keySet + .map(name => MetalsQuickPickItem(name, name)) + .toList :+ allElem).asJava, + placeHolder = Messages.PickSymbolForReferenceSearch, + ) + ) + .asScala + .map { + case None => symbols + case Some(res) if res.itemId == null => symbols + case Some(id) => symsMap.get(id.itemId).getOrElse(symbols) + } + } else Future.successful(symbols) + } + } + // for `import package.{AA as B@@B}` we look for occurrences at `import package.{@@AA as BB}`, // since rename is not a position occurrence in semanticDB private def occurrencesForRenamedImport( @@ -375,6 +456,14 @@ final class ReferenceProvider( if check(info) } yield info.symbol + val nonSyntheticSymbols = for { + occ <- definitionDoc.occurrences + if occ.role.isDefinition + } yield occ.symbol + + def isSyntheticSymbol(symbol: String) = + !nonSyntheticSymbols.contains(symbol) + val isCandidate = if (defPath.isJava) candidates { info => @@ -385,17 +474,11 @@ final class ReferenceProvider( alternatives.isVarSetter(info) || alternatives.isCompanionObject(info) || alternatives.isCopyOrApplyParam(info) || - alternatives.isContructorParam(info) + alternatives.isContructorParam(info) || + alternatives.isJavaConstructor(info) || + alternatives.isApplyMethod(info, isSyntheticSymbol) }.toSet - val nonSyntheticSymbols = for { - occ <- definitionDoc.occurrences - if isCandidate(occ.symbol) || occ.symbol == symbol - if occ.role.isDefinition - } yield occ.symbol - - def isSyntheticSymbol = !nonSyntheticSymbols.contains(symbol) - def additionalAlternativesForSynthetic = for { info <- definitionDoc.symbols if info.symbol != name @@ -407,10 +490,10 @@ final class ReferenceProvider( if (defPath.isJava) isCandidate - else if (isSyntheticSymbol) - isCandidate -- nonSyntheticSymbols ++ additionalAlternativesForSynthetic + else if (isSyntheticSymbol(symbol)) + isCandidate ++ additionalAlternativesForSynthetic else - isCandidate -- nonSyntheticSymbols + isCandidate case None => Set.empty } } @@ -633,13 +716,12 @@ final class ReferenceProvider( snapshot: TextDocument, distance: TokenEditDistance, occ: SymbolOccurrence, - alternatives: Set[String], + isSymbol: Set[String], isIncludeDeclaration: Boolean, findRealRange: AdjustRange, includeSynthetics: Synthetic => Boolean, supportsPcRefs: Boolean, ): Future[Seq[Location]] = { - val isSymbol = alternatives + occ.symbol val isLocal = occ.symbol.isLocal if (isLocal && supportsPcRefs) compilers @@ -728,7 +810,30 @@ final class ReferenceProvider( * are ok and speed is more important, we just use the default noAdjustRange. */ else { - findRealRange(range, snapshot.text, reference.symbol).foreach(add) + val adjustedForConstructorRange = { + val symbol = Symbol(reference.symbol) + if (symbol.isConstructor && range.isPoint) { + lazy val forConstructorOccurence = range.withStartCharacter( + range.startCharacter - symbol.owner.displayName.length() + ) + if (reference.role.isDefinition) { + // if primary constructor definition find parent definition + snapshot.occurrences + .collectFirst { + case occ + if occ.symbol == symbol.owner.value && occ.role.isDefinition => + occ.range + } + .flatten + .getOrElse(forConstructorOccurence) + } else forConstructorOccurence + } else range + } + findRealRange( + adjustedForConstructorRange, + snapshot.text, + reference.symbol, + ).foreach(add) } } @@ -810,6 +915,21 @@ class SymbolAlternatives(symbol: String, name: String) { }) } + def isApplyMethod( + info: SymbolInformation, + isSyntheticSymbol: String => Boolean, + ): Boolean = + symbol == (Symbol(info.symbol) match { + case GlobalSymbol( + GlobalSymbol(owner, Descriptor.Term(obj)), + Descriptor.Method("apply", _), + ) => + if (isSyntheticSymbol(info.symbol)) + Symbols.Global(owner.value, Descriptor.Type(obj)) + else Symbols.Global(owner.value, Descriptor.Term(obj)) + case _ => "" + }) + // Returns true if `info` is a parameter of a synthetic `copy` or `apply` matching the occurrence field symbol. def isCopyOrApplyParam(info: SymbolInformation): Boolean = info.isParameter && 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 8c8043eb118..7156fec895e 100644 --- a/metals/src/main/scala/scala/meta/internal/rename/RenameProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/rename/RenameProvider.scala @@ -220,6 +220,7 @@ final class RenameProvider( txtParams, includeDeclaration = isJava, ), + isForRename = true, findRealRange = AdjustRange(findRealRange(newName)), includeSynthetic, ) @@ -425,6 +426,7 @@ final class RenameProvider( referenceProvider .references( toReferenceParams(loc, includeDeclaration = false), + isForRename = true, findRealRange = AdjustRange(findRealRange(newName)), ) .map(_.flatMap(_.locations :+ loc)) @@ -472,6 +474,7 @@ final class RenameProvider( if (shouldCheckImplementation) { for { implLocs <- implementationProvider.implementations(textParams) + // _ = pprint.log(implLocs) result <- { val result = for { implLoc <- implLocs @@ -480,6 +483,7 @@ final class RenameProvider( referenceProvider .references( locParams, + isForRename = true, findRealRange = AdjustRange(findRealRange(newName)), includeSynthetic, ) diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/MetalsGlobal.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/MetalsGlobal.scala index ed925e4064a..dd67cf6a24b 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/MetalsGlobal.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/MetalsGlobal.scala @@ -780,8 +780,9 @@ class MetalsGlobal( val name = if (defn.symbol.isPackageObject) defn.symbol.enclosingPackageClass.name else defn.name + val nameLength = name.dropLocal.decoded.length() val start = defn.pos.point - val end = start + name.dropLocal.decoded.length() + val end = start + nameLength Position.range(defn.pos.source, start, start, end) } } @@ -794,6 +795,11 @@ class MetalsGlobal( def namePosition: Position = { sel match { case _ if !sel.pos.isRange => sel.pos + case Select(New(qualifier), name) if name == nme.CONSTRUCTOR => + qualifier match { + case qualifier: Select => qualifier.namePosition + case _ => qualifier.pos.withStart(qualifier.pos.point) + } case Select(qualifier: Select, name) if (name == nme.apply || name == nme.unapply) && sel.pos.point == qualifier.pos.point => qualifier.namePosition diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/PcCollector.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/PcCollector.scala index ca19ba1eb85..e422dff4cb5 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/PcCollector.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/PcCollector.scala @@ -45,6 +45,11 @@ trait PcCollector[T] { self: WithCompilationUnit => def soughtOrOverride(sym: Symbol) = sought(sym) || sym.allOverriddenSymbols.exists(sought(_)) + def primaryContructorParent(sym: Symbol) = + soughtSet + .flatMap(sym => if (sym.isPrimaryConstructor) Some(sym.owner) else None) + .contains(sym) + def soughtTreeFilter(tree: Tree): Boolean = tree match { case ident: Ident @@ -57,7 +62,8 @@ trait PcCollector[T] { self: WithCompilationUnit => soughtOrOverride(sel.symbol) case df: MemberDef => (soughtOrOverride(df.symbol) || - isForComprehensionOwner(df)) + isForComprehensionOwner(df) || + primaryContructorParent(df.symbol)) case appl: Apply => appl.symbol != null && (owners(appl.symbol) || @@ -172,13 +178,22 @@ trait PcCollector[T] { self: WithCompilationUnit => * class <> = ??? * etc. */ - case df: MemberDef if isCorrectPos(df) && filter(df) => + case df: MemberDef + if isCorrectPos(df) && filter( + df + ) && !df.symbol.isPrimaryConstructor => (annotationChildren(df) ++ df.children).foldLeft({ - val t = collect( - df, - df.namePosition + val maybeConstructor = + Some(df.symbol.primaryConstructor).filter(_ != NoSymbol) + val collected = collect(df, df.namePosition) + // we also collect primary constructor on class, + // since primary constructor is synthetic and doesn't have needed span anymore + // later we cannot get the correct + val collectedConstructor = maybeConstructor.map(sym => + collect(df, df.namePosition, Some(sym)) ) - if (acc(t)) acc else acc + t + + acc + collected ++ collectedConstructor })(traverse(_, _)) /* Named parameters, since they don't show up in typed tree: * foo(<> = "abc") diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/PcReferencesProvider.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/PcReferencesProvider.scala index 06d0df536c1..3550749b18e 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/PcReferencesProvider.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/PcReferencesProvider.scala @@ -67,7 +67,6 @@ class BySymbolPCReferencesProvider( def result(): List[(String, Option[l.Range])] = { val sought = semanticDbSymbols .flatMap(compiler.compilerSymbol(_)) - .flatMap(symbolAlternatives(_)) if (sought.isEmpty) Nil else resultWithSought(sought) } diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/WithCompilationUnit.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/WithCompilationUnit.scala index a3b49d0a924..c511f566153 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/WithCompilationUnit.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/WithCompilationUnit.scala @@ -36,13 +36,39 @@ class WithCompilationUnit( * @return set of possible symbols */ def symbolAlternatives(sym: Symbol): Set[Symbol] = { + def allMembers(name: Name, symbol: Symbol): List[Symbol] = { + val member = symbol.info.member(name) + if (member.isOverloaded) + member.info.asInstanceOf[OverloadedType].alternatives + else List(member) + } + val all = if (sym.isClass) { - if (sym.owner.isMethod) Set(sym) ++ sym.localCompanion(pos) - else Set(sym, sym.companionModule, sym.companion.moduleClass) + def contructorLike(moduleClass: Symbol) = + sym.primaryConstructor :: allMembers(nme.apply, moduleClass).filter( + _.isSynthetic + ) + if (sym.owner.isMethod) + Set(sym) ++ sym + .localCompanion(pos) + .toList + .flatMap(comp => comp :: contructorLike(comp)) + else + Set( + sym, + sym.companionModule, + sym.companion.moduleClass + ) ++ contructorLike(sym.companion.moduleClass) } else if (sym.isModuleOrModuleClass) { - if (sym.owner.isMethod) Set(sym) ++ sym.localCompanion(pos) - else Set(sym, sym.companionClass, sym.moduleClass) + def contructorLike(moduleClass: Symbol) = + allMembers(nme.apply, moduleClass) + if (sym.owner.isMethod) + Set(sym) ++ sym.localCompanion(pos) ++ contructorLike(sym) + else + Set(sym, sym.companionClass, sym.moduleClass) ++ contructorLike( + sym.moduleClass + ) } else if (sym.isTerm && (sym.owner.isClass || sym.owner.isConstructor)) { val info = if (sym.owner.isClass) sym.owner.info diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/PcReferencesProvider.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/PcReferencesProvider.scala index 35fffbb6295..d5dc0a18740 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/PcReferencesProvider.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/PcReferencesProvider.scala @@ -32,7 +32,7 @@ class PcReferencesProvider( symbolSearch.soughtSymbols.map(_._1) } else { val semanticDBSymbols = request.alternativeSymbols().asScala.toSet + request.offsetOrSymbol().getRight() - Some(semanticDBSymbols.flatMap(SymbolProvider.compilerSymbol).flatMap(symbolAlternatives(_))).filter(_.nonEmpty) + Some(semanticDBSymbols.flatMap(SymbolProvider.compilerSymbol)).filter(_.nonEmpty) } def collect(parent: Option[Tree])( diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/WithCompilationUnit.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/WithCompilationUnit.scala index 8ce0fd390be..4abcef218e9 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/WithCompilationUnit.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/WithCompilationUnit.scala @@ -11,6 +11,7 @@ import scala.meta.pc.VirtualFileParams import dotty.tools.dotc.core.Contexts.* import dotty.tools.dotc.core.Flags import dotty.tools.dotc.core.NameOps.* +import dotty.tools.dotc.core.StdNames import dotty.tools.dotc.core.Symbols.* import dotty.tools.dotc.interactive.InteractiveDriver import dotty.tools.dotc.util.SourceFile @@ -73,11 +74,12 @@ class WithCompilationUnit( else Set.empty val all = if sym.is(Flags.ModuleClass) then - Set(sym, sym.companionModule, sym.companionModule.companion) + Set(sym, sym.companionModule, sym.companionModule.companion) ++ sym.info.member(StdNames.nme.apply).allSymbols else if sym.isClass then - Set(sym, sym.companionModule, sym.companion.moduleClass) + Set(sym, sym.companionModule, sym.companion.moduleClass, sym.primaryConstructor) + ++ sym.companion.moduleClass.info.member(StdNames.nme.apply).allSymbols.filter(_.is(Flags.Synthetic)) else if sym.is(Flags.Module) then - Set(sym, sym.companionClass, sym.moduleClass) + Set(sym, sym.companionClass, sym.moduleClass) ++ sym.moduleClass.info.member(StdNames.nme.apply).allSymbols else if sym.isTerm && (sym.owner.isClass || sym.owner.isConstructor) then val info = diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/Symbol.scala b/mtags/src/main/scala/scala/meta/internal/mtags/Symbol.scala index 85aa46b26b4..d1f4f7ff3d1 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/Symbol.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/Symbol.scala @@ -3,6 +3,7 @@ package scala.meta.internal.mtags import scala.annotation.tailrec import scala.util.control.NonFatal +import scala.meta.internal.mtags.DefinitionAlternatives.GlobalSymbol import scala.meta.internal.mtags.MtagsEnrichments._ import scala.meta.internal.semanticdb.Scala._ @@ -31,6 +32,20 @@ final class Symbol private (val value: String) { def isPackage: Boolean = desc.isPackage def isParameter: Boolean = desc.isParameter def isTypeParameter: Boolean = desc.isTypeParameter + def isConstructor: Boolean = + this match { + case GlobalSymbol(_, Descriptor.Method("", _)) => true + case _ => false + } + def isApply: Boolean = + this match { + case GlobalSymbol( + GlobalSymbol(_, Descriptor.Term(_)), + Descriptor.Method("apply", _) + ) => + true + case _ => false + } private lazy val desc: Descriptor = value.desc def owner: Symbol = Symbol(value.owner) diff --git a/tests/cross/src/test/scala/tests/highlight/DocumentHighlightSuite.scala b/tests/cross/src/test/scala/tests/highlight/DocumentHighlightSuite.scala index 9ce2fb726fd..a21b05fd018 100644 --- a/tests/cross/src/test/scala/tests/highlight/DocumentHighlightSuite.scala +++ b/tests/cross/src/test/scala/tests/highlight/DocumentHighlightSuite.scala @@ -1098,7 +1098,7 @@ class DocumentHighlightSuite extends BaseDocumentHighlightSuite { |object Main { | class <>[T](abc: T) | object <> { - | def apply(abc: Int, bde: Int) = new <>(abc + bde) + | def <>(abc: Int, bde: Int) = new <>(abc + bde) | } | val x = <>(123, 456) |}""".stripMargin diff --git a/tests/unit/src/main/scala/tests/TestingClient.scala b/tests/unit/src/main/scala/tests/TestingClient.scala index 424a4d1175a..08eb0e33aff 100644 --- a/tests/unit/src/main/scala/tests/TestingClient.scala +++ b/tests/unit/src/main/scala/tests/TestingClient.scala @@ -20,6 +20,7 @@ import scala.meta.internal.metals.ClientCommands import scala.meta.internal.metals.Debug import scala.meta.internal.metals.FileOutOfScalaCliBspScope import scala.meta.internal.metals.Icons +import scala.meta.internal.metals.Messages import scala.meta.internal.metals.Messages._ import scala.meta.internal.metals.MetalsEnrichments._ import scala.meta.internal.metals.ServerCommands @@ -410,6 +411,10 @@ class TestingClient(workspace: AbsolutePath, val buffers: Buffers) .contains(params.getMessage()) ) { shouldReloadAfterJavaHomeUpdate + } else if ( + params.getMessage() == Messages.PickSymbolForReferenceSearch + ) { + params.getActions().asScala.last } else { throw new IllegalArgumentException(params.toString) } diff --git a/tests/unit/src/test/scala/tests/ReferenceLspSuite.scala b/tests/unit/src/test/scala/tests/ReferenceLspSuite.scala index 3400016e66e..bd1bc720ef1 100644 --- a/tests/unit/src/test/scala/tests/ReferenceLspSuite.scala +++ b/tests/unit/src/test/scala/tests/ReferenceLspSuite.scala @@ -164,7 +164,7 @@ class ReferenceLspSuite extends BaseRangesSuite("reference") { |} |""".stripMargin, """|object Other { - | val mb = <
>.apply("b") + | val mb = <
>.<>("b") |} |""".stripMargin, ) @@ -196,10 +196,10 @@ class ReferenceLspSuite extends BaseRangesSuite("reference") { |""".stripMargin, ) - checkInSamePackage( // FIXME: should, but doesn't find the class declaration: https://github.com/scalameta/metals/issues/1553#issuecomment-617884934 + checkInSamePackage( "case-class-unapply-starting-elsewhere", """|sealed trait Stuff - |case class <>(n: Int) extends Stuff // doesn't find this + |case class <>(n: Int) extends Stuff |""".stripMargin, """| |object ByTheWay {