Skip to content
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

Function Arguments of named tuples can't infer param types despite explicit type #20456

Closed
bishabosha opened this issue May 22, 2024 · 4 comments · Fixed by #20497
Closed

Function Arguments of named tuples can't infer param types despite explicit type #20456

bishabosha opened this issue May 22, 2024 · 4 comments · Fixed by #20497
Labels
area:experimental:named-tuples Issues tied to the named tuples feature. area:typer itype:bug

Comments

@bishabosha
Copy link
Member

bishabosha commented May 22, 2024

Compiler version

3.5.0-RC1

Minimized code

import scala.language.experimental.namedTuples

val m: (foo: String => Int, bar: String => Int) = (foo = _.length, bar = _.length) // error // error
val n: (String => Int, String => Int) = (_.length, _.length)

Output

[error] foo.scala:30:60
[error] Missing parameter type
[error] 
[error] I could not infer the type of the parameter _$5
[error] in expanded function:
[error]   _$5 => _$5.length
[error] Expected type for the whole anonymous function:
[error]   Any
[error]   val m: (foo: String => Int, bar: String => Int) = (foo = _.length, bar = _.length)
[error]                                                            ^
[error] foo.scala:30:76
[error] Missing parameter type
[error] 
[error] I could not infer the type of the parameter _$6
[error] in expanded function:
[error]   _$6 => _$6.length
[error] Expected type for the whole anonymous function:
[error]   Any
[error]   val m: (foo: String => Int, bar: String => Int) = (foo = _.length, bar = _.length)
[error]                                                                            ^

Expectation

correctly infer the arguments, as with normal tuples.

Use Case

I would like to be able to construct programatically a type for a named tuple where all its fields are function types, and then the user should be able to construct this object without explicit parameter types

@bishabosha bishabosha added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels May 22, 2024
@bishabosha bishabosha changed the title Function Arguments of named tuples can't infer arguments despite explicit type Function Arguments of named tuples can't infer param types despite explicit type May 22, 2024
@bishabosha
Copy link
Member Author

bishabosha commented May 22, 2024

This looks like its because of the desugaring - i.e

(foo =  _.length) : (foo: String => Int)

becomes

NamedTuple.withNames(Tuple1(_.length))[Tuple1["foo"]] : (foo: String => Int)
//                          ^^^^^^^^
//                          nested lambda with no expected type

it might be worth reconsidering this desugaring, or perhaps tunnelling through any expected type to the argument of withNames.

A tentative plan - perhaps we can add a type ascription to some "watcher" type, that then is resolved on the outside -

(NamedTuple.withNames(Tuple1(_.length): <watch[target]>)[Tuple1["foo"]] : <target>) : (foo: String => Int)

@bishabosha bishabosha added area:typer area:experimental:named-tuples Issues tied to the named tuples feature. and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels May 27, 2024
@odersky
Copy link
Contributor

odersky commented May 29, 2024

Named tuples are constructed with NamedTuple.withNames:

  extension [V <: Tuple](x: V)
    inline def withNames[N <: Tuple]: NamedTuple[N, V] = x

So the call looks like

NamedTuple.withNames[V](tuple)[N]

It's important to have two type parameter lists because the V is usually inferred but the N needs to be given explicitly. But they don't nesessarily need to come in that order. One could also imagine to build named tuples using a function like this

def build[N <: Tuple]()[V <: Tuple](x: V): NamedTuple[N, V]

Maybe that would fix the problem. I think it might be worth trying out, but I don't have the bandwidth to do it myself right now.

@bishabosha
Copy link
Member Author

bishabosha commented May 29, 2024

This solution seems to work without needing clause interleaving, and leaves no trace after inlining:

object NamedTupleBuilder {
  opaque type Builder[N <: Tuple] = Unit
  def apply[N <: Tuple]: Builder[N] = ()
}
extension [N <: Tuple](inline b: NamedTupleBuilder.Builder[N])
  transparent inline def build[V <: Tuple](inline v: V): NamedTuple[N, V] = v.withNames[N]

then we can have the example here:

val m: (foo: String => Int, bar: String => Int) =
  (foo = _.length, bar = _.length)

desugar to

val m: (foo: String => Int, bar: String => Int) =
  NamedTupleBuilder.apply[("foo", "bar")].build((_.length, _.length))

and it infers perfectly.

here is the tree after typer:

scala> val m: (foo: String => Int, bar: String => Int) = NamedTupleBuilder.apply[("foo", "bar")].build((_.length, _.length))
[[syntax trees at end of                     typer]] // rs$line$5
package <empty> {
  final lazy module val rs$line$5: rs$line$5 = new rs$line$5()
  final module class rs$line$5() extends Object() { this: rs$line$5.type =>
    val m: (foo : String => Int, bar : String => Int) =
      NamedTuple.withNames[(String => Int, String => Int)](
        Tuple2.apply[String => Int, String => Int](
          {
            def $anonfun(_$1: String): Int = _$1.length()
            closure($anonfun)
          },
          {
            def $anonfun(_$2: String): Int = _$2.length()
            closure($anonfun)
          }
        )
      )[(("foo" : String), ("bar" : String))]
  }
}

val m: (foo : String => Int, bar : String => Int) = (rs$line$5$$$Lambda$2022/0x000000012d6005d8@5e08ed28,rs$line$5$$$Lambda$2023/0x000000012d6009a8@24b05292)

@bishabosha
Copy link
Member Author

bishabosha commented May 30, 2024

I have implemented a fix in #20497 - which also includes a variant of https://github.com/bishabosha/ops-mirror that makes use of named tuples to implement server logic

odersky added a commit that referenced this issue May 30, 2024
Adds a new `NamedTuple.build` method which fixes the types of the labels
first, as suggested in
#20456 (comment)

It requires `language.experimental.clauseInterleaving` language import.

Keeps `withNames` as a friendlier option for end-users

fixes #20456
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:experimental:named-tuples Issues tied to the named tuples feature. area:typer itype:bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants