4

From the context of a Scala 3 Macro:

  def aMacroImpl[T](using Type[T], Quotes): Expr[SpecialMap[_, _, _]] = {
    import quotes.reflect._
    val TRepr: TypeRepr = TypeRepr.of[T]
    val TSym: Symbol = TRepr.typeSymbol
    val SrtdMapSym: Symbol = TypeRepr.of[scala.collection.SortedMap].typeSymbol

    TSym.declaredFields.map{ f => TRepr.memberType(f) }.collect {
      case fieldTyepRepr if fieldTyepRepr.derivesFrom(SrtdMapSym) =>
        val fieldType = Inferred(fieldTyepRepr).tpe.asType
        fieldType match {
          ////////////////////////////////////////////////////////////
          case ... => // <-- This line is the subject of the question.
          ////////////////////////////////////////////////////////////
            '{SpecialMap.apply[k, v, sm](/* omitted for brevity*/)}
        }
    }
  }

  // More context.
  object SpecialMap {
    import scala.collection.mutable.Builder
    def apply[K, V, SM <: SortedMap[K, V]](bldr: Builder[(K, V), SM]): SpecialMap[K, V, SM] = {
      SpecialMap[K, V, SM](bldr.result)
    }
  }
  case class SpecialMap[K, V, SM <: SortedMap[K, V]](wrapedMap: SM)

What kind of syntax can replace "..." in the code snippet above, that can match on the given SortedMap type such that it can retrieve all three type parameters: k, v, and sm?

The enquirer has already tried the following:

  1. Incomplete Solutions:
/*a*/  case '[sm] => // provides sm, but not k or v

/*b*/  case '[SortedMap[k, v]] => // provides k and v but not sm
  1. Direct Compiler errors:
/*a*/  case '[sm[k, v]] => // sm$given1.Underlying does not take type parameters

/*b*/  case '[_[k, v]] => // ']' expected, but '[' found 

/*c*/  case 'sm[k, v] => /* symbol literal 'sm is no longer supported, use a string literal
"sm" or an application Symbol("sm") instead, or enclose in braces '{sm} if you want a
quoted expression. For now, you can also `import language.deprecated.symbolLiterals` to
accept the idiom, but this possibility might no longer be available in the future. */

/*d*/
  import language.deprecated.symbolLiterals
  fieldType match {
    case 'sm[k, v] => // '=>' expected, but '[' found

/*e*/
  type SM[k, v] = fieldType match {  // Not found: type fieldType
    case '[sm] => Type[sm]
    case _ => quotes.reflect.report.errorAndAbort("uh oh")
  }

/*h*/
  case '[sm] =>
   type SM[k1, v1] <: SortedMap[k1, v1] = Type[sm] // cannot combine bound and alias

/*f*/
case '[sm] =>
  type SM = Type[sm]
  fieldType match {
    case '[SM[k, v]] => // SM does not take type parameters
  1. Subtle Compiler Errors:
// Examples in this snippet lose the association between k, v, sm, and SortedMap[k,v].

// All yield the following error:
// Type argument sm does not conform to upper bound collection.SortedMap[k, v]

/*a*/
  case '[SortedMap[k, v]] =>
    fieldType match {
      case '[sm] if TypeRepr.of[sm].derivesFrom((TypeRepr.of[SortedMap[k, v]]).typeSymbol) =>
        '{SpecialMap.apply[k, v, sm](/***/)} // ... sm does not conform ...

/*b*/
  fieldType match {
    case '[sm] =>
      fieldType match {
      case '[SortedMap[k, v]] if TypeRepr.of[sm].derivesFrom((TypeRepr.of[SortedMap[k, v]]).typeSymbol) =>
        '{SpecialMap.apply[k, v, sm](/***/)} // ... sm does not conform ...

/*c*/
  (fieldType, fieldType) match {
    case ('[sm], '[SortedMap[k, v]]) =>
      '{SpecialMap.apply[k, v, sm](/***/)} // ... sm does not conform ...

/*d*/
  fieldType match {
    case '[sm1] =>
      type SM[k1,v1] = Type[sm1]
      fieldType match {
        case '[SM[k, v]] =>
          '{SpecialMap.apply[k, v, SM[k, v]](/***/)} // ... SM does not conform ...

/*e*/
  fieldType match {
    case '[sm1] =>
    fieldType match {
      case '[SortedMap[k, v]] =>
        type SM[k,v] = Type[sm1]
        '{SpecialMap.apply[k, v, SM[k, v]](/***/)}  // ... SM does not conform ...

Of course, explicitly writing every known concrete class derived from SortedMap works for any specific project, but libraries should accommodate unknown inheritors of the SortedMap trait.

Is there any way to capture all three type parameters along with their interdependent relationships?

Thank you for your consideration.

Ben McKenneby
  • 481
  • 4
  • 15

1 Answers1

1

I guess you can't guarantee at the compile time of macro that in

val fieldTypeTree = Inferred(fieldTyepRepr).tpe
val fieldType = fieldTypeTree.asType

(fieldType, fieldTypeTree.baseType(SrtdMapSym).asType) match {
  case ('[sm], '[SortedMap[k, v]]) =>
    '{SpecialMap.apply[k, v, sm](???)}
}

k, v, sm satisfy sm <: SortedMap[k, v].

So if you can't construct the tree via quotation '{SpecialMap.apply[k, v, sm](???)}, you should construct it manually.

Alternatively you can return '{SpecialMap.apply[k, v, sm & SortedMap[k, v]](???)}.


Also maybe something like the following is possible

val fieldTypeTree = Inferred(fieldTyepRepr).tpe
val fieldType = fieldTypeTree.asType
val parentType = fieldTypeTree.baseType(SrtdMapSym).asType

(fieldType, parentType) match {
  case ('[ft], '[pt]) =>
    '{foo[ft, pt]} match
      case '{
        type k
        type v
        type sm <: SortedMap[`k`, `v`]
        foo[`sm`, SortedMap[`k`, `v`]]
      } =>
        '{SpecialMap.apply[k, v, sm](???)}
  }

// outside def aMacroImpl, otherwise "access to method foo from wrong staging level"
def foo[A, B] = ???

For some reason this still doesn't typecheck: https://scastie.scala-lang.org/DmytroMitin/ZbtQWZFkTma6MlFM72x5Dg/2

Maybe because of bugs:

Type bound is not inferred inside quotation (type variable escapes?)

Pattern matching type variable does not infer bounds

assertion failed: unresolved symbols while pickling quote with type bounds

See also:

https://docs.scala-lang.org/scala3/guides/macros/quotes.html#type-variables-in-quoted-patterns

In some cases we need to define a pattern variable that is referenced several times or has some type bounds. To achieve this, it is possible to create pattern variables at the start of the pattern using type t with a type pattern variable.

fuseMap macro in scala 3

Explicit type conversion in Scala 3 macros

Get information of type in Scala3 macro

Turning inlined varargs into a generic tuple

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • 1
    Thank you for considering this question, Dmytro! Although a web of interdependent projects have all waited for this solution for almost a year, it has been that long since I've looked at any of them. :) Hopefully in the coming months, I can revive them and try to integrate your kind suggestions, but first I'm afraid I'll have to re-learn Scala 3 macros, almost from scratch, and regain familiarity with the applications in question. Emphasis: Thank you for shining a light on a possible way forward! – Ben McKenneby Sep 29 '22 at 16:02