diff --git a/metals/src/main/scala/scala/meta/internal/decorations/DecorationClient.scala b/metals/src/main/scala/scala/meta/internal/decorations/DecorationClient.scala index bb6e99f5541..47441ae3c04 100644 --- a/metals/src/main/scala/scala/meta/internal/decorations/DecorationClient.scala +++ b/metals/src/main/scala/scala/meta/internal/decorations/DecorationClient.scala @@ -20,7 +20,7 @@ case class DecorationOptions( ) object DecorationOptions { - def apply(range: Range, text: String) = + def apply(text: String, range: Range) = new DecorationOptions( range, renderOptions = ThemableDecorationInstanceRenderOptions( 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 a83dede426e..5773cfe1dde 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala @@ -594,12 +594,23 @@ class Compilers( compiler .syntheticDecorations(pcParams) .asScala - .map(_.map { decoration => - DecorationOptions( - adjust.adjustRange(decoration.range()), - decoration.label(), - ) - }) + .map { decorations => + val decorationOptions = decorations.map { decoration => + DecorationOptions( + decoration.label(), + adjust.adjustRange(decoration.range()), + ) + } + val isScala2 = compiler.scalaVersion().startsWith("2.") + if ( + isScala2 && path.isWorksheet && !decorationOptions.isEmpty() + ) { + // In scala 2 worksheet, last decoration is for synthetic `main` method + decorationOptions.remove(decorationOptions.size() - 1) + decorationOptions + } else decorationOptions + } + } .getOrElse(Future.successful(Nil.asJava)) 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 bac3083bf14..66f343dc7af 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala @@ -964,7 +964,14 @@ class MetalsLspService( for { _ <- buildServerPromise.future _ <- focusedDocument() - .map(publishSynthetics) + .map { path => + Future.sequence( + List( + publishSynthetics(path), + worksheetProvider.onDidFocus(path), + ) + ) + } .getOrElse(Future.successful(())) } yield () } else { diff --git a/mtags/src/main/scala-2/scala/meta/internal/mtags/MtagsEnrichments.scala b/mtags/src/main/scala-2/scala/meta/internal/mtags/MtagsEnrichments.scala index 76c707cf5d4..1db80b39651 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/mtags/MtagsEnrichments.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/mtags/MtagsEnrichments.scala @@ -233,6 +233,24 @@ trait MtagsEnrichments extends ScalametaCommonEnrichments { def encloses(other: RangeParams): Boolean = pos.start <= other.offset() && pos.end >= other.endOffset() + + def adjust( + text: Array[Char], + forRename: Boolean = false + ): (Position, Boolean) = { + val isBackticked = text(pos.start) == '`' && + text(pos.end - 1) == '`' && + pos.start != (pos.end - 1) // for one character names, e.g. `c` + // start-^^-end + val isOldNameBackticked = text(pos.start) == '`' && + (text(pos.end - 1) != '`' || pos.start == (pos.end - 1)) && + text(pos.end + 1) == '`' + if (isBackticked && forRename) + (pos.withStart(pos.start + 1).withEnd(pos.end - 1), true) + else if (isOldNameBackticked) // pos + (pos.withEnd(pos.end + 2), false) + else (pos, false) + } } implicit class XtensionRangeParameters(pos: RangeParams) { 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 be19ca4d20b..d387d5cfa71 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 @@ -1023,6 +1023,19 @@ class MetalsGlobal( } } + private val forCompMethods = + Set(nme.map, nme.flatMap, nme.withFilter, nme.foreach) + + // We don't want to collect synthethic `map`, `withFilter`, `foreach` and `flatMap` in for-comprenhensions + def isForComprehensionMethod(sel: Select): Boolean = { + val syntheticName = sel.name match { + case name: TermName => forCompMethods(name) + case _ => false + } + val wrongSpan = sel.qualifier.pos.includes(sel.namePosition.focusStart) + syntheticName && wrongSpan + } + // Extractor for both term and type applications like `foo(1)` and foo[T]` object TreeApply { def unapply(tree: Tree): Option[(Tree, List[Tree])] = 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 bce15a3d023..3fe06921a9d 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 @@ -3,7 +3,6 @@ package scala.meta.internal.pc import scala.reflect.internal.util.RangePosition import scala.meta.pc.OffsetParams -import scala.meta.pc.RangeParams import scala.meta.pc.VirtualFileParams abstract class PcCollector[T]( @@ -67,24 +66,6 @@ abstract class PcCollector[T]( all.filter(s => s != NoSymbol && !s.isError) } - def adjust( - pos: Position, - forRename: Boolean = false - ): (Position, Boolean) = { - val isBackticked = text(pos.start) == '`' && - text(pos.end - 1) == '`' && - pos.start != (pos.end - 1) // for one character names, e.g. `c` - // start-^^-end - val isOldNameBackticked = text(pos.start) == '`' && - (text(pos.end - 1) != '`' || pos.start == (pos.end - 1)) && - text(pos.end + 1) == '`' - if (isBackticked && forRename) - (pos.withStart(pos.start + 1).withEnd(pos.end - 1), true) - else if (isOldNameBackticked) // pos - (pos.withEnd(pos.end + 2), false) - else (pos, false) - } - private lazy val namedArgCache = { val parsedTree = parseTree(unit.source) parsedTree.collect { case arg @ AssignOrNamedArg(_, rhs) => @@ -195,28 +176,10 @@ abstract class PcCollector[T]( None } - def treesInRange(rangeParams: RangeParams): List[Tree] = { - val pos: Position = - unit.position(rangeParams.offset()).withEnd(rangeParams.endOffset()) - val tree = locateTree(pos, unit.lastBody, false) - if (tree.isEmpty) { - List(unit.lastBody) - } else if (!tree.pos.isDefined || rangeParams.offset() <= tree.pos.start) { - List(tree) - } else { - tree.children - .filter(c => - !c.pos.isDefined || - c.pos.start <= rangeParams.endOffset && c.pos.end >= rangeParams - .offset() - ) - } - } - def result(): List[T] = { params match { case _: OffsetParams => resultWithSought() - case _ => resultAllOccurences()(List(unit.lastBody)) + case _ => resultAllOccurences().toList } } @@ -274,37 +237,29 @@ abstract class PcCollector[T]( sought.exists(f) } - traverseSought(soughtTreeFilter, soughtFilter)(List(unit.lastBody)) + traverseSought(soughtTreeFilter, soughtFilter).toList case None => Nil } } - def resultAllOccurences(includeSynthetics: Boolean = false)( - toTraverse: List[Tree] = List(unit.lastBody) - ): List[T] = { + def resultAllOccurences(): Set[T] = { def noTreeFilter = (_: Tree) => true def noSoughtFilter = (_: (Symbol => Boolean)) => true - traverseSought(noTreeFilter, noSoughtFilter, includeSynthetics)(toTraverse) + traverseSought(noTreeFilter, noSoughtFilter) } def traverseSought( filter: Tree => Boolean, - soughtFilter: (Symbol => Boolean) => Boolean, - includeSynthetics: Boolean = false - )( - toTraverse: List[Tree] - ): List[T] = { - def syntheticFilter(tree: Tree) = includeSynthetics && - tree.pos.isOffset && - tree.symbol.isImplicit + soughtFilter: (Symbol => Boolean) => Boolean + ): Set[T] = { // Now find all matching symbols in the document, comments identify <<>> as the symbol we are looking for def traverseWithParent(parent: Option[Tree])( - acc: List[T], + acc: Set[T], tree: Tree - ): List[T] = { - val traverse: (List[T], Tree) => List[T] = traverseWithParent( + ): Set[T] = { + val traverse: (Set[T], Tree) => Set[T] = traverseWithParent( Some(tree) ) def collect(t: Tree, pos: Position, sym: Option[Symbol] = None): T = @@ -315,13 +270,11 @@ abstract class PcCollector[T]( * All indentifiers such as: * val a = <> */ - case ident: Ident - if ident.pos.isRange && filter(ident) || - syntheticFilter(ident) => + case ident: Ident if ident.pos.isRange && filter(ident) => if (ident.symbol == NoSymbol) - collect(ident, ident.pos, fallbackSymbol(ident.name, pos)) :: acc + acc + collect(ident, ident.pos, fallbackSymbol(ident.name, pos)) else - collect(ident, ident.pos) :: acc + acc + collect(ident, ident.pos) /** * Needed for type trees such as: @@ -330,10 +283,10 @@ abstract class PcCollector[T]( case tpe: TypeTree if tpe.pos.isRange && tpe.original != null && filter(tpe) => tpe.original.children.foldLeft( - collect( + acc + collect( tpe.original, typePos(tpe) - ) :: acc + ) )(traverse(_, _)) /** @@ -343,20 +296,11 @@ abstract class PcCollector[T]( case sel: Select if sel.pos.isRange && filter(sel) => val newAcc = if (isForComprehensionMethod(sel)) acc - else collect(sel, sel.namePosition) :: acc + else acc + collect(sel, sel.namePosition) traverse( newAcc, sel.qualifier ) - - case sel: Select if syntheticFilter(sel) => - traverse( - collect( - sel, - sel.pos - ) :: acc, - sel.qualifier - ) /* all definitions: * def <> = ??? * class <> = ??? @@ -368,7 +312,7 @@ abstract class PcCollector[T]( df, df.namePosition ) - if (acc.contains(t)) acc else t :: acc + if (acc(t)) acc else acc + t })(traverse(_, _)) /* Named parameters, since they don't show up in typed tree: * foo(<> = "abc") @@ -401,35 +345,18 @@ abstract class PcCollector[T]( */ case bind: Bind if bind.pos.isDefined && filter(bind) => bind.children.foldLeft( - collect( + acc + collect( bind, bind.namePosition - ) :: acc + ) )(traverse(_, _)) - /** - * We don't want to collect type parameters in for-comp map, flatMap etc. - */ - case TypeApply(sel: Select, _) if isForComprehensionMethod(sel) => - traverse(acc, sel.qualifier) - /** * We don't automatically traverser types like: * val opt: Option[<>] = */ case tpe: TypeTree if tpe.original != null => tpe.original.children.foldLeft(acc)(traverse(_, _)) - - /** - * For collecting synthetic type parameters: - * def hello[T](t: T) = t - * val x = hello<<[List[Int]]>>(List<>(1)) - */ - case tpe: TypeTree if includeSynthetics && tpe.pos.isOffset => - collect( - tpe, - tpe.pos - ) :: acc /** * Some type trees don't have symbols attached such as: * type A = List[_ <: <>[Int]] @@ -439,19 +366,13 @@ abstract class PcCollector[T]( soughtFilter(_.decodedName == id.name.decoded) => fallbackSymbol(id.name, id.pos) match { case Some(sym) if soughtFilter(_ == sym) => - collect( + acc + collect( id, id.pos - ) :: acc + ) case _ => acc } - /** - * We don't want to traverse type from synthetic match-case in - * val <> - */ - case Typed(expr, tpt) if !tpt.pos.isRange => - traverse(acc, expr) /** * For traversing annotations: * @<>("") @@ -495,10 +416,10 @@ abstract class PcCollector[T]( case name: NameTree if soughtFilter(_ == name.symbol) && name.pos.isRange => tree.children.foldLeft( - collect( + acc + collect( name, name.namePosition - ) :: acc + ) )(traverse(_, _)) // needed for `classOf[<>]` @@ -507,7 +428,7 @@ abstract class PcCollector[T]( val posStart = text.indexOfSlice(sym.decodedName, lit.pos.start) if (posStart == -1) acc else - collect( + acc + collect( lit, new RangePosition( lit.pos.source, @@ -516,13 +437,14 @@ abstract class PcCollector[T]( posStart + sym.decodedName.length ), Option(sym) - ) :: acc + ) case _ => tree.children.foldLeft(acc)(traverse(_, _)) } } - toTraverse.flatMap(traverseWithParent(None)(List.empty[T], _)) + val all = traverseWithParent(None)(Set.empty[T], unit.lastBody) + all } private def annotationChildren(mdef: MemberDef): List[Tree] = { @@ -543,17 +465,4 @@ abstract class PcCollector[T]( } } - private val forCompMethods = - Set(nme.map, nme.flatMap, nme.withFilter, nme.foreach) - - // We don't want to collect synthethic `map`, `withFilter`, `foreach` and `flatMap` in for-comprenhensions - private def isForComprehensionMethod(sel: Select): Boolean = { - val syntheticName = sel.name match { - case name: TermName => forCompMethods(name) - case _ => false - } - val wrongSpan = sel.qualifier.pos.includes(sel.namePosition.focusStart) - syntheticName && wrongSpan - } - } diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/PcDocumentHighlightProvider.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/PcDocumentHighlightProvider.scala index df202aa79ea..ad23b9c1dc7 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/PcDocumentHighlightProvider.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/PcDocumentHighlightProvider.scala @@ -1,5 +1,6 @@ package scala.meta.internal.pc +import scala.meta.internal.mtags.MtagsEnrichments._ import scala.meta.pc.OffsetParams import org.eclipse.lsp4j.DocumentHighlight @@ -14,7 +15,7 @@ final class PcDocumentHighlightProvider( def collect( parent: Option[Tree] )(tree: Tree, toAdjust: Position, sym: Option[Symbol]): DocumentHighlight = { - val (pos, _) = adjust(toAdjust) + val (pos, _) = toAdjust.adjust(text) tree match { case _: MemberDef => new DocumentHighlight(pos.toLsp, DocumentHighlightKind.Write) @@ -26,5 +27,5 @@ final class PcDocumentHighlightProvider( } def highlights(): List[DocumentHighlight] = - result().distinct + result() } diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/PcInlineValueProviderImpl.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/PcInlineValueProviderImpl.scala index 2e38093b4f1..f29a8397ae6 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/PcInlineValueProviderImpl.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/PcInlineValueProviderImpl.scala @@ -10,13 +10,13 @@ import scala.meta.pc.OffsetParams import org.eclipse.{lsp4j => l} final class PcInlineValueProviderImpl( - val compiler: MetalsGlobal, + val cp: MetalsGlobal, val params: OffsetParams ) extends InlineValueProvider { - import compiler._ + import cp._ val pcCollector: PcCollector[Occurence] = - new PcCollector[Occurence](compiler, params) { + new PcCollector[Occurence](cp, params) { def collect( parent: Option[compiler.Tree] )( @@ -24,7 +24,7 @@ final class PcInlineValueProviderImpl( pos: Position, sym: Option[compiler.Symbol] ): Occurence = { - val (adjustedPos, _) = adjust(pos) + val (adjustedPos, _) = pos.adjust(this.text) Occurence( tree.asInstanceOf[Tree], parent.map(_.asInstanceOf[Tree]), @@ -38,7 +38,7 @@ final class PcInlineValueProviderImpl( override val text: Array[Char] = pcCollector.text override def defAndRefs(): Either[String, (Definition, List[Reference])] = { - val allOccurences = pcCollector.result().distinct + val allOccurences = pcCollector.result() for { definition <- allOccurences .collectFirst { case Occurence(defn: ValDef, _, pos) => diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/PcRenameProvider.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/PcRenameProvider.scala index eb7d77a4746..0cef6a61ce1 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/PcRenameProvider.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/PcRenameProvider.scala @@ -37,7 +37,7 @@ class PcRenameProvider( def collect( parent: Option[Tree] )(tree: Tree, toAdjust: Position, sym: Option[Symbol]): l.TextEdit = { - val (pos, stripBackticks) = adjust(toAdjust, forRename = true) + val (pos, stripBackticks) = toAdjust.adjust(text, forRename = true) new l.TextEdit( pos.toLsp, if (stripBackticks) newName.stripBackticks else newName @@ -47,7 +47,7 @@ class PcRenameProvider( def rename(): List[l.TextEdit] = { val symbols = soughtSymbols.map(_._1).getOrElse(Set.empty) if (symbols.nonEmpty && symbols.forall(canRenameSymbol(_))) - result().distinct + result() else Nil } diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/PcSemanticTokensProvider.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/PcSemanticTokensProvider.scala index 80c56517dfc..142c901102a 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/PcSemanticTokensProvider.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/PcSemanticTokensProvider.scala @@ -1,4 +1,5 @@ package scala.meta.internal.pc +import scala.meta.internal.mtags.MtagsEnrichments._ import scala.meta.internal.pc.SemanticTokens._ import scala.meta.pc.Node import scala.meta.pc.VirtualFileParams @@ -59,7 +60,7 @@ final class PcSemanticTokensProvider( Some( makeNode( sym = sym, - pos = adjust(pos)._1, + pos = pos.adjust(text)._1, isDefinition = isDefinition(tree), isDeclaration = isDeclaration(tree) ) @@ -71,7 +72,6 @@ final class PcSemanticTokensProvider( Collector .result() .flatten - .distinct .sortWith((n1, n2) => if (n1.start() == n2.start()) n1.end() < n2.end() else n1.start() < n2.start() diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/PcSyntheticDecorationsProvider.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/PcSyntheticDecorationsProvider.scala index e915258b355..bbff3bb0e63 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/PcSyntheticDecorationsProvider.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/PcSyntheticDecorationsProvider.scala @@ -1,10 +1,11 @@ package scala.meta.internal.pc +import scala.meta.internal.mtags.MtagsEnrichments._ import scala.meta.pc.SyntheticDecoration import scala.meta.pc.SyntheticDecorationsParams final class PcSyntheticDecorationsProvider( - protected val compiler: MetalsGlobal, // compiler + protected val compiler: MetalsGlobal, val params: SyntheticDecorationsParams ) { import compiler._ @@ -27,7 +28,7 @@ final class PcSyntheticDecorationsProvider( ): Synthetics = tree match { case ImplicitConversion(name, pos) if params.implicitConversions() => - val adjusted = adjust(pos) + val adjusted = pos.adjust(text)._1 decorations .add( Decoration( @@ -48,7 +49,7 @@ final class PcSyntheticDecorationsProvider( val label = if (allImplicit) names.mkString("(", ", ", ")") else names.mkString(", ", ", ", "") - val adjusted = adjust(pos) + val adjusted = pos.adjust(text)._1 decorations.add( Decoration( adjusted.focusEnd.toLsp, @@ -58,7 +59,7 @@ final class PcSyntheticDecorationsProvider( ) case TypeParameters(types, pos) if params.typeParameters() => val label = types.map(toLabel(_, pos)).mkString("[", ", ", "]") - val adjusted = adjust(pos) + val adjusted = pos.adjust(text)._1 decorations.add( Decoration( adjusted.focusEnd.toLsp, @@ -68,8 +69,8 @@ final class PcSyntheticDecorationsProvider( ) case InferredType(tpe, pos) if params.inferredTypes() => val label = toLabel(tpe, pos) - val adjusted = adjust(pos) - if (decorations.containsDef(adjusted.start)) decorations + val adjusted = pos.adjust(text)._1 + if (decorations.containsDef(adjusted.end)) decorations else decorations.add( Decoration( @@ -77,7 +78,7 @@ final class PcSyntheticDecorationsProvider( ": " + label, DecorationKind.InferredType ), - adjusted.start + adjusted.end ) case _ => decorations } @@ -90,20 +91,6 @@ final class PcSyntheticDecorationsProvider( tree.children.foldLeft(decorations)(traverse(_, _)) } - private def adjust( - pos: Position - ): Position = { - if (pos.isOffset) pos - else { - val isOldNameBackticked = text(pos.start) == '`' && - (text(pos.end - 1) != '`' || pos.start == (pos.end - 1)) && - text(pos.end + 1) == '`' - if (isOldNameBackticked) // pos - pos.withEnd(pos.end + 2) - else pos - } - } - object ImplicitConversion { def unapply(tree: Tree): Option[(String, Position)] = tree match { case Apply(fun, args) if isImplicitConversion(fun) => @@ -122,9 +109,20 @@ final class PcSyntheticDecorationsProvider( val allImplicit = providedArgs.isEmpty val pos = providedArgs.lastOption.fold(tree.pos)(_.pos) Some(implicitArgs.map(_.symbol.decodedName), pos, allImplicit) + case Apply(ta: TypeApply, Apply(fun, _) :: _) + if fun.pos.isOffset && isValueOf(fun.symbol) => + val pos = ta.pos + Some( + List("new " + nme.valueOf.decoded.capitalize + "(...)"), + pos, + true + ) case _ => None } - def isSyntheticArg(arg: Tree): Boolean = + private def isValueOf(symbol: Symbol) = + symbol != null && symbol.safeOwner.decodedName == nme.valueOf.decoded.capitalize + + private def isSyntheticArg(arg: Tree): Boolean = arg.pos.isOffset && arg.symbol != null && arg.symbol.isImplicit } @@ -171,7 +169,7 @@ final class PcSyntheticDecorationsProvider( c => !c.isWhitespace && c != '=' && c != '{', dd.rhs.pos.start - 1 ) - dd.pos.withEnd(tpeIdx + 1) + dd.pos.withEnd(Math.max(dd.namePosition.end, tpeIdx + 1)) } } private def isCompilerGeneratedSymbol(sym: Symbol) = @@ -181,19 +179,6 @@ final class PcSyntheticDecorationsProvider( !vd.rhs.pos.isRange || vd.rhs.pos.start > vd.namePosition.end } - private val forCompMethods = - Set(nme.map, nme.flatMap, nme.withFilter, nme.foreach) - - // We don't want to collect synthethic `map`, `withFilter`, `foreach` and `flatMap` in for-comprenhensions - private def isForComprehensionMethod(sel: Select): Boolean = { - val syntheticName = sel.name match { - case name: TermName => forCompMethods(name) - case _ => false - } - val wrongSpan = sel.qualifier.pos.includes(sel.namePosition.focusStart) - syntheticName && wrongSpan - } - private def syntheticTupleApply(sel: Select): Boolean = { if (compiler.definitions.isTupleType(sel.tpe.finalResultType)) { sel match { diff --git a/mtags/src/main/scala-3/scala/meta/internal/mtags/MtagsEnrichments.scala b/mtags/src/main/scala-3/scala/meta/internal/mtags/MtagsEnrichments.scala index 26279ae5b65..dfe62ae3022 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/mtags/MtagsEnrichments.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/mtags/MtagsEnrichments.scala @@ -106,8 +106,51 @@ object MtagsEnrichments extends ScalametaCommonEnrichments: def encloses(other: RangeParams): Boolean = pos.start <= other.offset() && pos.end >= other.endOffset() + + /** + * @return (adjusted position, should strip backticks) + */ + def adjust( + text: Array[Char], + forRename: Boolean = false, + )(using Context): (SourcePosition, Boolean) = + if !pos.span.isCorrect(text) then (pos, false) + else + val pos0 = + val span = pos.span + if span.exists && span.point > span.end then + pos.withSpan( + span + .withStart(span.point) + .withEnd(span.point + (span.end - span.start)) + ) + else pos + + val pos1 = + if pos0.end > 0 && text(pos0.end - 1) == ',' then + pos0.withEnd(pos0.end - 1) + else pos0 + val isBackticked = + text(pos1.start) == '`' && + pos1.end > 0 && + text(pos1.end - 1) == '`' + // when the old name contains backticks, the position is incorrect + val isOldNameBackticked = text(pos1.start) != '`' && + pos1.start > 0 && + text(pos1.start - 1) == '`' && + text(pos1.end) == '`' + if isBackticked && forRename then + (pos1.withStart(pos1.start + 1).withEnd(pos1.end - 1), true) + else if isOldNameBackticked then + (pos1.withStart(pos1.start - 1).withEnd(pos1.end + 1), false) + else (pos1, false) + end adjust end extension + extension (span: Span) + def isCorrect(text: Array[Char]): Boolean = + !span.isZeroExtent && span.exists && span.start < text.size && span.end <= text.size + extension (pos: RangeParams) def encloses(other: SourcePosition): Boolean = pos.offset() <= other.start && pos.endOffset() >= other.end @@ -235,6 +278,16 @@ object MtagsEnrichments extends ScalametaCommonEnrichments: for sel <- imp.selectors.find(_.span.contains(span)) yield imp.expr.symbol.info.member(sel.name).symbol + private val forCompMethods = + Set(nme.map, nme.flatMap, nme.withFilter, nme.foreach) + extension (sel: Select) + def isForComprehensionMethod(using Context): Boolean = + val syntheticName = sel.name match + case name: TermName => forCompMethods(name) + case _ => false + val wrongSpan = sel.qualifier.span.contains(sel.nameSpan) + syntheticName && wrongSpan + extension (denot: Denotation) def allSymbols: List[Symbol] = denot match diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/PcCollector.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/PcCollector.scala index 81d2fa2667c..eb69a65645e 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/PcCollector.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/PcCollector.scala @@ -7,7 +7,6 @@ import scala.meta as m import scala.meta.internal.metals.CompilerOffsetParams import scala.meta.internal.mtags.MtagsEnrichments.* import scala.meta.pc.OffsetParams -import scala.meta.pc.RangeParams import scala.meta.pc.VirtualFileParams import dotty.tools.dotc.ast.NavigateAST @@ -38,6 +37,7 @@ abstract class PcCollector[T]( val uri = params.uri() val filePath = Paths.get(uri) val sourceText = params.text + val text = sourceText.toCharArray() val source = SourceFile.virtual(filePath.toString, sourceText) driver.run(uri, source) @@ -73,45 +73,6 @@ abstract class PcCollector[T]( parent: Option[Tree] )(tree: Tree, pos: SourcePosition, symbol: Option[Symbol]): T - /** - * @return (adjusted position, should strip backticks) - */ - def adjust( - pos1: SourcePosition, - forRename: Boolean = false, - ): (SourcePosition, Boolean) = - if !pos1.span.isCorrect then (pos1, false) - else - val pos0 = - val span = pos1.span - if span.exists && span.point > span.end then - pos1.withSpan( - span - .withStart(span.point) - .withEnd(span.point + (span.end - span.start)) - ) - else pos1 - - val pos = - if pos0.end > 0 && sourceText(pos0.end - 1) == ',' then - pos0.withEnd(pos0.end - 1) - else pos0 - val isBackticked = - sourceText(pos.start) == '`' && - pos.end > 0 && - sourceText(pos.end - 1) == '`' - // when the old name contains backticks, the position is incorrect - val isOldNameBackticked = sourceText(pos.start) != '`' && - pos.start > 0 && - sourceText(pos.start - 1) == '`' && - sourceText(pos.end) == '`' - if isBackticked && forRename then - (pos.withStart(pos.start + 1).withEnd(pos.end - 1), true) - else if isOldNameBackticked then - (pos.withStart(pos.start - 1).withEnd(pos.end + 1), false) - else (pos, false) - end adjust - def symbolAlternatives(sym: Symbol) = def member(parent: Symbol) = parent.info.member(sym.name).symbol def primaryConstructorTypeParam(owner: Symbol) = @@ -360,26 +321,16 @@ abstract class PcCollector[T]( if symbols.nonEmpty then Some((symbols, namePos)) else None end collectAllExtensionParamSymbols - def treesInRange(rangeParams: RangeParams): List[Tree] = - val pos = driver.sourcePosition(rangeParams) - val path = Interactive - .pathTo(driver.openedTrees(uri), pos)(using driver.currentCtx) - val trees = - path.headOption.getOrElse(unit.tpdTree) - List(trees) - def result(): List[T] = params match case _: OffsetParams => resultWithSought() - case _ => resultAllOccurences()(List(unit.tpdTree)) + case _ => resultAllOccurences().toList - def resultAllOccurences(includeSynthetics: Boolean = false)( - toTraverse: List[Tree] = List(unit.tpdTree) - ): List[T] = + def resultAllOccurences(): Set[T] = def noTreeFilter = (_: Tree) => true def noSoughtFilter = (_: Symbol => Boolean) => true - traverseSought(noTreeFilter, noSoughtFilter, includeSynthetics)(toTraverse) + traverseSought(noTreeFilter, noSoughtFilter) def resultWithSought(): List[T] = soughtSymbols(path) match @@ -422,9 +373,7 @@ abstract class PcCollector[T]( def soughtFilter(f: Symbol => Boolean): Boolean = sought.exists(f) - traverseSought(soughtTreeFilter, soughtFilter)( - List(unit.tpdTree) - ).toList + traverseSought(soughtTreeFilter, soughtFilter).toList case None => Nil @@ -435,16 +384,12 @@ abstract class PcCollector[T]( def traverseSought( filter: Tree => Boolean, soughtFilter: (Symbol => Boolean) => Boolean, - includeSynthetics: Boolean = false, - )(toTraverse: List[Tree]): List[T] = - def syntheticFilter(tree: Tree) = includeSynthetics && - tree.span.isSynthetic && - tree.symbol.isOneOf(Flags.GivenOrImplicit) + ): Set[T] = def collectNamesWithParent( - occurences: List[T], + occurences: Set[T], tree: Tree, parent: Option[Tree], - ): List[T] = + ): Set[T] = def collect( tree: Tree, pos: SourcePosition, @@ -456,16 +401,14 @@ abstract class PcCollector[T]( * All indentifiers such as: * val a = <> */ - case ident: Ident - if ident.span.isCorrect && filter(ident) || - syntheticFilter(ident) => + case ident: Ident if ident.span.isCorrect && filter(ident) => // symbols will differ for params in different ext methods, but source pos will be the same if soughtFilter(_.sourcePos == ident.symbol.sourcePos) then - collect( + occurences + collect( ident, ident.sourcePos, - ) :: occurences + ) else occurences /** * All select statements such as: @@ -473,8 +416,8 @@ abstract class PcCollector[T]( */ case sel: Select if sel.span.isCorrect && filter(sel) && - !isForComprehensionMethod(sel) => - collect( + !sel.isForComprehensionMethod => + occurences + collect( sel, pos.withSpan(selectNameSpan(sel)), ) @@ -498,18 +441,24 @@ abstract class PcCollector[T]( ) else occurences case _ => occurences - ) :: occurences /** * for synthetic decorations in: * given Conversion[Int, String] = _.toString * val x: String = <<>>123 */ - case Select(id: Ident, name) - if syntheticFilter(id) && name == nme.apply => - collect( - id, - id.sourcePos, - ) :: occurences + case sel: Select + if sel.span.isZeroExtent && sel.symbol.is(Flags.ExtensionMethod) => + parent match + case Some(a: Apply) => + val span = a.span.withStart(a.span.point) + val amendedSelect = sel.withSpan(span) + if filter(amendedSelect) then + occurences + collect( + amendedSelect, + pos.withSpan(span), + ) + else occurences + case _ => occurences /* all definitions: * def <> = ??? * class <> = ??? @@ -520,14 +469,14 @@ abstract class PcCollector[T]( filter(df) && !isGeneratedGiven(df) => val annots = collectTrees(df.mods.annotations) val traverser = - new PcCollector.DeepFolderWithParent[List[T]]( + new PcCollector.DeepFolderWithParent[Set[T]]( collectNamesWithParent ) annots.foldLeft( - collect( + occurences + collect( df, pos.withSpan(df.nameSpan), - ) :: occurences + ) ) { case (set, tree) => traverser(set, tree) } @@ -575,57 +524,55 @@ abstract class PcCollector[T]( case mdf: MemberDef if mdf.mods.annotations.nonEmpty => val trees = collectTrees(mdf.mods.annotations) val traverser = - new PcCollector.DeepFolderWithParent[List[T]]( + new PcCollector.DeepFolderWithParent[Set[T]]( collectNamesWithParent ) - trees.foldLeft(occurences) { case (list, tree) => - traverser(list, tree) + trees.foldLeft(occurences) { case (set, tree) => + traverser(set, tree) } /** * For traversing import selectors: * import scala.util.<> */ case imp: Import if filter(imp) => - imp.selectors.collect { - case sel: ImportSelector - if soughtFilter(_.decodedName == sel.name.decoded) => - // Show both rename and main together - val spans = - if !sel.renamed.isEmpty then - Set(sel.renamed.span, sel.imported.span) - else Set(sel.imported.span) - // See https://github.com/scalameta/metals/pull/5100 - val symbol = imp.expr.symbol.info.member(sel.name).symbol match - // We can get NoSymbol when we import "_", "*"", "given" or when the names don't match - // eg. "@@" doesn't match "$at$at". - // Then we try to find member based on decodedName - case NoSymbol => - imp.expr.symbol.info.allMembers - .find(_.name.decoded == sel.name.decoded) - .map(_.symbol) - .getOrElse(NoSymbol) - case sym => sym - spans.filter(_.isCorrect).map { span => - collect( - imp, - pos.withSpan(span), - Some(symbol), - ) - } - }.flatten ++ occurences - case tt: TypeTree if includeSynthetics && parent.exists(isTypeApply) => - collect( - tt, - tt.sourcePos, - ) :: occurences + imp.selectors + .collect { + case sel: ImportSelector + if soughtFilter(_.decodedName == sel.name.decoded) => + // Show both rename and main together + val spans = + if !sel.renamed.isEmpty then + Set(sel.renamed.span, sel.imported.span) + else Set(sel.imported.span) + // See https://github.com/scalameta/metals/pull/5100 + val symbol = imp.expr.symbol.info.member(sel.name).symbol match + // We can get NoSymbol when we import "_", "*"", "given" or when the names don't match + // eg. "@@" doesn't match "$at$at". + // Then we try to find member based on decodedName + case NoSymbol => + imp.expr.symbol.info.allMembers + .find(_.name.decoded == sel.name.decoded) + .map(_.symbol) + .getOrElse(NoSymbol) + case sym => sym + spans.filter(_.isCorrect).map { span => + collect( + imp, + pos.withSpan(span), + Some(symbol), + ) + } + } + .flatten + .toSet ++ occurences case inl: Inlined => val traverser = - new PcCollector.DeepFolderWithParent[List[T]]( + new PcCollector.DeepFolderWithParent[Set[T]]( collectNamesWithParent ) val trees = inl.call :: inl.bindings - trees.foldLeft(occurences) { case (list, tree) => - traverser(list, tree) + trees.foldLeft(occurences) { case (set, tree) => + traverser(set, tree) } case o => occurences @@ -633,8 +580,9 @@ abstract class PcCollector[T]( end collectNamesWithParent val traverser = - new PcCollector.DeepFolderWithParent[List[T]](collectNamesWithParent) - toTraverse.flatMap(traverser(List.empty[T], _)) + new PcCollector.DeepFolderWithParent[Set[T]](collectNamesWithParent) + val all = traverser(Set.empty[T], unit.tpdTree) + all end traverseSought // @note (tgodzik) Not sure currently how to get rid of the warning, but looks to correctly @@ -657,27 +605,6 @@ abstract class PcCollector[T]( Span(span.start, span.start + realName.length, point) else Span(point, span.end, point) else span - - private val forCompMethods = - Set(nme.map, nme.flatMap, nme.withFilter, nme.foreach) - - // We don't want to collect synthethic `map`, `withFilter`, `foreach` and `flatMap` in for-comprenhensions - private def isForComprehensionMethod(sel: Select): Boolean = - val syntheticName = sel.name match - case name: TermName => forCompMethods(name) - case _ => false - val wrongSpan = sel.qualifier.span.contains(sel.nameSpan) - syntheticName && wrongSpan - - /** - * We don't want to collect type parameters in for-comp map, flatMap etc. - */ - private def isTypeApply(t: Tree): Boolean = - t match - case TypeApply(sel: Select, _) if isForComprehensionMethod(sel) => false - case _: TypeApply => true - case _ => false - end PcCollector object PcCollector: diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/PcDocumentHighlightProvider.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/PcDocumentHighlightProvider.scala index e36fa9c152b..9e1c40208b1 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/PcDocumentHighlightProvider.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/PcDocumentHighlightProvider.scala @@ -22,7 +22,7 @@ final class PcDocumentHighlightProvider( toAdjust: SourcePosition, sym: Option[Symbol], ): DocumentHighlight = - val (pos, _) = adjust(toAdjust) + val (pos, _) = toAdjust.adjust(text) tree match case _: NamedDefTree => DocumentHighlight(pos.toLsp, DocumentHighlightKind.Write) diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/PcInlineValueProviderImpl.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/PcInlineValueProviderImpl.scala index 1a5a9f26148..07c765e673c 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/PcInlineValueProviderImpl.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/PcInlineValueProviderImpl.scala @@ -22,8 +22,6 @@ final class PcInlineValueProviderImpl( ) extends PcCollector[Occurence](driver, params) with InlineValueProvider: - val text = params.text.toCharArray() - val position: l.Position = pos.toLsp.getStart() override def collect(parent: Option[Tree])( @@ -31,12 +29,12 @@ final class PcInlineValueProviderImpl( pos: SourcePosition, sym: Option[Symbol], ): Occurence = - val (adjustedPos, _) = adjust(pos) + val (adjustedPos, _) = pos.adjust(text) Occurence(tree, parent, adjustedPos) override def defAndRefs(): Either[String, (Definition, List[Reference])] = val newctx = driver.currentCtx.fresh.setCompilationUnit(unit) - val allOccurences = result().distinct + val allOccurences = result() for definition <- allOccurences .collectFirst { case Occurence(defn: ValDef, _, pos) => diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/PcRenameProvider.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/PcRenameProvider.scala index 5f3c8857dbc..22320db0e5d 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/PcRenameProvider.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/PcRenameProvider.scala @@ -34,7 +34,7 @@ final class PcRenameProvider( def collect( parent: Option[Tree] )(tree: Tree, toAdjust: SourcePosition, sym: Option[Symbol]): l.TextEdit = - val (pos, stripBackticks) = adjust(toAdjust, forRename = true) + val (pos, stripBackticks) = toAdjust.adjust(text, forRename = true) l.TextEdit( pos.toLsp, if stripBackticks then newName.stripBackticks else newName, @@ -46,7 +46,7 @@ final class PcRenameProvider( val (symbols, _) = soughtSymbols(path).getOrElse(Set.empty, pos) if symbols.nonEmpty && symbols.forall(canRenameSymbol(_)) then - val res = result().distinct + val res = result() res else Nil end rename diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/PcSemanticTokensProvider.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/PcSemanticTokensProvider.scala index d17c83340e1..851fcd7cf04 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/PcSemanticTokensProvider.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/PcSemanticTokensProvider.scala @@ -67,7 +67,7 @@ final class PcSemanticTokensProvider( Some( makeNode( sym = sym, - pos = adjust(pos)._1, + pos = pos.adjust(text)._1, isDefinition = isDefinition(tree), isDeclaration = isDeclaration(tree), ) diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/PcSyntheticDecorationProvider.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/PcSyntheticDecorationProvider.scala index 34b7a120385..6484fcce061 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/PcSyntheticDecorationProvider.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/PcSyntheticDecorationProvider.scala @@ -14,7 +14,6 @@ import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.ast.tpd.* import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.core.Flags -import dotty.tools.dotc.core.Names.TermName import dotty.tools.dotc.core.StdNames.* import dotty.tools.dotc.core.Types.* import dotty.tools.dotc.interactive.Interactive @@ -32,6 +31,7 @@ class PcSyntheticDecorationsProvider( val uri = params.uri() val filePath = Paths.get(uri) val sourceText = params.text + val text = sourceText.toCharArray() val source = SourceFile.virtual(filePath.toString, sourceText) driver.run(uri, source) @@ -39,20 +39,18 @@ class PcSyntheticDecorationsProvider( val unit = driver.currentCtx.run.units.head def tpdTree = unit.tpdTree - // def tpdTree = pprint.log(unit.tpdTree) def provide(): List[SyntheticDecoration] = val deepFolder = DeepFolder[Synthetics](collectDecorations) deepFolder(Synthetics.empty, tpdTree).decorations - // TODO: Add adjust def collectDecorations( decorations: Synthetics, tree: Tree, ): Synthetics = tree match case ImplicitConversion(name, range) if params.implicitConversions() => - val adjusted = adjust(range) + val adjusted = range.adjust(text)._1 decorations .add( Decoration( @@ -75,7 +73,7 @@ class PcSyntheticDecorationsProvider( else names.mkString(", ", ", ", "") decorations.add( Decoration( - adjust(pos).toLsp, + pos.adjust(text)._1.toLsp, label, DecorationKind.ImplicitParameter, ) @@ -85,13 +83,13 @@ class PcSyntheticDecorationsProvider( val label = tpes.map(toLabel(_, pos)).mkString("[", ", ", "]") decorations.add( Decoration( - adjust(pos).endPos.toLsp, + pos.adjust(text)._1.endPos.toLsp, label, DecorationKind.TypeParameter, ) ) case InferredType(tpe, pos, defTree) if params.inferredTypes() => - val adjustedPos = adjustForInit(defTree, adjust(pos)).endPos + val adjustedPos = pos.adjust(text)._1.endPos if decorations.containsDef(adjustedPos.start) then decorations else decorations.add( @@ -151,61 +149,6 @@ class PcSyntheticDecorationsProvider( case _ => true else false case _ => false - - private def adjustForInit(defTree: Tree, pos: SourcePosition) = - defTree match - case dd: DefDef if dd.symbol.isConstructor => - if dd.rhs.isEmpty then dd - else - val tpeIdx = sourceText.lastIndexWhere( - c => !c.isWhitespace && c != '=', - dd.rhs.span.start - 1, - ) - pos.withEnd(tpeIdx + 1) - case _ => pos - - /** - * @return (adjusted position, should strip backticks) - */ - private def adjust( - pos1: SourcePosition, - forRename: Boolean = false, - ): SourcePosition = - if !pos1.span.isCorrect then pos1 - else - val pos0 = - val span = pos1.span - if span.exists && span.point > span.end then - pos1.withSpan( - span - .withStart(span.point) - .withEnd(span.point + (span.end - span.start)) - ) - else pos1 - val pos = - if pos0.end > 0 && sourceText(pos0.end - 1) == ',' then - pos0.withEnd(pos0.end - 1) - else pos0 - val isBackticked = - sourceText(pos.start) == '`' && - pos.end > 0 && - sourceText(pos.end - 1) == '`' - // when the old name contains backticks, the position is incorrect - val isOldNameBackticked = sourceText(pos.start) != '`' && - pos.start > 0 && - sourceText(pos.start - 1) == '`' && - sourceText(pos.end) == '`' - if isBackticked && forRename then - pos.withStart(pos.start + 1).withEnd(pos.end - 1) - else if isOldNameBackticked then - pos.withStart(pos.start - 1).withEnd(pos.end + 1) - else pos - end adjust - - extension (span: Span) - def isCorrect = - !span.isZeroExtent && span.exists && span.start < sourceText.size && span.end <= sourceText.size - end PcSyntheticDecorationsProvider object ImplicitConversion: @@ -239,7 +182,17 @@ object ImplicitParameters: val allImplicit = providedArgs.isEmpty val pos = implicitArgs.head.sourcePos Some(implicitArgs.map(_.symbol.decodedName), pos, allImplicit) + case Apply(ta @ TypeApply(fun, _), _) + if fun.span.isSynthetic && isValueOf(fun) => + Some( + List("new " + tpnme.valueOf.decoded.capitalize + "(...)"), + fun.sourcePos, + true, + ) case _ => None + private def isValueOf(tree: Tree)(using Context) = + val symbol = tree.symbol.maybeOwner + symbol.name.decoded == tpnme.valueOf.decoded.capitalize private def isSyntheticArg(tree: Tree)(using Context) = tree match case tree: Ident => tree.span.isSynthetic && tree.symbol.isOneOf(Flags.GivenOrImplicit) @@ -249,7 +202,7 @@ end ImplicitParameters object TypeParameters: def unapply(tree: Tree)(using Context) = tree match - case TypeApply(sel: Select, _) if isForComprehensionMethod(sel) => None + case TypeApply(sel: Select, _) if sel.isForComprehensionMethod => None case TypeApply(fun, args) if inferredTypeArgs(args) => val tpes = args.map(_.tpe.stripTypeVar.widen.finalResultType) Some((tpes, tree.endPos, fun)) @@ -259,16 +212,6 @@ object TypeParameters: case tt: TypeTree if tt.span.exists && !tt.span.isZeroExtent => true case _ => false } - private val forCompMethods = - Set(nme.map, nme.flatMap, nme.withFilter, nme.foreach) - // We don't want to collect synthethic `map`, `withFilter`, `foreach` and `flatMap` in for-comprenhensions - private def isForComprehensionMethod(sel: Select)(using Context): Boolean = - val syntheticName = sel.name match - case name: TermName => forCompMethods(name) - case _ => false - val wrongSpan = sel.qualifier.span.contains(sel.nameSpan) - syntheticName && wrongSpan - end TypeParameters object InferredType: @@ -291,7 +234,7 @@ object InferredType: case bd @ Bind( name, Ident(nme.WILDCARD), - ) /* if equalSpan(bd.span, bd.nameSpan)*/ => + ) => Some(bd.symbol.info, bd.namePos, bd) case _ => None diff --git a/tests/cross/src/test/scala/tests/pc/SyntheticDecorationsSuite.scala b/tests/cross/src/test/scala/tests/pc/SyntheticDecorationsSuite.scala index c423cfd2493..b259a252281 100644 --- a/tests/cross/src/test/scala/tests/pc/SyntheticDecorationsSuite.scala +++ b/tests/cross/src/test/scala/tests/pc/SyntheticDecorationsSuite.scala @@ -4,7 +4,7 @@ import scala.meta.internal.pc.DecorationKind import tests.BaseSyntheticDecorationsSuite -class SynthethicDecorationsSuite extends BaseSyntheticDecorationsSuite { +class SyntheticDecorationsSuite extends BaseSyntheticDecorationsSuite { check( "type-params", @@ -490,4 +490,18 @@ class SynthethicDecorationsSuite extends BaseSyntheticDecorationsSuite { ), ) + check( + "valueOf".tag(IgnoreScalaVersion.forLessThan("2.13.0")), + """|object O { + | def foo[Total <: Int](implicit total: ValueOf[Total]): Int = total.value + | val m = foo[500] + |} + |""".stripMargin, + """|object O { + | def foo[Total <: Int](implicit total: ValueOf[Total]): Int = total.value + | val m: Int = foo[500](new ValueOf(...)) + |} + |""".stripMargin, + ) + } diff --git a/tests/slow/src/test/scala/tests/feature/SyntheticDecorationsScala3ExpectSuite.scala b/tests/slow/src/test/scala/tests/feature/SyntheticDecorationsScala3ExpectSuite.scala index a6949a2b5cd..62625ab1d3e 100644 --- a/tests/slow/src/test/scala/tests/feature/SyntheticDecorationsScala3ExpectSuite.scala +++ b/tests/slow/src/test/scala/tests/feature/SyntheticDecorationsScala3ExpectSuite.scala @@ -3,33 +3,28 @@ package tests.feature import scala.meta.internal.io.PathIO import scala.meta.internal.metals.Buffers import scala.meta.internal.metals.ClientConfiguration -import scala.meta.internal.metals.CompilerSyntheticDecorationsParams -import scala.meta.internal.metals.CompilerVirtualFileParams import scala.meta.internal.metals.Embedded -import scala.meta.internal.metals.EmptyCancelToken import scala.meta.internal.metals.MetalsEnrichments._ import scala.meta.internal.metals.MtagsBinaries import scala.meta.internal.metals.ProgressTicks import scala.meta.internal.metals.StatusBar -import scala.meta.internal.metals.TextEdits -import scala.meta.internal.metals.UserConfiguration import scala.meta.internal.metals.{BuildInfo => V} +import scala.meta.pc.PresentationCompiler -import org.eclipse.lsp4j.TextEdit -import tests.DirectoryExpectSuite -import tests.ExpectTestCase +import tests.BaseSyntheticDecorationsExpectSuite import tests.FakeTime import tests.InputProperties import tests.TestMtagsResolver import tests.TestingClient class SyntheticDecorationsScala3ExpectSuite( -) extends DirectoryExpectSuite("decorations3") { - override lazy val input: InputProperties = InputProperties.scala3() - private val compiler = { +) extends BaseSyntheticDecorationsExpectSuite( + "decorations3", + InputProperties.scala3(), + ) { + override val compiler: PresentationCompiler = { val resolver = new TestMtagsResolver() resolver.resolve(V.scala3) match { - case Some(mtags: MtagsBinaries.Artifacts) => val time = new FakeTime val client = new TestingClient(PathIO.workingDirectory, Buffers()) @@ -51,45 +46,4 @@ class SyntheticDecorationsScala3ExpectSuite( } } - val userConfig: UserConfiguration = UserConfiguration().copy( - showInferredType = Some("true"), - showImplicitArguments = true, - showImplicitConversionsAndClasses = true, - ) - - override def testCases(): List[ExpectTestCase] = { - input.scalaFiles.map { file => - ExpectTestCase( - file, - () => { - val vFile = CompilerVirtualFileParams( - file.file.toURI, - file.code, - EmptyCancelToken, - ) - val pcParams = CompilerSyntheticDecorationsParams( - vFile, - true, - true, - true, - true, - ) - val decorations = - compiler.syntheticDecorations(pcParams).get().asScala.toList - val edits = decorations.map { d => - new TextEdit( - d.range, - "/*" + d.label + "*/", - ) - } - - TextEdits.applyEdits(file.code, edits) - }, - ) - } - } - - override def afterAll(): Unit = { - compiler.shutdown() - } } diff --git a/tests/unit/src/test/scala/tests/SyntheticDecorationsExpectSuite.scala b/tests/unit/src/main/scala/tests/BaseSyntheticDecorationsExpectSuite.scala similarity index 81% rename from tests/unit/src/test/scala/tests/SyntheticDecorationsExpectSuite.scala rename to tests/unit/src/main/scala/tests/BaseSyntheticDecorationsExpectSuite.scala index d0e54eed836..4c78bc52499 100644 --- a/tests/unit/src/test/scala/tests/SyntheticDecorationsExpectSuite.scala +++ b/tests/unit/src/main/scala/tests/BaseSyntheticDecorationsExpectSuite.scala @@ -6,17 +6,17 @@ import scala.meta.internal.metals.EmptyCancelToken import scala.meta.internal.metals.MetalsEnrichments._ import scala.meta.internal.metals.TextEdits import scala.meta.internal.metals.UserConfiguration -import scala.meta.internal.pc.ScalaPresentationCompiler +import scala.meta.pc.PresentationCompiler import org.eclipse.lsp4j.TextEdit -class SyntheticDecorationsExpectSuite - extends DirectoryExpectSuite("decorations") { +abstract class BaseSyntheticDecorationsExpectSuite( + name: String, + inputProperties: => InputProperties, +) extends DirectoryExpectSuite(name) { + def compiler: PresentationCompiler - override lazy val input: InputProperties = InputProperties.scala2() - private val compiler = new ScalaPresentationCompiler( - classpath = input.classpath.entries.map(_.toNIO) - ) + override lazy val input: InputProperties = inputProperties val userConfig: UserConfiguration = UserConfiguration().copy( showInferredType = Some("true"), showImplicitArguments = true, diff --git a/tests/unit/src/test/scala/tests/SaveExpect.scala b/tests/unit/src/test/scala/tests/SaveExpect.scala index cab5a3ef462..e5739c1c5f5 100644 --- a/tests/unit/src/test/scala/tests/SaveExpect.scala +++ b/tests/unit/src/test/scala/tests/SaveExpect.scala @@ -17,7 +17,7 @@ object SaveExpect { new FoldingRangeScala3Suite, new WorkspaceSymbolExpectSuite, new SemanticTokensExpectSuite, - new SyntheticDecorationsExpectSuite, + new decorations.SyntheticDecorationsExpectSuite, ).foreach { suite => val header = suite.suiteName.length + 2 println("=" * header) diff --git a/tests/unit/src/test/scala/tests/decorations/SyntheticDecorationsExpectSuite.scala b/tests/unit/src/test/scala/tests/decorations/SyntheticDecorationsExpectSuite.scala new file mode 100644 index 00000000000..baa427d08fe --- /dev/null +++ b/tests/unit/src/test/scala/tests/decorations/SyntheticDecorationsExpectSuite.scala @@ -0,0 +1,17 @@ +package tests.decorations + +import scala.meta.internal.pc.ScalaPresentationCompiler +import scala.meta.pc.PresentationCompiler + +import tests.BaseSyntheticDecorationsExpectSuite +import tests.InputProperties + +class SyntheticDecorationsExpectSuite + extends BaseSyntheticDecorationsExpectSuite( + "decorations", + InputProperties.scala2(), + ) { + override val compiler: PresentationCompiler = new ScalaPresentationCompiler( + classpath = InputProperties.scala2().classpath.entries.map(_.toNIO) + ) +} diff --git a/tests/unit/src/test/scala/tests/decorations/SyntheticDecorationsLspSuite.scala b/tests/unit/src/test/scala/tests/decorations/SyntheticDecorationsLspSuite.scala index 7c108cfce65..c501bd0fbe3 100644 --- a/tests/unit/src/test/scala/tests/decorations/SyntheticDecorationsLspSuite.scala +++ b/tests/unit/src/test/scala/tests/decorations/SyntheticDecorationsLspSuite.scala @@ -898,38 +898,38 @@ class SyntheticDecorationsLspSuite extends BaseLspSuite("implicits") { ) } yield () } - // Todo: Unignore test - // test("value-of") { - // for { - // _ <- initialize( - // """|/metals.json - // |{ - // | "a": {} - // |} - // |/a/src/main/scala/Main.scala - // |object O { - // | def foo[Total <: Int](implicit total: ValueOf[Total]): Int = total.value - // | val m = foo[500] - // |} - // |""".stripMargin - // ) - // _ <- server.didChangeConfiguration( - // """{ - // | "show-implicit-arguments": true - // |} - // |""".stripMargin - // ) - // _ <- server.didOpen("a/src/main/scala/Main.scala") - // _ <- server.didSave("a/src/main/scala/Main.scala")(identity) - // _ = assertNoDiagnostics() - // _ = assertNoDiff( - // client.workspaceDecorations, - // """|object O { - // | def foo[Total <: Int](implicit total: ValueOf[Total]): Int = total.value - // | val m = foo[500](new ValueOf(...)) - // |} - // |""".stripMargin, - // ) - // } yield () - // } + + test("value-of") { + for { + _ <- initialize( + """|/metals.json + |{ + | "a": {} + |} + |/a/src/main/scala/Main.scala + |object O { + | def foo[Total <: Int](implicit total: ValueOf[Total]): Int = total.value + | val m = foo[500] + |} + |""".stripMargin + ) + _ <- server.didChangeConfiguration( + """{ + | "show-implicit-arguments": true + |} + |""".stripMargin + ) + _ <- server.didOpen("a/src/main/scala/Main.scala") + _ <- server.didSave("a/src/main/scala/Main.scala")(identity) + _ = assertNoDiagnostics() + _ = assertNoDiff( + client.workspaceDecorations, + """|object O { + | def foo[Total <: Int](implicit total: ValueOf[Total]): Int = total.value + | val m = foo[500](new ValueOf(...)) + |} + |""".stripMargin, + ) + } yield () + } } diff --git a/tests/unit/src/test/scala/tests/worksheets/WorksheetLspSuite.scala b/tests/unit/src/test/scala/tests/worksheets/WorksheetLspSuite.scala index 78fd5877042..a617a113cd1 100644 --- a/tests/unit/src/test/scala/tests/worksheets/WorksheetLspSuite.scala +++ b/tests/unit/src/test/scala/tests/worksheets/WorksheetLspSuite.scala @@ -57,7 +57,7 @@ class WorksheetLspSuite extends tests.BaseWorksheetLspSuite(V.scala213) { _ = assertNoDiff(identity, "render: String") _ = assertNoDiagnostics() _ = assertNoDiff( - client.workspaceDecorations, + client.workspaceDecorations(path), """|import $dep.`com.lihaoyi::scalatags:0.9.0` |import scalatags.Text.all._ |val htmlFile = html(