Skip to content

Commit 089584d

Browse files
committed
Auto merge of rust-lang#101736 - GuillaumeGomez:rollup-f71kjdb, r=GuillaumeGomez
Rollup of 8 pull requests Successful merges: - rust-lang#100185 (Fix `ReErased` leaking into typeck due to `typeof(...)` recovery) - rust-lang#100291 (constify some `CStr` methods) - rust-lang#101677 (Add test for rust-lang#101211) - rust-lang#101723 (Impove diagnostic for `.await`ing non-futures) - rust-lang#101724 (Allow unauthenticated users to add the `const-hack` label) - rust-lang#101731 (rustdoc: improve rustdoc HTML suggestions handling of nested generics) - rust-lang#101732 (Feature gate the `rustdoc::missing_doc_code_examples` lint) - rust-lang#101735 (rustdoc: fix treatment of backslash-escaped HTML) Failed merges: r? `@ghost` `@rustbot` modify labels: rollup
2 parents fa6ee93 + 031a2f8 commit 089584d

40 files changed

+592
-87
lines changed

compiler/rustc_ast_lowering/src/expr.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -146,13 +146,19 @@ impl<'hir> LoweringContext<'_, 'hir> {
146146
|this| this.with_new_scopes(|this| this.lower_block_expr(block)),
147147
),
148148
ExprKind::Await(ref expr) => {
149-
let span = if expr.span.hi() < e.span.hi() {
150-
expr.span.shrink_to_hi().with_hi(e.span.hi())
149+
let dot_await_span = if expr.span.hi() < e.span.hi() {
150+
let span_with_whitespace = self
151+
.tcx
152+
.sess
153+
.source_map()
154+
.span_extend_while(expr.span, char::is_whitespace)
155+
.unwrap_or(expr.span);
156+
span_with_whitespace.shrink_to_hi().with_hi(e.span.hi())
151157
} else {
152158
// this is a recovered `await expr`
153159
e.span
154160
};
155-
self.lower_expr_await(span, expr)
161+
self.lower_expr_await(dot_await_span, expr)
156162
}
157163
ExprKind::Closure(
158164
ref binder,

compiler/rustc_feature/src/active.rs

+2
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ declare_features! (
221221
(active, rustc_private, "1.0.0", Some(27812), None),
222222
/// Allows using internal rustdoc features like `doc(primitive)` or `doc(keyword)`.
223223
(active, rustdoc_internals, "1.58.0", Some(90418), None),
224+
/// Allows using the `rustdoc::missing_doc_code_examples` lint
225+
(active, rustdoc_missing_doc_code_examples, "1.31.0", Some(101730), None),
224226
/// Allows using `#[start]` on a function indicating that it is the program entrypoint.
225227
(active, start, "1.0.0", Some(29633), None),
226228
/// Allows using `#[structural_match]` which indicates that a type is structurally matchable.

compiler/rustc_lint/src/levels.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -440,8 +440,10 @@ impl<'s> LintLevelsBuilder<'s> {
440440
sp,
441441
reason,
442442
);
443-
for id in ids {
444-
self.insert_spec(*id, (level, src));
443+
for &id in ids {
444+
if self.check_gated_lint(id, attr.span) {
445+
self.insert_spec(id, (level, src));
446+
}
445447
}
446448
if let Level::Expect(expect_id) = level {
447449
self.lint_expectations.push((

compiler/rustc_lint_defs/src/lib.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -658,18 +658,21 @@ macro_rules! declare_lint {
658658
macro_rules! declare_tool_lint {
659659
(
660660
$(#[$attr:meta])* $vis:vis $tool:ident ::$NAME:ident, $Level: ident, $desc: expr
661+
$(, @feature_gate = $gate:expr;)?
661662
) => (
662-
$crate::declare_tool_lint!{$(#[$attr])* $vis $tool::$NAME, $Level, $desc, false}
663+
$crate::declare_tool_lint!{$(#[$attr])* $vis $tool::$NAME, $Level, $desc, false $(, @feature_gate = $gate;)?}
663664
);
664665
(
665666
$(#[$attr:meta])* $vis:vis $tool:ident ::$NAME:ident, $Level:ident, $desc:expr,
666667
report_in_external_macro: $rep:expr
668+
$(, @feature_gate = $gate:expr;)?
667669
) => (
668-
$crate::declare_tool_lint!{$(#[$attr])* $vis $tool::$NAME, $Level, $desc, $rep}
670+
$crate::declare_tool_lint!{$(#[$attr])* $vis $tool::$NAME, $Level, $desc, $rep $(, @feature_gate = $gate;)?}
669671
);
670672
(
671673
$(#[$attr:meta])* $vis:vis $tool:ident ::$NAME:ident, $Level:ident, $desc:expr,
672674
$external:expr
675+
$(, @feature_gate = $gate:expr;)?
673676
) => (
674677
$(#[$attr])*
675678
$vis static $NAME: &$crate::Lint = &$crate::Lint {
@@ -680,8 +683,9 @@ macro_rules! declare_tool_lint {
680683
report_in_external_macro: $external,
681684
future_incompatible: None,
682685
is_plugin: true,
683-
feature_gate: None,
686+
$(feature_gate: Some($gate),)?
684687
crate_level_only: false,
688+
..$crate::Lint::default_fields_for_macro()
685689
};
686690
);
687691
}

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1292,6 +1292,7 @@ symbols! {
12921292
rustc_variance,
12931293
rustdoc,
12941294
rustdoc_internals,
1295+
rustdoc_missing_doc_code_examples,
12951296
rustfmt,
12961297
rvalue_static_promotion,
12971298
s,

compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1160,8 +1160,8 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
11601160
// and if not maybe suggest doing something else? If we kept the expression around we
11611161
// could also check if it is an fn call (very likely) and suggest changing *that*, if
11621162
// it is from the local crate.
1163-
err.span_suggestion_verbose(
1164-
expr.span.shrink_to_hi().with_hi(span.hi()),
1163+
err.span_suggestion(
1164+
span,
11651165
"remove the `.await`",
11661166
"",
11671167
Applicability::MachineApplicable,

compiler/rustc_typeck/src/astconv/mod.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -2672,7 +2672,10 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
26722672
self.normalize_ty(ast_ty.span, array_ty)
26732673
}
26742674
hir::TyKind::Typeof(ref e) => {
2675-
let ty = tcx.type_of(tcx.hir().local_def_id(e.hir_id));
2675+
let ty_erased = tcx.type_of(tcx.hir().local_def_id(e.hir_id));
2676+
let ty = tcx.fold_regions(ty_erased, |r, _| {
2677+
if r.is_erased() { tcx.lifetimes.re_static } else { r }
2678+
});
26762679
let span = ast_ty.span;
26772680
tcx.sess.emit_err(TypeofReservedKeywordUsed {
26782681
span,

library/core/src/ffi/c_str.rs

+12-7
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,10 @@ enum FromBytesWithNulErrorKind {
120120
}
121121

122122
impl FromBytesWithNulError {
123-
fn interior_nul(pos: usize) -> FromBytesWithNulError {
123+
const fn interior_nul(pos: usize) -> FromBytesWithNulError {
124124
FromBytesWithNulError { kind: FromBytesWithNulErrorKind::InteriorNul(pos) }
125125
}
126-
fn not_nul_terminated() -> FromBytesWithNulError {
126+
const fn not_nul_terminated() -> FromBytesWithNulError {
127127
FromBytesWithNulError { kind: FromBytesWithNulErrorKind::NotNulTerminated }
128128
}
129129

@@ -294,7 +294,8 @@ impl CStr {
294294
/// ```
295295
///
296296
#[unstable(feature = "cstr_from_bytes_until_nul", issue = "95027")]
297-
pub fn from_bytes_until_nul(bytes: &[u8]) -> Result<&CStr, FromBytesUntilNulError> {
297+
#[rustc_const_unstable(feature = "cstr_from_bytes_until_nul", issue = "95027")]
298+
pub const fn from_bytes_until_nul(bytes: &[u8]) -> Result<&CStr, FromBytesUntilNulError> {
298299
let nul_pos = memchr::memchr(0, bytes);
299300
match nul_pos {
300301
Some(nul_pos) => {
@@ -343,7 +344,8 @@ impl CStr {
343344
/// assert!(cstr.is_err());
344345
/// ```
345346
#[stable(feature = "cstr_from_bytes", since = "1.10.0")]
346-
pub fn from_bytes_with_nul(bytes: &[u8]) -> Result<&Self, FromBytesWithNulError> {
347+
#[rustc_const_unstable(feature = "const_cstr_methods", issue = "101719")]
348+
pub const fn from_bytes_with_nul(bytes: &[u8]) -> Result<&Self, FromBytesWithNulError> {
347349
let nul_pos = memchr::memchr(0, bytes);
348350
match nul_pos {
349351
Some(nul_pos) if nul_pos + 1 == bytes.len() => {
@@ -493,7 +495,8 @@ impl CStr {
493495
#[must_use = "this returns the result of the operation, \
494496
without modifying the original"]
495497
#[stable(feature = "rust1", since = "1.0.0")]
496-
pub fn to_bytes(&self) -> &[u8] {
498+
#[rustc_const_unstable(feature = "const_cstr_methods", issue = "101719")]
499+
pub const fn to_bytes(&self) -> &[u8] {
497500
let bytes = self.to_bytes_with_nul();
498501
// SAFETY: to_bytes_with_nul returns slice with length at least 1
499502
unsafe { bytes.get_unchecked(..bytes.len() - 1) }
@@ -520,7 +523,8 @@ impl CStr {
520523
#[must_use = "this returns the result of the operation, \
521524
without modifying the original"]
522525
#[stable(feature = "rust1", since = "1.0.0")]
523-
pub fn to_bytes_with_nul(&self) -> &[u8] {
526+
#[rustc_const_unstable(feature = "const_cstr_methods", issue = "101719")]
527+
pub const fn to_bytes_with_nul(&self) -> &[u8] {
524528
// SAFETY: Transmuting a slice of `c_char`s to a slice of `u8`s
525529
// is safe on all supported targets.
526530
unsafe { &*(&self.inner as *const [c_char] as *const [u8]) }
@@ -543,7 +547,8 @@ impl CStr {
543547
/// assert_eq!(cstr.to_str(), Ok("foo"));
544548
/// ```
545549
#[stable(feature = "cstr_to_str", since = "1.4.0")]
546-
pub fn to_str(&self) -> Result<&str, str::Utf8Error> {
550+
#[rustc_const_unstable(feature = "const_cstr_methods", issue = "101719")]
551+
pub const fn to_str(&self) -> Result<&str, str::Utf8Error> {
547552
// N.B., when `CStr` is changed to perform the length check in `.to_bytes()`
548553
// instead of in `from_ptr()`, it may be worth considering if this should
549554
// be rewritten to do the UTF-8 check inline with the length calculation

library/core/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@
159159
#![feature(const_slice_from_ref)]
160160
#![feature(const_slice_index)]
161161
#![feature(const_is_char_boundary)]
162+
#![feature(const_cstr_methods)]
162163
//
163164
// Language features:
164165
#![feature(abi_unadjusted)]

library/core/src/slice/memchr.rs

+24-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Copyright 2015 Andrew Gallant, bluss and Nicolas Koch
33

44
use crate::cmp;
5+
use crate::intrinsics;
56
use crate::mem;
67

78
const LO_USIZE: usize = usize::repeat_u8(0x01);
@@ -35,13 +36,31 @@ fn repeat_byte(b: u8) -> usize {
3536
/// Returns the first index matching the byte `x` in `text`.
3637
#[must_use]
3738
#[inline]
38-
pub fn memchr(x: u8, text: &[u8]) -> Option<usize> {
39-
// Fast path for small slices
40-
if text.len() < 2 * USIZE_BYTES {
41-
return text.iter().position(|elt| *elt == x);
39+
pub const fn memchr(x: u8, text: &[u8]) -> Option<usize> {
40+
#[inline]
41+
fn rt_impl(x: u8, text: &[u8]) -> Option<usize> {
42+
// Fast path for small slices
43+
if text.len() < 2 * USIZE_BYTES {
44+
return text.iter().position(|elt| *elt == x);
45+
}
46+
47+
memchr_general_case(x, text)
48+
}
49+
50+
const fn const_impl(x: u8, bytes: &[u8]) -> Option<usize> {
51+
let mut i = 0;
52+
while i < bytes.len() {
53+
if bytes[i] == x {
54+
return Some(i);
55+
}
56+
i += 1;
57+
}
58+
59+
None
4260
}
4361

44-
memchr_general_case(x, text)
62+
// SAFETY: The const and runtime versions have identical behavior
63+
unsafe { intrinsics::const_eval_select((x, text), const_impl, rt_impl) }
4564
}
4665

4766
fn memchr_general_case(x: u8, text: &[u8]) -> Option<usize> {

src/librustdoc/lint.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,13 @@ where
6464
}
6565

6666
macro_rules! declare_rustdoc_lint {
67-
($(#[$attr:meta])* $name: ident, $level: ident, $descr: literal $(,)?) => {
67+
(
68+
$(#[$attr:meta])* $name: ident, $level: ident, $descr: literal $(,)?
69+
$(@feature_gate = $gate:expr;)?
70+
) => {
6871
declare_tool_lint! {
6972
$(#[$attr])* pub rustdoc::$name, $level, $descr
73+
$(, @feature_gate = $gate;)?
7074
}
7175
}
7276
}
@@ -123,7 +127,8 @@ declare_rustdoc_lint! {
123127
/// [rustdoc book]: ../../../rustdoc/lints.html#missing_doc_code_examples
124128
MISSING_DOC_CODE_EXAMPLES,
125129
Allow,
126-
"detects publicly-exported items without code samples in their documentation"
130+
"detects publicly-exported items without code samples in their documentation",
131+
@feature_gate = rustc_span::symbol::sym::rustdoc_missing_doc_code_examples;
127132
}
128133

129134
declare_rustdoc_lint! {

src/librustdoc/passes/check_doc_test_visibility.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ pub(crate) fn look_for_tests<'tcx>(cx: &DocContext<'tcx>, dox: &str, item: &Item
117117

118118
find_testable_code(dox, &mut tests, ErrorCodes::No, false, None);
119119

120-
if tests.found_tests == 0 && cx.tcx.sess.is_nightly_build() {
120+
if tests.found_tests == 0 && cx.tcx.features().rustdoc_missing_doc_code_examples {
121121
if should_have_doc_example(cx, item) {
122122
debug!("reporting error for {:?} (hir_id={:?})", item, hir_id);
123123
let sp = item.attr_span(cx.tcx);

src/librustdoc/passes/html_tags.rs

+81-4
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,34 @@ fn extract_path_backwards(text: &str, end_pos: usize) -> Option<usize> {
9494
if current_pos == end_pos { None } else { Some(current_pos) }
9595
}
9696

97+
fn extract_path_forward(text: &str, start_pos: usize) -> Option<usize> {
98+
use rustc_lexer::{is_id_continue, is_id_start};
99+
let mut current_pos = start_pos;
100+
loop {
101+
if current_pos < text.len() && text[current_pos..].starts_with("::") {
102+
current_pos += 2;
103+
} else {
104+
break;
105+
}
106+
let mut chars = text[current_pos..].chars();
107+
if let Some(c) = chars.next() {
108+
if is_id_start(c) {
109+
current_pos += c.len_utf8();
110+
} else {
111+
break;
112+
}
113+
}
114+
while let Some(c) = chars.next() {
115+
if is_id_continue(c) {
116+
current_pos += c.len_utf8();
117+
} else {
118+
break;
119+
}
120+
}
121+
}
122+
if current_pos == start_pos { None } else { Some(current_pos) }
123+
}
124+
97125
fn is_valid_for_html_tag_name(c: char, is_empty: bool) -> bool {
98126
// https://spec.commonmark.org/0.30/#raw-html
99127
//
@@ -218,19 +246,68 @@ impl<'a, 'tcx> DocVisitor for InvalidHtmlTagsLinter<'a, 'tcx> {
218246
// If a tag looks like `<this>`, it might actually be a generic.
219247
// We don't try to detect stuff `<like, this>` because that's not valid HTML,
220248
// and we don't try to detect stuff `<like this>` because that's not valid Rust.
221-
if let Some(Some(generics_start)) = (is_open_tag
222-
&& dox[..range.end].ends_with('>'))
249+
let mut generics_end = range.end;
250+
if let Some(Some(mut generics_start)) = (is_open_tag
251+
&& dox[..generics_end].ends_with('>'))
223252
.then(|| extract_path_backwards(&dox, range.start))
224253
{
254+
while generics_start != 0
255+
&& generics_end < dox.len()
256+
&& dox.as_bytes()[generics_start - 1] == b'<'
257+
&& dox.as_bytes()[generics_end] == b'>'
258+
{
259+
generics_end += 1;
260+
generics_start -= 1;
261+
if let Some(new_start) = extract_path_backwards(&dox, generics_start) {
262+
generics_start = new_start;
263+
}
264+
if let Some(new_end) = extract_path_forward(&dox, generics_end) {
265+
generics_end = new_end;
266+
}
267+
}
268+
if let Some(new_end) = extract_path_forward(&dox, generics_end) {
269+
generics_end = new_end;
270+
}
225271
let generics_sp = match super::source_span_for_markdown_range(
226272
tcx,
227273
&dox,
228-
&(generics_start..range.end),
274+
&(generics_start..generics_end),
229275
&item.attrs,
230276
) {
231277
Some(sp) => sp,
232278
None => item.attr_span(tcx),
233279
};
280+
// Sometimes, we only extract part of a path. For example, consider this:
281+
//
282+
// <[u32] as IntoIter<u32>>::Item
283+
// ^^^^^ unclosed HTML tag `u32`
284+
//
285+
// We don't have any code for parsing fully-qualified trait paths.
286+
// In theory, we could add it, but doing it correctly would require
287+
// parsing the entire path grammar, which is problematic because of
288+
// overlap between the path grammar and Markdown.
289+
//
290+
// The example above shows that ambiguity. Is `[u32]` intended to be an
291+
// intra-doc link to the u32 primitive, or is it intended to be a slice?
292+
//
293+
// If the below conditional were removed, we would suggest this, which is
294+
// not what the user probably wants.
295+
//
296+
// <[u32] as `IntoIter<u32>`>::Item
297+
//
298+
// We know that the user actually wants to wrap the whole thing in a code
299+
// block, but the only reason we know that is because `u32` does not, in
300+
// fact, implement IntoIter. If the example looks like this:
301+
//
302+
// <[Vec<i32>] as IntoIter<i32>::Item
303+
//
304+
// The ideal fix would be significantly different.
305+
if (generics_start > 0 && dox.as_bytes()[generics_start - 1] == b'<')
306+
|| (generics_end < dox.len() && dox.as_bytes()[generics_end] == b'>')
307+
{
308+
diag.emit();
309+
return;
310+
}
234311
// multipart form is chosen here because ``Vec<i32>`` would be confusing.
235312
diag.multipart_suggestion(
236313
"try marking as source code",
@@ -278,7 +355,7 @@ impl<'a, 'tcx> DocVisitor for InvalidHtmlTagsLinter<'a, 'tcx> {
278355
for (event, range) in p {
279356
match event {
280357
Event::Start(Tag::CodeBlock(_)) => in_code_block = true,
281-
Event::Html(text) | Event::Text(text) if !in_code_block => {
358+
Event::Html(text) if !in_code_block => {
282359
extract_tags(&mut tags, &text, range, &mut is_in_comment, &report_diag)
283360
}
284361
Event::End(Tag::CodeBlock(_)) => in_code_block = false,

src/test/rustdoc-ui/check-fail.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// compile-flags: -Z unstable-options --check
22

3+
#![feature(rustdoc_missing_doc_code_examples)]
34
#![deny(missing_docs)]
45
#![deny(rustdoc::all)]
56

0 commit comments

Comments
 (0)