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

Regression (?) in softwaremill/tapir - refined type returned in a macro unable to be matched #19458

Closed
jchyb opened this issue Jan 16, 2024 · 6 comments
Assignees
Labels
area:metaprogramming:compiletime The scala.compiletime package area:metaprogramming:reflection Issues related to the quotes reflection API itype:bug

Comments

@jchyb
Copy link
Contributor

jchyb commented Jan 16, 2024

Compiler version

3.4.0-RC1-bin-20240115-31f837e-NIGHTLY,
compiles successfully with 3.3.1

Minimized code

main.scala:

import scala.compiletime._
final class Greater[V]
final class Less[V]

// original codebase used non-transparent inlines in the defs below, but I think this would end up passing an abstract type parameter to the macro, so I don't think that should have worked (but transparent inlines should)
transparent inline given validatorForGreater[N: Numeric, NM <: N](using witness: ValueOf[NM]): ValidatorForPredicate[N, Greater[NM]] = ???
transparent inline given validatorForLess[N: Numeric, NM <: N](using witness: ValueOf[NM]): ValidatorForPredicate[N, Less[NM]] = ???
transparent inline def summonValidators[N, A <: Tuple]: List[ValidatorForPredicate[N, Any]] = {
  inline erasedValue[A] match
    case _: EmptyTuple => Nil
    case _: (head *: tail) =>
      summonInline[ValidatorForPredicate[N, head]]
        .asInstanceOf[ValidatorForPredicate[N, Any]] :: summonValidators[N, tail]
}
transparent inline given validatorForAnd[N, Predicates](using mirror: IntersectionTypeMirror[Predicates]): ValidatorForPredicate[N, Predicates] =
  new ValidatorForPredicate[N, Predicates] {
    summonValidators[N, mirror.ElementTypes]
  }

trait ValidatorForPredicate[Value, Predicate]

@main def main() = validatorForAnd[Int, Greater[1] & Less[3]]

macro.scala:

import scala.quoted.Quotes

import scala.annotation.implicitNotFound
import scala.quoted.*
import scala.collection.View.Empty

trait IntersectionTypeMirror[A] {
  type ElementTypes <: Tuple
}

class IntersectionTypeMirrorImpl[A, T <: Tuple] extends IntersectionTypeMirror[A] {
  override type ElementTypes = T
}

object IntersectionTypeMirror {

  transparent inline given derived[A]: IntersectionTypeMirror[A] = ${ derivedImpl[A] }

  private def derivedImpl[A](using Quotes, Type[A]): Expr[IntersectionTypeMirror[A]] = {
    import quotes.reflect.*

    val tplPrependType = TypeRepr.of[? *: ?]
    val tplConcatType = TypeRepr.of[Tuple.Concat]

    def prependTypes(head: TypeRepr, tail: TypeRepr): TypeRepr =
      AppliedType(tplPrependType, List(head, tail))

    def concatTypes(left: TypeRepr, right: TypeRepr): TypeRepr =
      AppliedType(tplConcatType, List(left, right))

    def rec(tpe: TypeRepr): TypeRepr = {
      tpe.dealias match
        case AndType(left, right) => concatTypes(rec(left), rec(right))
        case t                    => prependTypes(t, TypeRepr.of[EmptyTuple])
    }
    val tupled =
      TypeRepr.of[A].dealias match {
        case and: AndType => rec(and).asType.asInstanceOf[Type[Elems]]
        case tpe          => report.errorAndAbort(s"${tpe.show} is not an intersection type")
      }
    type Elems

    given Type[Elems] = tupled

    Apply(
      TypeApply(
        Select.unique(
          New(
            Applied(
              TypeTree.of[IntersectionTypeMirrorImpl],
              List(
                TypeTree.of[A],
                TypeTree.of[Elems]
              )
            )
          ),
          "<init>"
        ),
        List(
          TypeTree.of[A],
          TypeTree.of[Elems]
        )
      ),
      Nil
    ).asExprOf[IntersectionTypeMirror[A]]
  }
}

Output

[error] ./main.scala:22:20
[error] cannot reduce inline match with
[error]  scrutinee:  scala.compiletime.erasedValue[mirror$proxy1.ElementTypes] : mirror$proxy1.ElementTypes
[error]  patterns :  case _:EmptyTuple
[error]              case _:*:[head @ _, tail @ _]
[error] @main def main() = validatorForAnd[Int, Greater[1] & Less[3]]
[error]                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expectation

I think it should compile. The code used in tapir actually uses non-transparent inlines in code that calls transparent inlines, which I don't think should have worked, but did (probably because of the previous match types). But even after correcting that, the code errors with what seems to be an abstract type member.

@jchyb jchyb added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label area:match-types regression This worked in a previous version but doesn't anymore area:metaprogramming:reflection Issues related to the quotes reflection API and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Jan 16, 2024
@WojciechMazur
Copy link
Contributor

I think this issue origined in the SIP-56 and the new match types #18262 (comment)
@sjrd can you can a look at this issue?

@sjrd
Copy link
Member

sjrd commented Jan 16, 2024

This is an inline match, not a match type. SIP-56 did not touch those.

@sjrd sjrd assigned nicolasstucki and unassigned sjrd Jan 16, 2024
@jchyb jchyb added area:metaprogramming:compiletime The scala.compiletime package and removed area:match-types labels Jan 16, 2024
@nicolasstucki
Copy link
Contributor

Minimized

macro.scala // same

main.scala

import scala.compiletime._

inline def summonValidators[A <: Tuple]: Int =
  inline erasedValue[A] match
    case _: EmptyTuple => 1
    case _: (head *: tail) => 2

def main() =
  val mirror = IntersectionTypeMirror.derived[Some[Any] & Option[Int]]
  summonValidators[mirror.ElementTypes]

@nicolasstucki
Copy link
Contributor

For the minimized example, the type that we get from rec(and)

        case and: AndType => rec(and).asType.asInstanceOf[Type[Elems]]

is

scala.Tuple.Concat[
  scala.*:[_ >: scala.Nothing <: scala.Any, _ >: scala.Nothing <: scala.Any][scala.Some[scala.Any], scala.Tuple$package.EmptyTuple],
  scala.*:[_ >: scala.Nothing <: scala.Any, _ >: scala.Nothing <: scala.Any][scala.Option[scala.Int], scala.Tuple$package.EmptyTuple]]

This is a nonsensical type. I suspect there is a bug in the macro implementation. My guess is in the use of

    val tplPrependType = TypeRepr.of[? *: ?]

@nicolasstucki
Copy link
Contributor

This is a way to fix prependTypes

    def prependTypes(head: TypeRepr, tail: TypeRepr): TypeRepr =
      (head.asType, tail.asType) match {
        case ('[h], '[type t <: Tuple; t]) => TypeRepr.of[h *: t]
      }

@jchyb
Copy link
Contributor Author

jchyb commented Mar 28, 2024

Fixed and merged in tapir

@jchyb jchyb closed this as completed Mar 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:metaprogramming:compiletime The scala.compiletime package area:metaprogramming:reflection Issues related to the quotes reflection API itype:bug
Projects
None yet
Development

No branches or pull requests

5 participants