Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API: Make item Visibility visible in the API #294

Merged
merged 11 commits into from
Nov 14, 2023
137 changes: 8 additions & 129 deletions marker_api/src/ast/item.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{fmt::Debug, marker::PhantomData};
use std::fmt::Debug;

use crate::{
common::{HasNodeId, ItemId, SpanId},
Expand Down Expand Up @@ -264,144 +264,23 @@ use impl_item_data;
#[derive(Debug)]
#[cfg_attr(feature = "driver-api", derive(typed_builder::TypedBuilder))]
pub struct Visibility<'ast> {
#[cfg_attr(feature = "driver-api", builder(setter(skip), default))]
_lifetime: PhantomData<&'ast ()>,
#[cfg_attr(feature = "driver-api", builder(setter(into), default))]
span: FfiOption<SpanId>,
kind: VisibilityKind,
sem: crate::sem::Visibility<'ast>,
}

impl<'ast> Visibility<'ast> {
/// The [`Span`] of the visibility, if it has been declared.
pub fn span(&self) -> Option<&Span<'ast>> {
self.span.copy().map(|span| with_cx(self, |cx| cx.span(span)))
}

/// Returns `true` if the item is declared as public, without any restrictions.
///
/// ```
/// // This returns `true`
/// pub fn unicorn() {}
///
/// // This returns `false`, since the visibility is restricted to a specified path.
/// pub(crate) fn giraffe() {}
///
/// // This returns `false`, since the visibility is not defined
/// fn dragon() {}
/// ```
///
/// See [`Visibility::is_pub_with_path`] to detect pub declarations with a
/// defined path.
pub fn is_pub(&self) -> bool {
matches!(self.kind, VisibilityKind::Public | VisibilityKind::DefaultPub)
}

/// Returns `true` if the item is declared as `pub(...)` with a path in brackets
/// that defines the scope, where the item is visible.
pub fn is_pub_with_path(&self) -> bool {
matches!(self.kind, VisibilityKind::Path(_) | VisibilityKind::Crate(_))
}

/// Returns `true` if the visibility is declared as `pub(crate)`. This is a
/// special case of the `pub(<path>)` visibility.
///
/// This function checks if the visibility is restricted and the defined path
/// belongs to the root module of the crate. Meaning, that this can also be `true`,
/// if the visibility uses `pub(super)` to travel up to the crate root.
// Ignore, since the `in crate::example_1` path doesn't work for doc tests
/// ```ignore
/// // lib.rs
///
/// mod example_1 {
/// // Returns `false` since no visibility is declared
/// fn foo() {}
///
/// // Returns `false` since the item is not visible from the root of the crate.
/// pub(in crate::example_1) fn bar() {}
///
/// // Returns `true` as the visibility is restricted to the root of the crate.
/// pub(crate) fn baz() {}
///
/// // Returns `true` as the visibility is restricted to `super` which is the
/// // root of the crate.
/// pub(super) fn boo() {}
/// }
///
/// // Returns `false` since the visibility is not restricted
/// fn example_in_root() {}
/// ```
pub fn is_pub_crate(&self) -> bool {
matches!(self.kind, VisibilityKind::Crate(_))
}

/// Returns `true` if a visibility has been explicitly specified.
pub fn is_explicit(&self) -> bool {
!matches!(self.kind, VisibilityKind::Default(_) | VisibilityKind::DefaultPub)
/// This function returns the semantic representation for the [`Visibility`]
/// of this item. That visibility can be used to check if the item is public
/// or restricted to specific modules.
pub fn semantics(&self) -> &crate::sem::Visibility<'ast> {
&self.sem
}

/// Returns the [`ItemId`] of the module where this item is visible in, if the
/// visibility is defined to be public inside a specified path.
///
/// See [`Visibility::module_id`] to get the `ItemId`, even if the item or
/// field uses the default visibility.
pub fn pub_with_path_module_id(&self) -> Option<ItemId> {
match self.kind {
VisibilityKind::Path(id) | VisibilityKind::Crate(id) => Some(id),
_ => None,
}
}

