diff --git a/presentation-compiler/src/main/dotty/tools/pc/ExtractMethodProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/ExtractMethodProvider.scala index 4416d0c0d000..c72a0602f1ce 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/ExtractMethodProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/ExtractMethodProvider.scala @@ -13,6 +13,7 @@ import dotty.tools.dotc.ast.Trees.* import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.ast.tpd.DeepFolder import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.core.Flags import dotty.tools.dotc.core.Symbols.Symbol import dotty.tools.dotc.core.Types.MethodType import dotty.tools.dotc.core.Types.PolyType @@ -116,9 +117,15 @@ final class ExtractMethodProvider( typeParams.toList.sortBy(_.decodedName), ) end localRefs + val optEnclosing = + path.dropWhile(src => !src.sourcePos.encloses(range)) match + case Nil => None + case _ :: (app @ Apply(fun, args)) :: _ if args.exists(ImplicitParameters.isSyntheticArg(_)) => Some(app) + case found :: _ => Some(found) + val edits = for - enclosing <- path.find(src => src.sourcePos.encloses(range)) + enclosing <- optEnclosing extracted = extractFromBlock(enclosing) head <- extracted.headOption expr <- extracted.lastOption @@ -131,11 +138,14 @@ final class ExtractMethodProvider( val exprType = prettyPrint(expr.typeOpt.widen) val name = genName(indexedCtx.scopeSymbols.map(_.decodedName).toSet, "newMethod") - val (methodParams, typeParams) = + val (allMethodParams, typeParams) = localRefs(extracted, stat.sourcePos, extractedPos) - val methodParamsText = methodParams - .map(sym => s"${sym.decodedName}: ${prettyPrint(sym.info)}") - .mkString(", ") + val (methodParams, implicitParams) = allMethodParams.partition(!_.isOneOf(Flags.GivenOrImplicit)) + def toParamText(params: List[Symbol]) = + params.map(sym => s"${sym.decodedName}: ${prettyPrint(sym.info)}") + .mkString(", ") + val methodParamsText = toParamText(methodParams) + val implicitParamsText = if implicitParams.nonEmpty then s"(given ${toParamText(implicitParams)})" else "" val typeParamsText = typeParams .map(_.decodedName) match case Nil => "" @@ -155,7 +165,7 @@ final class ExtractMethodProvider( if noIndent && extracted.length > 1 then (" {", s"$newIndent}") else ("", "") val defText = - s"def $name$typeParamsText($methodParamsText): $exprType =$obracket\n${toExtract}\n$cbracket\n$newIndent" + s"def $name$typeParamsText($methodParamsText)$implicitParamsText: $exprType =$obracket\n${toExtract}\n$cbracket\n$newIndent" val replacedText = s"$name($exprParamsText)" List( new l.TextEdit( diff --git a/presentation-compiler/src/main/dotty/tools/pc/IndexedContext.scala b/presentation-compiler/src/main/dotty/tools/pc/IndexedContext.scala index 6b74e3aa2ec1..7c2c34cf5ebb 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/IndexedContext.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/IndexedContext.scala @@ -36,8 +36,8 @@ sealed trait IndexedContext: Result.InScope // when all the conflicting symbols came from an old version of the file case Some(symbols) if symbols.nonEmpty && symbols.forall(_.isStale) => Result.Missing - case Some(_) => Result.Conflict - case None => Result.Missing + case Some(symbols) if symbols.exists(rename(_).isEmpty) => Result.Conflict + case _ => Result.Missing end lookupSym /** diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala index c4fdb97c0418..b3f836801460 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala @@ -3,6 +3,8 @@ package dotty.tools.pc import java.nio.file.Paths +import scala.annotation.tailrec + import scala.meta.internal.metals.ReportContext import dotty.tools.pc.utils.InteractiveEnrichments.* import dotty.tools.pc.printer.ShortenedTypePrinter @@ -194,10 +196,10 @@ object ImplicitConversion: def unapply(tree: Tree)(using params: InlayHintsParams, ctx: Context) = if (params.implicitConversions()) { tree match - case Apply(fun: Ident, args) if isSynthetic(fun) => + case Apply(fun: Ident, args) if isSynthetic(fun) && args.exists(!_.span.isZeroExtent) => implicitConversion(fun, args) case Apply(Select(fun, name), args) - if name == nme.apply && isSynthetic(fun) => + if name == nme.apply && isSynthetic(fun) && args.exists(!_.span.isZeroExtent) => implicitConversion(fun, args) case _ => None } else None @@ -218,7 +220,7 @@ object ImplicitParameters: if (params.implicitParameters()) { tree match case Apply(fun, args) - if args.exists(isSyntheticArg) && !tree.sourcePos.span.isZeroExtent => + if args.exists(isSyntheticArg) && !tree.sourcePos.span.isZeroExtent && !args.exists(isQuotes(_)) => val (implicitArgs, providedArgs) = args.partition(isSyntheticArg) val allImplicit = providedArgs.isEmpty || providedArgs.forall { case Ident(name) => name == nme.MISSING @@ -229,10 +231,12 @@ object ImplicitParameters: case _ => None } else None - private def isSyntheticArg(tree: Tree)(using Context) = tree match + @tailrec + def isSyntheticArg(tree: Tree)(using Context): Boolean = tree match case tree: Ident => - tree.span.isSynthetic && tree.symbol.isOneOf(Flags.GivenOrImplicit) && - !isQuotes(tree) + tree.span.isSynthetic && tree.symbol.isOneOf(Flags.GivenOrImplicit) + case Apply(fun, _ ) if tree.span.isZeroExtent => isSyntheticArg(fun) + case TypeApply(fun, _ ) if tree.span.isZeroExtent => isSyntheticArg(fun) case _ => false // Decorations for Quotes are rarely useful diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala index df0bb70b596c..1e310ca0e8ec 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala @@ -279,7 +279,14 @@ object OverrideCompletions: else "" (indent, indent, lastIndent) end calcIndent - val abstractMembers = defn.typeOpt.abstractTermMembers.map(_.symbol) + val abstractMembers = + defn.tpe.abstractTermMembers.map(_.symbol).groupBy(_.owner).map { + case (owner, members) => (owner, members.sortWith{ (sym1, sym2) => + if(sym1.sourcePos.exists && sym2.sourcePos.exists) + sym1.sourcePos.start <= sym2.sourcePos.start + else !sym2.sourcePos.exists + }) + }.toSeq.sortBy(_._1.name.decoded).flatMap(_._2) val caseClassOwners = Set("Product", "Equals") val overridables = @@ -506,6 +513,8 @@ object OverrideCompletions: defn match case td: TypeDef if text.charAt(td.rhs.span.end) == ':' => Some(td.rhs.span.end) + case TypeDef(_, temp : Template) => + temp.parentsOrDerived.lastOption.map(_.span.end).filter(text.charAt(_) == ':') case _ => None private def fallbackFromParent(parent: Tree, name: String)(using Context) = diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala index 03c4fa2bc5bc..f660baa6af6d 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala @@ -1952,3 +1952,34 @@ class CompletionSuite extends BaseCompletionSuite: """TestEnum test |""".stripMargin, ) + + @Test def `i6477-1` = + checkEdit( + """|package a + |import a.b.SomeClass as SC + | + |package b { + | class SomeClass + |} + |package c { + | class SomeClass + |} + | + |val bar: SC = ??? + |val foo: SomeClass@@ + |""".stripMargin, + """|package a + |import a.b.SomeClass as SC + |import a.c.SomeClass + | + |package b { + | class SomeClass + |} + |package c { + | class SomeClass + |} + | + |val bar: SC = ??? + |val foo: SomeClass + |""".stripMargin, + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/edit/AutoImplementAbstractMembersSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/edit/AutoImplementAbstractMembersSuite.scala index 04c3f8a018e9..ffe4e293ba30 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/edit/AutoImplementAbstractMembersSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/edit/AutoImplementAbstractMembersSuite.scala @@ -345,10 +345,10 @@ class AutoImplementAbstractMembersSuite extends BaseCodeActionSuite: |object Main { | class Baz extends Bar { | - | override def foo: Int = ??? - | | override def bar: Int = ??? | + | override def foo: Int = ??? + | | } |} |""".stripMargin @@ -1243,7 +1243,6 @@ class AutoImplementAbstractMembersSuite extends BaseCodeActionSuite: | |object A { | trait Base: - | def foo(x: Int): Int | def bar(x: String): String | | class <>(x: Int, y: String) extends Base: @@ -1256,13 +1255,10 @@ class AutoImplementAbstractMembersSuite extends BaseCodeActionSuite: | |object A { | trait Base: - | def foo(x: Int): Int | def bar(x: String): String | | class Concrete(x: Int, y: String) extends Base: | - | override def foo(x: Int): Int = ??? - | | override def bar(x: String): String = ??? | | @@ -1272,6 +1268,35 @@ class AutoImplementAbstractMembersSuite extends BaseCodeActionSuite: |""".stripMargin, ) + @Test def `braceless-case-class` = + checkEdit( + """|package a + | + |trait Base: + | def foo(x: Int): Int + | def bar(x: String): String + | + |case class <>() extends Base: + | def aaa = "aaa" + |end Concrete + |""".stripMargin, + """|package a + | + |trait Base: + | def foo(x: Int): Int + | def bar(x: String): String + | + |case class Concrete() extends Base: + | + | override def foo(x: Int): Int = ??? + | + | override def bar(x: String): String = ??? + | + | def aaa = "aaa" + |end Concrete + |""".stripMargin + ) + def checkEdit( original: String, expected: String diff --git a/presentation-compiler/test/dotty/tools/pc/tests/edit/AutoImportsSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/edit/AutoImportsSuite.scala index a862df975d0b..ce5ae4a1cca4 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/edit/AutoImportsSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/edit/AutoImportsSuite.scala @@ -405,6 +405,37 @@ class AutoImportsSuite extends BaseAutoImportsSuite: |""".stripMargin, ) + @Test def `i6477` = + checkEdit( + """|package a + |import a.b.SomeClass as SC + | + |package b { + | class SomeClass + |} + |package c { + | class SomeClass + |} + | + |val bar: SC = ??? + |val foo: <> = ??? + |""".stripMargin, + """|package a + |import a.b.SomeClass as SC + |import a.c.SomeClass + | + |package b { + | class SomeClass + |} + |package c { + | class SomeClass + |} + | + |val bar: SC = ??? + |val foo: SomeClass = ??? + |""".stripMargin + ) + private def ammoniteWrapper(code: String): String = // Vaguely looks like a scala file that Ammonite generates // from a sc file. diff --git a/presentation-compiler/test/dotty/tools/pc/tests/edit/ExtractMethodSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/edit/ExtractMethodSuite.scala index 2bb896660123..bc8b91fed5e8 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/edit/ExtractMethodSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/edit/ExtractMethodSuite.scala @@ -446,3 +446,95 @@ class ExtractMethodSuite extends BaseExtractMethodSuite: | } |}""".stripMargin ) + + @Test def `i6476` = + checkEdit( + """|object O { + | class C + | def foo(i: Int)(implicit o: C) = i + | + | @@val o = { + | implicit val c = new C + | <> + | ??? + | } + |} + |""".stripMargin, + """|object O { + | class C + | def foo(i: Int)(implicit o: C) = i + | + | def newMethod()(given c: C): Int = + | foo(2) + | + | val o = { + | implicit val c = new C + | newMethod() + | ??? + | } + |} + |""".stripMargin + ) + + + @Test def `i6476-2` = + checkEdit( + """|object O { + | class C + | def foo(i: Int)(implicit o: C) = i + | + | @@val o = { + | <> + | ??? + | } + |} + |""".stripMargin, + """|object O { + | class C + | def foo(i: Int)(implicit o: C) = i + | + | def newMethod(): Int = + | foo(2)(new C) + | + | val o = { + | newMethod() + | ??? + | } + |} + |""".stripMargin + ) + + @Test def `i6476-3` = + checkEdit( + """|object O { + | class C + | class D + | def foo(i: Int)(using o: C)(x: Int)(using d: D) = i + | + | @@val o = { + | given C = new C + | given D = new D + | val w = 2 + | <> + | ??? + | } + |} + |""".stripMargin, + """|object O { + | class C + | class D + | def foo(i: Int)(using o: C)(x: Int)(using d: D) = i + | + | def newMethod(w: Int)(given given_C: C, given_D: D): Int = + | foo(w)(w) + | + | val o = { + | given C = new C + | given D = new D + | val w = 2 + | newMethod(w) + | ??? + | } + |} + |""".stripMargin + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/InlayHintsSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/InlayHintsSuite.scala index 8ce7cdce4382..fac30bc757b7 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/InlayHintsSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/InlayHintsSuite.scala @@ -920,4 +920,24 @@ class InlayHintsSuite extends BaseInlayHintsSuite { | case '[field *: fields] => ??? |""".stripMargin ) + + @Test def `arg-apply` = + check( + """|object Main: + | case class A() + | case class B[T]() + | given A = A() + | implicit def bar(using a: A): B[A] = B[A]() + | def foo(using b: B[A]): String = "aaa" + | val g: String = foo + |""".stripMargin, + """|object Main: + | case class A() + | case class B[T]() + | given A = A() + | implicit def bar(using a: A): B[A] = B[A]() + | def foo(using b: B[A]): String = "aaa" + | val g: String = foo/*(using bar<<(5:15)>>)*/ + |""".stripMargin + ) }