Skip to content

Instantly share code, notes, and snippets.

@retronym
Last active November 13, 2024 11:50
Show Gist options
  • Save retronym/54739603fd0c92403b507dbda1144993 to your computer and use it in GitHub Desktop.
Save retronym/54739603fd0c92403b507dbda1144993 to your computer and use it in GitHub Desktop.
package demo
import java.io.{File, PrintWriter, StringWriter}
import scala.annotation.nowarn
import scala.reflect.internal.Flags.STATIC
import scala.reflect.internal.util.ScalaClassLoader.URLClassLoader
import scala.tools.nsc.backend.jvm.AsmUtils
import scala.tools.nsc.{Global, Phase}
import scala.tools.nsc.plugins.{Plugin, PluginComponent}
import scala.tools.nsc.transform.TypingTransformers
object SeqMatchLint {
def main(args: Array[String]): Unit = {
val settings = new scala.tools.nsc.Settings()
settings.stopAfter.value = List("seq-match-lint")
settings.embeddedDefaults(getClass.getClassLoader)
val isInSBT = !settings.classpath.isSetByUser
if (isInSBT) settings.usejavacp.value = true
val global = new Global(settings) {
self =>
@nowarn("cat=deprecation&msg=early initializers")
object late extends {
val global: self.type = self
} with SeqMatchLintPlugin
override protected def loadPlugins(): List[Plugin] = late :: Nil
}
import global._
val run = new Run()
run.compileUnits(newCompilationUnit(
"""
|class Suspicious {
| def m(c: Some[Seq[String]]) = {
| c match { case Some(x :: y :: Nil) => ; case _ => }
| c.get match { case x :: Nil => ; case _ => }
| c match { case Some(List(x, _*)) => ; case _ => }
| c.get match { case List(x, _*) => ; case _ => }
| }
|}
| """.stripMargin) :: Nil)
if (reporter.hasErrors) sys.exit(1)
}
}
abstract class SeqMatchLintPlugin extends Plugin {
import global._
override val description: String = "seq-match-lint"
override val name: String = "seq-match-lint"
override val components: List[PluginComponent] = List(new PluginComponent with TypingTransformers {
val global: SeqMatchLintPlugin.this.global.type = SeqMatchLintPlugin.this.global
override def newPhase(prev: Phase): Phase = new StdPhase(prev) {
override def apply(unit: CompilationUnit): Unit = {
newTransformer(unit).transformUnit(unit)
}
}
override val runsAfter: List[String] = "typer" :: Nil
override val phaseName: String = "seq-match-lint"
def newTransformer(unit: CompilationUnit) = new TypingTransformer(unit) {
var inPat = false
var scrut: Type = NoType
override def transform(tree: Tree): Tree = tree match {
case Match(sel, cases) =>
transform(sel)
val saved = scrut
scrut = sel.tpe
try cases.foreach(transform)
finally scrut = saved
tree
case CaseDef(pat, self, body) =>
val saved = inPat
inPat = true
try {
pat match {
case Apply(fun, args) if fun.tpe.resultType.typeSymbol == definitions.ConsClass =>
if (!scrut.typeSymbol.isNonBottomSubClass(definitions.ListClass)) {
reporter.error(pat.pos, s"Matching value of type ${scrut} with ::")
}
case u @ UnApply(unapplyFun, _)
if !scrut.typeSymbol.isNonBottomSubClass(definitions.ListClass) && u.tpe.typeSymbol == definitions.ListClass =>
reporter.error(unapplyFun.pos, s"Matching value of type ${scrut} with ::")
super.transform(tree)
case _ =>
getClass
}
transform(pat)
}
finally inPat = saved
transform(self)
transform(body)
tree
case Apply(fun, arg :: Nil)
if inPat && fun.tpe.isInstanceOf[MethodType] && !fun.tpe.params.head.info.typeSymbol.isNonBottomSubClass(definitions.ListClass) && arg.tpe.typeSymbol == definitions.ConsClass =>
reporter.error(arg.pos, s"Matching value of type ${fun.tpe.params.head.info} with ::")
super.transform(tree)
case Apply(fun, (u @ UnApply(unapplyFun, _)) :: Nil)
if inPat && fun.tpe.isInstanceOf[MethodType] && !fun.tpe.params.head.info.typeSymbol.isNonBottomSubClass(definitions.ListClass) && u.tpe.typeSymbol == definitions.ListClass =>
reporter.error(unapplyFun.pos, s"Matching value of type ${fun.tpe.params.head.info} with ::")
super.transform(tree)
case _ =>
super.transform(tree)
}
}
})
}
@retronym
Copy link
Author

retronym commented Nov 13, 2024

<console>:4: error: Matching value of type Seq[String] with ::
    c match { case Some(x :: y :: Nil) => ; case _  => }
                          ^
<console>:5: error: Matching value of type Seq[String] with ::
    c.get match { case x :: Nil => ; case _  => }
                         ^
<console>:6: error: Matching value of type Seq[String] with ::
    c match { case Some(List(x, _*)) => ; case _  => }
                        ^
<console>:7: error: Matching value of type Seq[String] with ::
    c.get match { case List(x, _*) => ; case _  => }
                       ^

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment