Skip to content

Recursive macro looses elements in a constructed tuple #8780

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

Closed
letalvoj opened this issue Apr 23, 2020 · 5 comments
Closed

Recursive macro looses elements in a constructed tuple #8780

letalvoj opened this issue Apr 23, 2020 · 5 comments

Comments

@letalvoj
Copy link

letalvoj commented Apr 23, 2020

Related to #8779, as pointed out by further exploration by @Blaisorblade

Minimized code

import scala.compiletime._, scala.annotation.tailrec, scala.deriving.productElement

@tailrec inline def mapRec[T <: Tuple, A, B](p:Product)(i:Int)(f: A => B) <: Tuple = inline erasedValue[T] match
  case _: (A *: tt) => (f(productElement[A](p,i)) *: mapRec[tt,A,B](p)(i+1)(f)).asInstanceOf[tt]
  case _: (t *: tt) => (productElement[t](p,i) *: mapRec[tt,A,B](p)(i+1)(f)).asInstanceOf[tt]
  case _: Unit => () : Unit

mapRec[(Int, Int, String),Int,String]((1,2,"lol"))(0)(_.toString)                                                                                                                                                      

Output

val res4: (Int, String) = (1,2)

Expectation

Updated based on the apparent confusion in the comments.
Runtime fail or

val res4: (Int, Int, String) = (1,2,lol)
@odersky
Copy link
Contributor

odersky commented Apr 23, 2020

That's just a cast from Tuple2 to Tuple2 which succeeds at runtime

@odersky odersky closed this as completed Apr 23, 2020
@Blaisorblade
Copy link
Contributor

Why does dropping the casts change the return value? That was part of the intended bug report.

scala> @tailrec inline def mapRec[T <: Tuple, A, B](p:Product)(i:Int)(f: A => B) <: Tuple = inline erasedValue[T] match
     |   case _: (A *: tt) => (f(productElement[A](p,i)) *: mapRec[tt,A,B](p)(i+1)(f))
     |   case _: (t *: tt) => (productElement[t](p,i) *: mapRec[tt,A,B](p)(i+1)(f))
     |   case _: Unit => () : Unit
def mapRec[T <: Tuple, A, B](p: Product)(i: Int)(f: A => B): Tuple

scala> mapRec[(Int, Int, String),Int,String]((1,2,"lol"))(0)(_.toString)
val res0: String *: Tuple = (1,2,lol)

@odersky
Copy link
Contributor

odersky commented Apr 23, 2020

I don't know and don't have the time to look into it, but I would guess it's a consequence of #8739. If you can rule this out and find a different specific problem, then please re-open. But as it stand I do not see anyone taking this up, and in that case it's better to leave it closed.

@letalvoj
Copy link
Author

@Blaisorblade oh I overlooked a bracket. You were casting (t *: tt) to tt. I thought that it was just the updated tail.

So the issue in this case is that the last element t *: Unit was ereased by casting it to Unit. Which is a valid behaviour, right? So this issue is closed correctly.

@Blaisorblade
Copy link
Contributor

🤦 okay, that'll teach me to hurry, sorry everybody; I was trying to do something else first. The fixed code indeed works correctly.

scala> @tailrec inline def mapRec[T <: Tuple, A, B](p:Product)(i:Int)(f: A => B) <: Tuple = inline erasedValue[T] match
     |   case _: (A *: tt) => f(productElement[A](p,i)) *: mapRec[tt,A,B](p)(i+1)(f).asInstanceOf[tt]
     |   case _: (t *: tt) => productElement[t](p,i) *: mapRec[tt,A,B](p)(i+1)(f).asInstanceOf[tt]
     |   case _: Unit => () : Unit
def mapRec[T <: Tuple, A, B](p: Product)(i: Int)(f: A => B): Tuple

scala> mapRec[(Int, Int, String),Int,String]((1,2,"lol"))(0)(_.toString)
val res0: String *: Tuple = (1,2,lol)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants