rzk-0.10.0: src/Rzk/TypeCheck/Error.hs
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
-- | Type errors, and how they are shown.
--
-- An error is captured /at its own scope/: it packages the context it was raised
-- in, existentially, so its scope index does not escape into the error type. The
-- old representation instead nested the error one @Inc@ deeper at every binder
-- (@ScopedTypeError@) and unwound the whole stack at printing time, inventing a
-- name per binder as it went. That unwinding loop, and both of its
-- @FIXME: very inefficient filter@ sites, are gone: the context already knows what
-- everything in scope is called (see "Rzk.TypeCheck.Display").
--
-- The error type is therefore /not/ scope-indexed, which is why entering a binder
-- no longer has to re-index the error channel.
module Rzk.TypeCheck.Error where
import Control.Monad.Foil (Distinct)
import qualified Control.Monad.Foil as Foil
import Data.List (intercalate)
import Language.Rzk.Foil.Syntax
import Language.Rzk.Foil.Names (TModality (..), VarIdent, getVarIdent,
ppVarIdentWithLocation)
import qualified Language.Rzk.Syntax as Rzk
import Rzk.TypeCheck.Context
import Rzk.TypeCheck.Display
data TypeError n
= TypeErrorOther String
| TypeErrorUnify (TermT n) (TermT n) (TermT n)
| TypeErrorUnifyTerms (TermT n) (TermT n)
| TypeErrorNotPair (TermT n) (TermT n)
| TypeErrorNotModal (Term n) TModality (TermT n)
| TypeErrorModalityMismatch TModality TModality (Term n)
| TypeErrorUnaccessibleVar (Foil.Name n) TModality TModality
| TypeErrorNotTypeInModal (TermT n)
| TypeErrorNotFunction (TermT n) (TermT n)
| TypeErrorUnexpectedLambda (Term n) (TermT n)
| TypeErrorUnexpectedPair (Term n) (TermT n)
| TypeErrorUnexpectedRefl (Term n) (TermT n)
| TypeErrorCannotInferBareLambda (Term n)
| TypeErrorCannotInferBareRefl (Term n)
| TypeErrorCannotInferHole (Term n)
| TypeErrorUnsolvedHole (Maybe VarIdent) (TermT n)
| TypeErrorUndefined VarIdent
| TypeErrorTopeNotSatisfied [TermT n] (TermT n)
| TypeErrorTopeContextDisjoint (TermT n) [TermT n]
| TypeErrorTopesNotEquivalent (TermT n) (TermT n)
| TypeErrorInvalidArgumentType (Term n) (TermT n)
| TypeErrorDuplicateTopLevel [VarIdent] VarIdent
| TypeErrorUnusedVariable (Foil.Name n) (TermT n)
| TypeErrorUnusedUsedVariables [Foil.Name n] (Foil.Name n)
| TypeErrorImplicitAssumption (Foil.Name n, TermT n) (Foil.Name n)
-- | An error, together with the context it was raised in.
--
-- The scope index is existential: an error raised under a binder is /already/
-- complete (its context says what its names are called), so it needs nothing
-- from the enclosing scope and can be thrown straight through it.
data TypeErrorInScopedContext where
TypeErrorInScopedContext
:: Distinct n => Context n -> TypeError n -> TypeErrorInScopedContext
ppModality :: TModality -> String
ppModality = \case
Flat -> "♭"
Sharp -> "♯"
Op -> "ᵒᵖ"
Id -> "_id"
-- * Rendering
data OutputDirection = TopDown | BottomUp
deriving (Eq)
block :: OutputDirection -> [String] -> String
block TopDown = intercalate "\n"
block BottomUp = intercalate "\n" . reverse
namedBlock :: OutputDirection -> String -> [String] -> String
namedBlock dir name lines_ = block dir $
name : map indent lines_
where
indent = intercalate "\n" . map (" " ++) . lines
ppTypeError :: Naming n -> TypeError n -> String
ppTypeError naming = \case
TypeErrorOther msg -> msg
TypeErrorUnify term expected actual -> block TopDown
[ "cannot unify expected type"
, " " <> ppU (untyped expected)
, "with actual type"
, " " <> ppU (untyped actual)
, "for term"
, " " <> ppU (untyped term) ]
TypeErrorUnifyTerms expected actual -> block TopDown
[ "cannot unify term"
, " " <> ppU (untyped expected)
, "with term"
, " " <> ppU (untyped actual) ]
TypeErrorNotPair term ty -> block TopDown
[ "expected a cube product or dependent pair"
, "but got type"
, " " <> ppU (untyped ty)
, "for term"
, " " <> ppU (untyped term)
, case ty of
TypeFunT{} -> "\nPerhaps the term is applied to too few arguments?"
_ -> ""
]
TypeErrorNotModal term m ty -> block TopDown
[ "expected modal type " <> ppModality m <> " ?"
, "but got type"
, " " <> ppU (untyped ty)
, "for term"
, " " <> ppU term
]
TypeErrorModalityMismatch expected actual term -> block TopDown
[ "modality mismatch"
, " expected " <> ppModality expected
, " but got " <> ppModality actual
, "for term"
, " " <> ppU term
]
TypeErrorUnaccessibleVar _var varMod locks -> block TopDown
[ "unaccessible var with modality " <> ppModality varMod
, " under locks " <> ppModality locks
]
TypeErrorNotTypeInModal ty -> block TopDown
[ "expected a type inside modal type"
, "but got"
, " " <> ppU (untyped ty)
]
TypeErrorUnexpectedLambda term ty -> block TopDown
[ "unexpected lambda abstraction"
, " " <> ppU term
, "when typechecking against a non-function type"
, " " <> ppTyped ty
]
TypeErrorUnexpectedPair term ty -> block TopDown
[ "unexpected pair"
, " " <> ppU term
, "when typechecking against a type that is not a product or a dependent sum"
, " " <> ppTyped ty
]
TypeErrorUnexpectedRefl term ty -> block TopDown
[ "unexpected refl"
, " " <> ppU term
, "when typechecking against a type that is not an identity type"
, " " <> ppTyped ty
]
TypeErrorNotFunction term ty -> block TopDown
[ "expected a function or extension type"
, "but got type"
, " " <> ppU (untyped ty)
, "for term"
, " " <> ppU (untyped term)
, case term of
AppT _ty f _x -> "\nPerhaps the term\n " <> ppU (untyped f) <> "\nis applied to too many arguments?"
_ -> ""
]
TypeErrorCannotInferBareLambda term -> block TopDown
[ "cannot infer the type of the argument"
, "in lambda abstraction"
, " " <> ppU term
]
TypeErrorCannotInferBareRefl term -> block TopDown
[ "cannot infer the type of term"
, " " <> ppU term
]
TypeErrorCannotInferHole term -> block TopDown
[ "cannot infer the type of a hole"
, " " <> ppU term
, "a hole is only allowed where its type is already known (checking position)"
]
TypeErrorUnsolvedHole mname goal -> block TopDown
[ "found an unsolved hole" <> maybe "" (\name -> " ?" <> show name) mname
, "expected type (goal):"
, " " <> ppU (untyped goal)
]
TypeErrorUndefined var -> block TopDown
[ "undefined variable: " <> show var ]
TypeErrorTopeNotSatisfied topes tope -> block TopDown
[ "local context is not included in (does not entail) the tope"
, " " <> ppU (untyped tope)
, "in local context (normalised)"
, intercalate "\n" (map ((" " <>) . ppTyped) topes)] -- FIXME: remove
TypeErrorTopeContextDisjoint tope topes -> block TopDown
[ "the tope"
, " " <> ppU (untyped tope)
, "is disjoint from the local tope context (their conjunction is the empty tope ⊥),"
, "so this restriction face or recOR branch is vacuous everywhere"
, "in local context (normalised)"
, intercalate "\n" (map ((" " <>) . ppTyped) topes)]
TypeErrorTopesNotEquivalent expected actual -> block TopDown
[ "expected tope"
, " " <> ppU (untyped expected)
, "but got"
, " " <> ppU (untyped actual) ]
TypeErrorInvalidArgumentType argType argKind -> block TopDown
[ "invalid function parameter type"
, " " <> ppU argType
, "function parameter can be a cube, a shape, or a type"
, "but given parameter type has type"
, " " <> ppU (untyped argKind)
]
TypeErrorDuplicateTopLevel previous lastName -> block TopDown
[ "duplicate top-level definition"
, " " <> ppVarIdentWithLocation lastName
, "previous top-level definitions found at"
, intercalate "\n"
[ " " <> ppVarIdentWithLocation name
| name <- previous ]
]
TypeErrorUnusedVariable name type_ -> block TopDown
[ "unused variable"
, " " <> ppVar name <> " : " <> ppU (untyped type_)
]
TypeErrorUnusedUsedVariables vars name -> block TopDown
[ "unused variables"
, " " <> unwords (map ppVar vars)
, "declared as used in definition of"
, " " <> ppVar name
]
TypeErrorImplicitAssumption (a, aType) name -> block TopDown
[ "implicit assumption"
, " " <> ppVar a <> " : " <> ppU (untyped aType)
, "used in definition of"
, " " <> ppVar name
]
where
ppU = ppTerm naming
ppTyped = ppTermT naming
ppVar = ppName naming
ppAction :: Naming n -> Int -> Action n -> String
ppAction naming n = unlines . map (replicate (2 * n) ' ' <>) . \case
ActionTypeCheck term ty ->
[ "typechecking"
, " " <> ppU term
, "against type"
, " " <> ppU (untyped ty) ]
ActionUnify term expected actual ->
[ "unifying expected type"
, " " <> ppU (untyped expected)
, "with actual type"
, " " <> ppU (untyped actual)
, "for term"
, " " <> ppU (untyped term) ]
ActionUnifyTerms expected actual ->
[ "unifying term (expected)"
, " " <> ppTyped expected
, "with term (actual)"
, " " <> ppTyped actual ]
ActionInfer term ->
[ "inferring type for term"
, " " <> ppU term ]
ActionContextEntailedBy topes term ->
[ "checking if local context"
, intercalate "\n" (map ((" " <>) . ppU . untyped) topes)
, "includes (is entailed by) restriction tope"
, " " <> ppU (untyped term) ]
ActionContextEntails topes term ->
[ "checking if local context"
, intercalate "\n" (map ((" " <>) . ppU . untyped) topes)
, "is included in (entails) the tope"
, " " <> ppU (untyped term) ]
ActionContextEntailsUnion topes terms ->
[ "checking if local context"
, intercalate "\n" (map ((" " <>) . ppU . untyped) topes)
, "is included in (entails) the union of the topes"
, intercalate "\n" (map ((" " <>) . ppU . untyped) terms) ]
ActionWHNF term ->
[ "computing WHNF for term"
, " " <> ppTyped term ]
ActionNF term ->
[ "computing normal form for term"
, " " <> ppU (untyped term) ]
ActionCheckCoherence (ltope, lterm) (rtope, rterm) ->
[ "checking coherence for"
, " " <> ppU (untyped ltope)
, " |-> " <> ppU (untyped lterm)
, "and"
, " " <> ppU (untyped rtope)
, " |-> " <> ppU (untyped rterm) ]
ActionCloseSection Nothing ->
[ "closing the file"
, "and collecting assumptions (variables)" ]
ActionCloseSection (Just sectionName) ->
[ "closing #section " <> Rzk.printTree sectionName
, "and collecting assumptions (variables)"]
ActionCheckLetValue orig ->
[ "checking the local definition "
<> maybe "_" (Rzk.printTree . getVarIdent) orig ]
where
ppU = ppTerm naming
ppTyped = ppTermT naming
-- | The context an error was raised in: where it happened, what was being
-- checked, the tope context, the stack of judgements, and the hypotheses.
ppContext :: OutputDirection -> Context n -> String
ppContext dir ctx@Context{..} = block dir $ dropWhile null
[ block TopDown
[ case ctxLocation of
_ | dir == TopDown -> "" -- FIXME
Just (LocationInfo (Just path) (Just lineNo)) ->
path <> " (line " <> show lineNo <> "):"
Just (LocationInfo (Just path) _) ->
path <> ":"
_ -> ""
, case ctxCurrentCommand of
Just (Rzk.CommandDefine _loc name _vars _params _ty _term) ->
" Error occurred when checking\n #define " <> Rzk.printTree name
Just (Rzk.CommandPostulate _loc name _vars _params _ty ) ->
" Error occurred when checking\n #postulate " <> Rzk.printTree name
Just (Rzk.CommandCheck _loc term ty) ->
" Error occurred when checking\n " <> Rzk.printTree term <> " : " <> Rzk.printTree ty
Just (Rzk.CommandCompute _loc term) ->
" Error occurred when computing\n " <> Rzk.printTree term
Just (Rzk.CommandComputeNF _loc term) ->
" Error occurred when computing NF for\n " <> Rzk.printTree term
Just (Rzk.CommandComputeWHNF _loc term) ->
" Error occurred when computing WHNF for\n " <> Rzk.printTree term
Just (Rzk.CommandSetOption _loc optionName _optionValue) ->
" Error occurred when trying to set option\n #set-option " <> show optionName
Just command@Rzk.CommandUnsetOption{} ->
" Error occurred when trying to unset option\n " <> Rzk.printTree command
Just command@Rzk.CommandAssume{} ->
" Error occurred when checking assumption\n " <> Rzk.printTree command
Just (Rzk.CommandSection _loc name) ->
" Error occurred when checking\n #section " <> Rzk.printTree name
Just (Rzk.CommandSectionEnd _loc name) ->
" Error occurred when checking\n #end " <> Rzk.printTree name
Nothing -> " Error occurred outside of any command!"
]
, ""
, case filter (not . isTopeTop) (availableTopes ctx) of
[] -> "Local tope context is unrestricted (⊤)."
topes -> namedBlock TopDown "Local tope context:"
[ " " <> ppU (untyped tope)
| tope <- topes ]
, ""
, block dir
[ "when " <> ppAction naming 0 action
| action <- ctxActionStack ]
, namedBlock TopDown "Definitions in context:"
[ block dir
[ ppName naming name <> " : " <> ppU (untyped (varType info))
| (name, info) <- reverse (varsInScope ctx) ] ]
]
where
naming = namingOfContext ctx
ppU = ppTerm naming
isTopeTop TopeTopT{} = True
isTopeTop _ = False
-- | An error, with the context it was raised in.
ppTypeErrorInScopedContext :: OutputDirection -> TypeErrorInScopedContext -> String
ppTypeErrorInScopedContext dir (TypeErrorInScopedContext ctx err) = block dir
[ ppTypeError (namingOfContext ctx) err
, ""
, ppContext dir ctx
]