Skip to content

Commit 0f32a79

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 aa64e48 commit 0f32a79

14 files changed

Lines changed: 205 additions & 58 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
@@ -1810,6 +1810,10 @@ impl EnumVariant {
18101810
);
18111811
InstantiatedVariant { inner: self, args }
18121812
}
1813+
1814+
pub fn is_doc_hidden(&self, db: &dyn HirDatabase) -> bool {
1815+
self.attrs(db).is_doc_hidden_raw()
1816+
}
18131817
}
18141818

18151819
// FIXME: Rename to `EnumVariant`
@@ -3516,6 +3520,10 @@ impl Macro {
35163520
as_name_opt(source.value.name())
35173521
}
35183522
MacroId::MacroRulesId(id) => {
3523+
if let Some((reexport_name, _)) = self.legacy_macros_reexport(db) {
3524+
return reexport_name;
3525+
}
3526+
35193527
let loc = id.lookup(db);
35203528
let source = loc.source(db);
35213529
as_name_opt(source.value.name())
@@ -3624,6 +3632,50 @@ impl Macro {
36243632
let attrs = self.attrs(db);
36253633
MacroBraces::extract(attrs.attrs)
36263634
}
3635+
3636+
// Feature: Detection of exported `macro_rules!`
3637+
//
3638+
// `macro_rules!` macros can only be publicly exported via `#[macro_export]` and at the crate root.
3639+
// For proper scoping, a common practice is to mark the macro `#[doc(hidden)]`, then have a public reexport
3640+
// where you want to export the macro. rust-analyzer generally supports this practice and detects the correct
3641+
// way to call the macro, however as a crate author you must make sure two conditions are fulfilled:
3642+
//
3643+
// 1. The reexport is *exactly in the same module* as the macro. If this condition is not fulfilled,
3644+
// rust-analyzer will fail to detect the reexport and consider the macro hidden. Of course, you can then
3645+
// reexport the reexport wherever you like.
3646+
// 2. For best experience, the reexport should be renaming. That is, declare the macro with a different name
3647+
// than you want (a common practice is to call it `__<my_macro>`) then rename it in the reexport, like
3648+
// `pub use __my_macro as my_macro`. If you do not do this, rust-analyzer will still find the reexport,
3649+
// but it will also consider the macro not hidden at the crate root.
3650+
3651+
/// Legacy (`macro_rules!`) macros cannot have proper namespacing resolution in Rust (more precisely, they
3652+
/// cannot have it across crates). Therefore, it is a common practice to make a macro `#[doc(hidden)]`
3653+
/// and put `pub use __macro as macro` in the same module. In such cases, this function will return
3654+
/// the reexport.
3655+
fn legacy_macros_reexport(
3656+
&self,
3657+
db: &dyn HirDatabase,
3658+
) -> Option<(Name, hir_def::per_ns::MacrosItem)> {
3659+
let MacroId::MacroRulesId(makro) = self.id else {
3660+
return None;
3661+
};
3662+
if !AttrFlags::query(db, makro.into())
3663+
.contains(AttrFlags::IS_DOC_HIDDEN | AttrFlags::IS_MACRO_EXPORT)
3664+
{
3665+
return None;
3666+
}
3667+
let loc = makro.loc(db);
3668+
let def_map = loc.container.def_map(db);
3669+
if loc.container == def_map.crate_root(db) {
3670+
// `#[macro_export]` macros defined at the crate root cannot have a reexport,
3671+
// it'll cause conflicts with the builtin export.
3672+
return None;
3673+
}
3674+
let scope = &def_map[loc.container].scope;
3675+
let (reexport_name, reexport) =
3676+
scope.macros().find(|(_, reexport)| reexport.def == self.id)?;
3677+
Some((reexport_name.clone(), reexport))
3678+
}
36273679
}
36283680

36293681
// Feature: Macro Brace Style Attribute
@@ -4036,6 +4088,10 @@ impl AssocItem {
40364088
}
40374089
}
40384090
}
4091+
4092+
pub fn is_doc_hidden(&self, db: &dyn HirDatabase) -> bool {
4093+
self.attrs(db).is_doc_hidden_raw()
4094+
}
40394095
}
40404096

40414097
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
@@ -489,7 +489,7 @@ impl ExtendedVariant {
489489
fn should_be_hidden(self, db: &RootDatabase, krate: Crate) -> bool {
490490
match self {
491491
ExtendedVariant::Variant { variant: var, .. } => {
492-
var.attrs(db).is_doc_hidden() && var.module(db).krate(db) != krate
492+
var.is_doc_hidden(db) && var.module(db).krate(db) != krate
493493
}
494494
_ => false,
495495
}

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::{
@@ -152,7 +152,7 @@ pub fn filter_assoc_items(
152152
.copied()
153153
.filter(|assoc_item| {
154154
if ignore_items == IgnoreAssocItems::DocHiddenAttrPresent
155-
&& assoc_item.attrs(sema.db).is_doc_hidden()
155+
&& assoc_item.is_doc_hidden(sema.db)
156156
{
157157
if let hir::AssocItem::Function(f) = assoc_item
158158
&& !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: 3 additions & 4 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
}

0 commit comments

Comments
 (0)