Skip to content

Commit

Permalink
Merge pull request #2070 from fzyzcjy/feat/12218
Browse files Browse the repository at this point in the history
Support `#[frb(name)]` on fields to rename them ; Automatically rename Dart field names if they conflict with Dart keywords
  • Loading branch information
fzyzcjy authored Jun 12, 2024
2 parents c93793c + 71ebe0c commit 89ff866
Show file tree
Hide file tree
Showing 97 changed files with 6,437 additions and 481 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::codegen::ir::mir::default::MirDefaultValue;
use crate::codegen::ir::mir::field::MirField;
use crate::codegen::ir::mir::ty::delegate::MirTypeDelegate;
use crate::codegen::ir::mir::ty::MirType;
use crate::utils::dart_keywords::make_string_keyword_safe;
use crate::utils::dart_keywords;
use convert_case::{Case, Casing};
use std::borrow::Cow;

Expand Down Expand Up @@ -61,7 +61,7 @@ fn default_value_to_dart_style(value: &str) -> String {
format!(
"{}.{}",
enum_name,
make_string_keyword_safe(variant_name.to_string()).to_case(Case::Camel)
dart_keywords::escape(variant_name.to_case(Case::Camel))
)
}
_ => value.to_string(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ impl<'a> EnumRefApiDartGenerator<'a> {

fn generate_implements_exception(&self, variant: &MirEnumVariant) -> &str {
let has_backtrace = matches!(&variant.kind,
MirVariantKind::Struct(MirStruct {is_fields_named: true, fields, ..}) if fields.iter().any(|field| field.name.raw == BACKTRACE_IDENT));
MirVariantKind::Struct(MirStruct {is_fields_named: true, fields, ..}) if fields.iter().any(|field| field.name.rust_style() == BACKTRACE_IDENT));
if self.mir.is_exception && has_backtrace {
"@Implements<FrbBacktracedException>()"
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::codegen::generator::api_dart::spec_generator::misc::generate_dart_com
use crate::codegen::ir::mir::ty::enumeration::{MirEnum, MirEnumVariant};
use crate::library::codegen::generator::api_dart::spec_generator::base::*;
use crate::utils::basic_code::dart_header_code::DartHeaderCode;
use crate::utils::dart_keywords::make_string_keyword_safe;
use crate::utils::dart_keywords;
use itertools::Itertools;

impl<'a> EnumRefApiDartGenerator<'a> {
Expand Down Expand Up @@ -41,7 +41,7 @@ impl<'a> EnumRefApiDartGenerator<'a> {

fn generate_mode_simple_variant(&self, variant: &MirEnumVariant) -> String {
let variant_name = if self.context.config.dart_enums_style {
make_string_keyword_safe(variant.name.dart_style())
dart_keywords::escape(variant.name.dart_style())
} else {
variant.name.rust_style().to_string()
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ fn generate_decode_statement(
)
}

fn get_variable_name(field: &MirFuncInput) -> &str {
fn get_variable_name(field: &MirFuncInput) -> String {
field.inner.name.rust_style()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ impl<'a> WireRustGeneratorMiscTrait for StructRefWireRustGenerator<'a> {
.enumerate()
.map(|(i, field)| {
let field_access = if src.is_fields_named {
field.name.raw.clone()
field.name.rust_style().to_owned()
} else {
i.to_string()
};
Expand Down
47 changes: 32 additions & 15 deletions frb_codegen/src/library/codegen/ir/mir/ident.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,32 @@
use crate::codegen::generator::codec::sse::lang::Lang;
use crate::utils::cbindgen_keywords;
use crate::utils::{cbindgen_keywords, dart_keywords};
use convert_case::{Case, Casing};

crate::mir! {
#[serde(transparent)]
pub struct MirIdent {
pub raw: String,
rust_style: String,
dart_style: Option<String>,
}
}

impl MirIdent {
pub fn new(raw: String) -> MirIdent {
MirIdent { raw }
pub fn new(rust_style: String, dart_style: Option<String>) -> MirIdent {
MirIdent {
rust_style,
dart_style,
}
}

pub fn rust_style(&self) -> &str {
&self.raw
pub fn rust_style(&self) -> String {
self.rust_style.clone()
}

pub fn c_style(&self) -> String {
convert_rust_to_c_style(&self.raw)
convert_rust_to_c_style(&self.rust_style)
}

pub fn dart_style(&self) -> String {
(self.raw.strip_prefix("r#").unwrap_or(self.raw.as_str())).to_case(Case::Camel)
(self.dart_style.clone()).unwrap_or_else(|| convert_rust_to_dart_style(&self.rust_style))
}

pub fn style(&self, lang: &Lang) -> String {
Expand All @@ -35,24 +39,37 @@ impl MirIdent {

impl std::fmt::Display for MirIdent {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
fmt.write_str(&self.raw)
fmt.write_str(&self.rust_style)?;
if let Some(dart_style) = &self.dart_style {
write!(fmt, "(dart_style={})", dart_style)?;
}
Ok(())
}
}

fn convert_rust_to_c_style(raw: &str) -> String {
let mut ans = raw.to_owned();

if let Some(stripped) = ans.strip_prefix("r#") {
ans = stripped.to_owned();
}
let mut ans = strip_prefix_rhash(raw).to_owned();

// match behavior of ffigen
if &ans == "async" {
ans = "async1".to_owned();
}
if &ans == "interface" {
ans = "interface1".to_owned();
}

// match behavior of cbindgen
cbindgen_keywords::escape(&mut ans);

ans
}

fn convert_rust_to_dart_style(raw: &str) -> String {
let ans = strip_prefix_rhash(raw).to_case(Case::Camel);

dart_keywords::escape(ans)
}

fn strip_prefix_rhash(raw: &str) -> &str {
raw.strip_prefix("r#").unwrap_or(raw)
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ pub(super) fn parse_auto_accessor_of_field(
type_parser: &mut TypeParser,
context: &TypeParserParsingContext,
) -> anyhow::Result<MirFuncAndSanityCheckInfo> {
let rust_method_name = format!("{}_{}", accessor_mode.verb_str(), field.name.raw);
let rust_method_name = format!("{}_{}", accessor_mode.verb_str(), field.name.rust_style());

let owner = MirFuncOwnerInfoMethod {
owner_ty: ty_direct_parse.to_owned(),
actual_method_name: rust_method_name,
actual_method_dart_name: Some(field.name.raw.clone()),
actual_method_dart_name: Some(field.name.rust_style().to_owned()),
mode: MirFuncOwnerInfoMethodMode::Instance,
trait_def: None,
};
Expand All @@ -47,7 +47,7 @@ pub(super) fn parse_auto_accessor_of_field(
if accessor_mode == MirFuncAccessorMode::Setter {
inputs.push(MirFuncInput {
ownership_mode: None,
inner: create_mir_field(field.ty.clone(), &field.name.raw),
inner: create_mir_field(field.ty.clone(), &field.name.rust_style()),
});
}

Expand Down Expand Up @@ -124,15 +124,15 @@ fn compute_self_arg(
fn compute_src_lineno_pseudo(struct_name: &NamespacedName, field: &MirField) -> usize {
let mut hasher = Sha1::new();
hasher.update(struct_name.rust_style().as_bytes());
hasher.update(field.name.raw.as_bytes());
hasher.update(field.name.rust_style().as_bytes());
let digest = hasher.finalize();
usize::from_le_bytes(digest[..8].try_into().unwrap())
}

fn create_mir_field(ty: MirType, name: &str) -> MirField {
MirField {
ty,
name: MirIdent::new(name.to_owned()),
name: MirIdent::new(name.to_owned(), None),
is_final: true,
is_rust_public: None,
comments: vec![],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ impl<'a, 'b> FunctionParser<'a, 'b> {
Ok(FunctionPartialInfo {
inputs: vec![MirFuncInput {
inner: MirField {
name: MirIdent::new(name),
name: MirIdent::new(name, None),
ty,
is_final: true,
is_rust_public: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,10 @@ impl<'a, 'b, 'c> TypeParserWithContext<'a, 'b, 'c> {
src_enum: &HirFlatEnum,
variant: &Variant,
) -> anyhow::Result<MirEnumVariant> {
let variant_name = MirIdent::new(variant.ident.to_string());
let variant_name = MirIdent::new(variant.ident.to_string(), None);
Ok(MirEnumVariant {
name: variant_name.clone(),
wrapper_name: MirIdent::new(format!("{}_{}", src_enum.name.name, variant.ident)),
wrapper_name: MirIdent::new(format!("{}_{}", src_enum.name.name, variant.ident), None),
comments: parse_comments(&variant.attrs),
kind: match variant.fields.iter().next() {
None => MirVariantKind::Value,
Expand Down Expand Up @@ -118,6 +118,7 @@ impl<'a, 'b, 'c> TypeParserWithContext<'a, 'b, 'c> {
.as_ref()
.map(ToString::to_string)
.unwrap_or_else(|| format!("field{idx}")),
None,
),
ty: self.parse_type_with_context(&field.ty, |c| {
c.with_struct_or_enum_attributes(attributes.clone())
Expand All @@ -141,7 +142,7 @@ pub(crate) fn compute_enum_variant_kind_struct_name(
variant_name: &MirIdent,
) -> NamespacedName {
let variant_namespace = enum_name.namespace.join(&enum_name.name);
NamespacedName::new(variant_namespace, variant_name.raw.clone())
NamespacedName::new(variant_namespace, variant_name.rust_style().to_owned())
}

struct EnumOrStructParserEnum<'a, 'b, 'c, 'd>(&'d mut TypeParserWithContext<'a, 'b, 'c>);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ impl<'a, 'b, 'c> TypeParserWithContext<'a, 'b, 'c> {
})?;
let attributes = FrbAttributes::parse(&field.attrs)?;
Ok(MirField {
name: MirIdent::new(field_name),
name: MirIdent::new(field_name, attributes.name()),
ty: field_type,
is_final: !attributes.non_final(),
is_rust_public: Some(matches!(field.vis, Visibility::Public(_))),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ impl<'a, 'b, 'c> TypeParserWithContext<'a, 'b, 'c> {
.enumerate()
.map(|(idx, ty)| MirField {
ty: ty.clone(),
name: MirIdent::new(format!("field{idx}")),
name: MirIdent::new(format!("field{idx}"), None),
is_final: true,
is_rust_public: None,
comments: vec![],
Expand Down
23 changes: 3 additions & 20 deletions frb_codegen/src/library/utils/dart_keywords.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,11 @@
use anyhow::bail;
use convert_case::{Case, Casing};

/// If the given string is a Dart keyword, then
/// convert it to PascalCase to avoid issues.
/// If the string is not a keyword, then the original
/// is returned.
pub(crate) fn make_string_keyword_safe(input: String) -> String {
if check_for_keywords(&[input.clone()]).is_err() {
input.to_case(Case::Pascal)
pub(crate) fn escape(input: String) -> String {
if DART_KEYWORDS.contains(&input.as_str()) {
format!("{input}_")
} else {
input
}
}

// the function signature is not covered while the whole body is covered - looks like a bug in coverage tool
// frb-coverage:ignore-start
fn check_for_keywords(v: &[String]) -> anyhow::Result<()> {
// frb-coverage:ignore-end
if let Some(s) = v.iter().find(|s| DART_KEYWORDS.contains(&s.as_str())) {
bail!("Api name cannot be a dart keyword: {}", s);
};
Ok(())
}

// https://dart.dev/guides/language/language-tour#keywords
const DART_KEYWORDS: [&str; 63] = [
"abstract",
Expand Down
Loading

0 comments on commit 89ff866

Please sign in to comment.