authorbors <bors@rust-lang.org> 2025-10-05 04:36:49 UTC
committerbors <bors@rust-lang.org> 2025-10-05 04:36:49 UTC
loge2c96cc06bdbdbc6f59c7551194d6a742260d6ff
tree5da6ddca7b7ec935878c35826b9b3db41f698f07
parent227ac7c3cd486872d5c2352b3df02b571500e53a
parenta0a4905723b3aca4a68fa7527fd66a10290c782c

Auto merge of #147363 - Zalathar:rollup-d9kd06g, r=Zalathar

Rollup of 7 pull requests Successful merges: - rust-lang/rust#147288 (compiletest: Make `DirectiveLine` responsible for name/value splitting) - rust-lang/rust#147309 (Add documentation about unwinding to wasm targets) - rust-lang/rust#147310 (Mark `PatternTypo` suggestion as maybe incorrect) - rust-lang/rust#147320 (Avoid to suggest pattern match on the similarly named in fn signature) - rust-lang/rust#147328 (Implement non-poisoning `Mutex::with_mut`, `RwLock::with` and `RwLock::with_mut`) - rust-lang/rust#147337 (Make `fmt::Write` a diagnostic item) - rust-lang/rust#147349 (Improve the advice given by panic_immediate_abort) r? `@ghost` `@rustbot` modify labels: rollup

22 files changed, 442 insertions(+), 171 deletions(-)

