diff --git a/build.sbt b/build.sbt index 408a7e7..4c56cfc 100644 --- a/build.sbt +++ b/build.sbt @@ -12,7 +12,7 @@ skip in publish := true val rewrites = project.settings( moduleName := "scala-rewrites", - libraryDependencies += "ch.epfl.scala" %% "scalafix-core" % scalafixVersion, + libraryDependencies += "ch.epfl.scala" %% "scalafix-rules" % scalafixVersion, ) val input = project.settings( diff --git a/input/src/main/java/fix/scala213/ExplicitNonNullaryApplyI.java b/input/src/main/java/fix/scala213/ExplicitNonNullaryApplyI.java new file mode 100644 index 0000000..047fb1e --- /dev/null +++ b/input/src/main/java/fix/scala213/ExplicitNonNullaryApplyI.java @@ -0,0 +1,5 @@ +package fix.scala213; + +interface ExplicitNonNullaryApplyI { + String foo(); +} diff --git a/input/src/main/scala/fix/scala213/ExplicitNonNullaryApply.scala b/input/src/main/scala/fix/scala213/ExplicitNonNullaryApply.scala new file mode 100644 index 0000000..bdae54f --- /dev/null +++ b/input/src/main/scala/fix/scala213/ExplicitNonNullaryApply.scala @@ -0,0 +1,76 @@ +/* +rule = fix.scala213.ExplicitNonNullaryApply +*/ +package fix.scala213 + +class ExplicitNonNullaryApply { + val enna = new ExplicitNonNullaryApply() + + def prop = "" + def meth() = "" + + def id[A](x: A) = x + + + def def_prop_p = prop + def def_meth_p = meth + def def_meth_m = meth() + + def def_id = id("") + def def_id_ta = id[String]("") + + + def def_this_prop_p = this.prop + def def_this_meth_p = this.meth + def def_this_meth_m = this.meth() +//def def_this_meth_m_in = this meth () + + def def_this_id_m = this.id("") + def def_this_id_m_ta = this.id[String]("") + def def_this_id_m_in = this id "" + def def_this_id_m_in_ta = this id[String] "" + + + def def_enna_prop_p = enna.prop + def def_enna_meth_p = enna.meth + def def_enna_meth_m = enna.meth() +//def def_enna_meth_m_in = enna meth () + + def def_enna_id_m = enna.id("") + def def_enna_id_m_ta = enna.id[String]("") + def def_enna_id_m_in = enna id "" + def def_enna_id_m_in_ta = enna id[String] "" + + + def def_this_enna_prop_p = this.enna.prop + def def_this_enna_meth_p = this.enna.meth + def def_this_enna_meth_m = this.enna.meth() +//def def_this_enna_meth_m_in = this.enna meth () + + def def_this_enna_id_m = this.enna.id("") + def def_this_enna_id_m_ta = this.enna.id[String]("") + def def_this_enna_id_m_in = this.enna id "" + def def_this_enna_id_m_in_ta = this.enna id[String] "" + + + def def_enna_this_prop_p = ExplicitNonNullaryApply.this.prop + def def_enna_this_meth_p = ExplicitNonNullaryApply.this.meth + def def_enna_this_meth_m = ExplicitNonNullaryApply.this.meth() +//def def_enna_this_meth_m_in = ExplicitNonNullaryApply.this meth () + + def def_enna_this_id_m = ExplicitNonNullaryApply.this.id("") + def def_enna_this_id_m_ta = ExplicitNonNullaryApply.this.id[String]("") + def def_enna_this_id_m_in = ExplicitNonNullaryApply.this id "" + def def_enna_this_id_m_in_ta = ExplicitNonNullaryApply.this id[String] "" + + + def def_enna_enna_prop_p = enna.enna.prop + def def_enna_enna_meth_p = enna.enna.meth + def def_enna_enna_meth_m = enna.enna.meth() +//def def_enna_enna_meth_m_in = enna.enna meth () + + def def_enna_enna_id_m = enna.enna.id("") + def def_enna_enna_id_m_ta = enna.enna.id[String]("") + def def_enna_enna_id_m_in = enna.enna id "" + def def_enna_enna_id_m_in_ta = enna.enna id[String] "" +} diff --git a/input/src/main/scala/fix/scala213/ExplicitNonNullaryApplyJava.scala b/input/src/main/scala/fix/scala213/ExplicitNonNullaryApplyJava.scala new file mode 100644 index 0000000..09e382a --- /dev/null +++ b/input/src/main/scala/fix/scala213/ExplicitNonNullaryApplyJava.scala @@ -0,0 +1,88 @@ +/* +rule = fix.scala213.ExplicitNonNullaryApply +*/ +package fix.scala213 + +import ExplicitNonNullaryApplyJavaDefs._ + +class ExplicitNonNullaryApplyJava { + ???.## + ???.getClass() + ???.getClass + ???.hashCode() + ???.hashCode + ???.toString() + ???.toString + ???.asInstanceOf[Int] + ???.isInstanceOf[Int] + + any.## + any.getClass() + any.getClass + any.hashCode() + any.hashCode + any.toString() + any.toString + any.asInstanceOf[Int] + any.isInstanceOf[Int] + + ref.## + ref.getClass() + ref.getClass + ref.hashCode() + ref.hashCode + ref.toString() + ref.toString + ref.asInstanceOf[Int] + ref.isInstanceOf[Int] + + obj.## + obj.getClass() + obj.getClass + obj.hashCode() + obj.hashCode + obj.toString() + obj.toString + obj.asInstanceOf[Int] + obj.isInstanceOf[Int] + + str.## + str.getClass() + str.getClass + str.hashCode() + str.hashCode + str.toString() + str.toString + str.asInstanceOf[Int] + str.isInstanceOf[Int] + + cm.toString() + cm.toString + cp.toString() + cp.toString + + cm.run() + cm.run + cp.run() + cp.run + + cm.foo() + cm.foo + cp.foo() + cp.foo + + vcm.toString() + vcm.toString + vcp.toString() + vcp.toString + + ccm.toString() + ccm.toString + ccp.toString() + ccp.toString + + vccm.toString() + vccm.toString + vccp.toString() + vccp.toString +} diff --git a/input/src/main/scala/fix/scala213/ExplicitNonNullaryApplyJavaDefs.scala b/input/src/main/scala/fix/scala213/ExplicitNonNullaryApplyJavaDefs.scala new file mode 100644 index 0000000..7d46394 --- /dev/null +++ b/input/src/main/scala/fix/scala213/ExplicitNonNullaryApplyJavaDefs.scala @@ -0,0 +1,30 @@ +/**/ +package fix.scala213 + +object ExplicitNonNullaryApplyJavaDefs { + class CM extends Runnable with ExplicitNonNullaryApplyI { override def toString() = ""; def run() = (); def foo() = "" } + class CP extends Runnable with ExplicitNonNullaryApplyI { override def toString = ""; def run = (); def foo = "" } + + case class CCM() extends Runnable with ExplicitNonNullaryApplyI { override def toString() = ""; def run() = (); def foo() = "" } + case class CCP() extends Runnable with ExplicitNonNullaryApplyI { override def toString = ""; def run = (); def foo = "" } + + class VCM(val x: String) extends AnyVal { override def toString() = "" } + class VCP(val x: String) extends AnyVal { override def toString = "" } + + case class VCCM(x: String) extends AnyVal { override def toString() = "" } + case class VCCP(x: String) extends AnyVal { override def toString = "" } + + val any: Any = "" + val ref: AnyRef = "" + val obj: Object = "" + val str: String = "" + + val cm: CM = new CM() + val cp: CP = new CP() + val vcm: VCM = new VCM("") + val vcp: VCP = new VCP("") + val ccm: CCM = CCM() + val ccp: CCP = CCP() + val vccm: VCCM = VCCM("") + val vccp: VCCP = VCCP("") +} diff --git a/input/src/main/scala/fix/scala213/ExplicitNonNullaryApplyOver.scala b/input/src/main/scala/fix/scala213/ExplicitNonNullaryApplyOver.scala new file mode 100644 index 0000000..265f032 --- /dev/null +++ b/input/src/main/scala/fix/scala213/ExplicitNonNullaryApplyOver.scala @@ -0,0 +1,20 @@ +/* +rule = fix.scala213.ExplicitNonNullaryApply +*/ +package fix.scala213 + +class ExplicitNonNullaryApplyOver { + class Meth { def d() = "" } + class Prop { def d = "" } + + class Meth2Prop extends Meth { override def d = "" } + class Prop2Meth extends Prop { override def d() = "" } + + val meth2prop = new Meth2Prop + val prop2meth = new Prop2Meth + + meth2prop.d() + meth2prop.d // <- should call with parens + prop2meth.d() + prop2meth.d // <- should call with parens +} diff --git a/input/src/main/scala/fix/scala213/NoAutoApply.scala b/input/src/main/scala/fix/scala213/NoAutoApply.scala new file mode 100644 index 0000000..a1eb9d5 --- /dev/null +++ b/input/src/main/scala/fix/scala213/NoAutoApply.scala @@ -0,0 +1,39 @@ +/* +rule = fix.scala213.ExplicitNonNullaryApply +*/ +package fix.scala213 + +object NoAutoApply { + object buz { + override def toString(): String = "" + def empty[T]() = List.empty[T] + } + val x: Iterator[Int] = ??? + def foo() = println(1) + def bar = 32 + foo + println(foo) + foo() + bar + x.next + class buz() { + def qux() = List(1) + } + new buz().qux.toIterator.next + new java.util.ArrayList[Int]().toString + val builder = List.newBuilder[Int] + builder.result() +//builder result () + builder.result + fix.scala213.NoAutoApply.buz.empty[String] + var opt: Option[() => Int] = None + opt = None + println(1.toString) + println(buz.toString) // not inserted + List(builder) map (_.result) + builder.## + def lzy(f: => Int) = { + var k = f _ + k = () => 3 + } +} diff --git a/output/src/main/java/fix/scala213/ExplicitNonNullaryApplyI.java b/output/src/main/java/fix/scala213/ExplicitNonNullaryApplyI.java new file mode 100644 index 0000000..047fb1e --- /dev/null +++ b/output/src/main/java/fix/scala213/ExplicitNonNullaryApplyI.java @@ -0,0 +1,5 @@ +package fix.scala213; + +interface ExplicitNonNullaryApplyI { + String foo(); +} diff --git a/output/src/main/scala/fix/scala213/ExplicitNonNullaryApply.scala b/output/src/main/scala/fix/scala213/ExplicitNonNullaryApply.scala new file mode 100644 index 0000000..a15f37a --- /dev/null +++ b/output/src/main/scala/fix/scala213/ExplicitNonNullaryApply.scala @@ -0,0 +1,73 @@ +package fix.scala213 + +class ExplicitNonNullaryApply { + val enna = new ExplicitNonNullaryApply() + + def prop = "" + def meth() = "" + + def id[A](x: A) = x + + + def def_prop_p = prop + def def_meth_p = meth() + def def_meth_m = meth() + + def def_id = id("") + def def_id_ta = id[String]("") + + + def def_this_prop_p = this.prop + def def_this_meth_p = this.meth() + def def_this_meth_m = this.meth() +//def def_this_meth_m_in = this meth () + + def def_this_id_m = this.id("") + def def_this_id_m_ta = this.id[String]("") + def def_this_id_m_in = this id "" + def def_this_id_m_in_ta = this id[String] "" + + + def def_enna_prop_p = enna.prop + def def_enna_meth_p = enna.meth() + def def_enna_meth_m = enna.meth() +//def def_enna_meth_m_in = enna meth () + + def def_enna_id_m = enna.id("") + def def_enna_id_m_ta = enna.id[String]("") + def def_enna_id_m_in = enna id "" + def def_enna_id_m_in_ta = enna id[String] "" + + + def def_this_enna_prop_p = this.enna.prop + def def_this_enna_meth_p = this.enna.meth() + def def_this_enna_meth_m = this.enna.meth() +//def def_this_enna_meth_m_in = this.enna meth () + + def def_this_enna_id_m = this.enna.id("") + def def_this_enna_id_m_ta = this.enna.id[String]("") + def def_this_enna_id_m_in = this.enna id "" + def def_this_enna_id_m_in_ta = this.enna id[String] "" + + + def def_enna_this_prop_p = ExplicitNonNullaryApply.this.prop + def def_enna_this_meth_p = ExplicitNonNullaryApply.this.meth() + def def_enna_this_meth_m = ExplicitNonNullaryApply.this.meth() +//def def_enna_this_meth_m_in = ExplicitNonNullaryApply.this meth () + + def def_enna_this_id_m = ExplicitNonNullaryApply.this.id("") + def def_enna_this_id_m_ta = ExplicitNonNullaryApply.this.id[String]("") + def def_enna_this_id_m_in = ExplicitNonNullaryApply.this id "" + def def_enna_this_id_m_in_ta = ExplicitNonNullaryApply.this id[String] "" + + + def def_enna_enna_prop_p = enna.enna.prop + def def_enna_enna_meth_p = enna.enna.meth() + def def_enna_enna_meth_m = enna.enna.meth() +//def def_enna_enna_meth_m_in = enna.enna meth () + + def def_enna_enna_id_m = enna.enna.id("") + def def_enna_enna_id_m_ta = enna.enna.id[String]("") + def def_enna_enna_id_m_in = enna.enna id "" + def def_enna_enna_id_m_in_ta = enna.enna id[String] "" +} diff --git a/output/src/main/scala/fix/scala213/ExplicitNonNullaryApplyJava.scala b/output/src/main/scala/fix/scala213/ExplicitNonNullaryApplyJava.scala new file mode 100644 index 0000000..6f41bde --- /dev/null +++ b/output/src/main/scala/fix/scala213/ExplicitNonNullaryApplyJava.scala @@ -0,0 +1,86 @@ + +package fix.scala213 + +import ExplicitNonNullaryApplyJavaDefs._ + +class ExplicitNonNullaryApplyJava { + ???.## + ???.getClass() + ???.getClass + ???.hashCode() + ???.hashCode + ???.toString() + ???.toString + ???.asInstanceOf[Int] + ???.isInstanceOf[Int] + + any.## + any.getClass() + any.getClass + any.hashCode() + any.hashCode + any.toString() + any.toString + any.asInstanceOf[Int] + any.isInstanceOf[Int] + + ref.## + ref.getClass() + ref.getClass + ref.hashCode() + ref.hashCode + ref.toString() + ref.toString + ref.asInstanceOf[Int] + ref.isInstanceOf[Int] + + obj.## + obj.getClass() + obj.getClass + obj.hashCode() + obj.hashCode + obj.toString() + obj.toString + obj.asInstanceOf[Int] + obj.isInstanceOf[Int] + + str.## + str.getClass() + str.getClass + str.hashCode() + str.hashCode + str.toString() + str.toString + str.asInstanceOf[Int] + str.isInstanceOf[Int] + + cm.toString() + cm.toString + cp.toString() + cp.toString + + cm.run() + cm.run + cp.run() + cp.run + + cm.foo() + cm.foo + cp.foo() + cp.foo + + vcm.toString() + vcm.toString + vcp.toString() + vcp.toString + + ccm.toString() + ccm.toString + ccp.toString() + ccp.toString + + vccm.toString() + vccm.toString + vccp.toString() + vccp.toString +} diff --git a/output/src/main/scala/fix/scala213/ExplicitNonNullaryApplyJavaDefs.scala b/output/src/main/scala/fix/scala213/ExplicitNonNullaryApplyJavaDefs.scala new file mode 100644 index 0000000..eefab8e --- /dev/null +++ b/output/src/main/scala/fix/scala213/ExplicitNonNullaryApplyJavaDefs.scala @@ -0,0 +1,29 @@ +package fix.scala213 + +object ExplicitNonNullaryApplyJavaDefs { + class CM extends Runnable with ExplicitNonNullaryApplyI { override def toString() = ""; def run() = (); def foo() = "" } + class CP extends Runnable with ExplicitNonNullaryApplyI { override def toString = ""; def run = (); def foo = "" } + + case class CCM() extends Runnable with ExplicitNonNullaryApplyI { override def toString() = ""; def run() = (); def foo() = "" } + case class CCP() extends Runnable with ExplicitNonNullaryApplyI { override def toString = ""; def run = (); def foo = "" } + + class VCM(val x: String) extends AnyVal { override def toString() = "" } + class VCP(val x: String) extends AnyVal { override def toString = "" } + + case class VCCM(x: String) extends AnyVal { override def toString() = "" } + case class VCCP(x: String) extends AnyVal { override def toString = "" } + + val any: Any = "" + val ref: AnyRef = "" + val obj: Object = "" + val str: String = "" + + val cm: CM = new CM() + val cp: CP = new CP() + val vcm: VCM = new VCM("") + val vcp: VCP = new VCP("") + val ccm: CCM = CCM() + val ccp: CCP = CCP() + val vccm: VCCM = VCCM("") + val vccp: VCCP = VCCP("") +} diff --git a/output/src/main/scala/fix/scala213/ExplicitNonNullaryApplyOver.scala b/output/src/main/scala/fix/scala213/ExplicitNonNullaryApplyOver.scala new file mode 100644 index 0000000..ed02411 --- /dev/null +++ b/output/src/main/scala/fix/scala213/ExplicitNonNullaryApplyOver.scala @@ -0,0 +1,17 @@ +package fix.scala213 + +class ExplicitNonNullaryApplyOver { + class Meth { def d() = "" } + class Prop { def d = "" } + + class Meth2Prop extends Meth { override def d = "" } + class Prop2Meth extends Prop { override def d() = "" } + + val meth2prop = new Meth2Prop + val prop2meth = new Prop2Meth + + meth2prop.d() + meth2prop.d() // <- should call with parens + prop2meth.d() + prop2meth.d() // <- should call with parens +} diff --git a/output/src/main/scala/fix/scala213/NoAutoApply.scala b/output/src/main/scala/fix/scala213/NoAutoApply.scala new file mode 100644 index 0000000..91d9ace --- /dev/null +++ b/output/src/main/scala/fix/scala213/NoAutoApply.scala @@ -0,0 +1,37 @@ + +package fix.scala213 + +object NoAutoApply { + object buz { + override def toString(): String = "" + def empty[T]() = List.empty[T] + } + val x: Iterator[Int] = ??? + def foo() = println(1) + def bar = 32 + foo() + println(foo()) + foo() + bar + x.next() + class buz() { + def qux() = List(1) + } + new buz().qux().toIterator.next() + new java.util.ArrayList[Int]().toString + val builder = List.newBuilder[Int] + builder.result() +//builder result () + builder.result() + fix.scala213.NoAutoApply.buz.empty[String]() + var opt: Option[() => Int] = None + opt = None + println(1.toString) + println(buz.toString) // not inserted + List(builder) map (_.result()) + builder.## + def lzy(f: => Int) = { + var k = f _ + k = () => 3 + } +} diff --git a/rewrites/src/main/resources/META-INF/services/scalafix.v1.Rule b/rewrites/src/main/resources/META-INF/services/scalafix.v1.Rule index d6f128f..810275c 100644 --- a/rewrites/src/main/resources/META-INF/services/scalafix.v1.Rule +++ b/rewrites/src/main/resources/META-INF/services/scalafix.v1.Rule @@ -1,5 +1,6 @@ fix.scala213.Any2StringAdd fix.scala213.Core +fix.scala213.ExplicitNonNullaryApply fix.scala213.ExplicitNullaryEtaExpansion fix.scala213.NullaryHashHash fix.scala213.ScalaSeq diff --git a/rewrites/src/main/scala/fix/scala213/ExplicitNonNullaryApply.scala b/rewrites/src/main/scala/fix/scala213/ExplicitNonNullaryApply.scala new file mode 100644 index 0000000..5073b57 --- /dev/null +++ b/rewrites/src/main/scala/fix/scala213/ExplicitNonNullaryApply.scala @@ -0,0 +1,68 @@ +package fix.scala213 + +import scala.PartialFunction.{ cond, condOpt } +import scala.collection.mutable +import scala.util.control.Exception.nonFatalCatch + +import metaconfig.Configured + +import scala.meta._ +import scala.meta.internal.pc.ScalafixGlobal + +import scalafix.v1._ +import scalafix.internal.rule.CompilerException +import scalafix.internal.v1.LazyValue + +final class ExplicitNonNullaryApply(global: LazyValue[ScalafixGlobal]) + extends SemanticRule("fix.scala213.ExplicitNonNullaryApply") +{ + def this() = this(LazyValue.later(() => ScalafixGlobal.newCompiler(Nil, Nil, Map.empty))) + + override def fix(implicit doc: SemanticDocument): Patch = { + try unsafeFix() catch { + case _: CompilerException => + shutdownCompiler() + global.restart() + try unsafeFix() catch { + case _: CompilerException => Patch.empty /* ignore compiler crashes */ + } + } + } + + private def unsafeFix()(implicit doc: SemanticDocument) = { + lazy val power = new impl.Power(global.value) + val handled = mutable.Set.empty[Name] + + def fix(tree: Tree, meth: Term, noTypeArgs: Boolean, noArgs: Boolean) = { + for { + name <- termName(meth) + if handled.add(name) && name.value != "##" // fast-track https://github.com/scala/scala/pull/8814 + if noArgs + if name.isReference + if !name.parent.exists(_.is[Term.ApplyInfix]) + info <- name.symbol.info + if !power.isJavaDefined(name) // !info.isJava + if cond(info.signature) { case MethodSignature(_, List(Nil), _) => true } + } yield Patch.addRight(if (noTypeArgs) name else tree, "()") + }.asPatch + + doc.tree.collect { + case t @ q"$meth[..$targs](...$args)" => fix(t, meth, targs.isEmpty, args.isEmpty) + case t @ q"$meth(...$args)" => fix(t, meth, true, args.isEmpty) + }.asPatch + } + + private def termName(term: Term): Option[Name] = condOpt(term) { + case name: Term.Name => name + case Term.Select(_, name: Term.Name) => name + case Type.Select(_, name: Type.Name) => name + } + + override def withConfiguration(config: Configuration) = + Configured.ok(new ExplicitNonNullaryApply(LazyValue.later { () => + ScalafixGlobal.newCompiler(config.scalacClasspath, config.scalacOptions, Map.empty) + })) + + override def afterComplete() = shutdownCompiler() + def shutdownCompiler() = for (g <- global) nonFatalCatch { g.askShutdown(); g.close() } +} diff --git a/rewrites/src/main/scala/impl/Power.scala b/rewrites/src/main/scala/impl/Power.scala new file mode 100644 index 0000000..4db1cd6 --- /dev/null +++ b/rewrites/src/main/scala/impl/Power.scala @@ -0,0 +1,35 @@ +package impl + +import scala.meta._ +import scala.meta.internal.pc.ScalafixGlobal +import scala.meta.internal.proxy.GlobalProxy + +import scalafix.v1._ +import scalafix.internal.rule.CompilerException + +final class Power(g: ScalafixGlobal)(implicit doc: SemanticDocument) { + private lazy val unit = g.newCompilationUnit(doc.input.text, doc.input.syntax) + + def isJavaDefined(t: Tree): Boolean = + try isJavaDefinedUnsafe(gsymbol(t)) catch { + case e: Throwable => throw CompilerException(e) + } + + private def isJavaDefinedUnsafe(meth: g.Symbol) = { + def test(sym: g.Symbol) = sym.isJavaDefined || sym.owner == g.definitions.AnyClass + test(meth) || meth.overrides.exists(test) + } + + private def gsymbol(t: Tree): g.Symbol = { + GlobalProxy.typedTreeAt(g, unit.position(t.pos.start)) + + val sym = g + .inverseSemanticdbSymbols(t.symbol.value) + .find(t.symbol.value == g.semanticdbSymbol(_)) + .getOrElse(g.NoSymbol) + + if (sym.info.exists(g.definitions.NothingTpe == _)) + sym.overrides.lastOption.getOrElse(sym) + else sym + } +}