Skip to content

transparent macro : Unexpected loss of refinement #17398

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

Open
ftucky opened this issue May 3, 2023 · 4 comments
Open

transparent macro : Unexpected loss of refinement #17398

ftucky opened this issue May 3, 2023 · 4 comments
Labels
area:metaprogramming:quotes Issues related to quotes and splices itype:bug

Comments

@ftucky
Copy link

ftucky commented May 3, 2023

Compiler version

3.2.2,
3.3.1-RC1-bin-20230328-5c2efc5-NIGHTLY

Minimized code

Transparent Macro:

import scala.quoted.{Quotes,Type,Expr}

// Library part
trait View

// type class
trait ViewOf[M]:
  type Out <: View

object ViewOf :
  transparent inline def whiteMacro[M]: ViewOf[M] = ${whiteMacroImpl[M]}

  def whiteMacroImpl[M:Type](using quotes:Quotes) : Expr[ViewOf[M]]  =
    val res = Type.of[View] match
      case '[tpe] => '{
          new ViewOf[M]:
            type Out = tpe
        }
    
    println(res.show)
    res
  end whiteMacroImpl
end ViewOf

Usage:

whiteMacro[Nothing]

Output

{
  final class $anon() extends refine.ViewOf[scala.Nothing] {
    type Out = refine.View
  }

  (new $anon(): refine.ViewOf[scala.Nothing])
}

Expectation

The returned type is expected to be refined:

{
  final class $anon() extends refine.ViewOf[scala.Nothing] {
    type Out = refine.View
  }

  (new $anon(): refine.ViewOf[scala.Nothing] {
    type Out >: refine.View <: refine.View
  })
}

Observations:

(1) When the upper bound of Out in trait ViewOf[M] is removed, correct behavior is restored:

trait ViewOf[M]:
  type Out

(2) When the type assigned in the anonymous class does not come from the type match, correct behavior is restored:

      case '[tpe] => '{
          new ViewOf[M]:
            type Out = View
        }

(3) When a type ascription is placed on the anonymous class, correct behavior is restored. (thank you, @smarter )

      case '[tpe] => '{
          (new ViewOf[M]:
            type Out = tpe
          ) : ViewOf[M] { type Out = tpe }
        }

More context

https://stackoverflow.com/questions/74549477/scala3-crafting-types-through-metaprogramming/74575615#74575615

@ftucky ftucky added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels May 3, 2023
@sjrd
Copy link
Member

sjrd commented May 3, 2023

That seems as designed to me, and not related to macros to me. If you manually write such an anonymous class at call site, it will also lose its refinement due to type inference. You would also have to explicitly type is as a refinement to preserve it.

(3) When a type ascription is placed on the anonymous class, correct behavior is restored.

So that is the correct solution (not a workaround).

@bishabosha
Copy link
Member

refinements of anonymous classes are only inferred if they derive from scala.Selectable

@ftucky
Copy link
Author

ftucky commented May 3, 2023

OK, then observations (1) and (2) (where refinement of an anonymous class not deriving from Selectable is inferred) are bugs !

@smarter
Copy link
Member

smarter commented May 3, 2023

What's going on here is a bit subtle:

  1. ViewOf has a defined member Out, normally this means that new ViewOf[M] { type Out = ... } should have an inferred type that preserves the refinement. The logic to do this is in https://github.com/lampepfl/dotty/blob/b8d2966e2f45e21f2800cd125ac72d80e5f2cb2a/compiler/src/dotty/tools/dotc/core/TypeOps.scala#L421-L432
  2. This works fine when the member of ViewOf is the fully abstract type Out but fails if we add an upper-bound like type Out <: View
  3. The reason this fails is that the logic I mentioned above only kicks in if the refinement is a more precise type than the original type. Here, we check that tpe is a subtype of View, but because tpe is fully abstract this fails!
  4. Normally, this should lead to a follow-on override error, but this doesn't happen because we don't do override checks inside quotes, see Quotes are not checked for override errors / @deprecated usage / @experimental usage #17400
  5. I don't think that we can give tpe a more precise type right now, but once SIP-53 - Quote pattern explicit type variable syntax #17362 is merged we should be able to do:
      case '[type tpe <: View; tpe] => '{
          new ViewOf[M]:
            type Out = tpe
        }
  6. Meanwhile, it's possible to work-around this by ascribing a refinement type explicitly:
      case '[tpe] => '{
          (new ViewOf[M]:
            type Out = tpe): ViewOf[M] { type Out = tpe }
        }
    ... or by using an intersection:
      case '[tpe] => '{
          new ViewOf[M]:
            type Out = View & tpe
        }

@smarter smarter added area:metaprogramming:quotes Issues related to quotes and splices and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels May 3, 2023
@nicolasstucki nicolasstucki changed the title WhiteboxMacro : Unexpected loss of refinement transparent macro : Unexpected loss of refinement May 24, 2023
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 itype:bug
Projects
None yet
Development

No branches or pull requests

4 participants