Skip to content

Commit 1f7eb4f

Browse files
committed
make missing_doc lint respect the visibility rules
Previously, the `exported_items` set created by the privacy pass was incomplete. Specifically, it did not include items that had been defined at a private path but then `pub use`d at a public path. This commit finds all crate exports during the privacy pass. Consequently, some code in the reachable pass and in rustdoc is no longer necessary. This commit then removes the separate `MissingDocLintVisitor` lint pass, opting to check missing_doc lint in the same pass as the other lint checkers using the visibility result computed by the privacy pass. Fixes #9777.
1 parent 4d9b95f commit 1f7eb4f

File tree

8 files changed

+253
-244
lines changed

8 files changed

+253
-244
lines changed

src/librustc/driver/driver.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -305,11 +305,10 @@ pub fn phase_3_run_analysis_passes(sess: Session,
305305

306306
let reachable_map =
307307
time(time_passes, "reachability checking", (), |_|
308-
reachable::find_reachable(ty_cx, method_map, exp_map2,
309-
&exported_items));
308+
reachable::find_reachable(ty_cx, method_map, &exported_items));
310309

311310
time(time_passes, "lint checking", (), |_|
312-
lint::check_crate(ty_cx, crate));
311+
lint::check_crate(ty_cx, &exported_items, crate));
313312

