Structural types: Structural access not allowed … because it has a parameter type with an unstable erasure

I try to write a generic method that forwards an Instant or an OffsetDateTime to a given LocalTime. For example, if I have the OffsetDateTime 2023-10-03T18:36:18.29+02:00 and LocalTime 18:00, then the result should be 2023-10-04T18:00:00+02:00. Or, if the LocalTime input is 19:00, then result should be 2023-10-03T19:00:00+02:00

However, Instant and OffsetDateTime miss a common interface, so I decided to give structural types a try.

import reflect.Selectable.reflectiveSelectable
import java.time.*

def forwardTo[
    T <: {
      def truncatedTo(t: TemporalUnit): T
      def `with`(t: LocalTime): T
      def isBefore(t: T): Boolean
      def plus(a: Long, u: TemporalUnit): T
    }
](time: T, localTime: LocalTime): T =
  val adjustedTime = time.truncatedTo(ChronoUnit.DAYS).`with`(localTime)
  if adjustedTime.isBefore(time) then adjustedTime.plus(1, ChronoUnit.DAYS)
  else adjustedTime

Unfortunately, this results in a compiler error:

[error] -- Error: Time.scala:38:28 
[error] 38 |    if adjustedTime.isBefore(time) then adjustedTime.plus(1, ChronoUnit.DAYS)
[error]    |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^
[error]    |Structural access not allowed on method isBefore because it has a parameter type with an unstable erasure

Is there something I am doing wrong? Am I using the wrong tool for the task at hand? Any feedback is appreciated.

Scastie Link:

I just realized that it can be done without structural types:

  def forwardTo(time: Instant, localTime: LocalTime, zoneId: ZoneId = ZoneId.systemDefault()): Instant =
    val offsetDateTime = OffsetDateTime.ofInstant(time, zoneId)
    forwardTo(offsetDateTime, localTime).toInstant

  def forwardTo(time: OffsetDateTime, localTime: LocalTime): OffsetDateTime =
    val adjustedTime = time.truncatedTo(ChronoUnit.DAYS).`with`(localTime)

    if adjustedTime.isBefore(time) then adjustedTime.plus(1, ChronoUnit.DAYS)
    else adjustedTime

But if someone wants to shed some light on the Structural access not allowed on method isBefore because it has a parameter type with an unstable erasure message, then this would still be appreciated.

It’s a restriction specifically of the reflectiveSelectable implementation of Selectable. Since reflectiveSelectable uses JVM method handles it needs a precise parameter and return type signature for isBefore. But since isBefore uses a local type parameter, that signature cannot be generated.

For normal method calls that’s not a problem since there is a clever “bridge” machinery to compensate for the rigidity of JVM signatures. But those techniques cannot be used for reflective access.

So, it’s a rather technical implementation restriction due to a limitation of the JVM. Note also that this only applies to reflectiveSelectable, Other implementations of Selectable might not have that restriction.

2 Likes