Simon Jakobi pushed to branch wip/sjakobi/lazy-field-anns at Glasgow Haskell Compiler / GHC
Commits:
-
19e0f321
by Simon Jakobi at 2026-06-24T13:57:54+02:00
16 changed files:
- + changelog.d/lazy-field-annotations
- compiler/GHC/Driver/Flags.hs
- compiler/GHC/Tc/Errors/Ppr.hs
- compiler/GHC/Tc/Errors/Types.hs
- compiler/GHC/Tc/TyCl.hs
- docs/users_guide/exts/strict.rst
- libraries/ghc-internal/src/GHC/Internal/LanguageExtensions.hs
- + testsuite/tests/deSugar/should_run/LazyFieldAnnotationsSemantics.hs
- + testsuite/tests/deSugar/should_run/LazyFieldAnnotationsSemantics.stdout
- testsuite/tests/deSugar/should_run/all.T
- + testsuite/tests/typecheck/should_compile/LazyFieldAnnotations.hs
- testsuite/tests/typecheck/should_compile/all.T
- testsuite/tests/typecheck/should_fail/LazyFieldsDisabled.stderr
- + testsuite/tests/typecheck/should_fail/LazyFieldsDisabledStrictData.hs
- + testsuite/tests/typecheck/should_fail/LazyFieldsDisabledStrictData.stderr
- testsuite/tests/typecheck/should_fail/all.T
Changes:
| 1 | +section: language
|
|
| 2 | +synopsis: Add the `LazyFieldAnnotations` extension
|
|
| 3 | +issues: #24455
|
|
| 4 | +mrs: !16246
|
|
| 5 | + |
|
| 6 | +description: {
|
|
| 7 | + The new :extension:`LazyFieldAnnotations` extension permits the prefix
|
|
| 8 | + ``~`` lazy field annotation on data and GADT constructor fields without
|
|
| 9 | + enabling :extension:`StrictData`. :extension:`StrictData` (and therefore
|
|
| 10 | + :extension:`Strict`) now implies :extension:`LazyFieldAnnotations` and
|
|
| 11 | + continues to control the default strictness of unannotated fields.
|
|
| 12 | +} |
| ... | ... | @@ -261,6 +261,7 @@ extensionName = \case |
| 261 | 261 | LangExt.ExplicitLevelImports -> "ExplicitLevelImports"
|
| 262 | 262 | LangExt.ImplicitStagePersistence -> "ImplicitStagePersistence"
|
| 263 | 263 | LangExt.QualifiedStrings -> "QualifiedStrings"
|
| 264 | + LangExt.LazyFieldAnnotations -> "LazyFieldAnnotations"
|
|
| 264 | 265 | |
| 265 | 266 | -- | Is this extension known by any other names? For example
|
| 266 | 267 | -- -XGeneralizedNewtypeDeriving is accepted
|
| ... | ... | @@ -343,6 +344,7 @@ impliedXFlags |
| 343 | 344 | |
| 344 | 345 | , (LangExt.TemplateHaskell, On LangExt.TemplateHaskellQuotes)
|
| 345 | 346 | , (LangExt.Strict, On LangExt.StrictData)
|
| 347 | + , (LangExt.StrictData, On LangExt.LazyFieldAnnotations)
|
|
| 346 | 348 | |
| 347 | 349 | -- Historically only UnboxedTuples was required for unboxed sums to work.
|
| 348 | 350 | -- To avoid breaking code, we make UnboxedTuples imply UnboxedSums.
|
| ... | ... | @@ -3231,7 +3231,7 @@ instance Diagnostic TcRnMessage where |
| 3231 | 3231 | TcRnHasFieldResolvedIncomplete{}
|
| 3232 | 3232 | -> noHints
|
| 3233 | 3233 | TcRnBadFieldAnnotation _ _ LazyFieldsDisabled
|
| 3234 | - -> [suggestExtension LangExt.StrictData]
|
|
| 3234 | + -> [suggestExtension LangExt.LazyFieldAnnotations]
|
|
| 3235 | 3235 | TcRnBadFieldAnnotation{}
|
| 3236 | 3236 | -> noHints
|
| 3237 | 3237 | TcRnSuperclassCycle{}
|
| ... | ... | @@ -6355,7 +6355,7 @@ data PatSynInvalidRhsReason |
| 6355 | 6355 | |
| 6356 | 6356 | data BadFieldAnnotationReason where
|
| 6357 | 6357 | {-| A lazy data type field annotation (~) was used without enabling the
|
| 6358 | - extension StrictData.
|
|
| 6358 | + extension LazyFieldAnnotations.
|
|
| 6359 | 6359 | |
| 6360 | 6360 | Test cases:
|
| 6361 | 6361 | LazyFieldsDisabled
|
| ... | ... | @@ -5186,7 +5186,7 @@ checkValidDataCon dflags existential_ok tc con |
| 5186 | 5186 | ; let check_bang :: Type -> HsSrcBang -> HsImplBang -> Int -> TcM ()
|
| 5187 | 5187 | check_bang orig_arg_ty bang rep_bang n
|
| 5188 | 5188 | | HsSrcBang _ _ SrcLazy <- bang
|
| 5189 | - , not (bang_opt_strict_data bang_opts)
|
|
| 5189 | + , not (xopt LangExt.LazyFieldAnnotations dflags)
|
|
| 5190 | 5190 | = addErrTc (bad_bang n LazyFieldsDisabled)
|
| 5191 | 5191 | |
| 5192 | 5192 | -- Warn about UNPACK without "!"
|
| ... | ... | @@ -155,6 +155,44 @@ Note the following points: |
| 155 | 155 | for more discussion and examples.
|
| 156 | 156 | |
| 157 | 157 | |
| 158 | +.. _lazy-field-annotations:
|
|
| 159 | + |
|
| 160 | +Lazy field annotations
|
|
| 161 | +----------------------
|
|
| 162 | + |
|
| 163 | +.. extension:: LazyFieldAnnotations
|
|
| 164 | + :shortdesc: Allow the prefix ``~`` lazy field annotation on constructor fields.
|
|
| 165 | + |
|
| 166 | + :since: 10.2.1
|
|
| 167 | + |
|
| 168 | + Allow the prefix ``~`` lazy field annotation on data and GADT constructor
|
|
| 169 | + fields.
|
|
| 170 | + |
|
| 171 | +``LazyFieldAnnotations`` allows a prefix ``~`` lazy annotation in every
|
|
| 172 | +constructor-field position where a prefix ``!`` strict annotation is accepted:
|
|
| 173 | +Haskell-98 prefix and record fields, and GADT argument types and record
|
|
| 174 | +fields. ::
|
|
| 175 | + |
|
| 176 | + {-# LANGUAGE LazyFieldAnnotations #-}
|
|
| 177 | + |
|
| 178 | + data A = A ~Int Bool
|
|
| 179 | + data B = B { b1 :: ~Int, b2 :: !Bool }
|
|
| 180 | + |
|
| 181 | + data C where
|
|
| 182 | + C1 :: ~Int -> C
|
|
| 183 | + C2 :: { c1 :: ~Int, c2 :: !Bool } -> C
|
|
| 184 | + |
|
| 185 | +A field annotated with ``~`` is lazy: it is not forced when the constructor is
|
|
| 186 | +built, but held as a thunk — the same behaviour as an unannotated field.
|
|
| 187 | + |
|
| 188 | +The ``~`` annotation must be written in prefix form::
|
|
| 189 | + |
|
| 190 | + data T = MkT ~Int -- valid
|
|
| 191 | + data T = MkT ~ Int -- invalid
|
|
| 192 | + |
|
| 193 | +See `GHC Proposal #229 <https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0229-whitespace-bang-patterns.rst>`__
|
|
| 194 | +for the precise rules.
|
|
| 195 | + |
|
| 158 | 196 | .. _strict-data:
|
| 159 | 197 | |
| 160 | 198 | Strict-by-default data types
|
| ... | ... | @@ -163,6 +201,8 @@ Strict-by-default data types |
| 163 | 201 | .. extension:: StrictData
|
| 164 | 202 | :shortdesc: Treat datatype fields as strict by default.
|
| 165 | 203 | |
| 204 | + :implies: :extension:`LazyFieldAnnotations`
|
|
| 205 | + |
|
| 166 | 206 | :since: 8.0.1
|
| 167 | 207 | |
| 168 | 208 | Make fields of data types defined in the current module strict by default.
|
| ... | ... | @@ -183,13 +223,8 @@ we interpret it as if they had written :: |
| 183 | 223 | |
| 184 | 224 | The extension only affects definitions in this module.
|
| 185 | 225 | |
| 186 | -The ``~`` annotation must be written in prefix form::
|
|
| 187 | - |
|
| 188 | - data T = MkT ~Int -- valid
|
|
| 189 | - data T = MkT ~ Int -- invalid
|
|
| 190 | - |
|
| 191 | -See `GHC Proposal #229 <https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0229-whitespace-bang-patterns.rst>`__
|
|
| 192 | -for the precise rules.
|
|
| 226 | +The ``~`` lazy field annotation is provided by :extension:`LazyFieldAnnotations`
|
|
| 227 | +(which ``StrictData`` implies); see :ref:`lazy-field-annotations`.
|
|
| 193 | 228 | |
| 194 | 229 | .. _strict:
|
| 195 | 230 |
| ... | ... | @@ -169,6 +169,7 @@ data Extension |
| 169 | 169 | | ExplicitLevelImports
|
| 170 | 170 | | ImplicitStagePersistence
|
| 171 | 171 | | QualifiedStrings
|
| 172 | + | LazyFieldAnnotations
|
|
| 172 | 173 | deriving (Eq, Enum, Show, Generic, Bounded)
|
| 173 | 174 | -- 'Ord' and 'Bounded' are provided for GHC API users (see discussions
|
| 174 | 175 | -- in https://gitlab.haskell.org/ghc/ghc/merge_requests/2707 and
|
| 1 | +{-# LANGUAGE LazyFieldAnnotations #-}
|
|
| 2 | + |
|
| 3 | +-- | LazyFieldAnnotations does not change the default strictness of fields.
|
|
| 4 | +-- Without StrictData an unannotated field stays lazy and a ~ field is lazy
|
|
| 5 | +-- too; a ! field is still strict.
|
|
| 6 | +module Main where
|
|
| 7 | + |
|
| 8 | +import Control.Exception (try, evaluate, SomeException)
|
|
| 9 | + |
|
| 10 | +data T = Plain Int -- unannotated: lazy
|
|
| 11 | + | Lazy ~Int -- explicit ~: lazy
|
|
| 12 | + | Strict !Int -- explicit !: strict
|
|
| 13 | + |
|
| 14 | +-- | Does building the constructor force the field?
|
|
| 15 | +forcesField :: T -> IO Bool
|
|
| 16 | +forcesField x = do
|
|
| 17 | + r <- try (evaluate x) :: IO (Either SomeException T)
|
|
| 18 | + pure (either (const True) (const False) r)
|
|
| 19 | + |
|
| 20 | +main :: IO ()
|
|
| 21 | +main = do
|
|
| 22 | + print =<< forcesField (Plain undefined) -- False: lazy
|
|
| 23 | + print =<< forcesField (Lazy undefined) -- False: lazy
|
|
| 24 | + print =<< forcesField (Strict undefined) -- True: strict |
| 1 | +False
|
|
| 2 | +False
|
|
| 3 | +True |
| ... | ... | @@ -76,3 +76,5 @@ test('T19680A', normal, compile_and_run, ['']) |
| 76 | 76 | test('T20024', exit_code(1), compile_and_run, [''])
|
| 77 | 77 | |
| 78 | 78 | test('Or5', exit_code(1), compile_and_run, [''])
|
| 79 | + |
|
| 80 | +test('LazyFieldAnnotationsSemantics', normal, compile_and_run, ['']) |
| 1 | +{-# LANGUAGE LazyFieldAnnotations #-}
|
|
| 2 | +{-# LANGUAGE GADTs #-}
|
|
| 3 | + |
|
| 4 | +-- | LazyFieldAnnotations alone accepts the prefix ~ annotation in every
|
|
| 5 | +-- constructor-field position, without enabling StrictData.
|
|
| 6 | +module LazyFieldAnnotations where
|
|
| 7 | + |
|
| 8 | +-- Haskell-98 prefix and record fields
|
|
| 9 | +data A = A ~Int Bool
|
|
| 10 | +data B = B { b1 :: ~Int, b2 :: !Bool }
|
|
| 11 | + |
|
| 12 | +-- GADT argument types and record fields
|
|
| 13 | +data C where
|
|
| 14 | + C1 :: ~Int -> C
|
|
| 15 | + C2 :: { c1 :: ~Int, c2 :: !Bool } -> C |
| ... | ... | @@ -967,4 +967,5 @@ test('T26805a', normal, compile, ['']) |
| 967 | 967 | test('T24464', normal, compile, [''])
|
| 968 | 968 | test('ExpansionQLIm', normal, compile, [''])
|
| 969 | 969 | test('T23135', normal, compile, [''])
|
| 970 | +test('LazyFieldAnnotations', normal, compile, [''])
|
|
| 970 | 971 |
| 1 | - |
|
| 2 | 1 | LazyFieldsDisabled.hs:3:10: error: [GHC-81601]
|
| 3 | 2 | • Lazy field annotations (~) are disabled
|
| 4 | 3 | on the first argument of ‘A’
|
| 5 | 4 | • In the definition of data constructor ‘A’
|
| 6 | 5 | In the data type declaration for ‘A’
|
| 7 | 6 | Suggested fix:
|
| 8 | - Perhaps you intended to use the ‘StrictData’ extension (implied by ‘Strict’) |
|
| 7 | + Perhaps you intended to use the ‘LazyFieldAnnotations’ extension (implied by ‘StrictData’)
|
|
| 8 | + |
| 1 | +{-# LANGUAGE StrictData #-}
|
|
| 2 | +{-# LANGUAGE NoLazyFieldAnnotations #-}
|
|
| 3 | + |
|
| 4 | +-- | A later NoLazyFieldAnnotations overrides the implication from StrictData,
|
|
| 5 | +-- so ~ is rejected even though unannotated fields remain strict.
|
|
| 6 | +module LazyFieldsDisabledStrictData where
|
|
| 7 | + |
|
| 8 | +data A = A ~Int |
| 1 | +LazyFieldsDisabledStrictData.hs:8:10: error: [GHC-81601]
|
|
| 2 | + • Lazy field annotations (~) are disabled
|
|
| 3 | + on the first argument of ‘A’
|
|
| 4 | + • In the definition of data constructor ‘A’
|
|
| 5 | + In the data type declaration for ‘A’
|
|
| 6 | + Suggested fix:
|
|
| 7 | + Perhaps you intended to use the ‘LazyFieldAnnotations’ extension (implied by ‘StrictData’)
|
|
| 8 | + |
| ... | ... | @@ -690,6 +690,7 @@ test('T21444', normal, compile_fail, ['']) |
| 690 | 690 | test('T23308', normal, compile_fail, [''])
|
| 691 | 691 | test('MultiAssocDefaults', normal, compile_fail, [''])
|
| 692 | 692 | test('LazyFieldsDisabled', normal, compile_fail, [''])
|
| 693 | +test('LazyFieldsDisabledStrictData', normal, compile_fail, [''])
|
|
| 693 | 694 | test('TyfamsDisabled', normal, compile_fail, [''])
|
| 694 | 695 | test('CommonFieldResultTypeMismatch', normal, compile_fail, [''])
|
| 695 | 696 | test('CommonFieldTypeMismatch', normal, compile_fail, [''])
|