Skip to content

Commit 5773838

Browse files
Support #[doc(hidden)] #[macro_export] macro_rules! that are reexported in a place different than the crate root
With some limitations forced on us by the analyzer, as explained in the code.
1 parent 987ea33 commit 5773838

14 files changed

Lines changed: 206 additions & 59 deletions

File tree

crates/hir-expand/src/mod_path.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,15 @@ impl ModPath {
192192
) -> impl fmt::Display + 'a {
193193
Display { db, path: self, edition: Some(edition) }
194194
}
195+
196+
pub fn last_name(&self) -> Name {
197+
self.segments.last().cloned().unwrap_or_else(|| match self.kind {
198+
PathKind::DollarCrate(_) | PathKind::Abs | PathKind::Plain => Name::missing(),
199+
PathKind::SELF => Name::new_symbol_root(sym::self_),
200+
PathKind::Super(_) => Name::new_symbol_root(sym::super_),
201+
PathKind::Crate => Name::new_symbol_root(sym::crate_),
202+
})
203+
}
195204
}
196205

197206
impl Extend<Name> for ModPath {

crates/hir/src/attrs.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,42 @@ impl AttrsWithOwner {
110110
self.attrs.contains(AttrFlags::IS_DOC_NOTABLE_TRAIT)
111111
}
112112

113+
/// Never call this for macros.
113114
#[inline]
114-
pub fn is_doc_hidden(&self) -> bool {
115+
pub(crate) fn is_doc_hidden_raw(&self) -> bool {
115116
self.attrs.contains(AttrFlags::IS_DOC_HIDDEN)
116117
}
117118

119+
/// This checks whether this item is `#[doc(hidden)]`. However macros are special in this regard:
120+
/// we want to treat usages of non-hidden reexports of hidden macros as non-hidden, see [`Macro::legacy_macros_reexport()`].
121+
/// We don't have a real semantic way to distinguish between them, so we use the name as an indication.
122+
pub fn is_doc_hidden(
123+
&self,
124+
db: &dyn HirDatabase,
125+
under_name: &Name,
126+
in_module: Module,
127+
) -> bool {
128+
if !self.is_doc_hidden_raw() {
129+
return false;
130+
}
131+
132+
if let AttrsOwner::AttrDef(AttrDefId::MacroId(makro)) = self.owner
133+
&& let Some((reexport_name, reexport)) =
134+
(Macro { id: makro }).legacy_macros_reexport(db)
135+
&& *under_name == reexport_name
136+
&& reexport.vis.is_visible_from(db, in_module.id)
137+
{
138+
// Two things are noticeable here:
139+
// 1. We don't check whether the reexport has `#[doc(hidden)]`, because we have no easy and performant way
140+
// to check attributes of reexports.
141+
// 2. If the reexport has the same name as the macro, we always treat it as non-hidden. We do so because
142+
// it's common to use the same name.
143+
return false;
144+
}
145+
146+
true
147+
}
148+
118149
#[inline]
119150
pub fn is_deprecated(&self) -> bool {
120151
self.attrs.contains(AttrFlags::IS_DEPRECATED)

crates/hir/src/lib.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1694,6 +1694,10 @@ impl EnumVariant {
16941694
pub fn is_unstable(self, db: &dyn HirDatabase) -> bool {
16951695
AttrFlags::query(db, self.id.into()).contains(AttrFlags::IS_UNSTABLE)
16961696
}
1697+
1698+
pub fn is_doc_hidden(self, db: &dyn HirDatabase) -> bool {
1699+
AttrFlags::query(db, self.id.into()).contains(AttrFlags::IS_DOC_HIDDEN)
1700+
}
16971701
}
16981702

16991703
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -3247,6 +3251,10 @@ impl Macro {
32473251
as_name_opt(source.value.name())
32483252
}
32493253
MacroId::MacroRulesId(id) => {
3254+
if let Some((reexport_name, _)) = self.legacy_macros_reexport(db) {
3255+
return reexport_name;
3256+
}
3257+
32503258
let loc = id.lookup(db);
32513259
let source = loc.source(db);
32523260
as_name_opt(source.value.name())
@@ -3357,6 +3365,50 @@ impl Macro {
33573365
let attrs = self.attrs(db);
33583366
MacroBraces::extract(attrs.attrs)
33593367
}
3368+
3369+
// Feature: Detection of Exported `macro_rules!`
3370+
//
3371+
// `macro_rules!` macros can only be publicly exported via `#[macro_export]` and at the crate root.
3372+
// For proper scoping, a common practice is to mark the macro `#[doc(hidden)]`, then have a public reexport
3373+
// where you want to export the macro. rust-analyzer generally supports this practice and detects the correct
3374+
// way to call the macro, however as a crate author you must make sure two conditions are fulfilled:
3375+
//
3376+
// 1. The reexport is *exactly in the same module* as the macro. If this condition is not fulfilled,
3377+
// rust-analyzer will fail to detect the reexport and consider the macro hidden. Of course, you can then
3378+
// reexport the reexport wherever you like.
3379+
// 2. For best experience, the reexport should be renaming. That is, declare the macro with a different name
3380+
// than you want (a common practice is to call it `__<my_macro>`) then rename it in the reexport, like
3381+
// `pub use __my_macro as my_macro`. If you do not do this, rust-analyzer will still find the reexport,
3382+
// but it will also consider the macro not hidden at the crate root.
3383+
3384+
/// Legacy (`macro_rules!`) macros cannot have proper namespacing resolution in Rust (more precisely, they
3385+
/// cannot have it across crates). Therefore, it is a common practice to make a macro `#[doc(hidden)]`
3386+
/// and put `pub use __macro as macro` in the same module. In such cases, this function will return
3387+
/// the reexport.
3388+
fn legacy_macros_reexport(
3389+
&self,
3390+
db: &dyn HirDatabase,
3391+
) -> Option<(Name, hir_def::per_ns::MacrosItem)> {
3392+
let MacroId::MacroRulesId(makro) = self.id else {
3393+
return None;
3394+
};
3395+
if !AttrFlags::query(db, makro.into())
3396+
.contains(AttrFlags::IS_DOC_HIDDEN | AttrFlags::IS_MACRO_EXPORT)
3397+
{
3398+
return None;
3399+
}
3400+
let loc = makro.loc(db);
3401+
let def_map = loc.container.def_map(db);
3402+
if loc.container == def_map.crate_root(db) {
3403+
// `#[macro_export]` macros defined at the crate root cannot have a reexport,
3404+
// it'll cause conflicts with the builtin export.
3405+
return None;
3406+
}
3407+
let scope = &def_map[loc.container].scope;
3408+
let (reexport_name, reexport) =
3409+
scope.macros().find(|(_, reexport)| reexport.def == self.id)?;
3410+
Some((reexport_name.clone(), reexport))
3411+
}
33603412
}
33613413

33623414
// Feature: Macro Brace Style Attribute
@@ -3769,6 +3821,10 @@ impl AssocItem {
37693821
}
37703822
}
37713823
}
3824+
3825+
pub fn is_doc_hidden(&self, db: &dyn HirDatabase) -> bool {
3826+
self.attrs(db).is_doc_hidden_raw()
3827+
}
37723828
}
37733829

