diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
 # kempe
 
+## 0.1.0.1
+
+  * Better debug pretty-printer
+  * Pattern match exchaustiveness checker so that pattern matches don't do
+    something heinous at runtime
+
 ## 0.1.0.0
 
 Initial release
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
 Inspiration is primarily from [Mirth](https://github.com/mirth-lang/mirth).
 
 See manual
-[here](http://hackage.haskell.org/package/kempe/src/doc/manual.pdf).
+[here](http://hackage.haskell.org/package/kempe/src/docs/manual.pdf).
 
 ## Installation
 
@@ -28,8 +28,6 @@
   * Unification takes too long
   * Errors don't have position information
   * Monomorphization fails on recursive polymorphic functions
-  * If pattern matches fail at runtime, code just keeps running with whatever
-    was after the jumps (no pattern match exhaustiveness checker)
   * Can't export or call C functions with more than 6 arguments; can't call or
     export large arguments (i.e. structs) passed by value.
 
diff --git a/bench/Bench.hs b/bench/Bench.hs
--- a/bench/Bench.hs
+++ b/bench/Bench.hs
@@ -9,6 +9,7 @@
 import           Kempe.Asm.X86.ControlFlow
 import           Kempe.Asm.X86.Linear
 import           Kempe.Asm.X86.Liveness
+import           Kempe.Check.Pattern
 import           Kempe.File
 import           Kempe.IR
 import           Kempe.IR.Opt
@@ -43,6 +44,10 @@
                       , bench "closedModule" $ nf (runSpecialize =<<) (runAssign p)
                       , bench "closure" $ nf (\m -> closure (m, mkModuleMap m)) (bivoid <$> snd p)
                       ]
+                , env eitherMod $ \ e ->
+                    bgroup "Pattern match exhaustiveness checker"
+                        [ bench "lib/either.kmp" $ nf checkModuleExhaustive e
+                        ]
                   , env parsedInteresting $ \ ~(f, n) ->
                       bgroup "Inliner"
                         [ bench "examples/factorial.kmp" $ nf inline (snd f)
@@ -96,6 +101,7 @@
           splitmix = yeetIO . parseWithMax =<< BSL.readFile "examples/splitmix.kmp"
           fac = yeetIO . parseWithMax =<< BSL.readFile "examples/factorial.kmp"
           num = yeetIO . parseWithMax =<< BSL.readFile "lib/numbertheory.kmp"
+          eitherMod = yeetIO . parse =<< BSL.readFile "lib/either.kmp"
           parsedInteresting = (,) <$> fac <*> num
           prelude = yeetIO . parseWithMax =<< BSL.readFile "prelude/fn.kmp"
           forTyEnv = (,,) <$> parsedM <*> splitmix <*> prelude
diff --git a/docs/manual.pdf b/docs/manual.pdf
Binary files a/docs/manual.pdf and b/docs/manual.pdf differ
diff --git a/kempe.cabal b/kempe.cabal
--- a/kempe.cabal
+++ b/kempe.cabal
@@ -1,6 +1,6 @@
 cabal-version:   3.0
 name:            kempe
-version:         0.1.0.0
+version:         0.1.0.1
 license:         BSD-3-Clause
 license-file:    LICENSE
 copyright:       Copyright: (c) 2020 Vanessa McHale
@@ -40,6 +40,7 @@
         Kempe.Pipeline
         Kempe.Shuttle
         Kempe.Inline
+        Kempe.Check.Pattern
         Kempe.IR
         Kempe.IR.Opt
         Kempe.Asm.X86
@@ -49,6 +50,7 @@
 
     hs-source-dirs:   src
     other-modules:
+        Kempe.Check.Restrict
         Kempe.Unique
         Kempe.Name
         Kempe.Error
diff --git a/src/Data/Foldable/Ext.hs b/src/Data/Foldable/Ext.hs
--- a/src/Data/Foldable/Ext.hs
+++ b/src/Data/Foldable/Ext.hs
@@ -1,7 +1,12 @@
 module Data.Foldable.Ext ( foldMapA
+                         , foldMapAlternative
                          ) where
 
-import           Data.Foldable (fold)
+import           Control.Applicative (Alternative)
+import           Data.Foldable       (asum, fold)
+
+foldMapAlternative :: (Traversable t, Alternative f) => (a -> f b) -> t a -> f b
+foldMapAlternative f xs = asum (f <$> xs)
 
 foldMapA :: (Applicative f, Traversable t, Monoid m) => (a -> f m) -> t a -> f m
 foldMapA = (fmap fold .) . traverse
diff --git a/src/Kempe/AST.hs b/src/Kempe/AST.hs
--- a/src/Kempe/AST.hs
+++ b/src/Kempe/AST.hs
@@ -34,6 +34,7 @@
 import           Control.DeepSeq         (NFData)
 import           Data.Bifunctor          (Bifunctor (..))
 import qualified Data.ByteString.Lazy    as BSL
+import           Data.Foldable           (toList)
 import           Data.Functor            (void)
 import           Data.Int                (Int64, Int8)
 import qualified Data.IntMap             as IM
@@ -47,7 +48,8 @@
 import           Kempe.Name
 import           Kempe.Unique
 import           Numeric.Natural
-import           Prettyprinter           (Doc, Pretty (pretty), align, braces, brackets, colon, concatWith, fillSep, hsep, parens, pipe, sep, (<+>))
+import           Prettyprinter           (Doc, Pretty (pretty), align, braces, brackets, colon, concatWith, fillSep, hsep, parens, pipe, sep, vsep, (<+>))
+import           Prettyprinter.Ext
 
 data BuiltinTy = TyInt
                | TyBool
@@ -100,7 +102,7 @@
     pretty (TyApp _ ty ty') = parens (pretty ty <+> pretty ty')
 
 data Pattern c b = PatternInt b Integer
-                 | PatternCons c (TyName c) -- a constructed pattern
+                 | PatternCons { patternKind :: c, patternName :: TyName c } -- a constructed pattern
                  | PatternWildcard b
                  | PatternBool b Bool
                  deriving (Eq, Ord, Generic, NFData, Functor, Foldable, Traversable)
@@ -118,6 +120,10 @@
     pretty PatternWildcard{}  = "_"
     pretty (PatternCons _ tn) = pretty tn
 
+prettyTypedPattern :: Pattern (StackType ()) (StackType ()) -> Doc ann
+prettyTypedPattern (PatternCons ty tn) = parens (pretty tn <+> ":" <+> pretty ty)
+prettyTypedPattern p                   = pretty p
+
 instance Pretty (Atom c a) where
     pretty (AtName _ n)    = pretty n
     pretty (Dip _ as)      = "dip(" <> fillSep (fmap pretty as) <> ")"
@@ -128,7 +134,14 @@
     pretty (BoolLit _ b)   = pretty b
     pretty (WordLit _ w)   = pretty w <> "u"
     pretty (Int8Lit _ i)   = pretty i <> "i8"
+    pretty (Case _ ls)     = "case" <+> braces (align (vsep (toList $ fmap (uncurry prettyLeaf) ls)))
 
+prettyLeaf :: Pattern c a -> [Atom c a] -> Doc ann
+prettyLeaf p as = pipe <+> pretty p <+> "->" <+> align (fillSep (fmap pretty as))
+
+prettyTypedLeaf :: Pattern (StackType ()) (StackType ()) -> [Atom (StackType ()) (StackType ())] -> Doc ann
+prettyTypedLeaf p as = pipe <+> prettyTypedPattern p <+> "->" <+> align (fillSep (fmap prettyTyped as))
+
 prettyTyped :: Atom (StackType ()) (StackType ()) -> Doc ann
 prettyTyped (AtName ty n)    = parens (pretty n <+> ":" <+> pretty ty)
 prettyTyped (Dip _ as)       = "dip(" <> fillSep (prettyTyped <$> as) <> ")"
@@ -139,6 +152,7 @@
 prettyTyped (BoolLit _ b)    = pretty b
 prettyTyped (Int8Lit _ i)    = pretty i <> "i8"
 prettyTyped (WordLit _ n)    = pretty n <> "u"
+prettyTyped (Case _ ls)      = "case" <+> braces (align (vsep (toList $ fmap (uncurry prettyTypedLeaf) ls)))
 
 data Atom c b = AtName b (Name b)
               | Case b (NonEmpty (Pattern c b, [Atom c b]))
@@ -240,9 +254,9 @@
     pretty Kabi = "kabi"
 
 prettyKempeDecl :: (Atom c b -> Doc ann) -> KempeDecl a c b -> Doc ann
-prettyKempeDecl atomizer (FunDecl _ n is os as) = pretty n <+> ":" <+> sep (fmap pretty is) <+> "--" <+> sep (fmap pretty os) <+> "=:" <+> brackets (align (fillSep (atomizer <$> as)))
+prettyKempeDecl atomizer (FunDecl _ n is os as) = pretty n <+> align (":" <+> sep (fmap pretty is) <+> "--" <+> sep (fmap pretty os) <#> "=:" <+> brackets (align (fillSep (atomizer <$> as))))
 prettyKempeDecl _ (Export _ abi n)              = "%foreign" <+> pretty abi <+> pretty n
-prettyKempeDecl _ (ExtFnDecl _ n is os b)       = pretty n <+> ":" <+> sep (fmap pretty is) <+> "--" <+> sep (fmap pretty os) <+> "=:" <+> "$cfun" <> pretty (decodeUtf8 b)
+prettyKempeDecl _ (ExtFnDecl _ n is os b)       = pretty n <+> align (":" <+> sep (fmap pretty is) <+> "--" <+> sep (fmap pretty os) <#> "=:" <+> "$cfun" <> pretty (decodeUtf8 b))
 prettyKempeDecl _ (TyDecl _ tn ns ls)           = "type" <+> pretty tn <+> hsep (fmap pretty ns) <+> braces (concatWith (\x y -> x <+> pipe <+> y) $ fmap (uncurry prettyTyLeaf) ls)
 
 instance Pretty (KempeDecl a b c) where
diff --git a/src/Kempe/Check/Pattern.hs b/src/Kempe/Check/Pattern.hs
new file mode 100644
--- /dev/null
+++ b/src/Kempe/Check/Pattern.hs
@@ -0,0 +1,100 @@
+-- | Check pattern match exhaustiveness, since we don't really handle that in
+-- source code.
+--
+-- This is pretty easy because of how patterns work in Kempe.
+--
+-- Some of this code is from Dickinson, but we don't need the Maranget approach
+-- because the pattern matching is simpler in Kempe.
+module Kempe.Check.Pattern ( checkModuleExhaustive
+                           ) where
+
+import           Control.Monad              (forM_)
+import           Control.Monad.State.Strict (State, execState)
+import           Data.Coerce                (coerce)
+import           Data.Foldable              (toList, traverse_)
+import           Data.Foldable.Ext
+import qualified Data.IntMap.Strict         as IM
+import qualified Data.IntSet                as IS
+import           Data.List.NonEmpty         (NonEmpty (..))
+import           Kempe.AST
+import           Kempe.Error
+import           Kempe.Name
+import           Kempe.Unique
+import           Lens.Micro                 (Lens')
+import           Lens.Micro.Mtl             (modifying)
+
+checkAtom :: PatternEnv -> Atom c b -> Maybe (Error b)
+checkAtom env (Case l ls) =
+    if isExhaustive env $ fmap fst ls
+        then Nothing
+        else Just (InexhaustiveMatch l)
+checkAtom _ _ = Nothing
+
+checkDecl :: PatternEnv -> KempeDecl a c b -> Maybe (Error b)
+checkDecl env (FunDecl _ _ _ _ as) = foldMapAlternative (checkAtom env) as
+checkDecl _ _                      = Nothing
+
+checkModule :: PatternEnv -> Module a c b -> Maybe (Error b)
+checkModule env = foldMapAlternative (checkDecl env)
+
+checkModuleExhaustive :: Module a c b -> Maybe (Error b)
+checkModuleExhaustive m =
+    let env = runPatternM $ patternEnvDecls m
+        in checkModule env m
+
+data PatternEnv = PatternEnv { allCons :: IM.IntMap IS.IntSet -- ^ all constructors indexed by type
+                             , types   :: IM.IntMap Int -- ^ all types indexed by constructor
+                             }
+
+allConsLens :: Lens' PatternEnv (IM.IntMap IS.IntSet)
+allConsLens f s = fmap (\x -> s { allCons = x }) (f (allCons s))
+
+typesLens :: Lens' PatternEnv (IM.IntMap Int)
+typesLens f s = fmap (\x -> s { types = x }) (f (types s))
+
+type PatternM = State PatternEnv
+
+patternEnvDecls :: Module a c b -> PatternM ()
+patternEnvDecls = traverse_ declAdd
+
+declAdd :: KempeDecl a c b -> PatternM ()
+declAdd FunDecl{}                             = pure ()
+declAdd ExtFnDecl{}                           = pure ()
+declAdd Export{}                              = pure ()
+declAdd (TyDecl _ (Name _ (Unique i) _) _ ls) = do
+    forM_ ls $ \(Name _ (Unique j) _, _) ->
+        modifying typesLens (IM.insert j i)
+    let cons = IS.fromList $ toList (unUnique . unique . fst <$> ls)
+    modifying allConsLens (IM.insert i cons)
+
+runPatternM :: PatternM a -> PatternEnv
+runPatternM = flip execState (PatternEnv mempty mempty)
+
+internalError :: a
+internalError = error "Internal error: lookup in a PatternEnv failed"
+
+-- given a constructor name, get the IntSet of all constructors of that type
+assocUniques :: PatternEnv -> Name a -> IS.IntSet
+assocUniques env (Name _ (Unique i) _) =
+    let ty = IM.findWithDefault internalError i (types env)
+        in IM.findWithDefault internalError ty (allCons env)
+
+hasWildcard :: Foldable t => t (Pattern c b) -> Bool
+hasWildcard = any isWildcard where
+    isWildcard PatternWildcard{} = True
+    isWildcard _                 = False
+
+-- | Only works on well-typed stuff
+isExhaustive :: PatternEnv -> NonEmpty (Pattern c b) -> Bool
+isExhaustive _ (PatternWildcard{}:|_)                      = True
+isExhaustive _ (PatternInt{}:|ps)                          = hasWildcard ps
+isExhaustive _ (PatternBool _ True:|PatternBool _ False:_) = True
+isExhaustive _ (PatternBool _ False:|PatternBool _ True:_) = True
+isExhaustive _ (PatternBool{}:|ps)                         = hasWildcard ps
+isExhaustive env ps@(PatternCons{}:|_)                     = isCompleteSet env (fmap patternName ps)
+
+isCompleteSet :: PatternEnv -> NonEmpty (TyName a) -> Bool
+isCompleteSet env ns@(n:|_) =
+    let allU = assocUniques env n
+        ty = coerce (unique <$> toList ns)
+        in IS.null (allU IS.\\ IS.fromList ty)
diff --git a/src/Kempe/Check/Restrict.hs b/src/Kempe/Check/Restrict.hs
new file mode 100644
--- /dev/null
+++ b/src/Kempe/Check/Restrict.hs
@@ -0,0 +1,15 @@
+-- | Check that sum types have <256 constructors
+module Kempe.Check.Restrict ( restrictConstructors
+                            ) where
+
+import           Data.Foldable.Ext
+import           Kempe.AST
+import           Kempe.Error       (Error (FatSumType))
+
+restrictConstructors :: Module a c b -> Maybe (Error a)
+restrictConstructors = foldMapAlternative restrictDecl
+
+restrictDecl :: KempeDecl a c b -> Maybe (Error a)
+restrictDecl (TyDecl l n _ ls) | length ls > 256 = Just (FatSumType l n)
+                               | otherwise = Nothing
+restrictDecl _                 = Nothing
diff --git a/src/Kempe/Error.hs b/src/Kempe/Error.hs
--- a/src/Kempe/Error.hs
+++ b/src/Kempe/Error.hs
@@ -3,6 +3,7 @@
 {-# LANGUAGE OverloadedStrings #-}
 
 module Kempe.Error ( Error (..)
+                   , mErr
                    ) where
 
 import           Control.DeepSeq   (NFData)
@@ -24,8 +25,14 @@
              | InvalidCImport a (Name a)
              | IllKinded a (KempeTy a)
              | BadType a
+             | FatSumType a (TyName a)
+             | InexhaustiveMatch a
              deriving (Generic, NFData)
 
+mErr :: Maybe (Error ()) -> Either (Error ()) ()
+mErr Nothing    = Right ()
+mErr (Just err) = Left err
+
 instance (Pretty a) => Show (Error a) where
     show = show . pretty
 
@@ -40,5 +47,7 @@
     pretty (InvalidCImport _ n)          = pretty n <+> "imported functions can have at most one return value"
     pretty (IllKinded _ ty)              = "Ill-kinded type:" <+> squotes (pretty ty) <> ". Note that type variables have kind ⭑ in Kempe."
     pretty (BadType _)                   = "All types appearing in a signature must have kind ⭑"
+    pretty (FatSumType _ tn)             = "Sum type" <+> pretty tn <+> "has too many constructors! Sum types are limited to 256 constructors in Kempe."
+    pretty InexhaustiveMatch{}           = "Inexhaustive pattern match."
 
 instance (Pretty a, Typeable a) => Exception (Error a)
diff --git a/src/Kempe/File.hs b/src/Kempe/File.hs
--- a/src/Kempe/File.hs
+++ b/src/Kempe/File.hs
@@ -13,10 +13,12 @@
 import           Control.Exception         (Exception, throwIO)
 import           Data.Bifunctor            (bimap)
 import qualified Data.ByteString.Lazy      as BSL
+import           Data.Functor              (void)
 import qualified Data.Set                  as S
 import           Data.Tuple.Extra          (fst3)
 import           Kempe.AST
 import           Kempe.Asm.X86.Type
+import           Kempe.Check.Pattern
 import           Kempe.Error
 import           Kempe.IR
 import           Kempe.Lexer
@@ -32,7 +34,9 @@
 tcFile fp = do
     contents <- BSL.readFile fp
     (maxU, m) <- yeetIO $ parseWithMax contents
-    pure $ fst <$> runTypeM maxU (checkModule m)
+    pure $ do
+        void $ runTypeM maxU (checkModule m)
+        mErr $ checkModuleExhaustive (void <$> m)
 
 yeetIO :: Exception e => Either e a -> IO a
 yeetIO = either throwIO pure
diff --git a/src/Kempe/Shuttle.hs b/src/Kempe/Shuttle.hs
--- a/src/Kempe/Shuttle.hs
+++ b/src/Kempe/Shuttle.hs
@@ -3,8 +3,9 @@
 module Kempe.Shuttle ( monomorphize
                      ) where
 
-import           Data.Functor       (void)
+import           Data.Functor        (void)
 import           Kempe.AST
+import           Kempe.Check.Pattern
 import           Kempe.Error
 import           Kempe.Inline
 import           Kempe.Monomorphize
@@ -15,7 +16,9 @@
                     -> Either (Error ()) (Module () (ConsAnn MonoStackType) (StackType ()), (Int, SizeEnv))
 inlineAssignFlatten ctx m = do
     -- check before inlining otherwise users would get weird errors
-    void $ runTypeM ctx (checkModule m)
+    void $ do
+        void $ runTypeM ctx (checkModule m)
+        mErr $ checkModuleExhaustive (void <$> m)
     (mTy, i) <- runTypeM ctx (assignModule $ inline m)
     runMonoM i (flattenModule mTy)
 
diff --git a/test/Type.hs b/test/Type.hs
--- a/test/Type.hs
+++ b/test/Type.hs
@@ -26,6 +26,7 @@
         , tyInfer "lib/gaussian.kmp"
         , badType "test/err/merge.kmp" "Type a_5 -- Int a_4 is not as general as type a_3 -- a_3 a_3"
         , badType "test/err/kind.kmp" "Ill-kinded type: '(Maybe_1 Maybe_1)'. Note that type variables have kind \11089 in Kempe."
+        , badType "test/err/patternMatch.kmp" "Inexhaustive pattern match."
         , testAssignment "test/data/ty.kmp"
         , testAssignment "lib/either.kmp"
         , testAssignment "prelude/fn.kmp"
