Type inference issue
Opened this issue · 3 comments
Apologies for the title that is quite vague; I don't know how to better qualify it.
Compiler version
3.6.4
Minimized code
import scala.annotation.nowarn
import scala.concurrent.Future
class InferenceTest {
trait Endpoint[INPUT] {
def serverSecurityLogic[F[_]](f: Any => F[Any]): PartialServerEndpoint[INPUT, F] = ???
}
class ServerEndpoint[F[_]] {
type INPUT
}
object ServerEndpoint {
type Full[_INPUT, F[_]] = ServerEndpoint[F] {
type INPUT = _INPUT
}
}
trait PartialServerEndpoint[INPUT, F[_]] {
def serverLogicSuccess(f: Any => INPUT => F[Any]): ServerEndpoint.Full[INPUT, F] = ???
def serverLogic(f: Any => INPUT => F[Any]): ServerEndpoint.Full[INPUT, F] = ???
}
private val getEndpoint: Endpoint[Unit] = ???
private val createEndpoint: Endpoint[Any] = ???
private def authenticate(jwt: Any): Future[Any] = ???
private def get(authenticatedUser: Any): Future[Any] = ???
private def create(authenticatedUser: Any)(createData: Any): Future[Any] = ???
@nowarn
private def toRoutes(e: ServerEndpoint[Future]): Unit = toRoutes(List(e))
private def toRoutes(serverEndpoints: List[ServerEndpoint[Future]]): Unit = ???
toRoutes(
List(
getEndpoint
.serverSecurityLogic(authenticate).serverLogicSuccess(user => _ => get(user)),
createEndpoint
.serverSecurityLogic(authenticate).serverLogic(create)
)
)
}Output
[error] -- [E134] Type Error: .../InferenceTest.scala:44:2
[error] 44 | toRoutes(
[error] | ^^^^^^^^
[error] |None of the overloaded alternatives of method toRoutes in class InferenceTest with types
[error] | (serverEndpoints:
[error] | List[InferenceTest.this.ServerEndpoint[scala.concurrent.Future]]): Unit
[error] | (e: InferenceTest.this.ServerEndpoint[scala.concurrent.Future]): Unit
[error] |match arguments (List[InferenceTest.this.ServerEndpoint[? >: F[X0] <: F]])
[error] |
[error] |where: F is a type variable with constraint >: scala.concurrent.Future and <: [_] =>> Any
[error] one error found
[error] (Compile / compileIncremental) Compilation failed
Expectation
The strictly identical same code works in Scala 2.13.16 (just changing using to implicit vals in the class declaration).
Hence I would expect it to compile without error in Scala 3 as well.
Workaround
Explicitly typing the list does work but I'm unclear as why it would be required now.
- toRoutes(List(
+ toRoutes(List[ServerEndpoint[Future]](We likely need a minimization without the Tapir dependencies.
Sure, here's a "not so minimal" reproduction but without any 3rd party dependencies.
import scala.annotation.nowarn
import scala.concurrent.Future
class InferenceTest {
// Models
case class AuthenticationError(message: String)
case class BusinessDTO(id: Long, name: String)
case class User(id: Long)
// Endpoints
trait Endpoint[SECURITY_INPUT, INPUT, ERROR_OUTPUT, OUTPUT, -R] {
def serverSecurityLogic[PRINCIPAL, F[_]](f: SECURITY_INPUT => F[Either[ERROR_OUTPUT, PRINCIPAL]]): PartialServerEndpoint[SECURITY_INPUT, PRINCIPAL, INPUT, ERROR_OUTPUT, OUTPUT, R, F] = ???
}
class ServerEndpoint[-R, F[_]] {
type SECURITY_INPUT
type PRINCIPAL
type INPUT
type ERROR_OUTPUT
type OUTPUT
}
object ServerEndpoint {
type Full[_SECURITY_INPUT, _PRINCIPAL, _INPUT, _ERROR_OUTPUT, _OUTPUT, -R, F[_]] = ServerEndpoint[R, F] {
type SECURITY_INPUT = _SECURITY_INPUT
type PRINCIPAL = _PRINCIPAL
type INPUT = _INPUT
type ERROR_OUTPUT = _ERROR_OUTPUT
type OUTPUT = _OUTPUT
}
}
trait PartialServerEndpoint[SECURITY_INPUT, PRINCIPAL, INPUT, ERROR_OUTPUT, OUTPUT, -R, F[_]] {
def serverLogicSuccess(f: PRINCIPAL => INPUT => F[OUTPUT]): ServerEndpoint.Full[SECURITY_INPUT, PRINCIPAL, INPUT, ERROR_OUTPUT, OUTPUT, R, F] = ???
def serverLogic(f: PRINCIPAL => INPUT => F[Either[ERROR_OUTPUT, OUTPUT]]): ServerEndpoint.Full[SECURITY_INPUT, PRINCIPAL, INPUT, ERROR_OUTPUT, OUTPUT, R, F] = ???
}
trait PekkoStreams {}
trait WebSockets {}
private val getEndpoint: Endpoint[String, Unit, AuthenticationError, Seq[BusinessDTO], Any] = ???
private val createEndpoint: Endpoint[String, BusinessDTO, AuthenticationError, Unit, Any] = ???
// Business implementation
private def authenticate(jwt: String): Future[Either[AuthenticationError, User]] = ???
private def get(authenticatedUser: User): Future[Seq[BusinessDTO]] = ???
private def create(authenticatedUser: User)(createData: BusinessDTO): Future[Either[AuthenticationError, Unit]] = ???
// Server mapping
@nowarn
private def toRoutes(e: ServerEndpoint[PekkoStreams & WebSockets, Future]): Unit = toRoutes(List(e))
private def toRoutes(serverEndpoints: List[ServerEndpoint[PekkoStreams & WebSockets, Future]]): Unit = ???
toRoutes(
List(
getEndpoint
.serverSecurityLogic(authenticate)
.serverLogicSuccess(user => _ => get(user)),
createEndpoint
.serverSecurityLogic(authenticate)
.serverLogic(create),
)
)
}The associated error is:
[error] -- [E134] Type Error: .../InferenceTest.scala:67:2
[error] 67 | toRoutes(
[error] | ^^^^^^^^
[error] |None of the overloaded alternatives of method toRoutes in class InferenceTest with types
[error] | (serverEndpoints:
[error] | List[
[error] | InferenceTest.this.ServerEndpoint[
[error] | InferenceTest.this.PekkoStreams & InferenceTest.this.WebSockets,
[error] | scala.concurrent.Future]
[error] | ]
[error] |): Unit
[error] | (e:
[error] | InferenceTest.this.ServerEndpoint[
[error] | InferenceTest.this.PekkoStreams & InferenceTest.this.WebSockets,
[error] | scala.concurrent.Future]
[error] |): Unit
[error] |match arguments (List[InferenceTest.this.ServerEndpoint[Any, ? >: F[X0] <: F]])
[error] |
[error] |where: F is a type variable with constraint >: scala.concurrent.Future and <: [_] =>> Any
[error] one error found
[error] (Compile / compileIncremental) Compilation failed
I hadn't realized before but removing the toRoutes(e: ServerEndpoint[PekkoStreams & WebSockets, Future]) (the one not used) makes it compile fine.
Took a bit of time to minimize it a bit more.
import scala.annotation.nowarn
import scala.concurrent.Future
class InferenceTest {
trait Endpoint[INPUT] {
def serverSecurityLogic[F[_]](f: Any => F[Any]): PartialServerEndpoint[INPUT, F] = ???
}
class ServerEndpoint[F[_]] {
type INPUT
}
object ServerEndpoint {
type Full[_INPUT, F[_]] = ServerEndpoint[F] {
type INPUT = _INPUT
}
}
trait PartialServerEndpoint[INPUT, F[_]] {
def serverLogicSuccess(f: Any => INPUT => F[Any]): ServerEndpoint.Full[INPUT, F] = ???
def serverLogic(f: Any => INPUT => F[Any]): ServerEndpoint.Full[INPUT, F] = ???
}
private val getEndpoint: Endpoint[Unit] = ???
private val createEndpoint: Endpoint[Any] = ???
private def authenticate(jwt: Any): Future[Any] = ???
private def get(authenticatedUser: Any): Future[Any] = ???
private def create(authenticatedUser: Any)(createData: Any): Future[Any] = ???
@nowarn
private def toRoutes(e: ServerEndpoint[Future]): Unit = toRoutes(List(e))
private def toRoutes(serverEndpoints: List[ServerEndpoint[Future]]): Unit = ???
toRoutes(
List(
getEndpoint
.serverSecurityLogic(authenticate).serverLogicSuccess(user => _ => get(user)),
createEndpoint
.serverSecurityLogic(authenticate).serverLogic(create)
)
)
}[error] -- [E134] Type Error: .../InferenceTest.scala:44:2
[error] 44 | toRoutes(
[error] | ^^^^^^^^
[error] |None of the overloaded alternatives of method toRoutes in class InferenceTest with types
[error] | (serverEndpoints:
[error] | List[InferenceTest.this.ServerEndpoint[scala.concurrent.Future]]): Unit
[error] | (e: InferenceTest.this.ServerEndpoint[scala.concurrent.Future]): Unit
[error] |match arguments (List[InferenceTest.this.ServerEndpoint[? >: F[X0] <: F]])
[error] |
[error] |where: F is a type variable with constraint >: scala.concurrent.Future and <: [_] =>> Any
[error] one error found
[error] (Compile / compileIncremental) Compilation failed
Updated the original post with this minification as well.