Scala 3 reflection: Sequence argument type annotation `*` cannot be used here:

I am trying to define a Selectable “dynamically” using the AST. My first attempt resulted in an error regarding overriding methods. I found out that the method names were incorrect. I have since deleted that post because now I have another error.

My current test is as follows:

package examples.instantiate

package test

import scala.annotation.experimental

@experimental
@main
def InstantiateYVals: Unit =
  val myCommand = MyMacroYVals.client[Int](1)
  val x = myCommand.field
  val y = myCommand.selectDynamic("field")
  val z = myCommand.applyDynamic("field")()

  println("Vals 2")

A get the same error on all three calls:

[error] -- Error: /home/hmf/VSCodeProjects/sploty/meta/src/examples/instantiate/InstantiateYVals.scala:34:25 
[error] 34 |  val x = myCommand.field
[error]    |                         ^
[error]    |Sequence argument type annotation `*` cannot be used here:
[error]    |it is not the only argument to be passed to the corresponding repeated parameter Any*
[error] -- Error: /home/hmf/VSCodeProjects/sploty/meta/src/examples/instantiate/InstantiateYVals.scala:35:34 
[error] 35 |  val y = myCommand.selectDynamic("field")
[error]    |                                  ^^^^^^^
[error]    |Sequence argument type annotation `*` cannot be used here:
[error]    |it is not the only argument to be passed to the corresponding repeated parameter Any*
[error] -- Error: /home/hmf/VSCodeProjects/sploty/meta/src/examples/instantiate/InstantiateYVals.scala:36:41 
[error] 36 |  val z = myCommand.applyDynamic("field")()
[error]    |                                         ^
[error]    |Sequence argument type annotation `*` cannot be used here:
[error]    |it is not the only argument to be passed to the corresponding repeated parameter Any*
[error] three errors found

The types and code seem to be correct. Here is the output of the macro function:

clsDefY
class Struct extends java.lang.Object with scala.Selectable {
  val field: scala.Int = 1
  final def selectDynamic(name: scala.Predef.String): scala.Any = {
    scala.Predef.println(_root_.scala.StringContext.apply("Calling selectDynamic(?)").s())
    -102
  }
  def applyDynamic(`name₂`: scala.Predef.String)(args: scala.collection.immutable.Seq[scala.Any] @scala.annotation.internal.Repeated): scala.Any = {
    scala.Predef.println(_root_.scala.StringContext.apply("Calling applyDynamic(?)").s())
    -103
  }
}
newClsY
(new Struct(): Struct)
[Y] ---> 
Struct
resultY
{
  class Struct extends java.lang.Object with scala.Selectable {
    val field: scala.Int = 1
    final def selectDynamic(name: scala.Predef.String): scala.Any = {
      scala.Predef.println(_root_.scala.StringContext.apply("Calling selectDynamic(?)").s())
      -102
    }
    def applyDynamic(`name₂`: scala.Predef.String)(args: scala.collection.immutable.Seq[scala.Any] @scala.annotation.internal.Repeated): scala.Any = {
      scala.Predef.println(_root_.scala.StringContext.apply("Calling applyDynamic(?)").s())
      -103
    }
  }

  (new Struct(): Struct)
}

Below I provide the code I used for the test. Can anyone see the error I am making? Basically I use SelectableYBase to extract the method type and symbol to construct the new class. I may be messing up here.

TIA

package examples.instantiate

import scala.annotation.experimental
import scala.quoted.*

