//! #[diplomat::attr] and other attributes use crate::ast; use crate::ast::attrs::{AttrInheritContext, DiplomatBackendAttrCfg, StandardAttribute}; use crate::hir::lowering::ErrorStore; use crate::hir::{ EnumVariant, LoweringError, Method, Mutability, OpaqueId, ReturnType, SelfType, SuccessType, TraitDef, Type, TypeDef, TypeId, }; use syn::Meta; pub use crate::ast::attrs::RenameAttr; /// Diplomat attribute that can be specified on items, methods, and enum variants. These /// can be used to control the codegen in a particular backend. /// /// Most of these are specified via `#[diplomat::attr(some cfg here, attrname)]`, where `some cfg here` /// can be used to pick which backends something applies to. #[non_exhaustive] #[derive(Clone, Default, Debug)] pub struct Attrs { /// "disable" this item: do not generate code for it in the backend /// /// This attribute is always inherited except to variants pub disable: bool, /// Mark this item deprecated in FFI. pub deprecated: Option, /// An optional namespace. None is equivalent to the root namespace. /// /// This attribute is inherited to types (and is not allowed elsewhere) pub namespace: Option, /// Rename this item/method/variant /// /// This attribute is inherited except through methods and variants (and is not allowed on variants) pub rename: RenameAttr, /// Rename this item in the C ABI. This *must* be respected by backends. /// /// This attribute is inherited except through variants pub abi_rename: RenameAttr, /// This method is "special": it should generate something other than a regular method on the other side. /// This can be something like a constructor, an accessor, a stringifier etc. /// /// This attribute does not participate in inheritance and must always /// be specified on individual methods pub special_method: Option, /// This user-defined type can be used as the error type in a Result. pub custom_errors: bool, /// This user-defined type has a "default" state that can be computed purely foreign-language-side /// /// Can be applied to enum variants to signal the default pub default: bool, /// From #[diplomat::demo()]. Created from [`crate::ast::attrs::Attrs::demo_attrs`]. /// List of attributes specific to automatic demo generation. /// Currently just for demo_gen in diplomat-tool (which generates sample webpages), but could be used for broader purposes (i.e., demo Android apps) pub demo_attrs: DemoInfo, /// From #[diplomat::attr()]. If true, generates a mocking interface for this type. pub generate_mocking_interface: bool, /// From #[diplomat::attr()]. If true, Diplomat will check that this struct has the same memory layout in backends which support it. Allows this struct to be used in slices ([`super::Slice::Struct`]) and to be borrowed in function parameters. pub abi_compatible: bool, } // #region: Demo specific attributes. /// For `#[diplomat::demo(input(...))]`, stored in [DemoInfo::input_cfg]. #[non_exhaustive] #[derive(Clone, Default, Debug)] pub struct DemoInputCFG { /// `#[diplomat::demo(input(label = "..."))]` /// Label that this input parameter should have. Let demo_gen pick a valid name if this is empty. /// /// For instance pub label: String, /// `#[diplomat::demo(input(default_value = "..."))]` /// Sets the default value for a parameter. /// /// Should ALWAYS be a string. The HTML renderer is expected to do validation for us. pub default_value: String, } #[non_exhaustive] #[derive(Clone, Default, Debug)] pub struct DemoInfo { /// `#[diplomat::demo(generate)]`. If automatic generation is disabled by default (see [`diplomat_tool::demo_gen::DemoConfig`]), then the below render terminus will be allowed to generate. pub generate: bool, /// `#[diplomat::demo(default_constructor)]` /// We search for any methods specially tagged with `Constructor`, but if there's are no default Constructors and there's NamedConstructor that you want to be default instead, use this. /// TODO: Should probably ignore other `Constructors` if a default has been set. pub default_constructor: bool, /// `#[diplomat::demo(external)]` represents an item that we will not evaluate, and should be passed to the rendering engine to provide. pub external: bool, /// `#[diplomat::demo(custom_func = "/file/name/here.mjs")]` can be used above any `struct` definition in the bridge. The linked `.mjs` should contain a JS definition of functions that should be bundled with demo_gen's output. /// /// We call these functions "custom functions", as they are JS functions that are not automagically generated by demo_gen, but rather included as part of its JS output in the `RenderInfo` object. /// /// For more information on custom functions (and their use), see the relevant chapter in [the book](https://rust-diplomat.github.io/diplomat/demo_gen/custom_functions.html). /// /// Files are located relative to lib.rs. /// pub custom_func: Option, /// `#[diplomat::demo(input(...))]` represents configuration options for anywhere we might expect user input. pub input_cfg: DemoInputCFG, } // #endregion /// Attributes that mark methods as "special" #[non_exhaustive] #[derive(Clone, Debug)] pub enum SpecialMethod { /// A constructor. /// /// Must return Self (or Result for backends with `fallible_constructors` enabled ) Constructor, /// A named constructor, with optional name. If the name isn't specified, it will be derived /// from the method name /// /// Must return Self (or Result for backends with `fallible_constructors` enabled ) NamedConstructor(Option), /// A getter, with optional name. If the name isn't specified, it will be derived /// from the method name /// /// Must have no parameters and must return something. Getter(Option), /// A setter, with optional name. If the name isn't specified, it will be derived /// from the method name /// /// Must have no return type (aside from potentially a `Result<(), _>`) and must have one parameter Setter(Option), /// A stringifier. Must have no parameters and return a string (DiplomatWrite) Stringifier, /// A comparison operator. Currently not universally supported Comparison, /// An iterator (a type that is mutated to produce new values) Iterator, /// An iterable (a type that can produce an iterator) Iterable, /// Indexes into the type using an integer Indexer, /// Arithmetic operators. May not return references Add, Sub, Mul, Div, /// In-place arithmetic operators. Must not return a value AddAssign, SubAssign, MulAssign, DivAssign, } impl SpecialMethod { pub fn from_path_and_meta(path: &str, meta: &Meta) -> Result, LoweringError> { let parse_meta = |meta| match StandardAttribute::from_meta(meta) { Ok(StandardAttribute::String(s)) => Ok(Some(s)), Ok(StandardAttribute::Empty) => Ok(None), Ok(_) | Err(_) => Err(LoweringError::Other(format!( "`{path}` must have a single string parameter or no parameter" ))), }; match path { "constructor" => Ok(Some(Self::Constructor)), "named_constructor" => Ok(Some(Self::NamedConstructor(parse_meta(meta)?))), "getter" => Ok(Some(Self::Getter(parse_meta(meta)?))), "setter" => Ok(Some(Self::Setter(parse_meta(meta)?))), "stringifier" => Ok(Some(Self::Stringifier)), "comparison" => Ok(Some(Self::Comparison)), "iterator" => Ok(Some(Self::Iterator)), "iterable" => Ok(Some(Self::Iterable)), "indexer" => Ok(Some(Self::Indexer)), "add" => Ok(Some(Self::Add)), "sub" => Ok(Some(Self::Sub)), "mul" => Ok(Some(Self::Mul)), "div" => Ok(Some(Self::Div)), "add_assign" => Ok(Some(Self::AddAssign)), "sub_assign" => Ok(Some(Self::SubAssign)), "mul_assign" => Ok(Some(Self::MulAssign)), "div_assign" => Ok(Some(Self::DivAssign)), _ => Ok(None), } } // Returns the standard operator string (if any) associated with this special method pub fn operator_str(&self) -> Option<&str> { match self { SpecialMethod::Add => Some("+"), SpecialMethod::Sub => Some("-"), SpecialMethod::Mul => Some("*"), SpecialMethod::Div => Some("/"), SpecialMethod::AddAssign => Some("+="), SpecialMethod::SubAssign => Some("-="), SpecialMethod::MulAssign => Some("*="), SpecialMethod::DivAssign => Some("/="), SpecialMethod::Indexer => Some("[]"), _ => None, } } } /// For special methods that affect type semantics, whether this type has this method. /// /// This will likely only contain a subset of special methods, but feel free to add more as needed. #[derive(Debug, Default)] #[non_exhaustive] pub struct SpecialMethodPresence { pub comparator: bool, /// If it is an iterator, the type it iterates over pub iterator: Option, /// If it is an iterable, the iterator type it returns (*not* the type it iterates over, /// perform lookup on that type to access) pub iterable: Option, } /// Where the attribute was found. Some attributes are only allowed in some contexts /// (e.g. namespaces cannot be specified on methods) #[non_exhaustive] // might add module attrs in the future #[derive(Debug)] pub enum AttributeContext<'a, 'b> { Type(TypeDef<'a>), Trait(&'a TraitDef), EnumVariant(&'a EnumVariant), Method(&'a Method, Option, &'b mut SpecialMethodPresence), Function(&'a Method), Module, Param, SelfParam, Field, } fn maybe_error_unsupported( auto_found: bool, attribute: &str, backend: &str, errors: &mut ErrorStore, ) { if !auto_found { errors.push(LoweringError::Other(format!( "`{attribute}` not supported in backend {backend}" ))); } } impl Attrs { pub fn from_ast( ast: &ast::Attrs, validator: &(impl AttributeValidator + ?Sized), parent_attrs: &Attrs, errors: &mut ErrorStore, ) -> Self { let mut this = parent_attrs.clone(); // Backends must support this since it applies to the macro/C code. // No special inheritance, was already appropriately inherited in AST this.abi_rename = ast.abi_rename.clone(); this.deprecated = ast.deprecated.clone(); let support = validator.attrs_supported(); let backend = validator.primary_name(); for attr in &ast.attrs { let mut auto_found = false; match validator.satisfies_cfg(&attr.cfg, Some(&mut auto_found)) { Ok(satisfies) if !satisfies => continue, Err(e) => { errors.push(e); continue; } Ok(_) => {} }; let path = attr.meta.path(); if let Some(path) = path.get_ident() { let path = path.to_string(); let warn_auto = |errors: &mut ErrorStore| { if auto_found { errors.push(LoweringError::Other(format!( "Diplomat attribute {path:?} gated on 'auto' but is not one that works with 'auto'" ))); } }; // Check against the set of attributes that can have platform support if support.check_string(&path) == Some(false) { maybe_error_unsupported(auto_found, &path, backend, errors); continue; } match SpecialMethod::from_path_and_meta(&path, &attr.meta) { Ok(Some(kind)) => { if let Some(ref existing) = this.special_method { errors.push(LoweringError::Other(format!( "Multiple special method markers found on the same method, found {path} and {existing:?}" ))); } else { this.special_method = Some(kind); } } Err(error) => errors.push(error), Ok(None) => match path.as_str() { // No match found in the special methods, check the other keywords "disable" => { if let Meta::Path(_) = attr.meta { if this.disable { errors.push(LoweringError::Other( "Duplicate `disable` attribute".into(), )); } else { this.disable = true; } } else { errors.push(LoweringError::Other( "`disable` must be a simple path".into(), )) } warn_auto(errors); } "default" => { if let Meta::Path(_) = attr.meta { if this.default { errors.push(LoweringError::Other( "Duplicate `default` attribute".into(), )); } else { this.default = true; } } else { errors.push(LoweringError::Other( "`default` must be a simple path".into(), )) } } "rename" => { match RenameAttr::from_meta(&attr.meta) { Ok(rename) => { // We use the override extend mode: a single ast::Attrs // will have had these attributes inherited into the list by appending // to the end; so a later attribute in the list is more pertinent. this.rename.extend(&rename); } Err(e) => errors.push(LoweringError::Other(format!( "`rename` attr failed to parse: {e:?}" ))), } warn_auto(errors); } "namespace" => match StandardAttribute::from_meta(&attr.meta) { Ok(StandardAttribute::String(s)) if s.is_empty() => { this.namespace = None } Ok(StandardAttribute::String(s)) => this.namespace = Some(s), Ok(_) | Err(_) => { errors.push(LoweringError::Other( "`namespace` must have a single string parameter".to_string(), )); } }, "error" => { this.custom_errors = true; } "generate_mocking_interface" => { if !support.generate_mocking_interface { maybe_error_unsupported( auto_found, "generate_mocking_interface", backend, errors, ); continue; } this.generate_mocking_interface = true; } "abi_compatible" => { if !support.abi_compatibles { maybe_error_unsupported( auto_found, "abi_compatible", backend, errors, ); continue; } this.abi_compatible = true; } _ => { errors.push(LoweringError::Other(format!( "Unknown diplomat attribute {path}: expected one of: `disable, rename, namespace, constructor, stringifier, comparison, named_constructor, getter, setter, indexer, error`" ))); } }, } } } for attr in &ast.demo_attrs { let path = attr.meta.path(); if let Some(path_ident) = path.get_ident() { if path_ident == "external" { this.demo_attrs.external = true; } else if path_ident == "default_constructor" { this.demo_attrs.default_constructor = true; } else if path_ident == "generate" { this.demo_attrs.generate = true; } else if path_ident == "input" { let meta_list = attr .meta .require_list() .expect("Could not get MetaList, expected #[diplomat::demo(input(...))]"); meta_list .parse_nested_meta(|meta| { if meta.path.is_ident("label") { let value = meta.value()?; let s: syn::LitStr = value.parse()?; this.demo_attrs.input_cfg.label = s.value(); Ok(()) } else if meta.path.is_ident("default_value") { let value = meta.value()?; let str_val: String; let ahead = value.lookahead1(); if ahead.peek(syn::LitFloat) { let s: syn::LitFloat = value.parse()?; str_val = s.base10_parse::()?.to_string(); } else if ahead.peek(syn::LitInt) { let s: syn::LitInt = value.parse()?; str_val = s.base10_parse::()?.to_string(); } else { let s: syn::LitStr = value.parse()?; str_val = s.value(); } this.demo_attrs.input_cfg.default_value = str_val; Ok(()) } else { Err(meta.error(format!( "Unsupported ident {:?}", meta.path.get_ident() ))) } }) .expect("Could not read input(...)"); } else if path_ident == "custom_func" { let v = &attr.meta.require_name_value().unwrap().value; if let syn::Expr::Lit(s) = v { if let syn::Lit::Str(string) = &s.lit { this.demo_attrs.custom_func = Some(string.value()); } else { errors.push(LoweringError::Other(format!( "#[diplomat::demo(custom_func={s:?}) must be a literal string." ))); } } else { errors.push(LoweringError::Other(format!( "#[diplomat::demo(custom_func={v:?}) must be a literal string." ))); } } else { errors.push(LoweringError::Other(format!( "Unknown demo_attr: {path_ident:?}" ))); } } else { errors.push(LoweringError::Other(format!("Unknown demo_attr: {path:?}"))); } } this } /// Validate that this attribute is allowed in this context pub(crate) fn validate( &self, validator: &(impl AttributeValidator + ?Sized), mut context: AttributeContext, errors: &mut ErrorStore, ) { // use an exhaustive destructure so new attributes are handled let Attrs { disable, deprecated: _deprecated, namespace, rename, abi_rename, special_method, custom_errors, default, demo_attrs: _, generate_mocking_interface, abi_compatible, } = &self; if *disable && matches!(context, AttributeContext::EnumVariant(..)) { errors.push(LoweringError::Other( "`disable` cannot be used on enum variants".into(), )) } if let Some(ref special) = special_method { if let AttributeContext::Method(method, self_id, ref mut special_method_presence) = context { let check_param_count = |name: &str, count: usize, errors: &mut ErrorStore| { if method.params.len() != count { errors.push(LoweringError::Other(format!( "{name} must have exactly {count} parameter{}", if count == 1 { "" } else { "s" } ))) } }; let check_self_param = |name: &str, need_self: bool, errors: &mut ErrorStore| { if method.param_self.is_some() != need_self { errors.push(LoweringError::Other(format!( "{name} must{} accept a self parameter", if need_self { "" } else { " not" } ))); } }; match special { SpecialMethod::Constructor | SpecialMethod::NamedConstructor(..) => { check_self_param("Constructors", false, errors); let output = method.output.success_type(); match method.output { ReturnType::Infallible(_) => (), ReturnType::Fallible(..) => { // Only report an error if constructors *are* supported but failable constructors *arent* if validator.attrs_supported().constructors && !validator.attrs_supported().fallible_constructors { errors.push(LoweringError::Other( "This backend doesn't support fallible constructors" .to_string(), )) } } ReturnType::Nullable(..) => { errors.push(LoweringError::Other("Diplomat doesn't support turning nullable methods into constructors".to_string())); } } if let SuccessType::OutType(t) = &output { if t.id() != self_id || self_id.is_none() { errors.push(LoweringError::Other( "Constructors must return Self!".to_string(), )); } } else { errors.push(LoweringError::Other( "Constructors must return Self!".to_string(), )); } } SpecialMethod::Getter(_) => { if !method.params.is_empty() { errors .push(LoweringError::Other("Getter cannot have parameters".into())); } if method.param_self.is_none() && !validator.attrs_supported().static_accessors { errors.push(LoweringError::Other(format!("No self parameter on Getter {} but static_acessors are not supported",method.name.as_str()))); } // Currently does not forbid nullable getters, could if desired } SpecialMethod::Setter(_) => { if !matches!(method.output.success_type(), SuccessType::Unit) { errors.push(LoweringError::Other("Setters must return unit".into())); } if method.param_self.is_none() && !validator.attrs_supported().static_accessors { errors.push(LoweringError::Other(format!("No self parameter on Setter {} but static_acessors are not supported",method.name.as_str()))); } check_param_count("Setter", 1, errors); // Currently does not forbid fallible setters, could if desired } SpecialMethod::Stringifier => { if !method.params.is_empty() { errors .push(LoweringError::Other("Getter cannot have parameters".into())); } if !matches!(method.output.success_type(), SuccessType::Write) { errors.push(LoweringError::Other( "Stringifier must return string".into(), )); } } SpecialMethod::Comparison => { check_param_count("Comparator", 1, errors); if special_method_presence.comparator { errors.push(LoweringError::Other( "Cannot define two comparators on the same type".into(), )); } special_method_presence.comparator = true; // In the long run we can actually support heterogeneous comparators. Not a priority right now. const COMPARATOR_ERROR: &str = "Comparator's parameter must be identical to self"; check_self_param("Comparators", true, errors); if let Some(ref selfty) = method.param_self { if let Some(param) = method.params.first() { match (&selfty.ty, ¶m.ty) { (SelfType::Opaque(p), Type::Opaque(p2)) => { if p.tcx_id != p2.tcx_id { errors.push(LoweringError::Other( COMPARATOR_ERROR.into(), )); } if p.owner.mutability != Mutability::Immutable || p2.owner.mutability != Mutability::Immutable { errors.push(LoweringError::Other( "comparators must accept immutable parameters" .into(), )); } if p2.optional.0 { errors.push(LoweringError::Other( "comparators must accept non-optional parameters" .into(), )); } } (SelfType::Struct(p), Type::Struct(p2)) => { if p.tcx_id != p2.tcx_id { errors.push(LoweringError::Other( COMPARATOR_ERROR.into(), )); } if p.owner .as_borrowed() .map(|o| !o.mutability.is_immutable()) .unwrap_or(false) || p2 .owner .as_borrowed() .map(|o| !o.mutability.is_immutable()) .unwrap_or(false) { errors.push(LoweringError::Other( "comparators must accept immutable parameters" .into(), )); } } (SelfType::Enum(p), Type::Enum(p2)) => { if p.tcx_id != p2.tcx_id { errors.push(LoweringError::Other( COMPARATOR_ERROR.into(), )); } } _ => { errors.push(LoweringError::Other(COMPARATOR_ERROR.into())); } } } } } SpecialMethod::Iterator => { if special_method_presence.iterator.is_some() { errors.push(LoweringError::Other( "Cannot mark type as iterator twice".into(), )); } check_param_count("Iterator", 0, errors); // In theory we could support struct and enum iterators. The benefit is slight: // it generates probably inefficient code whilst being rather weird when it comes to the // "structs and enums convert across the boundary" norm for backends. // // Essentially, the `&mut self` behavior won't work right. // // Furthermore, in some backends (like Dart) defining an iterator may requiring adding fields, // which may not be possible for enums, and would still be an odd-one-out field for structs.g s check_self_param("Iterator", true, errors); if let Some(this) = &method.param_self { if !matches!(this.ty, SelfType::Opaque(..)) { errors.push(LoweringError::Other( "Iterators only allowed on opaques".into(), )) } } if let ReturnType::Nullable(ref o) = method.output { if let SuccessType::Unit = o { errors.push(LoweringError::Other( "Iterator method must return something".into(), )); } special_method_presence.iterator = Some(o.clone()); } else if let ReturnType::Infallible(SuccessType::OutType( crate::hir::OutType::Opaque( ref o @ crate::hir::OpaquePath { optional: crate::hir::Optional(true), .. }, ), )) = method.output { let mut o = o.clone(); o.optional = crate::hir::Optional(false); special_method_presence.iterator = Some(SuccessType::OutType(crate::hir::OutType::Opaque(o))); } else { errors.push(LoweringError::Other( "Iterator method must return nullable value".into(), )); } } SpecialMethod::Iterable => { if special_method_presence.iterable.is_some() { errors.push(LoweringError::Other( "Cannot mark type as iterable twice".into(), )); } check_param_count("Iterator", 0, errors); check_self_param("Iterables", true, errors); match method.output.success_type() { SuccessType::OutType(ty) => { if let Some(TypeId::Opaque(id)) = ty.id() { special_method_presence.iterable = Some(id); } else { errors.push(LoweringError::Other( "Iterables must return a custom opaque type".into(), )) } } _ => errors.push(LoweringError::Other( "Iterables must return a custom type".into(), )), } } SpecialMethod::Indexer => { check_param_count("Indexer", 1, errors); check_self_param("Indexer", true, errors); if method.output.success_type().is_unit() { errors.push(LoweringError::Other("Indexer must return a value".into())); } } e @ (SpecialMethod::Add | SpecialMethod::Sub | SpecialMethod::Mul | SpecialMethod::Div) => { let name = match e { SpecialMethod::Add => "Add", SpecialMethod::Sub => "Sub", SpecialMethod::Mul => "Mul", SpecialMethod::Div => "Div", _ => unreachable!(), }; check_param_count(name, 1, errors); check_self_param(name, true, errors); if method.output.success_type().is_unit() { errors .push(LoweringError::Other(format!("{name} must return a value"))); } } e @ (SpecialMethod::AddAssign | SpecialMethod::SubAssign | SpecialMethod::MulAssign | SpecialMethod::DivAssign) => { let name = match e { SpecialMethod::AddAssign => "AddAssign", SpecialMethod::SubAssign => "SubAssign", SpecialMethod::MulAssign => "MulAssign", SpecialMethod::DivAssign => "DivAssign", _ => unreachable!(), }; check_param_count(name, 1, errors); check_self_param(name, true, errors); if let Some(self_param) = &method.param_self { if matches!(self_param.ty, SelfType::Struct(_) | SelfType::Enum(_)) { errors.push(LoweringError::Other("*Assign arithmetic operations not allowed on non-opaque types. \ Use the non-mutating arithmetic operators instead".to_string())); } else if self_param.ty.is_immutably_borrowed() { errors.push(LoweringError::Other(format!( "{name} must take self by mutable reference" ))); } } if !method.output.success_type().is_unit() { errors.push(LoweringError::Other(format!( "{name} must not return a value" ))); } } } } else { errors.push(LoweringError::Other(format!("Special method (type {special:?}) not allowed on non-method context {context:?}"))) } } if namespace.is_some() && matches!( context, AttributeContext::Method(..) | AttributeContext::EnumVariant(..) ) { errors.push(LoweringError::Other( "`namespace` can only be used on types".to_string(), )); } if *default && !matches!(context, AttributeContext::EnumVariant(..)) { errors.push(LoweringError::Other( "`default` can only be used on types and enum variants".to_string(), )); } if matches!( context, AttributeContext::Param | AttributeContext::SelfParam | AttributeContext::Field ) { if *disable { errors.push(LoweringError::Other(format!( "`disable`s cannot be used on an {context:?}." ))); } if namespace.is_some() { errors.push(LoweringError::Other(format!( "`namespace` cannot be used on an {context:?}." ))); } if !rename.is_empty() || !abi_rename.is_empty() { errors.push(LoweringError::Other(format!( "`rename`s cannot be used on an {context:?}." ))); } if special_method.is_some() { errors.push(LoweringError::Other(format!( "{context:?} cannot be special methods." ))); } } if *custom_errors && !matches!( context, AttributeContext::Type(..) | AttributeContext::Trait(..) | AttributeContext::Function(..) ) { errors.push(LoweringError::Other( "`error` can only be used on types".to_string(), )); } if *generate_mocking_interface && !matches!(context, AttributeContext::Type(TypeDef::Opaque(..))) { errors.push(LoweringError::Other( "`generate_mocking_interface` can only be used on opaque types".to_string(), )); } if *abi_compatible && !matches!(context, AttributeContext::Type(TypeDef::Struct(..))) { errors.push(LoweringError::Other( "`abi_compatible` can only be used on non-output-only struct types.".into(), )); } } pub(crate) fn for_inheritance(&self, context: AttrInheritContext) -> Attrs { let rename = self.rename.attrs_for_inheritance(context, false); // Disabling shouldn't inherit to variants let disable = if context == AttrInheritContext::Variant { false } else { self.disable }; let namespace = if matches!( context, AttrInheritContext::Module | AttrInheritContext::Type ) { self.namespace.clone() } else { None }; Attrs { disable, deprecated: None, rename, namespace, // Should not inherit from enums to their variants default: false, // Was already inherited on the AST side abi_rename: Default::default(), // Never inherited special_method: None, // Not inherited custom_errors: false, demo_attrs: Default::default(), // Not inherited generate_mocking_interface: false, abi_compatible: false, } } } /// Non-exhaustive list of what attributes and other features your backend is able to handle, based on #[diplomat::attr(...)] contents. /// Set this through an [`AttributeValidator`]. /// /// See [`SpecialMethod`] and [`Attrs`] for your specific implementation needs. /// /// For example, the current dart backend supports [`BackendAttrSupport::constructors`]. So when it encounters: /// ```ignore /// struct Sample {} /// impl Sample { /// #[diplomat::attr(constructor)] /// pub fn new() -> Box { /// Box::new(Sample{}) /// } /// } /// /// ``` /// /// It generates /// ```dart /// factory Sample() /// ``` /// /// If a backend does not support a specific `#[diplomat::attr(...)]`, it may error. #[non_exhaustive] #[derive(Copy, Clone, Debug, Default)] pub struct BackendAttrSupport { /// Namespacing types, e.g. C++ `namespace`. pub namespacing: bool, /// Rust can directly acccess the memory of this language, like C and C++. /// This is not supported in any garbage-collected language. pub memory_sharing: bool, /// This language's structs are non-exhaustive by default, i.e. adding /// fields is not a breaking change. pub non_exhaustive_structs: bool, /// Whether the language supports method overloading pub method_overloading: bool, /// Whether the language uses UTF-8 strings pub utf8_strings: bool, /// Whether the language uses UTF-16 strings pub utf16_strings: bool, /// Whether the language supports using slices with 'static lifetimes. pub static_slices: bool, /// Whether the language supports marking types as having a default value pub defaults: bool, // Special methods /// Marking a method as a constructor to generate special constructor methods. pub constructors: bool, /// Marking a method as a named constructor to generate special named constructor methods. pub named_constructors: bool, /// Marking constructors as being able to return errors. This is possible in languages where /// errors are thrown as exceptions (Dart), but not for example in C++, where errors are /// returned as values (constructors usually have to return the type itself). pub fallible_constructors: bool, /// Marking methods as field getters and setters, see [`SpecialMethod::Getter`] and [`SpecialMethod::Setter`] pub accessors: bool, /// Marking *static* methods as field getters and setters, see [`SpecialMethod::Getter`] and [`SpecialMethod::Setter`] pub static_accessors: bool, /// Marking a method as the `to_string` method, which is special in this language. pub stringifiers: bool, /// Marking a method as the `compare_to` method, which is special in this language. pub comparators: bool, /// Marking a method as the `next` method, which is special in this language. pub iterators: bool, /// Marking a method as the `iterator` method, which is special in this language. pub iterables: bool, /// Marking a method as the `[]` operator, which is special in this language. pub indexing: bool, /// Marking a method as an arithmetic operator (+-*/[=]) pub arithmetic: bool, /// Support for Option and Option pub option: bool, /// Allowing callback arguments pub callbacks: bool, /// Allowing traits pub traits: bool, /// Marking a user-defined type as being a valid error result type. pub custom_errors: bool, /// Traits are safe to Send between threads (safe to mark as std::marker::Send) pub traits_are_send: bool, /// Traits are safe to Sync between threads (safe to mark as std::marker::Sync) pub traits_are_sync: bool, /// Whether to generate mocking interface. pub generate_mocking_interface: bool, /// Passing of structs that only hold (non-slice) primitive types /// (for use in slices and languages that support taking direct pointers to structs): pub abi_compatibles: bool, /// Whether or not the language supports &Struct or &mut Struct pub struct_refs: bool, /// Whether the language supports generating functions not associated with any type. pub free_functions: bool, } impl BackendAttrSupport { #[cfg(test)] fn all_true() -> Self { Self { namespacing: true, memory_sharing: true, non_exhaustive_structs: true, method_overloading: true, utf8_strings: true, utf16_strings: true, static_slices: true, defaults: true, constructors: true, named_constructors: true, fallible_constructors: true, static_accessors: true, accessors: true, stringifiers: true, comparators: true, iterators: true, iterables: true, indexing: true, arithmetic: true, option: true, callbacks: true, traits: true, custom_errors: true, traits_are_send: true, traits_are_sync: true, generate_mocking_interface: true, abi_compatibles: true, struct_refs: true, free_functions: true, } } fn check_string(&self, v: &str) -> Option { match v { "namespacing" => Some(self.namespacing), "memory_sharing" => Some(self.memory_sharing), "non_exhaustive_structs" => Some(self.non_exhaustive_structs), "method_overloading" => Some(self.method_overloading), "utf8_strings" => Some(self.utf8_strings), "utf16_strings" => Some(self.utf16_strings), "static_slices" => Some(self.static_slices), "default" => Some(self.defaults), "constructors" => Some(self.constructors), "named_constructors" => Some(self.named_constructors), "fallible_constructors" => Some(self.fallible_constructors), "accessors" => Some(self.accessors), "stringifiers" => Some(self.stringifiers), "comparators" => Some(self.comparators), "iterators" => Some(self.iterators), "iterables" => Some(self.iterables), "indexing" => Some(self.indexing), "arithmetic" => Some(self.arithmetic), "option" => Some(self.option), "callbacks" => Some(self.callbacks), "traits" => Some(self.traits), "custom_errors" => Some(self.custom_errors), "traits_are_send" => Some(self.traits_are_send), "traits_are_sync" => Some(self.traits_are_sync), "abi_compatibles" => Some(self.abi_compatibles), "struct_refs" => Some(self.struct_refs), "free_functions" => Some(self.free_functions), _ => None, } } } /// Defined by backends when validating attributes pub trait AttributeValidator { /// The primary name of the backend, for use in diagnostics fn primary_name(&self) -> &str; /// Does this backend satisfy `cfg(backend_name)`? /// (Backends are allowed to satisfy multiple backend names, useful when there /// are multiple backends for a language) fn is_backend(&self, backend_name: &str) -> bool; /// does this backend satisfy cfg(name = value)? fn is_name_value(&self, name: &str, value: &str) -> Result; /// What backedn attrs does this support? fn attrs_supported(&self) -> BackendAttrSupport; /// Provided, checks if type satisfies a `DiplomatBackendAttrCfg` /// /// auto_found helps check for `auto`, which is only allowed within `any` and at the top level. When `None`, /// `auto` is not allowed. fn satisfies_cfg( &self, cfg: &DiplomatBackendAttrCfg, mut auto_found: Option<&mut bool>, ) -> Result { Ok(match *cfg { DiplomatBackendAttrCfg::Not(ref c) => !self.satisfies_cfg(c, None)?, DiplomatBackendAttrCfg::Any(ref cs) => { #[allow(clippy::needless_option_as_deref)] // False positive: we need this for reborrowing for c in cs { if self.satisfies_cfg(c, auto_found.as_deref_mut())? { return Ok(true); } } false } DiplomatBackendAttrCfg::All(ref cs) => { for c in cs { if !self.satisfies_cfg(c, None)? { return Ok(false); } } true } DiplomatBackendAttrCfg::Auto => { if let Some(found) = auto_found { *found = true; return Ok(true); } else { return Err(LoweringError::Other("auto in diplomat::attr() is only allowed at the top level and within `any`".into())); } } DiplomatBackendAttrCfg::Star => true, DiplomatBackendAttrCfg::BackendName(ref n) => self.is_backend(n), DiplomatBackendAttrCfg::NameValue(ref n, ref v) => self.is_name_value(n, v)?, }) } // Provided, constructs an attribute fn attr_from_ast( &self, ast: &ast::Attrs, parent_attrs: &Attrs, errors: &mut ErrorStore, ) -> Attrs { Attrs::from_ast(ast, self, parent_attrs, errors) } // Provided: validates an attribute in the context in which it was constructed fn validate(&self, attrs: &Attrs, context: AttributeContext, errors: &mut ErrorStore) { attrs.validate(self, context, errors) } } /// A basic attribute validator #[non_exhaustive] #[derive(Default)] pub struct BasicAttributeValidator { /// The primary name of this backend (should be unique, ideally) pub backend_name: String, /// The attributes supported pub support: BackendAttrSupport, /// Additional names for this backend pub other_backend_names: Vec, /// override is_name_value() #[allow(clippy::type_complexity)] // dyn fn is not that complex pub is_name_value: Option bool>>, } impl BasicAttributeValidator { pub fn new(backend_name: &str) -> Self { BasicAttributeValidator { backend_name: backend_name.into(), ..Self::default() } } } impl AttributeValidator for BasicAttributeValidator { fn primary_name(&self) -> &str { &self.backend_name } fn is_backend(&self, backend_name: &str) -> bool { self.backend_name == backend_name || self.other_backend_names.iter().any(|n| n == backend_name) } fn is_name_value(&self, name: &str, value: &str) -> Result { Ok(if name == "supports" { // destructure so new fields are forced to be added let BackendAttrSupport { namespacing, memory_sharing, non_exhaustive_structs, method_overloading, utf8_strings, utf16_strings, static_slices, defaults, constructors, named_constructors, fallible_constructors, accessors, static_accessors, stringifiers, comparators, iterators, iterables, indexing, arithmetic, option, callbacks, traits, custom_errors, traits_are_send, traits_are_sync, generate_mocking_interface, abi_compatibles, struct_refs, free_functions, } = self.support; match value { "namespacing" => namespacing, "memory_sharing" => memory_sharing, "non_exhaustive_structs" => non_exhaustive_structs, "method_overloading" => method_overloading, "utf8_strings" => utf8_strings, "utf16_strings" => utf16_strings, "static_slices" => static_slices, "defaults" => defaults, "constructors" => constructors, "named_constructors" => named_constructors, "fallible_constructors" => fallible_constructors, "accessors" => accessors, "static_accessors" => static_accessors, "stringifiers" => stringifiers, "comparators" => comparators, "iterators" => iterators, "iterables" => iterables, "indexing" => indexing, "arithmetic" => arithmetic, "option" => option, "callbacks" => callbacks, "traits" => traits, "custom_errors" => custom_errors, "traits_are_send" => traits_are_send, "traits_are_sync" => traits_are_sync, "generate_mocking_interface" => generate_mocking_interface, "abi_compatibles" => abi_compatibles, "struct_refs" => struct_refs, "free_functions" => free_functions, _ => { return Err(LoweringError::Other(format!( "Unknown supports = value found: {value}" ))) } } } else if let Some(ref nv) = self.is_name_value { nv(name, value) } else { false }) } fn attrs_supported(&self) -> BackendAttrSupport { self.support } } #[cfg(test)] mod tests { use crate::hir; use std::fmt::Write; macro_rules! uitest_lowering_attr { ($attrs:expr, $($file:tt)*) => { let parsed: syn::File = syn::parse_quote! { $($file)* }; let mut output = String::new(); let mut attr_validator = hir::BasicAttributeValidator::new("tests"); attr_validator.support = $attrs; match hir::TypeContext::from_syn(&parsed, Default::default(), attr_validator) { Ok(_context) => (), Err(e) => { for (ctx, err) in e { writeln!(&mut output, "Lowering error in {ctx}: {err}").unwrap(); } } }; insta::with_settings!({}, { insta::assert_snapshot!(output) }); } } #[test] fn test_auto() { uitest_lowering_attr! { hir::BackendAttrSupport { comparators: true, ..Default::default()}, #[diplomat::bridge] mod ffi { use std::cmp; #[diplomat::opaque] #[diplomat::attr(auto, namespace = "should_not_show_up")] struct Opaque; impl Opaque { #[diplomat::attr(auto, comparison)] pub fn comparator_static(&self, other: &Opaque) -> cmp::Ordering { todo!() } #[diplomat::attr(*, iterator)] pub fn next(&mut self) -> Option { self.0.next() } #[diplomat::attr(auto, rename = "bar")] pub fn auto_doesnt_work_on_renames(&self) { } #[diplomat::attr(auto, disable)] pub fn auto_doesnt_work_on_disables(&self) { } } } } } #[test] fn test_comparator() { uitest_lowering_attr! { hir::BackendAttrSupport::all_true(), #[diplomat::bridge] mod ffi { use std::cmp; #[diplomat::opaque] struct Opaque; struct Struct { field: u8 } impl Opaque { #[diplomat::attr(auto, comparison)] pub fn comparator_static(other: &Opaque) -> cmp::Ordering { todo!() } #[diplomat::attr(auto, comparison)] pub fn comparator_none(&self) -> cmp::Ordering { todo!() } #[diplomat::attr(auto, comparison)] pub fn comparator_othertype(other: Struct) -> cmp::Ordering { todo!() } #[diplomat::attr(auto, comparison)] pub fn comparator_badreturn(&self, other: &Opaque) -> u8 { todo!() } #[diplomat::attr(auto, comparison)] pub fn comparison_correct(&self, other: &Opaque) -> cmp::Ordering { todo!() } pub fn comparison_unmarked(&self, other: &Opaque) -> cmp::Ordering { todo!() } pub fn ordering_wrong(&self, other: cmp::Ordering) { todo!() } #[diplomat::attr(auto, comparison)] pub fn comparison_mut(&self, other: &mut Opaque) -> cmp::Ordering { todo!() } #[diplomat::attr(auto, comparison)] pub fn comparison_opt(&self, other: Option<&Opaque>) -> cmp::Ordering { todo!() } } impl Struct { #[diplomat::attr(auto, comparison)] pub fn comparison_other(self, other: &Opaque) -> cmp::Ordering { todo!() } #[diplomat::attr(auto, comparison)] pub fn comparison_correct(self, other: Self) -> cmp::Ordering { todo!() } #[diplomat::attr(auto, comparison)] pub fn comparison_ref(&self, other: &Self) -> cmp::Ordering { todo!() } #[diplomat::attr(auto, comparison)] pub fn comparison_mut(&mut self, other: &Self) -> cmp::Ordering { todo!() } } } } } #[test] fn test_iterator() { uitest_lowering_attr! { hir::BackendAttrSupport::all_true(), #[diplomat::bridge] mod ffi { #[diplomat::opaque] struct Opaque(Vec); #[diplomat::opaque] struct OpaqueIterator<'a>(std::slice::Iter<'a>); impl Opaque { #[diplomat::attr(auto, iterable)] pub fn iterable<'a>(&'a self) -> Box> { Box::new(OpaqueIterator(self.0.iter())) } } impl OpaqueIterator { #[diplomat::attr(auto, iterator)] pub fn next(&mut self) -> Option { self.0.next() } } #[diplomat::opaque] struct Broken; impl Broken { #[diplomat::attr(auto, iterable)] pub fn iterable_no_return(&self) {} #[diplomat::attr(auto, iterable)] pub fn iterable_no_self() -> Box { todo!() } #[diplomat::attr(auto, iterable)] pub fn iterable_non_custom(&self) -> u8 { todo!() } } #[diplomat::opaque] struct BrokenIterator; impl BrokenIterator { #[diplomat::attr(auto, iterator)] pub fn iterator_no_return(&self) {} #[diplomat::attr(auto, iterator)] pub fn iterator_no_self() -> Option { todo!() } #[diplomat::attr(auto, iterator)] pub fn iterator_no_option(&self) -> u8 { todo!() } } } } } #[test] fn test_unsupported_features() { uitest_lowering_attr! { hir::BackendAttrSupport::default(), #[diplomat::bridge] mod ffi { use std::cmp; use diplomat_runtime::DiplomatOption; #[diplomat::opaque] struct Opaque; struct Struct { pub a: u8, pub b: u8, pub c: DiplomatOption, } struct Struct2 { pub a: DiplomatOption, } #[diplomat::out] struct OutStruct { pub option: DiplomatOption } impl Opaque { pub fn take_option(&self, option: DiplomatOption) { todo!() } // Always ok since this translates to a Resulty return pub fn returning_option_is_ok(&self) -> Option { todo!() } } } } } #[test] fn test_mocking_interface_for_opaque_type() { uitest_lowering_attr! { hir::BackendAttrSupport::all_true(), #[diplomat::bridge] mod ffi { #[diplomat::opaque] #[diplomat::attr(tests, generate_mocking_interface)] pub struct Foo { pub x: u32, pub y: u32, } impl Foo { pub fn new() -> Box { Box::new(Self { x: 0, y: 0 }) } pub fn get_x(&self) -> u32 { self.x } pub fn get_y(&self) -> u32 { self.y } } } } } #[test] fn test_mocking_interface_for_non_opaque_type() { uitest_lowering_attr! { hir::BackendAttrSupport::all_true(), #[diplomat::bridge] mod ffi { #[diplomat::attr(tests, generate_mocking_interface)] pub struct Foo { pub x: u32, pub y: u32, } impl Foo { pub fn new() -> Self { Self { x: 0, y: 0 } } pub fn get_x(self) -> u32 { self.x } pub fn get_y(self) -> u32 { self.y } } } } } #[test] fn test_mocking_interface_for_unsupported_backend() { uitest_lowering_attr! { hir::BackendAttrSupport::default(), #[diplomat::bridge] mod ffi { #[diplomat::attr(tests, generate_mocking_interface)] pub struct Foo { pub x: u32, pub y: u32, } impl Foo { pub fn new() -> Self { Self { x: 0, y: 0 } } pub fn get_x(self) -> u32 { self.x } pub fn get_y(self) -> u32 { self.y } } } } } #[test] fn test_primitive_struct_slices() { uitest_lowering_attr! { hir::BackendAttrSupport::all_true(), #[diplomat::bridge] mod ffi { #[diplomat::attr(auto, abi_compatible)] pub struct Foo { pub x: u32, pub y: u32 } impl Foo { pub fn takes_slice(sl : &[Foo]) { todo!() } } } } } #[test] fn test_primitive_struct_slices_for_unsupported_backend() { uitest_lowering_attr! { hir::BackendAttrSupport::default(), #[diplomat::bridge] mod ffi { #[diplomat::attr(auto, abi_compatible)] pub struct Foo { pub x: u32, pub y: u32 } impl Foo { pub fn takes_slice(sl : &[Foo]) { todo!() } } } } } #[test] fn test_struct_ref_for_unsupported_backend() { uitest_lowering_attr! { hir::BackendAttrSupport::default(), #[diplomat::bridge] mod ffi { #[diplomat::attr(auto, abi_compatible)] pub struct Foo { pub x: u32, pub y: u32 } impl Foo { pub fn takes_mut(&mut self) { todo!() } } } } } }