compiler/Thrift/Compiler/GenEnum.hs (335 lines of code) (raw):

-- Copyright (c) Facebook, Inc. and its affiliates. module Thrift.Compiler.GenEnum ( genEnumImports , genEnumDecl ) where import Prelude hiding (Enum) import Control.Monad import Data.List import Data.Text (Text) import Language.Haskell.Exts.Syntax hiding (Type, Decl) import qualified Data.Set as Set import qualified Language.Haskell.Exts.Syntax as HS import TextShow import Thrift.Compiler.GenConst import Thrift.Compiler.GenTypedef import Thrift.Compiler.GenUtils import Thrift.Compiler.Plugins.Haskell import Thrift.Compiler.Types -- Data Type Declaration ------------------------------------------------------- genEnumImports :: Set.Set Import genEnumImports = Set.fromList [ QImport "Prelude" "Prelude" , QImport "Control.Exception" "Exception" , QImport "Control.DeepSeq" "DeepSeq" , QImport "Data.Aeson" "Aeson" , QImport "Data.Default" "Default" , QImport "Data.Function" "Function" , QImport "Data.Hashable" "Hashable" , QImport "Data.Int" "Int" , SymImport "Prelude" [ ".", "++", ">", "==" ] ] genEnumDecl :: HS Enum -> [HS.Decl ()] genEnumDecl Enum{ enumIsPseudo=True,..} = genTypedefDecl typedef ++ concatMap genConstDecl consts where typedef = Typedef { tdName = enumName , tdResolvedName = enumResolvedName , tdTag = IsNewtype , tdType = AnnotatedType I32 Nothing (Arity0Loc nlc) , tdResolvedType = I32 , tdLoc = TypedefLoc nlc nlc , tdAnns = Nothing , tdSAnns = [] } consts = flip map enumConstants $ \EnumValue{..} -> Const { constName = evName , constResolvedName = evResolvedName , constType = AnnotatedType (TNamed enumName) Nothing (Arity0Loc nlc) , constResolvedType = TNewtype (mkName enumName enumResolvedName) I32 noLoc , constVal = UntypedConst nlc $ IntConst (fromIntegral evValue) (showt evValue) , constResolvedVal = Literal $ New $ fromIntegral evValue , constLoc = ConstLoc nlc nlc nlc NoSep , constSAnns = [] } genEnumDecl Enum{..} = -- Enum Declaration [ DataDecl () (DataType ()) Nothing (DHead () $ textToName enumResolvedName) -- We generate them in sorted order so that we can derive Bounded correctly ((genConstr <$> sortOn evValue enumConstants) ++ [genUnknownConstr | not enumNoUnknown]) -- Deriving (if null enumConstants then mzero else pure $ deriving_ $ map (IRule () Nothing Nothing . IHCon ()) $ [ qualSym "Prelude" "Eq" , qualSym "Prelude" "Show" ] ++ [ qualSym "Prelude" "Ord" | canDeriveOrd ]) ] ++ -- Instances if null enumConstants then map (genEmptyInstance enumResolvedName) -- Using the symbol (==) in the AST is technically wrong, but it -- generates correct pretty-printed code and allows us to reuse more code [ ("Prelude", "Eq", [ "(==)" ]) , ("Prelude", "Show", [ "show" ]) , ("Prelude", "Ord", [ "compare" ]) , ("Aeson", "ToJSON", [ "toJSON" ]) , ("Default", "Default", [ "def" ]) , ("Hashable", "Hashable", [ "hashWithSalt" ]) , ("DeepSeq", "NFData", [ "rnf" ]) , ("Thrift", "ThriftEnum", [ "toThriftEnum", "fromThriftEnum", "allThriftEnumValues", "toThriftEnumEither" ] ) ] else [ genToJSON enumResolvedName , genNFData enumResolvedName , genDefault enumResolvedName enumConstants , genHashable enumResolvedName , genThriftEnumInst enumResolvedName enumConstants enumNoUnknown ] ++ [genOrd enumResolvedName | not canDeriveOrd] where genConstr :: HS EnumValue -> QualConDecl () genConstr EnumValue{..} = QualConDecl () Nothing Nothing (ConDecl () (textToName evResolvedName) []) -- If the stars align we can derive the Enum instance -- This requires the Enum to contain exactly the values [0 .. n-1] canDeriveOrd = and $ zipWith (==) [0..] $ sort $ map evValue enumConstants -- Use 2 underscores to avoid name collisions. genUnknownConstr :: QualConDecl () genUnknownConstr = QualConDecl () Nothing Nothing (ConDecl () (textToName $ enumResolvedName <> "__UNKNOWN") [genType (TSpecial HsInt)]) -- Ord Instance ---------------------------------------------------------------- genOrd :: Text -> HS.Decl () genOrd name = InstDecl () Nothing (IRule () Nothing Nothing $ IHApp () (IHCon () $ qualSym "Prelude" "Ord") (TyCon () $ unqualSym name)) (Just $ map (InsDecl ()) [ FunBind () [ Match () (textToName "compare") [] (UnGuardedRhs () $ qvar "Function" "on" `app` qvar "Prelude" "compare" `app` qvar "Thrift" "fromThriftEnum") Nothing ] ]) -- Aeson Instances ------------------------------------------------------------- genToJSON :: Text -> HS.Decl () genToJSON name = InstDecl () Nothing (IRule () Nothing Nothing $ IHApp () (IHCon () $ qualSym "Aeson" "ToJSON") (TyCon () $ unqualSym name)) (Just $ map (InsDecl ()) [ FunBind () [ Match () (textToName "toJSON") [] (UnGuardedRhs () $ qvar "Aeson" "toJSON" `compose` qvar "Thrift" "fromThriftEnum") Nothing ] ]) -- Generate NFData Instance --------------------------------------------------- genNFData :: Text -> HS.Decl () genNFData name = InstDecl () Nothing (IRule () Nothing Nothing $ IHApp () (IHCon () $ qualSym "DeepSeq" "NFData") (TyCon () $ unqualSym name)) (Just $ map (InsDecl ()) [ FunBind () [ Match () (textToName "rnf") [ PApp () (unqualSym arg) [] ] (UnGuardedRhs () $ qvar "Prelude" "seq" `app` var arg `app` unit) Nothing ] ]) where arg = "__" <> name unit = Con () (Special () (UnitCon ())) -- Generate Default Instance -------------------------------------------------- genDefault :: Text -> [HS EnumValue] -> HS.Decl () genDefault name consts = InstDecl () Nothing (IRule () Nothing Nothing $ IHApp () (IHCon () $ qualSym "Default" "Default") (TyCon () $ unqualSym name)) (Just $ map (InsDecl ()) [ FunBind () [ Match () (textToName "def") [] (UnGuardedRhs () $ case consts of EnumValue{..} : _ -> con evResolvedName [] -> qvar "Exception" "throw" `app` (qvar "Thrift" "ProtocolException" `app` stringLit ("def: enum " <> name <> "has no constructors"))) Nothing ] ]) -- Generate Hashable Instance ------------------------------------------------- genHashable :: Text -> HS.Decl () genHashable name = InstDecl () Nothing (IRule () Nothing Nothing $ IHApp () (IHCon () $ qualSym "Hashable" "Hashable") (TyCon () $ unqualSym name)) (Just [ InsDecl () $ FunBind () [ Match () (textToName "hashWithSalt") [ pvar "_salt", pvar "_val" ] (UnGuardedRhs () $ qvar "Hashable" "hashWithSalt" `app` var "_salt" `app` (qvar "Thrift" "fromThriftEnum" `app` var "_val")) Nothing ] ]) -- Generate Empty Instance ----------------------------------------------------- genEmptyInstance :: Text -> (Text, Text, [Text]) -> HS.Decl () genEmptyInstance name (mname, className, methods) = InstDecl () Nothing (IRule () Nothing Nothing $ IHApp () (IHCon () $ qualSym mname className) (TyCon () $ unqualSym name)) (Just $ map (\method -> InsDecl () $ FunBind () [ Match () (textToName method) [] (UnGuardedRhs () $ qvar "Exception" "throw" `app` (qvar "Thrift" "ProtocolException" `app` stringLit (mconcat [ method, ": Thrift enum '", name, "' is uninhabited"]))) Nothing ]) methods) -- Thrift Enum Instance -------------------------------------------------------- genThriftEnumInst :: Text -> [HS EnumValue] -> Bool -> HS.Decl () genThriftEnumInst ename consts enumNoUnknown = InstDecl () Nothing (IRule () Nothing Nothing (IHApp () (IHCon () (qualSym "Thrift" "ThriftEnum")) (TyCon () (unqualSym ename)))) (Just $ map (InsDecl () . FunBind ()) [ map genToEnumMatch consts ++ [ if enumNoUnknown then genToEnumCatchAll else genToEnumUnknown ] , map genFromEnumMatch consts ++ [ if enumNoUnknown then genFromEnumCatchAll else genFromEnumUnknown ] , genAllEnumValues consts , map genToEnumEitherMatch consts ++ [genToEnumEitherUnknown] ] ) where genToEnumMatch :: HS EnumValue -> Match () genToEnumMatch EnumValue{..} = Match () (textToName "toThriftEnum") [ PLit () (if evValue < 0 then Negative () else Signless ()) (Int () (abs $ fromIntegral evValue) (show evValue)) ] (UnGuardedRhs () $ Con () $ unqualSym evResolvedName) Nothing genToEnumCatchAll = Match () (textToName "toThriftEnum") [ pvar "_val" ] (UnGuardedRhs () $ qvar "Exception" "throw" `app` (qvar "Thrift" "ProtocolException" `app` infixApp "++" (stringLit $ "toThriftEnum: not a valid identifier for enum " <> ename <> ": ") (qvar "Prelude" "show" `app` var "_val"))) Nothing genToEnumUnknown = Match () (textToName "toThriftEnum") [ pvar "val" ] (UnGuardedRhs () $ var (ename <> "__UNKNOWN") `app` var "val") Nothing genFromEnumMatch :: HS EnumValue -> Match () genFromEnumMatch EnumValue{..} = Match () (textToName "fromThriftEnum") [PApp () (unqualSym evResolvedName) []] (UnGuardedRhs () $ intLit evValue) Nothing genFromEnumCatchAll = Match () (textToName "fromThriftEnum") [ pvar "_val" ] (UnGuardedRhs () $ qvar "Exception" "throw" `app` (qvar "Thrift" "ProtocolException" `app` infixApp "++" (stringLit $ "fromThriftEnum: not a valid identifier for enum " <> ename <> ": ") (qvar "Prelude" "show" `app` var "_val"))) Nothing genFromEnumUnknown = Match () (textToName "fromThriftEnum") [PApp () (unqualSym (ename <> "__UNKNOWN")) [pvar "val"]] (UnGuardedRhs () $ var "val") Nothing genToEnumEitherMatch :: HS EnumValue -> Match () genToEnumEitherMatch EnumValue{..} = Match () (textToName "toThriftEnumEither") [ PLit () (if evValue < 0 then Negative () else Signless ()) (Int () (abs $ fromIntegral evValue) (show evValue)) ] (UnGuardedRhs () $ qvar "Prelude" "Right" `app` var evResolvedName) Nothing genToEnumEitherUnknown = Match () (textToName "toThriftEnumEither") [ pvar "val" ] (UnGuardedRhs () $ qvar "Prelude" "Left" `app` infixApp "++" (stringLit $ "toThriftEnumEither: not a valid identifier for enum " <> ename <> ": ") (qvar "Prelude" "show" `app` var "val")) Nothing genAllEnumValues :: [HS EnumValue] -> [Match ()] genAllEnumValues cs = [ Match () (textToName "allThriftEnumValues") [] (UnGuardedRhs () $ listE (genEnumExp <$> sortOn evValue cs)) Nothing ] where genEnumExp :: HS EnumValue -> Exp () genEnumExp EnumValue{..} = Con () $ unqualSym evResolvedName