scala / scala3

The Scala 3 compiler, also known as Dotty.
https://dotty.epfl.ch
Apache License 2.0
5.8k stars 1.04k forks source link

Type is simplified too much before being passed to an implicit macro #17544

Open neko-kai opened 1 year ago

neko-kai commented 1 year ago

Compiler version

3.3.0-RC6 and 3.2.2

Minimized code

// file:exampleMacro.scala

package example

import scala.quoted.*

case class X[T](x: String)

object X {
  inline given exampleMacro[T]: X[T] = ${ exampleMacroImpl[T] }

  def exampleMacroImpl[T: Type](using qctx: Quotes): Expr[X[T]] = {
    '{ X[T](${ Expr(Type.show[T]) }) }
  }
}
// file:example.scala

package example

trait TraitSuper
trait TraitSub extends TraitSuper

@main def main: Unit = {
  println(X.exampleMacro[TraitSuper & TraitSub].x) // outputs: verbatim type tree
  println(implicitly[X[TraitSuper & TraitSub]].x) // output: modified type tree
}

Output

example.TraitSuper & example.TraitSub
example.TraitSub

Expectation

The type received on the macro side differs when the macro is invoked implicitly versus when the macro is invoked explicitly – expected to receive the same type tree in both cases.

bishabosha commented 1 year ago

These types are equivalent (=:=), so you should not rely upon structural equality, is there another case where they are not equivalent?

neko-kai commented 1 year ago

@bishabosha Theoretically I shouldn't rely on the way equivalent types are written, but to do so in the case of izumi-reflect, I would have to perform a quadratic operation at runtime to remove the redundant members of intersection when constructing a Tag. Since when used in ZIO, intersection tags with type parameters can appear in hot spots, I would rather avoid doing that - although maybe that's the right thing to do anyway. This is the case that surfaces this - https://github.com/zio/izumi-reflect/commit/339b5b82c5898ea35fd57f38fccc0c350321d016#diff-9d5c97bbb6049264132a5d88dbee33be24fcb9f2b14ef8ba542b400c9bbf532aR177 Since in def tag1[T: Tag]: Tag[T with Trait1] = Tag[T with Trait1] I can't tell what T is until runtime, I can't remove Trait1 from the intersection before runtime. Now when a concrete Tag is summoned for Trait3[Dep] with Trait1 the Trait1 doesn't get into the macro and the tags end up unequal wrt their serialized form.

bishabosha commented 1 year ago

could you benchmark this slow path in "typical" use cases?