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, ¶m.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