37743830
impl HasVisibility for AssocItem {

crates/ide-assists/src/handlers/add_missing_match_arms.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ impl ExtendedVariant {
498498
fn should_be_hidden(self, db: &RootDatabase, krate: Crate) -> bool {
499499
match self {
500500
ExtendedVariant::Variant { variant: var, .. } => {
501-
var.attrs(db).is_doc_hidden() && var.module(db).krate(db) != krate
501+
var.is_doc_hidden(db) && var.module(db).krate(db) != krate
502502
}
503503
_ => false,
504504
}

crates/ide-assists/src/utils.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::slice;
44

55
pub(crate) use gen_trait_fn_body::gen_trait_fn_body;
66
use hir::{
7-
HasAttrs as HirHasAttrs, HirDisplay, InFile, ModuleDef, PathResolution, Semantics,
7+
HirDisplay, InFile, ModuleDef, PathResolution, Semantics,
88
db::{ExpandDatabase, HirDatabase},
99
};
1010
use ide_db::{
@@ -151,7 +151,7 @@ pub fn filter_assoc_items(
151151
.copied()
152152
.filter(|assoc_item| {
153153
if ignore_items == IgnoreAssocItems::DocHiddenAttrPresent
154-
&& assoc_item.attrs(sema.db).is_doc_hidden()
154+
&& assoc_item.is_doc_hidden(sema.db)
155155
{
156156
if let hir::AssocItem::Function(f) = assoc_item
157157
&& !f.has_body(sema.db)

crates/ide-completion/src/completions.rs

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ impl Completions {
230230
resolution: hir::ScopeDef,
231231
doc_aliases: Vec<syntax::SmolStr>,
232232
) {
233-
let is_private_editable = match ctx.def_is_visible(&resolution) {
233+
let is_private_editable = match ctx.def_is_visible(&resolution, &local_name) {
234234
Visible::Yes => false,
235235
Visible::Editable => true,
236236
Visible::No => return,
@@ -251,7 +251,7 @@ impl Completions {
251251
local_name: hir::Name,
252252
resolution: hir::ScopeDef,
253253
) {
254-
let is_private_editable = match ctx.def_is_visible(&resolution) {
254+
let is_private_editable = match ctx.def_is_visible(&resolution, &local_name) {
255255
Visible::Yes => false,
256256
Visible::Editable => true,
257257
Visible::No => return,
@@ -300,7 +300,7 @@ impl Completions {
300300
mac: hir::Macro,
301301
local_name: hir::Name,
302302
) {
303-
let is_private_editable = match ctx.is_visible(&mac) {
303+
let is_private_editable = match ctx.is_visible(&mac, &local_name) {
304304
Visible::Yes => false,
305305
Visible::Editable => true,
306306
Visible::No => return,
@@ -321,7 +321,7 @@ impl Completions {
321321
func: hir::Function,
322322
local_name: Option<hir::Name>,
323323
) {
324-
let is_private_editable = match ctx.is_visible(&func) {
324+
let is_private_editable = match ctx.is_visible(&func, &Name::missing()) {
325325
Visible::Yes => false,
326326
Visible::Editable => true,
327327
Visible::No => return,
@@ -342,9 +342,8 @@ impl Completions {
342342
dot_access: &DotAccess<'_>,
343343
func: hir::Function,
344344
receiver: Option<SmolStr>,
345-
local_name: Option<hir::Name>,
346345
) {
347-
let is_private_editable = match ctx.is_visible(&func) {
346+
let is_private_editable = match ctx.is_visible(&func, &Name::missing()) {
348347
Visible::Yes => false,
349348
Visible::Editable => true,
350349
Visible::No => return,
@@ -354,7 +353,6 @@ impl Completions {
354353
RenderContext::new(ctx).private_editable(is_private_editable).doc_aliases(doc_aliases),
355354
dot_access,
356355
receiver,
357-
local_name,
358356
func,
359357
)
360358
.add_to(self, ctx.db);
@@ -367,7 +365,7 @@ impl Completions {
367365
func: hir::Function,
368366
import: LocatedImport,
369367
) {
370-
let is_private_editable = match ctx.is_visible(&func) {
368+
let is_private_editable = match ctx.is_visible(&func, &Name::missing()) {
371369
Visible::Yes => false,
372370
Visible::Editable => true,
373371
Visible::No => return,
@@ -380,14 +378,13 @@ impl Completions {
380378
.import_to_add(Some(import)),
381379
dot_access,
382380
None,
383-
None,
384381
func,
385382
)
386383
.add_to(self, ctx.db);
387384
}
388385

389386
pub(crate) fn add_const(&mut self, ctx: &CompletionContext<'_, '_>, konst: hir::Const) {
390-
let is_private_editable = match ctx.is_visible(&konst) {
387+
let is_private_editable = match ctx.is_visible(&konst, &Name::missing()) {
391388
Visible::Yes => false,
392389
Visible::Editable => true,
393390
Visible::No => return,
@@ -403,7 +400,7 @@ impl Completions {
403400
ctx: &CompletionContext<'_, '_>,
404401
type_alias: hir::TypeAlias,
405402
) {
406-
let is_private_editable = match ctx.is_visible(&type_alias) {
403+
let is_private_editable = match ctx.is_visible(&type_alias, &Name::missing()) {
407404
Visible::Yes => false,
408405
Visible::Editable => true,
409406
Visible::No => return,
@@ -432,7 +429,7 @@ impl Completions {
432429
variant: hir::EnumVariant,
433430
path: hir::ModPath,
434431
) {
435-
if !ctx.check_stability_and_hidden(variant) {
432+
if !ctx.check_stability_and_hidden(variant, &Name::missing()) {
436433
return;
437434
}
438435
if let Some(builder) =
@@ -449,7 +446,7 @@ impl Completions {
449446
variant: hir::EnumVariant,
450447
local_name: Option<hir::Name>,
451448
) {
452-
if !ctx.check_stability_and_hidden(variant) {
449+
if !ctx.check_stability_and_hidden(variant, &Name::missing()) {
453450
return;
454451
}
455452
if let PathCompletionCtx { kind: PathKind::Pat { pat_ctx }, .. } = path_ctx {
@@ -473,7 +470,7 @@ impl Completions {
473470
field: hir::Field,
474471
ty: &hir::Type<'_>,
475472
) {
476-
let is_private_editable = match ctx.is_visible(&field) {
473+
let is_private_editable = match ctx.is_visible(&field, &Name::missing()) {
477474
Visible::Yes => false,
478475
Visible::Editable => true,
479476
Visible::No => return,
@@ -497,7 +494,7 @@ impl Completions {
497494
path: Option<hir::ModPath>,
498495
local_name: Option<hir::Name>,
499496
) {
500-
let is_private_editable = match ctx.is_visible(&strukt) {
497+
let is_private_editable = match ctx.is_visible(&strukt, &Name::missing()) {
501498
Visible::Yes => false,
502499
Visible::Editable => true,
503500
Visible::No => return,
@@ -520,7 +517,7 @@ impl Completions {
520517
path: Option<hir::ModPath>,
521518
local_name: Option<hir::Name>,
522519
) {
523-
let is_private_editable = match ctx.is_visible(&un) {
520+
let is_private_editable = match ctx.is_visible(&un, &Name::missing()) {
524521
Visible::Yes => false,
525522
Visible::Editable => true,
526523
Visible::No => return,
@@ -575,7 +572,7 @@ impl Completions {
575572
variant: hir::EnumVariant,
576573
local_name: Option<hir::Name>,
577574
) {
578-
if !ctx.check_stability_and_hidden(variant) {
575+
if !ctx.check_stability_and_hidden(variant, &Name::missing()) {
579576
return;
580577
}
581578
self.add_opt(render_variant_pat(
@@ -595,7 +592,7 @@ impl Completions {
595592
variant: hir::EnumVariant,
596593
path: hir::ModPath,
597594
) {
598-
if !ctx.check_stability_and_hidden(variant) {
595+
if !ctx.check_stability_and_hidden(variant, &Name::missing()) {
599596
return;
600597
}
601598
let path = Some(&path);
@@ -616,7 +613,7 @@ impl Completions {
616613
strukt: hir::Struct,
617614
local_name: Option<hir::Name>,
618615
) {
619-
let is_private_editable = match ctx.is_visible(&strukt) {
616+
let is_private_editable = match ctx.is_visible(&strukt, &Name::missing()) {
620617
Visible::Yes => false,
621618
Visible::Editable => true,
622619
Visible::No => return,

crates/ide-completion/src/completions/dot.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ pub(crate) fn complete_dot(
6969
has_parens,
7070
);
7171
complete_methods(ctx, &future_output, &traits_in_scope, |func| {
72-
acc.add_method(ctx, &dot_access, func, Some(await_str.clone()), None)
72+
acc.add_method(ctx, &dot_access, func, Some(await_str.clone()))
7373
});
7474
}
7575
}
@@ -83,7 +83,7 @@ pub(crate) fn complete_dot(
8383
has_parens,
8484
);
8585
complete_methods(ctx, receiver_ty, &traits_in_scope, |func| {
86-
acc.add_method(ctx, dot_access, func, None, None)
86+
acc.add_method(ctx, dot_access, func, None)
8787
});
8888

8989
if ctx.config.enable_auto_iter && !receiver_ty.strip_references().impls_iterator(ctx.db) {
@@ -118,7 +118,7 @@ pub(crate) fn complete_dot(
118118
ctx: dot_access.ctx,
119119
};
120120
complete_methods(ctx, &iter, &traits_in_scope, |func| {
121-
acc.add_method(ctx, &dot_access, func, Some(iter_sym.clone()), None)
121+
acc.add_method(ctx, &dot_access, func, Some(iter_sym.clone()))
122122
});
123123
}
124124
}
@@ -191,7 +191,6 @@ pub(crate) fn complete_undotted_self(
191191
},
192192
func,
193193
Some(SmolStr::new_static(param_name)),
194-
None,
195194
)
196195
});
197196
}
@@ -256,7 +255,7 @@ fn complete_methods(
256255
let do_complete = match &same_name {
257256
hash_map::Entry::Vacant(_) => true,
258257
hash_map::Entry::Occupied(same_func) => {
259-
match self.ctx.is_visible(same_func.get()) {
258+
match self.ctx.is_visible(same_func.get(), same_func.key()) {
260259
crate::context::Visible::Yes => false,
261260
crate::context::Visible::Editable => true,
262261
crate::context::Visible::No => true,

0 commit comments

Comments
 (0)