From 8e3423f8b63e34fca62d7fe05a940975e5ce5e76 Mon Sep 17 00:00:00 2001 From: Darrell Roberts Date: Tue, 4 Jun 2024 15:26:11 -0400 Subject: [PATCH 01/17] Allow swift decorator constraints to apply to generics --- .../input.rs | 17 +++++++++++ .../output.swift | 28 +++++++++++++++++++ core/src/language/swift.rs | 8 +++++- core/tests/snapshot_tests.rs | 1 + 4 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 core/data/tests/generic_struct_with_constraints_and_decorators/input.rs create mode 100644 core/data/tests/generic_struct_with_constraints_and_decorators/output.swift diff --git a/core/data/tests/generic_struct_with_constraints_and_decorators/input.rs b/core/data/tests/generic_struct_with_constraints_and_decorators/input.rs new file mode 100644 index 00000000..962e5e4c --- /dev/null +++ b/core/data/tests/generic_struct_with_constraints_and_decorators/input.rs @@ -0,0 +1,17 @@ +#[typeshare(swift = "Equatable")] +pub struct Button { + /// Label of the button + pub label: String, + /// Accessibility label if it needed to be different than label + pub accessibility_label: Option, + /// Optional tooltips that provide extra explanation for a button + pub tooltip: Option, + /// Button action if there one + pub action: Option, + /// Icon if there is one + pub icon: Option, + /// Button state + pub state: ButtonState, + /// Button Mode + pub style: ButtonStyle, +} diff --git a/core/data/tests/generic_struct_with_constraints_and_decorators/output.swift b/core/data/tests/generic_struct_with_constraints_and_decorators/output.swift new file mode 100644 index 00000000..1d403024 --- /dev/null +++ b/core/data/tests/generic_struct_with_constraints_and_decorators/output.swift @@ -0,0 +1,28 @@ +import Foundation + +public struct Button: Codable, Equatable { + /// Label of the button + public let label: String + /// Accessibility label if it needed to be different than label + public let accessibility_label: String? + /// Optional tooltips that provide extra explanation for a button + public let tooltip: String? + /// Button action if there one + public let action: T? + /// Icon if there is one + public let icon: Icon? + /// Button state + public let state: ButtonState + /// Button Mode + public let style: ButtonStyle + + public init(label: String, accessibility_label: String?, tooltip: String?, action: T?, icon: Icon?, state: ButtonState, style: ButtonStyle) { + self.label = label + self.accessibility_label = accessibility_label + self.tooltip = tooltip + self.action = action + self.icon = icon + self.state = state + self.style = style + } +} diff --git a/core/src/language/swift.rs b/core/src/language/swift.rs index 83df5f17..f2fba105 100644 --- a/core/src/language/swift.rs +++ b/core/src/language/swift.rs @@ -278,7 +278,13 @@ impl Language for Swift { .for_each(|d| decs.push(d.clone())); } - let generic_constraint_string = default_generic_constraints.get_constraints().join(" & "); + // Include any decorator constraints on the generic types. + let generic_constraint_string = default_generic_constraints + .get_constraints() + .chain(decs.iter()) + .collect::>() + .into_iter() + .join(" & "); writeln!( w, diff --git a/core/tests/snapshot_tests.rs b/core/tests/snapshot_tests.rs index 6e119bfa..346d3b89 100644 --- a/core/tests/snapshot_tests.rs +++ b/core/tests/snapshot_tests.rs @@ -569,4 +569,5 @@ tests! { go ]; can_generate_anonymous_struct_with_skipped_fields: [swift, kotlin, scala, typescript, go]; + generic_struct_with_constraints_and_decorators: [swift]; } From 99504997947ddb3c41c8b2e22bf44d6feaf49a98 Mon Sep 17 00:00:00 2001 From: Darrell Roberts Date: Tue, 4 Jun 2024 17:00:00 -0400 Subject: [PATCH 02/17] Update generics for writing enum --- core/src/language/swift.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/language/swift.rs b/core/src/language/swift.rs index f2fba105..5b9983f2 100644 --- a/core/src/language/swift.rs +++ b/core/src/language/swift.rs @@ -452,10 +452,15 @@ impl Language for Swift { self.write_comments(w, 0, &shared.comments)?; let indirect = if shared.is_recursive { "indirect " } else { "" }; + let generic_constraint_string = self .default_generic_constraints .get_constraints() + .chain(decs.iter()) + .collect::>() + .into_iter() .join(" & "); + writeln!( w, "public {}enum {}{}: {} {{", From c5f47d523411583d07fb9555edb853c0332141ee Mon Sep 17 00:00:00 2001 From: Darrell Roberts Date: Wed, 5 Jun 2024 13:06:49 -0400 Subject: [PATCH 03/17] allow defining generic constraints via "#[typeshare(swiftGenericConstraints = "Constraint")]" --- .../input.rs | 9 +- .../output.swift | 6 +- core/src/language/kotlin.rs | 8 +- core/src/language/swift.rs | 138 +++++++++++------- core/src/parser.rs | 60 +++++--- core/src/rust_types.rs | 8 +- 6 files changed, 145 insertions(+), 84 deletions(-) diff --git a/core/data/tests/generic_struct_with_constraints_and_decorators/input.rs b/core/data/tests/generic_struct_with_constraints_and_decorators/input.rs index 962e5e4c..0ce78a36 100644 --- a/core/data/tests/generic_struct_with_constraints_and_decorators/input.rs +++ b/core/data/tests/generic_struct_with_constraints_and_decorators/input.rs @@ -1,5 +1,8 @@ -#[typeshare(swift = "Equatable")] -pub struct Button { +#[typeshare( + swift = "Equatable, Identifiable", + swiftGenericConstraints = "T: Equatable & SomeThingElse, V: Equatable" +)] +pub struct Button { /// Label of the button pub label: String, /// Accessibility label if it needed to be different than label @@ -9,7 +12,7 @@ pub struct Button { /// Button action if there one pub action: Option, /// Icon if there is one - pub icon: Option, + pub icon: Option, /// Button state pub state: ButtonState, /// Button Mode diff --git a/core/data/tests/generic_struct_with_constraints_and_decorators/output.swift b/core/data/tests/generic_struct_with_constraints_and_decorators/output.swift index 1d403024..46834b7e 100644 --- a/core/data/tests/generic_struct_with_constraints_and_decorators/output.swift +++ b/core/data/tests/generic_struct_with_constraints_and_decorators/output.swift @@ -1,6 +1,6 @@ import Foundation -public struct Button: Codable, Equatable { +public struct Button: Codable, Equatable, Identifiable { /// Label of the button public let label: String /// Accessibility label if it needed to be different than label @@ -10,13 +10,13 @@ public struct Button: Codable, Equatable { /// Button action if there one public let action: T? /// Icon if there is one - public let icon: Icon? + public let icon: V? /// Button state public let state: ButtonState /// Button Mode public let style: ButtonStyle - public init(label: String, accessibility_label: String?, tooltip: String?, action: T?, icon: Icon?, state: ButtonState, style: ButtonStyle) { + public init(label: String, accessibility_label: String?, tooltip: String?, action: T?, icon: V?, state: ButtonState, style: ButtonStyle) { self.label = label self.accessibility_label = accessibility_label self.tooltip = tooltip diff --git a/core/src/language/kotlin.rs b/core/src/language/kotlin.rs index ebd9f68e..1cbb0e7d 100644 --- a/core/src/language/kotlin.rs +++ b/core/src/language/kotlin.rs @@ -1,6 +1,6 @@ use super::{Language, ScopedCrateTypes}; use crate::language::SupportedLanguage; -use crate::parser::{remove_dash_from_identifier, ParsedData}; +use crate::parser::{remove_dash_from_identifier, ParsedData, KOTLIN_DECORATOR}; use crate::rust_types::{RustTypeFormatError, SpecialRustType}; use crate::{ rename::RenameExt, @@ -169,7 +169,11 @@ impl Language for Kotlin { writeln!(w)?; } write!(w, ")")?; - if let Some(kotlin_decorators) = rs.decorators.get(&SupportedLanguage::Kotlin) { + if let Some(kotlin_decorators) = rs + .decorators + .get(&SupportedLanguage::Kotlin) + .and_then(|d| d.get(KOTLIN_DECORATOR)) + { let redacted_decorator = String::from(REDACTED_TO_STRING); if kotlin_decorators.iter().any(|d| *d == redacted_decorator) { writeln!(w, " {{")?; diff --git a/core/src/language/swift.rs b/core/src/language/swift.rs index 5b9983f2..85c6dbed 100644 --- a/core/src/language/swift.rs +++ b/core/src/language/swift.rs @@ -1,9 +1,13 @@ use crate::{ language::{Language, SupportedLanguage}, - parser::{remove_dash_from_identifier, ParsedData}, + parser::{ + remove_dash_from_identifier, ParsedData, SWIFT_DECORATOR, + SWIFT_GENERIC_CONSTRAINTS_DECORATOR, + }, rename::RenameExt, rust_types::{ - RustEnum, RustEnumVariant, RustStruct, RustTypeAlias, RustTypeFormatError, SpecialRustType, + DecoratorMap, RustEnum, RustEnumVariant, RustStruct, RustTypeAlias, RustTypeFormatError, + SpecialRustType, }, GenerationError, }; @@ -266,9 +270,13 @@ impl Language for Swift { // If there are no decorators found for this struct, still write `Codable` and default decorators for structs let mut decs = self.get_default_decorators(); - let default_generic_constraints = self.default_generic_constraints.clone(); + // let default_generic_constraints = self.default_generic_constraints.clone(); // Check if this struct's decorators contains swift in the hashmap - if let Some(swift_decs) = rs.decorators.get(&SupportedLanguage::Swift) { + if let Some(swift_decs) = rs + .decorators + .get(&SupportedLanguage::Swift) + .and_then(|d| d.get(SWIFT_DECORATOR)) + { // For reach item in the received decorators in the typeshared struct add it to the original vector // this avoids duplicated of `Codable` without needing to `.sort()` then `.dedup()` // Note: the list received from `rs.decorators` is already deduped @@ -278,32 +286,14 @@ impl Language for Swift { .for_each(|d| decs.push(d.clone())); } - // Include any decorator constraints on the generic types. - let generic_constraint_string = default_generic_constraints - .get_constraints() - .chain(decs.iter()) - .collect::>() - .into_iter() - .join(" & "); + let generic_names_and_constraints = + self.generic_constraints(&rs.decorators, &rs.generic_types); writeln!( w, - "public struct {}{}: {} {{", - type_name, + "public struct {type_name}{}: {} {{", (!rs.generic_types.is_empty()) - .then(|| format!( - "<{}>", - rs.generic_types - .iter() - .map(|t| format!( - "{}{}", - t, - (!generic_constraint_string.is_empty()) - .then(|| format!(": {}", generic_constraint_string)) - .unwrap_or_default() - )) - .join(", ") - )) + .then(|| format!("<{generic_names_and_constraints}>",)) .unwrap_or_default(), decs.join(", ") )?; @@ -414,7 +404,12 @@ impl Language for Swift { .for_each(|dec| decs.push(dec)); // Check if this enum's decorators contains swift in the hashmap - if let Some(swift_decs) = e.shared().decorators.get(&SupportedLanguage::Swift) { + if let Some(swift_decs) = e + .shared() + .decorators + .get(&SupportedLanguage::Swift) + .and_then(|d| d.get(SWIFT_DECORATOR)) + { // Add any decorators from the typeshared enum decs.extend( // Note: `swift_decs` is already deduped @@ -453,34 +448,14 @@ impl Language for Swift { self.write_comments(w, 0, &shared.comments)?; let indirect = if shared.is_recursive { "indirect " } else { "" }; - let generic_constraint_string = self - .default_generic_constraints - .get_constraints() - .chain(decs.iter()) - .collect::>() - .into_iter() - .join(" & "); + let generic_names_and_constraints = + self.generic_constraints(&e.shared().decorators, &e.shared().generic_types); writeln!( w, - "public {}enum {}{}: {} {{", - indirect, - enum_name, + "public {indirect}enum {enum_name}{}: {} {{", (!e.shared().generic_types.is_empty()) - .then(|| format!( - "<{}>", - e.shared() - .generic_types - .iter() - .map(|t| format!( - "{}{}", - t, - (!generic_constraint_string.is_empty()) - .then(|| format!(": {}", generic_constraint_string)) - .unwrap_or_default() - )) - .join(", ") - )) + .then(|| format!("<{generic_names_and_constraints}>",)) .unwrap_or_default(), decs.join(", ") )?; @@ -812,6 +787,67 @@ impl Swift { writeln!(w, "public struct CodableVoid: {} {{}}", decs.join(", ")) } + + /// Build the generic constraints output. This checks for the `swiftGenericConstraints` typeshare attribute and combines + /// it with the `default_generic_constraints` configuration. If no `swiftGenericConstraints` is defined then we just use + /// `default_generic_constraints`. + fn generic_constraints<'a>( + &'a self, + decorator_map: &'a DecoratorMap, + generic_types: &'a [String], + ) -> String { + let swift_generic_contraints_annotated = decorator_map + .get(&SupportedLanguage::Swift) + .and_then(|d| d.get(SWIFT_GENERIC_CONSTRAINTS_DECORATOR)) + .map(|generic_constraints| { + generic_constraints + .iter() + .flat_map(|generic_constraint| { + let mut gen_name_val_iter = generic_constraint.split(':'); + let generic_name = gen_name_val_iter.next()?; + let generic_name_constraints = gen_name_val_iter + .next()? + .split('&') + .map(|s| s.trim()) + .collect::>(); + Some((generic_name, generic_name_constraints)) + }) + .map(|(gen_name, mut gen_constraints)| { + gen_constraints.extend( + self.default_generic_constraints + .get_constraints() + .map(|s| s.as_str()), + ); + (gen_name, gen_constraints) + }) + .collect::>() + }) + .unwrap_or_default(); + + if swift_generic_contraints_annotated.is_empty() { + generic_types + .iter() + .map(|type_name| { + format!( + "{type_name}: {}", + self.default_generic_constraints + .get_constraints() + .join(" & ") + ) + }) + .join(", ") + } else { + swift_generic_contraints_annotated + .iter() + .map(|(type_name, constraints)| { + format!( + "{type_name}: {constraints}", + constraints = constraints.iter().join(" & ") + ) + }) + .join(", ") + } + } } fn swift_keyword_aware_rename(name: &str) -> String { diff --git a/core/src/parser.rs b/core/src/parser.rs index a0be4cef..e13bee8a 100644 --- a/core/src/parser.rs +++ b/core/src/parser.rs @@ -2,8 +2,9 @@ use crate::{ language::{CrateName, SupportedLanguage}, rename::RenameExt, rust_types::{ - FieldDecorator, Id, RustEnum, RustEnumShared, RustEnumVariant, RustEnumVariantShared, - RustField, RustItem, RustStruct, RustType, RustTypeAlias, RustTypeParseError, + DecoratorMap, FieldDecorator, Id, RustEnum, RustEnumShared, RustEnumVariant, + RustEnumVariantShared, RustField, RustItem, RustStruct, RustType, RustTypeAlias, + RustTypeParseError, }, visitors::{ImportedType, TypeShareVisitor}, }; @@ -23,6 +24,16 @@ use thiserror::Error; const TYPESHARE: &str = "typeshare"; const SERDE: &str = "serde"; +/// The typeshare decorator name attribute. +pub type DecoratorName = &'static str; + +/// The typeshare attribute for swift type constraints. +pub const SWIFT_DECORATOR: DecoratorName = "swift"; +/// The typeshare attribute for kotlin. +pub const KOTLIN_DECORATOR: DecoratorName = "kotlin"; +/// The typeshare attribute for swift generic constraints. +pub const SWIFT_GENERIC_CONSTRAINTS_DECORATOR: DecoratorName = "swiftGenericConstraints"; + /// Errors that can occur while parsing Rust source input. #[derive(Debug, Error)] #[allow(missing_docs)] @@ -676,30 +687,33 @@ fn literal_to_string(lit: &syn::Lit) -> Option { /// Checks the struct or enum for decorators like `#[typeshare(swift = "Codable, Equatable")]` /// Takes a slice of `syn::Attribute`, returns a `HashMap>`, where `language` is `SupportedLanguage` and `decoration_words` is `String` -fn get_decorators(attrs: &[syn::Attribute]) -> HashMap> { +fn get_decorators(attrs: &[syn::Attribute]) -> DecoratorMap { // The resulting HashMap, Key is the language, and the value is a vector of decorators words that will be put onto structures - let mut out: HashMap> = HashMap::new(); - - for value in get_name_value_meta_items(attrs, "swift", TYPESHARE) { - let decorators: Vec = value.split(',').map(|s| s.trim().to_string()).collect(); - - // lastly, get the entry in the hashmap output and extend the value, or insert what we have already found - let decs = out.entry(SupportedLanguage::Swift).or_default(); - decs.extend(decorators); - // Sorting so all the added decorators will be after the normal ([`String`], `Codable`) in alphabetical order - decs.sort_unstable(); - decs.dedup(); //removing any duplicates just in case - } + let mut out: DecoratorMap = HashMap::new(); + + let add_decorators = + |name: &'static str, supported_lang: SupportedLanguage, map: &mut DecoratorMap| { + for value in get_name_value_meta_items(attrs, name, TYPESHARE) { + let constraints = || value.split(',').map(|s| s.trim().to_string()); + map.entry(supported_lang) + .and_modify(|dec_map| { + dec_map + .entry(name) + .and_modify(|dec_vals| dec_vals.extend(constraints())) + .or_insert(constraints().collect()); + }) + .or_insert(HashMap::from_iter([(name, constraints().collect())])); + } + }; - for value in get_name_value_meta_items(attrs, "kotlin", TYPESHARE) { - let decorators: Vec = value.split(',').map(|s| s.trim().to_string()).collect(); - let decs = out.entry(SupportedLanguage::Kotlin).or_default(); - decs.extend(decorators); - decs.sort_unstable(); - decs.dedup(); //removing any duplicates just in case - } + add_decorators(SWIFT_DECORATOR, SupportedLanguage::Swift, &mut out); + add_decorators( + SWIFT_GENERIC_CONSTRAINTS_DECORATOR, + SupportedLanguage::Swift, + &mut out, + ); + add_decorators(KOTLIN_DECORATOR, SupportedLanguage::Kotlin, &mut out); - //return our hashmap mapping of language -> Vec out } diff --git a/core/src/rust_types.rs b/core/src/rust_types.rs index 0d0afa75..88932509 100644 --- a/core/src/rust_types.rs +++ b/core/src/rust_types.rs @@ -6,8 +6,12 @@ use syn::{Expr, ExprLit, Lit, TypeArray, TypeSlice}; use thiserror::Error; use crate::language::SupportedLanguage; +use crate::parser::DecoratorName; use crate::visitors::accept_type; +/// Type level typeshare attributes are mapped by target language and a mapping of attribute. +pub type DecoratorMap = HashMap>>; + /// 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 { @@ -43,7 +47,7 @@ pub struct RustStruct { /// so we need to collect them here. pub comments: Vec, /// Attributes that exist for this struct. - pub decorators: HashMap>, + pub decorators: DecoratorMap, } /// Rust type alias. @@ -566,7 +570,7 @@ pub struct RustEnumShared { /// Decorators applied to the enum for generation in other languages /// /// Example: `#[typeshare(swift = "Equatable, Comparable, Hashable")]`. - pub decorators: HashMap>, + pub decorators: DecoratorMap, /// True if this enum references itself in any field of any variant /// Swift needs the special keyword `indirect` for this case pub is_recursive: bool, From ee8c538f48f8e549df82b1604bdff26553a2f4f7 Mon Sep 17 00:00:00 2001 From: Darrell Roberts Date: Wed, 5 Jun 2024 15:01:48 -0400 Subject: [PATCH 04/17] always make CodableVoid Equatable --- core/data/tests/can_generate_generic_struct/output.swift | 2 +- core/data/tests/can_handle_unit_type/output.swift | 2 +- core/src/language/swift.rs | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/core/data/tests/can_generate_generic_struct/output.swift b/core/data/tests/can_generate_generic_struct/output.swift index 0b8c79ae..f2a1005b 100644 --- a/core/data/tests/can_generate_generic_struct/output.swift +++ b/core/data/tests/can_generate_generic_struct/output.swift @@ -88,4 +88,4 @@ public enum CoreEnumUsingGenericStruct: Codable { } /// () isn't codable, so we use this instead to represent Rust's unit type -public struct CodableVoid: Codable {} +public struct CodableVoid: Codable, Equatable {} diff --git a/core/data/tests/can_handle_unit_type/output.swift b/core/data/tests/can_handle_unit_type/output.swift index c7407e99..41de9b9a 100644 --- a/core/data/tests/can_handle_unit_type/output.swift +++ b/core/data/tests/can_handle_unit_type/output.swift @@ -46,4 +46,4 @@ public enum EnumHasVoidType: Codable { } /// () isn't codable, so we use this instead to represent Rust's unit type -public struct CodableVoid: Codable {} +public struct CodableVoid: Codable, Equatable {} diff --git a/core/src/language/swift.rs b/core/src/language/swift.rs index 85c6dbed..d45008dc 100644 --- a/core/src/language/swift.rs +++ b/core/src/language/swift.rs @@ -780,6 +780,9 @@ impl Swift { let mut decs = self.get_default_decorators(); + // Unit type can be used as generic impl constrained to Equatable. + decs.push("Equatable".into()); + // If there are no decorators found for this struct, still write `Codable` and default decorators for structs if !decs.contains(&CODABLE.to_string()) { decs.push(CODABLE.to_string()); From e4970b2430193a2e368c8f43d89366f2bfa26e16 Mon Sep 17 00:00:00 2001 From: Darrell Roberts Date: Wed, 5 Jun 2024 17:27:26 -0400 Subject: [PATCH 05/17] No need for decorator language --- core/src/language/kotlin.rs | 8 ++--- core/src/language/swift.rs | 21 +++---------- core/src/parser.rs | 62 +++++++++++++++++++------------------ core/src/rust_types.rs | 4 +-- 4 files changed, 40 insertions(+), 55 deletions(-) diff --git a/core/src/language/kotlin.rs b/core/src/language/kotlin.rs index 1cbb0e7d..3a026f77 100644 --- a/core/src/language/kotlin.rs +++ b/core/src/language/kotlin.rs @@ -1,6 +1,6 @@ use super::{Language, ScopedCrateTypes}; use crate::language::SupportedLanguage; -use crate::parser::{remove_dash_from_identifier, ParsedData, KOTLIN_DECORATOR}; +use crate::parser::{remove_dash_from_identifier, DecoratorKind, ParsedData}; use crate::rust_types::{RustTypeFormatError, SpecialRustType}; use crate::{ rename::RenameExt, @@ -169,11 +169,7 @@ impl Language for Kotlin { writeln!(w)?; } write!(w, ")")?; - if let Some(kotlin_decorators) = rs - .decorators - .get(&SupportedLanguage::Kotlin) - .and_then(|d| d.get(KOTLIN_DECORATOR)) - { + if let Some(kotlin_decorators) = rs.decorators.get(&DecoratorKind::Kotlin) { let redacted_decorator = String::from(REDACTED_TO_STRING); if kotlin_decorators.iter().any(|d| *d == redacted_decorator) { writeln!(w, " {{")?; diff --git a/core/src/language/swift.rs b/core/src/language/swift.rs index d45008dc..490ce442 100644 --- a/core/src/language/swift.rs +++ b/core/src/language/swift.rs @@ -1,9 +1,6 @@ use crate::{ language::{Language, SupportedLanguage}, - parser::{ - remove_dash_from_identifier, ParsedData, SWIFT_DECORATOR, - SWIFT_GENERIC_CONSTRAINTS_DECORATOR, - }, + parser::{remove_dash_from_identifier, DecoratorKind, ParsedData}, rename::RenameExt, rust_types::{ DecoratorMap, RustEnum, RustEnumVariant, RustStruct, RustTypeAlias, RustTypeFormatError, @@ -272,11 +269,7 @@ impl Language for Swift { // let default_generic_constraints = self.default_generic_constraints.clone(); // Check if this struct's decorators contains swift in the hashmap - if let Some(swift_decs) = rs - .decorators - .get(&SupportedLanguage::Swift) - .and_then(|d| d.get(SWIFT_DECORATOR)) - { + if let Some(swift_decs) = rs.decorators.get(&DecoratorKind::Swift) { // For reach item in the received decorators in the typeshared struct add it to the original vector // this avoids duplicated of `Codable` without needing to `.sort()` then `.dedup()` // Note: the list received from `rs.decorators` is already deduped @@ -404,12 +397,7 @@ impl Language for Swift { .for_each(|dec| decs.push(dec)); // Check if this enum's decorators contains swift in the hashmap - if let Some(swift_decs) = e - .shared() - .decorators - .get(&SupportedLanguage::Swift) - .and_then(|d| d.get(SWIFT_DECORATOR)) - { + if let Some(swift_decs) = e.shared().decorators.get(&DecoratorKind::Swift) { // Add any decorators from the typeshared enum decs.extend( // Note: `swift_decs` is already deduped @@ -800,8 +788,7 @@ impl Swift { generic_types: &'a [String], ) -> String { let swift_generic_contraints_annotated = decorator_map - .get(&SupportedLanguage::Swift) - .and_then(|d| d.get(SWIFT_GENERIC_CONSTRAINTS_DECORATOR)) + .get(&DecoratorKind::SwiftGenericConstraints) .map(|generic_constraints| { generic_constraints .iter() diff --git a/core/src/parser.rs b/core/src/parser.rs index e13bee8a..e8c8131e 100644 --- a/core/src/parser.rs +++ b/core/src/parser.rs @@ -24,15 +24,27 @@ use thiserror::Error; const TYPESHARE: &str = "typeshare"; const SERDE: &str = "serde"; -/// The typeshare decorator name attribute. -pub type DecoratorName = &'static str; +/// Supported typeshare type level decorator attributes. +#[derive(PartialEq, Eq, Debug, Hash, Copy, Clone)] +pub enum DecoratorKind { + /// The typeshare attribute for swift type constraints "swift" + Swift, + /// The typeshare attribute for swift generic constraints "swiftGenericConstraints" + SwiftGenericConstraints, + /// The typeshare attribute for kotlin "kotlin" + Kotlin, +} -/// The typeshare attribute for swift type constraints. -pub const SWIFT_DECORATOR: DecoratorName = "swift"; -/// The typeshare attribute for kotlin. -pub const KOTLIN_DECORATOR: DecoratorName = "kotlin"; -/// The typeshare attribute for swift generic constraints. -pub const SWIFT_GENERIC_CONSTRAINTS_DECORATOR: DecoratorName = "swiftGenericConstraints"; +impl DecoratorKind { + /// This decorator as a str. + fn as_str(&self) -> &str { + match self { + DecoratorKind::Swift => "swift", + DecoratorKind::SwiftGenericConstraints => "swiftGenericConstraints", + DecoratorKind::Kotlin => "kotlin", + } + } +} /// Errors that can occur while parsing Rust source input. #[derive(Debug, Error)] @@ -691,28 +703,18 @@ fn get_decorators(attrs: &[syn::Attribute]) -> DecoratorMap { // The resulting HashMap, Key is the language, and the value is a vector of decorators words that will be put onto structures let mut out: DecoratorMap = HashMap::new(); - let add_decorators = - |name: &'static str, supported_lang: SupportedLanguage, map: &mut DecoratorMap| { - for value in get_name_value_meta_items(attrs, name, TYPESHARE) { - let constraints = || value.split(',').map(|s| s.trim().to_string()); - map.entry(supported_lang) - .and_modify(|dec_map| { - dec_map - .entry(name) - .and_modify(|dec_vals| dec_vals.extend(constraints())) - .or_insert(constraints().collect()); - }) - .or_insert(HashMap::from_iter([(name, constraints().collect())])); - } - }; - - add_decorators(SWIFT_DECORATOR, SupportedLanguage::Swift, &mut out); - add_decorators( - SWIFT_GENERIC_CONSTRAINTS_DECORATOR, - SupportedLanguage::Swift, - &mut out, - ); - add_decorators(KOTLIN_DECORATOR, SupportedLanguage::Kotlin, &mut out); + let add_decorators = |name: DecoratorKind, map: &mut DecoratorMap| { + for value in get_name_value_meta_items(attrs, name.as_str(), TYPESHARE) { + let constraints = || value.split(',').map(|s| s.trim().to_string()); + map.entry(name) + .and_modify(|dec_vals| dec_vals.extend(constraints())) + .or_insert(constraints().collect()); + } + }; + + add_decorators(DecoratorKind::Swift, &mut out); + add_decorators(DecoratorKind::SwiftGenericConstraints, &mut out); + add_decorators(DecoratorKind::Kotlin, &mut out); out } diff --git a/core/src/rust_types.rs b/core/src/rust_types.rs index 88932509..146849b0 100644 --- a/core/src/rust_types.rs +++ b/core/src/rust_types.rs @@ -6,11 +6,11 @@ use syn::{Expr, ExprLit, Lit, TypeArray, TypeSlice}; use thiserror::Error; use crate::language::SupportedLanguage; -use crate::parser::DecoratorName; +use crate::parser::DecoratorKind; use crate::visitors::accept_type; /// Type level typeshare attributes are mapped by target language and a mapping of attribute. -pub type DecoratorMap = HashMap>>; +pub type DecoratorMap = HashMap>; /// 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)] From a38d41dd38a78ee39eb05041a9faa05ada4d43db Mon Sep 17 00:00:00 2001 From: Darrell Roberts Date: Wed, 5 Jun 2024 17:36:52 -0400 Subject: [PATCH 06/17] refactor --- core/src/parser.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/core/src/parser.rs b/core/src/parser.rs index e8c8131e..49fac29c 100644 --- a/core/src/parser.rs +++ b/core/src/parser.rs @@ -701,22 +701,23 @@ fn literal_to_string(lit: &syn::Lit) -> Option { /// Takes a slice of `syn::Attribute`, returns a `HashMap>`, where `language` is `SupportedLanguage` and `decoration_words` is `String` fn get_decorators(attrs: &[syn::Attribute]) -> DecoratorMap { // The resulting HashMap, Key is the language, and the value is a vector of decorators words that will be put onto structures - let mut out: DecoratorMap = HashMap::new(); - - let add_decorators = |name: DecoratorKind, map: &mut DecoratorMap| { - for value in get_name_value_meta_items(attrs, name.as_str(), TYPESHARE) { + let mut decorator_map: DecoratorMap = HashMap::new(); + + for decorator_kind in [ + DecoratorKind::Swift, + DecoratorKind::SwiftGenericConstraints, + DecoratorKind::Kotlin, + ] { + for value in get_name_value_meta_items(attrs, decorator_kind.as_str(), TYPESHARE) { let constraints = || value.split(',').map(|s| s.trim().to_string()); - map.entry(name) + decorator_map + .entry(decorator_kind) .and_modify(|dec_vals| dec_vals.extend(constraints())) .or_insert(constraints().collect()); } - }; - - add_decorators(DecoratorKind::Swift, &mut out); - add_decorators(DecoratorKind::SwiftGenericConstraints, &mut out); - add_decorators(DecoratorKind::Kotlin, &mut out); + } - out + decorator_map } fn get_tag_key(attrs: &[syn::Attribute]) -> Option { From cc894cda1603fcbef2aded4e40795af508d036a0 Mon Sep 17 00:00:00 2001 From: Darrell Roberts Date: Wed, 5 Jun 2024 17:38:49 -0400 Subject: [PATCH 07/17] Update code comment for changed implementation --- core/src/parser.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/parser.rs b/core/src/parser.rs index 49fac29c..7e2d549a 100644 --- a/core/src/parser.rs +++ b/core/src/parser.rs @@ -698,9 +698,8 @@ fn literal_to_string(lit: &syn::Lit) -> Option { } /// Checks the struct or enum for decorators like `#[typeshare(swift = "Codable, Equatable")]` -/// Takes a slice of `syn::Attribute`, returns a `HashMap>`, where `language` is `SupportedLanguage` and `decoration_words` is `String` +/// Takes a slice of `syn::Attribute`, returns a [`DecoratorMap`]. fn get_decorators(attrs: &[syn::Attribute]) -> DecoratorMap { - // The resulting HashMap, Key is the language, and the value is a vector of decorators words that will be put onto structures let mut decorator_map: DecoratorMap = HashMap::new(); for decorator_kind in [ From 42c9e704cbe710300177b31c05836479949a4e98 Mon Sep 17 00:00:00 2001 From: Darrell Roberts Date: Wed, 5 Jun 2024 21:30:16 -0400 Subject: [PATCH 08/17] Iterate over type generics and combine swiftGenericConstraints generics and non annotated generics --- core/src/language/swift.rs | 61 +++++++++++++++----------------------- 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/core/src/language/swift.rs b/core/src/language/swift.rs index 490ce442..0797b4d9 100644 --- a/core/src/language/swift.rs +++ b/core/src/language/swift.rs @@ -8,7 +8,7 @@ use crate::{ }, GenerationError, }; -use itertools::Itertools; +use itertools::{Either, Itertools}; use joinery::JoinableIterator; use lazy_format::lazy_format; use std::{ @@ -113,8 +113,8 @@ impl GenericConstraints { } } /// Get an iterator over all constraints. - pub fn get_constraints(&self) -> impl Iterator { - self.constraints.iter() + pub fn get_constraints(&self) -> impl Iterator { + self.constraints.iter().map(|s| s.as_str()) } fn split_constraints(constraints: String) -> Vec { @@ -792,51 +792,38 @@ impl Swift { .map(|generic_constraints| { generic_constraints .iter() - .flat_map(|generic_constraint| { + .filter_map(|generic_constraint| { let mut gen_name_val_iter = generic_constraint.split(':'); let generic_name = gen_name_val_iter.next()?; - let generic_name_constraints = gen_name_val_iter + let mut generic_name_constraints = gen_name_val_iter .next()? .split('&') .map(|s| s.trim()) .collect::>(); + // Merge default generic constraints with annotated constraints. + generic_name_constraints + .extend(self.default_generic_constraints.get_constraints()); Some((generic_name, generic_name_constraints)) }) - .map(|(gen_name, mut gen_constraints)| { - gen_constraints.extend( - self.default_generic_constraints - .get_constraints() - .map(|s| s.as_str()), - ); - (gen_name, gen_constraints) - }) - .collect::>() + .collect::>() }) .unwrap_or_default(); - if swift_generic_contraints_annotated.is_empty() { - generic_types - .iter() - .map(|type_name| { - format!( - "{type_name}: {}", - self.default_generic_constraints - .get_constraints() - .join(" & ") - ) - }) - .join(", ") - } else { - swift_generic_contraints_annotated - .iter() - .map(|(type_name, constraints)| { - format!( - "{type_name}: {constraints}", - constraints = constraints.iter().join(" & ") - ) - }) - .join(", ") - } + generic_types + .iter() + .map( + |type_name| match swift_generic_contraints_annotated.get(type_name.as_str()) { + // Use constraints from swiftGenericConstraints decorator. + Some(constraints) => (type_name, Either::Left(constraints.iter().copied())), + // Use the default generic constraints if it is not part of a swiftGenericConstraints decorator. + None => ( + type_name, + Either::Right(self.default_generic_constraints.get_constraints()), + ), + }, + ) + .map(|(type_name, mut constraints)| format!("{type_name}: {}", constraints.join(" & "))) + .join(", ") } } From becbe5f738346a1318dc2150244076d0c339047c Mon Sep 17 00:00:00 2001 From: Darrell Roberts Date: Thu, 6 Jun 2024 07:49:08 -0400 Subject: [PATCH 09/17] Update test to include generic not mentioned in swiftGenericConstraints --- .../input.rs | 10 ++++++++-- .../output.swift | 14 +++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/core/data/tests/generic_struct_with_constraints_and_decorators/input.rs b/core/data/tests/generic_struct_with_constraints_and_decorators/input.rs index 0ce78a36..ecc31e75 100644 --- a/core/data/tests/generic_struct_with_constraints_and_decorators/input.rs +++ b/core/data/tests/generic_struct_with_constraints_and_decorators/input.rs @@ -2,9 +2,9 @@ swift = "Equatable, Identifiable", swiftGenericConstraints = "T: Equatable & SomeThingElse, V: Equatable" )] -pub struct Button { +pub struct Button { /// Label of the button - pub label: String, + pub label: I, /// Accessibility label if it needed to be different than label pub accessibility_label: Option, /// Optional tooltips that provide extra explanation for a button @@ -18,3 +18,9 @@ pub struct Button { /// Button Mode pub style: ButtonStyle, } + +#[typeshare] +pub struct ButtonState; + +#[typeshare] +pub struct ButtonStyle; diff --git a/core/data/tests/generic_struct_with_constraints_and_decorators/output.swift b/core/data/tests/generic_struct_with_constraints_and_decorators/output.swift index 46834b7e..607f7875 100644 --- a/core/data/tests/generic_struct_with_constraints_and_decorators/output.swift +++ b/core/data/tests/generic_struct_with_constraints_and_decorators/output.swift @@ -1,8 +1,16 @@ import Foundation -public struct Button: Codable, Equatable, Identifiable { +public struct ButtonState: Codable { + public init() {} +} + +public struct ButtonStyle: Codable { + public init() {} +} + +public struct Button: Codable, Equatable, Identifiable { /// Label of the button - public let label: String + public let label: I /// Accessibility label if it needed to be different than label public let accessibility_label: String? /// Optional tooltips that provide extra explanation for a button @@ -16,7 +24,7 @@ public struct Button Date: Thu, 6 Jun 2024 08:15:22 -0400 Subject: [PATCH 10/17] Refactor --- core/src/parser.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/core/src/parser.rs b/core/src/parser.rs index 7e2d549a..0f6b0c2a 100644 --- a/core/src/parser.rs +++ b/core/src/parser.rs @@ -700,7 +700,7 @@ fn literal_to_string(lit: &syn::Lit) -> Option { /// Checks the struct or enum for decorators like `#[typeshare(swift = "Codable, Equatable")]` /// Takes a slice of `syn::Attribute`, returns a [`DecoratorMap`]. fn get_decorators(attrs: &[syn::Attribute]) -> DecoratorMap { - let mut decorator_map: DecoratorMap = HashMap::new(); + let mut decorator_map: DecoratorMap = DecoratorMap::new(); for decorator_kind in [ DecoratorKind::Swift, @@ -708,11 +708,10 @@ fn get_decorators(attrs: &[syn::Attribute]) -> DecoratorMap { DecoratorKind::Kotlin, ] { for value in get_name_value_meta_items(attrs, decorator_kind.as_str(), TYPESHARE) { - let constraints = || value.split(',').map(|s| s.trim().to_string()); decorator_map .entry(decorator_kind) - .and_modify(|dec_vals| dec_vals.extend(constraints())) - .or_insert(constraints().collect()); + .or_default() + .extend(value.split(',').map(|s| s.trim().to_string())); } } From f1a3b87aa4f904e3677ec87187170b7c8ef08bd2 Mon Sep 17 00:00:00 2001 From: Darrell Roberts Date: Thu, 6 Jun 2024 09:02:28 -0400 Subject: [PATCH 11/17] Make constraints on CodableVoid a config option. Remove excessive allocations. --- cli/src/config.rs | 2 ++ cli/src/main.rs | 1 + core/src/language/swift.rs | 35 +++++++++++++++++------------------ core/tests/snapshot_tests.rs | 5 +++-- 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/cli/src/config.rs b/cli/src/config.rs index eac8c5eb..caac7e54 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -34,6 +34,8 @@ pub struct SwiftParams { pub type_mappings: HashMap, pub default_decorators: Vec, pub default_generic_constraints: Vec, + /// The contraints to apply to `CodableVoid`. + pub codablevoid_constraints: Vec, } #[derive(Default, Serialize, Deserialize, PartialEq, Eq, Debug)] diff --git a/cli/src/main.rs b/cli/src/main.rs index c914f396..981c23a3 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -146,6 +146,7 @@ fn language( config.swift.default_generic_constraints, ), multi_file, + codablevoid_constraints: config.swift.codablevoid_constraints, ..Default::default() }), SupportedLanguage::Kotlin => Box::new(Kotlin { diff --git a/core/src/language/swift.rs b/core/src/language/swift.rs index 0797b4d9..84b5a242 100644 --- a/core/src/language/swift.rs +++ b/core/src/language/swift.rs @@ -151,6 +151,8 @@ pub struct Swift { pub no_version_header: bool, /// Are we generating mutliple modules? pub multi_file: bool, + /// The contraints to apply to `CodableVoid`. + pub codablevoid_constraints: Vec, } impl Language for Swift { @@ -276,7 +278,7 @@ impl Language for Swift { swift_decs .iter() .filter(|d| d.as_str() != CODABLE) - .for_each(|d| decs.push(d.clone())); + .for_each(|d| decs.push(d)); } let generic_names_and_constraints = @@ -387,14 +389,11 @@ impl Language for Swift { fn write_enum(&mut self, w: &mut dyn Write, e: &RustEnum) -> io::Result<()> { /// Determines the decorators needed for an enum given an array of decorators /// that should always be present - fn determine_decorators(always_present: &[String], e: &RustEnum) -> Vec { - let mut decs = vec![]; + fn determine_decorators<'a>(always_present: &'a [&str], e: &'a RustEnum) -> Vec<&'a str> { + let mut decs: Vec<&str> = vec![]; // Add the decorators that should always be present - always_present - .iter() - .cloned() - .for_each(|dec| decs.push(dec)); + decs.extend(always_present); // Check if this enum's decorators contains swift in the hashmap if let Some(swift_decs) = e.shared().decorators.get(&DecoratorKind::Swift) { @@ -404,8 +403,8 @@ impl Language for Swift { swift_decs .iter() // Avoids needing to sort / dedup - .filter(|d| !always_present.contains(d)) - .map(|d| d.to_owned()), + .filter(|d| !always_present.contains(&d.as_str())) + .map(|d| d.as_str()), ); } @@ -417,13 +416,13 @@ impl Language for Swift { swift_keyword_aware_rename(&format!("{}{}", self.prefix, shared.id.renamed)); let always_present = match e { RustEnum::Unit(_) => { - let mut always_present = vec!["String".into()]; + let mut always_present = vec!["String"]; always_present.append(&mut self.get_default_decorators()); always_present } RustEnum::Algebraic { .. } => self.get_default_decorators(), }; - let decs = determine_decorators(&always_present, e); + let decs = determine_decorators(&always_present, e).join(", "); // Make a suitable name for an anonymous struct enum variant let make_anonymous_struct_name = |variant_name: &str| format!("{}{}Inner", shared.id.renamed, variant_name); @@ -445,7 +444,7 @@ impl Language for Swift { (!e.shared().generic_types.is_empty()) .then(|| format!("<{generic_names_and_constraints}>",)) .unwrap_or_default(), - decs.join(", ") + decs )?; let coding_keys_info = self.write_enum_variants(w, e, make_anonymous_struct_name)?; @@ -745,9 +744,9 @@ impl Swift { } impl Swift { - fn get_default_decorators(&self) -> Vec { - let mut decs: Vec = vec![CODABLE.to_string()]; - decs.extend(self.default_decorators.iter().cloned()); + fn get_default_decorators(&self) -> Vec<&str> { + let mut decs = vec![CODABLE]; + decs.extend(self.default_decorators.iter().map(|s| s.as_str())); decs } @@ -769,11 +768,11 @@ impl Swift { let mut decs = self.get_default_decorators(); // Unit type can be used as generic impl constrained to Equatable. - decs.push("Equatable".into()); + decs.extend(self.codablevoid_constraints.iter().map(|s| s.as_str())); // If there are no decorators found for this struct, still write `Codable` and default decorators for structs - if !decs.contains(&CODABLE.to_string()) { - decs.push(CODABLE.to_string()); + if !decs.contains(&CODABLE) { + decs.push(CODABLE); } writeln!(w, "public struct CodableVoid: {} {{}}", decs.join(", ")) diff --git a/core/tests/snapshot_tests.rs b/core/tests/snapshot_tests.rs index 346d3b89..3bca4114 100644 --- a/core/tests/snapshot_tests.rs +++ b/core/tests/snapshot_tests.rs @@ -383,6 +383,7 @@ tests! { can_generate_generic_struct: [ swift { prefix: "Core".into(), + codablevoid_constraints: vec!["Equatable".into()] }, kotlin, scala, @@ -542,7 +543,7 @@ tests! { generate_types_with_keywords: [swift]; // TODO: how is this different from generates_empty_structs_and_initializers? use_correct_decoded_variable_name: [swift, kotlin, scala, typescript, go]; - can_handle_unit_type: [swift, kotlin, scala, typescript, go]; + can_handle_unit_type: [swift { codablevoid_constraints: vec!["Equatable".into()]} , kotlin, scala, typescript, go]; //3 tests for adding decorators to enums and structs const_enum_decorator: [ swift{ prefix: "OP".to_string(), } ]; @@ -569,5 +570,5 @@ tests! { go ]; can_generate_anonymous_struct_with_skipped_fields: [swift, kotlin, scala, typescript, go]; - generic_struct_with_constraints_and_decorators: [swift]; + generic_struct_with_constraints_and_decorators: [swift { codablevoid_constraints: vec!["Equatable".into()]}]; } From b9a22aec65a7ea39cec6bf540ad25e31b54a0df2 Mon Sep 17 00:00:00 2001 From: Darrell Roberts Date: Thu, 6 Jun 2024 09:33:33 -0400 Subject: [PATCH 12/17] reduce allocations --- core/src/language/swift.rs | 87 ++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/core/src/language/swift.rs b/core/src/language/swift.rs index 84b5a242..c0c7c3c4 100644 --- a/core/src/language/swift.rs +++ b/core/src/language/swift.rs @@ -267,19 +267,23 @@ impl Language for Swift { let type_name = swift_keyword_aware_rename(&format!("{}{}", self.prefix, rs.id.renamed)); // If there are no decorators found for this struct, still write `Codable` and default decorators for structs - let mut decs = self.get_default_decorators(); - - // let default_generic_constraints = self.default_generic_constraints.clone(); // Check if this struct's decorators contains swift in the hashmap - if let Some(swift_decs) = rs.decorators.get(&DecoratorKind::Swift) { + let decs = if let Some(swift_decs) = rs.decorators.get(&DecoratorKind::Swift) { // For reach item in the received decorators in the typeshared struct add it to the original vector // this avoids duplicated of `Codable` without needing to `.sort()` then `.dedup()` // Note: the list received from `rs.decorators` is already deduped - swift_decs - .iter() - .filter(|d| d.as_str() != CODABLE) - .for_each(|d| decs.push(d)); + Either::Left( + self.get_default_decorators().chain( + swift_decs + .iter() + .filter(|d| d.as_str() != CODABLE) + .map(|s| s.as_str()), + ), + ) + } else { + Either::Right(self.get_default_decorators()) } + .join(", "); let generic_names_and_constraints = self.generic_constraints(&rs.decorators, &rs.generic_types); @@ -290,7 +294,7 @@ impl Language for Swift { (!rs.generic_types.is_empty()) .then(|| format!("<{generic_names_and_constraints}>",)) .unwrap_or_default(), - decs.join(", ") + decs )?; for f in &rs.fields { @@ -389,40 +393,39 @@ impl Language for Swift { fn write_enum(&mut self, w: &mut dyn Write, e: &RustEnum) -> io::Result<()> { /// Determines the decorators needed for an enum given an array of decorators /// that should always be present - fn determine_decorators<'a>(always_present: &'a [&str], e: &'a RustEnum) -> Vec<&'a str> { - let mut decs: Vec<&str> = vec![]; - - // Add the decorators that should always be present - decs.extend(always_present); - - // Check if this enum's decorators contains swift in the hashmap - if let Some(swift_decs) = e.shared().decorators.get(&DecoratorKind::Swift) { - // Add any decorators from the typeshared enum - decs.extend( + fn determine_decorators<'a>( + always_present: &'a [&str], + e: &'a RustEnum, + ) -> impl Iterator { + always_present.iter().copied().chain( + if let Some(swift_decs) = e.shared().decorators.get(&DecoratorKind::Swift) { + // Add any decorators from the typeshared enum // Note: `swift_decs` is already deduped - swift_decs - .iter() - // Avoids needing to sort / dedup - .filter(|d| !always_present.contains(&d.as_str())) - .map(|d| d.as_str()), - ); - } - - decs + Either::Left( + swift_decs + .iter() + .map(|s| s.as_str()) + // Avoids needing to sort / dedup + .filter(|d| !always_present.contains(d)), + ) + } else { + Either::Right(std::iter::empty()) + }, + ) } let shared = e.shared(); let enum_name = swift_keyword_aware_rename(&format!("{}{}", self.prefix, shared.id.renamed)); let always_present = match e { - RustEnum::Unit(_) => { - let mut always_present = vec!["String"]; - always_present.append(&mut self.get_default_decorators()); - always_present - } - RustEnum::Algebraic { .. } => self.get_default_decorators(), + RustEnum::Unit(_) => ["String"] + .into_iter() + .chain(self.get_default_decorators()) + .collect::>(), + RustEnum::Algebraic { .. } => self.get_default_decorators().collect::>(), }; let decs = determine_decorators(&always_present, e).join(", "); + // Make a suitable name for an anonymous struct enum variant let make_anonymous_struct_name = |variant_name: &str| format!("{}{}Inner", shared.id.renamed, variant_name); @@ -744,10 +747,10 @@ impl Swift { } impl Swift { - fn get_default_decorators(&self) -> Vec<&str> { - let mut decs = vec![CODABLE]; - decs.extend(self.default_decorators.iter().map(|s| s.as_str())); - decs + fn get_default_decorators(&self) -> impl Iterator { + [CODABLE] + .into_iter() + .chain(self.default_decorators.iter().map(|s| s.as_str())) } /// When using mulitple file generation we write this into a separate module vs at the @@ -765,10 +768,10 @@ impl Swift { r"/// () isn't codable, so we use this instead to represent Rust's unit type" )?; - let mut decs = self.get_default_decorators(); - - // Unit type can be used as generic impl constrained to Equatable. - decs.extend(self.codablevoid_constraints.iter().map(|s| s.as_str())); + let mut decs = self + .get_default_decorators() + .chain(self.codablevoid_constraints.iter().map(|s| s.as_str())) + .collect::>(); // If there are no decorators found for this struct, still write `Codable` and default decorators for structs if !decs.contains(&CODABLE) { From 8ab96794592ccb5ac537880de364c37cc74d528b Mon Sep 17 00:00:00 2001 From: Darrell Roberts Date: Thu, 6 Jun 2024 16:55:55 -0400 Subject: [PATCH 13/17] Reduce allocations --- core/src/language/swift.rs | 69 ++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 22 deletions(-) diff --git a/core/src/language/swift.rs b/core/src/language/swift.rs index c0c7c3c4..32998de1 100644 --- a/core/src/language/swift.rs +++ b/core/src/language/swift.rs @@ -12,6 +12,7 @@ use itertools::{Either, Itertools}; use joinery::JoinableIterator; use lazy_format::lazy_format; use std::{ + borrow::Cow, collections::{BTreeSet, HashMap}, fs::File, io::{self, Write}, @@ -241,7 +242,8 @@ impl Language for Swift { self.write_comments(w, 0, &ty.comments)?; let swift_prefix = &self.prefix; - let type_name = swift_keyword_aware_rename(&format!("{}{}", swift_prefix, ty.id.renamed)); + let type_name = + swift_keyword_aware_rename(Cow::Owned(format!("{}{}", swift_prefix, ty.id.renamed))); writeln!( w, @@ -264,7 +266,8 @@ impl Language for Swift { writeln!(w)?; self.write_comments(w, 0, &rs.comments)?; - let type_name = swift_keyword_aware_rename(&format!("{}{}", self.prefix, rs.id.renamed)); + let type_name = + swift_keyword_aware_rename(Cow::Owned(format!("{}{}", self.prefix, rs.id.renamed))); // If there are no decorators found for this struct, still write `Codable` and default decorators for structs // Check if this struct's decorators contains swift in the hashmap @@ -307,7 +310,9 @@ impl Language for Swift { if f.id.renamed.chars().any(|c| c == '-') { coding_keys.push(format!( r##"{} = "{}""##, - remove_dash_from_identifier(&swift_keyword_aware_rename(&f.id.renamed)), + remove_dash_from_identifier( + swift_keyword_aware_rename(Cow::Borrowed(&f.id.renamed)).as_ref() + ), &f.id.renamed )); @@ -315,9 +320,9 @@ impl Language for Swift { // situation like this should_write_coding_keys = true; } else { - coding_keys.push(remove_dash_from_identifier(&swift_keyword_aware_rename( - &f.id.renamed, - ))); + coding_keys.push(remove_dash_from_identifier( + swift_keyword_aware_rename(Cow::Borrowed(&f.id.renamed)).as_ref(), + )); } let case_type: String = match f.type_override(SupportedLanguage::Swift) { @@ -330,7 +335,9 @@ impl Language for Swift { writeln!( w, "\tpublic let {}: {}{}", - remove_dash_from_identifier(&swift_keyword_aware_rename(&f.id.renamed)), + remove_dash_from_identifier( + swift_keyword_aware_rename(Cow::Borrowed(&f.id.renamed)).as_ref() + ), case_type, (f.has_default && !f.ty.is_optional()) .then_some("?") @@ -378,7 +385,9 @@ impl Language for Swift { w, "\n\t\tself.{} = {}", remove_dash_from_identifier(&f.id.renamed), - remove_dash_from_identifier(&swift_keyword_aware_rename(&f.id.renamed)) + remove_dash_from_identifier( + swift_keyword_aware_rename(Cow::Borrowed(&f.id.renamed)).as_ref() + ) )?; } if !rs.fields.is_empty() { @@ -416,7 +425,7 @@ impl Language for Swift { let shared = e.shared(); let enum_name = - swift_keyword_aware_rename(&format!("{}{}", self.prefix, shared.id.renamed)); + swift_keyword_aware_rename(Cow::Owned(format!("{}{}", self.prefix, shared.id.renamed))); let always_present = match e { RustEnum::Unit(_) => ["String"] .into_iter() @@ -551,13 +560,17 @@ impl Swift { self.write_comments(w, 1, &v.shared().comments)?; if v.shared().id.renamed == variant_name { // We don't need to handle any renaming - writeln!(w, "\tcase {}", &swift_keyword_aware_rename(&variant_name))?; + writeln!( + w, + "\tcase {}", + &swift_keyword_aware_rename(Cow::Borrowed(&variant_name)) + )?; } else { // We do need to handle renaming writeln!( w, "\tcase {} = {:?}", - swift_keyword_aware_rename(&variant_name), + swift_keyword_aware_rename(Cow::Borrowed(&variant_name)), &v.shared().id.renamed )?; } @@ -590,16 +603,20 @@ impl Swift { }; coding_keys.push(if variant_name == v.shared().id.renamed { - swift_keyword_aware_rename(&variant_name) + swift_keyword_aware_rename(Cow::Borrowed(&variant_name)).into_owned() } else { format!( r##"{} = "{}""##, - swift_keyword_aware_rename(&variant_name), + swift_keyword_aware_rename(Cow::Borrowed(&variant_name)), &v.shared().id.renamed ) }); - write!(w, "\tcase {}", swift_keyword_aware_rename(&variant_name))?; + write!( + w, + "\tcase {}", + swift_keyword_aware_rename(Cow::Borrowed(&variant_name)) + )?; match v { RustEnumVariant::Unit(_) => { @@ -616,7 +633,8 @@ impl Swift { case .{case_name}: try container.encode(CodingKeys.{case_name}, forKey: .{tag_key})", tag_key = tag_key, - case_name = swift_keyword_aware_rename(&variant_name), + case_name = + swift_keyword_aware_rename(Cow::Borrowed(&variant_name)), )); } RustEnumVariant::Tuple { ty, .. } => { @@ -624,7 +642,11 @@ impl Swift { let case_type = self .format_type(ty, e.shared().generic_types.as_slice()) .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - write!(w, "({})", swift_keyword_aware_rename(&case_type))?; + write!( + w, + "({})", + swift_keyword_aware_rename(Cow::Borrowed(&case_type)) + )?; if content_optional { decoding_cases.push(format!( @@ -639,7 +661,8 @@ impl Swift { return }}", content_key = content_key, - case_type = swift_keyword_aware_rename(&case_type), + case_type = + swift_keyword_aware_rename(Cow::Borrowed(&case_type)), case_name = &variant_name )) } else { @@ -651,7 +674,8 @@ impl Swift { return }}", content_key = content_key, - case_type = swift_keyword_aware_rename(&case_type), + case_type = + swift_keyword_aware_rename(Cow::Borrowed(&case_type)), case_name = &variant_name, )); } @@ -829,9 +853,10 @@ impl Swift { } } -fn swift_keyword_aware_rename(name: &str) -> String { - if SWIFT_KEYWORDS.contains(&name) { - return format!("`{}`", name); +fn swift_keyword_aware_rename(name: Cow) -> Cow { + if SWIFT_KEYWORDS.contains(&name.as_ref()) { + Cow::Owned(format!("`{name}`")) + } else { + name } - name.to_string() } From db5dcae62f2e6daabbfa045be01a3b15ec0307e8 Mon Sep 17 00:00:00 2001 From: Darrell Roberts Date: Thu, 6 Jun 2024 18:31:35 -0400 Subject: [PATCH 14/17] Cleanup --- core/src/language/swift.rs | 62 +++++++++++++------------------------- 1 file changed, 21 insertions(+), 41 deletions(-) diff --git a/core/src/language/swift.rs b/core/src/language/swift.rs index 32998de1..984ebe14 100644 --- a/core/src/language/swift.rs +++ b/core/src/language/swift.rs @@ -242,8 +242,7 @@ impl Language for Swift { self.write_comments(w, 0, &ty.comments)?; let swift_prefix = &self.prefix; - let type_name = - swift_keyword_aware_rename(Cow::Owned(format!("{}{}", swift_prefix, ty.id.renamed))); + let type_name = swift_keyword_aware_rename(format!("{}{}", swift_prefix, ty.id.renamed)); writeln!( w, @@ -266,8 +265,7 @@ impl Language for Swift { writeln!(w)?; self.write_comments(w, 0, &rs.comments)?; - let type_name = - swift_keyword_aware_rename(Cow::Owned(format!("{}{}", self.prefix, rs.id.renamed))); + let type_name = swift_keyword_aware_rename(format!("{}{}", self.prefix, rs.id.renamed)); // If there are no decorators found for this struct, still write `Codable` and default decorators for structs // Check if this struct's decorators contains swift in the hashmap @@ -310,9 +308,7 @@ impl Language for Swift { if f.id.renamed.chars().any(|c| c == '-') { coding_keys.push(format!( r##"{} = "{}""##, - remove_dash_from_identifier( - swift_keyword_aware_rename(Cow::Borrowed(&f.id.renamed)).as_ref() - ), + remove_dash_from_identifier(swift_keyword_aware_rename(&f.id.renamed).as_ref()), &f.id.renamed )); @@ -321,7 +317,7 @@ impl Language for Swift { should_write_coding_keys = true; } else { coding_keys.push(remove_dash_from_identifier( - swift_keyword_aware_rename(Cow::Borrowed(&f.id.renamed)).as_ref(), + swift_keyword_aware_rename(&f.id.renamed).as_ref(), )); } @@ -335,9 +331,7 @@ impl Language for Swift { writeln!( w, "\tpublic let {}: {}{}", - remove_dash_from_identifier( - swift_keyword_aware_rename(Cow::Borrowed(&f.id.renamed)).as_ref() - ), + remove_dash_from_identifier(swift_keyword_aware_rename(&f.id.renamed).as_ref()), case_type, (f.has_default && !f.ty.is_optional()) .then_some("?") @@ -385,9 +379,7 @@ impl Language for Swift { w, "\n\t\tself.{} = {}", remove_dash_from_identifier(&f.id.renamed), - remove_dash_from_identifier( - swift_keyword_aware_rename(Cow::Borrowed(&f.id.renamed)).as_ref() - ) + remove_dash_from_identifier(swift_keyword_aware_rename(&f.id.renamed).as_ref()) )?; } if !rs.fields.is_empty() { @@ -424,8 +416,7 @@ impl Language for Swift { } let shared = e.shared(); - let enum_name = - swift_keyword_aware_rename(Cow::Owned(format!("{}{}", self.prefix, shared.id.renamed))); + let enum_name = swift_keyword_aware_rename(format!("{}{}", self.prefix, shared.id.renamed)); let always_present = match e { RustEnum::Unit(_) => ["String"] .into_iter() @@ -560,17 +551,13 @@ impl Swift { self.write_comments(w, 1, &v.shared().comments)?; if v.shared().id.renamed == variant_name { // We don't need to handle any renaming - writeln!( - w, - "\tcase {}", - &swift_keyword_aware_rename(Cow::Borrowed(&variant_name)) - )?; + writeln!(w, "\tcase {}", &swift_keyword_aware_rename(&variant_name))?; } else { // We do need to handle renaming writeln!( w, "\tcase {} = {:?}", - swift_keyword_aware_rename(Cow::Borrowed(&variant_name)), + swift_keyword_aware_rename(&variant_name), &v.shared().id.renamed )?; } @@ -603,20 +590,16 @@ impl Swift { }; coding_keys.push(if variant_name == v.shared().id.renamed { - swift_keyword_aware_rename(Cow::Borrowed(&variant_name)).into_owned() + swift_keyword_aware_rename(&variant_name).into_owned() } else { format!( r##"{} = "{}""##, - swift_keyword_aware_rename(Cow::Borrowed(&variant_name)), + swift_keyword_aware_rename(&variant_name), &v.shared().id.renamed ) }); - write!( - w, - "\tcase {}", - swift_keyword_aware_rename(Cow::Borrowed(&variant_name)) - )?; + write!(w, "\tcase {}", swift_keyword_aware_rename(&variant_name))?; match v { RustEnumVariant::Unit(_) => { @@ -633,8 +616,7 @@ impl Swift { case .{case_name}: try container.encode(CodingKeys.{case_name}, forKey: .{tag_key})", tag_key = tag_key, - case_name = - swift_keyword_aware_rename(Cow::Borrowed(&variant_name)), + case_name = swift_keyword_aware_rename(&variant_name), )); } RustEnumVariant::Tuple { ty, .. } => { @@ -642,11 +624,7 @@ impl Swift { let case_type = self .format_type(ty, e.shared().generic_types.as_slice()) .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - write!( - w, - "({})", - swift_keyword_aware_rename(Cow::Borrowed(&case_type)) - )?; + write!(w, "({})", swift_keyword_aware_rename(&case_type))?; if content_optional { decoding_cases.push(format!( @@ -661,8 +639,7 @@ impl Swift { return }}", content_key = content_key, - case_type = - swift_keyword_aware_rename(Cow::Borrowed(&case_type)), + case_type = swift_keyword_aware_rename(&case_type), case_name = &variant_name )) } else { @@ -674,8 +651,7 @@ impl Swift { return }}", content_key = content_key, - case_type = - swift_keyword_aware_rename(Cow::Borrowed(&case_type)), + case_type = swift_keyword_aware_rename(&case_type), case_name = &variant_name, )); } @@ -853,7 +829,11 @@ impl Swift { } } -fn swift_keyword_aware_rename(name: Cow) -> Cow { +fn swift_keyword_aware_rename<'a, T>(name: T) -> Cow<'a, str> +where + T: Into>, +{ + let name = name.into(); if SWIFT_KEYWORDS.contains(&name.as_ref()) { Cow::Owned(format!("`{name}`")) } else { From 890786816fdb0dd9a756e0d1c759d487cd125b35 Mon Sep 17 00:00:00 2001 From: Charles Pierce Date: Wed, 12 Jun 2024 12:02:55 -0700 Subject: [PATCH 15/17] chore: update changelogs and bump versions for v1.10.0-beta.5 (#178) --- CHANGELOG.md | 7 ++++++- Cargo.lock | 4 ++-- cli/Cargo.toml | 4 ++-- core/Cargo.toml | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34c4bf17..722934ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Version 1.10.0-beta +## 1.10.0-beta.5 + +- Added support for Swift generic constraints via `#[typeshare(swiftGenericConstraints)]` [#174](https://github.com/1Password/typeshare/pull/174) +- Added Swift config option for defining constraints on `CodableVoid` generated type [#174](https://github.com/1Password/typeshare/pull/174) + ## 1.10.0-beta.4 Fixed a bug involving `#[typeshare(skip)]` on fields in struct variants of enums. @@ -14,7 +19,7 @@ This release brings support for multiple file generation, allowing splitting gen files when used in large projects. This can dramatically increase compilation speed of the generated files and increase maintainability. -This is a *pre-release* version which may have bugs or break compatibility. +This is a _pre-release_ version which may have bugs or break compatibility. - Multiple file output [#166](https://github.com/1Password/typeshare/pull/166) diff --git a/Cargo.lock b/Cargo.lock index 858b49ec..185310d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -610,7 +610,7 @@ dependencies = [ [[package]] name = "typeshare-cli" -version = "1.10.0-beta.4" +version = "1.10.0-beta.5" dependencies = [ "anyhow", "clap", @@ -625,7 +625,7 @@ dependencies = [ [[package]] name = "typeshare-core" -version = "1.10.0-beta.4" +version = "1.10.0-beta.5" dependencies = [ "anyhow", "cool_asserts", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 9d123591..4e551f71 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "typeshare-cli" -version = "1.10.0-beta.4" +version = "1.10.0-beta.5" edition = "2021" description = "Command Line Tool for generating language files with typeshare" license = "MIT OR Apache-2.0" @@ -22,5 +22,5 @@ once_cell = "1" rayon = "1.10" serde = { version = "1", features = ["derive"] } toml = "0.8" -typeshare-core = { path = "../core", version = "1.10.0-beta.4" } +typeshare-core = { path = "../core", version = "1.10.0-beta.5" } anyhow = "1" diff --git a/core/Cargo.toml b/core/Cargo.toml index c0d16dc5..cb937233 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "typeshare-core" -version = "1.10.0-beta.4" +version = "1.10.0-beta.5" license = "MIT OR Apache-2.0" edition = "2021" description = "The code generator used by Typeshare's command line tool" From 49e402e6d16d1f019a1d0b30d71f123ae3bab431 Mon Sep 17 00:00:00 2001 From: Darrell Roberts <33698065+darrell-roberts@users.noreply.github.com> Date: Fri, 28 Jun 2024 13:24:22 -0400 Subject: [PATCH 16/17] Allow skipping fields/variants via target_os argument (#176) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Allow passing a target_os arg for applying filter to #[cfg(target_os = "target"] that do not match Co-authored-by: René Léveillé --- cli/src/args.rs | 7 + cli/src/config.rs | 2 + cli/src/main.rs | 14 +- cli/src/parse.rs | 83 ++++-------- cli/src/writer.rs | 5 +- core/Cargo.toml | 1 + .../data/tests/excluded_by_target_os/input.rs | 34 +++++ .../tests/excluded_by_target_os/output.go | 12 ++ .../tests/excluded_by_target_os/output.kt | 17 +++ .../tests/excluded_by_target_os/output.scala | 23 ++++ .../tests/excluded_by_target_os/output.swift | 9 ++ .../tests/excluded_by_target_os/output.ts | 8 ++ .../input.rs | 7 + .../output.swift | 1 + core/src/parser.rs | 122 ++++++++++++++---- core/src/visitors.rs | 69 +++++++--- core/tests/agnostic_tests.rs | 1 + core/tests/snapshot_tests.rs | 20 ++- 18 files changed, 332 insertions(+), 103 deletions(-) create mode 100644 core/data/tests/excluded_by_target_os/input.rs create mode 100644 core/data/tests/excluded_by_target_os/output.go create mode 100644 core/data/tests/excluded_by_target_os/output.kt create mode 100644 core/data/tests/excluded_by_target_os/output.scala create mode 100644 core/data/tests/excluded_by_target_os/output.swift create mode 100644 core/data/tests/excluded_by_target_os/output.ts create mode 100644 core/data/tests/excluded_by_target_os_full_module/input.rs create mode 100644 core/data/tests/excluded_by_target_os_full_module/output.swift diff --git a/cli/src/args.rs b/cli/src/args.rs index 8bf11d01..ed73124d 100644 --- a/cli/src/args.rs +++ b/cli/src/args.rs @@ -17,6 +17,7 @@ pub const ARG_GENERATE_CONFIG: &str = "generate-config-file"; pub const ARG_OUTPUT_FILE: &str = "output-file"; pub const ARG_OUTPUT_FOLDER: &str = "output-folder"; pub const ARG_FOLLOW_LINKS: &str = "follow-links"; +pub const ARG_TARGET_OS: &str = "target_os"; #[cfg(feature = "go")] const AVAILABLE_LANGUAGES: [&str; 5] = ["kotlin", "scala", "swift", "typescript", "go"]; @@ -147,5 +148,11 @@ pub(crate) fn build_command() -> Command<'static> { .help("Directories within which to recursively find and process rust files") .required_unless(ARG_GENERATE_CONFIG) .min_values(1), + ).arg( + Arg::new(ARG_TARGET_OS) + .long("target-os") + .help("Optional restrict to target_os") + .takes_value(true) + .required(false) ) } diff --git a/cli/src/config.rs b/cli/src/config.rs index caac7e54..c3d2dc5f 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -64,6 +64,8 @@ pub(crate) struct Config { pub scala: ScalaParams, #[cfg(feature = "go")] pub go: GoParams, + #[serde(skip)] + pub target_os: Option, } pub(crate) fn store_config(config: &Config, file_path: Option<&str>) -> anyhow::Result<()> { diff --git a/cli/src/main.rs b/cli/src/main.rs index 981c23a3..0b4ee57b 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -5,12 +5,13 @@ use anyhow::{anyhow, Context}; use args::{ build_command, ARG_CONFIG_FILE_NAME, ARG_FOLLOW_LINKS, ARG_GENERATE_CONFIG, ARG_JAVA_PACKAGE, ARG_KOTLIN_PREFIX, ARG_MODULE_NAME, ARG_OUTPUT_FOLDER, ARG_SCALA_MODULE_NAME, - ARG_SCALA_PACKAGE, ARG_SWIFT_PREFIX, ARG_TYPE, + ARG_SCALA_PACKAGE, ARG_SWIFT_PREFIX, ARG_TARGET_OS, ARG_TYPE, }; use clap::ArgMatches; use config::Config; use ignore::{overrides::OverrideBuilder, types::TypesBuilder, WalkBuilder}; use parse::{all_types, parse_input, parser_inputs}; +use rayon::iter::ParallelBridge; use std::collections::HashMap; #[cfg(feature = "go")] use typeshare_core::language::Go; @@ -103,6 +104,9 @@ fn main() -> anyhow::Result<()> { } let multi_file = options.value_of(ARG_OUTPUT_FOLDER).is_some(); + + let target_os = config.target_os.clone(); + let lang = language(language_type, config, multi_file); let ignored_types = lang.ignored_reference_types(); @@ -110,10 +114,15 @@ fn main() -> anyhow::Result<()> { // a git-ignored directory to be processed, add the specific directory to // the list of directories given to typeshare when it's invoked in the // makefiles + // TODO: The `ignore` walker supports parallel walking. We should use this + // and implement a `ParallelVisitor` that builds up the mapping of parsed + // data. That way both walking and parsing are in parallel. + // https://docs.rs/ignore/latest/ignore/struct.WalkParallel.html let crate_parsed_data = parse_input( - parser_inputs(walker_builder, language_type, multi_file), + parser_inputs(walker_builder, language_type, multi_file).par_bridge(), &ignored_types, multi_file, + target_os, )?; // Collect all the types into a map of the file name they @@ -211,6 +220,7 @@ fn override_configuration(mut config: Config, options: &ArgMatches) -> Config { config.go.package = go_package.to_string(); } + config.target_os = options.value_of(ARG_TARGET_OS).map(|s| s.to_string()); config } diff --git a/cli/src/parse.rs b/cli/src/parse.rs index 8edbff11..47ed56ae 100644 --- a/cli/src/parse.rs +++ b/cli/src/parse.rs @@ -4,7 +4,6 @@ use ignore::WalkBuilder; use rayon::iter::{IntoParallelIterator, ParallelIterator}; use std::{ collections::{hash_map::Entry, HashMap}, - ops::Not, path::PathBuf, }; use typeshare_core::{ @@ -28,12 +27,12 @@ pub fn parser_inputs( walker_builder: WalkBuilder, language_type: SupportedLanguage, multi_file: bool, -) -> Vec { +) -> impl Iterator { walker_builder .build() .filter_map(Result::ok) .filter(|dir_entry| !dir_entry.path().is_dir()) - .filter_map(|dir_entry| { + .filter_map(move |dir_entry| { let crate_name = if multi_file { CrateName::find_crate_name(dir_entry.path())? } else { @@ -47,7 +46,6 @@ pub fn parser_inputs( crate_name, }) }) - .collect() } /// The output file name to write to. @@ -90,74 +88,47 @@ pub fn all_types(file_mappings: &HashMap) -> CrateTypes { /// Collect all the parsed sources into a mapping of crate name to parsed data. pub fn parse_input( - inputs: Vec, + inputs: impl ParallelIterator, ignored_types: &[&str], multi_file: bool, + target_os: Option, ) -> anyhow::Result> { inputs .into_par_iter() .try_fold( HashMap::new, - |mut results: HashMap, + |mut parsed_crates: HashMap, ParserInput { file_path, file_name, crate_name, }| { - match std::fs::read_to_string(&file_path) - .context("Failed to read input") - .and_then(|data| { - typeshare_core::parser::parse( - &data, - crate_name.clone(), - file_name.clone(), - file_path, - ignored_types, - multi_file, - ) - .context("Failed to parse") - }) - .map(|parsed_data| { - parsed_data.and_then(|parsed_data| { - is_parsed_data_empty(&parsed_data) - .not() - .then_some((crate_name, parsed_data)) - }) - })? { - Some((crate_name, parsed_data)) => { - match results.entry(crate_name) { - Entry::Occupied(mut entry) => { - entry.get_mut().add(parsed_data); - } - Entry::Vacant(entry) => { - entry.insert(parsed_data); - } - } - Ok::<_, anyhow::Error>(results) - } - None => Ok(results), + let parsed_result = typeshare_core::parser::parse( + &std::fs::read_to_string(&file_path) + .with_context(|| format!("Failed to read input: {file_name}"))?, + crate_name.clone(), + file_name.clone(), + file_path, + ignored_types, + multi_file, + target_os.clone(), + ) + .with_context(|| format!("Failed to parse: {file_name}"))?; + + if let Some(parsed_data) = parsed_result { + parsed_crates + .entry(crate_name) + .or_default() + .add(parsed_data); } + + Ok(parsed_crates) }, ) - .try_reduce(HashMap::new, |mut file_maps, mapping| { - for (crate_name, parsed_data) in mapping { - match file_maps.entry(crate_name) { - Entry::Occupied(mut e) => { - e.get_mut().add(parsed_data); - } - Entry::Vacant(e) => { - e.insert(parsed_data); - } - } + .try_reduce(HashMap::new, |mut file_maps, parsed_crates| { + for (crate_name, parsed_data) in parsed_crates { + file_maps.entry(crate_name).or_default().add(parsed_data); } Ok(file_maps) }) } - -/// Check if we have not parsed any relavent typehsared types. -fn is_parsed_data_empty(parsed_data: &ParsedData) -> bool { - parsed_data.enums.is_empty() - && parsed_data.aliases.is_empty() - && parsed_data.structs.is_empty() - && parsed_data.errors.is_empty() -} diff --git a/cli/src/writer.rs b/cli/src/writer.rs index dae179cd..4fbbc968 100644 --- a/cli/src/writer.rs +++ b/cli/src/writer.rs @@ -67,13 +67,14 @@ fn check_write_file(outfile: &PathBuf, output: Vec) -> anyhow::Result<()> { if !output.is_empty() { let out_dir = outfile .parent() - .context(format!("Could not get parent for {outfile:?}"))?; + .with_context(|| format!("Could not get parent for {outfile:?}"))?; // If the output directory doesn't already exist, create it. if !out_dir.exists() { fs::create_dir_all(out_dir).context("failed to create output directory")?; } - fs::write(outfile, output).context("failed to write output")?; + fs::write(outfile, output) + .with_context(|| format!("failed to write output: {}", outfile.to_string_lossy()))?; } Ok(()) } diff --git a/core/Cargo.toml b/core/Cargo.toml index cb937233..783c7668 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -20,3 +20,4 @@ anyhow = "1" expect-test = "1.5" once_cell = "1" cool_asserts = "2" +syn = { version = "2", features = ["full", "visit", "extra-traits"] } diff --git a/core/data/tests/excluded_by_target_os/input.rs b/core/data/tests/excluded_by_target_os/input.rs new file mode 100644 index 00000000..5fb07c65 --- /dev/null +++ b/core/data/tests/excluded_by_target_os/input.rs @@ -0,0 +1,34 @@ +#![cfg(feature = "online")] +#![allow(dead_code)] + +use std::collection::HashMap; + +#[typeshare] +pub enum TestEnum { + Variant1, + #[cfg(target_os = "ios")] + Variant2, + #[cfg(any(target_os = "ios", feature = "test"))] + Variant3, + #[cfg(all(target_os = "ios", feature = "test"))] + Variant4, + #[cfg(target_os = "android")] + Variant5, +} + +#[typeshare] +#[cfg(target_os = "ios")] +pub struct TestStruct; + +#[typeshare] +#[cfg(target_os = "ios")] +type TypeAlias = String; + +#[typeshare] +#[cfg(any(target_os = "ios", feature = "test"))] +pub enum Test {} + +#[typeshare] +#[cfg(feature = "super")] +#[cfg(target_os = "android")] +pub enum SomeEnum {} diff --git a/core/data/tests/excluded_by_target_os/output.go b/core/data/tests/excluded_by_target_os/output.go new file mode 100644 index 00000000..bc7a64ec --- /dev/null +++ b/core/data/tests/excluded_by_target_os/output.go @@ -0,0 +1,12 @@ +package proto + +import "encoding/json" + +type TestEnum string +const ( + TestEnumVariant1 TestEnum = "Variant1" + TestEnumVariant5 TestEnum = "Variant5" +) +type SomeEnum string +const ( +) diff --git a/core/data/tests/excluded_by_target_os/output.kt b/core/data/tests/excluded_by_target_os/output.kt new file mode 100644 index 00000000..73f0c614 --- /dev/null +++ b/core/data/tests/excluded_by_target_os/output.kt @@ -0,0 +1,17 @@ +package com.agilebits.onepassword + +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerialName + +@Serializable +enum class TestEnum(val string: String) { + @SerialName("Variant1") + Variant1("Variant1"), + @SerialName("Variant5") + Variant5("Variant5"), +} + +@Serializable +enum class SomeEnum(val string: String) { +} + diff --git a/core/data/tests/excluded_by_target_os/output.scala b/core/data/tests/excluded_by_target_os/output.scala new file mode 100644 index 00000000..6876c694 --- /dev/null +++ b/core/data/tests/excluded_by_target_os/output.scala @@ -0,0 +1,23 @@ +package com.agilebits + +package onepassword { + +sealed trait TestEnum { + def serialName: String +} +object TestEnum { + case object Variant1 extends TestEnum { + val serialName: String = "Variant1" + } + case object Variant5 extends TestEnum { + val serialName: String = "Variant5" + } +} + +sealed trait SomeEnum { + def serialName: String +} +object SomeEnum { +} + +} diff --git a/core/data/tests/excluded_by_target_os/output.swift b/core/data/tests/excluded_by_target_os/output.swift new file mode 100644 index 00000000..4a6c4e3a --- /dev/null +++ b/core/data/tests/excluded_by_target_os/output.swift @@ -0,0 +1,9 @@ +import Foundation + +public enum TestEnum: String, Codable { + case variant1 = "Variant1" + case variant5 = "Variant5" +} + +public enum SomeEnum: String, Codable { +} diff --git a/core/data/tests/excluded_by_target_os/output.ts b/core/data/tests/excluded_by_target_os/output.ts new file mode 100644 index 00000000..2f793b62 --- /dev/null +++ b/core/data/tests/excluded_by_target_os/output.ts @@ -0,0 +1,8 @@ +export enum TestEnum { + Variant1 = "Variant1", + Variant5 = "Variant5", +} + +export enum SomeEnum { +} + diff --git a/core/data/tests/excluded_by_target_os_full_module/input.rs b/core/data/tests/excluded_by_target_os_full_module/input.rs new file mode 100644 index 00000000..f6d27da8 --- /dev/null +++ b/core/data/tests/excluded_by_target_os_full_module/input.rs @@ -0,0 +1,7 @@ +#![cfg(feature = "online")] +#![allow(dead_code)] +#![cfg(any(target_os = "android", feature = "testing"))] +#![cfg(target_os = "wasm32")] + +#[typeshare] +pub struct IgnoredUnlessAndroid; diff --git a/core/data/tests/excluded_by_target_os_full_module/output.swift b/core/data/tests/excluded_by_target_os_full_module/output.swift new file mode 100644 index 00000000..fecc4ab4 --- /dev/null +++ b/core/data/tests/excluded_by_target_os_full_module/output.swift @@ -0,0 +1 @@ +import Foundation diff --git a/core/src/parser.rs b/core/src/parser.rs index 0f6b0c2a..b1a8edf2 100644 --- a/core/src/parser.rs +++ b/core/src/parser.rs @@ -8,6 +8,7 @@ use crate::{ }, visitors::{ImportedType, TypeShareVisitor}, }; +use itertools::Either; use proc_macro2::Ident; use std::{ collections::{BTreeSet, HashMap, HashSet}, @@ -16,7 +17,7 @@ use std::{ }; use syn::{ ext::IdentExt, parse::ParseBuffer, punctuated::Punctuated, visit::Visit, Attribute, Expr, - ExprLit, Fields, GenericParam, ItemEnum, ItemStruct, ItemType, LitStr, Meta, MetaList, + ExprLit, Fields, GenericParam, ItemEnum, ItemStruct, ItemType, Lit, LitStr, Meta, MetaList, MetaNameValue, Token, }; use thiserror::Error; @@ -127,6 +128,10 @@ impl ParsedData { self.import_types.extend(other.import_types); self.type_names.extend(other.type_names); self.errors.append(&mut other.errors); + + self.file_name = other.file_name; + self.crate_name = other.crate_name; + self.multi_file = other.multi_file; } pub(crate) fn push(&mut self, rust_thing: RustItem) { @@ -145,6 +150,14 @@ impl ParsedData { } } } + + /// If this file was skipped by the visitor. + pub fn is_empty(&self) -> bool { + self.structs.is_empty() + && self.enums.is_empty() + && self.aliases.is_empty() + && self.errors.is_empty() + } } /// Parse the given Rust source string into `ParsedData`. @@ -155,6 +168,7 @@ pub fn parse( file_path: PathBuf, ignored_types: &[&str], mult_file: bool, + target_os: Option, ) -> Result, ParseError> { // We will only produce output for files that contain the `#[typeshare]` // attribute, so this is a quick and easy performance win @@ -164,11 +178,17 @@ pub fn parse( // Parse and process the input, ensuring we parse only items marked with // `#[typeshare]` - let mut import_visitor = - TypeShareVisitor::new(crate_name, file_name, file_path, ignored_types, mult_file); + let mut import_visitor = TypeShareVisitor::new( + crate_name, + file_name, + file_path, + ignored_types, + mult_file, + target_os, + ); import_visitor.visit_file(&syn::parse_file(source_code)?); - Ok(Some(import_visitor.parsed_data())) + Ok(import_visitor.parsed_data()) } /// Parses a struct into a definition that more succinctly represents what @@ -176,7 +196,10 @@ pub fn parse( /// /// This function can currently return something other than a struct, which is a /// hack. -pub(crate) fn parse_struct(s: &ItemStruct) -> Result { +pub(crate) fn parse_struct( + s: &ItemStruct, + target_os: Option<&str>, +) -> Result { let serde_rename_all = serde_rename_all(&s.attrs); let generic_types = s @@ -207,7 +230,7 @@ pub(crate) fn parse_struct(s: &ItemStruct) -> Result { let fields = f .named .iter() - .filter(|field| !is_skipped(&field.attrs)) + .filter(|field| !is_skipped(&field.attrs, target_os)) .map(|f| { let ty = if let Some(ty) = get_field_type_override(&f.attrs) { ty.parse()? @@ -276,7 +299,7 @@ pub(crate) fn parse_struct(s: &ItemStruct) -> Result { /// /// This function can currently return something other than an enum, which is a /// hack. -pub(crate) fn parse_enum(e: &ItemEnum) -> Result { +pub(crate) fn parse_enum(e: &ItemEnum, target_os: Option<&str>) -> Result { let generic_types = e .generics .params @@ -311,8 +334,8 @@ pub(crate) fn parse_enum(e: &ItemEnum) -> Result { .variants .iter() // Filter out variants we've been told to skip - .filter(|v| !is_skipped(&v.attrs)) - .map(|v| parse_enum_variant(v, &serde_rename_all)) + .filter(|v| !is_skipped(&v.attrs, target_os)) + .map(|v| parse_enum_variant(v, &serde_rename_all, target_os)) .collect::, _>>()?; // Check if the enum references itself recursively in any of its variants @@ -374,6 +397,7 @@ pub(crate) fn parse_enum(e: &ItemEnum) -> Result { fn parse_enum_variant( v: &syn::Variant, enum_serde_rename_all: &Option, + target_os: Option<&str>, ) -> Result { let shared = RustEnumVariantShared { id: get_ident(Some(&v.ident), &v.attrs, enum_serde_rename_all), @@ -408,7 +432,7 @@ fn parse_enum_variant( fields: fields_named .named .iter() - .filter(|f| !is_skipped(&f.attrs)) + .filter(|f| !is_skipped(&f.attrs, target_os)) .map(|f| { let field_type = if let Some(ty) = get_field_type_override(&f.attrs) { ty.parse()? @@ -489,7 +513,6 @@ pub(crate) fn get_name_value_meta_items<'a>( ) -> impl Iterator + 'a { attrs.iter().flat_map(move |attr| { get_meta_items(attr, ident) - .iter() .filter_map(|arg| match arg { Meta::NameValue(name_value) if name_value.path.is_ident(name) => { expr_to_string(&name_value.value) @@ -501,15 +524,16 @@ pub(crate) fn get_name_value_meta_items<'a>( } /// Returns all arguments passed into `#[{ident}(...)]` where `{ident}` can be `serde` or `typeshare` attributes -fn get_meta_items(attr: &syn::Attribute, ident: &str) -> Vec { +#[inline(always)] +fn get_meta_items(attr: &syn::Attribute, ident: &str) -> impl Iterator { if attr.path().is_ident(ident) { - attr.parse_args_with(Punctuated::::parse_terminated) - .iter() - .flat_map(|meta| meta.iter()) - .cloned() - .collect() + Either::Left( + attr.parse_args_with(Punctuated::::parse_terminated) + .into_iter() + .flat_map(|punctuated| punctuated.into_iter()), + ) } else { - Vec::default() + Either::Right(std::iter::empty()) } } @@ -565,19 +589,69 @@ fn parse_comment_attrs(attrs: &[Attribute]) -> Vec { } // `#[typeshare(skip)]` or `#[serde(skip)]` -fn is_skipped(attrs: &[syn::Attribute]) -> bool { +fn is_skipped(attrs: &[syn::Attribute], target_os: Option<&str>) -> bool { attrs.iter().any(|attr| { get_meta_items(attr, SERDE) - .into_iter() .chain(get_meta_items(attr, TYPESHARE)) .any(|arg| matches!(arg, Meta::Path(path) if path.is_ident("skip"))) - }) + }) || target_os + .map(|target| attrs.iter().any(|attr| target_os_skip(attr, target))) + .unwrap_or(false) +} + +/// Check if we have a `target_os` cfg that dooes not match command line +/// argument `--target-os`. +#[inline] +pub(crate) fn target_os_skip(attr: &Attribute, target_os: &str) -> bool { + get_meta_items(attr, "cfg") + .find_map(|meta| match &meta { + // a single #[cfg(target_os = "target")] + Meta::NameValue(MetaNameValue { + path, + value: + Expr::Lit(ExprLit { + lit: Lit::Str(v), .. + }), + .. + }) if path.is_ident("target_os") => Some(v.value()), + // combined with any or all + // Ex: #[cfg(any(target_os = "target", feature = "test"))] + Meta::List(meta_list) + if meta_list.path.is_ident("any") || meta_list.path.is_ident("all") => + { + target_os_from_meta_list(meta_list) + } + _ => None, + }) + .map(|os| os != target_os) + .unwrap_or(false) +} + +/// Parses `target_os = "os"` value from `any` or `all` meta list. +#[inline] +fn target_os_from_meta_list(list: &MetaList) -> Option { + let name_values: Punctuated = + list.parse_args_with(Punctuated::parse_terminated).ok()?; + + name_values + .into_iter() + .find_map(|name_value| { + name_value + .path + .is_ident("target_os") + .then_some(name_value.value) + }) + .and_then(|val_expr| match val_expr { + Expr::Lit(ExprLit { + lit: Lit::Str(val), .. + }) => Some(val.value()), + _ => None, + }) } fn serde_attr(attrs: &[syn::Attribute], ident: &str) -> bool { attrs.iter().any(|attr| { get_meta_items(attr, SERDE) - .iter() .any(|arg| matches!(arg, Meta::Path(path) if path.is_ident(ident))) }) } @@ -601,14 +675,14 @@ fn get_field_decorators( attrs .iter() .flat_map(|attr| get_meta_items(attr, TYPESHARE)) - .flat_map(|meta| { + .filter_map(|meta| { if let Meta::List(list) = meta { Some(list) } else { None } }) - .flat_map(|list: MetaList| match list.path.get_ident() { + .filter_map(|list: MetaList| match list.path.get_ident() { Some(ident) if languages.contains(&ident.try_into().unwrap()) => { Some((ident.try_into().unwrap(), list)) } diff --git a/core/src/visitors.rs b/core/src/visitors.rs index 66d8dc49..3cb32ed4 100644 --- a/core/src/visitors.rs +++ b/core/src/visitors.rs @@ -2,13 +2,13 @@ use crate::{ language::CrateName, parser::{ - has_typeshare_annotation, parse_enum, parse_struct, parse_type_alias, ErrorInfo, - ParseError, ParsedData, + has_typeshare_annotation, parse_enum, parse_struct, parse_type_alias, target_os_skip, + ErrorInfo, ParseError, ParsedData, }, rust_types::{RustEnumVariant, RustItem}, }; -use std::{collections::HashSet, path::PathBuf}; -use syn::{visit::Visit, ItemUse, UseTree}; +use std::{collections::HashSet, ops::Not, path::PathBuf}; +use syn::{visit::Visit, Attribute, ItemUse, UseTree}; /// List of some popular crate names that we can ignore /// during import parsing. @@ -50,6 +50,7 @@ pub struct TypeShareVisitor<'a> { #[allow(dead_code)] file_path: PathBuf, ignored_types: &'a [&'a str], + target_os: Option, } impl<'a> TypeShareVisitor<'a> { @@ -60,25 +61,31 @@ impl<'a> TypeShareVisitor<'a> { file_path: PathBuf, ignored_types: &'a [&'a str], multi_file: bool, + target_os: Option, ) -> Self { Self { parsed_data: ParsedData::new(crate_name, file_name, multi_file), file_path, ignored_types, + target_os, } } + #[inline] /// Consume the visitor and return parsed data. - pub fn parsed_data(self) -> ParsedData { - if self.parsed_data.multi_file { - let mut s = self; - s.reconcile_referenced_types(); - s.parsed_data - } else { - self.parsed_data - } + pub fn parsed_data(self) -> Option { + self.parsed_data.is_empty().not().then(|| { + if self.parsed_data.multi_file { + let mut s = self; + s.reconcile_referenced_types(); + s.parsed_data + } else { + self.parsed_data + } + }) } + #[inline] fn collect_result(&mut self, result: Result) { match result { Ok(data) => self.parsed_data.push(data), @@ -181,6 +188,16 @@ impl<'a> TypeShareVisitor<'a> { self.parsed_data.import_types = diff; } + + /// Is this type annoted with at `#[cfg(target_os = "target")]` that does + /// not match `--target-os` argument? + #[inline(always)] + fn target_os_skipped(&self, attrs: &[Attribute]) -> bool { + self.target_os + .as_ref() + .map(|target_os| attrs.iter().any(|attr| target_os_skip(attr, target_os))) + .unwrap_or(false) + } } impl<'ast, 'a> Visit<'ast> for TypeShareVisitor<'a> { @@ -249,8 +266,8 @@ impl<'ast, 'a> Visit<'ast> for TypeShareVisitor<'a> { /// Collect rust structs. fn visit_item_struct(&mut self, i: &'ast syn::ItemStruct) { - if has_typeshare_annotation(&i.attrs) { - self.collect_result(parse_struct(i)); + if has_typeshare_annotation(&i.attrs) && !self.target_os_skipped(&i.attrs) { + self.collect_result(parse_struct(i, self.target_os.as_deref())); } syn::visit::visit_item_struct(self, i); @@ -258,8 +275,8 @@ impl<'ast, 'a> Visit<'ast> for TypeShareVisitor<'a> { /// Collect rust enums. fn visit_item_enum(&mut self, i: &'ast syn::ItemEnum) { - if has_typeshare_annotation(&i.attrs) { - self.collect_result(parse_enum(i)); + if has_typeshare_annotation(&i.attrs) && !self.target_os_skipped(&i.attrs) { + self.collect_result(parse_enum(i, self.target_os.as_deref())); } syn::visit::visit_item_enum(self, i); @@ -267,12 +284,29 @@ impl<'ast, 'a> Visit<'ast> for TypeShareVisitor<'a> { /// Collect rust type aliases. fn visit_item_type(&mut self, i: &'ast syn::ItemType) { - if has_typeshare_annotation(&i.attrs) { + if has_typeshare_annotation(&i.attrs) && !self.target_os_skipped(&i.attrs) { self.collect_result(parse_type_alias(i)); } syn::visit::visit_item_type(self, i); } + + /// Track potentially skipped modules. + fn visit_item_mod(&mut self, i: &'ast syn::ItemMod) { + if let Some(target_os) = self.target_os.as_ref() { + if i.attrs.iter().any(|attr| target_os_skip(attr, target_os)) { + println!("skip {}", i.ident); + } + }; + + syn::visit::visit_item_mod(self, i); + } + + fn visit_file(&mut self, i: &'ast syn::File) { + if !self.target_os_skipped(&i.attrs) { + syn::visit::visit_file(self, i); + } + } } /// Exclude popular crates that won't be typeshared. @@ -573,6 +607,7 @@ mod test { "file_path".into(), &[], true, + None, ); visitor.visit_file(&file); diff --git a/core/tests/agnostic_tests.rs b/core/tests/agnostic_tests.rs index a30bbf77..c299d68a 100644 --- a/core/tests/agnostic_tests.rs +++ b/core/tests/agnostic_tests.rs @@ -19,6 +19,7 @@ pub fn process_input( "file_path".into(), &[], false, + None, )? .unwrap(); diff --git a/core/tests/snapshot_tests.rs b/core/tests/snapshot_tests.rs index 3bca4114..7907de75 100644 --- a/core/tests/snapshot_tests.rs +++ b/core/tests/snapshot_tests.rs @@ -51,6 +51,7 @@ fn check( test_name: &str, file_name: impl AsRef, mut lang: Box, + target_os: Option<&str>, ) -> Result<(), anyhow::Error> { let _extension = file_name .as_ref() @@ -72,6 +73,7 @@ fn check( "file_path".into(), &[], false, + target_os.map(|s| s.to_owned()), )? .unwrap(); lang.generate_types(&mut typeshare_output, &HashMap::new(), parsed_data)?; @@ -221,6 +223,16 @@ macro_rules! language_instance { }; } +macro_rules! target_os { + ($target_os:literal) => { + Some($target_os) + }; + + () => { + None + }; +} + /// This macro removes the boilerplate involved in creating typeshare snapshot /// tests. Usage looks like: /// @@ -299,12 +311,13 @@ macro_rules! tests { })? ),+ $(,)? - ]; + ] $(target_os: $target_os:tt)?; )*) => {$( mod $test { use super::check; const TEST_NAME: &str = stringify!($test); + const TARGET_OS: Option<&str> = target_os!($($target_os)?); $( #[test] @@ -313,6 +326,7 @@ macro_rules! tests { TEST_NAME, output_file_for_ident!($language), language_instance!($language $({ $($lang_config)* })?), + TARGET_OS ) } )+ @@ -570,5 +584,7 @@ tests! { go ]; can_generate_anonymous_struct_with_skipped_fields: [swift, kotlin, scala, typescript, go]; - generic_struct_with_constraints_and_decorators: [swift { codablevoid_constraints: vec!["Equatable".into()]}]; + generic_struct_with_constraints_and_decorators: [swift { codablevoid_constraints: vec!["Equatable".into()] }]; + excluded_by_target_os: [ swift, kotlin, scala, typescript, go ] target_os: "android"; + // excluded_by_target_os_full_module: [swift] target_os: "ios"; } From 0284e32be7d993385f3ff83d8a380f1113a19c90 Mon Sep 17 00:00:00 2001 From: ComplexSpaces Date: Fri, 28 Jun 2024 13:12:48 -0500 Subject: [PATCH 17/17] chore: update changelogs and bump versions for v1.10.0-beta.6 (#179) --- CHANGELOG.md | 4 ++++ Cargo.lock | 4 ++-- cli/Cargo.toml | 4 ++-- core/Cargo.toml | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 722934ce..4e359910 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Version 1.10.0-beta +## 1.10.0-beta.6 + +- Added support for skipping fields/variants via the `target_os` argument [#176](https://github.com/1Password/typeshare/pull/176) + ## 1.10.0-beta.5 - Added support for Swift generic constraints via `#[typeshare(swiftGenericConstraints)]` [#174](https://github.com/1Password/typeshare/pull/174) diff --git a/Cargo.lock b/Cargo.lock index 185310d4..739a35e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -610,7 +610,7 @@ dependencies = [ [[package]] name = "typeshare-cli" -version = "1.10.0-beta.5" +version = "1.10.0-beta.6" dependencies = [ "anyhow", "clap", @@ -625,7 +625,7 @@ dependencies = [ [[package]] name = "typeshare-core" -version = "1.10.0-beta.5" +version = "1.10.0-beta.6" dependencies = [ "anyhow", "cool_asserts", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 4e551f71..c6ea7bc6 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "typeshare-cli" -version = "1.10.0-beta.5" +version = "1.10.0-beta.6" edition = "2021" description = "Command Line Tool for generating language files with typeshare" license = "MIT OR Apache-2.0" @@ -22,5 +22,5 @@ once_cell = "1" rayon = "1.10" serde = { version = "1", features = ["derive"] } toml = "0.8" -typeshare-core = { path = "../core", version = "1.10.0-beta.5" } +typeshare-core = { path = "../core", version = "1.10.0-beta.6" } anyhow = "1" diff --git a/core/Cargo.toml b/core/Cargo.toml index 783c7668..116a8820 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "typeshare-core" -version = "1.10.0-beta.5" +version = "1.10.0-beta.6" license = "MIT OR Apache-2.0" edition = "2021" description = "The code generator used by Typeshare's command line tool"