Dotty: tasty-reflect: Inspecting type tree fails when a macro is called inside a block or a non-type ascripted method

Created on 21 Apr 2020  路  22Comments  路  Source: lampepfl/dotty

To reproduce execute:

git clone https://github.com/zio/izumi-reflect.git
cd izumi-reflect
git checkout feature/dotty-macro-expansion-fails-when-not-inside-type-ascripted-method
sbt '++0.24.0-bin-20200420-c560211-NIGHTLY test:compile'

Output

scala[error] -- Error: /private/tmp/izumi-reflect/izumi-reflect/izumi-reflect/src/test/scala/izumi/reflect/test/CurrentDottySupportExtentTest.scala:26:22 [error] 26 | val bazTag = LTT[Baz] [error] | ^^^^^^^^ [error] |Exception occurred while executing macro expansion. [error] |scala.NotImplementedError: an implementation is missing [error] | at scala.Predef$.$qmark$qmark$qmark(Predef.scala:347) [error] | at izumi.reflect.dottyreflection.Inspector.inspectSymbol(Inspector.scala:120) [error] | at izumi.reflect.dottyreflection.Inspector.prefixOf(Inspector.scala:132) [error] | at izumi.reflect.dottyreflection.Inspector.asNameRefSym(Inspector.scala:219) [error] | at izumi.reflect.dottyreflection.Inspector.inspectSymbol(Inspector.scala:113) [error] | at izumi.reflect.dottyreflection.Inspector.inspectTree(Inspector.scala:107) [error] | at izumi.reflect.dottyreflection.Inspector.buildTypeRef(Inspector.scala:33) [error] | at izumi.reflect.dottyreflection.TypeInspections$.apply(TypeInspections.scala:10) [error] | at izumi.reflect.dottyreflection.Inspect$.inspectAny(Inspect.scala:21) [error] | [error] | This location contains code that was inlined from CurrentDottySupportExtentTest.scala:26 [error] | This location contains code that was inlined from package.scala:73 [error] -- Error: /private/tmp/izumi-reflect/izumi-reflect/izumi-reflect/src/test/scala/izumi/reflect/test/CurrentDottySupportExtentTest.scala:27:23 [error] 27 | val bazTag2 = LTT[Baz] [error] | ^^^^^^^^ [error] |Exception occurred while executing macro expansion. [error] |scala.NotImplementedError: an implementation is missing [error] | at scala.Predef$.$qmark$qmark$qmark(Predef.scala:347) [error] | at izumi.reflect.dottyreflection.Inspector.inspectSymbol(Inspector.scala:120) [error] | at izumi.reflect.dottyreflection.Inspector.prefixOf(Inspector.scala:132) [error] | at izumi.reflect.dottyreflection.Inspector.asNameRefSym(Inspector.scala:219) [error] | at izumi.reflect.dottyreflection.Inspector.inspectSymbol(Inspector.scala:113) [error] | at izumi.reflect.dottyreflection.Inspector.inspectTree(Inspector.scala:107) [error] | at izumi.reflect.dottyreflection.Inspector.buildTypeRef(Inspector.scala:33) [error] | at izumi.reflect.dottyreflection.TypeInspections$.apply(TypeInspections.scala:10) [error] | at izumi.reflect.dottyreflection.Inspect$.inspectAny(Inspect.scala:21) [error] | [error] | This location contains code that was inlined from CurrentDottySupportExtentTest.scala:27 [error] | This location contains code that was inlined from package.scala:73 [error] -- Error: /private/tmp/izumi-reflect/izumi-reflect/izumi-reflect/src/test/scala/izumi/reflect/test/CurrentDottySupportExtentTest.scala:29:23 [error] 29 | val barXTag = LTT[Bar[X]] [error] | ^^^^^^^^^^^ [error] |Exception occurred while executing macro expansion. [error] |scala.NotImplementedError: an implementation is missing [error] | at scala.Predef$.$qmark$qmark$qmark(Predef.scala:347) [error] | at izumi.reflect.dottyreflection.Inspector.inspectSymbol(Inspector.scala:120) [error] | at izumi.reflect.dottyreflection.Inspector.prefixOf(Inspector.scala:132) [error] | at izumi.reflect.dottyreflection.Inspector.asNameRefSym(Inspector.scala:219) [error] | at izumi.reflect.dottyreflection.Inspector.asNameRef(Inspector.scala:212) [error] | at izumi.reflect.dottyreflection.Inspector.inspectTType(Inspector.scala:66) [error] | at izumi.reflect.dottyreflection.Inspector.inspectTree(Inspector.scala:105) [error] | at izumi.reflect.dottyreflection.Inspector.buildTypeRef(Inspector.scala:33) [error] | at izumi.reflect.dottyreflection.TypeInspections$.apply(TypeInspections.scala:10) [error] | at izumi.reflect.dottyreflection.Inspect$.inspectAny(Inspect.scala:21) [error] | [error] | This location contains code that was inlined from CurrentDottySupportExtentTest.scala:29 [error] | This location contains code that was inlined from package.scala:73

Expectation

Compilation succeeds if the commented out wrappng method def test(): Unit = { ... } is uncommented.

That is, the content of the quoted.Type[_] value depends directly on whether or not the LTT inline method is called inside a def test or inside the class body, even though the types referred to are defined inside the current block in both situations. Moreover if the inline calls are placed inside a method, but without a type ascription, def test() = { ... }, the result is the same error.

/cc @nicolasstucki

metaprogramming bug

All 22 comments

Can you try to minimize this into a self contained example? We will treat these with priority.

@odersky I'll try, but I probably wouldn't have enough time until next week. As it is, the macro codebase of that project is fairly small, but yes, it's not an isolated snippet and may be not ideal for debugging.

@neko-kai what tree do you get in Inspector.scala#L118? I notice that you don't match ValDef.

@nicolasstucki
I've pushed a commit adding debug messages to exceptions in that branch. The tree is a ValDef - but that's not supposed to be there, we're recursing over symbol.tree to collect information about the inheritance hierarchy, so we need the tree to always be the definition tree, unless there's some other way to traverse the type hierarchy.

Error:(26, 23) Exception occurred while executing macro expansion.
java.lang.RuntimeException: SYMBOL TREE, UNSUPPORTED: class dotty.tools.dotc.ast.Trees$ValDef - ValDef(<local CurrentDottySupportExtentTest>,TypeTree[NoType],EmptyTree)
    at izumi.reflect.dottyreflection.Inspector.inspectSymbol(Inspector.scala:120)
    at izumi.reflect.dottyreflection.Inspector.prefixOf(Inspector.scala:132)
    at izumi.reflect.dottyreflection.Inspector.asNameRefSym(Inspector.scala:217)
    at izumi.reflect.dottyreflection.Inspector.inspectSymbol(Inspector.scala:113)
    at izumi.reflect.dottyreflection.Inspector.inspectTree(Inspector.scala:107)
    at izumi.reflect.dottyreflection.Inspector.buildTypeRef(Inspector.scala:33)
    at izumi.reflect.dottyreflection.TypeInspections$.apply(TypeInspections.scala:10)
    at izumi.reflect.dottyreflection.Inspect$.inspectAny(Inspect.scala:21)
      val bazTag = LTT[Baz]

Note, if I add code to recurse on the ValDef#tpt it arrives nowhere anyway:

Error:(26, 23) Exception occurred while executing macro expansion.
java.lang.RuntimeException: TTYPE, UNSUPPORTED: class dotty.tools.dotc.core.Types$NoType$ - NoType
    at izumi.reflect.dottyreflection.Inspector.inspectTType(Inspector.scala:94)
    at izumi.reflect.dottyreflection.Inspector.inspectTree(Inspector.scala:105)
    at izumi.reflect.dottyreflection.Inspector.inspectSymbol(Inspector.scala:119)
    at izumi.reflect.dottyreflection.Inspector.prefixOf(Inspector.scala:134)
    at izumi.reflect.dottyreflection.Inspector.asNameRefSym(Inspector.scala:219)
    at izumi.reflect.dottyreflection.Inspector.inspectSymbol(Inspector.scala:113)
    at izumi.reflect.dottyreflection.Inspector.inspectTree(Inspector.scala:107)
    at izumi.reflect.dottyreflection.Inspector.buildTypeRef(Inspector.scala:33)
    at izumi.reflect.dottyreflection.TypeInspections$.apply(TypeInspections.scala:10)
    at izumi.reflect.dottyreflection.Inspect$.inspectAny(Inspect.scala:21)
      val bazTag = LTT[Baz]

Also, in the macro expansion for LTT[List[Int]], below LTT[Baz] aboe, a Types.LazyRef occurs, which seems like is a very low-level type that doesn't have any methods in tasty-reflect, specifically there's no way to call .underlying and dereference it:

[error] 52 |      val listIntTag0 = LTT[List[Int]]
[error]    |                        ^^^^^^^^^^^^^^
[error]    |Exception occurred while executing macro expansion.
[error]    |java.lang.RuntimeException: TTYPE, UNSUPPORTED: class dotty.tools.dotc.core.Types$LazyRef - LazyRef(...)
[error]    |    at izumi.reflect.dottyreflection.Inspector.inspectTType(Inspector.scala:94)
[error]    |    at izumi.reflect.dottyreflection.Inspector.inspectToB(Inspector.scala:154)
[error]    |    at izumi.reflect.dottyreflection.Inspector.$anonfun$3(Inspector.scala:64)
[error]    |    at scala.collection.immutable.List.map(List.scala:223)
[error]    |    at izumi.reflect.dottyreflection.Inspector.inspectTType(Inspector.scala:64)
[error]    |    at izumi.reflect.dottyreflection.Inspector.inspectTType(Inspector.scala:71)
[error]    |    at izumi.reflect.dottyreflection.Inspector.inspectTType(Inspector.scala:90)
[error]    |    at izumi.reflect.dottyreflection.Inspector.inspectTree(Inspector.scala:105)
[error]    |    at izumi.reflect.dottyreflection.Inspector.inspectSymbol(Inspector.scala:115)
[error]    |    at izumi.reflect.dottyreflection.Inspector.inspectTType(Inspector.scala:87)
[error]    |    at izumi.reflect.dottyreflection.Inspector.inspectToB(Inspector.scala:154)
[error]    |    at izumi.reflect.dottyreflection.Inspector.$anonfun$3(Inspector.scala:64)
[error]    |    at scala.collection.immutable.List.map(List.scala:223)

Still need a self contained minimized example

@nicolasstucki
I've made a minimized example here:

https://github.com/7mind/dottyreflection-repro

You can test using that repo, It consists of just one file with minimized code and two files with test cases:

  1. inspecting the same class succeeds or fails with CyclicReferenceException depending on the point in code at which the inline was called:
package test

import izumi.reflect.dottyreflection.Inspect.inspect

object Test1 extends App {

  // ok in class body
  class Foo
  println(inspect[Foo])

  // not ok in expression
  def test() = {
    locally {
      inspect[Foo]
    }
  }

}

Exception

