[Git][ghc/ghc][wip/T18914] Use HsOuterExplicit in instance sigs in deriving-generated code
Ryan Scott
gitlab at gitlab.haskell.org
Sat Nov 7 13:40:07 UTC 2020
Ryan Scott pushed to branch wip/T18914 at Glasgow Haskell Compiler / GHC
Commits:
1553ed81 by Ryan Scott at 2020-11-07T08:37:26-05:00
Use HsOuterExplicit in instance sigs in deriving-generated code
Issue #18914 revealed that `GeneralizedNewtypeDeriving` would generate code
that mentions unbound type variables, which is dangerously fragile. The
problem (and fix) is described in the new `Wrinkle: Use HsOuterExplicit`
in `Note [GND and QuantifiedConstraints]`. The gist of it: make sure to
put the top-level `forall`s in `deriving`-generated instance signatures in an
`HsOuterExplicit` to ensure that they scope over the bodies of methods
correctly. A side effect of this process is that it will expand any type
synonyms in the instance signature, which will surface any `forall`s that
are hidden underneath type synonyms (such as in the test case for #18914).
While I was in town, I also performed some maintenance on `NewHsTypeX`, which
powers `GeneralizedNewtypeDeriving`:
* I renamed `NewHsTypeX` to `HsCoreTy`, which more accurately describes its
intended purpose (#15706).
* To make sure that mistakes similar to #18914 do not occur later, I added an
additional validity check when renaming `HsCoreTy`s that complains if an
`HsCoreTy`s contains an out-of-scope type variable. See the new
`Note [Renaming HsCoreTys]` in `GHC.Rename.HsType` for the details.
Fixes #15706. Fixes #18914.
- - - - -
10 changed files:
- compiler/GHC/Hs/Type.hs
- compiler/GHC/Rename/HsType.hs
- compiler/GHC/Tc/Deriv/Generate.hs
- compiler/GHC/Tc/Gen/HsType.hs
- compiler/GHC/Tc/Gen/Sig.hs
- compiler/GHC/Tc/TyCl/Instance.hs
- testsuite/tests/deriving/should_compile/T14578.stderr
- + testsuite/tests/deriving/should_compile/T18914.hs
- testsuite/tests/deriving/should_compile/all.T
- utils/haddock
Changes:
=====================================
compiler/GHC/Hs/Type.hs
=====================================
@@ -24,7 +24,7 @@ module GHC.Hs.Type (
HsArrow(..), arrowToHsType,
hsLinear, hsUnrestricted, isUnrestricted,
- HsType(..), NewHsTypeX(..), LHsType, HsKind, LHsKind,
+ HsType(..), HsCoreTy(..), LHsType, HsKind, LHsKind,
HsForAllTelescope(..), HsTyVarBndr(..), LHsTyVarBndr,
LHsQTyVars(..),
HsOuterTyVarBndrs(..), HsOuterFamEqnTyVarBndrs, HsOuterSigTyVarBndrs,
@@ -1077,16 +1077,19 @@ data HsType pass
| XHsType
(XXType pass)
-data NewHsTypeX
- = NHsCoreTy Type -- An escape hatch for tunnelling a *closed*
- -- Core Type through HsSyn.
- -- See also Note [Typechecking NHsCoreTys] in
- -- GHC.Tc.Gen.HsType.
- deriving Data
+-- An escape hatch for tunnelling a Core 'Type' through 'HsType'.
+-- For more details on how this works, see:
+--
+-- * @Note [Renaming HsCoreTys]@ in "GHC.Rename.HsType"
+--
+-- * @Note [Typechecking HsCoreTys]@ in "GHC.Tc.Gen.HsType"
+newtype HsCoreTy
+ = HsCoreTy Type
-- ^ - 'GHC.Parser.Annotation.AnnKeywordId' : None
+ deriving Data
-instance Outputable NewHsTypeX where
- ppr (NHsCoreTy ty) = ppr ty
+instance Outputable HsCoreTy where
+ ppr (HsCoreTy ty) = ppr ty
type instance XForAllTy (GhcPass _) = NoExtField
type instance XQualTy (GhcPass _) = NoExtField
@@ -1124,7 +1127,7 @@ type instance XTyLit (GhcPass _) = NoExtField
type instance XWildCardTy (GhcPass _) = NoExtField
-type instance XXType (GhcPass _) = NewHsTypeX
+type instance XXType (GhcPass _) = HsCoreTy
-- Note [Literal source text] in GHC.Types.Basic for SourceText fields in
@@ -2250,7 +2253,7 @@ hsTypeNeedsParens p = go_hs_ty
go_hs_ty (HsOpTy{}) = p >= opPrec
go_hs_ty (HsParTy{}) = False
go_hs_ty (HsDocTy _ (L _ t) _) = go_hs_ty t
- go_hs_ty (XHsType (NHsCoreTy ty)) = go_core_ty ty
+ go_hs_ty (XHsType (HsCoreTy ty)) = go_core_ty ty
go_core_ty (TyVarTy{}) = False
go_core_ty (AppTy{}) = p >= appPrec
=====================================
compiler/GHC/Rename/HsType.hs
=====================================
@@ -39,6 +39,7 @@ import GHC.Prelude
import {-# SOURCE #-} GHC.Rename.Splice( rnSpliceType )
+import GHC.Core.TyCo.FVs ( tyCoVarsOfTypeList )
import GHC.Driver.Session
import GHC.Hs
import GHC.Rename.Env
@@ -48,6 +49,7 @@ import GHC.Rename.Utils ( HsDocContext(..), inHsDocContext, withHsDocContext
, checkShadowedRdrNames )
import GHC.Rename.Fixity ( lookupFieldFixityRn, lookupFixityRn
, lookupTyFixityRn )
+import GHC.Rename.Unbound ( notInScopeErr )
import GHC.Tc.Utils.Monad
import GHC.Types.Name.Reader
import GHC.Builtin.Names
@@ -634,10 +636,20 @@ rnHsTyKi env (HsDocTy _ ty haddock_doc)
= do { (ty', fvs) <- rnLHsTyKi env ty
; return (HsDocTy noExtField ty' haddock_doc, fvs) }
-rnHsTyKi _ (XHsType (NHsCoreTy ty))
- = return (XHsType (NHsCoreTy ty), emptyFVs)
- -- The emptyFVs probably isn't quite right
- -- but I don't think it matters
+-- See Note [Renaming HsCoreTys]
+rnHsTyKi env (XHsType (HsCoreTy ty))
+ = do mapM_ (check_in_scope . nameRdrName) fvs_list
+ return (XHsType (HsCoreTy ty), fvs)
+ where
+ fvs_list = map getName $ tyCoVarsOfTypeList ty
+ fvs = mkFVs fvs_list
+
+ check_in_scope :: RdrName -> RnM ()
+ check_in_scope rdr_name = do
+ mb_name <- lookupLocalOccRn_maybe rdr_name
+ when (isNothing mb_name) $
+ addErr $ withHsDocContext (rtke_ctxt env) $
+ notInScopeErr rdr_name
rnHsTyKi env ty@(HsExplicitListTy _ ip tys)
= do { data_kinds <- xoptM LangExt.DataKinds
@@ -661,6 +673,39 @@ rnHsArrow _env (HsLinearArrow u) = return (HsLinearArrow u, emptyFVs)
rnHsArrow env (HsExplicitMult u p)
= (\(mult, fvs) -> (HsExplicitMult u mult, fvs)) <$> rnLHsTyKi env p
+{-
+Note [Renaming HsCoreTys]
+~~~~~~~~~~~~~~~~~~~~~~~~~
+HsCoreTy is an escape hatch that allows embedding Core Types in HsTypes.
+As such, there's not much to be done in order to rename an HsCoreTy,
+since it's already been renamed to some extent. However, in an attempt to
+detect ill-formed HsCoreTys, the renamer checks to see if all free type
+variables in an HsCoreTy are in scope. To see why this can matter, consider
+this example from #18914:
+
+ type T f = forall a. f a
+
+ class C f where
+ m :: T f
+
+ newtype N f a = MkN (f a)
+ deriving C
+
+Because of #18914, a previous GHC would generate the following code:
+
+ instance C f => C (N f) where
+ m :: T (N f)
+ m = coerce @(f a) -- The type within @(...) is an HsCoreTy
+ @(N f a) -- So is this
+ (m @f)
+
+There are two HsCoreTys in play—(f a) and (N f a)—both of which have
+`f` and `a` as free type variables. The `f` is in scope from the instance head,
+but `a` is completely unbound, which is what led to #18914. To avoid this sort
+of mistake going forward, the renamer will now detect that `a` is unbound and
+throw an error accordingly.
+-}
+
--------------
rnTyVar :: RnTyKiEnv -> RdrName -> RnM Name
rnTyVar env rdr_name
=====================================
compiler/GHC/Tc/Deriv/Generate.hs
=====================================
@@ -1819,6 +1819,94 @@ a truly higher-rank type like so:
Then the same situation will arise again. But at least it won't arise for the
common case of methods with ordinary, prenex-quantified types.
+-----
+-- Wrinkle: Use HsOuterExplicit
+-----
+
+One minor complication with the plan above is that we need to ensure that the
+type variables from a method's instance signature properly scope over the body
+of the method. For example, recall:
+
+ instance (C m, forall p q. Coercible p q => Coercible (m p) (m q)) =>
+ C (T m) where
+ join :: forall a. T m (T m a) -> T m a
+ join = coerce @( m (m a) -> m a)
+ @(T m (T m a) -> T m a)
+ join
+
+In the example above, it is imperative that the `a` in the instance signature
+for `join` scope over the body of `join` by way of ScopedTypeVariables.
+This might sound obvious, but note that in gen_Newtype_binds, which is
+responsible for generating the code above, the type in `join`'s instance
+signature is given as a Core type, whereas gen_Newtype_binds will eventually
+produce HsBinds (i.e., source Haskell) that is renamed and typechecked. We
+must ensure that `a` is in scope over the body of `join` during renaming
+or else the generated code will be rejected.
+
+In short, we need to convert the instance signature from a Core type to an
+HsType (i.e., a source Haskell type). Two possible options are:
+
+1. Convert the Core type entirely to an HsType (i.e., a source Haskell type).
+2. Embed the entire Core type using HsCoreTy.
+
+Neither option is quite satisfactory:
+
+1. Converting a Core type to an HsType in full generality is surprisingly
+ complicated. Previous versions of GHCs did this, but it was the source of
+ numerous bugs (see #14579 and #16518, for instance).
+2. While HsCoreTy is much less complicated that option (1), it's not quite
+ what we want. In order for `a` to be in scope over the body of `join` during
+ renaming, the `forall` must be contained in an HsOuterExplicit.
+ (See Note [Lexically scoped type variables] in GHC.Hs.Type.) HsCoreTy
+ bypasses HsOuterExplicit, so this won't work either.
+
+As a compromise, we adopt a combination of the two options above:
+
+* Split apart the top-level ForAllTys in the instance signature's Core type,
+* Convert the top-level ForAllTys to an HsOuterExplicit, and
+* Embed the remainder of the Core type in an HsCoreTy.
+
+This retains most of the simplicity of option (2) while still ensuring that
+the type variables are correctly scoped.
+
+Note that splitting apart top-level ForAllTys will expand any type synonyms
+in the Core type itself. This ends up being important to fix a corner case
+observed in #18914. Consider this example:
+
+ type T f = forall a. f a
+
+ class C f where
+ m :: T f
+
+ newtype N f a = MkN (f a)
+ deriving C
+
+What code should `deriving C` generate? It will have roughly the following
+shape:
+
+ instance C f => C (N f) where
+ m :: T (N f)
+ m = coerce @(...) (...) (m @f)
+
+At a minimum, we must instantiate `coerce` with `@(T f)` and `@(T (N f))`, but
+with the `forall`s removed in order to make them monotypes. However, the
+`forall` is hidden underneath the `T` type synonym, so we must first expand `T`
+before we can strip of the `forall`. Expanding `T`, we get
+`coerce @(forall a. f a) @(forall a. N f a)`, and after omitting the `forall`s,
+we get `coerce @(f a) @(N f a)`.
+
+We can't stop there, however, or else we would end up with this code:
+
+ instance C f => C (N f) where
+ m :: T (N f)
+ m = coerce @(f a) @(N f a) (m @f)
+
+Notice that the type variable `a` is completely unbound. In order to make sure
+that `a` is in scope, we must /also/ expand the `T` in `m :: T (N f)` to get
+`m :: forall a. N f a`. Fortunately, we will do just that in the plan outlined
+above, since when we split off the top-level ForAllTys in the instance
+signature, we must first expand the T type synonym.
+
Note [GND and ambiguity]
~~~~~~~~~~~~~~~~~~~~~~~~
We make an effort to make the code generated through GND be robust w.r.t.
@@ -1891,13 +1979,30 @@ gen_Newtype_binds loc cls inst_tvs inst_tys rhs_ty
, -- The derived instance signature, e.g.,
--
-- op :: forall c. a -> [T x] -> c -> Int
+ --
+ -- Make sure that `forall c` is in an HsOuterExplicit so that it
+ -- scopes over the body of `op`. See "Wrinkle: Use HsOuterExplicit" in
+ -- Note [GND and QuantifiedConstraints].
L loc $ ClassOpSig noExtField False [loc_meth_RDR]
- $ L loc $ mkHsImplicitSigType $ nlHsCoreTy to_ty
+ $ L loc $ mkHsExplicitSigType
+ (map mk_hs_tvb to_tvbs)
+ (nlHsCoreTy to_rho)
)
where
Pair from_ty to_ty = mkCoerceClassMethEqn cls inst_tvs inst_tys rhs_ty meth_id
- (_, _, from_tau) = tcSplitSigmaTy from_ty
- (_, _, to_tau) = tcSplitSigmaTy to_ty
+ (_, _, from_tau) = tcSplitSigmaTy from_ty
+ (to_tvbs, to_rho) = tcSplitForAllTysInvis to_ty
+ (_, to_tau) = tcSplitPhiTy to_rho
+ -- The use of tcSplitForAllTysInvis above expands type synonyms, which
+ -- is important to ensure correct type variable scoping.
+ -- See "Wrinkle: Use HsOuterExplicit" in
+ -- Note [GND and QuantifiedConstraints].
+
+ mk_hs_tvb :: VarBndr TyVar flag -> LHsTyVarBndr flag GhcPs
+ mk_hs_tvb (Bndr tv flag) = noLoc $ KindedTyVar noExtField
+ flag
+ (noLoc (getRdrName tv))
+ (nlHsCoreTy (tyVarKind tv))
meth_RDR = getRdrName meth_id
loc_meth_RDR = L loc meth_RDR
@@ -1951,7 +2056,7 @@ nlHsAppType e s = noLoc (HsAppType noExtField e hs_ty)
hs_ty = mkHsWildCardBndrs $ parenthesizeHsType appPrec $ nlHsCoreTy s
nlHsCoreTy :: Type -> LHsType GhcPs
-nlHsCoreTy = noLoc . XHsType . NHsCoreTy
+nlHsCoreTy = noLoc . XHsType . HsCoreTy
mkCoerceClassMethEqn :: Class -- the class being derived
-> [TyVar] -- the tvs in the instance head (this includes
@@ -2079,15 +2184,15 @@ genAuxBindSpecDup loc original_rdr_name dup_spec
genAuxBindSpecSig :: SrcSpan -> AuxBindSpec -> LHsSigWcType GhcPs
genAuxBindSpecSig loc spec = case spec of
DerivCon2Tag tycon _
- -> mk_sig $ L loc $ XHsType $ NHsCoreTy $
+ -> mk_sig $ L loc $ XHsType $ HsCoreTy $
mkSpecSigmaTy (tyConTyVars tycon) (tyConStupidTheta tycon) $
mkParentType tycon `mkVisFunTyMany` intPrimTy
DerivTag2Con tycon _
-> mk_sig $ L loc $
- XHsType $ NHsCoreTy $ mkSpecForAllTys (tyConTyVars tycon) $
+ XHsType $ HsCoreTy $ mkSpecForAllTys (tyConTyVars tycon) $
intTy `mkVisFunTyMany` mkParentType tycon
DerivMaxTag _ _
- -> mk_sig (L loc (XHsType (NHsCoreTy intTy)))
+ -> mk_sig (L loc (XHsType (HsCoreTy intTy)))
DerivDataDataType _ _ _
-> mk_sig (nlHsTyVar dataType_RDR)
DerivDataConstr _ _ _
=====================================
compiler/GHC/Tc/Gen/HsType.hs
=====================================
@@ -942,8 +942,8 @@ tc_infer_hs_type mode (HsSpliceTy _ (HsSpliced _ _ (HsSplicedTy ty)))
tc_infer_hs_type mode (HsDocTy _ ty _) = tc_infer_lhs_type mode ty
--- See Note [Typechecking NHsCoreTys]
-tc_infer_hs_type _ (XHsType (NHsCoreTy ty))
+-- See Note [Typechecking HsCoreTys]
+tc_infer_hs_type _ (XHsType (HsCoreTy ty))
= do env <- getLclEnv
-- Raw uniques since we go from NameEnv to TvSubstEnv.
let subst_prs :: [(Unique, TcTyVar)]
@@ -967,21 +967,21 @@ tc_infer_hs_type mode other_ty
; return (ty', kv) }
{-
-Note [Typechecking NHsCoreTys]
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-NHsCoreTy is an escape hatch that allows embedding Core Types in HsTypes.
-As such, there's not much to be done in order to typecheck an NHsCoreTy,
+Note [Typechecking HsCoreTys]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+HsCoreTy is an escape hatch that allows embedding Core Types in HsTypes.
+As such, there's not much to be done in order to typecheck an HsCoreTy,
since it's already been typechecked to some extent. There is one thing that
we must do, however: we must substitute the type variables from the tcl_env.
To see why, consider GeneralizedNewtypeDeriving, which is one of the main
-clients of NHsCoreTy (example adapted from #14579):
+clients of HsCoreTy (example adapted from #14579):
newtype T a = MkT a deriving newtype Eq
This will produce an InstInfo GhcPs that looks roughly like this:
instance forall a_1. Eq a_1 => Eq (T a_1) where
- (==) = coerce @( a_1 -> a_1 -> Bool) -- The type within @(...) is an NHsCoreTy
+ (==) = coerce @( a_1 -> a_1 -> Bool) -- The type within @(...) is an HsCoreTy
@(T a_1 -> T a_1 -> Bool) -- So is this
(==)
@@ -997,9 +997,9 @@ environment (tcl_env) with [a_1 :-> a_2]. This gives us:
To ensure that the body of this instance is well scoped, every occurrence of
the `a` type variable should refer to a_2, the new skolem. However, the
-NHsCoreTys mention a_1, not a_2. Luckily, the tcl_env provides exactly the
+HsCoreTys mention a_1, not a_2. Luckily, the tcl_env provides exactly the
substitution we need ([a_1 :-> a_2]) to fix up the scoping. We apply this
-substitution to each NHsCoreTy and all is well:
+substitution to each HsCoreTy and all is well:
instance forall a_2. Eq a_2 => Eq (T a_2) where
(==) = coerce @( a_2 -> a_2 -> Bool)
@@ -1196,7 +1196,7 @@ tc_hs_type mode ty@(HsAppTy {}) ek = tc_infer_hs_type_ek mode ty ek
tc_hs_type mode ty@(HsAppKindTy{}) ek = tc_infer_hs_type_ek mode ty ek
tc_hs_type mode ty@(HsOpTy {}) ek = tc_infer_hs_type_ek mode ty ek
tc_hs_type mode ty@(HsKindSig {}) ek = tc_infer_hs_type_ek mode ty ek
-tc_hs_type mode ty@(XHsType (NHsCoreTy{})) ek = tc_infer_hs_type_ek mode ty ek
+tc_hs_type mode ty@(XHsType (HsCoreTy{})) ek = tc_infer_hs_type_ek mode ty ek
tc_hs_type mode ty@(HsWildCardTy _) ek = tcAnonWildCardOcc mode ty ek
{-
=====================================
compiler/GHC/Tc/Gen/Sig.hs
=====================================
@@ -291,7 +291,7 @@ no_anon_wc_ty lty = go lty
HsTyLit{} -> True
HsTyVar{} -> True
HsStarTy{} -> True
- XHsType (NHsCoreTy{}) -> True -- Core type, which does not have any wildcard
+ XHsType (HsCoreTy{}) -> True -- Core type, which does not have any wildcard
gos = all go
=====================================
compiler/GHC/Tc/TyCl/Instance.hs
=====================================
@@ -2002,7 +2002,7 @@ mkDefMethBind dfun_id clas sel_id dm_name
mk_vta :: LHsExpr GhcRn -> Type -> LHsExpr GhcRn
mk_vta fun ty = noLoc (HsAppType noExtField fun (mkEmptyWildCardBndrs $ nlHsParTy
- $ noLoc $ XHsType $ NHsCoreTy ty))
+ $ noLoc $ XHsType $ HsCoreTy ty))
-- NB: use visible type application
-- See Note [Default methods in instances]
=====================================
testsuite/tests/deriving/should_compile/T14578.stderr
=====================================
@@ -9,9 +9,8 @@ Derived class instances:
GHC.Base.sconcat ::
GHC.Base.NonEmpty (T14578.Wat f g a) -> T14578.Wat f g a
GHC.Base.stimes ::
- forall b.
- GHC.Real.Integral b =>
- b -> T14578.Wat f g a -> T14578.Wat f g a
+ forall (b :: *).
+ GHC.Real.Integral b => b -> T14578.Wat f g a -> T14578.Wat f g a
(GHC.Base.<>)
= GHC.Prim.coerce
@(T14578.App (Data.Functor.Compose.Compose f g) a
@@ -39,8 +38,10 @@ Derived class instances:
instance GHC.Base.Functor f =>
GHC.Base.Functor (T14578.App f) where
GHC.Base.fmap ::
- forall a b. (a -> b) -> T14578.App f a -> T14578.App f b
- (GHC.Base.<$) :: forall a b. a -> T14578.App f b -> T14578.App f a
+ forall (a :: *) (b :: *).
+ (a -> b) -> T14578.App f a -> T14578.App f b
+ (GHC.Base.<$) ::
+ forall (a :: *) (b :: *). a -> T14578.App f b -> T14578.App f a
GHC.Base.fmap
= GHC.Prim.coerce
@((a -> b) -> f a -> f b)
@@ -52,17 +53,19 @@ Derived class instances:
instance GHC.Base.Applicative f =>
GHC.Base.Applicative (T14578.App f) where
- GHC.Base.pure :: forall a. a -> T14578.App f a
+ GHC.Base.pure :: forall (a :: *). a -> T14578.App f a
(GHC.Base.<*>) ::
- forall a b.
+ forall (a :: *) (b :: *).
T14578.App f (a -> b) -> T14578.App f a -> T14578.App f b
GHC.Base.liftA2 ::
- forall a b c.
+ forall (a :: *) (b :: *) (c :: *).
(a -> b -> c) -> T14578.App f a -> T14578.App f b -> T14578.App f c
(GHC.Base.*>) ::
- forall a b. T14578.App f a -> T14578.App f b -> T14578.App f b
+ forall (a :: *) (b :: *).
+ T14578.App f a -> T14578.App f b -> T14578.App f b
(GHC.Base.<*) ::
- forall a b. T14578.App f a -> T14578.App f b -> T14578.App f a
+ forall (a :: *) (b :: *).
+ T14578.App f a -> T14578.App f b -> T14578.App f a
GHC.Base.pure
= GHC.Prim.coerce
@(a -> f a) @(a -> T14578.App f a) (GHC.Base.pure @f)
=====================================
testsuite/tests/deriving/should_compile/T18914.hs
=====================================
@@ -0,0 +1,12 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE RankNTypes #-}
+module T18914 where
+
+type T f = forall a. f a
+
+class C f where
+ m1 :: T f
+ m2 :: forall a. f a
+
+newtype N f a = MkN (f a)
+ deriving C
=====================================
testsuite/tests/deriving/should_compile/all.T
=====================================
@@ -127,3 +127,4 @@ test('T17339', normal, compile,
test('T17880', normal, compile, [''])
test('T18055', normal, compile, [''])
test('T18321', normal, compile, [''])
+test('T18914', normal, compile, [''])
=====================================
utils/haddock
=====================================
@@ -1 +1 @@
-Subproject commit ad9cbad7312a64e6757c32bd9488c55ba4f2fec9
+Subproject commit 6534129304fd113324acc4a0b6c6f0a7c04e0c59
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/1553ed815fae079b7464b8ba7516f9889b661ad7
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/1553ed815fae079b7464b8ba7516f9889b661ad7
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/20201107/158b3c42/attachment-0001.html>
More information about the ghc-commits
mailing list