fsharp icon indicating copy to clipboard operation
fsharp copied to clipboard

`[<System.ParamArray>]` parameter in member function causes type check error.

Open muqiuhan opened this issue 1 year ago • 3 comments

When I use the parameter of [<ParamArray>] attribute in member function, I cannot pass the parameter using pipeline operator:

> type X () = member _.ID ([<System.ParamArray>] arr) = arr;;
type X =
  new: unit -> X
  member ID: [<System.ParamArray>] arr: 'a -> 'a

> let x = X();;                                              
val x: X

> x.ID([| 1; 2; 3 |]);;                                      
val it: int array = [|1; 2; 3|]

> [| 1; 2; 3 |] |> x.ID;;

  [| 1; 2; 3 |] |> x.ID;;
  -----------------^^^^
/home/muqiu/stdin(10,18): error FS0001: This expression was expected to have type
    'int array'    
but here has type
    'unit'

And no type check error occurs when the [<ParamArray>] attribute is not used or using [<ParamArray>] at the top level:

> type X () = member _.ID (arr) = arr;;
type X =
  new: unit -> X
  member ID: arr: 'a -> 'a

> let x = X();;                        
val x: X

> [| 1; 2; 3 |] |> x.ID;;
val it: int array = [|1; 2; 3|]

> let ID ([<System.ParamArray>] arr) = arr;;     
val ID: [<System.ParamArray>] arr: 'a -> 'a

> [| 1; 2; 3 |] |> ID;;                     
val it: int array = [|1; 2; 3|]

Why does [<ParamArray>] behave differently in member functions? Is this a compiler bug?

muqiuhan avatar Nov 05 '24 13:11 muqiuhan

Probably a duplicate of https://github.com/dotnet/fsharp/issues/11918.

I think it's a type inference + overload resolution thing: a method with a single parameter annotated with [<ParamArray>] can also be treated as a nullary method (or a method taking a single parameter of type unit).

But there is not perfect symmetry in how overload resolution works for directly-invoked methods and methods used in a first-class way (e.g., with piping). See https://github.com/dotnet/fsharp/issues/11918#issuecomment-894294044.

brianrourkeboll avatar Nov 05 '24 17:11 brianrourkeboll

The error message is confusing, though:

> open System
-
- type T =
-     static member M ([<ParamArray>] xs : int array) = xs
-
- let xs = [|1..10|]
-
- xs |> T.M;;

  xs |> T.M;;
  ------^^^

stdin(8,7): error FS0001: This expression was expected to have type
    'int array'
but here has type
    'unit'

It sounds like the opposite of what the source code looks like.

Especially since this works:

() |> T.M

brianrourkeboll avatar Nov 05 '24 17:11 brianrourkeboll

It is same situation for optional parameters. And this only occurs when the method doesn't have overloads.

type X () = 
  member _.ID ([<System.ParamArray>] arr) = arr
  // another overload
  member _.ID (x: int) = x

  member _.OptionalParameter (?x: int) = x

let x = X()
// will not fail
[| 1; 2; 3 |] |> x.ID
// will fail
1 |> x.OptionalParameter 

There is a comment say the optional and out args are resolved to their default values when the method doesn't have other overloads. Can we change this behavior to make it works like a normal function? https://github.com/dotnet/fsharp/blob/5065f5bcff7498422d782caa538b43d19b492d7b/src/Compiler/Checking/Expressions/CheckExpressions.fs#L9944-L9947

ijklam avatar Apr 25 '25 08:04 ijklam