Error:(14, 14) Exception occurred while executing macro expansion.
dotty.tools.dotc.core.CyclicReference: 
    at dotty.tools.dotc.core.CyclicReference$.apply(TypeErrors.scala:156)
    at dotty.tools.dotc.core.SymDenotations$SymDenotation.completeFrom(SymDenotations.scala:257)
    at dotty.tools.dotc.core.Denotations$Denotation.completeInfo$1(Denotations.scala:186)
    at dotty.tools.dotc.core.Denotations$Denotation.info(Denotations.scala:188)
    at dotty.tools.dotc.ast.tpd$.polyDefDef(tpd.scala:235)
    at dotty.tools.dotc.ast.tpd$.DefDef(tpd.scala:225)
    at dotty.tools.dotc.ast.tpd$.DefDef(tpd.scala:222)
    at dotty.tools.dotc.tastyreflect.FromSymbol$.defDefFromSym(FromSymbol.scala:46)
    at dotty.tools.dotc.tastyreflect.FromSymbol$.definitionFromSym(FromSymbol.scala:19)
    at dotty.tools.dotc.tastyreflect.FromSymbol$.$anonfun$5(FromSymbol.scala:35)
    at scala.collection.immutable.List.map(List.scala:250)
    at dotty.tools.dotc.tastyreflect.FromSymbol$.classDef(FromSymbol.scala:35)
    at dotty.tools.dotc.tastyreflect.FromSymbol$.definitionFromSym(FromSymbol.scala:16)
    at dotty.tools.dotc.tastyreflect.ReflectionCompilerInterface.Symbol_tree(ReflectionCompilerInterface.scala:1672)
    at dotty.tools.dotc.tastyreflect.ReflectionCompilerInterface.Symbol_tree(ReflectionCompilerInterface.scala:1671)
    at scala.tasty.Reflection$SymbolOps$.tree(Reflection.scala:2161)
    at izumi.reflect.dottyreflection.Inspector.symbolToNameReference(Inspect.scala:84)
    at izumi.reflect.dottyreflection.Inspector.inspectSymbol(Inspect.scala:66)
    at izumi.reflect.dottyreflection.Inspector.inspectTree(Inspect.scala:39)
    at izumi.reflect.dottyreflection.Inspector.buildTypeRef(Inspect.scala:29)
    at izumi.reflect.dottyreflection.Inspect$.inspectAny(Inspect.scala:9)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:567)
    at dotty.tools.dotc.transform.Splicer$Interpreter.interpretedStaticMethodCall$$anonfun$2$$anonfun$1(Splicer.scala:320)
    at dotty.tools.dotc.transform.Splicer$Interpreter.stopIfRuntimeException(Splicer.scala:380)
    at dotty.tools.dotc.transform.Splicer$Interpreter.interpretedStaticMethodCall$$anonfun$1(Splicer.scala:320)
    at dotty.tools.dotc.transform.Splicer$Interpreter.interpretTree(Splicer.scala:245)
    at dotty.tools.dotc.transform.Splicer$Interpreter.interpretTree$$anonfun$4(Splicer.scala:265)
    at dotty.tools.dotc.transform.Splicer$.$anonfun$2(Splicer.scala:49)
    at scala.Option.fold(Option.scala:263)
    at dotty.tools.dotc.transform.Splicer$.splice(Splicer.scala:49)
    at dotty.tools.dotc.typer.Inliner.dotty$tools$dotc$typer$Inliner$$expandMacro(Inliner.scala:1430)
    at dotty.tools.dotc.typer.Inliner$InlineTyper.typedApply(Inliner.scala:1258)
    at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:2345)
    at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2404)
    at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:124)
    at dotty.tools.dotc.typer.Typer.op$1(Typer.scala:2443)
    at dotty.tools.dotc.typer.Typer.typed(Typer.scala:2452)
    at dotty.tools.dotc.typer.Typer.typed(Typer.scala:2455)
    at dotty.tools.dotc.typer.ReTyper.typedTyped(ReTyper.scala:60)
    at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:2350)
    at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2404)
    at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:124)
    at dotty.tools.dotc.typer.Typer.op$1(Typer.scala:2443)
    at dotty.tools.dotc.typer.Typer.typed(Typer.scala:2452)
    at dotty.tools.dotc.typer.Typer.op$1(Typer.scala:2439)
    at dotty.tools.dotc.typer.Typer.typed(Typer.scala:2452)
    at dotty.tools.dotc.typer.Typer.typed(Typer.scala:2455)
    at dotty.tools.dotc.typer.Inliner.op$1(Inliner.scala:753)
    at dotty.tools.dotc.typer.Inliner.inlined(Inliner.scala:772)
    at dotty.tools.dotc.typer.Inliner$.inlineCall(Inliner.scala:115)
    at dotty.tools.dotc.typer.Typer.adaptNoArgsOther$4(Typer.scala:3084)
    at dotty.tools.dotc.typer.Typer.adaptNoArgs$1(Typer.scala:3181)
    at dotty.tools.dotc.typer.Typer.adapt1(Typer.scala:3389)
    at dotty.tools.dotc.typer.Typer.op$3(Typer.scala:2777)
    at dotty.tools.dotc.typer.Typer.adapt(Typer.scala:2778)
    at dotty.tools.dotc.typer.Typer.op$1(Typer.scala:2443)
    at dotty.tools.dotc.typer.Typer.typed(Typer.scala:2452)
    at dotty.tools.dotc.typer.Typer.typed(Typer.scala:2455)
    at dotty.tools.dotc.typer.Typer.typedExpr(Typer.scala:2566)
    at dotty.tools.dotc.typer.Typer.typedBlock(Typer.scala:854)
    at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:2353)
    at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2404)
    at dotty.tools.dotc.typer.ProtoTypes$FunProto.$anonfun$2(ProtoTypes.scala:337)
    at dotty.tools.dotc.typer.ProtoTypes$FunProto.cacheTypedArg(ProtoTypes.scala:293)
    at dotty.tools.dotc.typer.ProtoTypes$FunProto.typedArg(ProtoTypes.scala:338)
    at dotty.tools.dotc.typer.Applications$ApplyToUntyped.typedArg(Applications.scala:795)
    at dotty.tools.dotc.typer.Applications$ApplyToUntyped.typedArg(Applications.scala:795)
    at dotty.tools.dotc.typer.Applications$Application.addTyped$1(Applications.scala:530)
    at dotty.tools.dotc.typer.Applications$Application.matchArgs(Applications.scala:599)
    at dotty.tools.dotc.typer.Applications$Application.init(Applications.scala:355)
    at dotty.tools.dotc.typer.Applications$TypedApply.<init>(Applications.scala:692)
    at dotty.tools.dotc.typer.Applications$ApplyToUntyped.<init>(Applications.scala:794)
    at dotty.tools.dotc.typer.Applications.ApplyTo(Applications.scala:997)
    at dotty.tools.dotc.typer.Typer.ApplyTo(Typer.scala:85)
    at dotty.tools.dotc.typer.Applications.simpleApply$1(Applications.scala:864)
    at dotty.tools.dotc.typer.Applications.realApply$5$$anonfun$4(Applications.scala:925)
    at dotty.tools.dotc.typer.Typer.tryEither(Typer.scala:2574)
    at dotty.tools.dotc.typer.Applications.realApply$1(Applications.scala:936)
    at dotty.tools.dotc.typer.Applications.typedApply(Applications.scala:972)
    at dotty.tools.dotc.typer.Typer.typedApply(Typer.scala:85)
    at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:2345)
    at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2404)
    at dotty.tools.dotc.typer.Typer.op$1(Typer.scala:2443)
    at dotty.tools.dotc.typer.Typer.typed(Typer.scala:2452)
    at dotty.tools.dotc.typer.Typer.typed(Typer.scala:2455)
    at dotty.tools.dotc.typer.Typer.typedExpr(Typer.scala:2566)
    at dotty.tools.dotc.typer.Typer.typedBlock(Typer.scala:854)
    at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:2353)
    at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2404)
    at dotty.tools.dotc.typer.Typer.op$1(Typer.scala:2443)
    at dotty.tools.dotc.typer.Typer.typed(Typer.scala:2452)
    at dotty.tools.dotc.typer.Typer.typed(Typer.scala:2455)
    at dotty.tools.dotc.typer.Namer.typedAheadExpr$$anonfun$1(Namer.scala:1329)
    at dotty.tools.dotc.typer.Namer.typedAhead(Namer.scala:1319)
    at dotty.tools.dotc.typer.Namer.typedAheadExpr(Namer.scala:1329)
    at dotty.tools.dotc.typer.Namer.rhsType$2(Namer.scala:1463)
    at dotty.tools.dotc.typer.Namer.cookedRhsType$1(Namer.scala:1474)
    at dotty.tools.dotc.typer.Namer.lhsType$1(Namer.scala:1475)
    at dotty.tools.dotc.typer.Namer.inferredType$1(Namer.scala:1486)
    at dotty.tools.dotc.typer.Namer.valOrDefDefSig(Namer.scala:1494)
    at dotty.tools.dotc.typer.Namer.defDefSig(Namer.scala:1567)
    at dotty.tools.dotc.typer.Namer$Completer.typeSig(Namer.scala:813)
    at dotty.tools.dotc.typer.Namer$Completer.completeInCreationContext(Namer.scala:930)
    at dotty.tools.dotc.typer.Namer$Completer.complete(Namer.scala:841)
    at dotty.tools.dotc.core.SymDenotations$SymDenotation.completeFrom(SymDenotations.scala:259)
    at dotty.tools.dotc.core.Denotations$Denotation.completeInfo$1(Denotations.scala:186)
    at dotty.tools.dotc.core.Denotations$Denotation.info(Denotations.scala:188)
    at dotty.tools.dotc.ast.tpd$.polyDefDef(tpd.scala:235)
    at dotty.tools.dotc.ast.tpd$.DefDef(tpd.scala:225)
    at dotty.tools.dotc.ast.tpd$.DefDef(tpd.scala:222)
    at dotty.tools.dotc.tastyreflect.FromSymbol$.defDefFromSym(FromSymbol.scala:46)
    at dotty.tools.dotc.tastyreflect.FromSymbol$.definitionFromSym(FromSymbol.scala:19)
    at dotty.tools.dotc.tastyreflect.FromSymbol$.$anonfun$5(FromSymbol.scala:35)
    at scala.collection.immutable.List.map(List.scala:250)
    at dotty.tools.dotc.tastyreflect.FromSymbol$.classDef(FromSymbol.scala:35)
    at dotty.tools.dotc.tastyreflect.FromSymbol$.definitionFromSym(FromSymbol.scala:16)
    at dotty.tools.dotc.tastyreflect.ReflectionCompilerInterface.Symbol_tree(ReflectionCompilerInterface.scala:1672)
    at dotty.tools.dotc.tastyreflect.ReflectionCompilerInterface.Symbol_tree(ReflectionCompilerInterface.scala:1671)
    at scala.tasty.Reflection$SymbolOps$.tree(Reflection.scala:2161)
    at izumi.reflect.dottyreflection.Inspector.symbolToNameReference(Inspect.scala:84)
    at izumi.reflect.dottyreflection.Inspector.inspectSymbol(Inspect.scala:66)
    at izumi.reflect.dottyreflection.Inspector.inspectTree(Inspect.scala:39)
    at izumi.reflect.dottyreflection.Inspector.buildTypeRef(Inspect.scala:29)
    at izumi.reflect.dottyreflection.Inspect$.inspectAny(Inspect.scala:9)
      inspect[Foo]

  1. A local class inside an expression has inaccessible prefix (ValDef with no type):
