Search code examples
scalascala-3singleton-typematch-types

Scala 3 "a match type could not be fully reduced" with literal types


I'm learning Scala 3, and I was intrigued by match types and literal types.

I'd like to write a function that takes one of a few literal types, and returns a particular type as a function of which literal type was passed in.

Here's a fairly minimal example of what I'm trying to do:

type TestMatchType[T] = T match
  case "String1" => Int
  case "String2" => String

def testMatchType[T](input: T): TestMatchType[T] =
  input match
    case "String1": "String1" => 1
    case "String2": "String2" => "Two"

val string1Output: Int = testMatchType("String1")

In words:

  • "I'd like to write a function, testMatchType, which takes an argument whose type is either the type literal "String1", or the type literal "String2". If an argument with type "String1" is passed, the function should return an Int. If an argument with type "String2" is passed, the function should return a String. If an argument is passed whose type is not one of these type literals, a compile-time error is produced."

However, when I try to compile the above code, I get the following error:

1 |val string1Output: Int = testMatchType("String1")
  |                         ^^^^^^^^^^^^^^^^^^^^^^^^
  |               Found:    TestMatchType[String]
  |               Required: Int
  |
  |               Note: a match type could not be fully reduced:
  |
  |                 trying to reduce  TestMatchType[String]
  |                 failed since selector  String
  |                 does not match  case ("String1" : String) => Int
  |                 and cannot be shown to be disjoint from it either.
  |                 Therefore, reduction cannot advance to the remaining case
  |
  |                   case ("String2" : String) => String

I'm trying to grok that error message. I'm struggling to understand the line "selector String does not match case ("String1" : String) => Int". I would love some advice if there's a way to make this work.

Thank you!


Solution

  • You don't need to use "foo": "foo", just use _: "foo".

    However, the error happens because T will not be inferred as a singleton type, so the return type will never be Int or String either, it'll remain TestMatchType[String]. You'll either have to use T <: Singleton or use input.type as an argument to TestMatchType (I prefer the latter).

    def testMatchType[T](input: T): TestMatchType[input.type] =
      input match
        case _: "String1" => 1
        case _: "String2" => "Two"
    

    Scastie