Skip to content

Special case inline unapply seq #15191

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

Conversation

gorilskij
Copy link

@gorilskij gorilskij commented May 16, 2022

Replacement for #15053

Resolves #8577

Fixed a bug where extension inline unapplySeq would fail to type by adding a few special cases in Inliner.scala

Copy link
Contributor

@nicolasstucki nicolasstucki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a bug to fix with the handling of type arguments. This is the cause of the failing/commented tests.

val unapplyInfo = sym.info match
case info: PolyType => info.instantiate(targs.map(_.tpe))
case info: PolyType => info.instantiate(targs.map(_.tpe)) match
case MethodTpe(_, _, rt: PolyType) => rt.instantiate(targs.map(_.tpe))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an issue here as we are reusing the same type arguments for the two type parameter lists. We can make this evident with the following test where the two type parameter lists have different sizes and we use them in the type of the unapply.

    extension (ctx: StringContext) def macroE: MacroE.StringContext = MacroE(ctx)
    extension [T,X <: DummyImplicit] (inline ctx: MacroE.StringContext) inline def unapplySeq[U](inline input: U)(using X): Option[Seq[U]] =
      ${ implUnapplyE('ctx, 'input) }

    val macroE"$xE" = 5

To fix this I propose something like the following where we extract the two type argument lists

    val (targs1, targs2) = fun match
      case TypeApply(Apply(TypeApply(_, targs1), _), targs2) => (targs1, targs2) // extension [U](y: Y) def unapply[Y](x: X)
      case TypeApply(_, targs) => (targs, Nil) // def unapply[U](x: X)
      case Apply(TypeApply(_, targs1), _) => (targs1, Nil) // extension (y: Y) def unapply[T](x: X)
      case _ => (Nil, Nil)

    val unapplyInfo = sym.info match
      case info: PolyType => info.instantiate(targs1.map(_.tpe)) match
        case MethodTpe(_, _, rt: PolyType) => rt.instantiate(targs2.map(_.tpe))
        case MethodTpe(_, _, rt) if sym.flags.is(ExtensionMethod) => rt
        case info => info
      case MethodTpe(_, _, rt: PolyType) => rt.instantiate(targs1.map(_.tpe))
      case MethodTpe(_, _, rt) if sym.flags.is(ExtensionMethod) => rt
      case info => info

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this change, all the tests you commented out start working.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the cases MethodTpe(_, _, rt: PolyType) we assume that these are extension methods because no other definition have that type. I would make this a bit more explicit and add the flag test for this branch as well.

-        case MethodTpe(_, _, rt: PolyType) => rt.instantiate(targs2.map(_.tpe))
+        case MethodTpe(_, _, rt: PolyType) if sym.flags.is(ExtensionMethod) =>
+          rt.instantiate(targs2.map(_.tpe))

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also refactor the common code and make the logic related to extensions clearer.

     def extensionInfo(info: Type, targs: List[Tree]): Type =
       info match
        case MethodTpe(_, _, rt: PolyType) => rt.instantiate(targs.map(_.tpe))
        case MethodTpe(_, _, rt) => rt
        case info => info
     val unapplyInfo = sym.info match
      case pt: PolyType =>
        val info = pt.instantiate(targs1.map(_.tpe))
        if sym.flags.is(ExtensionMethod) then extensionInfo(info, targs2) else info
      case info =>
        if sym.flags.is(ExtensionMethod) then extensionInfo(info, targs1) else info

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to work if we completely remove the whole targs and unapplyInfo ceremony and simply pass fun.tpe.widen instead (see last commit).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Widening might not be the correct thing to do. We might be missing some test cases. I imagine that if we have path dependent types widening will make us loose precision.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a particular test case in mind?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One that comes to my mind but is not supported due to some other bug is #13158 (comment). We could imagine a similar unapply with a path dependent type on a term that is not a parameter. Also, we might try to have a path dependent type in the trailing given parameters of the unapply.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about the example you give in the comment but the code example in #12991 compiles and runs correctly in this branch

@nicolasstucki nicolasstucki force-pushed the special-case-inline-unapplySeq branch from d7849a5 to d0138bf Compare July 8, 2022 14:12
@nicolasstucki
Copy link
Contributor

Rebased and squashed commits

@nicolasstucki nicolasstucki enabled auto-merge July 8, 2022 14:15
@smarter
Copy link
Member

smarter commented Jul 10, 2022

@nicolasstucki enabled auto-merge 2 days ago

In case you didn't notice, the merge is currently blocked because of merge conflicts.

Copy link
Contributor

@nicolasstucki nicolasstucki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

It needed a rebase, but this generated some conflicts with another feature. Moved this into #16358 with the fix for the other issue.

@nicolasstucki
Copy link
Contributor

Thank you @gorilskij

auto-merge was automatically disabled November 17, 2022 08:23

Pull request was closed

nicolasstucki added a commit that referenced this pull request Dec 7, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support String interpolator inline unapply
3 participants