@experimental
object MyMacroYVals:

  trait SelectableYBase extends scala.Selectable:
    final def selectDynamic(name: String): Any = 
      println(s"selectDynamic($name)")
      100
    def applyDynamic(name: String)(args: Any*): Any =
      println(s"selectDynamic($name)(${args.mkString(",")})")
      100

    
  transparent inline def client[I](r: I): Any = ${MyMacroYVals.clientImpl[I]('r)}

  def clientImpl[I: Type](r: Expr[I])(using Quotes): Expr[Any] = 
    import quotes.reflect.*

    val selectable = TypeTree.of[scala.Selectable]

    // selectDynamic
    val apiTypeApply = TypeRepr.of[SelectableYBase]
    assert(apiTypeApply.dealias.typeSymbol.isClassDef && apiTypeApply.dealias.classSymbol.isDefined)
    val clssSym = apiTypeApply.dealias.classSymbol.get

    val selectName = "selectDynamic"
    val selectMethods = clssSym.declaredMethod(selectName)
    assert(selectMethods.nonEmpty)
    val selectMethod = selectMethods.head
    val selectMethodInfo = selectMethod.info
    val selectMethodType = selectMethodInfo match
      case r:MethodType => 
        val rtt = Inferred(r)
        r
      case _ => report.errorAndAbort("Expected a method type")
    
    def selectDynamicY(parent: Symbol) = 
      Symbol.newMethod(parent, 
        selectName, 
        selectMethodType
      )


    // applyDynamic
    val applyName = "applyDynamic"
    val applyMethods = clssSym.declaredMethod(applyName)
    assert(applyMethods.nonEmpty)
    val applyMethod = applyMethods.head
    val applyMethodInfo = applyMethod.info
    val applyMethodType = applyMethodInfo match
      case r:MethodType => 
        val rtt = Inferred(r)
        r
      case _ => report.errorAndAbort("Expected a method type")
    
    def applyDynamicY(parent: Symbol) = 
      Symbol.newMethod(parent, 
        applyName, 
        applyMethodType
      )

    // First parent must be a class
    val parentsY = List(TypeTree.of[Object], selectable)
    def declsX(cls: Symbol): List[Symbol] =
      List(
        Symbol.newVal(cls, "field", TypeRepr.of[I], Flags.Inline, Symbol.noSymbol ),
        selectDynamicY(cls),
        applyDynamicY(cls)
        )
    val clsX = Symbol.newClass(Symbol.spliceOwner, "Struct", parents = parentsY.map(_.tpe), declsX, selfType = None)

    // Defining methods
    val selectDef = DefDef(selectMethod, 
      argss => 
        val nameTree = argss(0)(0)
        val name = nameTree.asExprOf[String]
        val code = '{
            // val n = $name
            // println(s"Calling selectDynamic($n)")
            println(s"Calling selectDynamic(?)")
            -102
            }
        Some(code.asTerm)
        )

    val applyDef = DefDef(applyMethod, 
      argss => 
        val nameTree = argss(0)(0)
        val name = nameTree.asExprOf[String]
        val code = '{
            // val n = $name
            // println(s"Calling applyDynamic($n)")
            println(s"Calling applyDynamic(?)")
            -103
            }
        Some(code.asTerm)
      )

    // Defining value members
    val fieldSymbol = clsX.declaredField("field")
    val fieldValue = r.asTerm
    val fieldDef2: ValDef = ValDef.apply(fieldSymbol, Some(fieldValue))

    // adding methods and value
    val clsDefY = ClassDef(clsX, parentsY, body = List(fieldDef2, selectDef, applyDef))
    println("clsDefY")
    println(clsDefY.show)

    val xx = clsX.info.typeSymbol.typeRef
    val yy = TypeTree.ref(clsX.info.typeSymbol)
    val newClsX = Typed(Apply(Select(New(TypeIdent(clsX)), clsX.primaryConstructor), Nil), yy)
    println("newClsY")
    println(newClsX.show)

    val typ1 = clsX.info
    val tt = yy.tpe
    val ttt = yy.tpe.asType
    val blk = ttt match
          case '[t] => 
            println("[Y] ---> ")
            println(Type.show[t])
            Block(List(clsDefY), newClsX).asExprOf[t]


    val resultY = blk
    println("resultY")
    println(resultY.show)
    resultY


end MyMacroYVals

My macro experience is from F#, not Scala, so this is grug advice…

The error refers to a varadic argument being passed alongside some other parameter.

