[Git][ghc/ghc][wip/T25281] More wibbles
Simon Peyton Jones (@simonpj)
gitlab at gitlab.haskell.org
Fri Sep 27 22:14:56 UTC 2024
Simon Peyton Jones pushed to branch wip/T25281 at Glasgow Haskell Compiler / GHC
Commits:
d7def41c by Simon Peyton Jones at 2024-09-27T23:14:39+01:00
More wibbles
- - - - -
8 changed files:
- compiler/GHC/HsToCore/Binds.hs
- compiler/GHC/HsToCore/Expr.hs
- compiler/GHC/HsToCore/Match.hs
- compiler/GHC/HsToCore/Pmc.hs
- compiler/GHC/HsToCore/Utils.hs
- compiler/GHC/Types/Id/Make.hs
- compiler/GHC/Types/Var.hs
- testsuite/tests/parser/should_compile/DumpTypecheckedAst.stderr
Changes:
=====================================
compiler/GHC/HsToCore/Binds.hs
=====================================
@@ -63,7 +63,6 @@ import GHC.Builtin.Types ( naturalTy, typeSymbolKind, charTy )
import GHC.Tc.Types.Evidence
import GHC.Types.Id
-import GHC.Types.Id.Make ( nospecId )
import GHC.Types.Name
import GHC.Types.Var.Set
import GHC.Types.Var.Env
@@ -832,7 +831,7 @@ dsSpec mb_poly_rhs (L loc (SpecPrag poly_id spec_co spec_inl))
-- perhaps with the body of the lambda wrapped in some WpLets
-- E.g. /\a \(d:Eq a). let d2 = $df d in [] (Maybe a) d2
- ; dsHsWrapperForRuleLHS spec_app $ \core_app -> do
+ ; dsHsWrapper spec_app $ \core_app -> do
{ let ds_lhs = core_app (Var poly_id)
spec_ty = mkLamTypes spec_bndrs (exprType ds_lhs)
@@ -1355,7 +1354,7 @@ dsHsWrappers [] k = k []
dsHsWrapper :: HsWrapper -> ((CoreExpr -> CoreExpr) -> DsM a) -> DsM a
dsHsWrapper hs_wrap thing_inside
- = ds_hs_wrapper False hs_wrap $ \ core_wrap ->
+ = ds_hs_wrapper hs_wrap $ \ core_wrap ->
addTyCs FromSource (hsWrapDictBinders hs_wrap) $
-- addTyCs: Add type evidence to the refinement type
-- predicate of the coverage checker
@@ -1366,15 +1365,10 @@ dsHsWrapper hs_wrap thing_inside
-- oracle.
thing_inside core_wrap
-dsHsWrapperForRuleLHS :: HsWrapper -> ((CoreExpr -> CoreExpr) -> DsM a) -> DsM a
-dsHsWrapperForRuleLHS = ds_hs_wrapper True
-
-ds_hs_wrapper :: Bool -- True <=> LHS of a RULE
- -- See (NC1) in Note [Desugaring non-canonical evidence]
- -> HsWrapper
+ds_hs_wrapper :: HsWrapper
-> ((CoreExpr -> CoreExpr) -> DsM a)
-> DsM a
-ds_hs_wrapper is_rule_lhs wrap = go wrap
+ds_hs_wrapper wrap = go wrap
where
go WpHole k = k $ \e -> e
go (WpTyApp ty) k = k $ \e -> App e (Type ty)
@@ -1382,6 +1376,8 @@ ds_hs_wrapper is_rule_lhs wrap = go wrap
go (WpTyLam tv) k = k $ Lam tv
go (WpCast co) k = assert (coercionRole co == Representational) $
k $ \e -> mkCastDs e co
+ go (WpEvApp tm) k = do { core_tm <- dsEvTerm tm
+ ; k $ \e -> e `App` core_tm }
go (WpLet ev_binds) k = dsTcEvBinds ev_binds $ \bs ->
k (mkCoreLets bs)
go (WpCompose c1 c2) k = go c1 $ \w1 ->
@@ -1391,37 +1387,15 @@ ds_hs_wrapper is_rule_lhs wrap = go wrap
do { x <- newSysLocalDs st
; go c1 $ \w1 ->
go c2 $ \w2 ->
- let app f a = mkCoreAppDs (text "dsHsWrapper") f a
+ let app f a = mkCoreApp (text "dsHsWrapper") f a
arg = w1 (Var x)
in k (\e -> (Lam x (w2 (app e arg)))) }
- go (WpEvApp tm) k = do { core_tm <- dsEvTerm tm
-
- -- See Note [Desugaring non-canonical evidence]
- ; unspecables <- getUnspecables
- ; let vs = exprFreeVarsList core_tm
- is_unspecable_var v = v `S.member` unspecables
- is_specable
- | is_rule_lhs = True
- | otherwise = not $ any (is_unspecable_var) vs
-
- ; k (\e -> app_ev is_specable e core_tm) }
-- See Note [Coercions returned from tcSubMult] in GHC.Tc.Utils.Unify.
go (WpMultCoercion co) k = do { unless (isReflexiveCo co) $
diagnosticDs DsMultiplicityCoercionsNotSupported
; k $ \e -> e }
--- We are about to construct an evidence application `f dict`. If the dictionary is
--- non-specialisable, instead construct
--- nospec f dict
--- See Note [nospecId magic] in GHC.Types.Id.Make for what `nospec` does.
-app_ev :: Bool -> CoreExpr -> CoreExpr -> CoreExpr
-app_ev is_specable k core_tm
- | not is_specable
- = Var nospecId `App` Type (exprType k) `App` k `App` core_tm
-
- | otherwise
- = k `App` core_tm
--------------------------------------
dsTcEvBinds_s :: [TcEvBinds] -> ([CoreBind] -> DsM a) -> DsM a
=====================================
compiler/GHC/HsToCore/Expr.hs
=====================================
@@ -44,10 +44,11 @@ import GHC.Tc.Types.Evidence
import GHC.Tc.Utils.Monad
import GHC.Tc.Instance.Class (lookupHasFieldLabel)
+import GHC.Core
+import GHC.Core.FVs( exprsFreeVarsList )
import GHC.Core.FamInstEnv( topNormaliseType )
import GHC.Core.Type
import GHC.Core.TyCo.Rep
-import GHC.Core
import GHC.Core.Utils
import GHC.Core.Make
import GHC.Core.PatSyn
@@ -61,6 +62,7 @@ import GHC.Types.CostCentre
import GHC.Types.Id
import GHC.Types.Id.Info
import GHC.Types.Id.Make
+import GHC.Types.Var( isInvisibleAnonPiTyBinder )
import GHC.Types.Basic
import GHC.Types.SrcLoc
import GHC.Types.Tickish
@@ -74,9 +76,10 @@ import GHC.Builtin.Names
import GHC.Utils.Misc
import GHC.Utils.Outputable as Outputable
import GHC.Utils.Panic
-import Control.Arrow ( first )
+import Control.Arrow( first )
import Control.Monad
import Data.Maybe( isJust )
+import qualified Data.Set as S
{-
************************************************************************
@@ -576,6 +579,73 @@ There are several special cases to handle
* HsVar: special magic for `noinline`
* HsVar: special magic for `seq`
+Note [Desugaring seq]
+~~~~~~~~~~~~~~~~~~~~~
+There are a few subtleties in the desugaring of `seq`, all
+implemented in the `seqId` case of `ds_app_var`:
+
+ 1. (as described in #1031)
+
+ Consider,
+ f x y = x `seq` (y `seq` (# x,y #))
+
+ Because the argument to the outer 'seq' has an unlifted type, we'll use
+ call-by-value, and compile it as if we had
+
+ f x y = case (y `seq` (# x,y #)) of v -> x `seq` v
+
+ But that is bad, because we now evaluate y before x!
+
+ Seq is very, very special! So we recognise it right here, and desugar to
+ case x of _ -> case y of _ -> (# x,y #)
+
+ 2. (as described in #2273)
+
+ Consider
+ let chp = case b of { True -> fst x; False -> 0 }
+ in chp `seq` ...chp...
+ Here the seq is designed to plug the space leak of retaining (snd x)
+ for too long.
+
+ If we rely on the ordinary inlining of seq, we'll get
+ let chp = case b of { True -> fst x; False -> 0 }
+ case chp of _ { I# -> ...chp... }
+
+ But since chp is cheap, and the case is an alluring context, we'll
+ inline chp into the case scrutinee. Now there is only one use of chp,
+ so we'll inline a second copy. Alas, we've now ruined the purpose of
+ the seq, by re-introducing the space leak:
+ case (case b of {True -> fst x; False -> 0}) of
+ I# _ -> ...case b of {True -> fst x; False -> 0}...
+
+ We can try to avoid doing this by ensuring that the binder-swap in the
+ case happens, so we get this at an early stage:
+ case chp of chp2 { I# -> ...chp2... }
+ But this is fragile. The real culprit is the source program. Perhaps we
+ should have said explicitly
+ let !chp2 = chp in ...chp2...
+
+ But that's painful. So the code here does a little hack to make seq
+ more robust: a saturated application of 'seq' is turned *directly* into
+ the case expression, thus:
+ x `seq` e2 ==> case x of x -> e2 -- Note shadowing!
+ e1 `seq` e2 ==> case x of _ -> e2
+
+ So we desugar our example to:
+ let chp = case b of { True -> fst x; False -> 0 }
+ case chp of chp { I# -> ...chp... }
+ And now all is well.
+
+ The reason it's a hack is because if you define mySeq=seq, the hack
+ won't work on mySeq.
+
+ 3. (as described in #2409)
+
+ The isInternalName ensures that we don't turn
+ True `seq` e
+ into
+ case True of True { ... }
+ which stupidly tries to bind the datacon 'True'.
-}
dsApp :: HsExpr GhcTc -> DsM CoreExpr
@@ -663,7 +733,7 @@ ds_app_var (L loc fun_id) hs_args core_args
-- otherwise this might not be the implicit HasField instance
-> do { sel_id <- dsLookupGlobalId (flSelector fl)
; ds_app_rec_sel sel_id rest_args }
- _ -> ds_app_done fun_id core_args }
+ _ -> ds_app_finish fun_id core_args }
-----------------------
-- Warn about identities for (fromInteger :: Integer -> Integer) etc
@@ -678,7 +748,7 @@ ds_app_var (L loc fun_id) hs_args core_args
-- So we are converting ty -> ty
diagnosticDs (DsIdentitiesFound fun_id conv_ty)
- ; ds_app_done fun_id core_args }
+ ; ds_app_finish fun_id core_args }
-----------------------
-- Warn about unused return value in
@@ -690,7 +760,7 @@ ds_app_var (L loc fun_id) hs_args core_args
= do { tracePm ">>" (ppr loc $$ ppr arg_ty $$ ppr (isGeneratedSrcSpan (locA loc)))
; when (isGeneratedSrcSpan (locA loc)) $ -- It is a compiler-generated (>>)
warnDiscardedDoBindings hs_arg m_ty arg_ty
- ; ds_app_done fun_id core_args }
+ ; ds_app_finish fun_id core_args }
-----------------------
-- Deal with `noinline`
@@ -716,11 +786,30 @@ ds_app_var (L loc fun_id) hs_args core_args
-----------------------
-- Phew! No more special cases. Just build an applications
| otherwise
- = ds_app_done fun_id core_args
-
-ds_app_done :: Id -> [CoreExpr] -> DsM CoreExpr
-ds_app_done fun_id core_args = return (mkCoreApps (Var fun_id) core_args)
+ = ds_app_finish fun_id core_args
+---------------
+ds_app_finish :: Id -> [CoreExpr] -> DsM CoreExpr
+-- We are about to construct an application that may include evidence applications
+-- `f dict`. If the dictionary is non-specialisable, instead construct
+-- nospec f dict
+-- See Note [nospecId magic] in GHC.Types.Id.Make for what `nospec` does.
+-- See Note [Desugaring non-canonical evidence]
+ds_app_finish fun_id core_args
+ = do { unspecables <- getUnspecables
+ ; let fun_ty = idType fun_id
+ free_dicts = exprsFreeVarsList
+ [ e | (e,pi_bndr) <- core_args `zip` fst (splitPiTys fun_ty)
+ , isInvisibleAnonPiTyBinder pi_bndr ]
+ is_unspecable_var v = v `S.member` unspecables
+
+ fun | not (S.null unspecables) -- Fast path
+ , any (is_unspecable_var) free_dicts
+ = Var nospecId `App` Type fun_ty `App` Var fun_id
+ | otherwise
+ = Var fun_id
+
+ ; return (mkCoreApps fun core_args) }
---------------
ds_app_rec_sel :: Id -> [CoreExpr] -> DsM CoreExpr
@@ -757,14 +846,12 @@ ds_app_rec_sel sel_id core_args
; let maxCons = maxUncoveredPatterns dflags
; diagnosticDs $ DsIncompleteRecordSelector (idName sel_id) cons_wo_field maxCons }
- ; return (mkCoreApps (Var sel_id) core_args) }
+ ; ds_app_finish sel_id core_args }
| otherwise
= pprPanic "ds_app_rec_sel" (ppr sel_id $$ ppr (idDetails sel_id))
where
--- apply_type_args applies the record selector to its
--- initial type args, returning the remaining args, if any
apply_type_args :: Id -> [CoreExpr] -> (Type, [CoreExpr])
-- Apply function to the initial /type/ args;
-- return the type of the instantiated function,
=====================================
compiler/GHC/HsToCore/Match.hs
=====================================
@@ -303,7 +303,7 @@ matchView (var :| vars) ty eqns@(eqn1 :| _)
-- compile the view expressions
; viewExpr' <- dsExpr viewExpr
; return (mkViewMatchResult var'
- (mkCoreAppDs (text "matchView") viewExpr' (Var var))
+ (mkCoreApp (text "matchView") viewExpr' (Var var))
match_result) }
-- decompose the first pattern and leave the rest alone
=====================================
compiler/GHC/HsToCore/Pmc.hs
=====================================
@@ -264,7 +264,7 @@ Furthermore, HasField constraints allow to delay the completeness check from
the field access site to a caller, as in test cases TcIncompleteRecSel and T24891:
accessDot :: HasField "sel2" t Int => t -> Int
- accessDot x = x.sel2
+ accessDot x = x.sel2 -- getField @Symbol @"sel2" @t @Int x
solveDot :: Dot -> Int
solveDot = accessDot
@@ -348,20 +348,22 @@ Finally, there are 2 more items addressing -XOverloadedRecordDot:
We want to catch these applications in the saturated (2) case.
(The unsaturated case is handled implicitly by (7).)
For example, we do not want to generate a warning for function `ldiDot`!
- For that, we need to be smart in `decomposeRecSelHead`, which matches out
- the record selector. It must treat the above expression similar to a vanilla
- RecSel app `sel2 d`.
- This is a bit nasty since we cannot look at the unfolding of `$dHasField`.
- Tested in T24891.
-
- 7. For `accessDot` above, `decomposeRecSelHead` will fail to find a record
- selector, because type `t` is not obviously a record type.
+
+ Function `GHC.HsToCore.Expr.ds_app_var` spots the `getField` application,
+ and then treats the above expression similar to a vanilla (RecSel app sel2 d).
+ This is a bit nasty (it has to do instance lookup) since we cannot look at
+ the unfolding of `$dHasField`. Tested in T24891.
+
+ 7. For `accessDot` above, `ds_app_var` will fail to find a record selector,
+ because type `t` is not obviously a record type.
+
That's good, because it means we won't emit a warning for `accessDot`.
- But we should really emit a warning for `solveDot`!
- There, the compiler solves a `HasField` constraint and without an immediate
- `getField`, roughly `solveDot = accessDot @Dot $d`.
- It is the job of the solver to warn about incompleteness here,
- in `GHC.Tc.Instance.Class.matchHasField`.
+
+ But we really should emit a warning for `solveDot`! There, the
+ compiler solves a `HasField` constraint and without an immediate
+ `getField`, roughly `solveDot = accessDot @Dot $d`. It must be the job
+ of the solver to warn about incompleteness here, in
+ `GHC.Tc.Instance.Class.matchHasField`.
What makes this complicated is that we do not *also* want to warn in the
example `dot d = d.sel2` above, which is covered by more precise case (6)!
=====================================
compiler/GHC/HsToCore/Utils.hs
=====================================
@@ -28,8 +28,7 @@ module GHC.HsToCore.Utils (
mkCoPrimCaseMatchResult, mkCoAlgCaseMatchResult, mkCoSynCaseMatchResult,
wrapBind, wrapBinds,
- mkErrorAppDs, mkCoreAppDs, mkCoreAppsDs, mkCastDs,
- mkFailExpr,
+ mkErrorAppDs, mkCastDs, mkFailExpr,
seqVar,
@@ -77,7 +76,6 @@ import GHC.Types.Unique.Set
import GHC.Types.Unique.Supply
import GHC.Unit.Module
import GHC.Builtin.Names
-import GHC.Types.Name( isInternalName )
import GHC.Utils.Outputable
import GHC.Utils.Panic
import GHC.Types.SrcLoc
@@ -337,7 +335,7 @@ mkPatSynCase var ty alt fail = do
matcher <- dsLExpr $ mkLHsWrap wrapper $
nlHsTyApp matcher_id [getRuntimeRep ty, ty]
cont <- mkCoreLams bndrs <$> runMatchResult fail match_result
- return $ mkCoreAppsDs (text "patsyn" <+> ppr var) matcher [Var var, ensure_unstrict cont, Lam voidArgId fail]
+ return $ mkCoreApps matcher [Var var, ensure_unstrict cont, Lam voidArgId fail]
where
MkCaseAlt{ alt_pat = psyn,
alt_bndrs = bndrs,
@@ -469,102 +467,6 @@ mkFailExpr :: HsMatchContextRn -> Type -> DsM CoreExpr
mkFailExpr ctxt ty
= mkErrorAppDs pAT_ERROR_ID ty (matchContextErrString ctxt)
-{-
-'mkCoreAppDs' and 'mkCoreAppsDs' handle the special-case desugaring of 'seq'.
-
-Note [Desugaring seq]
-~~~~~~~~~~~~~~~~~~~~~
-
-There are a few subtleties in the desugaring of `seq`:
-
- 1. (as described in #1031)
-
- Consider,
- f x y = x `seq` (y `seq` (# x,y #))
-
- Because the argument to the outer 'seq' has an unlifted type, we'll use
- call-by-value, and compile it as if we had
-
- f x y = case (y `seq` (# x,y #)) of v -> x `seq` v
-
- But that is bad, because we now evaluate y before x!
-
- Seq is very, very special! So we recognise it right here, and desugar to
- case x of _ -> case y of _ -> (# x,y #)
-
- 2. (as described in #2273)
-
- Consider
- let chp = case b of { True -> fst x; False -> 0 }
- in chp `seq` ...chp...
- Here the seq is designed to plug the space leak of retaining (snd x)
- for too long.
-
- If we rely on the ordinary inlining of seq, we'll get
- let chp = case b of { True -> fst x; False -> 0 }
- case chp of _ { I# -> ...chp... }
-
- But since chp is cheap, and the case is an alluring context, we'll
- inline chp into the case scrutinee. Now there is only one use of chp,
- so we'll inline a second copy. Alas, we've now ruined the purpose of
- the seq, by re-introducing the space leak:
- case (case b of {True -> fst x; False -> 0}) of
- I# _ -> ...case b of {True -> fst x; False -> 0}...
-
- We can try to avoid doing this by ensuring that the binder-swap in the
- case happens, so we get this at an early stage:
- case chp of chp2 { I# -> ...chp2... }
- But this is fragile. The real culprit is the source program. Perhaps we
- should have said explicitly
- let !chp2 = chp in ...chp2...
-
- But that's painful. So the code here does a little hack to make seq
- more robust: a saturated application of 'seq' is turned *directly* into
- the case expression, thus:
- x `seq` e2 ==> case x of x -> e2 -- Note shadowing!
- e1 `seq` e2 ==> case x of _ -> e2
-
- So we desugar our example to:
- let chp = case b of { True -> fst x; False -> 0 }
- case chp of chp { I# -> ...chp... }
- And now all is well.
-
- The reason it's a hack is because if you define mySeq=seq, the hack
- won't work on mySeq.
-
- 3. (as described in #2409)
-
- The isInternalName ensures that we don't turn
- True `seq` e
- into
- case True of True { ... }
- which stupidly tries to bind the datacon 'True'.
--}
-
--- NB: Make sure the argument is not representation-polymorphic
-mkCoreAppDs :: SDoc -> CoreExpr -> CoreExpr -> CoreExpr
-mkCoreAppDs _ (Var f `App` Type _r `App` Type ty1 `App` Type ty2 `App` arg1) arg2
- | f `hasKey` seqIdKey -- Note [Desugaring seq], points (1) and (2)
- = Case arg1 case_bndr ty2 [Alt DEFAULT [] arg2]
- where
- case_bndr = case arg1 of
- Var v1 | isInternalName (idName v1)
- -> v1 -- Note [Desugaring seq], points (2) and (3)
- _ -> mkWildValBinder ManyTy ty1
-
-mkCoreAppDs _ (Var f `App` Type _r) arg
- | f `hasKey` noinlineIdKey -- See Note [noinlineId magic] in GHC.Types.Id.Make
- , (fun, args) <- collectArgs arg
- , not (null args)
- = (Var f `App` Type (exprType fun) `App` fun)
- `mkCoreApps` args
-
-mkCoreAppDs s fun arg = mkCoreApp s fun arg -- The rest is done in GHC.Core.Make
-
--- NB: No argument can be representation-polymorphic
-mkCoreAppsDs :: SDoc -> CoreExpr -> [CoreExpr] -> CoreExpr
-mkCoreAppsDs s fun args = foldl' (mkCoreAppDs s) fun args
-
mkCastDs :: CoreExpr -> Coercion -> CoreExpr
-- We define a desugarer-specific version of GHC.Core.Utils.mkCast,
-- because in the immediate output of the desugarer, we can have
=====================================
compiler/GHC/Types/Id/Make.hs
=====================================
@@ -2199,7 +2199,7 @@ But actually we give 'noinline' a wired-in name for three distinct reasons:
Solution: in the desugarer, rewrite
noinline (f x y) ==> noinline f x y
- This is done in GHC.HsToCore.Utils.mkCoreAppDs.
+ This is done in the `noinlineId` case of `GHC.HsToCore.Expr.ds_app_var`
This is only needed for noinlineId, not noInlineConstraintId (wrinkle
(W1) below), because the latter never shows up in user code.
=====================================
compiler/GHC/Types/Var.hs
=====================================
@@ -82,7 +82,8 @@ module GHC.Types.Var (
-- * PiTyBinder
PiTyBinder(..), PiTyVarBinder,
- isInvisiblePiTyBinder, isVisiblePiTyBinder,
+ isInvisiblePiTyBinder, isInvisibleAnonPiTyBinder,
+ isVisiblePiTyBinder,
isTyBinder, isNamedPiTyBinder, isAnonPiTyBinder,
namedPiTyBinder_maybe, anonPiTyBinderType_maybe, piTyBinderType,
@@ -751,7 +752,6 @@ instance Outputable PiTyBinder where
ppr (Named (Bndr v Specified)) = char '@' <> ppr v
ppr (Named (Bndr v Inferred)) = braces (ppr v)
-
-- | 'PiTyVarBinder' is like 'PiTyBinder', but there can only be 'TyVar'
-- in the 'Named' field.
type PiTyVarBinder = PiTyBinder
@@ -761,6 +761,10 @@ isInvisiblePiTyBinder :: PiTyBinder -> Bool
isInvisiblePiTyBinder (Named (Bndr _ vis)) = isInvisibleForAllTyFlag vis
isInvisiblePiTyBinder (Anon _ af) = isInvisibleFunArg af
+isInvisibleAnonPiTyBinder :: PiTyBinder -> Bool
+isInvisibleAnonPiTyBinder (Named {}) = False
+isInvisibleAnonPiTyBinder (Anon _ af) = isInvisibleFunArg af
+
-- | Does this binder bind a visible argument?
isVisiblePiTyBinder :: PiTyBinder -> Bool
isVisiblePiTyBinder = not . isInvisiblePiTyBinder
=====================================
testsuite/tests/parser/should_compile/DumpTypecheckedAst.stderr
=====================================
@@ -1997,7 +1997,7 @@
(NoExtField)
(L
(EpAnn
- (EpaSpan { <no location info> })
+ (EpaSpan { DumpTypecheckedAst.hs:20:8-15 })
(NameAnnTrailing
[])
(EpaComments
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/d7def41c2c041f1332031466f2eb6f83f46c99c4
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/d7def41c2c041f1332031466f2eb6f83f46c99c4
You're receiving this email because of your account on gitlab.haskell.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/ghc-commits/attachments/20240927/b0ed3778/attachment-0001.html>
More information about the ghc-commits
mailing list