glean-0.1.0.0: glean/schema/gen/Glean/Schema/Gen/Rust.hs
{-
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.
-}
{-# LANGUAGE OverloadedStrings #-}
-- | Glean's Rust schema generation
--
module Glean.Schema.Gen.Rust
( genSchemaRust
) where
import Control.Monad
import qualified Data.HashMap.Strict as HashMap
import Data.Maybe
import Data.Text (Text)
import qualified Data.Text as Text
import System.FilePath
import TextShow
import Glean.Schema.Gen.Utils
import Glean.Angle.Types
import Glean.Schema.Types
import Glean.Types (SchemaId(..))
import Data.List.Extra (nubOrd)
genSchemaRust
:: SchemaId
-> Version
-> [ResolvedPredicateDef]
-> [ResolvedTypeDef]
-> Maybe Oncall
-> [(FilePath, Text)]
genSchemaRust hash version preddefs typedefs _ =
[ ( dir </> Text.unpack (underscored namespaces) ++ ".rs"
, genNamespace namespaces version
hash namePolicy preds types)
| (namespaces, (_deps, preds, types)) <- HashMap.toList declsPerNamespace ]
where
dir = "schema"
namePolicy = mkNamePolicy preddefs typedefs
declsPerNamespace =
addNamespaceDependencies $ sortDeclsByNamespace preddefs typedefs
{- -----------------------------------------------------------------------------
We're going to generate:
python.rs
src.rs
builtin.rs
etc.
-}
genNamespace
:: NameSpaces
-> Version
-> SchemaId
-> NamePolicy
-> [ResolvedPredicateDef]
-> [ResolvedTypeDef]
-> Text
genNamespace namespaces version
hash namePolicy preddefs typedefs
= Text.intercalate newline $
header : [Text.intercalate (newline <> newline) pieces]
where
someDecls = map PredicateDecl preddefs ++
map (\TypeDef{..} -> TypeDecl typeDefRef typeDefType) typedefs
ordered = orderDecls someDecls
(gen :: [[Text]], extra :: [Text]) = runM [] namePolicy typedefs $ do
ps <- mapM (genDecl namespaces) ordered
return (map fst ps ++ map snd ps)
pieces = concat gen ++ reverse extra
genDecl :: NameSpaces -> SomeDecl -> M ([Text],[Text])
genDecl ns (PredicateDecl p) = genPred ns p
genDecl ns (TypeDecl tref ty) = ([],) <$> genType ns tref ty
namespace = dotted namespaces
header = Text.unlines $
[ "/*"
, " * Copyright (c) Meta Platforms, Inc. and affiliates."
, " *"
, " * This source code is licensed under the MIT license found in the"
, " * LICENSE file in the root directory of this source tree."
, " *"
, " * \x40generated"
, " * Regenerate with glean/schema/gen/Glean/Schema/Gen/Rust.hs"
, " * buck2 run glean/schema/gen:gen-schema -- --dir glean/schema/source --rust pyrefly/pyrefly/lib/report/glean"
, " */"
, ""
, "#![allow(warnings)]"
, "use serde::Deserialize;"
, "use serde::Serialize;"
, "use serde_json::Value;"
, ""
, "use crate::report::glean::schema::*;"
] ++
-- builtin only
if namespace /= "builtin" then [] else
[ "// Schema version"
, "pub const VERSION: i64 = " <> showt version <> ";"
, "pub const SCHEMA_ID: &str = \"" <> unSchemaId hash <> "\";"
]
-- -----------------------------------------------------------------------------
addExtraDecls :: M [Text] -> M [Text]
addExtraDecls act = do
most <- act
extra <- popDefs
return (nubOrd (extra ++ most))
indentLines :: [Text] -> [Text]
indentLines = map (\t -> if Text.null t then t else " " <> t)
addIndent :: Int -> Text
addIndent n = Text.replicate n " "
optionalize :: Text -> Text
optionalize name = "Option<" <> safe name <> ">"
box :: Text -> Text
box name = "Box<" <> safe name <> ">"
safe :: Text -> Text
safe name
| name `elem`
[ "type", "super", "crate", "self", "as", "mut", "mod", "box"
, "ref", "const", "dyn", "use", "extern", "unsafe", "trait"
, "macro", "Value"
]
= name <> "_"
| otherwise = if name == "builtin.Unit" then "builtin::Unit" else name
shareTypeDef :: NameSpaces -> ResolvedType' s -> M Text
shareTypeDef here t = do
(no, name) <- nameThisType t
case no of
Old -> return ()
New -> do
let tref = TypeRef name 0
pushDefs =<< genType here tref t
return name
rustTy :: NameSpaces -> ResolvedType' s -> M Text
rustTy here t = case t of
-- Basic types
ByteTy{} -> return "u8"
NatTy{} -> return "u64"
BooleanTy{} -> return "bool"
StringTy{} -> return "String"
-- Containers
ArrayTy ByteTy -> return "Vec<u8>"
ArrayTy ty -> do
inner <- rustTy here ty
return $ "Vec<" <> inner <> ">"
RecordTy{} -> shareTypeDef here t
SumTy{} -> shareTypeDef here t
SetTy ty -> do
inner <- rustTy here ty
return $ "Vec<" <> inner <> ">"
MaybeTy tInner -> do
inner <- rustTy here tInner
return (optionalize inner)
-- References
PredicateTy _ pred ->
rustName here <$> predicateName pred
NamedTy _ typeRef -> do
rustName here <$> typeName typeRef
EnumeratedTy _ -> shareTypeDef here t
TyVar{} -> error "rustTy: TyVar"
HasTy{} -> error "rustTy: HasTy"
HasKey{} -> error "rustTy: HasKey"
ElementsOf{} -> error "rustTy: ElementsOf"
rustName :: NameSpaces -> (NameSpaces, Text) -> Text
rustName here (ns, name)
| ns == here = safe name
| null ns = safe name
| Text.null (underscored ns) = safe name
| otherwise = underscored ns <> "::" <> safe name
annotation :: Text
annotation = "#[derive(Clone, Debug, Deserialize, Eq, Hash, "
<> "PartialEq, Serialize)]"
wrapInBoxIf :: Bool -> Text -> Text
wrapInBoxIf useBox text =
if useBox
then "Box::new(" <> text <> ")"
else text
-- Returns a tuple of the param string and the construction string
getFieldsForConstructor :: NameSpaces -> ResolvedType' s -> Text -> Text -> Bool
-> M ([Text], Text)
getFieldsForConstructor here ty typeName defaultParamName useBox = case ty of
RecordTy fields -> do
params <- forM fields $ \(FieldDef nm fieldTy) -> do
tyName <- withRecordFieldHint nm (rustTy here fieldTy)
return $ safe nm <> ": " <> tyName
let fieldAssignments = map (\(FieldDef nm _) -> safe nm) fields
construction = typeName <> " {"
<> "\n" <> addIndent 4
<> Text.intercalate
(",\n" <> addIndent 4)
fieldAssignments <>
"\n" <> addIndent 3 <> "}"
return (params, wrapInBoxIf useBox construction)
_ -> do
let param = defaultParamName <> ": " <> typeName
return ([param], wrapInBoxIf useBox defaultParamName)
genPred :: NameSpaces -> ResolvedPredicateDef -> M ([Text], [Text])
genPred here PredicateDef{..} = do
pName <- predicateName predicateDefRef
let
name = rustName here pName
type_id = "u64"
withPredicateDefHint (snd pName) $ do
let
appendName suffix = case pName of (ns, x) -> (ns, x <> suffix)
name_key = appendName "_key"
name_value = appendName "_value"
has_value = predicateDefValueType /= unitT
(type_key, define_key) <-
if shouldNameKeyType predicateDefKeyType
then define_kt here predicateDefKeyType name_key
else do
keyTy <- rustTy here predicateDefKeyType
return (keyTy, [])
-- Define a type for the value, only if it's not Unit
(type_value, define_value) <-
if not has_value
then return ("()", [])
else do
define_kt here predicateDefValueType name_value
-- Generate the new() function parameters and construction
(keyParams, keyConstruction) <-
getFieldsForConstructor here predicateDefKeyType type_key "key" True
(valueParams, valueConstruction) <-
if has_value
then getFieldsForConstructor here predicateDefValueType type_value "value" False
else return ([], "")
let allParams = keyParams ++ valueParams
paramList = Text.intercalate ", " allParams
let
-- Predicate struct definition
define = (:[]) $ myUnlines $ concat
[ [ annotation ]
, [ "pub struct " <> name <> " {" ]
, indentLines $ catMaybes
[ Just $ "pub id: " <> type_id <> ","
, Just $ "pub key: " <> box type_key <> ","
, if has_value
then Just $ "pub value: " <> type_value <> ","
else Nothing
]
, [ "}" ]
, [ ""]
, [ "impl " <> name <> " {" ]
, indentLines
[ "pub fn GLEAN_name() -> String {"
, addIndent 1
<> "String::from(\""
<> predicateRef_name predicateDefRef
<> "."
<> showt (predicateRef_version predicateDefRef)
<> "\")"
, "}"
, ""
, "pub fn new(" <> paramList <> ") -> Self {"
, addIndent 1 <> name <> " {"
, addIndent 2 <> "id: 0,"
, addIndent 2 <> "key: " <> keyConstruction <> ","
] ++
[addIndent 3
<> "value: " <> valueConstruction
<> ","
| has_value] ++
[ addIndent 2 <> "}"
, addIndent 1 <> "}"
]
, [ "}" ]
]
extra <- popDefs
return (define, nubOrd (extra ++ define_key ++ define_value))
define_kt ::
NameSpaces -> ResolvedType -> (NameSpaces, Text) -> M (Text, [Text])
define_kt here typ name_kt = do
let gname = joinDot name_kt
tref = TypeRef gname 0
ref <- rustTy here (NamedTy () (TypeRef gname 0))
def <- genType here tref typ
return (ref,def)
makeEnum :: Text -> [Name] -> M [Text]
makeEnum name vals = do
let
variants = Text.unlines $ indentLines (map (<> ",") vals)
declare = annotation <> newline
<> "pub enum " <> name <> " {" <> newline <> variants <> "}"
return [declare]
genType :: NameSpaces -> TypeRef -> ResolvedType' s -> M [Text]
genType here tref ty = addExtraDecls $ do
tName@(_, root) <- typeName tref
let name = rustName here tName
withTypeDefHint root $ do
case ty of
RecordTy fields -> do
fieldTexts <- forM fields $ \(FieldDef nm ty) -> do
tyName <- withRecordFieldHint nm (rustTy here ty)
return $ "pub " <> safe nm <> ": " <> tyName <> ","
let
define | null fields =
annotation <> newline <>
"pub struct " <> name <> " {}"
| otherwise = myUnlines $ concat
[ [ annotation ]
, [ "pub struct " <> name <> " {" ]
, indentLines fieldTexts
, [ "}" ]
]
return [define]
SumTy fields -> do
variantTexts <- forM fields $ \(FieldDef nm ty) -> do
tyName <- withUnionFieldHint nm (rustTy here ty)
return $ safe nm <> "(" <> safe tyName <> "),"
let
define | null fields =
annotation <> newline <>
"pub enum " <> name <> " {}"
| otherwise = myUnlines $ concat
[ [ annotation ]
, [ "pub enum " <> name <> " {" ]
, indentLines variantTexts
, [ "}" ]
]
return [define]
EnumeratedTy vals -> makeEnum name vals
_other_ty -> do
t <- rustTy here ty
return ["pub type " <> name <> " = " <> t <> ";"]