dotnet / fsharp

The F# compiler, F# core library, F# language service, and F# tooling integration for Visual Studio
https://dotnet.microsoft.com/languages/fsharp
MIT License
3.92k stars 786 forks source link

Improve error reporting: Function accidentally partially applied #1107

Open isaacabraham opened 8 years ago

isaacabraham commented 8 years ago

What

Oftentimes, you may add a curried argument to a function after the fact, or called a curried function and simply forgot the last argument. All callsites will break (correct) but the error is generally not what you would expect. Look at the following example: -

let foo a b = a + b
let c = foo 10
let d = c + 10

The error returned is error FS0001: The type 'int' does not match the type 'int -> int'

Why

New developers to F# may be unfamiliar with the concept of pervasive type inference and / or of passing functions as values and / or of partially applied functions. The error message does not make entirely clear why this error is occurring.

How

This makes sense, but again, to a new developer, it's completely confusing. A preferred error may be something like: -

The value 'c' is a partially applied function with signature 'int -> int' and is not compatible with 10. Did you forget to provide all the partial arguments when creating 'c'?

If it is not easily possible from the AST to understand that this is a partially applied function (entirely possible), it should still be possible to make a clearer message saying you are trying to do something across a function and a simple value.

smoothdeveloper commented 8 years ago

The value 'c' is a partially applied function with signature 'int -> int' and is not compatible with 'operator (+) int -> int -> int'. Did you forget to provide missing arguments to call 'c'?

isaacabraham commented 8 years ago

@smoothdeveloper what would that look like if you were passing in to another function e.g. bar c

where bar has signature int -> unit

Another example related to this is: -

let foo a b = a + b
let c = foo 10

which gives the following error message (!) error FS0001: This expression was expected to have type int but here has type 'a -> 'c

smoothdeveloper commented 8 years ago

@isaacabraham, I think the message could say something like:

The value 'bar' expects a value of type 'int' but was given 'c' which has a value of type 'int -> int'

isaacabraham commented 8 years ago

I think something it's important to add something about the "did you forget to provide all arguments to the partial function" though - explaining that the types don't match is good, but we want to go further than that and show them where the underlying issue was most likely caused - in other words, to avoid them having to follow the breadcrumbs themselves.

dsyme commented 8 years ago

@isaacabraham I suspect partial application is a red-herring here - you would want to improve the error message whenever doing something like f + 1 where f is not given enough arguments.

I'm also saying that because tracking whether a function type arises from partial application is not something done in the compiler today. Adding that tracking would be a bit invasive on the compiler codebase. Sometimes you have to do it to get better error messages, but at first it is better to try to work using the information we have (a function type, and perhaps an expression f).

isaacabraham commented 8 years ago

@dsyme Yep. I'm not suggesting massive changes to the compiler (maybe when I get it building locally I'll be able to say more...) - I think all of these error message changes should be fairly low hanging fruits in terms of effort to implement and benefit to end users.

isaacabraham commented 3 years ago

@cartermp do you think the stuff you've been doing in VS addresses this in some way?

cartermp commented 3 years ago

None of the recent stuff that's shipping, no. This draft PR does help things I think, since it mentions the function name which can be a little more of a clue: https://github.com/dotnet/fsharp/pull/11121

But it doesn't really have any special wording for when it's a function type. That might be tricky to pull off. The whole PR is quite tricky actually, I will likely work on it more when I get the time. Or if I can nerd snipe @dsyme into galaxy-braining it into completion :)

isaacabraham commented 3 years ago

Maybe I'm confusing this was another one - the one where you don't put parens around arguments or something :-)

KathleenDollard commented 3 years ago

Let's see if we can get the right language so we can do this!!!

let foo a b = a + b
let c = foo 10
let d = c + 10
let e = (foo 10) + 10
let f = (foo 10 10) + c
let g = (foo 10) + (foo 10) // different error

I see two problems here: We don't warn on what may be an accidental partial application and that our message for insufficient arguments is confusing. I see these as separate because they can appear on different lines quite far apart in the code (c and d above) or in a single line. Because partial application is perfectly valid and wonderful, we don't want an error or warning where c is declared.

A few thoughts:

If we decide to do this, it's a separate issue, but I'm including it here because if we increase the likelihood a user (even a beginner) can spot the partial application, it takes a little pressure off this message.

In the current message, it isn't what was expected and what was inferred. And technically, we aren't expecting a type but an item of that type. Maybe:

The wording for g isn't strictly for this issue, but I think the text should closely parallel.

Looking forward to your thoughts!

isaacabraham commented 2 years ago

I think we should omit use of the word 'val' as much as possible - it's confusing to lots of people. Either remove it completely or replace with something more verbose eg "A value of type" etc

smoothdeveloper commented 2 years ago

image

When I hover on the squiggled + maybe the error message should say:

argument x expects value of typeintbut a function with signature int -> int was provided instead. Are you missing parameter application to a partial function?

The second sentence in that message would only trigger if there is a match between the function output type and the parameter, otherwise the message remains the first sentence only.