1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
/*
* Copyright (c) godot-rust; Bromeon and contributors.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
// Some duplication with godot-codegen/signals.rs; see comments there.
use proc_macro2::{Delimiter, Ident, TokenStream, TokenTree};
use quote::{ToTokens, format_ident, quote};
use crate::util::bail;
use crate::{ParseResult, util};
/// Holds information known from a signal's definition
pub struct SignalDefinition {
/// The signal's function signature (simplified, not original declaration).
pub fn_signature: venial::Function,
/// The signal's non-gdext attributes (all except #[signal]).
pub external_attributes: Vec<venial::Attribute>,
/// Whether there is going to be a type-safe builder for this signal (true by default).
pub has_builder: bool,
/// Whether to register with Godot using a `_`-prefixed name, hiding the signal from docs/autocomplete.
/// Consistent with `#[class(internal)]` naming.
pub is_internal: bool,
}
// ----------------------------------------------------------------------------------------------------------------------------------------------
/// Limits the visibility of signals to a few "blessed" syntaxes, excluding `pub(in PATH)`.
///
/// This is necessary because the signal collection (containing all signals) must have the widest visibility of any signal, and for
/// that a total order must exist. `in` paths cannot be semantically analyzed by proc-macros.
///
/// Documented in <https://godot-rust.github.io/book/register/signals.html#signal-visibility>.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub enum SignalVisibility {
Priv,
PubSuper,
PubCrate,
Pub,
}
impl SignalVisibility {
pub fn try_parse(tokens: Option<&venial::VisMarker>) -> Option<Self> {
// No tokens: private.
let Some(tokens) = tokens else {
return Some(Self::Priv);
};
debug_assert_eq!(tokens.tk_token1.to_string(), "pub");
// Early exit if `pub` without following `(...)` group.
let group = match &tokens.tk_token2 {
None => return Some(Self::Pub),
Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Parenthesis => group,
_ => return None,
};
// `pub(...)` -> extract `...` part.
let mut tokens_in_paren = group.stream().into_iter();
let vis = match tokens_in_paren.next() {
Some(TokenTree::Ident(ident)) if ident == "super" => Self::PubSuper,
Some(TokenTree::Ident(ident)) if ident == "crate" => Self::PubCrate,
_ => return None,
};
// No follow-up tokens allowed.
if tokens_in_paren.next().is_some() {
return None;
}
Some(vis)
}
}
impl ToTokens for SignalVisibility {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::Priv => { /* do nothing */ }
Self::Pub => tokens.extend(quote! { pub }),
Self::PubSuper => tokens.extend(quote! { pub(super) }),
Self::PubCrate => tokens.extend(quote! { pub(crate) }),
}
}
}
// ----------------------------------------------------------------------------------------------------------------------------------------------
/// Extracted syntax info for a declared signal.
struct SignalDetails<'a> {
/// `fn my_signal(i: i32, s: GString)` -- simplified from original declaration.
fn_signature: &'a venial::Function,
/// `MyClass`
#[allow(unused)]
// Current impl doesn't need it, but we already have it, too annoying to add/remove during refactors.
class_name: &'a Ident,
/// `i32`, `GString`
param_types: Vec<venial::TypeExpr>,
/// `i`, `s`
param_names: Vec<Ident>,
/// `"i"`, `"s"`
param_names_str: Vec<String>,
/// `(i32, GString)`
param_tuple: TokenStream,
/// `MySignal`
signal_name: &'a Ident,
/// `"_my_signal"` if `is_internal`, otherwise `"my_signal"`. Used for Godot registration and `TypedSignal::extract`.
godot_name_str: String,
/// `#[cfg(..)] #[cfg_attr(published_docs, doc(cfg(..)))] #[cfg(..)]`
signal_cfg_attrs: Vec<&'a venial::Attribute>,
/// `///` `#[doc = ...]`
signal_doc_attrs: Vec<&'a venial::Attribute>,
/// `MyClass_MySignal`
individual_struct_name: Ident,
/// Visibility, e.g. `pub(crate)`
vis_marker: Option<venial::VisMarker>,
// /// Detected visibility as strongly typed enum.
// vis_classified: SignalVisibility,
}
impl<'a> SignalDetails<'a> {
pub fn extract(
fn_signature: &'a venial::Function, // *Not* the original #[signal], just the signature part (no attributes, body, etc).
class_name: &'a Ident,
external_attributes: &'a [venial::Attribute],
is_internal: bool,
) -> ParseResult<SignalDetails<'a>> {
let mut param_types = vec![];
let mut param_names = vec![];
let mut param_names_str = vec![];
for (param, _punct) in fn_signature.params.inner.iter() {
match param {
venial::FnParam::Typed(param) => {
param_types.push(param.ty.clone());
param_names.push(param.name.clone());
param_names_str.push(param.name.to_string());
}
venial::FnParam::Receiver(receiver) => {
return bail!(receiver, "#[signal] cannot have receiver (self) parameter");
}
};
}
// Transport #[cfg] attributes to the FFI glue, to ensure signals which were conditionally
// removed from compilation don't cause errors.
let signal_cfg_attrs = util::extract_cfg_attrs(external_attributes)
.into_iter()
.collect();
// Preserve docs.
let signal_doc_attrs = util::extract_doc_attrs(external_attributes)
.into_iter()
.collect();
let param_tuple = quote! { ( #( #param_types, )* ) };
let signal_name = &fn_signature.name;
let rust_name_str = fn_signature.name.to_string();
let godot_name_str = if is_internal {
format!("_{rust_name_str}")
} else {
rust_name_str.clone()
};
let individual_struct_name = format_ident!(
"__godot_Signal_{class_name}_{signal_name}",
span = signal_name.span()
);
let vis_marker = &fn_signature.vis_marker;
let Some(_vis_classified) = SignalVisibility::try_parse(vis_marker.as_ref()) else {
return bail!(
vis_marker,
"invalid visibility `{}` for #[signal]; supported are `pub`, `pub(crate)`, `pub(super)` and private (no visibility marker)",
vis_marker.to_token_stream().to_string()
);
};
Ok(Self {
fn_signature,
class_name,
param_types,
param_names,
param_names_str,
param_tuple,
signal_name,
godot_name_str,
signal_cfg_attrs,
signal_doc_attrs,
individual_struct_name,
vis_marker: vis_marker.clone(),
// vis_classified,
})
}
}
/// Returns tuple of:
/// * Code registering signals with Godot engine.
/// * Symbolic APIs for signals (collection struct + individual signal types + `WithSignal`/`Deref` impls).
pub fn make_signal_registrations(
signals: &[SignalDefinition],
class_name: &Ident,
class_name_obj: &TokenStream,
no_typed_signals: bool,
) -> ParseResult<(Vec<TokenStream>, Option<TokenStream>)> {
let mut signal_registrations = Vec::new();
let mut collection_api = SignalCollection::default();
// let mut max_visibility = SignalVisibility::Priv;
for signal in signals {
let SignalDefinition {
fn_signature,
external_attributes,
has_builder,
is_internal,
} = signal;
let details =
SignalDetails::extract(fn_signature, class_name, external_attributes, *is_internal)?;
// Type-safe signal builder API, if available.
if *has_builder {
collection_api.extend_with(&details);
// max_visibility = max_visibility.max(details.vis_classified);
}
let mut registration = make_signal_registration(&details, class_name_obj);
// Warn if signal name starts with `_` but isn't declared as #[signal(internal)].
if !is_internal && details.godot_name_str.starts_with('_') {
let warning_fn = Ident::new("signal_underscore_prefix", fn_signature.name.span());
registration = quote! {
::godot::__deprecated::emit_deprecated_warning!(#warning_fn);
#registration
};
}
signal_registrations.push(registration);
}
// Rewrite the above using #[cfg].
let signal_symbols =
(!no_typed_signals).then(|| make_signal_symbols(class_name, collection_api));
Ok((signal_registrations, signal_symbols))
}
// ----------------------------------------------------------------------------------------------------------------------------------------------
fn make_signal_registration(details: &SignalDetails, class_name_obj: &TokenStream) -> TokenStream {
let SignalDetails {
param_types,
param_names,
param_names_str,
godot_name_str,
signal_cfg_attrs,
..
} = details;
let param_list = util::make_signature_param_type(param_types);
let indexes = 0..param_types.len();
let param_property_infos = quote! {
[
// Don't use raw sys pointers directly; it's very easy to have objects going out of scope.
#(
<#param_list as ::godot::meta::conv::ParamTuple>
::property_info(#indexes, #param_names_str).unwrap(),
)*
]
};
let signal_parameters_count = param_names.len();
// Don't use quote_spanned! for the entire block -- the unsafe code should NOT be attributed to the user's signal definition.
quote! {
#(#signal_cfg_attrs)*
unsafe {
use ::godot::sys;
let parameters_info: [::godot::register::info::PropertyInfo; #signal_parameters_count] = #param_property_infos;
let mut parameters_info_sys: [sys::GDExtensionPropertyInfo; #signal_parameters_count] =
std::array::from_fn(|i| parameters_info[i].property_sys());
let signal_name = ::godot::builtin::StringName::from(#godot_name_str);
sys::interface_fn!(classdb_register_extension_class_signal)(
sys::get_library(),
#class_name_obj.string_sys(),
signal_name.string_sys(),
parameters_info_sys.as_ptr(),
sys::GDExtensionInt::from(#signal_parameters_count as i64),
);
}
}
}
// ----------------------------------------------------------------------------------------------------------------------------------------------
/// A collection struct accessible via `.signals()` in the generated impl.
///
/// Also defines individual signal types.
#[derive(Default)]
struct SignalCollection {
/// The individual `my_signal()` accessors, returning concrete signal types.
provider_methods: Vec<TokenStream>,
/// The actual signal definitions, including both `struct` and `impl` blocks.
individual_structs: Vec<TokenStream>,
}
impl SignalCollection {
fn extend_with(&mut self, details: &SignalDetails) {
let SignalDetails {
signal_name,
godot_name_str,
signal_cfg_attrs,
signal_doc_attrs,
individual_struct_name,
vis_marker,
..
} = details;
self.provider_methods.push(quote! {
// Deliberately not #[doc(hidden)] for IDE completion.
#(#signal_cfg_attrs)*
#(#signal_doc_attrs)*
// Note: this could be `pub` always and would still compile (maybe warning with the following message).
// associated function `SignalCollection::my_signal` is reachable at visibility `pub(crate)`
//
// However, it would still lead to a compile error when declaring the individual signal struct `pub` (or any other
// visibility that exceeds the class visibility). So, we can as well declare the visibility here.
#vis_marker fn #signal_name(&mut self) -> #individual_struct_name<'c, C> {
#individual_struct_name {
__typed: ::godot::signal::TypedSignal::<'c, C, _>::extract(&mut self.__internal_obj, #godot_name_str)
}
}
});
self.individual_structs
.push(make_signal_individual_struct(details))
}
fn is_empty(&self) -> bool {
self.individual_structs.is_empty()
}
}
fn make_asarg_params(params: &venial::Punctuated<venial::FnParam>) -> TokenStream {
// Could be specialized by trying to parse types, but won't be 100% accurate due to lack of semantics (AsArg could be a safe fallback). E.g.:
// if ty.tokens.iter().any(|tk| matches!(tk, TokenTree::Ident(ident) if ident == "Gd")) {
// quote! { impl ::godot::meta::AsObjectArg<#some_inner_ty> }
// }
let mut tokens = TokenStream::new();
for (param, _punct) in params.iter() {
match param {
venial::FnParam::Typed(param) => {
let param_name = ¶m.name;
let param_type = ¶m.ty;
tokens.extend(quote! {
#param_name: impl ::godot::meta::AsArg<#param_type>,
});
}
venial::FnParam::Receiver(_) => {
unreachable!("signals have no receivers; already checked")
}
};
}
tokens
}
fn make_signal_individual_struct(details: &SignalDetails) -> TokenStream {
let emit_params = make_asarg_params(&details.fn_signature.params);
let SignalDetails {
// class_name,
param_names,
param_tuple,
signal_cfg_attrs,
individual_struct_name,
vis_marker,
..
} = details;
// Define the individual types + trait impls. The idea was originally to use a module to reduce namespace pollution:
// let module_name = format_ident!("__godot_signal_{class_name}_{signal_name}");
// #(#signal_cfg_attrs)* pub mod #module_name { use super::*; ... }
// #(#signal_cfg_attrs)* pub(crate) use #module_name::#individual_struct_name;
// However, there are some challenges:
// - Visibility is a pain to handle: rustc doesn't like re-exporting private symbols as pub, and we can't know the visibility of the
// surrounding class struct. Users must explicitly declare #[signal]s `pub` if they want wider visibility; this must not exceed the
// visibility of the class itself.
// - Not yet clear if we should have each signal + related types in separate module. If #[signal] is supported in #[godot_api(secondary)]
// impl blocks, then we would have to group them by the impl block. Rust doesn't allow partial modules, so they'd need to have individual
// names as well, possibly explicitly chosen by the user.
//
// For now, #[doc(hidden)] is used in some places to limit namespace pollution at least in IDEs + docs. This also means that the generated
// code is less observable by the user. If someone comes up with a good idea to handle all this, let us know :)
quote! {
#(#signal_cfg_attrs)*
#[allow(non_camel_case_types)]
#[doc(hidden)] // Signal struct is hidden, but the method returning it is not (IDE completion).
#vis_marker struct #individual_struct_name<'c, C: ::godot::obj::WithSignals> {
#[doc(hidden)]
__typed: ::godot::signal::TypedSignal<'c, C, #param_tuple>,
}
// Concrete convenience API is macro-based; many parts are delegated to TypedSignal via Deref/DerefMut.
#(#signal_cfg_attrs)*
impl<C: ::godot::obj::WithSignals> #individual_struct_name<'_, C> {
pub fn emit(&mut self, #emit_params) {
use ::godot::meta::AsArg;
#(
::godot::private::arg_into_owned!(infer #param_names);
//let #param_names = #param_names.into_arg();
)*
self.__typed.emit_tuple((#( #param_names, )*));
}
}
#(#signal_cfg_attrs)*
impl<'c, C: ::godot::obj::WithSignals> std::ops::Deref for #individual_struct_name<'c, C> {
type Target = ::godot::signal::TypedSignal<'c, C, #param_tuple>;
fn deref(&self) -> &Self::Target {
&self.__typed
}
}
#(#signal_cfg_attrs)*
impl<C: ::godot::obj::WithSignals> std::ops::DerefMut for #individual_struct_name<'_, C> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.__typed
}
}
}
}
/// Generates symbolic API for signals:
/// * collection (unspecified-name struct holding methods to access each signal)
/// * can be absent if the class declares no own #[signal]s.
/// * individual signal types
/// * trait impls
fn make_signal_symbols(
class_name: &Ident,
collection_api: SignalCollection,
// max_visibility: SignalVisibility,
) -> TokenStream {
// Earlier implementation generated a simplified code when no #[signal] was declared: only WithSignals/WithUserSignals impl, but no own
// collection, instead the associated type pointing to the base class. This has however some problems:
// * Part of the reason for user-defined collection is to store UserSignalObject instead of Gd, which can store &mut self.
// This is necessary for self.signals().some_base_signal().emit(), if such a signal is connected to Self::connect*() taking &mut self;
// Gd would cause a borrow error.
// * Once we add Rust-Rust inheritance, we'd need to differentiate case again, which can be tricky since #[godot_api] has no information
// about the base class.
//
// As compromise, we always generate a collection struct if either at least 1 #[signal] is declared or the struct has a Base<T> field.
// We also provide opt-out via #[godot_api(no_typed_signals)].
let declares_no_signals = collection_api.is_empty();
let collection_struct_name =
format_ident!("__godot_Signals_{class_name}", span = class_name.span());
let collection_struct_methods = &collection_api.provider_methods;
let with_signals_impl = make_with_signals_impl(class_name, &collection_struct_name);
let upcast_deref_impl = make_upcast_deref_impl(class_name, &collection_struct_name);
let individual_structs = collection_api.individual_structs;
// The collection cannot be `pub` because `Deref::Target` contains the class type, which leads to "leak private type" errors.
// We thus adopt the visibility of the #[derive(GodotClass)] struct, imported via macro trick.
//
// A previous approach (that cannot access the struct visibility) used "max visibility": the user decides which visibility is acceptable f
// or individual #[signal]s. They can all be at most the class visibility. Since we assume that decision is correct, the signal collection
// itself can also share the widest visibility of any #[signal]. This approach however still led to problems because there's a 2-way
// dependency: `impl WithSignals for MyClass` has an associated type `SignalCollection` that mentions the generated collection type. If
// that collection type has *lower* visibility than the class, we *also* run into "leak private type" errors.
// Unrelated, we could use the following for encapsulation:
// mod #signal_mod_name {
// pub use super::*;
// ... // all the code below
// }
// pub use #signal_mod_name::*;
//
// This now makes signal types/methods invisible to the surrounding scope, so we'd need to adjust visibility in some cases:
// * private -> `pub(super)`
// * `pub(super)` -> pub(in super::super)
//
// Benefit of encapsulating would be:
// * No need for `#[doc(hidden)]` on internal symbols like fields.
// * Any potential #[cfg]s (in the past for >= 4.2 API level) would not need to be repeated. This is less of a problem if the #[cfg]
// is used inside the macro instead of generated code.
// * Less scope pollution (even though names are mangled).
//
// Downside is slightly higher complexity and introducing signals in secondary blocks becomes harder (although we could use another
// module name, we'd need a way to create unique names).
let visibility_macro = util::format_class_visibility_macro(class_name);
// Span propagation: the visibility macro causes error messages for "method not found" on the signals collection to point to
// `#[derive(GodotClass)]` instead of `impl ClassName`. This is because `macro_rules!` hygiene overrides input token spans. Using
// `quote_spanned!` here doesn't help since spans are lost when passing through declarative macros (it works without surrounding macro).
// A proc-macro helper instead of `macro_rules!` could preserve spans, but adds complexity.
let mut code = quote! {
#visibility_macro! {
#[allow(non_camel_case_types)]
#[doc(hidden)] // Only on struct, not methods, to allow completion in IDEs.
struct #collection_struct_name<'c, C> {
// Hiding necessary because it's in the same scope as the user-defined class, so appearing in IDE completion.
#[doc(hidden)]
__internal_obj: Option<::godot::private::UserSignalObject<'c, C>>
}
}
impl<'c, C> #collection_struct_name<'c, C>
where // bounds: see UserSignalObject::into_typed_signal().
C: ::godot::obj::WithUserSignals +
::godot::obj::WithSignals<__SignalObj<'c> = ::godot::private::UserSignalObject<'c, C>>,
{
#( #collection_struct_methods )*
}
#with_signals_impl
#upcast_deref_impl
#( #individual_structs )*
};
// base_field_macro! is a macro that expands to all input tokens if the class declares a Base<T> field, and to nothing otherwise.
// This makes sure that WithSignals is only implemented for classes with a base field, and avoids compile errors about it.
// Only when no #[signal] is declared -> otherwise the user explicitly requests it, and a compile error is better to guide them.
if declares_no_signals {
let base_field_macro = util::format_class_base_field_macro(class_name);
code = quote! {
#base_field_macro! { #code }
};
}
code
}
/// Declare `impl WithSignals` and `impl WithUserSignals` with own signal collection.
fn make_with_signals_impl(class_name: &Ident, collection_struct_name: &Ident) -> TokenStream {
quote! {
impl ::godot::obj::WithSignals for #class_name {
type SignalCollection<'c, C: ::godot::obj::WithSignals> = #collection_struct_name<'c, C>;
#[doc(hidden)]
type __SignalObj<'c> = ::godot::private::UserSignalObject<'c, Self>;
#[doc(hidden)]
fn __signals_from_external(external: & ::godot::obj::Gd<Self>) -> Self::SignalCollection<'_, Self> {
Self::SignalCollection {
__internal_obj: Some(::godot::private::UserSignalObject::External {
gd: external.clone().upcast_object()
})
}
}
}
impl ::godot::obj::WithUserSignals for #class_name {
fn signals(&mut self) -> Self::SignalCollection<'_, Self> {
Self::SignalCollection {
__internal_obj: Some(::godot::private::UserSignalObject::Internal { self_mut: self })
}
}
}
}
}
fn make_upcast_deref_impl(class_name: &Ident, collection_struct_name: &Ident) -> TokenStream {
quote! {
impl<'c, C: ::godot::obj::WithSignals> std::ops::Deref for #collection_struct_name<'c, C> {
type Target = <
<
#class_name as ::godot::obj::GodotClass
>::Base as ::godot::obj::WithSignals
>::SignalCollection<'c, C>;
fn deref(&self) -> &Self::Target {
type Derived = #class_name;
::godot::private::signal_collection_to_base::<C, Derived>(self)
}
}
impl<'c, C: ::godot::obj::WithSignals> std::ops::DerefMut for #collection_struct_name<'c, C> {
fn deref_mut(&mut self) -> &mut Self::Target {
type Derived = #class_name;
::godot::private::signal_collection_to_base_mut::<C, Derived>(self)
}
}
}
}
// ----------------------------------------------------------------------------------------------------------------------------------------------
#[cfg(test)] #[cfg_attr(published_docs, doc(cfg(test)))]
mod tests {
use super::*;
#[test]
fn parse_signal_visibility() {
#[rustfmt::skip]
let list = [
(quote! { pub }, Some(SignalVisibility::Pub)),
(quote! { pub(crate) }, Some(SignalVisibility::PubCrate)),
(quote! {}, Some(SignalVisibility::Priv)),
(quote! { pub(super) }, Some(SignalVisibility::PubSuper)),
(quote! { pub(self) }, None), // not supported (equivalent to private)
(quote! { pub(in crate::foo) }, None),
];
let parsed = list
.iter()
.map(|(vis, _)| {
// Dummy function, because venial has no per-item parser in public API.
let item = venial::parse_item(quote! {
#vis fn f() {}
});
let Ok(venial::Item::Function(f)) = item else {
panic!("expected function")
};
SignalVisibility::try_parse(f.vis_marker.as_ref())
})
.collect::<Vec<_>>();
for ((_, expected), actual) in list.iter().zip(parsed.iter()) {
assert_eq!(expected, actual);
}
}
#[test]
fn signal_visibility_order() {
assert!(SignalVisibility::Pub > SignalVisibility::PubCrate);
assert!(SignalVisibility::PubCrate > SignalVisibility::PubSuper);
assert!(SignalVisibility::PubSuper > SignalVisibility::Priv);
}
}