• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! #[diplomat::attr] and other attributes
2 
3 use crate::ast;
4 use crate::ast::attrs::{AttrInheritContext, DiplomatBackendAttrCfg, StandardAttribute};
5 use crate::hir::lowering::ErrorStore;
6 use crate::hir::{
7     EnumVariant, LoweringError, Method, Mutability, OpaqueId, ReturnType, SelfType, SuccessType,
8     TraitDef, Type, TypeDef, TypeId,
9 };
10 use syn::Meta;
11 
12 pub use crate::ast::attrs::RenameAttr;
13 
14 /// Diplomat attribute that can be specified on items, methods, and enum variants. These
15 /// can be used to control the codegen in a particular backend.
16 ///
17 /// Most of these are specified via `#[diplomat::attr(some cfg here, attrname)]`, where `some cfg here`
18 /// can be used to pick which backends something applies to.
19 #[non_exhaustive]
20 #[derive(Clone, Default, Debug)]
21 pub struct Attrs {
22     /// "disable" this item: do not generate code for it in the backend
23     ///
24     /// This attribute is always inherited except to variants
25     pub disable: bool,
26     /// An optional namespace. None is equivalent to the root namespace.
27     ///
28     /// This attribute is inherited to types (and is not allowed elsewhere)
29     pub namespace: Option<String>,
30     /// Rename this item/method/variant
31     ///
32     /// This attribute is inherited except through methods and variants (and is not allowed on variants)
33     pub rename: RenameAttr,
34     /// Rename this item in the C ABI. This *must* be respected by backends.
35     ///
36     /// This attribute is inherited except through variants
37     pub abi_rename: RenameAttr,
38     /// This method is "special": it should generate something other than a regular method on the other side.
39     /// This can be something like a constructor, an accessor, a stringifier etc.
40     ///
41     /// This attribute does not participate in inheritance and must always
42     /// be specified on individual methods
43     pub special_method: Option<SpecialMethod>,
44     /// This user-defined type can be used as the error type in a Result.
45     pub custom_errors: bool,
46 
47     /// From #[diplomat::demo()]. Created from [`crate::ast::attrs::Attrs::demo_attrs`].
48     /// List of attributes specific to automatic demo generation.
49     /// Currently just for demo_gen in diplomat-tool (which generates sample webpages), but could be used for broader purposes (i.e., demo Android apps)
50     pub demo_attrs: DemoInfo,
51 }
52 
53 // #region: Demo specific attributes.
54 
55 /// For `#[diplomat::demo(input(...))]`, stored in [DemoInfo::input_cfg].
56 #[non_exhaustive]
57 #[derive(Clone, Default, Debug)]
58 pub struct DemoInputCFG {
59     /// `#[diplomat::demo(input(label = "..."))]`
60     /// Label that this input parameter should have. Let demo_gen pick a valid name if this is empty.
61     ///
62     /// For instance <label for="v">Number Here</label><input name="v"/>
63     pub label: String,
64 
65     /// `#[diplomat::demo(input(default_value = "..."))]`
66     /// Sets the default value for a parameter.
67     ///
68     /// Should ALWAYS be a string. The HTML renderer is expected to do validation for us.
69     pub default_value: String,
70 }
71 
72 #[non_exhaustive]
73 #[derive(Clone, Default, Debug)]
74 pub struct DemoInfo {
75     /// `#[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.
76     pub generate: bool,
77 
78     /// `#[diplomat::demo(default_constructor)]`
79     /// 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.
80     /// TODO: Should probably ignore other `Constructors` if a default has been set.
81     pub default_constructor: bool,
82 
83     /// `#[diplomat::demo(external)]` represents an item that we will not evaluate, and should be passed to the rendering engine to provide.
84     pub external: bool,
85 
86     /// `#[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.
87     ///
88     /// 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.
89     ///
90     /// For more information on custom functions (and their use), see the relevant chapter in [the book](https://rust-diplomat.github.io/book/demo_gen/custom_functions.html).
91     ///
92     /// Files are located relative to lib.rs.
93     ///
94     pub custom_func: Option<String>,
95 
96     /// `#[diplomat::demo(input(...))]` represents configuration options for anywhere we might expect user input.
97     pub input_cfg: DemoInputCFG,
98 }
99 
100 // #endregion
101 
102 /// Attributes that mark methods as "special"
103 #[non_exhaustive]
104 #[derive(Clone, Debug)]
105 pub enum SpecialMethod {
106     /// A constructor.
107     ///
108     /// Must return Self (or Result<Self> for backends with `fallible_constructors` enabled )
109     Constructor,
110     /// A named constructor, with optional name. If the name isn't specified, it will be derived
111     /// from the method name
112     ///
113     /// Must return Self (or Result<Self> for backends with `fallible_constructors` enabled )
114     NamedConstructor(Option<String>),
115 
116     /// A getter, with optional name. If the name isn't specified, it will be derived
117     /// from the method name
118     ///
119     /// Must have no parameters and must return something.
120     Getter(Option<String>),
121     /// A setter, with optional name. If the name isn't specified, it will be derived
122     /// from the method name
123     ///
124     /// Must have no return type (aside from potentially a `Result<(), _>`) and must have one parameter
125     Setter(Option<String>),
126     /// A stringifier. Must have no parameters and return a string (DiplomatWrite)
127     Stringifier,
128     /// A comparison operator. Currently unsupported
129     Comparison,
130     /// An iterator (a type that is mutated to produce new values)
131     Iterator,
132     /// An iterable (a type that can produce an iterator)
133     Iterable,
134     /// Indexes into the type using an integer
135     Indexer,
136 }
137 
138 /// For special methods that affect type semantics, whether this type has this method.
139 ///
140 /// This will likely only contain a subset of special methods, but feel free to add more as needed.
141 #[derive(Debug, Default)]
142 #[non_exhaustive]
143 pub struct SpecialMethodPresence {
144     pub comparator: bool,
145     /// If it is an iterator, the type it iterates over
146     pub iterator: Option<SuccessType>,
147     /// If it is an iterable, the iterator type it returns (*not* the type it iterates over,
148     /// perform lookup on that type to access)
149     pub iterable: Option<OpaqueId>,
150 }
151 
152 /// Where the attribute was found. Some attributes are only allowed in some contexts
153 /// (e.g. namespaces cannot be specified on methods)
154 #[non_exhaustive] // might add module attrs in the future
155 #[derive(Debug)]
156 pub enum AttributeContext<'a, 'b> {
157     Type(TypeDef<'a>),
158     Trait(&'a TraitDef),
159     EnumVariant(&'a EnumVariant),
160     Method(&'a Method, TypeId, &'b mut SpecialMethodPresence),
161     Module,
162     Param,
163     SelfParam,
164     Field,
165 }
166 
maybe_error_unsupported( auto_found: bool, attribute: &str, backend: &str, errors: &mut ErrorStore, )167 fn maybe_error_unsupported(
168     auto_found: bool,
169     attribute: &str,
170     backend: &str,
171     errors: &mut ErrorStore,
172 ) {
173     if !auto_found {
174         errors.push(LoweringError::Other(format!(
175             "`{attribute}` not supported in backend {backend}"
176         )));
177     }
178 }
179 impl Attrs {
from_ast( ast: &ast::Attrs, validator: &(impl AttributeValidator + ?Sized), parent_attrs: &Attrs, errors: &mut ErrorStore, ) -> Self180     pub fn from_ast(
181         ast: &ast::Attrs,
182         validator: &(impl AttributeValidator + ?Sized),
183         parent_attrs: &Attrs,
184         errors: &mut ErrorStore,
185     ) -> Self {
186         let mut this = parent_attrs.clone();
187         // Backends must support this since it applies to the macro/C code.
188         // No special inheritance, was already appropriately inherited in AST
189         this.abi_rename = ast.abi_rename.clone();
190 
191         let support = validator.attrs_supported();
192         let backend = validator.primary_name();
193         for attr in &ast.attrs {
194             let mut auto_found = false;
195             let mut auto_used = false;
196             let satisfies = match validator.satisfies_cfg(&attr.cfg, Some(&mut auto_found)) {
197                 Ok(satisfies) => satisfies,
198                 Err(e) => {
199                     errors.push(e);
200                     continue;
201                 }
202             };
203             if satisfies {
204                 let path = attr.meta.path();
205                 if let Some(path) = path.get_ident() {
206                     if path == "disable" {
207                         if let Meta::Path(_) = attr.meta {
208                             if this.disable {
209                                 errors.push(LoweringError::Other(
210                                     "Duplicate `disable` attribute".into(),
211                                 ));
212                             } else {
213                                 this.disable = true;
214                             }
215                         } else {
216                             errors.push(LoweringError::Other(
217                                 "`disable` must be a simple path".into(),
218                             ))
219                         }
220                     } else if path == "rename" {
221                         match RenameAttr::from_meta(&attr.meta) {
222                             Ok(rename) => {
223                                 // We use the override extend mode: a single ast::Attrs
224                                 // will have had these attributes inherited into the list by appending
225                                 // to the end; so a later attribute in the list is more pertinent.
226                                 this.rename.extend(&rename);
227                             }
228                             Err(e) => errors.push(LoweringError::Other(format!(
229                                 "`rename` attr failed to parse: {e:?}"
230                             ))),
231                         }
232                     } else if path == "namespace" {
233                         if !support.namespacing {
234                             maybe_error_unsupported(auto_found, "constructor", backend, errors);
235                             continue;
236                         }
237                         auto_used = true;
238                         match StandardAttribute::from_meta(&attr.meta) {
239                             Ok(StandardAttribute::String(s)) if s.is_empty() => {
240                                 this.namespace = None
241                             }
242                             Ok(StandardAttribute::String(s)) => this.namespace = Some(s),
243                             Ok(_) | Err(_) => {
244                                 errors.push(LoweringError::Other(
245                                     "`namespace` must have a single string parameter".to_string(),
246                                 ));
247                                 continue;
248                             }
249                         }
250                     } else if path == "constructor"
251                         || path == "stringifier"
252                         || path == "comparison"
253                         || path == "iterable"
254                         || path == "iterator"
255                         || path == "indexer"
256                     {
257                         if let Some(ref existing) = this.special_method {
258                             errors.push(LoweringError::Other(format!(
259                             "Multiple special method markers found on the same method, found {path} and {existing:?}"
260                         )));
261                             continue;
262                         }
263                         let kind = if path == "constructor" {
264                             if !support.constructors {
265                                 maybe_error_unsupported(auto_found, "constructor", backend, errors);
266                                 continue;
267                             }
268                             auto_used = true;
269                             SpecialMethod::Constructor
270                         } else if path == "stringifier" {
271                             if !support.stringifiers {
272                                 maybe_error_unsupported(auto_found, "stringifier", backend, errors);
273                                 continue;
274                             }
275                             auto_used = true;
276                             SpecialMethod::Stringifier
277                         } else if path == "iterable" {
278                             if !support.iterables {
279                                 maybe_error_unsupported(auto_found, "iterable", backend, errors);
280                                 continue;
281                             }
282                             auto_used = true;
283                             SpecialMethod::Iterable
284                         } else if path == "iterator" {
285                             if !support.iterators {
286                                 maybe_error_unsupported(auto_found, "iterator", backend, errors);
287                                 continue;
288                             }
289                             auto_used = true;
290                             SpecialMethod::Iterator
291                         } else if path == "indexer" {
292                             if !support.indexing {
293                                 maybe_error_unsupported(auto_found, "indexer", backend, errors);
294                                 continue;
295                             }
296                             auto_used = true;
297                             SpecialMethod::Indexer
298                         } else {
299                             if !support.comparators {
300                                 maybe_error_unsupported(auto_found, "comparator", backend, errors);
301                                 continue;
302                             }
303                             auto_used = true;
304                             SpecialMethod::Comparison
305                         };
306 
307                         this.special_method = Some(kind);
308                     } else if path == "named_constructor" || path == "getter" || path == "setter" {
309                         if let Some(ref existing) = this.special_method {
310                             errors.push(LoweringError::Other(format!(
311                             "Multiple special method markers found on the same method, found {path} and {existing:?}"
312                         )));
313                             continue;
314                         }
315                         let kind = if path == "named_constructor" {
316                             if !support.named_constructors {
317                                 maybe_error_unsupported(
318                                     auto_found,
319                                     "named_constructors",
320                                     backend,
321                                     errors,
322                                 );
323                                 continue;
324                             }
325                             auto_used = true;
326                             SpecialMethod::NamedConstructor
327                         } else if path == "getter" {
328                             if !support.accessors {
329                                 maybe_error_unsupported(auto_found, "accessors", backend, errors);
330                                 continue;
331                             }
332                             auto_used = true;
333                             SpecialMethod::Getter
334                         } else {
335                             if !support.accessors {
336                                 maybe_error_unsupported(auto_found, "accessors", backend, errors);
337                                 continue;
338                             }
339                             auto_used = true;
340                             SpecialMethod::Setter
341                         };
342                         match StandardAttribute::from_meta(&attr.meta) {
343                             Ok(StandardAttribute::String(s)) => {
344                                 this.special_method = Some(kind(Some(s)))
345                             }
346                             Ok(StandardAttribute::Empty) => this.special_method = Some(kind(None)),
347                             Ok(_) | Err(_) => {
348                                 errors.push(LoweringError::Other(format!(
349                                     "`{path}` must have a single string parameter or no parameter",
350                                 )));
351                                 continue;
352                             }
353                         }
354                     } else if path == "error" {
355                         if !support.custom_errors {
356                             maybe_error_unsupported(auto_found, "error", backend, errors);
357                             continue;
358                         }
359                         auto_used = true;
360                         this.custom_errors = true;
361                     } else {
362                         errors.push(LoweringError::Other(format!(
363                             "Unknown diplomat attribute {path}: expected one of: `disable, rename, namespace, constructor, stringifier, comparison, named_constructor, getter, setter, indexer, error`"
364                         )));
365                     }
366                     if auto_found && !auto_used {
367                         errors.push(LoweringError::Other(format!(
368                             "Diplomat attribute {path} gated on 'auto' but is not one that works with 'auto'"
369                         )));
370                     }
371                 } else {
372                     errors.push(LoweringError::Other(format!(
373                         "Unknown diplomat attribute {path:?}: expected one of: `disable, rename, namespace, constructor, stringifier, comparison, named_constructor, getter, setter, indexer, error`"
374                     )));
375                 }
376             }
377         }
378 
379         for attr in &ast.demo_attrs {
380             let path = attr.meta.path();
381             if let Some(path_ident) = path.get_ident() {
382                 if path_ident == "external" {
383                     this.demo_attrs.external = true;
384                 } else if path_ident == "default_constructor" {
385                     this.demo_attrs.default_constructor = true;
386                 } else if path_ident == "generate" {
387                     this.demo_attrs.generate = true;
388                 } else if path_ident == "input" {
389                     let meta_list = attr
390                         .meta
391                         .require_list()
392                         .expect("Could not get MetaList, expected #[diplomat::demo(input(...))]");
393 
394                     meta_list
395                         .parse_nested_meta(|meta| {
396                             if meta.path.is_ident("label") {
397                                 let value = meta.value()?;
398                                 let s: syn::LitStr = value.parse()?;
399                                 this.demo_attrs.input_cfg.label = s.value();
400                                 Ok(())
401                             } else if meta.path.is_ident("default_value") {
402                                 let value = meta.value()?;
403 
404                                 let str_val: String;
405 
406                                 let ahead = value.lookahead1();
407                                 if ahead.peek(syn::LitFloat) {
408                                     let s: syn::LitFloat = value.parse()?;
409                                     str_val = s.base10_parse::<f64>()?.to_string();
410                                 } else if ahead.peek(syn::LitInt) {
411                                     let s: syn::LitInt = value.parse()?;
412                                     str_val = s.base10_parse::<i64>()?.to_string();
413                                 } else {
414                                     let s: syn::LitStr = value.parse()?;
415                                     str_val = s.value();
416                                 }
417                                 this.demo_attrs.input_cfg.default_value = str_val;
418                                 Ok(())
419                             } else {
420                                 Err(meta.error(format!(
421                                     "Unsupported ident {:?}",
422                                     meta.path.get_ident()
423                                 )))
424                             }
425                         })
426                         .expect("Could not read input(...)");
427                 } else if path_ident == "custom_func" {
428                     let v = &attr.meta.require_name_value().unwrap().value;
429 
430                     if let syn::Expr::Lit(s) = v {
431                         if let syn::Lit::Str(string) = &s.lit {
432                             this.demo_attrs.custom_func = Some(string.value());
433                         } else {
434                             errors.push(LoweringError::Other(format!(
435                                 "#[diplomat::demo(custom_func={s:?}) must be a literal string."
436                             )));
437                         }
438                     } else {
439                         errors.push(LoweringError::Other(format!(
440                             "#[diplomat::demo(custom_func={v:?}) must be a literal string."
441                         )));
442                     }
443                 } else {
444                     errors.push(LoweringError::Other(format!(
445                         "Unknown demo_attr: {path_ident:?}"
446                     )));
447                 }
448             } else {
449                 errors.push(LoweringError::Other(format!("Unknown demo_attr: {path:?}")));
450             }
451         }
452 
453         this
454     }
455 
456     /// Validate that this attribute is allowed in this context
validate( &self, validator: &(impl AttributeValidator + ?Sized), mut context: AttributeContext, errors: &mut ErrorStore, )457     pub(crate) fn validate(
458         &self,
459         validator: &(impl AttributeValidator + ?Sized),
460         mut context: AttributeContext,
461         errors: &mut ErrorStore,
462     ) {
463         // use an exhaustive destructure so new attributes are handled
464         let Attrs {
465             disable,
466             namespace,
467             rename,
468             abi_rename,
469             special_method,
470             custom_errors,
471             demo_attrs: _,
472         } = &self;
473 
474         if *disable && matches!(context, AttributeContext::EnumVariant(..)) {
475             errors.push(LoweringError::Other(
476                 "`disable` cannot be used on enum variants".into(),
477             ))
478         }
479 
480         if let Some(ref special) = special_method {
481             if let AttributeContext::Method(method, self_id, ref mut special_method_presence) =
482                 context
483             {
484                 match special {
485                     SpecialMethod::Constructor | SpecialMethod::NamedConstructor(..) => {
486                         if method.param_self.is_some() {
487                             errors.push(LoweringError::Other(
488                                 "Constructors must not accept a self parameter".to_string(),
489                             ))
490                         }
491                         let output = method.output.success_type();
492                         match method.output {
493                             ReturnType::Infallible(_) => (),
494                             ReturnType::Fallible(..) => {
495                                 if !validator.attrs_supported().fallible_constructors {
496                                     errors.push(LoweringError::Other(
497                                         "This backend doesn't support fallible constructors"
498                                             .to_string(),
499                                     ))
500                                 }
501                             }
502                             ReturnType::Nullable(..) => {
503                                 errors.push(LoweringError::Other("Diplomat doesn't support turning nullable methods into constructors".to_string()));
504                             }
505                         }
506 
507                         if let SuccessType::OutType(t) = &output {
508                             if t.id() != Some(self_id) {
509                                 errors.push(LoweringError::Other(
510                                     "Constructors must return Self!".to_string(),
511                                 ));
512                             }
513                         } else {
514                             errors.push(LoweringError::Other(
515                                 "Constructors must return Self!".to_string(),
516                             ));
517                         }
518                     }
519                     SpecialMethod::Getter(_) => {
520                         if !method.params.is_empty() {
521                             errors
522                                 .push(LoweringError::Other("Getter cannot have parameters".into()));
523                         }
524 
525                         // Currently does not forbid nullable getters, could if desired
526                     }
527 
528                     SpecialMethod::Setter(_) => {
529                         if !matches!(method.output.success_type(), SuccessType::Unit) {
530                             errors.push(LoweringError::Other("Setters must return unit".into()));
531                         }
532                         if method.params.len() != 1 {
533                             errors.push(LoweringError::Other(
534                                 "Setter must have exactly one parameter".into(),
535                             ))
536                         }
537 
538                         // Currently does not forbid fallible setters, could if desired
539                     }
540                     SpecialMethod::Stringifier => {
541                         if !method.params.is_empty() {
542                             errors
543                                 .push(LoweringError::Other("Getter cannot have parameters".into()));
544                         }
545                         if !matches!(method.output.success_type(), SuccessType::Write) {
546                             errors.push(LoweringError::Other(
547                                 "Stringifier must return string".into(),
548                             ));
549                         }
550                     }
551                     SpecialMethod::Comparison => {
552                         if method.params.len() != 1 {
553                             errors.push(LoweringError::Other(
554                                 "Comparator must have single parameter".into(),
555                             ));
556                         }
557                         if special_method_presence.comparator {
558                             errors.push(LoweringError::Other(
559                                 "Cannot define two comparators on the same type".into(),
560                             ));
561                         }
562                         special_method_presence.comparator = true;
563                         // In the long run we can actually support heterogeneous comparators. Not a priority right now.
564                         const COMPARATOR_ERROR: &str =
565                             "Comparator's parameter must be identical to self";
566                         if let Some(ref selfty) = method.param_self {
567                             if let Some(param) = method.params.first() {
568                                 match (&selfty.ty, &param.ty) {
569                                     (SelfType::Opaque(p), Type::Opaque(p2)) => {
570                                         if p.tcx_id != p2.tcx_id {
571                                             errors.push(LoweringError::Other(
572                                                 COMPARATOR_ERROR.into(),
573                                             ));
574                                         }
575 
576                                         if p.owner.mutability != Mutability::Immutable
577                                             || p2.owner.mutability != Mutability::Immutable
578                                         {
579                                             errors.push(LoweringError::Other(
580                                                 "comparators must accept immutable parameters"
581                                                     .into(),
582                                             ));
583                                         }
584 
585                                         if p2.optional.0 {
586                                             errors.push(LoweringError::Other(
587                                                 "comparators must accept non-optional parameters"
588                                                     .into(),
589                                             ));
590                                         }
591                                     }
592                                     (SelfType::Struct(p), Type::Struct(p2)) => {
593                                         if p.tcx_id != p2.tcx_id {
594                                             errors.push(LoweringError::Other(
595                                                 COMPARATOR_ERROR.into(),
596                                             ));
597                                         }
598                                     }
599                                     (SelfType::Enum(p), Type::Enum(p2)) => {
600                                         if p.tcx_id != p2.tcx_id {
601                                             errors.push(LoweringError::Other(
602                                                 COMPARATOR_ERROR.into(),
603                                             ));
604                                         }
605                                     }
606                                     _ => {
607                                         errors.push(LoweringError::Other(COMPARATOR_ERROR.into()));
608                                     }
609                                 }
610                             }
611                         } else {
612                             errors
613                                 .push(LoweringError::Other("Comparator must be non-static".into()));
614                         }
615                     }
616                     SpecialMethod::Iterator => {
617                         if special_method_presence.iterator.is_some() {
618                             errors.push(LoweringError::Other(
619                                 "Cannot mark type as iterator twice".into(),
620                             ));
621                         }
622                         if !method.params.is_empty() {
623                             errors.push(LoweringError::Other(
624                                 "Iterators cannot take parameters".into(),
625                             ))
626                         }
627                         // In theory we could support struct and enum iterators. The benefit is slight:
628                         // it generates probably inefficient code whilst being rather weird when it comes to the
629                         // "structs and enums convert across the boundary" norm for backends.
630                         //
631                         // Essentially, the `&mut self` behavior won't work right.
632                         //
633                         // Furthermore, in some backends (like Dart) defining an iterator may requiring adding fields,
634                         // which may not be possible for enums, and would still be an odd-one-out field for structs.g s
635                         if let Some(this) = &method.param_self {
636                             if !matches!(this.ty, SelfType::Opaque(..)) {
637                                 errors.push(LoweringError::Other(
638                                     "Iterators only allowed on opaques".into(),
639                                 ))
640                             }
641                         } else {
642                             errors.push(LoweringError::Other("Iterators must take self".into()))
643                         }
644 
645                         if let ReturnType::Nullable(ref o) = method.output {
646                             if let SuccessType::Unit = o {
647                                 errors.push(LoweringError::Other(
648                                     "Iterator method must return something".into(),
649                                 ));
650                             }
651                             special_method_presence.iterator = Some(o.clone());
652                         } else if let ReturnType::Infallible(SuccessType::OutType(
653                             crate::hir::OutType::Opaque(
654                                 ref o @ crate::hir::OpaquePath {
655                                     optional: crate::hir::Optional(true),
656                                     ..
657                                 },
658                             ),
659                         )) = method.output
660                         {
661                             let mut o = o.clone();
662                             o.optional = crate::hir::Optional(false);
663 
664                             special_method_presence.iterator =
665                                 Some(SuccessType::OutType(crate::hir::OutType::Opaque(o)));
666                         } else {
667                             errors.push(LoweringError::Other(
668                                 "Iterator method must return nullable value".into(),
669                             ));
670                         }
671                     }
672                     SpecialMethod::Iterable => {
673                         if special_method_presence.iterable.is_some() {
674                             errors.push(LoweringError::Other(
675                                 "Cannot mark type as iterable twice".into(),
676                             ));
677                         }
678                         if !method.params.is_empty() {
679                             errors.push(LoweringError::Other(
680                                 "Iterables cannot take parameters".into(),
681                             ))
682                         }
683                         if method.param_self.is_none() {
684                             errors.push(LoweringError::Other("Iterables must take self".into()))
685                         }
686 
687                         match method.output.success_type() {
688                             SuccessType::OutType(ty) => {
689                                 if let Some(TypeId::Opaque(id)) = ty.id() {
690                                     special_method_presence.iterable = Some(id);
691                                 } else {
692                                     errors.push(LoweringError::Other(
693                                         "Iterables must return a custom opaque type".into(),
694                                     ))
695                                 }
696                             }
697                             _ => errors.push(LoweringError::Other(
698                                 "Iterables must return a custom type".into(),
699                             )),
700                         }
701                     }
702                     SpecialMethod::Indexer => {
703                         if method.params.len() != 1 {
704                             errors.push(LoweringError::Other(
705                                 "Indexer must have exactly one parameter".into(),
706                             ));
707                         }
708 
709                         if method.output.success_type().is_unit() {
710                             errors.push(LoweringError::Other("Indexer must return a value".into()));
711                         }
712                     }
713                 }
714             } else {
715                 errors.push(LoweringError::Other(format!("Special method (type {special:?}) not allowed on non-method context {context:?}")))
716             }
717         }
718 
719         if namespace.is_some()
720             && matches!(
721                 context,
722                 AttributeContext::Method(..) | AttributeContext::EnumVariant(..)
723             )
724         {
725             errors.push(LoweringError::Other(
726                 "`namespace` can only be used on types".to_string(),
727             ));
728         }
729 
730         if matches!(
731             context,
732             AttributeContext::Param | AttributeContext::SelfParam | AttributeContext::Field
733         ) {
734             if *disable {
735                 errors.push(LoweringError::Other(format!(
736                     "`disable`s cannot be used on an {context:?}."
737                 )));
738             }
739 
740             if namespace.is_some() {
741                 errors.push(LoweringError::Other(format!(
742                     "`namespace` cannot be used on an {context:?}."
743                 )));
744             }
745 
746             if !rename.is_empty() || !abi_rename.is_empty() {
747                 errors.push(LoweringError::Other(format!(
748                     "`rename`s cannot be used on an {context:?}."
749                 )));
750             }
751 
752             if special_method.is_some() {
753                 errors.push(LoweringError::Other(format!(
754                     "{context:?} cannot be special methods."
755                 )));
756             }
757         }
758 
759         if *custom_errors
760             && !matches!(
761                 context,
762                 AttributeContext::Type(..) | AttributeContext::Trait(..)
763             )
764         {
765             errors.push(LoweringError::Other(
766                 "`error` can only be used on types".to_string(),
767             ));
768         }
769     }
770 
for_inheritance(&self, context: AttrInheritContext) -> Attrs771     pub(crate) fn for_inheritance(&self, context: AttrInheritContext) -> Attrs {
772         let rename = self.rename.attrs_for_inheritance(context, false);
773 
774         // Disabling shouldn't inherit to variants
775         let disable = if context == AttrInheritContext::Variant {
776             false
777         } else {
778             self.disable
779         };
780         let namespace = if matches!(
781             context,
782             AttrInheritContext::Module | AttrInheritContext::Type
783         ) {
784             self.namespace.clone()
785         } else {
786             None
787         };
788 
789         Attrs {
790             disable,
791             rename,
792             namespace,
793             // Was already inherited on the AST side
794             abi_rename: Default::default(),
795             // Never inherited
796             special_method: None,
797             // Not inherited
798             custom_errors: false,
799             demo_attrs: Default::default(),
800         }
801     }
802 }
803 
804 /// Non-exhaustive list of what attributes and other features your backend is able to handle, based on #[diplomat::attr(...)] contents.
805 /// Set this through an [`AttributeValidator`].
806 ///
807 /// See [`SpecialMethod`] and [`Attrs`] for your specific implementation needs.
808 ///
809 /// For example, the current dart backend supports [`BackendAttrSupport::constructors`]. So when it encounters:
810 /// ```ignore
811 /// struct Sample {}
812 /// impl Sample {
813 ///     #[diplomat::attr(constructor)]
814 ///     pub fn new() -> Box<Self> {
815 ///         Box::new(Sample{})
816 ///     }
817 /// }
818 ///
819 /// ```
820 ///
821 /// It generates
822 /// ```dart
823 /// factory Sample()
824 /// ```
825 ///
826 /// If a backend does not support a specific `#[diplomat::attr(...)]`, it may error.
827 #[non_exhaustive]
828 #[derive(Copy, Clone, Debug, Default)]
829 pub struct BackendAttrSupport {
830     /// Namespacing types, e.g. C++ `namespace`.
831     pub namespacing: bool,
832     /// Rust can directly acccess the memory of this language, like C and C++.
833     /// This is not supported in any garbage-collected language.
834     pub memory_sharing: bool,
835     /// This language's structs are non-exhaustive by default, i.e. adding
836     /// fields is not a breaking change.
837     pub non_exhaustive_structs: bool,
838     /// Whether the language supports method overloading
839     pub method_overloading: bool,
840     /// Whether the language uses UTF-8 strings
841     pub utf8_strings: bool,
842     /// Whether the language uses UTF-16 strings
843     pub utf16_strings: bool,
844     /// Whether the language supports using slices with 'static lifetimes.
845     pub static_slices: bool,
846 
847     // Special methods
848     /// Marking a method as a constructor to generate special constructor methods.
849     pub constructors: bool,
850     /// Marking a method as a named constructor to generate special named constructor methods.
851     pub named_constructors: bool,
852     /// Marking constructors as being able to return errors. This is possible in languages where
853     /// errors are thrown as exceptions (Dart), but not for example in C++, where errors are
854     /// returned as values (constructors usually have to return the type itself).
855     pub fallible_constructors: bool,
856     /// Marking methods as field getters and setters, see [`SpecialMethod::Getter`] and [`SpecialMethod::Setter`]
857     pub accessors: bool,
858     /// Marking a method as the `to_string` method, which is special in this language.
859     pub stringifiers: bool,
860     /// Marking a method as the `compare_to` method, which is special in this language.
861     pub comparators: bool,
862     /// Marking a method as the `next` method, which is special in this language.
863     pub iterators: bool,
864     /// Marking a method as the `iterator` method, which is special in this language.
865     pub iterables: bool,
866     /// Marking a method as the `[]` operator, which is special in this language.
867     pub indexing: bool,
868 
869     /// Support for Option<Struct> and Option<Primitive>
870     pub option: bool,
871     /// Allowing callback arguments
872     pub callbacks: bool,
873     /// Allowing traits
874     pub traits: bool,
875     /// Marking a user-defined type as being a valid error result type.
876     pub custom_errors: bool,
877     /// Traits are safe to Send between threads (safe to mark as std::marker::Send)
878     pub traits_are_send: bool,
879     /// Traits are safe to Sync between threads (safe to mark as std::marker::Sync)
880     pub traits_are_sync: bool,
881 }
882 
883 impl BackendAttrSupport {
884     #[cfg(test)]
all_true() -> Self885     fn all_true() -> Self {
886         Self {
887             namespacing: true,
888             memory_sharing: true,
889             non_exhaustive_structs: true,
890             method_overloading: true,
891             utf8_strings: true,
892             utf16_strings: true,
893             static_slices: true,
894 
895             constructors: true,
896             named_constructors: true,
897             fallible_constructors: true,
898             accessors: true,
899             stringifiers: true,
900             comparators: true,
901             iterators: true,
902             iterables: true,
903             indexing: true,
904             option: true,
905             callbacks: true,
906             traits: true,
907             custom_errors: true,
908             traits_are_send: true,
909             traits_are_sync: true,
910         }
911     }
912 }
913 
914 /// Defined by backends when validating attributes
915 pub trait AttributeValidator {
916     /// The primary name of the backend, for use in diagnostics
primary_name(&self) -> &str917     fn primary_name(&self) -> &str;
918     /// Does this backend satisfy `cfg(backend_name)`?
919     /// (Backends are allowed to satisfy multiple backend names, useful when there
920     /// are multiple backends for a language)
is_backend(&self, backend_name: &str) -> bool921     fn is_backend(&self, backend_name: &str) -> bool;
922     /// does this backend satisfy cfg(name = value)?
is_name_value(&self, name: &str, value: &str) -> Result<bool, LoweringError>923     fn is_name_value(&self, name: &str, value: &str) -> Result<bool, LoweringError>;
924     /// What backedn attrs does this support?
attrs_supported(&self) -> BackendAttrSupport925     fn attrs_supported(&self) -> BackendAttrSupport;
926 
927     /// Provided, checks if type satisfies a `DiplomatBackendAttrCfg`
928     ///
929     /// auto_found helps check for `auto`, which is only allowed within `any` and at the top level. When `None`,
930     /// `auto` is not allowed.
satisfies_cfg( &self, cfg: &DiplomatBackendAttrCfg, mut auto_found: Option<&mut bool>, ) -> Result<bool, LoweringError>931     fn satisfies_cfg(
932         &self,
933         cfg: &DiplomatBackendAttrCfg,
934         mut auto_found: Option<&mut bool>,
935     ) -> Result<bool, LoweringError> {
936         Ok(match *cfg {
937             DiplomatBackendAttrCfg::Not(ref c) => !self.satisfies_cfg(c, None)?,
938             DiplomatBackendAttrCfg::Any(ref cs) => {
939                 #[allow(clippy::needless_option_as_deref)]
940                 // False positive: we need this for reborrowing
941                 for c in cs {
942                     if self.satisfies_cfg(c, auto_found.as_deref_mut())? {
943                         return Ok(true);
944                     }
945                 }
946                 false
947             }
948             DiplomatBackendAttrCfg::All(ref cs) => {
949                 for c in cs {
950                     if !self.satisfies_cfg(c, None)? {
951                         return Ok(false);
952                     }
953                 }
954                 true
955             }
956             DiplomatBackendAttrCfg::Auto => {
957                 if let Some(found) = auto_found {
958                     *found = true;
959                     return Ok(true);
960                 } else {
961                     return Err(LoweringError::Other("auto in diplomat::attr() is only allowed at the top level and within `any`".into()));
962                 }
963             }
964             DiplomatBackendAttrCfg::Star => true,
965             DiplomatBackendAttrCfg::BackendName(ref n) => self.is_backend(n),
966             DiplomatBackendAttrCfg::NameValue(ref n, ref v) => self.is_name_value(n, v)?,
967         })
968     }
969 
970     // Provided, constructs an attribute
attr_from_ast( &self, ast: &ast::Attrs, parent_attrs: &Attrs, errors: &mut ErrorStore, ) -> Attrs971     fn attr_from_ast(
972         &self,
973         ast: &ast::Attrs,
974         parent_attrs: &Attrs,
975         errors: &mut ErrorStore,
976     ) -> Attrs {
977         Attrs::from_ast(ast, self, parent_attrs, errors)
978     }
979 
980     // Provided: validates an attribute in the context in which it was constructed
validate(&self, attrs: &Attrs, context: AttributeContext, errors: &mut ErrorStore)981     fn validate(&self, attrs: &Attrs, context: AttributeContext, errors: &mut ErrorStore) {
982         attrs.validate(self, context, errors)
983     }
984 }
985 
986 /// A basic attribute validator
987 #[non_exhaustive]
988 #[derive(Default)]
989 pub struct BasicAttributeValidator {
990     /// The primary name of this backend (should be unique, ideally)
991     pub backend_name: String,
992     /// The attributes supported
993     pub support: BackendAttrSupport,
994     /// Additional names for this backend
995     pub other_backend_names: Vec<String>,
996     /// override is_name_value()
997     #[allow(clippy::type_complexity)] // dyn fn is not that complex
998     pub is_name_value: Option<Box<dyn Fn(&str, &str) -> bool>>,
999 }
1000 
1001 impl BasicAttributeValidator {
new(backend_name: &str) -> Self1002     pub fn new(backend_name: &str) -> Self {
1003         BasicAttributeValidator {
1004             backend_name: backend_name.into(),
1005             ..Self::default()
1006         }
1007     }
1008 }
1009 
1010 impl AttributeValidator for BasicAttributeValidator {
primary_name(&self) -> &str1011     fn primary_name(&self) -> &str {
1012         &self.backend_name
1013     }
is_backend(&self, backend_name: &str) -> bool1014     fn is_backend(&self, backend_name: &str) -> bool {
1015         self.backend_name == backend_name
1016             || self.other_backend_names.iter().any(|n| n == backend_name)
1017     }
is_name_value(&self, name: &str, value: &str) -> Result<bool, LoweringError>1018     fn is_name_value(&self, name: &str, value: &str) -> Result<bool, LoweringError> {
1019         Ok(if name == "supports" {
1020             // destructure so new fields are forced to be added
1021             let BackendAttrSupport {
1022                 namespacing,
1023                 memory_sharing,
1024                 non_exhaustive_structs,
1025                 method_overloading,
1026                 utf8_strings,
1027                 utf16_strings,
1028                 static_slices,
1029 
1030                 constructors,
1031                 named_constructors,
1032                 fallible_constructors,
1033                 accessors,
1034                 stringifiers,
1035                 comparators,
1036                 iterators,
1037                 iterables,
1038                 indexing,
1039                 option,
1040                 callbacks,
1041                 traits,
1042                 custom_errors,
1043                 traits_are_send,
1044                 traits_are_sync,
1045             } = self.support;
1046             match value {
1047                 "namespacing" => namespacing,
1048                 "memory_sharing" => memory_sharing,
1049                 "non_exhaustive_structs" => non_exhaustive_structs,
1050                 "method_overloading" => method_overloading,
1051                 "utf8_strings" => utf8_strings,
1052                 "utf16_strings" => utf16_strings,
1053                 "static_slices" => static_slices,
1054 
1055                 "constructors" => constructors,
1056                 "named_constructors" => named_constructors,
1057                 "fallible_constructors" => fallible_constructors,
1058                 "accessors" => accessors,
1059                 "stringifiers" => stringifiers,
1060                 "comparators" => comparators,
1061                 "iterators" => iterators,
1062                 "iterables" => iterables,
1063                 "indexing" => indexing,
1064                 "option" => option,
1065                 "callbacks" => callbacks,
1066                 "traits" => traits,
1067                 "custom_errors" => custom_errors,
1068                 "traits_are_send" => traits_are_send,
1069                 "traits_are_sync" => traits_are_sync,
1070                 _ => {
1071                     return Err(LoweringError::Other(format!(
1072                         "Unknown supports = value found: {value}"
1073                     )))
1074                 }
1075             }
1076         } else if let Some(ref nv) = self.is_name_value {
1077             nv(name, value)
1078         } else {
1079             false
1080         })
1081     }
attrs_supported(&self) -> BackendAttrSupport1082     fn attrs_supported(&self) -> BackendAttrSupport {
1083         self.support
1084     }
1085 }
1086 
1087 #[cfg(test)]
1088 mod tests {
1089     use crate::hir;
1090     use std::fmt::Write;
1091 
1092     macro_rules! uitest_lowering_attr {
1093         ($attrs:expr, $($file:tt)*) => {
1094             let parsed: syn::File = syn::parse_quote! { $($file)* };
1095 
1096             let mut output = String::new();
1097 
1098             let mut attr_validator = hir::BasicAttributeValidator::new("tests");
1099             attr_validator.support = $attrs;
1100             match hir::TypeContext::from_syn(&parsed, attr_validator) {
1101                 Ok(_context) => (),
1102                 Err(e) => {
1103                     for (ctx, err) in e {
1104                         writeln!(&mut output, "Lowering error in {ctx}: {err}").unwrap();
1105                     }
1106                 }
1107             };
1108             insta::with_settings!({}, {
1109                 insta::assert_snapshot!(output)
1110             });
1111         }
1112     }
1113 
1114     #[test]
test_auto()1115     fn test_auto() {
1116         uitest_lowering_attr! { hir::BackendAttrSupport { comparators: true, ..Default::default()},
1117             #[diplomat::bridge]
1118             mod ffi {
1119                 use std::cmp;
1120 
1121                 #[diplomat::opaque]
1122                 #[diplomat::attr(auto, namespace = "should_not_show_up")]
1123                 struct Opaque;
1124 
1125 
1126                 impl Opaque {
1127                     #[diplomat::attr(auto, comparison)]
1128                     pub fn comparator_static(&self, other: &Opaque) -> cmp::Ordering {
1129                         todo!()
1130                     }
1131                     #[diplomat::attr(*, iterator)]
1132                     pub fn next(&mut self) -> Option<u8> {
1133                         self.0.next()
1134                     }
1135                     #[diplomat::attr(auto, rename = "bar")]
1136                     pub fn auto_doesnt_work_on_renames(&self) {
1137                     }
1138                     #[diplomat::attr(auto, disable)]
1139                     pub fn auto_doesnt_work_on_disables(&self) {
1140                     }
1141                 }
1142 
1143             }
1144         }
1145     }
1146 
1147     #[test]
test_comparator()1148     fn test_comparator() {
1149         uitest_lowering_attr! { hir::BackendAttrSupport::all_true(),
1150             #[diplomat::bridge]
1151             mod ffi {
1152                 use std::cmp;
1153 
1154                 #[diplomat::opaque]
1155                 struct Opaque;
1156 
1157                 struct Struct {
1158                     field: u8
1159                 }
1160 
1161 
1162                 impl Opaque {
1163                     #[diplomat::attr(auto, comparison)]
1164                     pub fn comparator_static(other: &Opaque) -> cmp::Ordering {
1165                         todo!()
1166                     }
1167                     #[diplomat::attr(auto, comparison)]
1168                     pub fn comparator_none(&self) -> cmp::Ordering {
1169                         todo!()
1170                     }
1171                     #[diplomat::attr(auto, comparison)]
1172                     pub fn comparator_othertype(other: Struct) -> cmp::Ordering {
1173                         todo!()
1174                     }
1175                     #[diplomat::attr(auto, comparison)]
1176                     pub fn comparator_badreturn(&self, other: &Opaque) -> u8 {
1177                         todo!()
1178                     }
1179                     #[diplomat::attr(auto, comparison)]
1180                     pub fn comparison_correct(&self, other: &Opaque) -> cmp::Ordering {
1181                         todo!()
1182                     }
1183                     pub fn comparison_unmarked(&self, other: &Opaque) -> cmp::Ordering {
1184                         todo!()
1185                     }
1186                     pub fn ordering_wrong(&self, other: cmp::Ordering) {
1187                         todo!()
1188                     }
1189                     #[diplomat::attr(auto, comparison)]
1190                     pub fn comparison_mut(&self, other: &mut Opaque) -> cmp::Ordering {
1191                         todo!()
1192                     }
1193                     #[diplomat::attr(auto, comparison)]
1194                     pub fn comparison_opt(&self, other: Option<&Opaque>) -> cmp::Ordering {
1195                         todo!()
1196                     }
1197                 }
1198 
1199                 impl Struct {
1200                     #[diplomat::attr(auto, comparison)]
1201                     pub fn comparison_other(self, other: &Opaque) -> cmp::Ordering {
1202                         todo!()
1203                     }
1204                     #[diplomat::attr(auto, comparison)]
1205                     pub fn comparison_correct(self, other: Self) -> cmp::Ordering {
1206                         todo!()
1207                     }
1208                 }
1209             }
1210         }
1211     }
1212 
1213     #[test]
test_iterator()1214     fn test_iterator() {
1215         uitest_lowering_attr! { hir::BackendAttrSupport::all_true(),
1216             #[diplomat::bridge]
1217             mod ffi {
1218 
1219                 #[diplomat::opaque]
1220                 struct Opaque(Vec<u8>);
1221                 #[diplomat::opaque]
1222                 struct OpaqueIterator<'a>(std::slice::Iter<'a>);
1223 
1224 
1225                 impl Opaque {
1226                     #[diplomat::attr(auto, iterable)]
1227                     pub fn iterable<'a>(&'a self) -> Box<OpaqueIterator<'a>> {
1228                         Box::new(OpaqueIterator(self.0.iter()))
1229                     }
1230                 }
1231 
1232                 impl OpaqueIterator {
1233                     #[diplomat::attr(auto, iterator)]
1234                     pub fn next(&mut self) -> Option<u8> {
1235                         self.0.next()
1236                     }
1237                 }
1238 
1239                 #[diplomat::opaque]
1240                 struct Broken;
1241 
1242                 impl Broken {
1243                     #[diplomat::attr(auto, iterable)]
1244                     pub fn iterable_no_return(&self) {}
1245                     #[diplomat::attr(auto, iterable)]
1246                     pub fn iterable_no_self() -> Box<BrokenIterator> { todo!() }
1247 
1248                     #[diplomat::attr(auto, iterable)]
1249                     pub fn iterable_non_custom(&self) -> u8 { todo!() }
1250                 }
1251 
1252                 #[diplomat::opaque]
1253                 struct BrokenIterator;
1254 
1255                 impl BrokenIterator {
1256                     #[diplomat::attr(auto, iterator)]
1257                     pub fn iterator_no_return(&self) {}
1258                     #[diplomat::attr(auto, iterator)]
1259                     pub fn iterator_no_self() -> Option<u8> { todo!() }
1260 
1261                     #[diplomat::attr(auto, iterator)]
1262                     pub fn iterator_no_option(&self) -> u8 { todo!() }
1263                 }
1264             }
1265         }
1266     }
1267 
1268     #[test]
test_unsupported_features()1269     fn test_unsupported_features() {
1270         uitest_lowering_attr! { hir::BackendAttrSupport::default(),
1271             #[diplomat::bridge]
1272             mod ffi {
1273                 use std::cmp;
1274                 use diplomat_runtime::DiplomatOption;
1275 
1276                 #[diplomat::opaque]
1277                 struct Opaque;
1278 
1279                 struct Struct {
1280                     pub a: u8,
1281                     pub b: u8,
1282                     pub c: DiplomatOption<u8>,
1283                 }
1284 
1285                 struct Struct2 {
1286                     pub a: DiplomatOption<Struct>,
1287                 }
1288 
1289                 #[diplomat::out]
1290                 struct OutStruct {
1291                     pub option: DiplomatOption<u8>
1292                 }
1293 
1294 
1295                 impl Opaque {
1296                     pub fn take_option(&self, option: DiplomatOption<u8>) {
1297                         todo!()
1298                     }
1299                     // Always ok since this translates to a Resulty return
1300                     pub fn returning_option_is_ok(&self) -> Option<u8> {
1301                         todo!()
1302                     }
1303                 }
1304 
1305             }
1306         }
1307     }
1308 }
1309