以下のコードがエラーになるかどうか?という話
case class A private(x: Int) object B { def f = A(2) }
まずオプションの有無とScala versionによってややこしい。以下が一覧
Scala 2 | Scala 3 | |
---|---|---|
no option | OK | NG |
-Xsource:3 | NG | - |
-source:3.0-migration | - | OK |
Scala 2から3で変更があったが、それぞれcompiler optionの付与で、別の挙動に変更可能。
大抵Scala 3の挙動の方が望ましいはずである。
とはいえ、compiler optionで指定だと、全部が変わってしまうので、もう少し細かく制御したいぞ! という人のための対策
scalafixでそもそもprivate constructor自体を警告して sealed abstract case class
強制
この警告出すにはscalafix必要だが、修正後は、wartremoverやscalafixなどの外部の仕組みは必要なく、思った通りにアクセス制御可能、というメリット。
ただ、そもそも sealed abstract case class
自体が無理やりな回避策感があるし、どうせScala 3のデフォルトは勝手にprivateになるので、
あくまで一時的な対策という感じか・・・?
package fix import scala.meta.Defn import scala.meta.Mod import scalafix.Patch import scalafix.lint.Diagnostic import scalafix.lint.LintSeverity import scalafix.v1.SyntacticDocument import scalafix.v1.SyntacticRule class CaseClassPrivateConstructor extends SyntacticRule("CaseClassPrivateConstructor") { override def fix(implicit doc: SyntacticDocument): Patch = { doc.tree.collect { case c: Defn.Class if c.mods.exists(_.is[Mod.Case]) && c.mods.forall(!_.is[Mod.Abstract]) => c.ctor.mods .find(_.is[Mod.Private]).map { p => Patch.lint( Diagnostic( id = "", message = "case classのconstructorをprivateにしても`-Xsource:3`を指定してないのでapplyがpublicになってしまっていて実質意味がないです。本当にprivateにしたい場合はsealed abstract case classなどを検討してください", position = p.pos, severity = LintSeverity.Warning ) ) }.asPatch }.asPatch } }
wartremoverでapplyを検知
今さっき作った。細かい検証終わったらmergeして、後でリリースする。 applyのoverloadがある場合の検査が雑で正確ではない問題がある。
TODO: scalafixのSemanticRuleでwartremoverと同様のものを作る・・・?