package test

import izumi.reflect.dottyreflection.Inspect.inspect

object Test2 extends App {

  // local class not ok
  locally {
    class Bar
    inspect[Bar]
  }

}

Exception

Error:(10, 12) Exception occurred while executing macro expansion.
java.lang.RuntimeException: Type, UNSUPPORTED: class dotty.tools.dotc.core.Types$NoType$ - NoType
    at izumi.reflect.dottyreflection.Inspector.inspectType(Inspect.scala:58)
    at izumi.reflect.dottyreflection.Inspector.inspectTree(Inspect.scala:37)
    at izumi.reflect.dottyreflection.Inspector.inspectSymbol(Inspect.scala:72)
    at izumi.reflect.dottyreflection.Inspector.symbolToNameReference(Inspect.scala:86)
    at izumi.reflect.dottyreflection.Inspector.inspectSymbol(Inspect.scala:66)
    at izumi.reflect.dottyreflection.Inspector.inspectTree(Inspect.scala:39)
    at izumi.reflect.dottyreflection.Inspector.buildTypeRef(Inspect.scala:29)
    at izumi.reflect.dottyreflection.Inspect$.inspectAny(Inspect.scala:9)
    inspect[Bar]

3. Algorithm

package izumi.reflect.dottyreflection

import scala.quoted.{Expr, QuoteContext}

