Skip to content

Checking flags for new symbols using Quotes API is too restrictive #17764

Closed
@pweisenburger

Description

@pweisenburger

Compiler version

3.3.2-RC1-bin-20230602-57c2b66-NIGHTLY

Minimized code

#16565 introduced checks for the flags when creating new symbols using the Quotes API. However, this makes it difficult to copy flags from existing symbols under expansion to new symbols. Below is a (not very useful but minimized) example that empties the body of DefDef nodes and changes the return type to Unit. It works under Scala 3.3.0 but fails in Scala 3.3.1 due to the check and the fact that DefDef the macro encounters has more flags than the check allows; in the example these are Flags.Artifact and Flags.Synthetic.

Overall, I would advocate for allowing the flags with the comment "Flags that could be allowed", plus Flags.Artifact. I think there is no reason to be overly restrictive.

Also, the code worked in 3.3.0; it would be nice to make 3.3.1 not break it.

Example

import scala.annotation.experimental
import scala.quoted.*

@experimental
inline def makeUnit(inline expr: Unit): Unit = ${ makeUnitImpl('expr) }

@experimental
def makeUnitImpl(expr: Expr[Unit])(using Quotes): Expr[Unit] =
  import quotes.reflect.*

  object transformer extends TreeMap:
    override def transformTerm(term: Term)(owner: Symbol) = term match
      case Lambda(_, _) =>
        val Block(List(lambda: DefDef), closure: Closure) = term: @unchecked
        val transformedLambda = transformSubTrees(List(lambda))(owner).head
        Block(List(transformedLambda), transformTerm(Closure(Ref(transformedLambda.symbol), closure.tpeOpt))(owner))
      case _ =>
        super.transformTerm(term)(owner)

    override def transformStatement(stat: Statement)(owner: Symbol) = stat match
      case DefDef(name, List(TermParamClause(_)), _, rhs) =>
        val MethodType(paramNames, paramTypes, resultType) = stat.symbol.info: @unchecked
        val info = MethodType(paramNames)(_ => paramTypes, _ => TypeRepr.of[Unit])
        val privateWithin = if stat.symbol.flags is Flags.Protected then stat.symbol.protectedWithin else stat.symbol.privateWithin

        //
        // this is the part where we want to use the same flags as the existing symbol `stat.symbol.flags`
        //
        val symbol = Symbol.newMethod(owner, name, info, stat.symbol.flags, privateWithin.fold(Symbol.noSymbol) { _.typeSymbol })
        DefDef(symbol, paramss => rhs map { _ => Literal(UnitConstant()) })
      case _ =>
        super.transformStatement(stat)(owner)

  transformer.transformTerm(expr.asTerm)(Symbol.spliceOwner).asExprOf[Unit]
import scala.annotation.experimental
import scala.quoted.*

@experimental
def test = makeUnit((x: Int) => x)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions