diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcCollector.scala b/presentation-compiler/src/main/dotty/tools/pc/PcCollector.scala index cf9e31cd4524..89bfd05a7386 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcCollector.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcCollector.scala @@ -36,6 +36,7 @@ abstract class PcCollector[T]( val uri = params.uri().nn val filePath = Paths.get(uri).nn val sourceText = params.text().nn + val text = sourceText.toCharArray().nn val source = SourceFile.virtual(filePath.toString(), sourceText) driver.run(uri, source) @@ -70,45 +71,6 @@ abstract class PcCollector[T]( parent: Option[Tree] )(tree: Tree| EndMarker, pos: SourcePosition, symbol: Option[Symbol]): T - /** - * @return (adjusted position, should strip backticks) - */ - def adjust( - pos1: SourcePosition, - forRename: Boolean = false - ): (SourcePosition, Boolean) = - if !pos1.span.isCorrect then (pos1, false) - else - val pos0 = - val span = pos1.span - if span.exists && span.point > span.end then - pos1.withSpan( - span - .withStart(span.point) - .withEnd(span.point + (span.end - span.start)) - ) - else pos1 - - val pos = - if pos0.end > 0 && sourceText(pos0.end - 1) == ',' then - pos0.withEnd(pos0.end - 1) - else pos0 - val isBackticked = - sourceText(pos.start) == '`' && - pos.end > 0 && - sourceText(pos.end - 1) == '`' - // when the old name contains backticks, the position is incorrect - val isOldNameBackticked = sourceText(pos.start) != '`' && - pos.start > 0 && - sourceText(pos.start - 1) == '`' && - sourceText(pos.end) == '`' - if isBackticked && forRename then - (pos.withStart(pos.start + 1).withEnd(pos.end - 1), true) - else if isOldNameBackticked then - (pos.withStart(pos.start - 1).withEnd(pos.end + 1), false) - else (pos, false) - end adjust - def symbolAlternatives(sym: Symbol) = def member(parent: Symbol) = parent.info.member(sym.name).symbol def primaryConstructorTypeParam(owner: Symbol) = @@ -447,7 +409,7 @@ abstract class PcCollector[T]( */ case sel: Select if sel.span.isCorrect && filter(sel) && - !isForComprehensionMethod(sel) => + !sel.isForComprehensionMethod => occurrences + collect( sel, pos.withSpan(selectNameSpan(sel)) @@ -602,17 +564,6 @@ abstract class PcCollector[T]( Span(span.start, span.start + realName.length, point) else Span(point, span.end, point) else span - - private val forCompMethods = - Set(nme.map, nme.flatMap, nme.withFilter, nme.foreach) - - // We don't want to collect synthethic `map`, `withFilter`, `foreach` and `flatMap` in for-comprenhensions - private def isForComprehensionMethod(sel: Select): Boolean = - val syntheticName = sel.name match - case name: TermName => forCompMethods(name) - case _ => false - val wrongSpan = sel.qualifier.span.contains(sel.nameSpan) - syntheticName && wrongSpan end PcCollector object PcCollector: diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcDocumentHighlightProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/PcDocumentHighlightProvider.scala index aeb9480930f9..cd4f4919b1ef 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcDocumentHighlightProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcDocumentHighlightProvider.scala @@ -23,7 +23,7 @@ final class PcDocumentHighlightProvider( toAdjust: SourcePosition, sym: Option[Symbol] ): DocumentHighlight = - val (pos, _) = adjust(toAdjust) + val (pos, _) = toAdjust.adjust(text) tree match case _: NamedDefTree => DocumentHighlight(pos.toLsp, DocumentHighlightKind.Write) diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcInlineValueProviderImpl.scala b/presentation-compiler/src/main/dotty/tools/pc/PcInlineValueProviderImpl.scala index e591f89f0152..39365475a075 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcInlineValueProviderImpl.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcInlineValueProviderImpl.scala @@ -27,8 +27,6 @@ final class PcInlineValueProviderImpl( ) extends PcCollector[Option[Occurence]](driver, params) with InlineValueProvider: - val text = params.text().nn.toCharArray().nn - val position: l.Position = pos.toLsp.getStart().nn override def collect(parent: Option[Tree])( @@ -38,7 +36,7 @@ final class PcInlineValueProviderImpl( ): Option[Occurence] = tree match case tree: Tree => - val (adjustedPos, _) = adjust(pos) + val (adjustedPos, _) = pos.adjust(text) Some(Occurence(tree, parent, adjustedPos)) case _ => None diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcRenameProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/PcRenameProvider.scala index 56924f3cfded..8a441e1e385a 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcRenameProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcRenameProvider.scala @@ -35,7 +35,7 @@ final class PcRenameProvider( def collect( parent: Option[Tree] )(tree: Tree | EndMarker, toAdjust: SourcePosition, sym: Option[Symbol]): l.TextEdit = - val (pos, stripBackticks) = adjust(toAdjust, forRename = true) + val (pos, stripBackticks) = toAdjust.adjust(text, forRename = true) l.TextEdit( pos.toLsp, if stripBackticks then newName.stripBackticks else newName diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcSemanticTokensProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/PcSemanticTokensProvider.scala index 92efcb034564..c9af3a5f671a 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcSemanticTokensProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcSemanticTokensProvider.scala @@ -74,7 +74,7 @@ final class PcSemanticTokensProvider( Some( makeNode( sym = sym, - pos = adjust(pos)._1, + pos = pos.adjust(text)._1, isDefinition = isDefinition(tree), isDeclaration = isDeclaration(tree) ) diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcSyntheticDecorationProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/PcSyntheticDecorationProvider.scala new file mode 100644 index 000000000000..62c24dcc7990 --- /dev/null +++ b/presentation-compiler/src/main/dotty/tools/pc/PcSyntheticDecorationProvider.scala @@ -0,0 +1,266 @@ +package dotty.tools.pc + + +import java.nio.file.Paths + +import scala.meta.internal.metals.ReportContext +import dotty.tools.pc.utils.MtagsEnrichments.* +import dotty.tools.pc.printer.ShortenedTypePrinter +import scala.meta.pc.SymbolSearch +import scala.meta.pc.SyntheticDecoration +import scala.meta.pc.SyntheticDecorationsParams +import scala.meta.internal.pc.DecorationKind +import scala.meta.internal.pc.Decoration + + +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.ast.tpd.* +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Flags +import dotty.tools.dotc.core.StdNames.* +import dotty.tools.dotc.core.Types.* +import dotty.tools.dotc.interactive.Interactive +import dotty.tools.dotc.interactive.InteractiveDriver +import dotty.tools.dotc.util.SourceFile +import dotty.tools.dotc.util.SourcePosition +import dotty.tools.dotc.util.Spans.Span +import dotty.tools.pc.IndexedContext + +final class PcSyntheticDecorationsProvider( + driver: InteractiveDriver, + params: SyntheticDecorationsParams, + symbolSearch: SymbolSearch, +)(using ReportContext): + + val uri = params.uri().nn + val filePath = Paths.get(uri).nn + val sourceText = params.text().nn + val text = sourceText.toCharArray().nn + val source = + SourceFile.virtual(filePath.toString, sourceText) + driver.run(uri, source) + given ctx: Context = driver.currentCtx + val unit = driver.currentCtx.run.nn.units.head + + def tpdTree = unit.tpdTree + + def provide(): List[SyntheticDecoration] = + val deepFolder = DeepFolder[Synthetics](collectDecorations) + deepFolder(Synthetics.empty, tpdTree).decorations + + def collectDecorations( + decorations: Synthetics, + tree: Tree, + ): Synthetics = + tree match + case ImplicitConversion(name, range) if params.implicitConversions() => + val adjusted = range.adjust(text)._1 + decorations + .add( + Decoration( + adjusted.startPos.toLsp, + name + "(", + DecorationKind.ImplicitConversion, + ) + ) + .add( + Decoration( + adjusted.endPos.toLsp, + ")", + DecorationKind.ImplicitConversion, + ) + ) + case ImplicitParameters(names, pos, allImplicit) + if params.implicitParameters() => + val label = + if allImplicit then names.mkString("(", ", ", ")") + else names.mkString(", ", ", ", "") + decorations.add( + Decoration( + pos.adjust(text)._1.toLsp, + label, + DecorationKind.ImplicitParameter, + ) + ) + case TypeParameters(tpes, pos, sel) + if params.typeParameters() && !syntheticTupleApply(sel) => + val label = tpes.map(toLabel(_, pos)).mkString("[", ", ", "]") + decorations.add( + Decoration( + pos.adjust(text)._1.endPos.toLsp, + label, + DecorationKind.TypeParameter, + ) + ) + case InferredType(tpe, pos, defTree) if params.inferredTypes() => + val adjustedPos = pos.adjust(text)._1.endPos + if decorations.containsDef(adjustedPos.start) then decorations + else + decorations.add( + Decoration( + adjustedPos.toLsp, + ": " + toLabel(tpe, pos), + DecorationKind.InferredType, + ), + adjustedPos.start, + ) + case _ => decorations + + private def toLabel( + tpe: Type, + pos: SourcePosition, + ): String = + val tpdPath = + Interactive.pathTo(unit.tpdTree, pos.span) + + val indexedCtx = IndexedContext(Interactive.contextOfPath(tpdPath)) + val printer = ShortenedTypePrinter( + symbolSearch + )(using indexedCtx) + def optDealias(tpe: Type): Type = + def isInScope(tpe: Type): Boolean = + tpe match + case tref: TypeRef => + indexedCtx.lookupSym( + tref.currentSymbol + ) == IndexedContext.Result.InScope + case AppliedType(tycon, args) => + isInScope(tycon) && args.forall(isInScope) + case _ => true + if isInScope(tpe) + then tpe + else tpe.metalsDealias(using indexedCtx.ctx) + + val dealiased = optDealias(tpe) + printer.tpe(dealiased) + end toLabel + + private val definitions = IndexedContext(ctx).ctx.definitions + private def syntheticTupleApply(tree: Tree): Boolean = + tree match + case sel: Select => + if definitions.isTupleNType(sel.symbol.info.finalResultType) then + sel match + case Select(tupleClass: Ident, _) + if !tupleClass.span.isZeroExtent && + tupleClass.span.exists && + tupleClass.name.startsWith("Tuple") => + val pos = tupleClass.sourcePos + !sourceText.slice(pos.start, pos.end).mkString.startsWith("Tuple") + case _ => true + else false + case _ => false +end PcSyntheticDecorationsProvider + +object ImplicitConversion: + def unapply(tree: Tree)(using Context) = + tree match + case Apply(fun: Ident, args) if isSynthetic(fun) => + implicitConversion(fun, args) + case Apply(Select(fun, name), args) + if name == nme.apply && isSynthetic(fun) => + implicitConversion(fun, args) + case _ => None + private def isSynthetic(tree: Tree)(using Context) = + tree.span.isSynthetic && tree.symbol.isOneOf(Flags.GivenOrImplicit) + + private def implicitConversion(fun: Tree, args: List[Tree])(using Context) = + val lastArgPos = + args.lastOption.map(_.sourcePos).getOrElse(fun.sourcePos) + Some( + fun.symbol.decodedName, + lastArgPos.withStart(fun.sourcePos.start), + ) +end ImplicitConversion + +object ImplicitParameters: + def unapply(tree: Tree)(using Context) = + tree match + case Apply(fun, args) + if args.exists(isSyntheticArg) && !tree.sourcePos.span.isZeroExtent => + val (implicitArgs, providedArgs) = args.partition(isSyntheticArg) + val allImplicit = providedArgs.isEmpty + val pos = implicitArgs.head.sourcePos + Some(implicitArgs.map(_.symbol.decodedName), pos, allImplicit) + case Apply(ta @ TypeApply(fun, _), _) + if fun.span.isSynthetic && isValueOf(fun) => + Some( + List("new " + tpnme.valueOf.decoded.capitalize + "(...)"), + fun.sourcePos, + true, + ) + case _ => None + private def isValueOf(tree: Tree)(using Context) = + val symbol = tree.symbol.maybeOwner + symbol.name.decoded == tpnme.valueOf.decoded.capitalize + private def isSyntheticArg(tree: Tree)(using Context) = tree match + case tree: Ident => + tree.span.isSynthetic && tree.symbol.isOneOf(Flags.GivenOrImplicit) + case _ => false +end ImplicitParameters + +object TypeParameters: + def unapply(tree: Tree)(using Context) = + tree match + case TypeApply(sel: Select, _) if sel.isForComprehensionMethod => None + case TypeApply(fun, args) if inferredTypeArgs(args) => + val pos = fun match + case sel: Select if sel.isInfix => + sel.sourcePos.withEnd(sel.nameSpan.end) + case _ => fun.sourcePos + val tpes = args.map(_.tpe.stripTypeVar.widen.finalResultType) + Some((tpes, pos.endPos, fun)) + case _ => None + private def inferredTypeArgs(args: List[Tree]): Boolean = + args.forall { + case tt: TypeTree if tt.span.exists && !tt.span.isZeroExtent => true + case _ => false + } +end TypeParameters + +object InferredType: + def unapply(tree: Tree)(using Context) = + tree match + case vd @ ValDef(_, tpe, _) + if isValidSpan(tpe.span, vd.nameSpan) && + !vd.symbol.is(Flags.Enum) => + if vd.symbol == vd.symbol.sourceSymbol then + Some(tpe.tpe, tpe.sourcePos.withSpan(vd.nameSpan), vd) + else None + case vd @ DefDef(_, _, tpe, _) + if isValidSpan(tpe.span, vd.nameSpan) && + tpe.span.start >= vd.nameSpan.end && + !vd.symbol.isConstructor && + !vd.symbol.is(Flags.Mutable) => + if vd.symbol == vd.symbol.sourceSymbol then + Some(tpe.tpe, tpe.sourcePos, vd) + else None + case bd @ Bind( + name, + Ident(nme.WILDCARD), + ) => + Some(bd.symbol.info, bd.namePos, bd) + case _ => None + + private def isValidSpan(tpeSpan: Span, nameSpan: Span): Boolean = + tpeSpan.isZeroExtent && + nameSpan.exists && + !nameSpan.isZeroExtent + +end InferredType + +case class Synthetics( + decorations: List[Decoration], + definitions: Set[Int], +): + def containsDef(offset: Int) = definitions(offset) + def add(decoration: Decoration, offset: Int) = + copy( + decorations = decoration :: decorations, + definitions = definitions + offset, + ) + def add(decoration: Decoration) = + copy(decorations = decoration :: decorations) + +object Synthetics: + def empty: Synthetics = Synthetics(Nil, Set.empty) \ No newline at end of file diff --git a/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala b/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala index 4dc9cd6d743f..87b67015f9e4 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala @@ -106,6 +106,19 @@ case class ScalaPresentationCompiler( new PcSemanticTokensProvider(driver, params).provide().asJava } + override def syntheticDecorations( + params: SyntheticDecorationsParams + ): ju.concurrent.CompletableFuture[ju.List[SyntheticDecoration]] = + compilerAccess.withInterruptableCompiler(Some(params))( + new ju.ArrayList[SyntheticDecoration](), + params.token(), + ) { access => + val driver = access.compiler() + new PcSyntheticDecorationsProvider(driver, params, search) + .provide() + .asJava + } + override def getTasty( targetUri: URI, isHttpEnabled: Boolean diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala index 54325c89945b..ef698c50d6c2 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala @@ -38,7 +38,7 @@ object NamedArgCompletions: )(using ctx: Context): List[CompletionValue] = path match case (ident: Ident) :: ValDef(_, _, _) :: Block(_, app: Apply) :: _ - if !isInfix(pos, app) => + if !app.fun.isInfix => contribute( Some(ident), app, @@ -58,7 +58,7 @@ object NamedArgCompletions: val contribution = for app <- getApplyForContextFunctionParam(rest) - if !isInfix(pos, app) + if !app.fun.isInfix yield contribute( Some(ident), app, @@ -71,18 +71,6 @@ object NamedArgCompletions: end match end contribute - private def isInfix(pos: SourcePosition, apply: Apply)(using ctx: Context) = - apply.fun match - case Select(New(_), _) => false - case Select(_, name) if name.decoded == "apply" => false - case Select(This(_), _) => false - // is a select statement without a dot `qual.name` - case sel @ Select(qual, _) if !sel.symbol.is(Flags.Synthetic) => - !(qual.span.end until sel.nameSpan.start) - .map(pos.source.apply) - .contains('.') - case _ => false - private def contribute( ident: Option[Ident], apply: Apply, diff --git a/presentation-compiler/src/main/dotty/tools/pc/utils/MtagsEnrichments.scala b/presentation-compiler/src/main/dotty/tools/pc/utils/MtagsEnrichments.scala index 337e0790b738..c006dda2c652 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/utils/MtagsEnrichments.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/utils/MtagsEnrichments.scala @@ -108,8 +108,51 @@ object MtagsEnrichments extends CommonMtagsEnrichments: def encloses(other: RangeParams): Boolean = pos.start <= other.offset() && pos.end >= other.endOffset() + + /** + * @return (adjusted position, should strip backticks) + */ + def adjust( + text: Array[Char], + forRename: Boolean = false, + )(using Context): (SourcePosition, Boolean) = + if !pos.span.isCorrect(text) then (pos, false) + else + val pos0 = + val span = pos.span + if span.exists && span.point > span.end then + pos.withSpan( + span + .withStart(span.point) + .withEnd(span.point + (span.end - span.start)) + ) + else pos + + val pos1 = + if pos0.end > 0 && text(pos0.end - 1) == ',' then + pos0.withEnd(pos0.end - 1) + else pos0 + val isBackticked = + text(pos1.start) == '`' && + pos1.end > 0 && + text(pos1.end - 1) == '`' + // when the old name contains backticks, the position is incorrect + val isOldNameBackticked = text(pos1.start) != '`' && + pos1.start > 0 && + text(pos1.start - 1) == '`' && + text(pos1.end) == '`' + if isBackticked && forRename then + (pos1.withStart(pos1.start + 1).withEnd(pos1.end - 1), true) + else if isOldNameBackticked then + (pos1.withStart(pos1.start - 1).withEnd(pos1.end + 1), false) + else (pos1, false) + end adjust end extension + extension (span: Span) + def isCorrect(text: Array[Char]): Boolean = + !span.isZeroExtent && span.exists && span.start < text.size && span.end <= text.size + extension (pos: RangeParams) def encloses(other: SourcePosition): Boolean = pos.offset() <= other.start && pos.endOffset() >= other.end @@ -213,6 +256,8 @@ object MtagsEnrichments extends CommonMtagsEnrichments: end symbolDocumentation end extension + private val infixNames = + Set(nme.apply, nme.unapply, nme.unapplySeq) extension (tree: Tree) def qual: Tree = tree match @@ -228,6 +273,19 @@ object MtagsEnrichments extends CommonMtagsEnrichments: val denot = sym.denot.asSeenFrom(pre.tpe.widenTermRefExpr) (denot.info, sym.withUpdatedTpe(denot.info)) catch case NonFatal(e) => (sym.info, sym) + + def isInfix(using ctx: Context) = + tree match + case Select(New(_), _) => false + case Select(_, name: TermName) if infixNames(name) => false + case Select(This(_), _) => false + // is a select statement without a dot `qual.name` + case sel @ Select(qual, _) if !sel.symbol.is(Synthetic) => + val source = tree.source + !(qual.span.end until sel.nameSpan.start) + .map(source.apply) + .contains('.') + case _ => false end extension extension (imp: Import) @@ -235,6 +293,16 @@ object MtagsEnrichments extends CommonMtagsEnrichments: for sel <- imp.selectors.find(_.span.contains(span)) yield imp.expr.symbol.info.member(sel.name).symbol + private val forCompMethods = + Set(nme.map, nme.flatMap, nme.withFilter, nme.foreach) + extension (sel: Select) + def isForComprehensionMethod(using Context): Boolean = + val syntheticName = sel.name match + case name: TermName => forCompMethods(name) + case _ => false + val wrongSpan = sel.qualifier.span.contains(sel.nameSpan) + syntheticName && wrongSpan + extension (denot: Denotation) def allSymbols: List[Symbol] = denot match diff --git a/presentation-compiler/test/dotty/tools/pc/base/BaseSyntheticDecorationsSuite.scala b/presentation-compiler/test/dotty/tools/pc/base/BaseSyntheticDecorationsSuite.scala new file mode 100644 index 000000000000..450a42f26153 --- /dev/null +++ b/presentation-compiler/test/dotty/tools/pc/base/BaseSyntheticDecorationsSuite.scala @@ -0,0 +1,60 @@ +package dotty.tools.pc.base + +import java.net.URI + +import scala.meta.internal.jdk.CollectionConverters._ +import scala.meta.internal.metals.CompilerSyntheticDecorationsParams +import scala.meta.internal.metals.CompilerVirtualFileParams +import scala.language.unsafeNulls + +import dotty.tools.pc.utils.TextEdits + +import org.eclipse.lsp4j.TextEdit + +class BaseSyntheticDecorationsSuite extends BasePCSuite { + + def check( + base: String, + expected: String, + kind: Option[Int] = None, + ): Unit = + def pkgWrap(text: String) = + if (text.contains("package")) text + else s"package test\n$text" + + val withPkg = pkgWrap(base) + val vFile = CompilerVirtualFileParams( + URI.create("file:/Decorations.scala"), + withPkg, + ) + + val pcParams = CompilerSyntheticDecorationsParams( + vFile, + true, + true, + true, + true, + ) + + val allDecorations = presentationCompiler + .syntheticDecorations( + pcParams + ) + .get() + .asScala + .toList + + val decorations = kind match { + case Some(k) => allDecorations.filter(_.kind == k) + case _ => allDecorations + } + + val edits = decorations.map(d => new TextEdit(d.range(), d.label())) + val obtained = TextEdits.applyEdits(withPkg, edits) + + assertNoDiff( + obtained, + pkgWrap(expected), + ) + +} \ No newline at end of file diff --git a/presentation-compiler/test/dotty/tools/pc/tests/decorations/SyntheticDecorationsSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/decorations/SyntheticDecorationsSuite.scala new file mode 100644 index 000000000000..49ac1478cd35 --- /dev/null +++ b/presentation-compiler/test/dotty/tools/pc/tests/decorations/SyntheticDecorationsSuite.scala @@ -0,0 +1,486 @@ +package dotty.tools.pc.tests.decorations + +import scala.meta.internal.pc.DecorationKind + +import dotty.tools.pc.base.BaseSyntheticDecorationsSuite + +import org.junit.Test + +class SyntheticDecorationsSuite extends BaseSyntheticDecorationsSuite: + + @Test def `type-params` = + check( + """|object Main { + | def hello[T](t: T) = t + | val x = hello(List(1)) + |} + |""".stripMargin, + """|object Main { + | def hello[T](t: T) = t + | val x = hello[List[Int]](List[Int](1)) + |} + |""".stripMargin, + kind = Some(DecorationKind.TypeParameter) + ) + + @Test def `type-params2` = + check( + """|object Main { + | def hello[T](t: T) = t + | val x = hello(Map((1,"abc"))) + |} + |""".stripMargin, + """|object Main { + | def hello[T](t: T) = t + | val x = hello[Map[Int, String]](Map[Int, String]((1,"abc"))) + |} + |""".stripMargin, + kind = Some(DecorationKind.TypeParameter) + ) + + @Test def `implicit-param` = + check( + """|case class User(name: String) + |object Main { + | implicit val imp: Int = 2 + | def addOne(x: Int)(implicit one: Int) = x + one + | val x = addOne(1) + |} + |""".stripMargin, + """|case class User(name: String) + |object Main { + | implicit val imp: Int = 2 + | def addOne(x: Int)(implicit one: Int) = x + one + | val x = addOne(1)(imp) + |} + |""".stripMargin, + kind = Some(DecorationKind.ImplicitParameter) + ) + + @Test def `implicit-conversion` = + check( + """|case class User(name: String) + |object Main { + | implicit def intToUser(x: Int): User = new User(x.toString) + | val y: User = 1 + |} + |""".stripMargin, + """|case class User(name: String) + |object Main { + | implicit def intToUser(x: Int): User = new User(x.toString) + | val y: User = intToUser(1) + |} + |""".stripMargin, + kind = Some(DecorationKind.ImplicitConversion) + ) + + @Test def `using-param` = + check( + """|case class User(name: String) + |object Main { + | implicit val imp: Int = 2 + | def addOne(x: Int)(using one: Int) = x + one + | val x = addOne(1) + |} + |""".stripMargin, + """|case class User(name: String) + |object Main { + | implicit val imp: Int = 2 + | def addOne(x: Int)(using one: Int) = x + one + | val x = addOne(1)(imp) + |} + |""".stripMargin, + kind = Some(DecorationKind.ImplicitParameter) + ) + + @Test def `given-conversion` = + check( + """|case class User(name: String) + |object Main { + | given intToUser: Conversion[Int, User] = User(_.toString) + | val y: User = 1 + |} + |""".stripMargin, + """|case class User(name: String) + |object Main { + | given intToUser: Conversion[Int, User] = User(_.toString) + | val y: User = intToUser(1) + |} + |""".stripMargin, + kind = Some(DecorationKind.ImplicitConversion) + ) + + @Test def `given-conversion2` = + check( + """|trait Xg: + | def doX: Int + |trait Yg: + | def doY: String + |given (using Xg): Yg with + | def doY = "7" + |""".stripMargin, + """|trait Xg: + | def doX: Int + |trait Yg: + | def doY: String + |given (using Xg): Yg with + | def doY: String = "7" + |""".stripMargin + ) + + @Test def `basic` = + check( + """|object Main { + | val foo = 123 + |} + |""".stripMargin, + """|object Main { + | val foo: Int = 123 + |} + |""".stripMargin + ) + + @Test def `list` = + check( + """|object Main { + | val foo = List[Int](123) + |} + |""".stripMargin, + """|object Main { + | val foo: List[Int] = List[Int](123) + |} + |""".stripMargin + ) + + @Test def `list2` = + check( + """|object O { + | def m = 1 :: List(1) + |} + |""".stripMargin, + """|object O { + | def m: List[Int] = 1 ::[Int] List[Int](1) + |} + |""".stripMargin + ) + + @Test def `two-param` = + check( + """|object Main { + | val foo = Map((1, "abc")) + |} + |""".stripMargin, + """|object Main { + | val foo: Map[Int, String] = Map[Int, String]((1, "abc")) + |} + |""".stripMargin + ) + + @Test def `tuple` = + check( + """|object Main { + | val foo = (123, 456) + |} + |""".stripMargin, + """|object Main { + | val foo: (Int, Int) = (123, 456) + |} + |""".stripMargin + ) + + @Test def `import-needed` = + check( + """|object Main { + | val foo = List[String]("").toBuffer[String] + |} + |""".stripMargin, + """|object Main { + | val foo: Buffer[String] = List[String]("").toBuffer[String] + |} + |""".stripMargin + ) + + @Test def `lambda-type` = + check( + """|object Main { + | val foo = () => 123 + |} + |""".stripMargin, + """|object Main { + | val foo: () => Int = () => 123 + |} + |""".stripMargin + ) + + @Test def `block` = + check( + """|object Main { + | val foo = { val z = 123; z + 2} + |} + |""".stripMargin, + """|object Main { + | val foo: Int = { val z: Int = 123; z + 2} + |} + |""".stripMargin + ) + + @Test def `refined-types` = + check( + """|object O{ + | trait Foo { + | type T + | type G + | } + | + | val c = new Foo { type T = Int; type G = Long} + |} + |""".stripMargin, + """|object O{ + | trait Foo { + | type T + | type G + | } + | + | val c: Foo{type T = Int; type G = Long} = new Foo { type T = Int; type G = Long} + |} + |""".stripMargin + ) + + @Test def `refined-types1` = + check( + """|object O{ + | trait Foo { + | type T + | } + | val c = new Foo { type T = Int } + | val d = c + |} + |""".stripMargin, + """|object O{ + | trait Foo { + | type T + | } + | val c: Foo{type T = Int} = new Foo { type T = Int } + | val d: Foo{type T = Int} = c + |} + |""".stripMargin + ) + + @Test def `refined-types4` = + check( + """|trait Foo extends Selectable { + | type T + |} + | + |val c = new Foo { + | type T = Int + | val x = 0 + | def y = 0 + | var z = 0 + |} + |""".stripMargin, + """|trait Foo extends Selectable { + | type T + |} + | + |val c: Foo{type T = Int; val x: Int; def y: Int; val z: Int; def z_=(x$1: Int): Unit} = new Foo { + | type T = Int + | val x: Int = 0 + | def y: Int = 0 + | var z: Int = 0 + |} + |""".stripMargin + ) + + @Test def `dealias` = + check( + """|class Foo() { + | type T = Int + | def getT: T = 1 + |} + | + |object O { + | val c = new Foo().getT + |} + |""".stripMargin, + """|class Foo() { + | type T = Int + | def getT: T = 1 + |} + | + |object O { + | val c: Int = new Foo().getT + |} + |""".stripMargin + ) + + @Test def `dealias2` = + check( + """|object Foo { + | type T = Int + | def getT: T = 1 + | val c = getT + |} + |""".stripMargin, + """|object Foo { + | type T = Int + | def getT: T = 1 + | val c: T = getT + |} + |""".stripMargin + ) + + @Test def `dealias3` = + check( + """|object Foo: + | opaque type T = Int + | def getT: T = 1 + |val c = Foo.getT + |""".stripMargin, + """|object Foo: + | opaque type T = Int + | def getT: T = 1 + |val c: T = Foo.getT + |""".stripMargin + ) + + @Test def `dealias4` = + check( + """|object O: + | type M = Int + | type W = M => Int + | def get: W = ??? + | + |val m = O.get + |""".stripMargin, + """|object O: + | type M = Int + | type W = M => Int + | def get: W = ??? + | + |val m: Int => Int = O.get + |""".stripMargin + ) + + @Test def `dealias5` = + check( + """|object O: + | opaque type M = Int + | type W = M => Int + | def get: W = ??? + | + |val m = O.get + |""".stripMargin, + """|object O: + | opaque type M = Int + | type W = M => Int + | def get: W = ??? + | + |val m: M => Int = O.get + |""".stripMargin + ) + + @Test def `explicit-tuple` = + check( + """|object Main { + | val x = Tuple2.apply(1, 2) + |} + |""".stripMargin, + """|object Main { + | val x: (Int, Int) = Tuple2.apply[Int, Int](1, 2) + |} + |""".stripMargin + ) + + @Test def `explicit-tuple1` = + check( + """|object Main { + | val x = Tuple2(1, 2) + |} + |""".stripMargin, + """|object Main { + | val x: (Int, Int) = Tuple2[Int, Int](1, 2) + |} + |""".stripMargin + ) + + @Test def `tuple-unapply` = + check( + """|object Main { + | val (fst, snd) = (1, 2) + |} + |""".stripMargin, + """|object Main { + | val (fst: Int, snd: Int) = (1, 2) + |} + |""".stripMargin + ) + + @Test def `list-unapply` = + check( + """|object Main { + | val hd :: tail = List(1, 2) + |} + |""".stripMargin, + """|object Main { + | val hd: Int ::[Int] tail: List[Int] = List[Int](1, 2) + |} + |""".stripMargin + ) + + @Test def `list-match` = + check( + """|object Main { + | val x = List(1, 2) match { + | case hd :: tail => hd + | } + |} + |""".stripMargin, + """|object Main { + | val x: Int = List[Int](1, 2) match { + | case hd: Int ::[Int] tail: List[Int] => hd + | } + |} + |""".stripMargin + ) + + @Test def `case-class-unapply` = + check( + """|object Main { + |case class Foo[A](x: A, y: A) + | val Foo(fst, snd) = Foo(1, 2) + |} + |""".stripMargin, + """|object Main { + |case class Foo[A](x: A, y: A) + | val Foo[Int](fst: Int, snd: Int) = Foo[Int](1, 2) + |} + |""".stripMargin + ) + + @Test def `valueOf` = + check( + """|object O { + | def foo[Total <: Int](implicit total: ValueOf[Total]): Int = total.value + | val m = foo[500] + |} + |""".stripMargin, + """|object O { + | def foo[Total <: Int](implicit total: ValueOf[Total]): Int = total.value + | val m: Int = foo[500](new ValueOf(...)) + |} + |""".stripMargin + ) + + @Test def `case-class1` = + check( + """|object O { + |case class A(x: Int, g: Int)(implicit y: String) + |} + |""".stripMargin, + """|object O { + |case class A(x: Int, g: Int)(implicit y: String) + |} + |""".stripMargin + ) + diff --git a/project/Build.scala b/project/Build.scala index 6d778820bf57..1df259e862e8 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1128,7 +1128,7 @@ object Build { BuildInfoPlugin.buildInfoDefaultSettings lazy val presentationCompilerSettings = { - val mtagsVersion = "1.1.0+53-af181de4-SNAPSHOT" + val mtagsVersion = "1.1.0+79-325e7ef0-SNAPSHOT" Seq( resolvers ++= Resolver.sonatypeOssRepos("snapshots"),