Skip to content

Commit 6494193

Browse files
Merge #9681
9681: Respect `#[doc(hidden)]` in dot-completion r=jonas-schievink a=jonas-schievink This adds `CompletionContext::is_visible` as a convenience method that checks visibility, presence of `doc(hidden)`, and whether the completed item is in the same crate as the completion site or not. We only complete `doc(hidden)` items from the same crate. This doesn't yet work for *all* completions: `qualified_path` completions use `Module::scope` and `ScopeDef`, which doesn't work with this. Part of #7718 Co-authored-by: Jonas Schievink <jonasschievink@gmail.com>
2 parents 4107106 + c8d915e commit 6494193

File tree

6 files changed

+136
-13
lines changed

6 files changed

+136
-13
lines changed

crates/hir/src/attrs.rs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ use hir_ty::db::HirDatabase;
1111
use syntax::ast;
1212

1313
use crate::{
14-
Adt, Const, ConstParam, Enum, Field, Function, GenericParam, Impl, LifetimeParam, MacroDef,
15-
Module, ModuleDef, Static, Struct, Trait, TypeAlias, TypeParam, Union, Variant,
14+
Adt, AssocItem, Const, ConstParam, Enum, Field, Function, GenericParam, Impl, LifetimeParam,
15+
MacroDef, Module, ModuleDef, Static, Struct, Trait, TypeAlias, TypeParam, Union, Variant,
1616
};
1717

