scala3 icon indicating copy to clipboard operation
scala3 copied to clipboard

TASTy does not include reference to resolved term when using `summonAll` and `summonFrom`

Open mrdziuban opened this issue 9 months ago • 5 comments

Compiler version

3.3.5, 3.4.3, 3.5.2, and 3.6.3

Minimized code

https://github.com/mrdziuban/scala3-compiletime-tasty

That repo contains two files:

  1. Test.scala which summons an instance of cats.Show[String] using four inline methods:
    val instWithSummon = summon[cats.Show[String]]
    val instWithSummonAll = compiletime.summonAll[cats.Show[String] *: EmptyTuple]
    val instWithSummonInline = compiletime.summonInline[cats.Show[String]]
    inline def instWithSummonFrom = compiletime.summonFrom { case s: cats.Show[String] => s }
    
  2. TastyQueryTest.scala which logs the AST of each method call using tasty-query

Output

The output of TastyQueryTest.scala shows that the calls to instWithSummon and instWithSummonInline contain a reference to catsShowForString, which is the instance that satisfies the implicit search, but neither instWithSummonAll nor instWithSummonFrom have a reference to it

expand output
********************************************************************************
instWithSummon:
  Inlined(
    expr = Inlined(
      expr = Ident(
        name = UniqueName(underlying = SimpleName(name = "x"), separator = "$proxy", num = 1)
      ),
      caller = None,
      bindings = List()
    ),
    caller = Some(
      value = TypeIdent(name = ObjectClassTypeName(underlying = SimpleTypeName(name = "Predef")))
    ),
    bindings = List(
      ValDef(
        name = UniqueName(underlying = SimpleName(name = "x"), separator = "$proxy", num = 1),
        tpt = TypeWrapper(
          tp = AppliedType(TypeRef(PackageRef(cats), Show), List(TypeRef(TermRef(PackageRef(scala), Predef), String)))
        ),
        rhs = Some(value = Ident(name = SimpleName(name = "catsShowForString"))),
        symbol = symbol[instWithSummon>x$proxy1]
      )
    )
  )
********************************************************************************

********************************************************************************
instWithSummonAll:
  TypeApply(
    fun = Select(
      qualifier = Select(
        qualifier = Ident(name = SimpleName(name = "compiletime")),
        name = SimpleName(name = "package$package")
      ),
      name = SignedName(
        underlying = SimpleName(name = "summonAll"),
        sig = Signature(
          paramsSig = List(TypeLen(len = 1)),
          resSig = SignatureName(
            items = List(SimpleName(name = "scala"), SimpleName(name = "Product"))
          )
        ),
        target = SimpleName(name = "summonAll")
      )
    ),
    args = List(
      AppliedTypeTree(
        tycon = TypeIdent(name = SimpleTypeName(name = "*:")),
        args = List(
          AppliedTypeTree(
            tycon = SelectTypeTree(
              qualifier = TypeWrapper(tp = PackageRef(cats)),
              name = SimpleTypeName(name = "Show")
            ),
            args = List(TypeIdent(name = SimpleTypeName(name = "String")))
          ),
          TypeIdent(name = SimpleTypeName(name = "EmptyTuple"))
        )
      )
    )
  )
********************************************************************************

********************************************************************************
instWithSummonInline:
  Ident(name = SimpleName(name = "catsShowForString"))
********************************************************************************

********************************************************************************
instWithSummonFrom:
  Typed(
    expr = InlineMatch(
      selector = None,
      cases = List(
        CaseDef(
          pattern = Bind(
            name = SimpleName(name = "s"),
            body = TypeTest(
              body = WildcardPattern(
                tpe = AppliedType(TypeRef(PackageRef(cats), Show), List(TypeRef(TermRef(PackageRef(scala), Predef), String)))
              ),
              tpt = AppliedTypeTree(
                tycon = SelectTypeTree(
                  qualifier = TypeWrapper(tp = PackageRef(cats)),
                  name = SimpleTypeName(name = "Show")
                ),
                args = List(TypeIdent(name = SimpleTypeName(name = "String")))
              )
            ),
            symbol = symbol[instWithSummonFrom>s]
          ),
          guard = None,
          body = Block(
            stats = List(),
            expr = Typed(
              expr = Ident(name = SimpleName(name = "s")),
              tpt = TypeWrapper(
                tp = AppliedType(TypeRef(PackageRef(cats), Show), List(TypeRef(TermRef(PackageRef(scala), Predef), String)))
              )
            )
          )
        )
      )
    ),
    tpt = TypeWrapper(
      tp = AppliedType(TypeRef(PackageRef(cats), Show), List(TypeRef(TermRef(PackageRef(scala), Predef), String)))
    )
  )
********************************************************************************

Expectation

I would expect the TASTy for all four of these calls to have a reference to catsShowForString.

mrdziuban avatar Mar 03 '25 22:03 mrdziuban

I've started debugging this focused first on summonAll as it compares to summonInline.

I've found that summonInline is inlined during the typer phase (and therefore before the pickler phase) because it's transparent and Inlines.needsInlining returns true during the typer phase for transparent symbols.

summonAll is not inlined until the inlining phase (after the pickler phase) because it's not transparent.

I can confirm that if I make it transparent then the tree the pickler phase sees has a reference to the resolved term. To do so, I updated TreePickler#pickleTree to print every tree it receives with tree.show and used this code:

val ordInt = compiletime.summonAll[Ordering[Int] *: EmptyTuple]

As-is (without transparent) I get:

val ordInt: Ordering[Int] *: EmptyTuple =
  compiletime.package$package.summonAll[*:[Ordering[Int], EmptyTuple]]

After adding transparent I get:

val ordInt: Ordering[Int] *: EmptyTuple =
  Tuple1.apply[scala.math.Ordering.Int.type](scala.math.Ordering.Int):
    Ordering[Int] *: EmptyTuple

I see that summonInline was made transparent in https://github.com/scala/scala3/commit/254879888d4813d7cc0d336702dd2c824a3956fa and that summonAll wasn't added until until a couple months later in https://github.com/scala/scala3/commit/3aa00b19107b6383bf059f8c6108e5af641790f3. @nicolasstucki do you have any thoughts on whether it would be reasonable to change summonAll to be transparent?

mrdziuban avatar Mar 06 '25 20:03 mrdziuban

Continuing to debug with summonFrom.

I've found that summonFrom is converted to an InlineMatch during the typer phase in Applications.scala, which results in two checks in Inlines.needsInlining to be false -- isInlineable(tree.symbol) and needsTransparentInlining(tree).

The end result is the same -- the InlineMatch is not inlined until the inlining phase, after the pickler. I'm much less clear on how to address this, especially given the comment in Applications.scala about how summonFrom needs to expand lazily.

mrdziuban avatar Mar 06 '25 20:03 mrdziuban

related: https://github.com/scala/scala3/issues/16313

to be watertight we should really abort compilation if any post-pickling implicit search is attempted from a non-transparent inline method - transitively

bishabosha avatar Mar 07 '25 07:03 bishabosha

@mrdziuban thanks for the great issue summary!

This issue was proposed for a Spree. However I am afraid that it might require more discussions and specifications before we can implement something.

@bishabosha @sjrd @nicolasstucki what is the current status of summon* methods? Do you know if and how we could move forward with this issue?

mbovel avatar Apr 04 '25 21:04 mbovel

The fact that INLINED tasty nodes dont store a reference where they were inlined from, is that a feature or an oversight?

bishabosha avatar Jun 02 '25 14:06 bishabosha