blob: 69e9e149f814dcd5c92765dcb8263c9e986f2f0c [file] [log] [blame] [edit]
use std::collections::HashSet;
use protobuf::descriptor::*;
use crate::customize::ctx::CustomizeElemCtx;
use crate::customize::rustproto_proto::customize_from_rustproto_for_enum;
use crate::gen::code_writer::CodeWriter;
use crate::gen::code_writer::Visibility;
use crate::gen::descriptor::write_fn_descriptor;
use crate::gen::inside::protobuf_crate_path;
use crate::gen::protoc_insertion_point::write_protoc_insertion_point_for_enum;
use crate::gen::protoc_insertion_point::write_protoc_insertion_point_for_enum_value;
use crate::gen::rust::ident::RustIdent;
use crate::gen::rust::ident_with_path::RustIdentWithPath;
use crate::gen::rust::snippets::EXPR_NONE;
use crate::gen::scope::EnumValueWithContext;
use crate::gen::scope::EnumWithScope;
use crate::gen::scope::RootScope;
use crate::gen::scope::WithScope;
#[derive(Clone)]
pub(crate) struct EnumValueGen<'a> {
value: EnumValueWithContext<'a>,
enum_rust_name: RustIdentWithPath,
}
impl<'a> EnumValueGen<'a> {
fn parse(
value: EnumValueWithContext<'a>,
enum_rust_name: &RustIdentWithPath,
) -> EnumValueGen<'a> {
EnumValueGen {
value: value.clone(),
enum_rust_name: enum_rust_name.clone(),
}
}
// enum value
fn number(&self) -> i32 {
self.value.proto.proto().number()
}
// name of enum variant in generated rust code
pub fn rust_name_inner(&self) -> RustIdent {
self.value.rust_name()
}
pub fn rust_name_outer(&self) -> RustIdentWithPath {
self.enum_rust_name
.to_path()
.with_ident(self.rust_name_inner())
}
}
// Codegen for enum definition
pub(crate) struct EnumGen<'a> {
enum_with_scope: &'a EnumWithScope<'a>,
type_name: RustIdentWithPath,
lite_runtime: bool,
customize: CustomizeElemCtx<'a>,
path: &'a [i32],
info: Option<&'a SourceCodeInfo>,
}
impl<'a> EnumGen<'a> {
pub fn new(
enum_with_scope: &'a EnumWithScope<'a>,
customize: &CustomizeElemCtx<'a>,
_root_scope: &RootScope,
path: &'a [i32],
info: Option<&'a SourceCodeInfo>,
) -> EnumGen<'a> {
let customize = customize.child(
&customize_from_rustproto_for_enum(enum_with_scope.en.proto().options.get_or_default()),
&enum_with_scope.en,
);
let lite_runtime = customize.for_elem.lite_runtime.unwrap_or_else(|| {
enum_with_scope
.file_descriptor()
.proto()
.options
.optimize_for()
== file_options::OptimizeMode::LITE_RUNTIME
});
EnumGen {
enum_with_scope,
type_name: enum_with_scope.rust_name().to_path(),
lite_runtime,
customize,
path,
info,
}
}
fn allow_alias(&self) -> bool {
self.enum_with_scope
.en
.proto()
.options
.get_or_default()
.allow_alias()
}
fn values_all(&self) -> Vec<EnumValueGen> {
let mut r = Vec::new();
for p in self.enum_with_scope.values() {
r.push(EnumValueGen::parse(p, &self.type_name));
}
r
}
fn values_unique(&self) -> Vec<EnumValueGen> {
let mut used = HashSet::new();
let mut r = Vec::new();
for p in self.enum_with_scope.values() {
if !used.insert(p.proto.proto().number()) {
continue;
}
r.push(EnumValueGen::parse(p, &self.type_name));
}
r
}
pub fn write(&self, w: &mut CodeWriter) {
self.write_enum(w);
if self.allow_alias() {
w.write_line("");
self.write_impl_eq(w);
w.write_line("");
self.write_impl_hash(w);
}
w.write_line("");
self.write_impl_enum(w);
if !self.lite_runtime {
w.write_line("");
self.write_impl_enum_full(w);
}
w.write_line("");
self.write_impl_default(w);
w.write_line("");
self.write_impl_self(w);
}
fn write_impl_self(&self, w: &mut CodeWriter) {
if !self.lite_runtime {
w.impl_self_block(&format!("{}", self.type_name), |w| {
self.write_generated_enum_descriptor_data(w);
});
}
}
fn write_enum(&self, w: &mut CodeWriter) {
w.all_documentation(self.info, self.path);
let mut derive = Vec::new();
derive.push("Clone");
derive.push("Copy");
if !self.allow_alias() {
derive.push("PartialEq");
}
derive.push("Eq");
derive.push("Debug");
if !self.allow_alias() {
derive.push("Hash");
} else {
w.comment("Note: you cannot use pattern matching for enums with allow_alias option");
}
w.derive(&derive);
let ref type_name = self.type_name;
write_protoc_insertion_point_for_enum(
w,
&self.customize.for_elem,
&self.enum_with_scope.en,
);
w.expr_block(&format!("pub enum {}", type_name), |w| {
for value in self.values_all() {
write_protoc_insertion_point_for_enum_value(
w,
&self.customize.for_children,
&value.value.proto,
);
if self.allow_alias() {
w.write_line(&format!(
"{}, // {}",
value.rust_name_inner(),
value.number()
));
} else {
w.write_line(&format!(
"{} = {},",
value.rust_name_inner(),
value.number()
));
}
}
});
}
fn write_impl_enum_fn_value(&self, w: &mut CodeWriter) {
w.def_fn("value(&self) -> i32", |w| {
if self.allow_alias() {
w.match_expr("*self", |w| {
for value in self.values_all() {
w.case_expr(
&format!("{}", value.rust_name_outer()),
&format!("{}", value.number()),
);
}
});
} else {
w.write_line("*self as i32")
}
});
}
fn write_impl_enum_const_name(&self, w: &mut CodeWriter) {
w.write_line(&format!(
"const NAME: &'static str = \"{}\";",
self.enum_with_scope.en.name()
));
}
fn write_impl_enum_fn_from_i32(&self, w: &mut CodeWriter) {
w.def_fn(
&format!(
"from_i32(value: i32) -> ::std::option::Option<{}>",
self.type_name
),
|w| {
w.match_expr("value", |w| {
let values = self.values_unique();
for value in values {
w.write_line(&format!(
"{} => ::std::option::Option::Some({}),",
value.number(),
value.rust_name_outer()
));
}
w.write_line(&format!("_ => {}", EXPR_NONE));
});
},
);
}
fn write_impl_enum_const_values(&self, w: &mut CodeWriter) {
w.write_line(&format!("const VALUES: &'static [{}] = &[", self.type_name));
w.indented(|w| {
for value in self.values_all() {
w.write_line(&format!("{},", value.rust_name_outer()));
}
});
w.write_line("];");
}
fn write_impl_enum(&self, w: &mut CodeWriter) {
w.impl_for_block(
&format!("{}::Enum", protobuf_crate_path(&self.customize.for_elem)),
&format!("{}", self.type_name),
|w| {
self.write_impl_enum_const_name(w);
w.write_line("");
self.write_impl_enum_fn_value(w);
w.write_line("");
self.write_impl_enum_fn_from_i32(w);
w.write_line("");
self.write_impl_enum_const_values(w);
},
);
}
fn write_impl_enum_full(&self, w: &mut CodeWriter) {
let ref type_name = self.type_name;
w.impl_for_block(
&format!(
"{}::EnumFull",
protobuf_crate_path(&self.customize.for_elem)
),
&format!("{}", type_name),
|w| {
self.write_impl_enum_full_fn_enum_descriptor(w);
w.write_line("");
self.write_impl_enum_full_fn_descriptor(w);
},
);
}
fn write_impl_enum_full_fn_enum_descriptor(&self, w: &mut CodeWriter) {
write_fn_descriptor(
&self.enum_with_scope.en,
self.enum_with_scope.scope(),
&self.customize.for_elem,
w,
);
}
fn rust_enum_descriptor_is_enum_index(&self) -> bool {
if self.allow_alias() {
false
} else {
self.values_all()
.into_iter()
.enumerate()
.all(|(i, value)| (i as i32) == value.number())
}
}
fn write_impl_enum_full_fn_descriptor(&self, w: &mut CodeWriter) {
let sig = format!(
"descriptor(&self) -> {}::reflect::EnumValueDescriptor",
protobuf_crate_path(&self.customize.for_elem)
);
w.def_fn(&sig, |w| {
if self.rust_enum_descriptor_is_enum_index() {
w.write_line("let index = *self as usize;");
} else {
w.write_line("let index = match self {");
w.indented(|w| {
for (i, value) in self.values_all().into_iter().enumerate() {
w.write_line(&format!(
"{}::{} => {},",
self.type_name,
value.rust_name_inner(),
i
));
}
});
w.write_line("};");
}
w.write_line(&format!("Self::enum_descriptor().value_by_index(index)"));
});
}
fn write_generated_enum_descriptor_data(&self, w: &mut CodeWriter) {
let sig = format!(
"generated_enum_descriptor_data() -> {}::reflect::GeneratedEnumDescriptorData",
protobuf_crate_path(&self.customize.for_elem)
);
w.fn_block(
Visibility::Path(
self.enum_with_scope
.scope()
.rust_path_to_file()
.to_reverse(),
),
&sig,
|w| {
w.write_line(&format!(
"{}::reflect::GeneratedEnumDescriptorData::new::<{}>(\"{}\")",
protobuf_crate_path(&self.customize.for_elem),
self.type_name,
self.enum_with_scope.name_to_package(),
));
},
);
}
fn write_impl_eq(&self, w: &mut CodeWriter) {
assert!(self.allow_alias());
w.impl_for_block(
"::std::cmp::PartialEq",
&format!("{}", self.type_name),
|w| {
w.def_fn("eq(&self, other: &Self) -> bool", |w| {
w.write_line(&format!(
"{}::Enum::value(self) == {}::Enum::value(other)",
protobuf_crate_path(&self.customize.for_elem),
protobuf_crate_path(&self.customize.for_elem)
));
});
},
);
}
fn write_impl_hash(&self, w: &mut CodeWriter) {
assert!(self.allow_alias());
w.impl_for_block("::std::hash::Hash", &format!("{}", self.type_name), |w| {
w.def_fn("hash<H : ::std::hash::Hasher>(&self, state: &mut H)", |w| {
w.write_line(&format!(
"state.write_i32({}::Enum::value(self))",
protobuf_crate_path(&self.customize.for_elem)
));
});
});
}
fn write_impl_default(&self, w: &mut CodeWriter) {
let first_value = &self.enum_with_scope.values()[0];
if first_value.proto.proto().number() != 0 {
// This warning is emitted only for proto2
// (because in proto3 first enum variant number is always 0).
// `Default` implemented unconditionally to simplify certain
// generic operations, e. g. reading a map.
// Also, note that even in proto2 some operations fallback to
// first enum value, e. g. `get_xxx` for unset field,
// so this implementation is not completely unreasonable.
w.comment("Note, `Default` is implemented although default value is not 0");
}
w.impl_for_block(
"::std::default::Default",
&format!("{}", self.type_name),
|w| {
w.def_fn("default() -> Self", |w| {
w.write_line(&format!(
"{}::{}",
&self.type_name,
&first_value.rust_name()
))
});
},
);
}
}