1818
pub trait HasAttrs {
@@ -86,6 +86,37 @@ macro_rules! impl_has_attrs_enum {
8686
impl_has_attrs_enum![Struct, Union, Enum for Adt];
8787
impl_has_attrs_enum![TypeParam, ConstParam, LifetimeParam for GenericParam];
8888

89+
impl HasAttrs for AssocItem {
90+
fn attrs(self, db: &dyn HirDatabase) -> AttrsWithOwner {
91+
match self {
92+
AssocItem::Function(it) => it.attrs(db),
93+
AssocItem::Const(it) => it.attrs(db),
94+
AssocItem::TypeAlias(it) => it.attrs(db),
95+
}
96+
}
97+
98+
fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> {
99+
match self {
100+
AssocItem::Function(it) => it.docs(db),
101+
AssocItem::Const(it) => it.docs(db),
102+
AssocItem::TypeAlias(it) => it.docs(db),
103+
}
104+
}
105+
106+
fn resolve_doc_path(
107+
self,
108+
db: &dyn HirDatabase,
109+
link: &str,
110+
ns: Option<Namespace>,
111+
) -> Option<ModuleDef> {
112+
match self {
113+
AssocItem::Function(it) => it.resolve_doc_path(db, link, ns),
114+
AssocItem::Const(it) => it.resolve_doc_path(db, link, ns),
115+
AssocItem::TypeAlias(it) => it.resolve_doc_path(db, link, ns),
116+
}
117+
}
118+
}
119+
89120
fn resolve_doc_path(
90121
db: &dyn HirDatabase,
91122
def: AttrDefId,

crates/hir/src/lib.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2744,3 +2744,32 @@ pub trait HasVisibility {
27442744
vis.is_visible_from(db.upcast(), module.id)
27452745
}
27462746
}
2747+
2748+
/// Trait for obtaining the defining crate of an item.
2749+
pub trait HasCrate {
2750+
fn krate(&self, db: &dyn HirDatabase) -> Crate;
2751+
}
2752+
2753+
impl<T: hir_def::HasModule> HasCrate for T {
2754+
fn krate(&self, db: &dyn HirDatabase) -> Crate {
2755+
self.module(db.upcast()).krate().into()
2756+
}
2757+
}
2758+
2759+
impl HasCrate for AssocItem {
2760+
fn krate(&self, db: &dyn HirDatabase) -> Crate {
2761+
self.module(db).krate()
2762+
}
2763+
}
2764+
2765+
impl HasCrate for Field {
2766+
fn krate(&self, db: &dyn HirDatabase) -> Crate {
2767+
self.parent_def(db).module(db).krate()
2768+
}
2769+
}
2770+
2771+
impl HasCrate for Function {
2772+
fn krate(&self, db: &dyn HirDatabase) -> Crate {
2773+
self.module(db).krate()
2774+
}
2775+
}

crates/hir_def/src/attr.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use either::Either;
1212
use hir_expand::{hygiene::Hygiene, name::AsName, AstId, InFile};
1313
use itertools::Itertools;
1414
use la_arena::ArenaMap;
15-
use mbe::ast_to_token_tree;
15+
use mbe::{ast_to_token_tree, DelimiterKind};
1616
use smallvec::{smallvec, SmallVec};
1717
use syntax::{
1818
ast::{self, AstNode, AttrsOwner},
@@ -290,6 +290,13 @@ impl Attrs {
290290
Some(Documentation(buf))
291291
}
292292
}
293+
294+
pub fn has_doc_hidden(&self) -> bool {
295+
self.by_key("doc").tt_values().any(|tt| {
296+
tt.delimiter_kind() == Some(DelimiterKind::Parenthesis) &&
297+
matches!(&*tt.token_trees, [tt::TokenTree::Leaf(tt::Leaf::Ident(ident))] if ident.text == "hidden")
298+
})
299+
}
293300
}
294301

295302
impl AttrsWithOwner {

crates/ide_completion/src/completions/dot.rs

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Completes references after dot (fields and method calls).
22
33
use either::Either;
4-
use hir::{HasVisibility, ScopeDef};
4+
use hir::ScopeDef;
55
use rustc_hash::FxHashSet;
66

77
use crate::{context::CompletionContext, patterns::ImmediateLocation, Completions};
@@ -63,9 +63,7 @@ fn complete_fields(
6363
) {
6464
for receiver in receiver.autoderef(ctx.db) {
6565
for (field, ty) in receiver.fields(ctx.db) {
66-
if ctx.scope.module().map_or(false, |m| !field.is_visible_from(ctx.db, m)) {
67-
// Skip private field. FIXME: If the definition location of the
68-
// field is editable, we should show the completion
66+
if !ctx.is_visible(&field) {
6967
continue;
7068
}
7169
f(Either::Left(field), ty);
@@ -87,7 +85,7 @@ fn complete_methods(
8785
let traits_in_scope = ctx.scope.traits_in_scope();
8886
receiver.iterate_method_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, func| {
8987
if func.self_param(ctx.db).is_some()
90-
&& ctx.scope.module().map_or(true, |m| func.is_visible_from(ctx.db, m))
88+
&& ctx.is_visible(&func)
9189
&& seen_methods.insert(func.name(ctx.db))
9290
{
9391
f(func);
@@ -210,6 +208,33 @@ fn foo(a: A) { a.$0 }
210208
);
211209
}
212210

211+
#[test]
212+
fn test_doc_hidden_filtering() {
213+
check(
214+
r#"
215+
//- /lib.rs crate:lib deps:dep
216+
fn foo(a: dep::A) { a.$0 }
217+
//- /dep.rs crate:dep
218+
pub struct A {
219+
#[doc(hidden)]
220+
pub hidden_field: u32,
221+
pub pub_field: u32,
222+
}
223+
224+
impl A {
225+
pub fn pub_method(&self) {}
226+
227+
#[doc(hidden)]
228+
pub fn hidden_method(&self) {}
229+
}
230+
"#,
231+
expect![[r#"
232+
fd pub_field u32
233+
me pub_method() fn(&self)
234+
"#]],
235+
)
236+
}
237+
213238
#[test]
214239
fn test_union_field_completion() {
215240
check(

crates/ide_completion/src/completions/qualified_path.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
33
use std::iter;
44

5-
use hir::HasVisibility;
65
use rustc_hash::FxHashSet;
76
use syntax::{ast, AstNode};
87

@@ -120,6 +119,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
120119
_ => true,
121120
};
122121

122+
// FIXME: respect #[doc(hidden)] (see `CompletionContext::is_visible`)
123123
if add_resolution {
124124
acc.add_resolution(ctx, name, &def);
125125
}
@@ -163,7 +163,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
163163
if let Some(krate) = krate {
164164
let traits_in_scope = ctx.scope.traits_in_scope();
165165
ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| {
166-
if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
166+
if !ctx.is_visible(&item) {
167167
return None;
168168
}
169169
add_assoc_item(acc, ctx, item);
@@ -172,7 +172,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
172172

173173
// Iterate assoc types separately
174174
ty.iterate_assoc_items(ctx.db, krate, |item| {
175-
if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
175+
if !ctx.is_visible(&item) {
176176
return None;
177177
}
178178
if let hir::AssocItem::TypeAlias(ty) = item {
@@ -185,7 +185,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
185185
hir::PathResolution::Def(hir::ModuleDef::Trait(t)) => {
186186
// Handles `Trait::assoc` as well as `<Ty as Trait>::assoc`.
187187
for item in t.items(ctx.db) {
188-
if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
188+
if !ctx.is_visible(&item) {
189189
continue;
190190
}
191191
add_assoc_item(acc, ctx, item);
@@ -206,7 +206,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
206206
let traits_in_scope = ctx.scope.traits_in_scope();
207207
let mut seen = FxHashSet::default();
208208
ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| {
209-
if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
209+
if !ctx.is_visible(&item) {
210210
return None;
211211
}
212212

crates/ide_completion/src/context.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,37 @@ impl<'a> CompletionContext<'a> {
361361
self.path_context.as_ref().and_then(|it| it.qualifier.as_ref())
362362
}
363363

364+
/// Checks if an item is visible and not `doc(hidden)` at the completion site.
365+
pub(crate) fn is_visible<I>(&self, item: &I) -> bool
366+
where
367+
I: hir::HasVisibility + hir::HasAttrs + hir::HasCrate + Copy,
368+
{
369+
self.is_visible_impl(&item.visibility(self.db), &item.attrs(self.db), item.krate(self.db))
370+
}
371+
372+
fn is_visible_impl(
373+
&self,
374+
vis: &hir::Visibility,
375+
attrs: &hir::Attrs,
376+
defining_crate: hir::Crate,
377+
) -> bool {
378+
let module = match self.scope.module() {
379+
Some(it) => it,
380+
None => return false,
381+
};
382+
if !vis.is_visible_from(self.db, module.into()) {
383+
// FIXME: if the definition location is editable, also show private items
384+
return false;
385+
}
386+
387+
if module.krate() != defining_crate && attrs.has_doc_hidden() {
388+
// `doc(hidden)` items are only completed within the defining crate.
389+
return false;
390+
}
391+
392+
true
393+
}
394+
364395
fn fill_impl_def(&mut self) {
365396
self.impl_def = self
366397
.sema

0 commit comments

Comments
 (0)