mirror of
https://github.com/rust-lang/rust.git
synced 2026-05-21 17:52:12 +03:00
Rollup merge of #156413 - fmease:rustdoc-ltd-improvs, r=GuillaumeGomez
rustdoc: Correctness & perf improvements to link-to-definition Rewrite the way we resolve type-dependent paths (incl. method calls) in `SpanMapVisitor`. Instead of trying to (re)find the enclosing body owner each time we encounter such a node, potentially calling `typeck_body` several times per body, keep track of the "active" `BodyId` in the visitor and cache the corresponding `TypeckResults`. The "active" `BodyId` is updated in `visit_nested_body` (add) and in `visit_item` (remove). This fixes a class of ICEs where we tried to look up the definition of type-relative paths located in non-body items nested in bodies, in the overarching body which is never correct. rustdoc@main has a hack to fix this issue for paths specifically found in assoc types in impls. Fixes rust-lang/rust#147882. Fixes rust-lang/rust#147057. Fixes rust-lang/rust#155327. Fixes rust-lang/rust#149089. Fixes rust-lang/rust#150153. Fixes rust-lang/rust#156418. Supersedes rust-lang/rust#147911. --- `--generate-link-to-definition` is not enabled by default, so we can't perf this as is. Instead, I'm benching them against PR rust-lang/rust#156348 in PR https://github.com/rust-lang/rust/pull/156355 using the same parent commit for the try-builds. For context, LTD is *extremely* costly as the perf run for the baseline PR shows: https://github.com/rust-lang/rust/pull/156348#issuecomment-4411427326. "Absolute results": https://github.com/rust-lang/rust/pull/156355#issuecomment-4421922342. "Relative results": [https://perf.rust-lang.org/compare.html?...](https://perf.rust-lang.org/compare.html?start=68c2bff6cb08a87e59246064a6a9f37098e22c3f&end=78d15de5525370011388c8f63847e873c4de14ed&stat=instructions%3Au). Excerpt of the relative results: Primary: Benchmark | Profile | Scenario | Backend | Target | % Change | Significance Threshold | Significance Factor ---|---|---|---|---|---|---|--- nalgebra-0.33.0 | doc | full | llvm | x64 | -5.82% | 0.20% | 29.09x diesel-2.2.10 | doc | full | llvm | x64 | -3.41% | 0.20% | 17.05x image-0.25.6 | doc | full | llvm | x64 | -2.03% | 0.20% | 10.16x regex-automata-0.4.8 | doc | full | llvm | x64 | -1.94% | 0.20% | 9.71x cargo-0.87.1 | doc | full | llvm | x64 | -1.64% | 0.20% | 8.21x clap_derive-4.5.32 | doc | full | llvm | x64 | -1.63% | 0.20% | 8.14x cranelift-codegen-0.119.0 | doc | full | llvm | x64 | -1.48% | 0.20% | 7.41x syn-2.0.101 | doc | full | llvm | x64 | -1.07% | 0.20% | 5.35x ripgrep-14.1.1 | doc | full | llvm | x64 | -0.96% | 0.20% | 4.78x stm32f4-0.15.1 | doc | full | llvm | x64 | -0.80% | 0.20% | 4.01x serde-1.0.219 | doc | full | llvm | x64 | -0.67% | 0.20% | 3.33x hyper-1.6.0 | doc | full | llvm | x64 | -0.66% | 0.20% | 3.30x eza-0.21.2 | doc | full | llvm | x64 | -0.55% | 0.20% | 2.74x unicode-normalization-0.1.24 | doc | full | llvm | x64 | -0.49% | 0.20% | 2.45x serde_derive-1.0.219 | doc | full | llvm | x64 | -0.45% | 0.20% | 2.26x typenum-1.18.0 | doc | full | llvm | x64 | -0.42% | 0.20% | 2.08x bitmaps-3.2.1 | doc | full | llvm | x64 | -0.32% | 0.20% | 1.61x Secondary: Benchmark | Profile | Scenario | Backend | Target | % Change | Significance Threshold | Significance Factor ---|---|---|---|---|---|---|--- deep-vector | doc | full | llvm | x64 | -23.96% | 0.20% | 119.78x large-workspace | doc | full | llvm | x64 | -2.16% | 0.20% | 10.81x deeply-nested-multi | doc | full | llvm | x64 | -1.35% | 0.20% | 6.75x serde-1.0.219-threads4 | doc | full | llvm | x64 | -0.67% | 0.20% | 3.33x wg-grammar | doc | full | llvm | x64 | -0.32% | 0.20% | 1.62x tt-muncher | opt | full | llvm | x64 | -0.31% | 0.20% | 1.56x nalgebra-0.33.0: Query/Function | Time (%) | Time (s) | Time delta | Executions | Executions delta | Hits | Hits delta ---|---|---|---|---|---|---|--- Totals | 109.57% | 2.185 | -0.138 (-5.9%) | 1614146 | -5602 (-0.3%) | 16983840 | -1436455 (-7.8%) typeck_root | 15.75% | 0.344 | -0.105 (-23.4%) | 1704 | -954 (-35.9%) | 393 | -6758 (-94.5%) ...|...|...|...|...|...|...|...
This commit is contained in:
@@ -653,6 +653,15 @@ add the `--scrape-tests` flag.
|
||||
This flag enables the generation of links in the source code pages which allow the reader
|
||||
to jump to a type definition.
|
||||
|
||||
> [!WARNING]
|
||||
> In very specific scenarios, enabling this feature may lead to your program getting rejected if you
|
||||
> rely on rustdoc intentionally not running all semantic analysis passes on function bodies to aid
|
||||
> with documenting `cfg`-conditional items.
|
||||
>
|
||||
> More concretely, rustdoc may choose to type-check bodies if they contain type-dependent paths
|
||||
> including method calls. This may result in name resolution and type errors getting reported that
|
||||
> rustdoc would usually suppress.
|
||||
|
||||
### `--test-builder`: `rustc`-like program to build tests
|
||||
|
||||
* Tracking issue: [#102981](https://github.com/rust-lang/rust/issues/102981)
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId};
|
||||
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
|
||||
use rustc_hir::intravisit::{self, Visitor, VisitorExt};
|
||||
use rustc_hir::{ExprKind, HirId, Item, ItemKind, Mod, Node, QPath};
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_middle::ty::{self, TyCtxt};
|
||||
use rustc_span::{BytePos, ExpnKind};
|
||||
|
||||
use crate::clean::{self, PrimitiveType, rustc_span};
|
||||
@@ -82,7 +83,8 @@ pub(crate) fn collect_spans_and_sources(
|
||||
generate_link_to_definition: bool,
|
||||
) -> (FxIndexMap<PathBuf, String>, FxHashMap<Span, LinkFromSrc>) {
|
||||
if include_sources {
|
||||
let mut visitor = SpanMapVisitor { tcx, matches: FxHashMap::default() };
|
||||
let mut visitor =
|
||||
SpanMapVisitor { tcx, maybe_typeck_results: None, matches: FxHashMap::default() };
|
||||
|
||||
if generate_link_to_definition {
|
||||
tcx.hir_walk_toplevel_module(&mut visitor);
|
||||
@@ -96,49 +98,63 @@ pub(crate) fn collect_spans_and_sources(
|
||||
|
||||
struct SpanMapVisitor<'tcx> {
|
||||
pub(crate) tcx: TyCtxt<'tcx>,
|
||||
pub(crate) maybe_typeck_results: Option<LazyTypeckResults<'tcx>>,
|
||||
pub(crate) matches: FxHashMap<Span, LinkFromSrc>,
|
||||
}
|
||||
|
||||
impl SpanMapVisitor<'_> {
|
||||
impl<'tcx> SpanMapVisitor<'tcx> {
|
||||
/// Returns the typeck results of the current body if we're in one.
|
||||
///
|
||||
/// This will typeck the body if it hasn't been already. Since rustdoc intentionally doesn't run
|
||||
/// all semantic analysis passes on function bodies at the time of writing, this can lead to us
|
||||
/// "suddenly" rejecting the user's code under `--generate-link-to-definition` while accepting
|
||||
/// it if that flag isn't passed! So use this method sparingly and think about the consequences
|
||||
/// including performance!
|
||||
///
|
||||
/// This behavior is documented in the rustdoc book. Ideally, it wouldn't be that way but no
|
||||
/// good solution has been found so far. Don't think about adding some sort of flag to rustc to
|
||||
/// suppress diagnostic emission that would be unsound wrt. `ErrorGuaranteed`[^1] and generally
|
||||
/// be quite hacky!
|
||||
///
|
||||
/// [^1]: Historical context:
|
||||
/// <https://github.com/rust-lang/rust/issues/69426#issuecomment-1019412352>.
|
||||
fn maybe_typeck_results(&mut self) -> Option<&'tcx ty::TypeckResults<'tcx>> {
|
||||
let results = self.maybe_typeck_results.as_mut()?;
|
||||
let results = results.cache.get_or_insert_with(|| self.tcx.typeck_body(results.body_id));
|
||||
Some(results)
|
||||
}
|
||||
|
||||
fn link_for_def(&self, def_id: DefId) -> LinkFromSrc {
|
||||
if def_id.is_local() {
|
||||
LinkFromSrc::Local(rustc_span(def_id, self.tcx))
|
||||
} else {
|
||||
LinkFromSrc::External(def_id)
|
||||
}
|
||||
}
|
||||
|
||||
/// This function is where we handle `hir::Path` elements and add them into the "span map".
|
||||
fn handle_path(&mut self, path: &rustc_hir::Path<'_>, only_use_last_segment: bool) {
|
||||
fn handle_path(&mut self, path: &hir::Path<'_>, only_use_last_segment: bool) {
|
||||
match path.res {
|
||||
// FIXME: For now, we handle `DefKind` if it's not a `DefKind::TyParam`.
|
||||
// Would be nice to support them too alongside the other `DefKind`
|
||||
// (such as primitive types!).
|
||||
Res::Def(kind, def_id) if kind != DefKind::TyParam => {
|
||||
let link = if def_id.as_local().is_some() {
|
||||
LinkFromSrc::Local(rustc_span(def_id, self.tcx))
|
||||
} else {
|
||||
LinkFromSrc::External(def_id)
|
||||
};
|
||||
// FIXME: Properly support type parameters. Note they resolve just fine. The issue is
|
||||
// that our highlighter would then also linkify their *definition site* for some reason
|
||||
// linking them to themselves. Const parameters don't exhibit this issue.
|
||||
Res::Def(DefKind::TyParam, _) => {}
|
||||
Res::Def(_, def_id) => {
|
||||
// The segments can be empty for `use *;` in a non-crate-root scope in Rust 2015.
|
||||
let span = path.segments.last().map_or(path.span, |seg| seg.ident.span);
|
||||
// In case the path ends with generics, we remove them from the span.
|
||||
let span = if only_use_last_segment
|
||||
&& let Some(path_span) = path.segments.last().map(|segment| segment.ident.span)
|
||||
{
|
||||
path_span
|
||||
let span = if only_use_last_segment {
|
||||
span
|
||||
} else {
|
||||
path.segments
|
||||
.last()
|
||||
.map(|last| {
|
||||
// In `use` statements, the included item is not in the path segments.
|
||||
// However, it doesn't matter because you can't have generics on `use`
|
||||
// statements.
|
||||
if path.span.contains(last.ident.span) {
|
||||
path.span.with_hi(last.ident.span.hi())
|
||||
} else {
|
||||
path.span
|
||||
}
|
||||
})
|
||||
.unwrap_or(path.span)
|
||||
// In `use` statements, the included item is not in the path segments. However,
|
||||
// it doesn't matter because you can't have generics on `use` statements.
|
||||
if path.span.contains(span) { path.span.with_hi(span.hi()) } else { path.span }
|
||||
};
|
||||
self.matches.insert(span.into(), link);
|
||||
self.matches.insert(span.into(), self.link_for_def(def_id));
|
||||
}
|
||||
Res::Local(_) if let Some(span) = self.tcx.hir_res_span(path.res) => {
|
||||
let path_span = if only_use_last_segment
|
||||
&& let Some(path_span) = path.segments.last().map(|segment| segment.ident.span)
|
||||
{
|
||||
path_span
|
||||
let path_span = if only_use_last_segment {
|
||||
path.segments.last().unwrap().ident.span
|
||||
} else {
|
||||
path.span
|
||||
};
|
||||
@@ -149,7 +165,6 @@ fn handle_path(&mut self, path: &rustc_hir::Path<'_>, only_use_last_segment: boo
|
||||
self.matches
|
||||
.insert(path.span.into(), LinkFromSrc::Primitive(PrimitiveType::from(p)));
|
||||
}
|
||||
Res::Err => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -216,43 +231,6 @@ fn handle_macro(&mut self, span: rustc_span::Span) -> bool {
|
||||
self.matches.insert(new_span.into(), link_from_src);
|
||||
true
|
||||
}
|
||||
|
||||
fn infer_id(&mut self, hir_id: HirId, expr_hir_id: Option<HirId>, span: Span) {
|
||||
let tcx = self.tcx;
|
||||
let body_id = tcx.hir_enclosing_body_owner(hir_id);
|
||||
// FIXME: this is showing error messages for parts of the code that are not
|
||||
// compiled (because of cfg)!
|
||||
//
|
||||
// See discussion in https://github.com/rust-lang/rust/issues/69426#issuecomment-1019412352
|
||||
let typeck_results = tcx.typeck_body(tcx.hir_body_owned_by(body_id).id());
|
||||
// Interestingly enough, for method calls, we need the whole expression whereas for static
|
||||
// method/function calls, we need the call expression specifically.
|
||||
if let Some(def_id) = typeck_results.type_dependent_def_id(expr_hir_id.unwrap_or(hir_id)) {
|
||||
let link = if def_id.as_local().is_some() {
|
||||
LinkFromSrc::Local(rustc_span(def_id, tcx))
|
||||
} else {
|
||||
LinkFromSrc::External(def_id)
|
||||
};
|
||||
self.matches.insert(span, link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is a reimplementation of `hir_enclosing_body_owner` which allows to fail without
|
||||
// panicking.
|
||||
fn hir_enclosing_body_owner(tcx: TyCtxt<'_>, hir_id: HirId) -> Option<LocalDefId> {
|
||||
for (_, node) in tcx.hir_parent_iter(hir_id) {
|
||||
// FIXME: associated type impl items don't have an associated body, so we don't handle
|
||||
// them currently.
|
||||
if let Node::ImplItem(impl_item) = node
|
||||
&& matches!(impl_item.kind, rustc_hir::ImplItemKind::Type(_))
|
||||
{
|
||||
return None;
|
||||
} else if let Some((def_id, _)) = node.associated_body() {
|
||||
return Some(def_id);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
|
||||
@@ -262,7 +240,24 @@ fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
|
||||
self.tcx
|
||||
}
|
||||
|
||||
fn visit_path(&mut self, path: &rustc_hir::Path<'tcx>, _id: HirId) {
|
||||
fn visit_nested_body(&mut self, body_id: hir::BodyId) -> Self::Result {
|
||||
let maybe_typeck_results =
|
||||
self.maybe_typeck_results.replace(LazyTypeckResults { body_id, cache: None });
|
||||
self.visit_body(self.tcx.hir_body(body_id));
|
||||
self.maybe_typeck_results = maybe_typeck_results;
|
||||
}
|
||||
|
||||
fn visit_anon_const(&mut self, ct: &'tcx hir::AnonConst) {
|
||||
// FIXME: Typeck'ing anon consts leads to ICEs in rustc if the parent body wasn't typeck'ed
|
||||
// yet. See #156418. Figure out what the best and proper solution for this is. Until
|
||||
// then, let's prevent `typeck` from being called on anon consts by not setting
|
||||
// `maybe_typeck_results` to `Some(_)`.
|
||||
let maybe_typeck_results = self.maybe_typeck_results.take();
|
||||
self.visit_body(self.tcx.hir_body(ct.body));
|
||||
self.maybe_typeck_results = maybe_typeck_results;
|
||||
}
|
||||
|
||||
fn visit_path(&mut self, path: &hir::Path<'tcx>, _id: HirId) {
|
||||
if self.handle_macro(path.span) {
|
||||
return;
|
||||
}
|
||||
@@ -272,25 +267,32 @@ fn visit_path(&mut self, path: &rustc_hir::Path<'tcx>, _id: HirId) {
|
||||
|
||||
fn visit_qpath(&mut self, qpath: &QPath<'tcx>, id: HirId, _span: rustc_span::Span) {
|
||||
match *qpath {
|
||||
QPath::TypeRelative(qself, path) => {
|
||||
if matches!(path.res, Res::Err) {
|
||||
let tcx = self.tcx;
|
||||
if let Some(body_id) = hir_enclosing_body_owner(tcx, id) {
|
||||
let typeck_results = tcx.typeck_body(tcx.hir_body_owned_by(body_id).id());
|
||||
let path = rustc_hir::Path {
|
||||
// We change the span to not include parens.
|
||||
span: path.ident.span,
|
||||
res: typeck_results.qpath_res(qpath, id),
|
||||
segments: &[],
|
||||
};
|
||||
self.handle_path(&path, false);
|
||||
}
|
||||
} else {
|
||||
self.infer_id(path.hir_id, Some(id), path.ident.span.into());
|
||||
QPath::TypeRelative(qself, segment) => {
|
||||
// FIXME: This doesn't work for paths in *types* since HIR ty lowering currently
|
||||
// doesn't write back the resolution of type-relative paths. Updating it to
|
||||
// do so should be a simple fix.
|
||||
// FIXME: This obviously doesn't support item signatures / non-bodies. Sadly, rustc
|
||||
// currently doesn't keep around that information & thus can't provide an API
|
||||
// for it.
|
||||
// `ItemCtxt`s would need a place to write back the resolution of type-
|
||||
// dependent definitions. Ideally there was some sort of query keyed on the
|
||||
// `LocalDefId` of the owning item that returns some table with which we can
|
||||
// map the `HirId` to a `DefId`.
|
||||
// Of course, we could re-HIR-ty-lower such paths *here* if we were to extend
|
||||
// the public API of HIR analysis. However, I strongly advise against it as
|
||||
// it would be too much of a hack.
|
||||
if let Some(typeck_results) = self.maybe_typeck_results() {
|
||||
let path = hir::Path {
|
||||
// We change the span to not include parens.
|
||||
span: segment.ident.span,
|
||||
res: typeck_results.qpath_res(qpath, id),
|
||||
segments: std::slice::from_ref(segment),
|
||||
};
|
||||
self.handle_path(&path, false);
|
||||
}
|
||||
|
||||
rustc_ast::visit::try_visit!(self.visit_ty_unambig(qself));
|
||||
self.visit_path_segment(path);
|
||||
self.visit_path_segment(segment);
|
||||
}
|
||||
QPath::Resolved(maybe_qself, path) => {
|
||||
self.handle_path(path, true);
|
||||
@@ -323,23 +325,27 @@ fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: rustc_span::Span, id: HirId) {
|
||||
intravisit::walk_mod(self, m);
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'tcx>) {
|
||||
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
|
||||
match expr.kind {
|
||||
ExprKind::MethodCall(segment, ..) => {
|
||||
self.infer_id(segment.hir_id, Some(expr.hir_id), segment.ident.span.into())
|
||||
}
|
||||
ExprKind::Call(call, ..) => self.infer_id(call.hir_id, None, call.span.into()),
|
||||
_ => {
|
||||
if self.handle_macro(expr.span) {
|
||||
// We don't want to go deeper into the macro.
|
||||
return;
|
||||
if let Some(typeck_results) = self.maybe_typeck_results()
|
||||
&& let Some(def_id) = typeck_results.type_dependent_def_id(expr.hir_id)
|
||||
{
|
||||
self.matches.insert(segment.ident.span.into(), self.link_for_def(def_id));
|
||||
}
|
||||
}
|
||||
// We don't want to go deeper into the macro.
|
||||
_ if self.handle_macro(expr.span) => return,
|
||||
_ => {}
|
||||
}
|
||||
intravisit::walk_expr(self, expr);
|
||||
}
|
||||
|
||||
fn visit_item(&mut self, item: &'tcx Item<'tcx>) {
|
||||
// We're no longer in a body since we've crossed an item boundary.
|
||||
// Temporarily take away the typeck results which are only valid in bodies.
|
||||
let maybe_typeck_results = self.maybe_typeck_results.take();
|
||||
|
||||
match item.kind {
|
||||
ItemKind::Static(..)
|
||||
| ItemKind::Const(..)
|
||||
@@ -359,6 +365,15 @@ fn visit_item(&mut self, item: &'tcx Item<'tcx>) {
|
||||
// We already have "visit_mod" above so no need to check it here.
|
||||
| ItemKind::Mod(..) => {}
|
||||
}
|
||||
|
||||
intravisit::walk_item(self, item);
|
||||
|
||||
self.maybe_typeck_results = maybe_typeck_results;
|
||||
}
|
||||
}
|
||||
|
||||
/// Lazily computed & cached [`ty::TypeckResults`].
|
||||
struct LazyTypeckResults<'tcx> {
|
||||
body_id: hir::BodyId,
|
||||
cache: Option<&'tcx ty::TypeckResults<'tcx>>,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// Like test `assoc-items.rs` but now utilizing unstable features.
|
||||
// FIXME: Make use of (m)GCA assoc consts once they no longer ICE!
|
||||
//@ compile-flags: -Zunstable-options --generate-link-to-definition
|
||||
#![feature(return_type_notation)]
|
||||
|
||||
//@ has 'src/assoc_items_extra/assoc-items-extra.rs.html'
|
||||
|
||||
trait Trait0 {
|
||||
fn fn0() -> impl Sized;
|
||||
fn fn1() -> impl Sized;
|
||||
}
|
||||
|
||||
fn item<T: Trait0>()
|
||||
where
|
||||
//@ has - '//a[@href="#9"]' 'fn0'
|
||||
<T as Trait0>::fn0(..): Copy, // Item, AssocFn, Resolved
|
||||
// FIXME: Support this:
|
||||
//@ !has - '//a[@href="#10"]' 'fn1'
|
||||
T::fn1(..): Copy, // Item, AssocFn, TypeRelative
|
||||
{}
|
||||
@@ -1,58 +1,106 @@
|
||||
// This test ensures that patterns also get a link generated.
|
||||
|
||||
//@ compile-flags: -Zunstable-options --generate-link-to-definition
|
||||
|
||||
#![crate_name = "foo"]
|
||||
//@ has 'src/assoc_items/assoc-items.rs.html'
|
||||
|
||||
//@ has 'src/foo/assoc-items.rs.html'
|
||||
|
||||
pub trait Trait {
|
||||
type T;
|
||||
}
|
||||
pub trait Another {
|
||||
type T;
|
||||
const X: u32;
|
||||
trait Trait0 {
|
||||
fn fn0();
|
||||
fn fn1();
|
||||
fn fn2();
|
||||
fn fn3();
|
||||
const CT0: usize;
|
||||
const CT1: usize;
|
||||
type Ty0;
|
||||
type Ty1;
|
||||
type Ty2: Bound0;
|
||||
type Ty3: Bound0;
|
||||
}
|
||||
|
||||
pub struct Foo;
|
||||
|
||||
impl Foo {
|
||||
pub fn new() -> Self { Foo }
|
||||
trait Bound0 {
|
||||
fn fn0();
|
||||
const CT0: usize;
|
||||
}
|
||||
|
||||
pub struct C;
|
||||
fn expr<T: Trait0>() {
|
||||
//@ has - '//a[@href="#6"]' 'fn0'
|
||||
let _ = <T as Trait0>::fn0; // Expr, AssocFn, Resolved
|
||||
//@ has - '//a[@href="#7"]' 'fn1'
|
||||
let _ = T::fn1; // Expr, AssocFn, TypeRelative
|
||||
|
||||
impl C {
|
||||
pub fn wat() {}
|
||||
//@ has - '//a[@href="#8"]' 'fn2'
|
||||
let _ = <T as Trait0>::fn2(); // Expr, AssocFn, Resolved
|
||||
//@ has - '//a[@href="#9"]' 'fn3'
|
||||
let _ = T::fn3(); // Expr, AssocFn, TypeRelative
|
||||
|
||||
//@ has - '//a[@href="#10"]' 'CT0'
|
||||
let _ = <T as Trait0>::CT0; // Expr, AssocConst, Resolved
|
||||
//@ has - '//a[@href="#11"]' 'CT1'
|
||||
let _ = <T>::CT1; // Expr, AssocConst, TypeRelative
|
||||
|
||||
//@ has - '//a[@href="#12"]' 'Ty0'
|
||||
let _: <T as Trait0>::Ty0; // Expr, AssocTy, Resolved
|
||||
// FIXME: Support this:
|
||||
//@ !has - '//a[@href="#13"]' 'Ty1'
|
||||
let _: T::Ty1; // Expr, AssocTy, TypeRelative
|
||||
|
||||
//@ has - '//a[@href="#14"]' 'Ty2'
|
||||
//@ has - '//a[@href="#19"]' 'fn0'
|
||||
let _ = <T as Trait0>::Ty2::fn0();
|
||||
|
||||
// FIXME: Support this:
|
||||
//@ !has - '//a[@href="#14"]' 'Ty3'
|
||||
//@ has - '//a[@href="#20"]' 'CT0'
|
||||
let _ = T::Ty2::CT0;
|
||||
}
|
||||
|
||||
// These two links must not change and in particular must contain `/derive.`!
|
||||
//@ has - '//a[@href="{{channel}}/core/fmt/macros/derive.Debug.html"]' 'Debug'
|
||||
//@ has - '//a[@href="{{channel}}/core/cmp/derive.PartialEq.html"]' 'PartialEq'
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Bar;
|
||||
impl Trait for Bar {
|
||||
type T = Foo;
|
||||
}
|
||||
impl Another for Bar {
|
||||
type T = C;
|
||||
const X: u32 = 12;
|
||||
trait Trait1 {
|
||||
const CT0: usize;
|
||||
const CT1: usize;
|
||||
const CT2: usize;
|
||||
|
||||
fn scope();
|
||||
}
|
||||
|
||||
pub fn bar() {
|
||||
//@ has - '//a[@href="#20"]' 'new'
|
||||
<Bar as Trait>::T::new();
|
||||
//@ has - '//a[@href="#26"]' 'wat'
|
||||
<Bar as Another>::T::wat();
|
||||
|
||||
match 12u32 {
|
||||
//@ has - '//a[@href="#14"]' 'X'
|
||||
<Bar as Another>::X => {}
|
||||
fn pat() {
|
||||
//@ has - '//a[@href="#56"]' 'CT0'
|
||||
if let <() as Trait1>::CT0 = 0 {} // Pat, AssocConst, Resolved
|
||||
|
||||
match 0 {
|
||||
//@ has - '//a[@href="#57"]' 'CT1'
|
||||
<() as Trait1>::CT1 => {} // Pat, AssocConst, Resolved
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Far {
|
||||
//@ has - '//a[@href="#10"]' 'T'
|
||||
x: <Bar as Trait>::T,
|
||||
impl Trait1 for () {
|
||||
const CT0: usize = 0;
|
||||
const CT1: usize = 1;
|
||||
const CT2: usize = 2;
|
||||
|
||||
fn scope() {
|
||||
//@ has - '//a[@href="#58"]' 'CT2'
|
||||
if let Self::CT2 = 0 {} // Pat, AssocConst, TypeRelative
|
||||
}
|
||||
}
|
||||
|
||||
trait Trait2 {
|
||||
const CT0: usize;
|
||||
type Ty0;
|
||||
type Ty1;
|
||||
}
|
||||
|
||||
impl Trait2 for () {
|
||||
const CT0: usize = 0;
|
||||
type Ty0 = ();
|
||||
type Ty1 = ();
|
||||
}
|
||||
|
||||
struct Item<T: Trait2> {
|
||||
//@ has - '//a[@href="#87"]' 'CT0'
|
||||
f0: [(); <() as Trait2>::CT0], // Item, AssocConst, Resolved
|
||||
//@ has - '//a[@href="#88"]' 'Ty0'
|
||||
f1: <T as Trait2>::Ty0, // Item, AssocTy, Resolved
|
||||
// FIXME: Support this:
|
||||
//@ !has - '//a[@href="#89"]' 'Ty1'
|
||||
f2: T::Ty1, // Item, AssocTy, TypeRelative
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
// This test ensures that associated types don't crash rustdoc jump to def.
|
||||
|
||||
//@ compile-flags: -Zunstable-options --generate-link-to-definition
|
||||
|
||||
|
||||
#![crate_name = "foo"]
|
||||
|
||||
//@ has 'src/foo/assoc-types.rs.html'
|
||||
|
||||
pub trait Trait {
|
||||
type Node;
|
||||
}
|
||||
|
||||
pub fn y<G: Trait>() {
|
||||
struct X<G>(G);
|
||||
|
||||
impl<G: Trait> Trait for X<G> {
|
||||
type Node = G::Node;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// This test ensures that items with no body don't panic when generating
|
||||
// jump to def links.
|
||||
|
||||
//@ compile-flags: -Zunstable-options --generate-link-to-definition
|
||||
|
||||
#![crate_name = "foo"]
|
||||
|
||||
//@ has 'src/foo/no-body-items.rs.html'
|
||||
|
||||
pub trait A {
|
||||
type T;
|
||||
type U;
|
||||
}
|
||||
|
||||
impl A for () {
|
||||
type T = Self::U;
|
||||
type U = ();
|
||||
}
|
||||
|
||||
pub trait C {
|
||||
type X;
|
||||
}
|
||||
|
||||
pub struct F<T: C>(pub T::X);
|
||||
@@ -0,0 +1,11 @@
|
||||
// Ensure that we don't crash on anonymous constants.
|
||||
// FIXME: Ideally, we would actually support linkifying type-dependent paths in
|
||||
// anon consts but for now that's disabled until we figure out what the
|
||||
// proper solution is implementation-wise.
|
||||
// issue: <https://github.com/rust-lang/rust/issues/156418>
|
||||
//@ check-pass
|
||||
|
||||
fn scope() {
|
||||
struct Hold<const N: usize>;
|
||||
let _ = X::<{ 0usize.saturating_add(1) }>;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// When trying to resolve a type-relative path (e.g., `T::Item` where `T` is a type param) in an
|
||||
// item that's nested inside of a body (e.g., of a function or constant), we once tried to look up
|
||||
// the definition in the `TypeckResults` of the body owner which is wrong and led to compiler ICEs.
|
||||
//
|
||||
// We now make sure to invalidate the `TypeckResults` when crossing a body - item border.
|
||||
//
|
||||
// For additional context, `TypeckResults` as returned by queries like `typeck` store the typeck
|
||||
// results of *bodies* only. In item signatures / non-bodies, there's no equivalent at the time of
|
||||
// writing, so it's impossible to resolve HIR TypeRelative paths (identified by a `HirId`) to their
|
||||
// definition (`DefId`) in other parts of the compiler / in tools.
|
||||
|
||||
//@ compile-flags: -Zunstable-options --generate-link-to-definition
|
||||
//@ check-pass
|
||||
// issue: <https://github.com/rust-lang/rust/issues/147882>
|
||||
|
||||
fn scope() {
|
||||
struct X<T: Iterator>(T::Item);
|
||||
|
||||
trait Trait {
|
||||
type Ty;
|
||||
|
||||
fn func<T: Iterator>()
|
||||
where
|
||||
T::Item: Copy
|
||||
{}
|
||||
}
|
||||
|
||||
impl<T: Iterator> Trait for T {
|
||||
type Ty = T::Item;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user