Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

const support #57

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion core/src/language/go.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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!(
Expand Down
6 changes: 5 additions & 1 deletion core/src/language/kotlin.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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")?;
Expand Down
11 changes: 10 additions & 1 deletion core/src/language/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion core/src/language/swift.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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;
Expand Down
6 changes: 5 additions & 1 deletion core/src/language/typescript.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand Down Expand Up @@ -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!(
Expand Down
86 changes: 83 additions & 3 deletions core/src/parser.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -23,6 +25,8 @@ pub struct ParsedData {
pub enums: Vec<RustEnum>,
/// Type aliases defined in the source
pub aliases: Vec<RustTypeAlias>,
/// Constant variables defined in the source
pub consts: Vec<RustConst>,
}

impl ParsedData {
Expand All @@ -31,13 +35,15 @@ 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) {
match rust_thing {
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),
}
}
}
Expand All @@ -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`.
Expand All @@ -90,6 +102,9 @@ pub fn parse(input: &str) -> Result<ParsedData, ParseError> {
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)?)
}
_ => {}
}
}
Expand All @@ -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
Expand Down Expand Up @@ -386,6 +402,70 @@ fn parse_type_alias(t: &ItemType) -> Result<RustTypeAlias, ParseError> {
})
}

fn parse_const(c: &ItemConst) -> Result<RustConst, ParseError> {
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<RustConstExpr, ParseError> {
struct ExprLitVisitor(pub Option<Result<RustConstExpr, ParseError>>);
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
Expand Down
23 changes: 23 additions & 0 deletions core/src/rust_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,29 @@ pub struct RustStruct {
pub decorators: HashMap<String, Vec<String>>,
}

/// 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);
Expand Down