c-expr-dsl-0.1.0.0: src/C/Expr/Typecheck.hs
{-# LANGUAGE CPP #-}
#if __GLASGOW_HASKELL__ >=908
{-# LANGUAGE TypeAbstractions #-}
#endif
-- | Public entry point for typechecking macros.
module C.Expr.Typecheck (
tcMacros
, TypecheckedMacroTypeExpr(..)
, TypecheckedMacroValueExpr(..)
, MacroTcResult(..)
-- * Errors
, MacroTcError(..)
, pprMacroTcError
) where
import Data.Foldable qualified as Foldable
import Data.Map (Map)
import Data.Map.Strict qualified as Map
import Data.Type.Equality ((:~:) (..))
import Data.Type.Nat qualified as Nat
import Data.Vec.Lazy (Vec)
import Data.Vec.Lazy qualified as Vec
import GHC.Generics
import C.Expr.Syntax
import C.Expr.Typecheck.Expr
import C.Expr.Typecheck.Interface.Type qualified as T
import C.Expr.Typecheck.Interface.Value qualified as V
import C.Expr.Typecheck.Type
-- | Batch-typecheck a sequence of macros
--
-- The macros are processed in order. Each successful macro is added to the
-- internal 'TypeEnv' so that subsequent macros can reference it. A macro that
-- fails to typecheck is /not/ added to the environment; later macros that
-- reference it will fail with an unbound-variable error.
--
-- @typeOfAnn@ projects each variable's parse annotation to its type, if known
-- (e.g. for @typedef@ names the embedder supplies); 'Nothing' falls back to the
-- internal 'TypeEnv' of previously-typechecked macros.
tcMacros ::
forall ann.
(ann -> Maybe QuantTy)
-- ^ See the documentation of 'C.Expr.Typecheck.Type.Tc'.
-> [Macro ann]
-> Map Identifier (MacroTcResult ann)
tcMacros typeOfAnn macros =
let (_, tcRs) = Foldable.foldl' step (Map.empty, Map.empty) macros
in tcRs
where
step ::
(TypeEnv, Map Identifier (MacroTcResult ann))
-> Macro ann
-> (TypeEnv, Map Identifier (MacroTcResult ann))
step (env, acc) (Macro _loc name params body) =
let result :: MacroTcResult ann
result = tcMacroOne typeOfAnn env name params body
env' = case result of
MacroTcTypeExpr cmt ->
Map.insert name (macroTypeType cmt) env
MacroTcValueExpr cmv ->
Map.insert name (macroValueType cmv) env
MacroTcError _ ->
env
in (env', Map.insert name result acc)
{-------------------------------------------------------------------------------
Types
-------------------------------------------------------------------------------}
-- | The macro is a C type expression (e.g., @#define FOO int@).
data TypecheckedMacroTypeExpr ann = TypecheckedMacroTypeExpr{
macroTypeBody :: T.Expr ann
, macroTypeType :: QuantTy
}
deriving stock (Eq, Show, Generic, Functor, Foldable, Traversable)
-- | The macro is a value expression (e.g., @#define BAR 1@).
data TypecheckedMacroValueExpr ann = forall ctx. TypecheckedMacroValueExpr{
macroValueParams :: Vec ctx Identifier
, macroValueBody :: V.Expr ctx ann
, macroValueType :: QuantTy
}
instance Eq ann => Eq (TypecheckedMacroValueExpr ann) where
(TypecheckedMacroValueExpr @_ @c1 p1 b1 t1) == (TypecheckedMacroValueExpr @_ @c2 p2 b2 t2) =
t1 == t2 && (
Vec.withDict p1 $ Vec.withDict p2 $
case Nat.eqNat @c1 @c2 of
Just Refl -> p1 == p2 && b1 == b2
Nothing -> False
)
deriving stock instance Show ann => Show (TypecheckedMacroValueExpr ann)
deriving stock instance Functor TypecheckedMacroValueExpr
deriving stock instance Foldable TypecheckedMacroValueExpr
deriving stock instance Traversable TypecheckedMacroValueExpr
-- | The result of typechecking a single macro.
data MacroTcResult ann =
MacroTcTypeExpr (TypecheckedMacroTypeExpr ann)
| MacroTcValueExpr (TypecheckedMacroValueExpr ann)
-- | The @c-expr-dsl@ typechecker rejected the macro.
| MacroTcError MacroTcError
deriving stock instance (Show ann) => Show (MacroTcResult ann)
deriving stock instance (Eq ann) => Eq (MacroTcResult ann)
{-------------------------------------------------------------------------------
Internal: typecheck a single macro against a given 'TypeEnv'.
-------------------------------------------------------------------------------}
-- | Typecheck a single macro against a given 'TypeEnv'.
tcMacroOne ::
forall ctx ann.
(ann -> Maybe QuantTy)
-> TypeEnv
-> Identifier
-> Vec ctx Identifier
-> Expr ctx (Ps ann)
-> MacroTcResult ann
tcMacroOne typeOfAnn tyEnv name params expr =
case tcExpr tyEnv name params (fmapExpr typeOfAnn expr) of
Left err -> MacroTcError err
Right res -> classify res
where
classify :: (Type Ty, Quant (FunValue, Type Ty)) -> MacroTcResult ann
classify = \case
(MacroTypeTy, quant)
| not (Vec.null params) ->
MacroTcError $
TcUnsupportedTypeWithLocalParameters name (Vec.toList params)
| otherwise ->
let texpr :: T.Expr ann
texpr = T.fromExpr expr
in if isIncompleteType texpr then
MacroTcError $ TcIncompleteTypeMacro name
else
MacroTcTypeExpr $ TypecheckedMacroTypeExpr texpr quant
(_, quant) ->
(\vexpr -> MacroTcValueExpr $
TypecheckedMacroValueExpr params vexpr quant) $
V.fromExpr expr
-- | An incomplete type at the top level of a type-like macro: 'void' or
-- 'const'-wrapped 'void'. Pointer indirection makes the type complete, so
-- 'void *' (and 'const void *') are not flagged.
isIncompleteType :: T.Expr var -> Bool
isIncompleteType = \case
T.TypeLit TypeVoid -> True
T.App T.Const e -> isIncompleteType e
_ -> False