bon_macros/util/
visibility.rs

1use crate::util::prelude::*;
2
3pub(crate) trait VisibilityExt {
4    /// Returns [`syn::Visibility`] that is equivalent to the current visibility
5    /// but for an item that is inside of the child module. This basically does
6    /// the following conversions.
7    ///
8    /// - `pub` -> `pub` (unchanged)
9    /// - `pub(crate)` -> `pub(crate)` (unchanged)
10    /// - `pub(self)` or ` ` (default private visibility) -> `pub(super)`
11    /// - `pub(super)` -> `pub(in super::super)`
12    /// - `pub(in relative::path)` -> `pub(in super::relative::path)`
13    /// - `pub(in ::absolute::path)` -> `pub(in ::absolute::path)` (unchanged)
14    /// - `pub(in crate::path)` -> `pub(in crate::path)` (unchanged)
15    /// - `pub(in self::path)` -> `pub(in super::path)`
16    /// - `pub(in super::path)` -> `pub(in super::super::path)`
17    ///
18    /// Note that absolute paths in `pub(in ...)` are not supported with Rust 2018+,
19    /// according to the [Rust reference]:
20    ///
21    /// > Edition Differences: Starting with the 2018 edition, paths for pub(in path)
22    /// > must start with crate, self, or super. The 2015 edition may also use paths
23    /// > starting with :: or modules from the crate root.
24    ///
25    /// # Errors
26    ///
27    /// This function may return an error if it encounters some unexpected syntax.
28    /// For example, some syntax that isn't known to the latest version of Rust
29    /// this code was written for.
30    ///
31    /// [Rust reference]: https://doc.rust-lang.org/reference/visibility-and-privacy.html#pubin-path-pubcrate-pubsuper-and-pubself
32    fn into_equivalent_in_child_module(self) -> Result<syn::Visibility>;
33}
34
35impl VisibilityExt for syn::Visibility {
36    fn into_equivalent_in_child_module(mut self) -> Result<syn::Visibility> {
37        match &mut self {
38            Self::Public(_) => Ok(self),
39            Self::Inherited => Ok(syn::parse_quote!(pub(super))),
40            Self::Restricted(syn::VisRestricted {
41                path,
42                in_token: None,
43                ..
44            }) => {
45                if path.is_ident("crate") {
46                    return Ok(self);
47                }
48
49                if path.is_ident("super") {
50                    return Ok(syn::parse_quote!(pub(in super::#path)));
51                }
52
53                if path.is_ident("self") {
54                    return Ok(syn::parse_quote!(pub(super)));
55                }
56
57                bail!(
58                    &self,
59                    "Expected either `crate` or `super` or `in some::path` inside of \
60                    `pub(...)` but got something else. This may be because a new \
61                    syntax for visibility was released in a newer Rust version, \
62                    but this crate doesn't support it."
63                );
64            }
65            Self::Restricted(syn::VisRestricted {
66                path,
67                in_token: Some(_),
68                ..
69            }) => {
70                if path.leading_colon.is_some() {
71                    return Ok(self);
72                }
73
74                if path.starts_with_segment("crate") {
75                    return Ok(self);
76                }
77
78                if let Some(first_segment) = path.segments.first_mut() {
79                    if first_segment.ident == "self" {
80                        let span = first_segment.ident.span();
81                        *first_segment = syn::parse_quote_spanned!(span=>super);
82                        return Ok(self);
83                    }
84                }
85
86                path.segments.insert(0, syn::parse_quote!(super));
87
88                Ok(self)
89            }
90        }
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    use syn::parse_quote as pq;
99
100    #[test]
101    fn all_tests() {
102        #[track_caller]
103        // One less `&` character to type in assertions
104        #[allow(clippy::needless_pass_by_value)]
105        fn test(vis: syn::Visibility, expected: syn::Visibility) {
106            let actual = vis.into_equivalent_in_child_module().unwrap();
107            assert!(
108                actual == expected,
109                "got:\nactual: {}\nexpected: {}",
110                actual.to_token_stream(),
111                expected.to_token_stream()
112            );
113        }
114
115        test(pq!(pub), pq!(pub));
116        test(pq!(pub(crate)), pq!(pub(crate)));
117        test(pq!(pub(self)), pq!(pub(super)));
118        test(pq!(), pq!(pub(super)));
119        test(pq!(pub(super)), pq!(pub(in super::super)));
120
121        test(
122            pq!(pub(in relative::path)),
123            pq!(pub(in super::relative::path)),
124        );
125        test(pq!(pub(in crate::path)), pq!(pub(in crate::path)));
126        test(pq!(pub(in self::path)), pq!(pub(in super::path)));
127        test(pq!(pub(in super::path)), pq!(pub(in super::super::path)));
128
129        test(pq!(pub(in ::absolute::path)), pq!(pub(in ::absolute::path)));
130    }
131}