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

Scala 3 not applying Scala 2 implicit conversions in the precense of subtyping #21200

Open
BalmungSan opened this issue Jul 15, 2024 · 1 comment
Labels
area:implicits related to implicits itype:bug

Comments

@BalmungSan
Copy link

Compiler version

3.3.3 (LTS)

Description

I found some weird behavior with Scala 3 while interoperating with a Scala 2 library, related to implicit conversions in the presence of subtyping.

This is the real code, using Natchez: https://scastie.scala-lang.org/BalmungSan/kGHAAsrsTWeFwMxryhoPeg/2
The code should compile because there is an implicit conversion from String to TraceValue, but the compiler is not picking it.

If I emulate the library using Scala 3: https://scastie.scala-lang.org/BalmungSan/kGHAAsrsTWeFwMxryhoPeg/15
Then, it works as expected (despite the conversion warning).

However, if I emulate the library using Scala 2: https://scastie.scala-lang.org/BalmungSan/kGHAAsrsTWeFwMxryhoPeg/16
Then, it fails again.

Is this the expected behavior? If so, is there any workaround on the user side? Or, is there any fix we may apply on the library side (other than migrating the whole codebase to Scala 3)?

Minimized code

Code
import io.github.iltotore.iron.:|
import io.github.iltotore.iron.RefinedTypeOps
import io.github.iltotore.iron.constraint.any.Not
import io.github.iltotore.iron.constraint.collection.Empty

object types:
  type NonEmptyString = String :| Not[Empty]
  trait NonEmptyStringOps[A <: NonEmptyString] extends RefinedTypeOps[String, Not[Empty], A]

  opaque type CustomId <: NonEmptyString = NonEmptyString
  object CustomId extends NonEmptyStringOps[CustomId]
end types

/** Scala 2 library. */
object observability:
  trait Trace {
    def put(tags: (String, TraceValue)*): Unit
  }

  sealed trait TraceValue {
    def value: Any
  }
  
  object TraceValue {
    final case class Str(value: String) extends TraceValue
    final case class Num(value: Int) extends TraceValue
    final case class Bool(value: Boolean) extends TraceValue

    import scala.language.implicitConversions

    implicit def conversion[A](a: A)(implicit ev: ToTraceValue[A]): TraceValue = ev(a)

    sealed trait ToTraceValue[A] {
      def apply(a: A): TraceValue
    }

    object ToTraceValue {
      implicit val forString: ToTraceValue[String] = new ToTraceValue[String] {
        def apply(str: String): TraceValue =
          TraceValue.Str(str)
      }
          
      implicit val forInt: ToTraceValue[Int] = new ToTraceValue[Int] {
        def apply(num: Int): TraceValue =
          TraceValue.Num(num)
      }
         
      implicit val forBoolean: ToTraceValue[Boolean] = new ToTraceValue[Boolean] {
        def apply(bool: Boolean): TraceValue =
          TraceValue.Bool(bool)
      }
    }
  }
end observability 

object logic:
  import types.CustomId
  import observability.Trace

  def foo(id: CustomId)(using trace: Trace): Unit =
    trace.put("id" -> id)
  end foo
end logic

@main
def main(): Unit =
  println("It compiles!")
end main

Output

Found: (id : types.CustomId)
Required: observability.TraceValue

The following import might fix the problem:

import observability.TraceValue.ToTraceValue.forString

@BalmungSan BalmungSan added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Jul 15, 2024
@Gedochao Gedochao added itype:question area:implicits related to implicits and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Jul 17, 2024
@EugeneFlesselle
Copy link
Contributor

EugeneFlesselle commented Jul 17, 2024

This is definitely an issue, given the following minimization:

import scala.language.implicitConversions

trait Trace
trait ToTrace[A]

given ToTrace[String] = ???

object Test:
  type H = "Hello"
  val s: H = ???

  locally:
    given [A: ToTrace]: Conversion[A, Trace] = ???
    val _: Trace = s : String // ok
    val _: Trace = s // ok
  
  locally:
    implicit def conversion[A: ToTrace](a: A): Trace = ???
    val _: Trace = s : String // ok
    val _: Trace = s : "Hello" // ok
    val _: Trace = s // error

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:implicits related to implicits itype:bug
Projects
None yet
Development

No branches or pull requests

3 participants