[Git][ghc/ghc][wip/sjakobi/T16836-implicit-field-strictness] Add -Wimplicit-field-strictness (#16836)
Simon Jakobi pushed to branch wip/sjakobi/T16836-implicit-field-strictness at Glasgow Haskell Compiler / GHC Commits: 1dfbf23a by Simon Jakobi at 2026-08-19T23:34:20+02:00 Add -Wimplicit-field-strictness (#16836) This opt-in warning fires when a data constructor field lacks an explicit strictness annotation (! or ~), so that users can insulate themselves against changes to the strictness default, e.g. via StrictData. It complements the LazyFieldAnnotations extension (4762a8bf30f) from GHC proposal 752, which makes ~ annotations available for this purpose. One diagnostic is emitted per data declaration, grouped by constructor. The hint deliberately names both ! and ~ without picking one: the choice is the user's. Newtypes and 'type data' declarations are exempt since strictness annotations are rejected there. Fixes #16836. Assisted-by: Claude Fable 5 - - - - - 16 changed files: - + changelog.d/implicit-field-strictness-warning - compiler/GHC/Driver/Flags.hs - compiler/GHC/Driver/Session.hs - compiler/GHC/Tc/Errors/Ppr.hs - compiler/GHC/Tc/Errors/Types.hs - compiler/GHC/Tc/TyCl.hs - compiler/GHC/Types/Error/Codes.hs - compiler/GHC/Types/Hint.hs - compiler/GHC/Types/Hint/Ppr.hs - docs/users_guide/using-warnings.rst - + testsuite/tests/warnings/should_compile/T16836a.hs - + testsuite/tests/warnings/should_compile/T16836a.stderr - + testsuite/tests/warnings/should_compile/T16836b.hs - + testsuite/tests/warnings/should_compile/T16836c.hs - + testsuite/tests/warnings/should_compile/T16836c.stderr - testsuite/tests/warnings/should_compile/all.T Changes: ===================================== changelog.d/implicit-field-strictness-warning ===================================== @@ -0,0 +1,10 @@ +section: compiler +synopsis: Add `-Wimplicit-field-strictness` +issues: #16836 + +description: { + The new opt-in warning :ghc-flag:`-Wimplicit-field-strictness` reports + data constructor fields that lack an explicit strictness annotation + (``!`` or ``~``). Writing ``~`` requires + :extension:`LazyFieldAnnotations`. +} ===================================== compiler/GHC/Driver/Flags.hs ===================================== @@ -1142,6 +1142,7 @@ data WarningFlag = | Opt_WarnUnrecognisedModifiers -- ^ @since 10.0 | Opt_WarnSemaphoreOpenFailure -- Since 10.0.1 | Opt_WarnDefaultedCallStack -- ^ @since 10.2 + | Opt_WarnImplicitFieldStrictness -- ^ @since 10.2 deriving (Eq, Ord, Show, Enum, Bounded) -- | Return the names of a WarningFlag @@ -1251,6 +1252,7 @@ warnFlagNames wflag = case wflag of Opt_WarnTypeEqualityRequiresOperators -> "type-equality-requires-operators" :| [] Opt_WarnMissingRoleAnnotations -> "missing-role-annotations" :| [] Opt_WarnImplicitRhsQuantification -> "implicit-rhs-quantification" :| [] + Opt_WarnImplicitFieldStrictness -> "implicit-field-strictness" :| [] Opt_WarnIncompleteExportWarnings -> "incomplete-export-warnings" :| [] Opt_WarnIncompleteRecordSelectors -> "incomplete-record-selectors" :| [] Opt_WarnBadlyLevelledTypes -> "badly-levelled-types" :| [] ===================================== compiler/GHC/Driver/Session.hs ===================================== @@ -2449,6 +2449,7 @@ wWarningFlagsDeps = [minBound..maxBound] >>= \x -> case x of Opt_WarnUnrecognisedModifiers -> warnSpec x Opt_WarnSemaphoreOpenFailure -> warnSpec x Opt_WarnDefaultedCallStack -> warnSpec x + Opt_WarnImplicitFieldStrictness -> warnSpec x warningGroupsDeps :: [(Deprecation, FlagSpec WarningGroup)] warningGroupsDeps = map mk warningGroups ===================================== compiler/GHC/Tc/Errors/Ppr.hs ===================================== @@ -1384,6 +1384,24 @@ instance Diagnostic TcRnMessage where hang (text "Missing role annotation" <> colon) 2 (text "type role" <+> ppr name <+> hsep (map ppr roles)) + TcRnImplicitFieldStrictness _name _lazy_anns cons -> mkSimpleDecorated $ + hang (text "Constructor fields without explicit strictness" <> colon) + 2 (vcat (map ppr_con cons)) + where + ppr_con (con, fields) = + bullet <+> text "In" <+> quotes (ppr con) <> colon <+> ppr_fields fields + ppr_fields fields + | let names = concat [ns | ImplicitStrictnessRecField _ ns <- fields] + , not (null names) + = text "field" <> plural names <+> and_list (map (quotes . ppr) names) + | otherwise + = let poss = [i | ImplicitStrictnessPosField _ i <- fields] + in text "the" <+> and_list (map speakNth poss) + <+> text "field" <> plural poss + and_list [] = empty + and_list [x] = x + and_list xs = hsep (punctuate comma (init xs)) <+> text "and" <+> last xs + TcRnIllformedTypePattern p -> mkSimpleDecorated $ hang (text "Ill-formed type pattern:") 2 (ppr p) @@ -2693,6 +2711,8 @@ instance Diagnostic TcRnMessage where -> ErrorWithoutFlag TcRnMissingRoleAnnotation{} -> WarningWithFlag Opt_WarnMissingRoleAnnotations + TcRnImplicitFieldStrictness{} + -> WarningWithFlag Opt_WarnImplicitFieldStrictness TcRnIllegalInvisTyVarBndr{} -> ErrorWithoutFlag TcRnIllegalWildcardTyVarBndr{} @@ -3428,6 +3448,12 @@ instance Diagnostic TcRnMessage where -> noHints TcRnMissingRoleAnnotation{} -> noHints + TcRnImplicitFieldStrictness _ lazy_anns _ + -> SuggestExplicitFieldStrictness + : [ useExtensionInOrderTo + (text "to allow" <+> quotes (char '~') <+> text "annotations") + LangExt.LazyFieldAnnotations + | not lazy_anns ] TcRnIllegalInvisTyVarBndr{} -> [suggestExtension LangExt.TypeAbstractions] TcRnIllegalWildcardTyVarBndr{} ===================================== compiler/GHC/Tc/Errors/Types.hs ===================================== @@ -123,6 +123,7 @@ module GHC.Tc.Errors.Types ( , TypeSyntax(..) , typeSyntaxExtension , SuggestLinear(..) + , ImplicitStrictnessField(..) -- * Errors for hs-boot and signature files , BadBootDecls(..) @@ -4235,6 +4236,24 @@ data TcRnMessage where -} TcRnMissingRoleAnnotation :: Name -> [Role] -> TcRnMessage + + {-| TcRnImplicitFieldStrictness is a warning that occurs when a data + constructor field lacks an explicit strictness annotation (@!@ or @~@) + + Controlled by flags: + - Wimplicit-field-strictness + + Test cases: + T16836a, T16836b + + -} + TcRnImplicitFieldStrictness + :: Name -- ^ the type constructor + -> Bool -- ^ whether @LazyFieldAnnotations@ is enabled + -> [(Name, [ImplicitStrictnessField])] + -- ^ per data constructor, the fields lacking annotations + -> TcRnMessage + {-| TcRnPatersonCondFailure is an error that occurs when an instance declaration fails to conform to the Paterson conditions. Which particular condition fails depends on the constructor of PatersonCondFailure @@ -6399,6 +6418,14 @@ data PatSynInvalidRhsReason | PatSynUnboundVar !Name deriving (Generic) +-- | A constructor field lacking an explicit strictness annotation, as +-- reported by 'TcRnImplicitFieldStrictness'. +data ImplicitStrictnessField + = -- | A record field group @x, y :: ty@ sharing one (absent) annotation + ImplicitStrictnessRecField SrcSpan [RdrName] + | -- | A positional argument (1-based index) + ImplicitStrictnessPosField SrcSpan Int + data BadFieldAnnotationReason where {-| A lazy data type field annotation (~) was used without enabling the extension LazyFieldAnnotations. ===================================== compiler/GHC/Tc/TyCl.hs ===================================== @@ -4023,8 +4023,39 @@ dataDeclChecks tc_name mctxt cons ; is_boot <- tcIsHsBootOrSig -- Are we compiling an hs-boot file? ; unless (not (null cons) || empty_data_decls || is_boot) $ addErrTc (TcRnEmptyDataDeclsDisabled tc_name) + + ; warn_implicit_strictness <- woptM Opt_WarnImplicitFieldStrictness + ; when warn_implicit_strictness $ case cons of + DataTypeCons False data_cons + | let offenders = concatMap conImplicitStrictnessFields data_cons + , not (null offenders) + -> do { lazy_anns <- xoptM LangExt.LazyFieldAnnotations + ; setSrcSpan (getSrcSpan tc_name) $ addDiagnosticTc $ + TcRnImplicitFieldStrictness tc_name lazy_anns offenders } + _ -> return () + ; return gadt_syntax } +conImplicitStrictnessFields :: LConDecl GhcRn -> [(Name, [ImplicitStrictnessField])] +conImplicitStrictnessFields (L _ con) + | null fields = [] + | otherwise = [ (unLoc n, fields) | n <- getConNames con ] + where + fields = case con of + ConDeclH98 { con_args = PrefixCon _ args } -> pos_fields args + ConDeclH98 { con_args = InfixCon _ a1 a2 } -> pos_fields [a1, a2] + ConDeclH98 { con_args = RecCon _ (L _ flds) } -> rec_fields flds + ConDeclGADT { con_g_args = PrefixConGADT _ args } -> pos_fields args + ConDeclGADT { con_g_args = RecConGADT _ (L _ flds) } -> rec_fields flds + + pos_fields args = [ ImplicitStrictnessPosField (getLocA (cdf_type f)) i + | (i, f) <- zip [1 :: Int ..] args + , NoSrcStrict <- [cdf_bang f] ] + rec_fields flds = [ ImplicitStrictnessRecField (getLocA (cdf_type spec)) + [ rdr | L _ (FieldOcc rdr _) <- names ] + | L _ (HsConDeclRecField _ names spec) <- flds + , NoSrcStrict <- [cdf_bang spec] ] + ----------------------------------- data DataDeclInfo ===================================== compiler/GHC/Types/Error/Codes.hs ===================================== @@ -542,6 +542,7 @@ type family GhcDiagnosticCode c = n | n -> c where GhcDiagnosticCode "TcRnNegativeNumTypeLiteral" = 93632 GhcDiagnosticCode "TcRnUnusedQuantifiedTypeVar" = 54180 GhcDiagnosticCode "TcRnMissingRoleAnnotation" = 65490 + GhcDiagnosticCode "TcRnImplicitFieldStrictness" = 47032 GhcDiagnosticCode "TcRnUntickedPromotedThing" = 49957 GhcDiagnosticCode "TcRnIllegalBuiltinSyntax" = 39716 ===================================== compiler/GHC/Types/Hint.hs ===================================== @@ -343,6 +343,14 @@ data GhcHint -} | SuggestAddStandaloneKindSignature Name + {-| Suggests to annotate each constructor field with explicit strictness + (@!@ or @~@), without picking one. + + Triggered by: 'GHC.Tc.Errors.Types.TcRnImplicitFieldStrictness' + Test case(s): warnings/should_compile/T16836a + -} + | SuggestExplicitFieldStrictness + {-| Suggests the user to fill in the wildcard constraint to disambiguate which constraint that is. ===================================== compiler/GHC/Types/Hint/Ppr.hs ===================================== @@ -185,6 +185,9 @@ instance Outputable GhcHint where -> text "Use a standalone deriving declaration instead" SuggestAddStandaloneKindSignature name -> text "Add a standalone kind signature for" <+> quotes (ppr name) + SuggestExplicitFieldStrictness + -> text "Annotate each field with" <+> quotes (char '!') + <+> text "(strict) or" <+> quotes (char '~') <+> text "(lazy)" SuggestFillInWildcardConstraint -> text "Fill in the wildcard constraint yourself" SuggestAppropriateTHTick ns ===================================== docs/users_guide/using-warnings.rst ===================================== @@ -2505,6 +2505,28 @@ of ``-W(no-)*``. In other words the type-class role cannot be accidentally left representational or phantom, which could affected the code correctness. +.. ghc-flag:: -Wimplicit-field-strictness + :shortdesc: warn when constructor fields lack explicit strictness annotations + :type: dynamic + :reverse: -Wno-implicit-field-strictness + :category: + + :since: 10.2.1 + :default: off + + .. index:: + single: strictness annotations, missing + + If you would like GHC to check that every data constructor field carries + an explicit strictness annotation — ``!`` (strict) or ``~`` (lazy) — use + the :ghc-flag:`-Wimplicit-field-strictness` option. It reports one warning + per data declaration, listing the unannotated fields of each constructor. + Writing ``~`` requires :extension:`LazyFieldAnnotations`. + + The warning applies to ``data`` and ``data instance`` declarations, + including GADT syntax. Newtypes and ``type data`` declarations are exempt, + as strictness annotations are rejected there. + .. ghc-flag:: -Wimplicit-rhs-quantification :shortdesc: warn when type variables on the RHS of a type synonym are implicitly quantified :type: dynamic ===================================== testsuite/tests/warnings/should_compile/T16836a.hs ===================================== @@ -0,0 +1,37 @@ +{-# OPTIONS_GHC -Wimplicit-field-strictness #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +module T16836a where + +-- plain multi-constructor data +-- warns for both constructors +data T a = MkT a Bool + | MkT2 !Int a + +-- record with a shared field group +-- warns for x, y and z; not for b +data R = MkR { x, y :: Int, z :: Char, b :: !Bool } + +-- infix constructor +-- warns for the first argument +data I = Int :+: !Bool + +-- GADT syntax +-- warns for the first argument +data G a where + MkG :: Int -> !Bool -> G a + +-- GADT record syntax +-- warns for gx +data GR a where + MkGR :: { gx :: Int, gy :: !Bool } -> GR a + +-- data family instance +-- warns +data family F a +data instance F Int = MkF Char + +-- fully annotated +-- doesn't warn +data S = MkS !Int !Bool ===================================== testsuite/tests/warnings/should_compile/T16836a.stderr ===================================== @@ -0,0 +1,55 @@ +T16836a.hs:9:1: warning: [GHC-47032] [-Wimplicit-field-strictness] + • Constructor fields without explicit strictness: + • In ‘MkT’: the first and second fields + • In ‘MkT2’: the second field + • In the data type declaration for ‘T’ + Suggested fixes: + • Annotate each field with ‘!’ (strict) or ‘~’ (lazy) + • Use the ‘LazyFieldAnnotations’ extension (implied by ‘StrictData’) + to allow ‘~’ annotations + +T16836a.hs:14:1: warning: [GHC-47032] [-Wimplicit-field-strictness] + • Constructor fields without explicit strictness: + • In ‘MkR’: fields ‘x’, ‘y’ and ‘z’ + • In the data type declaration for ‘R’ + Suggested fixes: + • Annotate each field with ‘!’ (strict) or ‘~’ (lazy) + • Use the ‘LazyFieldAnnotations’ extension (implied by ‘StrictData’) + to allow ‘~’ annotations + +T16836a.hs:18:1: warning: [GHC-47032] [-Wimplicit-field-strictness] + • Constructor fields without explicit strictness: + • In ‘:+:’: the first field + • In the data type declaration for ‘I’ + Suggested fixes: + • Annotate each field with ‘!’ (strict) or ‘~’ (lazy) + • Use the ‘LazyFieldAnnotations’ extension (implied by ‘StrictData’) + to allow ‘~’ annotations + +T16836a.hs:22:1: warning: [GHC-47032] [-Wimplicit-field-strictness] + • Constructor fields without explicit strictness: + • In ‘MkG’: the first field + • In the data type declaration for ‘G’ + Suggested fixes: + • Annotate each field with ‘!’ (strict) or ‘~’ (lazy) + • Use the ‘LazyFieldAnnotations’ extension (implied by ‘StrictData’) + to allow ‘~’ annotations + +T16836a.hs:27:1: warning: [GHC-47032] [-Wimplicit-field-strictness] + • Constructor fields without explicit strictness: + • In ‘MkGR’: field ‘gx’ + • In the data type declaration for ‘GR’ + Suggested fixes: + • Annotate each field with ‘!’ (strict) or ‘~’ (lazy) + • Use the ‘LazyFieldAnnotations’ extension (implied by ‘StrictData’) + to allow ‘~’ annotations + +T16836a.hs:32:1: warning: [GHC-47032] [-Wimplicit-field-strictness] + • Constructor fields without explicit strictness: + • In ‘MkF’: the first field + • In the data family instance declaration for ‘F’ + Suggested fixes: + • Annotate each field with ‘!’ (strict) or ‘~’ (lazy) + • Use the ‘LazyFieldAnnotations’ extension (implied by ‘StrictData’) + to allow ‘~’ annotations + ===================================== testsuite/tests/warnings/should_compile/T16836b.hs ===================================== @@ -0,0 +1,25 @@ +{-# OPTIONS_GHC -Wimplicit-field-strictness #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeData #-} +{-# LANGUAGE EmptyDataDecls #-} +{-# LANGUAGE LazyFieldAnnotations #-} +module T16836b where + +-- fully annotated declarations don't warn +data T a = MkT ~a !Bool +data R = MkR { x, y :: !Int, z :: ~Char } +data G a where + MkG :: !Int -> ~Bool -> G a +data family F a +data instance F Int = MkF !Char + +-- newtypes can't have annotations; exempt +newtype N = MkN Int + +-- 'type data' can't have annotations; exempt +type data TD = MkTD Bool + +-- no fields, nothing to annotate +data E +data Nullary = A | B ===================================== testsuite/tests/warnings/should_compile/T16836c.hs ===================================== @@ -0,0 +1,6 @@ +{-# OPTIONS_GHC -Wimplicit-field-strictness #-} +{-# LANGUAGE StrictData #-} +module T16836c where + +-- unannotated fields warn under StrictData too +data T a = MkT a !Bool ~Char ===================================== testsuite/tests/warnings/should_compile/T16836c.stderr ===================================== @@ -0,0 +1,6 @@ +T16836c.hs:6:1: warning: [GHC-47032] [-Wimplicit-field-strictness] + • Constructor fields without explicit strictness: + • In ‘MkT’: the first field + • In the data type declaration for ‘T’ + Suggested fix: Annotate each field with ‘!’ (strict) or ‘~’ (lazy) + ===================================== testsuite/tests/warnings/should_compile/all.T ===================================== @@ -91,3 +91,6 @@ test('T25901_imp_unused_3', [extra_files(['T25901_helper_3.hs'])], multimod_comp test('T25901_imp_unused_4', normal, compile, ['-Wunused-imports']) test('T25901_imp_dodgy_1', [extra_files(['T25901_helper_1.hs'])], multimod_compile, ['T25901_imp_dodgy_1', '-v0 -Wdodgy-imports']) test('T25901_imp_dodgy_2', [extra_files(['T25901_helper_2.hs'])], multimod_compile, ['T25901_imp_dodgy_2', '-v0 -Wdodgy-imports']) +test('T16836a', normal, compile, ['']) +test('T16836b', normal, compile, ['']) +test('T16836c', normal, compile, ['']) View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/1dfbf23afd2e0603e84cb889f9069980... -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/1dfbf23afd2e0603e84cb889f9069980... You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
participants (1)
-
Simon Jakobi (@sjakobi)