From 4742d7d012f2acdc35aed7e83bc8fd95966fd39d Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Wed, 28 Feb 2024 13:28:03 +0100 Subject: [PATCH] Weekly backport from metals (#19787) https://github.com/scalameta/metals/pull/6074 https://github.com/scalameta/metals/pull/6129 --- .../tools/pc/completions/Completions.scala | 28 +- .../pc/completions/MatchCaseCompletions.scala | 355 +++++++++--------- .../completion/CompletionCaseSuite.scala | 51 ++- .../completion/CompletionExtensionSuite.scala | 41 ++ .../completion/CompletionMatchSuite.scala | 47 ++- .../completion/CompletionWorkspaceSuite.scala | 49 +++ 6 files changed, 368 insertions(+), 203 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 d6ca15b35129..955982afcf51 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala @@ -497,14 +497,18 @@ class Completions( val query = completionPos.query if completionMode.is(Mode.Scope) && query.nonEmpty then val visitor = new CompilerSearchVisitor(sym => - indexedContext.lookupSym(sym) match - case IndexedContext.Result.InScope => false - case _ => - completionsWithSuffix( - sym, - sym.decodedName, - CompletionValue.Workspace(_, _, _, sym) - ).map(visit).forall(_ == true), + if !(sym.is(Flags.ExtensionMethod) || + (sym.maybeOwner.is(Flags.Implicit) && sym.maybeOwner.isClass)) + then + indexedContext.lookupSym(sym) match + case IndexedContext.Result.InScope => false + case _ => + completionsWithSuffix( + sym, + sym.decodedName, + CompletionValue.Workspace(_, _, _, sym) + ).map(visit).forall(_ == true) + else false, ) Some(search.search(query, buildTargetIdentifier, visitor).nn) else if completionMode.is(Mode.Member) then @@ -526,8 +530,10 @@ class Completions( ) end isImplicitClass - def isImplicitClassMethod = sym.is(Flags.Method) && !sym.isConstructor && - isImplicitClass(sym.maybeOwner) + def isDefaultVariableSetter = sym.is(Flags.Accessor) && sym.is(Flags.Method) + def isImplicitClassMember = + isImplicitClass(sym.maybeOwner) && !sym.is(Flags.Synthetic) && sym.isPublic + && !sym.isConstructor && !isDefaultVariableSetter if isExtensionMethod then completionsWithSuffix( @@ -535,7 +541,7 @@ class Completions( sym.decodedName, CompletionValue.Extension(_, _, _) ).map(visit).forall(_ == true) - else if isImplicitClassMethod then + else if isImplicitClassMember then completionsWithSuffix( sym, sym.decodedName, diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/MatchCaseCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/MatchCaseCompletions.scala index edb489228d92..bd94f2f3983a 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/MatchCaseCompletions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/MatchCaseCompletions.scala @@ -6,10 +6,12 @@ import java.net.URI import scala.collection.mutable import scala.collection.mutable.ListBuffer import scala.jdk.CollectionConverters._ +import scala.meta.internal.metals.ReportContext import scala.meta.internal.pc.CompletionFuzzy import scala.meta.pc.PresentationCompilerConfig import scala.meta.pc.SymbolSearch +import dotty.tools.toOption import dotty.tools.dotc.ast.tpd.* import dotty.tools.dotc.core.Constants.Constant import dotty.tools.dotc.core.Contexts.Context @@ -30,6 +32,8 @@ import dotty.tools.dotc.util.SourcePosition import dotty.tools.pc.AutoImports.AutoImportsGenerator import dotty.tools.pc.AutoImports.SymbolImport import dotty.tools.pc.MetalsInteractive.* +import dotty.tools.pc.printer.ShortenedTypePrinter +import dotty.tools.pc.printer.ShortenedTypePrinter.IncludeDefaultParam import dotty.tools.pc.utils.MtagsEnrichments.* import org.eclipse.lsp4j as l @@ -62,7 +66,7 @@ object CaseKeywordCompletion: patternOnly: Option[String] = None, hasBind: Boolean = false, includeExhaustive: Option[NewLineOptions] = None - ): List[CompletionValue] = + )(using ReportContext): List[CompletionValue] = import indexedContext.ctx val definitions = indexedContext.ctx.definitions val clientSupportsSnippets = config.isCompletionSnippetsEnabled() @@ -72,153 +76,162 @@ object CaseKeywordCompletion: patternOnly, hasBind ) - val parents: Parents = selector match + val printer = ShortenedTypePrinter(search, IncludeDefaultParam.Never)(using indexedContext) + val selTpe = selector match case EmptyTree => - val seenFromType = parent match - case TreeApply(fun, _) if !fun.typeOpt.isErroneous => fun.typeOpt - case _ => parent.typeOpt - seenFromType.paramInfoss match - case (head :: Nil) :: _ - if definitions.isFunctionType(head) || head.isRef( - definitions.PartialFunctionClass - ) => - val argTypes = head.argTypes.init - new Parents(argTypes, definitions) - case _ => new Parents(NoType, definitions) - case sel => new Parents(sel.typeOpt, definitions) - - val selectorSym = parents.selector.widen.metalsDealias.typeSymbol - - // Special handle case when selector is a tuple or `FunctionN`. - if definitions.isTupleClass(selectorSym) || definitions.isFunctionClass( - selectorSym - ) - then - if patternOnly.isEmpty then - val selectorTpe = parents.selector.show - val tpeLabel = - if !selectorTpe.contains("x$1") then selectorTpe - else selector.symbol.info.show - val label = s"case ${tpeLabel} =>" - List( - CompletionValue.CaseKeyword( - selectorSym, - label, - Some( - if config.isCompletionSnippetsEnabled() then "case ($0) =>" - else "case () =>" - ), - Nil, - range = Some(completionPos.toEditRange), - command = config.parameterHintsCommand().nn.asScala, + parent match + case TreeApply(fun, _) if !fun.tpe.isErroneous => + fun.tpe.paramInfoss match + case (head :: Nil) :: _ + if definitions.isFunctionType(head) || head.isRef( + definitions.PartialFunctionClass + ) => + val args = head.argTypes.init + if args.length > 1 then + Some(definitions.tupleType(args).widen.metalsDealias) + else args.headOption.map(_.widen.metalsDealias) + case _ => None + case _ => None + case sel => + Some(sel.tpe.widen.metalsDealias) + + selTpe + .map { selTpe => + val selectorSym = selTpe.typeSymbol + // Special handle case when selector is a tuple or `FunctionN`. + if definitions.isTupleClass(selectorSym) || definitions.isFunctionClass( + selectorSym ) - ) - else Nil - else - val result = ListBuffer.empty[SymbolImport] - val isVisited = mutable.Set.empty[Symbol] - def visit(symImport: SymbolImport): Unit = - - def recordVisit(s: Symbol): Unit = - if s != NoSymbol && !isVisited(s) then - isVisited += s - recordVisit(s.moduleClass) - recordVisit(s.sourceModule) - - val sym = symImport.sym - if !isVisited(sym) then - recordVisit(sym) - if completionGenerator.fuzzyMatches(symImport.name) then - result += symImport - end visit - - // Step 0: case for selector type - selectorSym.info match - case NoType => () - case _ => - if !(selectorSym.is(Sealed) && - (selectorSym.is(Abstract) || selectorSym.is(Trait))) - then visit((autoImportsGen.inferSymbolImport(selectorSym))) - - // Step 1: walk through scope members. - def isValid(sym: Symbol) = !parents.isParent(sym) - && (sym.is(Case) || sym.is(Flags.Module) || sym.isClass) - && parents.isSubClass(sym, false) - && (sym.isPublic || sym.isAccessibleFrom(selectorSym.info)) - indexedContext.scopeSymbols - .foreach(s => - val ts = s.info.metalsDealias.typeSymbol - if isValid(ts) then visit(autoImportsGen.inferSymbolImport(ts)) - ) - // Step 2: walk through known subclasses of sealed types. - val sealedDescs = subclassesForType( - parents.selector.widen.metalsDealias.bounds.hi - ) - sealedDescs.foreach { sym => - val symbolImport = autoImportsGen.inferSymbolImport(sym) - visit(symbolImport) - } - val res = result.result().flatMap { - case si @ SymbolImport(sym, name, importSel) => - completionGenerator.labelForCaseMember(sym, name.value).map { label => - (si, label) - } - } - val caseItems = - if res.isEmpty then completionGenerator.caseKeywordOnly + then + if patternOnly.isEmpty then + val selectorTpe = selTpe.show + val tpeLabel = + if !selectorTpe.contains("x$1") then selectorTpe + else selector.symbol.info.show + val label = s"case ${tpeLabel} =>" + List( + CompletionValue.CaseKeyword( + selectorSym, + label, + Some( + if config.isCompletionSnippetsEnabled() then "case ($0) =>" + else "case () =>" + ), + Nil, + range = Some(completionPos.toEditRange), + command = config.parameterHintsCommand().toOption.flatMap(_.asScala), + ) + ) + else Nil else - res.map((si, label) => - completionGenerator.toCompletionValue( - si.sym, - label, - autoImportsGen.renderImports(si.importSel.toList), + val result = ListBuffer.empty[SymbolImport] + val isVisited = mutable.Set.empty[Symbol] + + val isBottom = Set[Symbol]( + definitions.NullClass, + definitions.NothingClass, + ) + val tpes = Set(selectorSym, selectorSym.companion) + def isSubclass(sym: Symbol) = tpes.exists(par => sym.isSubClass(par)) + + def visit(symImport: SymbolImport): Unit = + + def recordVisit(s: Symbol): Unit = + if s != NoSymbol && !isVisited(s) then + isVisited += s + recordVisit(s.moduleClass) + recordVisit(s.sourceModule) + + val sym = symImport.sym + if !isVisited(sym) then + recordVisit(sym) + if completionGenerator.fuzzyMatches(symImport.name) then + result += symImport + end visit + + // Step 1: walk through scope members. + def isValid(sym: Symbol) = !tpes(sym) && + !isBottom(sym) && + isSubclass(sym) && + (sym.is(Case) || sym.is(Flags.Module) || sym.isClass) && + (sym.isPublic || sym.isAccessibleFrom(selectorSym.info)) + + indexedContext.scopeSymbols + .foreach(s => + val ts = s.info.metalsDealias.typeSymbol + if isValid(ts) then visit(autoImportsGen.inferSymbolImport(ts)) ) + // Step 2: walk through known subclasses of sealed types. + val sealedDescs = subclassesForType( + selTpe.bounds.hi ) + sealedDescs.foreach { sym => + val symbolImport = autoImportsGen.inferSymbolImport(sym) + visit(symbolImport) + } + val res = result.result().flatMap { + case si @ SymbolImport(sym, name, importSel) => + completionGenerator.labelForCaseMember(sym, name.value).map { + label => + (si, label) + } + } + val caseItems = + if res.isEmpty then completionGenerator.caseKeywordOnly + else + res.map((si, label) => + completionGenerator.toCompletionValue( + si.sym, + label, + autoImportsGen.renderImports(si.importSel.toList), + ) + ) - includeExhaustive match - // In `List(foo).map { cas@@} we want to provide also `case (exhaustive)` completion - // which works like exhaustive match. - case Some(NewLineOptions(moveToNewLine, addNewLineAfter)) => - val sealedMembers = - val sealedMembers0 = - res.filter((si, _) => sealedDescs.contains(si.sym)) - sortSubclasses( - selectorSym.info, - sealedMembers0, - completionPos.sourceUri, - search - ) - sealedMembers match - case Nil => caseItems - case (_, label) :: tail => - val (newLine, addIndent) = - if moveToNewLine then ("\n\t", "\t") else ("", "") - val insertText = Some( - tail - .map(_._2) - .mkString( - if clientSupportsSnippets then - s"$newLine${label} $$0\n$addIndent" - else s"$newLine${label}\n$addIndent", - s"\n$addIndent", - if addNewLineAfter then "\n" else "" + includeExhaustive match + // In `List(foo).map { cas@@} we want to provide also `case (exhaustive)` completion + // which works like exhaustive match. + case Some(NewLineOptions(moveToNewLine, addNewLineAfter)) => + val sealedMembers = + val sealedMembers0 = + res.filter((si, _) => sealedDescs.contains(si.sym)) + sortSubclasses( + selectorSym.info, + sealedMembers0, + completionPos.sourceUri, + search, + ) + sealedMembers match + case (_, label) :: tail if tail.length > 0 => + val (newLine, addIndent) = + if moveToNewLine then ("\n\t", "\t") else ("", "") + val insertText = Some( + tail + .map(_._2) + .mkString( + if clientSupportsSnippets then + s"$newLine${label} $$0\n$addIndent" + else s"$newLine${label}\n$addIndent", + s"\n$addIndent", + if addNewLineAfter then "\n" else "", + ) ) - ) - val allImports = - sealedMembers.flatMap(_._1.importSel).distinct - val importEdit = autoImportsGen.renderImports(allImports) - val exhaustive = CompletionValue.MatchCompletion( - s"case (exhaustive)", - insertText, - importEdit.toList, - s" ${selectorSym.decodedName} (${res.length} cases)" - ) - exhaustive :: caseItems + val allImports = + sealedMembers.flatMap(_._1.importSel).distinct + val importEdit = autoImportsGen.renderImports(allImports) + val exhaustive = CompletionValue.MatchCompletion( + s"case (exhaustive)", + insertText, + importEdit.toList, + s" ${printer.tpe(selTpe)} (${res.length} cases)", + ) + exhaustive :: caseItems + case _ => caseItems + end match + case None => caseItems end match - case None => caseItems - end match - end if - + end if + } + .getOrElse(Nil) end contribute /** @@ -237,14 +250,18 @@ object CaseKeywordCompletion: search: SymbolSearch, autoImportsGen: AutoImportsGenerator, noIndent: Boolean - ): List[CompletionValue] = + )(using ReportContext): List[CompletionValue] = import indexedContext.ctx val clientSupportsSnippets = config.isCompletionSnippetsEnabled() + val printer = ShortenedTypePrinter(search, IncludeDefaultParam.Never)(using indexedContext) + val completionGenerator = CompletionValueGenerator( completionPos, clientSupportsSnippets ) + + val tpeStr = printer.tpe(selector.tpe.widen.metalsDealias.bounds.hi) val tpe = selector.typeOpt.widen.metalsDealias.bounds.hi match case tr @ TypeRef(_, _) => tr.underlying case t => t @@ -290,7 +307,7 @@ object CaseKeywordCompletion: "match (exhaustive)", insertText, importEdit.toList, - s" ${tpe.typeSymbol.decodedName} (${labels.length} cases)" + s" $tpeStr (${labels.length} cases)" ) List(basicMatch, exhaustive) completions @@ -315,14 +332,13 @@ object CaseKeywordCompletion: defnSymbols.getOrElse(semancticName, -1) } - def sealedStrictDescendants(sym: Symbol)(using Context): List[Symbol] = - sym.sealedStrictDescendants - .filter(child => - !(child.is(Sealed) && (child.is(Abstract) || child.is(Trait))) - && (child.isPublic || child.isAccessibleFrom(sym.info)) && - child.name != StdNames.tpnme.LOCAL_CHILD - ) - .map(_.sourceSymbol) + def sealedDescendants(sym: Symbol)(using Context): List[Symbol] = + sym.sealedDescendants.filter(child => + !(child.is(Sealed) && (child.is(Abstract) || child.is(Trait))) + && child.maybeOwner.exists + && (child.isPublic || child.isAccessibleFrom(sym.info)) + && child.name != StdNames.tpnme.LOCAL_CHILD + ) def subclassesForType(tpe: Type)(using Context): List[Symbol] = /** @@ -345,16 +361,17 @@ object CaseKeywordCompletion: * because `A <:< (B & C) == false`. */ def isExhaustiveMember(sym: Symbol): Boolean = - val symTpe = sym.info match + sym.info match case cl: ClassInfo => - cl.parents + val parentsMerged = cl.parents .reduceLeftOption((tp1, tp2) => tp1.&(tp2)) .getOrElse(sym.info) - case simple => simple - symTpe <:< tpe + + cl.selfType <:< tpe || parentsMerged <:< tpe + case simple => simple <:< tpe val parents = getParentTypes(tpe, List.empty) - parents.toList.map(sealedStrictDescendants) match + parents.toList.map(sealedDescendants) match case Nil => Nil case subcls :: Nil => subcls case subcls => @@ -365,34 +382,6 @@ object CaseKeywordCompletion: end CaseKeywordCompletion -class Parents(val selector: Type, definitions: Definitions)(using Context): - def this(tpes: List[Type], definitions: Definitions)(using Context) = - this( - tpes match - case Nil => NoType - case head :: Nil => head - case _ => definitions.tupleType(tpes) - , - definitions - ) - - val isParent: Set[Symbol] = - Set(selector.typeSymbol, selector.typeSymbol.companion) - .filterNot(_ == NoSymbol) - val isBottom: Set[Symbol] = Set[Symbol]( - definitions.NullClass, - definitions.NothingClass - ) - def isSubClass(typeSymbol: Symbol, includeReverse: Boolean)(using - Context - ): Boolean = - !isBottom(typeSymbol) && - isParent.exists { parent => - typeSymbol.isSubClass(parent) || - (includeReverse && parent.isSubClass(typeSymbol)) - } -end Parents - class CompletionValueGenerator( completionPos: CompletionPos, clientSupportsSnippets: Boolean, diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionCaseSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionCaseSuite.scala index 7a00c0397644..521880b3a84b 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionCaseSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionCaseSuite.scala @@ -261,7 +261,7 @@ class CompletionCaseSuite extends BaseCompletionSuite: |}""".stripMargin, """|case None => scala |case Some(value) => scala - |case (exhaustive) Option (2 cases) + |case (exhaustive) Option[Int] (2 cases) |""".stripMargin ) @@ -303,7 +303,7 @@ class CompletionCaseSuite extends BaseCompletionSuite: |}""".stripMargin, """|case None => scala |case Some(value) => scala - |case (exhaustive) Option (2 cases) + |case (exhaustive) Option[Int] (2 cases) |""".stripMargin ) @@ -317,7 +317,7 @@ class CompletionCaseSuite extends BaseCompletionSuite: |}""".stripMargin, """|case None => scala |case Some(value) => scala - |case (exhaustive) Option (2 cases) + |case (exhaustive) Option[Int] (2 cases) |""".stripMargin ) @@ -531,7 +531,7 @@ class CompletionCaseSuite extends BaseCompletionSuite: |""".stripMargin ) - @Test def `private-member` = + @Test def `private-member1` = check( """ |package example @@ -724,7 +724,7 @@ class CompletionCaseSuite extends BaseCompletionSuite: |""".stripMargin, "case (Int, Int) => scala", ) - + @Test def `keyword-only` = check( """ @@ -738,3 +738,44 @@ class CompletionCaseSuite extends BaseCompletionSuite: "case", ) + @Test def `union-type` = + check( + """ + |case class Foo(a: Int) + |case class Bar(b: Int) + | + |object O { + | val x: Foo | Bar = ??? + | val y = List(x).map{ ca@@ } + |}""".stripMargin, + """|case Bar(b) => test + |case Foo(a) => test + |case (exhaustive) Foo | Bar (2 cases) + |""".stripMargin + ) + + + @Test def `union-type-edit` = + checkEdit( + """ + |case class Foo(a: Int) + |case class Bar(b: Int) + | + |object O { + | val x: Foo | Bar = ??? + | val y = List(x).map{ ca@@ } + |}""".stripMargin, + s"""|case class Foo(a: Int) + |case class Bar(b: Int) + | + |object O { + | val x: Foo | Bar = ??? + | val y = List(x).map{ + |\tcase Foo(a) => $$0 + |\tcase Bar(b) => + | } + |} + |""".stripMargin, + filter = _.contains("exhaustive") + ) + 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 c01b38e7cf61..f48ba06f699c 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionExtensionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionExtensionSuite.scala @@ -374,3 +374,44 @@ class CompletionExtensionSuite extends BaseCompletionSuite: |} |""".stripMargin ) + + @Test def `implicit-val-var` = + check( + """|package example + | + |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@@ + |""".stripMargin, + """|testArg: Int (implicit) + |testVal: Int (implicit) + |testVar: Int (implicit) + |testOps(b: Int): String (implicit) + |""".stripMargin + ) + + @Test def `implicit-val-edit` = + checkEdit( + """|package example + | + |object Test: + | implicit class TestOps(a: Int): + | val testVal: Int = 42 + | + |def main = 100.test@@ + |""".stripMargin, + """|package example + | + |import example.Test.TestOps + | + |object Test: + | implicit class TestOps(a: Int): + | val testVal: Int = 42 + | + |def main = 100.testVal + |""".stripMargin + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionMatchSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionMatchSuite.scala index 142667171c75..c818307aa7df 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionMatchSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionMatchSuite.scala @@ -19,7 +19,7 @@ class CompletionMatchSuite extends BaseCompletionSuite: | Option(1) match@@ |}""".stripMargin, """|match - |match (exhaustive) Option (2 cases) + |match (exhaustive) Option[Int] (2 cases) |""".stripMargin ) @@ -31,7 +31,7 @@ class CompletionMatchSuite extends BaseCompletionSuite: | println(1) |}""".stripMargin, """|match - |match (exhaustive) Option (2 cases) + |match (exhaustive) Option[Int] (2 cases) |""".stripMargin ) @@ -42,7 +42,7 @@ class CompletionMatchSuite extends BaseCompletionSuite: | Option(1).match@@ |}""".stripMargin, """|match - |match (exhaustive) Option (2 cases) + |match (exhaustive) Option[Int] (2 cases) |""".stripMargin ) @@ -341,7 +341,7 @@ class CompletionMatchSuite extends BaseCompletionSuite: |object A { | List(Option(1)).map{ ca@@ } |}""".stripMargin, - """|case (exhaustive) Option (2 cases) + """|case (exhaustive) Option[Int] (2 cases) |""".stripMargin, filter = _.contains("exhaustive") ) @@ -670,3 +670,42 @@ class CompletionMatchSuite extends BaseCompletionSuite: |""".stripMargin, filter = _.contains("exhaustive"), ) + + @Test def `union-type` = + check( + """ + |case class Foo(a: Int) + |case class Bar(b: Int) + | + |object O { + | val x: Foo | Bar = ??? + | val y = x match@@ + |}""".stripMargin, + """|match + |match (exhaustive) Foo | Bar (2 cases) + |""".stripMargin + ) + + @Test def `union-type-edit` = + checkEdit( + """ + |case class Foo(a: Int) + |case class Bar(b: Int) + | + |object O { + | val x: Foo | Bar = ??? + | val y = x match@@ + |}""".stripMargin, + s"""|case class Foo(a: Int) + |case class Bar(b: Int) + | + |object O { + | val x: Foo | Bar = ??? + | val y = x match + |\tcase Foo(a) => $$0 + |\tcase Bar(b) => + | + |} + |""".stripMargin, + filter = _.contains("exhaustive") + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionWorkspaceSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionWorkspaceSuite.scala index 82085932247d..52e565a5a78b 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionWorkspaceSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionWorkspaceSuite.scala @@ -887,3 +887,52 @@ class CompletionWorkspaceSuite extends BaseCompletionSuite: """|method - demo.O(i: Int): Int |""".stripMargin ) + + @Test def `implicit-class-val` = + check( + """|package demo + | + |object O { + | implicit class CursorOps(val bar: Int) + |} + | + |object Main { + | val x = bar@@ + |} + |""".stripMargin, + "" + ) + + @Test def `implicit-class-def` = + check( + """|package demo + | + |object O { + | implicit class CursorOps(val bar: Int) { + | def fooBar = 42 + | } + |} + | + |object Main { + | val x = fooB@@ + |} + |""".stripMargin, + "" + ) + + @Test def `extension-method` = + check( + """|package demo + | + |object O { + | extension (bar: Int) { + | def fooBar = 42 + | } + |} + | + |object Main { + | val x = fooB@@ + |} + |""".stripMargin, + "" + )