object Inspect {
  inline def inspect[T <: AnyKind]: String = ${ inspectAny[T] }

  def inspectAny[T <: AnyKind : quoted.Type](using qctx0: QuoteContext): Expr[String] = {
    val ref = new Inspector().buildTypeRef[T]
    println(s"result: $ref")
    Expr(ref.toString)
  }
}

final case class NameReference(ref: String, prefix: Option[NameReference]) {
  override def toString: String = prefix.fold("")(p => s"$p::") + ref
}

class Inspector(implicit qctx: QuoteContext) {
  import qctx.tasty._

  protected def log(s: String) = {
    println(" -> " + s)
  }

  def buildTypeRef[T <: AnyKind](using tpe: quoted.Type[T]): NameReference = {
    val uns = tpe.unseal
    log(s" -------- about to inspect ${tpe} --------")
    val v = inspectTree(uns)
    log(s" -------- done inspecting ${tpe} --------")
    v
  }

  private def inspectTree(tpeTree: TypeTree): NameReference = {
    log(s"INSPECT: $tpeTree: ${tpeTree.getClass}")
    if (tpeTree.symbol.isNoSymbol) {
      inspectType(tpeTree.tpe)
    } else {
      inspectSymbol(tpeTree.symbol)
    }
  }

  private def inspectType(tpe: Type): NameReference = {
    log(s"INSPECT: type `$tpe`")
    tpe match {
      case r: TypeRef =>
        inspectSymbol(r.typeSymbol)

      case a: AnnotatedType =>
        inspectType(a.underlying)

      case lazyref if lazyref.getClass.getName.contains("LazyRef") => // upstream bug seems like
        log(s"LazyRef occured $lazyref")
        throw new RuntimeException(s"LazyRef occured $lazyref")

      case o =>
        log(s"Type, UNSUPPORTED: ${o.getClass} - $o")
        throw new RuntimeException(s"Type, UNSUPPORTED: ${o.getClass} - $o")
    }
  }

  private def inspectSymbol(symbol: Symbol): NameReference = {
    log(s"INSPECT: symbol `$symbol`")
    symbol.tree match {
      case c: ClassDef =>
        symbolToNameReference(symbol)

      case t: TypeDef =>
        inspectTree(t.rhs.asInstanceOf[TypeTree])

      case v: ValDef =>
        inspectTree(v.tpt)

      case o =>
        log(s"SYMBOL TREE, UNSUPPORTED: $o")
        throw new RuntimeException(s"SYMBOL TREE, UNSUPPORTED: ${o.getClass} - $o")
    }
  }

  private def symbolToNameReference(sym: Symbol): NameReference = {
    val prefix = if (sym.maybeOwner.isNoSymbol) {
      None
    } else {
      sym.owner.tree match {
        case _: PackageDef => None
        case _ => Some(inspectSymbol(sym.maybeOwner))
      }
    }
    NameReference(sym.fullName, prefix)
  }

}

@nicolasstucki : did you get a chance to look at this issue?

Unfortunately, I did not

This is still affecting users of ZIO on Scala 3 as of 0.27, there was a question on discord today from user aappddeevv bumping into this issue: https://discordapp.com/channels/629491597070827530/630498701860929559/763866388367671347

  1. Seems to have the correct behavior. Here the owner of Bar is the local dummy, a marker val that lets us know that this is locally defined in the constructor of Test2. This dummy can be identified with isLocalDummy.

Re (2): Seems like we need a "localDummyIdentifier" thingy, something similar to FQN but considering scoping. So we may distinguish different Bars in local contexts.

Previously we've been using prefixes, which may be logically incorrect but better than being unable to identify context at all.

Could you suggest something?

@pshirshov

Previously we've been using prefixes, which may be logically incorrect

Could you please elaborate, what's wrong with prefixes?

  1. The issue here is that we are trying to type the signature of the members at the same time we are expanding the macro. For non-transparent macros this could be fixed by expanding macros after Typer (see #7825). Not sure how to avoid it for transparent inline yet (seems to be the same issue as in #7592).

Could you please elaborate, what's wrong with prefixes?

You cannot disambiguate these classes:

locally {
class Bar
}

locally {
class Bar
}

Current izumi-reflect implementation will consider their typetags equal.

In this particular case, you should be able to distinguish them by their symbol.

We need a serializable identifier though. Anyway, that's different from this issue and actually not that important.

Maybe you can use their positions to serialize them when they are local.

Yup, that's one of the options.

Note that positions is unavailable for a symbols coming from another package.
And it is not that simple to distinguish between this two cases:

class T { class X }

val a = new T // a.X is accessible from other packages
val b = println({ class X; "hi" }) // b.X isn't accessible from the outside of this block

So I'm not sure if it is possible to reliably serialize identifiers regardless of where they are inspected/come from.

Usually, local definitions are represented with some special notation and a unique local id. This special notation tells us that it is local and hence not accessible from outside. If you want to serialize a symbol with a string name you may take inspiration from the semanticdb format https://scalameta.org/docs/semanticdb/specification.html#symbol.

Current relevant PR: lampepfl/dotty#9984 - this should fix the principal issue when merged (the interference between type inference and type info available in blackbox macros)

Was this page helpful?
0 / 5 - 0 ratings