{-# LANGUAGE ScopedTypeVariables #-}
module Language.Fortran.Analysis.Types
( analyseTypes, analyseTypesWithEnv, analyseAndCheckTypesWithEnv, extractTypeEnv, TypeEnv, TypeError )
where
import Language.Fortran.AST
import Prelude hiding (lookup, EQ, LT, GT)
import Data.Map (insert)
import qualified Data.Map as M
import Data.Maybe (maybeToList)
import Data.List (find)
import Control.Monad.State.Strict
import Data.Generics.Uniplate.Data
import Data.Data
import Data.Functor.Identity (Identity ())
import Language.Fortran.Analysis
import Language.Fortran.Intrinsics
import Language.Fortran.Util.Position
import Language.Fortran.ParserMonad (FortranVersion(..))
type TypeEnv = M.Map Name IDType
type TypeError = (String, SrcSpan)
type Infer a = State InferState a
data InferState = InferState { langVersion :: FortranVersion
, intrinsics :: IntrinsicsTable
, environ :: TypeEnv
, entryPoints :: M.Map Name (Name, Maybe Name)
, typeErrors :: [TypeError] }
deriving Show
type InferFunc t = t -> Infer ()
analyseTypes :: Data a => ProgramFile (Analysis a) -> (ProgramFile (Analysis a), TypeEnv)
analyseTypes = analyseTypesWithEnv M.empty
analyseTypesWithEnv :: Data a => TypeEnv -> ProgramFile (Analysis a) -> (ProgramFile (Analysis a), TypeEnv)
analyseTypesWithEnv env pf = (pf', tenv)
where
(pf', endState) = analyseTypesWithEnv' env pf
tenv = environ endState
analyseAndCheckTypesWithEnv
:: Data a => TypeEnv -> ProgramFile (Analysis a) -> (ProgramFile (Analysis a), TypeEnv, [TypeError])
analyseAndCheckTypesWithEnv env pf = (pf', tenv, terrs)
where
(pf', endState) = analyseTypesWithEnv' env pf
tenv = environ endState
terrs = typeErrors endState
analyseTypesWithEnv' :: Data a => TypeEnv -> ProgramFile (Analysis a) -> (ProgramFile (Analysis a), InferState)
analyseTypesWithEnv' env pf@(ProgramFile mi _) = runInfer (miVersion mi) env $ do
mapM_ intrinsicsExp (allExpressions pf)
mapM_ programUnit (allProgramUnits pf)
mapM_ declarator (allDeclarators pf)
mapM_ statement (allStatements pf)
eps <- gets (M.toList . entryPoints)
_ <- forM eps $ \ (eName, (fName, mRetName)) -> do
mFType <- getRecordedType fName
case mFType of
Just (IDType fVType fCType) -> do
recordMType fVType fCType eName
maybe (return ()) (error "Entry points with result variables unsupported" >> recordMType fVType Nothing) mRetName
_ -> return ()
annotateTypes pf
extractTypeEnv :: forall a. Data a => ProgramFile (Analysis a) -> TypeEnv
extractTypeEnv pf = M.union puEnv expEnv
where
puEnv = M.fromList [ (n, ty) | pu <- universeBi pf :: [ProgramUnit (Analysis a)]
, Named n <- [puName pu]
, ty <- maybeToList (idType (getAnnotation pu)) ]
expEnv = M.fromList [ (n, ty) | e@(ExpValue _ _ ValVariable{}) <- universeBi pf :: [Expression (Analysis a)]
, let n = varName e
, ty <- maybeToList (idType (getAnnotation e)) ]
type TransType f g a = (f (Analysis a) -> Infer (f (Analysis a))) -> g (Analysis a) -> Infer (g (Analysis a))
annotateTypes :: Data a => ProgramFile (Analysis a) -> Infer (ProgramFile (Analysis a))
annotateTypes pf = (transformBiM :: Data a => TransType Expression ProgramFile a) annotateExpression pf >>=
(transformBiM :: Data a => TransType ProgramUnit ProgramFile a) annotateProgramUnit
intrinsicsExp :: Data a => InferFunc (Expression (Analysis a))
intrinsicsExp (ExpSubscript _ _ nexp _) = intrinsicsHelper nexp
intrinsicsExp (ExpFunctionCall _ _ nexp _) = intrinsicsHelper nexp
intrinsicsExp _ = return ()
intrinsicsHelper :: Expression (Analysis a) -> StateT InferState Identity ()
intrinsicsHelper nexp | isNamedExpression nexp = do
itab <- gets intrinsics
case getIntrinsicReturnType (srcName nexp) itab of
Just _ -> do
let n = varName nexp
recordCType CTIntrinsic n
_ -> return ()
intrinsicsHelper _ = return ()
programUnit :: Data a => InferFunc (ProgramUnit (Analysis a))
programUnit pu@(PUFunction _ _ mRetType _ _ _ mRetVar blocks _)
| Named n <- puName pu = do
recordCType CTFunction n
case (mRetType, mRetVar) of
(Just (TypeSpec _ _ baseType _), Just v) -> recordBaseType baseType n >> recordBaseType baseType (varName v)
(Just (TypeSpec _ _ baseType _), _) -> recordBaseType baseType n
_ -> return ()
forM_ blocks $ \ block ->
sequence_ [ recordEntryPoint n (varName v) (fmap varName mRetVar') | (StEntry _ _ v _ mRetVar') <- allStatements block ]
programUnit pu@(PUSubroutine _ _ _ _ _ blocks _) | Named n <- puName pu = do
recordCType CTSubroutine n
forM_ blocks $ \ block ->
sequence_ [ recordEntryPoint n (varName v) Nothing | (StEntry _ _ v _ _) <- allStatements block ]
programUnit _ = return ()
declarator :: Data a => InferFunc (Declarator (Analysis a))
declarator (DeclArray _ _ v ddAList _ _) = recordCType (CTArray $ dimDeclarator ddAList) (varName v)
declarator _ = return ()
dimDeclarator :: AList DimensionDeclarator a -> [(Maybe Int, Maybe Int)]
dimDeclarator ddAList = [ (lb, ub) | DimensionDeclarator _ _ lbExp ubExp <- aStrip ddAList
, let lb = do ExpValue _ _ (ValInteger i) <- lbExp
return $ read i
, let ub = do ExpValue _ _ (ValInteger i) <- ubExp
return $ read i ]
statement :: Data a => InferFunc (Statement (Analysis a))
statement (StDeclaration _ _ (TypeSpec _ _ baseType _) mAttrAList declAList)
| mAttrs <- maybe [] aStrip mAttrAList
, attrDim <- find isAttrDimension mAttrs
, isParam <- any isAttrParameter mAttrs
, isExtrn <- any isAttrExternal mAttrs
, decls <- aStrip declAList = do
env <- gets environ
let cType n | isExtrn = CTExternal
| Just (AttrDimension _ _ ddAList) <- attrDim = CTArray (dimDeclarator ddAList)
| isParam = CTParameter
| Just (IDType _ (Just ct)) <- M.lookup n env
, ct /= CTIntrinsic = ct
| otherwise = CTVariable
let charLen (ExpValue _ _ (ValInteger i)) = CharLenInt (read i)
charLen (ExpValue _ _ ValStar) = CharLenStar
charLen _ = CharLenExp
let bType (Just e)
| TypeCharacter _ kind <- baseType = TypeCharacter (Just $ charLen e) kind
| otherwise = TypeCharacter (Just $ charLen e) Nothing
bType Nothing = baseType
forM_ decls $ \ decl -> case decl of
DeclArray _ _ v ddAList e _ -> recordType (bType e) (CTArray $ dimDeclarator ddAList) (varName v)
DeclVariable _ _ v e _ -> recordType (bType e) (cType n) n where n = varName v
statement (StExternal _ _ varAList) = do
let vars = aStrip varAList
mapM_ (recordCType CTExternal . varName) vars
statement (StExpressionAssign _ _ (ExpSubscript _ _ v ixAList) _)
| all isIxSingle (aStrip ixAList) = do
let n = varName v
mIDType <- getRecordedType n
case mIDType of
Just (IDType _ (Just CTArray{})) -> return ()
_ -> recordCType CTFunction n
statement (StFunction _ _ v _ _) = recordCType CTFunction (varName v)
statement (StExpressionAssign _ _ (ExpFunctionCall _ _ v Nothing) _) = recordCType CTFunction (varName v)
statement (StDimension _ _ declAList) = do
let decls = aStrip declAList
forM_ decls $ \ decl -> case decl of
DeclArray _ _ v ddAList _ _ -> recordCType (CTArray $ dimDeclarator ddAList) (varName v)
_ -> return ()
statement _ = return ()
annotateExpression :: Data a => Expression (Analysis a) -> Infer (Expression (Analysis a))
annotateExpression e@(ExpValue _ _ (ValVariable _)) = maybe e (`setIDType` e) `fmap` getRecordedType (varName e)
annotateExpression e@(ExpValue _ _ (ValIntrinsic _)) = maybe e (`setIDType` e) `fmap` getRecordedType (varName e)
annotateExpression e@(ExpValue _ _ (ValReal r)) = return $ realLiteralType r `setIDType` e
annotateExpression e@(ExpValue _ _ (ValComplex e1 e2)) = return $ complexLiteralType e1 e2 `setIDType` e
annotateExpression e@(ExpValue _ _ (ValInteger _)) = return $ IDType (Just TypeInteger) Nothing `setIDType` e
annotateExpression e@(ExpValue _ _ (ValLogical _)) = return $ IDType (Just TypeLogical) Nothing `setIDType` e
annotateExpression e@(ExpBinary _ _ op e1 e2) = flip setIDType e `fmap` binaryOpType (getSpan e) op e1 e2
annotateExpression e@(ExpUnary _ _ op e1) = flip setIDType e `fmap` unaryOpType (getSpan e1) op e1
annotateExpression e@(ExpSubscript _ _ e1 idxAList) = flip setIDType e `fmap` subscriptType (getSpan e) e1 idxAList
annotateExpression e@(ExpFunctionCall _ _ e1 parAList) = flip setIDType e `fmap` functionCallType (getSpan e) e1 parAList
annotateExpression e = return e
annotateProgramUnit :: Data a => ProgramUnit (Analysis a) -> Infer (ProgramUnit (Analysis a))
annotateProgramUnit pu | Named n <- puName pu = maybe pu (`setIDType` pu) `fmap` getRecordedType n
annotateProgramUnit pu = return pu
realLiteralType :: String -> IDType
realLiteralType r | 'd' `elem` r = IDType (Just TypeDoublePrecision) Nothing
| otherwise = IDType (Just TypeReal) Nothing
complexLiteralType :: Expression a -> Expression a -> IDType
complexLiteralType (ExpValue _ _ (ValReal r)) _
| IDType (Just TypeDoublePrecision) _ <- realLiteralType r = IDType (Just TypeDoubleComplex) Nothing
| otherwise = IDType (Just TypeComplex) Nothing
complexLiteralType _ _ = IDType (Just TypeComplex) Nothing
binaryOpType :: Data a => SrcSpan -> BinaryOp -> Expression (Analysis a) -> Expression (Analysis a) -> Infer IDType
binaryOpType ss op e1 e2 = do
mbt1 <- case getIDType e1 of
Just (IDType (Just bt) _) -> return $ Just bt
_ -> typeError "Unable to obtain type for first operand" (getSpan e1) >> return Nothing
mbt2 <- case getIDType e2 of
Just (IDType (Just bt) _) -> return $ Just bt
_ -> typeError "Unable to obtain type for second operand" (getSpan e2) >> return Nothing
case (mbt1, mbt2) of
(_, Nothing) -> return emptyType
(Nothing, _) -> return emptyType
(Just bt1, Just bt2) -> do
mbt <- case (bt1, bt2) of
(_ , TypeDoubleComplex ) -> return . Just $ TypeDoubleComplex
(TypeDoubleComplex , _ ) -> return . Just $ TypeDoubleComplex
(_ , TypeComplex ) -> return . Just $ TypeComplex
(TypeComplex , _ ) -> return . Just $ TypeComplex
(_ , TypeDoublePrecision ) -> return . Just $ TypeDoublePrecision
(TypeDoublePrecision , _ ) -> return . Just $ TypeDoublePrecision
(_ , TypeReal ) -> return . Just $ TypeReal
(TypeReal , _ ) -> return . Just $ TypeReal
(_ , TypeInteger ) -> return . Just $ TypeInteger
(TypeInteger , _ ) -> return . Just $ TypeInteger
(TypeByte , TypeByte ) -> return . Just $ TypeByte
(TypeLogical , TypeLogical ) -> return . Just $ TypeLogical
(TypeCustom _ , TypeCustom _ ) -> do
typeError "custom types / binary op not supported" ss
return Nothing
(TypeCharacter l1 k1 , TypeCharacter l2 _ )
| op == Concatenation -> return . Just $ TypeCharacter (liftM2 charLenConcat l1 l2) k1
| op `elem` [EQ, NE] -> return $ Just TypeLogical
| otherwise -> do typeError "Invalid op on character strings" ss
return Nothing
_ -> do typeError "Type error between operands of binary operator" ss
return Nothing
mbt' <- case mbt of
Just bt
| op `elem` [ Addition, Subtraction, Multiplication, Division
, Exponentiation, Concatenation, Or, XOr, And ] -> return $ Just bt
| op `elem` [GT, GTE, LT, LTE, EQ, NE, Equivalent, NotEquivalent] -> return $ Just TypeLogical
| BinCustom{} <- op -> typeError "custom binary ops not supported" ss >> return Nothing
_ -> return Nothing
return $ IDType mbt' Nothing
unaryOpType :: Data a => SrcSpan -> UnaryOp -> Expression (Analysis a) -> Infer IDType
unaryOpType ss op e = do
mbt <- case getIDType e of
Just (IDType (Just bt) _) -> return $ Just bt
_ -> typeError "Unable to obtain type for" (getSpan e) >> return Nothing
mbt' <- case (mbt, op) of
(Nothing, _) -> return Nothing
(Just TypeCustom{}, _) -> typeError "custom types / unary ops not supported" ss >> return Nothing
(_, UnCustom{}) -> typeError "custom unary ops not supported" ss >> return Nothing
(Just TypeLogical, Not) -> return $ Just TypeLogical
(Just bt, _)
| op `elem` [Plus, Minus] &&
bt `elem` numericTypes -> return $ Just bt
_ -> typeError "Type error for unary operator" ss >> return Nothing
return $ IDType mbt' Nothing
subscriptType :: Data a => SrcSpan -> Expression (Analysis a) -> AList Index (Analysis a) -> Infer IDType
subscriptType ss e1 (AList _ _ idxs) = do
let isInteger ie | Just (IDType (Just TypeInteger) _) <- getIDType ie = True | otherwise = False
forM_ idxs $ \ idx -> case idx of
IxSingle _ _ _ ie
| not (isInteger ie) -> typeError "Invalid or unknown type for index" (getSpan ie)
IxRange _ _ mie1 mie2 mie3
| Just ie1 <- mie1, not (isInteger ie1) -> typeError "Invalid or unknown type for index" (getSpan ie1)
| Just ie2 <- mie2, not (isInteger ie2) -> typeError "Invalid or unknown type for index" (getSpan ie2)
| Just ie3 <- mie3, not (isInteger ie3) -> typeError "Invalid or unknown type for index" (getSpan ie3)
_ -> return ()
case getIDType e1 of
Just ty@(IDType mbt (Just (CTArray dds))) -> do
when (length idxs /= length dds) $ typeError "Length of indices does not match rank of array." ss
let isSingle (IxSingle{}) = True; isSingle _ = False
if all isSingle idxs
then return $ IDType mbt Nothing
else return ty
_ -> return emptyType
functionCallType :: Data a => SrcSpan -> Expression (Analysis a) -> Maybe (AList Argument (Analysis a)) -> Infer IDType
functionCallType ss (ExpValue _ _ (ValIntrinsic n)) (Just (AList _ _ params)) = do
itab <- gets intrinsics
let mRetType = getIntrinsicReturnType n itab
case mRetType of
Nothing -> return emptyType
Just retType -> do
mbt <- case retType of
ITReal -> return $ Just TypeReal
ITInteger -> return $ Just TypeInteger
ITComplex -> return $ Just TypeComplex
ITDouble -> return $ Just TypeDoublePrecision
ITLogical -> return $ Just TypeLogical
ITCharacter -> return . Just $ TypeCharacter Nothing Nothing
ITParam i
| length params >= i, Argument _ _ _ e <- params !! (i-1)
-> return $ idVType =<< getIDType e
| otherwise -> typeError ("Invalid parameter list to intrinsic '" ++ n ++ "'") ss >> return Nothing
case mbt of
Nothing -> return emptyType
Just _ -> return $ IDType mbt Nothing
functionCallType ss e1 _ = case getIDType e1 of
Just (IDType (Just bt) (Just CTFunction)) -> return $ IDType (Just bt) Nothing
Just (IDType (Just bt) (Just CTExternal)) -> return $ IDType (Just bt) Nothing
_ -> typeError "non-function invoked by call" ss >> return emptyType
charLenConcat :: CharacterLen -> CharacterLen -> CharacterLen
charLenConcat l1 l2 = case (l1, l2) of
(CharLenExp , _ ) -> CharLenExp
(_ , CharLenExp ) -> CharLenExp
(CharLenStar , _ ) -> CharLenStar
(_ , CharLenStar ) -> CharLenStar
(CharLenColon , _ ) -> CharLenColon
(_ , CharLenColon ) -> CharLenColon
(CharLenInt i1 , CharLenInt i2 ) -> CharLenInt (i1 + i2)
numericTypes :: [BaseType]
numericTypes = [TypeDoubleComplex, TypeComplex, TypeDoublePrecision, TypeReal, TypeInteger, TypeByte]
inferState0 :: FortranVersion -> InferState
inferState0 v = InferState { environ = M.empty, entryPoints = M.empty, langVersion = v
, intrinsics = getVersionIntrinsics v, typeErrors = [] }
runInfer :: FortranVersion -> TypeEnv -> State InferState a -> (a, InferState)
runInfer v env = flip runState ((inferState0 v) { environ = env })
typeError :: String -> SrcSpan -> Infer ()
typeError msg ss = modify $ \ s -> s { typeErrors = (msg, ss):typeErrors s }
emptyType :: IDType
emptyType = IDType Nothing Nothing
recordType :: BaseType -> ConstructType -> Name -> Infer ()
recordType bt ct n = modify $ \ s -> s { environ = insert n (IDType (Just bt) (Just ct)) (environ s) }
recordMType :: Maybe BaseType -> Maybe ConstructType -> Name -> Infer ()
recordMType bt ct n = modify $ \ s -> s { environ = insert n (IDType bt ct) (environ s) }
recordCType :: ConstructType -> Name -> Infer ()
recordCType ct n = modify $ \ s -> s { environ = M.alter changeFunc n (environ s) }
where changeFunc mIDType = Just (IDType (mIDType >>= idVType) (Just ct))
recordBaseType :: BaseType -> Name -> Infer ()
recordBaseType bt n = modify $ \ s -> s { environ = M.alter changeFunc n (environ s) }
where changeFunc mIDType = Just (IDType (Just bt) (mIDType >>= idCType))
recordEntryPoint :: Name -> Name -> Maybe Name -> Infer ()
recordEntryPoint fn en mRetName = modify $ \ s -> s { entryPoints = M.insert en (fn, mRetName) (entryPoints s) }
getRecordedType :: Name -> Infer (Maybe IDType)
getRecordedType n = gets (M.lookup n . environ)
setIDType :: Annotated f => IDType -> f (Analysis a) -> f (Analysis a)
setIDType ty x
| a@Analysis {} <- getAnnotation x = setAnnotation (a { idType = Just ty }) x
| otherwise = x
getIDType :: (Annotated f, Data a) => f (Analysis a) -> Maybe IDType
getIDType x = idType (getAnnotation x)
type UniFunc f g a = f (Analysis a) -> [g (Analysis a)]
allProgramUnits :: Data a => UniFunc ProgramFile ProgramUnit a
allProgramUnits = universeBi
allDeclarators :: Data a => UniFunc ProgramFile Declarator a
allDeclarators = universeBi
allStatements :: (Data a, Data (f (Analysis a))) => UniFunc f Statement a
allStatements = universeBi
allExpressions :: (Data a, Data (f (Analysis a))) => UniFunc f Expression a
allExpressions = universeBi
isAttrDimension :: Attribute a -> Bool
isAttrDimension AttrDimension {} = True
isAttrDimension _ = False
isAttrParameter :: Attribute a -> Bool
isAttrParameter AttrParameter {} = True
isAttrParameter _ = False
isAttrExternal :: Attribute a -> Bool
isAttrExternal AttrExternal {} = True
isAttrExternal _ = False
isIxSingle :: Index a -> Bool
isIxSingle IxSingle {} = True
isIxSingle _ = False