Search code examples
scalaparser-combinators

Scala 2.9: Parsers subclass not recognizing "Elem" override?


I wrote a parser to act as a lexer. This lexer parses a file and returns a list of tokens, each of which is a case class or object that extends a common trait.

I am now trying to write a parser for the output of the lexer, but I have hit a very confusing snag. The parser is happy to implicitly cast my case objects, but throws a fit if I even try to call apply(classHere) manually.

The following is a simplified version of my code:

// CODE
trait Token

case class StringWrapperIgnoresCase(val string: String) {
  private case class InnerWrapper(s: String)

  lazy val lower = string.toLowerCase

  override lazy val hashCode = InnerWrapper(lower).hashCode

  override def equals(that: Any) =
    that.isInstanceOf[StringWrapperIgnoresCase] &&
      lower == that.asInstanceOf[StringWrapperIgnoresCase].lower
}

case class ID(val text: String)
  extends StringWrapperIgnoresCase(text)
  with Token {
    override def toString = "ID(" + text + ")"
  }

case object PERIOD extends Token

object Parser extends Parsers {
  type Elem = Token

  def doesWork: Parser[Token] = PERIOD

  def doesNotWork: Parser[Token] = ID
}

The compiler reports the following message about doesNotWork:

// ERROR MESSAGE
type mismatch;  found   : alan.parser.ID.type (with underlying type object alan.parser.ID)  required: alan.parser.Parser.Parser[alan.parser.Token]

How can I fix this?


Solution

  • Update: It wasn't clear to me from your question exactly what you were asking, but now that you've specified that you want a parser that matches any ID in your answer, here's a more idiomatic solution:

    val id: Parser[ID] = accept("ID", { case i: ID => i })
    

    Here you've provided a description of what the parser wants (for error messages) and a partial function with IDs as its domain. You could also use the acceptIf version that xiefei provides in a comment on your answer.


    When you refer to a case class (as opposed to a case object) without a parameter list, you get the automatically generated companion object, which is not an instance of the class itself. Consider the following:

    sealed trait Foo
    case class Bar(i: Int) extends Foo
    case object Baz extends Foo
    

    Now Baz: Foo is just fine, but Bar: Foo will give an error very similar to what you're seeing.

    Note also that what's happening here isn't strictly casting. The Parsers trait has a method with the following signature:

    implicit def accept(e: Elem): Parser[Elem]
    

    When you write this:

    def doesWork: Parser[Token] = PERIOD
    

    You're trying to type an Elem as a Parser[Elem], and the implicit conversion kicks in (see section 7.3 of the spec for more information about implicit conversions). When you write this, on the other hand:

    def doesNotWork: Parser[Token] = ID
    

    You're trying to type the ID companion object (which has type ID.type, not ID or Token, and therefore not Elem) as a Parser[Elem], and there's no implicit conversion that makes this possible.

    You're probably better off writing out accept(PERIOD) and accept(ID("whatever")), for now, at least, and obeying the deprecation warning that says the following when you try to compile your code:

    Case-to-case inheritance has potentially dangerous bugs which are unlikely to be fixed.