/// Returns the [`ItemId`] of the module that this item or field is visible in.
/// It will return `None`, if the item is public, as the visibility extends even past
/// the root module of the crate.
///
/// This function also works for items which have the default visibility, of the
/// module they are declared in.
///
/// ```
/// mod scope {
/// // Returns `None` since this is even visible outside the current crate
/// pub fn turtle() {}
///
/// // Returns the `ItemId` of the root module of the crate
/// pub(crate) fn shark() {}
///
/// // Returns the `ItemId` of the module it is declared in
/// fn dolphin() {}
/// }
/// ```
///
/// Note that this only returns the [`ItemId`] that this item is visible in
/// based on the declared visibility. The item might be reexported, which can
/// increase the visibility.
pub fn module_id(&self) -> Option<ItemId> {
match self.kind {
VisibilityKind::Path(id) | VisibilityKind::Crate(id) | VisibilityKind::Default(id) => Some(id),
_ => None,
}
}

// FIXME(xFrednet): Implement functions to check if an item is visible from a
// given `ItemId`. This can be done once rust-marker/marker#242 is implemented.
}

#[derive(Debug)]
#[allow(clippy::exhaustive_enums)]
#[cfg_attr(feature = "driver-api", visibility::make(pub))]
enum VisibilityKind {
/// The item is declared as `pub` without any restrictions
Public,
/// The visibility is restricted to a specific module using `pub(<path>)`.
/// The module, targeted by the path is identified by the [`ItemId`].
/// The `pub(crate)` has it's own variant in this struct.
Path(ItemId),
/// The visibility is restricted to the root module of the crate. The [`ItemId`]
/// identifies the root module.
Crate(ItemId),
/// The items doesn't have a declared visibility. The default is restricted to
/// a module, identified by the stored [`ItemId`]
Default(ItemId),
/// For items which are `pub` by default, like trait functions or enum variants
DefaultPub,
}

/// A body represents the expression of items.
Expand Down
2 changes: 2 additions & 0 deletions marker_api/src/sem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@

mod common;
mod generic;
mod item;
mod ty;

pub use common::*;
pub use generic::*;
pub use item::*;
pub use ty::*;
160 changes: 160 additions & 0 deletions marker_api/src/sem/item.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
use std::marker::PhantomData;

use crate::common::ItemId;

/// The declared visibility of an item or field.
///
/// ```
/// // An item without a visibility
/// fn moon() {}
///
/// // A public item
/// pub fn sun() {}
///
/// // An item with a restricted scope
/// pub(crate) fn star() {}
///
/// pub trait Planet {
/// // An item without a visibility. But it still has the semantic visibility
/// // of `pub` as this is inside a trait declaration.
/// fn mass();
/// }
/// ```
#[repr(C)]
#[derive(Debug)]
#[cfg_attr(feature = "driver-api", derive(typed_builder::TypedBuilder))]
pub struct Visibility<'ast> {
#[cfg_attr(feature = "driver-api", builder(setter(skip), default))]
_lifetime: PhantomData<&'ast ()>,
kind: VisibilityKind,
}

