{- Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. -} module Thrift.Compiler.GenStruct ( genStructDecl , genStructImports -- * Helpers , fixToJSONValue , ParseMode(..) , genFieldParser , genBuildValue, genBuildFields, genFieldBase, genParseType , getUn , transformValue, mkHashable, mkOrd ) where import Prelude hiding (exp) import Data.Maybe import Data.Set (union) import Data.Text (Text) import Language.Haskell.Exts.Syntax hiding (Name, Type, Annotation, Decl) import Language.Haskell.Exts.Pretty (prettyPrint) import qualified Data.Set as Set import qualified Data.Text as Text import qualified Language.Haskell.Exts.Syntax as HS import Thrift.Compiler.GenUtils import Thrift.Compiler.Plugins.Haskell import Thrift.Compiler.Types -- Generate Datatype ----------------------------------------------------------- genStructImports :: HS Struct -> Set.Set Import genStructImports Struct{..} = foldr (Set.union . getImports) baseImports structMembers where getImports :: HS (Field u) -> Set.Set Import getImports Field{..} = typeToImport fieldResolvedType baseImports = Set.fromList [ QImport "Prelude" "Prelude" , QImport "Control.DeepSeq" "DeepSeq" , QImport "Control.Monad" "Monad" , QImport "Control.Monad.ST.Trans" "ST" , QImport "Control.Monad.Trans.Class" "Trans" , QImport "Data.Aeson" "Aeson" , QImport "Data.Aeson.Types" "Aeson" , QImport "Data.Default" "Default" , QImport "Data.HashMap.Strict" "HashMap" , QImport "Data.Hashable" "Hashable" , QImport "Data.List" "List" , QImport "Data.Ord" "Ord" , QImport "Thrift.Binary.Parser" "Parser" , SymImport "Prelude" [ ".", "<$>", "<*>", ">>=", "==", "/=", "<", "++" ] , SymImport "Control.Applicative" [ "<|>", "*>", "<*" ] , SymImport "Data.Aeson" [ ".:", ".:?", ".=", ".!=" ] , SymImport "Data.Monoid" [ "<>" ] ] `union` (case structType of StructTy -> Set.empty ExceptionTy -> Set.singleton $ QImport "Control.Exception" "Exception") genStructDecl :: Bool -> HS Struct -> [HS.Decl ()] genStructDecl extraHasFields struct@Struct{..} = -- Struct Declaration [ DataDecl () (dataOrNew ()) Nothing (DHead () $ textToName structResolvedName) -- Record Constructor [QualConDecl () Nothing Nothing (RecDecl () (textToName structResolvedName) (map genFieldDecl structMembers)) ] -- Deriving (pure $ deriving_ $ map (IRule () Nothing Nothing . IHCon ()) $ [ qualSym "Prelude" "Eq" , qualSym "Prelude" "Show" ] ++ [ qualSym "Prelude" "Ord" | deriveOrd ]) -- Aeson Instances , genToJSONInst struct -- ThriftStruct Instance , genThriftStruct struct -- Other Instances , genNFData struct , genDefault struct , genHashable struct ] ++ [ genOrd struct | not deriveOrd ] ++ (case structType of StructTy -> [] ExceptionTy -> [ genException struct ]) ++ (if extraHasFields then genExtraHasFields struct else [] ) where dataOrNew = case structMembers of -- Make the struct a newtype iff it has exactly one element [_] -> NewType _ -> DataType deriveOrd = canDeriveOrd struct genFieldType :: HS (Field u) -> HS.Type () genFieldType Field{..} = opTy where baseTy = genType fieldResolvedType opTy = case fieldRequiredness of Optional{} -> TyApp () (qualType "Prelude" "Maybe") baseTy _ -> baseTy genFieldDecl :: HS (Field u) -> FieldDecl () genFieldDecl field@Field{..} = FieldDecl () [textToName fieldResolvedName] ty where baseTy = genFieldType field ty = case fieldLaziness of Lazy -> baseTy Strict | isBaseType fieldResolvedType -> TyBang () (BangedTy ()) (Unpack ()) baseTy | otherwise -> TyBang () (BangedTy ()) (NoUnpack ()) baseTy -- Generate Aeson Instances ---------------------------------------------------- genToJSONInst :: HS Struct -> HS.Decl () genToJSONInst struct@Struct{..} = InstDecl () Nothing (IRule () Nothing Nothing $ IHApp () (IHCon () $ qualSym "Aeson" "ToJSON") (TyCon () $ unqualSym structResolvedName)) (Just $ map (InsDecl ()) [ genToJSON "toJSON" ":" (qvar "Aeson" "object") struct ]) genToJSON :: Text -> Text -> Exp () -> HS Struct -> HS.Decl () genToJSON name op combine Struct{..} = FunBind () [ Match () (textToName name) [ PApp () (unqualSym structResolvedName) $ map (\Field{..} -> pvar ("__field__" <> fieldName)) structMembers ] (UnGuardedRhs () $ combine `app` foldr (genToJSONField op) (qvar "Prelude" "mempty") structMembers) Nothing ] genToJSONField :: Text -> HS (Field u) -> Exp () -> Exp () genToJSONField op Field{..} exps = case fieldRequiredness of Optional{} -> qvar "Prelude" "maybe" `app` qvar "Prelude" "id" `app` (Var () (UnQual () (Symbol () (Text.unpack op))) `compose` LeftSection () (stringLit fieldName) (QVarOp () (UnQual () (Symbol () ".=")))) `app` fieldValue `app` exps _ -> infixApp op (infixApp ".=" (stringLit fieldName) fieldValue) exps where fieldValue = case (fieldRequiredness, fixToJSONValue fieldResolvedType) of (_, Nothing) -> val (Optional{}, Just f) -> qvar "Prelude" "fmap" `app` f `app` val (_ , Just f) -> f `app` val val = var $ "__field__" <> fieldName fixToJSONValue :: HSType t -> Maybe (Exp ()) fixToJSONValue I8 = Nothing fixToJSONValue I16 = Nothing fixToJSONValue I32 = Nothing fixToJSONValue I64 = Nothing fixToJSONValue (TSpecial HsInt) = Nothing fixToJSONValue TFloat = Nothing fixToJSONValue TDouble = Nothing fixToJSONValue TBool = Nothing fixToJSONValue (TSpecial HsString) = Nothing fixToJSONValue (TSpecial HsByteString) = Just $ qvar "Text" "decodeUtf8" fixToJSONValue TText = Nothing fixToJSONValue TBytes = Just $ qvar "Thrift" "encodeBase64Text" fixToJSONValue (TList u) = app (qvar "Prelude" "map") <$> fixToJSONValue u fixToJSONValue (TSpecial (HsVector vec u)) = app (qvar (hsVectorQual vec) "map") <$> fixToJSONValue u fixToJSONValue (TSet u) = app (qvar "Set" "map") <$> fixToJSONValue u fixToJSONValue (THashSet u) = app (qvar "HashSet" "map") <$> fixToJSONValue u fixToJSONValue (TMap k v) = fixToJSONMap (qvar "Map" "mapKeys") (qvar "Map" "map") k v fixToJSONValue (THashMap k v) = fixToJSONMap (qvar "Thrift" "hmMapKeys") (qvar "HashMap" "map") k v fixToJSONValue TEnum{} = Nothing fixToJSONValue TStruct{} = Nothing fixToJSONValue TException{} = Nothing fixToJSONValue TUnion{} = Nothing fixToJSONValue (TTypedef _ ty _loc) = fixToJSONValue ty fixToJSONValue (TNewtype name ty _loc) = Just $ maybe id compose (fixToJSONValue ty) $ getUn name fixToJSONMap :: Exp () -> Exp () -> HSType k -> HSType v -> Maybe (Exp ()) fixToJSONMap mapK mapV k v | t:ts <- catMaybes [ keyToStr, fixK, fixV ] = Just $ foldl compose t ts | otherwise = Nothing where keyToStr | isAesonKey k = Nothing | otherwise = Just $ mapK `app` qvar "Thrift" "keyToStr" fixK = app mapK <$> fixToJSONValue k fixV = app mapV <$> fixToJSONValue v getUn :: Name -> Exp () getUn Name{..} = case resolvedName of UName name -> var $ "un" <> name QName m name -> qvar m $ "un" <> name isAesonKey :: HSType t -> Bool isAesonKey TText = True isAesonKey (TSpecial HsString) = True -- ByteString isn't actually an Aeson key, but it will be transformed to Text by -- fixToJSONValue, so we don't want to encode it twice isAesonKey TBytes = True isAesonKey (TSpecial HsByteString) = True isAesonKey _ = False -- Generate ThriftStruct Instance ---------------------------------------------- genThriftStruct :: HS Struct -> HS.Decl () genThriftStruct struct@Struct{..} = InstDecl () Nothing (IRule () Nothing Nothing $ IHApp () (IHCon () $ qualSym "Thrift" "ThriftStruct") (TyCon () $ unqualSym structResolvedName)) (Just $ map (InsDecl ()) [ genBuilder struct , genParser struct ]) data ParseMode = P_FieldMode | P_ListMode insertParens :: ParseMode -> Exp () -> Exp () insertParens P_FieldMode = id insertParens P_ListMode = Paren () genBuilder :: HS Struct -> HS.Decl () genBuilder Struct{..} = FunBind () [ Match () (textToName "buildStruct") [ PVar () $ textToName "_proxy" , PApp () (unqualSym structResolvedName) $ map (PVar () . textToName . ("__field__" <>) . fieldName) structMembers ] (UnGuardedRhs () $ genBuildFields structMembers) Nothing ] genBuildFields :: [HS (Field u)] -> Exp () genBuildFields fields = protocolFun "genStruct" `app` (case genBuildField NoField fields of NoField -> id ReqField _ f -> f OptField _ f -> f) (HS.List () []) data PreviousField = NoField -- Both ReqField and OptField contain continuations for building the output -- of the function | ReqField FieldId (Exp () -> Exp ()) | OptField Text (Exp () -> Exp ()) genBuildField :: PreviousField -> [HS (Field u)] -> PreviousField genBuildField prev [] = prev genBuildField prev (Field{..} : fs) = flip genBuildField fs $ case fieldRequiredness of Optional{} | null fs -> ReqField fieldId $ \e -> combine $ Case () (var ("__field__" <> fieldName)) [ Alt () (PApp () (qualSym "Prelude" "Just") [ pvar "_val" ]) (UnGuardedRhs () $ (infixApp ":" (genField $ var "_val") e)) Nothing , Alt () (PApp () (qualSym "Prelude" "Nothing") []) (UnGuardedRhs () $ e) Nothing ] Optional{} -> OptField fieldName $ \e -> combine $ Let () (BDecls () [ PatBind () (PTuple () Boxed [ pvar ("__cereal__" <> fieldName) , pvar ("__id__" <> fieldName) ]) (UnGuardedRhs () $ Case () (var ("__field__" <> fieldName)) [ Alt () (PApp () (qualSym "Prelude" "Just") [ pvar "_val" ]) (UnGuardedRhs () $ Tuple () Boxed [ Con () (UnQual () (Symbol () ":")) `app` genField (var "_val") , intLit fieldId ]) Nothing , Alt () (PApp () (qualSym "Prelude" "Nothing") []) (UnGuardedRhs () $ Tuple () Boxed [ qvar "Prelude" "id" , lastId ]) Nothing ]) Nothing ]) $ (var $ "__cereal__" <> fieldName) `app` e _ -> ReqField fieldId $ combine . infixApp ":" (genField $ var $ "__field__" <> fieldName) where genField = genFieldBase fieldResolvedType fieldName fieldId lastId (lastId, combine) = case prev of NoField -> (intLit (0 :: FieldId), id) ReqField fid f -> (intLit fid, f) OptField name f -> (var $ "__id__" <> name, f) genFieldBase :: HSType t -> Text -> FieldId -> Exp () -> Exp () -> Exp () genFieldBase ty name fid lastId arg = case getPrim ty of Nothing -> protocolFun "genField" `app` stringLit name `app` genThriftType ty `app` intLit fid `app` lastId `app` (genBuildValue ty `app` arg) Just P_Bool -> protocolFun "genFieldBool" `app` stringLit name `app` intLit fid `app` lastId `app` arg Just primTy -> protocolFun "genFieldPrim" `app` stringLit name `app` genThriftType ty `app` intLit fid `app` lastId `app` genBuildPrim primTy `app` arg genBuildValue :: HSType t -> Exp () -- Primatives genBuildValue I8 = protocolFun "genByte" genBuildValue I16 = protocolFun "genI16" genBuildValue I32 = protocolFun "genI32" genBuildValue I64 = protocolFun "genI64" genBuildValue (TSpecial HsInt) = protocolFun "genI64" `compose` qvar "Prelude" "fromIntegral" genBuildValue TFloat = protocolFun "genFloat" genBuildValue TDouble = protocolFun "genDouble" genBuildValue TBool = protocolFun "genBool" genBuildValue TText = protocolFun "genText" genBuildValue (TSpecial HsString) = genBuildValue TText `compose` qvar "Text" "pack" genBuildValue (TSpecial HsByteString) = protocolFun "genByteString" genBuildValue TBytes = protocolFun "genBytes" -- Containers genBuildValue (TList ty) = genBuildList ty genBuildValue (TSpecial (HsVector vec ty)) = genBuildList ty `compose` qvar (hsVectorQual vec) "toList" genBuildValue (TSet ty) = genBuildList ty `compose` qvar "Set" "toList" genBuildValue (THashSet ty) = genBuildList ty `compose` qvar "HashSet" "toList" genBuildValue (TMap k v) = genBuildMap k v `compose` qvar "Map" "toList" genBuildValue (THashMap k v) = genBuildMap k v `compose` qvar "HashMap" "toList" -- Named Types genBuildValue (TTypedef _ ty _loc) = genBuildValue ty genBuildValue (TNewtype name ty _loc) = genBuildValue ty `compose` getUn name genBuildValue (TStruct _ _loc) = protocolFun "buildStruct" genBuildValue (TException _ _loc) = protocolFun "buildStruct" genBuildValue (TUnion _ _loc) = protocolFun "buildStruct" genBuildValue (TEnum _ _loc _) = protocolFun "genI32" `compose` qvar "Prelude" "fromIntegral" `compose` qvar "Thrift" "fromThriftEnum" genBuildPrim :: PrimType -> Exp () genBuildPrim P_I8 = protocolFun "genBytePrim" genBuildPrim P_I16 = protocolFun "genI16Prim" genBuildPrim P_I32 = protocolFun "genI32Prim" genBuildPrim P_I64 = protocolFun "genI64Prim" genBuildPrim P_Bool = protocolFun "genBoolPrim" genBuildList :: HSType t -> Exp () genBuildList ty = case getPrim ty of Just primTy -> protocolFun "genListPrim" `app` genThriftType ty `app` genBuildPrim primTy Nothing -> protocolFun "genList" `app` genThriftType ty `app` genBuildValue ty genBuildMap :: HSType k -> HSType v -> Exp () genBuildMap k v = case (getPrim k, getPrim v) of (Just primK, Just primV) -> protocolFun "genMapPrim" `app` genThriftType k `app` genThriftType v `app` qcon "Prelude" (if isStringType k then "True" else "False") `app` genBuildPrim primK `app` genBuildPrim primV _ -> protocolFun "genMap" `app` genThriftType k `app` genThriftType v `app` qcon "Prelude" (if isStringType k then "True" else "False") `app` genBuildValue k `app` genBuildValue v isStringType :: HSType t -> Bool isStringType (TSpecial HsString) = True isStringType (TSpecial HsByteString) = True isStringType TText = True isStringType TBytes = True isStringType (TTypedef _ t _loc) = isStringType t isStringType (TNewtype _ t _loc) = isStringType t isStringType _ = False genFieldParser :: [HS (Field u)] -> Text -> (Exp () -> Exp ()) -> Rhs () genFieldParser fields constructorName constructorWrapper = UnGuardedRhs () $ qvar "ST" "runSTT" `app` Do () -- We need this because of some issue in runST (t10447741) ( Qualifier () (qvar "Prelude" "return" `app` unit_con ()) : -- field <- newSTRef Nothing map (\field@Field{..} -> Generator () (pvar ("__field__" <> fieldName)) (qvar "ST" "newSTRef" `app` case fieldRequiredness of Default -> genFieldDefault field _ -> qcon "Prelude" "Nothing")) fields ++ -- let __parse = do {..} [ LetStmt () $ BDecls () [ FunBind () -- the __parse function recursively builds the output and stores the -- parsed values in STRefs that are initialized in the outer scope [ Match () (textToName "_parse") [ pvar "_lastId" ] (UnGuardedRhs () $ Do () -- Field Case: pattern match on parsed field id [ Generator () (pvar "_fieldBegin") $ qvar "Trans" "lift" `app` (protocolFun "parseFieldBegin" `app` var "_lastId" `app` var "_idMap") , Qualifier () $ Case () (var "_fieldBegin") [ Alt () (PApp () (qualSym "Thrift" "FieldBegin") [ pvar "_type", pvar "_id", pvar "_bool" ]) (UnGuardedRhs () $ Do () [ Qualifier () $ Case () (var "_id") $ map genParseValue fields ++ [ Alt () (PWildCard ()) (UnGuardedRhs () $ qvar "Trans" "lift" `app` (protocolFun "parseSkip" `app` var "_type" `app` (qcon "Prelude" "Just" `app` var "_bool"))) Nothing ] , Qualifier () $ var "_parse" `app` var "_id" ]) Nothing , Alt () (PApp () (qualSym "Thrift" "FieldEnd") []) (UnGuardedRhs () $ Do () $ -- Get the values from the STRefs map (\Field{..} -> Generator () (PBangPat () $ case fieldRequiredness of Required{} -> pvar $ "__maybe__" <> fieldName _ -> pvar $ "__val__" <> fieldName) $ qvar "ST" "readSTRef" `app` var ("__field__" <> fieldName)) fields ++ [ Qualifier () $ foldr matchArg buildOutput fields ]) Nothing ] ]) Nothing ] , PatBind () (pvar "_idMap") (UnGuardedRhs () $ qvar "HashMap" "fromList" `app` HS.List () (map (\Field{..} -> Tuple () Boxed [ stringLit fieldName, intLit fieldId ]) fields)) Nothing ] -- call _parse , Qualifier () $ var "_parse" `app` intLit (0 :: Int) ]) where buildOutput = qvar "Prelude" "pure" `app` constructorWrapper (foldl app (con constructorName) (map mkFieldName fields)) mkFieldName Field{..} = var $ "__val__" <> fieldName matchArg :: HS (Field u) -> Exp () -> Exp () matchArg Field{..} exp = case fieldRequiredness of Required{} -> Case () (var $ "__maybe__" <> fieldName) [ Alt () (PApp () (qualSym "Prelude" "Nothing") []) (UnGuardedRhs () $ qvar "Prelude" "fail" `app` stringLit ("Error parsing type " <> constructorName <> ": missing required field " <> fieldName <> " of type " <> Text.pack ( prettyPrint $ genType fieldResolvedType) )) Nothing , Alt () (PApp () (qualSym "Prelude" "Just") [ pvar ("__val__" <> fieldName) ]) (UnGuardedRhs () exp) Nothing ] _ -> exp genParser :: HS Struct -> HS.Decl () genParser Struct{..} = FunBind () [ Match () (textToName "parseStruct") [ pvar "_proxy" ] (genFieldParser structMembers structResolvedName (Paren ())) Nothing ] genParseValue :: HS (Field u) -> Alt () genParseValue Field{..} = Alt () (PLit () (if fieldId < 0 then Negative () else Signless ()) (Int () (abs $ fromIntegral fieldId) (show fieldId))) (GuardedRhss () -- check that the parsed type is correct [ GuardedRhs () [ Qualifier () $ infixApp "==" (var "_type") (genThriftType fieldResolvedType) ] $ Do () [ Generator () (PBangPat () $ pvar "_val") $ qvar "Trans" "lift" `app` genParseType P_FieldMode fieldResolvedType , Qualifier () $ qvar "ST" "writeSTRef" `app` var ("__field__" <> fieldName) `app` case fieldRequiredness of Default -> var "_val" _ -> qcon "Prelude" "Just" `app` var "_val" ] ]) Nothing genParseType :: ParseMode -> HSType t -> Exp () -- Base types genParseType _ I8 = protocolFun "parseByte" genParseType _ I16 = protocolFun "parseI16" genParseType _ I32 = protocolFun "parseI32" genParseType _ I64 = protocolFun "parseI64" genParseType m (TSpecial HsInt) = insertParens m $ infixApp "<$>" (qvar "Prelude" "fromIntegral") (protocolFun "parseI64") genParseType _ TFloat = protocolFun "parseFloat" genParseType _ TDouble = protocolFun "parseDouble" genParseType P_FieldMode TBool = protocolFun "parseBoolF" `app` var "_bool" genParseType P_ListMode TBool = protocolFun "parseBool" genParseType _ TText = protocolFun "parseText" genParseType m (TSpecial HsString) = insertParens m $ infixApp "<$>" (qvar "Text" "unpack") (protocolFun "parseText") genParseType _ (TSpecial HsByteString) = protocolFun "parseByteString" genParseType _ TBytes = protocolFun "parseBytes" -- Containers genParseType _ (TList ty) = infixApp "<$>" (qvar "Prelude" "snd") (genParseList ty) genParseType m (TSpecial (HsVector vec ty)) = insertParens m $ infixApp "<$>" (qvar "Prelude" "uncurry" `app` qvar (hsVectorQual vec) "fromListN") (genParseList ty) genParseType m (TSet ty) = insertParens m $ infixApp "<$>" (qvar "Set" "fromList" `compose` qvar "Prelude" "snd") (genParseList ty) genParseType m (THashSet ty) = insertParens m $ infixApp "<$>" (qvar "HashSet" "fromList" `compose` qvar "Prelude" "snd") (genParseList ty) genParseType m (TMap k v) = insertParens m $ infixApp "<$>" (qvar "Map" "fromList") (genParseMap k v) genParseType m (THashMap k v) = insertParens m $ infixApp "<$>" (qvar "HashMap" "fromList") (genParseMap k v) -- Named Types genParseType m (TTypedef _ ty _loc) = genParseType m ty genParseType m (TNewtype name ty _loc) = qvar "Prelude" "fmap" `app` Con () (nameToQName name) `app` genParseType m ty genParseType _ TStruct{} = protocolFun "parseStruct" genParseType _ TException{} = protocolFun "parseStruct" genParseType _ TUnion{} = protocolFun "parseStruct" genParseType _ (TEnum Name{..} _loc nounknown) = qvar "Thrift" (parseEnumFun nounknown) `app` var "_proxy" `app` stringLit (localName resolvedName) where parseEnumFun :: Bool -> Text parseEnumFun True = "parseEnumNoUnknown" parseEnumFun False = "parseEnum" genParseList :: HSType t -> Exp () genParseList ty = protocolFun "parseList" `app` genParseType P_ListMode ty genParseMap :: HSType k -> HSType v -> Exp () genParseMap k v = protocolFun "parseMap" `app` genParseType P_ListMode k `app` genParseType P_ListMode v `app` qcon "Prelude" (if isStringType k then "True" else "False") -- Generate NFData Instance ---------------------------------------------------- genNFData :: HS Struct -> HS.Decl () genNFData Struct{..} = InstDecl () Nothing (IRule () Nothing Nothing $ IHApp () (IHCon () $ qualSym "DeepSeq" "NFData") (TyCon () $ unqualSym structResolvedName)) (Just $ map (InsDecl ()) [ FunBind () [ Match () (textToName "rnf") [ PApp () (unqualSym structResolvedName) $ map mkPattern structMembers ] (UnGuardedRhs () $ foldr seqify (Con () (Special () (UnitCon ()))) structMembers) Nothing ] ]) where seqify field = InfixApp () (forcedField field) infixSeq infixSeq = QVarOp () $ qualSym "Prelude" "seq" forcedField Field{..} = qvar "DeepSeq" "rnf" `app` var ("__field__" <> fieldName) mkPattern :: HS (Field u) -> Pat () mkPattern Field{..} = pvar $ "__field__" <> fieldName -- Generate Default Instance --------------------------------------------------- genDefault :: HS Struct -> HS.Decl () genDefault Struct{..} = InstDecl () Nothing (IRule () Nothing Nothing $ IHApp () (IHCon () $ qualSym "Default" "Default") (TyCon () $ unqualSym structResolvedName)) (Just $ map (InsDecl ()) [ FunBind () [ Match () (textToName "def") [] (UnGuardedRhs () $ foldl app (con structResolvedName) $ map genFieldDefault structMembers) Nothing ] ]) genFieldDefault :: HS (Field u) -> Exp () genFieldDefault Field{..} = case fieldRequiredness of Optional{} -> qcon "Prelude" "Nothing" _ -> case fieldResolvedVal of Just val -> genConst fieldResolvedType val Nothing -> typeToDefault fieldResolvedType -- Generate Hashable Instance -------------------------------------------------- genHashable :: HS Struct -> HS.Decl () genHashable Struct{..} = InstDecl () Nothing (IRule () Nothing Nothing $ IHApp () (IHCon () (qualSym "Hashable" "Hashable")) $ simpleType structResolvedName) (Just [ InsDecl () $ FunBind () [ Match () (textToName "hashWithSalt") [ pvar "__salt" , PApp () (unqualSym structResolvedName) $ map (\Field{..} -> pvar $ "_" <> fieldName) structMembers ] (UnGuardedRhs () $ foldl (\salt Field{..} -> qvar "Hashable" "hashWithSalt" `app` salt `app` transformValue mkHashable fieldRequiredness fieldResolvedType (var $ "_" <> fieldName)) (var "__salt") structMembers) Nothing ] ]) -- Some thrift types aren't hashable, so we need to modify them before passing -- them to hashWithSalt mkHashable :: HSType t -> Maybe (Exp ()) mkHashable = \case I8 -> Nothing I16 -> Nothing I32 -> Nothing I64 -> Nothing (TSpecial HsInt) -> Nothing TFloat -> Nothing TDouble -> Nothing TText -> Nothing TBool -> Nothing (TSpecial HsString) -> Nothing (TSpecial HsByteString) -> Nothing TBytes -> Nothing -- elems gives the set elements in ascending order, so we do not need to -- sort the result (TSet u) -> Just $ mapTransform mkHashable u $ qvar "Set" "elems" (THashSet u) -> Just $ qvar "List" "sort" `compose` mapTransform mkHashable u (qvar "HashSet" "toList") (TList u) -> (qvar "Prelude" "map" `app`) <$> mkHashable u (TSpecial (HsVector vec u)) -> Just $ mapTransform mkHashable u $ qvar (hsVectorQual vec) "toList" (TMap k v) -> Just $ mapTransformPair mkHashable k v $ qvar "Map" "toAscList" (THashMap k v) -> Just $ qvar "List" "sort" `compose` mapTransformPair mkHashable k v (qvar "HashMap" "toList") TStruct{} -> Nothing TException{} -> Nothing TUnion{} -> Nothing TEnum{} -> Nothing (TTypedef _ u _loc) -> mkHashable u TNewtype{} -> Nothing transformValue :: (HSType t -> Maybe (Exp ())) -> Requiredness u a -> HSType t -> Exp () -> Exp () transformValue transform req ty exp = case (req, transform ty) of (_, Nothing) -> exp (Optional{}, Just f) -> qvar "Prelude" "fmap" `app` f `app` exp (_, Just f) -> f `app` exp mapTransform :: (HSType t -> Maybe (Exp ())) -> HSType t -> Exp () -> Exp () mapTransform transform u = case transform u of Nothing -> id Just f -> compose $ qvar "Prelude" "map" `app` f mapTransformPair :: (forall t. HSType t -> Maybe (Exp ())) -> HSType k -> HSType v -> Exp () -> Exp () mapTransformPair transform k v = compose $ qvar "Prelude" "map" `app` Lambda () [ PTuple () Boxed [ pvar "_k", pvar "_v" ] ] (Tuple () Boxed [ applyT transform k "_k", applyT transform v "_v" ]) applyT :: (forall u. HSType u -> Maybe (Exp ())) -> HSType t -> Text -> Exp () applyT transform u v = case transform u of Nothing -> var v Just f -> f `app` var v canDerive :: (forall t. HSType t -> Maybe a) -> HS Struct -> Bool canDerive f Struct{..} = all (\Field{..} -> isNothing $ f fieldResolvedType) structMembers -- Generate Hashable Instance -------------------------------------------------- genOrd :: HS Struct -> HS.Decl () genOrd Struct{..} = InstDecl () Nothing (IRule () Nothing Nothing $ IHApp () (IHCon () (qualSym "Ord" "Ord")) $ simpleType structResolvedName) (Just [ InsDecl () $ FunBind () [ Match () (textToName "compare") [ pvar "__a" , pvar "__b" ] (UnGuardedRhs () $ case structMembers of [] -> qcon "Ord" "EQ" _:_ -> foldr1 (\scrutinee continue -> Case () scrutinee [ Alt () (PApp () (qualSym "Ord" c) []) (UnGuardedRhs () result) Nothing | (c, result) <- [ ("LT", qcon "Ord" "LT") , ("GT", qcon "Ord" "GT") , ("EQ", continue) ] ]) [ qvar "Ord" "compare" `app` f (var fieldResolvedName `app` var "__a") `app` f (var fieldResolvedName `app` var "__b") | Field{..} <- structMembers , let f = transformValue mkOrd fieldRequiredness fieldResolvedType ]) Nothing ] ]) mkOrd :: HSType t -> Maybe (Exp ()) mkOrd = \case I8 -> Nothing I16 -> Nothing I32 -> Nothing I64 -> Nothing (TSpecial HsInt) -> Nothing TFloat -> Nothing TDouble -> Nothing TText -> Nothing TBool -> Nothing (TSpecial HsString) -> Nothing TBytes -> Nothing (TSpecial HsByteString) -> Nothing (TSet u) -> (qvar "Set" "map" `app`) <$> mkOrd u (THashSet u) -> Just $ qvar "List" "sort" `compose` mapTransform mkOrd u (qvar "HashSet" "toList") (TList u) -> (qvar "Prelude" "map" `app`) <$> mkOrd u (TSpecial (HsVector vec u)) -> (qvar (hsVectorQual vec) "map" `app`) <$> mkOrd u (TMap k v) -> case (mkOrd k, mkOrd v) of (Nothing, Nothing) -> Nothing _ -> Just $ mapTransformPair mkOrd k v $ qvar "Map" "toAscList" (THashMap k v) -> Just $ qvar "List" "sort" `compose` mapTransformPair mkOrd k v (qvar "HashMap" "toList") TStruct{} -> Nothing TException{} -> Nothing TUnion{} -> Nothing TEnum{} -> Nothing (TTypedef _ u _loc) -> mkOrd u TNewtype{} -> Nothing canDeriveOrd :: HS Struct -> Bool canDeriveOrd = canDerive mkOrd -- Generate Exception Instance ------------------------------------------------- genException :: HS Struct -> HS.Decl () genException Struct{..} = InstDecl () Nothing (IRule () Nothing Nothing $ IHApp () (IHCon () $ qualSym "Exception" "Exception") (TyCon () $ unqualSym structResolvedName)) Nothing -- Generate Extra HasField Instances ------------------------------------------- genExtraHasFields :: HS Struct -> [HS.Decl ()] genExtraHasFields Struct{..} = genHasField structResolvedName <$> structMembers genHasField :: Text -> HS (Field u) -> HS.Decl () genHasField name field@Field{..} = InstDecl () Nothing (IRule () Nothing Nothing ihead) (Just [InsDecl () getFieldDecl]) where ihead = IHApp () (IHApp () (IHApp () (IHCon () $ qualSym "Thrift" "HasField") promotedName) (TyCon () $ unqualSym name) ) (TyParen () $ genFieldType field) promotedName = promoteText fieldName getFieldDecl = FunBind () . (:[]) $ Match () (textToName "getField") mempty (UnGuardedRhs () (var fieldResolvedName)) Nothing promoteText :: Text -> HS.Type () promoteText s = TyPromoted () $ PromotedString () fieldNameString fieldNameString where -- We need the thrift_ prefix in order to prevent naming conflicts between -- fields like struct.val and struct.struct_val fieldNameString = Text.unpack s