Auto merge of #148762 - matthiaskrgr:rollup-4oifvkr, r=matthiaskrgr

Rollup of 4 pull requests

Successful merges:

 - rust-lang/rust#148248 (Constify `ControlFlow` methods without unstable bounds)
 - rust-lang/rust#148285 (Constify `ControlFlow` methods with unstable bounds)
 - rust-lang/rust#148510 (compiletest: Do the known-directives check only once, and improve its error message)
 - rust-lang/rust#148655 (Fix invalid macro tag generation for keywords which can be followed by values)

r? `@ghost`
`@rustbot` modify labels: rollup
This commit is contained in:
bors 2025-11-09 23:15:34 +00:00
commit 8401398e1f
8 changed files with 177 additions and 98 deletions

View file

@ -1,3 +1,4 @@
use crate::marker::Destruct;
use crate::{convert, ops};
/// Used to tell an operation whether it should exit early or go on as usual.
@ -150,7 +151,8 @@ impl<B, C> ControlFlow<B, C> {
/// ```
#[inline]
#[stable(feature = "control_flow_enum_is", since = "1.59.0")]
pub fn is_break(&self) -> bool {
#[rustc_const_unstable(feature = "min_const_control_flow", issue = "148738")]
pub const fn is_break(&self) -> bool {
matches!(*self, ControlFlow::Break(_))
}
@ -166,7 +168,8 @@ impl<B, C> ControlFlow<B, C> {
/// ```
#[inline]
#[stable(feature = "control_flow_enum_is", since = "1.59.0")]
pub fn is_continue(&self) -> bool {
#[rustc_const_unstable(feature = "min_const_control_flow", issue = "148738")]
pub const fn is_continue(&self) -> bool {
matches!(*self, ControlFlow::Continue(_))
}
@ -183,7 +186,11 @@ impl<B, C> ControlFlow<B, C> {
/// ```
#[inline]
#[stable(feature = "control_flow_enum", since = "1.83.0")]
pub fn break_value(self) -> Option<B> {
#[rustc_const_unstable(feature = "const_control_flow", issue = "148739")]
pub const fn break_value(self) -> Option<B>
where
Self: [const] Destruct,
{
match self {
ControlFlow::Continue(..) => None,
ControlFlow::Break(x) => Some(x),
@ -257,7 +264,8 @@ impl<B, C> ControlFlow<B, C> {
/// ```
#[inline]
#[unstable(feature = "control_flow_ok", issue = "140266")]
pub fn break_ok(self) -> Result<B, C> {
#[rustc_const_unstable(feature = "min_const_control_flow", issue = "148738")]
pub const fn break_ok(self) -> Result<B, C> {
match self {
ControlFlow::Continue(c) => Err(c),
ControlFlow::Break(b) => Ok(b),
@ -268,7 +276,11 @@ impl<B, C> ControlFlow<B, C> {
/// to the break value in case it exists.
#[inline]
#[stable(feature = "control_flow_enum", since = "1.83.0")]
pub fn map_break<T>(self, f: impl FnOnce(B) -> T) -> ControlFlow<T, C> {
#[rustc_const_unstable(feature = "const_control_flow", issue = "148739")]
pub const fn map_break<T, F>(self, f: F) -> ControlFlow<T, C>
where
F: [const] FnOnce(B) -> T + [const] Destruct,
{
match self {
ControlFlow::Continue(x) => ControlFlow::Continue(x),
ControlFlow::Break(x) => ControlFlow::Break(f(x)),
@ -288,7 +300,11 @@ impl<B, C> ControlFlow<B, C> {
/// ```
#[inline]
#[stable(feature = "control_flow_enum", since = "1.83.0")]
pub fn continue_value(self) -> Option<C> {
#[rustc_const_unstable(feature = "const_control_flow", issue = "148739")]
pub const fn continue_value(self) -> Option<C>
where
Self: [const] Destruct,
{
match self {
ControlFlow::Continue(x) => Some(x),
ControlFlow::Break(..) => None,
@ -361,7 +377,8 @@ impl<B, C> ControlFlow<B, C> {
/// ```
#[inline]
#[unstable(feature = "control_flow_ok", issue = "140266")]
pub fn continue_ok(self) -> Result<C, B> {
#[rustc_const_unstable(feature = "min_const_control_flow", issue = "148738")]
pub const fn continue_ok(self) -> Result<C, B> {
match self {
ControlFlow::Continue(c) => Ok(c),
ControlFlow::Break(b) => Err(b),
@ -372,7 +389,11 @@ impl<B, C> ControlFlow<B, C> {
/// to the continue value in case it exists.
#[inline]
#[stable(feature = "control_flow_enum", since = "1.83.0")]
pub fn map_continue<T>(self, f: impl FnOnce(C) -> T) -> ControlFlow<B, T> {
#[rustc_const_unstable(feature = "const_control_flow", issue = "148739")]
pub const fn map_continue<T, F>(self, f: F) -> ControlFlow<B, T>
where
F: [const] FnOnce(C) -> T + [const] Destruct,
{
match self {
ControlFlow::Continue(x) => ControlFlow::Continue(f(x)),
ControlFlow::Break(x) => ControlFlow::Break(x),

View file

@ -789,6 +789,9 @@ impl<'a> Iterator for TokenIter<'a> {
}
}
/// Used to know if a keyword followed by a `!` should never be treated as a macro.
const NON_MACRO_KEYWORDS: &[&str] = &["if", "while", "match", "break", "return", "impl"];
/// This iterator comes from the same idea than "Peekable" except that it allows to "peek" more than
/// just the next item by using `peek_next`. The `peek` method always returns the next item after
/// the current one whereas `peek_next` will return the next item after the last one peeked.
@ -1010,6 +1013,19 @@ impl<'src> Classifier<'src> {
}
}
fn new_macro_span(
&mut self,
text: &'src str,
sink: &mut dyn FnMut(Span, Highlight<'src>),
before: u32,
file_span: Span,
) {
self.in_macro = true;
let span = new_span(before, text, file_span);
sink(DUMMY_SP, Highlight::EnterSpan { class: Class::Macro(span) });
sink(span, Highlight::Token { text, class: None });
}
/// Single step of highlighting. This will classify `token`, but maybe also a couple of
/// following ones as well.
///
@ -1216,16 +1232,46 @@ impl<'src> Classifier<'src> {
LiteralKind::Float { .. } | LiteralKind::Int { .. } => Class::Number,
},
TokenKind::GuardedStrPrefix => return no_highlight(sink),
TokenKind::Ident | TokenKind::RawIdent
if let Some((TokenKind::Bang, _)) = self.peek_non_trivia() =>
{
self.in_macro = true;
let span = new_span(before, text, file_span);
sink(DUMMY_SP, Highlight::EnterSpan { class: Class::Macro(span) });
sink(span, Highlight::Token { text, class: None });
TokenKind::RawIdent if let Some((TokenKind::Bang, _)) = self.peek_non_trivia() => {
self.new_macro_span(text, sink, before, file_span);
return;
}
TokenKind::Ident => self.classify_ident(before, text),
// Macro non-terminals (meta vars) take precedence.
TokenKind::Ident if self.in_macro_nonterminal => {
self.in_macro_nonterminal = false;
Class::MacroNonTerminal
}
TokenKind::Ident => {
let file_span = self.file_span;
let span = || new_span(before, text, file_span);
match text {
"ref" | "mut" => Class::RefKeyWord,
"false" | "true" => Class::Bool,
"self" | "Self" => Class::Self_(span()),
"Option" | "Result" => Class::PreludeTy(span()),
"Some" | "None" | "Ok" | "Err" => Class::PreludeVal(span()),
_ if self.is_weak_keyword(text) || is_keyword(Symbol::intern(text)) => {
// So if it's not a keyword which can be followed by a value (like `if` or
// `return`) and the next non-whitespace token is a `!`, then we consider
// it's a macro.
if !NON_MACRO_KEYWORDS.contains(&text)
&& matches!(self.peek_non_trivia(), Some((TokenKind::Bang, _)))
{
self.new_macro_span(text, sink, before, file_span);
return;
}
Class::KeyWord
}
// If it's not a keyword and the next non whitespace token is a `!`, then
// we consider it's a macro.
_ if matches!(self.peek_non_trivia(), Some((TokenKind::Bang, _))) => {
self.new_macro_span(text, sink, before, file_span);
return;
}
_ => Class::Ident(span()),
}
}
TokenKind::RawIdent | TokenKind::UnknownPrefix | TokenKind::InvalidIdent => {
Class::Ident(new_span(before, text, file_span))
}
@ -1246,27 +1292,6 @@ impl<'src> Classifier<'src> {
}
}
fn classify_ident(&mut self, before: u32, text: &'src str) -> Class {
// Macro non-terminals (meta vars) take precedence.
if self.in_macro_nonterminal {
self.in_macro_nonterminal = false;
return Class::MacroNonTerminal;
}
let file_span = self.file_span;
let span = || new_span(before, text, file_span);
match text {
"ref" | "mut" => Class::RefKeyWord,
"false" | "true" => Class::Bool,
"self" | "Self" => Class::Self_(span()),
"Option" | "Result" => Class::PreludeTy(span()),
"Some" | "None" | "Ok" | "Err" => Class::PreludeVal(span()),
_ if self.is_weak_keyword(text) || is_keyword(Symbol::intern(text)) => Class::KeyWord,
_ => Class::Ident(span()),
}
}
fn is_weak_keyword(&mut self, text: &str) -> bool {
// NOTE: `yeet` (`do yeet $expr`), `catch` (`do catch $block`), `default` (specialization),
// `contract_{ensures,requires}`, `builtin` (builtin_syntax) & `reuse` (fn_delegation) are

View file

@ -11,7 +11,7 @@ use crate::debuggers::{extract_cdb_version, extract_gdb_version};
pub(crate) use crate::directives::auxiliary::AuxProps;
use crate::directives::auxiliary::parse_and_update_aux;
use crate::directives::directive_names::{
KNOWN_DIRECTIVE_NAMES, KNOWN_HTMLDOCCK_DIRECTIVE_NAMES, KNOWN_JSONDOCCK_DIRECTIVE_NAMES,
KNOWN_DIRECTIVE_NAMES_SET, KNOWN_HTMLDOCCK_DIRECTIVE_NAMES, KNOWN_JSONDOCCK_DIRECTIVE_NAMES,
};
pub(crate) use crate::directives::file::FileDirectives;
use crate::directives::line::{DirectiveLine, line_directive};
@ -53,13 +53,10 @@ impl EarlyProps {
config: &Config,
file_directives: &FileDirectives<'_>,
) -> Self {
let testfile = file_directives.path;
let mut props = EarlyProps::default();
let mut poisoned = false;
iter_directives(
config.mode,
&mut poisoned,
file_directives,
// (dummy comment to force args into vertical layout)
&mut |ln: &DirectiveLine<'_>| {
@ -67,11 +64,6 @@ impl EarlyProps {
},
);
if poisoned {
eprintln!("errors encountered during EarlyProps parsing: {}", testfile);
panic!("errors encountered during EarlyProps parsing");
}
props
}
}
@ -358,12 +350,10 @@ impl TestProps {
let file_contents = fs::read_to_string(testfile).unwrap();
let file_directives = FileDirectives::from_file_contents(testfile, &file_contents);
let mut poisoned = false;
iter_directives(
config.mode,
&mut poisoned,
&file_directives,
// (dummy comment to force args into vertical layout)
&mut |ln: &DirectiveLine<'_>| {
if !ln.applies_to_test_revision(test_revision) {
return;
@ -634,11 +624,6 @@ impl TestProps {
);
},
);
if poisoned {
eprintln!("errors encountered during TestProps parsing: {}", testfile);
panic!("errors encountered during TestProps parsing");
}
}
if self.should_ice {
@ -775,6 +760,34 @@ impl TestProps {
}
}
pub(crate) fn do_early_directives_check(
mode: TestMode,
file_directives: &FileDirectives<'_>,
) -> Result<(), String> {
let testfile = file_directives.path;
for directive_line @ DirectiveLine { line_number, .. } in &file_directives.lines {
let CheckDirectiveResult { is_known_directive, trailing_directive } =
check_directive(directive_line, mode);
if !is_known_directive {
return Err(format!(
"ERROR: unknown compiletest directive `{directive}` at {testfile}:{line_number}",
directive = directive_line.display(),
));
}
if let Some(trailing_directive) = &trailing_directive {
return Err(format!(
"ERROR: detected trailing compiletest directive `{trailing_directive}` at {testfile}:{line_number}\n\
HELP: put the directive on its own line: `//@ {trailing_directive}`"
));
}
}
Ok(())
}
pub(crate) struct CheckDirectiveResult<'ln> {
is_known_directive: bool,
trailing_directive: Option<&'ln str>,
@ -786,7 +799,7 @@ fn check_directive<'a>(
) -> CheckDirectiveResult<'a> {
let &DirectiveLine { name: directive_name, .. } = directive_ln;
let is_known_directive = KNOWN_DIRECTIVE_NAMES.contains(&directive_name)
let is_known_directive = KNOWN_DIRECTIVE_NAMES_SET.contains(&directive_name)
|| match mode {
TestMode::Rustdoc => KNOWN_HTMLDOCCK_DIRECTIVE_NAMES.contains(&directive_name),
TestMode::RustdocJson => KNOWN_JSONDOCCK_DIRECTIVE_NAMES.contains(&directive_name),
@ -799,7 +812,7 @@ fn check_directive<'a>(
let trailing_directive = directive_ln
.remark_after_space()
.map(|remark| remark.trim_start().split(' ').next().unwrap())
.filter(|token| KNOWN_DIRECTIVE_NAMES.contains(token));
.filter(|token| KNOWN_DIRECTIVE_NAMES_SET.contains(token));
// FIXME(Zalathar): Consider emitting specialized error/help messages for
// bogus directive names that are similar to real ones, e.g.:
@ -811,7 +824,6 @@ fn check_directive<'a>(
fn iter_directives(
mode: TestMode,
poisoned: &mut bool,
file_directives: &FileDirectives<'_>,
it: &mut dyn FnMut(&DirectiveLine<'_>),
) {
@ -837,36 +849,7 @@ fn iter_directives(
}
}
for directive_line @ &DirectiveLine { line_number, .. } in &file_directives.lines {
// Perform unknown directive check on Rust files.
if testfile.extension() == Some("rs") {
let CheckDirectiveResult { is_known_directive, trailing_directive } =
check_directive(directive_line, mode);
if !is_known_directive {
*poisoned = true;
error!(
"{testfile}:{line_number}: detected unknown compiletest test directive `{}`",
directive_line.display(),
);
return;
}
if let Some(trailing_directive) = &trailing_directive {
*poisoned = true;
error!(
"{testfile}:{line_number}: detected trailing compiletest test directive `{}`",
trailing_directive,
);
help!("put the trailing directive in its own line: `//@ {}`", trailing_directive);
return;
}
}
for directive_line in &file_directives.lines {
it(directive_line);
}
}
@ -1304,12 +1287,9 @@ pub(crate) fn make_test_description(
let mut ignore_message = None;
let mut should_fail = false;
let mut local_poisoned = false;
// Scan through the test file to handle `ignore-*`, `only-*`, and `needs-*` directives.
iter_directives(
config.mode,
&mut local_poisoned,
file_directives,
&mut |ln @ &DirectiveLine { line_number, .. }| {
if !ln.applies_to_test_revision(test_revision) {
@ -1358,11 +1338,6 @@ pub(crate) fn make_test_description(
},
);
if local_poisoned {
eprintln!("errors encountered when trying to make test description: {}", path);
panic!("errors encountered when trying to make test description");
}
// The `should-fail` annotation doesn't apply to pretty tests,
// since we run the pretty printer across all tests by default.
// If desired, we could add a `should-fail-pretty` annotation.

View file

@ -1,3 +1,9 @@
use std::collections::HashSet;
use std::sync::LazyLock;
pub(crate) static KNOWN_DIRECTIVE_NAMES_SET: LazyLock<HashSet<&str>> =
LazyLock::new(|| KNOWN_DIRECTIVE_NAMES.iter().copied().collect());
/// This was originally generated by collecting directives from ui tests and then extracting their
/// directive names. This is **not** an exhaustive list of all possible directives. Instead, this is
/// a best-effort approximation for diagnostics. Add new directives to this list when needed.

View file

@ -3,8 +3,8 @@ use semver::Version;
use crate::common::{Config, Debugger, TestMode};
use crate::directives::{
AuxProps, DirectivesCache, EarlyProps, Edition, EditionRange, FileDirectives,
extract_llvm_version, extract_version_range, iter_directives, line_directive, parse_edition,
self, AuxProps, DirectivesCache, EarlyProps, Edition, EditionRange, FileDirectives,
extract_llvm_version, extract_version_range, line_directive, parse_edition,
parse_normalize_rule,
};
use crate::executor::{CollectedTestDesc, ShouldFail};
@ -767,7 +767,10 @@ fn threads_support() {
fn run_path(poisoned: &mut bool, path: &Utf8Path, file_contents: &str) {
let file_directives = FileDirectives::from_file_contents(path, file_contents);
iter_directives(TestMode::Ui, poisoned, &file_directives, &mut |_| {});
let result = directives::do_early_directives_check(TestMode::Ui, &file_directives);
if result.is_err() {
*poisoned = true;
}
}
#[test]

View file

@ -869,6 +869,12 @@ fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &Te
let file_contents =
fs::read_to_string(&test_path).expect("reading test file for directives should succeed");
let file_directives = FileDirectives::from_file_contents(&test_path, &file_contents);
if let Err(message) = directives::do_early_directives_check(cx.config.mode, &file_directives) {
// FIXME(Zalathar): Overhaul compiletest error handling so that we
// don't have to resort to ad-hoc panics everywhere.
panic!("directives check failed:\n{message}");
}
let early_props = EarlyProps::from_file_directives(&cx.config, &file_directives);
// Normally we create one structure per revision, with two exceptions:

View file

@ -0,0 +1,13 @@
// This code crashed because a `if` followed by a `!` was considered a macro,
// creating an invalid class stack.
// Regression test for <https://github.com/rust-lang/rust/issues/148617>.
//@ compile-flags: -Zunstable-options --generate-macro-expansion
enum Enum {
Variant,
}
pub fn repro() {
if !matches!(Enum::Variant, Enum::Variant) {}
}

View file

@ -0,0 +1,30 @@
// This test ensures that keywords which can be followed by values (and therefore `!`)
// are not considered as macros.
// This is a regression test for <https://github.com/rust-lang/rust/issues/148617>.
#![crate_name = "foo"]
#![feature(negative_impls)]
//@ has 'src/foo/keyword-macros.rs.html'
//@ has - '//*[@class="rust"]//*[@class="number"]' '2'
//@ has - '//*[@class="rust"]//*[@class="number"]' '0'
//@ has - '//*[@class="rust"]//*[@class="number"]' '1'
const ARR: [u8; 2] = [!0,! 1];
trait X {}
//@ has - '//*[@class="rust"]//*[@class="kw"]' 'impl'
impl !X for i32 {}
fn a() {
//@ has - '//*[@class="rust"]//*[@class="kw"]' 'if'
if! true{}
//@ has - '//*[@class="rust"]//*[@class="kw"]' 'match'
match !true { _ => {} }
//@ has - '//*[@class="rust"]//*[@class="kw"]' 'while'
let _ = while !true {
//@ has - '//*[@class="rust"]//*[@class="kw"]' 'break'
break !true;
};
}