E is not a legal path since it is not a concrete type (Scala 3)
Asked Answered
B

2

7

I'm trying to migrate a library from Scala 2.13 to Scala 3, but the existing code does not compile.

Here is a snippet

trait Identity
trait Authenticator

trait Env {
  type I <: Identity
  type A <: Authenticator
}

trait IdentityService[T <: Identity]
trait AuthenticatorService[T <: Authenticator]

trait Environment[E <: Env] {
  def identityService[T <: Identity]: IdentityService[E#I]
  def authenticatorService: AuthenticatorService[E#A]
}

The Scala 3 compiler fails with:

error] 14 |  def identityService[T <: Identity]: IdentityService[E#I]
[error]    |                                                      ^
[error]    |                                         E is not a legal path
[error]    |                                         since it is not a concrete type
[error] -- Error: 
[error] 15 |  def authenticatorService: AuthenticatorService[E#A]
[error]    |                                                 ^
[error]    |                                         E is not a legal path
[error]    |                                         since it is not a concrete type
[error] two errors found

You can try directly at https://scastie.scala-lang.org/GuqSqC9yQS6uMiw9wyKdQg

Bernabernadene answered 22/11, 2023 at 15:52 Comment(2)
While unfortunately I don't know how to help, I thought it was still a good idea to leave the documentation describing the dropped feature docs.scala-lang.org/scala3/reference/dropped-features/…Communalize
github.com/playframework/play-silhouette/pull/36Mauritamauritania
M
7

You can use match types (and Aux-pattern for technical reasons, namely refined types Env { type I = i } can't be type patterns)

trait Env:
  type I <: Identity
  type A <: Authenticator
object Env:
  type AuxI[_I <: Identity] = Env { type I = _I }
  type AuxA[_A <: Authenticator] = Env { type A = _A }

trait IdentityService[T <: Identity]
trait AuthenticatorService[T <: Authenticator]

// match types
type I[E <: Env] <: Identity = E match
  case Env.AuxI[i] => i // lower case is significant

type A[E <: Env] <: Authenticator = E match
  case Env.AuxA[a] => a

trait Environment[E <: Env]:
  def identityService[T <: Identity]: IdentityService[I[E]]
  def authenticatorService: AuthenticatorService[A[E]]

What does Dotty offer to replace type projections?

In Scala 3, how to replace General Type Projection that has been dropped?

https://users.scala-lang.org/t/converting-code-using-simple-type-projections-to-dotty/6516

Alternatively you can use type classes.

Mauritamauritania answered 22/11, 2023 at 20:2 Comment(0)
S
2

Here's a solution using path-dependent types:

//> using scala 3

trait Identity
trait Authenticator

trait Env {
  type I <: Identity
  type A <: Authenticator
}

trait IdentityService[I <: Identity]
trait AuthenticatorService[A <: Authenticator]

trait Environment[E <: Env](using val env: E) {
  def identityService: IdentityService[env.I]
  def authenticatorService: AuthenticatorService[env.A]
}
Selfidentity answered 23/11, 2023 at 19:34 Comment(1)
This technique is easier than @Dmytro Mitin one, at least in my use case.Springlet

© 2022 - 2024 — McMap. All rights reserved.