From 4f829b6a23739cdd8ec0c63796a195da51ef6fe7 Mon Sep 17 00:00:00 2001 From: Jane Lewis Date: Thu, 26 Jan 2023 18:45:00 -0800 Subject: [PATCH 1/3] Foundational work for const support --- core/src/language/go.rs | 6 ++++- core/src/language/kotlin.rs | 6 ++++- core/src/language/mod.rs | 11 +++++++- core/src/language/swift.rs | 6 ++++- core/src/language/typescript.rs | 6 ++++- core/src/parser.rs | 47 +++++++++++++++++++++++++++++++-- core/src/rust_types.rs | 22 +++++++++++++++ 7 files changed, 97 insertions(+), 7 deletions(-) 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..ccf0f4f0 100644 --- a/core/src/parser.rs +++ b/core/src/parser.rs @@ -2,14 +2,15 @@ use crate::{ rename::RenameExt, rust_types::{ Id, RustEnum, RustEnumShared, RustEnumVariant, RustEnumVariantShared, RustField, - RustStruct, RustType, RustTypeAlias, RustTypeParseError, + RustStruct, RustType, RustTypeAlias, RustTypeParseError, RustConst, SpecialRustType }, }; use proc_macro2::{Ident, Span}; use std::{collections::HashMap, convert::TryFrom}; -use syn::GenericParam; +use syn::{Expr, GenericParam, ItemConst}; use syn::{Fields, ItemEnum, ItemStruct, ItemType}; use thiserror::Error; +use crate::rust_types::RustConstExpr; // TODO: parsing is very opinionated and makes some decisions that should be // getting made at code generation time. Fix this. @@ -23,6 +24,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 +34,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 +42,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), } } } @@ -90,6 +95,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 +111,7 @@ enum RustThing { Struct(RustStruct), Enum(RustEnum), Alias(RustTypeAlias), + Const(RustConst), } /// Parses a struct into a definition that more succinctly represents what @@ -386,6 +395,40 @@ 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(_, _)) => { + todo!() + }, + RustType::Special(SpecialRustType::Vec(_)) => { + todo!() + }, + RustType::Special(SpecialRustType::Option(_)) => { + todo!() + }, + RustType::Special(s) => s, + _ => todo!() + }; + Ok(RustConst { + id: get_ident(Some(&c.ident), &c.attrs, &None), + r#type: ty, + expr + }) +} + +fn parse_const_expr(c: &Expr) -> Result { + todo!() +} + // 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..aa001325 100644 --- a/core/src/rust_types.rs +++ b/core/src/rust_types.rs @@ -41,6 +41,28 @@ 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(i64), + Float(f64), + String(String) +} + /// Rust type alias. /// ``` /// pub struct MasterPassword(String); From c71d2095eec7863acb566984449e37b25a416683 Mon Sep 17 00:00:00 2001 From: Jane Lewis Date: Fri, 27 Jan 2023 08:35:18 -0800 Subject: [PATCH 2/3] Implement constant expression parsing routine --- core/src/parser.rs | 69 +++++++++++++++++++++++++++++++++--------- core/src/rust_types.rs | 5 +-- 2 files changed, 58 insertions(+), 16 deletions(-) diff --git a/core/src/parser.rs b/core/src/parser.rs index ccf0f4f0..f7ed8996 100644 --- a/core/src/parser.rs +++ b/core/src/parser.rs @@ -1,16 +1,17 @@ +use crate::rust_types::RustConstExpr; use crate::{ rename::RenameExt, rust_types::{ - Id, RustEnum, RustEnumShared, RustEnumVariant, RustEnumVariantShared, RustField, - RustStruct, RustType, RustTypeAlias, RustTypeParseError, RustConst, SpecialRustType + 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::{Expr, GenericParam, ItemConst}; +use syn::visit::Visit; +use syn::{Expr, ExprLit, GenericParam, ItemConst, Lit, LitBool, LitFloat}; use syn::{Fields, ItemEnum, ItemStruct, ItemType}; use thiserror::Error; -use crate::rust_types::RustConstExpr; // TODO: parsing is very opinionated and makes some decisions that should be // getting made at code generation time. Fix this. @@ -69,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`. @@ -398,7 +405,6 @@ 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) { @@ -406,27 +412,62 @@ fn parse_const(c: &ItemConst) -> Result { } else { RustType::try_from(c.ty.as_ref())? } { - RustType::Special(SpecialRustType::HashMap(_, _)) => { - todo!() - }, + RustType::Special(SpecialRustType::HashMap(_, _)) => { + todo!() + } RustType::Special(SpecialRustType::Vec(_)) => { todo!() - }, + } RustType::Special(SpecialRustType::Option(_)) => { todo!() - }, + } RustType::Special(s) => s, - _ => todo!() + _ => todo!(), }; Ok(RustConst { id: get_ident(Some(&c.ident), &c.attrs, &None), r#type: ty, - expr + expr, }) } -fn parse_const_expr(c: &Expr) -> Result { - todo!() +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 diff --git a/core/src/rust_types.rs b/core/src/rust_types.rs index aa001325..88b0d10e 100644 --- a/core/src/rust_types.rs +++ b/core/src/rust_types.rs @@ -58,9 +58,10 @@ pub struct RustConst { /// boundary. #[derive(Debug, Clone)] pub enum RustConstExpr { - Int(i64), + Int(i128), Float(f64), - String(String) + Boolean(bool), + String(String), } /// Rust type alias. From 9c78843029e7ebeb90e645dde5d6084f51237a84 Mon Sep 17 00:00:00 2001 From: Jane Lewis Date: Fri, 27 Jan 2023 08:39:17 -0800 Subject: [PATCH 3/3] Implement type checking logic for constants --- core/src/parser.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/core/src/parser.rs b/core/src/parser.rs index f7ed8996..4922b231 100644 --- a/core/src/parser.rs +++ b/core/src/parser.rs @@ -412,17 +412,13 @@ fn parse_const(c: &ItemConst) -> Result { } else { RustType::try_from(c.ty.as_ref())? } { - RustType::Special(SpecialRustType::HashMap(_, _)) => { - todo!() - } - RustType::Special(SpecialRustType::Vec(_)) => { - todo!() - } - RustType::Special(SpecialRustType::Option(_)) => { - todo!() + RustType::Special(SpecialRustType::HashMap(_, _)) + | RustType::Special(SpecialRustType::Vec(_)) + | RustType::Special(SpecialRustType::Option(_)) => { + return Err(ParseError::RustConstTypeInvalid); } RustType::Special(s) => s, - _ => todo!(), + _ => return Err(ParseError::RustConstTypeInvalid), }; Ok(RustConst { id: get_ident(Some(&c.ident), &c.attrs, &None),