314313
CrateAnalysis {
315314
exp_map2: exp_map2,

src/librustc/middle/lint.rs

Lines changed: 128 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
//! Context itself, span_lint should be used instead of add_lint.
3535
3636
use driver::session;
37+
use middle::privacy;
3738
use middle::trans::adt; // for `adt::is_ffi_safe`
3839
use middle::ty;
3940
use middle::pat_util;
@@ -317,21 +318,28 @@ pub fn get_lint_dict() -> LintDict {
317318
return map;
318319
}
319320

320-
struct Context {
321+
struct Context<'self> {
321322
// All known lint modes (string versions)
322323
dict: @LintDict,
323324
// Current levels of each lint warning
324325
cur: SmallIntMap<(level, LintSource)>,
325326
// context we're checking in (used to access fields like sess)
326327
tcx: ty::ctxt,
328+
// Items exported by the crate; used by the missing_doc lint.
329+
exported_items: &'self privacy::ExportedItems,
330+
// The id of the current `ast::struct_def` being walked.
331+
cur_struct_def_id: ast::NodeId,
332+
// Whether some ancestor of the current node was marked
333+
// #[doc(hidden)].
334+
is_doc_hidden: bool,
327335

328336
// When recursing into an attributed node of the ast which modifies lint
329337
// levels, this stack keeps track of the previous lint levels of whatever
330338
// was modified.
331339
lint_stack: ~[(lint, level, LintSource)],
332340
}
333341

334-
impl Context {
342+
impl<'self> Context<'self> {
335343
fn get_level(&self, lint: lint) -> level {
336344
match self.cur.find(&(lint as uint)) {
337345
Some(&(lvl, _)) => lvl,
@@ -440,9 +448,16 @@ impl Context {
440448
true
441449
};
442450

451+
let old_is_doc_hidden = self.is_doc_hidden;
452+
self.is_doc_hidden = self.is_doc_hidden ||
453+
attrs.iter().any(|attr| ("doc" == attr.name() && match attr.meta_item_list()
454+
{ None => false,
455+
Some(l) => attr::contains_name(l, "hidden") }));
456+
443457
f(self);
444458

445459
// rollback
460+
self.is_doc_hidden = old_is_doc_hidden;
446461
do pushed.times {
447462
let (lint, lvl, src) = self.lint_stack.pop();
448463
self.set_level(lint, lvl, src);
@@ -870,125 +885,83 @@ fn check_unnecessary_allocation(cx: &Context, e: &ast::Expr) {
870885
}
871886
}
872887

873-
struct MissingDocLintVisitor(ty::ctxt);
874-
875-
impl MissingDocLintVisitor {
876-
fn check_attrs(&self, attrs: &[ast::Attribute], id: ast::NodeId,
877-
sp: Span, msg: ~str) {
878-
if !attrs.iter().any(|a| a.node.is_sugared_doc) {
879-
self.sess.add_lint(missing_doc, id, sp, msg);
880-
}
881-
}
882-
883-
fn check_struct(&self, sdef: &ast::struct_def) {
884-
for field in sdef.fields.iter() {
885-
match field.node.kind {
886-
ast::named_field(_, vis) if vis != ast::private => {
887-
self.check_attrs(field.node.attrs, field.node.id, field.span,
888-
~"missing documentation for a field");
889-
}
890-
ast::unnamed_field | ast::named_field(*) => {}
891-
}
892-
}
893-
}
894-
895-
fn doc_hidden(&self, attrs: &[ast::Attribute]) -> bool {
896-
do attrs.iter().any |attr| {
897-
"doc" == attr.name() &&
898-
match attr.meta_item_list() {
899-
Some(l) => attr::contains_name(l, "hidden"),
900-
None => false // not of the form #[doc(...)]
901-
}
902-
}
888+
fn check_missing_doc_attrs(cx: &Context,
889+
id: ast::NodeId,
890+
attrs: &[ast::Attribute],
891+
sp: Span,
892+
desc: &'static str) {
893+
// If we're building a test harness, then warning about
894+
// documentation is probably not really relevant right now.
895+
if cx.tcx.sess.opts.test { return }
896+
897+
// `#[doc(hidden)]` disables missing_doc check.
898+
if cx.is_doc_hidden { return }
899+
900+
// Only check publicly-visible items, using the result from the
901+
// privacy pass.
902+
if !cx.exported_items.contains(&id) { return }
903+
904+
if !attrs.iter().any(|a| a.node.is_sugared_doc) {
905+
cx.span_lint(missing_doc, sp,
906+
format!("missing documentation for {}", desc));
903907
}
904908
}
905909

906-
impl Visitor<()> for MissingDocLintVisitor {
907-
fn visit_ty_method(&mut self, m:&ast::TypeMethod, _: ()) {
908-
if self.doc_hidden(m.attrs) { return }
909-
910-
// All ty_method objects are linted about because they're part of a
911-
// trait (no visibility)
912-
self.check_attrs(m.attrs, m.id, m.span,
913-
~"missing documentation for a method");
914-
visit::walk_ty_method(self, m, ());
915-
}
910+
fn check_missing_doc_item(cx: &mut Context, it: &ast::item) { // XXX doesn't need to be mut
911+
let desc = match it.node {
912+
ast::item_fn(*) => "a function",
913+
ast::item_mod(*) => "a module",
914+
ast::item_enum(*) => "an enum",
915+
ast::item_struct(*) => "a struct",
916+
ast::item_trait(*) => "a trait",
917+
_ => return
918+
};
919+
check_missing_doc_attrs(cx, it.id, it.attrs, it.span, desc);
920+
}
916921

917-
fn visit_fn(&mut self, fk: &visit::fn_kind, d: &ast::fn_decl,
918-
b: &ast::Block, sp: Span, id: ast::NodeId, _: ()) {
919-
// Only warn about explicitly public methods.
920-
match *fk {
921-
visit::fk_method(_, _, m) => {
922-
if self.doc_hidden(m.attrs) {
923-
return;
924-
}
925-
// If we're in a trait implementation, no need to duplicate
926-
// documentation
927-
if m.vis == ast::public {
928-
self.check_attrs(m.attrs, id, sp,
929-
~"missing documentation for a method");
922+
fn check_missing_doc_method(cx: &Context, m: &ast::method) {
923+
let did = ast::DefId {
924+
crate: ast::LOCAL_CRATE,
925+
node: m.id
926+
};
927+
match cx.tcx.methods.find(&did) {
928+
None => cx.tcx.sess.span_bug(m.span, "missing method descriptor?!"),
929+
Some(md) => {
930+
match md.container {
931+
// Always check default methods defined on traits.
932+
ty::TraitContainer(*) => {}
933+
// For methods defined on impls, it depends on whether
934+
// it is an implementation for a trait or is a plain
935+
// impl.
936+
ty::ImplContainer(cid) => {
937+
match ty::impl_trait_ref(cx.tcx, cid) {
938+
Some(*) => return, // impl for trait: don't doc
939+
None => {} // plain impl: doc according to privacy
940+
}
930941
}
931942
}
932-
_ => {}
933943
}
934-
visit::walk_fn(self, fk, d, b, sp, id, ());
935944
}
945+
check_missing_doc_attrs(cx, m.id, m.attrs, m.span, "a method");
946+
}
936947

937-
fn visit_item(&mut self, it: @ast::item, _: ()) {
938-
// If we're building a test harness, then warning about documentation is
939-
// probably not really relevant right now
940-
if self.sess.opts.test { return }
941-
if self.doc_hidden(it.attrs) { return }
942-
943-
match it.node {
944-
ast::item_struct(sdef, _) if it.vis == ast::public => {
945-
self.check_attrs(it.attrs, it.id, it.span,
946-
~"missing documentation for a struct");
947-
self.check_struct(sdef);
948-
}
949-
950-
// Skip implementations because they inherit documentation from the
951-
// trait (which was already linted)
952-
ast::item_impl(_, Some(*), _, _) => return,
953-
954-
ast::item_trait(*) if it.vis != ast::public => return,
955-
ast::item_trait(*) => self.check_attrs(it.attrs, it.id, it.span,
956-
~"missing documentation for a trait"),
957-
958-
ast::item_fn(*) if it.vis == ast::public => {
959-
self.check_attrs(it.attrs, it.id, it.span,
960-
~"missing documentation for a function");
961-
}
962-
963-
ast::item_mod(*) if it.vis == ast::public => {
964-
self.check_attrs(it.attrs, it.id, it.span,
965-
~"missing documentation for a module");
966-
}
967-
968-
ast::item_enum(ref edef, _) if it.vis == ast::public => {
969-
self.check_attrs(it.attrs, it.id, it.span,
970-
~"missing documentation for an enum");
971-
for variant in edef.variants.iter() {
972-
if variant.node.vis == ast::private { continue; }
973-
974-
self.check_attrs(variant.node.attrs, variant.node.id,
975-
variant.span,
976-
~"missing documentation for a variant");
977-
match variant.node.kind {
978-
ast::struct_variant_kind(sdef) => {
979-
self.check_struct(sdef);
980-
}
981-
_ => ()
982-
}
983-
}
984-
}
948+
fn check_missing_doc_ty_method(cx: &Context, tm: &ast::TypeMethod) {
949+
check_missing_doc_attrs(cx, tm.id, tm.attrs, tm.span, "a type method");
950+
}
985951

986-
_ => {}
987-
}
988-
visit::walk_item(self, it, ());
952+
fn check_missing_doc_struct_field(cx: &Context, sf: &ast::struct_field) {
953+
match sf.node.kind {
954+
ast::named_field(_, vis) if vis != ast::private =>
955+
check_missing_doc_attrs(cx, cx.cur_struct_def_id, sf.node.attrs,
956+
sf.span, "a struct field"),
957+
_ => {}
989958
}
990959
}
991960

961+
fn check_missing_doc_variant(cx: &Context, v: &ast::variant) {
962+
check_missing_doc_attrs(cx, v.node.id, v.node.attrs, v.span, "a variant");
963+
}
964+
992965
/// Checks for use of items with #[deprecated], #[experimental] and
993966
/// #[unstable] (or none of them) attributes.
994967
fn check_stability(cx: &Context, e: &ast::Expr) {
@@ -1062,13 +1035,14 @@ fn check_stability(cx: &Context, e: &ast::Expr) {
10621035
cx.span_lint(lint, e.span, msg);
10631036
}
10641037

1065-
impl Visitor<()> for Context {
1038+
impl<'self> Visitor<()> for Context<'self> {
10661039
fn visit_item(&mut self, it: @ast::item, _: ()) {
10671040
do self.with_lint_attrs(it.attrs) |cx| {
10681041
check_item_ctypes(cx, it);
10691042
check_item_non_camel_case_types(cx, it);
10701043
check_item_non_uppercase_statics(cx, it);
10711044
check_heap_item(cx, it);
1045+
check_missing_doc_item(cx, it);
10721046

10731047
do cx.visit_ids |v| {
10741048
v.visit_item(it, ());
@@ -1111,6 +1085,8 @@ impl Visitor<()> for Context {
11111085
match *fk {
11121086
visit::fk_method(_, _, m) => {
11131087
do self.with_lint_attrs(m.attrs) |cx| {
1088+
check_missing_doc_method(cx, m);
1089+
11141090
do cx.visit_ids |v| {
11151091
v.visit_fn(fk, decl, body, span, id, ());
11161092
}
@@ -1120,9 +1096,45 @@ impl Visitor<()> for Context {
11201096
_ => recurse(self),
11211097
}
11221098
}
1099+
1100+
fn visit_ty_method(&mut self, t: &ast::TypeMethod, _: ()) {
1101+
do self.with_lint_attrs(t.attrs) |cx| {
1102+
check_missing_doc_ty_method(cx, t);
1103+
1104+
visit::walk_ty_method(cx, t, ());
1105+
}
1106+
}
1107+
1108+
fn visit_struct_def(&mut self,
1109+
s: @ast::struct_def,
1110+
i: ast::Ident,
1111+
g: &ast::Generics,
1112+
id: ast::NodeId,
1113+
_: ()) {
1114+
let old_id = self.cur_struct_def_id;
1115+
self.cur_struct_def_id = id;
1116+
visit::walk_struct_def(self, s, i, g, id, ());
1117+
self.cur_struct_def_id = old_id;
1118+
}
1119+
1120+
fn visit_struct_field(&mut self, s: @ast::struct_field, _: ()) {
1121+
do self.with_lint_attrs(s.node.attrs) |cx| {
1122+
check_missing_doc_struct_field(cx, s);
1123+
1124+
visit::walk_struct_field(cx, s, ());
1125+
}
1126+
}
1127+
1128+
fn visit_variant(&mut self, v: &ast::variant, g: &ast::Generics, _: ()) {
1129+
do self.with_lint_attrs(v.node.attrs) |cx| {
1130+
check_missing_doc_variant(cx, v);
1131+
1132+
visit::walk_variant(cx, v, g, ());
1133+
}
1134+
}
11231135
}
11241136

1125-
impl ast_util::IdVisitingOperation for Context {
1137+
impl<'self> ast_util::IdVisitingOperation for Context<'self> {
11261138
fn visit_id(&self, id: ast::NodeId) {
11271139
match self.tcx.sess.lints.pop(&id) {
11281140
None => {}
@@ -1135,17 +1147,16 @@ impl ast_util::IdVisitingOperation for Context {
11351147
}
11361148
}
11371149

1138-
pub fn check_crate(tcx: ty::ctxt, crate: &ast::Crate) {
1139-
// This visitor contains more state than is currently maintained in Context,
1140-
// and there's no reason for the Context to keep track of this information
1141-
// really
1142-
let mut dox = MissingDocLintVisitor(tcx);
1143-
visit::walk_crate(&mut dox, crate, ());
1144-
1150+
pub fn check_crate(tcx: ty::ctxt,
1151+
exported_items: &privacy::ExportedItems,
1152+
crate: &ast::Crate) {
11451153
let mut cx = Context {
11461154
dict: @get_lint_dict(),
11471155
cur: SmallIntMap::new(),
11481156
tcx: tcx,
1157+
exported_items: exported_items,
1158+
cur_struct_def_id: -1,
1159+
is_doc_hidden: false,
11491160
lint_stack: ~[],
11501161
};
11511162

0 commit comments

Comments
 (0)