-
Notifications
You must be signed in to change notification settings - Fork 1.1k
How to construct a structural type via metaprogramming? #14056
-
If I have class Foo[T] extends Selectable
, how do I construct the type
Foo[T] { val bar : Int val baz : String }
?
And this is just an example. Obviously I want to be able to control how many fields, their names and types.
It seems that the quotes API does not provide a way to construct a RefinedType
.
Beta Was this translation helpful? Give feedback.
All reactions
You need to use the Refined
type wich does have a Refined.apply
factory method.
Here is a prototype macro that converts a tuple ("name1" -> T1, "name2" -> T2, ...)
into Record { val name1: T1; val name2: T2; ... }
:
Replies: 2 comments 12 replies
-
You need to use the Refined
type wich does have a Refined.apply
factory method.
Here is a prototype macro that converts a tuple ("name1" -> T1, "name2" -> T2, ...)
into Record { val name1: T1; val name2: T2; ... }
:
Beta Was this translation helpful? Give feedback.
All reactions
-
@AugustNagro I hope this example helps https://gist.github.com/prolativ/0fca0f4c1a36abca291837007ff80dd0
makeSel
will create an instance of
Sel { val field: String def parameterless: String def emptyParamList(): String def singleParam(arg1: Int): String def curried(arg1: Int)(arg2: Boolean): String }
Beta Was this translation helpful? Give feedback.
All reactions
-
@prolativ Thanks, that works great and I was able to figure out the same last night.
One problem is with creating a refinement methodType that returns this.type. For example, something like
Sel { def thisMethod(arg1: Int): this.type }
In my example github repo I am doing something like
val refinement = RecursiveType: parent => val mt = MethodType(List("arg1"))( _ => List(TypeRepr.of[Int]), _ => parent.recThis ) Refinement(getterRefinement, setter, mt)
However, as Main.scala shows, scalac stack overflows when compiling an invalid method (like mySql.wrongName(22)
). Would this be a dotty bug or is there a better way to set this.type
?
Beta Was this translation helpful? Give feedback.
All reactions
-
Unfortunately it looks like what you're trying to do is kind of illegal even without a macro. This snippet
trait Sel extends Selectable val sel = (new Sel {}).asInstanceOf[{def thisMethod(arg1: Int): this.type}]
causes a warning:
-- Warning: -------------------------------------------------------------------- 2 |val sel = (new Sel {}).asInstanceOf[{def thisMethod(arg1: Int): this.type}] | ^^^^ | self reference in refinement is deprecated
Beta Was this translation helpful? Give feedback.
All reactions
-
Beta Was this translation helpful? Give feedback.
All reactions
-
The macro supports the pattern, but it also warns about the deprecation of this feature.
The example in #17409 shows
8 | def makeThing: { def me: this.type } = ??? | ^^^^ | self reference in refinement is deprecated
Beta Was this translation helpful? Give feedback.
All reactions
-
Is it possible to create a Refinement method that has an implicit parameter? Ala
Foo[T] { def bar(i: Int)(using MyImplict): String }
Beta Was this translation helpful? Give feedback.
All reactions
-
This compiles
trait MyImplict trait Sel extends Selectable val sel = (new Sel {}).asInstanceOf[Sel { def bar(i: Int)(using MyImplict): String }]
so I assume it is.
The only limitations that I can think of right now is that refinement methods cannot:
- have a type parameter
- be extension methods
Beta Was this translation helpful? Give feedback.
All reactions
-
From a Macro I mean :)
From your (awesome) gist I see we can generate curried methods:
val tpe5 = Refinement(tpe4, "curried", MethodType(List("arg1"))(_ => List(TypeRepr.of[Int]), _ => MethodType(List("arg2"))(_ => List(TypeRepr.of[Boolean]), _ => TypeRepr.of[String])))
But I don't see a way to make an argument implicit.
Beta Was this translation helpful? Give feedback.
All reactions
-
Hmm, interesting... It looks like the compiler internally uses ContextualMethodType
rather than just MethodType
to express a method with (using ...)
parameters clause but currently there seems no way to create contextual methods from the quotes' reflect API. @nicolasstucki are there any plans to add this functionality in the nearest future?
Beta Was this translation helpful? Give feedback.
All reactions
-
Here's a workaround. Lets say we have
case class MyImplicit[A](x: A) class Foo extends Selectable: def applyDynamic(name: String)(a: Int, b: Int, c: MyImplicit[Int]): Int = a + b + c.x
And want to create this refinement:
Foo { def bar(a: Int, b: Int)(using MyImplicit[Int]): Int }
Such that the following runs:
@main def fooMain(): Unit = // Foo { def bar(a: Int, b: Int)(using MyImplicit[Int]): Int val myFoo = fooMacro[Int] given MyImplicit[Int] = MyImplicit(3) println(myFoo.bar(1, 2)) // 6
We can do it via the following macro. The idea is that we write a dummy Expr with the desired using clause by hand. Then we extract the ContextualMethodType, and apply it to our own refinement.
trait Dummy[A] { def dummy(using myImplicit: MyImplicit[A]): Unit } transparent inline def fooMacro[A] = ${ fooMacroImpl[A] } private def fooMacroImpl[A: Type](using q: Quotes): Expr[Any] = import q.reflect.* val dummyExpr = '{ class D extends Dummy[A]: def dummy(using myImplicit: MyImplicit[A]): A = ??? } val Inlined(_, _, Block(List(ClassDef(_, _, _, _, List(defdef))), _)) = dummyExpr.asTerm: @unchecked val methodType = defdef .asInstanceOf[DefDef] .symbol .typeRef .translucentSuperType .asInstanceOf[MethodType] println(methodType) val refinement = Refinement( TypeRepr.of[Foo], "bar", MethodType(List("a", "b"))( _ => List(TypeRepr.of[Int], TypeRepr.of[Int]), _ => methodType ) ) refinement.asType match case '[tpe] => '{ new Foo().asInstanceOf[tpe] } end fooMacroImpl
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
We do not have the ability to create ContextMethodType
s. We should be able to make them. I opened #18477 to track this issue.
Beta Was this translation helpful? Give feedback.