packages feed

hs-bindgen-1.0.0.0: src-internal/HsBindgen/Frontend/Pass/AdjustTypes.hs

module HsBindgen.Frontend.Pass.AdjustTypes (
    adjustTypes
  ) where

import Data.Proxy (Proxy (Proxy))
import Numeric.Natural (Natural)

import HsBindgen.Frontend.Pass.AdjustTypes.IsPass
import HsBindgen.Frontend.Pass.MangleNames.IsPass
import HsBindgen.Frontend.Pass.TypecheckMacros.IsPass
import HsBindgen.Frontend.TranslationUnit qualified as C
import HsBindgen.IR.C qualified as C
import HsBindgen.IR.Pass

-- | Adjust function argument types
--
-- The C reference describes that function parameters of some specific types are
-- adjusted. See:
--
-- <https://en.cppreference.com/w/c/language/function_declaration.html#Explanation>
--
-- We apply some of these adjustments to reduce ambiguity about the
-- representation of function parameter types. These adjustments are applied
-- everywhere that a C type occurs in the AST, even recursively. The adjustments
-- we apply are:
--
-- > * any parameter of function type is adjusted to the corresponding pointer
-- >  type
--
-- > * any parameter of array type is adjusted to the corresponding pointer
-- >   type
--
adjustTypes ::
     C.TranslationUnit l MangleNames
  -> C.TranslationUnit l AdjustTypes
adjustTypes unit =
      let
        decls' = map processDecl unit.decls
        unit' =  C.TranslationUnit {
              decls        = decls'
            , includeGraph = unit.includeGraph
            , meta         = unit.meta
            }
      in
        unit'

{-------------------------------------------------------------------------------
  Decls
-------------------------------------------------------------------------------}

processDecl :: C.Decl l MangleNames -> C.Decl l AdjustTypes
processDecl decl =
    C.Decl {
        info = coercePass decl.info
      , ann  = decl.ann
      , kind = processDeclKind decl.kind
      }

processDeclKind :: C.DeclKind l MangleNames -> C.DeclKind l AdjustTypes
processDeclKind kind =
    case kind of
      C.DeclStruct struct                  -> C.DeclStruct               $ processStruct struct
      C.DeclUnion union                    -> C.DeclUnion                $ processUnion union
      C.DeclTypedef typedef                -> C.DeclTypedef              $ processTypedef typedef
      C.DeclEnum enum                      -> C.DeclEnum                 $ processEnum enum
      C.DeclUntaggedEnumConstant enumConst -> C.DeclUntaggedEnumConstant $ processUntaggedEnumConstant enumConst
      C.DeclOpaque mSize                   -> C.DeclOpaque mSize
      C.DeclMacro macro                    -> C.DeclMacro                $ processMacro macro
      C.DeclFunction function              -> C.DeclFunction             $ processFunction function
      C.DeclGlobal global                  -> C.DeclGlobal               $ processGlobal global

processStruct :: C.Struct MangleNames -> C.Struct AdjustTypes
processStruct struct =
    C.Struct {
        fields    = map processField struct.fields
      , flam      = C.mapFlamField processRegularField struct.flam
      , sizeof    = struct.sizeof
      , alignment = struct.alignment
      , ann       = struct.ann
      }

processUnion :: C.Union MangleNames -> C.Union AdjustTypes
processUnion union =
    C.Union {
        sizeof    = union.sizeof
      , alignment = union.alignment
      , fields    = map processField union.fields
      , ann       = union.ann
      }

processField :: C.Field MangleNames -> C.Field AdjustTypes
processField = \case
    C.FieldRegular field -> C.FieldRegular $ processRegularField field
    C.FieldImplicit field -> C.FieldImplicit $ processImplicitField field

processRegularField :: C.RegularField MangleNames -> C.RegularField AdjustTypes
processRegularField field =
    C.RegularField {
        info   = coercePass field.info
      , typ    = processType field.typ
      , offset = field.offset
      , width  = field.width
      , ann    = field.ann
      }

processImplicitField :: C.ImplicitField MangleNames -> C.ImplicitField AdjustTypes
processImplicitField field =
    C.ImplicitField {
        info   = coercePass field.info
      , typRef = processAnonRef field.typRef
      , offset = field.offset
      , indirect = fmap processIndirectField field.indirect
      , ann    = field.ann
      }

processAnonRef :: C.AnonRef MangleNames -> C.AnonRef AdjustTypes
processAnonRef = \case
    C.AnonRef ref -> C.AnonRef ref
    C.AnonExtBinding ext -> C.AnonExtBinding $ processExtBindingRef ext

processIndirectField :: C.IndirectField MangleNames -> C.IndirectField AdjustTypes
processIndirectField field =
    C.IndirectField {
        info   = coercePass field.info
      , typ    = processType field.typ
      , offset = field.offset
      , width  = field.width
      , path   = fmap processAnonRef field.path
      , ann    = coercePassAnn (Proxy @'("IndirectField", MangleNames, AdjustTypes)) field.ann
      }

processTypedef :: C.Typedef MangleNames -> C.Typedef AdjustTypes
processTypedef typedef =
    C.Typedef {
        typ = processType typedef.typ
      , ann = typedef.ann
      }

processEnum :: C.Enum MangleNames -> C.Enum AdjustTypes
processEnum enum =
    C.Enum {
        typ       = processType enum.typ
      , sizeof    = enum.sizeof
      , alignment = enum.alignment
      , constants = map processEnumConstant enum.constants
      , ann       = enum.ann
      }

processEnumConstant :: C.EnumConstant MangleNames -> C.EnumConstant AdjustTypes
processEnumConstant enumConstant =
    C.EnumConstant {
        info  = coercePass enumConstant.info
      , value = enumConstant.value
      }

processUntaggedEnumConstant ::
     C.UntaggedEnumConstant MangleNames
  -> C.UntaggedEnumConstant AdjustTypes
processUntaggedEnumConstant enumConst =
    C.UntaggedEnumConstant {
        typ = enumConst.typ
      , constant = processEnumConstant enumConst.constant
      }

processMacro :: MacroBody MangleNames l -> MacroBody AdjustTypes l
processMacro macro =
    case macro of
      MacroType  typ -> MacroType  $ processMacroType typ
      MacroValue val -> MacroValue $ processMacroExpr val

-- NOTE: currently type-like macro expressions don't support array or function
-- constructors. If they do in the future, then we might have to recurse into
-- the macro expression.
processMacroType :: TypecheckedMacroType l MangleNames -> TypecheckedMacroType l AdjustTypes
processMacroType macroType = coercePass macroType

-- NOTE: currently value-like macro expressions don't support array or function
-- constructors. If they do in the future, then we might have to recurse into
-- the macro expression.
processMacroExpr :: TypecheckedMacroValue l MangleNames -> TypecheckedMacroValue l AdjustTypes
processMacroExpr macroExpr = coercePass macroExpr

processFunction :: C.Function MangleNames -> C.Function AdjustTypes
processFunction function =
    C.Function{
        args  = map processFunctionArg function.args
      , res   = processType function.res
      , attrs = function.attrs
      , ann   = function.ann
      }

processFunctionArg :: C.FunctionArg MangleNames -> C.FunctionArg AdjustTypes
processFunctionArg functionArg =
    C.FunctionArg{
        name = functionArg.name
      , typ  = typ'
      , ann  = ann'
      }
  where
    (typ', ann') = adjustFunArg $ processType functionArg.typ

processGlobal :: C.Global MangleNames -> C.Global AdjustTypes
processGlobal global = C.Global{
      typ = processType global.typ
    , ann = global.ann
    }

{-------------------------------------------------------------------------------
  Types
-------------------------------------------------------------------------------}

processType ::
     C.Type MangleNames
  -> C.Type AdjustTypes
processType = \case
    C.TypePrim primTy ->
      C.TypePrim primTy
    C.TypeComplex primTy ->
      C.TypeComplex primTy
    C.TypeRef name ->
      C.TypeRef name
    C.TypeEnum ref ->
      C.TypeEnum $ C.Ref {
          name       = ref.name
        , underlying = processType ref.underlying
        }
    C.TypeMacro ref ->
      C.TypeMacro $ C.MacroRef {
          name       = ref.name
        , underlying = processType ref.underlying
        }
    C.TypeTypedef ref ->
      C.TypeTypedef $ C.Ref {
          name       = ref.name
        , underlying = processType ref.underlying
        }
    C.TypePointers n ty ->
      C.TypePointers n      $ processType ty
    C.TypeConstArray n ty ->
      C.TypeConstArray n    $ processType ty
    C.TypeIncompleteArray ty ->
      C.TypeIncompleteArray $ processType ty
    C.TypeFun args res ->
      C.TypeFun (map processTypeFunArg args) (processType res)
    C.TypeVoid ->
      C.TypeVoid
    C.TypeBlock ty ->
      C.TypeBlock $ processType ty
    C.TypeQual qual ty ->
      C.TypeQual qual $ processType ty
    C.TypeExtBinding ref ->
      C.TypeExtBinding $ processExtBindingRef ref

processExtBindingRef :: C.ExtBindingRef MangleNames -> C.ExtBindingRef AdjustTypes
processExtBindingRef ref = C.Ref {
          name       = ref.name
        , underlying = processType ref.underlying
        }

processTypeFunArg :: C.TypeFunArg MangleNames -> C.TypeFunArg AdjustTypes
processTypeFunArg arg =
    C.TypeFunArgF {
        typ = typ'
      , ann = ann'
      }
  where
    (typ', ann') = adjustFunArg $ processType arg.typ

-- | This is where the actual adjustments take place.
--
-- Function arguments of function type are changed to *pointer* to function
-- type. For example, the former is adjusted to the latter:
--
-- > int foo (char f (float))
-- > int foo (char (*f)(float))
--
-- Function arguments of array type are changed to pointer to the array element
-- type. For example, the former is adjusted to the latter:
--
-- > int foo (char xs[])
-- > int foo (char * xs)
--
-- The original type before adjustment is recorded in an an 'HsBindgen.Frontend.Pass.Ann'otation.
--
adjustFunArg :: C.Type AdjustTypes -> (C.Type AdjustTypes, AdjustedFrom AdjustTypes)
adjustFunArg ty
  | Just cls <- classifyCanonicalTypeArray ty
  , let elemTy = getArrayElementType cls
  , let constQual
          | C.isErasedTypeConstQualified ty
          , not (C.isErasedTypeConstQualified elemTy)
          = C.TypeQual C.QualConst
          | otherwise
          = id
  = (C.TypePointers 1 $ constQual elemTy, AdjustedFromArray ty)
  | C.isCanonicalTypeFunction ty
  = (C.TypePointers 1 ty, AdjustedFromFunction ty)
  | otherwise
  = (ty, NotAdjusted)

-- | An array of known size or unknown size
data ArrayClassification p =
    -- | Array of known size
    ConstantArrayClassification
      Natural     -- ^ Array size
      (C.Type p)  -- ^ Array element type

    -- | Array of unkown size
  | IncompleteArrayClassification
      (C.Type p)  -- ^ Array element type

getArrayElementType :: ArrayClassification p -> C.Type p
getArrayElementType (ConstantArrayClassification _ ty) = ty
getArrayElementType (IncompleteArrayClassification ty) = ty

-- | Is the canonical type an array type?
--
-- If so, is it an array of known size or unknown size? And what is the /full
-- type/ of the array elements?
classifyCanonicalTypeArray ::
     C.Type AdjustTypes
  -> Maybe (ArrayClassification AdjustTypes)
classifyCanonicalTypeArray ty =
    -- We do not use getCanonicalType here, because we do not want to
    -- canonicalize the array /element/ type.
    case ty of
      C.TypePrim _pt          -> Nothing
      C.TypeRef _declId       -> Nothing
      C.TypeEnum _ref         -> Nothing
      C.TypeMacro ref         -> classifyCanonicalTypeArray ref.underlying
      C.TypeTypedef ref       -> classifyCanonicalTypeArray ref.underlying
      C.TypePointers _n _t    -> Nothing
      C.TypeConstArray n t    -> Just (ConstantArrayClassification n t)
      C.TypeFun _args _res    -> Nothing
      C.TypeVoid              -> Nothing
      C.TypeIncompleteArray t -> Just (IncompleteArrayClassification t)
      C.TypeBlock _t          -> Nothing
      C.TypeQual _q t         -> classifyCanonicalTypeArray t
      C.TypeExtBinding ref    -> classifyCanonicalTypeArray ref.underlying
      C.TypeComplex _pt       -> Nothing