[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: d156d887 by Simon Jakobi at 2026-08-23T13:54:10+02:00 Add -Wimplicit-field-strictness (#16836) This opt-in warning fires when a data constructor field lacks an explicit strictness annotation (`!` or `~`). It complements the LazyFieldAnnotations extension (4762a8bf30f) from GHC proposal 752, which makes `~` annotations available for this purpose. Deciding which fields to report requires their levity, so the check runs after typechecking. To keep the noise down, the diagnostic is emitted once per data declaration, grouped by constructor. Closes #16836. Assisted-by: Claude Fable 5 - - - - - 20 changed files: - + changelog.d/implicit-field-strictness-warning - changelog.d/lazy-field-annotations - 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/exts/strict.rst - 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/T16836d.hs - + testsuite/tests/warnings/should_compile/T16836d.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 +mrs: !16555 + +description: { + The new opt-in warning :ghc-flag:`-Wimplicit-field-strictness` reports + data constructor fields that lack an explicit strictness annotation + (``!`` or ``~``). +} ===================================== changelog.d/lazy-field-annotations ===================================== @@ -11,4 +11,7 @@ description: { continues to control the default strictness of unannotated fields. See `GHC Proposal #752 <https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0752-lazy-field-annotations.rst>`_. + + Also note the new opt-in :ghc-flag:`-Wimplicit-field-strictness` warning, which + reports fields lacking an explicit annotation. } ===================================== 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,21 @@ instance Diagnostic TcRnMessage where hang (text "Missing role annotation" <> colon) 2 (text "type role" <+> ppr name <+> hsep (map ppr roles)) + TcRnImplicitFieldStrictness _lazy_anns cons -> mkSimpleDecorated $ + hang (text "These constructor fields lack an explicit strictness annotation" <> colon) + 2 (vcat (map ppr_con (NE.toList cons))) + where + ppr_con (con, fields) = + bullet <+> text "In" <+> quotes (ppr con) <> colon <+> ppr_fields (NE.toList fields) + ppr_fields fields + | let names = [n | ImplicitStrictnessRecField n <- fields] + , not (null names) + = text "field" <> plural names <+> quotedListWithAnd (map ppr names) + | otherwise + = let poss = [i | ImplicitStrictnessPosField i <- fields] + in text "field" <> plural poss + <+> unquotedListWith (text "and") (map int poss) + TcRnIllformedTypePattern p -> mkSimpleDecorated $ hang (text "Ill-formed type pattern:") 2 (ppr p) @@ -2693,6 +2708,8 @@ instance Diagnostic TcRnMessage where -> ErrorWithoutFlag TcRnMissingRoleAnnotation{} -> WarningWithFlag Opt_WarnMissingRoleAnnotations + TcRnImplicitFieldStrictness{} + -> WarningWithFlag Opt_WarnImplicitFieldStrictness TcRnIllegalInvisTyVarBndr{} -> ErrorWithoutFlag TcRnIllegalWildcardTyVarBndr{} @@ -3428,6 +3445,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,23 @@ 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, T16836c, T16836d + + -} + TcRnImplicitFieldStrictness + :: Bool -- ^ whether @LazyFieldAnnotations@ is enabled + -> NonEmpty (Name, NonEmpty 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 +6417,14 @@ data PatSynInvalidRhsReason | PatSynUnboundVar !Name deriving (Generic) +-- | A constructor field lacking an explicit strictness annotation, as +-- reported by 'TcRnImplicitFieldStrictness'. +data ImplicitStrictnessField + -- | A record field + = ImplicitStrictnessRecField FieldLabelString + -- | A positional argument (1-based index) + | ImplicitStrictnessPosField Int + data BadFieldAnnotationReason where {-| A lazy data type field annotation (~) was used without enabling the extension LazyFieldAnnotations. ===================================== compiler/GHC/Tc/TyCl.hs ===================================== @@ -5022,6 +5022,16 @@ checkValidTyCon tc ; mapM_ (checkValidDataCon dflags ex_ok tc) data_cons ; mapM_ (checkPartialRecordField data_cons) (tyConFieldLabels tc) + ; warn_implicit_strictness <- woptM Opt_WarnImplicitFieldStrictness + ; when (warn_implicit_strictness + && not (isNewTyCon tc) + && not (isTypeDataTyCon tc)) $ + whenIsJust (NE.nonEmpty (mapMaybe conImplicitStrictnessFields data_cons)) $ + \offenders -> + do { lazy_anns <- xoptM LangExt.LazyFieldAnnotations + ; addDiagnosticTc $ + TcRnImplicitFieldStrictness lazy_anns offenders } + -- Check that fields with the same name share a type ; mapM_ check_fields groups }} where @@ -5072,6 +5082,29 @@ checkValidTyCon tc res2 = dataConOrigResTy con2 fty2 = dataConFieldType con2 lbl +-- | For a given data constructor, collect the fields to report for +-- @-Wimplicit-field-strictness@. +-- +-- Only fields whose type is known to be lifted are collected: unlifted +-- fields are unconditionally strict, and annotating one with @!@ or +-- @~@ would trigger @-Wredundant-strictness-flags@. +conImplicitStrictnessFields :: DataCon -> Maybe (Name, NonEmpty ImplicitStrictnessField) +conImplicitStrictnessFields con + | Just ne_fields <- NE.nonEmpty fields + = Just (dataConName con, ne_fields) + | otherwise + = Nothing + where + fld_refs = case dataConFieldLabels con of + [] -> map ImplicitStrictnessPosField [1..] + lbls -> map (ImplicitStrictnessRecField . flLabel) lbls + fields = [ ref + | (ref, arg_ty, HsSrcBang _ _ NoSrcStrict) + <- zip3 fld_refs + (map scaledThing (dataConOrigArgTys con)) + (dataConSrcBangs con) + , typeLevity_maybe arg_ty == Just Lifted ] + checkPartialRecordField :: [DataCon] -> FieldLabel -> TcM () -- Checks the partial record field selector, and warns. -- See Note [Checking partial record field] ===================================== 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 @~@). + + 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/exts/strict.rst ===================================== @@ -194,6 +194,9 @@ The ``~`` annotation must be written in prefix form:: See `GHC Proposal #229 <https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0229-whitespace-bang-patterns.rst>`__ for the precise rules. +See also :ghc-flag:`-Wimplicit-field-strictness`, which warns about +fields lacking an explicit annotation. + .. _strict-data: Strict-by-default data types ===================================== docs/users_guide/using-warnings.rst ===================================== @@ -2505,6 +2505,21 @@ 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 + + This warning reports data constructor fields that lack an explicit + strictness annotation (``!`` or ``~``). + .. 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] + • These constructor fields lack an explicit strictness annotation: + • In ‘MkT’: fields 1 and 2 + • In ‘MkT2’: field 2 + • 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] + • These constructor fields lack an explicit strictness annotation: + • 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] + • These constructor fields lack an explicit strictness annotation: + • In ‘:+:’: field 1 + • 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] + • These constructor fields lack an explicit strictness annotation: + • In ‘MkG’: field 1 + • 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] + • These constructor fields lack an explicit strictness annotation: + • 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:33:1: warning: [GHC-47032] [-Wimplicit-field-strictness] + • These constructor fields lack an explicit strictness annotation: + • In ‘MkF’: field 1 + • 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] + • These constructor fields lack an explicit strictness annotation: + • In ‘MkT’: field 1 + • In the data type declaration for ‘T’ + Suggested fix: Annotate each field with ‘!’ (strict) or ‘~’ (lazy) + ===================================== testsuite/tests/warnings/should_compile/T16836d.hs ===================================== @@ -0,0 +1,20 @@ +{-# OPTIONS_GHC -Wimplicit-field-strictness #-} +{-# LANGUAGE MagicHash #-} +{-# LANGUAGE UnboxedTuples #-} +{-# LANGUAGE UnliftedDatatypes #-} +module T16836d where + +import GHC.Exts + +-- unlifted fields can't be usefully annotated; exempt +data P = MkP Int# (# Int, Int #) + +type UD :: UnliftedType +data UD = MkUD + +-- a field of an unlifted data type is exempt too +data Q = MkQ UD + +-- mixed constructor +-- warns only for the lifted field 2 +data M = MkM Int# Int ===================================== testsuite/tests/warnings/should_compile/T16836d.stderr ===================================== @@ -0,0 +1,9 @@ +T16836d.hs:20:1: warning: [GHC-47032] [-Wimplicit-field-strictness] + • These constructor fields lack an explicit strictness annotation: + • In ‘MkM’: field 2 + • In the data type declaration for ‘M’ + Suggested fixes: + • Annotate each field with ‘!’ (strict) or ‘~’ (lazy) + • Use the ‘LazyFieldAnnotations’ extension (implied by ‘StrictData’) + to allow ‘~’ annotations + ===================================== testsuite/tests/warnings/should_compile/all.T ===================================== @@ -91,3 +91,7 @@ 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, ['']) +test('T16836d', normal, compile, ['']) View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/d156d88713ac123db0f63307291064f2... -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/d156d88713ac123db0f63307291064f2... 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)