From ceb14cf01dd371024765455b1603b92b35bd7717 Mon Sep 17 00:00:00 2001 From: VirxEC Date: Tue, 26 Mar 2024 12:09:13 -0400 Subject: [PATCH] Even better union ergonomics --- Cargo.lock | 56 ++++----- Cargo.toml | 4 +- build.rs | 331 ++++++++++++++++++++++++++++++++++------------------- pytest.py | 5 +- 4 files changed, 246 insertions(+), 150 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e991a6b..905b2a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,7 +11,7 @@ dependencies = [ "attribute-derive-macro", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.55", ] [[package]] @@ -27,14 +27,14 @@ dependencies = [ "proc-macro2", "quote", "quote-use", - "syn 2.0.53", + "syn 2.0.55", ] [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "bitflags" @@ -62,13 +62,13 @@ checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.55", ] [[package]] name = "flatbuffers" -version = "24.3.7" -source = "git+https://github.com/google/flatbuffers?branch=master#67eb95de9281087ccbba9aafd6e8ab1958d12045" +version = "24.3.25" +source = "git+https://github.com/google/flatbuffers?branch=master#8f2e1dbd88d25ca76cbcaf9ac8c9919e0a787d31" dependencies = [ "bitflags", "rustc_version", @@ -91,7 +91,7 @@ checksum = "13a1bcfb855c1f340d5913ab542e36f25a1c56f57de79022928297632435dec2" dependencies = [ "attribute-derive", "quote", - "syn 2.0.53", + "syn 2.0.55", ] [[package]] @@ -102,9 +102,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "indoc" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "interpolator" @@ -218,9 +218,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.21.0-beta.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0c41d899f822e5f39186d6da130a822a0a43edb19992b51bf4ef6cd0b4cfd1" +checksum = "a02a88a17e74cadbc8ce77855e1d6c8ad0ab82901a4a9b5046bd01c1c0bd95cd" dependencies = [ "cfg-if", "indoc", @@ -236,9 +236,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.21.0-beta.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5509c2aa78c7e770077e41ba86f806e60dcee812e924ccb2d6fe78c0a0128ce2" +checksum = "a5eb0b6ecba38961f6f4bd6cd5906dfab3cd426ff37b2eed5771006aa31656f1" dependencies = [ "once_cell", "target-lexicon", @@ -246,9 +246,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.21.0-beta.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6bb234a86ed619a661f3bb3c2493aaff9cb937e33e198d17f5f20a15881e155" +checksum = "ba8a6e48a29b5d22e4fdaf132d8ba8d3203ee9f06362d48f244346902a594ec3" dependencies = [ "libc", "pyo3-build-config", @@ -256,27 +256,27 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.21.0-beta.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0b787de2c6832eb1eb393c9f82f976a5a87bda979780d9b853878846a8d2e4b" +checksum = "4e80493c5965f94a747d0782a607b2328a4eea5391327b152b00e2f3b001cede" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.53", + "syn 2.0.55", ] [[package]] name = "pyo3-macros-backend" -version = "0.21.0-beta.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e3b7beed357786d2afe845871964e824ad8af0df38a403f7d01cdc81aadb211" +checksum = "fcd7d86f42004025200e12a6a8119bd878329e6fddef8178eaafa4e4b5906c5b" dependencies = [ "heck", "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.53", + "syn 2.0.55", ] [[package]] @@ -296,7 +296,7 @@ checksum = "a7b5abe3fe82fdeeb93f44d66a7b444dedf2e4827defb0a8e69c437b2de2ef94" dependencies = [ "quote", "quote-use-macros", - "syn 2.0.53", + "syn 2.0.55", ] [[package]] @@ -308,7 +308,7 @@ dependencies = [ "derive-where", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.55", ] [[package]] @@ -322,7 +322,7 @@ dependencies = [ [[package]] name = "rlbot-flatbuffers-py" -version = "0.2.1" +version = "0.3.0" dependencies = [ "flatbuffers", "get-size", @@ -368,7 +368,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.55", ] [[package]] @@ -389,9 +389,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.53" +version = "2.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" +checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 606b158..bda3b83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rlbot-flatbuffers-py" -version = "0.2.1" +version = "0.3.0" edition = "2021" description = "A Python module implemented in Rust for serializing and deserializing RLBot's flatbuffers" repository = "https://github.com/VirxEC/rlbot-flatbuffers-py" @@ -18,7 +18,7 @@ name = "rlbot_flatbuffers" crate-type = ["cdylib"] [dependencies] -pyo3 = "0.21.0-beta.0" +pyo3 = "0.21.0" serde = "1.0.197" flatbuffers = { git = "https://github.com/google/flatbuffers", branch = "master" } get-size = { version = "0.1.4", features = ["derive"] } diff --git a/build.rs b/build.rs index 728d602..acbcb7e 100644 --- a/build.rs +++ b/build.rs @@ -84,7 +84,7 @@ impl PythonBindGenerator { file_contents.push(Cow::Borrowed(match bind_type { PythonBindType::Struct => "use pyo3::{pyclass, pymethods, types::PyBytes, Bound, Py, Python};", PythonBindType::Enum => "use pyo3::{pyclass, pymethods, types::PyBytes, Bound, Python};", - PythonBindType::Union => "use pyo3::{pyclass, pymethods, Py, Python};", + PythonBindType::Union => "use pyo3::{pyclass, pymethods, Py, PyObject, Python, ToPyObject};", })); file_contents.push(Cow::Borrowed("")); @@ -404,12 +404,12 @@ impl PythonBindGenerator { self.generate_union_py_methods(); - self.write_str("#[pyclass(module = \"rlbot_flatbuffers\", get_all, set_all)]"); + self.write_str("#[pyclass(module = \"rlbot_flatbuffers\")]"); self.write_str("#[derive(Debug, Default, Clone)]"); self.write_string(format!("pub struct {} {{", self.struct_name)); - self.file_contents - .push(Cow::Owned(format!(" pub item_type: {}Type,", self.struct_name))); + self.write_str(" #[pyo3(get)]"); + self.write_string(format!(" pub item_type: {}Type,", self.struct_name)); for variable_info in &self.types { let variable_type = &variable_info[1]; @@ -457,7 +457,6 @@ impl PythonBindGenerator { self.write_str(" }"); self.write_str(" }"); - self.write_str(""); self.generate_str_method(); @@ -915,6 +914,99 @@ impl PythonBindGenerator { self.write_str(" }"); self.write_str(" }"); + self.write_str(""); + self.write_str(" #[getter(item)]"); + self.write_str(" pub fn get_item(&mut self, py: Python) -> Option {"); + self.write_str(" match self.item_type {"); + + for variable_info in &self.types { + let variable_name = &variable_info[0]; + + if variable_name == "NONE" { + self.file_contents + .push(Cow::Owned(format!(" {}Type::None => None,", self.struct_name))); + } else { + let wanted_snake_case_name = &variable_info[2]; + + self.file_contents.push(Cow::Owned(format!( + " {}Type::{variable_name} => self.{wanted_snake_case_name}.as_ref().map(|x| x.to_object(py)),", + self.struct_name + ))); + } + } + + self.write_str(" }"); + self.write_str(" }"); + self.write_str(""); + self.write_str(" #[setter(item)]"); + self.write_string(format!( + " pub fn set_item(&mut self, item: Option<{}Union>) {{", + self.struct_name + )); + self.write_str(" match item {"); + + for variable_info in &self.types { + let variable_name = &variable_info[0]; + + if variable_name == "NONE" { + self.file_contents.push(Cow::Borrowed(" None => {")); + self.file_contents.push(Cow::Owned(format!( + " self.item_type = {}Type::None;", + self.struct_name + ))); + + for variable_info in &self.types { + let variable_type = &variable_info[1]; + + if variable_type.is_empty() { + continue; + } + + let snake_case_name = &variable_info[2]; + + self.file_contents + .push(Cow::Owned(format!(" self.{snake_case_name} = None;",))); + } + + self.file_contents.push(Cow::Borrowed(" }")); + } else { + let wanted_snake_case_name = &variable_info[2]; + + self.file_contents.push(Cow::Owned(format!( + " Some({}Union::{}({wanted_snake_case_name})) => {{", + self.struct_name, variable_name + ))); + self.file_contents.push(Cow::Owned(format!( + " self.item_type = {}Type::{variable_name};", + self.struct_name + ))); + + for variable_info in &self.types { + let variable_type = &variable_info[1]; + + if variable_type.is_empty() { + continue; + } + + let snake_case_name = &variable_info[2]; + + if wanted_snake_case_name == snake_case_name { + self.file_contents.push(Cow::Owned(format!( + " self.{snake_case_name} = Some({snake_case_name});", + ))); + } else { + self.file_contents + .push(Cow::Owned(format!(" self.{snake_case_name} = None;",))); + } + } + + self.file_contents.push(Cow::Borrowed(" }")); + } + } + + self.write_str(" }"); + self.write_str(" }"); + self.write_str(""); } fn generate_enum_new_method(&mut self) { @@ -1378,144 +1470,145 @@ fn pyi_generator(type_data: &[(String, String, Vec>)]) -> io::Result if is_union { file_contents.push(Cow::Owned(format!(" item_type: {type_name}Type"))); - } - let mut python_types = Vec::new(); + let types = types + .iter() + .map(|variable_info| variable_info[0].as_str()) + .filter(|variable_name| *variable_name != "NONE") + .collect::>(); + let union_str = types.join(" | "); - 'outer: for variable_info in types { - let mut variable_name = variable_info[0].as_str(); + file_contents.push(Cow::Owned(format!(" item: Optional[{union_str}]"))); + file_contents.push(Cow::Borrowed("")); + file_contents.push(Cow::Borrowed(" def __init__(")); + file_contents.push(Cow::Owned(format!(" self, item: Optional[{union_str}] = None"))); + file_contents.push(Cow::Borrowed(" ): ...")); + } else { + let mut python_types = Vec::new(); - if variable_name == "NONE" { - continue; - } + 'outer: for variable_info in types { + let mut variable_name = variable_info[0].as_str(); - if is_union { - variable_name = &variable_info[2]; - } + if variable_name == "NONE" { + continue; + } - let variable_type = if is_union { - format!("Option<{}>", variable_info[1]) - } else { - variable_info[1].clone() - }; + if is_union { + variable_name = &variable_info[2]; + } - if is_enum { - file_contents.push(Cow::Owned(format!(" {variable_name} = {type_name}({variable_type})"))); - continue; - } + let variable_type = if is_union { + format!("Option<{}>", variable_info[1]) + } else { + variable_info[1].clone() + }; - for (rust_type, python_type) in primitive_map { - if variable_type == rust_type { - python_types.push(python_type.to_string()); - file_contents.push(Cow::Owned(format!(" {variable_name}: {python_type}"))); - continue 'outer; + if is_enum { + file_contents.push(Cow::Owned(format!(" {variable_name} = {type_name}({variable_type})"))); + continue; } - } - if variable_type.starts_with("Vec<") { - let type_name = variable_type - .trim_start_matches("Vec<") - .trim_end_matches('>') - .trim_end_matches('T'); + for (rust_type, python_type) in primitive_map { + if variable_type == rust_type { + python_types.push(python_type.to_string()); + file_contents.push(Cow::Owned(format!(" {variable_name}: {python_type}"))); + continue 'outer; + } + } - python_types.push(format!("list[{type_name}]")); - file_contents.push(Cow::Owned(format!(" {variable_name}: list[{type_name}]"))); - } else if variable_type.starts_with("Option<") { - let type_name = variable_type - .trim_start_matches("Option<") - .trim_start_matches("Box<") - .trim_end_matches('>') - .trim_end_matches('T'); + if variable_type.starts_with("Vec<") { + let type_name = variable_type + .trim_start_matches("Vec<") + .trim_end_matches('>') + .trim_end_matches('T'); - let python_type = if type_name == "bool" { - "bool" - } else if type_name == "i32" || type_name == "u32" { - "int" - } else if type_name == "f32" { - "float" - } else if type_name == "String" { - "str" - } else if type_name == "Float" { - "Float | float" - } else if type_name == "Bool" { - "Bool | bool" - } else { - type_name - }; + python_types.push(format!("list[{type_name}]")); + file_contents.push(Cow::Owned(format!(" {variable_name}: list[{type_name}]"))); + } else if variable_type.starts_with("Option<") { + let type_name = variable_type + .trim_start_matches("Option<") + .trim_start_matches("Box<") + .trim_end_matches('>') + .trim_end_matches('T'); + + let python_type = if type_name == "bool" { + "bool" + } else if type_name == "i32" || type_name == "u32" { + "int" + } else if type_name == "f32" { + "float" + } else if type_name == "String" { + "str" + } else if type_name == "Float" { + "Float | float" + } else if type_name == "Bool" { + "Bool | bool" + } else { + type_name + }; - python_types.push(format!("Optional[{python_type}]")); - file_contents.push(Cow::Owned(format!(" {variable_name}: Optional[{python_type}]"))); - } else if variable_type.starts_with("Box<") && variable_type.ends_with("T>") { - let type_name = variable_type.trim_start_matches("Box<").trim_end_matches("T>"); + python_types.push(format!("Optional[{python_type}]")); + file_contents.push(Cow::Owned(format!(" {variable_name}: Optional[{python_type}]"))); + } else if variable_type.starts_with("Box<") && variable_type.ends_with("T>") { + let type_name = variable_type.trim_start_matches("Box<").trim_end_matches("T>"); - python_types.push(type_name.to_string()); - file_contents.push(Cow::Owned(format!(" {variable_name}: {type_name}"))); - } else if variable_type.ends_with('T') { - let type_name = variable_type.trim_end_matches('T'); + python_types.push(type_name.to_string()); + file_contents.push(Cow::Owned(format!(" {variable_name}: {type_name}"))); + } else if variable_type.ends_with('T') { + let type_name = variable_type.trim_end_matches('T'); - python_types.push(type_name.to_string()); - file_contents.push(Cow::Owned(format!(" {variable_name}: {type_name}"))); - } else { - python_types.push(variable_type.clone()); - file_contents.push(Cow::Owned(format!(" {variable_name}: {variable_type}"))); + python_types.push(type_name.to_string()); + file_contents.push(Cow::Owned(format!(" {variable_name}: {type_name}"))); + } else { + python_types.push(variable_type.clone()); + file_contents.push(Cow::Owned(format!(" {variable_name}: {variable_type}"))); + } } - } - - file_contents.push(Cow::Borrowed("")); - if is_enum { - file_contents.push(Cow::Borrowed(" def __init__(self, value: int = 0): ...")); - } else if is_union { - file_contents.push(Cow::Borrowed(" def __init__(")); - - let types = types - .iter() - .map(|variable_info| variable_info[0].as_str()) - .filter(|variable_name| *variable_name != "NONE") - .collect::>(); - let union_str = types.join(" | "); + file_contents.push(Cow::Borrowed("")); - file_contents.push(Cow::Owned(format!(" self, item: Optional[{union_str}] = None"))); - file_contents.push(Cow::Borrowed(" ): ...")); - } else { - file_contents.push(Cow::Borrowed(" def __init__(")); - file_contents.push(Cow::Borrowed(" self,")); + if is_enum { + file_contents.push(Cow::Borrowed(" def __init__(self, value: int = 0): ...")); + } else { + file_contents.push(Cow::Borrowed(" def __init__(")); + file_contents.push(Cow::Borrowed(" self,")); - let mut i = 0; - for variable_info in types { - if &variable_info[0] == "NONE" { - continue; - } + let mut i = 0; + for variable_info in types { + if &variable_info[0] == "NONE" { + continue; + } - let variable_name = &variable_info[0]; + let variable_name = &variable_info[0]; - let variable_type = variable_info[1].clone(); - - let default_value = match variable_type.as_str() { - "bool" => Cow::Borrowed("False"), - "i32" | "u32" | "f32" | "u8" => Cow::Borrowed("0"), - "String" => Cow::Borrowed("\"\""), - t => { - if t.starts_with("Vec<") { - Cow::Borrowed("[]") - } else if t.starts_with("Box<") { - let inner_type = t.trim_start_matches("Box<").trim_end_matches('>').trim_end_matches('T'); - Cow::Owned(format!("{inner_type}()")) - } else if t.starts_with("Option<") { - Cow::Borrowed("None") - } else { - Cow::Owned(format!("{}()", t.trim_end_matches('T'))) + let variable_type = variable_info[1].clone(); + + let default_value = match variable_type.as_str() { + "bool" => Cow::Borrowed("False"), + "i32" | "u32" | "f32" | "u8" => Cow::Borrowed("0"), + "String" => Cow::Borrowed("\"\""), + t => { + if t.starts_with("Vec<") { + Cow::Borrowed("[]") + } else if t.starts_with("Box<") { + let inner_type = t.trim_start_matches("Box<").trim_end_matches('>').trim_end_matches('T'); + Cow::Owned(format!("{inner_type}()")) + } else if t.starts_with("Option<") { + Cow::Borrowed("None") + } else { + Cow::Owned(format!("{}()", t.trim_end_matches('T'))) + } } - } - }; + }; - let python_type = &python_types[i]; - file_contents.push(Cow::Owned(format!(" {variable_name}: {python_type}={default_value},"))); + let python_type = &python_types[i]; + file_contents.push(Cow::Owned(format!(" {variable_name}: {python_type}={default_value},"))); - i += 1; - } + i += 1; + } - file_contents.push(Cow::Borrowed(" ): ...")); + file_contents.push(Cow::Borrowed(" ): ...")); + } } file_contents.push(Cow::Borrowed(" def __str__(self) -> str: ...")); diff --git a/pytest.py b/pytest.py index 752cb07..f6376a8 100644 --- a/pytest.py +++ b/pytest.py @@ -25,7 +25,10 @@ print(repr(RenderType())) render_type = RenderType(Line3D(Vector3(0, 0, 0), Vector3(1, 1, 1), Color(255))) - render_type.line_3_d.color.a = 150 + if isinstance(render_type.item, Line3D): + render_type.item.color.a = 150 + else: + raise ValueError("Expected Line3D") print(repr(render_type)) print(render_type)