scala3 icon indicating copy to clipboard operation
scala3 copied to clipboard

Inline given with using parameter isn't properly inlined

Open Iltotore opened this issue 3 years ago • 0 comments

Note: related to #14282. I am not sure if this change is published in 3.1.3/3.2.0-RC3

Compiler version

Tested in 3.1.3 and 3.2.0-RC3

Minimized code & Output

Inline methods in given instances with using parameters do not always get inlined properly, making them unusable with Expr#value (and similar) in a macro. This behaviour being tricky (works sometimes, even with using parameters), I'm struggling to minmize my example. Here a shrinked code from my current project:

Constraint.scala

trait Constraint[T, C]:

  inline def test(value: T): Boolean

  inline def message: String

macros.scala

import scala.quoted.*

object macros:

  inline def assertCondition(inline cond: Boolean, inline message: String): Unit = ${assertConditionImpl('cond, 'message)}

  private def assertConditionImpl(cond: Expr[Boolean], message: Expr[String])(using Quotes): Expr[Unit] =

    val report = quotes.reflect.report

    val condValue = cond.valueOrAbort //.getOrElse(report.errorAndAbort(s"Expected a known value. Got ${cond.show}"))
    val messageValue = message.value.getOrElse("<Unknown message>")

    if !condValue then report.errorAndAbort(messageValue)
    else '{()}

end macros

Two example Constraint instances:

final class Greater[V <: Int]

inline given [V <: Int]: Constraint[Int, Greater[V]] with

  override inline def test(value: Int): Boolean = value > compiletime.constValue[V]

  override inline def message: String = "Should be greater"


  final class DescribedAs[C, V <: String]

  inline given [T, C, Impl <: Constraint[T, C], V <: String](using Impl): Constraint[T, DescribedAs[C, V]] with

    override inline def test(value: T): Boolean = summonInline[Impl].test(value)
    override inline def message: String = constValue[V]

This works:

macros.assertCondition(summonInline[Constraint[Int, Greater[0]]].test(1), "test") //OK

But this doesn't:

macros.assertCondition(summonInline[Constraint[Int, Greater[0] DescribedAs "pos"]].test(1), "test")

with the following compilation error:

[error] 21 |    macros.assertCondition(summonInline[Constraint[Int, Greater[0] DescribedAs "pos"]].test(1), "test")
[error]    |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[error]    |Expected a known value.
[error]    |
[error]    |The value of: {
[error]    |  val x$1$proxy2: io.github.iltotore.iron.Main.given_Constraint_Int_Greater[0] = (new io.github.iltotore.iron.Main.given_Constraint_Int_Greater[0](): io.github.iltotore.iron.Main.g
iven_Constraint_Int_Greater[0])
[error]    |
[error]    |  ((false: scala.Boolean): scala.Boolean)
[error]    |}
[error]    |could not be extracted using scala.quoted.FromExpr$PrimitiveFromExpr@6ff6baf0
[error] one error found

As shown in the error, an unexpected leftover variable remains after inlining the method which prevents Expr#value to work. I guess this is because the using param do not get inlined in the given instance (and given-with disallows inline parameters).

For instance, it works when dropping the given ... with syntax:

class DescribedAsConstraint[T, C, Impl <: Constraint[T, C], V <: String](using Impl)
    extends Constraint[T, DescribedAs[C, V]]:

  override inline def test(value: T): Boolean = summonInline[Impl].test(value)

  override inline def message: String = constValue[V]

transparent inline given [T, C, Impl <: Constraint[T, C], V <: String](using inline constraint: Impl): Constraint[T, DescribedAs[C, V]] = new DescribedAsConstraint
macros.assertCondition(summonInline[Constraint[Int, Greater[0] DescribedAs "pos"]].test(1), "test") //OK

The potential solution (IMO) is to allow inline parameters in given-with statements and make them desugar like this:

trait Test:

  inline def foo: Boolean

From

inline given (using inline dep: Foo): Test with

  override inline def foo: Boolean = dep.booleanValue

to

//Or another class name
class Test$1(using inline dep: Foo): //Meaning inline must also be allowed in constructors like this to work

  override inline def foo: Boolean = dep.booleanValue

inline given (using inline dep: Foo): Test = new Test$1

Note: Nicolas Stucki commented in the previous issue that inline isn't required for given didn't test without but I will edit this issue once done.

Iltotore avatar Aug 07 '22 18:08 Iltotore