From 2430188225c1f8889362a673f759ba7c559c20f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Per=C5=82akowski?= <17816164+Perl99@users.noreply.github.com> Date: Fri, 5 Sep 2025 14:51:37 +0200 Subject: [PATCH 1/2] Improve symbol order in completions provided by the presentation compiler Extension methods that are not in the same file are placed after all Product methods and even after extension methods like "ensuring". This PR penalizes the following methods, so that they are no longer at the top of the suggestions: - scala.Product.* - scala.Equals.* - scala.Predef.ArrowAssoc.* - scala.Predef.Ensuring.* - scala.Predef.StringFormat.* - scala.Predef.nn - scala.Predef.runtimeChecked Resolves https://github.com/scalameta/metals/issues/7642 --- .../tools/pc/completions/Completions.scala | 22 +++++++++- .../completion/CompletionExtensionSuite.scala | 41 +++++++++++++++++++ .../pc/tests/completion/CompletionSuite.scala | 18 ++++---- 3 files changed, 70 insertions(+), 11 deletions(-) diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala index 61a93dc1bccb..2f5a46d19a86 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala @@ -766,6 +766,13 @@ class Completions( ).flatMap(_.alternatives.map(_.symbol)).toSet ) + private lazy val EqualsClass: ClassSymbol = requiredClass("scala.Equals") + private lazy val ArrowAssocClass: ClassSymbol = requiredClass("scala.Predef.ArrowAssoc") + private lazy val EnsuringClass: ClassSymbol = requiredClass("scala.Predef.Ensuring") + private lazy val StringFormatClass: ClassSymbol = requiredClass("scala.Predef.StringFormat") + private lazy val nnMethod: Symbol = defn.ScalaPredefModule.requiredMethod("nn") + private lazy val runtimeCheckedMethod: Symbol = defn.ScalaPredefModule.requiredMethod("runtimeChecked") + private def isNotLocalForwardReference(sym: Symbol)(using Context): Boolean = !sym.isLocalToBlock || !sym.srcPos.isAfter(completionPos.originalCursorPosition) || @@ -784,6 +791,17 @@ class Completions( (sym.isField && !isJavaClass && !isModuleOrClass) || sym.getter != NoSymbol catch case _ => false + def isInheritedFromScalaLibrary(sym: Symbol) = + sym.owner == defn.AnyClass || + sym.owner == defn.ObjectClass || + sym.owner == defn.ProductClass || + sym.owner == EqualsClass || + sym.owner == ArrowAssocClass || + sym.owner == EnsuringClass || + sym.owner == StringFormatClass || + sym == nnMethod || + sym == runtimeCheckedMethod + def symbolRelevance(sym: Symbol): Int = var relevance = 0 // symbols defined in this file are more relevant @@ -801,7 +819,7 @@ class Completions( case _ => // symbols whose owner is a base class are less relevant - if sym.owner == defn.AnyClass || sym.owner == defn.ObjectClass + if isInheritedFromScalaLibrary(sym) then relevance |= IsInheritedBaseMethod // symbols not provided via an implicit are more relevant if sym.is(Implicit) || @@ -813,7 +831,7 @@ class Completions( // accessors of case class members are more relevant if !sym.is(CaseAccessor) then relevance |= IsNotCaseAccessor // public symbols are more relevant - if !sym.isPublic then relevance |= IsNotCaseAccessor + if !sym.isPublic then relevance |= IsNotPublic // synthetic symbols are less relevant (e.g. `copy` on case classes) if sym.is(Synthetic) && !sym.isAllOf(EnumCase) then relevance |= IsSynthetic diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionExtensionSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionExtensionSuite.scala index b41084af4a8e..16535381165c 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionExtensionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionExtensionSuite.scala @@ -437,3 +437,44 @@ class CompletionExtensionSuite extends BaseCompletionSuite: |""".stripMargin, assertSingleItem = false ) + + @Test def `extension-for-case-class` = + check( + """|case class Bar(): + | def baz(): Unit = ??? + | + |object Bar: + | extension (f: Bar) + | def qux: Unit = ??? + | + |object Main: + | val _ = Bar().@@ + |""".stripMargin, + """|baz(): Unit + |copy(): Bar + |qux: Unit + |asInstanceOf[X0]: X0 + |canEqual(that: Any): Boolean + |equals(x$0: Any): Boolean + |getClass[X0 >: Bar](): Class[? <: X0] + |hashCode(): Int + |isInstanceOf[X0]: Boolean + |productArity: Int + |productElement(n: Int): Any + |productElementName(n: Int): String + |productElementNames: Iterator[String] + |productIterator: Iterator[Any] + |productPrefix: String + |synchronized[X0](x$0: X0): X0 + |toString(): String + |->[B](y: B): (Bar, B) + |ensuring(cond: Boolean): Bar + |ensuring(cond: Bar => Boolean): Bar + |ensuring(cond: Boolean, msg: => Any): Bar + |ensuring(cond: Bar => Boolean, msg: => Any): Bar + |nn: `?1`.type + |runtimeChecked: `?2`.type + |formatted(fmtstr: String): String + |→[B](y: B): (Bar, B) + | """.stripMargin + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala index 4dad18a78181..c02d217e45d9 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala @@ -109,18 +109,9 @@ class CompletionSuite extends BaseCompletionSuite: |tabulate[A](n: Int)(f: Int => A): List[A] |unapplySeq[A](x: List[A] @uncheckedVariance): UnapplySeqWrapper[A] |unfold[A, S](init: S)(f: S => Option[(A, S)]): List[A] - |->[B](y: B): (List.type, B) - |ensuring(cond: Boolean): List.type - |ensuring(cond: List.type => Boolean): List.type - |ensuring(cond: Boolean, msg: => Any): List.type - |ensuring(cond: List.type => Boolean, msg: => Any): List.type |fromSpecific(from: Any)(it: IterableOnce[Nothing]): List[Nothing] |fromSpecific(it: IterableOnce[Nothing]): List[Nothing] - |nn: List.type - |runtimeChecked scala.collection.immutable |toFactory(from: Any): Factory[Nothing, List[Nothing]] - |formatted(fmtstr: String): String - |→[B](y: B): (List.type, B) |iterableFactory[A]: Factory[A, List[A]] |asInstanceOf[X0]: X0 |equals(x$0: Any): Boolean @@ -129,6 +120,15 @@ class CompletionSuite extends BaseCompletionSuite: |isInstanceOf[X0]: Boolean |synchronized[X0](x$0: X0): X0 |toString(): String + |->[B](y: B): (List.type, B) + |ensuring(cond: Boolean): List.type + |ensuring(cond: List.type => Boolean): List.type + |ensuring(cond: Boolean, msg: => Any): List.type + |ensuring(cond: List.type => Boolean, msg: => Any): List.type + |nn: List.type + |runtimeChecked scala.collection.immutable + |formatted(fmtstr: String): String + |→[B](y: B): (List.type, B) |""".stripMargin ) From 20c8a3282ec29ad2944320e6da54abf8a0f158f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Per=C5=82akowski?= <17816164+Perl99@users.noreply.github.com> Date: Fri, 12 Sep 2025 15:22:41 +0200 Subject: [PATCH 2/2] Review fix --- .../src/main/dotty/tools/pc/completions/Completions.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala index 2f5a46d19a86..72b65b3cadb9 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala @@ -15,6 +15,7 @@ import dotty.tools.dotc.ast.untpd import dotty.tools.dotc.core.Comments.Comment import dotty.tools.dotc.core.Constants.Constant import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.core.Decorators.toTermName import dotty.tools.dotc.core.Denotations.SingleDenotation import dotty.tools.dotc.core.Flags import dotty.tools.dotc.core.Flags.* @@ -770,8 +771,8 @@ class Completions( private lazy val ArrowAssocClass: ClassSymbol = requiredClass("scala.Predef.ArrowAssoc") private lazy val EnsuringClass: ClassSymbol = requiredClass("scala.Predef.Ensuring") private lazy val StringFormatClass: ClassSymbol = requiredClass("scala.Predef.StringFormat") - private lazy val nnMethod: Symbol = defn.ScalaPredefModule.requiredMethod("nn") - private lazy val runtimeCheckedMethod: Symbol = defn.ScalaPredefModule.requiredMethod("runtimeChecked") + private lazy val nnMethod: Symbol = defn.ScalaPredefModule.info.member("nn".toTermName).symbol + private lazy val runtimeCheckedMethod: Symbol = defn.ScalaPredefModule.info.member("runtimeChecked".toTermName).symbol private def isNotLocalForwardReference(sym: Symbol)(using Context): Boolean = !sym.isLocalToBlock ||