module Language.Boogie.TypeChecker (
checkProgram,
exprType,
resolve,
TypeError (..),
typeErrorsDoc,
Checked,
Context (..),
emptyContext,
typeNames,
globalScope,
localScope,
mutableVars,
allVars,
allNames,
funProcNames,
funSig,
procSig,
setGlobals,
setIns,
setLocals,
setConstants,
enterFunction,
enterProcedure,
enterQuantified
) where
import Language.Boogie.AST
import Language.Boogie.Util
import Language.Boogie.Position
import Language.Boogie.PrettyPrinter
import Data.List
import Data.Maybe
import Data.Map (Map, (!))
import qualified Data.Map as M
import Control.Monad.Error
import Control.Monad.Trans.Error hiding (throwError)
import Control.Applicative
import Text.PrettyPrint
checkProgram :: Program -> Checked Context
checkProgram (Program decls) = do
pass1 <- foldAccum collectTypes emptyContext decls
_pass2 <- mapAccum_ (checkTypeSynonyms pass1) decls
_pass3 <- mapAccum_ (checkCycles pass1 decls) (M.keys (ctxTypeSynonyms pass1))
pass4 <- foldAccum checkSignatures pass1 decls
pass5 <- foldAccum checkBodies pass4 decls
return pass5
exprType :: Context -> Expression -> Type
exprType c expr = case checkExpression c expr of
Left _ -> (error . show) (text "encountered ill-typed expression during execution:" <+> exprDoc expr)
Right t -> t
enterFunction :: FSig -> [Id] -> [Expression] -> Maybe Type -> Context -> Context
enterFunction sig formals actuals mRetType c = c
{
ctxTypeVars = [],
ctxIns = M.fromList (zip formals argTypes),
ctxLocals = M.empty,
ctxModifies = [],
ctxTwoState = False,
ctxInLoop = False
}
where
inst = case fInstance c sig actuals mRetType of
Left _ -> (error . show) (text "encountered ill-typed function application during execution:" <+>
text (fsigName sig) <+> parens (commaSep (map text formals)) <+>
text "to actual arguments" <+> parens (commaSep (map exprDoc actuals)))
Right u -> typeSubst u
argTypes = map inst (fsigArgTypes sig)
enterProcedure :: PSig -> PDef -> [Expression] -> [Expression] -> Context -> Context
enterProcedure sig def actuals lhss c = c
{
ctxTypeVars = [],
ctxIns = M.fromList $ zip ins inTypes,
ctxLocals = M.union (M.fromList $ zip localNames localTypes) (M.fromList $ zip outs outTypes),
ctxWhere = foldl addWhere (ctxWhere c) (zip (ins ++ outs ++ localNames) (paramWhere ++ localWhere)),
ctxModifies = psigModifies sig,
ctxTwoState = True,
ctxInLoop = False
}
where
ins = pdefIns def
outs = pdefOuts def
locals = fst (pdefBody def)
inst = case pInstance c sig actuals lhss of
Left _ -> (error . show) (text "encountered ill-typed procedure call during execution:" <+>
text (psigName sig) <+> text "with actual arguments" <+> parens (commaSep (map exprDoc actuals)) <+>
text "and left-hand sides" <+> parens (commaSep (map exprDoc lhss)))
Right u -> typeSubst u
inTypes = map inst (psigArgTypes sig)
outTypes = map inst (psigRetTypes sig)
localTypes = map (inst . itwType) locals
localNames = map itwId locals
addWhere m (id, w) = M.insert id w m
localWhere = map itwWhere locals
paramWhere = map (paramSubst sig def . itwWhere) (psigArgs sig ++ psigRets sig)
enterQuantified :: [Id] -> [IdType] -> Context -> Context
enterQuantified tv vars c = c'
{
ctxIns = foldl addIn (ctxIns c) vars
}
where
c' = c { ctxTypeVars = tv }
addIn ins (id, t) = M.insert id (resolve c' t) ins
data TypeError = TypeError SourcePos Doc
instance ErrorList TypeError where
listMsg s = [TypeError noPos (text s)]
typeErrorDoc (TypeError pos msgDoc) = text "Type error in" <+> text (show pos) $+$ msgDoc
typeErrorsDoc errs = (vsep . punctuate newline . map typeErrorDoc) errs
type Checked a = Either [TypeError] a
throwTypeError pos msgDoc = throwError [TypeError pos msgDoc]
data ErrorAccum a = ErrorAccum [TypeError] a
instance Monad ErrorAccum where
return x = ErrorAccum [] x
ErrorAccum errs x >>= f = case (f x) of
ErrorAccum es v -> ErrorAccum (errs ++ es) v
accum :: Checked a -> a -> ErrorAccum a
accum cx y = case cx of
Left e -> ErrorAccum e y
Right x -> ErrorAccum [] x
report :: ErrorAccum a -> Checked a
report (ErrorAccum [] x) = Right x
report (ErrorAccum es _) = Left es
foldAccum :: (a -> b -> Checked a) -> a -> [b] -> Checked a
foldAccum f c nodes = report $ foldM (acc f) c nodes
where
acc f x y = accum (f x y) x
mapAccum :: (b -> Checked c) -> c -> [b] -> Checked [c]
mapAccum f def nodes = report $ mapM (acc f) nodes
where
acc f x = accum (f x) def
mapAccumA_ :: (a -> Checked ()) -> [a] -> ErrorAccum ()
mapAccumA_ f nodes = mapM_ (acc f) nodes
where
acc f x = accum (f x) ()
mapAccum_ :: (a -> Checked ()) -> [a] -> Checked ()
mapAccum_ f nodes = report $ mapAccumA_ f nodes
zipWithAccum_ :: (a -> b -> Checked ()) -> [a] -> [b] -> Checked ()
zipWithAccum_ f xs ys = report $ zipWithM_ (acc f) xs ys
where
acc f x y = accum (f x y) ()
data Context = Context
{
ctxTypeConstructors :: Map Id Int,
ctxTypeSynonyms :: Map Id ([Id], Type),
ctxGlobals :: Map Id Type,
ctxConstants :: Map Id Type,
ctxFunctions :: Map Id FSig,
ctxProcedures :: Map Id PSig,
ctxWhere :: Map Id Expression,
ctxTypeVars :: [Id],
ctxIns :: Map Id Type,
ctxLocals :: Map Id Type,
ctxModifies :: [Id],
ctxLabels :: [Id],
ctxEncLabels :: [Id],
ctxTwoState :: Bool,
ctxInLoop :: Bool,
ctxPos :: SourcePos
}
emptyContext = Context {
ctxTypeConstructors = M.empty,
ctxTypeSynonyms = M.empty,
ctxGlobals = M.empty,
ctxConstants = M.empty,
ctxFunctions = M.empty,
ctxProcedures = M.empty,
ctxWhere = M.empty,
ctxTypeVars = [],
ctxIns = M.empty,
ctxLocals = M.empty,
ctxModifies = [],
ctxLabels = [],
ctxEncLabels = [],
ctxTwoState = False,
ctxInLoop = False,
ctxPos = noPos
}
setGlobals ctx g = ctx { ctxGlobals = g }
setIns ctx i = ctx { ctxIns = i }
setLocals ctx l = ctx { ctxLocals = l }
setConstants ctx c = ctx { ctxConstants = c }
typeNames c = M.keys (ctxTypeConstructors c) ++ M.keys (ctxTypeSynonyms c)
globalScope c = M.union (ctxGlobals c) (ctxConstants c)
localScope c = M.union (ctxIns c) (ctxLocals c)
mutableVars c = M.union (ctxGlobals c) (ctxLocals c)
allVars c = M.union (localScope c) (ctxGlobals c)
allNames c = M.union (localScope c) (globalScope c)
funProcNames c = M.keys (ctxFunctions c) ++ M.keys (ctxProcedures c)
funSig name c = ctxFunctions c ! name
procSig name c = ctxProcedures c ! name
checkTypeVar :: Context -> Id -> Checked Context
checkTypeVar c v
| v `elem` typeNames c = throwTypeError (ctxPos c) (text v <+> text "already used as a type constructor or synonym")
| v `elem` ctxTypeVars c = throwTypeError (ctxPos c) (text "Multiple decalartions of type variable" <+> text v)
| otherwise = return c { ctxTypeVars = v : ctxTypeVars c }
checkType :: Context -> Type -> Checked ()
checkType c (MapType tv domains range) = do
c' <- foldAccum checkTypeVar c tv
mapAccum_ (checkType c') (domains ++ [range])
checkType c (Instance name args)
| name `elem` ctxTypeVars c && null args = return ()
| M.member name (ctxTypeConstructors c) = if n == length args
then mapAccum_ (checkType c) args
else throwTypeError (ctxPos c) (text "Wrong number of arguments" <+> int (length args) <+> text "given to the type constructor" <+> text name <+>
parens (text "expected" <+> int n))
| M.member name (ctxTypeSynonyms c) = if length formals == length args
then mapAccum_ (checkType c) args
else throwTypeError (ctxPos c) (text "Wrong number of arguments " <+> int (length args) <+> text "given to the type synonym" <+> text name <+>
parens (text "expected" <+> int (length formals)))
| otherwise = throwTypeError (ctxPos c) (text "Not in scope: type constructor or synonym" <+> text name)
where
n = ctxTypeConstructors c ! name
formals = fst (ctxTypeSynonyms c ! name)
checkType _ _ = return ()
resolve :: Context -> Type -> Type
resolve c (MapType tv domains range) = MapType tv (map (resolve c') domains) (resolve c' range)
where c' = c { ctxTypeVars = ctxTypeVars c ++ tv }
resolve c (Instance name args)
| name `elem` ctxTypeVars c = Instance name args
| otherwise = case M.lookup name (ctxTypeSynonyms c) of
Nothing -> Instance name (map (resolve c) args)
Just (formals, t) -> resolve c (typeSubst (M.fromList (zip formals args)) t)
resolve _ t = t
fInstance :: Context -> FSig -> [Expression] -> Maybe Type -> Checked TypeBinding
fInstance c sig actuals mRetType = case mRetType of
Nothing -> if not (null retOnlyTVs)
then throwTypeError (ctxPos c) (text "Cannot infer type arguments from the context:" <+> commaSep (map text retOnlyTVs) <+> text "(insert a coercion)")
else do
actualTypes <- mapAccum (checkExpression c) noType actuals
case oneSidedUnifier (fsigTypeVars sig) (fsigArgTypes sig) (ctxTypeVars c) actualTypes of
Nothing -> throwTypeError (ctxPos c) (text "Could not match formal argument types" <+>
doubleQuotes (commaSep (map typeDoc (fsigArgTypes sig))) <+>
text "against actual argument types" <+>
doubleQuotes (commaSep (map typeDoc actualTypes)) <+>
text "in the call to" <+> text (fsigName sig))
Just u -> return u
Just retType -> do
actualTypes <- mapAccum (checkExpression c) noType actuals
case oneSidedUnifier (fsigTypeVars sig) (fsigRetType sig : fsigArgTypes sig) (ctxTypeVars c) (retType : actualTypes) of
Nothing -> throwTypeError (ctxPos c) (text "Could not match function signature" <+>
doubleQuotes (sigDoc (fsigArgTypes sig) [fsigRetType sig]) <+>
text "against actual types" <+>
doubleQuotes (sigDoc actualTypes [retType]) <+>
text "in the call to" <+> text (fsigName sig))
Just u -> return u
where
tvs = fsigTypeVars sig
retOnlyTVs = filter (not . freeInArgs) tvs
freeInArgs tv = any (tv `isFreeIn`) (fsigArgTypes sig)
pInstance :: Context -> PSig -> [Expression] -> [Expression] -> Checked TypeBinding
pInstance c sig actuals lhss = do
actualTypes <- mapAccum (checkExpression c) noType actuals
lhssTypes <- mapAccum (checkExpression c) noType lhss
case oneSidedUnifier (psigTypeVars sig) (psigArgTypes sig ++ psigRetTypes sig) (ctxTypeVars c) (actualTypes ++ lhssTypes) of
Nothing -> throwTypeError (ctxPos c) (text "Could not match procedure signature" <+>
doubleQuotes (sigDoc (psigArgTypes sig) (psigRetTypes sig)) <+>
text "against actual types" <+>
doubleQuotes (sigDoc actualTypes lhssTypes) <+>
text "in the call to" <+> text (psigName sig))
Just u -> return u
checkExpression :: Context -> Expression -> Checked Type
checkExpression c (Pos pos e) = case e of
TT -> return BoolType
FF -> return BoolType
Numeral n -> return IntType
Var id -> case M.lookup id (allNames c) of
Nothing -> throwTypeError pos (text "Not in scope: variable or constant" <+> text id)
Just t -> return t
Application id args -> checkApplication cPos id args Nothing
MapSelection m args -> checkMapSelection cPos m args
MapUpdate m args val -> checkMapUpdate cPos m args val
Old e1 -> if ctxTwoState c
then checkExpression c { ctxLocals = M.empty } e1
else throwTypeError pos (text "Old expression in a single state context")
IfExpr cond e1 e2 -> checkIfExpression cPos cond e1 e2
Coercion e t -> checkCoercion cPos e t
UnaryExpression op e1 -> checkUnaryExpression cPos op e1
BinaryExpression op e1 e2 -> checkBinaryExpression cPos op e1 e2
Quantified qop tv vars e -> checkQuantified cPos qop tv vars e
where
cPos = c { ctxPos = pos }
checkApplication :: Context -> Id -> [Expression] -> Maybe Type -> Checked Type
checkApplication c id args mRetType = case M.lookup id (ctxFunctions c) of
Nothing -> throwTypeError (ctxPos c) (text "Not in scope: function" <+> text id)
Just sig -> do
u <- fInstance c sig args mRetType
return $ typeSubst u (fsigRetType sig)
checkMapSelection :: Context -> Expression -> [Expression] -> Checked Type
checkMapSelection c m args = do
mType <- checkExpression c m
case mType of
MapType tv domainTypes rangeType -> do
actualTypes <- mapAccum (checkExpression c) noType args
case oneSidedUnifier tv domainTypes (ctxTypeVars c) actualTypes of
Nothing -> throwTypeError (ctxPos c) (text "Could not match map domain types" <+> doubleQuotes (commaSep (map typeDoc domainTypes)) <+>
text "against map selection types" <+> doubleQuotes (commaSep (map typeDoc actualTypes)) <+>
text "for the map" <+> exprDoc m)
Just u -> return (typeSubst u rangeType)
t -> throwTypeError (ctxPos c) (text "Map selection applied to a non-map" <+> exprDoc m <+> text "of type" <+> doubleQuotes (typeDoc t))
checkMapUpdate :: Context -> Expression -> [Expression] -> Expression -> Checked Type
checkMapUpdate c m args val = do
t <- checkMapSelection c m args
actualT <- checkExpression c val
if t <==> actualT
then checkExpression c m
else throwTypeError (ctxPos c) (text "Update value type" <+> doubleQuotes (typeDoc actualT) <+> text "different from map range type" <+> doubleQuotes (typeDoc t))
checkIfExpression :: Context -> Expression -> Expression -> Expression -> Checked Type
checkIfExpression c cond e1 e2 = do
compareType c "if-expression condition" BoolType cond
t <- checkExpression c e1
compareType c "else-part of the if-expression" t e2
return t
checkCoercion :: Context -> Expression -> Type -> Checked Type
checkCoercion c e t = do
checkType c t
let t' = resolve c t
case node e of
Application id args -> checkApplication cPos id args (Just t')
_ -> compareType c "coerced expression" t' e >> return t'
where cPos = c { ctxPos = position e }
checkUnaryExpression :: Context -> UnOp -> Expression -> Checked Type
checkUnaryExpression c op e
| op == Neg = matchType IntType IntType
| op == Not = matchType BoolType BoolType
where
matchType t ret = do
t' <- checkExpression c e
if t' <==> t then return ret else throwTypeError (ctxPos c) (errorMsg t' op)
errorMsg t op = text "Invalid argument type" <+> doubleQuotes (typeDoc t) <+> text "to unary operator" <+> unOpDoc op
checkBinaryExpression :: Context -> BinOp -> Expression -> Expression -> Checked Type
checkBinaryExpression c op e1 e2
| elem op [Plus, Minus, Times, Div, Mod] = matchTypes (\t1 t2 -> t1 <==> IntType && t2 <==> IntType) IntType
| elem op [And, Or, Implies, Explies, Equiv] = matchTypes (\t1 t2 -> t1 <==> BoolType && t2 <==> BoolType) BoolType
| elem op [Ls, Leq, Gt, Geq] = matchTypes (\t1 t2 -> t1 <==> IntType && t2 <==> IntType) BoolType
| elem op [Eq, Neq] = matchTypes (\t1 t2 -> isJust (unifier (ctxTypeVars c) [t1] [t2])) BoolType
| op == Lc = matchTypes (<==>) BoolType
where
matchTypes pred ret = do
t1 <- checkExpression c e1
t2 <- checkExpression c e2
if pred t1 t2 then return ret else throwTypeError (ctxPos c) (errorMsg t1 t2 op)
errorMsg t1 t2 op = text "Invalid argument types" <+> doubleQuotes (typeDoc t1) <+> text "and" <+> doubleQuotes (typeDoc t2) <+> text "to binary operator" <+> binOpDoc op
checkQuantified :: Context -> QOp -> [Id] -> [IdType] -> Expression -> Checked Type
checkQuantified c Lambda tv vars e = do
c' <- foldAccum checkTypeVar c tv
quantifiedScope <- foldAccum (checkIdType localScope ctxIns setIns) c' vars
if not (null missingTV)
then throwTypeError (ctxPos c) (text "Type variable(s) must occur among the types of lambda parameters:" <+> commaSep (map text missingTV))
else do
rangeType <- checkExpression quantifiedScope e
return $ MapType tv varTypes rangeType
where
varTypes = map snd vars
missingTV = filter (not . freeInVars) tv
freeInVars v = any (v `isFreeIn`) varTypes
checkQuantified c qop tv vars e = do
c' <- foldAccum checkTypeVar c tv
quantifiedScope <- foldAccum (checkIdType localScope ctxIns setIns) c' vars
compareType quantifiedScope "quantified expression" BoolType e
return BoolType
checkStatement :: Context -> Statement -> Checked ()
checkStatement c (Pos pos s) = case s of
Predicate (SpecClause _ _ e) -> compareType cPos "predicate" BoolType e
Havoc vars -> checkLefts cPos (nub vars) (length (nub vars))
Assign lhss rhss -> checkAssign cPos lhss rhss
Call lhss name args -> checkCall cPos lhss name args
CallForall name args -> checkCallForall cPos name args
If cond thenBlock elseBlock -> checkIf cPos cond thenBlock elseBlock
While cond invs b -> checkWhile cPos cond invs b
Goto ids -> checkGoto cPos ids
Break Nothing -> checkSimpleBreak cPos
Break (Just l) -> checkLabelBreak cPos l
_ -> return ()
where
cPos = c { ctxPos = pos }
checkAssign :: Context -> [(Id , [[Expression]])] -> [Expression] -> Checked ()
checkAssign c lhss rhss = do
checkLefts c (map fst lhss) (length rhss)
rTypes <- mapAccum (checkExpression c) noType rhss
zipWithAccum_ (compareType c "assignment left-hand side") rTypes (map selectExpr lhss)
where
selectExpr (id, selects) = foldl mapSelectExpr (attachPos (ctxPos c) (Var id)) selects
checkCall :: Context -> [Id] -> Id -> [Expression] -> Checked ()
checkCall c lhss name args = case M.lookup name (ctxProcedures c) of
Nothing -> throwTypeError (ctxPos c) (text "Not in scope: procedure" <+> text name)
Just sig -> let illegalModifies = psigModifies sig \\ ctxModifies c in
if not (null illegalModifies)
then throwTypeError (ctxPos c) (text "Call modifies a global variable that is not in the enclosing procedure's modifies clause:" <+> commaSep (map text illegalModifies))
else do
checkLefts c lhss (length $ psigRetTypes sig)
let lhssExpr = map (attachPos (ctxPos c) . Var) lhss
pInstance c sig args lhssExpr >> return ()
checkCallForall :: Context -> Id -> [WildcardExpression] -> Checked ()
checkCallForall c name args = case M.lookup name (ctxProcedures c) of
Nothing -> throwTypeError (ctxPos c) (text "Not in scope: procedure" <+> text name)
Just sig -> if not (null (psigModifies sig))
then throwTypeError (ctxPos c) (text "Call forall to a procedure with a non-empty modifies clause")
else pInstance c sig { psigArgs = concrete (psigArgs sig) } concreteArgs [] >> return ()
where
concreteArgs = [e | (Expr e) <- args]
concrete at = [at !! i | i <- [0..length args 1], isConcrete (args !! i)]
isConcrete Wildcard = False
isConcrete (Expr _) = True
checkIf :: Context -> WildcardExpression -> Block -> (Maybe Block) -> Checked ()
checkIf c cond thenBlock elseBlock = report $ do
case cond of
Wildcard -> return ()
Expr e -> accum (compareType c "branching condition" BoolType e) ()
accum (checkBlock c thenBlock) ()
case elseBlock of
Nothing -> return ()
Just b -> accum (checkBlock c b) ()
checkWhile :: Context -> WildcardExpression -> [SpecClause] -> Block -> Checked ()
checkWhile c cond invs body = report $ do
case cond of
Wildcard -> return ()
Expr e -> accum (compareType c "loop condition" BoolType e) ()
mapAccumA_ (compareType c "loop invariant" BoolType) (map specExpr invs)
accum (checkBlock c {ctxInLoop = True} body) ()
checkGoto :: Context -> [Id] -> Checked ()
checkGoto c ids = if not (null unknownLabels)
then throwTypeError (ctxPos c) (text "Not in scope: label(s)" <+> commaSep (map text unknownLabels))
else return ()
where
unknownLabels = ids \\ ctxLabels c
checkSimpleBreak :: Context -> Checked ()
checkSimpleBreak c = if not (ctxInLoop c)
then throwTypeError (ctxPos c) (text "Break statement outside a loop")
else return ()
checkLabelBreak :: Context -> Id -> Checked ()
checkLabelBreak c l = if not (l `elem` ctxEncLabels c)
then throwTypeError (ctxPos c) (text "Break label" <+> text l <+> text "does not label an enclosing statement")
else return ()
collectLabels :: Context -> Block -> Checked Context
collectLabels c block = foldAccum checkLStatement c block
where
checkLStatement c (Pos pos (ls, (Pos _ st))) = do
c' <- foldM (addLabel pos) c ls
case st of
If _ thenBlock mElseBlock -> do
c'' <- collectLabels c' thenBlock
case mElseBlock of
Nothing -> return c''
Just elseBlock -> collectLabels c'' elseBlock
While _ _ bodyBlock -> collectLabels c' bodyBlock
_ -> return c'
addLabel pos c l = if l `elem` ctxLabels c
then throwTypeError pos (text "Multiple occurrences of label" <+> text l <+> text "in a procedure body")
else return c {ctxLabels = l : ctxLabels c}
checkBlock :: Context -> Block -> Checked ()
checkBlock c block = mapAccum_ (checkLStatement c) block
where
checkLStatement c (Pos _ (ls, st)) = checkStatement c { ctxEncLabels = ctxEncLabels c ++ ls} st
collectTypes :: Context -> Decl -> Checked Context
collectTypes c (Pos pos d) = case d of
TypeDecl ts -> foldM checkTypeDecl c { ctxPos = pos } ts
otherwise -> return c
checkTypeDecl :: Context -> NewType -> Checked Context
checkTypeDecl c (NewType name formals value)
| name `elem` (typeNames c) = throwTypeError (ctxPos c) (text "Multiple declarations of type constructor or synonym" <+> text name)
| otherwise = case value of
Nothing -> return c { ctxTypeConstructors = M.insert name (length formals) (ctxTypeConstructors c) }
Just t -> return c { ctxTypeSynonyms = M.insert name (formals, t) (ctxTypeSynonyms c) }
checkTypeSynonyms :: Context -> Decl -> Checked ()
checkTypeSynonyms c (Pos pos d) = case d of
TypeDecl ts -> mapAccum_ (checkNewType c { ctxPos = pos }) ts
otherwise -> return ()
where
checkNewType c (NewType name formals (Just t)) = do
c' <- foldAccum checkTypeVar c formals
checkType c' t
checkNewType _ _ = return ()
checkCycles :: Context -> [Decl] -> Id -> Checked ()
checkCycles c decls id = checkCyclesWith c id (value id)
where
checkCyclesWith c id t = case t of
Instance name args -> do
if M.member name (ctxTypeSynonyms c)
then if id == name
then throwTypeError firstPos (text "Cycle in the definition of type synonym" <+> text id)
else checkCyclesWith c id (value name)
else return ()
mapAccum_ (checkCyclesWith c id) args
MapType _ domains range -> mapAccum_ (checkCyclesWith c id) (range:domains)
_ -> return ()
value name = snd (ctxTypeSynonyms c ! name)
firstPos = head [pos | Pos pos (TypeDecl ts) <- decls, id `elem` map tId ts]
checkSignatures :: Context -> Decl -> Checked Context
checkSignatures c (Pos pos d) = case d of
VarDecl vars -> foldAccum (checkIdType globalScope ctxGlobals setGlobals) cPos (map noWhere vars)
ConstantDecl _ ids t _ _ -> foldAccum (checkIdType globalScope ctxConstants setConstants) cPos (zip ids (repeat t))
FunctionDecl name tv args ret _ -> checkFunctionSignature cPos name tv args ret
ProcedureDecl name tv args rets specs _ -> checkProcSignature cPos name tv args rets specs
otherwise -> return c
where
cPos = c { ctxPos = pos }
checkIdType :: (Context -> Map Id Type) -> (Context -> Map Id Type) -> (Context -> Map Id Type -> Context) -> Context -> IdType -> Checked Context
checkIdType scope get set c (i, t)
| M.member i (scope c) = throwTypeError (ctxPos c) (text "Multiple declarations of variable or constant" <+> text i)
| otherwise = checkType c t >> return (c `set` M.insert i (resolve c t) (get c))
checkFunctionSignature :: Context -> Id -> [Id] -> [FArg] -> FArg -> Checked Context
checkFunctionSignature c name tv args ret
| name `elem` funProcNames c = throwTypeError (ctxPos c) (text "Multiple declarations of function or procedure" <+> text name)
| otherwise = do
c' <- foldAccum checkTypeVar c tv
foldAccum checkFArg c' params
if not (null missingTV)
then throwTypeError (ctxPos c) (text "Type variable(s) must occur in function arguments or return type:" <+> commaSep (map text missingTV))
else return $ addFSig c name (FSig name tv argTypes retType)
where
params = args ++ [ret]
checkFArg c (Just id, t) = checkIdType ctxIns ctxIns setIns c (id, t)
checkFArg c (Nothing, t) = checkType c t >> return c
missingTV = filter (not . freeInParams) tv
freeInParams v = any (v `isFreeIn`) (map snd params)
addFSig c name sig = c { ctxFunctions = M.insert name sig (ctxFunctions c) }
argTypes = map (resolve c . snd) args
retType = (resolve c . snd) ret
checkProcSignature :: Context -> Id -> [Id] -> [IdTypeWhere] -> [IdTypeWhere] -> [Contract] -> Checked Context
checkProcSignature c name tv args rets specs
| name `elem` funProcNames c = throwTypeError (ctxPos c) (text "Multiple declarations of function or procedure" <+> text name)
| otherwise = do
c' <- foldAccum checkTypeVar c tv
foldAccum checkPArg c' params
if not (null missingTV)
then throwTypeError (ctxPos c) (text "Type variable(s) must occur in procedure in- our out-parameters:" <+> commaSep (map text missingTV))
else return $ addPSig c name (PSig name tv (map resolveType args) (map resolveType rets) specs)
where
params = args ++ rets
checkPArg c arg = checkIdType ctxIns ctxIns setIns c (noWhere arg)
missingTV = filter (not . freeInParams) tv
freeInParams v = any (v `isFreeIn`) (map itwType params)
addPSig c name sig = c { ctxProcedures = M.insert name sig (ctxProcedures c) }
resolveType (IdTypeWhere id t w) = IdTypeWhere id (resolve c t) w
checkBodies :: Context -> Decl -> Checked Context
checkBodies c (Pos pos d) = case d of
VarDecl vars -> foldAccum checkWhere cPos vars
ConstantDecl _ ids t (Just edges) _ -> checkParentInfo cPos ids t (map snd edges) >> return c
FunctionDecl name tv args ret (Just body) -> checkFunction cPos name tv args body >> return c
AxiomDecl e -> checkAxiom cPos e >> return c
ProcedureDecl name tv args rets specs mb -> checkProcedure cPos tv args rets specs mb >> return c
ImplementationDecl name tv args rets bodies -> checkImplementation cPos name tv args rets bodies >> return c
otherwise -> return c
where
cPos = c { ctxPos = pos }
checkWhere :: Context -> IdTypeWhere -> Checked Context
checkWhere c var = do
compareType c { ctxTwoState = False } "where clause" BoolType (itwWhere var)
return c { ctxWhere = M.insert (itwId var) (itwWhere var) (ctxWhere c) }
checkParentInfo :: Context -> [Id] -> Type -> [Id] -> Checked ()
checkParentInfo c ids t parents = if length parents /= length (nub parents)
then throwTypeError (ctxPos c) (text "Parent list contains duplicates:" <+> commaSep (map text parents))
else mapAccum_ checkParent parents
where
checkParent p = case M.lookup p (ctxConstants c) of
Nothing -> throwTypeError (ctxPos c) (text "Not in scope: constant" <+> text p)
Just t' -> if not (t <==> t')
then throwTypeError (ctxPos c) (text "Parent type" <+> doubleQuotes (typeDoc t') <+> text "is different from constant type" <+> doubleQuotes (typeDoc t))
else if p `elem` ids
then throwTypeError (ctxPos c) (text "Constant" <+> text p <+> text "is decalred to be its own parent")
else return ()
checkAxiom :: Context -> Expression -> Checked ()
checkAxiom c e = compareType c {ctxGlobals = M.empty } "axiom" BoolType e
checkFunction :: Context -> Id -> [Id] -> [FArg] -> Expression -> Checked ()
checkFunction c name tv args body = do
functionScope <- foldAccum addFArg c { ctxTypeVars = tv } args
compareType functionScope { ctxGlobals = M.empty } "function body" retType body
where
addFArg c (Just id, t) = checkIdType ctxIns ctxIns setIns c (id, t)
addFArg c _ = return c
sig = funSig name c
retType = fsigRetType sig
checkProcedure :: Context -> [Id] -> [IdTypeWhere] -> [IdTypeWhere] -> [Contract] -> (Maybe Body) -> Checked ()
checkProcedure c tv args rets specs mb = do
cArgs <- foldAccum (checkIdType localScope ctxIns setIns) c { ctxTypeVars = tv } (map noWhere args)
_ <- foldAccum checkWhere cArgs args
mapAccum_ (compareType cArgs "precondition" BoolType . specExpr) (preconditions specs)
cRets <- foldAccum (checkIdType localScope ctxLocals setLocals) cArgs (map noWhere rets)
_ <- foldAccum checkWhere cRets rets
mapAccum_ (compareType cRets {ctxTwoState = True} "postcondition" BoolType . specExpr) (postconditions specs)
if not (null invalidModifies)
then throwTypeError (ctxPos c) (text "Identifier in a modifies clause does not denote a global variable:" <+> commaSep (map text invalidModifies))
else case mb of
Nothing -> return ()
Just body -> checkBody cRets { ctxModifies = modifies specs, ctxTwoState = True } body
where invalidModifies = modifies specs \\ M.keys (ctxGlobals c)
checkBody :: Context -> Body -> Checked ()
checkBody c body = do
bodyScope <- foldAccum (checkIdType localScope ctxLocals setLocals) c (map noWhere (concat (fst body)))
_ <- foldAccum checkWhere bodyScope (concat (fst body))
bodyScope' <- collectLabels bodyScope (snd body)
checkBlock bodyScope' (snd body)
checkImplementation :: Context -> Id -> [Id] -> [IdType] -> [IdType] -> [Body] -> Checked ()
checkImplementation c name tv args rets bodies = case M.lookup name (ctxProcedures c) of
Nothing -> throwTypeError (ctxPos c) (text "Not in scope: procedure" <+> text name)
Just sig -> case boundUnifier [] (psigTypeVars sig) (psigArgTypes sig ++ psigRetTypes sig) tv (argTypes ++ retTypes) of
Nothing -> throwTypeError (ctxPos c) (text "Could not match procedure signature" <+>
doubleQuotes (sigDoc (psigArgTypes sig) (psigRetTypes sig)) <+>
text "against implementation signature" <+>
doubleQuotes (sigDoc argTypes retTypes) <+>
text "in the implementation of" <+> text name)
Just _ -> do
cArgs <- foldAccum (checkIdType localScope ctxIns setIns) c { ctxTypeVars = tv } args
cRets <- foldAccum (checkIdType localScope ctxLocals setLocals) cArgs rets
mapAccum_ (checkBody cRets { ctxModifies = (psigModifies sig), ctxTwoState = True }) bodies
where
argTypes = map (resolve c . snd) args
retTypes = map (resolve c . snd) rets
compareType :: Context -> String -> Type -> Expression -> Checked ()
compareType c msg t e = do
t' <- checkExpression c e
if t <==> t'
then return ()
else throwTypeError (ctxPos c) (text "Type of" <+> text msg <+> doubleQuotes (typeDoc t') <+> text "is different from" <+> doubleQuotes (typeDoc t))
checkLefts :: Context -> [Id] -> Int -> Checked ()
checkLefts c vars n = if length vars /= n
then throwTypeError (ctxPos c) (text "Expected" <+> int n <+> text "left-hand sides and got" <+> int (length vars))
else if vars /= nub vars
then throwTypeError (ctxPos c) (text "Variable occurs more than once among left-handes of a parallel assignment")
else if not (null immutableLhss)
then throwTypeError (ctxPos c) (text "Assignment to immutable variable(s):" <+> commaSep (map text immutableLhss))
else if not (null invalidGlobals)
then throwTypeError (ctxPos c) (text "Assignment to a global variable that is not in the enclosing procedure's modifies clause:" <+> commaSep (map text invalidGlobals))
else return ()
where
immutableLhss = vars \\ M.keys (mutableVars c)
invalidGlobals = (vars \\ M.keys (ctxLocals c)) \\ ctxModifies c