Overloaded record fields: implementation notes

Here be dragons. This page describes implementation details and progress on the implementation of the overloaded record fields plan. Development of the extension is taking place on forks of the ​ghc and ​packages-base repositories (on branch 'overloaded-record-fields').

The basic idea

Typechecking a record datatype still generates record selectors, but their names have a $sel prefix and end with the name of their type. Thus

data T = MkT { x :: Int }

generates

$sel_x_T :: T -> Int -- record selector (used to be called `x`)

The naming of cats

The AvailTC Name [Name] [OccName] constructor of AvailInfo represents a type and its pieces that are in scope. Record fields are now stored in a separate list (the third argument). Every field should have a corresponding selector (in the second argument). Since we have the name of the type (the first argument), we can find the selector corresponding to a field. Similar changes are required to the IEThingWith constructor of IE, which represents a thing that can be imported or exported.

The Parent type has an extra constructor FldParent Name OccName that stores the parent Name and the field OccName. The GlobalRdrElt (GRE) for a field stores the selector name directly, and uses the FldParent constructor to store the field. Thus a field foo of type T gives rise to two entries in the GlobalRdrEnv:

Note that the OccName used when adding a GRE to the environment now depends on the parent field: for FldParent it is the field rather than the selector name.

The dcFields field of DataCon stores a list of

type FieldLabel = (OccName, Name)

where the first component is the field and the second is the selector function.

Where an AST representation type (e.g. HsRecField or ConDeclField) contained an argument of type Located id for a field, it now stores a Located RdrName for the label and Maybe id for the selector. The parser supplies Nothing for the selector; it is filled in by the renamer (by rnHsRecFields1 in RnPat, and rnField in RnTypes). Partial functions are provided to extract the Located id, but they will panic if called on not-yet-renamed syntax.

Source expressions

The HsExpr type has extra constructors HsOverloadedRecFld OccName and HsSingleRecFld OccName id. When -XOverloadedRecordFields is enabled, and rnExpr encounters HsVar "x" where x refers to multiple GREs that are all record fields, it replaces it with HsOverloadedRecFld "x". When the typechecker sees HsOverloadedRecFld x it emits a wanted constraint Has alpha x beta and returns type alpha -> beta where alpha and beta are fresh unification variables.

When the flag is not enabled, rnExpr turns an unambiguous record field foo into HsSingleRecFld foo $sel_foo_T. The point of this constructor is so we can pretty-print the field name but store the selector name for typechecking.

Automatic instance generation

Has instances are generated, provided the extension is enabled, in tcInstDecls1 (the same time as derived instances (from deriving clauses) are generated). Every record field GRE in scope gives rise to an instance. Such instances are available when typechecking the current module (in tcg_inst_env) but not exported to other modules (via tcg_insts). At the moment, fresh DFunIds are generated for all instances in scope, and they are not exported in interface files, but perhaps this should change?