dotty-feature-requests icon indicating copy to clipboard operation
dotty-feature-requests copied to clipboard

Cannot instantiate traits with Scala 3 macros nor inline methods

Open xerial opened this issue 4 years ago • 0 comments

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
  • 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), but new 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

xerial avatar Dec 28 '20 15:12 xerial