Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type inference doesn't work correctly for union types used with extension method #18626

Closed
kpodsiad opened this issue Sep 30, 2023 · 5 comments · Fixed by #18680
Closed

Type inference doesn't work correctly for union types used with extension method #18626

kpodsiad opened this issue Sep 30, 2023 · 5 comments · Fixed by #18680

Comments

@kpodsiad
Copy link

Compiler version

3.3.1

Minimized code

//> using scala "3.3.1"

trait Random[F[_]] {
  def element[T](list: Seq[T]): F[T] = ???
}

trait Monad[F[_]] {
  def map[A, B](fa: F[A])(f: A => B): F[B]
  def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
}

object Monad {
  extension [F[_]: Monad, A](fa: F[A])
    def map[B](f: A => B): F[B] = ???
    def flatMap[B](f: A => F[B]): F[B] = ???
}

sealed trait Animal
object Animal {
  object Cat extends Animal
  object Dog extends Animal
}

type Mammal = Animal.Cat.type | Animal.Dog.type
val mammals: List[Mammal] = ???

def work[F[_]](random: Random[F])(using mf: Monad[F]) = {
  val result: F[Mammal] =
    mf.map(fa = random.element(mammals))(a => a)

  val result2: F[Mammal] = Monad.map(random.element(mammals))(a => a)

  import Monad.*

  val result3: F[Mammal] = random
    .element(mammals)
    .map { a =>
       a // << a is an Animal, compilation error
    }
}

Output

There is a compilation error in result3

Found:    (a : Animal)
Required: Animal.Cat.type | Animal.Dog.type

Expectation

result3 works correctly, the same as 1 & 2, inferred type should be Cat | Dog or Mammal.

@kpodsiad kpodsiad added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Sep 30, 2023
@dwijnand
Copy link
Member

dwijnand commented Oct 2, 2023

I'm not sure that this is a bug, despite it being unfortunate and a good argument being made on how those other variants work. Singleton types of non-case objects are very brittle - I believe if you make Cat and Dog case objects, then it should continue to work.

@dwijnand dwijnand added itype:enhancement and removed itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Oct 2, 2023
@mbovel
Copy link
Member

mbovel commented Oct 2, 2023

I believe if you make Cat and Dog case objects, then it should continue to work.

I tried the following:

sealed trait Animal
object Animal {
  case object Cat extends Animal
  case object Dog extends Animal
}

But it results in the same error.

Is it what you meant?

Also, this seems to be a similar to #18551, isn't it? In both situations, the problem is that a union of case objects is widened to it parent type.

Setting the type parameter of element explicitly solves the problem:

    .element[Mammal](mammals)

@kpodsiad
Copy link
Author

kpodsiad commented Oct 3, 2023

I'm fine with helping compiler a bit as I don't use union of case objects that often. However, it's a bummer that among many ways to do the same, results 1..3, the most popular one is not working as one expects.

If fixing it is problematic, can compiler suggests what to do in such cases? It would improve UX and help people who can't figure it out. Just throwing an idea, but probably cost outweighs benefits.

@dwijnand
Copy link
Member

dwijnand commented Oct 3, 2023

Also, this seems to be a similar to #18551, isn't it? In both situations, the problem is that a union of case objects is widened to it parent type.

Yeah, perhaps we can find where the TypeRef to Mammal is replaced by the simplified (and lossy) lub of the hard union, and make it not replace.

@kpodsiad
Copy link
Author

kpodsiad commented Oct 4, 2023

simpler testcase

//> using scala "3.3.1"

sealed trait Animal
object Animal {
  case object Cat extends Animal
  case object Dog extends Animal
}

type Mammal = Animal.Cat.type | Animal.Dog.type
val mammals: List[Mammal] = ???

val result = mammals.head
val mammal: Mammal = result // << error

@dwijnand dwijnand self-assigned this Oct 12, 2023
@dwijnand dwijnand linked a pull request Oct 12, 2023 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants