typeshare_model/parsed_data.rs
1use std::{
2 borrow::{Borrow, Cow},
3 cmp::Ord,
4 fmt::{self, Display},
5 path::{Component, Path},
6};
7
8use crate::decorator::DecoratorSet;
9
10/// A crate name.
11#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
12pub struct CrateName(String);
13
14impl Display for CrateName {
15 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16 write!(f, "{}", self.0)
17 }
18}
19
20impl CrateName {
21 pub const fn new(name: String) -> Self {
22 Self(name)
23 }
24
25 /// View this crate name as a string slice.
26 pub fn as_str(&self) -> &str {
27 self.0.as_str()
28 }
29
30 /// Extract the crate name from a give path to a rust source file. This is
31 /// defined as the name of the directory one level above the `src` directory
32 /// that cotains this source file, with any `-` replaced with `_`.
33 pub fn find_crate_name(path: &Path) -> Option<Self> {
34 path.components()
35 .rev()
36 // Only find paths that use normal components in the suffix. If we
37 // hit something like `..` or `C:\`, end the search immediately.
38 .take_while(|c| matches!(c, Component::Normal(_) | Component::CurDir))
39 // Skip `.` paths entirely
40 .filter_map(|c| match c {
41 Component::Normal(name) => Some(name),
42 _ => None,
43 })
44 // Find the `src` directory in our ancestors
45 .skip_while(|&name| name != "src")
46 // Find the first directory preceeding the `src` directory
47 .find(|&name| name != "src")?
48 // Convert this directory name to a string; fail if it isn't
49 // stringable
50 .to_str()
51 // Fix dashes
52 .map(|name| name.replace("-", "_"))
53 .map(CrateName)
54 }
55}
56
57impl PartialEq<str> for CrateName {
58 fn eq(&self, other: &str) -> bool {
59 self.as_str() == other
60 }
61}
62
63impl PartialEq<&str> for CrateName {
64 fn eq(&self, other: &&str) -> bool {
65 self == *other
66 }
67}
68
69/// Identifier used in Rust structs, enums, and fields. It includes the
70/// `original` name and the `renamed` value after the transformation based on `serde` attributes.
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct Id {
73 /// The original identifier name
74 pub original: TypeName,
75 /// The renamed identifier, based on serde attributes.
76 /// If there is no re-naming going on, this will be identical to
77 /// `original`.
78 pub renamed: TypeName,
79}
80
81impl std::fmt::Display for Id {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 if self.original == self.renamed {
84 write!(f, "({})", self.original)
85 } else {
86 write!(f, "({}, {})", self.original, self.renamed)
87 }
88 }
89}
90
91/// Rust struct.
92#[derive(Debug, Clone, PartialEq)]
93pub struct RustStruct {
94 /// The identifier for the struct.
95 pub id: Id,
96 /// The generic parameters that come after the struct name.
97 pub generic_types: Vec<TypeName>,
98 /// The fields of the struct.
99 pub fields: Vec<RustField>,
100 /// Comments that were in the struct source.
101 /// We copy comments over to the typeshared files,
102 /// so we need to collect them here.
103 pub comments: Vec<String>,
104 /// Attributes that exist for this struct.
105 pub decorators: DecoratorSet,
106}
107
108/// Rust type alias.
109/// ```
110/// pub struct MasterPassword(String);
111/// ```
112#[derive(Debug, Clone, PartialEq)]
113pub struct RustTypeAlias {
114 /// The identifier for the alias.
115 pub id: Id,
116 /// The generic parameters that come after the type alias name.
117 pub generic_types: Vec<TypeName>,
118 /// The type identifier that this type alias is aliasing
119 pub ty: RustType,
120 /// Comments that were in the type alias source.
121 pub comments: Vec<String>,
122}
123
124/// Rust field definition.
125#[derive(Debug, Clone, PartialEq, Eq)]
126pub struct RustField {
127 /// Identifier for the field.
128 pub id: Id,
129 /// Type of the field.
130 pub ty: RustType,
131 /// Comments that were in the original source.
132 pub comments: Vec<String>,
133 /// This will be true if the field has a `serde(default)` decorator.
134 /// Even if the field's type is not optional, we need to make it optional
135 /// for the languages we generate code for.
136 pub has_default: bool,
137 /// Language-specific decorators assigned to a given field.
138 /// The keys are language names (e.g. SupportedLanguage::TypeScript), the values are field decorators (e.g. readonly)
139 pub decorators: DecoratorSet,
140}
141
142/// A named Rust type.
143#[derive(Debug, Clone, PartialEq, Eq)]
144pub enum RustType {
145 /// A type with generic parameters. Consists of a type ID + parameters that come
146 /// after in angled brackets. Examples include:
147 /// - `SomeStruct<String>`
148 /// - `SomeEnum<u32>`
149 /// - `SomeTypeAlias<(), &str>`
150 /// However, there are some generic types that are considered to be _special_. These
151 /// include `Vec<T>` `HashMap<K, V>`, and `Option<T>`, which are part of `SpecialRustType` instead
152 /// of `RustType::Generic`.
153 ///
154 /// If a generic type is type-mapped via `typeshare.toml`, the generic parameters will be dropped automatically.
155 Generic {
156 #[allow(missing_docs)]
157 id: TypeName,
158 #[allow(missing_docs)]
159 parameters: Vec<RustType>,
160 },
161 /// A type that requires a special transformation to its respective language. This includes
162 /// many core types, like string types, basic container types, numbers, and other primitives.
163 Special(SpecialRustType),
164 /// A type with no generic parameters that is not considered a **special** type. This includes
165 /// all user-generated types and some types from the standard library or third-party crates.
166 /// However, these types can still be transformed as part of the type-map in `typeshare.toml`.
167 Simple {
168 #[allow(missing_docs)]
169 id: TypeName,
170 },
171}
172
173/// A special rust type that needs a manual type conversion
174#[derive(Debug, Clone, PartialEq, Eq)]
175#[non_exhaustive]
176pub enum SpecialRustType {
177 /// Represents `Vec<T>` from the standard library
178 Vec(Box<RustType>),
179 /// Represents `[T; N]` from the standard library
180 Array(Box<RustType>, usize),
181 /// Represents `&[T]` from the standard library
182 Slice(Box<RustType>),
183 /// Represents `HashMap<K, V>` from the standard library
184 HashMap(Box<RustType>, Box<RustType>),
185 /// Represents `Option<T>` from the standard library
186 Option(Box<RustType>),
187 /// Represents `()`
188 Unit,
189 /// Represents `String` from the standard library
190 String,
191 /// Represents `char`
192 Char,
193 /// Represents `i8`
194 I8,
195 /// Represents `i16`
196 I16,
197 /// Represents `i32`
198 I32,
199 /// Represents `i64`
200 I64,
201 /// Represents `u8`
202 U8,
203 /// Represents `u16`
204 U16,
205 /// Represents `u32`
206 U32,
207 /// Represents `u64`
208 U64,
209 /// Represents `isize`
210 ISize,
211 /// Represents `usize`
212 USize,
213 /// Represents `bool`
214 Bool,
215 /// Represents `f32`
216 F32,
217 /// Represents `f64`
218 F64,
219 /// Represents `I54` from `typeshare::I54`
220 I54,
221 /// Represents `U53` from `typeshare::U53`
222 U53,
223}
224
225impl RustType {
226 /// Check if a type contains a type with an ID that matches `ty`.
227 /// For example, `Box<String>` contains the types `Box` and `String`. Similarly,
228 /// `Vec<Option<HashMap<String, Url>>>` contains the types `Vec`, `Option`, `HashMap`,
229 /// `String`, and `Url`.
230 pub fn contains_type(&self, ty: &TypeName) -> bool {
231 match &self {
232 Self::Simple { id } => id == ty,
233 Self::Generic { id, parameters } => {
234 id == ty || parameters.iter().any(|p| p.contains_type(ty))
235 }
236 Self::Special(special) => special.contains_type(ty),
237 }
238 }
239
240 /// Get the ID (AKA name) of the type.
241 pub fn id(&self) -> &TypeName {
242 match &self {
243 Self::Simple { id } | Self::Generic { id, .. } => id,
244 Self::Special(special) => special.id(),
245 }
246 }
247 /// Check if the type is `Option<T>`
248 pub fn is_optional(&self) -> bool {
249 matches!(self, Self::Special(SpecialRustType::Option(_)))
250 }
251
252 /// Check if the type is `Option<Option<T>>`
253 pub fn is_double_optional(&self) -> bool {
254 match &self {
255 RustType::Special(SpecialRustType::Option(t)) => {
256 matches!(t.as_ref(), RustType::Special(SpecialRustType::Option(_)))
257 }
258 _ => false,
259 }
260 }
261 /// Check if the type is `Vec<T>`
262 pub fn is_vec(&self) -> bool {
263 matches!(self, Self::Special(SpecialRustType::Vec(_)))
264 }
265 /// Check if the type is `HashMap<K, V>`
266 pub fn is_hash_map(&self) -> bool {
267 matches!(self, Self::Special(SpecialRustType::HashMap(_, _)))
268 }
269
270 /// Get the generic parameters for this type. Returns an empty iterator if there are none.
271 /// For example, `Vec<String>`'s generic parameters would be `[String]`.
272 /// Meanwhile, `HashMap<i64, u32>`'s generic parameters would be `[i64, u32]`.
273 /// Finally, a type like `String` would have no generic parameters.
274 pub fn parameters(&self) -> Box<dyn Iterator<Item = &Self> + '_> {
275 match &self {
276 Self::Simple { .. } => Box::new(std::iter::empty()),
277 Self::Generic { parameters, .. } => Box::new(parameters.iter()),
278 Self::Special(special) => special.parameters(),
279 }
280 }
281
282 // /// Yield all the type names including nested generic types.
283 // pub fn all_reference_type_names(&self) -> impl Iterator<Item = &'_ str> + '_ {
284 // RustRefTypeIter {
285 // ty: Some(self),
286 // parameters: Vec::new(),
287 // }
288 // .filter(|s| accept_type(s))
289 // }
290}
291
292impl SpecialRustType {
293 /// Check if this type is equivalent to or contains `ty` in one of its generic parameters.
294 pub fn contains_type(&self, ty: &TypeName) -> bool {
295 match self {
296 Self::Vec(rty) | Self::Array(rty, _) | Self::Slice(rty) | Self::Option(rty) => {
297 rty.contains_type(ty)
298 }
299 Self::HashMap(rty1, rty2) => rty1.contains_type(ty) || rty2.contains_type(ty),
300 Self::Unit
301 | Self::String
302 | Self::Char
303 | Self::I8
304 | Self::I16
305 | Self::I32
306 | Self::I64
307 | Self::U8
308 | Self::U16
309 | Self::U32
310 | Self::U64
311 | Self::ISize
312 | Self::USize
313 | Self::Bool
314 | Self::F32
315 | Self::F64
316 | Self::I54
317 | Self::U53 => ty == self.id(),
318 }
319 }
320
321 /// Returns the Rust identifier for this special type.
322 pub const fn id(&self) -> &'static TypeName {
323 // Helper macro to handle the tedium of repeating the `const` block
324 // in each match arm
325 macro_rules! match_block {
326 {
327 match $this:ident {
328 $($pattern:pat => $out:literal,)*
329 }
330 } => {
331 match $this {
332 $($pattern => const {&TypeName(Cow::Borrowed($out))},)*
333 }
334 }
335 }
336
337 // TODO: I suspect there are bugs related to strings like `[]` being
338 // returned from this function (non-identifier strings) but it seems
339 // to work fine so I'll leave it for now.
340 match_block! {
341 match self {
342 Self::Unit => "()",
343 Self::F64 => "f64",
344 Self::F32 => "f32",
345 Self::Vec(_) => "Vec",
346 Self::Array(_, _) => "[]",
347 Self::Slice(_) => "&[]",
348 Self::Option(_) => "Option",
349 Self::HashMap(_, _) => "HashMap",
350 Self::String => "String",
351 Self::Char => "char",
352 Self::Bool => "bool",
353 Self::I8 => "i8",
354 Self::I16 => "i16",
355 Self::I32 => "i32",
356 Self::I64 => "i64",
357 Self::U8 => "u8",
358 Self::U16 => "u16",
359 Self::U32 => "u32",
360 Self::U64 => "u64",
361 Self::ISize => "isize",
362 Self::USize => "usize",
363 Self::U53 => "U53",
364 Self::I54 => "I54",
365 }
366 }
367 }
368
369 /// Iterate over the generic parameters for this type. Returns an empty iterator
370 /// if there are none.
371 pub fn parameters(&self) -> Box<dyn Iterator<Item = &RustType> + '_> {
372 match &self {
373 Self::Vec(rtype) | Self::Array(rtype, _) | Self::Slice(rtype) | Self::Option(rtype) => {
374 Box::new(std::iter::once(rtype.as_ref()))
375 }
376 Self::HashMap(rtype1, rtype2) => {
377 Box::new([rtype1.as_ref(), rtype2.as_ref()].into_iter())
378 }
379 Self::Unit
380 | Self::String
381 | Self::Char
382 | Self::I8
383 | Self::I16
384 | Self::I32
385 | Self::I64
386 | Self::U8
387 | Self::U16
388 | Self::U32
389 | Self::U64
390 | Self::ISize
391 | Self::USize
392 | Self::Bool
393 | Self::F32
394 | Self::F64
395 | Self::I54
396 | Self::U53 => Box::new(std::iter::empty()),
397 }
398 }
399}
400
401/// Parsed information about a Rust enum definition
402#[derive(Debug, Clone, PartialEq)]
403pub enum RustEnum {
404 /// A unit enum
405 ///
406 /// An example of such an enum:
407 ///
408 /// ```
409 /// enum UnitEnum {
410 /// Variant,
411 /// AnotherVariant,
412 /// Yay,
413 /// }
414 /// ```
415 Unit {
416 /// Shared context for this enum
417 shared: RustEnumShared,
418
419 /// All of the variants for this enum. This is a Unit enum, so all
420 /// of these variants have only unit data available.
421 unit_variants: Vec<RustEnumVariantShared>,
422 },
423
424 /// An algebraic enum
425 ///
426 /// An example of such an enum:
427 ///
428 /// ```
429 /// struct AssociatedData { /* ... */ }
430 ///
431 /// enum AlgebraicEnum {
432 /// UnitVariant,
433 /// TupleVariant(AssociatedData),
434 /// AnonymousStruct {
435 /// field: String,
436 /// another_field: bool,
437 /// },
438 /// }
439 /// ```
440 Algebraic {
441 /// The parsed value of the `#[serde(tag = "...")]` attribute
442 tag_key: String,
443 /// The parsed value of the `#[serde(content = "...")]` attribute
444 content_key: String,
445 /// Shared context for this enum.
446 shared: RustEnumShared,
447 /// The variants on this enum
448 variants: Vec<RustEnumVariant>,
449 },
450}
451
452impl RustEnum {
453 /// Get a reference to the inner shared content
454 pub fn shared(&self) -> &RustEnumShared {
455 match self {
456 Self::Unit { shared, .. } | Self::Algebraic { shared, .. } => shared,
457 }
458 }
459}
460
461/// Enum information shared among different enum types
462#[derive(Debug, Clone, PartialEq)]
463pub struct RustEnumShared {
464 /// The enum's ident
465 pub id: Id,
466 /// Generic parameters for the enum, e.g. `SomeEnum<T>` would produce `vec!["T"]`
467 pub generic_types: Vec<TypeName>,
468 /// Comments on the enum definition itself
469 pub comments: Vec<String>,
470
471 /// Decorators applied to the enum for generation in other languages
472 ///
473 /// Example: `#[typeshare(swift = "Equatable, Comparable, Hashable")]`.
474 pub decorators: DecoratorSet,
475 /// True if this enum references itself in any field of any variant
476 /// Swift needs the special keyword `indirect` for this case
477 pub is_recursive: bool,
478}
479
480/// Parsed information about a Rust enum variant
481#[derive(Debug, Clone, PartialEq)]
482#[non_exhaustive]
483pub enum RustEnumVariant {
484 /// A unit variant
485 Unit(RustEnumVariantShared),
486 /// A newtype tuple variant
487 Tuple {
488 /// The type of the single tuple field
489 ty: RustType,
490 /// Shared context for this enum.
491 shared: RustEnumVariantShared,
492 },
493 /// An anonymous struct variant
494 AnonymousStruct {
495 /// The fields of the anonymous struct
496 fields: Vec<RustField>,
497 /// Shared context for this enum.
498 shared: RustEnumVariantShared,
499 },
500}
501
502impl RustEnumVariant {
503 /// Get a reference to the inner shared content
504 pub fn shared(&self) -> &RustEnumVariantShared {
505 match self {
506 Self::Unit(shared)
507 | Self::Tuple { shared, .. }
508 | Self::AnonymousStruct { shared, .. } => shared,
509 }
510 }
511}
512
513/// Variant information shared among different variant types
514#[derive(Debug, Clone, PartialEq)]
515pub struct RustEnumVariantShared {
516 /// The variant's ident
517 pub id: Id,
518 /// Comments applied to the variant
519 pub comments: Vec<String>,
520}
521
522/// Rust const variable.
523///
524/// Typeshare can only handle numeric and string constants.
525/// ```
526/// pub const MY_CONST: &str = "constant value";
527/// ```
528#[derive(Debug, Clone, PartialEq)]
529pub struct RustConst {
530 /// The identifier for the constant.
531 pub id: Id,
532 /// The type identifier that this constant is referring to.
533 pub ty: RustType,
534 /// The expression that the constant contains.
535 pub expr: RustConstExpr,
536}
537
538/// A constant expression that can be shared via a constant variable across the typeshare
539/// boundary.
540#[derive(Debug, Clone, PartialEq)]
541#[non_exhaustive]
542pub enum RustConstExpr {
543 /// Expression represents an integer.
544 Int(i128),
545}
546
547/// An imported type reference.
548#[derive(Debug, Clone, PartialEq, Eq, Hash)]
549pub struct ImportedType {
550 /// Crate this type belongs to.
551 pub base_crate: CrateName,
552 /// Type name.
553 pub type_name: TypeName,
554}
555
556// TODO: replace this `Cow` with a pair of owned/borrowed types
557/// A type name.
558#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
559pub struct TypeName(Cow<'static, str>);
560
561impl TypeName {
562 #[inline]
563 #[must_use]
564 pub fn as_str(&self) -> &str {
565 self.0.as_ref()
566 }
567
568 #[inline]
569 #[must_use]
570 pub fn new(ident: &proc_macro2::Ident) -> Self {
571 Self::new_string(ident.to_string())
572 }
573
574 #[inline]
575 #[must_use]
576 pub fn new_string(ident: String) -> Self {
577 Self(Cow::Owned(ident))
578 }
579
580 #[inline]
581 #[must_use]
582 pub const fn new_static(ident: &'static str) -> Self {
583 Self(Cow::Borrowed(ident))
584 }
585}
586
587impl AsRef<str> for TypeName {
588 #[inline]
589 #[must_use]
590 fn as_ref(&self) -> &str {
591 self.as_str()
592 }
593}
594
595impl Borrow<str> for TypeName {
596 #[inline]
597 #[must_use]
598 fn borrow(&self) -> &str {
599 self.as_str()
600 }
601}
602
603impl fmt::Display for TypeName {
604 #[inline]
605 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
606 fmt::Display::fmt(&self.0, f)
607 }
608}
609
610impl PartialEq<str> for TypeName {
611 #[inline]
612 #[must_use]
613 fn eq(&self, other: &str) -> bool {
614 self.as_str() == other
615 }
616}
617
618impl PartialEq<&str> for TypeName {
619 #[inline]
620 #[must_use]
621 fn eq(&self, other: &&str) -> bool {
622 self == *other
623 }
624}
625
626#[cfg(test)]
627mod test {
628 use super::CrateName;
629 use std::path::Path;
630
631 #[test]
632 fn test_crate_name() {
633 let path = Path::new("/some/path/to/projects/core/foundation/op-proxy/src/android.rs");
634 assert_eq!(CrateName::find_crate_name(path).unwrap(), "op_proxy",);
635 }
636
637 #[test]
638 fn skip_curdir() {
639 let path = Path::new("/path/to/crate-name/./src/main.rs");
640 assert_eq!(CrateName::find_crate_name(path).unwrap(), "crate_name")
641 }
642
643 #[test]
644 fn bail_on_parent_dir() {
645 let path = Path::new("/path/to/crate-name/src/foo/../stuff.rs");
646 assert!(CrateName::find_crate_name(path).is_none());
647 }
648
649 #[test]
650 fn accept_parent_dir_before_crate() {
651 let path = Path::new("/path/to/../crate/src/foo/bar/stuff.rs");
652 assert_eq!(CrateName::find_crate_name(path).unwrap(), "crate");
653 }
654
655 #[test]
656 fn reject_rooted_src() {
657 let path = Path::new("/src/foo.rs");
658 assert!(CrateName::find_crate_name(path).is_none());
659 }
660}