From 46f84f5eac38117516b043a34502a53144afb96d Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Thu, 22 Feb 2024 19:37:25 +0100 Subject: [PATCH 1/5] bugfix: completions for implicit methods [skip ci] --- .../meta/internal/pc/CompletionProvider.scala | 59 ++++++++++++++++++- .../scala/meta/internal/pc/MetalsGlobal.scala | 7 +++ .../scala/meta/internal/pc/Signatures.scala | 2 +- .../internal/pc/completions/Completions.scala | 2 + .../tests/pc/CompletionWorkspaceSuite.scala | 58 ++++++++++++++++++ 5 files changed, 126 insertions(+), 2 deletions(-) diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/CompletionProvider.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/CompletionProvider.scala index b775b066535..091ea9d8059 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/CompletionProvider.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/CompletionProvider.scala @@ -99,6 +99,8 @@ class CompletionProvider( d.label case o: TextEditMember => o.label.getOrElse(labelWithSig) + case _: ExtensionMethod => + s"$labelWithSig (implicit)" case o: WorkspaceMember => s"$ident - ${o.sym.owner.fullName}" case _ => labelWithSig @@ -152,6 +154,31 @@ class CompletionProvider( item.setAdditionalTextEdits(i.autoImports.asJava) case d: DependecyMember => item.setTextEdit(d.edit) + case e: ExtensionMethod => + val impPos = importPosition.getOrElse(AutoImportPosition(0, 0, false)) + val suffix = + if ( + clientSupportsSnippets && e.sym.paramss.headOption.exists( + _.nonEmpty + ) + ) "($0)" + else "" + val (short, edits) = ShortenedNames.synthesize( + TypeRef( + ThisType(e.sym.owner), + e.sym, + Nil + ), + pos, + context, + impPos + ) + val edit: l.TextEdit = textEdit( + short + suffix, + e.editRange.getOrElse(editRange) + ) + item.setTextEdit(edit) + item.setAdditionalTextEdits(edits.asJava) case w: WorkspaceMember => def createTextEdit(identifier: String) = textEdit(w.wrap(identifier), w.editRange.getOrElse(editRange)) @@ -364,12 +391,42 @@ class CompletionProvider( if (kind == CompletionListKind.Scope) { workspaceSymbolListMembers(query, pos, visit) } else { - SymbolSearch.Result.COMPLETE + val selectType = latestParentTrees match { + case (i: Ident) :: _ => Option(i.tpe) + case (s: Select) :: _ => Option(s.tpe) + case (l: Literal) :: _ => Option(l.tpe) + case _ => None + } + selectType + .map(workspaceExtensionMethods(query, pos, visit, _)) + .getOrElse(SymbolSearch.Result.COMPLETE) } InterestingMembers(buf.result(), searchResults) } + private def workspaceExtensionMethods( + query: String, + pos: Position, + visit: Member => Boolean, + selectType: Type + ): SymbolSearch.Result = { + val context = doLocateContext(pos) + val visitor = new CompilerSearchVisitor( + context, + sym => + if (sym.safeOwner.isImplicit) { + val ownerConstructor = sym.owner.info.member(nme.CONSTRUCTOR) + ownerConstructor.info.paramss match { + case List(List(param)) if selectType <:< param.info => + visit(new ExtensionMethod(sym)) + case _ => false + } + } else false + ) + search.searchMethods(query, buildTargetIdentifier, visitor) + } + private def isFunction(symbol: Symbol): Boolean = { compiler.definitions.isFunctionSymbol( symbol.info.finalResultType.typeSymbol 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 b9e00e00906..d51cf574cf5 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 @@ -361,6 +361,13 @@ class MetalsGlobal( args.map(arg => loop(arg, None)) ) } + } else if (sym.isMethod && sym.safeOwner.isImplicit) { + history.tryShortenName(ShortName(sym.safeOwner)) + TypeRef( + NoPrefix, + shortSymbol, + args.map(arg => loop(arg, None)) + ) } else { TypeRef( loop(pre, Some(ShortName(sym))), diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/Signatures.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/Signatures.scala index eb2c1b49c5c..b589fa73d67 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/Signatures.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/Signatures.scala @@ -189,7 +189,7 @@ trait Signatures { compiler: MetalsGlobal => sym.isStaticMember || // Java static sym.owner.ownerChain.forall { s => // ensure the symbol can be referenced in a static manner, without any instance - s.isPackageClass || s.isPackageObjectClass || s.isModule + s.isPackageClass || s.isPackageObjectClass || s.isModule || s.isModuleClass } ) { history(name) = short 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..2d1436963cb 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 @@ -41,6 +41,8 @@ trait Completions { this: MetalsGlobal => def editRange: Option[l.Range] = None } + class ExtensionMethod(sym: Symbol) extends WorkspaceMember(sym) + class WorkspaceInterpolationMember( sym: Symbol, override val additionalTextEdits: List[l.TextEdit], diff --git a/tests/cross/src/test/scala/tests/pc/CompletionWorkspaceSuite.scala b/tests/cross/src/test/scala/tests/pc/CompletionWorkspaceSuite.scala index ca826152f28..be384acc81e 100644 --- a/tests/cross/src/test/scala/tests/pc/CompletionWorkspaceSuite.scala +++ b/tests/cross/src/test/scala/tests/pc/CompletionWorkspaceSuite.scala @@ -1082,4 +1082,62 @@ class CompletionWorkspaceSuite extends BaseCompletionSuite { |""".stripMargin, "" ) + + check( + "implicit-class", + """|package example + |object Test { + | implicit class TestOps(a: Int) { + | def testOps(b: Int) = ??? + | } + | implicit class TestOps2(a: String) { + | def testOps2(b: Int) = ??? + | } + | implicit class TestOps4[A](a: List[A]) { + | def testOps4(b: Int) = ??? + | } + | class TestOps3(a: String) { + | def testOps3(b: Int) = ??? + | } + |} + | + |object ActualTest { + | 1.tes@@ + |} + |""".stripMargin, + "testOps(b: Int): Nothing (implicit)", + filter = _.contains("testOps") + ) + + checkEdit( + "implicit-class-edit", + """|package example + | + |object Test { + | implicit class TestOps(a: Int) { + | def testOps(b: Int) = ??? + | } + |} + | + |object ActualTest { + | 1.tes@@ + |} + |""".stripMargin, + """|package example + | + |import example.Test.TestOps + | + |object Test { + | implicit class TestOps(a: Int) { + | def testOps(b: Int) = ??? + | } + |} + | + |object ActualTest { + | 1.testOps($0) + |} + |""".stripMargin, + filter = _.contains("testOps") + ) + } From d20053070aacbb1dc8f9fd7eb0b0bf39d05708fc Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Fri, 23 Feb 2024 16:30:33 +0100 Subject: [PATCH 2/5] deal with type constructors --- .../meta/internal/pc/CompletionProvider.scala | 21 +++-- .../scala/meta/internal/pc/MetalsGlobal.scala | 1 + .../pc/completions/FuzzyUpperBound.scala | 35 +++++++ .../scala/tests/pc/CompletionIssueSuite.scala | 48 ++-------- .../test/scala/tests/pc/CompletionSuite.scala | 9 +- .../tests/pc/CompletionWorkspaceSuite.scala | 93 ++++++++++++------- 6 files changed, 120 insertions(+), 87 deletions(-) create mode 100644 mtags/src/main/scala-2/scala/meta/internal/pc/completions/FuzzyUpperBound.scala diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/CompletionProvider.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/CompletionProvider.scala index 091ea9d8059..e7ad78b466e 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/CompletionProvider.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/CompletionProvider.scala @@ -387,19 +387,17 @@ class CompletionProvider( text, isAmmoniteScript ) + val searchResults = if (kind == CompletionListKind.Scope) { workspaceSymbolListMembers(query, pos, visit) } else { - val selectType = latestParentTrees match { - case (i: Ident) :: _ => Option(i.tpe) - case (s: Select) :: _ => Option(s.tpe) - case (l: Literal) :: _ => Option(l.tpe) - case _ => None + typedTreeAt(pos) match { + case Select(qualifier, _) + if qualifier.tpe != null && !qualifier.tpe.isError => + workspaceExtensionMethods(query, pos, visit, qualifier.tpe) + case _ => SymbolSearch.Result.COMPLETE } - selectType - .map(workspaceExtensionMethods(query, pos, visit, _)) - .getOrElse(SymbolSearch.Result.COMPLETE) } InterestingMembers(buf.result(), searchResults) @@ -417,8 +415,13 @@ class CompletionProvider( sym => if (sym.safeOwner.isImplicit) { val ownerConstructor = sym.owner.info.member(nme.CONSTRUCTOR) + def typeParams = sym.owner.info.typeParams ownerConstructor.info.paramss match { - case List(List(param)) if selectType <:< param.info => + case List(List(param)) + if selectType fuzzy_<:< TypeWithParams( + param.info, + typeParams + ) => visit(new ExtensionMethod(sym)) case _ => false } 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 d51cf574cf5..9b3a2a9820a 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 @@ -55,6 +55,7 @@ class MetalsGlobal( with completions.MillIvyCompletions with completions.SbtLibCompletions with completions.MultilineCommentCompletions + with completions.FuzzyUpperBound with Signatures with Compat with GlobalProxy diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/completions/FuzzyUpperBound.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/completions/FuzzyUpperBound.scala new file mode 100644 index 00000000000..43b3a464113 --- /dev/null +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/completions/FuzzyUpperBound.scala @@ -0,0 +1,35 @@ +package scala.meta.internal.pc.completions + +import scala.meta.internal.pc.MetalsGlobal + +trait FuzzyUpperBound { this: MetalsGlobal => + implicit class XtensionType(tpe: Type) { + def fuzzy_<:<(tpeWithParams: TypeWithParams) = { + def adjustedType = + performSubstitutions(tpeWithParams.tpe, tpeWithParams.typeParams) + + tpe <:< tpeWithParams.tpe || tpe <:< adjustedType + } + + private def performSubstitutions( + tpe: Type, + typeParams: List[Symbol] + ): Type = { + typeParams.find(_ == tpe.typeSymbol) match { + case Some(tpeDef) => + tpeDef.info match { + case bounds: TypeBounds => BoundedWildcardType(bounds) + case tpe => tpe + } + case None => + tpe match { + case TypeRef(pre, sym, args) => + TypeRef(pre, sym, args.map(performSubstitutions(_, typeParams))) + case t => t + } + } + } + } + + case class TypeWithParams(tpe: Type, typeParams: List[Symbol]) +} diff --git a/tests/cross/src/test/scala/tests/pc/CompletionIssueSuite.scala b/tests/cross/src/test/scala/tests/pc/CompletionIssueSuite.scala index 2564adcb130..821ea80a687 100644 --- a/tests/cross/src/test/scala/tests/pc/CompletionIssueSuite.scala +++ b/tests/cross/src/test/scala/tests/pc/CompletionIssueSuite.scala @@ -95,29 +95,17 @@ class CompletionIssueSuite extends BaseCompletionSuite { | NestedLea@@ |}""".stripMargin, """|package a + | + |import a.A.Nested.NestedLeaf |object A { | object Nested{ | object NestedLeaf | } |} |object B { - | A.Nested.NestedLeaf + | NestedLeaf |} - |""".stripMargin, - compat = Map( - "3" -> """|package a - | - |import a.A.Nested.NestedLeaf - |object A { - | object Nested{ - | object NestedLeaf - | } - |} - |object B { - | NestedLeaf - |} - |""".stripMargin - ) + |""".stripMargin ) checkEdit( @@ -146,6 +134,7 @@ class CompletionIssueSuite extends BaseCompletionSuite { | Sweden, | USA |} + |import all.World.Countries.Norway | |object World { | object Countries{ @@ -157,32 +146,9 @@ class CompletionIssueSuite extends BaseCompletionSuite { |} |import all.World.Countries.France |object B { - | val allCountries = Sweden + France + USA + World.Countries.Norway + | val allCountries = Sweden + France + USA + Norway |} - |""".stripMargin, - compat = Map( - "3" -> - """|package all - |import all.World.Countries.{ - | Sweden, - | USA - |} - |import all.World.Countries.Norway - | - |object World { - | object Countries{ - | object Sweden - | object Norway - | object France - | object USA - | } - |} - |import all.World.Countries.France - |object B { - | val allCountries = Sweden + France + USA + Norway - |} - |""".stripMargin - ) + |""".stripMargin ) check( diff --git a/tests/cross/src/test/scala/tests/pc/CompletionSuite.scala b/tests/cross/src/test/scala/tests/pc/CompletionSuite.scala index 8d81dfb63de..b51d0fcf4ff 100644 --- a/tests/cross/src/test/scala/tests/pc/CompletionSuite.scala +++ b/tests/cross/src/test/scala/tests/pc/CompletionSuite.scala @@ -380,11 +380,8 @@ class CompletionSuite extends BaseCompletionSuite { | } | Xtension@@ |}""".stripMargin, - """|XtensionMethod(a: Int): A.XtensionMethod - |""".stripMargin, - compat = Map( - "3" -> "XtensionMethod(a: Int): XtensionMethod" - ) + """|XtensionMethod(a: Int): XtensionMethod + |""".stripMargin ) check( @@ -1793,7 +1790,7 @@ class CompletionSuite extends BaseCompletionSuite { | val t: TT@@ |} |""".stripMargin, - "TTT[A] = O.TTT", + "TTT[A] = TTT", compat = Map( "3" -> "TTT[A <: Int] = List[A]" ) diff --git a/tests/cross/src/test/scala/tests/pc/CompletionWorkspaceSuite.scala b/tests/cross/src/test/scala/tests/pc/CompletionWorkspaceSuite.scala index be384acc81e..5083ee78508 100644 --- a/tests/cross/src/test/scala/tests/pc/CompletionWorkspaceSuite.scala +++ b/tests/cross/src/test/scala/tests/pc/CompletionWorkspaceSuite.scala @@ -652,20 +652,12 @@ class CompletionWorkspaceSuite extends BaseCompletionSuite { | Implicits@@ |} |""".stripMargin, - """|import scala.concurrent.ExecutionContext + """|import scala.concurrent.ExecutionContext.Implicits |object Main { - | ExecutionContext.Implicits + | Implicits |} |""".stripMargin, - filter = _ == "Implicits - scala.concurrent.ExecutionContext", - compat = Map { - "3" -> - """|import scala.concurrent.ExecutionContext.Implicits - |object Main { - | Implicits - |} - |""".stripMargin - } + filter = _ == "Implicits - scala.concurrent.ExecutionContext" ) // this test was intended to check that import is rendered correctly - without `$` symbol @@ -844,29 +836,12 @@ class CompletionWorkspaceSuite extends BaseCompletionSuite { | |package b { | - | import a.A + | import a.A.Beta | object B{ - | val x: A.Beta + | val x: Beta | } |} - |""".stripMargin, - compat = Map( - "3" -> - """|import a.A.Beta - |package a { - | object A { - | type Beta = String - | def m(): Int = ??? - | } - |} - | - |package b { - | object B{ - | val x: Beta - | } - |} - |""".stripMargin - ) + |""".stripMargin ) checkEdit( @@ -1140,4 +1115,60 @@ class CompletionWorkspaceSuite extends BaseCompletionSuite { filter = _.contains("testOps") ) + check( + "implicit-class-edit-bound2", + """|package example + | + |object Test { + | implicit class TestOps(a: List[Int]) { + | def testOps(b: Int) = ??? + | } + |} + | + |object ActualTest { + | List(1).tes@@ + |} + |""".stripMargin, + "testOps(b: Int): Nothing (implicit)", + filter = _.contains("testOps") + ) + + check( + "implicit-class-edit-bound3".tag(IgnoreScala3), + """|package example + | + |object Test { + | implicit class TestOps[T](a: List[T]) { + | def testOps(b: Int) = ??? + | } + |} + | + |object ActualTest { + | List(1).tes@@ + |} + |""".stripMargin, + "testOps(b: Int): Nothing (implicit)", + filter = _.contains("testOps") + ) + + check( + "implicit-class-edit-bound4".tag(IgnoreScala3), + """|package example + | + |case class A[-T](t: T) + | + |object Test { + | implicit class TestOps[T](a: A[T]) { + | def testOps(b: Int) = ??? + | } + |} + | + |object ActualTest { + | A(1).tes@@ + |} + |""".stripMargin, + "testOps(b: Int): Nothing (implicit)", + filter = _.contains("testOps") + ) + } From 07c9a4f225f1a99a493484672cfacf3096bd9f92 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Tue, 27 Feb 2024 14:27:56 +0100 Subject: [PATCH 3/5] test fixes --- .../scala/tests/BaseCompletionSuite.scala | 1 + .../tests/pc/CompletionWorkspaceSuite.scala | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/cross/src/main/scala/tests/BaseCompletionSuite.scala b/tests/cross/src/main/scala/tests/BaseCompletionSuite.scala index ea206c63371..6f8fecd0bc8 100644 --- a/tests/cross/src/main/scala/tests/BaseCompletionSuite.scala +++ b/tests/cross/src/main/scala/tests/BaseCompletionSuite.scala @@ -22,6 +22,7 @@ abstract class BaseCompletionSuite extends BasePCSuite { private def resolvedCompletions( params: CompilerOffsetParams ): CompletionList = { + presentationCompiler.restart() val result = presentationCompiler.complete(params).get() val newItems = result.getItems.asScala.map { item => item.data diff --git a/tests/cross/src/test/scala/tests/pc/CompletionWorkspaceSuite.scala b/tests/cross/src/test/scala/tests/pc/CompletionWorkspaceSuite.scala index 5083ee78508..7f2eaf6e4c3 100644 --- a/tests/cross/src/test/scala/tests/pc/CompletionWorkspaceSuite.scala +++ b/tests/cross/src/test/scala/tests/pc/CompletionWorkspaceSuite.scala @@ -841,7 +841,24 @@ class CompletionWorkspaceSuite extends BaseCompletionSuite { | val x: Beta | } |} - |""".stripMargin + |""".stripMargin, + compat = Map( + "3" -> + """|import a.A.Beta + |package a { + | object A { + | type Beta = String + | def m(): Int = ??? + | } + |} + | + |package b { + | object B{ + | val x: Beta + | } + |} + |""".stripMargin + ) ) checkEdit( From f1835c4f3b156aa8f1a7a99c1aedbf4c8a395ce3 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Wed, 28 Feb 2024 15:00:28 +0100 Subject: [PATCH 4/5] cleanup + unignore tests --- .../meta/internal/pc/CompletionProvider.scala | 14 +- .../internal/pc/completions/Completions.scala | 3 +- .../pc/CompletionExtensionMethodSuite.scala | 321 +++++++++++++----- .../tests/pc/CompletionSnippetSuite.scala | 2 +- .../tests/pc/CompletionWorkspaceSuite.scala | 87 ----- 5 files changed, 250 insertions(+), 177 deletions(-) diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/CompletionProvider.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/CompletionProvider.scala index e7ad78b466e..e82d14fd806 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/CompletionProvider.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/CompletionProvider.scala @@ -99,7 +99,7 @@ class CompletionProvider( d.label case o: TextEditMember => o.label.getOrElse(labelWithSig) - case _: ExtensionMethod => + case _: WorkspaceImplicitMember => s"$labelWithSig (implicit)" case o: WorkspaceMember => s"$ident - ${o.sym.owner.fullName}" @@ -154,19 +154,19 @@ class CompletionProvider( item.setAdditionalTextEdits(i.autoImports.asJava) case d: DependecyMember => item.setTextEdit(d.edit) - case e: ExtensionMethod => + case m: WorkspaceImplicitMember => val impPos = importPosition.getOrElse(AutoImportPosition(0, 0, false)) val suffix = if ( - clientSupportsSnippets && e.sym.paramss.headOption.exists( + clientSupportsSnippets && m.sym.paramss.headOption.exists( _.nonEmpty ) ) "($0)" else "" val (short, edits) = ShortenedNames.synthesize( TypeRef( - ThisType(e.sym.owner), - e.sym, + ThisType(m.sym.owner), + m.sym, Nil ), pos, @@ -175,7 +175,7 @@ class CompletionProvider( ) val edit: l.TextEdit = textEdit( short + suffix, - e.editRange.getOrElse(editRange) + editRange ) item.setTextEdit(edit) item.setAdditionalTextEdits(edits.asJava) @@ -422,7 +422,7 @@ class CompletionProvider( param.info, typeParams ) => - visit(new ExtensionMethod(sym)) + visit(new WorkspaceImplicitMember(sym)) case _ => false } } else false 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 2d1436963cb..d67dd77080b 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 @@ -41,7 +41,8 @@ trait Completions { this: MetalsGlobal => def editRange: Option[l.Range] = None } - class ExtensionMethod(sym: Symbol) extends WorkspaceMember(sym) + class WorkspaceImplicitMember(sym: Symbol) + extends ScopeMember(sym, sym.tpe, true, EmptyTree) class WorkspaceInterpolationMember( sym: Symbol, diff --git a/tests/cross/src/test/scala/tests/pc/CompletionExtensionMethodSuite.scala b/tests/cross/src/test/scala/tests/pc/CompletionExtensionMethodSuite.scala index 269053fc3eb..6a72cb5d978 100644 --- a/tests/cross/src/test/scala/tests/pc/CompletionExtensionMethodSuite.scala +++ b/tests/cross/src/test/scala/tests/pc/CompletionExtensionMethodSuite.scala @@ -4,11 +4,8 @@ import tests.BaseCompletionSuite class CompletionExtensionMethodSuite extends BaseCompletionSuite { - override def ignoreScalaVersion: Option[IgnoreScalaVersion] = - Some(IgnoreScala2) - check( - "simple", + "simple".tag(IgnoreScala2), """|package example | |object enrichments: @@ -25,18 +22,23 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { "simple-old-syntax", """|package example | - |object Test: - | implicit class TestOps(a: Int): + |object Test { + | implicit class TestOps(a: Int) { | def testOps(b: Int): String = ??? + | } + |} | - |def main = 100.test@@ + |object O{ + | def main = 100.test@@ + |} |""".stripMargin, """|testOps(b: Int): String (implicit) - |""".stripMargin + |""".stripMargin, + filter = _.contains("(implicit)") ) check( - "simple2", + "simple2".tag(IgnoreScala2), """|package example | |object enrichments: @@ -54,11 +56,15 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { "simple2-old-syntax", """|package example | - |object enrichments: - | implicit class TestOps(a: Int): + |object enrichments { + | implicit class TestOps(a: Int) { | def testOps(b: Int): String = ??? + | } + |} | - |def main = 100.t@@ + |object O { + | def main = 100.t@@ + |} |""".stripMargin, """|testOps(b: Int): String (implicit) |""".stripMargin, @@ -66,7 +72,7 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { ) check( - "simple-empty", + "simple-empty".tag(IgnoreScala2), """|package example | |object enrichments: @@ -84,11 +90,15 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { "simple-empty-old", """|package example | - |object enrichments: - | implicit class TestOps(a: Int): + |object enrichments { + | implicit class TestOps(a: Int) { | def testOps(b: Int): String = ??? + | } + |} | - |def main = 100.@@ + |object O { + | def main = 100.@@ + |} |""".stripMargin, """|testOps(b: Int): String (implicit) |""".stripMargin, @@ -96,7 +106,7 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { ) check( - "filter-by-type", + "filter-by-type".tag(IgnoreScala2), """|package example | |object enrichments: @@ -116,21 +126,26 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { "filter-by-type-old", """|package example | - |object enrichments: - | implicit class A(num: Int): + |object enrichments { + | implicit class A(num: Int) { | def identity2: Int = num + 1 - | implicit class B(str: String): + | } + | implicit class B(str: String) { | def identity: String = str + | } + |} | - |def main = "foo".iden@@ + |object O { + | def main = "foo".iden@@ + |} |""".stripMargin, """|identity: String (implicit) - |""".stripMargin // identity2 won't be available - + |""".stripMargin, // identity2 won't be available + filter = _.contains("(implicit)") ) check( - "filter-by-type-subtype", + "filter-by-type-subtype".tag(IgnoreScala2), """|package example | |class A @@ -154,11 +169,15 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { |class A |class B extends A | - |object enrichments: - | implicit class Test(a: A): + |object enrichments { + | implicit class Test(a: A) { | def doSomething: A = a + | } + |} | - |def main = (new B).do@@ + |object O { + | def main = (new B).do@@ + |} |""".stripMargin, """|doSomething: A (implicit) |""".stripMargin, @@ -166,7 +185,7 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { ) checkEdit( - "simple-edit", + "simple-edit".tag(IgnoreScala2), """|package example | |object enrichments: @@ -191,26 +210,35 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { "simple-edit-old", """|package example | - |object enrichments: - | implicit class A (num: Int): + |object enrichments { + | implicit class A (num: Int) { | def incr: Int = num + 1 + | } + |} | - |def main = 100.inc@@ + |object O { + | def main = 100.inc@@ + |} |""".stripMargin, """|package example | |import example.enrichments.A | - |object enrichments: - | implicit class A (num: Int): + |object enrichments { + | implicit class A (num: Int) { | def incr: Int = num + 1 + | } + |} | - |def main = 100.incr - |""".stripMargin + |object O { + | def main = 100.incr + |} + |""".stripMargin, + filter = _.contains("(implicit)") ) checkEdit( - "simple-edit-suffix", + "simple-edit-suffix".tag(IgnoreScala2), """|package example | |object enrichments: @@ -232,7 +260,7 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { ) checkEdit( - "name-conflict", + "name-conflict".tag(IgnoreScala2), """|package example | |import example.enrichments.* @@ -265,28 +293,38 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { "simple-edit-suffix-old", """|package example | - |object enrichments: - | implicit class A (val num: Int): + |object enrichments { + | implicit class A (val num: Int) { | def plus(other: Int): Int = num + other + | } + |} | - |def main = 100.pl@@ + |object O { + | def main = 100.pl@@ + |} |""".stripMargin, """|package example | |import example.enrichments.A | - |object enrichments: - | implicit class A (val num: Int): + |object enrichments { + | implicit class A (val num: Int) { | def plus(other: Int): Int = num + other + | } + |} | - |def main = 100.plus($0) + |object O { + | def main = 100.plus($0) + |} |""".stripMargin ) // NOTE: In 3.1.3, package object name includes the whole path to file // eg. in 3.2.2 we get `A$package`, but in 3.1.3 `/some/path/to/file/A$package` check( - "directly-in-pkg1".tag(IgnoreScalaVersion.forLessThan("3.2.2")), + "directly-in-pkg1".tag( + IgnoreScalaVersion.forLessThan("3.2.2") + ), """| |package example: | extension (num: Int) @@ -305,19 +343,24 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { IgnoreScalaVersion.forLessThan("3.2.2") ), """| - |package examples: - | implicit class A(num: Int): + |package examples { + | implicit class A(num: Int) { | def incr: Int = num + 1 + | } + |} | - |package examples2: + |package examples2 { | def main = 100.inc@@ + |} |""".stripMargin, """|incr: Int (implicit) |""".stripMargin ) check( - "directly-in-pkg2".tag(IgnoreScalaVersion.forLessThan("3.2.2")), + "directly-in-pkg2".tag( + IgnoreScalaVersion.forLessThan("3.2.2") + ), """|package example: | object X: | def fooBar(num: Int) = num + 1 @@ -333,22 +376,30 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { check( "directly-in-pkg2-old" .tag( - IgnoreScalaVersion.forLessThan("3.2.2") + IgnoreScalaVersion.for3LessThan("3.2.2") ), - """|package examples: - | object X: + """|package examples { + | object X { | def fooBar(num: Int) = num + 1 + | } | implicit class A (num: Int) { def incr: Int = num + 1 } + |} | - |package examples2: - | def main = 100.inc@@ + |package examples2 { + | object O { + | def main = 100.inc@@ + | } + |} |""".stripMargin, """|incr: Int (implicit) - |""".stripMargin + |""".stripMargin, + filter = _.contains("(implicit)") ) checkEdit( - "directly-in-pkg3".tag(IgnoreScalaVersion.forLessThan("3.2.2")), + "directly-in-pkg3".tag( + IgnoreScalaVersion.forLessThan("3.2.2") + ), """|package example: | extension (num: Int) def incr: Int = num + 1 | @@ -367,21 +418,44 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { checkEdit( "directly-in-pkg3-old" .tag( - IgnoreScalaVersion.forLessThan("3.2.2") + IgnoreScalaVersion.for3LessThan("3.2.2") ), - """|package examples: + """|package examples { | implicit class A (num: Int) { def incr: Int = num + 1 } + |} | - |package examples2: - | def main = 100.inc@@ + |package examples2 { + | object O { + | def main = 100.inc@@ + | } + |} |""".stripMargin, """|import examples.A - |package examples: + |package examples { | implicit class A (num: Int) { def incr: Int = num + 1 } + |} | - |package examples2: - | def main = 100.incr - |""".stripMargin + |package examples2 { + | object O { + | def main = 100.incr + | } + |} + |""".stripMargin, + compat = Map( + "2" -> """|package examples { + | implicit class A (num: Int) { def incr: Int = num + 1 } + |} + | + |package examples2 { + | + | import examples.A + | object O { + | def main = 100.incr + | } + |} + |""".stripMargin + ), + filter = _.contains("(implicit)") ) check( @@ -405,18 +479,25 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { check( "nested-pkg-old" .tag( - IgnoreScalaVersion.forLessThan("3.2.2") + IgnoreScalaVersion.for3LessThan("3.2.2") ), - """|package aa: // some comment - | package cc: - | implicit class A (num: Int): + """|package aa { // some comment + | package cc { + | implicit class A (num: Int){ | def increment2 = num + 2 - | implicit class A (num: Int): + | } + | } + | implicit class A (num: Int) { | def increment = num + 1 + | } + |} | | - |package bb: - | def main: Unit = 123.incre@@ + |package bb { + | object O { + | def main: Unit = 123.incre@@ + | } + |} |""".stripMargin, """|increment: Int (implicit) |increment2: Int (implicit) @@ -427,41 +508,119 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { "implicit-val-var".tag(IgnoreForScala3CompilerPC), """|package example | - |object Test: - | implicit class TestOps(val testArg: Int): + |object Test { + | implicit class TestOps(val testArg: Int) { | var testVar: Int = 42 | val testVal: Int = 42 | def testOps(b: Int): String = ??? + | } + |} | - |def main = 100.test@@ + |object O { + | def main = 100.test@@ + |} |""".stripMargin, """|testArg: Int (implicit) |testVal: Int (implicit) |testVar: Int (implicit) |testOps(b: Int): String (implicit) - |""".stripMargin + |""".stripMargin, + compat = Map( + "2" -> + """|testArg: Int (implicit) + |testOps(b: Int): String (implicit) + |testVal: Int (implicit) + |testVar: Int (implicit) + |""".stripMargin + ), + filter = _.contains("(implicit)") ) checkEdit( "implicit-val-edit".tag(IgnoreForScala3CompilerPC), """|package example | - |object Test: - | implicit class TestOps(a: Int): + |object Test { + | implicit class TestOps(a: Int) { | val testVal: Int = 42 + | } + |} | - |def main = 100.test@@ + |object O { + | def main = 100.test@@ + |} |""".stripMargin, """|package example | |import example.Test.TestOps | - |object Test: - | implicit class TestOps(a: Int): + |object Test { + | implicit class TestOps(a: Int) { | val testVal: Int = 42 + | } + |} | - |def main = 100.testVal - |""".stripMargin + |object O { + | def main = 100.testVal + |} + |""".stripMargin, + filter = _.contains("(implicit)") + ) + + check( + "complex-type-old", + """|package example + | + |object Test { + | implicit class TestOps(a: List[Int]) { + | def testOps(b: Int) = ??? + | } + |} + | + |object ActualTest { + | List(1).tes@@ + |} + |""".stripMargin, + "testOps(b: Int): Nothing (implicit)", + filter = _.contains("testOps") + ) + + check( + "complex-type-old2".tag(IgnoreScala3), + """|package example + | + |object Test { + | implicit class TestOps[T](a: List[T]) { + | def testOps(b: Int) = ??? + | } + |} + | + |object ActualTest { + | List(1).tes@@ + |} + |""".stripMargin, + "testOps(b: Int): Nothing (implicit)", + filter = _.contains("testOps") + ) + + check( + "complex-type-old3".tag(IgnoreScala3), + """|package example + | + |case class A[-T](t: T) + | + |object Test { + | implicit class TestOps[T](a: A[T]) { + | def testOps(b: Int) = ??? + | } + |} + | + |object ActualTest { + | A(1).tes@@ + |} + |""".stripMargin, + "testOps(b: Int): Nothing (implicit)", + filter = _.contains("testOps") ) } diff --git a/tests/cross/src/test/scala/tests/pc/CompletionSnippetSuite.scala b/tests/cross/src/test/scala/tests/pc/CompletionSnippetSuite.scala index 2d3c0ce0b17..87ee72600fa 100644 --- a/tests/cross/src/test/scala/tests/pc/CompletionSnippetSuite.scala +++ b/tests/cross/src/test/scala/tests/pc/CompletionSnippetSuite.scala @@ -219,7 +219,7 @@ class CompletionSnippetSuite extends BaseCompletionSuite { "2.13" -> """|Iterable |Iterable[$0] {} - |IterableOnce[$0] {} + |IterableOnce[$0] |""".stripMargin, "3" -> """|Iterable[$0] {} diff --git a/tests/cross/src/test/scala/tests/pc/CompletionWorkspaceSuite.scala b/tests/cross/src/test/scala/tests/pc/CompletionWorkspaceSuite.scala index 7f2eaf6e4c3..e20e791fded 100644 --- a/tests/cross/src/test/scala/tests/pc/CompletionWorkspaceSuite.scala +++ b/tests/cross/src/test/scala/tests/pc/CompletionWorkspaceSuite.scala @@ -1101,91 +1101,4 @@ class CompletionWorkspaceSuite extends BaseCompletionSuite { filter = _.contains("testOps") ) - checkEdit( - "implicit-class-edit", - """|package example - | - |object Test { - | implicit class TestOps(a: Int) { - | def testOps(b: Int) = ??? - | } - |} - | - |object ActualTest { - | 1.tes@@ - |} - |""".stripMargin, - """|package example - | - |import example.Test.TestOps - | - |object Test { - | implicit class TestOps(a: Int) { - | def testOps(b: Int) = ??? - | } - |} - | - |object ActualTest { - | 1.testOps($0) - |} - |""".stripMargin, - filter = _.contains("testOps") - ) - - check( - "implicit-class-edit-bound2", - """|package example - | - |object Test { - | implicit class TestOps(a: List[Int]) { - | def testOps(b: Int) = ??? - | } - |} - | - |object ActualTest { - | List(1).tes@@ - |} - |""".stripMargin, - "testOps(b: Int): Nothing (implicit)", - filter = _.contains("testOps") - ) - - check( - "implicit-class-edit-bound3".tag(IgnoreScala3), - """|package example - | - |object Test { - | implicit class TestOps[T](a: List[T]) { - | def testOps(b: Int) = ??? - | } - |} - | - |object ActualTest { - | List(1).tes@@ - |} - |""".stripMargin, - "testOps(b: Int): Nothing (implicit)", - filter = _.contains("testOps") - ) - - check( - "implicit-class-edit-bound4".tag(IgnoreScala3), - """|package example - | - |case class A[-T](t: T) - | - |object Test { - | implicit class TestOps[T](a: A[T]) { - | def testOps(b: Int) = ??? - | } - |} - | - |object ActualTest { - | A(1).tes@@ - |} - |""".stripMargin, - "testOps(b: Int): Nothing (implicit)", - filter = _.contains("testOps") - ) - } From d42252e63de8e8fdb5b93f91ee8a2eff3a47cc49 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Thu, 29 Feb 2024 13:10:28 +0100 Subject: [PATCH 5/5] review changes --- .../meta/internal/pc/CompletionProvider.scala | 5 +-- .../scala/meta/internal/pc/MetalsGlobal.scala | 32 ++++++++++++++++- .../pc/completions/FuzzyUpperBound.scala | 35 ------------------- .../pc/CompletionExtensionMethodSuite.scala | 6 ++-- 4 files changed, 35 insertions(+), 43 deletions(-) delete mode 100644 mtags/src/main/scala-2/scala/meta/internal/pc/completions/FuzzyUpperBound.scala diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/CompletionProvider.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/CompletionProvider.scala index e82d14fd806..120d237e50a 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/CompletionProvider.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/CompletionProvider.scala @@ -418,10 +418,7 @@ class CompletionProvider( def typeParams = sym.owner.info.typeParams ownerConstructor.info.paramss match { case List(List(param)) - if selectType fuzzy_<:< TypeWithParams( - param.info, - typeParams - ) => + if selectType <:< boundedWildcardType(param.info, typeParams) => visit(new WorkspaceImplicitMember(sym)) case _ => false } 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 9b3a2a9820a..bbc23321fe8 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 @@ -55,7 +55,6 @@ class MetalsGlobal( with completions.MillIvyCompletions with completions.SbtLibCompletions with completions.MultilineCommentCompletions - with completions.FuzzyUpperBound with Signatures with Compat with GlobalProxy @@ -1080,4 +1079,35 @@ class MetalsGlobal( } } + /** + * Creates a bounded wildcard type for a type of parameter + * using information about type parameters. + * + * E.g. for class A[T](x: List[T]) + * List[Int] <:< List[T] is false, + * this method for List[T] will return List[_ >: Nothing <: Any], + * and List[Int] <:< List[_ >: Nothing <: Any] is true. + */ + def boundedWildcardType( + tpe: Type, + typeParams: List[Symbol] + ): Type = { + if (typeParams.isEmpty) tpe + else { + typeParams.find(_ == tpe.typeSymbol) match { + case Some(tpeDef) => + tpeDef.info match { + case bounds: TypeBounds => BoundedWildcardType(bounds) + case tpe => tpe + } + case None => + tpe match { + case TypeRef(pre, sym, args) => + TypeRef(pre, sym, args.map(boundedWildcardType(_, typeParams))) + case t => t + } + } + } + } + } diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/completions/FuzzyUpperBound.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/completions/FuzzyUpperBound.scala deleted file mode 100644 index 43b3a464113..00000000000 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/completions/FuzzyUpperBound.scala +++ /dev/null @@ -1,35 +0,0 @@ -package scala.meta.internal.pc.completions - -import scala.meta.internal.pc.MetalsGlobal - -trait FuzzyUpperBound { this: MetalsGlobal => - implicit class XtensionType(tpe: Type) { - def fuzzy_<:<(tpeWithParams: TypeWithParams) = { - def adjustedType = - performSubstitutions(tpeWithParams.tpe, tpeWithParams.typeParams) - - tpe <:< tpeWithParams.tpe || tpe <:< adjustedType - } - - private def performSubstitutions( - tpe: Type, - typeParams: List[Symbol] - ): Type = { - typeParams.find(_ == tpe.typeSymbol) match { - case Some(tpeDef) => - tpeDef.info match { - case bounds: TypeBounds => BoundedWildcardType(bounds) - case tpe => tpe - } - case None => - tpe match { - case TypeRef(pre, sym, args) => - TypeRef(pre, sym, args.map(performSubstitutions(_, typeParams))) - case t => t - } - } - } - } - - case class TypeWithParams(tpe: Type, typeParams: List[Symbol]) -} diff --git a/tests/cross/src/test/scala/tests/pc/CompletionExtensionMethodSuite.scala b/tests/cross/src/test/scala/tests/pc/CompletionExtensionMethodSuite.scala index 6a72cb5d978..14f8a21184f 100644 --- a/tests/cross/src/test/scala/tests/pc/CompletionExtensionMethodSuite.scala +++ b/tests/cross/src/test/scala/tests/pc/CompletionExtensionMethodSuite.scala @@ -582,7 +582,7 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { |} |""".stripMargin, "testOps(b: Int): Nothing (implicit)", - filter = _.contains("testOps") + filter = _.contains("(implicit)") ) check( @@ -600,7 +600,7 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { |} |""".stripMargin, "testOps(b: Int): Nothing (implicit)", - filter = _.contains("testOps") + filter = _.contains("(implicit)") ) check( @@ -620,7 +620,7 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { |} |""".stripMargin, "testOps(b: Int): Nothing (implicit)", - filter = _.contains("testOps") + filter = _.contains("(implicit)") ) }