Skip to content

Conversation

jieyouxu
Copy link
Member

@jieyouxu jieyouxu commented Aug 19, 2025

Caution

This should only be merged after FCP on rust-lang/rust#145463 passes and the PR is itself merged.

This is being proposed to be removed in rust-lang/rust#145463 as we never intended for this to be valid syntax. The concrete example forms are in the nomination report in rust-lang/rust#145463 (comment).

Outdated discussion on the grammar

Note on TUPLE_INDEX

This production looks wrong to me

TUPLE_INDEX -> INTEGER_LITERAL

INTEGER_LITERAL covers all the decimal, binary, octal and hex literal forms, but AFAICT TUPLE_INDEX as implemented does not even accept the full range of decimal syntax. I.e.

DEC_LITERAL -> DEC_DIGIT (DEC_DIGIT|`_`)*

but e.g.

fn main() {
    let x = (1,);
    println!("{}", x.0_0); //~ ERROR no field `0_0` on type `({integer},)`
}

I believe the accepted TUPLE_INDEX grammar here is only

DEC_LITERAL -> DEC_DIGIT+

Let me know if that sounds right.

@ehuss
Copy link
Contributor

ehuss commented Aug 19, 2025

The grammar is defined by the AST. It is essentially the pre-expansion accepted text, or to think of it another way, it is the valid input to a macro metavariable.

I haven't looked at your PR to determine exactly what it does, but from what I can tell it rejects the suffix at parsing time. So I think the new grammar is something like:

TUPLE_INDEX -> DEC_LITERAL | BIN_LITERAL | OCT_LITERAL | HEX_LITERAL

Personally I think that is weird, and would be simpler if it could just be DEC_LITERAL. However, since it is an exact textual match, it still has the oddity that 04 is not the same as 4, so 🤷 .

@jieyouxu
Copy link
Member Author

jieyouxu commented Aug 19, 2025

It really does feel weird, because e.g.

let _x = (4,).0x0;
              ^^^

clearly does not work... before or after my PR.

@ehuss
Copy link
Contributor

ehuss commented Aug 19, 2025

To be clear, that does parse successfully. For example:

// Parse, but don't process:
#[cfg(false)]
fn f()  {
    let _x = (4,).0x0;
}

// Or via a macro:
macro_rules! m {
    ($e:expr) => {};
}

fn main() {
    m!((4,).0x0);
}

0x0 gives the same error message as 00. Neither is a textual match for 0.

@jieyouxu
Copy link
Member Author

Right... I'll update it to

TUPLE_INDEX -> DEC_LITERAL | BIN_LITERAL | OCT_LITERAL | HEX_LITERAL

like you suggested.

@traviscross traviscross added the S-waiting-on-stabilization Waiting for a stabilization PR to be merged in the main Rust repository label Aug 19, 2025
@traviscross traviscross removed the S-waiting-on-review Status: The marked PR is awaiting review from a maintainer label Aug 19, 2025
This is being proposed to be removed in
<rust-lang/rust#145463> as we never intended for
this to be valid syntax.

Also include an invalid tuple index with underscore example.
@jieyouxu
Copy link
Member Author

Included

let underscore = example.0_0; // ERROR no field `0_0` on type `(&str, &str, &str)`

example.

matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Sep 9, 2025
Reject invalid literal suffixes in tuple indexing, tuple struct indexing, and struct field name position

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

## Summary

Bump the ["suffixes on a tuple index are invalid" non-lint pseudo future-incompatibility warning (rust-lang#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 rust-lang#59418.
- We tried to hard reject all invalid suffixes in rust-lang#59421, but unfortunately it turns out there were proc macros accidentally relying on it: rust-lang#60138.
- We temporarily accepted `{i,u}{32,size}` in rust-lang#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#60186 as an ecosystem impact mitigation measure (cf. rust-lang#60138). Specifically, the following cases (particularly the construction of these forms in proc macros like reported in rust-lang#60138):

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

```rs
fn main() {
    let _x = (42,).0invalid; // Already error, already rejected by rust-lang#59421
    let _x = (42,).0i8;      // Already error, not one of the rust-lang#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 rust-lang#59421
    let _x = _x.0i8;      // Already error, not one of the rust-lang#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#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 (rust-lang#145463 (comment)).

- 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 AmlingPalantir/r4#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

- rust-lang/reference#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 rust-lang#60186 (comment).

[issue-60210]: rust-lang#60210
rust-timer added a commit to rust-lang/rust that referenced this pull request Sep 9, 2025
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: #60210
Closes #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 #59418.
- We tried to hard reject all invalid suffixes in #59421, but unfortunately it turns out there were proc macros accidentally relying on it: #60138.
- We temporarily accepted `{i,u}{32,size}` in #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 #60186 as an ecosystem impact mitigation measure (cf. #60138). Specifically, the following cases (particularly the construction of these forms in proc macros like reported in #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 #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 (#145463 (comment)).

- 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 AmlingPalantir/r4#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

- rust-lang/reference#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 #60186 (comment).

[issue-60210]: #60210
Copy link
Contributor

@ehuss ehuss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merging now that rust-lang/rust#145463 has merged. Thanks!

@ehuss ehuss added this pull request to the merge queue Sep 9, 2025
Merged via the queue into rust-lang:master with commit d01f991 Sep 9, 2025
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-stabilization Waiting for a stabilization PR to be merged in the main Rust repository
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants