Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NamedTuples: Error compiling macro that selects an argument #22863

Open
fede0664 opened this issue Mar 23, 2025 · 7 comments
Open

NamedTuples: Error compiling macro that selects an argument #22863

fede0664 opened this issue Mar 23, 2025 · 7 comments
Labels
area:metaprogramming:quotes Issues related to quotes and splices area:metaprogramming:reflection Issues related to the quotes reflection API area:named-tuples Issues tied to the named tuples feature. itype:bug

Comments

@fede0664
Copy link

Compiler version

3.7.0-RC1

Minimized code

MacroNamedTuple.scala

//> using scala 3.7.0-RC1
//> using jvm temurin:23

import scala.quoted.*
import scala.deriving.*

object MacroNamedTuple:
  inline def tupleStringArg[T](t: T, arg: String) = ${tupleArg('t,'arg)}

  def tupleArg[T](t: Expr[T], arg: Expr[String])(using Quotes) = 
    import quotes.reflect.*
    val sel = Select.unique(t.asTerm, arg.valueOrAbort)
    sel.asExprOf[String]

Example.scala

//> using scala 3.7.0-RC1
//> using jvm temurin:23

object Example:
  def main(args:Array[String]): Unit =
    val t = (name = "abc", v = 123)
    val str = MacroNamedTuple.tupleStringArg(t, "name")
    println(str)

Output

Running with:

scala-cli MacroNamedTuple.scala Example.scala
[error] ./Example.scala:7:46
[error] value name is not a member of (name : String, v : Int)
[error]     val str = MacroNamedTuple.tupleStringArg(t, "name")

Expectation

Should print "abc"
Changing Example to regular tuples works fine:

object Example:
  def main(args:Array[String]): Unit =
    val t = ("abc",123)
    val str = MacroNamedTuple.tupleStringArg(t, "_1")
    println(str)
@fede0664 fede0664 added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Mar 23, 2025
@soronpo
Copy link
Contributor

soronpo commented Mar 23, 2025

I think the arguments need to be inline as well

@fede0664
Copy link
Author

Tried it, same error message, see below:

//> using scala 3.7.0-RC1
//> using jvm temurin:23

import scala.quoted.*
import scala.deriving.*

object MacroNamedTuple:
  inline def tupleStringArg[T](inline t: T, inline arg: String) = ${tupleArg('t,'arg)}

  def tupleArg[T](t: Expr[T], arg: Expr[String])(using Quotes) = 
    import quotes.reflect.*
    val sel = Select.unique(t.asTerm, arg.valueOrAbort)
    sel.asExprOf[String]
//> using scala 3.7.0-RC1
//> using jvm temurin:23

object Example:
  def main(args:Array[String]): Unit =
    val t = (name = "abc", v = 123)
    val str = MacroNamedTuple.tupleStringArg(t, "name")
    println(str)
scala-cli MacroNamedTuple.scala Example.scala 
[error] ./Example.scala:7:46
[error] value name is not a member of (name : String, v : Int)
[error]     val str = MacroNamedTuple.tupleStringArg(t, "name")

@som-snytt
Copy link
Contributor

-Ydebug-error helps locate what is failing. unique indeed requires an existing member.

Normally, typedSelectWithAdapt will tryNamedTupleSelection. Not sure if that means the macro is supposed to do this translation to tuple selection "manually".

-Vprint:all helps show what an explicit (non-macro) named tuple looks like. One might expect to reason backwards, but that is not possible here.

My nitpick is that there are a dozen -Ydebug options. Frankly, just finding -Ydebug-error is not convenient. (I also have trouble with -Ysafe-init-global; except obviously, now I finally do have it "memorized" by rote.) I think -Vdebug:name,pos,error would be easier to navigate.

@Gedochao Gedochao added area:metaprogramming:reflection Issues related to the quotes reflection API area:metaprogramming:quotes Issues related to quotes and splices area:named-tuples Issues tied to the named tuples feature. and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Mar 24, 2025
@Gedochao
Copy link
Contributor

The same behaviour can be observed with the named tuples import import scala.language.experimental.namedTuples and --experimental on earlier versions (just noting, that it's not a regression).

@bishabosha
Copy link
Member

bishabosha commented Mar 24, 2025

if you expect this to work, (and not require manually resolving to .apply(n: Int)), then you should compare to the current state of Selectable:

//> using scala 3.7.0-RC1
//> using jvm temurin:23

class Record extends Selectable:
  def selectDynamic(s: String): Any = ???

object Example:
  def main(args:Array[String]): Unit =
    val t: Record { val name: String }
    val str = MacroNamedTuple.tupleStringArg(t, "name")
    println(str)

does this work? i would assume the same behavior accross all fake selections. so therefore extensions and implicit conversions should also resolve. My thought was that Select.unique was a low level operation that requires a real member to exist

there is Select.overloaded which maybe more forgiving?

@som-snytt
Copy link
Contributor

I had also tried overloaded, which also requires a member. (I removed some extra words from my comment because I'm not familiar with quotes, so I was just trying stuff out.)

@fede0664
Copy link
Author

Following @som-snytt advice I compiled using -Vprint:all options. These are my findings:

The line:

val str = MacroNamedTuple.tupleStringArg(t, "name")

Is correctly expanded to:

val str: String = t.name:String

But it's done in the latest compilation stage, when the compiler doesn't know anything about tuple names.

The compiler desugars val n = t.name in the first stage into this:

val n: String = NamedTuple.apply[(("name" : String), ("v" : String)), (String, Int)](t)(0)

To find a solution I replicated something similar using Macros. This works:

//> using scala 3.7.0-RC1
//> using jvm temurin:23

import scala.quoted.*
import scala.deriving.*

object Helper:
 def valueAt[V](t: Tuple, pos:Int): V = t(pos).asInstanceOf[V]

object MacroNamedTuple:
 inline def tupleAnyArg[T](inline t: T, inline arg: String) = ${tupleArg('t,'arg)}

 def tupleArg[T: Type](t: Expr[T], arg: Expr[String])(using Quotes) = 
   import quotes.reflect.*
   val tt = TypeRepr.of[T]
   val tp = Typed(t.asTerm, TypeTree.of[Tuple])
   val names = tt.typeArgs(0).typeArgs.map{case c:ConstantType => c.constant.value.toString}
   val pos = names.indexOf(arg.valueOrAbort)
   val tpa = tt.typeArgs(1).typeArgs(pos).asType
   val app = tpa match
     case '[t] =>
       '{
         Helper.valueAt[t](${tp.asExprOf[Tuple]}, ${Expr(pos)})
       }
   app
//> using scala 3.7.0-RC1
//> using jvm temurin:23

object Example:
 def main(args:Array[String]): Unit =
   val t = (name = "abc", v = 123)
   val n1 = MacroNamedTuple.tupleAnyArg(t, "v")
   println(n1)    
   val n2 = MacroNamedTuple.tupleAnyArg(t, "name")
   println(n2)
scala-cli MacroNamedTuple.scala Example.scala
Compiled project (Scala 3.7.0-RC1, JVM (temurin:23))
123
abc

Not sure if this is the desired behavior dealing with NamedTuples in macros, if that's the case please close this issue.
Thank you all for the suggestions and advice

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:metaprogramming:quotes Issues related to quotes and splices area:metaprogramming:reflection Issues related to the quotes reflection API area:named-tuples Issues tied to the named tuples feature. itype:bug
Projects
None yet
Development

No branches or pull requests

5 participants