Support creating a new instance of a given Type[A] with Scala 3 macro #17897
Replies: 0 comments 18 replies
-
You'll need to drop down to the Reflect level ( |
Beta Was this translation helpful? Give feedback.
-
@LPTK I tried rewriting code using TreeMap, but code like |
Beta Was this translation helpful? Give feedback.
-
Inline method also doesn't work because of the same reason: inline def gen[A] = { new A {} } // Compile error: A is not a class type If there is a way to tell the compiler that A is a class, this code generation should be safe. |
Beta Was this translation helpful? Give feedback.
-
Here's what I had in mind, and it works, though I have to admit it took me a while to figure it out: scala> import scala.quoted._
scala> def mkImpl[T: Type](using Quotes): Expr[T] =
| import quotes.reflect._
| val init = TypeRepr.of[T].typeSymbol.declarations.find(_.name=="<init>").get
| val t = Apply(Select(New(TypeTree.of[T]),init),List())
| t.asExprOf[T]
def mkImpl
[T](using evidence$1: quoted.Type[T], x$1: quoted.Quotes): quoted.Expr[T]
scala> inline def mk[T]: T = ${mkImpl[T]}
def mk[T] => T
scala> class C
// defined class C
scala> mk[C]
val res0: C = C@4414691f
It's of course not safe if the class' constructor takes parameters: scala> class Oops(x: Int)
// defined class Oops
scala> mk[Oops]
1 |mk[Oops]
|^^^^^^^^
|wrong number of arguments at <no phase> for (x: Int): Oops: (Oops#<init> : (x: Int): Oops), expected: 1, found: 0
| This location contains code that was inlined from rs$line$6:1
1 |mk[Oops]
|^^^^^^^^
|missing argument for parameter x of constructor Oops: (x: Int): Oops
| This location contains code that was inlined from rs$line$6:1 |
Beta Was this translation helpful? Give feedback.
-
@LPTK A problem is if {
final class $anon() extends C
(new $anon(): C)
} But dotty's Type.underlyingClassRef cannot resolve this anonymous class, and shows For mk[C], an error |
Beta Was this translation helpful? Give feedback.
-
Oh, OK. In that case you need to build a tree defining an anonymous class and instantiating it. It should be possible in theory, but possibly quite painful to figure out. |
Beta Was this translation helpful? Give feedback.
-
In the compiler we have a convenience method for that, but it's not exposed in tasty-reflect as far as I can see: https://github.com/lampepfl/dotty/blob/51407b86225f3142c7b0cbafd67cfaf19b5c3672/compiler/src/dotty/tools/dotc/ast/tpd.scala#L330-L340 |
Beta Was this translation helpful? Give feedback.
-
@smarter good to know! There are actually a lot of things which are not exposed in the Reflect API, but which would be very helpful. For instance, I did not find a way to get a constructor of the class other than by going through OTOH, problems like the one we have here would be definitively solved in a very nice way if untyped |
Beta Was this translation helpful? Give feedback.
-
Created a ticket because this might be a corner case bug of the type checker: https://github.com/lampepfl/dotty/issues/10942 |
Beta Was this translation helpful? Give feedback.
-
@xerial to be clear, the code you propose: def newInstanceImpl[A](using quotes:Quotes, t:Type[A]): Expr[A] = {
'{
new A {} // Compile error: A is not a class type
}
} can never work, and that's by design, since the new quotes are statically typed. Any solution will have to go through dynamic |
Beta Was this translation helpful? Give feedback.
-
@LPTK Term manipulation is fine as long as AnonClass and Template nodes are exposed to Reflect API :) |
Beta Was this translation helpful? Give feedback.
-
Then you should update lampepfl/dotty#10942 to reflect that, because the error raised by the code you currently show there is completely legitimate, and detracts from the point you're trying to make. |
Beta Was this translation helpful? Give feedback.
-
And also, if there is a way to tell the compiler that A is a trait or class, static type checking should be able to be supported like this: def newInstanceImpl[A: TraitOrClassType](using quotes:Quotes): Expr[A] = {
'{
new A { ... }
}
} |
Beta Was this translation helpful? Give feedback.
-
It's more complicated than that, even if |
Beta Was this translation helpful? Give feedback.
-
oops. While I'm rewriting the ticket, it was transferred to #155 and I've lost my update... |
Beta Was this translation helpful? Give feedback.
-
Any update on this issue ? |
Beta Was this translation helpful? Give feedback.
-
@smarter pointed out, we basically need to generate AnonClass from a given type parameter (e.g., trait type A) https://github.com/lampepfl/dotty/blob/51407b86225f3142c7b0cbafd67cfaf19b5c3672/compiler/src/dotty/tools/dotc/ast/tpd.scala#L330-L340 inside Scala macros. Another approach would be adding support for rewriting Template nodes so that we can rewrite an expression like |
Beta Was this translation helpful? Give feedback.
-
From this below which is elegant, simple and was implemented in Scala 2 given the comments above:
Reading this thread me feel like scala 3 macros have the dreaded "2nd system disease". Say I have a class name as a string "Double". How in scala 3 macros can I instantiate like this:
So far I tried:
But I get an error. It shall be that simple... otherwise easier to just bypass macros. But then what's their usefulness really if you cannot even do something as basic as this, that C++ could do decades ago. |
Beta Was this translation helpful? Give feedback.
-
I'd like to generate a code for creating a new instance of a given type A. In Scala 3, however, it seems there is no easy way to tell a given Type[A] can be used with
new
:Is there any workaround for this?
In Scala 2 macros, there was no type check, so we can write such a macro like this:
This functionality is essential for migrating Airframe DI (https://wvlet.org/airframe/docs/airframe) to Scala 3. Here is an example of the usage: https://github.com/wvlet/airframe/blob/master/airframe-di-macros/src/main/scala-2/wvlet/airframe/AirframeMacros.scala#L82
Beta Was this translation helpful? Give feedback.
All reactions