From 61dc47bf9a68da38434a14ed7ade3796e414cf03 Mon Sep 17 00:00:00 2001 From: tgodzik Date: Sat, 20 Jul 2024 17:53:46 +0200 Subject: [PATCH 01/13] feature: Inferred type for Scala 3 --- build.sbt | 13 +- .../META-INF/services/scalafix.v1.Rule | 1 + .../internal/rule/ExplicitResultTypes.scala | 218 ++++-------------- .../internal/rule/ExplicitResultTypes.scala | 84 +++++++ .../internal/pc/CompilerOffsetParams.scala | 25 ++ .../scala/scalafix/internal/pc/Embedded.scala | 112 +++++++++ .../internal/pc/PcExplicitResultTypes.scala | 135 +++++++++++ .../pc/PresentationCompilerClassloader.scala | 26 +++ .../pc/PresentationCompilerConfigImpl.scala | 31 +++ .../rule/ExplicitResultTypesBase.scala | 169 ++++++++++++++ .../rule/ExplicitResultTypesConfig.scala | 3 + .../scala/scalafix/tests/rule/RuleSuite.scala | 1 + .../test/explicitResultTypes/AnyRefWith.scala | 0 .../EnumerationValue.scala | 0 .../ExplicitResultTypesArrayBuffer.scala | 0 .../ExplicitResultTypesBackQuoteComplex.scala | 0 .../ExplicitResultTypesImports.scala | 0 .../ExplicitResultTypesInfix.scala | 0 .../ExplicitResultTypesNil.scala | 0 .../ExplicitResultTypesPartialFunction.scala | 0 .../ExplicitResultTypesPrefix.scala | 0 .../test/explicitResultTypes/FinalVal.scala | 0 .../explicitResultTypes/ImmutableSeq.scala | 0 .../test/explicitResultTypes/LowerBound.scala | 0 .../RefinementConfig.scala | 0 .../test/explicitResultTypes/Rename.scala | 0 .../explicitResultTypes/ScalaPackage.scala | 0 .../SeenFromTypeParams.scala | 0 .../SymbolReplacement.scala | 0 .../explicitResultTypes/ThisTypeRef.scala | 0 .../Tuple1ExplicitResultTypes.scala | 0 .../test/explicitResultTypes/package.scala | 0 .../explicitResultTypes/testpkg/package.scala | 0 .../EnumerationValue.scala | 21 ++ .../ExplicitResultTypesBackQuoteComplex.scala | 10 + .../ExplicitResultTypesImports.scala | 50 ++++ .../ExplicitResultTypesPartialFunction.scala | 5 + .../ExplicitResultTypesPrefix.scala | 18 ++ .../explicitResultTypes/ImmutableSeq.scala | 12 + .../test/explicitResultTypes/LowerBound.scala | 15 ++ .../SymbolReplacement.scala | 12 + .../explicitResultTypes/ThisTypeRef.scala | 19 ++ .../test/explicitResultTypes/AnyRefWith.scala | 0 .../ExplicitResultTypesArrayBuffer.scala | 0 .../ExplicitResultTypesInfix.scala | 0 .../ExplicitResultTypesNil.scala | 0 .../ExplicitResultTypesOnlyImplicits.scala | 0 .../ExplicitResultTypesOutline.scala | 0 .../ExplicitResultTypesPathDependent.scala | 0 .../ExplicitResultTypesRefinement.scala | 0 .../ExplicitResultTypesSingleton.scala | 0 .../ExplicitResultTypesSupermethod.scala | 0 .../ExplicitResultTypesTrait.scala | 0 .../test/explicitResultTypes/FinalVal.scala | 0 .../RefinementConfig.scala | 0 .../test/explicitResultTypes/Rename.scala | 0 .../explicitResultTypes/ScalaPackage.scala | 0 .../SeenFromTypeParams.scala | 0 .../Tuple1ExplicitResultTypes.scala | 0 .../test/explicitResultTypes/package.scala | 0 .../explicitResultTypes/testpkg/package.scala | 0 61 files changed, 812 insertions(+), 168 deletions(-) create mode 100644 scalafix-rules/src/main/scala-3/scalafix/internal/rule/ExplicitResultTypes.scala create mode 100644 scalafix-rules/src/main/scala/scalafix/internal/pc/CompilerOffsetParams.scala create mode 100644 scalafix-rules/src/main/scala/scalafix/internal/pc/Embedded.scala create mode 100644 scalafix-rules/src/main/scala/scalafix/internal/pc/PcExplicitResultTypes.scala create mode 100644 scalafix-rules/src/main/scala/scalafix/internal/pc/PresentationCompilerClassloader.scala create mode 100644 scalafix-rules/src/main/scala/scalafix/internal/pc/PresentationCompilerConfigImpl.scala create mode 100644 scalafix-rules/src/main/scala/scalafix/internal/rule/ExplicitResultTypesBase.scala rename scalafix-rules/src/main/{scala-2 => scala}/scalafix/internal/rule/ExplicitResultTypesConfig.scala (98%) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/explicitResultTypes/AnyRefWith.scala (100%) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/explicitResultTypes/EnumerationValue.scala (100%) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/explicitResultTypes/ExplicitResultTypesArrayBuffer.scala (100%) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/explicitResultTypes/ExplicitResultTypesBackQuoteComplex.scala (100%) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/explicitResultTypes/ExplicitResultTypesImports.scala (100%) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/explicitResultTypes/ExplicitResultTypesInfix.scala (100%) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/explicitResultTypes/ExplicitResultTypesNil.scala (100%) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/explicitResultTypes/ExplicitResultTypesPartialFunction.scala (100%) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/explicitResultTypes/ExplicitResultTypesPrefix.scala (100%) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/explicitResultTypes/FinalVal.scala (100%) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/explicitResultTypes/ImmutableSeq.scala (100%) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/explicitResultTypes/LowerBound.scala (100%) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/explicitResultTypes/RefinementConfig.scala (100%) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/explicitResultTypes/Rename.scala (100%) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/explicitResultTypes/ScalaPackage.scala (100%) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/explicitResultTypes/SeenFromTypeParams.scala (100%) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/explicitResultTypes/SymbolReplacement.scala (100%) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/explicitResultTypes/ThisTypeRef.scala (100%) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/explicitResultTypes/Tuple1ExplicitResultTypes.scala (100%) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/explicitResultTypes/package.scala (100%) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/explicitResultTypes/testpkg/package.scala (100%) create mode 100644 scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/EnumerationValue.scala create mode 100644 scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/ExplicitResultTypesBackQuoteComplex.scala create mode 100644 scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/ExplicitResultTypesImports.scala create mode 100644 scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/ExplicitResultTypesPartialFunction.scala create mode 100644 scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/ExplicitResultTypesPrefix.scala create mode 100644 scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/ImmutableSeq.scala create mode 100644 scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/LowerBound.scala create mode 100644 scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/SymbolReplacement.scala create mode 100644 scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/ThisTypeRef.scala rename scalafix-tests/output/src/main/{scala-2 => scala}/test/explicitResultTypes/AnyRefWith.scala (100%) rename scalafix-tests/output/src/main/{scala-2 => scala}/test/explicitResultTypes/ExplicitResultTypesArrayBuffer.scala (100%) rename scalafix-tests/output/src/main/{scala-2 => scala}/test/explicitResultTypes/ExplicitResultTypesInfix.scala (100%) rename scalafix-tests/output/src/main/{scala-2 => scala}/test/explicitResultTypes/ExplicitResultTypesNil.scala (100%) rename scalafix-tests/output/src/main/{scala-2 => scala}/test/explicitResultTypes/ExplicitResultTypesOnlyImplicits.scala (100%) rename scalafix-tests/output/src/main/{scala-2 => scala}/test/explicitResultTypes/ExplicitResultTypesOutline.scala (100%) rename scalafix-tests/output/src/main/{scala-2 => scala}/test/explicitResultTypes/ExplicitResultTypesPathDependent.scala (100%) rename scalafix-tests/output/src/main/{scala-2 => scala}/test/explicitResultTypes/ExplicitResultTypesRefinement.scala (100%) rename scalafix-tests/output/src/main/{scala-2 => scala}/test/explicitResultTypes/ExplicitResultTypesSingleton.scala (100%) rename scalafix-tests/output/src/main/{scala-2 => scala}/test/explicitResultTypes/ExplicitResultTypesSupermethod.scala (100%) rename scalafix-tests/output/src/main/{scala-2 => scala}/test/explicitResultTypes/ExplicitResultTypesTrait.scala (100%) rename scalafix-tests/output/src/main/{scala-2 => scala}/test/explicitResultTypes/FinalVal.scala (100%) rename scalafix-tests/output/src/main/{scala-2 => scala}/test/explicitResultTypes/RefinementConfig.scala (100%) rename scalafix-tests/output/src/main/{scala-2 => scala}/test/explicitResultTypes/Rename.scala (100%) rename scalafix-tests/output/src/main/{scala-2 => scala}/test/explicitResultTypes/ScalaPackage.scala (100%) rename scalafix-tests/output/src/main/{scala-2 => scala}/test/explicitResultTypes/SeenFromTypeParams.scala (100%) rename scalafix-tests/output/src/main/{scala-2 => scala}/test/explicitResultTypes/Tuple1ExplicitResultTypes.scala (100%) rename scalafix-tests/output/src/main/{scala-2 => scala}/test/explicitResultTypes/package.scala (100%) rename scalafix-tests/output/src/main/{scala-2 => scala}/test/explicitResultTypes/testpkg/package.scala (100%) diff --git a/build.sbt b/build.sbt index 0a4c75cbb..c990e8ec9 100644 --- a/build.sbt +++ b/build.sbt @@ -111,6 +111,14 @@ lazy val rules = projectMatrix description := "Built-in Scalafix rules", isFullCrossVersion, buildInfoSettingsForRules, + libraryDependencies ++= List( + ("org.scalameta" % "mtags-interfaces" % "1.3.4") + .exclude("org.eclipse.lsp4j", "org.eclipse.lsp4j") + .exclude("org.eclipse.lsp4j", "org.eclipse.lsp4j.jsonrpc"), + // latest version release for JDK 8, this will be dropped from interfaces at some point + "org.eclipse.lsp4j" % "org.eclipse.lsp4j" % "0.20.1", + coursierInterfaces + ), libraryDependencies ++= { if (!isScala3.value) Seq( @@ -119,7 +127,10 @@ lazy val rules = projectMatrix semanticdbScalacCore, collectionCompat ) - else Nil + else + List( + "org.scala-lang" %% "scala3-presentation-compiler" % scalaVersion.value + ) }, // companion of `.dependsOn(core)` // issue reported in https://github.com/sbt/sbt/issues/7405 diff --git a/scalafix-rules/src/main/resources-3/META-INF/services/scalafix.v1.Rule b/scalafix-rules/src/main/resources-3/META-INF/services/scalafix.v1.Rule index 9ac4114ad..f25260297 100644 --- a/scalafix-rules/src/main/resources-3/META-INF/services/scalafix.v1.Rule +++ b/scalafix-rules/src/main/resources-3/META-INF/services/scalafix.v1.Rule @@ -1,4 +1,5 @@ scalafix.internal.rule.DisableSyntax +scalafix.internal.rule.ExplicitResultTypes scalafix.internal.rule.NoAutoTupling scalafix.internal.rule.NoValInForComprehension scalafix.internal.rule.OrganizeImports diff --git a/scalafix-rules/src/main/scala-2/scalafix/internal/rule/ExplicitResultTypes.scala b/scalafix-rules/src/main/scala-2/scalafix/internal/rule/ExplicitResultTypes.scala index e5c9186dc..2290c3570 100644 --- a/scalafix-rules/src/main/scala-2/scalafix/internal/rule/ExplicitResultTypes.scala +++ b/scalafix-rules/src/main/scala-2/scalafix/internal/rule/ExplicitResultTypes.scala @@ -3,23 +3,28 @@ package scalafix.internal.rule import scala.util.control.NonFatal import scala.meta._ -import scala.meta.contrib._ import scala.meta.internal.pc.ScalafixGlobal import buildinfo.RulesBuildInfo import metaconfig.Configured import scalafix.internal.compat.CompilerCompat._ +import scalafix.internal.pc.PcExplicitResultTypes import scalafix.internal.v1.LazyValue import scalafix.patch.Patch -import scalafix.util.TokenOps import scalafix.v1._ final class ExplicitResultTypes( - config: ExplicitResultTypesConfig, - global: LazyValue[Option[ScalafixGlobal]] -) extends SemanticRule("ExplicitResultTypes") { - - def this() = this(ExplicitResultTypesConfig.default, LazyValue.now(None)) + val config: ExplicitResultTypesConfig, + global: LazyValue[Option[ScalafixGlobal]], + fallback: LazyValue[Option[PcExplicitResultTypes]] +) extends SemanticRule("ExplicitResultTypes") + with ExplicitResultTypesBase[Scala2Printer] { + + def this() = this( + ExplicitResultTypesConfig.default, + LazyValue.now(None), + LazyValue.now(None) + ) val compilerScalaVersion: String = RulesBuildInfo.scalaVersion @@ -69,184 +74,63 @@ final class ExplicitResultTypes( if ( config.scalacClasspath.nonEmpty && inputBinaryScalaVersion != runtimeBinaryScalaVersion ) { - Configured.error( - s"The ExplicitResultTypes rule needs to run with the same Scala binary version as the one used to compile target sources ($inputBinaryScalaVersion). " + - s"To fix this problem, either remove ExplicitResultTypes from .scalafix.conf or make sure Scalafix is loaded with $inputBinaryScalaVersion." - ) + config.conf // Support deprecated explicitReturnTypes config + .getOrElse("explicitReturnTypes", "ExplicitResultTypes")( + ExplicitResultTypesConfig.default + ) + .map(c => + new ExplicitResultTypes( + c, + LazyValue.now(None), + LazyValue.now(Option(PcExplicitResultTypes.dynamic(config))) + ) + ) } else { config.conf // Support deprecated explicitReturnTypes config .getOrElse("explicitReturnTypes", "ExplicitResultTypes")( ExplicitResultTypesConfig.default ) - .map(c => new ExplicitResultTypes(c, newGlobal)) + .map(c => new ExplicitResultTypes(c, newGlobal, LazyValue.now(None))) } } override def fix(implicit ctx: SemanticDocument): Patch = - try unsafeFix() - catch { + try { + val typesLazy = global.value.map(new CompilerTypePrinter(_, config)) + implicit val printer = new Scala2Printer(typesLazy, fallback) + unsafeFix() + } catch { case _: CompilerException if !config.fatalWarnings => Patch.empty } +} - def unsafeFix()(implicit ctx: SemanticDocument): Patch = { - global.value match { - case Some(value) => - val types = new CompilerTypePrinter(value, config) - ctx.tree.collect { - case t @ Defn.Val(mods, Pat.Var(name) :: Nil, None, body) - if isRuleCandidate(t, name, mods, body) => - fixDefinition(t, body, types) - - case t @ Defn.Var(mods, Pat.Var(name) :: Nil, None, Some(body)) - if isRuleCandidate(t, name, mods, body) => - fixDefinition(t, body, types) - - case t @ Defn.Def(mods, name, _, _, None, body) - if isRuleCandidate(t, name, mods, body) => - fixDefinition(t, body, types) - }.asPatch - case None => Patch.empty - } - } - - // Don't explicitly annotate vals when the right-hand body is a single call - // to `implicitly`. Prevents ambiguous implicit. Not annotating in such cases, - // this a common trick employed implicit-heavy code to workaround SI-2712. - // Context: https://gitter.im/typelevel/cats?at=584573151eb3d648695b4a50 - private def isImplicitly(term: Term): Boolean = term match { - case Term.ApplyType(Term.Name("implicitly"), _) => true - case _ => false - } - - def defnName(defn: Defn): Option[Name] = Option(defn).collect { - case Defn.Val(_, Pat.Var(name) :: Nil, _, _) => name - case Defn.Var(_, Pat.Var(name) :: Nil, _, _) => name - case Defn.Def(_, name, _, _, _, _) => name - } - - def defnBody(defn: Defn): Option[Term] = Option(defn).collect { - case Defn.Val(_, _, _, term) => term - case Defn.Var(_, _, _, Some(term)) => term - case Defn.Def(_, _, _, _, _, term) => term - } - - def visibility(mods: Iterable[Mod]): MemberVisibility = - mods - .collectFirst { - case _: Mod.Private => MemberVisibility.Private - case _: Mod.Protected => MemberVisibility.Protected - } - .getOrElse(MemberVisibility.Public) - - def kind(defn: Defn): Option[MemberKind] = Option(defn).collect { - case _: Defn.Val => MemberKind.Val - case _: Defn.Def => MemberKind.Def - case _: Defn.Var => MemberKind.Var - } - - def isRuleCandidate[D <: Defn]( - defn: D, - nm: Name, - mods: Iterable[Mod], - body: Term - )(implicit ev: Extract[D, Mod], ctx: SemanticDocument): Boolean = { - import config._ - - def matchesMemberVisibility(): Boolean = - memberVisibility.contains(visibility(mods)) - - def matchesMemberKind(): Boolean = - kind(defn).exists(memberKind.contains) - - def isFinalLiteralVal: Boolean = - defn.is[Defn.Val] && - mods.exists(_.is[Mod.Final]) && - body.is[Lit] - - def matchesSimpleDefinition(): Boolean = - config.skipSimpleDefinitions.isSimpleDefinition(body) - - def isImplicit: Boolean = - defn.hasMod(mod"implicit") && !isImplicitly(body) - - def hasParentWihTemplate: Boolean = - defn.parent.exists(_.is[Template.Body]) - - def qualifyingImplicit: Boolean = - isImplicit && !isFinalLiteralVal - - def matchesConfig: Boolean = - matchesMemberKind && matchesMemberVisibility && !matchesSimpleDefinition - - def qualifyingNonImplicit: Boolean = { - !onlyImplicits && - hasParentWihTemplate && - !defn.hasMod(mod"implicit") - } - - matchesConfig && { - qualifyingImplicit || qualifyingNonImplicit - } - } - +class Scala2Printer( + globalPrinter: Option[CompilerTypePrinter], + fallback: LazyValue[Option[PcExplicitResultTypes]] +) extends Printer { def defnType( defn: Defn, replace: Token, - space: String, - types: CompilerTypePrinter + space: String )(implicit ctx: SemanticDocument - ): Option[Patch] = - for { - name <- defnName(defn) - defnSymbol <- name.symbol.asNonEmpty - patch <- types.toPatch(name.pos, defnSymbol, replace, defn, space) - } yield patch - - def fixDefinition(defn: Defn, body: Term, types: CompilerTypePrinter)(implicit - ctx: SemanticDocument - ): Patch = { - val lst = ctx.tokenList - val option = SymbolMatcher.exact("scala/Option.") - val list = SymbolMatcher.exact( - "scala/package.List.", - "scala/collection/immutable/List." - ) - val seq = SymbolMatcher.exact( - "scala/package.Seq.", - "scala/collection/Seq.", - "scala/collection/immutable/Seq." - ) - def patchEmptyValue(term: Term): Patch = { - term match { - case q"${option(_)}.empty[$_]" => - Patch.replaceTree(term, "None") - case q"${list(_)}.empty[$_]" => - Patch.replaceTree(term, "Nil") - case q"${seq(_)}.empty[$_]" => - Patch.replaceTree(term, "Nil") - case _ => - Patch.empty - } + ): Option[Patch] = { + + globalPrinter match { + case Some(types) => + for { + name <- ExplicitResultTypesBase.defnName(defn) + defnSymbol <- name.symbol.asNonEmpty + patch <- types.toPatch(name.pos, defnSymbol, replace, defn, space) + } yield { + patch + } + case None => + fallback.value.flatMap { fallbackExplicit => + fallbackExplicit.defnType(replace) + } } - import lst._ - for { - start <- defn.tokens.headOption - end <- body.tokens.headOption - // Left-hand side tokens in definition. - // Example: `val x = ` from `val x = rhs.banana` - lhsTokens = slice(start, end) - replace <- lhsTokens.reverseIterator.find(x => - !x.is[Token.Equals] && !x.is[Trivia] - ) - space = { - if (TokenOps.needsLeadingSpaceBeforeColon(replace)) " " - else "" - } - typePatch <- defnType(defn, replace, space, types) - valuePatchOpt = defnBody(defn).map(patchEmptyValue) - } yield typePatch + valuePatchOpt - }.asPatch.atomic + } } diff --git a/scalafix-rules/src/main/scala-3/scalafix/internal/rule/ExplicitResultTypes.scala b/scalafix-rules/src/main/scala-3/scalafix/internal/rule/ExplicitResultTypes.scala new file mode 100644 index 000000000..5b0ad4e4f --- /dev/null +++ b/scalafix-rules/src/main/scala-3/scalafix/internal/rule/ExplicitResultTypes.scala @@ -0,0 +1,84 @@ +package scalafix.internal.rule + +import scala.meta.* + +import buildinfo.RulesBuildInfo +import dotty.tools.pc.ScalaPresentationCompiler +import metaconfig.Configured +import scalafix.internal.pc.PcExplicitResultTypes +import scalafix.patch.Patch +import scalafix.v1.* + +final class ExplicitResultTypes( + val config: ExplicitResultTypesConfig, + fallback: Option[PcExplicitResultTypes] +) extends SemanticRule("ExplicitResultTypes") + with ExplicitResultTypesBase[Scala3Printer] { + + def this() = this(ExplicitResultTypesConfig.default, None) + + val compilerScalaVersion: String = RulesBuildInfo.scalaVersion + + private def toBinaryVersion(v: String) = v.split('.').take(2).mkString(".") + + override def description: String = + "Inserts type annotations for inferred public members." + + override def isRewrite: Boolean = true + + override def afterComplete(): Unit = { + shutdownCompiler() + } + + private def shutdownCompiler(): Unit = { + fallback.foreach(_.shutdownCompiler()) + } + + override def withConfiguration(config: Configuration): Configured[Rule] = { + config.conf // Support deprecated explicitReturnTypes config + .getOrElse("explicitReturnTypes", "ExplicitResultTypes")( + ExplicitResultTypesConfig.default + ) + .map(c => + new ExplicitResultTypes( + c, + Option { + if ( + toBinaryVersion(config.scalaVersion) == toBinaryVersion( + compilerScalaVersion + ) + ) + PcExplicitResultTypes + .static(config, new ScalaPresentationCompiler()) + else + PcExplicitResultTypes.dynamic(config) + } + ) + ) + } + + override def fix(implicit ctx: SemanticDocument): Patch = + try { + implicit val printer = new Scala3Printer(fallback) + unsafeFix() + } catch { + case _: Throwable if !config.fatalWarnings => + Patch.empty + } + +} + +class Scala3Printer( + fallback: Option[PcExplicitResultTypes] +) extends Printer { + + def defnType( + defn: Defn, + replace: Token, + space: String + )(implicit + ctx: SemanticDocument + ): Option[Patch] = { + fallback.flatMap(_.defnType(replace)) + } +} diff --git a/scalafix-rules/src/main/scala/scalafix/internal/pc/CompilerOffsetParams.scala b/scalafix-rules/src/main/scala/scalafix/internal/pc/CompilerOffsetParams.scala new file mode 100644 index 000000000..94b8720bd --- /dev/null +++ b/scalafix-rules/src/main/scala/scalafix/internal/pc/CompilerOffsetParams.scala @@ -0,0 +1,25 @@ +package scalafix.internal.pc + +import java.net.URI +import java.util.concurrent.CompletableFuture +import java.util.concurrent.CompletionStage + +import scala.meta.pc.CancelToken +import scala.meta.pc.OffsetParams + +case class CompilerOffsetParams( + uri: URI, + text: String, + offset: Int +) extends OffsetParams { + + override def token(): CancelToken = new CancelToken { + + override def checkCanceled(): Unit = () + + override def onCancel(): CompletionStage[java.lang.Boolean] = + CompletableFuture.completedFuture(java.lang.Boolean.FALSE) + + } + +} diff --git a/scalafix-rules/src/main/scala/scalafix/internal/pc/Embedded.scala b/scalafix-rules/src/main/scala/scalafix/internal/pc/Embedded.scala new file mode 100644 index 000000000..70d6f699c --- /dev/null +++ b/scalafix-rules/src/main/scala/scalafix/internal/pc/Embedded.scala @@ -0,0 +1,112 @@ +package scalafix.internal.pc + +import java.net.URLClassLoader +import java.util.ServiceLoader + +import scala.collection.concurrent.TrieMap +import scala.jdk.CollectionConverters._ + +import scala.meta.pc.PresentationCompiler + +import coursierapi.Dependency +import coursierapi.Fetch +import coursierapi.MavenRepository + +object Embedded { + + private val presentationCompilers: TrieMap[String, URLClassLoader] = + TrieMap.empty + + def presentationCompiler( + scalaVersion: String + ): PresentationCompiler = { + val classloader = presentationCompilers.getOrElseUpdate( + scalaVersion, + newPresentationCompilerClassLoader(scalaVersion) + ) + val presentationCompilerClassname = + if (supportPresentationCompilerInDotty(scalaVersion)) { + "dotty.tools.pc.ScalaPresentationCompiler" + } else { + "scala.meta.pc.ScalaPresentationCompiler" + } + + serviceLoader( + classOf[PresentationCompiler], + presentationCompilerClassname, + classloader + ) + } + + private def supportPresentationCompilerInDotty(scalaVersion: String) = { + scalaVersion + .replaceAll(raw"-RC\d+", "") + .split("\\.") + .take(3) + .map(_.toInt) match { + case Array(3, minor, patch) => minor > 3 || minor == 3 && patch >= 4 + case _ => false + } + } + + private def scala3PresentationCompilerDependencies(version: String) = + if (supportPresentationCompilerInDotty(version)) { + val dep = Dependency + .of("org.scala-lang", "scala3-presentation-compiler_3", version) + + // some versions of the presentation compiler depend on versions only build for JDK 11 + dep.addExclusion("org.eclipse.lsp4j", "org.eclipse.lsp4j") + dep.addExclusion("org.eclipse.lsp4j", "org.eclipse.lsp4j.jsonrpc") + // last built with JDK 8 + val lsp4jDep = + Dependency.of("org.eclipse.lsp4j", "org.eclipse.lsp4j", "0.20.1") + List(dep, lsp4jDep) + } else + List( + // TODO should use build info etc. instead of using 1.3.4 + Dependency.of("org.scalameta", s"mtags_${version}", "1.3.4") + ) + + private def serviceLoader[T]( + cls: Class[T], + className: String, + classloader: URLClassLoader + ): T = { + val services = ServiceLoader.load(cls, classloader).iterator() + if (services.hasNext) services.next() + else { + val cls = classloader.loadClass(className) + val ctor = cls.getDeclaredConstructor() + ctor.setAccessible(true) + ctor.newInstance().asInstanceOf[T] + } + } + + private def newPresentationCompilerClassLoader( + scalaVersion: String + ): URLClassLoader = { + + val deps = + scala3PresentationCompilerDependencies(scalaVersion) + val jars = Fetch + .create() + .addDependencies(deps: _*) + .addRepositories( + MavenRepository.of( + "https://oss.sonatype.org/content/repositories/snapshots" + ) + ) + .fetch() + .asScala + .map(_.toPath()) + .toSeq + val allJars = jars.iterator + val allURLs = allJars.map(_.toUri.toURL).toArray + // Share classloader for a subset of types. + val parent = + new scalafix.internal.pc.PresentationCompilerClassLoader( + this.getClass.getClassLoader + ) + new URLClassLoader(allURLs, parent) + } +} diff --git a/scalafix-rules/src/main/scala/scalafix/internal/pc/PcExplicitResultTypes.scala b/scalafix-rules/src/main/scala/scalafix/internal/pc/PcExplicitResultTypes.scala new file mode 100644 index 000000000..3368ffe37 --- /dev/null +++ b/scalafix-rules/src/main/scala/scalafix/internal/pc/PcExplicitResultTypes.scala @@ -0,0 +1,135 @@ +package scalafix.internal.pc + +import java.nio.file.Paths + +import scala.jdk.CollectionConverters._ +import scala.util.Random +import scala.util.Try + +import scala.meta._ +import scala.meta.inputs.Input.File +import scala.meta.inputs.Input.VirtualFile +import scala.meta.pc.PresentationCompiler +import scala.meta.trees.Origin.DialectOnly +import scala.meta.trees.Origin.Parsed + +import scalafix.internal.v1.LazyValue +import scalafix.patch.Patch +import scalafix.patch.Patch.empty +import scalafix.v1._ + +/** + * Fallback tries to download and use existing presentation compiler either from + * Metals or the Scala compiler itself. + * + * @param pc + */ +final class PcExplicitResultTypes( + pc: LazyValue[Option[PresentationCompiler]] +) { + + def shutdownCompiler(): Unit = { + pc.value.foreach { + _.shutdown() + } + } + + def defnName(defn: Defn): Option[Name] = Option(defn).collect { + case Defn.Val(_, Pat.Var(name) :: Nil, _, _) => name + case Defn.Var(_, Pat.Var(name) :: Nil, _, _) => name + case Defn.Def(_, name, _, _, _, _) => name + } + + def defnType( + replace: Token + )(implicit + ctx: SemanticDocument + ): Option[Patch] = + for { + pc <- pc.value + } yield { + ctx.tree.origin match { + case _: DialectOnly => empty + case scala.meta.trees.Origin.None => empty + case parsed: Parsed => + val text = parsed.source.input.text + val uri = parsed.source.input match { + // case Ammonite(input) => + case File(path, _) => path.toURI + case VirtualFile(path, _) => Paths.get(path).toUri() + case _ => Paths.get(s"./A${Random.nextInt()}.scala").toUri() + } + val params = new CompilerOffsetParams( + uri, + text, + replace.pos.end + ) + val result = pc.insertInferredType(params).get() + // TODO we need to actually insert after each change + val allPatches: List[Patch] = result.asScala.toList + .map { edit => + val start = edit.getRange().getStart() + val last = ctx.tokens.tokens.takeWhile { token => + val beforeLine = token.pos.endLine < start.getLine() + val beforeColumn = token.pos.endLine == start + .getLine() && token.pos.endColumn <= start.getCharacter() + beforeLine || beforeColumn + + }.last + Patch.addRight(last, edit.getNewText()) + } + allPatches.reduce[Patch] { case (p1, p2) => + p1 + p2 + } + } + } + +} + +object PcExplicitResultTypes { + private def configure( + config: Configuration, + pc: PresentationCompiler + ): PresentationCompiler = { + val symbolReplacements = + config.conf.dynamic.ExplicitResultTypes.symbolReplacements + .as[Map[String, String]] + .getOrElse(Map.empty) + + pc.withConfiguration( + PresentationCompilerConfigImpl( + symbolPrefixes = symbolReplacements.asJava + ) + ).newInstance( + "ExplicitResultTypes", + config.scalacClasspath.map(_.toNIO).asJava, + // getting assertion errors if included + config.scalacOptions.filter(!_.contains("-release")).asJava + ) + } + + def dynamic(config: Configuration): PcExplicitResultTypes = { + val newPc: LazyValue[Option[PresentationCompiler]] = + LazyValue.from { () => + Try( + configure( + config, + Embedded.presentationCompiler(config.scalaVersion) + ) + ) + } + new PcExplicitResultTypes(newPc) + } + + def static( + config: Configuration, + pc: PresentationCompiler + ): PcExplicitResultTypes = { + val newPc: LazyValue[Option[PresentationCompiler]] = + LazyValue.from { () => + Try(configure(config, pc)) + } + new PcExplicitResultTypes(newPc) + } + +} diff --git a/scalafix-rules/src/main/scala/scalafix/internal/pc/PresentationCompilerClassloader.scala b/scalafix-rules/src/main/scala/scalafix/internal/pc/PresentationCompilerClassloader.scala new file mode 100644 index 000000000..56083ac5b --- /dev/null +++ b/scalafix-rules/src/main/scala/scalafix/internal/pc/PresentationCompilerClassloader.scala @@ -0,0 +1,26 @@ +package scalafix.internal.pc + +/** + * ClassLoader that is used to reflectively invoke presentation compiler APIs. + * + * The presentation compiler APIs are compiled against exact Scala versions of + * the compiler while Scalafix rule only runs in a single Scala version. In + * order to communicate between Scalafix and the reflectively loaded compiler, + * this classloader shares a subset of Java classes that appear in method + * signatures of the `PresentationCompiler` class. + */ +class PresentationCompilerClassLoader(parent: ClassLoader) + extends ClassLoader(null) { + override def findClass(name: String): Class[?] = { + val isShared = + name.startsWith("org.eclipse.lsp4j") || + name.startsWith("com.google.gson") || + name.startsWith("scala.meta.pc") || + name.startsWith("javax") + if (isShared) { + parent.loadClass(name) + } else { + throw new ClassNotFoundException(name) + } + } +} diff --git a/scalafix-rules/src/main/scala/scalafix/internal/pc/PresentationCompilerConfigImpl.scala b/scalafix-rules/src/main/scala/scalafix/internal/pc/PresentationCompilerConfigImpl.scala new file mode 100644 index 000000000..5a705bbd8 --- /dev/null +++ b/scalafix-rules/src/main/scala/scalafix/internal/pc/PresentationCompilerConfigImpl.scala @@ -0,0 +1,31 @@ +package scalafix.internal.pc + +import java.util.Optional +import java.util.concurrent.TimeUnit + +import scala.meta.pc.PresentationCompilerConfig +import scala.meta.pc.PresentationCompilerConfig.OverrideDefFormat + +case class PresentationCompilerConfigImpl( + debug: Boolean = false, + parameterHintsCommand: Optional[String] = Optional.empty(), + completionCommand: Optional[String] = Optional.empty(), + symbolPrefixes: java.util.Map[String, String] = + PresentationCompilerConfig.defaultSymbolPrefixes(), + overrideDefFormat: OverrideDefFormat = OverrideDefFormat.Ascii, + isCompletionItemDetailEnabled: Boolean = true, + isCompletionItemDocumentationEnabled: Boolean = true, + isHoverDocumentationEnabled: Boolean = true, + snippetAutoIndent: Boolean = true, + isSignatureHelpDocumentationEnabled: Boolean = true, + isCompletionSnippetsEnabled: Boolean = true, + isCompletionItemResolve: Boolean = true, + isStripMarginOnTypeFormattingEnabled: Boolean = true, + timeoutDelay: Long = 20, + timeoutUnit: TimeUnit = TimeUnit.SECONDS, + semanticdbCompilerOptions: java.util.List[String] = + PresentationCompilerConfig.defaultSemanticdbCompilerOptions() +) extends PresentationCompilerConfig { + + override def isDefaultSymbolPrefixes(): Boolean = false +} diff --git a/scalafix-rules/src/main/scala/scalafix/internal/rule/ExplicitResultTypesBase.scala b/scalafix-rules/src/main/scala/scalafix/internal/rule/ExplicitResultTypesBase.scala new file mode 100644 index 000000000..f08537fe5 --- /dev/null +++ b/scalafix-rules/src/main/scala/scalafix/internal/rule/ExplicitResultTypesBase.scala @@ -0,0 +1,169 @@ +package scalafix.internal.rule + +import scala.meta._ +import scala.meta.contrib._ + +import scalafix.util.TokenOps +import scalafix.v1._ + +trait Printer { + def defnType( + defn: Defn, + replace: Token, + space: String + )(implicit + ctx: SemanticDocument + ): Option[Patch] + +} + +trait ExplicitResultTypesBase[P <: Printer] { + + val config: ExplicitResultTypesConfig + + // Don't explicitly annotate vals when the right-hand body is a single call + // to `implicitly` or `summon`. Prevents ambiguous implicit. Not annotating in such cases, + // this a common trick employed implicit-heavy code to workaround SI-2712. + // Context: https://gitter.im/typelevel/cats?at=584573151eb3d648695b4a50 + protected def isImplicitly(term: Term): Boolean = term match { + case Term.ApplyType(Term.Name("implicitly"), _) => true + case Term.ApplyType(Term.Name("summon"), _) => true + case _ => false + } + + protected def defnBody(defn: Defn): Option[Term] = Option(defn).collect { + case Defn.Val(_, _, _, term) => term + case Defn.Var(_, _, _, Some(term)) => term + case Defn.Def(_, _, _, _, _, term) => term + } + + protected def visibility(mods: Iterable[Mod]): MemberVisibility = + mods + .collectFirst { + case _: Mod.Private => MemberVisibility.Private + case _: Mod.Protected => MemberVisibility.Protected + } + .getOrElse(MemberVisibility.Public) + + protected def kind(defn: Defn): Option[MemberKind] = Option(defn).collect { + case _: Defn.Val => MemberKind.Val + case _: Defn.Def => MemberKind.Def + case _: Defn.Var => MemberKind.Var + } + + protected def isRuleCandidate[D <: Defn]( + defn: D, + mods: Iterable[Mod], + body: Term + )(implicit ev: Extract[D, Mod]): Boolean = { + + import config._ + + def matchesMemberVisibility(): Boolean = + memberVisibility.contains(visibility(mods)) + + def matchesMemberKind(): Boolean = + kind(defn).exists(memberKind.contains) + + def isFinalLiteralVal: Boolean = + defn.is[Defn.Val] && + mods.exists(_.is[Mod.Final]) && + body.is[Lit] + + def matchesSimpleDefinition(): Boolean = + config.skipSimpleDefinitions.isSimpleDefinition(body) + + def isImplicit: Boolean = + defn.hasMod(Mod.Implicit()) && !isImplicitly(body) + + def hasParentWihTemplate: Boolean = + defn.parent.exists(_.is[Template.Body]) + + def qualifyingImplicit: Boolean = + isImplicit && !isFinalLiteralVal + + def matchesConfig: Boolean = + matchesMemberKind() && matchesMemberVisibility() && !matchesSimpleDefinition() + + def qualifyingNonImplicit: Boolean = { + !onlyImplicits && + hasParentWihTemplate && + !defn.hasMod(Mod.Implicit()) + } + + matchesConfig && { + qualifyingImplicit || qualifyingNonImplicit + } + } + + def fixDefinition(defn: Defn, body: Term)(implicit + ctx: SemanticDocument, + printer: Printer + ): Patch = { + val lst = ctx.tokenList + val option = SymbolMatcher.exact("scala/Option.") + val list = SymbolMatcher.exact( + "scala/package.List.", + "scala/collection/immutable/List." + ) + val seq = SymbolMatcher.exact( + "scala/package.Seq.", + "scala/collection/Seq.", + "scala/collection/immutable/Seq." + ) + def patchEmptyValue(term: Term): Patch = { + term match { + case Term.ApplyType(Term.Select(option(_), Term.Name("empty")), _) => + Patch.replaceTree(term, "None") + case Term.ApplyType(Term.Select(list(_), Term.Name("empty")), _) => + Patch.replaceTree(term, "Nil") + case Term.ApplyType(Term.Select(seq(_), Term.Name("empty")), _) => + Patch.replaceTree(term, "Nil") + case _ => + Patch.empty + } + } + import lst._ + for { + start <- defn.tokens.headOption + end <- body.tokens.headOption + // Left-hand side tokens in definition. + // Example: `val x = ` from `val x = rhs.banana` + lhsTokens = slice(start, end) + replace <- lhsTokens.reverseIterator.find(x => + !x.is[Token.Equals] && !x.is[Trivia] + ) + space = { + if (TokenOps.needsLeadingSpaceBeforeColon(replace)) " " + else "" + } + typePatch <- printer.defnType(defn, replace, space) + valuePatchOpt = defnBody(defn).map(patchEmptyValue) + } yield typePatch + valuePatchOpt + }.asPatch.atomic + + def unsafeFix()(implicit ctx: SemanticDocument, printer: Printer): Patch = { + ctx.tree.collect { + case t @ Defn.Val(mods, Pat.Var(_) :: Nil, None, body) + if isRuleCandidate(t, mods, body) => + fixDefinition(t, body) + + case t @ Defn.Var(mods, Pat.Var(_) :: Nil, None, Some(body)) + if isRuleCandidate(t, mods, body) => + fixDefinition(t, body) + + case t @ Defn.Def(mods, _, _, _, None, body) + if isRuleCandidate(t, mods, body) => + fixDefinition(t, body) + }.asPatch + } +} + +object ExplicitResultTypesBase { + + def defnName(defn: Defn): Option[Name] = Option(defn).collect { + case Defn.Val(_, Pat.Var(name) :: Nil, _, _) => name + case Defn.Var(_, Pat.Var(name) :: Nil, _, _) => name + case Defn.Def(_, name, _, _, _, _) => name + } +} diff --git a/scalafix-rules/src/main/scala-2/scalafix/internal/rule/ExplicitResultTypesConfig.scala b/scalafix-rules/src/main/scala/scalafix/internal/rule/ExplicitResultTypesConfig.scala similarity index 98% rename from scalafix-rules/src/main/scala-2/scalafix/internal/rule/ExplicitResultTypesConfig.scala rename to scalafix-rules/src/main/scala/scalafix/internal/rule/ExplicitResultTypesConfig.scala index fac64e09f..03b4c5aa6 100644 --- a/scalafix-rules/src/main/scala-2/scalafix/internal/rule/ExplicitResultTypesConfig.scala +++ b/scalafix-rules/src/main/scala/scalafix/internal/rule/ExplicitResultTypesConfig.scala @@ -52,6 +52,9 @@ object ExplicitResultTypesConfig { } case class SimpleDefinitions(kinds: Set[String]) { + + import scala.meta.classifiers.XtensionClassifiable + private def isSimpleRef(tree: m.Tree): Boolean = tree match { case _: m.Name => true case t: m.Term.Select => isSimpleRef(t.qual) diff --git a/scalafix-tests/expect/src/test/scala/scalafix/tests/rule/RuleSuite.scala b/scalafix-tests/expect/src/test/scala/scalafix/tests/rule/RuleSuite.scala index 6d26b5973..79a6e7421 100644 --- a/scalafix-tests/expect/src/test/scala/scalafix/tests/rule/RuleSuite.scala +++ b/scalafix-tests/expect/src/test/scala/scalafix/tests/rule/RuleSuite.scala @@ -36,6 +36,7 @@ class RuleSuite extends AbstractSemanticRuleSuite with AnyFunSuiteLike { override def runOn(diffTest: RuleTest): Unit = { if ( buildinfo.RulesBuildInfo.scalaVersion.startsWith("3") && + props.scalaVersion.startsWith("2") && diffTest.path.input.toNIO.toString.contains("explicitResultTypes") ) return else super.runOn(diffTest) diff --git a/scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/AnyRefWith.scala b/scalafix-tests/input/src/main/scala/test/explicitResultTypes/AnyRefWith.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/AnyRefWith.scala rename to scalafix-tests/input/src/main/scala/test/explicitResultTypes/AnyRefWith.scala diff --git a/scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/EnumerationValue.scala b/scalafix-tests/input/src/main/scala/test/explicitResultTypes/EnumerationValue.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/EnumerationValue.scala rename to scalafix-tests/input/src/main/scala/test/explicitResultTypes/EnumerationValue.scala diff --git a/scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesArrayBuffer.scala b/scalafix-tests/input/src/main/scala/test/explicitResultTypes/ExplicitResultTypesArrayBuffer.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesArrayBuffer.scala rename to scalafix-tests/input/src/main/scala/test/explicitResultTypes/ExplicitResultTypesArrayBuffer.scala diff --git a/scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesBackQuoteComplex.scala b/scalafix-tests/input/src/main/scala/test/explicitResultTypes/ExplicitResultTypesBackQuoteComplex.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesBackQuoteComplex.scala rename to scalafix-tests/input/src/main/scala/test/explicitResultTypes/ExplicitResultTypesBackQuoteComplex.scala diff --git a/scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesImports.scala b/scalafix-tests/input/src/main/scala/test/explicitResultTypes/ExplicitResultTypesImports.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesImports.scala rename to scalafix-tests/input/src/main/scala/test/explicitResultTypes/ExplicitResultTypesImports.scala diff --git a/scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesInfix.scala b/scalafix-tests/input/src/main/scala/test/explicitResultTypes/ExplicitResultTypesInfix.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesInfix.scala rename to scalafix-tests/input/src/main/scala/test/explicitResultTypes/ExplicitResultTypesInfix.scala diff --git a/scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesNil.scala b/scalafix-tests/input/src/main/scala/test/explicitResultTypes/ExplicitResultTypesNil.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesNil.scala rename to scalafix-tests/input/src/main/scala/test/explicitResultTypes/ExplicitResultTypesNil.scala diff --git a/scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesPartialFunction.scala b/scalafix-tests/input/src/main/scala/test/explicitResultTypes/ExplicitResultTypesPartialFunction.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesPartialFunction.scala rename to scalafix-tests/input/src/main/scala/test/explicitResultTypes/ExplicitResultTypesPartialFunction.scala diff --git a/scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesPrefix.scala b/scalafix-tests/input/src/main/scala/test/explicitResultTypes/ExplicitResultTypesPrefix.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesPrefix.scala rename to scalafix-tests/input/src/main/scala/test/explicitResultTypes/ExplicitResultTypesPrefix.scala diff --git a/scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/FinalVal.scala b/scalafix-tests/input/src/main/scala/test/explicitResultTypes/FinalVal.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/FinalVal.scala rename to scalafix-tests/input/src/main/scala/test/explicitResultTypes/FinalVal.scala diff --git a/scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/ImmutableSeq.scala b/scalafix-tests/input/src/main/scala/test/explicitResultTypes/ImmutableSeq.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/ImmutableSeq.scala rename to scalafix-tests/input/src/main/scala/test/explicitResultTypes/ImmutableSeq.scala diff --git a/scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/LowerBound.scala b/scalafix-tests/input/src/main/scala/test/explicitResultTypes/LowerBound.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/LowerBound.scala rename to scalafix-tests/input/src/main/scala/test/explicitResultTypes/LowerBound.scala diff --git a/scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/RefinementConfig.scala b/scalafix-tests/input/src/main/scala/test/explicitResultTypes/RefinementConfig.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/RefinementConfig.scala rename to scalafix-tests/input/src/main/scala/test/explicitResultTypes/RefinementConfig.scala diff --git a/scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/Rename.scala b/scalafix-tests/input/src/main/scala/test/explicitResultTypes/Rename.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/Rename.scala rename to scalafix-tests/input/src/main/scala/test/explicitResultTypes/Rename.scala diff --git a/scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/ScalaPackage.scala b/scalafix-tests/input/src/main/scala/test/explicitResultTypes/ScalaPackage.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/ScalaPackage.scala rename to scalafix-tests/input/src/main/scala/test/explicitResultTypes/ScalaPackage.scala diff --git a/scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/SeenFromTypeParams.scala b/scalafix-tests/input/src/main/scala/test/explicitResultTypes/SeenFromTypeParams.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/SeenFromTypeParams.scala rename to scalafix-tests/input/src/main/scala/test/explicitResultTypes/SeenFromTypeParams.scala diff --git a/scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/SymbolReplacement.scala b/scalafix-tests/input/src/main/scala/test/explicitResultTypes/SymbolReplacement.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/SymbolReplacement.scala rename to scalafix-tests/input/src/main/scala/test/explicitResultTypes/SymbolReplacement.scala diff --git a/scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/ThisTypeRef.scala b/scalafix-tests/input/src/main/scala/test/explicitResultTypes/ThisTypeRef.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/ThisTypeRef.scala rename to scalafix-tests/input/src/main/scala/test/explicitResultTypes/ThisTypeRef.scala diff --git a/scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/Tuple1ExplicitResultTypes.scala b/scalafix-tests/input/src/main/scala/test/explicitResultTypes/Tuple1ExplicitResultTypes.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/Tuple1ExplicitResultTypes.scala rename to scalafix-tests/input/src/main/scala/test/explicitResultTypes/Tuple1ExplicitResultTypes.scala diff --git a/scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/package.scala b/scalafix-tests/input/src/main/scala/test/explicitResultTypes/package.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/package.scala rename to scalafix-tests/input/src/main/scala/test/explicitResultTypes/package.scala diff --git a/scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/testpkg/package.scala b/scalafix-tests/input/src/main/scala/test/explicitResultTypes/testpkg/package.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/explicitResultTypes/testpkg/package.scala rename to scalafix-tests/input/src/main/scala/test/explicitResultTypes/testpkg/package.scala diff --git a/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/EnumerationValue.scala b/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/EnumerationValue.scala new file mode 100644 index 000000000..1ed4f778d --- /dev/null +++ b/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/EnumerationValue.scala @@ -0,0 +1,21 @@ +package test.explicitResultTypes + +object EnumerationValue { + object Day extends Enumeration { + type Day = Value + val Weekday, Weekend = Value + } + object Bool extends Enumeration { + type Bool = Value + val True, False = Value + } + import Bool._ + def day(d: Day.Value): Unit = ??? + val d: test.explicitResultTypes.EnumerationValue.Day.Value = + if (true) Day.Weekday + else Day.Weekend + day(d) + val b: test.explicitResultTypes.EnumerationValue.Bool.Value = + if (true) True + else False +} diff --git a/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/ExplicitResultTypesBackQuoteComplex.scala b/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/ExplicitResultTypesBackQuoteComplex.scala new file mode 100644 index 000000000..0db562504 --- /dev/null +++ b/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/ExplicitResultTypesBackQuoteComplex.scala @@ -0,0 +1,10 @@ +package test.explicitResultTypes + +// https://github.com/scalacenter/scalafix/issues/1219 +object ExplicitResultTypesBackQuoteComplex { + val it: Iterable[?] = null.asInstanceOf[Iterable[_]] + def method(a: String*) = a.head + val func: Seq[String] => String = method _ + val hash = new java.util.HashMap[String, Int]() + val func2: (String, java.util.function.Function[? >: String <: Object, ? <: Int]) => Int = hash.computeIfAbsent _ +} diff --git a/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/ExplicitResultTypesImports.scala b/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/ExplicitResultTypesImports.scala new file mode 100644 index 000000000..2ac307c87 --- /dev/null +++ b/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/ExplicitResultTypesImports.scala @@ -0,0 +1,50 @@ + +package test.explicitResultTypes + +import scala.util._ +import scala.collection.immutable.ListSet +import scala.concurrent.duration.FiniteDuration +import scala.concurrent.duration.FiniteDuration +import java.util.TimeZone +import scala.collection.Searching.SearchResult +import java.util.Locale.Category + +object ExplicitResultTypesImports { + + val x: ListSet[Int] = scala.collection.immutable.ListSet.empty[Int] + // val Either = scala.util.Either + + val duplicate1: FiniteDuration = null.asInstanceOf[scala.concurrent.duration.FiniteDuration] + val duplicate2: FiniteDuration = null.asInstanceOf[scala.concurrent.duration.FiniteDuration] + + val timezone: TimeZone = null.asInstanceOf[java.util.TimeZone] + + val inner: SearchResult = null.asInstanceOf[scala.collection.Searching.SearchResult] + + final val javaEnum: Category = java.util.Locale.Category.DISPLAY + + // TODO: respect type aliases? + type MyResult = Either[Int, String] + val inferTypeAlias: Either[Int, String] = null.asInstanceOf[Either[Int, String]] + + val wildcardImport: Try[Int] = Try(1) + + sealed abstract class ADT + object ADT { + case object A extends ADT + case object B extends ADT + } + val productWithSerializable: List[ADT] = List(ADT.A, ADT.B) + + sealed abstract class ADT2 + trait Mixin[T] + object ADT2 { + case object A extends ADT2 with Mixin[Int] + case object B extends ADT2 with Mixin[String] + case object C extends ADT2 with Mixin[Int] + } + val longSharedParent1: List[ADT2 & Mixin[? >: Int & String <: Int | String]] = List(ADT2.A, ADT2.B) + val longSharedParent2: List[ADT2 & Mixin[Int]] = List(ADT2.A, ADT2.C) + + val juMap: java.util.Map[Int, String] = java.util.Collections.emptyMap[Int, String]() +} \ No newline at end of file diff --git a/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/ExplicitResultTypesPartialFunction.scala b/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/ExplicitResultTypesPartialFunction.scala new file mode 100644 index 000000000..f42cc07b7 --- /dev/null +++ b/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/ExplicitResultTypesPartialFunction.scala @@ -0,0 +1,5 @@ +package test.explicitResultTypes + +object PartialFunction { + def empty[A, B]: scala.PartialFunction[A, B] = scala.PartialFunction.empty[A, B] +} diff --git a/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/ExplicitResultTypesPrefix.scala b/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/ExplicitResultTypesPrefix.scala new file mode 100644 index 000000000..d455f917f --- /dev/null +++ b/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/ExplicitResultTypesPrefix.scala @@ -0,0 +1,18 @@ + +package test.explicitResultTypes + +import java.nio.file.Paths + +object ExplicitResultTypesPrefix { + class Path + def path: java.nio.file.Path = Paths.get("") + object inner { + val file: java.nio.file.Path = path + object inner { + val nio: java.nio.file.Path = path + object inner { + val java: _root_.java.nio.file.Path = path + } + } + } +} diff --git a/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/ImmutableSeq.scala b/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/ImmutableSeq.scala new file mode 100644 index 000000000..b0fc01ad7 --- /dev/null +++ b/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/ImmutableSeq.scala @@ -0,0 +1,12 @@ +package test.explicitResultTypes + +import scala.collection.immutable.Seq + +object ImmutableSeq { + def seq(): collection.Seq[Int] = Seq.empty[Int] + def scalaSeq(): scala.Seq[Int] = Seq.empty[Int] + def foo: scala.collection.Seq[Int] = seq() + def scalaFoo: Seq[Int] = scalaSeq() + + def foo(a: Int*): List[Int] = identity(a.toList) +} diff --git a/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/LowerBound.scala b/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/LowerBound.scala new file mode 100644 index 000000000..4c6c5e453 --- /dev/null +++ b/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/LowerBound.scala @@ -0,0 +1,15 @@ +package test.explicitResultTypes + +object LowerBound { + trait Bound + class City extends Bound + class Town extends Bound + class Village extends Bound + sealed abstract class BoundObject[T <: Bound] { + def list(): List[Bound] = Nil + } + case object A extends BoundObject[City] + case object B extends BoundObject[Town] + case object C extends BoundObject[Village] + val x: List[BoundObject[? >: City & Town & Village <: City | Town | Village]] = List(A, B, C) +} diff --git a/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/SymbolReplacement.scala b/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/SymbolReplacement.scala new file mode 100644 index 000000000..01690cb19 --- /dev/null +++ b/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/SymbolReplacement.scala @@ -0,0 +1,12 @@ +package test.explicitResultTypes + +object SymbolReplacement { + trait Timer + object DefaultTimer extends Timer + def myTimer: DefaultTimer.type = DefaultTimer + + trait Animal + class Dog extends Animal + val dog: Dog = new Dog() +} + diff --git a/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/ThisTypeRef.scala b/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/ThisTypeRef.scala new file mode 100644 index 000000000..9edd72eff --- /dev/null +++ b/scalafix-tests/output/src/main/scala-3/test/explicitResultTypes/ThisTypeRef.scala @@ -0,0 +1,19 @@ + +package test.explicitResultTypes + +object ThisTypeRef { + trait Base { + class T + } + class Sub extends Base { + val ref: T = identity(new T()) + } + + trait ThisType { + def cp(): this.type + } + class ThisTypeImpl extends ThisType { + def cp(): ThisTypeImpl.this.type = this + } + +} diff --git a/scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/AnyRefWith.scala b/scalafix-tests/output/src/main/scala/test/explicitResultTypes/AnyRefWith.scala similarity index 100% rename from scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/AnyRefWith.scala rename to scalafix-tests/output/src/main/scala/test/explicitResultTypes/AnyRefWith.scala diff --git a/scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesArrayBuffer.scala b/scalafix-tests/output/src/main/scala/test/explicitResultTypes/ExplicitResultTypesArrayBuffer.scala similarity index 100% rename from scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesArrayBuffer.scala rename to scalafix-tests/output/src/main/scala/test/explicitResultTypes/ExplicitResultTypesArrayBuffer.scala diff --git a/scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesInfix.scala b/scalafix-tests/output/src/main/scala/test/explicitResultTypes/ExplicitResultTypesInfix.scala similarity index 100% rename from scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesInfix.scala rename to scalafix-tests/output/src/main/scala/test/explicitResultTypes/ExplicitResultTypesInfix.scala diff --git a/scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesNil.scala b/scalafix-tests/output/src/main/scala/test/explicitResultTypes/ExplicitResultTypesNil.scala similarity index 100% rename from scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesNil.scala rename to scalafix-tests/output/src/main/scala/test/explicitResultTypes/ExplicitResultTypesNil.scala diff --git a/scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesOnlyImplicits.scala b/scalafix-tests/output/src/main/scala/test/explicitResultTypes/ExplicitResultTypesOnlyImplicits.scala similarity index 100% rename from scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesOnlyImplicits.scala rename to scalafix-tests/output/src/main/scala/test/explicitResultTypes/ExplicitResultTypesOnlyImplicits.scala diff --git a/scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesOutline.scala b/scalafix-tests/output/src/main/scala/test/explicitResultTypes/ExplicitResultTypesOutline.scala similarity index 100% rename from scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesOutline.scala rename to scalafix-tests/output/src/main/scala/test/explicitResultTypes/ExplicitResultTypesOutline.scala diff --git a/scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesPathDependent.scala b/scalafix-tests/output/src/main/scala/test/explicitResultTypes/ExplicitResultTypesPathDependent.scala similarity index 100% rename from scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesPathDependent.scala rename to scalafix-tests/output/src/main/scala/test/explicitResultTypes/ExplicitResultTypesPathDependent.scala diff --git a/scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesRefinement.scala b/scalafix-tests/output/src/main/scala/test/explicitResultTypes/ExplicitResultTypesRefinement.scala similarity index 100% rename from scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesRefinement.scala rename to scalafix-tests/output/src/main/scala/test/explicitResultTypes/ExplicitResultTypesRefinement.scala diff --git a/scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesSingleton.scala b/scalafix-tests/output/src/main/scala/test/explicitResultTypes/ExplicitResultTypesSingleton.scala similarity index 100% rename from scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesSingleton.scala rename to scalafix-tests/output/src/main/scala/test/explicitResultTypes/ExplicitResultTypesSingleton.scala diff --git a/scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesSupermethod.scala b/scalafix-tests/output/src/main/scala/test/explicitResultTypes/ExplicitResultTypesSupermethod.scala similarity index 100% rename from scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesSupermethod.scala rename to scalafix-tests/output/src/main/scala/test/explicitResultTypes/ExplicitResultTypesSupermethod.scala diff --git a/scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesTrait.scala b/scalafix-tests/output/src/main/scala/test/explicitResultTypes/ExplicitResultTypesTrait.scala similarity index 100% rename from scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/ExplicitResultTypesTrait.scala rename to scalafix-tests/output/src/main/scala/test/explicitResultTypes/ExplicitResultTypesTrait.scala diff --git a/scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/FinalVal.scala b/scalafix-tests/output/src/main/scala/test/explicitResultTypes/FinalVal.scala similarity index 100% rename from scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/FinalVal.scala rename to scalafix-tests/output/src/main/scala/test/explicitResultTypes/FinalVal.scala diff --git a/scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/RefinementConfig.scala b/scalafix-tests/output/src/main/scala/test/explicitResultTypes/RefinementConfig.scala similarity index 100% rename from scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/RefinementConfig.scala rename to scalafix-tests/output/src/main/scala/test/explicitResultTypes/RefinementConfig.scala diff --git a/scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/Rename.scala b/scalafix-tests/output/src/main/scala/test/explicitResultTypes/Rename.scala similarity index 100% rename from scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/Rename.scala rename to scalafix-tests/output/src/main/scala/test/explicitResultTypes/Rename.scala diff --git a/scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/ScalaPackage.scala b/scalafix-tests/output/src/main/scala/test/explicitResultTypes/ScalaPackage.scala similarity index 100% rename from scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/ScalaPackage.scala rename to scalafix-tests/output/src/main/scala/test/explicitResultTypes/ScalaPackage.scala diff --git a/scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/SeenFromTypeParams.scala b/scalafix-tests/output/src/main/scala/test/explicitResultTypes/SeenFromTypeParams.scala similarity index 100% rename from scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/SeenFromTypeParams.scala rename to scalafix-tests/output/src/main/scala/test/explicitResultTypes/SeenFromTypeParams.scala diff --git a/scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/Tuple1ExplicitResultTypes.scala b/scalafix-tests/output/src/main/scala/test/explicitResultTypes/Tuple1ExplicitResultTypes.scala similarity index 100% rename from scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/Tuple1ExplicitResultTypes.scala rename to scalafix-tests/output/src/main/scala/test/explicitResultTypes/Tuple1ExplicitResultTypes.scala diff --git a/scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/package.scala b/scalafix-tests/output/src/main/scala/test/explicitResultTypes/package.scala similarity index 100% rename from scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/package.scala rename to scalafix-tests/output/src/main/scala/test/explicitResultTypes/package.scala diff --git a/scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/testpkg/package.scala b/scalafix-tests/output/src/main/scala/test/explicitResultTypes/testpkg/package.scala similarity index 100% rename from scalafix-tests/output/src/main/scala-2/test/explicitResultTypes/testpkg/package.scala rename to scalafix-tests/output/src/main/scala/test/explicitResultTypes/testpkg/package.scala From 7c2268c00baaf738dcbb850a158801d048d988a3 Mon Sep 17 00:00:00 2001 From: Brice Jaglin Date: Fri, 27 Sep 2024 14:03:35 +0200 Subject: [PATCH 02/13] run ExplicitResultTypes on Scala 3 projects --- .scalafix-scala3.conf | 20 ------------ .scalafix-scala2.conf => .scalafix.conf | 0 project/ScalafixBuild.scala | 8 +---- .../scalafix/internal/util/DocConstants.scala | 32 +++++++++---------- .../src/main/scala/scalafix/v0/Symbol.scala | 4 +-- 5 files changed, 19 insertions(+), 45 deletions(-) delete mode 100644 .scalafix-scala3.conf rename .scalafix-scala2.conf => .scalafix.conf (100%) diff --git a/.scalafix-scala3.conf b/.scalafix-scala3.conf deleted file mode 100644 index 8114d5a78..000000000 --- a/.scalafix-scala3.conf +++ /dev/null @@ -1,20 +0,0 @@ -rules = [ - OrganizeImports, - RemoveUnused -] - -OrganizeImports { - groupedImports = Explode - expandRelative = true - removeUnused = true - groups = [ - "re:javax?\\." - "scala." - "scala.meta." - "*" - ] -} - -RemoveUnused { - imports = false -} \ No newline at end of file diff --git a/.scalafix-scala2.conf b/.scalafix.conf similarity index 100% rename from .scalafix-scala2.conf rename to .scalafix.conf diff --git a/project/ScalafixBuild.scala b/project/ScalafixBuild.scala index 3f66fc02c..8c92cd127 100644 --- a/project/ScalafixBuild.scala +++ b/project/ScalafixBuild.scala @@ -316,13 +316,7 @@ object ScalafixBuild extends AutoPlugin with GhpagesKeys { if (scalaVersion.value.startsWith("3")) Set.empty else Set.empty }, - mimaBinaryIssueFilters ++= Mima.ignoredABIProblems, - scalafixConfig := { - if (scalaBinaryVersion.value.startsWith("2")) - Some(file(".scalafix-scala2.conf")) - else - Some(file(".scalafix-scala3.conf")) - } + mimaBinaryIssueFilters ++= Mima.ignoredABIProblems ) /** diff --git a/scalafix-core/src/main/scala/scalafix/internal/util/DocConstants.scala b/scalafix-core/src/main/scala/scalafix/internal/util/DocConstants.scala index 462218ee3..8008f3e82 100644 --- a/scalafix-core/src/main/scala/scalafix/internal/util/DocConstants.scala +++ b/scalafix-core/src/main/scala/scalafix/internal/util/DocConstants.scala @@ -2,24 +2,24 @@ package scalafix.internal.util import org.typelevel.paiges.Doc object DocConstants { - val `|` = Doc.char('|') - val `&` = Doc.char('&') - val `.` = Doc.char('.') - val `#` = Doc.char('#') - val `(` = Doc.char('(') - val `)` = Doc.char(')') - val `[` = Doc.char('[') - val `]` = Doc.char(']') - val `{` = Doc.char('{') - val `}` = Doc.char('}') - val `=>` = Doc.text("=>") - val `=>>` = Doc.text("=>>") - val `()` = Doc.text("()") - val `*` = Doc.char('*') + val `|`: Doc = Doc.char('|') + val `&`: Doc = Doc.char('&') + val `.`: Doc = Doc.char('.') + val `#`: Doc = Doc.char('#') + val `(`: Doc = Doc.char('(') + val `)`: Doc = Doc.char(')') + val `[`: Doc = Doc.char('[') + val `]`: Doc = Doc.char(']') + val `{`: Doc = Doc.char('{') + val `}`: Doc = Doc.char('}') + val `=>`: Doc = Doc.text("=>") + val `=>>`: Doc = Doc.text("=>>") + val `()`: Doc = Doc.text("()") + val `*`: Doc = Doc.char('*') val L: Doc = Doc.char('L') val f: Doc = Doc.char('f') - val `:` = Doc.char(':') - val `@` = Doc.char('@') + val `:`: Doc = Doc.char(':') + val `@`: Doc = Doc.char('@') val `val`: Doc = Doc.text("val") val `var`: Doc = Doc.text("var") val `def`: Doc = Doc.text("def") diff --git a/scalafix-core/src/main/scala/scalafix/v0/Symbol.scala b/scalafix-core/src/main/scala/scalafix/v0/Symbol.scala index 7d1310bf3..941d0f278 100644 --- a/scalafix-core/src/main/scala/scalafix/v0/Symbol.scala +++ b/scalafix-core/src/main/scala/scalafix/v0/Symbol.scala @@ -53,9 +53,9 @@ object Symbol { // https://github.com/scalameta/scalameta/pull/1241. def apply(s: String): Symbol = { object naiveParser { - val EOL = System.lineSeparator() + val EOL: String = System.lineSeparator() var i = 0 - def fail(message: String = "invalid symbol format") = { + def fail(message: String = "invalid symbol format"): Nothing = { val caret = " " * (i - 1) + "^" throw new IllegalArgumentException(s"$message$EOL$s$EOL$caret") } From 71714acc558c2a710e022a8d999dc81a76c9620f Mon Sep 17 00:00:00 2001 From: Brice Jaglin Date: Fri, 27 Sep 2024 14:17:56 +0200 Subject: [PATCH 03/13] make it clear that we also use minor version to check compatibility --- .../scalafix/internal/rule/ExplicitResultTypes.scala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/scalafix-rules/src/main/scala-3/scalafix/internal/rule/ExplicitResultTypes.scala b/scalafix-rules/src/main/scala-3/scalafix/internal/rule/ExplicitResultTypes.scala index 5b0ad4e4f..8d28b39a4 100644 --- a/scalafix-rules/src/main/scala-3/scalafix/internal/rule/ExplicitResultTypes.scala +++ b/scalafix-rules/src/main/scala-3/scalafix/internal/rule/ExplicitResultTypes.scala @@ -19,7 +19,7 @@ final class ExplicitResultTypes( val compilerScalaVersion: String = RulesBuildInfo.scalaVersion - private def toBinaryVersion(v: String) = v.split('.').take(2).mkString(".") + private def stripPatchVersion(v: String) = v.split('.').take(2).mkString(".") override def description: String = "Inserts type annotations for inferred public members." @@ -44,9 +44,8 @@ final class ExplicitResultTypes( c, Option { if ( - toBinaryVersion(config.scalaVersion) == toBinaryVersion( - compilerScalaVersion - ) + stripPatchVersion(config.scalaVersion) == + stripPatchVersion(compilerScalaVersion) ) PcExplicitResultTypes .static(config, new ScalaPresentationCompiler()) From c8a266dfa34ad7fb04e2a2c3ee153ec95cfb4197 Mon Sep 17 00:00:00 2001 From: Brice Jaglin Date: Fri, 27 Sep 2024 14:32:24 +0200 Subject: [PATCH 04/13] lift more common stuff to ExplicitResultTypesBase --- .../internal/rule/ExplicitResultTypes.scala | 17 +++-------------- .../internal/rule/ExplicitResultTypes.scala | 13 +------------ .../internal/rule/ExplicitResultTypesBase.scala | 15 ++++++++++++++- 3 files changed, 18 insertions(+), 27 deletions(-) diff --git a/scalafix-rules/src/main/scala-2/scalafix/internal/rule/ExplicitResultTypes.scala b/scalafix-rules/src/main/scala-2/scalafix/internal/rule/ExplicitResultTypes.scala index 2290c3570..382ddd6c5 100644 --- a/scalafix-rules/src/main/scala-2/scalafix/internal/rule/ExplicitResultTypes.scala +++ b/scalafix-rules/src/main/scala-2/scalafix/internal/rule/ExplicitResultTypes.scala @@ -5,7 +5,6 @@ import scala.util.control.NonFatal import scala.meta._ import scala.meta.internal.pc.ScalafixGlobal -import buildinfo.RulesBuildInfo import metaconfig.Configured import scalafix.internal.compat.CompilerCompat._ import scalafix.internal.pc.PcExplicitResultTypes @@ -17,8 +16,7 @@ final class ExplicitResultTypes( val config: ExplicitResultTypesConfig, global: LazyValue[Option[ScalafixGlobal]], fallback: LazyValue[Option[PcExplicitResultTypes]] -) extends SemanticRule("ExplicitResultTypes") - with ExplicitResultTypesBase[Scala2Printer] { +) extends ExplicitResultTypesBase[Scala2Printer] { def this() = this( ExplicitResultTypesConfig.default, @@ -26,15 +24,6 @@ final class ExplicitResultTypes( LazyValue.now(None) ) - val compilerScalaVersion: String = RulesBuildInfo.scalaVersion - - private def toBinaryVersion(v: String) = v.split('.').take(2).mkString(".") - - override def description: String = - "Inserts type annotations for inferred public members. " + - s"Only compatible with Scala 2.x." - override def isRewrite: Boolean = true - override def afterComplete(): Unit = { shutdownCompiler() } @@ -68,9 +57,9 @@ final class ExplicitResultTypes( } } val inputBinaryScalaVersion = - toBinaryVersion(config.scalaVersion) + stripPatchVersion(config.scalaVersion) val runtimeBinaryScalaVersion = - toBinaryVersion(compilerScalaVersion) + stripPatchVersion(compilerScalaVersion) if ( config.scalacClasspath.nonEmpty && inputBinaryScalaVersion != runtimeBinaryScalaVersion ) { diff --git a/scalafix-rules/src/main/scala-3/scalafix/internal/rule/ExplicitResultTypes.scala b/scalafix-rules/src/main/scala-3/scalafix/internal/rule/ExplicitResultTypes.scala index 8d28b39a4..ec64aa5bd 100644 --- a/scalafix-rules/src/main/scala-3/scalafix/internal/rule/ExplicitResultTypes.scala +++ b/scalafix-rules/src/main/scala-3/scalafix/internal/rule/ExplicitResultTypes.scala @@ -2,7 +2,6 @@ package scalafix.internal.rule import scala.meta.* -import buildinfo.RulesBuildInfo import dotty.tools.pc.ScalaPresentationCompiler import metaconfig.Configured import scalafix.internal.pc.PcExplicitResultTypes @@ -12,20 +11,10 @@ import scalafix.v1.* final class ExplicitResultTypes( val config: ExplicitResultTypesConfig, fallback: Option[PcExplicitResultTypes] -) extends SemanticRule("ExplicitResultTypes") - with ExplicitResultTypesBase[Scala3Printer] { +) extends ExplicitResultTypesBase[Scala3Printer] { def this() = this(ExplicitResultTypesConfig.default, None) - val compilerScalaVersion: String = RulesBuildInfo.scalaVersion - - private def stripPatchVersion(v: String) = v.split('.').take(2).mkString(".") - - override def description: String = - "Inserts type annotations for inferred public members." - - override def isRewrite: Boolean = true - override def afterComplete(): Unit = { shutdownCompiler() } diff --git a/scalafix-rules/src/main/scala/scalafix/internal/rule/ExplicitResultTypesBase.scala b/scalafix-rules/src/main/scala/scalafix/internal/rule/ExplicitResultTypesBase.scala index f08537fe5..68cdd7353 100644 --- a/scalafix-rules/src/main/scala/scalafix/internal/rule/ExplicitResultTypesBase.scala +++ b/scalafix-rules/src/main/scala/scalafix/internal/rule/ExplicitResultTypesBase.scala @@ -3,6 +3,7 @@ package scalafix.internal.rule import scala.meta._ import scala.meta.contrib._ +import buildinfo.RulesBuildInfo import scalafix.util.TokenOps import scalafix.v1._ @@ -17,10 +18,22 @@ trait Printer { } -trait ExplicitResultTypesBase[P <: Printer] { +abstract class ExplicitResultTypesBase[P <: Printer] + extends SemanticRule("ExplicitResultTypes") { + + override def description: String = + "Inserts type annotations for inferred public members." + + override def isRewrite: Boolean = true val config: ExplicitResultTypesConfig + protected val compilerScalaVersion: String = + RulesBuildInfo.scalaVersion + + protected def stripPatchVersion(v: String): String = + v.split('.').take(2).mkString(".") + // Don't explicitly annotate vals when the right-hand body is a single call // to `implicitly` or `summon`. Prevents ambiguous implicit. Not annotating in such cases, // this a common trick employed implicit-heavy code to workaround SI-2712. From 954cdf778c67692e748327ea4981d402edf026aa Mon Sep 17 00:00:00 2001 From: Brice Jaglin Date: Fri, 27 Sep 2024 14:47:47 +0200 Subject: [PATCH 05/13] improve comment --- .../scalafix/internal/pc/PcExplicitResultTypes.scala | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/scalafix-rules/src/main/scala/scalafix/internal/pc/PcExplicitResultTypes.scala b/scalafix-rules/src/main/scala/scalafix/internal/pc/PcExplicitResultTypes.scala index 3368ffe37..c10c210b5 100644 --- a/scalafix-rules/src/main/scala/scalafix/internal/pc/PcExplicitResultTypes.scala +++ b/scalafix-rules/src/main/scala/scalafix/internal/pc/PcExplicitResultTypes.scala @@ -18,12 +18,6 @@ import scalafix.patch.Patch import scalafix.patch.Patch.empty import scalafix.v1._ -/** - * Fallback tries to download and use existing presentation compiler either from - * Metals or the Scala compiler itself. - * - * @param pc - */ final class PcExplicitResultTypes( pc: LazyValue[Option[PresentationCompiler]] ) { @@ -86,6 +80,10 @@ final class PcExplicitResultTypes( } +/** + * Prepare the static presentation compiler already in the classpath or download + * and classload one dynamically. + */ object PcExplicitResultTypes { private def configure( config: Configuration, From 466d5a193ad125150010da9c9424636b3875a6d0 Mon Sep 17 00:00:00 2001 From: Brice Jaglin Date: Fri, 27 Sep 2024 14:35:27 +0200 Subject: [PATCH 06/13] hide the underlying presentation compiler --- .../main/scala/scalafix/internal/pc/PcExplicitResultTypes.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scalafix-rules/src/main/scala/scalafix/internal/pc/PcExplicitResultTypes.scala b/scalafix-rules/src/main/scala/scalafix/internal/pc/PcExplicitResultTypes.scala index c10c210b5..513f6ac40 100644 --- a/scalafix-rules/src/main/scala/scalafix/internal/pc/PcExplicitResultTypes.scala +++ b/scalafix-rules/src/main/scala/scalafix/internal/pc/PcExplicitResultTypes.scala @@ -18,7 +18,7 @@ import scalafix.patch.Patch import scalafix.patch.Patch.empty import scalafix.v1._ -final class PcExplicitResultTypes( +final class PcExplicitResultTypes private ( pc: LazyValue[Option[PresentationCompiler]] ) { From 89f9e0b69917aa3ac792c9533aff8a29a5477602 Mon Sep 17 00:00:00 2001 From: Brice Jaglin Date: Fri, 27 Sep 2024 14:46:55 +0200 Subject: [PATCH 07/13] use blocking cache to prevent duplicates --- .../src/main/scala/scalafix/internal/pc/Embedded.scala | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/scalafix-rules/src/main/scala/scalafix/internal/pc/Embedded.scala b/scalafix-rules/src/main/scala/scalafix/internal/pc/Embedded.scala index 70d6f699c..46d96a9e7 100644 --- a/scalafix-rules/src/main/scala/scalafix/internal/pc/Embedded.scala +++ b/scalafix-rules/src/main/scala/scalafix/internal/pc/Embedded.scala @@ -3,7 +3,6 @@ package scalafix.internal.pc import java.net.URLClassLoader import java.util.ServiceLoader -import scala.collection.concurrent.TrieMap import scala.jdk.CollectionConverters._ import scala.meta.pc.PresentationCompiler @@ -14,15 +13,15 @@ import coursierapi.MavenRepository object Embedded { - private val presentationCompilers: TrieMap[String, URLClassLoader] = - TrieMap.empty + private val presentationCompilers = + new java.util.concurrent.ConcurrentHashMap[String, URLClassLoader]() def presentationCompiler( scalaVersion: String ): PresentationCompiler = { - val classloader = presentationCompilers.getOrElseUpdate( + val classloader = presentationCompilers.computeIfAbsent( scalaVersion, - newPresentationCompilerClassLoader(scalaVersion) + newPresentationCompilerClassLoader ) val presentationCompilerClassname = if (supportPresentationCompilerInDotty(scalaVersion)) { From 946e1436fe6f1bcbc7102f7ffddec668bb5cab51 Mon Sep 17 00:00:00 2001 From: Brice Jaglin Date: Fri, 27 Sep 2024 18:11:51 +0200 Subject: [PATCH 08/13] remove mtags / mtags-interfaces to simplify the implementation They would have been useful for 2 things. 1. Support early Scala 3 versions (mtags) Considering RemoveUnused already requires a minimum Scala 3 version, and that we have presentation compiler for LTS patch releases, I don't think it is a big deal. 2. Support Scala 3 from cli_2 (mtags-interfaces + PC) Since Scalafix (build) clients have been selecting the cli version based on the target for years, and we found a backward-compatible way to let them ask for a given Scala 3 minor version, the migration path to cli_3 is easy. In both cases, dynamic loading was needed, causing potential resolution issues and more classloader isolation overhead. --- build.sbt | 13 +---- .../internal/rule/ExplicitResultTypes.scala | 57 ++++++------------- .../pc/CompilerOffsetParams.scala | 0 .../internal => scala-3}/pc/Embedded.scala | 48 ++++------------ .../pc/PcExplicitResultTypes.scala | 6 +- .../pc/PresentationCompilerClassloader.scala | 0 .../pc/PresentationCompilerConfigImpl.scala | 0 .../internal/rule/ExplicitResultTypes.scala | 51 +++++++++++------ .../scala/scalafix/tests/rule/RuleSuite.scala | 15 +++-- .../rules/ExplicitResultTypesSuite.scala | 26 +++++++++ .../rules/ExplicitResultTypesSuite.scala | 37 ++++++++++++ 11 files changed, 140 insertions(+), 113 deletions(-) rename scalafix-rules/src/main/{scala/scalafix/internal => scala-3}/pc/CompilerOffsetParams.scala (100%) rename scalafix-rules/src/main/{scala/scalafix/internal => scala-3}/pc/Embedded.scala (55%) rename scalafix-rules/src/main/{scala/scalafix/internal => scala-3}/pc/PcExplicitResultTypes.scala (97%) rename scalafix-rules/src/main/{scala/scalafix/internal => scala-3}/pc/PresentationCompilerClassloader.scala (100%) rename scalafix-rules/src/main/{scala/scalafix/internal => scala-3}/pc/PresentationCompilerConfigImpl.scala (100%) create mode 100644 scalafix-tests/integration/src/test/scala-2/scalafix/tests/rules/ExplicitResultTypesSuite.scala create mode 100644 scalafix-tests/integration/src/test/scala-3/scalafix/tests/rules/ExplicitResultTypesSuite.scala diff --git a/build.sbt b/build.sbt index c990e8ec9..13967093e 100644 --- a/build.sbt +++ b/build.sbt @@ -111,14 +111,6 @@ lazy val rules = projectMatrix description := "Built-in Scalafix rules", isFullCrossVersion, buildInfoSettingsForRules, - libraryDependencies ++= List( - ("org.scalameta" % "mtags-interfaces" % "1.3.4") - .exclude("org.eclipse.lsp4j", "org.eclipse.lsp4j") - .exclude("org.eclipse.lsp4j", "org.eclipse.lsp4j.jsonrpc"), - // latest version release for JDK 8, this will be dropped from interfaces at some point - "org.eclipse.lsp4j" % "org.eclipse.lsp4j" % "0.20.1", - coursierInterfaces - ), libraryDependencies ++= { if (!isScala3.value) Seq( @@ -128,8 +120,9 @@ lazy val rules = projectMatrix collectionCompat ) else - List( - "org.scala-lang" %% "scala3-presentation-compiler" % scalaVersion.value + Seq( + "org.scala-lang" %% "scala3-presentation-compiler" % scalaVersion.value, + coursierInterfaces ) }, // companion of `.dependsOn(core)` diff --git a/scalafix-rules/src/main/scala-2/scalafix/internal/rule/ExplicitResultTypes.scala b/scalafix-rules/src/main/scala-2/scalafix/internal/rule/ExplicitResultTypes.scala index 382ddd6c5..3c60e4168 100644 --- a/scalafix-rules/src/main/scala-2/scalafix/internal/rule/ExplicitResultTypes.scala +++ b/scalafix-rules/src/main/scala-2/scalafix/internal/rule/ExplicitResultTypes.scala @@ -7,22 +7,16 @@ import scala.meta.internal.pc.ScalafixGlobal import metaconfig.Configured import scalafix.internal.compat.CompilerCompat._ -import scalafix.internal.pc.PcExplicitResultTypes import scalafix.internal.v1.LazyValue import scalafix.patch.Patch import scalafix.v1._ final class ExplicitResultTypes( val config: ExplicitResultTypesConfig, - global: LazyValue[Option[ScalafixGlobal]], - fallback: LazyValue[Option[PcExplicitResultTypes]] + global: LazyValue[Option[ScalafixGlobal]] ) extends ExplicitResultTypesBase[Scala2Printer] { - def this() = this( - ExplicitResultTypesConfig.default, - LazyValue.now(None), - LazyValue.now(None) - ) + def this() = this(ExplicitResultTypesConfig.default, LazyValue.now(None)) override def afterComplete(): Unit = { shutdownCompiler() @@ -63,30 +57,23 @@ final class ExplicitResultTypes( if ( config.scalacClasspath.nonEmpty && inputBinaryScalaVersion != runtimeBinaryScalaVersion ) { - config.conf // Support deprecated explicitReturnTypes config - .getOrElse("explicitReturnTypes", "ExplicitResultTypes")( - ExplicitResultTypesConfig.default - ) - .map(c => - new ExplicitResultTypes( - c, - LazyValue.now(None), - LazyValue.now(Option(PcExplicitResultTypes.dynamic(config))) - ) - ) + Configured.error( + s"The ExplicitResultTypes rule needs to run with the same Scala binary version as the one used to compile target sources ($inputBinaryScalaVersion). " + + s"To fix this problem, either remove ExplicitResultTypes from .scalafix.conf or make sure Scalafix is loaded with $inputBinaryScalaVersion." + ) } else { config.conf // Support deprecated explicitReturnTypes config .getOrElse("explicitReturnTypes", "ExplicitResultTypes")( ExplicitResultTypesConfig.default ) - .map(c => new ExplicitResultTypes(c, newGlobal, LazyValue.now(None))) + .map(c => new ExplicitResultTypes(c, newGlobal)) } } override def fix(implicit ctx: SemanticDocument): Patch = try { val typesLazy = global.value.map(new CompilerTypePrinter(_, config)) - implicit val printer = new Scala2Printer(typesLazy, fallback) + implicit val printer = new Scala2Printer(typesLazy) unsafeFix() } catch { case _: CompilerException if !config.fatalWarnings => @@ -95,8 +82,7 @@ final class ExplicitResultTypes( } class Scala2Printer( - globalPrinter: Option[CompilerTypePrinter], - fallback: LazyValue[Option[PcExplicitResultTypes]] + globalPrinter: Option[CompilerTypePrinter] ) extends Printer { def defnType( defn: Defn, @@ -104,22 +90,13 @@ class Scala2Printer( space: String )(implicit ctx: SemanticDocument - ): Option[Patch] = { - - globalPrinter match { - case Some(types) => - for { - name <- ExplicitResultTypesBase.defnName(defn) - defnSymbol <- name.symbol.asNonEmpty - patch <- types.toPatch(name.pos, defnSymbol, replace, defn, space) - } yield { - patch - } - case None => - fallback.value.flatMap { fallbackExplicit => - fallbackExplicit.defnType(replace) - } + ): Option[Patch] = + for { + name <- ExplicitResultTypesBase.defnName(defn) + defnSymbol <- name.symbol.asNonEmpty + printer <- globalPrinter + patch <- printer.toPatch(name.pos, defnSymbol, replace, defn, space) + } yield { + patch } - - } } diff --git a/scalafix-rules/src/main/scala/scalafix/internal/pc/CompilerOffsetParams.scala b/scalafix-rules/src/main/scala-3/pc/CompilerOffsetParams.scala similarity index 100% rename from scalafix-rules/src/main/scala/scalafix/internal/pc/CompilerOffsetParams.scala rename to scalafix-rules/src/main/scala-3/pc/CompilerOffsetParams.scala diff --git a/scalafix-rules/src/main/scala/scalafix/internal/pc/Embedded.scala b/scalafix-rules/src/main/scala-3/pc/Embedded.scala similarity index 55% rename from scalafix-rules/src/main/scala/scalafix/internal/pc/Embedded.scala rename to scalafix-rules/src/main/scala-3/pc/Embedded.scala index 46d96a9e7..81202b90e 100644 --- a/scalafix-rules/src/main/scala/scalafix/internal/pc/Embedded.scala +++ b/scalafix-rules/src/main/scala-3/pc/Embedded.scala @@ -3,7 +3,7 @@ package scalafix.internal.pc import java.net.URLClassLoader import java.util.ServiceLoader -import scala.jdk.CollectionConverters._ +import scala.jdk.CollectionConverters.* import scala.meta.pc.PresentationCompiler @@ -23,49 +23,14 @@ object Embedded { scalaVersion, newPresentationCompilerClassLoader ) - val presentationCompilerClassname = - if (supportPresentationCompilerInDotty(scalaVersion)) { - "dotty.tools.pc.ScalaPresentationCompiler" - } else { - "scala.meta.pc.ScalaPresentationCompiler" - } serviceLoader( classOf[PresentationCompiler], - presentationCompilerClassname, + "dotty.tools.pc.ScalaPresentationCompiler", classloader ) } - private def supportPresentationCompilerInDotty(scalaVersion: String) = { - scalaVersion - .replaceAll(raw"-RC\d+", "") - .split("\\.") - .take(3) - .map(_.toInt) match { - case Array(3, minor, patch) => minor > 3 || minor == 3 && patch >= 4 - case _ => false - } - } - - private def scala3PresentationCompilerDependencies(version: String) = - if (supportPresentationCompilerInDotty(version)) { - val dep = Dependency - .of("org.scala-lang", "scala3-presentation-compiler_3", version) - - // some versions of the presentation compiler depend on versions only build for JDK 11 - dep.addExclusion("org.eclipse.lsp4j", "org.eclipse.lsp4j") - dep.addExclusion("org.eclipse.lsp4j", "org.eclipse.lsp4j.jsonrpc") - // last built with JDK 8 - val lsp4jDep = - Dependency.of("org.eclipse.lsp4j", "org.eclipse.lsp4j", "0.20.1") - List(dep, lsp4jDep) - } else - List( - // TODO should use build info etc. instead of using 1.3.4 - Dependency.of("org.scalameta", s"mtags_${version}", "1.3.4") - ) - private def serviceLoader[T]( cls: Class[T], className: String, @@ -86,11 +51,18 @@ object Embedded { ): URLClassLoader = { val deps = - scala3PresentationCompilerDependencies(scalaVersion) + List( + Dependency.of( + "org.scala-lang", + "scala3-presentation-compiler_3", + scalaVersion + ) + ) val jars = Fetch .create() .addDependencies(deps: _*) .addRepositories( + // for some versions, the presentation compiler depends on mtags-interfaces SNAPSHOTs MavenRepository.of( "https://oss.sonatype.org/content/repositories/snapshots" ) diff --git a/scalafix-rules/src/main/scala/scalafix/internal/pc/PcExplicitResultTypes.scala b/scalafix-rules/src/main/scala-3/pc/PcExplicitResultTypes.scala similarity index 97% rename from scalafix-rules/src/main/scala/scalafix/internal/pc/PcExplicitResultTypes.scala rename to scalafix-rules/src/main/scala-3/pc/PcExplicitResultTypes.scala index 513f6ac40..2c6721124 100644 --- a/scalafix-rules/src/main/scala/scalafix/internal/pc/PcExplicitResultTypes.scala +++ b/scalafix-rules/src/main/scala-3/pc/PcExplicitResultTypes.scala @@ -2,11 +2,11 @@ package scalafix.internal.pc import java.nio.file.Paths -import scala.jdk.CollectionConverters._ +import scala.jdk.CollectionConverters.* import scala.util.Random import scala.util.Try -import scala.meta._ +import scala.meta.* import scala.meta.inputs.Input.File import scala.meta.inputs.Input.VirtualFile import scala.meta.pc.PresentationCompiler @@ -16,7 +16,7 @@ import scala.meta.trees.Origin.Parsed import scalafix.internal.v1.LazyValue import scalafix.patch.Patch import scalafix.patch.Patch.empty -import scalafix.v1._ +import scalafix.v1.* final class PcExplicitResultTypes private ( pc: LazyValue[Option[PresentationCompiler]] diff --git a/scalafix-rules/src/main/scala/scalafix/internal/pc/PresentationCompilerClassloader.scala b/scalafix-rules/src/main/scala-3/pc/PresentationCompilerClassloader.scala similarity index 100% rename from scalafix-rules/src/main/scala/scalafix/internal/pc/PresentationCompilerClassloader.scala rename to scalafix-rules/src/main/scala-3/pc/PresentationCompilerClassloader.scala diff --git a/scalafix-rules/src/main/scala/scalafix/internal/pc/PresentationCompilerConfigImpl.scala b/scalafix-rules/src/main/scala-3/pc/PresentationCompilerConfigImpl.scala similarity index 100% rename from scalafix-rules/src/main/scala/scalafix/internal/pc/PresentationCompilerConfigImpl.scala rename to scalafix-rules/src/main/scala-3/pc/PresentationCompilerConfigImpl.scala diff --git a/scalafix-rules/src/main/scala-3/scalafix/internal/rule/ExplicitResultTypes.scala b/scalafix-rules/src/main/scala-3/scalafix/internal/rule/ExplicitResultTypes.scala index ec64aa5bd..fa1bcf6e0 100644 --- a/scalafix-rules/src/main/scala-3/scalafix/internal/rule/ExplicitResultTypes.scala +++ b/scalafix-rules/src/main/scala-3/scalafix/internal/rule/ExplicitResultTypes.scala @@ -24,25 +24,42 @@ final class ExplicitResultTypes( } override def withConfiguration(config: Configuration): Configured[Rule] = { - config.conf // Support deprecated explicitReturnTypes config - .getOrElse("explicitReturnTypes", "ExplicitResultTypes")( - ExplicitResultTypesConfig.default + val majorMinorScalaVersion = + stripPatchVersion(config.scalaVersion) + + if (config.scalaVersion.startsWith("2.")) { + Configured.error( + s"The ExplicitResultTypes rule needs to run with the same Scala binary version as the one used to compile target sources ($majorMinorScalaVersion). " + + s"To fix this problem, either remove ExplicitResultTypes from .scalafix.conf or make sure Scalafix is loaded with $majorMinorScalaVersion." ) - .map(c => - new ExplicitResultTypes( - c, - Option { - if ( - stripPatchVersion(config.scalaVersion) == - stripPatchVersion(compilerScalaVersion) - ) - PcExplicitResultTypes - .static(config, new ScalaPresentationCompiler()) - else - PcExplicitResultTypes.dynamic(config) - } - ) + } else if ( + Seq("3.0", "3.1", "3.2").exists(v => config.scalaVersion.startsWith(v)) + ) { + Configured.error( + s"The ExplicitResultTypes rule requires Scala 3 target sources to be compiled with Scala 3.3.0 or greater, but they were compiled with ${config.scalaVersion}. " + + "To fix this problem, either remove ExplicitResultTypes from .scalafix.conf or upgrade the compiler in your build." ) + } else { + config.conf // Support deprecated explicitReturnTypes config + .getOrElse("explicitReturnTypes", "ExplicitResultTypes")( + ExplicitResultTypesConfig.default + ) + .map(c => + new ExplicitResultTypes( + c, + Option { + if ( + stripPatchVersion(config.scalaVersion) == + stripPatchVersion(compilerScalaVersion) + ) + PcExplicitResultTypes + .static(config, new ScalaPresentationCompiler()) + else + PcExplicitResultTypes.dynamic(config) + } + ) + ) + } } override def fix(implicit ctx: SemanticDocument): Patch = diff --git a/scalafix-tests/expect/src/test/scala/scalafix/tests/rule/RuleSuite.scala b/scalafix-tests/expect/src/test/scala/scalafix/tests/rule/RuleSuite.scala index 79a6e7421..1cd74c005 100644 --- a/scalafix-tests/expect/src/test/scala/scalafix/tests/rule/RuleSuite.scala +++ b/scalafix-tests/expect/src/test/scala/scalafix/tests/rule/RuleSuite.scala @@ -2,6 +2,7 @@ package scalafix.tests.rule import scala.util.control.NonFatal +import buildinfo.RulesBuildInfo import org.scalatest.exceptions.TestFailedException import org.scalatest.funsuite.AnyFunSuiteLike import scalafix.testkit._ @@ -32,13 +33,17 @@ object RuleSuite { } class RuleSuite extends AbstractSemanticRuleSuite with AnyFunSuiteLike { - // TODO: remove in https://github.com/scalacenter/scalafix/pull/2023 override def runOn(diffTest: RuleTest): Unit = { - if ( - buildinfo.RulesBuildInfo.scalaVersion.startsWith("3") && - props.scalaVersion.startsWith("2") && + def stripPatch(v: String) = v.split('.').take(2).mkString(".") + + val versionMismatch = + stripPatch(RulesBuildInfo.scalaVersion) != stripPatch(props.scalaVersion) + val explicitResultTypesTest = diffTest.path.input.toNIO.toString.contains("explicitResultTypes") - ) return + + // ExplicitResultTypes can only run against sources compiled with the sam + // binary version as the one used to compile the rule + if (versionMismatch && explicitResultTypesTest) return else super.runOn(diffTest) } diff --git a/scalafix-tests/integration/src/test/scala-2/scalafix/tests/rules/ExplicitResultTypesSuite.scala b/scalafix-tests/integration/src/test/scala-2/scalafix/tests/rules/ExplicitResultTypesSuite.scala new file mode 100644 index 000000000..d4fe71ba7 --- /dev/null +++ b/scalafix-tests/integration/src/test/scala-2/scalafix/tests/rules/ExplicitResultTypesSuite.scala @@ -0,0 +1,26 @@ +package scalafix.tests.rules + +import scala.meta.io.AbsolutePath + +import metaconfig.Configured +import org.scalatest.funsuite.AnyFunSuite +import scalafix.internal.rule.ExplicitResultTypes +import scalafix.v1.Configuration + +class ExplicitResultTypesSuite extends AnyFunSuite { + val rule = new ExplicitResultTypes() + val config: Configuration = + Configuration().withScalacClasspath(List(AbsolutePath.root)) + + test("Scala 3 is not supported") { + val scala3 = + config.withScalaVersion("3.3.4") + + val expected = + "The ExplicitResultTypes rule needs to run with the same Scala binary version as the one used to compile target sources (3.3). " + + "To fix this problem, either remove ExplicitResultTypes from .scalafix.conf or make sure Scalafix is loaded with 3.3." + + assert(rule.withConfiguration(scala3) == Configured.error(expected)) + } + +} diff --git a/scalafix-tests/integration/src/test/scala-3/scalafix/tests/rules/ExplicitResultTypesSuite.scala b/scalafix-tests/integration/src/test/scala-3/scalafix/tests/rules/ExplicitResultTypesSuite.scala new file mode 100644 index 000000000..0dda137bd --- /dev/null +++ b/scalafix-tests/integration/src/test/scala-3/scalafix/tests/rules/ExplicitResultTypesSuite.scala @@ -0,0 +1,37 @@ +package scalafix.tests.rules + +import scala.meta.io.AbsolutePath + +import metaconfig.Configured +import org.scalatest.funsuite.AnyFunSuite +import scalafix.internal.rule.ExplicitResultTypes +import scalafix.v1.Configuration + +class ExplicitResultTypesSuite extends AnyFunSuite { + val rule = new ExplicitResultTypes() + val config: Configuration = + Configuration().withScalacClasspath(List(AbsolutePath.root)) + + test("Scala 2 is not supported") { + val scala2 = + config.withScalaVersion("2.12.15") + + val expected = + "The ExplicitResultTypes rule needs to run with the same Scala binary version as the one used to compile target sources (2.12). " + + "To fix this problem, either remove ExplicitResultTypes from .scalafix.conf or make sure Scalafix is loaded with 2.12." + + assert(rule.withConfiguration(scala2) == Configured.error(expected)) + } + + test("Early Scala 3 is not supported") { + val earlyScala3 = + config.withScalaVersion("3.2.2") + + val expected = + "The ExplicitResultTypes rule requires Scala 3 target sources to be compiled with Scala 3.3.0 or greater, but they were compiled with 3.2.2. " + + "To fix this problem, either remove ExplicitResultTypes from .scalafix.conf or upgrade the compiler in your build." + + assert(rule.withConfiguration(earlyScala3) == Configured.error(expected)) + } + +} From 5ff95bb57fbab95181717e57f4074fddd81ef86d Mon Sep 17 00:00:00 2001 From: Brice Jaglin Date: Fri, 27 Sep 2024 18:24:05 +0200 Subject: [PATCH 09/13] all rules are now supported on all versions --- project/ScalafixBuild.scala | 7 ------- .../main/resources-3/META-INF/services/scalafix.v1.Rule | 9 --------- .../META-INF/services/scalafix.v1.Rule | 0 3 files changed, 16 deletions(-) delete mode 100644 scalafix-rules/src/main/resources-3/META-INF/services/scalafix.v1.Rule rename scalafix-rules/src/main/{resources-2 => resources}/META-INF/services/scalafix.v1.Rule (100%) diff --git a/project/ScalafixBuild.scala b/project/ScalafixBuild.scala index 8c92cd127..673f7dc74 100644 --- a/project/ScalafixBuild.scala +++ b/project/ScalafixBuild.scala @@ -280,13 +280,6 @@ object ScalafixBuild extends AutoPlugin with GhpagesKeys { Compile / console / scalacOptions := compilerOptions.value :+ "-Yrepl-class-based", Compile / doc / scalacOptions ++= scaladocOptions, - Compile / unmanagedResourceDirectories ++= { - val resourceParentDir = (Compile / resourceDirectory).value.getParentFile - CrossVersion.partialVersion(scalaVersion.value) match { - case Some((major, _)) => Seq(resourceParentDir / s"resources-${major}") - case _ => Seq() - } - }, // Don't package sources & docs when publishing locally as it adds a significant // overhead when testing because of publishLocalTransitive. Tweaking publishArtifact // would more readable, but it would also affect remote (sonatype) publishing. diff --git a/scalafix-rules/src/main/resources-3/META-INF/services/scalafix.v1.Rule b/scalafix-rules/src/main/resources-3/META-INF/services/scalafix.v1.Rule deleted file mode 100644 index f25260297..000000000 --- a/scalafix-rules/src/main/resources-3/META-INF/services/scalafix.v1.Rule +++ /dev/null @@ -1,9 +0,0 @@ -scalafix.internal.rule.DisableSyntax -scalafix.internal.rule.ExplicitResultTypes -scalafix.internal.rule.NoAutoTupling -scalafix.internal.rule.NoValInForComprehension -scalafix.internal.rule.OrganizeImports -scalafix.internal.rule.ProcedureSyntax -scalafix.internal.rule.RedundantSyntax -scalafix.internal.rule.RemoveUnused -scalafix.internal.rule.LeakingImplicitClassVal diff --git a/scalafix-rules/src/main/resources-2/META-INF/services/scalafix.v1.Rule b/scalafix-rules/src/main/resources/META-INF/services/scalafix.v1.Rule similarity index 100% rename from scalafix-rules/src/main/resources-2/META-INF/services/scalafix.v1.Rule rename to scalafix-rules/src/main/resources/META-INF/services/scalafix.v1.Rule From 154d1a52dd254b8b7c66f5bd10246b72dbc0f118 Mon Sep 17 00:00:00 2001 From: Brice Jaglin Date: Fri, 27 Sep 2024 18:34:50 +0200 Subject: [PATCH 10/13] s/PcExplicitResultTypes/PresentationCompilerTypeInferrer/ --- ...la => PresentationCompilerTypeInferrer.scala} | 12 ++++++------ .../internal/rule/ExplicitResultTypes.scala | 16 ++++++++-------- 2 files changed, 14 insertions(+), 14 deletions(-) rename scalafix-rules/src/main/scala-3/pc/{PcExplicitResultTypes.scala => PresentationCompilerTypeInferrer.scala} (92%) diff --git a/scalafix-rules/src/main/scala-3/pc/PcExplicitResultTypes.scala b/scalafix-rules/src/main/scala-3/pc/PresentationCompilerTypeInferrer.scala similarity index 92% rename from scalafix-rules/src/main/scala-3/pc/PcExplicitResultTypes.scala rename to scalafix-rules/src/main/scala-3/pc/PresentationCompilerTypeInferrer.scala index 2c6721124..6326552fa 100644 --- a/scalafix-rules/src/main/scala-3/pc/PcExplicitResultTypes.scala +++ b/scalafix-rules/src/main/scala-3/pc/PresentationCompilerTypeInferrer.scala @@ -18,7 +18,7 @@ import scalafix.patch.Patch import scalafix.patch.Patch.empty import scalafix.v1.* -final class PcExplicitResultTypes private ( +final class PresentationCompilerTypeInferrer private ( pc: LazyValue[Option[PresentationCompiler]] ) { @@ -84,7 +84,7 @@ final class PcExplicitResultTypes private ( * Prepare the static presentation compiler already in the classpath or download * and classload one dynamically. */ -object PcExplicitResultTypes { +object PresentationCompilerTypeInferrer { private def configure( config: Configuration, pc: PresentationCompiler @@ -106,7 +106,7 @@ object PcExplicitResultTypes { ) } - def dynamic(config: Configuration): PcExplicitResultTypes = { + def dynamic(config: Configuration): PresentationCompilerTypeInferrer = { val newPc: LazyValue[Option[PresentationCompiler]] = LazyValue.from { () => Try( @@ -116,18 +116,18 @@ object PcExplicitResultTypes { ) ) } - new PcExplicitResultTypes(newPc) + new PresentationCompilerTypeInferrer(newPc) } def static( config: Configuration, pc: PresentationCompiler - ): PcExplicitResultTypes = { + ): PresentationCompilerTypeInferrer = { val newPc: LazyValue[Option[PresentationCompiler]] = LazyValue.from { () => Try(configure(config, pc)) } - new PcExplicitResultTypes(newPc) + new PresentationCompilerTypeInferrer(newPc) } } diff --git a/scalafix-rules/src/main/scala-3/scalafix/internal/rule/ExplicitResultTypes.scala b/scalafix-rules/src/main/scala-3/scalafix/internal/rule/ExplicitResultTypes.scala index fa1bcf6e0..97b7e8dde 100644 --- a/scalafix-rules/src/main/scala-3/scalafix/internal/rule/ExplicitResultTypes.scala +++ b/scalafix-rules/src/main/scala-3/scalafix/internal/rule/ExplicitResultTypes.scala @@ -4,13 +4,13 @@ import scala.meta.* import dotty.tools.pc.ScalaPresentationCompiler import metaconfig.Configured -import scalafix.internal.pc.PcExplicitResultTypes +import scalafix.internal.pc.PresentationCompilerTypeInferrer import scalafix.patch.Patch import scalafix.v1.* final class ExplicitResultTypes( val config: ExplicitResultTypesConfig, - fallback: Option[PcExplicitResultTypes] + pcTypeInferrer: Option[PresentationCompilerTypeInferrer] ) extends ExplicitResultTypesBase[Scala3Printer] { def this() = this(ExplicitResultTypesConfig.default, None) @@ -20,7 +20,7 @@ final class ExplicitResultTypes( } private def shutdownCompiler(): Unit = { - fallback.foreach(_.shutdownCompiler()) + pcTypeInferrer.foreach(_.shutdownCompiler()) } override def withConfiguration(config: Configuration): Configured[Rule] = { @@ -52,10 +52,10 @@ final class ExplicitResultTypes( stripPatchVersion(config.scalaVersion) == stripPatchVersion(compilerScalaVersion) ) - PcExplicitResultTypes + PresentationCompilerTypeInferrer .static(config, new ScalaPresentationCompiler()) else - PcExplicitResultTypes.dynamic(config) + PresentationCompilerTypeInferrer.dynamic(config) } ) ) @@ -64,7 +64,7 @@ final class ExplicitResultTypes( override def fix(implicit ctx: SemanticDocument): Patch = try { - implicit val printer = new Scala3Printer(fallback) + implicit val printer = new Scala3Printer(pcTypeInferrer) unsafeFix() } catch { case _: Throwable if !config.fatalWarnings => @@ -74,7 +74,7 @@ final class ExplicitResultTypes( } class Scala3Printer( - fallback: Option[PcExplicitResultTypes] + pcTypeInferrer: Option[PresentationCompilerTypeInferrer] ) extends Printer { def defnType( @@ -84,6 +84,6 @@ class Scala3Printer( )(implicit ctx: SemanticDocument ): Option[Patch] = { - fallback.flatMap(_.defnType(replace)) + pcTypeInferrer.flatMap(_.defnType(replace)) } } From c2afbcc145c292e1599ba8ef337ee0334f58d456 Mon Sep 17 00:00:00 2001 From: Brice Jaglin Date: Fri, 27 Sep 2024 21:51:35 +0200 Subject: [PATCH 11/13] on version mismatch, protect PC dynamic loading behind a flag --- project/ScalafixBuild.scala | 5 ++- .../internal/rule/ExplicitResultTypes.scala | 45 ++++++++++++------- .../rule/ExplicitResultTypesConfig.scala | 6 +++ .../scala/scalafix/tests/rule/RuleSuite.scala | 4 +- ...a3CompilerArtifactsOnVersionMismatch.scala | 15 +++++++ .../rules/ExplicitResultTypesSuite.scala | 34 ++++++++++++++ ...a3CompilerArtifactsOnVersionMismatch.scala | 5 +++ 7 files changed, 95 insertions(+), 19 deletions(-) create mode 100644 scalafix-tests/input/src/main/scala-3/test/explicitResultTypesRunAcrossMinors/FetchScala3CompilerArtifactsOnVersionMismatch.scala create mode 100644 scalafix-tests/output/src/main/scala-3/test/explicitResultTypesRunAcrossMinors/FetchScala3CompilerArtifactsOnVersionMismatch.scala diff --git a/project/ScalafixBuild.scala b/project/ScalafixBuild.scala index 673f7dc74..0755ac900 100644 --- a/project/ScalafixBuild.scala +++ b/project/ScalafixBuild.scala @@ -58,7 +58,10 @@ object ScalafixBuild extends AutoPlugin with GhpagesKeys { val xsource3 = TargetAxis(sv, xsource3 = true) (prevVersions :+ xsource3).map((sv, _)) - } :+ (scala3Next, TargetAxis(scala213)) + } ++ Seq( + (scala3Next, TargetAxis(scala213)), + (scala3Next, TargetAxis(scala3LTS)) + ) lazy val publishLocalTransitive = taskKey[Unit]("Run publishLocal on this project and its dependencies") diff --git a/scalafix-rules/src/main/scala-3/scalafix/internal/rule/ExplicitResultTypes.scala b/scalafix-rules/src/main/scala-3/scalafix/internal/rule/ExplicitResultTypes.scala index 97b7e8dde..212ebe939 100644 --- a/scalafix-rules/src/main/scala-3/scalafix/internal/rule/ExplicitResultTypes.scala +++ b/scalafix-rules/src/main/scala-3/scalafix/internal/rule/ExplicitResultTypes.scala @@ -40,25 +40,36 @@ final class ExplicitResultTypes( "To fix this problem, either remove ExplicitResultTypes from .scalafix.conf or upgrade the compiler in your build." ) } else { - config.conf // Support deprecated explicitReturnTypes config - .getOrElse("explicitReturnTypes", "ExplicitResultTypes")( - ExplicitResultTypesConfig.default - ) - .map(c => - new ExplicitResultTypes( - c, - Option { - if ( - stripPatchVersion(config.scalaVersion) == - stripPatchVersion(compilerScalaVersion) - ) - PresentationCompilerTypeInferrer - .static(config, new ScalaPresentationCompiler()) + config.conf.getOrElse("ExplicitResultTypes")(this.config).andThen { + conf => + val majorMinorCompilerScalaVersion = + stripPatchVersion(compilerScalaVersion) + + val matchingMinors = + majorMinorScalaVersion == majorMinorCompilerScalaVersion + + if ( + !matchingMinors && !conf.fetchScala3CompilerArtifactsOnVersionMismatch + ) { + Configured.error( + s"The ExplicitResultTypes rule was compiled with a different Scala 3 minor ($majorMinorCompilerScalaVersion) " + + s"than the target sources ($majorMinorScalaVersion). To fix this problem, make sure you are running the latest version of Scalafix. " + + "If that is the case, either change your build to stick to the Scala 3 LTS or Next versions supported by Scalafix, or " + + "enable ExplicitResultTypes.fetchScala3CompilerArtifactsOnVersionMismatch in .scalafix.conf in order to try to load what is needed dynamically." + ) + } else { + val pcTypeInferrer = + if (matchingMinors) + PresentationCompilerTypeInferrer.static( + config, + new ScalaPresentationCompiler() + ) else PresentationCompilerTypeInferrer.dynamic(config) - } - ) - ) + + Configured.Ok(new ExplicitResultTypes(conf, Some(pcTypeInferrer))) + } + } } } diff --git a/scalafix-rules/src/main/scala/scalafix/internal/rule/ExplicitResultTypesConfig.scala b/scalafix-rules/src/main/scala/scalafix/internal/rule/ExplicitResultTypesConfig.scala index 03b4c5aa6..6e72c4e4a 100644 --- a/scalafix-rules/src/main/scala/scalafix/internal/rule/ExplicitResultTypesConfig.scala +++ b/scalafix-rules/src/main/scala/scalafix/internal/rule/ExplicitResultTypesConfig.scala @@ -39,6 +39,12 @@ case class ExplicitResultTypesConfig( rewriteStructuralTypesToNamedSubclass: Boolean = true, @Description("If true, adds result types only to implicit definitions.") onlyImplicits: Boolean = false, + @Description( + "If true and the Scala 3 version Scalafix was compiled with differs from the target files' one," + + "attempts to resolve and download compiler artifacts dynamically via Coursier. " + + "Disabled by default as this introduces a performance overhead and might not work." + ) + fetchScala3CompilerArtifactsOnVersionMismatch: Boolean = false, @Hidden() symbolReplacements: Map[String, String] = Map.empty ) diff --git a/scalafix-tests/expect/src/test/scala/scalafix/tests/rule/RuleSuite.scala b/scalafix-tests/expect/src/test/scala/scalafix/tests/rule/RuleSuite.scala index 1cd74c005..b6f837ca7 100644 --- a/scalafix-tests/expect/src/test/scala/scalafix/tests/rule/RuleSuite.scala +++ b/scalafix-tests/expect/src/test/scala/scalafix/tests/rule/RuleSuite.scala @@ -39,7 +39,9 @@ class RuleSuite extends AbstractSemanticRuleSuite with AnyFunSuiteLike { val versionMismatch = stripPatch(RulesBuildInfo.scalaVersion) != stripPatch(props.scalaVersion) val explicitResultTypesTest = - diffTest.path.input.toNIO.toString.contains("explicitResultTypes") + diffTest.path.input.toNIO.toString.contains( + "explicitResultTypes" + java.io.File.separator // don't skip tests with a suffix + ) // ExplicitResultTypes can only run against sources compiled with the sam // binary version as the one used to compile the rule diff --git a/scalafix-tests/input/src/main/scala-3/test/explicitResultTypesRunAcrossMinors/FetchScala3CompilerArtifactsOnVersionMismatch.scala b/scalafix-tests/input/src/main/scala-3/test/explicitResultTypesRunAcrossMinors/FetchScala3CompilerArtifactsOnVersionMismatch.scala new file mode 100644 index 000000000..7dad5d143 --- /dev/null +++ b/scalafix-tests/input/src/main/scala-3/test/explicitResultTypesRunAcrossMinors/FetchScala3CompilerArtifactsOnVersionMismatch.scala @@ -0,0 +1,15 @@ +/* +rules = ExplicitResultTypes +ExplicitResultTypes.skipSimpleDefinitions = true +ExplicitResultTypes.fetchScala3CompilerArtifactsOnVersionMismatch = true + +// RuleSuite.scala does run this test for expect3_xTarget3_y with x != y +// because it is in a different folder than "explicitResultTypes" - the suffix +// matters. Take that into account if you move this file. + +*/ +package test.explicitResultTypesRunAcrossMinors + +object FetchScala3CompilerArtifactsOnVersionMismatch { + def foo = 1 +} diff --git a/scalafix-tests/integration/src/test/scala-3/scalafix/tests/rules/ExplicitResultTypesSuite.scala b/scalafix-tests/integration/src/test/scala-3/scalafix/tests/rules/ExplicitResultTypesSuite.scala index 0dda137bd..f781783a9 100644 --- a/scalafix-tests/integration/src/test/scala-3/scalafix/tests/rules/ExplicitResultTypesSuite.scala +++ b/scalafix-tests/integration/src/test/scala-3/scalafix/tests/rules/ExplicitResultTypesSuite.scala @@ -2,7 +2,9 @@ package scalafix.tests.rules import scala.meta.io.AbsolutePath +import metaconfig.Conf import metaconfig.Configured +import metaconfig.typesafeconfig.* import org.scalatest.funsuite.AnyFunSuite import scalafix.internal.rule.ExplicitResultTypes import scalafix.v1.Configuration @@ -34,4 +36,36 @@ class ExplicitResultTypesSuite extends AnyFunSuite { assert(rule.withConfiguration(earlyScala3) == Configured.error(expected)) } + test("Other Scala 3 minor versions are not supported by default") { + val neitherLTSNorNext = + config.withScalaVersion("3.4.0") + + val v = + buildinfo.RulesBuildInfo.scalaVersion.split('.').take(2).mkString(".") + + val expected = + s"The ExplicitResultTypes rule was compiled with a different Scala 3 minor ($v) than the target sources (3.4). " + + "To fix this problem, make sure you are running the latest version of Scalafix. " + + "If that is the case, either change your build to stick to the Scala 3 LTS or Next versions supported by Scalafix, or " + + "enable ExplicitResultTypes.fetchScala3CompilerArtifactsOnVersionMismatch in .scalafix.conf in order to try to load what is needed dynamically." + + assert( + rule.withConfiguration(neitherLTSNorNext) == Configured.error(expected) + ) + } + + test( + "fetchScala3CompilerArtifactsOnVersionMismatch unlocks dynamic loading" + ) { + val neitherLTSNorNext = + config.withScalaVersion("3.5.0") + + val conf = Conf + .parseString( + "ExplicitResultTypes.fetchScala3CompilerArtifactsOnVersionMismatch = true" + ) + .get + assert(rule.withConfiguration(neitherLTSNorNext.withConf(conf)).isOk) + } + } diff --git a/scalafix-tests/output/src/main/scala-3/test/explicitResultTypesRunAcrossMinors/FetchScala3CompilerArtifactsOnVersionMismatch.scala b/scalafix-tests/output/src/main/scala-3/test/explicitResultTypesRunAcrossMinors/FetchScala3CompilerArtifactsOnVersionMismatch.scala new file mode 100644 index 000000000..91de38dbd --- /dev/null +++ b/scalafix-tests/output/src/main/scala-3/test/explicitResultTypesRunAcrossMinors/FetchScala3CompilerArtifactsOnVersionMismatch.scala @@ -0,0 +1,5 @@ +package test.explicitResultTypesRunAcrossMinors + +object FetchScala3CompilerArtifactsOnVersionMismatch { + def foo: Int = 1 +} From 5f262d82a6717609f09194e094d70ffd7b6fff84 Mon Sep 17 00:00:00 2001 From: Brice Jaglin Date: Fri, 27 Sep 2024 22:14:47 +0200 Subject: [PATCH 12/13] ensure atomicity when global imports are added --- .../scala-3/pc/PresentationCompilerTypeInferrer.scala | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/scalafix-rules/src/main/scala-3/pc/PresentationCompilerTypeInferrer.scala b/scalafix-rules/src/main/scala-3/pc/PresentationCompilerTypeInferrer.scala index 6326552fa..5063f95d0 100644 --- a/scalafix-rules/src/main/scala-3/pc/PresentationCompilerTypeInferrer.scala +++ b/scalafix-rules/src/main/scala-3/pc/PresentationCompilerTypeInferrer.scala @@ -59,8 +59,7 @@ final class PresentationCompilerTypeInferrer private ( replace.pos.end ) val result = pc.insertInferredType(params).get() - // TODO we need to actually insert after each change - val allPatches: List[Patch] = result.asScala.toList + result.asScala.toList .map { edit => val start = edit.getRange().getStart() val last = ctx.tokens.tokens.takeWhile { token => @@ -72,9 +71,8 @@ final class PresentationCompilerTypeInferrer private ( }.last Patch.addRight(last, edit.getNewText()) } - allPatches.reduce[Patch] { case (p1, p2) => - p1 + p2 - } + .asPatch + .atomic } } From fe41df28e753e70286c3ddf835aa065d8f4ac8e5 Mon Sep 17 00:00:00 2001 From: Brice Jaglin Date: Fri, 27 Sep 2024 18:20:41 +0200 Subject: [PATCH 13/13] update docs --- docs/rules/ExplicitResultTypes.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/rules/ExplicitResultTypes.md b/docs/rules/ExplicitResultTypes.md index 99c634605..b90a28ab8 100644 --- a/docs/rules/ExplicitResultTypes.md +++ b/docs/rules/ExplicitResultTypes.md @@ -4,8 +4,7 @@ id: ExplicitResultTypes title: ExplicitResultTypes --- -This rewrite inserts type annotations for inferred public members. Only compatible with -scala 2.12 & 2.13. +This rewrite inserts type annotations for inferred public members. Example: @@ -35,6 +34,9 @@ This rule has several known limitations, which are most likely fixable with some effort. At the time of this writing, there are no short-term plans to address these issues however. +Scala 3 support is recent and therefore not widely tested. Expect annotations +to be be less precise than the ones added to sources compiled with Scala 2.x. + ### Imports ordering The rewrite inserts imports at the bottom of the global import list. Users are @@ -43,8 +45,8 @@ expected to organize the imports according to the conventions of their codebase. For example, the rewrite may produce the following diff. ```diff -import java.io.File -import scala.collection.mutable + import java.io.File + import scala.collection.mutable + import java.util.UUID ```