Giving good error messages is very important for modern compilers. The language Elm is famous for giving excellent user-friendly error messages. Parts of the F# community decided to improve the F# compiler messages as well. We want to make that a community effort and hope many people join us and help to make the compiler emit better error messages.  Isaac Abraham is mainatining a list with possible targets, so if you are interested in helping then take a look at this list.
Unfortunately working on compilers it’s not exactly the easiest task. In this post I want to show you how I tried to improve one compiler warning and hope this helps others to get started.
The task for this post is to improve the compiler warning for the following code:
Currently the compiler shows the following:
According to issue #1109Â we want to turn it into:
As you can see the new message is giving the user more details about the specific situation and also refers to a common error of newcomers.
Please notice that this is probably not the final wording for this warning, but once we have finished this it will be easy to iterate on the concrete warning.
Getting started
The first thing to is to get the compiler code on your machine. I’m using Windows and Visual Studio 2015 here. So what you want to do is the following:
- Fork https://github.com/Microsoft/visualfsharp on github
- Start a “Developer command prompt for Visual Studio 2015” in admin mode
- Run `git clone [your clone url]` and change into the new directory
- Run `git remote add upstream https://github.com/Microsoft/visualfsharp.git`
- Optionally: If you want to follow the exact steps from this post, then it makes sense to set your clone to the same commit where I started. You can do that by executing “git reset –hard 8a2e393999f440ea93769a288c37172d98db455a”
- Run `build.cmd`
This last step will download the internet for you, so don’t be too surprised when you find the lastest “Game of Thrones” episode somewhere inside your packages folder.
Anyways, if everything works then the output should look like the following:
If the build doesn’t work for you then read the DEVGUIDE very very carefully. If that doesn’t help then please open an issue in the Visual F# issue tracker.
Reproducing the compiler warning
If the build works from command line we should try to reproduce the error with our own freshly built compiler. Let’s open the solution in Visual Studio 2015, set the startup project to “FsiAnyCPU” and hit F5 to start a debug session in Visual Studio. If everything works we will see a F# Interactive Window and Visual Studio is in Debug mode:
Now we paste our code into the command line window:
Using the F# Interactive in debug mode is an easy way to do compiler debugging. There are other possibilities (like using fsc.exe), but the F# Interactive allows you to debug the compilation of single lines in isolation from the code above. It’s really neat.
Where to start with hacking
Since we now know how to reproduce the compiler warning, we want to make actual code changes. But where!?
An easy way to get started is by using the old error message as a search string. When we use Visual Studio’s solution-wide search and look for “This expression should have type” we find exactly two locations inside a file called FSStrings.resx file.
So this gives us a new search string “UnitTypeExpected1”.
This looks actually like we found something interesting in CompileOps.fs. Let set a breakpoint and see if it stops when we reproduce our warning:
Cool, so this seems to be the place where the warning gets emitted. Now we need to find the place where the discriminated union case “UnitTypeExpected” is created. After another little search we are in TypeChecker.fs:
We can try to set another breakpoint and see what happens in that code. The interesting part is the inner else part.
The code in there looks scary and cryptic at first, but it actually shows the power of using a ML dialect for building compilers. Inside this code the compiler already knows that we want to emit a warning. By pattern matching on the abstract syntax tree (AST) we decide if the left side of the expression is a property. In that case the compiler seem to emit a different warning (see also that code above from CompileOps.fs).
So after hitting the breakpoint and looking in the value of exprOpt I’m pretty sure we are in the right place:
So somewhere inside that AST we can actually find our name x. Now we have all we need.
Making the code changes
It’s good practice to never make code changes on the master branch. So let’s create a feature branch with “git checkout -b assignment-warning”.
Now let’s start by adding our new compiler warning to FSStrings.resx:
Now we go back to TypeChecker.fs and try to come up with some pattern matching on the AST that finds our special case. After playing around a bit I found:
This doesn’t look super nice, but it follows one of the important rules from the contributor guide – “use similar style to nearby existing code”. We probably want to tidy this up later when initial code review is done. But in the beginning we should stay as close to the original as possible.
Since we decided to store additional data, this already tells us we need to extent the UnitTypeExpected type in TypeChecker.fsi and TypeChecker.fs. After that we can go to CompileOps.fs and use that data:
That’s basically all we need to do. Let’s fix the the remaining errors (don’t worry the compiler will show you all the places) and then reproduce the warning again:
Wow. We did it. We actually changed the compiler. And it didn’t even hurt that much.
Unit tests
Compiler tests (especially if you want to tests negative results like warnings or exceptions) can’t be easily done in standard unit tests. The F# team came up with a different approach where the test runner compiles a .fs file and you can use regular expressions to match against the compiler output.
In order to run the existing test suite call “build.cmd all”.
This will take a while, but after all tests are done we should see:
So good news is we didn’t break anything, but the bad news is that we don’t have tests covering our case. So let’s add one.
We create tests/fsharpqa/Source/Warnings/WarnIfPossibleAssignment.fs:
Now we need to include the test in a test suite. Since there isn’t a good fitting existing suite we need to create tests/fsharpqa/Source/Warnings/env.lst:
And we need to register this new suite in /tests/fsharpqa/Source/test.lst:
Now we can run the test with “src\RunTests.cmd release fsharpqa Misc01” (more details can be found in the TESTGUIDE):
So at least we now know that our test gets executed 😉
The concrete test results can be found in a file called 258,259FSharpQA_Failures.log alongside our WarnIfPossibleAssignment.fs:
So it seems we cannot match the same line twice. For simplicitly of this blog post we will go with a reduced test:
Wrapping it up
After we ran “build.cmd all” again it’s now time for the final phase. Commit the changes locally and use “git push origin assignment-warning” to publish your code.
Now go to the Visual F# GitHub repo and create a pull request with an explanation of what you did and what you intended to fix. Often it’s also useful to include one or two screenshots.
Your PR will then go through code review process. Usually you will get a lot of questions and probably you need to change some things in your proposal. This process can take a while so bring a bit of patience.
Anyways, here is my PR for this change – wish me