[Git][ghc/ghc][wip/marge_bot_batch_merge_job] 4 commits: Ignore ticks in the pattern-match term oracle
Marge Bot pushed to branch wip/marge_bot_batch_merge_job at Glasgow Haskell Compiler / GHC Commits: b388d093 by Brian McKenna at 2026-07-18T17:51:50-04:00 Ignore ticks in the pattern-match term oracle The term-oracle in the pattern-match checker is keyed by a canonical form of the scrutinee, computed by `makeDictsCoherent`. That canonical form was tick-sensitive: two occurrences of an otherwise identical expression that happened to carry different ticks were treated as distinct values, breaking long-distance information. This shows up in practice under `-finfo-table-map`, because the desugarer wraps every record-selector use site in a `SourceNote` carrying that site's span. For example: data Box = Box { unBox :: Maybe Int } f b = case unBox b of Nothing -> 0 Just _ -> let Just x = unBox b in x The two `unBox b` expressionss carry different SourceNote spans, the pattern-match checker sees them as different, the long-distance information from the outer `Just _` branch never reaches the let-pattern, and `Just x = unBox b` is wrongly reported as non-exhaustive. We now strip all ticks in `makeDictsCoherent`. This is documented as Wrinkle (UD1) of Note [Unique dictionaries in the TmOracle CoreMap]. Fixes #27314 - - - - - c23e1acb by Mrjtjmn at 2026-07-18T17:52:45-04:00 Add explanations for unsolved Typeable constraints This commit adds explanations for unsolved 'Typeable' constraints. GHC will now provide additional explanations for an unsolved constraint of the form 'Typeable ty', explain why GHC did not solve Typeable constraint. e.g.: - 'ty' is a polymorphic type (e.g. forall a. a -> a) - 'ty' is a qualified type (e.g. Eq Int => Int) - 'ty' is an unboxed sum type - 'ty' is an unreduced type family application - 'ty' whose kind is not typeable Fixes #26532 - - - - - 3d51b87e by Artem Pelenitsyn at 2026-07-19T04:39:33-04:00 ghc-internal: Lock.hs: fix typo and indentation - - - - - 622b50f4 by Duncan Coutts at 2026-07-19T04:39:34-04:00 Fix failing test GcStaticPointers for non-moving GC Minor mistake in asserting something before checking for that same thing. Specifically, Bdescr asserts HEAP_ALLOCED_GC, but Bdescr was being used prior to a guard that checks HEAP_ALLOCED_GC. The solution is just to move the use of Bdescr after the guard. Thanks to Simon Jakobi for identifying the problem. - - - - - 17 changed files: - + changelog.d/T26532 - + changelog.d/T27314.md - compiler/GHC/HsToCore/Pmc/Solver.hs - compiler/GHC/Tc/Errors.hs - compiler/GHC/Tc/Errors/Ppr.hs - compiler/GHC/Tc/Errors/Types.hs - compiler/GHC/Tc/Instance/Typeable.hs - libraries/ghc-internal/src/GHC/Internal/IO/Handle/Lock.hs - rts/sm/NonMovingMark.c - + testsuite/tests/pmcheck/should_compile/T27314.hs - testsuite/tests/pmcheck/should_compile/all.T - testsuite/tests/typecheck/should_fail/T15067.stderr - + testsuite/tests/typecheck/should_fail/T26532.hs - + testsuite/tests/typecheck/should_fail/T26532.stderr - testsuite/tests/typecheck/should_fail/T9858b.stderr - testsuite/tests/typecheck/should_fail/TcStaticPointersFail02.stderr - testsuite/tests/typecheck/should_fail/all.T Changes: ===================================== changelog.d/T26532 ===================================== @@ -0,0 +1,8 @@ +section: compiler +issues: #26532 +mrs: !16351 +synopsis: + Add explanations for unsolved Typeable constraints +description: + GHC now provides additional explanations for an unsolved constraint of the + form ``Typeable ty``, explain why GHC did not solve ``Typeable`` constraint. ===================================== changelog.d/T27314.md ===================================== @@ -0,0 +1,10 @@ +section: compiler +issues: #27314 +mrs: !16118 +synopsis: + Fix spurious ``-Wincomplete-uni-patterns`` warning under ``-finfo-table-map``. +description: + The pattern-match checker now ignores ticks when comparing scrutinees in + its CoreMap, so long-distance information is no longer lost across + function-application scrutinees because debug source annotations + (e.g. SourceNotes added by ``-finfo-table-map``) were inserted. ===================================== compiler/GHC/HsToCore/Pmc/Solver.hs ===================================== @@ -1000,8 +1000,9 @@ makeDictsCoherent (Case scrut bndr ty alts) , let expr' = makeDictsCoherent expr ] makeDictsCoherent (Cast expr co) = Cast (makeDictsCoherent expr) co -makeDictsCoherent (Tick tick expr) - = Tick tick (makeDictsCoherent expr) +makeDictsCoherent (Tick _tick expr) + -- See Wrinkle (UD1) in Note [Unique dictionaries in the TmOracle CoreMap] + = makeDictsCoherent expr makeDictsCoherent ty@(Type {}) = ty makeDictsCoherent co@(Coercion {}) @@ -1061,6 +1062,25 @@ In the end, replacing dictionaries with an error value in the pattern-match checker was the most self-contained, although we might want to revisit once we implement a more robust approach to computing equality in the pattern-match checker (see #19272). + +Wrinkle (UD1): ticks +-------------------- +'makeDictsCoherent' also drops all ticks. The CoreMap key represents +value-level equality, which ticks never affect. + +Example (#27314): with -finfo-table-map every record-selector use site is +wrapped in a 'SourceNote' carrying that site's span (see +Note [Record-selector ticks] in GHC.HsToCore.Ticks). Given + + data Box = Box { unBox :: Maybe Int } + f b = case unBox b of + Nothing -> 0 + Just _ -> let Just x = unBox b in x + +the two `unBox b`s carry different SourceNote spans. Without tick stripping +the CoreMap treats them as distinct expressions. Long-distance information +from the outer `Just _` branch therefore never reaches the let-pattern, and +`Just x = unBox b` is wrongly reported as non-exhaustive. -} {- Note [The Pos/Neg invariant] ===================================== compiler/GHC/Tc/Errors.hs ===================================== @@ -12,7 +12,7 @@ module GHC.Tc.Errors( import GHC.Prelude -import GHC.Builtin.Names (hasFieldClassName) +import GHC.Builtin.Names (hasFieldClassName, typeableClassName) import GHC.Driver.Env (hsc_units) import GHC.Driver.DynFlags @@ -21,6 +21,7 @@ import GHC.Driver.Config.Diagnostic import GHC.Rename.Unbound +import GHC.Tc.Instance.Typeable (kindIsTypeable) import GHC.Tc.Types import GHC.Tc.Utils.Monad import GHC.Tc.Errors.Types @@ -2565,9 +2566,52 @@ getNoBuiltinInstMsg item = do { rdr_env <- getGlobalRdrEnv ; fam_envs <- tcGetFamInstEnvs ; mbNoHasFieldMsg <- hasFieldInfo_maybe rdr_env fam_envs item - ; return $ fmap NoBuiltinHasFieldMsg mbNoHasFieldMsg + ; mbNoTypeableMsg <- typeableInfo_maybe item + ; return $ case (mbNoHasFieldMsg, mbNoTypeableMsg) of + (Just hasFieldMsg, _) -> Just $ NoBuiltinHasFieldMsg hasFieldMsg + (_, Just typeableMsg) -> Just $ NoBuiltinTypeableMsg typeableMsg + _ -> Nothing } +-- | Try to produce an explanatory message for why GHC was not able to use +-- a built-in instance to solve a 'Typeable' constraint. +typeableInfo_maybe :: ErrorItem -> TcM (Maybe TypeableMsg) +typeableInfo_maybe item + | Just ty <- typeable_maybe (errorItemPred item) + = if -- Polymorphic types like (forall a. a -> a) are not typeable + -- see Note [No Typeable for polytypes or qualified types] in GHC.Tc.Instance.Class + | isForAllTy ty -> return $ Just $ NoTypeableForPolytype ty + + -- Qualified types like (Num a => blah) are not typeable + | Just (af,_mult_,_arg,_ret) <- splitFunTy_maybe ty + , not $ isVisibleFunArg af + -> return $ Just $ NoTypeableForQualifiedType ty + + -- Unboxed sum types like (# Int, Int #) are not typeable + | Just (tc, _tys) <- splitTyConApp_maybe ty + , isUnboxedSumTyCon tc -> return $ Just $ NoTypeableForUnboxedSumType ty + + -- Unreduced type family applications are not typeable + | Just (tc, _tys) <- splitTyConApp_maybe ty + , isTypeFamilyTyCon tc -> return $ Just $ NoTypeableForUnreducedTypeFamilyApplication ty + + -- TyCons whose kind is non-typeable, are not typeable + | Just (tc, _tys) <- splitTyConApp_maybe ty + , not (kindIsTypeable (tyConKind tc)) + -> return $ Just $ NoTypeableForTyConWithNonTypeableKind ty (tyConKind tc) + + | otherwise -> return Nothing + | otherwise = return Nothing + +-- | Is this constraint definitely a 'Typeable' constraint? +typeable_maybe :: PredType -> Maybe Type +typeable_maybe pred = + case classifyPredType pred of + ClassPred cls tys + | className cls == typeableClassName, [_k, ty] <- tys -> Just ty + _ -> Nothing + + {- Note [Error messages for unsolved HasField constraints] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The HasField type-class has special instance solving logic, implemented in @@ -2781,7 +2825,7 @@ hasFieldInfo_maybe rdr_env fam_inst_envs item -> TcM (Maybe (Either (PatSyn, SimilarName) (TyCon, SimilarName))) with_parent n = fmap (bimap (,n) (,n)) <$> get_parent n --- | Is this constraint definitely 'HasField'? +-- | Is this constraint definitely a 'HasField' constraint? hasField_maybe :: PredType -> Maybe (Type, Type, Type) hasField_maybe pred = case classifyPredType pred of ===================================== compiler/GHC/Tc/Errors/Ppr.hs ===================================== @@ -4314,15 +4314,6 @@ pprTcSolverReportMsg ctxt | any isFunTy (filterOutInvisibleTypes (classTyCon clas) tys) = text "(maybe you haven't applied a function to enough arguments?)" - -- Clarify the mysterious "No instance for (Typeable T) - | className clas == typeableClassName - , [_,ty] <- tys -- Look for (Typeable (k->*) (T k)) - , Just (tc,_) <- tcSplitTyConApp_maybe ty - , not (isTypeFamilyTyCon tc) - = hang (text "GHC can't yet do polykinded") - 2 (text "Typeable" <+> - parens (ppr ty <+> dcolon <+> ppr (typeKind ty))) - | otherwise = empty @@ -5200,6 +5191,7 @@ pprCoercibleMsg (OutOfScopeNewtypeConstructor dc import_suggs) = pprNoBuiltinInstanceMsg :: NoBuiltinInstanceMsg -> SDoc pprNoBuiltinInstanceMsg = \case NoBuiltinHasFieldMsg msg -> pprHasFieldMsg msg + NoBuiltinTypeableMsg msg -> pprTypeableMsg msg pprHasFieldMsg :: HasFieldMsg -> SDoc pprHasFieldMsg = \case @@ -5278,6 +5270,28 @@ pprHasFieldPatSynMsg fld pat_syns = in packHText occ == field_label fld +pprTypeableMsg :: TypeableMsg -> SDoc +pprTypeableMsg = \case + NoTypeableForPolytype ty -> + ppr_is ty "a polymorphic type" + NoTypeableForQualifiedType ty -> + ppr_is ty "a qualified type" + NoTypeableForUnboxedSumType ty -> + ppr_is ty "an unboxed sum type" + NoTypeableForUnreducedTypeFamilyApplication ty -> + ppr_is ty "an unreduced type family application" + NoTypeableForTyConWithNonTypeableKind ty k -> + vcat + [ text "NB:" <+> "The kind of" <+> quotes (ppr ty) + , nest 2 (quotes (ppr k)) <+> "contains foralls," + , "for which the built-in" + <+> quotes (text "Typeable") <+> text "solver cannot produce evidence." ] + where + ppr_is ty reason = + text "NB:" <+> quotes (ppr ty) <+> text "is" <+> text reason + $$ "for which the built-in" + <+> quotes (text "Typeable") <+> text "solver cannot produce evidence." + pprWhenMatching :: SolverReportErrCtxt -> WhenMatching -> SDoc pprWhenMatching ctxt (WhenMatching cty1 cty2 sub_o mb_sub_t_or_k) = sdocOption sdocPrintExplicitCoercions $ \printExplicitCoercions -> @@ -5516,6 +5530,7 @@ tcSolverReportMsgHints ctxt = \case noBuiltinInstanceHints :: NoBuiltinInstanceMsg -> [GhcHint] noBuiltinInstanceHints = \case NoBuiltinHasFieldMsg noHasFieldMsg -> hasFieldMsgHints noHasFieldMsg + NoBuiltinTypeableMsg _ -> noHints hasFieldMsgHints :: HasFieldMsg -> [GhcHint] hasFieldMsgHints = \case @@ -7998,4 +8013,3 @@ pprHsCtxt = \case ppr_stmt (TransStmt { trS_by = by, trS_using = using , trS_form = form }) = pprTransStmt by using form ppr_stmt stmt = pprStmt stmt - ===================================== compiler/GHC/Tc/Errors/Types.hs ===================================== @@ -78,6 +78,7 @@ module GHC.Tc.Errors.Types ( , CoercibleMsg(..) , NoBuiltinInstanceMsg(..) , HasFieldMsg(..) + , TypeableMsg(..) , TooFancyField(..) , PotentialInstances(..) , UnsupportedCallConvention(..) @@ -6034,9 +6035,9 @@ data CoercibleMsg -- a particular class. data NoBuiltinInstanceMsg = NoBuiltinHasFieldMsg HasFieldMsg + | NoBuiltinTypeableMsg TypeableMsg -- Other useful constructors might be: - -- NoBuiltinTypeableMsg -- explains polykinded Typeable restrictions -- NoBuiltinDataToTagMsg -- see conditions in Note [DataToTag overview] -- NoBuiltinWithDictMsg -- see Note [withDict] @@ -6066,6 +6067,20 @@ data HasFieldMsg -- | Using -XRebindableSyntax and a different 'HasField'. | CustomHasField TyCon -- ^ the custom HasField TyCon +-- | Explains why GHC wasn't able to provide a built-in 'Typeable' instance +-- for the given types. +data TypeableMsg + -- | Polymorphic types are not typeable. + = NoTypeableForPolytype Type + -- | Qualified types are not typeable. + | NoTypeableForQualifiedType Type + -- | Unboxed sum types are not typeable. + | NoTypeableForUnboxedSumType Type + -- | Unreduced Type Family Applications are not typeable. + | NoTypeableForUnreducedTypeFamilyApplication Type + -- | TyCons whose kind is not typeable are not typeable. + | NoTypeableForTyConWithNonTypeableKind Type Kind + -- | Why is a record field "too fancy" for GHC to be able to properly -- solve a 'HasField' constraint? data TooFancyField ===================================== compiler/GHC/Tc/Instance/Typeable.hs ===================================== @@ -7,7 +7,12 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeFamilies #-} -module GHC.Tc.Instance.Typeable(mkTypeableBinds, tyConIsTypeable) where +module GHC.Tc.Instance.Typeable ( + mkTypeableBinds + , tyConIsTypeable + , kindIsTypeable + ) +where import GHC.Prelude import GHC.Platform ===================================== libraries/ghc-internal/src/GHC/Internal/IO/Handle/Lock.hs ===================================== @@ -40,14 +40,14 @@ import GHC.Internal.IO.Handle.Lock.NoOp -- to interrupt it with asynchronous exceptions and/or for other threads to -- continue working, you MUST use threaded version of the runtime system. -- --- 2. The implementation uses relies on any of a number of locking --- facilities, depending upon what the platform supports: +-- 2. The implementation relies on any of a number of locking +-- facilities, depending upon what the platform supports: -- --- * 'LockFileEx' is used on Windows --- * On platforms that support it we use the @F_OFD_SETLK@ and @F_OFD_SETLKW@ @fnctl@s. --- * Otherwise we use @flock@ +-- * 'LockFileEx' is used on Windows +-- * On platforms that support it we use the @F_OFD_SETLK@ and @F_OFD_SETLKW@ @fnctl@s. +-- * Otherwise we use @flock@ -- --- hence all of their caveats also apply here. +-- All of their caveats also apply here. -- -- 3. On non-Windows platforms that don't support 'flock' (e.g. Solaris) this -- function throws 'FileLockingNotImplemented'. We deliberately choose to not ===================================== rts/sm/NonMovingMark.c ===================================== @@ -2018,8 +2018,10 @@ bool nonmovingTidyWeaks (struct MarkQueue_ *queue) // See Note [Weak pointer processing and the non-moving GC] in // MarkWeak.c - bdescr *key_bd = Bdescr((StgPtr) w->key); - bool key_in_nonmoving = HEAP_ALLOCED_GC(w->key) && block_get_flags(key_bd) & BF_NONMOVING; + bool key_in_nonmoving = + HEAP_ALLOCED_GC(w->key) && + block_get_flags(Bdescr((StgPtr) w->key)) & BF_NONMOVING; + if (!key_in_nonmoving || nonmovingIsNowAlive(w->key)) { nonmovingMarkLiveWeak(queue, w); did_work = true; ===================================== testsuite/tests/pmcheck/should_compile/T27314.hs ===================================== @@ -0,0 +1,8 @@ +module T27314 where + +data Box = Box { unBox :: Maybe Int } + +f :: Box -> Int +f b = case unBox b of + Nothing -> 0 + Just _ -> let Just x = unBox b in x ===================================== testsuite/tests/pmcheck/should_compile/all.T ===================================== @@ -93,6 +93,14 @@ test('T21360', normal, compile, [overlapping_incomplete+'-Wincomplete-record-upd test('T21360b', normal, compile, [overlapping_incomplete+'-Wincomplete-record-updates']) test('T23520', normal, compile, [overlapping_incomplete+'-Wincomplete-record-updates']) test('T25164', [extra_files(['T25164_aux.hs']), req_th], multimod_compile, ['T25164', '-v0']) +test( + 'T27314', + [ omit_ways(llvm_ways), # -finfo-table-map does not work with -fllvm (#26435) + when(js_arch(), skip) # javascript doesn't support -finfo-table-map yet and yields a warning we don't want to handle here + ], + compile, + ['-Wincomplete-uni-patterns -finfo-table-map'] +) # Other tests test('pmc001', [], compile, [overlapping_incomplete]) ===================================== testsuite/tests/typecheck/should_fail/T15067.stderr ===================================== @@ -1,13 +1,7 @@ T15067.hs:9:14: error: [GHC-39999] • No instance for ‘Typeable (# | #)’ arising from a use of ‘typeRep’ - GHC can't yet do polykinded - Typeable ((# | #) :: * - -> * - -> TYPE - (GHC.Internal.Types.SumRep - [GHC.Internal.Types.LiftedRep, - GHC.Internal.Types.LiftedRep])) + NB: ‘(# | #)’ is an unboxed sum type + for which the built-in ‘Typeable’ solver cannot produce evidence. • In the expression: typeRep In an equation for ‘floopadoop’: floopadoop = typeRep - ===================================== testsuite/tests/typecheck/should_fail/T26532.hs ===================================== @@ -0,0 +1,35 @@ +{-# LANGUAGE ImpredicativeTypes #-} +{-# LANGUAGE UnboxedSums #-} +{-# LANGUAGE TypeFamilies #-} + +module T26532 where + +import Data.Kind +import Type.Reflection + +test1 :: TypeRep (forall a. a -> a) +test1 = typeRep + +test2 :: TypeRep (Eq Int => Int) +test2 = typeRep + +test3 :: TypeRep (((),()) => Int) +test3 = typeRep + +test4 :: TypeRep (# Int | Bool #) +test4 = typeRep + +type F :: Type -> Type +type family F a where {} + +test5 :: TypeRep (F Int) +test5 = typeRep + +type PolyDF :: forall k. Type -> k +data family PolyDF a + +type ProxyPolyDF :: (forall k. Type -> k) -> Type +data ProxyPolyDF f = MkProxy + +test6 :: TypeRep (ProxyPolyDF PolyDF) +test6 = typeRep ===================================== testsuite/tests/typecheck/should_fail/T26532.stderr ===================================== @@ -0,0 +1,50 @@ +T26532.hs:11:9: error: [GHC-39999] + • No instance for ‘Typeable (forall a. a -> a)’ + arising from a use of ‘typeRep’ + NB: ‘forall a. a -> a’ is a polymorphic type + for which the built-in ‘Typeable’ solver cannot produce evidence. + • In the expression: typeRep + In an equation for ‘test1’: test1 = typeRep + +T26532.hs:14:9: error: [GHC-39999] + • No instance for ‘Typeable (Eq Int => Int)’ + arising from a use of ‘typeRep’ + NB: ‘Eq Int => Int’ is a qualified type + for which the built-in ‘Typeable’ solver cannot produce evidence. + • In the expression: typeRep + In an equation for ‘test2’: test2 = typeRep + +T26532.hs:17:9: error: [GHC-39999] + • No instance for ‘Typeable + ((() :: Constraint, () :: Constraint) => Int)’ + arising from a use of ‘typeRep’ + NB: ‘(() :: Constraint, () :: Constraint) => + Int’ is a qualified type + for which the built-in ‘Typeable’ solver cannot produce evidence. + • In the expression: typeRep + In an equation for ‘test3’: test3 = typeRep + +T26532.hs:20:9: error: [GHC-39999] + • No instance for ‘Typeable (# | #)’ + arising from a use of ‘typeRep’ + NB: ‘(# | #)’ is an unboxed sum type + for which the built-in ‘Typeable’ solver cannot produce evidence. + • In the expression: typeRep + In an equation for ‘test4’: test4 = typeRep + +T26532.hs:26:9: error: [GHC-39999] + • No instance for ‘Typeable (F Int)’ + arising from a use of ‘typeRep’ + NB: ‘F Int’ is an unreduced type family application + for which the built-in ‘Typeable’ solver cannot produce evidence. + • In the expression: typeRep + In an equation for ‘test5’: test5 = typeRep + +T26532.hs:35:9: error: [GHC-39999] + • No instance for ‘Typeable ProxyPolyDF’ + arising from a use of ‘typeRep’ + NB: The kind of ‘ProxyPolyDF’ + ‘(forall k. * -> k) -> *’ contains foralls, + for which the built-in ‘Typeable’ solver cannot produce evidence. + • In the expression: typeRep + In an equation for ‘test6’: test6 = typeRep ===================================== testsuite/tests/typecheck/should_fail/T9858b.stderr ===================================== @@ -2,7 +2,8 @@ T9858b.hs:7:8: error: [GHC-39999] • No instance for ‘Typeable (Eq Int => Int)’ arising from a use of ‘typeRep’ - (maybe you haven't applied a function to enough arguments?) + NB: ‘Eq Int => Int’ is a qualified type + for which the built-in ‘Typeable’ solver cannot produce evidence. • In the expression: typeRep (Proxy :: Proxy (Eq Int => Int)) In an equation for ‘test’: test = typeRep (Proxy :: Proxy (Eq Int => Int)) ===================================== testsuite/tests/typecheck/should_fail/TcStaticPointersFail02.stderr ===================================== @@ -11,6 +11,7 @@ TcStaticPointersFail02.hs:12:6: error: [GHC-39999] • No instance for ‘ghc-internal-0.1.0.0:GHC.Internal.Data.Typeable.Internal.Typeable (Monad m => a -> m a)’ arising from a static form - (maybe you haven't applied a function to enough arguments?) + NB: ‘Monad m => a -> m a’ is a qualified type + for which the built-in ‘Typeable’ solver cannot produce evidence. • In the expression: static return In an equation for ‘f2’: f2 = static return ===================================== testsuite/tests/typecheck/should_fail/all.T ===================================== @@ -761,3 +761,4 @@ test('T26823', normal, compile_fail, ['']) test('T26861', normal, compile_fail, ['']) test('T26862', normal, compile_fail, ['']) test('T27210', normal, compile_fail, ['']) +test('T26532', normal, compile_fail, ['']) View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/093125f22169b65bc0e9b00490ea2a2... -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/093125f22169b65bc0e9b00490ea2a2... 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)
-
Marge Bot (@marge-bot)