Skip to content

Union type with object used with generic type infers wrong type even with type ascription #14642

Closed
@martingd

Description

@martingd

Compiler version

Scala 3.1.1

Minimized code

Consider this code where the lines with l2 and l3 fails compilation.

case object A
case class B()
case class C()
type Union = A.type | B | C
val a: List[A.type] = ???
val b: List[B] = ???
val c: List[C] = ???
val l1: List[Union] = a ++ b                       // OK
val l2: List[Union] = a ++ b ++ c                  // [E007] Found: List[Object], Required: List[Union]
val l3: List[Union] = (a: List[Union]) ++ b ++ c   // [E007] Found: List[Object], Required: List[Union]
val l4: List[Union] = (a: List[Union]) ++ (b ++ c) // OK

Output

[error] -- [E007] Type Mismatch Error: /path/to/Test.scala:22:26 
[error] 22 |    val l2: List[Union] = a ++ b ++ c
[error]    |                          ^^^^^^^^^^^
[error]    |                          Found:    List[Object]
[error]    |                          Required: List[Union]
[error] -- [E007] Type Mismatch Error: /path/to/Test.scala:23:26 
[error] 23 |    val l3: List[Union] = (a: List[Union]) ++ b ++ c
[error]    |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^
[error]    |                          Found:    List[Object]
[error]    |                          Required: List[Union]

Expectation

I am aware that the compiler does not infer union types but instead infers Matchable. However, it seems odd that val l1 compiles when l2 and l3 don't.

The problem might be that (a ++ b) is inferred to List[Matchable] and therefore (a ++ b) ++ c gets tagged as no-more-specific-than-List[Matchable] and therefore fails the type ascription List[Union]. It seems (a ++ b) should be tagged as no-more-specific-than-List[A.type | B] and therefore (a ++ b) ++ c should be tagged as no-more-specific-than-List[(A.type | B) | C], which either infers to List[Matchable] with no ascription, or passes the List[Union] ascription.

Adding the extra set of parenthesis for l4 basically reduces the case to be similar to l1 and things compile.

Things (almost) work if A is a class and not an object

It is important to note that the problem is only present if A is an object. Changing A to be a class changes the behaviour:

case class A()
case class B()
case class C()
type Union = A | B | C
val a: List[A] = ???
val b: List[B] = ???
val c: List[C] = ???
val l1: List[Union] = a ++ b                     // OK
val l2: List[Union] = a ++ b ++ c                // [E007] Found: List[Object], Required: List[Test1.Union]
val l3: List[Union] = (a: List[Union]) ++ b ++ c // OK

Now, l2 still fails compilation but the type ascription in l3 fixes the issue. The problem with l2 looks similar to the problem reported in #11449.

The output is:

[error] -- [E007] Type Mismatch Error: /path/to/Test.scala:10:26 
[error] 10 |    val l2: List[Union] = a ++ b ++ c
[error]    |                          ^^^^^^^^^^^
[error]    |                          Found:    List[Object]
[error]    |                          Required: List[Union]

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions