DisambiguateRecordFields should cover record updates
I've been working on cleaning up the implementation of the NoFieldSelectors proposal (see !4017 (comment 314422)) and have come across an under-specified corner of the design.
The proposal implies this should be allowed (I've slightly expanded on an example from the proposal, to make the point):
{-# LANGUAGE NoFieldSelectors #-}
module C (Foo(Foo, bar, baz), baz) where
data Foo = Foo { bar :: Int, baz :: Int }
baz = 42
{-# LANGUAGE DisambiguateRecordFields #-}
module M where
import C
foo = Foo { bar = 23, baz = 1 }
x = baz
y = foo { baz = baz }
Note that baz is in scope as both a field and an integer value. Unfortunately the proposal does not clearly specify how name resolution works in such cases. And name resolution in M is subtle:
- In the definition of
foo, we can useDisambiguateRecordFieldsto determine thatbazrefers to the field of the data constructorFoo. This works today, even if there are many fields in scope calledbaz. - In the definition of
x, we ignore the fieldbazbecause it was compiled in a module withNoFieldSelectorsenabled, so this must be referring to the integer value. This is new behaviour, but is clearly intended by the specification ofNoFieldSelectors. We can implement it by ignoring such fields when looking up variables in expressions. No language extension is required at the use site, because the field is simply not in scope as a function. - In the definition of
y, in the record update, we seebazused as a field and as an expression. Should this be accepted? The use as an expression clearly refers to the integer value, just as withx. But what about the use as a field? We could make use of the fact thatbazmust be a field to ignore all non-fields in scope. However, this would be an extension to existing GHC Haskell (it is not part ofDisambiguateRecordFields).
The situation described in the last bullet can arise today, even without NoFieldSelectors:
module A where
f = ()
{-# LANGUAGE DisambiguateRecordFields #-}
module B where
import A
data T = MkT { f :: Int }
t = MkT { f = 1 } -- accepted (with DisambiguateRecordFields) today
t' = t { f = 2 } -- rejected today, f is ambiguous
My question is: should we change GHC to accept record updates like y and t', where we can disambiguate which name is meant merely by ignoring non-fields? If so, should this be a change to DisambiguateRecordFields or another extension? It is implied by the NoFieldSelectors proposal, but I would find it strange if NoFieldSelectors itself changed the rules on renaming record updates.