Skip to content

rustdoc fails to properly resolve Self in a submodule when the type is not in scope #84827

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
de-vri-es opened this issue May 2, 2021 · 6 comments
Assignees
Labels
A-intra-doc-links Area: Intra-doc links, the ability to link to items in docs by name C-bug Category: This is a bug. E-medium Call for participation: Medium difficulty. Experience needed to fix: Intermediate. E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue.

Comments

@de-vri-es
Copy link
Contributor

de-vri-es commented May 2, 2021

When a doc comment refers to Self inside an impl block for a type that is not in scope, rustdoc can't properly resolve Self.

It does resolve Self to a more specific name (Foo in the example below), but then it can't find that name.

A minimum example:

struct Foo {
	foo: i32,
}

mod bar {
	impl crate::Foo {
		/// Baz the [`Self::foo`].
		pub fn baz(&self) {
			println!("bazzing the foo");
		}
	}
}

This produces a broken link in the documentation and the following warning:

$ cargo doc
 Documenting foo v0.1.0 (/tmp/2021-05-02-18-45-24/foo)
warning: unresolved link to `Foo::foo`
 --> src/main.rs:7:16
  |
7 |         /// Baz the [`Self::foo`].
  |                      ^^^^^^^^^^^ no item named `Foo` in scope
  |
  = note: `#[warn(rustdoc::broken_intra_doc_links)]` on by default

warning: 1 warning emitted

    Finished dev [unoptimized + debuginfo] target(s) in 0.66s

Meta

I tested this using:

  • rustdoc 1.53.0-nightly (42816d6 2021-04-24)
  • rustdoc 1.51.0 (2fd73fa 2021-03-23)

Both show the same problem.

@rustbot modify labels: +A-rustdoc

@de-vri-es de-vri-es added the C-bug Category: This is a bug. label May 2, 2021
@rustbot rustbot added the T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue. label May 2, 2021
@m-ou-se m-ou-se added the A-intra-doc-links Area: Intra-doc links, the ability to link to items in docs by name label May 2, 2021
@de-vri-es
Copy link
Contributor Author

Also related: if you do import a type with the same name, then rustdoc will think that is Self.

That can be demonstrated with this example:

pub struct Foo {
	pub foo: i32,
}

pub mod bar {
	use crate::other::Foo;

	impl crate::Foo {
		/// Baz the [`Self::foo`].
		pub fn baz(&self) {
			println!("bazzing the foo");
		}
	}
}

pub mod other {
	pub struct Foo;
}

Which produces the following warning:

warning: unresolved link to `Foo::foo`
 --> src/main.rs:9:16
  |
9 |         /// Baz the [`Self::foo`].
  |                      ^^^^^^^^^^^ the struct `Foo` has no field or associated item named `foo`

And if I add a field with the same name, it really links to the wrong Foo.

@jyn514
Copy link
Member

jyn514 commented May 2, 2021

This is basically the issue from #83761 (comment), rustdoc should use the DefId instead of strings.

@jyn514 jyn514 added the E-medium Call for participation: Medium difficulty. Experience needed to fix: Intermediate. label May 2, 2021
@jyn514
Copy link
Member

jyn514 commented May 2, 2021

Relevant code in case someone wants to take a look:

// FIXME(jynelson): this shouldn't go through stringification, rustdoc should just use the DefId directly
let self_name = self_id.and_then(|self_id| {

@jyn514
Copy link
Member

jyn514 commented May 2, 2021

Basically the way to do this is to only do the string munging in

// Try looking for methods and associated items.
let mut split = path_str.rsplitn(2, "::");
// NB: `split`'s first element is always defined, even if the delimiter was not present.
// NB: `item_str` could be empty when resolving in the root namespace (e.g. `::std`).
let item_str = split.next().unwrap();
let item_name = Symbol::intern(item_str);
let path_root = split
.next()
.map(|f| f.to_owned())
// If there's no `::`, it's not an associated item.
// So we can be sure that `rustc_resolve` was accurate when it said it wasn't resolved.
.ok_or_else(|| {
debug!("found no `::`, assumming {} was correctly not in scope", item_name);
ResolutionFailure::NotResolved {
module_id,
partial_res: None,
unresolved: item_str.into(),
}
})?;
// FIXME(#83862): this arbitrarily gives precedence to primitives over modules to support
// links to primitives when `#[doc(primitive)]` is present. It should give an ambiguity
// error instead and special case *only* modules with `#[doc(primitive)]`, not all
// primitives.
resolve_primitive(&path_root, TypeNS)
.or_else(|| self.resolve_path(&path_root, TypeNS, module_id))
.and_then(|ty_res| {
let (res, fragment, side_channel) =
self.resolve_associated_item(ty_res, item_name, ns, module_id)?;
if self_id.is_none(); otherwise use self_id in the place of ty_res.

@jyn514 jyn514 added the E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. label May 2, 2021
@jyn514
Copy link
Member

jyn514 commented May 2, 2021

Ditto for

// Try looking for methods and associated items.
let mut split = path_str.rsplitn(2, "::");
// NB: `split`'s first element is always defined, even if the delimiter was not present.
// NB: `item_str` could be empty when resolving in the root namespace (e.g. `::std`).
let item_str = split.next().unwrap();
let item_name = Symbol::intern(item_str);
let path_root = split
.next()
.map(|f| f.to_owned())
// If there's no `::`, it's not an associated item.
// So we can be sure that `rustc_resolve` was accurate when it said it wasn't resolved.
.ok_or_else(|| {
debug!("found no `::`, assumming {} was correctly not in scope", item_name);
ResolutionFailure::NotResolved {
module_id,
partial_res: None,
unresolved: item_str.into(),
}
})?;
// FIXME(#83862): this arbitrarily gives precedence to primitives over modules to support
// links to primitives when `#[doc(primitive)]` is present. It should give an ambiguity
// error instead and special case *only* modules with `#[doc(primitive)]`, not all
// primitives.
resolve_primitive(&path_root, TypeNS)
.or_else(|| self.resolve_path(&path_root, TypeNS, module_id))
.and_then(|ty_res| {
let (res, fragment, side_channel) =
self.resolve_associated_item(ty_res, item_name, ns, module_id)?;

@tdelabro
Copy link
Contributor

tdelabro commented May 2, 2021

@rustbot claim

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
A-intra-doc-links Area: Intra-doc links, the ability to link to items in docs by name C-bug Category: This is a bug. E-medium Call for participation: Medium difficulty. Experience needed to fix: Intermediate. E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants