Skip to main content

hir_def/
visibility.rs

1//! Defines hir-level representation of visibility (e.g. `pub` and `pub(crate)`).
2
3use std::iter;
4
5use base_db::{Crate, SourceDatabase};
6use hir_expand::{InFile, Lookup};
7use la_arena::ArenaMap;
8use syntax::ast::{self, HasVisibility};
9
10use crate::{
11    AssocItemId, HasModule, ItemContainerId, LocalFieldId, ModuleId, ModuleIdLt, TraitId,
12    VariantId, nameres::DefMap, resolver::HasResolver, signatures::VariantFields, src::HasSource,
13};
14
15pub use crate::item_tree::{RawVisibility, VisibilityExplicitness};
16
17/// Visibility of an item, with the path resolved.
18#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
19pub enum Visibility {
20    /// Visibility is restricted to a certain module.
21    Module(ModuleId, VisibilityExplicitness),
22    /// Visibility is restricted to the crate.
23    PubCrate(Crate),
24    /// Visibility is unrestricted.
25    Public,
26}
27
28impl Visibility {
29    pub fn resolve(
30        db: &dyn SourceDatabase,
31        resolver: &crate::resolver::Resolver<'_>,
32        raw_vis: &RawVisibility,
33    ) -> Self {
34        // we fall back to public visibility (i.e. fail open) if the path can't be resolved
35        resolver.resolve_visibility(db, raw_vis).unwrap_or(Visibility::Public)
36    }
37
38    pub(crate) fn is_visible_from_other_crate(self) -> bool {
39        matches!(self, Visibility::Public)
40    }
41
42    #[tracing::instrument(skip_all)]
43    pub fn is_visible_from<'db>(
44        self,
45        db: &'db dyn SourceDatabase,
46        from_module: ModuleIdLt<'db>,
47    ) -> bool {
48        let to_module = match self {
49            Visibility::Module(m, _) => unsafe { m.to_db(db) },
50            Visibility::PubCrate(krate) => return from_module.krate(db) == krate,
51            Visibility::Public => return true,
52        };
53        if from_module == to_module {
54            // if the modules are the same, visibility is trivially satisfied
55            return true;
56        }
57        // if they're not in the same crate, it can't be visible
58        if from_module.krate(db) != to_module.krate(db) {
59            return false;
60        }
61        let def_map = from_module.def_map(db);
62        Self::is_visible_from_def_map_(db, def_map, to_module, from_module)
63    }
64
65    pub(crate) fn is_visible_from_def_map<'db>(
66        self,
67        db: &'db dyn SourceDatabase,
68        def_map: &'db DefMap,
69        from_module: ModuleIdLt<'db>,
70    ) -> bool {
71        if cfg!(debug_assertions) {
72            _ = def_map.modules[from_module];
73        }
74        let to_module = match self {
75            Visibility::Module(m, _) => m,
76            Visibility::PubCrate(krate) => return from_module.krate(db) == krate,
77            Visibility::Public => return true,
78        };
79        if from_module == to_module {
80            // if the modules are the same, visibility is trivially satisfied
81            return true;
82        }
83        // if they're not in the same crate, it can't be visible
84        if def_map.krate() != to_module.krate(db) {
85            return false;
86        }
87
88        if from_module == to_module && def_map.block_id() == to_module.block(db) {
89            // if the modules are the same, visibility is trivially satisfied
90            return true;
91        }
92        Self::is_visible_from_def_map_(db, def_map, to_module, from_module)
93    }
94
95    fn is_visible_from_def_map_<'db>(
96        db: &'db dyn SourceDatabase,
97        def_map: &'db DefMap,
98        mut to_module: ModuleIdLt<'db>,
99        mut from_module: ModuleIdLt<'db>,
100    ) -> bool {
101        debug_assert_eq!(to_module.krate(db), def_map.krate());
102        // `to_module` might be the root module of a block expression. Those have the same
103        // visibility as the containing module (even though no items are directly nameable from
104        // there, getting this right is important for method resolution).
105        // In that case, we adjust the visibility of `to_module` to point to the containing module.
106
107        // Additional complication: `to_module` might be in `from_module`'s `DefMap`, which we're
108        // currently computing, so we must not call the `def_map` query for it.
109        let def_map_block = def_map.block_id();
110        loop {
111            match (to_module.block(db), def_map_block) {
112                // `to_module` is not a block, so there is no parent def map to use.
113                (None, _) => (),
114                // `to_module` is at `def_map`'s block, no need to move further.
115                (Some(a), Some(b)) if a == b => {}
116                _ => {
117                    if let Some(parent) = to_module.def_map(db).parent() {
118                        to_module = parent;
119                        continue;
120                    }
121                }
122            }
123            break;
124        }
125
126        // from_module needs to be a descendant of to_module
127        let mut def_map = def_map;
128        let mut parent_arc;
129        loop {
130            if from_module == to_module {
131                return true;
132            }
133            match def_map[from_module].parent {
134                Some(parent) => from_module = parent,
135                None => {
136                    match def_map.parent() {
137                        Some(module) => {
138                            parent_arc = module.def_map(db);
139                            def_map = parent_arc;
140                            from_module = module;
141                        }
142                        // Reached the root module, nothing left to check.
143                        None => return false,
144                    }
145                }
146            }
147        }
148    }
149
150    /// Returns the most permissive visibility of `self` and `other`.
151    ///
152    /// If there is no subset relation between `self` and `other`, returns `None` (i.e. they're only
153    /// visible in unrelated modules).
154    pub(crate) fn max(
155        self,
156        db: &dyn SourceDatabase,
157        other: Visibility,
158        def_map: &DefMap,
159    ) -> Option<Visibility> {
160        match (self, other) {
161            (_, Visibility::Public) | (Visibility::Public, _) => Some(Visibility::Public),
162            (Visibility::PubCrate(krate), Visibility::PubCrate(krateb)) => {
163                if krate == krateb {
164                    Some(Visibility::PubCrate(krate))
165                } else {
166                    None
167                }
168            }
169            (Visibility::Module(mod_, _), Visibility::PubCrate(krate))
170            | (Visibility::PubCrate(krate), Visibility::Module(mod_, _)) => {
171                if mod_.krate(db) == krate { Some(Visibility::PubCrate(krate)) } else { None }
172            }
173            (Visibility::Module(mod_a, expl_a), Visibility::Module(mod_b, expl_b)) => {
174                if mod_a == mod_b {
175                    // Most module visibilities are `pub(self)`, and assuming no errors
176                    // this will be the common and thus fast path.
177                    return Some(Visibility::Module(
178                        mod_a,
179                        match (expl_a, expl_b) {
180                            (VisibilityExplicitness::Explicit, _)
181                            | (_, VisibilityExplicitness::Explicit) => {
182                                VisibilityExplicitness::Explicit
183                            }
184                            _ => VisibilityExplicitness::Implicit,
185                        },
186                    ));
187                }
188
189                if mod_a.krate(db) != def_map.krate() || mod_b.krate(db) != def_map.krate() {
190                    return None;
191                }
192
193                let def_block = def_map.block_id();
194                if mod_a.block(db) != def_block || mod_b.block(db) != def_block {
195                    return None;
196                }
197
198                let mut a_ancestors = iter::successors(Some(mod_a), |&m| def_map[m].parent);
199
200                if a_ancestors.any(|m| m == mod_b) {
201                    // B is above A
202                    return Some(Visibility::Module(mod_b, expl_b));
203                }
204
205                let mut b_ancestors = iter::successors(Some(mod_b), |&m| def_map[m].parent);
206                if b_ancestors.any(|m| m == mod_a) {
207                    // A is above B
208                    return Some(Visibility::Module(mod_a, expl_a));
209                }
210
211                None
212            }
213        }
214    }
215
216    /// Returns the least permissive visibility of `self` and `other`.
217    ///
218    /// If there is no subset relation between `self` and `other`, returns `None` (i.e. they're only
219    /// visible in unrelated modules).
220    pub(crate) fn min(
221        self,
222        db: &dyn SourceDatabase,
223        other: Visibility,
224        def_map: &DefMap,
225    ) -> Option<Visibility> {
226        match (self, other) {
227            (vis, Visibility::Public) | (Visibility::Public, vis) => Some(vis),
228            (Visibility::PubCrate(krate), Visibility::PubCrate(krateb)) => {
229                if krate == krateb {
230                    Some(Visibility::PubCrate(krate))
231                } else {
232                    None
233                }
234            }
235            (Visibility::Module(mod_, exp), Visibility::PubCrate(krate))
236            | (Visibility::PubCrate(krate), Visibility::Module(mod_, exp)) => {
237                if mod_.krate(db) == krate { Some(Visibility::Module(mod_, exp)) } else { None }
238            }
239            (Visibility::Module(mod_a, expl_a), Visibility::Module(mod_b, expl_b)) => {
240                if mod_a == mod_b {
241                    // Most module visibilities are `pub(self)`, and assuming no errors
242                    // this will be the common and thus fast path.
243                    return Some(Visibility::Module(
244                        mod_a,
245                        match (expl_a, expl_b) {
246                            (VisibilityExplicitness::Explicit, _)
247                            | (_, VisibilityExplicitness::Explicit) => {
248                                VisibilityExplicitness::Explicit
249                            }
250                            _ => VisibilityExplicitness::Implicit,
251                        },
252                    ));
253                }
254
255                if mod_a.krate(db) != def_map.krate() || mod_b.krate(db) != def_map.krate() {
256                    return None;
257                }
258
259                let def_block = def_map.block_id();
260                if mod_a.block(db) != def_block || mod_b.block(db) != def_block {
261                    return None;
262                }
263
264                let mut a_ancestors = iter::successors(Some(mod_a), |&m| def_map[m].parent);
265
266                if a_ancestors.any(|m| m == mod_b) {
267                    // B is above A
268                    return Some(Visibility::Module(mod_a, expl_a));
269                }
270
271                let mut b_ancestors = iter::successors(Some(mod_b), |&m| def_map[m].parent);
272                if b_ancestors.any(|m| m == mod_a) {
273                    // A is above B
274                    return Some(Visibility::Module(mod_b, expl_b));
275                }
276
277                None
278            }
279        }
280    }
281
282    #[inline]
283    pub fn is_public(&self) -> bool {
284        matches!(self, Visibility::Public)
285    }
286}
287
288#[salsa::tracked]
289impl VariantFields {
290    /// Resolve visibility of all specific fields of a struct or union variant.
291    #[salsa::tracked(returns(ref))]
292    pub fn field_visibilities(
293        db: &dyn SourceDatabase,
294        variant_id: VariantId,
295    ) -> ArenaMap<LocalFieldId, Visibility> {
296        let variant_fields = variant_id.fields(db);
297        let fields = variant_fields.fields();
298        if fields.is_empty() {
299            return ArenaMap::default();
300        }
301        let resolver = variant_id.module(db).resolver(db);
302        let mut res = ArenaMap::with_capacity(fields.len());
303        for (field_id, field_data) in fields.iter() {
304            res.insert(field_id, Visibility::resolve(db, &resolver, &field_data.visibility));
305        }
306        res
307    }
308}
309
310pub fn visibility_from_ast(
311    db: &dyn SourceDatabase,
312    has_resolver: impl HasResolver + HasModule,
313    ast_vis: InFile<Option<ast::Visibility>>,
314) -> Visibility {
315    let mut span_map = None;
316    let raw_vis = crate::item_tree::visibility_from_ast(db, ast_vis.value, &mut |range| {
317        span_map.get_or_insert_with(|| ast_vis.file_id.span_map(db)).span_for_range(range).ctx
318    });
319    match raw_vis {
320        RawVisibility::PubSelf(explicitness) => {
321            Visibility::Module(has_resolver.module(db), explicitness)
322        }
323        RawVisibility::PubCrate => Visibility::PubCrate(has_resolver.krate(db)),
324        RawVisibility::Public => Visibility::Public,
325        RawVisibility::Module(..) => Visibility::resolve(db, &has_resolver.resolver(db), &raw_vis),
326    }
327}
328
329#[salsa::tracked]
330impl AssocItemId {
331    /// Resolve visibility of an assoc item.
332    #[salsa::tracked(returns(copy))]
333    pub fn assoc_visibility(self, db: &dyn SourceDatabase) -> Visibility {
334        match self {
335            AssocItemId::FunctionId(function_id) => {
336                let loc = function_id.lookup(db);
337                trait_item_visibility(db, loc.container).unwrap_or_else(|| {
338                    let source = loc.source(db);
339                    visibility_from_ast(db, function_id, source.map(|src| src.visibility()))
340                })
341            }
342            AssocItemId::ConstId(const_id) => {
343                let loc = const_id.lookup(db);
344                trait_item_visibility(db, loc.container).unwrap_or_else(|| {
345                    let source = loc.source(db);
346                    visibility_from_ast(db, const_id, source.map(|src| src.visibility()))
347                })
348            }
349            AssocItemId::TypeAliasId(type_alias_id) => {
350                let loc = type_alias_id.lookup(db);
351                trait_item_visibility(db, loc.container).unwrap_or_else(|| {
352                    let source = loc.source(db);
353                    visibility_from_ast(db, type_alias_id, source.map(|src| src.visibility()))
354                })
355            }
356        }
357    }
358}
359
360fn trait_item_visibility(
361    db: &dyn SourceDatabase,
362    container: ItemContainerId,
363) -> Option<Visibility> {
364    match container {
365        ItemContainerId::TraitId(trait_) => Some(trait_visibility(db, trait_)),
366        _ => None,
367    }
368}
369
370fn trait_visibility(db: &dyn SourceDatabase, def: TraitId) -> Visibility {
371    let loc = def.lookup(db);
372    let source = loc.source(db);
373    visibility_from_ast(db, def, source.map(|src| src.visibility()))
374}