For a macro implementation I'm stacking types and field names with this boilerplate code:
trait Init
trait A_ {
type Push[T]
val int: Push[Int] = ???
val str: Push[String] = ???
}
object A extends A_ {
type Push[T] = Init with B[Init with T]
}
trait B_ {
type Push[T]
val int: Push[Int] = ???
val str: Push[String] = ???
}
trait B[Stack] extends B_ {
type Push[T] = C[Stack with T]
}
trait C[Stack] // etc..
val x: Init with B[Init with Int] = A.int
val y: C[Init with Int with String] = A.int.str
val z: C[Init with String with Int] = A.str.int
// etc..
This works fine. The user can write A.int.str
or A.str.int
and the macro can easily extract the involved types and fields.
Since many more types and variations are involved, I'd like to replace A_
and B_
with a single base class if possible to avoid the redundancy. So I tried this:
trait AB {
type Push[T]
val int: Push[Int] = ???
val str: Push[String] = ???
}
object A extends AB {
type Push[T] = Init with B[Init with T]
}
trait B[Stack] extends AB {
type Push[T] = C[Stack with T]
}
trait C[Stack] // etc..
val x: Init with B[Init with Int] = A.int
val y: Init with B[Init with String] = A.int.str
But as you can see, y
lost the Int
type on the way.
When calling str
, the Push
type used is still the one defined in A
. I had hoped for the returned Init with B[Init with T]
to return a new instance of B[Stack] extends AB
using its Push
type leading to C
and so on.
A self reference attempt fails too:
trait AB { self: {type Push[T]} =>
val int: Push[Int] = ???
val str: Push[String] = ???
}
object A extends AB {
type Push[T] = Init with B[Init with T]
}
trait B[Stack] extends AB {
type Push[T] = C[Stack with T]
}
trait C[Stack] // etc..
val x: A.Push[Int] = A.int
val y: A.Push[String] = A.int.str
I'm obviously missing something and another self reference implementation doesn't work either:
trait Pusher {
type Push[T] = _
}
trait AB { self: Pusher =>
val int: Push[Int] = ???
val str: Push[String] = ???
}
object A extends AB with Pusher {
override type Push[T] = Init with B[Init with T]
}
trait B[Stack] extends AB with Pusher {
override type Push[T] = C[Stack with T]
}
trait C[Stack] // etc..
val x: A.Push[Int] = A.int
val y: A.Push[String] = A.int.str
Is there a way to have a single implementation of int
/str
and get val y: C[Init with Int with String] = A.int.str
(or something similar) that I can use to extract the types in the macro implementation?
UPDATE:
@AlinGabrielArhip is right in his comment that the type information is actually carried on and compiles even though the types of the second approaches with a single base class are not inferred correctly which show with red lines in IntelliJ.
To give more context to my question, here are the two approaches that you can test in the REPL:
Start scala with `scala -Yrepl-class-based:false´
Paste and run the following 4 sections of code one at a time:
object SetupA {
import scala.reflect.macros.blackbox
import scala.language.experimental.macros
trait Init
trait Name {
val name: String = ""
}
trait Age {
val age: Int = 0
}
trait Person0_base {
type Push[T]
val name: Push[Name] = ???
val age : Push[Age] = ???
}
object PersonA extends Person0_base {
type Push[T] = Init with Person1[Init with T]
}
trait Person1_base {
type Push[T]
val name: Push[Name] = ???
val age : Push[Age] = ???
}
trait Person1[Stack] extends Person1_base {
type Push[T] = Person2[Stack with T]
}
trait Person2[Stack]
def impl[T](c: blackbox.Context)(person: c.Tree): c.Tree = {
import c.universe._
q"""
new Init with Name with Age {
// Data fetched from db..
override val name = "Ben"
override val age = 42
}
"""
}
def macroA[T](person: Person2[T]): T = macro impl[T]
}
import SetupA._
// Correctly inferred as `Init with Name with Age`
val personA = macroA(PersonA.name.age)
personA.name == "Ben"
personA.age == 42
object SetupB {
import scala.reflect.macros.blackbox
import scala.language.experimental.macros
trait Init
trait Name {
val name: String = ""
}
trait Age {
val age: Int = 0
}
trait Person_base {
type Push[T]
val name: Push[Name] = ???
val age : Push[Age] = ???
}
object PersonB extends Person_base {
type Push[T] = Init with Person1[Init with T]
}
trait Person1[Stack] extends Person_base {
type Push[T] = Person2[Stack with T]
}
trait Person2[Stack] {
val get: Stack = ???
}
def impl[T](c: blackbox.Context)(person: c.Tree): c.Tree = {
import c.universe._
q"""
new Init with Name with Age {
// Data fetched from db..
override val name = "Ben"
override val age = 42
}
"""
}
def macroB[T](person: Person2[T]): T = macro impl[T]
}
import SetupB._
// Incorrectly inferred as `PersonB.Push[Age]` - but still compiles!
val personB = macroB(PersonB.name.age)
personB.name == "Ben"
personB.age == 42
Is there a workaround to get correct type inference in this case?
CodePudding user response:
Had made this more complicated than necessary. By simply supplying Stack
and Push
as type parameters to the base class, type inference works as expected:
object Test {
import scala.reflect.macros.blackbox
import scala.language.experimental.macros
trait Init
trait Name {
val name: String = ""
}
trait Age {
val age: Int = 0
}
trait PersonBase[Stack, Push[_]] {
val name: Push[Stack with Name] = ???
val age : Push[Stack with Age] = ???
}
object Person extends PersonBase[Init, Person1]
trait Person1[Stack] extends PersonBase[Stack, Person2]
trait Person2[Stack] extends PersonBase[Stack, Person3]
trait Person3[Stack]
def impl[T](c: blackbox.Context)(person: c.Tree): c.Tree = {
import c.universe._
q"""
new Init with Name with Age {
// Data fetched from db...
override val name = "Ben"
override val age = 42
}
"""
}
def m[T](person: Person2[T]): T = macro impl[T]
}
import Test._
val person = m(Person.name.age)
person.name == "Ben"
person.age == 42