From 24b2d19045e4054b7b554d62409ef42103ff6fb6 Mon Sep 17 00:00:00 2001 From: tgodzik Date: Fri, 1 Dec 2023 11:14:21 +0100 Subject: [PATCH 1/3] improvement: Support completions for implicit classes Previously, we would only automatically suggest extension methods, not implicit classes. Now, we also properly suggest implicit classes. We could follow up with support for Scala 2 also. --- .../internal/pc/CompilerSearchVisitor.scala | 8 +- .../meta/internal/pc/SemanticdbSymbols.scala | 15 ++ .../pc/completions/CompletionProvider.scala | 5 +- .../pc/completions/CompletionValue.scala | 14 ++ .../internal/pc/completions/Completions.scala | 25 ++- .../internal/mtags/ScalaToplevelMtags.scala | 140 ++++++++++--- .../pc/CompletionExtensionMethodSuite.scala | 186 ++++++++++++++++++ .../mtest/src/main/scala/tests/PCSuite.scala | 2 +- .../feature/CompletionCrossLspSuite.scala | 48 +++++ .../test/scala/tests/ScalaToplevelSuite.scala | 12 +- 10 files changed, 414 insertions(+), 41 deletions(-) diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/CompilerSearchVisitor.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/CompilerSearchVisitor.scala index aef1b57b89e..6cdc980d473 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/CompilerSearchVisitor.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/CompilerSearchVisitor.scala @@ -10,6 +10,7 @@ import scala.meta.internal.metals.ReportContext import scala.meta.pc.* import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.core.Flags import dotty.tools.dotc.core.Names.* import dotty.tools.dotc.core.Symbols.* @@ -21,7 +22,12 @@ class CompilerSearchVisitor( val logger: Logger = Logger.getLogger(classOf[CompilerSearchVisitor].getName) private def isAccessible(sym: Symbol): Boolean = try - sym != NoSymbol && sym.isPublic && sym.isStatic + sym != NoSymbol && sym.isPublic && sym.isStatic || { + val owner = sym.maybeOwner + owner != NoSymbol && owner.isClass && + owner.is(Flags.Implicit) && + owner.isStatic && owner.isPublic + } catch case err: AssertionError => logger.log(Level.WARNING, err.getMessage()) diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/SemanticdbSymbols.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/SemanticdbSymbols.scala index c8749645d91..92429cbdd73 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/SemanticdbSymbols.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/SemanticdbSymbols.scala @@ -48,7 +48,20 @@ object SemanticdbSymbols: // however in scalac this method is defined only in `module Files` if typeSym.is(JavaDefined) then typeSym :: owner.info.decl(termName(value)).symbol :: Nil + /** + * Looks like decl doesn't work for: + * package a: + * implicit class A (i: Int): + * def inc = i + 1 + */ + else if typeSym == NoSymbol then + val searched = typeName(value) + owner.info.allMembers + .find(_.name == searched) + .map(_.symbol) + .toList else typeSym :: Nil + end if case Descriptor.Term(value) => val outSymbol = owner.info.decl(termName(value)).symbol if outSymbol.exists @@ -91,6 +104,8 @@ object SemanticdbSymbols: .map(_.symbol) .filter(sym => symbolName(sym) == s) .toList + end match + end tryMember parentSymbol.flatMap(tryMember) try diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/completions/CompletionProvider.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/completions/CompletionProvider.scala index ea5c2c2ec1a..67da964a58c 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/completions/CompletionProvider.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/completions/CompletionProvider.scala @@ -224,7 +224,7 @@ class CompletionProvider( def mkItemWithImports( v: CompletionValue.Workspace | CompletionValue.Extension | - CompletionValue.Interpolator + CompletionValue.Interpolator | CompletionValue.ImplicitClass ) = val sym = v.symbol path match @@ -277,7 +277,8 @@ class CompletionProvider( end mkItemWithImports completion match - case v: (CompletionValue.Workspace | CompletionValue.Extension) => + case v: (CompletionValue.Workspace | CompletionValue.Extension | + CompletionValue.ImplicitClass) => mkItemWithImports(v) case v: CompletionValue.Interpolator if v.isWorkspace || v.isExtension => mkItemWithImports(v) diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/completions/CompletionValue.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/completions/CompletionValue.scala index 8f02ebd3bd0..03173154ef9 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/completions/CompletionValue.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/completions/CompletionValue.scala @@ -130,6 +130,20 @@ object CompletionValue: override def description(printer: MetalsPrinter)(using Context): String = s"${printer.completionSymbol(symbol)} (extension)" + /** + * CompletionValue for old implicit classes methods via SymbolSearch + */ + case class ImplicitClass( + label: String, + symbol: Symbol, + override val snippetSuffix: CompletionSuffix, + override val importSymbol: Symbol, + ) extends Symbolic: + override def completionItemKind(using Context): CompletionItemKind = + CompletionItemKind.Method + override def description(printer: MetalsPrinter)(using Context): String = + s"${printer.completionSymbol(symbol)} (implicit)" + /** * @param shortenedNames shortened type names by `Printer`. This field should be used for autoImports * @param start Starting position of the completion diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/completions/Completions.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/completions/Completions.scala index 7d0b823b5c9..7a3ac0d3491 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/completions/Completions.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/completions/Completions.scala @@ -595,14 +595,35 @@ class Completions( Some(search.search(query, buildTargetIdentifier, visitor)) case CompletionKind.Members => val visitor = new CompilerSearchVisitor(sym => - if sym.is(ExtensionMethod) && + def isExtensionMethod = sym.is(ExtensionMethod) && qualType.widenDealias <:< sym.extensionParam.info.widenDealias - then + + def isImplicitClass(owner: Symbol) = + val constructorParam = + owner.info.allMembers + .find(_.symbol.isAllOf(Flags.PrivateParamAccessor)) + .map(_.info) + owner.isClass && owner.is(Flags.Implicit) && + constructorParam.exists(p => + qualType.widenDealias <:< p.widenDealias + ) + end isImplicitClass + + def isImplicitClassMethod = sym.is(Flags.Method) && + isImplicitClass(sym.maybeOwner) + + if isExtensionMethod then completionsWithSuffix( sym, sym.decodedName, CompletionValue.Extension(_, _, _), ).map(visit).forall(_ == true) + else if isImplicitClassMethod then + completionsWithSuffix( + sym, + sym.decodedName, + CompletionValue.ImplicitClass(_, _, _, sym.maybeOwner), + ).map(visit).forall(_ == true) else false, ) Some(search.searchMethods(query, buildTargetIdentifier, visitor)) diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala index c6cafdbb439..0dba337b8b2 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala @@ -79,8 +79,16 @@ class ScalaToplevelMtags( expectTemplate: Option[ExpectTemplate], prevWasDot: Boolean = false ): Unit = { - def newExpectTemplate: Some[ExpectTemplate] = - Some(ExpectTemplate(indent, currentOwner, false, false)) + def newExpectTemplate(isImplicit: Boolean = false): Some[ExpectTemplate] = + Some( + ExpectTemplate( + indent, + currentOwner, + false, + false, + isImplicit = isImplicit + ) + ) def newExpectCaseClassTemplate: Some[ExpectTemplate] = Some( ExpectTemplate( @@ -92,20 +100,27 @@ class ScalaToplevelMtags( isCaseClassConstructor = true ) ) - def newExpectClassTemplate: Some[ExpectTemplate] = + def newExpectClassTemplate( + isImplicit: Boolean = false + ): Some[ExpectTemplate] = Some( ExpectTemplate( indent, currentOwner, false, false, - isClassConstructor = true + isClassConstructor = true, + isImplicit = isImplicit ) ) def newExpectPkgTemplate: Some[ExpectTemplate] = Some(ExpectTemplate(indent, currentOwner, true, false)) def newExpectExtensionTemplate(owner: String): Some[ExpectTemplate] = Some(ExpectTemplate(indent, owner, false, true)) + def newExpectImplicitTemplate: Some[ExpectTemplate] = + Some( + ExpectTemplate(indent, currentOwner, false, false, isImplicit = true) + ) def newExpectIgnoreBody: Some[ExpectTemplate] = Some( ExpectTemplate( @@ -130,6 +145,8 @@ class ScalaToplevelMtags( def needEmitTermMember(): Boolean = includeMembers && !prevWasDot + def srcName = input.filename.stripSuffix(".scala") + if (!isDone) { val data = scanner.curr val currRegion = @@ -147,7 +164,7 @@ class ScalaToplevelMtags( val nextRegion = new Region.Package(currentOwner, currRegion) loop(indent, false, nextRegion, newExpectPkgTemplate) } else - loop(indent, false, currRegion, newExpectTemplate) + loop(indent, false, currRegion, newExpectTemplate()) case IDENTIFIER if dialect.allowExtensionMethods && data.name == "extension" => val nextOwner = @@ -177,21 +194,43 @@ class ScalaToplevelMtags( newExpectExtensionTemplate(nextOwner) ) case CLASS | TRAIT | OBJECT | ENUM if needEmitMember(currRegion) => - emitMember(false, currRegion.owner) + /* Scala 3 allows for toplevel implicit classes, but generates + * artificial package object. Scala 2 doesn't allow for it. + */ + val isScala3Implicit = + dialect.allowExtensionMethods && currRegion.produceSourceToplevel && + expectTemplate.exists(_.isImplicit) + val owner = if (isScala3Implicit) { + val name = s"$srcName$$package" + val pos = newPosition + val owner = withOwner(currRegion.owner) { + term(name, pos, Kind.OBJECT, 0) + } + owner + } else currRegion.owner + emitMember(isPackageObject = false, owner) val template = expectTemplate match { case Some(expect) if expect.isCaseClassConstructor => newExpectCaseClassTemplate - case _ => newExpectClassTemplate + case Some(expect) => + newExpectClassTemplate(expect.isImplicit) + case _ => + newExpectClassTemplate(isImplicit = false) } loop(indent, isAfterNewline = false, currRegion, template) // also covers extension methods because of `def` inside case DEF // extension group - if (includeMembers && dialect.allowExtensionMethods && currRegion.isExtension) => + if (includeMembers && (dialect.allowExtensionMethods && currRegion.isExtension || currRegion.isImplicit)) => acceptTrivia() newIdentifier.foreach { name => withOwner(currRegion.owner) { - term(name.name, name.pos, Kind.METHOD, EXTENSION) + method( + name.name, + region.overloads.disambiguator(name.name), + name.pos, + EXTENSION + ) } } loop(indent, isAfterNewline = false, currRegion, newExpectIgnoreBody) @@ -209,7 +248,12 @@ class ScalaToplevelMtags( acceptTrivia() newIdentifier.foreach { name => withOwner(expect.owner) { - term(name.name, name.pos, Kind.METHOD, EXTENSION) + method( + name.name, + region.overloads.disambiguator(name.name), + name.pos, + EXTENSION + ) } } loop(indent, isAfterNewline = false, currRegion, None) @@ -218,7 +262,6 @@ class ScalaToplevelMtags( if dialect.allowToplevelStatements && needEmitFileOwner(currRegion) => val pos = newPosition - val srcName = input.filename.stripSuffix(".scala") val name = s"$srcName$$package" val owner = withOwner(currRegion.owner) { term(name, pos, Kind.OBJECT, 0) @@ -306,7 +349,10 @@ class ScalaToplevelMtags( case COLON if dialect.allowSignificantIndentation => (expectTemplate, nextIsNL()) match { case (Some(expect), true) if needToParseBody(expect) => - val next = expect.startIndentedRegion(currRegion) + val next = expect.startIndentedRegion( + currRegion, + isImplicitClass = expect.isImplicit + ) resetRegion(next) scanner.nextToken() loop(0, isAfterNewline = true, next, None) @@ -340,7 +386,11 @@ class ScalaToplevelMtags( case Some(expect) if needToParseBody(expect) || needToParseExtension(expect) => val next = - expect.startInBraceRegion(currRegion, expect.isExtension) + expect.startInBraceRegion( + currRegion, + expect.isExtension, + expect.isImplicit + ) resetRegion(next) scanner.nextToken() loop(indent, isAfterNewline = false, next, None) @@ -351,7 +401,7 @@ class ScalaToplevelMtags( } case RBRACE => val nextRegion = currRegion match { - case Region.InBrace(_, prev, _, _) => resetRegion(prev) + case Region.InBrace(_, prev, _, _, _) => resetRegion(prev) case r => r } scanner.nextToken() @@ -391,7 +441,10 @@ class ScalaToplevelMtags( indent, isAfterNewline = false, currRegion.prev, - newExpectTemplate + newExpectTemplate( + // we still need the information if the current template is implicit + expectTemplate.exists(_.isImplicit) + ) ) case COMMA => val nextExpectTemplate = expectTemplate.filter(!_.isPackageBody) @@ -422,7 +475,7 @@ class ScalaToplevelMtags( val (shouldCreateClassTemplate, isAfterNewline) = emitEnumCases(region, nextIsNewLine) val nextExpectTemplate = - if (shouldCreateClassTemplate) newExpectClassTemplate + if (shouldCreateClassTemplate) newExpectClassTemplate() else expectTemplate.filter(!_.isPackageBody) loop( indent, @@ -431,6 +484,15 @@ class ScalaToplevelMtags( if (scanner.curr.token == CLASS) newExpectCaseClassTemplate else nextExpectTemplate ) + case IMPLICIT => + scanner.nextToken() + loop( + indent, + isAfterNewline, + currRegion, + newExpectImplicitTemplate, + prevWasDot + ) case t => val nextExpectTemplate = expectTemplate.filter(!_.isPackageBody) scanner.nextToken() @@ -832,7 +894,8 @@ object ScalaToplevelMtags { isExtension: Boolean = false, ignoreBody: Boolean = false, isCaseClassConstructor: Boolean = false, - isClassConstructor: Boolean = false + isClassConstructor: Boolean = false, + isImplicit: Boolean = false ) { /** @@ -845,15 +908,29 @@ object ScalaToplevelMtags { private def adjustRegion(r: Region): Region = if (isPackageBody) r.prev else r - def startInBraceRegion(prev: Region, extension: Boolean = false): Region = - new Region.InBrace(owner, adjustRegion(prev), extension) + def startInBraceRegion( + prev: Region, + extension: Boolean = false, + isImplicitClass: Boolean = false + ): Region = + new Region.InBrace(owner, adjustRegion(prev), extension, isImplicitClass) def startInParenRegion(prev: Region, isCaseClass: Boolean): Region = if (isCaseClass) Region.InParenCaseClass(owner, adjustRegion(prev), true) else Region.InParenClass(owner, adjustRegion(prev)) - def startIndentedRegion(prev: Region, extension: Boolean = false): Region = - new Region.Indented(owner, indent, adjustRegion(prev), extension) + def startIndentedRegion( + prev: Region, + extension: Boolean = false, + isImplicitClass: Boolean = false + ): Region = + new Region.Indented( + owner, + indent, + adjustRegion(prev), + extension, + isImplicitClass: Boolean + ) } @@ -863,6 +940,7 @@ object ScalaToplevelMtags { def acceptMembers: Boolean def produceSourceToplevel: Boolean = termOwner.isPackage def isExtension: Boolean = false + def isImplicit: Boolean = false val overloads: OverloadDisambiguator = new OverloadDisambiguator() def termOwner: String = owner // toplevel terms are wrapped into an artificial Object @@ -898,39 +976,43 @@ object ScalaToplevelMtags { owner: String, prev: Region, extension: Boolean = false, - override val termOwner: String + override val termOwner: String, + override val isImplicit: Boolean ) extends Region { def this( owner: String, prev: Region, - extension: Boolean - ) = this(owner, prev, extension, owner) + extension: Boolean, + isImplicit: Boolean + ) = this(owner, prev, extension, owner, isImplicit) def acceptMembers: Boolean = owner.endsWith("/") override def isExtension = extension override val withTermOwner: String => InBrace = termOwner => - InBrace(owner, prev, extension, termOwner) + InBrace(owner, prev, extension, termOwner, isImplicit) } final case class Indented( owner: String, exitIndent: Int, prev: Region, extension: Boolean = false, - override val termOwner: String + override val termOwner: String, + override val isImplicit: Boolean ) extends Region { def this( owner: String, exitIndent: Int, prev: Region, - extension: Boolean - ) = this(owner, exitIndent, prev, extension, owner) + extension: Boolean, + isImplicit: Boolean + ) = this(owner, exitIndent, prev, extension, owner, isImplicit) def acceptMembers: Boolean = owner.endsWith("/") override def isExtension = extension override val withTermOwner: String => Indented = termOwner => - Indented(owner, exitIndent, prev, extension, termOwner) + Indented(owner, exitIndent, prev, extension, termOwner, isImplicit) } final case class InParenClass( diff --git a/tests/cross/src/test/scala/tests/pc/CompletionExtensionMethodSuite.scala b/tests/cross/src/test/scala/tests/pc/CompletionExtensionMethodSuite.scala index c75c9b433fb..480f9c9f317 100644 --- a/tests/cross/src/test/scala/tests/pc/CompletionExtensionMethodSuite.scala +++ b/tests/cross/src/test/scala/tests/pc/CompletionExtensionMethodSuite.scala @@ -21,6 +21,20 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { |""".stripMargin ) + check( + "simple-old-syntax", + """|package example + | + |object Test: + | implicit class TestOps(a: Int): + | def testOps(b: Int): String = ??? + | + |def main = 100.test@@ + |""".stripMargin, + """|testOps(b: Int): String (implicit) + |""".stripMargin + ) + check( "simple2", """|package example @@ -36,6 +50,21 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { filter = _.contains("(extension)") ) + check( + "simple2-old-syntax", + """|package example + | + |object enrichments: + | implicit class TestOps(a: Int): + | def testOps(b: Int): String = ??? + | + |def main = 100.t@@ + |""".stripMargin, + """|testOps(b: Int): String (implicit) + |""".stripMargin, + filter = _.contains("(implicit)") + ) + check( "simple-empty", """|package example @@ -51,6 +80,21 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { filter = _.contains("(extension)") ) + check( + "simple-empty-old", + """|package example + | + |object enrichments: + | implicit class TestOps(a: Int): + | def testOps(b: Int): String = ??? + | + |def main = 100.@@ + |""".stripMargin, + """|testOps(b: Int): String (implicit) + |""".stripMargin, + filter = _.contains("(implicit)") + ) + check( "filter-by-type", """|package example @@ -68,6 +112,23 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { filter = _.contains("(extension)") ) + check( + "filter-by-type-old", + """|package example + | + |object enrichments: + | implicit class A(num: Int): + | def identity2: Int = num + 1 + | implicit class B(str: String): + | def identity: String = str + | + |def main = "foo".iden@@ + |""".stripMargin, + """|identity: String (implicit) + |""".stripMargin // incr won't be available + + ) + check( "filter-by-type-subtype", """|package example @@ -86,6 +147,24 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { filter = _.contains("(extension)") ) + check( + "filter-by-type-subtype-old", + """|package example + | + |class A + |class B extends A + | + |object enrichments: + | implicit class Test(a: A): + | def doSomething: A = a + | + |def main = (new B).do@@ + |""".stripMargin, + """|doSomething: A (implicit) + |""".stripMargin, + filter = _.contains("(implicit)") + ) + checkEdit( "simple-edit", """|package example @@ -108,6 +187,28 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { |""".stripMargin ) + checkEdit( + "simple-edit-old", + """|package example + | + |object enrichments: + | implicit class A (num: Int): + | def incr: Int = num + 1 + | + |def main = 100.inc@@ + |""".stripMargin, + """|package example + | + |import example.enrichments.A + | + |object enrichments: + | implicit class A (num: Int): + | def incr: Int = num + 1 + | + |def main = 100.incr + |""".stripMargin + ) + checkEdit( "simple-edit-suffix", """|package example @@ -157,6 +258,28 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { | val plus = 100.plus(19) | val y = 19.plus($0) |} + """ + ) + + checkEdit( + "simple-edit-suffix-old", + """|package example + | + |object enrichments: + | implicit class A (num: Int): + | def plus(other: Int): Int = num + other + | + |def main = 100.pl@@ + |""".stripMargin, + """|package example + | + |import example.enrichments.A + | + |object enrichments: + | implicit class A (num: Int): + | def plus(other: Int): Int = num + other + | + |def main = 100.plus($0) |""".stripMargin ) @@ -176,6 +299,20 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { |""".stripMargin ) + check( + "directly-in-pkg1-old".tag(IgnoreScalaVersion.forLessThan("3.2.2")), + """| + |package examples: + | implicit class A(num: Int): + | def incr: Int = num + 1 + | + |package examples2: + | def main = 100.inc@@ + |""".stripMargin, + """|incr: Int (implicit) + |""".stripMargin + ) + check( "directly-in-pkg2".tag(IgnoreScalaVersion.forLessThan("3.2.2")), """|package example: @@ -190,6 +327,20 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { |""".stripMargin ) + check( + "directly-in-pkg2-old".tag(IgnoreScalaVersion.forLessThan("3.2.2")), + """|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@@ + |""".stripMargin, + """|incr: Int (implicit) + |""".stripMargin + ) + checkEdit( "directly-in-pkg3".tag(IgnoreScalaVersion.forLessThan("3.2.2")), """|package example: @@ -207,6 +358,23 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { |""".stripMargin ) + checkEdit( + "directly-in-pkg3-old".tag(IgnoreScalaVersion.forLessThan("3.2.2")), + """|package examples: + | implicit class A (num: Int) { def incr: Int = num + 1 } + | + |package examples2: + | def main = 100.inc@@ + |""".stripMargin, + """|import examples.A + |package examples: + | implicit class A (num: Int) { def incr: Int = num + 1 } + | + |package examples2: + | def main = 100.incr + |""".stripMargin + ) + check( "nested-pkg".tag(IgnoreScalaVersion.forLessThan("3.2.2")), """|package a: // some comment @@ -225,4 +393,22 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { |""".stripMargin ) + check( + "nested-pkg-old".tag(IgnoreScalaVersion.forLessThan("3.2.2")), + """|package aa: // some comment + | package cc: + | implicit class A (num: Int): + | def increment2 = num + 2 + | implicit class A (num: Int): + | def increment = num + 1 + | + | + |package bb: + | def main: Unit = 123.incre@@ + |""".stripMargin, + """|increment: Int (implicit) + |increment2: Int (implicit) + |""".stripMargin + ) + } diff --git a/tests/mtest/src/main/scala/tests/PCSuite.scala b/tests/mtest/src/main/scala/tests/PCSuite.scala index 79f40b5b226..d74e0ec1d9e 100644 --- a/tests/mtest/src/main/scala/tests/PCSuite.scala +++ b/tests/mtest/src/main/scala/tests/PCSuite.scala @@ -100,7 +100,7 @@ trait PCSuite { case NonFatal(e) => println(s"warn: ${e.getMessage}") } - workspace.inputs(filename) = (code2, dialect) + workspace.inputs(file.toURI.toString()) = (code2, dialect) } } diff --git a/tests/slow/src/test/scala/tests/feature/CompletionCrossLspSuite.scala b/tests/slow/src/test/scala/tests/feature/CompletionCrossLspSuite.scala index d2a14f6bb1f..991aa42f4ec 100644 --- a/tests/slow/src/test/scala/tests/feature/CompletionCrossLspSuite.scala +++ b/tests/slow/src/test/scala/tests/feature/CompletionCrossLspSuite.scala @@ -106,6 +106,54 @@ class CompletionCrossLspSuite } yield () } + test("implicit-class") { + cleanWorkspace() + for { + _ <- initialize( + s"""/metals.json + |{ + | "a": { "scalaVersion": "${V.scala3}" } + |} + |/a/src/main/scala/a/B.scala + |package b + |implicit class B (num: Int): + | def plus(other: Int) = num + other + |/a/src/main/scala/a/A.scala + |package a + | + |object A { + | // @@ + |} + |""".stripMargin + ) + _ <- server.didOpen("a/src/main/scala/a/B.scala") + _ = assertNoDiagnostics() + _ <- assertCompletionEdit( + "1.p@@", + """|package a + | + |import b.B + | + |object A { + | 1.plus($0) + |} + |""".stripMargin, + filter = _.contains("plus"), + ) + _ <- assertCompletion( + "1.pl@@", + """|plus(other: Int): Int (implicit) + |""".stripMargin, + filter = _.contains("plus"), + ) + _ <- assertCompletion( + "\"plus is not available for string\".plu@@", + "", + filter = _.contains("plus"), + ) + } yield () + } + test("basic-scala3") { cleanWorkspace() for { diff --git a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala index ae9116ea5b6..169f1de83fc 100644 --- a/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala +++ b/tests/unit/src/test/scala/tests/ScalaToplevelSuite.scala @@ -348,8 +348,8 @@ class ScalaToplevelSuite extends BaseSuite { List( "a/", "a/A.", - "a/A.bar.", - "a/A.foo.", + "a/A.bar().", + "a/A.foo().", ), mode = All, dialect = dialects.Scala3, @@ -369,8 +369,8 @@ class ScalaToplevelSuite extends BaseSuite { List( "a/", "a/Test$package.", - "a/Test$package.bar.", - "a/Test$package.foo.", + "a/Test$package.bar().", + "a/Test$package.foo().", ), mode = All, dialect = dialects.Scala3, @@ -386,8 +386,8 @@ class ScalaToplevelSuite extends BaseSuite { | def baz: Long = ??? |""".stripMargin, List( - "a/", "a/Test$package.", "a/Test$package.foo.", "a/Test$package.bar.", - "a/Test$package.baz.", + "a/", "a/Test$package.", "a/Test$package.foo().", "a/Test$package.bar().", + "a/Test$package.baz().", ), mode = All, dialect = dialects.Scala3, From 4ed074bc8ac954e75ce19ea6278205612d3fa7fe Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Fri, 15 Dec 2023 19:40:39 +0100 Subject: [PATCH 2/3] bugfix: Only generate package object once --- .../meta/internal/mtags/ScalaToplevelMtags.scala | 15 +++++++++++---- .../tests/pc/CompletionExtensionMethodSuite.scala | 4 ++-- .../main/scala-3/example/ToplevelImplicit.scala | 10 ++++++++++ .../decorations3/example/EndMarker.scala | 2 +- .../decorations3/example/ToplevelImplicit.scala | 10 ++++++++++ .../example/ToplevelImplicit.scala | 10 ++++++++++ .../example/ToplevelImplicit.scala | 10 ++++++++++ .../test/resources/expect/toplevels-scala3.expect | 6 +++++- .../mtags-scala3/example/ToplevelImplicit.scala | 10 ++++++++++ .../semanticTokens3/example/EndMarker.scala | 2 +- .../example/ToplevelImplicit.scala | 10 ++++++++++ .../example/ToplevelImplicit.scala | 10 ++++++++++ .../example/ToplevelImplicit.scala | 10 ++++++++++ 13 files changed, 100 insertions(+), 9 deletions(-) create mode 100644 tests/input/src/main/scala-3/example/ToplevelImplicit.scala create mode 100644 tests/unit/src/test/resources/decorations3/example/ToplevelImplicit.scala create mode 100644 tests/unit/src/test/resources/definition-scala3/example/ToplevelImplicit.scala create mode 100644 tests/unit/src/test/resources/documentSymbol-scala3/example/ToplevelImplicit.scala create mode 100644 tests/unit/src/test/resources/mtags-scala3/example/ToplevelImplicit.scala create mode 100644 tests/unit/src/test/resources/semanticTokens3/example/ToplevelImplicit.scala create mode 100644 tests/unit/src/test/resources/semanticdb-scala3/example/ToplevelImplicit.scala create mode 100644 tests/unit/src/test/resources/toplevel-with-inner-scala3/example/ToplevelImplicit.scala diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala index 0dba337b8b2..418ae3f197d 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala @@ -197,17 +197,19 @@ class ScalaToplevelMtags( /* Scala 3 allows for toplevel implicit classes, but generates * artificial package object. Scala 2 doesn't allow for it. */ - val isScala3Implicit = + val needsToGenerateFileClass = dialect.allowExtensionMethods && currRegion.produceSourceToplevel && expectTemplate.exists(_.isImplicit) - val owner = if (isScala3Implicit) { + val owner = if (needsToGenerateFileClass) { val name = s"$srcName$$package" val pos = newPosition val owner = withOwner(currRegion.owner) { term(name, pos, Kind.OBJECT, 0) } owner - } else currRegion.owner + } else if (expectTemplate.exists(_.isImplicit)) { + currRegion.termOwner + } else { currRegion.owner } emitMember(isPackageObject = false, owner) val template = expectTemplate match { case Some(expect) if expect.isCaseClassConstructor => @@ -217,7 +219,12 @@ class ScalaToplevelMtags( case _ => newExpectClassTemplate(isImplicit = false) } - loop(indent, isAfterNewline = false, currRegion, template) + loop( + indent, + isAfterNewline = false, + if (needsToGenerateFileClass) currRegion.withTermOwner(owner) else currRegion, + template + ) // also covers extension methods because of `def` inside case DEF // extension group diff --git a/tests/cross/src/test/scala/tests/pc/CompletionExtensionMethodSuite.scala b/tests/cross/src/test/scala/tests/pc/CompletionExtensionMethodSuite.scala index 480f9c9f317..05e0c2ead35 100644 --- a/tests/cross/src/test/scala/tests/pc/CompletionExtensionMethodSuite.scala +++ b/tests/cross/src/test/scala/tests/pc/CompletionExtensionMethodSuite.scala @@ -258,9 +258,9 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { | val plus = 100.plus(19) | val y = 19.plus($0) |} - """ + """.stripMargin ) - + checkEdit( "simple-edit-suffix-old", """|package example diff --git a/tests/input/src/main/scala-3/example/ToplevelImplicit.scala b/tests/input/src/main/scala-3/example/ToplevelImplicit.scala new file mode 100644 index 00000000000..7651f8aee7b --- /dev/null +++ b/tests/input/src/main/scala-3/example/ToplevelImplicit.scala @@ -0,0 +1,10 @@ +package example + +// This wasn't possible in Scala 2 +implicit class Xtension(number: Int) { + def increment: Int = number + 1 +} + +implicit class XtensionAnyVal(private val number: Int) extends AnyVal { + def double: Int = number * 2 +} diff --git a/tests/unit/src/test/resources/decorations3/example/EndMarker.scala b/tests/unit/src/test/resources/decorations3/example/EndMarker.scala index 26c56797083..cb7489c8a8b 100644 --- a/tests/unit/src/test/resources/decorations3/example/EndMarker.scala +++ b/tests/unit/src/test/resources/decorations3/example/EndMarker.scala @@ -3,4 +3,4 @@ package example object EndMarker: def foo/*: Int*/ = 1 - end foo + end foo \ No newline at end of file diff --git a/tests/unit/src/test/resources/decorations3/example/ToplevelImplicit.scala b/tests/unit/src/test/resources/decorations3/example/ToplevelImplicit.scala new file mode 100644 index 00000000000..d9de637fac5 --- /dev/null +++ b/tests/unit/src/test/resources/decorations3/example/ToplevelImplicit.scala @@ -0,0 +1,10 @@ +package example + +// This wasn't possible in Scala 2 +implicit class Xtension(number: Int) { + def increment: Int = number + 1 +} + +implicit class XtensionAnyVal(private val number: Int) extends AnyVal { + def double: Int = number * 2 +} \ No newline at end of file diff --git a/tests/unit/src/test/resources/definition-scala3/example/ToplevelImplicit.scala b/tests/unit/src/test/resources/definition-scala3/example/ToplevelImplicit.scala new file mode 100644 index 00000000000..a98666153ea --- /dev/null +++ b/tests/unit/src/test/resources/definition-scala3/example/ToplevelImplicit.scala @@ -0,0 +1,10 @@ +package example + +// This wasn't possible in Scala 2 +implicit class Xtension/**/(number/**/: Int/*Int.scala*/) { + def increment/**/: Int/*Int.scala*/ = number/**/ +/*Int.scala*/ 1 +} + +implicit class XtensionAnyVal/**/(private val number/**/: Int/*Int.scala*/) extends AnyVal/*AnyVal.scala*/ { + def double/**/: Int/*Int.scala*/ = number/**/ */*Int.scala*/ 2 +} diff --git a/tests/unit/src/test/resources/documentSymbol-scala3/example/ToplevelImplicit.scala b/tests/unit/src/test/resources/documentSymbol-scala3/example/ToplevelImplicit.scala new file mode 100644 index 00000000000..19cfa235c8a --- /dev/null +++ b/tests/unit/src/test/resources/documentSymbol-scala3/example/ToplevelImplicit.scala @@ -0,0 +1,10 @@ +/*example(Package):10*/package example + +// This wasn't possible in Scala 2 +/*example.Xtension(Class):6*/implicit class Xtension(number: Int) { + /*example.Xtension#increment(Method):5*/def increment: Int = number + 1 +} + +/*example.XtensionAnyVal(Class):10*/implicit class XtensionAnyVal(private val number: Int) extends AnyVal { + /*example.XtensionAnyVal#double(Method):9*/def double: Int = number * 2 +} diff --git a/tests/unit/src/test/resources/expect/toplevels-scala3.expect b/tests/unit/src/test/resources/expect/toplevels-scala3.expect index 39a344acf53..96c318a2e61 100644 --- a/tests/unit/src/test/resources/expect/toplevels-scala3.expect +++ b/tests/unit/src/test/resources/expect/toplevels-scala3.expect @@ -41,6 +41,10 @@ example/PatternMatching.scala -> example/PatternMatching# example/Scalalib.scala -> example/Scalalib# example/StructuralTypes.scala -> example/StructuralTypes. example/ToplevelDefVal.scala -> example/ToplevelDefVal$package. +example/ToplevelImplicit.scala -> example/ToplevelImplicit$package. +example/ToplevelImplicit.scala -> example/ToplevelImplicit$package. +example/ToplevelImplicit.scala -> example/ToplevelImplicit$package.Xtension# +example/ToplevelImplicit.scala -> example/ToplevelImplicit$package.XtensionAnyVal# example/TryCatch.scala -> example/TryCatch# example/TypeEnum.scala -> example/TypeEnum# example/TypeParameters.scala -> example/TypeParameters# @@ -53,4 +57,4 @@ example/nested/LocalClass.scala -> example/nested/LocalClass# example/nested/package.scala -> example/PackageObjectSibling# example/nested/package.scala -> example/nested/package. example/package.scala -> example/package. -example/type/Backtick.scala -> example/type/Backtick# +example/type/Backtick.scala -> example/type/Backtick# \ No newline at end of file diff --git a/tests/unit/src/test/resources/mtags-scala3/example/ToplevelImplicit.scala b/tests/unit/src/test/resources/mtags-scala3/example/ToplevelImplicit.scala new file mode 100644 index 00000000000..0df96f53532 --- /dev/null +++ b/tests/unit/src/test/resources/mtags-scala3/example/ToplevelImplicit.scala @@ -0,0 +1,10 @@ +package example + +// This wasn't possible in Scala 2 +implicit class Xtension/*example.Xtension().*//*example.Xtension#*/(number/*example.Xtension#number.*/: Int) { + def increment/*example.Xtension#increment().*/: Int = number + 1 +} + +implicit class XtensionAnyVal/*example.XtensionAnyVal().*//*example.XtensionAnyVal#*/(private val number/*example.XtensionAnyVal#number.*/: Int) extends AnyVal { + def double/*example.XtensionAnyVal#double().*/: Int = number * 2 +} diff --git a/tests/unit/src/test/resources/semanticTokens3/example/EndMarker.scala b/tests/unit/src/test/resources/semanticTokens3/example/EndMarker.scala index 7e4e14ab074..0c9bc254a5a 100644 --- a/tests/unit/src/test/resources/semanticTokens3/example/EndMarker.scala +++ b/tests/unit/src/test/resources/semanticTokens3/example/EndMarker.scala @@ -3,4 +3,4 @@ <>/*keyword*/ <>/*class*/: <>/*keyword*/ <>/*method,definition*/ = <<1>>/*number*/ - <>/*keyword*/ <>/*method,definition*/ + <>/*keyword*/ <>/*method,definition*/ \ No newline at end of file diff --git a/tests/unit/src/test/resources/semanticTokens3/example/ToplevelImplicit.scala b/tests/unit/src/test/resources/semanticTokens3/example/ToplevelImplicit.scala new file mode 100644 index 00000000000..ce84a8602c7 --- /dev/null +++ b/tests/unit/src/test/resources/semanticTokens3/example/ToplevelImplicit.scala @@ -0,0 +1,10 @@ +<>/*keyword*/ <>/*namespace*/ + +<>/*comment*/ +<>/*modifier*/ <>/*keyword*/ <>/*class*/(<>/*variable,declaration,readonly*/: <>/*class,abstract*/) { + <>/*keyword*/ <>/*method,definition*/: <>/*class,abstract*/ = <>/*variable,readonly*/ <<+>>/*method*/ <<1>>/*number*/ +} + +<>/*modifier*/ <>/*keyword*/ <>/*class*/(<>/*modifier*/ <>/*keyword*/ <>/*variable,declaration,readonly*/: <>/*class,abstract*/) <>/*keyword*/ <>/*class,abstract*/ { + <>/*keyword*/ <>/*method,definition*/: <>/*class,abstract*/ = <>/*variable,readonly*/ <<*>>/*method*/ <<2>>/*number*/ +} \ No newline at end of file diff --git a/tests/unit/src/test/resources/semanticdb-scala3/example/ToplevelImplicit.scala b/tests/unit/src/test/resources/semanticdb-scala3/example/ToplevelImplicit.scala new file mode 100644 index 00000000000..180e4a65ddf --- /dev/null +++ b/tests/unit/src/test/resources/semanticdb-scala3/example/ToplevelImplicit.scala @@ -0,0 +1,10 @@ +package example + +// This wasn't possible in Scala 2 +implicit class Xtension/*example.ToplevelImplicit$package.Xtension#*/(number/*example.ToplevelImplicit$package.Xtension#number.*/: Int/*scala.Int#*/) { + def increment/*example.ToplevelImplicit$package.Xtension#increment().*/: Int/*scala.Int#*/ = number/*example.ToplevelImplicit$package.Xtension#number.*/ +/*scala.Int#`+`(+4).*/ 1 +} + +implicit class XtensionAnyVal/*example.ToplevelImplicit$package.XtensionAnyVal#*/(private val number/*example.ToplevelImplicit$package.XtensionAnyVal#number.*/: Int/*scala.Int#*/) extends AnyVal/*scala.AnyVal#*/ { + def double/*example.ToplevelImplicit$package.XtensionAnyVal#double().*/: Int/*scala.Int#*/ = number/*example.ToplevelImplicit$package.XtensionAnyVal#number.*/ */*scala.Int#`*`(+3).*/ 2 +} diff --git a/tests/unit/src/test/resources/toplevel-with-inner-scala3/example/ToplevelImplicit.scala b/tests/unit/src/test/resources/toplevel-with-inner-scala3/example/ToplevelImplicit.scala new file mode 100644 index 00000000000..2a6ab582f9f --- /dev/null +++ b/tests/unit/src/test/resources/toplevel-with-inner-scala3/example/ToplevelImplicit.scala @@ -0,0 +1,10 @@ +package example + +// This wasn't possible in Scala 2 +implicit class/*example.ToplevelImplicit$package.*/ Xtension/*example.ToplevelImplicit$package.Xtension#*/(number: Int) { + def increment: Int = number + 1 +} + +implicit class XtensionAnyVal/*example.ToplevelImplicit$package.XtensionAnyVal#*/(private val number: Int) extends AnyVal { + def double: Int = number * 2 +} From 6684c127d24b00bd5c2ffca3cba7b111c73c377d Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Fri, 15 Dec 2023 20:01:51 +0100 Subject: [PATCH 3/3] refactor: Tweaks after reviews --- .../internal/pc/CompilerSearchVisitor.scala | 14 ++++--- .../meta/internal/pc/SemanticdbSymbols.scala | 8 +--- .../internal/pc/completions/Completions.scala | 9 ++++- .../internal/mtags/ScalaToplevelMtags.scala | 3 +- .../src/main/scala/tests/BasePCSuite.scala | 1 + .../pc/CompletionExtensionMethodSuite.scala | 40 ++++++++++++------- .../resources/expect/toplevels-scala3.expect | 1 - 7 files changed, 46 insertions(+), 30 deletions(-) diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/CompilerSearchVisitor.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/CompilerSearchVisitor.scala index 6cdc980d473..8cfa3cb2fd0 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/CompilerSearchVisitor.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/CompilerSearchVisitor.scala @@ -21,13 +21,15 @@ class CompilerSearchVisitor( val logger: Logger = Logger.getLogger(classOf[CompilerSearchVisitor].getName) + private def isAccessibleImplicitClass(sym: Symbol) = + val owner = sym.maybeOwner + owner != NoSymbol && owner.isClass && + owner.is(Flags.Implicit) && + owner.isStatic && owner.isPublic + private def isAccessible(sym: Symbol): Boolean = try - sym != NoSymbol && sym.isPublic && sym.isStatic || { - val owner = sym.maybeOwner - owner != NoSymbol && owner.isClass && - owner.is(Flags.Implicit) && - owner.isStatic && owner.isPublic - } + sym != NoSymbol && sym.isPublic && sym.isStatic || + isAccessibleImplicitClass(sym) catch case err: AssertionError => logger.log(Level.WARNING, err.getMessage()) diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/SemanticdbSymbols.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/SemanticdbSymbols.scala index 92429cbdd73..3c8bcfa732b 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/SemanticdbSymbols.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/SemanticdbSymbols.scala @@ -51,15 +51,11 @@ object SemanticdbSymbols: /** * Looks like decl doesn't work for: * package a: - * implicit class A (i: Int): + * implicit class <> (i: Int): * def inc = i + 1 */ else if typeSym == NoSymbol then - val searched = typeName(value) - owner.info.allMembers - .find(_.name == searched) - .map(_.symbol) - .toList + owner.info.member(typeName(value)).symbol :: Nil else typeSym :: Nil end if case Descriptor.Term(value) => diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/completions/Completions.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/completions/Completions.scala index 7a3ac0d3491..d51d8307bd2 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/completions/Completions.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/completions/Completions.scala @@ -599,9 +599,14 @@ class Completions( qualType.widenDealias <:< sym.extensionParam.info.widenDealias def isImplicitClass(owner: Symbol) = + val constructorParam = - owner.info.allMembers - .find(_.symbol.isAllOf(Flags.PrivateParamAccessor)) + owner.info + .membersBasedOnFlags( + Flags.ParamAccessor, + Flags.EmptyFlags, + ) + .headOption .map(_.info) owner.isClass && owner.is(Flags.Implicit) && constructorParam.exists(p => diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala index 418ae3f197d..46c4ba40a72 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/ScalaToplevelMtags.scala @@ -222,7 +222,8 @@ class ScalaToplevelMtags( loop( indent, isAfterNewline = false, - if (needsToGenerateFileClass) currRegion.withTermOwner(owner) else currRegion, + if (needsToGenerateFileClass) currRegion.withTermOwner(owner) + else currRegion, template ) // also covers extension methods because of `def` inside diff --git a/tests/cross/src/main/scala/tests/BasePCSuite.scala b/tests/cross/src/main/scala/tests/BasePCSuite.scala index 84874519f08..f7ef495f650 100644 --- a/tests/cross/src/main/scala/tests/BasePCSuite.scala +++ b/tests/cross/src/main/scala/tests/BasePCSuite.scala @@ -225,6 +225,7 @@ abstract class BasePCSuite extends BaseSuite with PCSuite { } object IgnoreScalaVersion { + def apply(version: String): IgnoreScalaVersion = { IgnoreScalaVersion(_ == version) } diff --git a/tests/cross/src/test/scala/tests/pc/CompletionExtensionMethodSuite.scala b/tests/cross/src/test/scala/tests/pc/CompletionExtensionMethodSuite.scala index 05e0c2ead35..e1413bf9430 100644 --- a/tests/cross/src/test/scala/tests/pc/CompletionExtensionMethodSuite.scala +++ b/tests/cross/src/test/scala/tests/pc/CompletionExtensionMethodSuite.scala @@ -22,7 +22,7 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { ) check( - "simple-old-syntax", + "simple-old-syntax".tag(IgnoreForScala3CompilerPC), """|package example | |object Test: @@ -51,7 +51,7 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { ) check( - "simple2-old-syntax", + "simple2-old-syntax".tag(IgnoreForScala3CompilerPC), """|package example | |object enrichments: @@ -81,7 +81,7 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { ) check( - "simple-empty-old", + "simple-empty-old".tag(IgnoreForScala3CompilerPC), """|package example | |object enrichments: @@ -113,7 +113,7 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { ) check( - "filter-by-type-old", + "filter-by-type-old".tag(IgnoreForScala3CompilerPC), """|package example | |object enrichments: @@ -125,7 +125,7 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { |def main = "foo".iden@@ |""".stripMargin, """|identity: String (implicit) - |""".stripMargin // incr won't be available + |""".stripMargin // identity2 won't be available ) @@ -148,7 +148,7 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { ) check( - "filter-by-type-subtype-old", + "filter-by-type-subtype-old".tag(IgnoreForScala3CompilerPC), """|package example | |class A @@ -188,7 +188,7 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { ) checkEdit( - "simple-edit-old", + "simple-edit-old".tag(IgnoreForScala3CompilerPC), """|package example | |object enrichments: @@ -262,11 +262,11 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { ) checkEdit( - "simple-edit-suffix-old", + "simple-edit-suffix-old".tag(IgnoreForScala3CompilerPC), """|package example | |object enrichments: - | implicit class A (num: Int): + | implicit class A (val num: Int): | def plus(other: Int): Int = num + other | |def main = 100.pl@@ @@ -276,7 +276,7 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { |import example.enrichments.A | |object enrichments: - | implicit class A (num: Int): + | implicit class A (val num: Int): | def plus(other: Int): Int = num + other | |def main = 100.plus($0) @@ -300,7 +300,10 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { ) check( - "directly-in-pkg1-old".tag(IgnoreScalaVersion.forLessThan("3.2.2")), + "directly-in-pkg1-old" + .tag( + IgnoreScalaVersion.forLessThan("3.2.2").and(IgnoreForScala3CompilerPC) + ), """| |package examples: | implicit class A(num: Int): @@ -328,7 +331,10 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { ) check( - "directly-in-pkg2-old".tag(IgnoreScalaVersion.forLessThan("3.2.2")), + "directly-in-pkg2-old" + .tag( + IgnoreScalaVersion.forLessThan("3.2.2").and(IgnoreForScala3CompilerPC) + ), """|package examples: | object X: | def fooBar(num: Int) = num + 1 @@ -359,7 +365,10 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { ) checkEdit( - "directly-in-pkg3-old".tag(IgnoreScalaVersion.forLessThan("3.2.2")), + "directly-in-pkg3-old" + .tag( + IgnoreScalaVersion.forLessThan("3.2.2").and(IgnoreForScala3CompilerPC) + ), """|package examples: | implicit class A (num: Int) { def incr: Int = num + 1 } | @@ -394,7 +403,10 @@ class CompletionExtensionMethodSuite extends BaseCompletionSuite { ) check( - "nested-pkg-old".tag(IgnoreScalaVersion.forLessThan("3.2.2")), + "nested-pkg-old" + .tag( + IgnoreScalaVersion.forLessThan("3.2.2").and(IgnoreForScala3CompilerPC) + ), """|package aa: // some comment | package cc: | implicit class A (num: Int): diff --git a/tests/unit/src/test/resources/expect/toplevels-scala3.expect b/tests/unit/src/test/resources/expect/toplevels-scala3.expect index 96c318a2e61..2d139454969 100644 --- a/tests/unit/src/test/resources/expect/toplevels-scala3.expect +++ b/tests/unit/src/test/resources/expect/toplevels-scala3.expect @@ -42,7 +42,6 @@ example/Scalalib.scala -> example/Scalalib# example/StructuralTypes.scala -> example/StructuralTypes. example/ToplevelDefVal.scala -> example/ToplevelDefVal$package. example/ToplevelImplicit.scala -> example/ToplevelImplicit$package. -example/ToplevelImplicit.scala -> example/ToplevelImplicit$package. example/ToplevelImplicit.scala -> example/ToplevelImplicit$package.Xtension# example/ToplevelImplicit.scala -> example/ToplevelImplicit$package.XtensionAnyVal# example/TryCatch.scala -> example/TryCatch#