compiler/rustc_passes/src/errors.rs+1-1
......@@ -1365,7 +1365,7 @@ pub(crate) struct UnusedVarAssignedOnly {
13651365#[multipart_suggestion(
13661366 passes_unused_var_typo,
13671367 style = "verbose",
1368 applicability = "machine-applicable"
1368 applicability = "maybe-incorrect"
13691369)]
13701370pub(crate) struct PatternTypo {
13711371 #[suggestion_part(code = "{code}")]
compiler/rustc_passes/src/liveness.rs+5-2
......@@ -1691,7 +1691,10 @@ impl<'tcx> Liveness<'_, 'tcx> {
16911691 if ln == self.exit_ln { false } else { self.assigned_on_exit(ln, var) };
16921692
16931693 let mut typo = None;
1694 for (hir_id, _, span) in &hir_ids_and_spans {
1694 let filtered_hir_ids_and_spans = hir_ids_and_spans.iter().filter(|(hir_id, ..)| {
1695 !matches!(self.ir.tcx.parent_hir_node(*hir_id), hir::Node::Param(_))
1696 });
1697 for (hir_id, _, span) in filtered_hir_ids_and_spans.clone() {
16951698 let ty = self.typeck_results.node_type(*hir_id);
16961699 if let ty::Adt(adt, _) = ty.peel_refs().kind() {
16971700 let name = Symbol::intern(&name);
......@@ -1717,7 +1720,7 @@ impl<'tcx> Liveness<'_, 'tcx> {
17171720 }
17181721 }
17191722 if typo.is_none() {
1720 for (hir_id, _, span) in &hir_ids_and_spans {
1723 for (hir_id, _, span) in filtered_hir_ids_and_spans {
17211724 let ty = self.typeck_results.node_type(*hir_id);
17221725 // Look for consts of the same type with similar names as well, not just unit
17231726 // structs and variants.
compiler/rustc_span/src/symbol.rs+1
......@@ -236,6 +236,7 @@ symbols! {
236236 File,
237237 FileType,
238238 FmtArgumentsNew,
239 FmtWrite,
239240 Fn,
240241 FnMut,
241242 FnOnce,
library/core/src/fmt/mod.rs+1
......@@ -115,6 +115,7 @@ pub struct Error;
115115/// [`std::io::Write`]: ../../std/io/trait.Write.html
116116/// [flushable]: ../../std/io/trait.Write.html#tymethod.flush
117117#[stable(feature = "rust1", since = "1.0.0")]
118#[rustc_diagnostic_item = "FmtWrite"]
118119pub trait Write {
119120 /// Writes a string slice into this writer, returning whether the write
120121 /// succeeded.
library/core/src/panicking.rs+2-1
......@@ -35,7 +35,8 @@ use crate::panic::{Location, PanicInfo};
3535#[cfg(feature = "panic_immediate_abort")]
3636compile_error!(
3737 "panic_immediate_abort is now a real panic strategy! \
38 Enable it with the compiler flags `-Zunstable-options -Cpanic=immediate-abort`"
38 Enable it with `panic = \"immediate-abort\"` in Cargo.toml, \
39 or with the compiler flags `-Zunstable-options -Cpanic=immediate-abort`"
3940);
4041
4142// First we define the two main entry points that all panics go through.
library/std/src/sync/nonpoison/mutex.rs+34
......@@ -376,6 +376,40 @@ impl<T: ?Sized> Mutex<T> {
376376 pub const fn data_ptr(&self) -> *mut T {
377377 self.data.get()
378378 }
379
380 /// Acquires the mutex and provides mutable access to the underlying data by passing
381 /// a mutable reference to the given closure.
382 ///
383 /// This method acquires the lock, calls the provided closure with a mutable reference
384 /// to the data, and returns the result of the closure. The lock is released after
385 /// the closure completes, even if it panics.
386 ///
387 /// # Examples
388 ///
389 /// ```
390 /// #![feature(lock_value_accessors, nonpoison_mutex)]
391 ///
392 /// use std::sync::nonpoison::Mutex;
393 ///
394 /// let mutex = Mutex::new(2);
395 ///
396 /// let result = mutex.with_mut(|data| {
397 /// *data += 3;
398 ///
399 /// *data + 5
400 /// });
401 ///
402 /// assert_eq!(*mutex.lock(), 5);
403 /// assert_eq!(result, 10);
404 /// ```
405 #[unstable(feature = "lock_value_accessors", issue = "133407")]
406 // #[unstable(feature = "nonpoison_mutex", issue = "134645")]
407 pub fn with_mut<F, R>(&self, f: F) -> R
408 where
409 F: FnOnce(&mut T) -> R,
410 {
411 f(&mut self.lock())
412 }
379413}
380414
381415#[unstable(feature = "nonpoison_mutex", issue = "134645")]
library/std/src/sync/nonpoison/rwlock.rs+62
......@@ -498,6 +498,68 @@ impl<T: ?Sized> RwLock<T> {
498498 pub const fn data_ptr(&self) -> *mut T {
499499 self.data.get()
500500 }
501
502 /// Locks this `RwLock` with shared read access to the underlying data by passing
503 /// a reference to the given closure.
504 ///
505 /// This method acquires the lock, calls the provided closure with a reference
506 /// to the data, and returns the result of the closure. The lock is released after
507 /// the closure completes, even if it panics.
508 ///
509 /// # Examples
510 ///
511 /// ```
512 /// #![feature(lock_value_accessors, nonpoison_rwlock)]
513 ///
514 /// use std::sync::nonpoison::RwLock;
515 ///
516 /// let rwlock = RwLock::new(2);
517 /// let result = rwlock.with(|data| *data + 3);
518 ///
519 /// assert_eq!(result, 5);
520 /// ```
521 #[unstable(feature = "lock_value_accessors", issue = "133407")]
522 // #[unstable(feature = "nonpoison_rwlock", issue = "134645")]
523 pub fn with<F, R>(&self, f: F) -> R
524 where
525 F: FnOnce(&T) -> R,
526 {
527 f(&self.read())
528 }
529
530 /// Locks this `RwLock` with exclusive write access to the underlying data by passing
531 /// a mutable reference to the given closure.
532 ///
533 /// This method acquires the lock, calls the provided closure with a mutable reference
534 /// to the data, and returns the result of the closure. The lock is released after
535 /// the closure completes, even if it panics.
536 ///
537 /// # Examples
538 ///
539 /// ```
540 /// #![feature(lock_value_accessors, nonpoison_rwlock)]
541 ///
542 /// use std::sync::nonpoison::RwLock;
543 ///
544 /// let rwlock = RwLock::new(2);
545 ///
546 /// let result = rwlock.with_mut(|data| {
547 /// *data += 3;
548 ///
549 /// *data + 5
550 /// });
551 ///
552 /// assert_eq!(*rwlock.read(), 5);
553 /// assert_eq!(result, 10);
554 /// ```
555 #[unstable(feature = "lock_value_accessors", issue = "133407")]
556 // #[unstable(feature = "nonpoison_rwlock", issue = "134645")]
557 pub fn with_mut<F, R>(&self, f: F) -> R
558 where
559 F: FnOnce(&mut T) -> R,
560 {
561 f(&mut self.write())
562 }
501563}
502564
503565#[unstable(feature = "nonpoison_rwlock", issue = "134645")]
library/std/tests/sync/mutex.rs+14
......@@ -549,3 +549,17 @@ fn panic_while_mapping_unlocked_poison() {
549549
550550 drop(lock);
551551}
552
553#[test]
554fn test_mutex_with_mut() {
555 let mutex = std::sync::nonpoison::Mutex::new(2);
556
557 let result = mutex.with_mut(|value| {
558 *value += 3;
559
560 *value + 5
561 });
562
563 assert_eq!(*mutex.lock(), 5);
564 assert_eq!(result, 10);
565}
library/std/tests/sync/rwlock.rs+22
......@@ -861,3 +861,25 @@ fn panic_while_mapping_write_unlocked_poison() {
861861
862862 drop(lock);
863863}
864
865#[test]
866fn test_rwlock_with() {
867 let rwlock = std::sync::nonpoison::RwLock::new(2);
868 let result = rwlock.with(|value| *value + 3);
869
870 assert_eq!(result, 5);
871}
872
873#[test]
874fn test_rwlock_with_mut() {
875 let rwlock = std::sync::nonpoison::RwLock::new(2);
876
877 let result = rwlock.with_mut(|value| {
878 *value += 3;
879
880 *value + 5
881 });
882
883 assert_eq!(*rwlock.read(), 5);
884 assert_eq!(result, 10);
885}
src/doc/rustc/src/platform-support/wasm32-unknown-unknown.md+61-1
......@@ -157,7 +157,7 @@ $ cargo +nightly build -Zbuild-std=panic_abort,std --target wasm32-unknown-unkno
157157```
158158
159159Here the `mvp` "cpu" is a placeholder in LLVM for disabling all supported
160features by default. Cargo's `-Zbuild-std` feature, a Nightly Rust feature, is
160features by default. Cargo's [`-Zbuild-std`] feature, a Nightly Rust feature, is
161161then used to recompile the standard library in addition to your own code. This
162162will produce a binary that uses only the original WebAssembly features by
163163default and no proposals since its inception.
......@@ -207,3 +207,63 @@ conditionally compile code instead. This is notably different to the way native
207207platforms such as x86\_64 work, and this is due to the fact that WebAssembly
208208binaries must only contain code the engine understands. Native binaries work so
209209long as the CPU doesn't execute unknown code dynamically at runtime.
210
211## Unwinding
212
213By default the `wasm32-unknown-unknown` target is compiled with `-Cpanic=abort`.
214Historically this was due to the fact that there was no way to catch panics in
215wasm, but since mid-2025 the WebAssembly [`exception-handling`
216proposal](https://github.com/WebAssembly/exception-handling) reached
217stabilization. LLVM has support for this proposal as well and when this is all
218combined together it's possible to enable `-Cpanic=unwind` on wasm targets.
219
220Compiling wasm targets with `-Cpanic=unwind` is not as easy as just passing
221`-Cpanic=unwind`, however:
222
223```sh
224$ rustc foo.rs -Cpanic=unwind --target wasm32-unknown-unknown
225error: the crate `panic_unwind` does not have the panic strategy `unwind`
226```
227
228Notably the precompiled standard library that is shipped through Rustup is
229compiled with `-Cpanic=abort`, not `-Cpanic=unwind`. While this is the case
230you're going to be required to use Cargo's [`-Zbuild-std`] feature to build with
231unwinding support:
232
233```sh
234$ RUSTFLAGS='-Cpanic=unwind' cargo +nightly build --target wasm32-unknown-unknown -Zbuild-std
235```
236
237Note, however, that as of 2025-10-03 LLVM is still using the "legacy exception
238instructions" by default, not the officially standard version of the
239exception-handling proposal:
240
241```sh
242$ wasm-tools validate target/wasm32-unknown-unknown/debug/foo.wasm
243error: <sysroot>/library/std/src/sys/backtrace.rs:161:5
244function `std::sys::backtrace::__rust_begin_short_backtrace` failed to validate
245
246Caused by:
247 0: func 2 failed to validate
248 1: legacy_exceptions feature required for try instruction (at offset 0x880)
249```
250
251Fixing this requires passing `-Cllvm-args=-wasm-use-legacy-eh=false` to the Rust
252compiler as well:
253
254```sh
255$ RUSTFLAGS='-Cpanic=unwind -Cllvm-args=-wasm-use-legacy-eh=false' cargo +nightly build --target wasm32-unknown-unknown -Zbuild-std
256$ wasm-tools validate target/wasm32-unknown-unknown/debug/foo.wasm
257```
258
259At this time there are no concrete plans for adding new targets to the Rust
260compiler which have `-Cpanic=unwind` enabled-by-default. The most likely route
261to having this enabled is that in a few years when the `exception-handling`
262target feature is enabled by default in LLVM (due to browsers/runtime support
263propagating widely enough) the targets will switch to using `-Cpanic=unwind` by
264default. This is not for certain, however, and will likely be accompanied with
265either an MCP or an RFC about changing all wasm targets in the same manner. In
266the meantime using `-Cpanic=unwind` will require using [`-Zbuild-std`] and
267passing the appropriate flags to rustc.
268
269[`-Zbuild-std`]: ../../cargo/reference/unstable.html#build-std
src/doc/rustc/src/platform-support/wasm32-wasip1.md+6
......@@ -133,3 +133,9 @@ to Rust 1.80 the `target_env` condition was not set.
133133The default set of WebAssembly features enabled for compilation is currently the
134134same as [`wasm32-unknown-unknown`](./wasm32-unknown-unknown.md). See the
135135documentation there for more information.
136
137## Unwinding
138
139This target is compiled with `-Cpanic=abort` by default. For information on
140using `-Cpanic=unwind` see the [documentation about unwinding for
141`wasm32-unknown-unknown`](./wasm32-unknown-unknown.md#unwinding).
src/doc/rustc/src/platform-support/wasm32-wasip2.md+6
......@@ -67,3 +67,9 @@ It's recommended to conditionally compile code for this target with:
6767The default set of WebAssembly features enabled for compilation is currently the
6868same as [`wasm32-unknown-unknown`](./wasm32-unknown-unknown.md). See the
6969documentation there for more information.
70
71## Unwinding
72
73This target is compiled with `-Cpanic=abort` by default. For information on
74using `-Cpanic=unwind` see the [documentation about unwinding for
75`wasm32-unknown-unknown`](./wasm32-unknown-unknown.md#unwinding).
src/doc/rustc/src/platform-support/wasm32v1-none.md+11
......@@ -107,3 +107,14 @@ $ cargo +nightly build -Zbuild-std=panic_abort,std --target wasm32-unknown-unkno
107107Which not only rebuilds `std`, `core` and `alloc` (which is somewhat costly and annoying) but more importantly requires the use of nightly Rust toolchains (for the `-Zbuild-std` flag). This is very undesirable for the target audience, which consists of people targeting WebAssembly implementations that prioritize stability, simplicity and/or security over feature support.
108108
109109This `wasm32v1-none` target exists as an alternative option that works on stable Rust toolchains, without rebuilding the stdlib.
110
111## Unwinding
112
113This target is compiled with `-Cpanic=abort` by default. Using `-Cpanic=unwind`
114would require using the WebAssembly exception-handling proposal stabilized
115mid-2025, and if that's desired then you most likely don't want to use this
116target and instead want to use `wasm32-unknown-unknown` instead. It's unlikely
117that this target will ever support unwinding with the precompiled artifacts
118shipped through rustup. For documentation about using `-Zbuild-std` to enable
119using `-Cpanic=unwind` see the [documentation of
120`wasm32-unknown-unknown`](./wasm32-unknown-unknown.md#unwinding).
src/tools/compiletest/src/directives.rs+58-122
......@@ -15,6 +15,7 @@ use crate::directives::auxiliary::{AuxProps, parse_and_update_aux};
1515use crate::directives::directive_names::{
1616 KNOWN_DIRECTIVE_NAMES, KNOWN_HTMLDOCCK_DIRECTIVE_NAMES, KNOWN_JSONDOCCK_DIRECTIVE_NAMES,
1717};
18use crate::directives::line::{DirectiveLine, line_directive};
1819use crate::directives::needs::CachedNeedsConditions;
1920use crate::edition::{Edition, parse_edition};
2021use crate::errors::ErrorKind;
......@@ -25,6 +26,7 @@ use crate::{fatal, help};
2526pub(crate) mod auxiliary;
2627mod cfg;
2728mod directive_names;
29mod line;
2830mod needs;
2931#[cfg(test)]
3032mod tests;
......@@ -824,70 +826,6 @@ impl TestProps {
824826 }
825827}
826828
827/// If the given line begins with the appropriate comment prefix for a directive,
828/// returns a struct containing various parts of the directive.
829fn line_directive<'line>(
830 line_number: usize,
831 original_line: &'line str,
832) -> Option<DirectiveLine<'line>> {
833 // Ignore lines that don't start with the comment prefix.
834 let after_comment =
835 original_line.trim_start().strip_prefix(COMPILETEST_DIRECTIVE_PREFIX)?.trim_start();
836
837 let revision;
838 let raw_directive;
839
840 if let Some(after_open_bracket) = after_comment.strip_prefix('[') {
841 // A comment like `//@[foo]` only applies to revision `foo`.
842 let Some((line_revision, after_close_bracket)) = after_open_bracket.split_once(']') else {
843 panic!(
844 "malformed condition directive: expected `{COMPILETEST_DIRECTIVE_PREFIX}[foo]`, found `{original_line}`"
845 )
846 };
847
848 revision = Some(line_revision);
849 raw_directive = after_close_bracket.trim_start();
850 } else {
851 revision = None;
852 raw_directive = after_comment;
853 };
854
855 Some(DirectiveLine { line_number, revision, raw_directive })
856}
857
858/// The (partly) broken-down contents of a line containing a test directive,
859/// which [`iter_directives`] passes to its callback function.
860///
861/// For example:
862///
863/// ```text
864/// //@ compile-flags: -O
865/// ^^^^^^^^^^^^^^^^^ raw_directive
866///
867/// //@ [foo] compile-flags: -O
868/// ^^^ revision
869/// ^^^^^^^^^^^^^^^^^ raw_directive
870/// ```
871struct DirectiveLine<'ln> {
872 line_number: usize,
873 /// Some test directives start with a revision name in square brackets
874 /// (e.g. `[foo]`), and only apply to that revision of the test.
875 /// If present, this field contains the revision name (e.g. `foo`).
876 revision: Option<&'ln str>,
877 /// The main part of the directive, after removing the comment prefix
878 /// and the optional revision specifier.
879 ///
880 /// This is "raw" because the directive's name and colon-separated value
881 /// (if present) have not yet been extracted or checked.
882 raw_directive: &'ln str,
883}
884
885impl<'ln> DirectiveLine<'ln> {
886 fn applies_to_test_revision(&self, test_revision: Option<&str>) -> bool {
887 self.revision.is_none() || self.revision == test_revision
888 }
889}
890
891829pub(crate) struct CheckDirectiveResult<'ln> {
892830 is_known_directive: bool,
893831 trailing_directive: Option<&'ln str>,
......@@ -897,9 +835,7 @@ fn check_directive<'a>(
897835 directive_ln: &DirectiveLine<'a>,
898836 mode: TestMode,
899837) -> CheckDirectiveResult<'a> {
900 let &DirectiveLine { raw_directive: directive_ln, .. } = directive_ln;
901
902 let (directive_name, post) = directive_ln.split_once([':', ' ']).unwrap_or((directive_ln, ""));
838 let &DirectiveLine { name: directive_name, .. } = directive_ln;
903839
904840 let is_known_directive = KNOWN_DIRECTIVE_NAMES.contains(&directive_name)
905841 || match mode {
......@@ -908,20 +844,17 @@ fn check_directive<'a>(
908844 _ => false,
909845 };
910846
911 let trailing = post.trim().split_once(' ').map(|(pre, _)| pre).unwrap_or(post);
912 let trailing_directive = {
913 // 1. is the directive name followed by a space? (to exclude `:`)
914 directive_ln.get(directive_name.len()..).is_some_and(|s| s.starts_with(' '))
915 // 2. is what is after that directive also a directive (ex: "only-x86 only-arm")
916 && KNOWN_DIRECTIVE_NAMES.contains(&trailing)
917 }
918 .then_some(trailing);
847 // If it looks like the user tried to put two directives on the same line
848 // (e.g. `//@ only-linux only-x86_64`), signal an error, because the
849 // second "directive" would actually be ignored with no effect.
850 let trailing_directive = directive_ln
851 .remark_after_space()
852 .map(|remark| remark.trim_start().split(' ').next().unwrap())
853 .filter(|token| KNOWN_DIRECTIVE_NAMES.contains(token));
919854
920855 CheckDirectiveResult { is_known_directive, trailing_directive }
921856}
922857
923const COMPILETEST_DIRECTIVE_PREFIX: &str = "//@";
924
925858fn iter_directives(
926859 mode: TestMode,
927860 poisoned: &mut bool,
......@@ -939,15 +872,17 @@ fn iter_directives(
939872 // FIXME(jieyouxu): I feel like there's a better way to do this, leaving for later.
940873 if mode == TestMode::CoverageRun {
941874 let extra_directives: &[&str] = &[
942 "needs-profiler-runtime",
875 "//@ needs-profiler-runtime",
943876 // FIXME(pietroalbini): this test currently does not work on cross-compiled targets
944877 // because remote-test is not capable of sending back the *.profraw files generated by
945878 // the LLVM instrumentation.
946 "ignore-cross-compile",
879 "//@ ignore-cross-compile",
947880 ];
948881 // Process the extra implied directives, with a dummy line number of 0.
949 for raw_directive in extra_directives {
950 it(DirectiveLine { line_number: 0, revision: None, raw_directive });
882 for directive_str in extra_directives {
883 let directive_line = line_directive(0, directive_str)
884 .unwrap_or_else(|| panic!("bad extra-directive line: {directive_str:?}"));
885 it(directive_line);
951886 }
952887 }
953888
......@@ -977,7 +912,7 @@ fn iter_directives(
977912
978913 error!(
979914 "{testfile}:{line_number}: detected unknown compiletest test directive `{}`",
980 directive_line.raw_directive,
915 directive_line.display(),
981916 );
982917
983918 return;
......@@ -1073,13 +1008,9 @@ impl Config {
10731008 }
10741009
10751010 fn parse_custom_normalization(&self, line: &DirectiveLine<'_>) -> Option<NormalizeRule> {
1076 let &DirectiveLine { raw_directive, .. } = line;
1077
1078 // FIXME(Zalathar): Integrate name/value splitting into `DirectiveLine`
1079 // instead of doing it here.
1080 let (directive_name, raw_value) = raw_directive.split_once(':')?;
1011 let &DirectiveLine { name, .. } = line;
10811012
1082 let kind = match directive_name {
1013 let kind = match name {
10831014 "normalize-stdout" => NormalizeKind::Stdout,
10841015 "normalize-stderr" => NormalizeKind::Stderr,
10851016 "normalize-stderr-32bit" => NormalizeKind::Stderr32bit,
......@@ -1087,21 +1018,20 @@ impl Config {
10871018 _ => return None,
10881019 };
10891020
1090 let Some((regex, replacement)) = parse_normalize_rule(raw_value) else {
1091 error!("couldn't parse custom normalization rule: `{raw_directive}`");
1092 help!("expected syntax is: `{directive_name}: \"REGEX\" -> \"REPLACEMENT\"`");
1021 let Some((regex, replacement)) = line.value_after_colon().and_then(parse_normalize_rule)
1022 else {
1023 error!("couldn't parse custom normalization rule: `{}`", line.display());
1024 help!("expected syntax is: `{name}: \"REGEX\" -> \"REPLACEMENT\"`");
10931025 panic!("invalid normalization rule detected");
10941026 };
10951027 Some(NormalizeRule { kind, regex, replacement })
10961028 }
10971029
10981030 fn parse_name_directive(&self, line: &DirectiveLine<'_>, directive: &str) -> bool {
1099 let &DirectiveLine { raw_directive: line, .. } = line;
1100
1101 // Ensure the directive is a whole word. Do not match "ignore-x86" when
1102 // the line says "ignore-x86_64".
1103 line.starts_with(directive)
1104 && matches!(line.as_bytes().get(directive.len()), None | Some(&b' ') | Some(&b':'))
1031 // FIXME(Zalathar): Ideally, this should raise an error if a name-only
1032 // directive is followed by a colon, since that's the wrong syntax.
1033 // But we would need to fix tests that rely on the current behaviour.
1034 line.name == directive
11051035 }
11061036
11071037 fn parse_name_value_directive(
......@@ -1110,22 +1040,26 @@ impl Config {
11101040 directive: &str,
11111041 testfile: &Utf8Path,
11121042 ) -> Option<String> {
1113 let &DirectiveLine { line_number, raw_directive: line, .. } = line;
1114
1115 let colon = directive.len();
1116 if line.starts_with(directive) && line.as_bytes().get(colon) == Some(&b':') {
1117 let value = line[(colon + 1)..].to_owned();
1118 debug!("{}: {}", directive, value);
1119 let value = expand_variables(value, self);
1120 if value.is_empty() {
1121 error!("{testfile}:{line_number}: empty value for directive `{directive}`");
1122 help!("expected syntax is: `{directive}: value`");
1123 panic!("empty directive value detected");
1124 }
1125 Some(value)
1126 } else {
1127 None
1043 let &DirectiveLine { line_number, .. } = line;
1044
1045 if line.name != directive {
1046 return None;
1047 };
1048
1049 // FIXME(Zalathar): This silently discards directives with a matching
1050 // name but no colon. Unfortunately, some directives (e.g. "pp-exact")
1051 // currently rely on _not_ panicking here.
1052 let value = line.value_after_colon()?;
1053 debug!("{}: {}", directive, value);
1054 let value = expand_variables(value.to_owned(), self);
1055
1056 if value.is_empty() {
1057 error!("{testfile}:{line_number}: empty value for directive `{directive}`");
1058 help!("expected syntax is: `{directive}: value`");
1059 panic!("empty directive value detected");
11281060 }
1061
1062 Some(value)
11291063 }
11301064
11311065 fn set_name_directive(&self, line: &DirectiveLine<'_>, directive: &str, value: &mut bool) {
......@@ -1515,14 +1449,14 @@ pub(crate) fn make_test_description<R: Read>(
15151449}
15161450
15171451fn ignore_cdb(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1518 let &DirectiveLine { raw_directive: line, .. } = line;
1519
15201452 if config.debugger != Some(Debugger::Cdb) {
15211453 return IgnoreDecision::Continue;
15221454 }
15231455
15241456 if let Some(actual_version) = config.cdb_version {
1525 if let Some(rest) = line.strip_prefix("min-cdb-version:").map(str::trim) {
1457 if line.name == "min-cdb-version"
1458 && let Some(rest) = line.value_after_colon().map(str::trim)
1459 {
15261460 let min_version = extract_cdb_version(rest).unwrap_or_else(|| {
15271461 panic!("couldn't parse version range: {:?}", rest);
15281462 });
......@@ -1540,14 +1474,14 @@ fn ignore_cdb(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
15401474}
15411475
15421476fn ignore_gdb(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1543 let &DirectiveLine { raw_directive: line, .. } = line;
1544
15451477 if config.debugger != Some(Debugger::Gdb) {
15461478 return IgnoreDecision::Continue;
15471479 }
15481480
15491481 if let Some(actual_version) = config.gdb_version {
1550 if let Some(rest) = line.strip_prefix("min-gdb-version:").map(str::trim) {
1482 if line.name == "min-gdb-version"
1483 && let Some(rest) = line.value_after_colon().map(str::trim)
1484 {
15511485 let (start_ver, end_ver) = extract_version_range(rest, extract_gdb_version)
15521486 .unwrap_or_else(|| {
15531487 panic!("couldn't parse version range: {:?}", rest);
......@@ -1563,7 +1497,9 @@ fn ignore_gdb(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
15631497 reason: format!("ignored when the GDB version is lower than {rest}"),
15641498 };
15651499 }
1566 } else if let Some(rest) = line.strip_prefix("ignore-gdb-version:").map(str::trim) {
1500 } else if line.name == "ignore-gdb-version"
1501 && let Some(rest) = line.value_after_colon().map(str::trim)
1502 {
15671503 let (min_version, max_version) = extract_version_range(rest, extract_gdb_version)
15681504 .unwrap_or_else(|| {
15691505 panic!("couldn't parse version range: {:?}", rest);
......@@ -1590,14 +1526,14 @@ fn ignore_gdb(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
15901526}
15911527
15921528fn ignore_lldb(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1593 let &DirectiveLine { raw_directive: line, .. } = line;
1594
15951529 if config.debugger != Some(Debugger::Lldb) {
15961530 return IgnoreDecision::Continue;
15971531 }
15981532
15991533 if let Some(actual_version) = config.lldb_version {
1600 if let Some(rest) = line.strip_prefix("min-lldb-version:").map(str::trim) {
1534 if line.name == "min-lldb-version"
1535 && let Some(rest) = line.value_after_colon().map(str::trim)
1536 {
16011537 let min_version = rest.parse().unwrap_or_else(|e| {
16021538 panic!("Unexpected format of LLDB version string: {}\n{:?}", rest, e);
16031539 });
src/tools/compiletest/src/directives/auxiliary.rs+1-3
......@@ -50,9 +50,7 @@ pub(super) fn parse_and_update_aux(
5050 testfile: &Utf8Path,
5151 aux: &mut AuxProps,
5252) {
53 let &DirectiveLine { raw_directive: ln, .. } = directive_line;
54
55 if !(ln.starts_with("aux-") || ln.starts_with("proc-macro")) {
53 if !(directive_line.name.starts_with("aux-") || directive_line.name == "proc-macro") {
5654 return;
5755 }
5856
src/tools/compiletest/src/directives/cfg.rs+11-15
......@@ -6,8 +6,8 @@ use crate::directives::{DirectiveLine, IgnoreDecision};
66const EXTRA_ARCHS: &[&str] = &["spirv"];
77
88pub(super) fn handle_ignore(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
9 let parsed = parse_cfg_name_directive(config, line, "ignore");
10 let &DirectiveLine { raw_directive: line, .. } = line;
9 let parsed = parse_cfg_name_directive(config, line, "ignore-");
10 let line = line.display();
1111
1212 match parsed.outcome {
1313 MatchOutcome::NoMatch => IgnoreDecision::Continue,
......@@ -24,8 +24,8 @@ pub(super) fn handle_ignore(config: &Config, line: &DirectiveLine<'_>) -> Ignore
2424}
2525
2626pub(super) fn handle_only(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
27 let parsed = parse_cfg_name_directive(config, line, "only");
28 let &DirectiveLine { raw_directive: line, .. } = line;
27 let parsed = parse_cfg_name_directive(config, line, "only-");
28 let line = line.display();
2929
3030 match parsed.outcome {
3131 MatchOutcome::Match => IgnoreDecision::Continue,
......@@ -50,18 +50,14 @@ fn parse_cfg_name_directive<'a>(
5050 line: &'a DirectiveLine<'a>,
5151 prefix: &str,
5252) -> ParsedNameDirective<'a> {
53 let &DirectiveLine { raw_directive: line, .. } = line;
54
55 if !line.as_bytes().starts_with(prefix.as_bytes()) {
56 return ParsedNameDirective::not_a_directive();
57 }
58 if line.as_bytes().get(prefix.len()) != Some(&b'-') {
53 let Some(name) = line.name.strip_prefix(prefix) else {
5954 return ParsedNameDirective::not_a_directive();
60 }
61 let line = &line[prefix.len() + 1..];
55 };
6256
63 let (name, comment) =
64 line.split_once(&[':', ' ']).map(|(l, c)| (l, Some(c))).unwrap_or((line, None));
57 // FIXME(Zalathar): This currently allows either a space or a colon, and
58 // treats any "value" after a colon as though it were a remark.
59 // We should instead forbid the colon syntax for these directives.
60 let comment = line.remark_after_space().or_else(|| line.value_after_colon());
6561
6662 // Some of the matchers might be "" depending on what the target information is. To avoid
6763 // problems we outright reject empty directives.
......@@ -269,7 +265,7 @@ fn parse_cfg_name_directive<'a>(
269265 message: "when performing tests on dist toolchain"
270266 }
271267
272 if prefix == "ignore" && outcome == MatchOutcome::Invalid {
268 if prefix == "ignore-" && outcome == MatchOutcome::Invalid {
273269 // Don't error out for ignore-tidy-* diretives, as those are not handled by compiletest.
274270 if name.starts_with("tidy-") {
275271 outcome = MatchOutcome::External;
src/tools/compiletest/src/directives/line.rs created+108
......@@ -0,0 +1,108 @@
1use std::fmt;
2
3const COMPILETEST_DIRECTIVE_PREFIX: &str = "//@";
4
5/// If the given line begins with the appropriate comment prefix for a directive,
6/// returns a struct containing various parts of the directive.
7pub(crate) fn line_directive<'line>(
8 line_number: usize,
9 original_line: &'line str,
10) -> Option<DirectiveLine<'line>> {
11 // Ignore lines that don't start with the comment prefix.
12 let after_comment =
13 original_line.trim_start().strip_prefix(COMPILETEST_DIRECTIVE_PREFIX)?.trim_start();
14
15 let revision;
16 let raw_directive;
17
18 if let Some(after_open_bracket) = after_comment.strip_prefix('[') {
19 // A comment like `//@[foo]` only applies to revision `foo`.
20 let Some((line_revision, after_close_bracket)) = after_open_bracket.split_once(']') else {
21 panic!(
22 "malformed condition directive: expected `{COMPILETEST_DIRECTIVE_PREFIX}[foo]`, found `{original_line}`"
23 )
24 };
25
26 revision = Some(line_revision);
27 raw_directive = after_close_bracket.trim_start();
28 } else {
29 revision = None;
30 raw_directive = after_comment;
31 };
32
33 // The directive name ends at the first occurrence of colon, space, or end-of-string.
34 let name = raw_directive.split([':', ' ']).next().expect("split is never empty");
35
36 Some(DirectiveLine { line_number, revision, raw_directive, name })
37}
38
39/// The (partly) broken-down contents of a line containing a test directive,
40/// which `iter_directives` passes to its callback function.
41///
42/// For example:
43///
44/// ```text
45/// //@ compile-flags: -O
46/// ^^^^^^^^^^^^^^^^^ raw_directive
47/// ^^^^^^^^^^^^^ name
48///
49/// //@ [foo] compile-flags: -O
50/// ^^^ revision
51/// ^^^^^^^^^^^^^^^^^ raw_directive
52/// ^^^^^^^^^^^^^ name
53/// ```
54pub(crate) struct DirectiveLine<'ln> {
55 pub(crate) line_number: usize,
56
57 /// Some test directives start with a revision name in square brackets
58 /// (e.g. `[foo]`), and only apply to that revision of the test.
59 /// If present, this field contains the revision name (e.g. `foo`).
60 pub(crate) revision: Option<&'ln str>,
61
62 /// The main part of the directive, after removing the comment prefix
63 /// and the optional revision specifier.
64 ///
65 /// This is "raw" because the directive's name and colon-separated value
66 /// (if present) have not yet been extracted or checked.
67 raw_directive: &'ln str,
68
69 /// Name of the directive.
70 ///
71 /// Invariant: `self.raw_directive.starts_with(self.name)`
72 pub(crate) name: &'ln str,
73}
74
75impl<'ln> DirectiveLine<'ln> {
76 pub(crate) fn applies_to_test_revision(&self, test_revision: Option<&str>) -> bool {
77 self.revision.is_none() || self.revision == test_revision
78 }
79
80 /// Helper method used by `value_after_colon` and `remark_after_space`.
81 /// Don't call this directly.
82 fn rest_after_separator(&self, separator: u8) -> Option<&'ln str> {
83 let n = self.name.len();
84 if self.raw_directive.as_bytes().get(n) != Some(&separator) {
85 return None;
86 }
87
88 Some(&self.raw_directive[n + 1..])
89 }
90
91 /// If this directive uses `name: value` syntax, returns the part after
92 /// the colon character.
93 pub(crate) fn value_after_colon(&self) -> Option<&'ln str> {
94 self.rest_after_separator(b':')
95 }
96
97 /// If this directive uses `name remark` syntax, returns the part after
98 /// the separating space.
99 pub(crate) fn remark_after_space(&self) -> Option<&'ln str> {
100 self.rest_after_separator(b' ')
101 }
102
103 /// Allows callers to print `raw_directive` if necessary,
104 /// without accessing the field directly.
105 pub(crate) fn display(&self) -> impl fmt::Display {
106 self.raw_directive
107 }
108}
src/tools/compiletest/src/directives/needs.rs+4-11
......@@ -181,17 +181,10 @@ pub(super) fn handle_needs(
181181 },
182182 ];
183183
184 let &DirectiveLine { raw_directive: ln, .. } = ln;
184 let &DirectiveLine { name, .. } = ln;
185185
186 let (name, rest) = match ln.split_once([':', ' ']) {
187 Some((name, rest)) => (name, Some(rest)),
188 None => (ln, None),
189 };
190
191 // FIXME(jieyouxu): tighten up this parsing to reject using both `:` and ` ` as means to
192 // delineate value.
193186 if name == "needs-target-has-atomic" {
194 let Some(rest) = rest else {
187 let Some(rest) = ln.value_after_colon() else {
195188 return IgnoreDecision::Error {
196189 message: "expected `needs-target-has-atomic` to have a comma-separated list of atomic widths".to_string(),
197190 };
......@@ -233,7 +226,7 @@ pub(super) fn handle_needs(
233226
234227 // FIXME(jieyouxu): share multi-value directive logic with `needs-target-has-atomic` above.
235228 if name == "needs-crate-type" {
236 let Some(rest) = rest else {
229 let Some(rest) = ln.value_after_colon() else {
237230 return IgnoreDecision::Error {
238231 message:
239232 "expected `needs-crate-type` to have a comma-separated list of crate types"
......@@ -292,7 +285,7 @@ pub(super) fn handle_needs(
292285 break;
293286 } else {
294287 return IgnoreDecision::Ignore {
295 reason: if let Some(comment) = rest {
288 reason: if let Some(comment) = ln.remark_after_space() {
296289 format!("{} ({})", need.ignore_reason, comment.trim())
297290 } else {
298291 need.ignore_reason.into()
src/tools/compiletest/src/directives/tests.rs+6-5
......@@ -3,12 +3,11 @@ use std::io::Read;
33use camino::Utf8Path;
44use semver::Version;
55
6use super::{
6use crate::common::{Config, Debugger, TestMode};
7use crate::directives::{
78 DirectivesCache, EarlyProps, Edition, EditionRange, extract_llvm_version,
8 extract_version_range, iter_directives, parse_normalize_rule,
9 extract_version_range, iter_directives, line_directive, parse_edition, parse_normalize_rule,
910};
10use crate::common::{Config, Debugger, TestMode};
11use crate::directives::parse_edition;
1211use crate::executor::{CollectedTestDesc, ShouldPanic};
1312
1413fn make_test_description<R: Read>(
......@@ -955,7 +954,9 @@ fn test_needs_target_std() {
955954
956955fn parse_edition_range(line: &str) -> Option<EditionRange> {
957956 let config = cfg().build();
958 let line = super::DirectiveLine { line_number: 0, revision: None, raw_directive: line };
957
958 let line_with_comment = format!("//@ {line}");
959 let line = line_directive(0, &line_with_comment).unwrap();
959960
960961 super::parse_edition_range(&config, &line, "tmp.rs".into())
961962}
tests/ui/fn/invalid-sugg-for-unused-fn-arg-147303.rs created+13
......@@ -0,0 +1,13 @@
1// Regression test for <https://github.com/rust-lang/rust/issues/147303>.
2
3#![deny(unused_assignments, unused_variables)]
4
5mod m1 {
6 const _MAX_FMTVER_X1X_EVENTNUM: i32 = 0;
7}
8
9mod m2 {
10 fn fun(rough: i32) {} //~ERROR unused variable
11}
12
13fn main() {}
tests/ui/fn/invalid-sugg-for-unused-fn-arg-147303.stderr created+14
......@@ -0,0 +1,14 @@
1error: unused variable: `rough`
2 --> $DIR/invalid-sugg-for-unused-fn-arg-147303.rs:10:12
3 |
4LL | fn fun(rough: i32) {}
5 | ^^^^^ help: if this is intentional, prefix it with an underscore: `_rough`
6 |
7note: the lint level is defined here
8 --> $DIR/invalid-sugg-for-unused-fn-arg-147303.rs:3:29
9 |
10LL | #![deny(unused_assignments, unused_variables)]
11 | ^^^^^^^^^^^^^^^^
12
13error: aborting due to 1 previous error
14
tests/ui/lint/non-snake-case/lint-uppercase-variables.stderr+1-10
......@@ -58,16 +58,7 @@ warning: unused variable: `Foo`
5858 --> $DIR/lint-uppercase-variables.rs:33:17
5959 |
6060LL | fn in_param(Foo: foo::Foo) {}
61 | ^^^
62 |
63help: if this is intentional, prefix it with an underscore
64 |
65LL | fn in_param(_Foo: foo::Foo) {}
66 | +
67help: you might have meant to pattern match on the similarly named variant `Foo`
68 |
69LL | fn in_param(foo::Foo::Foo: foo::Foo) {}
70 | ++++++++++
61 | ^^^ help: if this is intentional, prefix it with an underscore: `_Foo`
7162
7263error: structure field `X` should have a snake case name
7364 --> $DIR/lint-uppercase-variables.rs:10:5