diff --git a/core/src/language/go.rs b/core/src/language/go.rs index 784cc88c..960e66ef 100644 --- a/core/src/language/go.rs +++ b/core/src/language/go.rs @@ -2,7 +2,7 @@ use std::io::Write; use crate::parser::ParsedData; use crate::rename::RenameExt; -use crate::rust_types::{RustTypeFormatError, SpecialRustType}; +use crate::rust_types::{RustConst, RustTypeFormatError, SpecialRustType}; use crate::{ language::Language, rust_types::{RustEnum, RustEnumVariant, RustField, RustStruct, RustTypeAlias}, @@ -119,6 +119,10 @@ impl Language for Go { Ok(()) } + fn write_const(&self, w: &mut dyn Write, c: &RustConst) -> std::io::Result<()> { + todo!() + } + fn write_struct(&self, w: &mut dyn Write, rs: &RustStruct) -> std::io::Result<()> { write_comments(w, 0, &rs.comments)?; writeln!( diff --git a/core/src/language/kotlin.rs b/core/src/language/kotlin.rs index c3719297..cff4793e 100644 --- a/core/src/language/kotlin.rs +++ b/core/src/language/kotlin.rs @@ -1,5 +1,5 @@ use super::Language; -use crate::rust_types::{RustTypeFormatError, SpecialRustType}; +use crate::rust_types::{RustConst, RustTypeFormatError, SpecialRustType}; use crate::{ parser::remove_dash_from_identifier, rename::RenameExt, @@ -98,6 +98,10 @@ impl Language for Kotlin { Ok(()) } + fn write_const(&self, w: &mut dyn Write, c: &RustConst) -> std::io::Result<()> { + todo!() + } + fn write_struct(&self, w: &mut dyn Write, rs: &RustStruct) -> std::io::Result<()> { self.write_comments(w, 0, &rs.comments)?; writeln!(w, "@Serializable")?; diff --git a/core/src/language/mod.rs b/core/src/language/mod.rs index 88b3e483..c1af866e 100644 --- a/core/src/language/mod.rs +++ b/core/src/language/mod.rs @@ -10,7 +10,7 @@ mod kotlin; mod swift; mod typescript; -use crate::rust_types::{RustType, RustTypeFormatError, SpecialRustType}; +use crate::rust_types::{RustConst, RustType, RustTypeFormatError, SpecialRustType}; pub use go::Go; pub use kotlin::Kotlin; pub use swift::Swift; @@ -133,6 +133,15 @@ pub trait Language { Ok(()) } + /// Write a constant variable. + /// Example of a constant variable: + /// ``` + /// const ANSWER_TO_EVERYTHING: u32 = 42; + /// ``` + fn write_const(&self, _w: &mut dyn Write, _c: &RustConst) -> std::io::Result<()> { + Ok(()) + } + /// Write a struct by converting it /// Example of a struct: /// ```ignore diff --git a/core/src/language/swift.rs b/core/src/language/swift.rs index 47e7f898..eb0cb9d2 100644 --- a/core/src/language/swift.rs +++ b/core/src/language/swift.rs @@ -1,4 +1,4 @@ -use crate::rust_types::{RustTypeFormatError, SpecialRustType}; +use crate::rust_types::{RustConst, RustTypeFormatError, SpecialRustType}; use crate::{ language::Language, parser::remove_dash_from_identifier, @@ -195,6 +195,10 @@ impl Language for Swift { Ok(()) } + fn write_const(&self, w: &mut dyn Write, c: &RustConst) -> std::io::Result<()> { + todo!() + } + fn write_struct(&self, w: &mut dyn Write, rs: &RustStruct) -> std::io::Result<()> { let mut coding_keys = vec![]; let mut should_write_coding_keys = false; diff --git a/core/src/language/typescript.rs b/core/src/language/typescript.rs index c6f43804..44047fb0 100644 --- a/core/src/language/typescript.rs +++ b/core/src/language/typescript.rs @@ -1,4 +1,4 @@ -use crate::rust_types::{RustType, RustTypeFormatError, SpecialRustType}; +use crate::rust_types::{RustConst, RustType, RustTypeFormatError, SpecialRustType}; use crate::{ language::Language, rust_types::{RustEnum, RustEnumVariant, RustField, RustStruct, RustTypeAlias}, @@ -89,6 +89,10 @@ impl Language for TypeScript { Ok(()) } + fn write_const(&self, w: &mut dyn Write, c: &RustConst) -> std::io::Result<()> { + todo!() + } + fn write_struct(&self, w: &mut dyn Write, rs: &RustStruct) -> std::io::Result<()> { self.write_comments(w, 0, &rs.comments)?; writeln!( diff --git a/core/src/parser.rs b/core/src/parser.rs index 6187542d..4922b231 100644 --- a/core/src/parser.rs +++ b/core/src/parser.rs @@ -1,13 +1,15 @@ +use crate::rust_types::RustConstExpr; use crate::{ rename::RenameExt, rust_types::{ - Id, RustEnum, RustEnumShared, RustEnumVariant, RustEnumVariantShared, RustField, - RustStruct, RustType, RustTypeAlias, RustTypeParseError, + Id, RustConst, RustEnum, RustEnumShared, RustEnumVariant, RustEnumVariantShared, RustField, + RustStruct, RustType, RustTypeAlias, RustTypeParseError, SpecialRustType, }, }; use proc_macro2::{Ident, Span}; use std::{collections::HashMap, convert::TryFrom}; -use syn::GenericParam; +use syn::visit::Visit; +use syn::{Expr, ExprLit, GenericParam, ItemConst, Lit, LitBool, LitFloat}; use syn::{Fields, ItemEnum, ItemStruct, ItemType}; use thiserror::Error; @@ -23,6 +25,8 @@ pub struct ParsedData { pub enums: Vec, /// Type aliases defined in the source pub aliases: Vec, + /// Constant variables defined in the source + pub consts: Vec, } impl ParsedData { @@ -31,6 +35,7 @@ impl ParsedData { self.structs.append(&mut other.structs); self.enums.append(&mut other.enums); self.aliases.append(&mut other.aliases); + self.consts.append(&mut other.consts); } fn push_rust_thing(&mut self, rust_thing: RustThing) { @@ -38,6 +43,7 @@ impl ParsedData { RustThing::Struct(s) => self.structs.push(s), RustThing::Enum(e) => self.enums.push(e), RustThing::Alias(a) => self.aliases.push(a), + RustThing::Const(c) => self.consts.push(c), } } } @@ -64,6 +70,12 @@ pub enum ParseError { SerdeTagRequired { enum_ident: String }, #[error("serde content attribute needs to be specified for algebraic enum {enum_ident}. e.g. #[serde(tag = \"type\", content = \"content\")]")] SerdeContentRequired { enum_ident: String }, + #[error("the expression assigned to this constant variable is not a numeric, boolean or string literal")] + RustConstExprInvalid, + #[error( + "you cannot use typeshare on a constant that is not a numeric, boolean or string literal" + )] + RustConstTypeInvalid, } /// Parse the given Rust source string into `ParsedData`. @@ -90,6 +102,9 @@ pub fn parse(input: &str) -> Result { syn::Item::Type(t) if has_typeshare_annotation(&t.attrs) => { parsed_data.aliases.push(parse_type_alias(t)?); } + syn::Item::Const(c) if has_typeshare_annotation(&c.attrs) => { + parsed_data.consts.push(parse_const(c)?) + } _ => {} } } @@ -103,6 +118,7 @@ enum RustThing { Struct(RustStruct), Enum(RustEnum), Alias(RustTypeAlias), + Const(RustConst), } /// Parses a struct into a definition that more succinctly represents what @@ -386,6 +402,70 @@ fn parse_type_alias(t: &ItemType) -> Result { }) } +fn parse_const(c: &ItemConst) -> Result { + let expr = parse_const_expr(&c.expr)?; + + // serialized_as needs to be supported in case the user wants to use a different type + // for the constant variable in a different language + let ty = match if let Some(ty) = get_serialized_as_type(&c.attrs) { + ty.parse()? + } else { + RustType::try_from(c.ty.as_ref())? + } { + RustType::Special(SpecialRustType::HashMap(_, _)) + | RustType::Special(SpecialRustType::Vec(_)) + | RustType::Special(SpecialRustType::Option(_)) => { + return Err(ParseError::RustConstTypeInvalid); + } + RustType::Special(s) => s, + _ => return Err(ParseError::RustConstTypeInvalid), + }; + Ok(RustConst { + id: get_ident(Some(&c.ident), &c.attrs, &None), + r#type: ty, + expr, + }) +} + +fn parse_const_expr(e: &Expr) -> Result { + struct ExprLitVisitor(pub Option>); + impl Visit<'_> for ExprLitVisitor { + fn visit_expr_lit(&mut self, el: &ExprLit) { + if self.0.is_some() { + // should we throw an error instead of silently ignoring a second literal? + // or would this create false positives? + return; + } + let check_literal_type = || { + Ok(match &el.lit { + Lit::Bool(LitBool { value, .. }) => RustConstExpr::Boolean(*value), + Lit::Float(lit_float) => { + let float: f64 = lit_float + .base10_parse() + .map_err(|_| ParseError::RustConstExprInvalid)?; + RustConstExpr::Float(float) + } + Lit::Int(lit_int) => { + let int: i128 = lit_int + .base10_parse() + .map_err(|_| ParseError::RustConstTypeInvalid)?; + RustConstExpr::Int(int) + } + Lit::Str(lit_str) => RustConstExpr::String(lit_str.value()), + _ => return Err(ParseError::RustConstTypeInvalid), + }) + }; + + self.0.replace(check_literal_type()); + } + } + let mut expr_visitor = ExprLitVisitor(None); + syn::visit::visit_expr(&mut expr_visitor, e); + expr_visitor + .0 + .map_or(Err(ParseError::RustConstTypeInvalid), |v| v) +} + // Helpers /// Parses any comment out of the given slice of attributes diff --git a/core/src/rust_types.rs b/core/src/rust_types.rs index f7eb5237..88b0d10e 100644 --- a/core/src/rust_types.rs +++ b/core/src/rust_types.rs @@ -41,6 +41,29 @@ pub struct RustStruct { pub decorators: HashMap>, } +/// Rust const variable. +/// +/// Typeshare can only handle numeric and string constants. +/// ``` +/// pub const MY_CONST: &str = "constant value"; +/// ``` +#[derive(Debug, Clone)] +pub struct RustConst { + pub id: Id, + pub r#type: SpecialRustType, + pub expr: RustConstExpr, +} + +/// A constant expression that can be shared via a constant variable across the typeshare +/// boundary. +#[derive(Debug, Clone)] +pub enum RustConstExpr { + Int(i128), + Float(f64), + Boolean(bool), + String(String), +} + /// Rust type alias. /// ``` /// pub struct MasterPassword(String);