Skip to content
This repository was archived by the owner on Jun 5, 2023. It is now read-only.
This repository was archived by the owner on Jun 5, 2023. It is now read-only.

Cannot instantiate traits with Scala 3 macros nor inline methods #155

Open
@xerial

Description

@xerial

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.
  • 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 TreeMap doesn't traverse Template class scala/scala3#10931, so this approach also didn't work

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions