Skip to content

Implementation of the incomplete record selectors proposal

This MR is an implementation of the proposal #516. It implements the base proposal and the long-range information optional extension. See the tests for examples of the warning. See Note [Detecting incomplete record selectors] in GHC.HsToCore.Expr for details

Note [Detecting incomplete record selectors]

A record selector occurence is incomplete iff. it could fail due to
being applied to a data type constructor not present for this record field.

  data T = T1 | T2 {x :: Int}
  d = x someComputation -- `d` may fail

There are 4 parts to detecting and warning about
incomplete record selectors to consider:

  - Detecting whether a general application of a record field can fail.
    This is stored in the `sel_incomplete` field of `IdDetails` datatype,
    which is a part of an `Id` and calculated when renaming a record selector
    in `mkOneRecordSelector`

  - Emitting a warning whenever a `HasField` constraint is solved.
    This is checked in `matchHasField` and emitted only for when
    the constraint is resolved with an implicit instance rather than a
    custom one (since otherwise the warning will be emitted in
      the custom implementation anyways)

      g :: HasField "x" t Int => t -> Int
      g = getField @"x"

      f :: T -> Int
      f = g -- warning will be emitted here

  - Emitting a warning for a general occurence of the record selector
    This is done during the renaming of a `HsRecSel` expression in `dsExpr`
    and simply pulls the information about incompleteness from the `Id`

      l :: T -> Int
      l a = x a -- warning will be emitted here

  - Emitting a warning for a record selector applied to a variable.
    In that case we want to use the long-range information from the
    pattern match checker to rule out impossible constructors. We first make
    sure that the field selector is a field selector of a data type; if it's a pattern
    synonym selector we go back to the previous case (this makes sure that there aren't
    infinite possibilities for the value of the argument variable). Then, we get a list
    of possible constructors that the argument can be based on the long-range information.
    We then check if any of those cases are pattern synonyms (since we don't know what values
    they can produce) or if they are data constructors without the record field associated
    with the record selector.

      z :: T -> Int
      z T1 = 0
      z a = x a -- warning will not be emitted here since `a` can only be `T2`
Edited by Matthew Pickering

Merge request reports