This conflict is implied when the macro defines the class extending SelectableYBase - that has a varadic parameter in applyDynamic that is incorrectly overridden with a method whose second parameter is a plain sequence. There is an annotation repeated on that second parameter, but is that sufficient?

If I were you I would strip everything out of the macro for the time being except for the applyDynamic support. I would probably suppress the method generation too and then add it back in to see at what point things break.

EDIT: I would use try defining applyDynamic with a plain sequence argument too just to confirm that the overrides correctly reproduce the method’s type signature.

1 Like

Thanks for the suggestions. Note that the trait Selectable requires that I implement these two methods. I confirmed this by using each one at a time.

The last suggestion seemed interesting, so I tried that and got:


Struct
resultY
{
  class Struct extends java.lang.Object with scala.Selectable {
    val field: scala.Int = 1
    final def applyDynamic(name: scala.Predef.String)(args: scala.collection.immutable.Seq[scala.Any]): scala.Any = {
      scala.Predef.println(_root_.scala.StringContext.apply("Calling applyDynamic(?)").s())
      -103
    }
    final def selectDynamic(`name₂`: scala.Predef.String): scala.Any = {
      scala.Predef.println(_root_.scala.StringContext.apply("Calling selectDynamic(?)").s())
      -102
    }
  }

  (new Struct(): Struct)
}
[error] -- [E007] Type Mismatch Error: /home/hmf/VSCodeProjects/sploty/meta/src/examples/instantiate/InstantiateYVals.scala:35:33 
[error] 35 |  val y = myCommand.selectDynamic("field")
[error]    |                                 ^^^^^^^^
[error]    |            Found:    (String, scala.collection.immutable.WrappedString)
[error]    |            Required: Seq[Any]
[error]    |----------------------------------------------------------------------------
[error]    | Explanation (enabled by `-explain`)
[error]    |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[error]    |
[error]    | Tree: Tuple2.apply[String, scala.collection.immutable.WrappedString]("selectDynamic",
[error]    |   wrapString("field"))
[error]    | I tried to show that
[error]    |   (String, scala.collection.immutable.WrappedString)
[error]    | conforms to
[error]    |   Seq[Any]
[error]    | but none of the attempts shown below succeeded:
[error]    |
[error]    |   ==> (String, scala.collection.immutable.WrappedString)  <:  Seq[Any]  = false
[error]    |
[error]    | The tests were made under the empty constraint
[error]     ----------------------------------------------------------------------------
[error] one error found
1 targets failed

The Selectable trait is a marker so that compilation at the call site are changed. So a:

val x = myCommand.field

should be replaced with

val x = myCommand.selectDynamic("field")

And an explicit call to that should remain unchanged. The error above seems to show that this is not so. The interaction between the compiler on the marker trait and the use of macros don’t seem to play well.

So this leaves me with the following questions:

  • Should the combination of these two techniques be possible?
  • If not, how can I get the selectDynamic to work using some other strategy?

Any ideas?

TIA

Yes, understood, of course - but to debug your problem I think you should simplify it temporarily. So just one method at a time, until you can see where it’s gone wrong. You can add all the fancy bits back in when you see the root cause of the error.

If needs be, consider removing the inheritance of Selectable too. Add another method to SelectableYBase so that you can call it explicitly in your test code.

I don’t know what code you wrote that provoked your last error, but did you see that error when doing a simple field access, or an explicit call to selectDynamic? If so, I’d hazard a guess that you still have the definition of applyDynamic hanging around in your macro instantiation, and that is being inlined into the call site for each of the calls, be they field access, method access or explcit calls.

If you’ve already done all of that minimisation I suggested and we’re talking at cross-purposes, then I’m afraid someone else better versed with the macro side of things will have to step in, you’re at level one support with me. :grinning:

@sageserpent-open

Went a step further. Tested without macros. The error I get seems to be related to structurally typed Selectables. Reported here:

Thanks for the help.

1 Like