Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,6 @@ hashbrown = { version = "0.14.*", features = [
], default-features = false }

[workspace.lints.rust]
# remember to update RUSTFLAGS in ci.yml if you add something here

elided_lifetimes_in_paths = "warn"
explicit_outlives_requirements = "warn"
Expand Down Expand Up @@ -241,3 +240,8 @@ print_stderr = "warn"

rc_buffer = "warn"
str_to_string = "warn"

[workspace.lints.rustdoc]

# We treat the codebase as one big unit, private links are fine.
private-intra-doc-links = "allow"
9 changes: 9 additions & 0 deletions crates/hir-expand/src/mod_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,15 @@ impl ModPath {
) -> impl fmt::Display + 'a {
Display { db, path: self, edition: Some(edition) }
}

pub fn last_name(&self) -> Name {
self.segments.last().cloned().unwrap_or_else(|| match self.kind {
PathKind::DollarCrate(_) | PathKind::Abs | PathKind::Plain => Name::missing(),
PathKind::SELF => Name::new_symbol_root(sym::self_),
PathKind::Super(_) => Name::new_symbol_root(sym::super_),
PathKind::Crate => Name::new_symbol_root(sym::crate_),
})
}
}

impl Extend<Name> for ModPath {
Expand Down
33 changes: 32 additions & 1 deletion crates/hir/src/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,42 @@ impl AttrsWithOwner {
self.attrs.contains(AttrFlags::IS_DOC_NOTABLE_TRAIT)
}

/// Never call this for macros.
#[inline]
pub fn is_doc_hidden(&self) -> bool {
pub(crate) fn is_doc_hidden_raw(&self) -> bool {
self.attrs.contains(AttrFlags::IS_DOC_HIDDEN)
}

/// This checks whether this item is `#[doc(hidden)]`. However macros are special in this regard:
/// we want to treat usages of non-hidden reexports of hidden macros as non-hidden, see [`Macro::legacy_macros_reexport()`].
/// We don't have a real semantic way to distinguish between them, so we use the name as an indication.
pub fn is_doc_hidden(
&self,
db: &dyn HirDatabase,
under_name: &Name,
in_module: Module,
) -> bool {
if !self.is_doc_hidden_raw() {
return false;
}

if let AttrsOwner::AttrDef(AttrDefId::MacroId(makro)) = self.owner
&& let Some((reexport_name, reexport)) =
(Macro { id: makro }).legacy_macros_reexport(db)
&& *under_name == reexport_name
&& reexport.vis.is_visible_from(db, in_module.id)
{
// Two things are noticeable here:
// 1. We don't check whether the reexport has `#[doc(hidden)]`, because we have no easy and performant way
// to check attributes of reexports.
// 2. If the reexport has the same name as the macro, we always treat it as non-hidden. We do so because
// it's common to use the same name.
return false;
}

true
}

#[inline]
pub fn is_deprecated(&self) -> bool {
self.attrs.contains(AttrFlags::IS_DEPRECATED)
Expand Down
56 changes: 56 additions & 0 deletions crates/hir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1694,6 +1694,10 @@ impl EnumVariant {
pub fn is_unstable(self, db: &dyn HirDatabase) -> bool {
AttrFlags::query(db, self.id.into()).contains(AttrFlags::IS_UNSTABLE)
}

pub fn is_doc_hidden(self, db: &dyn HirDatabase) -> bool {
AttrFlags::query(db, self.id.into()).contains(AttrFlags::IS_DOC_HIDDEN)
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -3247,6 +3251,10 @@ impl Macro {
as_name_opt(source.value.name())
}
MacroId::MacroRulesId(id) => {
if let Some((reexport_name, _)) = self.legacy_macros_reexport(db) {
return reexport_name;
}

let loc = id.lookup(db);
let source = loc.source(db);
as_name_opt(source.value.name())
Expand Down Expand Up @@ -3357,6 +3365,50 @@ impl Macro {
let attrs = self.attrs(db);
MacroBraces::extract(attrs.attrs)
}

// Feature: Detection of Exported `macro_rules!`
//
// `macro_rules!` macros can only be publicly exported via `#[macro_export]` and at the crate root.
// For proper scoping, a common practice is to mark the macro `#[doc(hidden)]`, then have a public reexport
// where you want to export the macro. rust-analyzer generally supports this practice and detects the correct
// way to call the macro, however as a crate author you must make sure two conditions are fulfilled:
//
// 1. The reexport is *exactly in the same module* as the macro. If this condition is not fulfilled,
// rust-analyzer will fail to detect the reexport and consider the macro hidden. Of course, you can then
// reexport the reexport wherever you like.
// 2. For best experience, the reexport should be renaming. That is, declare the macro with a different name
// than you want (a common practice is to call it `__<my_macro>`) then rename it in the reexport, like
// `pub use __my_macro as my_macro`. If you do not do this, rust-analyzer will still find the reexport,
// but it will also consider the macro not hidden at the crate root.

/// Legacy (`macro_rules!`) macros cannot have proper namespacing resolution in Rust (more precisely, they
/// cannot have it across crates). Therefore, it is a common practice to make a macro `#[doc(hidden)]`
/// and put `pub use __macro as macro` in the same module. In such cases, this function will return
/// the reexport.
fn legacy_macros_reexport(
&self,
db: &dyn HirDatabase,
) -> Option<(Name, hir_def::per_ns::MacrosItem)> {
let MacroId::MacroRulesId(makro) = self.id else {
return None;
};
if !AttrFlags::query(db, makro.into())
.contains(AttrFlags::IS_DOC_HIDDEN | AttrFlags::IS_MACRO_EXPORT)
{
return None;
}
let loc = makro.loc(db);
let def_map = loc.container.def_map(db);
if loc.container == def_map.crate_root(db) {
// `#[macro_export]` macros defined at the crate root cannot have a reexport,
// it'll cause conflicts with the builtin export.
return None;
}
let scope = &def_map[loc.container].scope;
let (reexport_name, reexport) =
scope.macros().find(|(_, reexport)| reexport.def == self.id)?;
Some((reexport_name.clone(), reexport))
}
}

// Feature: Macro Brace Style Attribute
Expand Down Expand Up @@ -3769,6 +3821,10 @@ impl AssocItem {
}
}
}

pub fn is_doc_hidden(&self, db: &dyn HirDatabase) -> bool {
self.attrs(db).is_doc_hidden_raw()
}
}

impl HasVisibility for AssocItem {
Expand Down
2 changes: 1 addition & 1 deletion crates/ide-assists/src/handlers/add_missing_match_arms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ impl ExtendedVariant {
fn should_be_hidden(self, db: &RootDatabase, krate: Crate) -> bool {
match self {
ExtendedVariant::Variant { variant: var, .. } => {
var.attrs(db).is_doc_hidden() && var.module(db).krate(db) != krate
var.is_doc_hidden(db) && var.module(db).krate(db) != krate
}
_ => false,
}
Expand Down
4 changes: 2 additions & 2 deletions crates/ide-assists/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::slice;

pub(crate) use gen_trait_fn_body::gen_trait_fn_body;
use hir::{
HasAttrs as HirHasAttrs, HirDisplay, InFile, ModuleDef, PathResolution, Semantics,
HirDisplay, InFile, ModuleDef, PathResolution, Semantics,
db::{ExpandDatabase, HirDatabase},
};
use ide_db::{
Expand Down Expand Up @@ -151,7 +151,7 @@ pub fn filter_assoc_items(
.copied()
.filter(|assoc_item| {
if ignore_items == IgnoreAssocItems::DocHiddenAttrPresent
&& assoc_item.attrs(sema.db).is_doc_hidden()
&& assoc_item.is_doc_hidden(sema.db)
{
if let hir::AssocItem::Function(f) = assoc_item
&& !f.has_body(sema.db)
Expand Down
35 changes: 16 additions & 19 deletions crates/ide-completion/src/completions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ impl Completions {
resolution: hir::ScopeDef,
doc_aliases: Vec<syntax::SmolStr>,
) {
let is_private_editable = match ctx.def_is_visible(&resolution) {
let is_private_editable = match ctx.def_is_visible(&resolution, &local_name) {
Visible::Yes => false,
Visible::Editable => true,
Visible::No => return,
Expand All @@ -251,7 +251,7 @@ impl Completions {
local_name: hir::Name,
resolution: hir::ScopeDef,
) {
let is_private_editable = match ctx.def_is_visible(&resolution) {
let is_private_editable = match ctx.def_is_visible(&resolution, &local_name) {
Visible::Yes => false,
Visible::Editable => true,
Visible::No => return,
Expand Down Expand Up @@ -300,7 +300,7 @@ impl Completions {
mac: hir::Macro,
local_name: hir::Name,
) {
let is_private_editable = match ctx.is_visible(&mac) {
let is_private_editable = match ctx.is_visible(&mac, &local_name) {
Visible::Yes => false,
Visible::Editable => true,
Visible::No => return,
Expand All @@ -321,7 +321,7 @@ impl Completions {
func: hir::Function,
local_name: Option<hir::Name>,
) {
let is_private_editable = match ctx.is_visible(&func) {
let is_private_editable = match ctx.is_visible(&func, &Name::missing()) {
Visible::Yes => false,
Visible::Editable => true,
Visible::No => return,
Expand All @@ -342,9 +342,8 @@ impl Completions {
dot_access: &DotAccess<'_>,
func: hir::Function,
receiver: Option<SmolStr>,
local_name: Option<hir::Name>,
) {
let is_private_editable = match ctx.is_visible(&func) {
let is_private_editable = match ctx.is_visible(&func, &Name::missing()) {
Visible::Yes => false,
Visible::Editable => true,
Visible::No => return,
Expand All @@ -354,7 +353,6 @@ impl Completions {
RenderContext::new(ctx).private_editable(is_private_editable).doc_aliases(doc_aliases),
dot_access,
receiver,
local_name,
func,
)
.add_to(self, ctx.db);
Expand All @@ -367,7 +365,7 @@ impl Completions {
func: hir::Function,
import: LocatedImport,
) {
let is_private_editable = match ctx.is_visible(&func) {
let is_private_editable = match ctx.is_visible(&func, &Name::missing()) {
Visible::Yes => false,
Visible::Editable => true,
Visible::No => return,
Expand All @@ -380,14 +378,13 @@ impl Completions {
.import_to_add(Some(import)),
dot_access,
None,
None,
func,
)
.add_to(self, ctx.db);
}

pub(crate) fn add_const(&mut self, ctx: &CompletionContext<'_, '_>, konst: hir::Const) {
let is_private_editable = match ctx.is_visible(&konst) {
let is_private_editable = match ctx.is_visible(&konst, &Name::missing()) {
Visible::Yes => false,
Visible::Editable => true,
Visible::No => return,
Expand All @@ -403,7 +400,7 @@ impl Completions {
ctx: &CompletionContext<'_, '_>,
type_alias: hir::TypeAlias,
) {
let is_private_editable = match ctx.is_visible(&type_alias) {
let is_private_editable = match ctx.is_visible(&type_alias, &Name::missing()) {
Visible::Yes => false,
Visible::Editable => true,
Visible::No => return,
Expand Down Expand Up @@ -432,7 +429,7 @@ impl Completions {
variant: hir::EnumVariant,
path: hir::ModPath,
) {
if !ctx.check_stability_and_hidden(variant) {
if !ctx.check_stability_and_hidden(variant, &Name::missing()) {
return;
}
if let Some(builder) =
Expand All @@ -449,7 +446,7 @@ impl Completions {
variant: hir::EnumVariant,
local_name: Option<hir::Name>,
) {
if !ctx.check_stability_and_hidden(variant) {
if !ctx.check_stability_and_hidden(variant, &Name::missing()) {
return;
}
if let PathCompletionCtx { kind: PathKind::Pat { pat_ctx }, .. } = path_ctx {
Expand All @@ -473,7 +470,7 @@ impl Completions {
field: hir::Field,
ty: &hir::Type<'_>,
) {
let is_private_editable = match ctx.is_visible(&field) {
let is_private_editable = match ctx.is_visible(&field, &Name::missing()) {
Visible::Yes => false,
Visible::Editable => true,
Visible::No => return,
Expand All @@ -497,7 +494,7 @@ impl Completions {
path: Option<hir::ModPath>,
local_name: Option<hir::Name>,
) {
let is_private_editable = match ctx.is_visible(&strukt) {
let is_private_editable = match ctx.is_visible(&strukt, &Name::missing()) {
Visible::Yes => false,
Visible::Editable => true,
Visible::No => return,
Expand All @@ -520,7 +517,7 @@ impl Completions {
path: Option<hir::ModPath>,
local_name: Option<hir::Name>,
) {
let is_private_editable = match ctx.is_visible(&un) {
let is_private_editable = match ctx.is_visible(&un, &Name::missing()) {
Visible::Yes => false,
Visible::Editable => true,
Visible::No => return,
Expand Down Expand Up @@ -575,7 +572,7 @@ impl Completions {
variant: hir::EnumVariant,
local_name: Option<hir::Name>,
) {
if !ctx.check_stability_and_hidden(variant) {
if !ctx.check_stability_and_hidden(variant, &Name::missing()) {
return;
}
self.add_opt(render_variant_pat(
Expand All @@ -595,7 +592,7 @@ impl Completions {
variant: hir::EnumVariant,
path: hir::ModPath,
) {
if !ctx.check_stability_and_hidden(variant) {
if !ctx.check_stability_and_hidden(variant, &Name::missing()) {
return;
}
let path = Some(&path);
Expand All @@ -616,7 +613,7 @@ impl Completions {
strukt: hir::Struct,
local_name: Option<hir::Name>,
) {
let is_private_editable = match ctx.is_visible(&strukt) {
let is_private_editable = match ctx.is_visible(&strukt, &Name::missing()) {
Visible::Yes => false,
Visible::Editable => true,
Visible::No => return,
Expand Down
9 changes: 4 additions & 5 deletions crates/ide-completion/src/completions/dot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ pub(crate) fn complete_dot(
has_parens,
);
complete_methods(ctx, &future_output, &traits_in_scope, |func| {
acc.add_method(ctx, &dot_access, func, Some(await_str.clone()), None)
acc.add_method(ctx, &dot_access, func, Some(await_str.clone()))
});
}
}
Expand All @@ -83,7 +83,7 @@ pub(crate) fn complete_dot(
has_parens,
);
complete_methods(ctx, receiver_ty, &traits_in_scope, |func| {
acc.add_method(ctx, dot_access, func, None, None)
acc.add_method(ctx, dot_access, func, None)
});

if ctx.config.enable_auto_iter && !receiver_ty.strip_references().impls_iterator(ctx.db) {
Expand Down Expand Up @@ -118,7 +118,7 @@ pub(crate) fn complete_dot(
ctx: dot_access.ctx,
};
complete_methods(ctx, &iter, &traits_in_scope, |func| {
acc.add_method(ctx, &dot_access, func, Some(iter_sym.clone()), None)
acc.add_method(ctx, &dot_access, func, Some(iter_sym.clone()))
});
}
}
Expand Down Expand Up @@ -191,7 +191,6 @@ pub(crate) fn complete_undotted_self(
},
func,
Some(SmolStr::new_static(param_name)),
None,
)
});
}
Expand Down Expand Up @@ -256,7 +255,7 @@ fn complete_methods(
let do_complete = match &same_name {
hash_map::Entry::Vacant(_) => true,
hash_map::Entry::Occupied(same_func) => {
match self.ctx.is_visible(same_func.get()) {
match self.ctx.is_visible(same_func.get(), same_func.key()) {
crate::context::Visible::Yes => false,
crate::context::Visible::Editable => true,
crate::context::Visible::No => true,
Expand Down
Loading
Loading