Search code examples
scalasbtscopt

scopt "not found: value" defining val with same name


For the following use of scopt:

import java.io.File

object Application extends App {

  case class Config(in: File = new File("."), out: File = new File("."), scripts: Seq[File] = Seq())

  val parser = new scopt.OptionParser[Config]("scopt") {
    head("Script Runner", "0.1")
    opt[File]('i', "in") required () valueName ("<path>") action { (x, c) =>
      c.copy(in = x)
    } text ("required in path property")
    opt[File]('o', "out") required () valueName ("<path>") action { (x, c) =>
      c.copy(out = x)
    } text ("required out path file property")
    arg[File]("<file>...") unbounded () required() action { (x, c) =>
      c.copy(scripts = c.scripts :+ x)
    } text ("unbounded script paths")
  }

  val config = parser.parse(args, Config())

  val scripts = config.map { argConfig => argConfig.scripts }

  config match {
    case Some(config) => println(config)
    case _ => println("config undefined")
  }
}

I get the compilation error:

[error] /Users/jamesclark/code/scratch/src/main/scala/Application.scala:13: not found: value x
[error]       c.copy(out = x)

If I rename either the Config parameter scripts or the val scripts then it compiles.

Can anyone enlighten me as to what's happening here? Is it a compiler issue, or am I missing some magic?

scala 2.11.8 / sbt 0.13.7 / scopt 3.5.0


Solution

  • Making the vals parser, config and scripts into lazy vals instead of vals gives the more helpful error message: /src/main/scala/Application.scala:23: variable definition needs type because 'scripts' is used as a named argument in its body.

    Giving scripts its type annotation val scripts: Option[Seq[File]] = ... solves the issue.

    Alternative ways to work around this are to rename either the val scripts or the case class Config(... scripts: Seq[File] ...) occurrences of scripts.

    The root of the issue seems to come from the scopt library since moving the definition of the parser to a separate scope removes the need for any of the renaming or type annotation work arounds.

    object Application extends App {
    
      def newParser(): OptionParser[Config] = {
        new scopt.OptionParser[Config]("scopt") {
          head("Script Runner", "0.1")
    
          opt[File]('i', "in") required () valueName ("<path>") action { (x, c) =>
            c.copy(in = x)
          } text ("required in path property")
    
          opt[File]('o', "out") required () valueName ("<path>") action { (x, c) =>
            c.copy(out = x)
          } text ("required out path file property")
    
          arg[File]("<file>...") unbounded () required() action { (x, c) =>
            c.copy(scripts = c.scripts :+ x)
          } text ("unbounded script paths")
        }
      }
    
      case class Config(in: File = new File("."), out: File = new File("."), scripts: Seq[File] = Seq())
    
      val parser = newParser()
    
      val config = parser.parse(args, Config())
    
      val scripts = config.map { argConfig => argConfig.scripts }
    
      config match {
        case Some(config) =>
          println(config)
        case _ =>
          println("config undefined")
      }
    }