bon_macros/util/
visibility.rs

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
use crate::util::prelude::*;

pub(crate) trait VisibilityExt {
    /// Returns [`syn::Visibility`] that is equivalent to the current visibility
    /// but for an item that is inside of the child module. This basically does
    /// the following conversions.
    ///
    /// - `pub` -> `pub` (unchanged)
    /// - `pub(crate)` -> `pub(crate)` (unchanged)
    /// - `pub(self)` or ` ` (default private visibility) -> `pub(super)`
    /// - `pub(super)` -> `pub(in super::super)`
    /// - `pub(in relative::path)` -> `pub(in super::relative::path)`
    /// - `pub(in ::absolute::path)` -> `pub(in ::absolute::path)` (unchanged)
    /// - `pub(in crate::path)` -> `pub(in crate::path)` (unchanged)
    /// - `pub(in self::path)` -> `pub(in super::path)`
    /// - `pub(in super::path)` -> `pub(in super::super::path)`
    ///
    /// Note that absolute paths in `pub(in ...)` are not supported with Rust 2018+,
    /// according to the [Rust reference]:
    ///
    /// > Edition Differences: Starting with the 2018 edition, paths for pub(in path)
    /// > must start with crate, self, or super. The 2015 edition may also use paths
    /// > starting with :: or modules from the crate root.
    ///
    /// # Errors
    ///
    /// This function may return an error if it encounters some unexpected syntax.
    /// For example, some syntax that isn't known to the latest version of Rust
    /// this code was written for.
    ///
    /// [Rust reference]: https://doc.rust-lang.org/reference/visibility-and-privacy.html#pubin-path-pubcrate-pubsuper-and-pubself
    fn into_equivalent_in_child_module(self) -> Result<syn::Visibility>;
}

impl VisibilityExt for syn::Visibility {
    fn into_equivalent_in_child_module(mut self) -> Result<syn::Visibility> {
        match &mut self {
            Self::Public(_) => Ok(self),
            Self::Inherited => Ok(syn::parse_quote!(pub(super))),
            Self::Restricted(syn::VisRestricted {
                path,
                in_token: None,
                ..
            }) => {
                if path.is_ident("crate") {
                    return Ok(self);
                }

                if path.is_ident("super") {
                    return Ok(syn::parse_quote!(pub(in super::#path)));
                }

                if path.is_ident("self") {
                    return Ok(syn::parse_quote!(pub(super)));
                }

                bail!(
                    &self,
                    "Expected either `crate` or `super` or `in some::path` inside of \
                    `pub(...)` but got something else. This may be because a new \
                    syntax for visibility was released in a newer Rust version, \
                    but this crate doesn't support it."
                );
            }
            Self::Restricted(syn::VisRestricted {
                path,
                in_token: Some(_),
                ..
            }) => {
                if path.leading_colon.is_some() {
                    return Ok(self);
                }

                if path.starts_with_segment("crate") {
                    return Ok(self);
                }

                if let Some(first_segment) = path.segments.first_mut() {
                    if first_segment.ident == "self" {
                        let span = first_segment.ident.span();
                        *first_segment = syn::parse_quote_spanned!(span=>super);
                        return Ok(self);
                    }
                }

                path.segments.insert(0, syn::parse_quote!(super));

                Ok(self)
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    use syn::parse_quote as pq;

    #[test]
    fn all_tests() {
        #[track_caller]
        // One less `&` character to type in assertions
        #[allow(clippy::needless_pass_by_value)]
        fn test(vis: syn::Visibility, expected: syn::Visibility) {
            let actual = vis.into_equivalent_in_child_module().unwrap();
            assert!(
                actual == expected,
                "got:\nactual: {}\nexpected: {}",
                actual.to_token_stream(),
                expected.to_token_stream()
            );
        }

        test(pq!(pub), pq!(pub));
        test(pq!(pub(crate)), pq!(pub(crate)));
        test(pq!(pub(self)), pq!(pub(super)));
        test(pq!(), pq!(pub(super)));
        test(pq!(pub(super)), pq!(pub(in super::super)));

        test(
            pq!(pub(in relative::path)),
            pq!(pub(in super::relative::path)),
        );
        test(pq!(pub(in crate::path)), pq!(pub(in crate::path)));
        test(pq!(pub(in self::path)), pq!(pub(in super::path)));
        test(pq!(pub(in super::path)), pq!(pub(in super::super::path)));

        test(pq!(pub(in ::absolute::path)), pq!(pub(in ::absolute::path)));
    }
}