Search code examples
scalacompiler-errorsforward-reference

Scala: Forward reference errors during type pattern matching


I'm looking to understand the compile-time errors generated by the following Scala code:

class MyClass {
    def determineType(x:Any):String = {
        x match {
            case Int => "It's an int."
            case Float => "It's a Float."
            case CIn  => "It's a CIn without a specifier."
            case c:CIn => "It's a CIn with a specifier."
            case DIn=> "It's a DIn without a specifier." // Error:Cannot resolve symbol DIn
            case d:DIn=> "It's a DIn with a specifier."
            case _ => "It's something else."
        }
    }
    case class CIn()
    class DIn
} // End class definition

def determineType(x:Any):String = {
    x match {
        case Int => "It's an int"
        case Float => "It's a Float"
        case COut  => "It's a COut without a specifier." // Error: Wrong forward reference
        case c:COut => "It's a COut with a specifier."
        case DOut=> "It's a DOut without a specifier." // Error: Cannot resolve symbol DOut.
        case d:DOut=> "It's a DOut with a specifier."
        case _ => "It's something else."
    }
}

case class COut()
class DOut()

In the version of determineTypewithin MyClass, both the case class CIn as well as the regular class DIn are defined and declared after determineType. A symbol resolution error is generated when trying to match against a DIn without a specifier variable.

At script scope, we similarly define a case class Cout and a regular class DOut. In the script-scope version of determineType we have two different errors. The first one is generated when referring to a case class type (Cout) without a specifier, and it reads "Wrong forward reference". The second one is generated when referring to a regular class type (DOut) without a specifier, and it is a symbol resolution error.

I'm new to the language and am thus not sure why the inclusion of specifiers in the case statements affects the way that forward references are treated.


Solution

  • If your write a match clause case Foo => you are not matching for a type (or class) Foo but a value Foo. So case Int => does not match for an integer value, and case DIn => does not work because there is no value (or object) DIn.

    The following works:

    val Foo = 1
    object Bar
    
    def testVal(x: Any) = x match {
      case Foo => "it's Foo"
      case Bar => "it's Bar"
      case _   => "none of the above"
    }
    
    testVal(1)  // Foo!
    testVal(Bar) // Bar
    testVal(Foo + 1) // none
    

    If you want to match for the instance of a class instead, you need to match for a clause case b: Baz => or case _: Baz (if you are not interested in binding the instance to a value). For case classes, you can further more use the automatically provided extractor, so with case class Baz() you can also match as case Baz() =>. This usually makes more sense when Baz contains arguments which are hereby extracted:

    case class Baz(i: Int)
    
    def testType(x: Any) = x match {
      case Baz(3) => "Baz with argument 3"
      case b: Baz => "Baz with argument other than 3"
      case Baz    => "Baz companion!"
      case _      => "none of the above"
    }
    
    testType(Baz)     // companion object!
    testType(Baz(3))
    testType(Baz(4))
    

    A case class also gives you a companion object so here you could actually match for that object using case Baz =>. An object is a kind of value.


    Finally, I don't know which Scala version you are using, but in regular current Scala, you cannot omit the match keyword on the RHS of your definition of determineType:

    def determineType(x: Any): String = {
      case _ => "anything"
    }
    
    <console>:53: error: missing parameter type for expanded function
    The argument types of an anonymous function must be fully known. (SLS 8.5)
    Expected type was: String
              def determineType(x: Any): String = {
                                                  ^
    

    You have to write:

    def determineType(x: Any): String = x match /* ! */ {
      case _ => "anything"
    }