about summary refs log tree commit diff
path: root/tests
diff options
context:
space:
mode:
authorMatthias Krüger <476013+matthiaskrgr@users.noreply.github.com>2025-09-09 17:32:20 +0200
committerGitHub <noreply@github.com>2025-09-09 17:32:20 +0200
commit12e548704e5c21c6f90a73937b3a7279119542c0 (patch)
treed32a7fa7d4e10e28ff744bf13d9cfd63b47e958d /tests
parentbe8de5d6a0fc5cb2924e174a809a0aff303f281a (diff)
parentddd99930f34b79f209c61cc25706a1dac1173762 (diff)
downloadrust-12e548704e5c21c6f90a73937b3a7279119542c0.tar.gz
rust-12e548704e5c21c6f90a73937b3a7279119542c0.zip
Rollup merge of #145463 - jieyouxu:error-suffix, r=fmease
Reject invalid literal suffixes in tuple indexing, tuple struct indexing, and struct field name position

Tracking issue: rust-lang/rust#60210
Closes rust-lang/rust#60210

## Summary

Bump the ["suffixes on a tuple index are invalid" non-lint pseudo future-incompatibility warning (#60210)][issue-60210][^non-lint] to a **hard error** across all editions, rejecting the remaining carve outs from accidentally accepted invalid suffixes since Rust **1.27**.

- We accidentally accepted invalid suffixes in tuple indexing positions in Rust **1.27**. Originally reported at https://github.com/rust-lang/rust/issues/59418.
- We tried to hard reject all invalid suffixes in https://github.com/rust-lang/rust/pull/59421, but unfortunately it turns out there were proc macros accidentally relying on it: https://github.com/rust-lang/rust/issues/60138.
- We temporarily accepted `{i,u}{32,size}` in https://github.com/rust-lang/rust/pull/60186 (the "*carve outs*") to mitigate *immediate* ecosystem impact, but it came with an FCW warning indicating that we wanted to reject it after a few Rust releases.
- Now (1.89.0) is a few Rust releases later (1.35.0), thus I'm proposing to **also reject the carve outs**.
    - `std::mem::offset_of!` stabilized in Rust **1.77.0** happens to use the same "don't expect suffix" code path which has the carve outs, so it also accepted the carve out suffixes. I'm proposing to **reject this case as well**.

## What specifically breaks?

Code that still relied on invalid `{i,u}{32,size}` suffixes being temporarily accepted by rust-lang/rust#60186 as an ecosystem impact mitigation measure (cf. rust-lang/rust#60138). Specifically, the following cases (particularly the construction of these forms in proc macros like reported in rust-lang/rust#60138):

### Position 1: Invalid `{i,u}{32,size}` suffixes in tuple indexing

```rs
fn main() {
    let _x = (42,).0invalid; // Already error, already rejected by #59421
    let _x = (42,).0i8;      // Already error, not one of the #60186 carve outs.
    let _x = (42,).0usize;   // warning: suffixes on a tuple index are invalid
}
```

### Position 2: Invalid `{i,u}{32,size}` suffixes in tuple struct indexing

```rs
fn main() {
    struct X(i32);
    let _x = X(42);
	let _x = _x.0invalid; // Already error, already rejected by #59421
    let _x = _x.0i8;      // Already error, not one of the #60186 carve outs.
    let _x = _x.0usize;   // warning: suffixes on a tuple index are invalid
}
```

### Position 3: Invalid `{i,u}{32,size}` suffixes in numeric struct field names

```rs
fn main() {
    struct X(i32, i32, i32);
    let _x = X(1, 2, 3);
    let _y = X { 0usize: 42, 1: 42, 2: 42 };    // warning: suffixes on a tuple index are invalid
	match _x {
        X { 0usize: 1, 1: 2, 2: 3 } => todo!(), // warning: suffixes on a tuple index are invalid
        _ => {}
    }
}
```

### Position 4: Invalid `{i,u}{32,size}` suffixes in `std::mem::offset_of!`

While investigating the warning, unfortunately I noticed `std::mem::offset_of!` also happens to use the "expect no suffix" code path which had the carve outs. So this was accepted since Rust **1.77.0** with the same FCW:

```rs
fn main() {
    #[repr(C)]
    pub struct Struct<T>(u8, T);

    assert_eq!(std::mem::offset_of!(Struct<u32>, 0usize), 0);
    //~^ WARN suffixes on a tuple index are invalid
}
```

### The above forms in proc macros

For instance, constructions like (see tracking issue rust-lang/rust#60210):

```rs
let i = 0;
quote! { foo.$i }
```

where the user needs to actually write

```rs
let i = syn::Index::from(0);
quote! { foo.$i }
```

### Crater results

Conducted a crater run (https://github.com/rust-lang/rust/pull/145463#issuecomment-3194920383).

- https://github.com/AmlingPalantir/r4/tree/256af3c72f094b298cd442097ef7c571d8001f29: genuine regression; "invalid suffix `usize`" in derive macro. Has a ton of other build warnings, last updated 6 years ago.
    - Exactly the kind of intended breakage. Minimized down to https://github.com/AmlingPalantir/r4/blob/256af3c72f094b298cd442097ef7c571d8001f29/validates_derive/src/lib.rs#L71-L75, where when interpolation uses `quote`'s `ToTokens` on a `usize` index (i.e. on tuple struct `Tup(())`), the generated suffix becomes `.0usize` (cf. Position 2).
    - Notified crate author of breakage in https://github.com/AmlingPalantir/r4/issues/1.
- Other failures are unrelated or spurious.

## Review remarks

- Commits 1-3 expands the test coverage to better reflect the current situation before doing any functional changes.
- Commit 4 is an intentional **breaking change**. We bump the non-lint "suffixes on a tuple index are invalid" warning into a hard error. Thus, this will need a crater run and a T-lang FCP.

## Tasks

- [x] Run crater to check if anyone is still relying on this being not a hard error. Determine degree of ecosystem breakage.
- [x] If degree of breakage seems acceptable, draft nomination report for T-lang for FCP.
- [x] Determine hard error on Edition 2024+, or on all editions.

## Accompanying Reference update

- https://github.com/rust-lang/reference/pull/1966

[^non-lint]: The FCW was implemented as a *non-lint* warning (meaning it has no associated lint name, and you can't `#![deny(..)]` it) because spans coming from proc macros could not be distinguished from regular field access. This warning was also intentionally impossible to silence. See https://github.com/rust-lang/rust/pull/60186#issuecomment-485581694.

[issue-60210]: https://github.com/rust-lang/rust/issues/60210
Diffstat (limited to 'tests')
-rw-r--r--tests/ui/parser/auxiliary/tuple-index-suffix-proc-macro-aux.rs33
-rw-r--r--tests/ui/parser/issues/issue-59418.rs18
-rw-r--r--tests/ui/parser/issues/issue-59418.stderr26
-rw-r--r--tests/ui/parser/tuple-index-suffix-proc-macro.rs32
-rw-r--r--tests/ui/parser/tuple-index-suffix-proc-macro.stderr26
-rw-r--r--tests/ui/parser/tuple-index-suffix.rs79
-rw-r--r--tests/ui/parser/tuple-index-suffix.stderr134
7 files changed, 304 insertions, 44 deletions
diff --git a/tests/ui/parser/auxiliary/tuple-index-suffix-proc-macro-aux.rs b/tests/ui/parser/auxiliary/tuple-index-suffix-proc-macro-aux.rs
new file mode 100644
index 00000000000..a5084b55aac
--- /dev/null
+++ b/tests/ui/parser/auxiliary/tuple-index-suffix-proc-macro-aux.rs
@@ -0,0 +1,33 @@
+#![feature(proc_macro_quote, proc_macro_span)]
+
+extern crate proc_macro;
+
+use proc_macro::{Ident, Literal, Span, TokenStream, TokenTree, quote};
+
+#[proc_macro]
+pub fn bad_tup_indexing(input: TokenStream) -> TokenStream {
+    let tt = input.into_iter().next().unwrap();
+    let TokenTree::Literal(indexing_expr) = tt else {
+        unreachable!();
+    };
+    quote! { (42,).$indexing_expr }
+}
+
+// Expects {IDENT, COMMA, LITERAL}
+#[proc_macro]
+pub fn bad_tup_struct_indexing(input: TokenStream) -> TokenStream {
+    let mut input = input.into_iter();
+
+    let ident = input.next().unwrap();
+    let _comma = input.next().unwrap();
+    let lit = input.next().unwrap();
+
+    let TokenTree::Ident(ident) = ident else {
+        unreachable!("id");
+    };
+    let TokenTree::Literal(indexing_expr) = lit else {
+        unreachable!("lit");
+    };
+
+    quote! { $ident.$indexing_expr }
+}
diff --git a/tests/ui/parser/issues/issue-59418.rs b/tests/ui/parser/issues/issue-59418.rs
deleted file mode 100644
index 0fa191d4a7e..00000000000
--- a/tests/ui/parser/issues/issue-59418.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-struct X(i32,i32,i32);
-
-fn main() {
-    let a = X(1, 2, 3);
-    let b = a.1suffix;
-    //~^ ERROR suffixes on a tuple index are invalid
-    println!("{}", b);
-    let c = (1, 2, 3);
-    let d = c.1suffix;
-    //~^ ERROR suffixes on a tuple index are invalid
-    println!("{}", d);
-    let s = X { 0suffix: 0, 1: 1, 2: 2 };
-    //~^ ERROR suffixes on a tuple index are invalid
-    match s {
-        X { 0suffix: _, .. } => {}
-        //~^ ERROR suffixes on a tuple index are invalid
-    }
-}
diff --git a/tests/ui/parser/issues/issue-59418.stderr b/tests/ui/parser/issues/issue-59418.stderr
deleted file mode 100644
index 347051e9f92..00000000000
--- a/tests/ui/parser/issues/issue-59418.stderr
+++ /dev/null
@@ -1,26 +0,0 @@
-error: suffixes on a tuple index are invalid
-  --> $DIR/issue-59418.rs:5:15
-   |
-LL |     let b = a.1suffix;
-   |               ^^^^^^^ invalid suffix `suffix`
-
-error: suffixes on a tuple index are invalid
-  --> $DIR/issue-59418.rs:9:15
-   |
-LL |     let d = c.1suffix;
-   |               ^^^^^^^ invalid suffix `suffix`
-
-error: suffixes on a tuple index are invalid
-  --> $DIR/issue-59418.rs:12:17
-   |
-LL |     let s = X { 0suffix: 0, 1: 1, 2: 2 };
-   |                 ^^^^^^^ invalid suffix `suffix`
-
-error: suffixes on a tuple index are invalid
-  --> $DIR/issue-59418.rs:15:13
-   |
-LL |         X { 0suffix: _, .. } => {}
-   |             ^^^^^^^ invalid suffix `suffix`
-
-error: aborting due to 4 previous errors
-
diff --git a/tests/ui/parser/tuple-index-suffix-proc-macro.rs b/tests/ui/parser/tuple-index-suffix-proc-macro.rs
new file mode 100644
index 00000000000..557c67738d3
--- /dev/null
+++ b/tests/ui/parser/tuple-index-suffix-proc-macro.rs
@@ -0,0 +1,32 @@
+//! See #59418.
+//!
+//! Like `tuple-index-suffix.rs`, but exercises the proc-macro interaction.
+
+//@ proc-macro: tuple-index-suffix-proc-macro-aux.rs
+
+extern crate tuple_index_suffix_proc_macro_aux;
+use tuple_index_suffix_proc_macro_aux as aux;
+
+fn main() {
+    struct TupStruct(i32);
+    let tup_struct = TupStruct(42);
+
+    // Previously, #60186 had carve outs for `{i,u}{32,usize}` as non-lint pseudo-FCW warnings. Now,
+    // they all hard error.
+
+    aux::bad_tup_indexing!(0usize);
+    //~^ ERROR suffixes on a tuple index are invalid
+    aux::bad_tup_struct_indexing!(tup_struct, 0isize);
+    //~^ ERROR suffixes on a tuple index are invalid
+
+    // Not part of the #60186 carve outs.
+
+    aux::bad_tup_indexing!(0u8);
+    //~^ ERROR suffixes on a tuple index are invalid
+    aux::bad_tup_struct_indexing!(tup_struct, 0u64);
+    //~^ ERROR suffixes on a tuple index are invalid
+
+    // NOTE: didn't bother with trying to figure out how to generate `struct P { 0u32: u32 }` using
+    // *only* `proc_macro` without help with `syn`/`quote`, looks like you can't with just
+    // `proc_macro::quote`?
+}
diff --git a/tests/ui/parser/tuple-index-suffix-proc-macro.stderr b/tests/ui/parser/tuple-index-suffix-proc-macro.stderr
new file mode 100644
index 00000000000..47d179d3555
--- /dev/null
+++ b/tests/ui/parser/tuple-index-suffix-proc-macro.stderr
@@ -0,0 +1,26 @@
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix-proc-macro.rs:17:28
+   |
+LL |     aux::bad_tup_indexing!(0usize);
+   |                            ^^^^^^ invalid suffix `usize`
+
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix-proc-macro.rs:19:47
+   |
+LL |     aux::bad_tup_struct_indexing!(tup_struct, 0isize);
+   |                                               ^^^^^^ invalid suffix `isize`
+
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix-proc-macro.rs:24:28
+   |
+LL |     aux::bad_tup_indexing!(0u8);
+   |                            ^^^ invalid suffix `u8`
+
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix-proc-macro.rs:26:47
+   |
+LL |     aux::bad_tup_struct_indexing!(tup_struct, 0u64);
+   |                                               ^^^^ invalid suffix `u64`
+
+error: aborting due to 4 previous errors
+
diff --git a/tests/ui/parser/tuple-index-suffix.rs b/tests/ui/parser/tuple-index-suffix.rs
new file mode 100644
index 00000000000..c4769500005
--- /dev/null
+++ b/tests/ui/parser/tuple-index-suffix.rs
@@ -0,0 +1,79 @@
+//! Regression test for both the original regression in #59418 where invalid suffixes in indexing
+//! positions were accidentally accepted, and also for the removal of the temporary carve out that
+//! mitigated ecosystem impact following trying to reject #59418 (this was implemented as a FCW
+//! tracked in #60210).
+//!
+//! Check that we hard error on invalid suffixes in tuple indexing subexpressions and struct numeral
+//! field names.
+
+struct X(i32,i32,i32);
+
+fn main() {
+    let tup_struct = X(1, 2, 3);
+    let invalid_tup_struct_suffix = tup_struct.0suffix;
+    //~^ ERROR suffixes on a tuple index are invalid
+    let previous_carve_out_tup_struct_suffix = tup_struct.0i32;
+    //~^ ERROR suffixes on a tuple index are invalid
+
+    let tup = (1, 2, 3);
+    let invalid_tup_suffix = tup.0suffix;
+    //~^ ERROR suffixes on a tuple index are invalid
+    let previous_carve_out_tup_suffix = tup.0u32;
+    //~^ ERROR suffixes on a tuple index are invalid
+
+    numeral_struct_field_name_suffix_invalid();
+    numeral_struct_field_name_suffix_previous_carve_out();
+}
+
+// Previously, there were very limited carve outs as a ecosystem impact mitigation implemented in
+// #60186. *Only* `{i,u}{32,usize}` suffixes were temporarily accepted. Now, they all hard error.
+fn previous_carve_outs() {
+    // Previously temporarily accepted by a pseudo-FCW (#60210), now hard error.
+
+    let previous_carve_out_i32 = (42,).0i32;     //~ ERROR suffixes on a tuple index are invalid
+    let previous_carve_out_i32 = (42,).0u32;     //~ ERROR suffixes on a tuple index are invalid
+    let previous_carve_out_isize = (42,).0isize; //~ ERROR suffixes on a tuple index are invalid
+    let previous_carve_out_usize = (42,).0usize; //~ ERROR suffixes on a tuple index are invalid
+
+    // Not part of the carve outs!
+    let error_i8 = (42,).0i8;      //~ ERROR suffixes on a tuple index are invalid
+    let error_u8 = (42,).0u8;      //~ ERROR suffixes on a tuple index are invalid
+    let error_i16 = (42,).0i16;    //~ ERROR suffixes on a tuple index are invalid
+    let error_u16 = (42,).0u16;    //~ ERROR suffixes on a tuple index are invalid
+    let error_i64 = (42,).0i64;    //~ ERROR suffixes on a tuple index are invalid
+    let error_u64 = (42,).0u64;    //~ ERROR suffixes on a tuple index are invalid
+    let error_i128 = (42,).0i128;  //~ ERROR suffixes on a tuple index are invalid
+    let error_u128 = (42,).0u128;  //~ ERROR suffixes on a tuple index are invalid
+}
+
+fn numeral_struct_field_name_suffix_invalid() {
+    let invalid_struct_name = X { 0suffix: 0, 1: 1, 2: 2 };
+    //~^ ERROR suffixes on a tuple index are invalid
+    match invalid_struct_name {
+        X { 0suffix: _, .. } => {}
+        //~^ ERROR suffixes on a tuple index are invalid
+    }
+}
+
+fn numeral_struct_field_name_suffix_previous_carve_out() {
+    let carve_out_struct_name = X { 0u32: 0, 1: 1, 2: 2 };
+    //~^ ERROR suffixes on a tuple index are invalid
+    match carve_out_struct_name {
+        X { 0u32: _, .. } => {}
+        //~^ ERROR suffixes on a tuple index are invalid
+    }
+}
+
+// Unfortunately, it turns out `std::mem::offset_of!` uses the same expect suffix code path.
+fn offset_of_suffix() {
+    #[repr(C)]
+    pub struct Struct<T>(u8, T);
+
+    // Previous pseudo-FCW carve outs
+    assert_eq!(std::mem::offset_of!(Struct<u32>, 0usize), 0);
+    //~^ ERROR suffixes on a tuple index are invalid
+
+    // Not part of carve outs
+    assert_eq!(std::mem::offset_of!(Struct<u32>, 0u8), 0);
+    //~^ ERROR suffixes on a tuple index are invalid
+}
diff --git a/tests/ui/parser/tuple-index-suffix.stderr b/tests/ui/parser/tuple-index-suffix.stderr
new file mode 100644
index 00000000000..6d96c6d3cbf
--- /dev/null
+++ b/tests/ui/parser/tuple-index-suffix.stderr
@@ -0,0 +1,134 @@
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix.rs:13:48
+   |
+LL |     let invalid_tup_struct_suffix = tup_struct.0suffix;
+   |                                                ^^^^^^^ invalid suffix `suffix`
+
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix.rs:15:59
+   |
+LL |     let previous_carve_out_tup_struct_suffix = tup_struct.0i32;
+   |                                                           ^^^^ invalid suffix `i32`
+
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix.rs:19:34
+   |
+LL |     let invalid_tup_suffix = tup.0suffix;
+   |                                  ^^^^^^^ invalid suffix `suffix`
+
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix.rs:21:45
+   |
+LL |     let previous_carve_out_tup_suffix = tup.0u32;
+   |                                             ^^^^ invalid suffix `u32`
+
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix.rs:33:40
+   |
+LL |     let previous_carve_out_i32 = (42,).0i32;
+   |                                        ^^^^ invalid suffix `i32`
+
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix.rs:34:40
+   |
+LL |     let previous_carve_out_i32 = (42,).0u32;
+   |                                        ^^^^ invalid suffix `u32`
+
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix.rs:35:42
+   |
+LL |     let previous_carve_out_isize = (42,).0isize;
+   |                                          ^^^^^^ invalid suffix `isize`
+
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix.rs:36:42
+   |
+LL |     let previous_carve_out_usize = (42,).0usize;
+   |                                          ^^^^^^ invalid suffix `usize`
+
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix.rs:39:26
+   |
+LL |     let error_i8 = (42,).0i8;
+   |                          ^^^ invalid suffix `i8`
+
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix.rs:40:26
+   |
+LL |     let error_u8 = (42,).0u8;
+   |                          ^^^ invalid suffix `u8`
+
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix.rs:41:27
+   |
+LL |     let error_i16 = (42,).0i16;
+   |                           ^^^^ invalid suffix `i16`
+
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix.rs:42:27
+   |
+LL |     let error_u16 = (42,).0u16;
+   |                           ^^^^ invalid suffix `u16`
+
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix.rs:43:27
+   |
+LL |     let error_i64 = (42,).0i64;
+   |                           ^^^^ invalid suffix `i64`
+
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix.rs:44:27
+   |
+LL |     let error_u64 = (42,).0u64;
+   |                           ^^^^ invalid suffix `u64`
+
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix.rs:45:28
+   |
+LL |     let error_i128 = (42,).0i128;
+   |                            ^^^^^ invalid suffix `i128`
+
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix.rs:46:28
+   |
+LL |     let error_u128 = (42,).0u128;
+   |                            ^^^^^ invalid suffix `u128`
+
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix.rs:50:35
+   |
+LL |     let invalid_struct_name = X { 0suffix: 0, 1: 1, 2: 2 };
+   |                                   ^^^^^^^ invalid suffix `suffix`
+
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix.rs:53:13
+   |
+LL |         X { 0suffix: _, .. } => {}
+   |             ^^^^^^^ invalid suffix `suffix`
+
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix.rs:59:37
+   |
+LL |     let carve_out_struct_name = X { 0u32: 0, 1: 1, 2: 2 };
+   |                                     ^^^^ invalid suffix `u32`
+
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix.rs:62:13
+   |
+LL |         X { 0u32: _, .. } => {}
+   |             ^^^^ invalid suffix `u32`
+
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix.rs:73:50
+   |
+LL |     assert_eq!(std::mem::offset_of!(Struct<u32>, 0usize), 0);
+   |                                                  ^^^^^^ invalid suffix `usize`
+
+error: suffixes on a tuple index are invalid
+  --> $DIR/tuple-index-suffix.rs:77:50
+   |
+LL |     assert_eq!(std::mem::offset_of!(Struct<u32>, 0u8), 0);
+   |                                                  ^^^ invalid suffix `u8`
+
+error: aborting due to 22 previous errors
+