Skip to content

Summoning a TupledFunction via summonFrom causes an incoherent type error that's inexplicably fixed by a no-op match-case #24742

@neko-kai

Description

@neko-kai

Compiler version

3.8.0-RC3, 3.7.4

Minimized code

(Scastie: https://scastie.scala-lang.org/by3NpM7vQVWNWlhY2O5Nqw)

//> using scala 3.8.0-RC3
//> using scalacOption -explain

package minimized

import scala.annotation.experimental
import scala.util.TupledFunction

type AnyFunction[+R] = (AnyRef { def tupled: ? => R }) | Function0[R] | Function1[?, R]

inline def nextMacro[A](inline a: AnyRef): A = inline a match { case xa: A => xa }

@experimental
object app {
  inline def untuppledVia_Good[Tup, R](tupFn: Tup => R): AnyFunction[R] = {
    compiletime.summonFrom {
      case untupper: TupledFunction[? <: AnyFunction[R], Tup => R] =>
        val fn = {
          // this match is a no-op, but is required for code to compile
          untupper match {
            case y =>
              y.untupled(tupFn)
          }
        }
        nextMacro[AnyFunction[R]](fn)
    }
  }

  inline def untuppledVia_Bad[Tup, R](tupFn: Tup => R): AnyFunction[R] = {
    compiletime.summonFrom {
      case untupper: TupledFunction[? <: AnyFunction[R], Tup => R] =>
        val fn = {
          // if the match is removed, compilation fails
          untupper.untupled(tupFn)
        }
        nextMacro[AnyFunction[R]](fn)
    }
  }

  @experimental @main def main(): Unit = {
    println(untuppledVia_Good((x: (Int, String, Boolean)) => println(x)))
    println(untuppledVia_Bad((x: (Int, String, Boolean)) => println(x)))
  }
}

Output

Found:    _
Required: (Int, String, Boolean) => Unit
-explain
Explanation
===========

Tree:

untupper.untupled(tupFn$proxy1)

I tried to show that
  _
conforms to
  (Int, String, Boolean) => Unit
but none of the attempts shown below succeeded:

  ==> _  <:  (Int, String, Boolean) => Unit
    ==> minimized.AnyFunction[Unit]  <:  (Int, String, Boolean) => Unit
      ==> AnyRef{def tupled: ? => Unit} | (() => Unit) | (? => Unit)  <:  (Int, String, Boolean) => Unit
        ==> AnyRef{def tupled: ? => Unit} | (() => Unit)  <:  (Int, String, Boolean) => Unit
          ==> AnyRef{def tupled: ? => Unit}  <:  (Int, String, Boolean) => Unit  = false

The tests were made under the empty constraint

  println(untuppledVia((x: (Int, String, Boolean)) => println(x)))

Expectation

Expected untuppledVia_Bad macro to work. Expected the no-op match on untupper to not be required and not affect anything.

Workarounds: Unwrapping val fn = { untupper.untupled(tupFn) }; nextMacro(fn) into untupper.untupled(tupFn) makes untuppledVia_Bad specifically compile, but in the maximized version of this macro I do need to pass the tree further. Note that simply wrapping with identity - identity(untupper.untupled(tupFn)) will also break compilation again.

Note: wildcards are not necessary to reproduce this, failure also happens if match uses a type variable: case untupper: TupledFunction[t, Tup => R]

Note: replacing compiletime.summonFrom with null match causes compilation to succeed

Metadata

Metadata

Assignees

No one assigned

    Labels

    itype:bugstat:needs triageEvery issue needs to have an "area" and "itype" label

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions