3

I am coding using scala3, leveraging programmatic structural types. The structural types happen to mimic existing case classes: their definition is pure boiler plate, hence the temptation to craft them through meta-programming.

I understand how to craft a function implementation, typically via typeclass derivation. But here we are trying to craft a (structural) type.

This was possible in scala2, via class macro annotation, but those are gone in scala3. Is there a way ? If so how ?

Code below is the result I would like to obtain :


// Library part
trait View extends scala.Selectable :
  def selectDynamic(key:String) =
    println(s"$key is being looked up")
    ???


// DSL Definition part
case class SomeDefWithInt   ( i : Int    )
case class SomeDefWithString( s : String )

// Boiler-plate code 
type ViewOf[M] = M match
  case SomeDefWithInt    => View { def i : Int    }
  case SomeDefWithString => View { def s : String }

// Mockup usage
class V extends View
val v = V()

v.asInstanceOf[ViewOf[SomeDefWithInt   ]].i
v.asInstanceOf[ViewOf[SomeDefWithString]].s

is it possible to create ViewOf[M] of an arbitrary case class M ?

Thank you !

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • Can you use code generation (like in https://github.com/typelevel/simulacrum-scalafix or https://github.com/scalanlp/breeze/blob/master/DEVELOP.md)? Compiler plugin would be an overkill, I guess. – Dmytro Mitin Nov 23 '22 at 15:50
  • Or can you hide `ViewOf` inside a type class? (Type classes is an alternative to match types.) And type class can be generated via def macros. I guess this was a usual approach when type macros [disappeared](https://docs.scala-lang.org/overviews/macros/changelog211.html#new-macro-powers) in Scala 2.11 (I mean replacing type macros with macro annotations, which are now off the table, or implicit macros). – Dmytro Mitin Nov 23 '22 at 15:53
  • 1
    https://stackoverflow.com/questions/59473523/how-to-generate-a-class-in-dotty-with-macro – Dmytro Mitin Nov 23 '22 at 15:59
  • 3
    Depends on your use case - https://github.com/VirtusLab/iskra uses Scala 3, `Selectable`, macros and type refinement to allow you to generate types which kinda do what you want. IntelliJ doesn't support it yet, but Scala Metals would let you use type hints with types generated through `dataFrame.types[YourType].map(_.caseClassField)` - but whether it's suitable for your case is hard to tell. Authors gave a talk about it last week in Scala conference in Warsaw but I am not sure when the videos will be available. – Mateusz Kubuszok Nov 23 '22 at 17:57
  • 1
    @MateuszKubuszok Thanks for the link. [OFF-TOPIC] So there are alternative encodings: https://github.com/VirtusLab/iskra vs. https://github.com/vincenzobaz/spark-scala3 – Dmytro Mitin Nov 23 '22 at 18:06
  • 2
    From what I saw there is also quill-spark module in https://github.com/zio/zio-quill so there is even more Spark refinements in Scala 3 :) But I was mostly familiar with Iskra thanks to presentation and this blog post: https://virtuslab.com/blog/reconciling-spark-apis-for-scala/ - since author asks about adding case class methods to their `Selectable` this might be worth investigating. – Mateusz Kubuszok Nov 23 '22 at 18:12
  • @Dmytro Thank you, Great insights and pointers as usual * I'd really like to avoid pre-compilation code generation if at all possible. * ViewOf[M] is meant is to be used by a DSL user, so no way to hide it within a derived type class. * I had the idea to sneak the type through a **transparent inline**, as in https://stackoverflow.com/questions/59473523/how-to-generate-a-class-in-dotty-with-macro. But no hope to use it in a further type definition. Scala3 intends to make things sounder. Creating types via metaprogramming is off-ground. – Jean-Jacques Lecler Nov 24 '22 at 08:57
  • @Mateusz, interesting pointer to https://github.com/VirtusLab/iskra. My initial question was meant to craft the structural types _for_ `Selectable` – Jean-Jacques Lecler Nov 24 '22 at 09:07
  • 1
    @Jean-JacquesLecler In Scala 3.3.0 macro annotations will appear https://stackoverflow.com/questions/67770927/macro-annotations-in-scala-3 https://stackoverflow.com/questions/59473523/how-to-generate-a-class-in-dotty-with-macro https://stackoverflow.com/questions/75159088/how-to-generate-parameterless-constructor-at-compile-time-using-scala-3-macro But new definitions are not visible from outside the macro expansion. And macro annotations can't generate a `type`. – Dmytro Mitin Mar 22 '23 at 07:18

2 Answers2

5

Just in case, here is what I meant by hiding ViewOf inside a type class (type classes is an alternative to match types). Sadly, in Scala 3 this is wordy.

(version 1)

import scala.annotation.experimental
import scala.quoted.{Expr, Quotes, Type, quotes}

// Library part
trait View extends Selectable {
  def applyDynamic(key: String)(args: Any*): Any = {
    println(s"$key is being looked up with $args")
    if (key == "i") 1
    else if (key == "s") "a"
    else ???
  }

  def selectDynamic(key: String): Any = {
    println(s"$key is being looked up")
    if (key == "i1") 2
    else if (key == "s1") "b"
    else ???
  }
}

// type class
trait ViewOf[M <: Product] {
  type Out <: View
}

object ViewOf {
  transparent inline given mkViewOf[M <: Product]: ViewOf[M] = ${givenImpl[M]}

  @experimental // because .newClass is @experimental
  def givenImpl[M <: Product : Type](using Quotes): Expr[ViewOf[M]] = {
    import quotes.reflect.*

    extension (symb: Symbol) {
      def setFlags(flags: Flags): Symbol = {
        given dotty.tools.dotc.core.Contexts.Context =
          quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
        symb.asInstanceOf[dotty.tools.dotc.core.Symbols.Symbol]
          .denot.setFlag(flags.asInstanceOf[dotty.tools.dotc.core.Flags.FlagSet])
        symb
      }
    }

    def newType(cls: Symbol, name: String, tpe: TypeRepr, flags: Flags = Flags.EmptyFlags, privateWithin: Symbol = Symbol.noSymbol): Symbol = {
      given dotty.tools.dotc.core.Contexts.Context =
        quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
      import dotty.tools.dotc.core.Decorators.toTypeName
      dotty.tools.dotc.core.Symbols.newSymbol(
        cls.asInstanceOf[dotty.tools.dotc.core.Symbols.Symbol],
        name.toTypeName,
        flags.asInstanceOf[dotty.tools.dotc.core.Flags.FlagSet],
        tpe.asInstanceOf[dotty.tools.dotc.core.Types.Type],
        privateWithin.asInstanceOf[dotty.tools.dotc.core.Symbols.Symbol]
      ).asInstanceOf[Symbol]
    }

    val M = TypeRepr.of[M]
    val fields = M.typeSymbol.caseFields

    val viewImplDecls = (cls: Symbol) =>
      fields.flatMap(fieldSymb =>
        Seq(
          Symbol.newMethod(cls, fieldSymb.name, MethodType(Nil)(_ => Nil, _ => M.memberType(fieldSymb)), // vararg? MatchError: Inlined
            Flags.Deferred, privateWithin = Symbol.noSymbol),
          Symbol.newVal(cls, fieldSymb.name + "1", M.memberType(fieldSymb),
            Flags.Deferred, privateWithin = Symbol.noSymbol)
        )
      )

    val viewImplParents = List(TypeTree.of[AnyRef], TypeTree.of[View])

    val viewImplCls = Symbol.newClass(Symbol.spliceOwner, "ViewImpl", viewImplParents.map(_.tpe), viewImplDecls, selfType = None)
      .setFlags(Flags.Trait)
    val methodDefs = fields.flatMap(fieldSymb => {
      val methodSymb = viewImplCls.declaredMethod(fieldSymb.name).head
      val valSymb = viewImplCls.fieldMember(fieldSymb.name + "1")
      Seq(
        DefDef(methodSymb, _ => None),
        ValDef(valSymb, None)
      )
    })
    val viewImplClsDef = ClassDef(viewImplCls, viewImplParents, body = methodDefs)

    val viewOfImplDecls = (cls: Symbol) => List(newType(cls, "Out",
      TypeBounds(viewImplCls.typeRef, viewImplCls.typeRef), Flags.Override))

    val viewOfTypeTree = TypeTree.of[ViewOf[M]]
    val viewOfImplParents = List(TypeTree.of[AnyRef], viewOfTypeTree)

    val viewOfImplCls = Symbol.newClass(Symbol.spliceOwner, "ViewOfImpl", viewOfImplParents.map(_.tpe), viewOfImplDecls, selfType = None)
    val outSymb = viewOfImplCls.declaredType("Out").head

    val outTypeDef = TypeDef(outSymb)
    val viewOfImplClsDef = ClassDef(viewOfImplCls, viewOfImplParents, body = List(outTypeDef))
    val newViewOfImpl = Apply(Select(New(TypeIdent(viewOfImplCls)), viewOfImplCls.primaryConstructor), Nil)

    val res = Block(List(viewImplClsDef, viewOfImplClsDef), newViewOfImpl).asExprOf[ViewOf[M]]
    println(res.show + "=" + res.asTerm.show(using Printer.TreeStructure))
    res
  }
}

extension (v: View) {
  def refine[M <: Product](using viewOf: ViewOf[M]): viewOf.Out = v.asInstanceOf[viewOf.Out]
}
// DSL Definition part
case class SomeDefWithInt   ( i : Int    )
case class SomeDefWithString( s : String )

// Mockup usage
class V extends View
val v = V()

println(v.refine[SomeDefWithInt].i())
// i is being looked up with ArraySeq()
// 1
println(v.refine[SomeDefWithString].s())
// s is being looked up with ArraySeq()
// a
println(v.refine[SomeDefWithInt].i1)
// i1 is being looked up
// 2
println(v.refine[SomeDefWithString].s1)
// s1 is being looked up
// b

//scalac: {
//  trait ViewImpl extends java.lang.Object with Macros.View {
//    def i(): scala.Int
//    val i1: scala.Int
//  }
//  class ViewOfImpl extends java.lang.Object with Macros.ViewOf[App.SomeDefWithInt] {
//    type Out // actually, type Out = ViewImpl
//  }
//  new ViewOfImpl()
//}=Block(List(ClassDef("ViewImpl", DefDef("<init>", Nil, Inferred(), None), List(Inferred(), Inferred()), None, List(DefDef("i", List(TermParamClause(Nil)), Inferred(), None), ValDef("i1", Inferred(), None))), ClassDef("ViewOfImpl", DefDef("<init>", Nil, Inferred(), None), List(Inferred(), Inferred()), None, List(TypeDef("Out", TypeBoundsTree(Inferred(), Inferred()))))), Apply(Select(New(Inferred()), "<init>"), Nil))

//scalac: {
//  trait ViewImpl extends java.lang.Object with Macros.View {
//    def s(): scala.Predef.String
//    val s1: scala.Predef.String
//  }
//  class ViewOfImpl extends java.lang.Object with Macros.ViewOf[App.SomeDefWithString] {
//    type Out // actually, type Out = ViewImpl
//  }
//  new ViewOfImpl()
//}=Block(List(ClassDef("ViewImpl", DefDef("<init>", Nil, Inferred(), None), List(Inferred(), Inferred()), None, List(DefDef("s", List(TermParamClause(Nil)), Inferred(), None), ValDef("s1", Inferred(), None))), ClassDef("ViewOfImpl", DefDef("<init>", Nil, Inferred(), None), List(Inferred(), Inferred()), None, List(TypeDef("Out", TypeBoundsTree(Inferred(), Inferred()))))), Apply(Select(New(Inferred()), "<init>"), Nil))

ViewOf[M] is meant is to be used by a DSL user, so no way to hide it within a derived type class.

Not sure I understood.


Method Override with Scala 3 Macros

`tq` equivalent in Scala 3 macros

How to generate a class in Dotty with macro?

How to splice multiple expressions in quote syntax of scala 3 macros?

How to access parameter list of case class in a dotty macro

https://github.com/lampepfl/dotty/discussions/14056


Another implementation of the type class (with a refinement type instead of trait type)

(version 2)

trait ViewOf[M <: Product] {
  type Out <: View
}

object ViewOf {
  transparent inline given mkViewOf[M <: Product]: ViewOf[M] = ${givenImpl[M]}

  @experimental // because .newClass is @experimental
  def givenImpl[M <: Product : Type](using Quotes): Expr[ViewOf[M]] = {
    import quotes.reflect.*

    def makeRefinement(parent: TypeRepr, names: List[String], infos: List[TypeRepr]): TypeRepr =
      names.zip(infos).foldLeft(parent){ case (acc, (name, tpe)) => Refinement(acc, name, tpe) }

    def newType(cls: Symbol, name: String, tpe: TypeRepr, flags: Flags = Flags.EmptyFlags, privateWithin: Symbol = Symbol.noSymbol): Symbol = {
      given dotty.tools.dotc.core.Contexts.Context =
        quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
      import dotty.tools.dotc.core.Decorators.toTypeName
      dotty.tools.dotc.core.Symbols.newSymbol(
        cls.asInstanceOf[dotty.tools.dotc.core.Symbols.Symbol],
        name.toTypeName,
        flags.asInstanceOf[dotty.tools.dotc.core.Flags.FlagSet],
        tpe.asInstanceOf[dotty.tools.dotc.core.Types.Type],
        privateWithin.asInstanceOf[dotty.tools.dotc.core.Symbols.Symbol]
      ).asInstanceOf[Symbol]
    }

    val M = TypeRepr.of[M]
    val fields = M.typeSymbol.caseFields

    val fieldNames = fields.flatMap(fieldSymb => Seq(fieldSymb.name, fieldSymb.name + "1"))
    val fieldMethodTypes = fields.flatMap(fieldSymb => Seq(
      MethodType(List("args"))(_ => List(AnnotatedType(TypeRepr.of[Any], '{new scala.annotation.internal.Repeated()}.asTerm)), _ => M.memberType(fieldSymb)),
      ByNameType(M.memberType(fieldSymb)))
    )
    val refinement = makeRefinement(TypeRepr.of[View], fieldNames, fieldMethodTypes)

    val viewOfImplDecls = (cls: Symbol) => List(newType(cls, "Out",
      TypeBounds(refinement, refinement), Flags.Override))

    val viewOfTypeTree = TypeTree.of[ViewOf[M]]
    val viewOfImplParents = List(TypeTree.of[AnyRef], viewOfTypeTree)

    val viewOfImplCls = Symbol.newClass(Symbol.spliceOwner, "ViewOfImpl", viewOfImplParents.map(_.tpe), viewOfImplDecls, selfType = None)
    val outSymb = viewOfImplCls.declaredType("Out").head

    val outTypeDef = TypeDef(outSymb)
    val viewOfImplClsDef = ClassDef(viewOfImplCls, viewOfImplParents, body = List(outTypeDef))
    val newViewOfImpl = Apply(Select(New(TypeIdent(viewOfImplCls)), viewOfImplCls.primaryConstructor), Nil)

    val res = Block(List(viewOfImplClsDef), newViewOfImpl).asExprOf[ViewOf[M]]
    println(res.show + "=" + res.asTerm.show(using Printer.TreeStructure))
    res
  }
}
println(v.refine[SomeDefWithInt].i(10, "x", true))
//i is being looked up with ArraySeq((10,x,true))
//1
println(v.refine[SomeDefWithString].s(20, "y", 30L))
//s is being looked up with ArraySeq((20,y,30))
//a
println(v.refine[SomeDefWithInt].i1)
//i1 is being looked up
//2
println(v.refine[SomeDefWithString].s1)
//s1 is being looked up
//b

//scalac: {
//  class ViewOfImpl extends java.lang.Object with Macros.ViewOf[App.SomeDefWithInt] {
//    type Out // actually, type Out = View {def i(args: Any*): Int; def i1: Int}
//  }
//  new ViewOfImpl()
//}=Block(List(ClassDef("ViewOfImpl", DefDef("<init>", Nil, Inferred(), None), List(Inferred(), Inferred()), None, List(TypeDef("Out", TypeBoundsTree(Inferred(), Inferred()))))), Apply(Select(New(Inferred()), "<init>"), Nil))

Also we can use Mirror instead of reflection

(version 3)

trait ViewOf[M <: Product] {
  type Out <: View
}

object ViewOf {
  transparent inline given mkViewOf[M <: Product]: ViewOf[M] = ${givenImpl[M]}

  def givenImpl[M <: Product : Type](using Quotes): Expr[ViewOf[M]] = {
    import quotes.reflect.*

    def makeRefinement(parent: TypeRepr, namesAndTypes: List[(String, TypeRepr)]): TypeRepr =
      namesAndTypes.foldLeft(parent) { case (acc, (name, tpe)) => Refinement(acc, name, tpe) }

    def mkNamesAndTypes[mels: Type, mets: Type]: List[(String, TypeRepr)] =
      (Type.of[mels], Type.of[mets]) match {
        case ('[EmptyTuple], '[EmptyTuple]) => Nil
        case ('[mel *: melTail], '[met *: metTail] ) => {
          val name = Type.valueOfConstant[mel].get.toString
          val name1 = name + "1"
            //scala.MatchError: Inlined(Ident(Macros$),List(),Apply(Select(New(Select(Select(Select(Ident(scala),annotation),internal),Repeated)),<init>),List())) (of class dotty.tools.dotc.ast.Trees$Inlined)
          //val methodType = MethodType(List("args"))(_ => List(AnnotatedType(TypeRepr.of[Any], '{new scala.annotation.internal.Repeated()}.asTerm)), _ => TypeRepr.of[met])
          val methodType = MethodType(Nil)(_ => Nil, _ => TypeRepr.of[met])
          val methodType1 = ByNameType(TypeRepr.of[met])
          (name, methodType) :: (name1, methodType1) :: mkNamesAndTypes[melTail, metTail]
        }
      }

    val namesAndTypes = Expr.summon[Mirror.ProductOf[M]].get match {
      case '{ $m: Mirror.ProductOf[M] { type MirroredElemLabels = mels; type MirroredElemTypes = mets } } =>
        mkNamesAndTypes[mels, mets]
    }

    val res = makeRefinement(TypeRepr.of[View], namesAndTypes).asType match {
      case '[tpe] =>
        '{
          new ViewOf[M] {
            type Out = tpe
          }
        }
    }

    println(res.show)
    res
  }
}

Unfortunately, this doesn't work because of an extra type ascription (Expr looses type refinement)

//scalac: {
//  final class $anon() extends Macros.ViewOf[App.SomeDefWithInt] {
//    type Out = Macros.View {
//      def i(): scala.Int
//      def i1: scala.Int
//    }
//  }
//
//  (new $anon(): Macros.ViewOf[App.SomeDefWithInt])  // <--- HERE!!!
//}

https://github.com/lampepfl/dotty/issues/15566 (for structural refinements i.e. defs, their loss seems to be expected behavior, but type refinement loss can be a bug)

So, at least once we have to use low-level newClass to avoid type ascription

(version 4)

trait ViewOf[M <: Product] {
  type Out <: View
}

object ViewOf {
  transparent inline given mkViewOf[M <: Product]: ViewOf[M] = ${givenImpl[M]}

  @experimental // because .newClass is @experimental
  def givenImpl[M <: Product : Type](using Quotes): Expr[ViewOf[M]] = {
    import quotes.reflect.*

    def makeRefinement(parent: TypeRepr, namesAndTypes: List[(String, TypeRepr)]): TypeRepr =
      namesAndTypes.foldLeft(parent) { case (acc, (name, tpe)) => Refinement(acc, name, tpe) }

    def mkNamesAndTypes[mels: Type, mets: Type]: List[(String, TypeRepr)] =
      (Type.of[mels], Type.of[mets]) match {
        case ('[EmptyTuple], '[EmptyTuple]) => Nil
        case ('[mel *: melTail], '[met *: metTail] ) => {
          val name = Type.valueOfConstant[mel].get.toString
          val name1 = name + "1"
          val methodType = MethodType(List("args"))(_ => List(AnnotatedType(TypeRepr.of[Any], '{new scala.annotation.internal.Repeated()}.asTerm)), _ => TypeRepr.of[met])
          val methodType1 = ByNameType(TypeRepr.of[met])
          (name, methodType) :: (name1, methodType1) :: mkNamesAndTypes[melTail, metTail]
        }
      }

    val namesAndTypes = Expr.summon[Mirror.ProductOf[M]].get match {
      case '{ $m: Mirror.ProductOf[M] { type MirroredElemLabels = mels; type MirroredElemTypes = mets } } =>
        mkNamesAndTypes[mels, mets]
    }

    val refinement = makeRefinement(TypeRepr.of[View], namesAndTypes)

    def newType(cls: Symbol, name: String, tpe: TypeRepr, flags: Flags = Flags.EmptyFlags, privateWithin: Symbol = Symbol.noSymbol): Symbol = {
      given dotty.tools.dotc.core.Contexts.Context =
        quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
      import dotty.tools.dotc.core.Decorators.toTypeName
      dotty.tools.dotc.core.Symbols.newSymbol(
        cls.asInstanceOf[dotty.tools.dotc.core.Symbols.Symbol],
        name.toTypeName,
        flags.asInstanceOf[dotty.tools.dotc.core.Flags.FlagSet],
        tpe.asInstanceOf[dotty.tools.dotc.core.Types.Type],
        privateWithin.asInstanceOf[dotty.tools.dotc.core.Symbols.Symbol]
      ).asInstanceOf[Symbol]
    }

    val viewOfImplDecls = (cls: Symbol) => List(newType(cls, "Out",
      TypeBounds(refinement, refinement),
      Flags.Override))

    val viewOfTypeTree = TypeTree.of[ViewOf[M]]
    val viewOfImplParents = List(TypeTree.of[AnyRef], viewOfTypeTree)

    val viewOfImplCls = Symbol.newClass(Symbol.spliceOwner, "ViewOfImpl", viewOfImplParents.map(_.tpe), viewOfImplDecls, selfType = None)
    val outSymb = viewOfImplCls.declaredType("Out").head

    val outTypeDef = TypeDef(outSymb)
    val viewOfImplClsDef = ClassDef(viewOfImplCls, viewOfImplParents, body = List(outTypeDef))
      // this would be an extra type ascription to be avoided
    // val newViewOfImpl = Typed(Apply(Select(New(TypeIdent(viewOfImplCls)), viewOfImplCls.primaryConstructor), Nil), TypeTree.of[ViewOf[M]])
    val newViewOfImpl = Apply(Select(New(TypeIdent(viewOfImplCls)), viewOfImplCls.primaryConstructor), Nil)

    val res = Block(List(viewOfImplClsDef), newViewOfImpl).asExprOf[ViewOf[M]]
    println(res.show + "=" + res.asTerm.show(using Printer.TreeStructure))
    res
  }
}
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • 1
    Thank you @dmytro. Impressive ! I thought it was not possible to retrieve a type from a value (other than a path dependent type). It actually **is**. In `def refine[M <: Product](using viewOf: ViewOf[M]): viewOf.Out = v.asInstanceOf[viewOf.Out]`, the type of `viewOf` is not only `ViewOf[M]`, but its generated refinement. Brilliant. _iskra_ is based on the same construction. Question answered. The additional material you provided for the construction of the refinement is extremely helpful too. – Jean-Jacques Lecler Nov 29 '22 at 09:03
  • @ftucky *"I thought it was not possible to retrieve a type from a value (other than a path dependent type)"* Well, `viewOf.Out` is a path-dependent type. – Dmytro Mitin Nov 29 '22 at 22:21
  • Forgive my ignorance, but where do `dotty.tools.dotc` symbols come from? Is there a dependency I need to add? – chuwy Feb 28 '23 at 14:17
  • 1
    @chuwy Yeah, [scala3-compiler](https://mvnrepository.com/artifact/org.scala-lang/scala3-compiler). And the sources are [here](https://github.com/lampepfl/dotty/tree/main/compiler). – Dmytro Mitin Feb 28 '23 at 15:12
  • https://users.scala-lang.org/t/how-to-refine-type-dynamically-in-scala-3-whitebox-macro/9220 – Dmytro Mitin Apr 06 '23 at 14:55
  • https://github.com/propensive/polyvinyl/ – Dmytro Mitin Apr 07 '23 at 07:53
0

The solution below is actually the (version 3) provided by @dmytro, slightly modified to make it work. It is purely based on Mirror, and construction of a Refinement. No scala3-compiler library, no @experimental. Excellent roll-out of a white-box macro.

package refine
import scala.quoted.{Quotes,Type,Expr}

// Library part
trait View extends Selectable :
  def applyDynamic(key: String)(args: Any*): Any =
    println(s"$key is being looked up with $args")
    key match
      case "i" => 1
      case "s" => "a"
      case _   => ???

  def selectDynamic(key: String): Any =
    println(s"$key is being looked up")
    key match 
      case "i1" => 2 
      case "s1" => "b"
      case _    => ???
end View

// type class
trait ViewOf[M <: Product]:
  type Out <: View

object ViewOf :
  transparent inline given mkViewOf[M <: Product]: ViewOf[M] = ${givenImpl[M]}

  def givenImpl[M <: Product : Type](using quotes:Quotes) : Expr[ViewOf[M]]  =
    import quotes.reflect.*
    import scala.deriving.Mirror

    def makeRefinement(parent: TypeRepr, namesAndTypes: List[(String, TypeRepr)]): TypeRepr =
      namesAndTypes.foldLeft(parent) { case (acc, (name, tpe)) => Refinement(acc, name, tpe) }

    def mkNamesAndTypes[mels: Type, mets: Type]: List[(String, TypeRepr)] =
      (Type.of[mels], Type.of[mets]) match {
        case ('[EmptyTuple], '[EmptyTuple]) => Nil
        case ('[mel *: melTail], '[met *: metTail] ) => {
          val name = Type.valueOfConstant[mel].get.toString
          val name1 = name + "1"
            //scala.MatchError: Inlined(Ident(Macros$),List(),Apply(Select(New(Select(Select(Select(Ident(scala),annotation),internal),Repeated)),<init>),List())) (of class dotty.tools.dotc.ast.Trees$Inlined)
          //val methodType = MethodType(List("args"))(_ => List(AnnotatedType(TypeRepr.of[Any], '{new scala.annotation.internal.Repeated()}.asTerm)), _ => TypeRepr.of[met])
          val methodType = MethodType(Nil)(_ => Nil, _ => TypeRepr.of[met])
          val methodType1 = ByNameType(TypeRepr.of[met])
          (name, methodType) :: (name1, methodType1) :: mkNamesAndTypes[melTail, metTail]
        }
      }

    val namesAndTypes = Expr.summon[Mirror.ProductOf[M]].get match
      case '{ $m: Mirror.ProductOf[M] { type MirroredElemLabels = mels; type MirroredElemTypes = mets } } =>
        mkNamesAndTypes[mels, mets]

    val res = makeRefinement(TypeRepr.of[View], namesAndTypes).asType match
      case '[tpe] => '{
          ( new ViewOf[M]:
              type Out = tpe
          ) : ViewOf[M] { type Out = tpe }
       }
    println(res.show)
    res
  end givenImpl
end ViewOf

extension (v: View) {
  def refine[M <: Product](using viewOf: ViewOf[M]): viewOf.Out = v.asInstanceOf[viewOf.Out]
}

Missing point was: the inferred of an anonymous class is not expected to be refined (except when the class is a Selectable, which is not the case of ViewOf[_]). A manual type-ascription with the correct refinement-type is required.