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 useDisambiguateRecordFields
to determine thatbaz
refers 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 fieldbaz
because it was compiled in a module withNoFieldSelectors
enabled, 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 seebaz
used 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 thatbaz
must 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.