From d118bd146078e3a270526bcd3dffe3f58cb576d4 Mon Sep 17 00:00:00 2001 From: Stuart Harris Date: Thu, 6 Jun 2024 17:29:25 +0100 Subject: [PATCH] build typeshare data --- crux_cli/src/codegen/parser.rs | 326 ++++++++++++++++++++++++++++- crux_cli/src/codegen/rust_types.rs | 31 +-- crux_core/Cargo.toml | 2 +- crux_macros/Cargo.toml | 2 +- 4 files changed, 323 insertions(+), 38 deletions(-) diff --git a/crux_cli/src/codegen/parser.rs b/crux_cli/src/codegen/parser.rs index 4a0e4093..ad686751 100644 --- a/crux_cli/src/codegen/parser.rs +++ b/crux_cli/src/codegen/parser.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use anyhow::Result; +use anyhow::{anyhow, bail, Result}; use rustdoc_types::{Crate, Id, Impl, ItemEnum, Path, Type}; use super::{ @@ -13,17 +13,20 @@ use crate::codegen::{ path_component::PathComponent, public_item::PublicItem, render::RenderingContext, + rust_types::{ + self, RustEnumShared, RustEnumVariant, RustEnumVariantShared, RustField, RustType, + }, }; /// The results of parsing Rust source input. #[derive(Default, Debug)] pub struct ParsedData { /// Structs defined in the source - pub structs: HashMap, + pub structs: Vec, /// Enums defined in the source - pub enums: HashMap, + pub enums: Vec, /// Type aliases defined in the source - pub aliases: HashMap, + pub aliases: Vec, } impl ParsedData { @@ -32,10 +35,83 @@ impl ParsedData { } } +#[derive(Debug, Clone)] +enum Value { + Namespace, + Struct(RustStruct), + Enum(RustEnum), + TypeAlias(RustTypeAlias), +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +struct Component { + type_: ComponentType, + name: String, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +enum ComponentType { + Enum, + Struct, + StructField, + TupleVariantField, + Variant, + TypeAlias, + Namespace, +} + +impl Component { + fn child_depth(&self) -> usize { + match self.type_ { + ComponentType::Enum => 1, + ComponentType::Struct => 1, + ComponentType::StructField => 2, + ComponentType::TupleVariantField => 3, + ComponentType::Variant => 2, + ComponentType::TypeAlias => 1, + ComponentType::Namespace => 0, + } + } +} + +impl TryFrom<&str> for Component { + type Error = anyhow::Error; + + fn try_from(component: &str) -> Result { + let Some((type_, name)) = component.split_once('-') else { + bail!("malformed component: {}", component); + }; + if type_.len() != 3 { + bail!("Invalid type: {}", type_); + } + let type_ = match type_.parse::()? { + 7 => ComponentType::Enum, + 9 => ComponentType::Struct, + 10 => { + if name.parse::().is_ok() { + ComponentType::TupleVariantField + } else { + ComponentType::StructField + } + } + 11 => ComponentType::Variant, + 19 => ComponentType::TypeAlias, + 21 => ComponentType::Namespace, + _ => bail!("Unknown type: {}", type_), + }; + let name = name + .split_whitespace() + .last() + .ok_or_else(|| anyhow!("malformed component: {}", name))? + .to_string(); + Ok(Self { type_, name }) + } +} + pub fn parse(crate_: &Crate) -> Result { let mut item_processor = ItemProcessor::new(crate_); - add_items(crate_, "Effect", &["Ffi"], &mut item_processor); - add_items(crate_, "App", &["Event", "ViewModel"], &mut item_processor); + add_items(crate_, &mut item_processor, "Effect", &["Ffi"]); + add_items(crate_, &mut item_processor, "App", &["Event", "ViewModel"]); item_processor.run(); let context = RenderingContext { @@ -69,22 +145,144 @@ pub fn parse(crate_: &Crate) -> Result { public_api.items.sort_by(PublicItem::grouping_cmp); - let mut parsed_data = ParsedData::new(); - println!(); - for item in public_api.items { + let mut data = HashMap::new(); + + for item in public_api.items.iter().rev() { + println!("{}", item); println!("{:?}", item.sortable_path); - println!("{}\n", item); + + let (subject, object) = parse_sortable_path(item.sortable_path.as_slice())?; + + println!("{subject:?}\n{object:?}"); + + let entry = data.entry(subject.clone()).or_insert_with(|| { + let component = subject.last().unwrap(); + let id = rust_types::Id { + original: component.name.clone(), + renamed: component.name.clone(), // TODO: serde rename + }; + match component.type_ { + ComponentType::Enum => Value::Enum(RustEnum::Algebraic { + tag_key: component.name.clone(), + content_key: String::new(), + shared: RustEnumShared { + id, + generic_types: Vec::new(), + comments: Vec::new(), + variants: Vec::new(), + is_recursive: false, + }, + }), + ComponentType::Struct => Value::Struct(RustStruct { + id, + generic_types: Vec::new(), + fields: Vec::new(), + comments: Vec::new(), + }), + _ => unimplemented!(), + } + }); + + let mut variant_type: Option = None; + + for component in object.iter().rev() { + let id = rust_types::Id { + original: component.name.clone(), + renamed: component.name.clone(), // TODO: serde rename + }; + + match component.type_ { + ComponentType::StructField => { + println!("adding StructField"); + let Value::Struct(ref mut value) = entry else { + bail!("entry is not a struct {:#?}", entry); + }; + + value.fields.push(RustField { + id, + ty: RustType::Simple { + id: component.name.clone(), + }, + comments: Vec::new(), + has_default: false, + }); + } + ComponentType::Variant => { + println!("adding Variant"); + let Value::Enum(ref mut value) = entry else { + bail!("entry is not an enum {:#?}", entry); + }; + + match value { + RustEnum::Algebraic { + shared: + RustEnumShared { + id: _, + generic_types: _, + comments: _, + ref mut variants, + is_recursive: _, + }, + .. + } => { + if !variants.iter().any(|v| v.shared().id == id) { + if let Some(variant_type) = variant_type.take() { + variants.push(variant_type); + } else { + variants.push(RustEnumVariant::AnonymousStruct { + fields: Vec::new(), + shared: RustEnumVariantShared { + id, + comments: Vec::new(), + }, + }); + } + } + } + _ => bail!("unexpected enum type"), + } + } + ComponentType::TupleVariantField => { + println!("adding TupleVariantField"); + variant_type = Some(RustEnumVariant::Tuple { + ty: rust_types::RustType::Simple { id: id.to_string() }, + shared: RustEnumVariantShared { + id, + comments: Vec::new(), + }, + }); + } + _ => (), + } + } + + println!(); } + + println!("{:#?}", data); + + let parsed_data = ParsedData::new(); Ok(parsed_data) } +fn parse_sortable_path(path: &[String]) -> Result<(Vec, Vec)> { + let mut subject = path + .iter() + .map(|s| s.as_str().try_into()) + .collect::>>()?; + let child_depth = subject.last().map_or(1, |c| c.child_depth()) - 1; + eprintln!("child_depth: {}", child_depth); + let object = subject.split_off(subject.len() - child_depth); + Ok((subject, object)) +} + fn add_items<'c: 'p, 'p>( crate_: &'c Crate, + item_processor: &'p mut ItemProcessor<'c>, trait_name: &'c str, filter: &'c [&'c str], - item_processor: &'p mut ItemProcessor<'c>, ) { for root in find_roots(crate_, trait_name, filter) { let item = &crate_.index[root.parent]; @@ -158,3 +356,109 @@ fn find_roots<'a>( } }) } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_parse_sortable_path_for_enum() { + let path = &["021-impl crux_core::App for shared::app::App", "007-Event"] + .iter() + .map(|s| s.to_string()) + .collect::>(); + + let (subject, object) = parse_sortable_path(path).unwrap(); + assert_eq!( + subject, + vec![ + Component { + type_: ComponentType::Namespace, + name: "shared::app::App".to_string() + }, + Component { + type_: ComponentType::Enum, + name: "Event".to_string() + } + ] + ); + assert_eq!(object, vec![]); + } + + #[test] + fn test_parse_sortable_path_for_variant() { + let path = &[ + "021-impl crux_core::App for shared::app::App", + "007-Event", + "011-Decrement", + ] + .iter() + .map(|s| s.to_string()) + .collect::>(); + + let (subject, object) = parse_sortable_path(path).unwrap(); + + assert_eq!( + subject, + vec![ + Component { + type_: ComponentType::Namespace, + name: "shared::app::App".to_string() + }, + Component { + type_: ComponentType::Enum, + name: "Event".to_string() + } + ] + ); + assert_eq!( + object, + vec![Component { + type_: ComponentType::Variant, + name: "Decrement".to_string() + }] + ); + } + + #[test] + fn test_parse_sortable_path_for_variant_tuple_field() { + let path = &[ + "021-impl crux_core::core::effect::Effect for shared::app::Effect", + "007-EffectFfi", + "011-Http", + "010-0", + ] + .iter() + .map(|s| s.to_string()) + .collect::>(); + + let (subject, object) = parse_sortable_path(path).unwrap(); + + assert_eq!( + subject, + vec![ + Component { + type_: ComponentType::Namespace, + name: "shared::app::Effect".to_string() + }, + Component { + type_: ComponentType::Enum, + name: "EffectFfi".to_string() + } + ] + ); + assert_eq!( + object, + vec![ + Component { + type_: ComponentType::Variant, + name: "Http".to_string() + }, + Component { + type_: ComponentType::TupleVariantField, + name: "0".to_string() + } + ] + ); + } +} diff --git a/crux_cli/src/codegen/rust_types.rs b/crux_cli/src/codegen/rust_types.rs index e6fe7655..83bac719 100644 --- a/crux_cli/src/codegen/rust_types.rs +++ b/crux_cli/src/codegen/rust_types.rs @@ -1,8 +1,6 @@ /// Identifier used in Rust structs, enums, and fields. It includes the `original` name and the `renamed` value after the transformation based on `serde` attributes. #[derive(Debug, Clone, PartialEq)] pub struct Id { - // the identifier from the rustdoc json - pub id: rustdoc_types::Id, /// The original identifier name pub original: String, /// The renamed identifier, based on serde attributes. @@ -11,22 +9,16 @@ pub struct Id { pub renamed: String, } -impl Id { - pub fn new(id: rustdoc_types::Id) -> Self { - Self { - id, - original: String::new(), - renamed: String::new(), +impl std::fmt::Display for Id { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.original == self.renamed { + write!(f, "({})", self.original) + } else { + write!(f, "({}, {})", self.original, self.renamed) } } } -impl From for Id { - fn from(id: rustdoc_types::Id) -> Self { - Self::new(id) - } -} - // Rust struct. #[derive(Debug, Clone, PartialEq)] pub struct RustStruct { @@ -42,17 +34,6 @@ pub struct RustStruct { pub comments: Vec, } -impl RustStruct { - pub fn new(id: Id) -> Self { - Self { - id, - generic_types: Vec::new(), - fields: Vec::new(), - comments: Vec::new(), - } - } -} - /// Rust type alias. /// ``` /// pub struct MasterPassword(String); diff --git a/crux_core/Cargo.toml b/crux_core/Cargo.toml index 2183a8f9..e52886c0 100644 --- a/crux_core/Cargo.toml +++ b/crux_core/Cargo.toml @@ -37,7 +37,7 @@ async-channel = "2.3" crux_http = { path = "../crux_http" } crux_time = { path = "../crux_time" } doctest_support = { path = "../doctest_support" } -serde = { version = "1.0.203", features = ["derive"] } +serde = { version = "1.0.202", features = ["derive"] } static_assertions = "1.1" rand = "0.8" url = "2.5.2" diff --git a/crux_macros/Cargo.toml b/crux_macros/Cargo.toml index f755c3ff..af7e5220 100644 --- a/crux_macros/Cargo.toml +++ b/crux_macros/Cargo.toml @@ -24,4 +24,4 @@ crux_core = { path = "../crux_core" } crux_http = { path = "../crux_http" } insta = "1.39" prettyplease = "0.2" -serde = { version = "1.0.203", features = ["derive"] } +serde = { version = "1.0.202", features = ["derive"] }