Sasha Bogicevic pushed to branch wip/27323 at Glasgow Haskell Compiler / GHC
Commits:
-
708c671a
by Sasha Bogicevic at 2026-07-22T19:35:14+02:00
9 changed files:
- + changelog.d/T27323
- compiler/GHC/Hs/Pat.hs
- compiler/GHC/HsToCore/Match.hs
- compiler/GHC/HsToCore/Pmc/Check.hs
- compiler/GHC/HsToCore/Pmc/Desugar.hs
- compiler/GHC/HsToCore/Utils.hs
- compiler/GHC/Tc/Gen/Pat.hs
- + testsuite/tests/pmcheck/should_compile/T27323.hs
- testsuite/tests/pmcheck/should_compile/all.T
Changes:
| 1 | +section: compiler
|
|
| 2 | +synopsis: Don't report compiler-generated bang patterns under -Wredundant-bang-patterns
|
|
| 3 | +description:
|
|
| 4 | + With ``-XStrict``, GHC inserts bang patterns on binders. When such a bang
|
|
| 5 | + cannot force anything - for instance on a binder of unlifted type such as
|
|
| 6 | + ``Int#`` - it was reported by ``-Wredundant-bang-patterns``, even though
|
|
| 7 | + there is no bang in the source to remove. The warning now only reports
|
|
| 8 | + bangs the user actually wrote.
|
|
| 9 | + |
|
| 10 | + As part of this change, the ``XBangPat GhcTc`` extension field of
|
|
| 11 | + ``BangPat`` changed from ``NoExtField`` to ``Origin``, recording whether a
|
|
| 12 | + bang was user-written or compiler-generated. GHC API users constructing or
|
|
| 13 | + matching typechecked bang patterns may need a one-line adjustment.
|
|
| 14 | +mrs: !16395
|
|
| 15 | +issues: #27323 |
| ... | ... | @@ -102,7 +102,11 @@ type instance XParPat GhcTc = NoExtField |
| 102 | 102 | |
| 103 | 103 | type instance XBangPat GhcPs = EpToken "!"
|
| 104 | 104 | type instance XBangPat GhcRn = NoExtField
|
| 105 | -type instance XBangPat GhcTc = NoExtField
|
|
| 105 | +-- | Was this bang written by the user, or inserted by 'decideBangHood'
|
|
| 106 | +-- because of -XStrict? Consulted by the pattern-match checker, which
|
|
| 107 | +-- only reports user-written bangs under -Wredundant-bang-patterns.
|
|
| 108 | +-- See Note [Dead bang patterns] in GHC.HsToCore.Pmc.Check.
|
|
| 109 | +type instance XBangPat GhcTc = Origin
|
|
| 106 | 110 | |
| 107 | 111 | type instance XListPat GhcPs = AnnList ()
|
| 108 | 112 | -- After parsing, ListPat can refer to a built-in Haskell list pattern
|
| ... | ... | @@ -414,7 +414,7 @@ tidy1 :: Id -- The Id being scrutinised |
| 414 | 414 | tidy1 v g (ParPat _ pat) = tidy1 v g (unLoc pat)
|
| 415 | 415 | tidy1 v g (SigPat _ pat _) = tidy1 v g (unLoc pat)
|
| 416 | 416 | tidy1 _ _ (WildPat ty) = return (idDsWrapper, WildPat ty)
|
| 417 | -tidy1 v g (BangPat _ (L l p)) = tidy_bang_pat v g l p
|
|
| 417 | +tidy1 v g (BangPat o (L l p)) = tidy_bang_pat v g o l p
|
|
| 418 | 418 | tidy1 v g (ModifiedPat _ _ pat) = tidy1 v g (unLoc pat)
|
| 419 | 419 | |
| 420 | 420 | -- case v of { x -> mr[] }
|
| ... | ... | @@ -530,30 +530,30 @@ tidy1 _ _ non_interesting_pat |
| 530 | 530 | = return (idDsWrapper, non_interesting_pat)
|
| 531 | 531 | |
| 532 | 532 | --------------------
|
| 533 | -tidy_bang_pat :: Id -> Bool -> SrcSpanAnnA -> Pat GhcTc
|
|
| 533 | +tidy_bang_pat :: Id -> Bool -> Origin -> SrcSpanAnnA -> Pat GhcTc
|
|
| 534 | 534 | -> DsM (DsWrapper, Pat GhcTc)
|
| 535 | 535 | |
| 536 | 536 | -- Discard par/sig under a bang
|
| 537 | -tidy_bang_pat v g _ (ParPat _ (L l p)) = tidy_bang_pat v g l p
|
|
| 538 | -tidy_bang_pat v g _ (SigPat _ (L l p) _) = tidy_bang_pat v g l p
|
|
| 537 | +tidy_bang_pat v g o _ (ParPat _ (L l p)) = tidy_bang_pat v g o l p
|
|
| 538 | +tidy_bang_pat v g o _ (SigPat _ (L l p) _) = tidy_bang_pat v g o l p
|
|
| 539 | 539 | |
| 540 | 540 | -- Push the bang-pattern inwards, in the hope that
|
| 541 | 541 | -- it may disappear next time
|
| 542 | -tidy_bang_pat v g l (AsPat x v' p)
|
|
| 543 | - = tidy1 v g (AsPat x v' (L l (BangPat noExtField p)))
|
|
| 544 | -tidy_bang_pat v g l (XPat (CoPat w p t))
|
|
| 545 | - = tidy1 v g (XPat $ CoPat w (BangPat noExtField (L l p)) t)
|
|
| 546 | -tidy_bang_pat v g l (OrPat x (p:|ps)) -- push bang into first pat alt
|
|
| 547 | - = tidy1 v g (OrPat x (L l (BangPat noExtField p) :| ps))
|
|
| 542 | +tidy_bang_pat v g o l (AsPat x v' p)
|
|
| 543 | + = tidy1 v g (AsPat x v' (L l (BangPat o p)))
|
|
| 544 | +tidy_bang_pat v g o l (XPat (CoPat w p t))
|
|
| 545 | + = tidy1 v g (XPat $ CoPat w (BangPat o (L l p)) t)
|
|
| 546 | +tidy_bang_pat v g o l (OrPat x (p:|ps)) -- push bang into first pat alt
|
|
| 547 | + = tidy1 v g (OrPat x (L l (BangPat o p) :| ps))
|
|
| 548 | 548 | |
| 549 | 549 | -- Discard bang around strict pattern
|
| 550 | -tidy_bang_pat v g _ p@(LitPat {}) = tidy1 v g p
|
|
| 551 | -tidy_bang_pat v g _ p@(ListPat {}) = tidy1 v g p
|
|
| 552 | -tidy_bang_pat v g _ p@(TuplePat {}) = tidy1 v g p
|
|
| 553 | -tidy_bang_pat v g _ p@(SumPat {}) = tidy1 v g p
|
|
| 550 | +tidy_bang_pat v g _ _ p@(LitPat {}) = tidy1 v g p
|
|
| 551 | +tidy_bang_pat v g _ _ p@(ListPat {}) = tidy1 v g p
|
|
| 552 | +tidy_bang_pat v g _ _ p@(TuplePat {}) = tidy1 v g p
|
|
| 553 | +tidy_bang_pat v g _ _ p@(SumPat {}) = tidy1 v g p
|
|
| 554 | 554 | |
| 555 | 555 | -- Data/newtype constructors
|
| 556 | -tidy_bang_pat v g l p@(ConPat { pat_con = L _ (RealDataCon dc)
|
|
| 556 | +tidy_bang_pat v g o l p@(ConPat { pat_con = L _ (RealDataCon dc)
|
|
| 557 | 557 | , pat_args = args
|
| 558 | 558 | , pat_con_ext = ConPatTc
|
| 559 | 559 | { cpt_arg_tys = arg_tys
|
| ... | ... | @@ -562,7 +562,7 @@ tidy_bang_pat v g l p@(ConPat { pat_con = L _ (RealDataCon dc) |
| 562 | 562 | -- Newtypes: push bang inwards (#9844)
|
| 563 | 563 | =
|
| 564 | 564 | if isNewTyCon (dataConTyCon dc)
|
| 565 | - then tidy1 v g (p { pat_args = push_bang_into_newtype_arg l (scaledThing ty) args })
|
|
| 565 | + then tidy1 v g (p { pat_args = push_bang_into_newtype_arg l o (scaledThing ty) args })
|
|
| 566 | 566 | else tidy1 v g p -- Data types: discard the bang
|
| 567 | 567 | where
|
| 568 | 568 | (ty:_) = dataConInstArgTys dc arg_tys
|
| ... | ... | @@ -582,28 +582,29 @@ tidy_bang_pat v g l p@(ConPat { pat_con = L _ (RealDataCon dc) |
| 582 | 582 | --
|
| 583 | 583 | -- NB: SigPatIn, ConPatIn should not happen
|
| 584 | 584 | |
| 585 | -tidy_bang_pat _ _ l p = return (idDsWrapper, BangPat noExtField (L l p))
|
|
| 585 | +tidy_bang_pat _ _ o l p = return (idDsWrapper, BangPat o (L l p))
|
|
| 586 | 586 | |
| 587 | 587 | -------------------
|
| 588 | 588 | push_bang_into_newtype_arg :: SrcSpanAnnA
|
| 589 | + -> Origin
|
|
| 589 | 590 | -> Type -- The type of the argument we are pushing
|
| 590 | 591 | -- onto
|
| 591 | 592 | -> HsConPatDetails GhcTc -> HsConPatDetails GhcTc
|
| 592 | 593 | -- See Note [Bang patterns and newtypes]
|
| 593 | 594 | -- We are transforming !(N p) into (N !p)
|
| 594 | -push_bang_into_newtype_arg l _ty (PrefixCon x (arg:args))
|
|
| 595 | +push_bang_into_newtype_arg l o _ty (PrefixCon x (arg:args))
|
|
| 595 | 596 | = assert (null args) $
|
| 596 | - PrefixCon x [L l (BangPat noExtField arg)]
|
|
| 597 | -push_bang_into_newtype_arg l _ty (RecCon x rf)
|
|
| 597 | + PrefixCon x [L l (BangPat o arg)]
|
|
| 598 | +push_bang_into_newtype_arg l o _ty (RecCon x rf)
|
|
| 598 | 599 | | HsRecFields { rec_flds = L lf fld : flds } <- rf
|
| 599 | 600 | , HsFieldBind { hfbRHS = arg } <- fld
|
| 600 | 601 | = assert (null flds) $
|
| 601 | 602 | RecCon x (rf { rec_flds = [L lf (fld { hfbRHS
|
| 602 | - = L l (BangPat noExtField arg) })] })
|
|
| 603 | -push_bang_into_newtype_arg l ty (RecCon _ rf) -- If a user writes !(T {})
|
|
| 603 | + = L l (BangPat o arg) })] })
|
|
| 604 | +push_bang_into_newtype_arg l o ty (RecCon _ rf) -- If a user writes !(T {})
|
|
| 604 | 605 | | HsRecFields { rec_flds = [] } <- rf
|
| 605 | - = PrefixCon noExtField [L l (BangPat noExtField (noLocA (WildPat ty)))]
|
|
| 606 | -push_bang_into_newtype_arg _ _ cd
|
|
| 606 | + = PrefixCon noExtField [L l (BangPat o (noLocA (WildPat ty)))]
|
|
| 607 | +push_bang_into_newtype_arg _ _ _ cd
|
|
| 607 | 608 | = pprPanic "push_bang_into_newtype_arg" (pprConArgs cd)
|
| 608 | 609 | |
| 609 | 610 | {-
|
| ... | ... | @@ -128,7 +128,9 @@ checkGrd grd = CA $ \inc -> case grd of |
| 128 | 128 | div <- addPhiCtNablas inc (PhiBotCt x)
|
| 129 | 129 | matched <- addPhiCtNablas inc (PhiNotBotCt x)
|
| 130 | 130 | -- See Note [Dead bang patterns]
|
| 131 | - -- mb_info = Just info <==> PmBang originates from bang pattern in source
|
|
| 131 | + -- mb_info = Just info <==> the PmBang originates from a user-written
|
|
| 132 | + -- bang pattern. Bangs inserted by -XStrict carry no info and are thus
|
|
| 133 | + -- never reported as redundant (#27323).
|
|
| 132 | 134 | let bangs | Just info <- mb_info = unitOL (div, info)
|
| 133 | 135 | | otherwise = NilOL
|
| 134 | 136 | tracePm "check:Bang" (ppr x <+> ppr div)
|
| ... | ... | @@ -267,6 +269,22 @@ dead. So for a source bang, we add the refined Nabla and the source info to |
| 267 | 269 | the 'RedSet's 'rs_bangs'. When collecting stuff to warn, we test that Nabla for
|
| 268 | 270 | inhabitants. If it's empty, we'll warn that it's redundant.
|
| 269 | 271 | |
| 272 | +Crucially, we only ever warn about bangs the user wrote. With -XStrict, the
|
|
| 273 | +compiler inserts bangs itself ('decideBangHood' in GHC.HsToCore.Utils), and
|
|
| 274 | +such a bang is often dead. Consider (#27323)
|
|
| 275 | + |
|
| 276 | + {-# LANGUAGE MagicHash, Strict #-}
|
|
| 277 | + idInt# :: Int# -> Int#
|
|
| 278 | + idInt# x = x
|
|
| 279 | + |
|
| 280 | +The binder `x` has unlifted type, so the bang that -XStrict inserts on it can
|
|
| 281 | +never force anything. But warning would be deeply confusing: there is no bang
|
|
| 282 | +in the source to delete! Whether a bang was user-written is recorded in the
|
|
| 283 | +'XBangPat GhcTc' extension field ('Origin'); generated bangs desugar to a
|
|
| 284 | +'PmBang' with no 'SrcInfo', so they still take part in divergence checking
|
|
| 285 | +(that matters for the inaccessible-RHS warnings of #21761) but never end up
|
|
| 286 | +in 'rs_bangs'.
|
|
| 287 | + |
|
| 270 | 288 | Note that we don't want to warn for a dead bang that appears on a redundant
|
| 271 | 289 | clause. That is because in that case, we recommend to delete the clause wholly,
|
| 272 | 290 | including its leading pattern match.
|
| ... | ... | @@ -18,6 +18,7 @@ import GHC.Data.FastString (unpackFS, lengthFS, mkFastStringShortText) |
| 18 | 18 | import GHC.Driver.DynFlags
|
| 19 | 19 | import GHC.Hs
|
| 20 | 20 | import GHC.Tc.Utils.TcMType (shortCutLit)
|
| 21 | +import GHC.Types.Basic (isGenerated)
|
|
| 21 | 22 | import GHC.Types.Id
|
| 22 | 23 | import GHC.Core.ConLike
|
| 23 | 24 | import GHC.Types.Name
|
| ... | ... | @@ -154,11 +155,11 @@ desugarPat x pat = case pat of |
| 154 | 155 | VarPat _ y -> pure (mkPmLetVar (unLoc y) x)
|
| 155 | 156 | ParPat _ p -> desugarLPat x p
|
| 156 | 157 | LazyPat _ _ -> pure GdEnd -- like a wildcard
|
| 157 | - BangPat _ p@(L l p') ->
|
|
| 158 | + BangPat orig p@(L l p') ->
|
|
| 158 | 159 | -- Add the bang in front of the list, because it will happen before any
|
| 159 | 160 | -- nested stuff.
|
| 160 | 161 | consGrdDag (PmBang x pm_loc) <$> desugarLPat x p
|
| 161 | - where pm_loc = Just (SrcInfo (L (locA l) (ppr p')))
|
|
| 162 | + where pm_loc = if isGenerated orig then Nothing else Just (SrcInfo (L (locA l) (ppr p')))
|
|
| 162 | 163 | |
| 163 | 164 | -- (x@pat) ==> Desugar pat with x as match var and handle impedance
|
| 164 | 165 | -- mismatch with incoming match var
|
| ... | ... | @@ -649,4 +650,12 @@ user *had* written a bang: |
| 649 | 650 | + In an equation for ‘idV’: idV !v = ...
|
| 650 | 651 | |
| 651 | 652 | So we live with the duplication.
|
| 653 | + |
|
| 654 | +There is one wrinkle (#27323): the bang inserted by 'decideBangHood' is
|
|
| 655 | +compiler-generated, marked 'Generated' in its 'XBangPat GhcTc' field. When
|
|
| 656 | +'desugarPat' sees such a bang, it emits a 'PmBang' without 'SrcInfo', so
|
|
| 657 | +that the checker performs the usual divergence check (giving the
|
|
| 658 | +inaccessible-RHS warning above) but never reports the bang under
|
|
| 659 | +-Wredundant-bang-patterns. See Note [Dead bang patterns] in
|
|
| 660 | +GHC.HsToCore.Pmc.Check.
|
|
| 652 | 661 | -} |
| ... | ... | @@ -53,6 +53,7 @@ import GHC.HsToCore.Monad |
| 53 | 53 | |
| 54 | 54 | import GHC.Core.Utils
|
| 55 | 55 | import GHC.Core.Make
|
| 56 | +import GHC.Types.Basic (Origin(Generated), GenReason(OtherExpansion), DoPmc(..))
|
|
| 56 | 57 | import GHC.Types.Id.Make
|
| 57 | 58 | import GHC.Types.Id
|
| 58 | 59 | import GHC.Types.Literal
|
| ... | ... | @@ -956,6 +957,12 @@ Specifically: |
| 956 | 957 | !pat => !pat -- always
|
| 957 | 958 | pat => !pat -- when -XStrict
|
| 958 | 959 | pat => pat -- otherwise
|
| 960 | + |
|
| 961 | +The bangs we add here are compiler-generated: we record 'Generated' in the
|
|
| 962 | +'XBangPat GhcTc' extension field, whereas a user-written bang is typechecked
|
|
| 963 | +to 'FromSource' (GHC.Tc.Gen.Pat). The pattern-match checker consults this
|
|
| 964 | +field so that -Wredundant-bang-patterns only reports bangs that the user
|
|
| 965 | +actually wrote (#27323). See Note [Dead bang patterns] in GHC.HsToCore.Pmc.Check.
|
|
| 959 | 966 | -}
|
| 960 | 967 | |
| 961 | 968 | |
| ... | ... | @@ -975,7 +982,7 @@ decideBangHood dflags lpat |
| 975 | 982 | ParPat x p -> L l (ParPat x (go p))
|
| 976 | 983 | LazyPat _ lp' -> lp'
|
| 977 | 984 | BangPat _ _ -> lp
|
| 978 | - _ -> L l (BangPat noExtField lp)
|
|
| 985 | + _ -> L l (BangPat (Generated OtherExpansion DoPmc) lp)
|
|
| 979 | 986 | |
| 980 | 987 | isTrueLHsExpr :: LHsExpr GhcTc -> Maybe (CoreExpr -> DsM CoreExpr)
|
| 981 | 988 |
| ... | ... | @@ -31,6 +31,7 @@ import GHC.Tc.Gen.Sig( TcPragEnv, lookupPragEnv, addInlinePrags ) |
| 31 | 31 | import GHC.Tc.Utils.Monad
|
| 32 | 32 | import GHC.Tc.Utils.Instantiate
|
| 33 | 33 | import GHC.Types.FieldLabel
|
| 34 | +import GHC.Types.Basic (Origin (FromSource))
|
|
| 34 | 35 | import GHC.Types.Id
|
| 35 | 36 | import GHC.Types.Var
|
| 36 | 37 | import GHC.Types.Name
|
| ... | ... | @@ -627,9 +628,9 @@ tc_pat scaled_exp_pat_ty@(Scaled w_pat exp_pat_ty) penv ps_pat thing_inside = |
| 627 | 628 | { (pat', res) <- tc_lpat scaled_exp_pat_ty penv pat thing_inside
|
| 628 | 629 | ; return (ParPat x pat', res) }
|
| 629 | 630 | |
| 630 | - BangPat x pat -> do
|
|
| 631 | + BangPat _ pat -> do
|
|
| 631 | 632 | { (pat', res) <- tc_lpat scaled_exp_pat_ty penv pat thing_inside
|
| 632 | - ; return (BangPat x pat', res) }
|
|
| 633 | + ; return (BangPat FromSource pat', res) }
|
|
| 633 | 634 | |
| 634 | 635 | OrPat _ pats -> do -- See Note [Implementation of OrPatterns], Typechecker (1)
|
| 635 | 636 | { let pats_list = NE.toList pats
|
| 1 | +{-# LANGUAGE MagicHash, Strict #-}
|
|
| 2 | +{-# OPTIONS_GHC -Wredundant-bang-patterns #-}
|
|
| 3 | + |
|
| 4 | +import GHC.Prim (Int#)
|
|
| 5 | + |
|
| 6 | +idInt# :: Int# -> Int#
|
|
| 7 | +idInt# x = x
|
|
| 8 | + |
|
| 9 | +main = pure () |
| ... | ... | @@ -190,4 +190,5 @@ test('T24845', [], compile, [overlapping_incomplete]) |
| 190 | 190 | test('T22652', [], compile, [overlapping_incomplete])
|
| 191 | 191 | test('T22652a', [], compile, [overlapping_incomplete])
|
| 192 | 192 | test('T24867', [], compile_fail, [overlapping_incomplete])
|
| 193 | +test('T27323', normal, compile, ['-Wredundant-bang-patterns'])
|
|
| 193 | 194 | test('T27360', normal, compile, [overlapping_incomplete + '-g3']) |