F# Mentorship Week 2— Contributing to the Compiler

Willie Tetlow
5 min readMar 5, 2018

--

The F# mentorship program is a 6 week community driven event where mentors volunteer to help you learn topics related to the language.

There are many topics you can get guidance on. Like a beginner’s introduction to F#, contributing to open source, or F# for machine learning.

This post is part of a series documenting my experience as a mentee, learning about and contributing to the F# compiler.

Week 2 Summary

  • Beginning Our Contribution
  • The First Two Errors
  • Investigating the Invalid Field Label Error
  • Resolving the Invalid Field Label Error

The Beginning Of Our Contribution

At the second meeting of the F# mentorship program, Avi Avni and I discussed my progress in Week 1 and our next adventure through the compiler!

After gaining some experience of working with the codebase we decided it was time to start looking at our contribution.

The Problem

Vasil Kirichenko’s tweet on Nested Record Modifications

If you have created nested record types in F# and tried to use the Copy and Update Record Expression, you will have come across the issue Vasily Kirichenko mentions above.

Nested Record Copy and Update

Once you start performing copy and updates more than one level deep the code starts to become convoluted.

Using the with keyword does highlight the immutable nature of record types and the issue can be resolved in someway through using functions but F# is known for being succinct and expressive.

If we could reduce the complexity of performing a copy and update, no matter how many levels deep, it would improve productivity and further align Copy and Update Expressions with F#’s language design principles.

The Solution

Nested Record Copy and Update Solution

The solution Avi and I propose is to allow accessing a nested record field from a single Copy and Update Expression.

This is a very useful addition. It greatly reduces the amount of boilerplate code required to update nested record fields. It is explicit in declaring its functionality and succinct in style — writing less code is always nice but only if it is still readable!

I was excited when Avi suggested that this was the feature we worked on together and couldn’t wait to get started!

I was set the task of compiling the new nested Copy and Update Expression and to investigate how to solve the first error.

The First Two Errors

Avi suggested putting a break-point at NameResolution.fs, line 3062.

I updated my test.fs file to contain the code above and started debugging the compiler.

See: Week 1 — Debugging the Compiler.

Line 3062: Function ResolveFieldPrim (Truncated at 3074)

When compiling the new Copy and Update Expression, these errors related to type checking are thrown.

The First Error

Compilation is failing when the function ResolveFieldPrim attempts to resolve the record field access, Address.Street.Name.

Invalid field label

The initial error — Invalid field label — means that the record field access failed because it can’t resolve the Name, back to a field that’s part of the Person record.

This expression was expected…

The second error — This expression was expected to have type — occurs because it thinks the with keyword is being used to copy and update the Address record type. Instead of copy and update the Person record by copying its nested records and updating the Address.Street.Name record field.

A similar error complaining about a Street record being giving instead of an Address type would occur if we wrote the following.

Failing Nested Record Type Access

Accessing nested properties like this, within a Copy and Update Expression, is failing because the compiler can’t resolve access of the record field back to the higher level record type during the type checking stage of compilation.

Investigating the Invalid Field Label Error

Understanding the conditions where an error does and does not occur is the first step in understanding how to solve it.

When writing this post, I created the following example to demonstrate different scenarios where the “This expression was expected…” error would happen.

Failing Nested Record Type Access

However, when compiling it I noticed something. This type of nested record field access does not result in the same “Invalid field label” error!

It appears that if there is only one level of nesting in the new Copy and Update Expression then the error does not occur.

So where is the error thrown?

The error is being thrown from ResolveFieldPrim (NameResolution.fs, ln 3138) when a check against the size of a list is greater than zero.

Invalid field label error being thrown (NameResolution.fs, ln 3138)

rest is populated as part of a function within ResolveFieldPrim called tyconSearch(NameResolution.fs, ln 3096) that resolves record field access from a list of identifiers. The list contains the identifiers that were not used in resolving the record field access.

For example, when resolving Address.Street.Name the rest list contains the identifier Name. The type system has resolved the access as Address.Street not Address.Street.Name.

Depth

It is quite clear from the example above, and the case where the error isn’t thrown, that the cause of this error is depth.

ResolveFieldPrim does not handle the case where a record field access, within a Copy and Update Expression, has a depth greater than one.

But the compiler can already handle nested record access?

F# uses nested record field access for accessing and binding record field values.

let name = person.Address.Street.Name

When compiling this code, the function ResolveExprDotLongIdent is used to build up a list of the record fields being accessed.

It calls into the same function as tyconSearchResolveLongIdentInTyconRefs — to resolve the record fields in the format (Record, Field). For example the let expression above would be represented as.

[(person, Address), (Address, Street), (Street, Name)]

Resolving the Invalid Field Label Error

To update ResolveFieldPrim so it can handle nested record field access, tyconSearch should follow a similar pattern to that used for resolving accessing and binding record field values.

When presented with a list of identifiers greater than two (more than one level of nesting) it should continue to try and resolve the fields instead of returning the rest list straight away.

Updated tyconSearch

The rest list should still be asserted as empty but only after attempting to resolve the list of identifiers. This allows the code to cover the previous case, where an identifier is not part of a valid record field access, and the new nested record field access.

After updating tyconSearch and compiling the test file. It now returns a warning instead of “Invalid field label”.

Warning displayed after updating tyconSearch

ResolveFieldPrim can now resolve nested record field access but the calling code cannot.

Before making any further changes I decided this was a good time to stop and catch-up with Avi.

During my second week with the F# compiler I began investigating the steps required in adding the new nested Copy and Update Expression and updated the first part of code to include this feature. To progress further, the update should be reviewed and the new warning looked at.

I hope I have clearly expressed the work undertaken in evaluating the first error and I can’t wait to write the next post on what followed!

--

--

Willie Tetlow

Senior Frontend Engineer at Shogun, Javascript and Functional Programming.