impl<'ast> Visibility<'ast> {
/// Returns `true` if the item is declared as public, without any restrictions.
///
/// ```
/// // This returns `true`
/// pub fn unicorn() {}
///
/// // This returns `false`, since the visibility is restricted to a specified path.
/// pub(crate) fn giraffe() {}
///
/// // This returns `false`, since the visibility is not defined
/// fn dragon() {}
/// ```
///
/// See [`Visibility::is_pub_in_path`] to detect pub declarations with a
/// defined path.
pub fn is_pub(&self) -> bool {
matches!(self.kind, VisibilityKind::Public | VisibilityKind::DefaultPub)
}

/// Returns `true` if the item is declared as `pub(in ..)` with a path in brackets
/// that defines the scope, where the item is visible.
pub fn is_pub_in_path(&self) -> bool {
matches!(self.kind, VisibilityKind::Path(_))
}

/// Returns `true` if the visibility is declared as `pub(crate)`. This is a
/// special case of the `pub(<path>)` visibility.
///
/// This function checks if the visibility is restricted and the defined path
/// belongs to the root module of the crate. Meaning, that this can also be `true`,
/// if the visibility uses `pub(super)` to travel up to the crate root.
// Ignore, since the `in crate::example_1` path doesn't work for doc tests
xFrednet marked this conversation as resolved.
Show resolved Hide resolved
/// ```ignore
/// // lib.rs
///
/// mod example_1 {
/// // Returns `false` since no visibility is declared
/// fn foo() {}
///
/// // Returns `false` since the item is not visible from the root of the crate.
/// pub(in crate::example_1) fn bar() {}
///
/// // Returns `true` as the visibility is restricted to the root of the crate.
/// pub(crate) fn baz() {}
///
/// // Returns `true` as the visibility is restricted to `super` which is the
/// // root of the crate.
/// pub(super) fn boo() {}
/// }
///
/// // Returns `false` since the visibility is not restricted
xFrednet marked this conversation as resolved.
Show resolved Hide resolved
/// fn example_in_root() {}
/// ```
pub fn is_pub_crate(&self) -> bool {
Copy link
Contributor

@Veetaha Veetaha Nov 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think people will be confused if we name this method after the special pub(crate) syntax when this method covers more than just that.

Suggested change
pub fn is_pub_crate(&self) -> bool {
pub fn is_crate_scoped(&self) -> bool {

Given the amount of info we get from rustc (the lost of AST context) consider this number of methods instead:

  • is_crate_scoped() -> bool (true if scope resolves to the current crate no matter what syntax was used)
  • is_pub() -> bool { self.scope().is_none() }
  • is_default() -> bool
  • scope() -> Option<ItemId> (renamed module_id)

In other words I'd like to make it explicit that the semantic visibility no longer represents the original syntax that was used.

You can also notice that in the list of methods I suggested there isn't pub_with_path_module_id. I think it's not needed because it's expressible with just vis.scope().filter(|_| !vis.is_default()) or similar.

If the user wants to check if the visibility was specified (non-default), they would use !is_default(). If they want to exclude pub they will use !is_default && !is_pub() which would leave them with pub(crate), pub(super)andpub(in ...)` syntaxes.

Maybe to have checks for AST syntax like is_pub_crate, is_pub_super, is_pub_in_path etc. (which would be available on ast::Visibility we could just re-parse the text snippet from the span manually? However, I wouldn't do it in this PR to just land this chunk of work and have something good enough 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like the suggested names. I'll make the changes. We should be able to reparse at least part of the text snippet. I haven't worked with Rustc's lexer and parser yet, which is partially why I'm avoiding such solutions 😅

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-parsing the text snippet is more of a hack. I wonder how clippy fights with this? Maybe other kinds of lint passes such as EarlyLintPass have the necessary ast info?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clippy usually just checks if things are public, and that's it. In Clippy we don't really have lints that span over more than a module and those that do don't require the visibility AFAIK.

For the EarlyLintPass, I've just checked and that one still has the path [1]. The big problem with this approach is that nodes from the AST, are hard to connect to the nodes from the HIR, as the AST doesn't have a unique ID for everything. HirIds are also structured differently.

The current rustc interface has covered all of Clippy's use cases so far. So I hope that this is enough for Marker.

matches!(self.kind, VisibilityKind::Crate(_))
}

/// Returns `true` if a visibility is the default visibility, meaning that it wasn't
/// declared.
pub fn is_default(&self) -> bool {
matches!(self.kind, VisibilityKind::Default(_) | VisibilityKind::DefaultPub)
}

/// Returns the [`ItemId`] of the module where this item is visible in, if the
/// visibility is defined to be public inside a specified path.
///
/// See [`Visibility::module_id`] to get the `ItemId`, even if the item or
/// field uses the default visibility.
pub fn pub_with_path_module_id(&self) -> Option<ItemId> {
match self.kind {
VisibilityKind::Path(id) | VisibilityKind::Crate(id) => Some(id),
_ => None,
}
}

/// Returns the [`ItemId`] of the module that this item or field is visible in.
/// It will return `None`, if the item is public, as the visibility extends even past
/// the root module of the crate.
///
/// This function also works for items which have the default visibility, of the
/// module they are declared in.
///
/// ```
/// mod scope {
/// // Returns `None` since this is even visible outside the current crate
/// pub fn turtle() {}
///
/// // Returns the `ItemId` of the root module of the crate
/// pub(crate) fn shark() {}
///
/// // Returns the `ItemId` of the module it is declared in
/// fn dolphin() {}
/// }
/// ```
///
/// Note that this only returns the [`ItemId`] that this item is visible in
/// based on the declared visibility. The item might be reexported, which can
/// increase the visibility.
pub fn module_id(&self) -> Option<ItemId> {
match self.kind {
VisibilityKind::Path(id) | VisibilityKind::Crate(id) | VisibilityKind::Default(id) => Some(id),
_ => None,
xFrednet marked this conversation as resolved.
Show resolved Hide resolved
}
}

// FIXME(xFrednet): Implement functions to check if an item is visible from a
// given `ItemId`. This can be done once rust-marker/marker#242 is implemented.
}

#[derive(Debug)]
#[allow(clippy::exhaustive_enums)]
#[cfg_attr(feature = "driver-api", visibility::make(pub))]
enum VisibilityKind {
/// The item is declared as `pub` without any restrictions
Public,
/// The visibility is restricted to a specific module using `pub(<path>)`.
/// The module, targeted by the path is identified by the [`ItemId`].
/// The `pub(crate)` has it's own variant in this struct.
Path(ItemId),
/// The visibility is restricted to the root module of the crate. The [`ItemId`]
/// identifies the root module.
Crate(ItemId),
/// The items doesn't have a declared visibility. The default is restricted to
/// a module, identified by the stored [`ItemId`]
Default(ItemId),
/// For items which are `pub` by default, like trait functions or enum variants
DefaultPub,
}
2 changes: 1 addition & 1 deletion marker_lints/src/not_using_has_span_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ marker_api::declare_lint! {

pub(crate) fn check_item<'ast>(cx: &'ast MarkerContext<'ast>, item: ItemKind<'ast>) {
let ItemKind::Fn(func) = item else { return };
if !func.visibility().is_pub() {
if !func.visibility().semantics().is_pub() {
return;
}

Expand Down
15 changes: 13 additions & 2 deletions marker_rustc_driver/src/conversion/marker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ use std::cell::{OnceCell, RefCell};

use crate::context::storage::Storage;
use marker_api::{
ast::{Body, CommonItemData, Crate, EnumVariant, ItemField, ModItem},
ast::{Body, CommonItemData, Crate, EnumVariant, ItemField, ModItem, Visibility as AstVisibility},
common::{Level, SymbolId},
prelude::*,
sem::{Visibility as SemVisibility, VisibilityKind},
span::{ExpnInfo, FilePos, Span, SpanSource},
};
use rustc_hash::FxHashMap;
Expand Down Expand Up @@ -329,7 +330,17 @@ impl<'ast, 'tcx> MarkerConverterInner<'ast, 'tcx> {
self.to_symbol_id(self.rustc_cx.crate_name(hir::def_id::LOCAL_CRATE)),
self.to_span_id(rustc_span::DUMMY_SP),
);
let data = CommonItemData::new(id, self.to_span_id(krate_mod.spans.inner_span), ident);
let data = CommonItemData::builder()
.id(id)
.span(self.to_span_id(krate_mod.spans.inner_span))
.vis(
AstVisibility::builder()
.span(None)
.sem(SemVisibility::builder().kind(VisibilityKind::DefaultPub).build())
.build(),
)
.ident(ident)
.build();
ModItem::builder()
.data(data)
.items(self.to_items(krate_mod.item_ids))
Expand Down
Loading