dotty-feature-requests
dotty-feature-requests copied to clipboard
Cannot instantiate traits with Scala 3 macros nor inline methods
Use Case
Creating an instance of a given Type[A]
enriched with helper traits.
This functionality is necessary for porting Airframe DI, a dependency injection library (https://wvlet.org/airframe/docs/airframe), to Scala 3. Scala 2 has an untyped quasiquotes, so we can generate new A with ... { ... }
. Scala 3 macros has strict type checking, so if A
might not be able to instantiate, it reject any code generation for new A { ... }
even when A
is just a trait.
Minimized code
scala> import scala.quoted._
scala> trait LogSupport
// defined trait LogSupport
scala> inline def mk[A] = { new A with LogSupport {} }
1 |inline def mk[A] = { new A with LogSupport {} }
| ^
| A is not a class type
Using Scala 3 macro show the same error:
def newInstanceImpl[A](using quotes:Quotes, t:Type[A]): Expr[A] = {
'{
new A {} // Compile error: A is not a class type
}
}
Output
The compiler internally generates an anonymous class if A is a trait:
val e = '{
trait A; new A {}
}
pritnln(e.show)
{
final class $anon() extends A
(new $anon(): A)
}
But if type A is given as Type[A]
to Scala 3 macros or inline methods, even though Type[A] is referencing a trait, we cannot generate code equivalent to new A {}
.
I've added a code to dotty to show the stack trace around this compile error. It seems Type.underlyingClassRef called at checkClassType failed to resolve the anonymous class generated by Dotty (3.0.0-M3):
[error] |A is not a class type:
[error] |java.lang.Throwable
[error] | at dotty.tools.dotc.typer.Checking.checkClassType(Checking.scala:762)
[error] | at dotty.tools.dotc.typer.Checking.checkClassType$(Checking.scala:653)
[error] | at dotty.tools.dotc.typer.Typer.checkClassType(Typer.scala:101)
[error] | at dotty.tools.dotc.typer.Namer$ClassCompleter.checkedParentType$2(Namer.scala:1180)
[error] | at dotty.tools.dotc.typer.Namer$ClassCompleter.$anonfun$1(Namer.scala:1212)
[error] | at scala.collection.immutable.List.map(List.scala:246)
[error] | at dotty.tools.dotc.typer.Namer$ClassCompleter.completeInCreationContext(Namer.scala:1212)
[error] | at dotty.tools.dotc.typer.Namer$Completer.complete(Namer.scala:732)
[error] | at dotty.tools.dotc.core.SymDenotations$SymDenotation.completeFrom(SymDenotations.scala:166)
[error] | at dotty.tools.dotc.core.Denotations$Denotation.completeInfo$1(Denotations.scala:188)
[error] | at dotty.tools.dotc.core.Denotations$Denotation.info(Denotations.scala:190)
[error] | at dotty.tools.dotc.core.SymDenotations$SymDenotation.ensureCompleted(SymDenotations.scala:370)
[error] | at dotty.tools.dotc.typer.Typer.retrieveSym(Typer.scala:2473)
[error] | at dotty.tools.dotc.typer.Typer.typedNamed$1(Typer.scala:2498)
[error] | at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2592)
[error] | at dotty.tools.dotc.typer.Typer.typed(Typer.scala:2662)
[error] | at dotty.tools.dotc.typer.Typer.typed(Typer.scala:2666)
[error] | at dotty.tools.dotc.typer.Typer.traverse$1(Typer.scala:2688)
[error] | at dotty.tools.dotc.typer.Typer.typedStats(Typer.scala:2738)
[error] | at dotty.tools.dotc.typer.Typer.typedBlockStats(Typer.scala:937)
Expectation
- There should be a way to instantiate a Trait with inline methods and Scala 3 macros.
- If A is not a trait, this compilation error is valid. We need a workaround if A is safe to instantiate when an anonymous class that extends A will be generated.
- Scala 2 has untyped quasiquote (
q"""new ${t} {}""")
, so we can generate such a code at ease https://github.com/lampepfl/dotty-feature-requests/issues/153
- Scala 2 has untyped quasiquote (
- As a workaround, I've also tried to rewriting the AST using TreeMap by creating a concrete trait (e.g.,
trait X; new X{}
, then rewrite X to A), butnew X {}
code will generate Template node, which is not yet exposed to Reflect API https://github.com/lampepfl/dotty/issues/10931, so this approach also didn't work