diff options
Diffstat (limited to 'src/gen/rust_types_values.rs')
-rw-r--r-- | src/gen/rust_types_values.rs | 617 |
1 files changed, 617 insertions, 0 deletions
diff --git a/src/gen/rust_types_values.rs b/src/gen/rust_types_values.rs new file mode 100644 index 0000000..cd67f2b --- /dev/null +++ b/src/gen/rust_types_values.rs @@ -0,0 +1,617 @@ +use std::cmp; + +use once_cell::sync::Lazy; +use protobuf::descriptor::*; +use protobuf::reflect::FileDescriptor; +use protobuf_parse::ProtobufAbsPath; +use regex::Regex; + +use crate::customize::Customize; +use crate::gen::field::type_ext::TypeExt; +use crate::gen::file_and_mod::FileAndMod; +use crate::gen::inside::protobuf_crate_path; +use crate::gen::message::RustTypeMessage; +use crate::gen::paths::proto_path_to_rust_mod; +use crate::gen::rust::component::RustPathComponent; +use crate::gen::rust::ident::RustIdent; +use crate::gen::rust::ident_with_path::RustIdentWithPath; +use crate::gen::rust::path::RustPath; +use crate::gen::rust::rel_path::RustRelativePath; +use crate::gen::rust::snippets::EXPR_NONE; +use crate::gen::rust::snippets::EXPR_VEC_NEW; +use crate::gen::scope::RootScope; +use crate::gen::scope::WithScope; +use crate::gen::strx::capitalize; +use crate::gen::well_known_types::is_well_known_type_full; + +// Represent subset of rust types used in generated code +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum RustType { + // integer: signed?, size in bits + Int(bool, u32), + // param is size in bits + Float(u32), + Bool, + Vec(Box<RustType>), + HashMap(Box<RustType>, Box<RustType>), + String, + // [T], not &[T] + Slice(Box<RustType>), + // str, not &str + Str, + Option(Box<RustType>), + MessageField(Box<RustType>), + // Box<T> + Uniq(Box<RustType>), + // &T + Ref(Box<RustType>), + // protobuf message + Message(RustTypeMessage), + // protobuf enum, not any enum + Enum(RustIdentWithPath, RustIdent, i32), + // protobuf enum or unknown + EnumOrUnknown(RustIdentWithPath, RustIdent, i32), + // oneof enum + Oneof(RustIdentWithPath), + // bytes::Bytes + Bytes, + // chars::Chars + Chars, + // group + Group, +} + +impl RustType { + #[inline] + pub(crate) fn to_code(&self, customize: &Customize) -> String { + match *self { + RustType::Int(true, bits) => format!("i{}", bits), + RustType::Int(false, bits) => format!("u{}", bits), + RustType::Float(bits) => format!("f{}", bits), + RustType::Bool => format!("bool"), + RustType::Vec(ref param) => format!("::std::vec::Vec<{}>", param.to_code(customize)), + RustType::HashMap(ref key, ref value) => format!( + "::std::collections::HashMap<{}, {}>", + key.to_code(customize), + value.to_code(customize) + ), + RustType::String => format!("::std::string::String"), + RustType::Slice(ref param) => format!("[{}]", param.to_code(customize)), + RustType::Str => format!("str"), + RustType::Option(ref param) => { + format!("::std::option::Option<{}>", param.to_code(customize)) + } + RustType::MessageField(ref param) => format!( + "{}::MessageField<{}>", + protobuf_crate_path(customize), + param.to_code(customize) + ), + RustType::Uniq(ref param) => format!("::std::boxed::Box<{}>", param.to_code(customize)), + RustType::Ref(ref param) => format!("&{}", param.to_code(customize)), + RustType::Message(ref name) => format!("{}", name), + RustType::Enum(ref name, ..) | RustType::Oneof(ref name) => format!("{}", name), + RustType::EnumOrUnknown(ref name, ..) => format!( + "{}::EnumOrUnknown<{}>", + protobuf_crate_path(customize), + name + ), + RustType::Group => format!("<group>"), + RustType::Bytes => format!("::bytes::Bytes"), + RustType::Chars => format!("{}::Chars", protobuf_crate_path(customize)), + } + } +} + +impl RustType { + pub(crate) fn u8() -> RustType { + RustType::Int(false, 8) + } + + pub(crate) fn i32() -> RustType { + RustType::Int(true, 32) + } + + /// `&str`. + pub(crate) fn amp_str() -> RustType { + RustType::Str.wrap_ref() + } + + /// `&[u8]`. + pub(crate) fn amp_slice_of_u8() -> RustType { + RustType::u8().wrap_slice().wrap_ref() + } + + /// Type is rust primitive? + pub(crate) fn is_primitive(&self) -> bool { + match *self { + RustType::Int(..) | RustType::Float(..) | RustType::Bool => true, + _ => false, + } + } + + pub fn is_u8(&self) -> bool { + match *self { + RustType::Int(false, 8) => true, + _ => false, + } + } + + pub fn is_copy(&self) -> bool { + if self.is_primitive() { + true + } else if let RustType::Enum(..) = *self { + true + } else if let RustType::EnumOrUnknown(..) = *self { + true + } else { + false + } + } + + fn is_str(&self) -> bool { + match *self { + RustType::Str => true, + _ => false, + } + } + + fn is_string(&self) -> bool { + match *self { + RustType::String => true, + _ => false, + } + } + + fn is_slice(&self) -> Option<&RustType> { + match *self { + RustType::Slice(ref v) => Some(&**v), + _ => None, + } + } + + fn is_slice_u8(&self) -> bool { + match self.is_slice() { + Some(t) => t.is_u8(), + None => false, + } + } + + fn is_message(&self) -> bool { + match *self { + RustType::Message(..) => true, + _ => false, + } + } + + fn is_enum(&self) -> bool { + match *self { + RustType::Enum(..) => true, + _ => false, + } + } + + fn is_enum_or_unknown(&self) -> bool { + match *self { + RustType::EnumOrUnknown(..) => true, + _ => false, + } + } + + pub fn is_ref(&self) -> Option<&RustType> { + match *self { + RustType::Ref(ref v) => Some(&**v), + _ => None, + } + } + + pub fn is_box(&self) -> Option<&RustType> { + match *self { + RustType::Uniq(ref v) => Some(&**v), + _ => None, + } + } + + // default value for type + pub fn default_value(&self, customize: &Customize, const_expr: bool) -> String { + match *self { + RustType::Ref(ref t) if t.is_str() => "\"\"".to_string(), + RustType::Ref(ref t) if t.is_slice().is_some() => "&[]".to_string(), + RustType::Int(..) => "0".to_string(), + RustType::Float(..) => "0.".to_string(), + RustType::Bool => "false".to_string(), + RustType::Vec(..) => EXPR_VEC_NEW.to_string(), + RustType::HashMap(..) => "::std::collections::HashMap::new()".to_string(), + RustType::String => "::std::string::String::new()".to_string(), + RustType::Bytes => "::bytes::Bytes::new()".to_string(), + RustType::Chars => format!("{}::Chars::new()", protobuf_crate_path(customize)), + RustType::Option(..) => EXPR_NONE.to_string(), + RustType::MessageField(..) => { + format!("{}::MessageField::none()", protobuf_crate_path(customize)) + } + RustType::Message(ref name) => format!("{}::new()", name), + RustType::Ref(ref m) if m.is_message() => match **m { + RustType::Message(ref name) => name.default_instance(customize), + _ => unreachable!(), + }, + // Note: default value of enum type may not be equal to default value of field + RustType::Enum(ref name, ref default, ..) => format!("{}::{}", name, default), + RustType::EnumOrUnknown(_, _, number) if const_expr => format!( + "{}::EnumOrUnknown::from_i32({})", + protobuf_crate_path(customize), + number, + ), + RustType::EnumOrUnknown(ref name, ref default, ..) if !const_expr => format!( + "{}::EnumOrUnknown::new({}::{})", + protobuf_crate_path(customize), + name, + default + ), + _ => panic!("cannot create default value for: {:?}", self), + } + } + + pub fn default_value_typed(self, customize: &Customize, const_expr: bool) -> RustValueTyped { + RustValueTyped { + value: self.default_value(customize, const_expr), + rust_type: self, + } + } + + /// Emit a code to clear a variable `v` + pub fn clear(&self, v: &str, customize: &Customize) -> String { + match *self { + RustType::Option(..) => format!("{} = {}", v, EXPR_NONE), + RustType::Vec(..) + | RustType::Bytes + | RustType::Chars + | RustType::String + | RustType::MessageField(..) + | RustType::HashMap(..) => format!("{}.clear()", v), + RustType::Bool + | RustType::Float(..) + | RustType::Int(..) + | RustType::Enum(..) + | RustType::EnumOrUnknown(..) => { + format!("{} = {}", v, self.default_value(customize, false)) + } + ref ty => panic!("cannot clear type: {:?}", ty), + } + } + + // expression to convert `v` of type `self` to type `target` + pub fn into_target(&self, target: &RustType, v: &str, customize: &Customize) -> String { + self.try_into_target(target, v, customize) + .expect(&format!("failed to convert {:?} into {:?}", self, target)) + } + + // https://github.com/rust-lang-nursery/rustfmt/issues/3131 + #[cfg_attr(rustfmt, rustfmt_skip)] + fn try_into_target(&self, target: &RustType, v: &str, customize: &Customize) -> Result<String, ()> { + { + if let Some(t1) = self.is_ref().and_then(|t| t.is_box()) { + if let Some(t2) = target.is_ref() { + if t1 == t2 { + return Ok(format!("&**{}", v)); + } + } + } + } + + match (self, target) { + (x, y) if x == y => return Ok(format!("{}", v)), + (&RustType::Ref(ref x), y) if **x == *y => return Ok(format!("*{}", v)), + (x, &RustType::Uniq(ref y)) if *x == **y => { + return Ok(format!("::std::boxed::Box::new({})", v)) + } + (&RustType::Uniq(ref x), y) if **x == *y => return Ok(format!("*{}", v)), + (&RustType::String, &RustType::Ref(ref t)) if **t == RustType::Str => { + return Ok(format!("&{}", v)) + } + (&RustType::Chars, &RustType::Ref(ref t)) if **t == RustType::Str => { + return Ok(format!("&{}", v)) + } + (&RustType::Ref(ref t1), &RustType::Ref(ref t2)) if t1.is_string() && t2.is_str() => { + return Ok(format!("&{}", v)) + } + (&RustType::Ref(ref t1), &RustType::String) + if match **t1 { + RustType::Str => true, + _ => false, + } => return Ok(format!("{}.to_owned()", v)), + (&RustType::Ref(ref t1), &RustType::Chars) + if match **t1 { + RustType::Str => true, + _ => false, + } => { + return Ok(format!("<{}::Chars as ::std::convert::From<_>>::from({}.to_owned())", + protobuf_crate_path(customize), v)) + }, + (&RustType::Ref(ref t1), &RustType::Vec(ref t2)) + if match (&**t1, &**t2) { + (&RustType::Slice(ref x), ref y) => **x == **y, + _ => false, + } => return Ok(format!("{}.to_vec()", v)), + (&RustType::Ref(ref t1), &RustType::Bytes) + if t1.is_slice_u8() => + return Ok(format!("<::bytes::Bytes as ::std::convert::From<_>>::from({}.to_vec())", v)), + (&RustType::Vec(ref x), &RustType::Ref(ref t)) + if match **t { + RustType::Slice(ref y) => x == y, + _ => false, + } => return Ok(format!("&{}", v)), + (&RustType::Bytes, &RustType::Ref(ref t)) + if match **t { + RustType::Slice(ref y) => **y == RustType::u8(), + _ => false, + } => return Ok(format!("&{}", v)), + (&RustType::Ref(ref t1), &RustType::Ref(ref t2)) + if match (&**t1, &**t2) { + (&RustType::Vec(ref x), &RustType::Slice(ref y)) => x == y, + _ => false, + } => return Ok(format!("&{}", v)), + (&RustType::Enum(..), &RustType::Int(true, 32)) => { + return Ok(format!("{}::Enum::value(&{})", protobuf_crate_path(customize), v)) + }, + (&RustType::EnumOrUnknown(..), &RustType::Int(true, 32)) => { + return Ok(format!("{}::EnumOrUnknown::value(&{})", protobuf_crate_path(customize), v)) + }, + (&RustType::Ref(ref t), &RustType::Int(true, 32)) if t.is_enum() => { + return Ok(format!("{}::Enum::value({})", protobuf_crate_path(customize), v)) + } + (&RustType::Ref(ref t), &RustType::Int(true, 32)) if t.is_enum_or_unknown() => { + return Ok(format!("{}::EnumOrUnknown::value({})", protobuf_crate_path(customize), v)) + }, + (&RustType::EnumOrUnknown(ref f, ..), &RustType::Enum(ref t, ..)) if f == t => { + return Ok(format!("{}::EnumOrUnknown::enum_value_or_default(&{})", protobuf_crate_path(customize), v)) + } + (&RustType::Enum(ref f, ..), &RustType::EnumOrUnknown(ref t, ..)) if f == t => { + return Ok(format!("{}::EnumOrUnknown::new({})", protobuf_crate_path(customize), v)) + } + _ => (), + }; + + if let &RustType::Ref(ref s) = self { + if let Ok(conv) = s.try_into_target(target, v, customize) { + return Ok(conv); + } + } + + Err(()) + } + + /// Type to view data of this type + pub fn ref_type(&self) -> RustType { + RustType::Ref(Box::new(match self { + &RustType::String | &RustType::Chars => RustType::Str, + &RustType::Vec(ref p) => RustType::Slice(p.clone()), + &RustType::Bytes => RustType::Slice(Box::new(RustType::u8())), + &RustType::Message(ref p) => RustType::Message(p.clone()), + &RustType::Uniq(ref p) => RustType::Uniq(p.clone()), + x => panic!("no ref type for {:?}", x), + })) + } + + pub(crate) fn wrap_ref(&self) -> RustType { + RustType::Ref(Box::new(self.clone())) + } + + pub(crate) fn wrap_slice(&self) -> RustType { + RustType::Slice(Box::new(self.clone())) + } + + pub fn elem_type(&self) -> RustType { + match self { + &RustType::Option(ref ty) => (**ty).clone(), + &RustType::MessageField(ref ty) => (**ty).clone(), + x => panic!("cannot get elem type of {:?}", x), + } + } + + // type of `v` in `for v in xxx` + pub fn iter_elem_type(&self) -> RustType { + match self { + &RustType::Vec(ref ty) + | &RustType::Option(ref ty) + | &RustType::MessageField(ref ty) => RustType::Ref(ty.clone()), + x => panic!("cannot iterate {:?}", x), + } + } + + pub fn value(self, value: String) -> RustValueTyped { + RustValueTyped { + value: value, + rust_type: self, + } + } +} + +/// Representation of an expression in code generator: text and type +pub(crate) struct RustValueTyped { + pub value: String, + pub rust_type: RustType, +} + +impl RustValueTyped { + pub fn into_type(&self, target: RustType, customize: &Customize) -> RustValueTyped { + let target_value = self.rust_type.into_target(&target, &self.value, customize); + RustValueTyped { + value: target_value, + rust_type: target, + } + } + + pub fn boxed(self, customize: &Customize) -> RustValueTyped { + self.into_type(RustType::Uniq(Box::new(self.rust_type.clone())), customize) + } +} + +fn file_last_component(file: &str) -> &str { + let bs = file.rfind('\\').map(|i| i + 1).unwrap_or(0); + let fs = file.rfind('/').map(|i| i + 1).unwrap_or(0); + &file[cmp::max(fs, bs)..] +} + +#[cfg(test)] +#[test] +fn test_file_last_component() { + assert_eq!("ab.proto", file_last_component("ab.proto")); + assert_eq!("ab.proto", file_last_component("xx/ab.proto")); + assert_eq!("ab.proto", file_last_component("xx\\ab.proto")); + assert_eq!("ab.proto", file_last_component("yy\\xx\\ab.proto")); +} + +fn is_descriptor_proto(file: &FileDescriptor) -> bool { + file.package() == "google.protobuf" && file_last_component(file.name()) == "descriptor.proto" +} + +fn make_path_to_path(source: &RustRelativePath, dest: &RustPath) -> RustPath { + if dest.is_absolute() { + return dest.clone(); + } + + let mut source = source.clone(); + let mut dest = dest.clone(); + while !source.is_empty() && source.first() == dest.first() { + source.remove_first().unwrap(); + dest.remove_first().unwrap(); + } + source.to_reverse().into_path().append(dest) +} + +pub(crate) fn make_path(source: &RustRelativePath, dest: &RustIdentWithPath) -> RustIdentWithPath { + make_path_to_path(source, &dest.path).with_ident(dest.ident.clone()) +} + +pub(crate) fn message_or_enum_to_rust_relative( + message_or_enum: &dyn WithScope, + current: &FileAndMod, +) -> RustIdentWithPath { + let same_file = message_or_enum.file_descriptor().name() == current.file; + if same_file { + // field type is a message or enum declared in the same file + make_path(¤t.relative_mod, &message_or_enum.rust_name_to_file()) + } else if let Some(name) = is_well_known_type_full(&message_or_enum.name_absolute()) { + // Well-known types are included in rust-protobuf library + // https://developers.google.com/protocol-buffers/docs/reference/google.protobuf + let file_descriptor = message_or_enum.file_descriptor(); + static REGEX: Lazy<Regex> = + Lazy::new(|| Regex::new(r"^google/protobuf/([^/]+\.proto)$").unwrap()); + let captures = REGEX + .captures(file_descriptor.name()) + .unwrap_or_else(|| panic!("`{}` does not match the regex", file_descriptor.name())); + let file_name = captures.get(1).unwrap().as_str(); + let mod_name = proto_path_to_rust_mod(file_name); + RustIdentWithPath::from(format!( + "{protobuf_crate}::well_known_types::{mod_name}::{name}", + protobuf_crate = protobuf_crate_path(¤t.customize), + )) + } else if is_descriptor_proto(&message_or_enum.file_descriptor()) { + // Messages defined in descriptor.proto + RustIdentWithPath::from(format!( + "{}::descriptor::{}", + protobuf_crate_path(¤t.customize), + message_or_enum.rust_name_to_file() + )) + } else { + current + .relative_mod + .to_reverse() + .into_path() + .append_component(RustPathComponent::SUPER) + .append_with_ident(message_or_enum.rust_name_with_file()) + } +} + +pub(crate) fn type_name_to_rust_relative( + type_name: &ProtobufAbsPath, + current: &FileAndMod, + root_scope: &RootScope, +) -> RustIdentWithPath { + assert!(!type_name.is_root()); + let message_or_enum = root_scope.find_message_or_enum(type_name); + message_or_enum_to_rust_relative(&message_or_enum, current) +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum PrimitiveTypeVariant { + Default, + TokioBytes, +} + +pub enum _TokioBytesType { + Bytes, + Chars, +} + +// ProtobufType trait name +pub(crate) enum ProtobufTypeGen { + Primitive(field_descriptor_proto::Type, PrimitiveTypeVariant), + Message(RustTypeMessage), + EnumOrUnknown(RustIdentWithPath), +} + +impl ProtobufTypeGen { + pub(crate) fn protobuf_value(&self, customize: &Customize) -> String { + match self { + ProtobufTypeGen::Primitive(t, PrimitiveTypeVariant::Default) => { + t.rust_type().to_code(customize) + } + ProtobufTypeGen::Primitive(_, PrimitiveTypeVariant::TokioBytes) => unimplemented!(), + ProtobufTypeGen::Message(m) => m.0.to_string(), + ProtobufTypeGen::EnumOrUnknown(e) => format!( + "{protobuf_crate}::EnumOrUnknown<{e}>", + protobuf_crate = protobuf_crate_path(customize) + ), + } + } + + pub(crate) fn _rust_type(&self, customize: &Customize) -> String { + match self { + &ProtobufTypeGen::Primitive(t, PrimitiveTypeVariant::Default) => format!( + "{}::reflect::types::ProtobufType{}", + protobuf_crate_path(customize), + capitalize(t.protobuf_name()) + ), + &ProtobufTypeGen::Primitive( + field_descriptor_proto::Type::TYPE_BYTES, + PrimitiveTypeVariant::TokioBytes, + ) => format!( + "{}::reflect::types::ProtobufTypeTokioBytes", + protobuf_crate_path(customize) + ), + &ProtobufTypeGen::Primitive( + field_descriptor_proto::Type::TYPE_STRING, + PrimitiveTypeVariant::TokioBytes, + ) => format!( + "{}::reflect::types::ProtobufTypeTokioChars", + protobuf_crate_path(customize) + ), + &ProtobufTypeGen::Primitive(.., PrimitiveTypeVariant::TokioBytes) => unreachable!(), + &ProtobufTypeGen::Message(ref name) => format!( + "{}::reflect::types::ProtobufTypeMessage<{}>", + protobuf_crate_path(customize), + name + ), + &ProtobufTypeGen::EnumOrUnknown(ref name) => format!( + "{}::reflect::types::ProtobufTypeEnumOrUnknown<{}>", + protobuf_crate_path(customize), + name + ), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn into_target_ref_box_to_ref() { + let t1 = RustType::Ref(Box::new(RustType::Uniq(Box::new(RustType::Message( + RustTypeMessage::from("Ab"), + ))))); + let t2 = RustType::Ref(Box::new(RustType::Message(RustTypeMessage::from("Ab")))); + + assert_eq!("&**v", t1.into_target(&t2, "v", &Customize::default())); + } +} |