Commit 6e3c901d authored by Ryan Scott's avatar Ryan Scott Committed by Ben Gamari
Browse files

Fix #13983 by creating a TyConFlavour type, and using it

An error message was referring to a type synonym as a datatype.
Annoyingly, learning that the TyCon over which the error message is
operating is actually a type synonym was previously impossible, since
that code only had access to a TcTyCon, which doesn't retain any
information about what sort of TyCon it is.

To rectify this, I created a new TyConFlavour datatype, intended to
capture roughly what sort of TyCon we're dealing with. I then performing
the necessary plumbing to ensure all TcTyCons have a TyConFlavour, and
propagated this information through to the relevant error message.

Test Plan: ./validate

Reviewers: goldfire, austin, bgamari, simonpj

Reviewed By: simonpj

Subscribers: simonpj, rwbarton, thomie

GHC Trac Issues: #13983

Differential Revision: https://phabricator.haskell.org/D3747
parent c940e3b9
......@@ -1310,15 +1310,14 @@ tcWildCardBindersX new_wc wc_names thing_inside
--
-- This function does not do telescope checking.
kcHsTyVarBndrs :: Name -- ^ of the thing being checked
-> Bool -- ^ True <=> the TyCon being kind-checked can be unsaturated
-> TyConFlavour -- ^ What sort of 'TyCon' is being checked
-> Bool -- ^ True <=> the decl being checked has a CUSK
-> Bool -- ^ True <=> the decl is an open type/data family
-> Bool -- ^ True <=> all the hsq_implicit are *kind* vars
-- (will give these kind * if -XNoTypeInType)
-> LHsQTyVars GhcRn
-> TcM (Kind, r) -- ^ The result kind, possibly with other info
-> TcM (TcTyCon, r) -- ^ A suitably-kinded TcTyCon
kcHsTyVarBndrs name unsat cusk open_fam all_kind_vars
kcHsTyVarBndrs name flav cusk all_kind_vars
(HsQTvs { hsq_implicit = kv_ns, hsq_explicit = hs_tvs
, hsq_dependent = dep_names }) thing_inside
| cusk
......@@ -1353,12 +1352,12 @@ kcHsTyVarBndrs name unsat cusk open_fam all_kind_vars
`unionVarSet` tyCoVarsOfType res_kind
unmentioned_kvs = filterOut (`elemVarSet` all_mentioned_tvs)
scoped_kvs
; reportFloatingKvs name all_tc_tvs unmentioned_kvs
; reportFloatingKvs name flav all_tc_tvs unmentioned_kvs
; let final_binders = map (mkNamedTyConBinder Specified) good_tvs
++ tc_binders
tycon = mkTcTyCon name final_binders res_kind
unsat (scoped_kvs ++ tc_tvs)
(scoped_kvs ++ tc_tvs) flav
-- the tvs contain the binders already
-- in scope from an enclosing class, but
-- re-adding tvs to the env't doesn't cause
......@@ -1374,10 +1373,12 @@ kcHsTyVarBndrs name unsat cusk open_fam all_kind_vars
bind_telescope hs_tvs thing_inside
; let -- NB: Don't add scoped_kvs to tyConTyVars, because they
-- must remain lined up with the binders
tycon = mkTcTyCon name binders res_kind unsat
(scoped_kvs ++ binderVars binders)
tycon = mkTcTyCon name binders res_kind
(scoped_kvs ++ binderVars binders) flav
; return (tycon, stuff) }
where
open_fam = tcFlavourIsOpen flav
-- if -XNoTypeInType and we know all the implicits are kind vars,
-- just give the kind *. This prevents test
-- dependent/should_fail/KindLevelsB from compiling, as it should
......@@ -1741,7 +1742,7 @@ tcTyClTyVars tycon_name thing_inside
-- See Note [Free-floating kind vars]
; zonked_scoped_tvs <- mapM zonkTcTyVarToTyVar scoped_tvs
; let still_sig_tvs = filter isSigTyVar zonked_scoped_tvs
; checkNoErrs $ reportFloatingKvs tycon_name
; checkNoErrs $ reportFloatingKvs tycon_name (tyConFlavour tycon)
zonked_scoped_tvs still_sig_tvs
-- Add the *unzonked* tyvars to the env't, because those
......@@ -2131,11 +2132,12 @@ funAppCtxt fun arg arg_no
2 (quotes (ppr arg))
-- See Note [Free-floating kind vars]
reportFloatingKvs :: Name -- of the tycon
-> [TcTyVar] -- all tyvars, not necessarily zonked
-> [TcTyVar] -- floating tyvars
reportFloatingKvs :: Name -- of the tycon
-> TyConFlavour -- What sort of TyCon it is
-> [TcTyVar] -- all tyvars, not necessarily zonked
-> [TcTyVar] -- floating tyvars
-> TcM ()
reportFloatingKvs tycon_name all_tvs bad_tvs
reportFloatingKvs tycon_name flav all_tvs bad_tvs
= unless (null bad_tvs) $ -- don't bother zonking if there's no error
do { all_tvs <- mapM zonkTcTyVarToTyVar all_tvs
; bad_tvs <- mapM zonkTcTyVarToTyVar bad_tvs
......@@ -2147,7 +2149,7 @@ reportFloatingKvs tycon_name all_tvs bad_tvs
report typeintype tidy_all_tvs tidy_bad_tv
= addErr $
vcat [ text "Kind variable" <+> quotes (ppr tidy_bad_tv) <+>
text "is implicitly bound in datatype"
text "is implicitly bound in" <+> ppr flav
, quotes (ppr tycon_name) <> comma <+>
text "but does not appear as the kind of any"
, text "of its type variables. Perhaps you meant"
......
......@@ -950,7 +950,7 @@ tcConArgs :: ConLike -> [TcSigmaType]
tcConArgs con_like arg_tys (PrefixCon arg_pats) penv thing_inside
= do { checkTc (con_arity == no_of_args) -- Check correct arity
(arityErr "constructor" con_like con_arity no_of_args)
(arityErr (text "constructor") con_like con_arity no_of_args)
; let pats_w_tys = zipEqual "tcConArgs" arg_pats arg_tys
; (arg_pats', res) <- tcMultiple tcConArg pats_w_tys
penv thing_inside
......@@ -961,7 +961,7 @@ tcConArgs con_like arg_tys (PrefixCon arg_pats) penv thing_inside
tcConArgs con_like arg_tys (InfixCon p1 p2) penv thing_inside
= do { checkTc (con_arity == 2) -- Check correct arity
(arityErr "constructor" con_like con_arity 2)
(arityErr (text "constructor") con_like con_arity 2)
; let [arg_ty1,arg_ty2] = arg_tys -- This can't fail after the arity check
; ([p1',p2'], res) <- tcMultiple tcConArg [(p1,arg_ty1),(p2,arg_ty2)]
penv thing_inside
......
......@@ -369,6 +369,7 @@ kcTyClGroup decls
kc_binders = tyConBinders tc
kc_res_kind = tyConResKind tc
kc_tyvars = tyConTyVars tc
kc_flav = tyConFlavour tc
; kvs <- kindGeneralize (mkTyConKind kc_binders kc_res_kind)
; let all_binders = mkNamedTyConBinders Inferred kvs ++ kc_binders
......@@ -382,8 +383,8 @@ kcTyClGroup decls
, ppr kc_tyvars, ppr (tcTyConScopedTyVars tc)]
; return (mkTcTyCon name all_binders' kc_res_kind'
(mightBeUnsaturatedTyCon tc)
(tcTyConScopedTyVars tc)) }
(tcTyConScopedTyVars tc)
kc_flav) }
generaliseTCD :: TcTypeEnv
-> LTyClDecl GhcRn -> TcM [TcTyCon]
......@@ -482,21 +483,26 @@ getInitialKind :: TyClDecl GhcRn
getInitialKind decl@(ClassDecl { tcdLName = L _ name, tcdTyVars = ktvs, tcdATs = ats })
= do { let cusk = hsDeclHasCusk decl
; (tycon, inner_prs) <-
kcHsTyVarBndrs name True cusk False True ktvs $
kcHsTyVarBndrs name ClassFlavour cusk True ktvs $
do { inner_prs <- getFamDeclInitialKinds (Just cusk) ats
; return (constraintKind, inner_prs) }
; return (extendEnvWithTcTyCon inner_prs tycon) }
getInitialKind decl@(DataDecl { tcdLName = L _ name
, tcdTyVars = ktvs
, tcdDataDefn = HsDataDefn { dd_kindSig = m_sig } })
, tcdDataDefn = HsDataDefn { dd_kindSig = m_sig
, dd_ND = new_or_data } })
= do { (tycon, _) <-
kcHsTyVarBndrs name True (hsDeclHasCusk decl) False True ktvs $
kcHsTyVarBndrs name flav (hsDeclHasCusk decl) True ktvs $
do { res_k <- case m_sig of
Just ksig -> tcLHsKindSig ksig
Nothing -> return liftedTypeKind
; return (res_k, ()) }
; return (mkTcTyConEnv tycon) }
where
flav = case new_or_data of
NewType -> NewtypeFlavour
DataType -> DataTypeFlavour
getInitialKind (FamDecl { tcdFam = decl })
= getFamDeclInitialKind Nothing decl
......@@ -504,8 +510,9 @@ getInitialKind (FamDecl { tcdFam = decl })
getInitialKind decl@(SynDecl { tcdLName = L _ name
, tcdTyVars = ktvs
, tcdRhs = rhs })
= do { (tycon, _) <- kcHsTyVarBndrs name False (hsDeclHasCusk decl)
False {- not open -} True ktvs $
= do { (tycon, _) <- kcHsTyVarBndrs name TypeSynonymFlavour
(hsDeclHasCusk decl)
True ktvs $
do { res_k <- case kind_annotation rhs of
Nothing -> newMetaKindVar
Just ksig -> tcLHsKindSig ksig
......@@ -534,12 +541,12 @@ getFamDeclInitialKind mb_cusk decl@(FamilyDecl { fdLName = L _ name
, fdResultSig = L _ resultSig
, fdInfo = info })
= do { (tycon, _) <-
kcHsTyVarBndrs name unsat cusk open True ktvs $
kcHsTyVarBndrs name flav cusk True ktvs $
do { res_k <- case resultSig of
KindSig ki -> tcLHsKindSig ki
TyVarSig (L _ (KindedTyVar _ ki)) -> tcLHsKindSig ki
_ -- open type families have * return kind by default
| open -> return liftedTypeKind
| tcFlavourIsOpen flav -> return liftedTypeKind
-- closed type families have their return kind inferred
-- by default
| otherwise -> newMetaKindVar
......@@ -547,10 +554,10 @@ getFamDeclInitialKind mb_cusk decl@(FamilyDecl { fdLName = L _ name
; return (mkTcTyConEnv tycon) }
where
cusk = famDeclHasCusk mb_cusk decl
(open, unsat) = case info of
DataFamily -> (True, True)
OpenTypeFamily -> (True, False)
ClosedTypeFamily _ -> (False, False)
flav = case info of
DataFamily -> DataFamilyFlavour
OpenTypeFamily -> OpenTypeFamilyFlavour
ClosedTypeFamily _ -> ClosedTypeFamilyFlavour
------------------------------------------------------------------------
kcLTyClDecl :: LTyClDecl GhcRn -> TcM ()
......@@ -616,8 +623,10 @@ kcConDecl (ConDeclH98 { con_name = name, con_qvars = ex_tvs
= addErrCtxt (dataConCtxtName [name]) $
-- the 'False' says that the existentials don't have a CUSK, as the
-- concept doesn't really apply here. We just need to bring the variables
-- into scope.
do { _ <- kcHsTyVarBndrs (unLoc name) False False False False
-- into scope. (Similarly, the choice of PromotedDataConFlavour isn't
-- particularly important.)
do { _ <- kcHsTyVarBndrs (unLoc name) PromotedDataConFlavour
False False
((fromMaybe emptyLHsQTvs ex_tvs)) $
do { _ <- tcHsContext (fromMaybe (noLoc []) ex_ctxt)
; mapM_ (tcHsOpenType . getBangType) (hsConDeclArgTys details)
......@@ -3101,7 +3110,7 @@ addTyConCtxt tc
= addErrCtxt ctxt
where
name = getName tc
flav = text (tyConFlavour tc)
flav = ppr (tyConFlavour tc)
ctxt = hsep [ text "In the", flav
, text "declaration for", quotes (ppr name) ]
......
......@@ -987,7 +987,7 @@ tyConArityErr :: TyCon -> [TcType] -> SDoc
-- ignoring the /invisible/ arguments, which the user does not see.
-- (e.g. Trac #10516)
tyConArityErr tc tks
= arityErr (tyConFlavour tc) (tyConName tc)
= arityErr (ppr (tyConFlavour tc)) (tyConName tc)
tc_type_arity tc_type_args
where
vis_tks = filterOutInvisibleTypes tc tks
......@@ -997,9 +997,9 @@ tyConArityErr tc tks
tc_type_arity = count isVisibleTyConBinder (tyConBinders tc)
tc_type_args = length vis_tks
arityErr :: Outputable a => String -> a -> Int -> Int -> SDoc
arityErr :: Outputable a => SDoc -> a -> Int -> Int -> SDoc
arityErr what name n m
= hsep [ text "The" <+> text what, quotes (ppr name), text "should have",
= hsep [ text "The" <+> what, quotes (ppr name), text "should have",
n_arguments <> comma, text "but has been given",
if m==0 then text "none" else int m]
where
......@@ -1281,7 +1281,7 @@ checkValidInstance ctxt hs_type ty
= failWithTc (text "Instance head is not headed by a class")
| isNothing mb_cls
= failWithTc (vcat [ text "Illegal instance for a" <+> text (tyConFlavour tc)
= failWithTc (vcat [ text "Illegal instance for a" <+> ppr (tyConFlavour tc)
, text "A class instance must be for a class" ])
| not arity_ok
......
......@@ -13,7 +13,7 @@ module TyCon(
TyCon, AlgTyConRhs(..), visibleDataCons,
AlgTyConFlav(..), isNoParent,
FamTyConFlav(..), Role(..), Injectivity(..),
RuntimeRepInfo(..),
RuntimeRepInfo(..), TyConFlavour(..),
-- * TyConBinder
TyConBinder, TyConBndrVis(..),
......@@ -103,6 +103,9 @@ module TyCon(
newTyConCo, newTyConCo_maybe,
pprPromotionQuote, mkTyConKind,
-- ** Predicated on TyConFlavours
tcFlavourCanBeUnsaturated, tcFlavourIsOpen,
-- * Runtime type representation
TyConRepName, tyConRepName_maybe,
mkPrelTyConRepName,
......@@ -722,7 +725,6 @@ data TyCon
| TcTyCon {
tyConUnique :: Unique,
tyConName :: Name,
tyConUnsat :: Bool, -- ^ can this tycon be unsaturated?
-- See Note [The binders/kind/arity fields of a TyCon]
tyConBinders :: [TyConBinder], -- ^ Full binders
......@@ -731,8 +733,10 @@ data TyCon
tyConKind :: Kind, -- ^ Kind of this TyCon
tyConArity :: Arity, -- ^ Arity
tcTyConScopedTyVars :: [TyVar] -- ^ Scoped tyvars over the
-- tycon's body. See Note [TcTyCon]
tcTyConScopedTyVars :: [TyVar], -- ^ Scoped tyvars over the
-- tycon's body. See Note [TcTyCon]
tcTyConFlavour :: TyConFlavour
-- ^ What sort of 'TyCon' this represents.
}
-- | Represents right-hand-sides of 'TyCon's for algebraic types
......@@ -1052,7 +1056,7 @@ so the coercion tycon CoT must have
Note [TcTyCon]
~~~~~~~~~~~~~~
TcTyCons are used for tow distinct purposes
TcTyCons are used for two distinct purposes
1. When recovering from a type error in a type declaration,
we want to put the erroneous TyCon in the environment in a
......@@ -1456,19 +1460,19 @@ mkSumTyCon name binders res_kind arity tyvars cons parent
mkTcTyCon :: Name
-> [TyConBinder]
-> Kind -- ^ /result/ kind only
-> Bool -- ^ Can this be unsaturated?
-> [TyVar] -- ^ Scoped type variables, see Note [TcTyCon]
-> TyConFlavour -- ^ What sort of 'TyCon' this represents
-> TyCon
mkTcTyCon name binders res_kind unsat scoped_tvs
mkTcTyCon name binders res_kind scoped_tvs flav
= TcTyCon { tyConUnique = getUnique name
, tyConName = name
, tyConTyVars = binderVars binders
, tyConBinders = binders
, tyConResKind = res_kind
, tyConKind = mkTyConKind binders res_kind
, tyConUnsat = unsat
, tyConArity = length binders
, tcTyConScopedTyVars = scoped_tvs }
, tcTyConScopedTyVars = scoped_tvs
, tcTyConFlavour = flav }
-- | Create an unlifted primitive 'TyCon', such as @Int#@.
mkPrimTyCon :: Name -> [TyConBinder]
......@@ -1587,7 +1591,8 @@ makeRecoveryTyCon :: TyCon -> TyCon
makeRecoveryTyCon tc
= mkTcTyCon (tyConName tc)
(tyConBinders tc) (tyConResKind tc)
(mightBeUnsaturatedTyCon tc) [{- no scoped vars -}]
[{- no scoped vars -}]
(tyConFlavour tc)
-- | Does this 'TyCon' represent something that cannot be defined in Haskell?
isPrimTyCon :: TyCon -> Bool
......@@ -1798,10 +1803,7 @@ isFamFreeTyCon _ = True
-- type synonym, because you should probably have expanded it first
-- But regardless, it's not decomposable
mightBeUnsaturatedTyCon :: TyCon -> Bool
mightBeUnsaturatedTyCon (SynonymTyCon {}) = False
mightBeUnsaturatedTyCon (FamilyTyCon { famTcFlav = flav}) = isDataFamFlav flav
mightBeUnsaturatedTyCon (TcTyCon { tyConUnsat = unsat }) = unsat
mightBeUnsaturatedTyCon _other = True
mightBeUnsaturatedTyCon = tcFlavourCanBeUnsaturated . tyConFlavour
-- | Is this an algebraic 'TyCon' declared with the GADT syntax?
isGadtSyntaxTyCon :: TyCon -> Bool
......@@ -2271,26 +2273,92 @@ instance Outputable TyCon where
-- corresponding TyCon, so we add the quote to distinguish it here
ppr tc = pprPromotionQuote tc <> ppr (tyConName tc)
tyConFlavour :: TyCon -> String
-- | Paints a picture of what a 'TyCon' represents, in broad strokes.
-- This is used towards more informative error messages.
data TyConFlavour
= ClassFlavour
| TupleFlavour Boxity
| SumFlavour
| DataTypeFlavour
| NewtypeFlavour
| AbstractTypeFlavour
| DataFamilyFlavour
| OpenTypeFamilyFlavour
| ClosedTypeFamilyFlavour
| TypeSynonymFlavour
| BuiltInTypeFlavour -- ^ e.g., the @(->)@ 'TyCon'.
| PromotedDataConFlavour
deriving Eq
instance Outputable TyConFlavour where
ppr = text . go
where
go ClassFlavour = "class"
go (TupleFlavour boxed) | isBoxed boxed = "tuple"
| otherwise = "unboxed tuple"
go SumFlavour = "unboxed sum"
go DataTypeFlavour = "data type"
go NewtypeFlavour = "newtype"
go AbstractTypeFlavour = "abstract type"
go DataFamilyFlavour = "data family"
go OpenTypeFamilyFlavour = "type family"
go ClosedTypeFamilyFlavour = "type family"
go TypeSynonymFlavour = "type synonym"
go BuiltInTypeFlavour = "built-in type"
go PromotedDataConFlavour = "promoted data constructor"
tyConFlavour :: TyCon -> TyConFlavour
tyConFlavour (AlgTyCon { algTcParent = parent, algTcRhs = rhs })
| ClassTyCon _ _ <- parent = "class"
| ClassTyCon _ _ <- parent = ClassFlavour
| otherwise = case rhs of
TupleTyCon { tup_sort = sort }
| isBoxed (tupleSortBoxity sort) -> "tuple"
| otherwise -> "unboxed tuple"
SumTyCon {} -> "unboxed sum"
DataTyCon {} -> "data type"
NewTyCon {} -> "newtype"
AbstractTyCon {} -> "abstract type"
-> TupleFlavour (tupleSortBoxity sort)
SumTyCon {} -> SumFlavour
DataTyCon {} -> DataTypeFlavour
NewTyCon {} -> NewtypeFlavour
AbstractTyCon {} -> AbstractTypeFlavour
tyConFlavour (FamilyTyCon { famTcFlav = flav })
| isDataFamFlav flav = "data family"
| otherwise = "type family"
tyConFlavour (SynonymTyCon {}) = "type synonym"
tyConFlavour (FunTyCon {}) = "built-in type"
tyConFlavour (PrimTyCon {}) = "built-in type"
tyConFlavour (PromotedDataCon {}) = "promoted data constructor"
tyConFlavour tc@(TcTyCon {})
= pprPanic "tyConFlavour sees a TcTyCon" (ppr tc)
= case flav of
DataFamilyTyCon{} -> DataFamilyFlavour
OpenSynFamilyTyCon -> OpenTypeFamilyFlavour
ClosedSynFamilyTyCon{} -> ClosedTypeFamilyFlavour
AbstractClosedSynFamilyTyCon -> ClosedTypeFamilyFlavour
BuiltInSynFamTyCon{} -> ClosedTypeFamilyFlavour
tyConFlavour (SynonymTyCon {}) = TypeSynonymFlavour
tyConFlavour (FunTyCon {}) = BuiltInTypeFlavour
tyConFlavour (PrimTyCon {}) = BuiltInTypeFlavour
tyConFlavour (PromotedDataCon {}) = PromotedDataConFlavour
tyConFlavour (TcTyCon { tcTyConFlavour = flav }) = flav
-- | Can this flavour of 'TyCon' appear unsaturated?
tcFlavourCanBeUnsaturated :: TyConFlavour -> Bool
tcFlavourCanBeUnsaturated ClassFlavour = True
tcFlavourCanBeUnsaturated DataTypeFlavour = True
tcFlavourCanBeUnsaturated NewtypeFlavour = True
tcFlavourCanBeUnsaturated DataFamilyFlavour = True
tcFlavourCanBeUnsaturated TupleFlavour{} = True
tcFlavourCanBeUnsaturated SumFlavour = True
tcFlavourCanBeUnsaturated AbstractTypeFlavour = True
tcFlavourCanBeUnsaturated BuiltInTypeFlavour = True
tcFlavourCanBeUnsaturated PromotedDataConFlavour = True
tcFlavourCanBeUnsaturated TypeSynonymFlavour = False
tcFlavourCanBeUnsaturated OpenTypeFamilyFlavour = False
tcFlavourCanBeUnsaturated ClosedTypeFamilyFlavour = False
-- | Is this flavour of 'TyCon' an open type family or a data family?
tcFlavourIsOpen :: TyConFlavour -> Bool
tcFlavourIsOpen DataFamilyFlavour = True
tcFlavourIsOpen OpenTypeFamilyFlavour = True
tcFlavourIsOpen ClosedTypeFamilyFlavour = False
tcFlavourIsOpen ClassFlavour = False
tcFlavourIsOpen DataTypeFlavour = False
tcFlavourIsOpen NewtypeFlavour = False
tcFlavourIsOpen TupleFlavour{} = False
tcFlavourIsOpen SumFlavour = False
tcFlavourIsOpen AbstractTypeFlavour = False
tcFlavourIsOpen BuiltInTypeFlavour = False
tcFlavourIsOpen PromotedDataConFlavour = False
tcFlavourIsOpen TypeSynonymFlavour = False
pprPromotionQuote :: TyCon -> SDoc
-- Promoted data constructors already have a tick in their OccName
......
<interactive>:2:1: error:
Kind variable ‘k’ is implicitly bound in datatype
Kind variable ‘k’ is implicitly bound in data type
‘D1’, but does not appear as the kind of any
of its type variables. Perhaps you meant
to bind it explicitly somewhere?
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
module T13983 where
import Data.Proxy
type Wat = forall (a :: k). Proxy a
T13983.hs:7:1: error:
• Kind variable ‘k’ is implicitly bound in type synonym
‘Wat’, but does not appear as the kind of any
of its type variables. Perhaps you meant
to bind it (with TypeInType) explicitly somewhere?
Type variables with inferred kinds: (k :: *)
• In the type declaration for ‘Wat’
......@@ -445,3 +445,4 @@ test('T13640', normal, compile_fail, [''])
test('T13677', normal, compile_fail, [''])
test('T13821A', expect_broken(13821), run_command, ['$MAKE -s --no-print-directory T13821A'])
test('T13821B', expect_broken(13821), backpack_typecheck_fail, [''])
test('T13983', normal, compile_fail, [''])
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment