From f2a80a0f891a09301d43c269b8704bcb2817a419 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 17 Aug 2024 13:03:27 -0400 Subject: [PATCH 1/6] A raw ref of a deref is always safe --- compiler/rustc_mir_build/src/check_unsafety.rs | 12 +++--------- tests/ui/static/raw-ref-deref-without-unsafe.rs | 1 - .../ui/static/raw-ref-deref-without-unsafe.stderr | 2 +- tests/ui/unsafe/place-expr-safe.rs | 14 ++++++++++++++ 4 files changed, 18 insertions(+), 11 deletions(-) create mode 100644 tests/ui/unsafe/place-expr-safe.rs diff --git a/compiler/rustc_mir_build/src/check_unsafety.rs b/compiler/rustc_mir_build/src/check_unsafety.rs index 8512763a595..0f0286fab29 100644 --- a/compiler/rustc_mir_build/src/check_unsafety.rs +++ b/compiler/rustc_mir_build/src/check_unsafety.rs @@ -512,17 +512,11 @@ fn visit_expr(&mut self, expr: &'a Expr<'tcx>) { // THIR desugars UNSAFE_STATIC into *UNSAFE_STATIC_REF, where // UNSAFE_STATIC_REF holds the addr of the UNSAFE_STATIC, so: take two steps && let ExprKind::Deref { arg } = self.thir[arg].kind - // FIXME(workingjubiee): we lack a clear reason to reject ThreadLocalRef here, - // but we also have no conclusive reason to allow it either! - && let ExprKind::StaticRef { .. } = self.thir[arg].kind { - // A raw ref to a place expr, even an "unsafe static", is okay! - // We short-circuit to not recursively traverse this expression. + // Taking a raw ref to a deref place expr is always safe. + // Make sure the expression we're deref'ing is safe, though. + visit::walk_expr(self, &self.thir[arg]); return; - // note: const_mut_refs enables this code, and it currently remains unsafe: - // static mut BYTE: u8 = 0; - // static mut BYTE_PTR: *mut u8 = unsafe { addr_of_mut!(BYTE) }; - // static mut DEREF_BYTE_PTR: *mut u8 = unsafe { addr_of_mut!(*BYTE_PTR) }; } } ExprKind::Deref { arg } => { diff --git a/tests/ui/static/raw-ref-deref-without-unsafe.rs b/tests/ui/static/raw-ref-deref-without-unsafe.rs index 289e55b7638..7146c564e91 100644 --- a/tests/ui/static/raw-ref-deref-without-unsafe.rs +++ b/tests/ui/static/raw-ref-deref-without-unsafe.rs @@ -9,7 +9,6 @@ // (it's fine to create raw refs to places!) the following derefs the ptr before creating its ref! static mut DEREF_BYTE_PTR: *mut u8 = ptr::addr_of_mut!(*BYTE_PTR); //~^ ERROR: use of mutable static -//~| ERROR: dereference of raw pointer fn main() { let _ = unsafe { DEREF_BYTE_PTR }; diff --git a/tests/ui/static/raw-ref-deref-without-unsafe.stderr b/tests/ui/static/raw-ref-deref-without-unsafe.stderr index f034499bbb5..d654d4b6e6b 100644 --- a/tests/ui/static/raw-ref-deref-without-unsafe.stderr +++ b/tests/ui/static/raw-ref-deref-without-unsafe.stderr @@ -14,6 +14,6 @@ LL | static mut DEREF_BYTE_PTR: *mut u8 = ptr::addr_of_mut!(*BYTE_PTR); | = note: mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior -error: aborting due to 2 previous errors +error: aborting due to 1 previous error For more information about this error, try `rustc --explain E0133`. diff --git a/tests/ui/unsafe/place-expr-safe.rs b/tests/ui/unsafe/place-expr-safe.rs new file mode 100644 index 00000000000..2590ea74046 --- /dev/null +++ b/tests/ui/unsafe/place-expr-safe.rs @@ -0,0 +1,14 @@ +//@ check-pass + +fn main() { + let ptr = std::ptr::null_mut::(); + let addr = &raw const *ptr; + + let local = 1; + let ptr = &local as *const i32; + let addr = &raw const *ptr; + + let boxed = Box::new(1); + let ptr = &*boxed as *const i32; + let addr = &raw const *ptr; +} From 367183bc0c64d2835dbfe498d995072c3d426ffe Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sun, 18 Aug 2024 12:33:21 -0400 Subject: [PATCH 2/6] Don't emit null pointer lint for raw ref of null deref --- compiler/rustc_lint/src/builtin.rs | 26 ++++++++++++------- .../rustc_mir_build/src/check_unsafety.rs | 2 -- tests/ui/lint/lint-deref-nullptr.rs | 4 +-- tests/ui/lint/lint-deref-nullptr.stderr | 14 +--------- tests/ui/static/raw-ref-deref-with-unsafe.rs | 9 ++++--- .../ui/static/raw-ref-deref-without-unsafe.rs | 6 ++--- .../raw-ref-deref-without-unsafe.stderr | 8 ------ 7 files changed, 28 insertions(+), 41 deletions(-) diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 8bd9c899a62..0d66ea54bd9 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -2657,8 +2657,8 @@ fn ty_find_init_error<'tcx>( /// /// ### Explanation /// - /// Dereferencing a null pointer causes [undefined behavior] even as a place expression, - /// like `&*(0 as *const i32)` or `addr_of!(*(0 as *const i32))`. + /// Dereferencing a null pointer causes [undefined behavior] if it is accessed + /// (loaded from or stored to). /// /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html pub DEREF_NULLPTR, @@ -2673,14 +2673,14 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) { /// test if expression is a null ptr fn is_null_ptr(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { match &expr.kind { - rustc_hir::ExprKind::Cast(expr, ty) => { - if let rustc_hir::TyKind::Ptr(_) = ty.kind { + hir::ExprKind::Cast(expr, ty) => { + if let hir::TyKind::Ptr(_) = ty.kind { return is_zero(expr) || is_null_ptr(cx, expr); } } // check for call to `core::ptr::null` or `core::ptr::null_mut` - rustc_hir::ExprKind::Call(path, _) => { - if let rustc_hir::ExprKind::Path(ref qpath) = path.kind { + hir::ExprKind::Call(path, _) => { + if let hir::ExprKind::Path(ref qpath) = path.kind { if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() { return matches!( cx.tcx.get_diagnostic_name(def_id), @@ -2697,7 +2697,7 @@ fn is_null_ptr(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { /// test if expression is the literal `0` fn is_zero(expr: &hir::Expr<'_>) -> bool { match &expr.kind { - rustc_hir::ExprKind::Lit(lit) => { + hir::ExprKind::Lit(lit) => { if let LitKind::Int(a, _) = lit.node { return a == 0; } @@ -2707,8 +2707,16 @@ fn is_zero(expr: &hir::Expr<'_>) -> bool { false } - if let rustc_hir::ExprKind::Unary(rustc_hir::UnOp::Deref, expr_deref) = expr.kind { - if is_null_ptr(cx, expr_deref) { + if let hir::ExprKind::Unary(hir::UnOp::Deref, expr_deref) = expr.kind + && is_null_ptr(cx, expr_deref) + { + if let hir::Node::Expr(hir::Expr { + kind: hir::ExprKind::AddrOf(hir::BorrowKind::Raw, ..), + .. + }) = cx.tcx.parent_hir_node(expr.hir_id) + { + // `&raw *NULL` is ok. + } else { cx.emit_span_lint(DEREF_NULLPTR, expr.span, BuiltinDerefNullptr { label: expr.span, }); diff --git a/compiler/rustc_mir_build/src/check_unsafety.rs b/compiler/rustc_mir_build/src/check_unsafety.rs index 0f0286fab29..f3e6301d9d1 100644 --- a/compiler/rustc_mir_build/src/check_unsafety.rs +++ b/compiler/rustc_mir_build/src/check_unsafety.rs @@ -509,8 +509,6 @@ fn visit_expr(&mut self, expr: &'a Expr<'tcx>) { } ExprKind::RawBorrow { arg, .. } => { if let ExprKind::Scope { value: arg, .. } = self.thir[arg].kind - // THIR desugars UNSAFE_STATIC into *UNSAFE_STATIC_REF, where - // UNSAFE_STATIC_REF holds the addr of the UNSAFE_STATIC, so: take two steps && let ExprKind::Deref { arg } = self.thir[arg].kind { // Taking a raw ref to a deref place expr is always safe. diff --git a/tests/ui/lint/lint-deref-nullptr.rs b/tests/ui/lint/lint-deref-nullptr.rs index d052dbd9b64..f83d88309b9 100644 --- a/tests/ui/lint/lint-deref-nullptr.rs +++ b/tests/ui/lint/lint-deref-nullptr.rs @@ -27,9 +27,9 @@ fn f() { let ub = &*ptr::null_mut::(); //~^ ERROR dereferencing a null pointer ptr::addr_of!(*ptr::null::()); - //~^ ERROR dereferencing a null pointer + // ^^ OKAY ptr::addr_of_mut!(*ptr::null_mut::()); - //~^ ERROR dereferencing a null pointer + // ^^ OKAY let offset = ptr::addr_of!((*ptr::null::()).field); //~^ ERROR dereferencing a null pointer } diff --git a/tests/ui/lint/lint-deref-nullptr.stderr b/tests/ui/lint/lint-deref-nullptr.stderr index c6f432e4e42..175431b2994 100644 --- a/tests/ui/lint/lint-deref-nullptr.stderr +++ b/tests/ui/lint/lint-deref-nullptr.stderr @@ -46,23 +46,11 @@ error: dereferencing a null pointer LL | let ub = &*ptr::null_mut::(); | ^^^^^^^^^^^^^^^^^^^^^^^ this code causes undefined behavior when executed -error: dereferencing a null pointer - --> $DIR/lint-deref-nullptr.rs:29:23 - | -LL | ptr::addr_of!(*ptr::null::()); - | ^^^^^^^^^^^^^^^^^^^ this code causes undefined behavior when executed - -error: dereferencing a null pointer - --> $DIR/lint-deref-nullptr.rs:31:27 - | -LL | ptr::addr_of_mut!(*ptr::null_mut::()); - | ^^^^^^^^^^^^^^^^^^^^^^^ this code causes undefined behavior when executed - error: dereferencing a null pointer --> $DIR/lint-deref-nullptr.rs:33:36 | LL | let offset = ptr::addr_of!((*ptr::null::()).field); | ^^^^^^^^^^^^^^^^^^^^^^^^ this code causes undefined behavior when executed -error: aborting due to 10 previous errors +error: aborting due to 8 previous errors diff --git a/tests/ui/static/raw-ref-deref-with-unsafe.rs b/tests/ui/static/raw-ref-deref-with-unsafe.rs index 0974948b3a0..5beed697179 100644 --- a/tests/ui/static/raw-ref-deref-with-unsafe.rs +++ b/tests/ui/static/raw-ref-deref-with-unsafe.rs @@ -1,13 +1,14 @@ //@ check-pass use std::ptr; -// This code should remain unsafe because of the two unsafe operations here, -// even if in a hypothetical future we deem all &raw (const|mut) *ptr exprs safe. - static mut BYTE: u8 = 0; static mut BYTE_PTR: *mut u8 = ptr::addr_of_mut!(BYTE); + +// This code should remain unsafe because reading from a static mut is *always* unsafe. + // An unsafe static's ident is a place expression in its own right, so despite the above being safe -// (it's fine to create raw refs to places!) the following derefs the ptr before creating its ref +// (it's fine to create raw refs to places!) the following *reads* from the static mut place before +// derefing it explicitly with the `*` below. static mut DEREF_BYTE_PTR: *mut u8 = unsafe { ptr::addr_of_mut!(*BYTE_PTR) }; fn main() { diff --git a/tests/ui/static/raw-ref-deref-without-unsafe.rs b/tests/ui/static/raw-ref-deref-without-unsafe.rs index 7146c564e91..97d08c815bf 100644 --- a/tests/ui/static/raw-ref-deref-without-unsafe.rs +++ b/tests/ui/static/raw-ref-deref-without-unsafe.rs @@ -1,10 +1,10 @@ use std::ptr; -// This code should remain unsafe because of the two unsafe operations here, -// even if in a hypothetical future we deem all &raw (const|mut) *ptr exprs safe. - static mut BYTE: u8 = 0; static mut BYTE_PTR: *mut u8 = ptr::addr_of_mut!(BYTE); + +// This code should remain unsafe because reading from a static mut is *always* unsafe. + // An unsafe static's ident is a place expression in its own right, so despite the above being safe // (it's fine to create raw refs to places!) the following derefs the ptr before creating its ref! static mut DEREF_BYTE_PTR: *mut u8 = ptr::addr_of_mut!(*BYTE_PTR); diff --git a/tests/ui/static/raw-ref-deref-without-unsafe.stderr b/tests/ui/static/raw-ref-deref-without-unsafe.stderr index d654d4b6e6b..b9c294e5c1f 100644 --- a/tests/ui/static/raw-ref-deref-without-unsafe.stderr +++ b/tests/ui/static/raw-ref-deref-without-unsafe.stderr @@ -1,11 +1,3 @@ -error[E0133]: dereference of raw pointer is unsafe and requires unsafe function or block - --> $DIR/raw-ref-deref-without-unsafe.rs:10:56 - | -LL | static mut DEREF_BYTE_PTR: *mut u8 = ptr::addr_of_mut!(*BYTE_PTR); - | ^^^^^^^^^ dereference of raw pointer - | - = note: raw pointers may be null, dangling or unaligned; they can violate aliasing rules and cause data races: all of these are undefined behavior - error[E0133]: use of mutable static is unsafe and requires unsafe function or block --> $DIR/raw-ref-deref-without-unsafe.rs:10:57 | From a53655a023cee5dee1873bd3dfc805d7c460fbeb Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Wed, 23 Oct 2024 19:15:23 -0400 Subject: [PATCH 3/6] rustdoc: adjust spacing and typography in header --- src/librustdoc/html/render/mod.rs | 4 ++-- src/librustdoc/html/static/css/rustdoc.css | 10 ++++++++-- src/librustdoc/html/templates/print_item.html | 2 +- tests/rustdoc-gui/item-info.goml | 2 +- tests/rustdoc-gui/scrape-examples-layout.goml | 8 ++++---- tests/rustdoc-gui/sidebar-source-code-display.goml | 2 +- tests/rustdoc-gui/source-anchor-scroll.goml | 8 ++++---- tests/rustdoc-gui/source-code-page.goml | 6 +++--- tests/rustdoc/anchors.no_const_anchor.html | 2 +- tests/rustdoc/anchors.no_const_anchor2.html | 2 +- tests/rustdoc/anchors.no_method_anchor.html | 2 +- tests/rustdoc/anchors.no_trait_method_anchor.html | 2 +- tests/rustdoc/anchors.no_tymethod_anchor.html | 2 +- tests/rustdoc/anchors.no_type_anchor.html | 2 +- tests/rustdoc/anchors.no_type_anchor2.html | 2 +- tests/rustdoc/assoc-type-source-link.rs | 4 ++-- tests/rustdoc/ensure-src-link.rs | 2 +- tests/rustdoc/external-macro-src.rs | 4 ++-- tests/rustdoc/source-version-separator.rs | 10 +++++----- tests/rustdoc/src-link-external-macro-26606.rs | 2 +- tests/rustdoc/src-links-auto-impls.rs | 6 +++--- tests/rustdoc/thread-local-src.rs | 4 ++-- tests/rustdoc/trait-src-link.rs | 12 ++++++------ 23 files changed, 53 insertions(+), 47 deletions(-) diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 399730a01c8..ce96d1d0a95 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -2010,9 +2010,9 @@ fn render_rightside(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, render ); if let Some(link) = src_href { if has_stability { - write!(rightside, " · source") + write!(rightside, " · Source") } else { - write!(rightside, "source") + write!(rightside, "Source") } } if has_stability && has_src_ref { diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index df9776ff5f8..db857745295 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -185,7 +185,7 @@ h1, h2, h3, h4 { grid-template-columns: minmax(105px, 1fr) minmax(0, max-content); grid-template-rows: minmax(25px, min-content) min-content min-content; padding-bottom: 6px; - margin-bottom: 11px; + margin-bottom: 15px; } .rustdoc-breadcrumbs { grid-area: main-heading-breadcrumbs; @@ -1004,6 +1004,7 @@ nav.sub { display: flex; height: 34px; flex-grow: 1; + margin-bottom: 4px; } .src nav.sub { margin: 0 0 -10px 0; @@ -2253,7 +2254,12 @@ in src-script.js and main.js /* We don't display this button on mobile devices. */ #copy-path { - display: none; + /* display: none; avoided as a layout hack. + When there's one line, we get an effective line-height of 34px, + because that's how big the image is, but if the header wraps, + they're packed more tightly than that. */ + width: 0; + visibility: hidden; } /* Text label takes up too much space at this size. */ diff --git a/src/librustdoc/html/templates/print_item.html b/src/librustdoc/html/templates/print_item.html index f60720b67c6..9fd575b2751 100644 --- a/src/librustdoc/html/templates/print_item.html +++ b/src/librustdoc/html/templates/print_item.html @@ -26,7 +26,7 @@ {% match src_href %} {% when Some with (href) %} {% if !stability_since_raw.is_empty() +%} · {%+ endif %} - source {#+ #} + Source {#+ #} {% else %} {% endmatch %} {# #} diff --git a/tests/rustdoc-gui/item-info.goml b/tests/rustdoc-gui/item-info.goml index c8aa7b31cad..1636e149692 100644 --- a/tests/rustdoc-gui/item-info.goml +++ b/tests/rustdoc-gui/item-info.goml @@ -20,7 +20,7 @@ store-position: ( {"x": second_line_x, "y": second_line_y}, ) assert: |first_line_x| != |second_line_x| && |first_line_x| == 516 && |second_line_x| == 272 -assert: |first_line_y| != |second_line_y| && |first_line_y| == 714 && |second_line_y| == 737 +assert: |first_line_y| != |second_line_y| && |first_line_y| == 718 && |second_line_y| == 741 // Now we ensure that they're not rendered on the same line. set-window-size: (1100, 800) diff --git a/tests/rustdoc-gui/scrape-examples-layout.goml b/tests/rustdoc-gui/scrape-examples-layout.goml index 96c78bbfe8b..5187ac486b0 100644 --- a/tests/rustdoc-gui/scrape-examples-layout.goml +++ b/tests/rustdoc-gui/scrape-examples-layout.goml @@ -80,8 +80,8 @@ click: ".scraped-example .button-holder .expand" store-value: (offset_y, 4) // First with desktop -assert-position: (".scraped-example", {"y": 252}) -assert-position: (".scraped-example .prev", {"y": 252 + |offset_y|}) +assert-position: (".scraped-example", {"y": 256}) +assert-position: (".scraped-example .prev", {"y": 256 + |offset_y|}) // Gradient background should be at the top of the code block. assert-css: (".scraped-example .example-wrap::before", {"top": "0px"}) @@ -90,8 +90,8 @@ assert-css: (".scraped-example .example-wrap::after", {"bottom": "0px"}) // Then with mobile set-window-size: (600, 600) store-size: (".scraped-example .scraped-example-title", {"height": title_height}) -assert-position: (".scraped-example", {"y": 287}) -assert-position: (".scraped-example .prev", {"y": 287 + |offset_y| + |title_height|}) +assert-position: (".scraped-example", {"y": 291}) +assert-position: (".scraped-example .prev", {"y": 291 + |offset_y| + |title_height|}) define-function: ( "check_title_and_code_position", diff --git a/tests/rustdoc-gui/sidebar-source-code-display.goml b/tests/rustdoc-gui/sidebar-source-code-display.goml index 742453c173b..1e77bcc2273 100644 --- a/tests/rustdoc-gui/sidebar-source-code-display.goml +++ b/tests/rustdoc-gui/sidebar-source-code-display.goml @@ -141,7 +141,7 @@ click: "#sidebar-button" wait-for-css: (".src .sidebar > *", {"visibility": "hidden"}) // We scroll to line 117 to change the scroll position. scroll-to: '//*[@id="117"]' -store-value: (y_offset, "2570") +store-value: (y_offset, "2578") assert-window-property: {"pageYOffset": |y_offset|} // Expanding the sidebar... click: "#sidebar-button" diff --git a/tests/rustdoc-gui/source-anchor-scroll.goml b/tests/rustdoc-gui/source-anchor-scroll.goml index f8794645705..4ad65bbbd61 100644 --- a/tests/rustdoc-gui/source-anchor-scroll.goml +++ b/tests/rustdoc-gui/source-anchor-scroll.goml @@ -8,13 +8,13 @@ set-window-size: (600, 800) assert-property: ("html", {"scrollTop": "0"}) click: '//a[text() = "barbar" and @href="#5-7"]' -assert-property: ("html", {"scrollTop": "200"}) +assert-property: ("html", {"scrollTop": "208"}) click: '//a[text() = "bar" and @href="#28-36"]' -assert-property: ("html", {"scrollTop": "231"}) +assert-property: ("html", {"scrollTop": "239"}) click: '//a[normalize-space() = "sub_fn" and @href="#2-4"]' -assert-property: ("html", {"scrollTop": "128"}) +assert-property: ("html", {"scrollTop": "136"}) // We now check that clicking on lines doesn't change the scroll // Extra information: the "sub_fn" function header is on line 1. click: '//*[@id="6"]' -assert-property: ("html", {"scrollTop": "128"}) +assert-property: ("html", {"scrollTop": "136"}) diff --git a/tests/rustdoc-gui/source-code-page.goml b/tests/rustdoc-gui/source-code-page.goml index 095354c2f4c..afb19462521 100644 --- a/tests/rustdoc-gui/source-code-page.goml +++ b/tests/rustdoc-gui/source-code-page.goml @@ -89,7 +89,7 @@ assert-css: (".src-line-numbers", {"text-align": "right"}) // do anything (and certainly not add a `#NaN` to the URL!). go-to: "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html" // We use this assert-position to know where we will click. -assert-position: ("//*[@id='1']", {"x": 88, "y": 163}) +assert-position: ("//*[@id='1']", {"x": 88, "y": 171}) // We click on the left of the "1" anchor but still in the "src-line-number" `
`.
 click: (163, 77)
 assert-document-property: ({"URL": "/lib.rs.html"}, ENDS_WITH)
@@ -165,7 +165,7 @@ assert-css: ("nav.sub", {"flex-direction": "row"})
 // offsetTop[nav.sub form] = offsetTop[#main-content] - offsetHeight[nav.sub form] - offsetTop[nav.sub form]
 assert-position: ("nav.sub form", {"y": 15})
 assert-property: ("nav.sub form", {"offsetHeight": 34})
-assert-position: ("h1", {"y": 64})
+assert-position: ("h1", {"y": 68})
 // 15 = 64 - 34 - 15
 
 // Now do the same check on moderately-sized, tablet mobile.
@@ -173,7 +173,7 @@ set-window-size: (700, 700)
 assert-css: ("nav.sub", {"flex-direction": "row"})
 assert-position: ("nav.sub form", {"y": 8})
 assert-property: ("nav.sub form", {"offsetHeight": 34})
-assert-position: ("h1", {"y": 50})
+assert-position: ("h1", {"y": 54})
 // 8 = 50 - 34 - 8
 
 // Check the sidebar directory entries have a marker and spacing (tablet).
diff --git a/tests/rustdoc/anchors.no_const_anchor.html b/tests/rustdoc/anchors.no_const_anchor.html
index 06673d87406..07a7507fa2e 100644
--- a/tests/rustdoc/anchors.no_const_anchor.html
+++ b/tests/rustdoc/anchors.no_const_anchor.html
@@ -1 +1 @@
-
source

const YOLO: u32

\ No newline at end of file +
Source

const YOLO: u32

\ No newline at end of file diff --git a/tests/rustdoc/anchors.no_const_anchor2.html b/tests/rustdoc/anchors.no_const_anchor2.html index 73c3d0a807b..091dac3e4b2 100644 --- a/tests/rustdoc/anchors.no_const_anchor2.html +++ b/tests/rustdoc/anchors.no_const_anchor2.html @@ -1 +1 @@ -
source

pub const X: i32 = 0i32

\ No newline at end of file +
Source

pub const X: i32 = 0i32

\ No newline at end of file diff --git a/tests/rustdoc/anchors.no_method_anchor.html b/tests/rustdoc/anchors.no_method_anchor.html index e8b61caa1c1..89f9898624c 100644 --- a/tests/rustdoc/anchors.no_method_anchor.html +++ b/tests/rustdoc/anchors.no_method_anchor.html @@ -1 +1 @@ -
source

pub fn new() -> Self

\ No newline at end of file +
Source

pub fn new() -> Self

\ No newline at end of file diff --git a/tests/rustdoc/anchors.no_trait_method_anchor.html b/tests/rustdoc/anchors.no_trait_method_anchor.html index abdb17c27dc..51656a3e58f 100644 --- a/tests/rustdoc/anchors.no_trait_method_anchor.html +++ b/tests/rustdoc/anchors.no_trait_method_anchor.html @@ -1 +1 @@ -
source

fn bar()

\ No newline at end of file +
Source

fn bar()

\ No newline at end of file diff --git a/tests/rustdoc/anchors.no_tymethod_anchor.html b/tests/rustdoc/anchors.no_tymethod_anchor.html index 23f4277c5b5..49ee624bdbc 100644 --- a/tests/rustdoc/anchors.no_tymethod_anchor.html +++ b/tests/rustdoc/anchors.no_tymethod_anchor.html @@ -1 +1 @@ -
source

fn foo()

\ No newline at end of file +
Source

fn foo()

\ No newline at end of file diff --git a/tests/rustdoc/anchors.no_type_anchor.html b/tests/rustdoc/anchors.no_type_anchor.html index 62b9295508f..c5ac3c93818 100644 --- a/tests/rustdoc/anchors.no_type_anchor.html +++ b/tests/rustdoc/anchors.no_type_anchor.html @@ -1 +1 @@ -
source

type T

\ No newline at end of file +
Source

type T

\ No newline at end of file diff --git a/tests/rustdoc/anchors.no_type_anchor2.html b/tests/rustdoc/anchors.no_type_anchor2.html index 9127104ded4..14dd31d87b6 100644 --- a/tests/rustdoc/anchors.no_type_anchor2.html +++ b/tests/rustdoc/anchors.no_type_anchor2.html @@ -1 +1 @@ -
source

pub type Y = u32

\ No newline at end of file +
Source

pub type Y = u32

\ No newline at end of file diff --git a/tests/rustdoc/assoc-type-source-link.rs b/tests/rustdoc/assoc-type-source-link.rs index 34b156b9649..a955a67a457 100644 --- a/tests/rustdoc/assoc-type-source-link.rs +++ b/tests/rustdoc/assoc-type-source-link.rs @@ -8,7 +8,7 @@ pub struct Bar; impl Bar { - //@ has - '//*[@id="implementations-list"]//*[@id="associatedtype.Y"]/a' 'source' + //@ has - '//*[@id="implementations-list"]//*[@id="associatedtype.Y"]/a' 'Source' //@ has - '//*[@id="implementations-list"]//*[@id="associatedtype.Y"]/a/@href' \ // '../src/foo/assoc-type-source-link.rs.html#14' pub type Y = u8; @@ -19,7 +19,7 @@ pub trait Foo { } impl Foo for Bar { - //@ has - '//*[@id="trait-implementations-list"]//*[@id="associatedtype.Z"]/a' 'source' + //@ has - '//*[@id="trait-implementations-list"]//*[@id="associatedtype.Z"]/a' 'Source' //@ has - '//*[@id="trait-implementations-list"]//*[@id="associatedtype.Z"]/a/@href' \ // '../src/foo/assoc-type-source-link.rs.html#25' type Z = u8; diff --git a/tests/rustdoc/ensure-src-link.rs b/tests/rustdoc/ensure-src-link.rs index 4156fdcba59..f70902b1756 100644 --- a/tests/rustdoc/ensure-src-link.rs +++ b/tests/rustdoc/ensure-src-link.rs @@ -2,5 +2,5 @@ // This test ensures that the [src] link is present on traits items. -//@ has foo/trait.Iterator.html '//*[@id="method.zip"]//a[@class="src"]' "source" +//@ has foo/trait.Iterator.html '//*[@id="method.zip"]//a[@class="src"]' "Source" pub use std::iter::Iterator; diff --git a/tests/rustdoc/external-macro-src.rs b/tests/rustdoc/external-macro-src.rs index f723af57fad..998687d93bd 100644 --- a/tests/rustdoc/external-macro-src.rs +++ b/tests/rustdoc/external-macro-src.rs @@ -5,8 +5,8 @@ #[macro_use] extern crate external_macro_src; -//@ has foo/index.html '//a[@href="../src/foo/external-macro-src.rs.html#3-12"]' 'source' +//@ has foo/index.html '//a[@href="../src/foo/external-macro-src.rs.html#3-12"]' 'Source' //@ has foo/struct.Foo.html -//@ has - '//a[@href="../src/foo/external-macro-src.rs.html#12"]' 'source' +//@ has - '//a[@href="../src/foo/external-macro-src.rs.html#12"]' 'Source' make_foo!(); diff --git a/tests/rustdoc/source-version-separator.rs b/tests/rustdoc/source-version-separator.rs index 9709bbe3c71..78b9d364d21 100644 --- a/tests/rustdoc/source-version-separator.rs +++ b/tests/rustdoc/source-version-separator.rs @@ -3,23 +3,23 @@ #![feature(staged_api)] //@ has foo/trait.Bar.html -//@ has - '//div[@class="main-heading"]/*[@class="sub-heading"]' '1.0.0 · source' +//@ has - '//div[@class="main-heading"]/*[@class="sub-heading"]' '1.0.0 · Source' #[stable(feature = "bar", since = "1.0")] pub trait Bar { - //@ has - '//*[@id="tymethod.foo"]/*[@class="rightside"]' '3.0.0 · source' + //@ has - '//*[@id="tymethod.foo"]/*[@class="rightside"]' '3.0.0 · Source' #[stable(feature = "foobar", since = "3.0")] fn foo(); } -//@ has - '//div[@id="implementors-list"]//*[@class="rightside"]' '4.0.0 · source' +//@ has - '//div[@id="implementors-list"]//*[@class="rightside"]' '4.0.0 · Source' //@ has foo/struct.Foo.html -//@ has - '//div[@class="main-heading"]/*[@class="sub-heading"]' '1.0.0 · source' +//@ has - '//div[@class="main-heading"]/*[@class="sub-heading"]' '1.0.0 · Source' #[stable(feature = "baz", since = "1.0")] pub struct Foo; impl Foo { - //@ has - '//*[@id="method.foofoo"]/*[@class="rightside"]' '3.0.0 · source' + //@ has - '//*[@id="method.foofoo"]/*[@class="rightside"]' '3.0.0 · Source' #[stable(feature = "foobar", since = "3.0")] pub fn foofoo() {} } diff --git a/tests/rustdoc/src-link-external-macro-26606.rs b/tests/rustdoc/src-link-external-macro-26606.rs index b5662be9b2d..0ce829f06f5 100644 --- a/tests/rustdoc/src-link-external-macro-26606.rs +++ b/tests/rustdoc/src-link-external-macro-26606.rs @@ -10,5 +10,5 @@ extern crate issue_26606_macro; //@ has issue_26606/constant.FOO.html -//@ has - '//a[@href="../src/issue_26606/src-link-external-macro-26606.rs.html#14"]' 'source' +//@ has - '//a[@href="../src/issue_26606/src-link-external-macro-26606.rs.html#14"]' 'Source' make_item!(FOO); diff --git a/tests/rustdoc/src-links-auto-impls.rs b/tests/rustdoc/src-links-auto-impls.rs index dd07f85eee7..5a777f59b7e 100644 --- a/tests/rustdoc/src-links-auto-impls.rs +++ b/tests/rustdoc/src-links-auto-impls.rs @@ -2,11 +2,11 @@ //@ has foo/struct.Unsized.html //@ has - '//*[@id="impl-Sized-for-Unsized"]/h3[@class="code-header"]' 'impl !Sized for Unsized' -//@ !has - '//*[@id="impl-Sized-for-Unsized"]//a[@class="src"]' 'source' +//@ !has - '//*[@id="impl-Sized-for-Unsized"]//a[@class="src"]' 'Source' //@ has - '//*[@id="impl-Sync-for-Unsized"]/h3[@class="code-header"]' 'impl Sync for Unsized' -//@ !has - '//*[@id="impl-Sync-for-Unsized"]//a[@class="src"]' 'source' +//@ !has - '//*[@id="impl-Sync-for-Unsized"]//a[@class="src"]' 'Source' //@ has - '//*[@id="impl-Any-for-T"]/h3[@class="code-header"]' 'impl Any for T' -//@ has - '//*[@id="impl-Any-for-T"]//a[@class="src rightside"]' 'source' +//@ has - '//*[@id="impl-Any-for-T"]//a[@class="src rightside"]' 'Source' pub struct Unsized { data: [u8], } diff --git a/tests/rustdoc/thread-local-src.rs b/tests/rustdoc/thread-local-src.rs index b23a9a48654..16509e8514d 100644 --- a/tests/rustdoc/thread-local-src.rs +++ b/tests/rustdoc/thread-local-src.rs @@ -1,6 +1,6 @@ #![crate_name = "foo"] -//@ has foo/index.html '//a[@href="../src/foo/thread-local-src.rs.html#1-6"]' 'source' +//@ has foo/index.html '//a[@href="../src/foo/thread-local-src.rs.html#1-6"]' 'Source' -//@ has foo/constant.FOO.html '//a[@href="../src/foo/thread-local-src.rs.html#6"]' 'source' +//@ has foo/constant.FOO.html '//a[@href="../src/foo/thread-local-src.rs.html#6"]' 'Source' thread_local!(pub static FOO: bool = false); diff --git a/tests/rustdoc/trait-src-link.rs b/tests/rustdoc/trait-src-link.rs index 7c3afb7d7d3..7bf883d52da 100644 --- a/tests/rustdoc/trait-src-link.rs +++ b/tests/rustdoc/trait-src-link.rs @@ -1,26 +1,26 @@ #![crate_name = "quix"] pub trait Foo { - //@ has quix/trait.Foo.html '//a[@href="../src/quix/trait-src-link.rs.html#4"]' 'source' + //@ has quix/trait.Foo.html '//a[@href="../src/quix/trait-src-link.rs.html#4"]' 'Source' fn required(); - //@ has quix/trait.Foo.html '//a[@href="../src/quix/trait-src-link.rs.html#7"]' 'source' + //@ has quix/trait.Foo.html '//a[@href="../src/quix/trait-src-link.rs.html#7"]' 'Source' fn provided() {} } pub struct Bar; impl Foo for Bar { - //@ has quix/struct.Bar.html '//a[@href="../src/quix/trait-src-link.rs.html#14"]' 'source' + //@ has quix/struct.Bar.html '//a[@href="../src/quix/trait-src-link.rs.html#14"]' 'Source' fn required() {} - //@ has quix/struct.Bar.html '//a[@href="../src/quix/trait-src-link.rs.html#7"]' 'source' + //@ has quix/struct.Bar.html '//a[@href="../src/quix/trait-src-link.rs.html#7"]' 'Source' } pub struct Baz; impl Foo for Baz { - //@ has quix/struct.Baz.html '//a[@href="../src/quix/trait-src-link.rs.html#22"]' 'source' + //@ has quix/struct.Baz.html '//a[@href="../src/quix/trait-src-link.rs.html#22"]' 'Source' fn required() {} - //@ has quix/struct.Baz.html '//a[@href="../src/quix/trait-src-link.rs.html#25"]' 'source' + //@ has quix/struct.Baz.html '//a[@href="../src/quix/trait-src-link.rs.html#25"]' 'Source' fn provided() {} } From d8dc31fd3d2a03bc4b01974328c92d2ffb2f4fea Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 23 Oct 2024 23:49:46 +0000 Subject: [PATCH 4/6] Consider param-env candidates even if they have errors --- .../src/traits/select/candidate_assembly.rs | 1 - tests/crashes/110630.rs | 28 ---------------- tests/crashes/115808.rs | 27 ---------------- tests/crashes/121052.rs | 32 ------------------- .../in-trait/unconstrained-impl-region.rs | 1 - .../in-trait/unconstrained-impl-region.stderr | 17 ++-------- .../error-reporting/apit-with-bad-path.rs | 10 ++++++ .../error-reporting/apit-with-bad-path.stderr | 14 ++++++++ .../where-clause-with-bad-path.rs | 10 ++++++ .../where-clause-with-bad-path.stderr | 14 ++++++++ 10 files changed, 50 insertions(+), 104 deletions(-) delete mode 100644 tests/crashes/110630.rs delete mode 100644 tests/crashes/115808.rs delete mode 100644 tests/crashes/121052.rs create mode 100644 tests/ui/traits/error-reporting/apit-with-bad-path.rs create mode 100644 tests/ui/traits/error-reporting/apit-with-bad-path.stderr create mode 100644 tests/ui/traits/error-reporting/where-clause-with-bad-path.rs create mode 100644 tests/ui/traits/error-reporting/where-clause-with-bad-path.stderr diff --git a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs index eea3867190d..c0122d3d552 100644 --- a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs +++ b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs @@ -244,7 +244,6 @@ fn assemble_candidates_from_caller_bounds<'o>( .param_env .caller_bounds() .iter() - .filter(|p| !p.references_error()) .filter_map(|p| p.as_trait_clause()) // Micro-optimization: filter out predicates relating to different traits. .filter(|p| p.def_id() == stack.obligation.predicate.def_id()) diff --git a/tests/crashes/110630.rs b/tests/crashes/110630.rs deleted file mode 100644 index f17f6f0781f..00000000000 --- a/tests/crashes/110630.rs +++ /dev/null @@ -1,28 +0,0 @@ -//@ known-bug: #110630 - -#![feature(generic_const_exprs)] - -use std::ops::Mul; - -pub trait Indices { - const NUM_ELEMS: usize = I::NUM_ELEMS * N; -} - -pub trait Concat { - type Output; -} - -pub struct Tensor, const N: usize> -where - [u8; I::NUM_ELEMS]: Sized, {} - -impl, J: Indices, const N: usize> Mul> for Tensor -where - I: Concat, - >::Output: Indices, - [u8; I::NUM_ELEMS]: Sized, - [u8; J::NUM_ELEMS]: Sized, - [u8; >::Output::NUM_ELEMS]: Sized, -{ - type Output = Tensor<>::Output, N>; -} diff --git a/tests/crashes/115808.rs b/tests/crashes/115808.rs deleted file mode 100644 index 79196ac9c65..00000000000 --- a/tests/crashes/115808.rs +++ /dev/null @@ -1,27 +0,0 @@ -//@ known-bug: #115808 -#![feature(generic_const_exprs)] - -use std::ops::Mul; - -pub trait Indices { - const NUM_ELEMS: usize; -} - -pub trait Concat { - type Output; -} - -pub struct Tensor, const N: usize> -where - [u8; I::NUM_ELEMS]: Sized, {} - -impl, J: Indices, const N: usize> Mul> for Tensor -where - I: Concat, - >::Output: Indices, - [u8; I::NUM_ELEMS]: Sized, - [u8; J::NUM_ELEMS]: Sized, - [u8; >::Output::NUM_ELEMS]: Sized, -{ - type Output = Tensor<>::Output, N>; -} diff --git a/tests/crashes/121052.rs b/tests/crashes/121052.rs deleted file mode 100644 index 5d16b06db23..00000000000 --- a/tests/crashes/121052.rs +++ /dev/null @@ -1,32 +0,0 @@ -//@ known-bug: #121052 -#![feature(generic_const_exprs, with_negative_coherence)] - -use std::ops::Mul; - -pub trait Indices { - const NUM_ELEMS: usize; -} - -impl, J: Indices, const N: usize> Mul for Tensor -where - I: Concat, - >::Output: Indices, - [u8; I::NUM_ELEMS]: Sized, - [u8; J::NUM_ELEMS]: Sized, - [u8; >::Output::NUM_ELEMS]: Sized, -{ -} - -pub trait Concat {} - -pub struct Tensor, const N: usize> {} - -impl, J: Indices, const N: usize> Mul for Tensor -where - I: Concat, - >::Output: Indices, - [u8; I::NUM_ELEMS]: Sized, - [u8; J::NUM_ELEMS]: Sized, - [u8; >::Output::NUM_ELEMS]: Sized, -{ -} diff --git a/tests/ui/async-await/in-trait/unconstrained-impl-region.rs b/tests/ui/async-await/in-trait/unconstrained-impl-region.rs index 95ba1f3f277..9382c232364 100644 --- a/tests/ui/async-await/in-trait/unconstrained-impl-region.rs +++ b/tests/ui/async-await/in-trait/unconstrained-impl-region.rs @@ -14,7 +14,6 @@ impl<'a> Actor for () { //~^ ERROR the lifetime parameter `'a` is not constrained by the impl trait, self type, or predicates type Message = &'a (); async fn on_mount(self, _: impl Inbox<&'a ()>) {} - //~^ ERROR the trait bound `impl Inbox<&'a ()>: Inbox<&'a ()>` is not satisfied } fn main() {} diff --git a/tests/ui/async-await/in-trait/unconstrained-impl-region.stderr b/tests/ui/async-await/in-trait/unconstrained-impl-region.stderr index 80dc5fdc747..ef7e4ef0eb8 100644 --- a/tests/ui/async-await/in-trait/unconstrained-impl-region.stderr +++ b/tests/ui/async-await/in-trait/unconstrained-impl-region.stderr @@ -1,22 +1,9 @@ -error[E0277]: the trait bound `impl Inbox<&'a ()>: Inbox<&'a ()>` is not satisfied - --> $DIR/unconstrained-impl-region.rs:16:5 - | -LL | async fn on_mount(self, _: impl Inbox<&'a ()>) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Inbox<&'a ()>` is not implemented for `impl Inbox<&'a ()>` - | -note: required by a bound in `<() as Actor>::on_mount` - --> $DIR/unconstrained-impl-region.rs:16:37 - | -LL | async fn on_mount(self, _: impl Inbox<&'a ()>) {} - | ^^^^^^^^^^^^^ required by this bound in `<() as Actor>::on_mount` - error[E0207]: the lifetime parameter `'a` is not constrained by the impl trait, self type, or predicates --> $DIR/unconstrained-impl-region.rs:13:6 | LL | impl<'a> Actor for () { | ^^ unconstrained lifetime parameter -error: aborting due to 2 previous errors +error: aborting due to 1 previous error -Some errors have detailed explanations: E0207, E0277. -For more information about an error, try `rustc --explain E0207`. +For more information about this error, try `rustc --explain E0207`. diff --git a/tests/ui/traits/error-reporting/apit-with-bad-path.rs b/tests/ui/traits/error-reporting/apit-with-bad-path.rs new file mode 100644 index 00000000000..57184b9d0a9 --- /dev/null +++ b/tests/ui/traits/error-reporting/apit-with-bad-path.rs @@ -0,0 +1,10 @@ +// Ensure that we don't emit an E0270 for "`impl AsRef: AsRef` not satisfied". + +fn foo(filename: impl AsRef) { + //~^ ERROR cannot find type `Path` in this scope + std::fs::write(filename, "hello").unwrap(); +} + +fn main() { + foo("/tmp/hello"); +} diff --git a/tests/ui/traits/error-reporting/apit-with-bad-path.stderr b/tests/ui/traits/error-reporting/apit-with-bad-path.stderr new file mode 100644 index 00000000000..19bd5e78b47 --- /dev/null +++ b/tests/ui/traits/error-reporting/apit-with-bad-path.stderr @@ -0,0 +1,14 @@ +error[E0412]: cannot find type `Path` in this scope + --> $DIR/apit-with-bad-path.rs:3:29 + | +LL | fn foo(filename: impl AsRef) { + | ^^^^ not found in this scope + | +help: consider importing this struct + | +LL + use std::path::Path; + | + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0412`. diff --git a/tests/ui/traits/error-reporting/where-clause-with-bad-path.rs b/tests/ui/traits/error-reporting/where-clause-with-bad-path.rs new file mode 100644 index 00000000000..cbe14887de4 --- /dev/null +++ b/tests/ui/traits/error-reporting/where-clause-with-bad-path.rs @@ -0,0 +1,10 @@ +// Ensure that we don't emit an E0270 for "`impl AsRef: AsRef` not satisfied". + +fn foo>(filename: T) { + //~^ ERROR cannot find type `Path` in this scope + std::fs::write(filename, "hello").unwrap(); +} + +fn main() { + foo("/tmp/hello"); +} diff --git a/tests/ui/traits/error-reporting/where-clause-with-bad-path.stderr b/tests/ui/traits/error-reporting/where-clause-with-bad-path.stderr new file mode 100644 index 00000000000..1137178f611 --- /dev/null +++ b/tests/ui/traits/error-reporting/where-clause-with-bad-path.stderr @@ -0,0 +1,14 @@ +error[E0412]: cannot find type `Path` in this scope + --> $DIR/where-clause-with-bad-path.rs:3:17 + | +LL | fn foo>(filename: T) { + | ^^^^ not found in this scope + | +help: consider importing this struct + | +LL + use std::path::Path; + | + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0412`. From 3dfc35214509e969cfeec76380af427881a36aa9 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Thu, 24 Oct 2024 16:59:59 +1100 Subject: [PATCH 5/6] Replace an FTP link in comments with an equivalent HTTPS link --- compiler/rustc_data_structures/src/graph/dominators/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_data_structures/src/graph/dominators/mod.rs b/compiler/rustc_data_structures/src/graph/dominators/mod.rs index 85b030c1a48..53ff67f60e3 100644 --- a/compiler/rustc_data_structures/src/graph/dominators/mod.rs +++ b/compiler/rustc_data_structures/src/graph/dominators/mod.rs @@ -2,7 +2,7 @@ //! //! Algorithm based on Loukas Georgiadis, //! "Linear-Time Algorithms for Dominators and Related Problems", -//! +//! //! //! Additionally useful is the original Lengauer-Tarjan paper on this subject, //! "A Fast Algorithm for Finding Dominators in a Flowgraph" From 282f291b7de339951f44e4d0ba6d64571876099a Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 24 Oct 2024 08:15:28 +0200 Subject: [PATCH 6/6] rustc_feature::Features: explain what that 'Option' is about --- compiler/rustc_feature/src/unstable.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 8f4c208f1fb..8182eb1e973 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -44,6 +44,8 @@ pub struct Features { } impl Features { + /// `since` should be set for stable features that are nevertheless enabled with a `#[feature]` + /// attribute, indicating since when they are stable. pub fn set_enabled_lang_feature(&mut self, name: Symbol, span: Span, since: Option) { self.enabled_lang_features.push((name, span, since)); self.enabled_features.insert(name); @@ -54,6 +56,10 @@ pub fn set_enabled_lib_feature(&mut self, name: Symbol, span: Span) { self.enabled_features.insert(name); } + /// Returns a list of triples with: + /// - feature gate name + /// - the span of the `#[feature]` attribute + /// - (for already stable features) the version since which it is stable pub fn enabled_lang_features(&self) -> &Vec<(Symbol, Span, Option)> { &self.enabled_lang_features }