Skip to content

Disambiguating record updates in the renamer using as-patterns

I think the following could reasonably be expected to work:

{-# LANGUAGE DuplicateRecordFields #-}
module M where
data A a = MkA { fld :: a }
data B b = MkB { fld :: b }

foo x r@(MkA{}) = r { fld = x }

When renaming the record update on the RHS of foo, the renamer would see r, and look up in some environment to see it is associated with the pattern MkA{} which disambiguates which record field fld refers to.

Currently rnPat doesn't create any kind of environment to keep track of the connection in an as-pattern:

rnPatAndThen mk (AsPat _ rdr at pat)
  = do { new_name <- newPatLName mk rdr
       ; pat' <- rnLPatAndThen mk pat
       ; return (AsPat noExtField new_name at pat') }

whereas the typechecker does:

tc_pat pat_ty penv ps_pat thing_inside = case ps_pat of
  ...
  AsPat x (L nm_loc name) at pat -> do
        { ...
        ; (wrap, bndr_id) <- setSrcSpanA nm_loc (tcPatBndr penv name pat_ty)
        ; (pat', res) <- tcExtendIdEnv1 name bndr_id $
                         tc_lpat (pat_ty `scaledSet`(mkCheckExpType $ idType bndr_id))
                                 penv pat thing_inside
        ; ... }

I think this would be a rather satisfying way to disambiguate record updates, although one shortcoming is that it does not handle sum types very well, as one has to have a separate case for each constructor. We would want something like or-patterns:

data C c = MkC1 { fld1, fld2, fld3 :: c } | MkC2 { fld1, fld2 :: c }
data D d = MkD1 { fld1, fld2 :: d }

-- OK, but requires listing all the constructors
bar1 c1 c2 r@(MkC1{} or MkC2{}) = r { fld1 = c1, fld2 = c2 }

-- Even better, just mention a single constructor!
bar2 c1 c2 r@(MkC1{} or _) = r { fld1 = c1, fld2 = c2 }
Edited by sheaf
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information