zlaja
zlaja

Reputation: 1363

Get information of type in Scala3 macro

I'm struggling to get information of type in Scala3 macro implementation. I'll explain problem through code.

Here is application logic:

object BlockServiceImpl extends BlockService:
  def authenticateUser0() = new ServiceCall[AuthUser,AuthUserResponse]:
     def invoke(request: AuthUser): Future[AuthUserResponse] = 
       println("BlockServiceImpl authenticateUser0 called")
       Future.successful(AuthUserResponse("test"))   

Now, for the logic I want to make endpoints with help of macro.

defineRoute("POST","/v1/block",BlockServiceImpl.authenticateUser0)

This is inline method:

inline def defineRoute[Q: RootJsonFormat ,R: RootJsonFormat](method: String, uri: String,inline call: () =>  ServiceCall[Q,R]): AkkaHttpCall = ${ methodImpl[Q,R]('uri, 'call)}

And this is implementation of macro:

def methodImpl[Q: Type,R: Type](uri: Expr[String],expr: Expr[Function0[ServiceCall[Q,R]]])(using ctx: Quotes): Expr[AkkaHttpCall] = ...

How can I get information that Q is AuthUser type during macro expansion in a compile time?

Upvotes: 1

Views: 353

Answers (3)

Randomness Slayer
Randomness Slayer

Reputation: 724

Bounding (See: Scala 3 Book: Context Bounds) the type of a parameter in a function can be achieved in several ways.


THIS IS WRONG (TY Dmytro!): When using generic parameters like so: [T : Type] we are aliasing a type

CORRECTION: When using generic parameters like so: [T : R] we are using syntactic sugar which represents an implicit parameter of type R[T]


For many applications, including yours, it can be beneficial to restrict the type of our generic parameter.


There are two main bounds, an "upper" and a "lower" bound.

The "upper" bound e.g. [T <: U] specifies that T must be of type U, or a subclass of U

The "lower" bound e.g. [T >: U] specifies that T must be of type U, or a super-class of U

It is possible to restrict both bounds, by first specifying the lower bound then the upper bound, e.g. [T >: Cat <: Animal]

Upvotes: 1

zlaja
zlaja

Reputation: 1363

I've solved it by putting information if Q and R were special cases (NotUsed, Done,..) in serializers for Q and R. The idea is took from the Lagom framework.

Upvotes: 0

gianluca aguzzi
gianluca aguzzi

Reputation: 1724

A possible solution could be to use pattern matching on quoted expressions.

So for example, you can define a method that is used to retrieve the compile-time type:

def tag[A <: AnyKind] = throw new IllegalStateException("use it only to pattern match types")

And then, in the macro expansion, you can perform pattern match as:

'{ tag[Q] } match {
      case '{ tag[AuthUser] } => // here I am sure that Q is AuthUser, since Q is matched with AuthUser
}

It is quite a trick (and is not very extensible as you have to add each type) so take everything I say with a grain of salt... I think that exists a clearer solution depends on your particular application logic :)

Upvotes: 2

Related Questions