disco-0.2: src/Disco/Error.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE StandaloneDeriving #-}
-- |
-- Module : Disco.Error
-- Copyright : disco team and contributors
-- Maintainer : byorgey@gmail.com
--
-- SPDX-License-Identifier: BSD-3-Clause
--
-- Type for collecting all potential Disco errors at the top level,
-- and a type for runtime errors.
module Disco.Error (DiscoError (..), EvalError (..), panic, outputDiscoErrors) where
import Prelude hiding ((<>))
import Text.Megaparsec (
ParseErrorBundle,
errorBundlePretty,
)
import Unbound.Generics.LocallyNameless (Name)
import Disco.Effects.LFresh
import Polysemy
import Polysemy.Error
import Polysemy.Output
import Polysemy.Reader
import Disco.Messages
import Disco.Names (ModuleName, QName)
import Disco.Parser (DiscoParseError)
import Disco.Pretty
import Disco.Typecheck.Solve
import Disco.Typecheck.Util (
LocTCError (..),
TCError (..),
)
import Disco.Types
import Disco.Types.Qualifiers
-- | Top-level error type for Disco.
data DiscoError where
-- | Module not found.
ModuleNotFound :: String -> DiscoError
-- | Cyclic import encountered.
CyclicImport :: [ModuleName] -> DiscoError
-- | Error encountered during typechecking.
TypeCheckErr :: LocTCError -> DiscoError
-- | Error encountered during parsing.
ParseErr :: ParseErrorBundle String DiscoParseError -> DiscoError
-- | Error encountered at runtime.
EvalErr :: EvalError -> DiscoError
-- | Something that shouldn't happen; indicates the presence of a
-- bug.
Panic :: String -> DiscoError
deriving (Show)
-- | Errors that can be generated at runtime.
data EvalError where
-- | An unbound name was encountered.
UnboundError :: QName core -> EvalError
-- | An unbound name that really shouldn't happen, coming from some
-- kind of internal name generation scheme.
UnboundPanic :: Name core -> EvalError
-- | Division by zero.
DivByZero :: EvalError
-- | Overflow, e.g. (2^66)!
Overflow :: EvalError
-- | Non-exhaustive case analysis.
NonExhaustive :: EvalError
-- | Infinite loop detected via black hole.
InfiniteLoop :: EvalError
-- | User-generated crash.
Crash :: String -> EvalError
deriving instance Show EvalError
panic :: Member (Error DiscoError) r => String -> Sem r a
panic = throw . Panic
outputDiscoErrors :: Member (Output (Message ann)) r => Sem (Error DiscoError ': r) () -> Sem r ()
outputDiscoErrors m = do
e <- runError m
either (err . pretty') return e
instance Pretty DiscoError where
pretty = \case
ModuleNotFound m -> "Error: couldn't find a module named '" <> text m <> "'."
CyclicImport ms -> cyclicImportError ms
TypeCheckErr (LocTCError Nothing te) -> prettyTCError te
TypeCheckErr (LocTCError (Just n) te) ->
nest 2 $
vcat
[ "While checking " <> pretty' n <> ":"
, prettyTCError te
]
ParseErr pe -> text (errorBundlePretty pe)
EvalErr ee -> prettyEvalError ee
Panic s ->
vcat
[ "Bug! " <> text s
, "Please report this as a bug at https://github.com/disco-lang/disco/issues/ ."
]
rtd :: String -> Sem r (Doc ann)
rtd page = "https://disco-lang.readthedocs.io/en/latest/reference/" <> text page <> ".html"
-- issue :: Int -> Sem r (Doc ann)
-- issue n = "See https://github.com/disco-lang/disco/issues/" <> text (show n)
squote :: String -> String
squote x = "'" ++ x ++ "'"
cyclicImportError ::
Members '[Reader PA, LFresh] r =>
[ModuleName] ->
Sem r (Doc ann)
cyclicImportError ms =
nest 2 $
vcat
[ "Error: module imports form a cycle:"
, intercalate " ->" (map pretty ms)
]
prettyEvalError :: Members '[Reader PA, LFresh] r => EvalError -> Sem r (Doc ann)
prettyEvalError = \case
UnboundPanic x ->
("Bug! No variable found named" <+> pretty' x <> ".")
$+$ "Please report this as a bug at https://github.com/disco-lang/disco/issues/ ."
UnboundError x -> "Error: encountered undefined name" <+> pretty' x <> ". Maybe you haven't defined it yet?"
DivByZero -> "Error: division by zero."
Overflow -> "Error: that number would not even fit in the universe!"
NonExhaustive -> "Error: value did not match any of the branches in a case expression."
InfiniteLoop -> "Error: infinite loop detected!"
Crash s -> "User crash:" <+> text s
-- [X] Step 1: nice error messages, make sure all are tested
-- [ ] Step 2: link to wiki/website with more info on errors!
-- [ ] Step 3: improve error messages according to notes below
-- [ ] Step 4: get it to return multiple error messages
-- [ ] Step 5: save parse locations, display with errors
prettyTCError :: Members '[Reader PA, LFresh] r => TCError -> Sem r (Doc ann)
prettyTCError = \case
-- XXX include some potential misspellings along with Unbound
-- see https://github.com/disco-lang/disco/issues/180
Unbound x suggestions ->
vcat $
["Error: there is nothing named" <+> pretty' x <> "."]
++ ["Perhaps you meant" <+> intercalate " or" (map (text . squote) suggestions) <> "?" | not (null suggestions)]
++ [rtd "unbound"]
Ambiguous x ms ->
vcat
[ "Error: the name" <+> pretty' x <+> "is ambiguous. It could refer to:"
, indent 2 . vcat . map (\m -> pretty' m <> "." <> pretty' x) $ ms
, rtd "ambiguous"
]
NoType x ->
vcat
[ "Error: the definition of" <+> pretty' x <+> "must have an accompanying type signature."
, "Try writing something like '"
<> pretty' x
<+> ": Int' (or whatever the type of"
<+> pretty' x
<+> "should be) first."
, rtd "missingtype"
]
NotCon c t ty ->
vcat
[ "Error: the expression"
, indent 2 $ pretty' t
, "must have both a" <+> conWord c <+> "type and also the incompatible type"
, indent 2 $ pretty' ty <> "."
, rtd "notcon"
]
EmptyCase ->
vcat
[ "Error: empty case expressions {? ?} are not allowed."
, rtd "empty-case"
]
PatternType c pat ty ->
vcat
[ "Error: the pattern"
, indent 2 $ pretty' pat
, "is supposed to have type"
, indent 2 $ pretty' ty <> ","
, "but instead it has a" <+> conWord c <+> "type."
, rtd "pattern-type"
]
DuplicateDecls x ->
vcat
[ "Error: duplicate type signature for" <+> pretty' x <> "."
, rtd "dup-sig"
]
DuplicateDefns x ->
vcat
[ "Error: duplicate definition for" <+> pretty' x <> "."
, rtd "dup-def"
]
DuplicateTyDefns s ->
vcat
[ "Error: duplicate definition for type" <+> text s <> "."
, rtd "dup-tydef"
]
-- XXX include all types involved in the cycle.
CyclicTyDef s ->
vcat
[ "Error: cyclic type definition for" <+> text s <> "."
, rtd "cyc-ty"
]
-- XXX lots more info! & Split into several different errors.
NumPatterns ->
vcat
[ "Error: number of arguments does not match."
, rtd "num-args"
]
NonlinearPattern p x ->
vcat
[ "Error: pattern" <+> pretty' p <+> "contains duplicate variable" <+> pretty' x <> "."
, rtd "nonlinear"
]
NoSearch ty ->
vcat
[ "Error: the type"
, indent 2 $ pretty' ty
, "is not searchable (i.e. it cannot be used in a forall)."
, rtd "no-search"
]
Unsolvable solveErr -> prettySolveError solveErr
-- XXX maybe include close edit-distance alternatives?
NotTyDef s ->
vcat
[ "Error: there is no built-in or user-defined type named '" <> text s <> "'."
, rtd "no-tydef"
]
NoTWild ->
vcat
[ "Error: wildcards (_) are not allowed in expressions."
, rtd "wildcard-expr"
]
-- XXX say how many are expected, how many there were, what the actual arguments were?
-- XXX distinguish between built-in and user-supplied type constructors in the error
-- message?
NotEnoughArgs con ->
vcat
[ "Error: not enough arguments for the type '" <> pretty' con <> "'."
, rtd "num-args-type"
]
TooManyArgs con ->
vcat
[ "Error: too many arguments for the type '" <> pretty' con <> "'."
, rtd "num-args-type"
]
-- XXX Mention the definition in which it was found
UnboundTyVar v suggestions ->
vcat $
["Error: Unknown type variable '" <> pretty' v <> "'."]
++ ["Perhaps you meant" <+> intercalate " or" (map (text . squote) suggestions) <> "?" | not (null suggestions)]
++ [rtd "unbound-tyvar"]
NoPolyRec s ss tys ->
vcat
[ "Error: in the definition of " <> text s <> parens (intercalate "," (map text ss)) <> ": recursive occurrences of" <+> text s <+> "may only have type variables as arguments."
, indent
2
( text s <> parens (intercalate "," (map pretty' tys)) <+> "does not follow this rule."
)
, rtd "no-poly-rec"
]
NoError -> empty
conWord :: Con -> Sem r (Doc ann)
conWord = \case
CArr -> "function"
CProd -> "pair"
CSum -> "sum"
CSet -> "set"
CBag -> "bag"
CList -> "list"
CContainer _ -> "container"
CMap -> "map"
CGraph -> "graph"
CUser s -> text s
prettySolveError :: Members '[Reader PA, LFresh] r => SolveError -> Sem r (Doc ann)
prettySolveError = \case
-- XXX say which types!
NoWeakUnifier ->
vcat
[ "Error: the shape of two types does not match."
, rtd "shape-mismatch"
]
-- XXX say more! XXX HIGHEST PRIORITY!
NoUnify ->
vcat
[ "Error: typechecking failed."
, rtd "typecheck-fail"
]
UnqualBase q b ->
vcat
[ "Error: values of type" <+> pretty' b <+> qualPhrase False q <> "."
, rtd "not-qual"
]
Unqual q ty ->
vcat
[ "Error: values of type" <+> pretty' ty <+> qualPhrase False q <> "."
, rtd "not-qual"
]
QualSkolem q a ->
vcat
[ "Error: type variable" <+> pretty' a <+> "represents any type, so we cannot assume values of that type"
, indent 2 (qualPhrase True q) <> "."
, rtd "qual-skolem"
]
qualPhrase :: Bool -> Qualifier -> Sem r (Doc ann)
qualPhrase b q
| q `elem` [QBool, QBasic, QSimple] = "are" <+> (if b then empty else "not") <+> qualAction q
| otherwise = "can" <> (if b then empty else "not") <+> "be" <+> qualAction q
qualAction :: Qualifier -> Sem r (Doc ann)
qualAction = \case
QNum -> "added and multiplied"
QSub -> "subtracted"
QDiv -> "divided"
QCmp -> "compared"
QEnum -> "enumerated"
QBool -> "boolean"
QBasic -> "basic"
QSimple -> "simple"