From cf2761a2c9a55f2c8cf811fcbfbc7d481716b081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sat, 5 Sep 2026 17:58:17 +0200 Subject: [PATCH 1/9] Remove outdated comment about removed feature `unnamed_fields` The comment was added in PR https://github.com/rust-lang/rust/pull/115131. The feature was removed in PR https://github.com/rust-lang/rust/pull/131045. --- compiler/rustc_ast/src/token.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/compiler/rustc_ast/src/token.rs b/compiler/rustc_ast/src/token.rs index 0144aabb24413..5ed40094c03f2 100644 --- a/compiler/rustc_ast/src/token.rs +++ b/compiler/rustc_ast/src/token.rs @@ -758,8 +758,6 @@ impl Token { OpenInvisible(InvisibleOrigin::MetaVar(MetaVarKind::Ty { .. } | MetaVarKind::Path)) => { true } - // For anonymous structs or unions, which only appear in specific positions - // (type of struct fields or union fields), we don't consider them as regular types _ => false, } } From 8af341adc327b5dc5e1cad22f2cd156af7225ef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Thu, 3 Sep 2026 22:53:12 +0200 Subject: [PATCH 2/9] Remove obsolete recovery logic for array & slice types The logic was added in commit ff61949860813247b26d96eb374b41b46becba81 (2020) to suppress noisy follow-up syntax errors. For example, consider `src/test/ui/issues/issue-39616.rs` from said commit. However, removing it nowadays doesn't regress any diagnostics. On the contrary, since we no longer try to recover from such malformed array & slice types, the code in `parse_generic_arg` responsible for attempting to recover from complex const exprs that weren't wrapped in braces `{` `}` has an easier time to recover from e.g., `[10]` (as in `Wrap<[10]>`) and doesn't need to create "dummy" diagnostics to attach suggestions onto. In the next commit, we'll remove the special casing of recovered array & array types in `parse_generic_arg` that's no longer necessary. --- compiler/rustc_parse/src/parser/ty.rs | 14 +--------- .../const-generics/bad-const-generic-exprs.rs | 2 -- .../bad-const-generic-exprs.stderr | 28 ++++++------------- 3 files changed, 9 insertions(+), 35 deletions(-) diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs index 31732882f7e86..b264ed18d7ed1 100644 --- a/compiler/rustc_parse/src/parser/ty.rs +++ b/compiler/rustc_parse/src/parser/ty.rs @@ -640,19 +640,7 @@ impl<'a> Parser<'a> { /// Parses an array (`[TYPE; EXPR]`) or slice (`[TYPE]`) type. /// The opening `[` bracket is already eaten. fn parse_array_or_slice_ty(&mut self) -> PResult<'a, TyKind> { - let elt_ty = match self.parse_ty() { - Ok(ty) => ty, - Err(err) - if self.look_ahead(1, |t| *t == token::CloseBracket) - | self.look_ahead(1, |t| *t == token::Semi) => - { - // Recover from `[LIT; EXPR]` and `[LIT]` - self.bump(); - let guar = err.emit(); - self.mk_ty(self.prev_token.span, TyKind::Err(guar)) - } - Err(err) => return Err(err), - }; + let elt_ty = self.parse_ty()?; let ty = if self.eat(exp!(Semi)) { let mut length = self.parse_expr_anon_const()?; diff --git a/tests/ui/const-generics/bad-const-generic-exprs.rs b/tests/ui/const-generics/bad-const-generic-exprs.rs index 423752ca25eba..3c2aaa56dcc26 100644 --- a/tests/ui/const-generics/bad-const-generic-exprs.rs +++ b/tests/ui/const-generics/bad-const-generic-exprs.rs @@ -18,14 +18,12 @@ fn main() { //~| HELP expressions must be enclosed in braces to be used as const generic arguments let _: Wow<[12]>; //~^ ERROR expected type - //~| ERROR invalid const generic expression //~| HELP expressions must be enclosed in braces to be used as const generic arguments let _: Wow<[0, 1, 3]>; //~^ ERROR expected type //~| HELP expressions must be enclosed in braces to be used as const generic arguments let _: Wow<[0xff; 8]>; //~^ ERROR expected type - //~| ERROR invalid const generic expression //~| HELP expressions must be enclosed in braces to be used as const generic arguments let _: Wow<[1, 2]>; // Regression test for issue #81698. //~^ ERROR expected type diff --git a/tests/ui/const-generics/bad-const-generic-exprs.stderr b/tests/ui/const-generics/bad-const-generic-exprs.stderr index 6d308bdc90708..294221a4134ef 100644 --- a/tests/ui/const-generics/bad-const-generic-exprs.stderr +++ b/tests/ui/const-generics/bad-const-generic-exprs.stderr @@ -58,12 +58,6 @@ error: expected type, found `12` | LL | let _: Wow<[12]>; | ^^ expected type - -error: invalid const generic expression - --> $DIR/bad-const-generic-exprs.rs:19:16 - | -LL | let _: Wow<[12]>; - | ^^^^ | help: expressions must be enclosed in braces to be used as const generic arguments | @@ -71,7 +65,7 @@ LL | let _: Wow<{ [12] }>; | + + error: expected type, found `0` - --> $DIR/bad-const-generic-exprs.rs:23:17 + --> $DIR/bad-const-generic-exprs.rs:22:17 | LL | let _: Wow<[0, 1, 3]>; | ^ expected type @@ -82,16 +76,10 @@ LL | let _: Wow<{ [0, 1, 3] }>; | + + error: expected type, found `0xff` - --> $DIR/bad-const-generic-exprs.rs:26:17 + --> $DIR/bad-const-generic-exprs.rs:25:17 | LL | let _: Wow<[0xff; 8]>; | ^^^^ expected type - -error: invalid const generic expression - --> $DIR/bad-const-generic-exprs.rs:26:16 - | -LL | let _: Wow<[0xff; 8]>; - | ^^^^^^^^^ | help: expressions must be enclosed in braces to be used as const generic arguments | @@ -99,7 +87,7 @@ LL | let _: Wow<{ [0xff; 8] }>; | + + error: expected type, found `1` - --> $DIR/bad-const-generic-exprs.rs:30:17 + --> $DIR/bad-const-generic-exprs.rs:28:17 | LL | let _: Wow<[1, 2]>; // Regression test for issue #81698. | ^ expected type @@ -110,7 +98,7 @@ LL | let _: Wow<{ [1, 2] }>; // Regression test for issue #81698. | + + error: expected type, found `0` - --> $DIR/bad-const-generic-exprs.rs:33:17 + --> $DIR/bad-const-generic-exprs.rs:31:17 | LL | let _: Wow<&0>; | ^ expected type @@ -121,7 +109,7 @@ LL | let _: Wow<{ &0 }>; | + + error: expected type, found `""` - --> $DIR/bad-const-generic-exprs.rs:36:17 + --> $DIR/bad-const-generic-exprs.rs:34:17 | LL | let _: Wow<("", 0)>; | ^^ expected type @@ -132,7 +120,7 @@ LL | let _: Wow<{ ("", 0) }>; | + + error: expected type, found `1` - --> $DIR/bad-const-generic-exprs.rs:39:17 + --> $DIR/bad-const-generic-exprs.rs:37:17 | LL | let _: Wow<(1 + 2) * 3>; | ^ expected type @@ -143,7 +131,7 @@ LL | let _: Wow<{ (1 + 2) * 3 }>; | + + error: expected one of `,` or `>`, found `0` - --> $DIR/bad-const-generic-exprs.rs:43:17 + --> $DIR/bad-const-generic-exprs.rs:41:17 | LL | let _: Wow; | - ^ expected one of `,` or `>` @@ -155,5 +143,5 @@ help: you might have meant to end the type parameters here LL | let _: Wow0>; | + -error: aborting due to 15 previous errors +error: aborting due to 13 previous errors From d82b08755b372f1dcfc36ba791f15b6c7d911010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Thu, 3 Sep 2026 23:11:06 +0200 Subject: [PATCH 3/9] Remove obsolete recovery code from `parse_generic_arg` See the previous commit for details. --- compiler/rustc_parse/src/parser/path.rs | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/compiler/rustc_parse/src/parser/path.rs b/compiler/rustc_parse/src/parser/path.rs index 664c089e3f153..d6eb013acda99 100644 --- a/compiler/rustc_parse/src/parser/path.rs +++ b/compiler/rustc_parse/src/parser/path.rs @@ -939,28 +939,7 @@ impl<'a> Parser<'a> { } match self.parse_ty() { - Ok(ty) => { - // Since the type parser recovers from some malformed slice and array types and - // successfully returns a type, we need to look for `TyKind::Err`s in the - // type to determine if error recovery has occurred and if the input is not a - // syntactically valid type after all. - if let ast::TyKind::Slice(inner_ty) | ast::TyKind::Array(inner_ty, _) = &ty.kind - && let ast::TyKind::Err(_) = inner_ty.kind - && let Some(snapshot) = snapshot - && let Some(expr) = - self.recover_unbraced_const_arg_that_can_begin_ty(snapshot) - { - return Ok(Some( - self.dummy_const_arg_needs_braces( - self.dcx() - .struct_span_err(expr.span, "invalid const generic expression"), - expr.span, - ), - )); - } - - GenericArg::Type(ty) - } + Ok(ty) => GenericArg::Type(ty), Err(err) => { let stopped_at_doc_comment = matches!(self.token.kind, token::DocComment(..)); From e92a69499dbc1bfa4e15ff57046757dc70780469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Fri, 4 Sep 2026 00:13:30 +0200 Subject: [PATCH 4/9] Simplify recovery from outer attributes on types Instead of checking for `#[` before trying to parse outer attributes, check for `#` or doc comments instead. This way, we automatically recover from leading doc comments and inner attributes on types, too. Previously, we would only recover from doc comments and inner attributes on types if they were preceded by normal outer attributes. Move the whole recovery into a separate more general function in preparation for using it in various other places, too. This means we drop the recovery from attributes in type contexts that aren't ascribed to a valid type (at least until the next sync point). For example, `type T = #[a];` and `fn f() -> #[a] {}` are now fatal (meaning the parser will only continue parsing from the next sync point). This could be considered unfortunate but honestly it didn't make much sense: Why would we only defatalize syntax errors in type contexts if there are preceding attributes (which aren't legal / expected on types in the first place, mind you); there's no reason to recover from e.g., `type T = #[a] @;` (which used to lead to bad follow-up diagnostics). Defatalizing syntax errors isn't inherently bad, it just needs to be done carefully and not made dependent on "random" conditions. --- compiler/rustc_parse/src/diagnostics.rs | 15 ----- compiler/rustc_parse/src/parser/attr.rs | 38 +++++++++++ compiler/rustc_parse/src/parser/ty.rs | 30 ++------- tests/ui/lifetimes/raw/multiple-prefixes.rs | 2 +- .../ui/lifetimes/raw/multiple-prefixes.stderr | 6 +- tests/ui/parser/attribute-on-empty.rs | 31 +++++---- tests/ui/parser/attribute-on-empty.stderr | 64 ++++++++++++------- .../doc-comment-in-generic-tuple-type.rs | 2 +- .../doc-comment-in-generic-tuple-type.stderr | 7 +- 9 files changed, 112 insertions(+), 83 deletions(-) diff --git a/compiler/rustc_parse/src/diagnostics.rs b/compiler/rustc_parse/src/diagnostics.rs index a09b2a38593ed..b52e2a6a4e3ce 100644 --- a/compiler/rustc_parse/src/diagnostics.rs +++ b/compiler/rustc_parse/src/diagnostics.rs @@ -1842,21 +1842,6 @@ pub(crate) struct AttributeOnParamType { pub span: Span, } -#[derive(Diagnostic)] -#[diag("attributes cannot be applied to types")] -pub(crate) struct AttributeOnType { - #[primary_span] - #[label("attributes are not allowed here")] - pub span: Span, - #[suggestion( - "remove attribute from here", - code = "", - applicability = "machine-applicable", - style = "tool-only" - )] - pub fix_span: Span, -} - #[derive(Diagnostic)] #[diag("attributes cannot be applied to generic arguments")] pub(crate) struct AttributeOnGenericArg { diff --git a/compiler/rustc_parse/src/parser/attr.rs b/compiler/rustc_parse/src/parser/attr.rs index b2ad898311cc3..091d01f658b51 100644 --- a/compiler/rustc_parse/src/parser/attr.rs +++ b/compiler/rustc_parse/src/parser/attr.rs @@ -507,4 +507,42 @@ impl<'a> Parser<'a> { Err(self.dcx().create_err(err)) } + + /// Recover from outer attributes in places where none were expected. + pub(super) fn recover_from_outer_attributes(&mut self, target: &str) -> PResult<'a, ()> { + // We check the token ourselves first to prevent `#` + // from getting added to the set of expected tokens. + if !self.may_recover() || !matches!(self.token.kind, token::Pound | token::DocComment(..)) { + return Ok(()); + } + + let attrs = self.parse_outer_attributes()?; + if attrs.is_empty() { + return Ok(()); + } + + let attrs = attrs.take_for_recovery(self.psess); + let span = attrs.first().unwrap().span.to(attrs.last().unwrap().span); + + let subject = if attrs.iter().all(|attr| matches!(attr.kind, ast::AttrKind::DocComment(..))) + { + "doc comments" + } else { + "attributes" + }; + + self.dcx() + .struct_span_err(span, format!("{subject} cannot be applied to {target}")) + .with_span_label(span, format!("{subject} are not allowed here")) + .with_span_suggestion_with_style( + span.until(self.token.span), + format!("remove these {subject}"), + String::new(), + rustc_errors::Applicability::MachineApplicable, + rustc_errors::SuggestionStyle::CompletelyHidden, + ) + .emit(); + + Ok(()) + } } diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs index b264ed18d7ed1..ff619f36448c6 100644 --- a/compiler/rustc_parse/src/parser/ty.rs +++ b/compiler/rustc_parse/src/parser/ty.rs @@ -12,10 +12,10 @@ use thin_vec::{ThinVec, thin_vec}; use super::{Parser, PathStyle, SeqSep, TokenType, Trailing}; use crate::diagnostics::{ - self, AttributeOnEmptyType, AttributeOnType, DynAfterMut, ExpectedFnPathFoundFnKeyword, - ExpectedMutOrConstInRawPointerType, FnPtrWithGenerics, FnPtrWithGenericsSugg, - HelpUseLatestEdition, InvalidCVariadicType, InvalidDynKeyword, LifetimeAfterMut, - NeedPlusAfterTraitObjectLifetime, NestedCVariadicType, ReturnTypesUseThinArrow, + self, DynAfterMut, ExpectedFnPathFoundFnKeyword, ExpectedMutOrConstInRawPointerType, + FnPtrWithGenerics, FnPtrWithGenericsSugg, HelpUseLatestEdition, InvalidCVariadicType, + InvalidDynKeyword, LifetimeAfterMut, NeedPlusAfterTraitObjectLifetime, NestedCVariadicType, + ReturnTypesUseThinArrow, }; use crate::parser::{FnContext, FnParseMode, FrontMatterParsingMode}; use crate::{exp, maybe_recover_from_interpolated_ty_qpath}; @@ -279,27 +279,7 @@ impl<'a> Parser<'a> { ) -> PResult<'a, Box> { let allow_qpath_recovery = recover_qpath == RecoverQPath::Yes; maybe_recover_from_interpolated_ty_qpath!(self, allow_qpath_recovery); - if self.token == token::Pound && self.look_ahead(1, |t| *t == token::OpenBracket) { - let attrs_wrapper = self.parse_outer_attributes()?; - let raw_attrs = attrs_wrapper.take_for_recovery(self.psess); - let attr_span = raw_attrs[0].span.to(raw_attrs.last().unwrap().span); - let (full_span, guar) = match self.parse_ty() { - Ok(ty) => { - let full_span = attr_span.until(ty.span); - let guar = self - .dcx() - .emit_err(AttributeOnType { span: attr_span, fix_span: full_span }); - (attr_span, guar) - } - Err(err) => { - err.cancel(); - let guar = self.dcx().emit_err(AttributeOnEmptyType { span: attr_span }); - (attr_span, guar) - } - }; - return Ok(self.mk_ty(full_span, TyKind::Err(guar))); - } if let Some(ty) = self.eat_metavar_seq_with_matcher( |mv_kind| matches!(mv_kind, MetaVarKind::Ty { .. }), |this| this.parse_ty_no_question_mark_recover(), @@ -307,6 +287,8 @@ impl<'a> Parser<'a> { return Ok(ty); } + self.recover_from_outer_attributes("types")?; + let lo = self.token.span; let mut impl_dyn_multi = false; let kind = if self.check(exp!(OpenParen)) { diff --git a/tests/ui/lifetimes/raw/multiple-prefixes.rs b/tests/ui/lifetimes/raw/multiple-prefixes.rs index f335373d8a7e7..d629ff76c8bbc 100644 --- a/tests/ui/lifetimes/raw/multiple-prefixes.rs +++ b/tests/ui/lifetimes/raw/multiple-prefixes.rs @@ -1,6 +1,6 @@ //@ edition: 2021 fn test(x: &'r#r#r ()) {} -//~^ ERROR expected type, found `#` +//~^ ERROR expected one of fn main() {} diff --git a/tests/ui/lifetimes/raw/multiple-prefixes.stderr b/tests/ui/lifetimes/raw/multiple-prefixes.stderr index 8d5479e0a4fc5..dacb1e8027859 100644 --- a/tests/ui/lifetimes/raw/multiple-prefixes.stderr +++ b/tests/ui/lifetimes/raw/multiple-prefixes.stderr @@ -1,8 +1,8 @@ -error: expected type, found `#` - --> $DIR/multiple-prefixes.rs:3:17 +error: expected one of `!` or `[`, found `r` + --> $DIR/multiple-prefixes.rs:3:18 | LL | fn test(x: &'r#r#r ()) {} - | ^ expected type + | ^ expected one of `!` or `[` error: aborting due to 1 previous error diff --git a/tests/ui/parser/attribute-on-empty.rs b/tests/ui/parser/attribute-on-empty.rs index 0177e6c1b59d4..ddcd8eb1e4f6b 100644 --- a/tests/ui/parser/attribute-on-empty.rs +++ b/tests/ui/parser/attribute-on-empty.rs @@ -1,29 +1,36 @@ -//! Regression test for: -//! +// Exercise outer attributes being applied to "nothing" in invalid contexts. struct Baz(i32); -fn main() { +fn f() { let _: Baz<#[cfg(false)]> = todo!(); //~^ ERROR attributes cannot be applied here } -fn f(_param: #[attr]) {} +fn g(_param: #[attr]) {} //~^ ERROR attributes cannot be applied to a function parameter's type //~| ERROR expected type, found `)` -fn g() -> #[attr] { 0 } -//~^ ERROR attributes cannot be applied here +fn barrier0() { + fn f() -> #[attr] { 0 } + //~^ ERROR attributes cannot be applied to types + //~| ERROR expected type, found `{` +} struct S { field: #[attr], - //~^ ERROR attributes cannot be applied here - field1: (#[attr], i32), - //~^ ERROR attributes cannot be applied here + //~^ ERROR attributes cannot be applied to types + //~| ERROR expected type, found `,` } -type Tuple = (#[attr], String); -//~^ ERROR attributes cannot be applied here +fn barrier1() { + type Tuple = (#[attr], String); + //~^ ERROR attributes cannot be applied to types + //~| ERROR expected type, found `,` +} impl #[attr] {} -//~^ ERROR attributes cannot be applied here +//~^ ERROR attributes cannot be applied to types +//~| ERROR expected type, found `{` + +fn main() {} diff --git a/tests/ui/parser/attribute-on-empty.stderr b/tests/ui/parser/attribute-on-empty.stderr index 6bcbf1ceb8d1f..21ff0beac53f1 100644 --- a/tests/ui/parser/attribute-on-empty.stderr +++ b/tests/ui/parser/attribute-on-empty.stderr @@ -1,5 +1,5 @@ error: attributes cannot be applied here - --> $DIR/attribute-on-empty.rs:7:16 + --> $DIR/attribute-on-empty.rs:6:16 | LL | let _: Baz<#[cfg(false)]> = todo!(); | - ^^^^^^^^^^^^^ attributes are not allowed here @@ -7,46 +7,66 @@ LL | let _: Baz<#[cfg(false)]> = todo!(); | while parsing the type for `_` error: attributes cannot be applied to a function parameter's type - --> $DIR/attribute-on-empty.rs:11:14 + --> $DIR/attribute-on-empty.rs:10:14 | -LL | fn f(_param: #[attr]) {} +LL | fn g(_param: #[attr]) {} | ^^^^^^^ attributes are not allowed here error: expected type, found `)` - --> $DIR/attribute-on-empty.rs:11:21 + --> $DIR/attribute-on-empty.rs:10:21 | -LL | fn f(_param: #[attr]) {} +LL | fn g(_param: #[attr]) {} | ^ expected type -error: attributes cannot be applied here - --> $DIR/attribute-on-empty.rs:15:11 +error: attributes cannot be applied to types + --> $DIR/attribute-on-empty.rs:15:15 | -LL | fn g() -> #[attr] { 0 } - | ^^^^^^^ attributes are not allowed here +LL | fn f() -> #[attr] { 0 } + | ^^^^^^^ attributes are not allowed here -error: attributes cannot be applied here - --> $DIR/attribute-on-empty.rs:19:12 +error: expected type, found `{` + --> $DIR/attribute-on-empty.rs:15:23 + | +LL | fn f() -> #[attr] { 0 } + | ^ expected type + +error: attributes cannot be applied to types + --> $DIR/attribute-on-empty.rs:21:12 | LL | field: #[attr], | ^^^^^^^ attributes are not allowed here -error: attributes cannot be applied here - --> $DIR/attribute-on-empty.rs:21:14 +error: expected type, found `,` + --> $DIR/attribute-on-empty.rs:21:19 | -LL | field1: (#[attr], i32), - | ^^^^^^^ attributes are not allowed here +LL | struct S { + | - while parsing this struct +LL | field: #[attr], + | ^ expected type -error: attributes cannot be applied here - --> $DIR/attribute-on-empty.rs:25:15 +error: attributes cannot be applied to types + --> $DIR/attribute-on-empty.rs:27:19 | -LL | type Tuple = (#[attr], String); - | ^^^^^^^ attributes are not allowed here +LL | type Tuple = (#[attr], String); + | ^^^^^^^ attributes are not allowed here -error: attributes cannot be applied here - --> $DIR/attribute-on-empty.rs:28:6 +error: expected type, found `,` + --> $DIR/attribute-on-empty.rs:27:26 + | +LL | type Tuple = (#[attr], String); + | ^ expected type + +error: attributes cannot be applied to types + --> $DIR/attribute-on-empty.rs:32:6 | LL | impl #[attr] {} | ^^^^^^^ attributes are not allowed here -error: aborting due to 8 previous errors +error: expected type, found `{` + --> $DIR/attribute-on-empty.rs:32:14 + | +LL | impl #[attr] {} + | ^ expected type + +error: aborting due to 11 previous errors diff --git a/tests/ui/parser/doc-comment-in-generic-tuple-type.rs b/tests/ui/parser/doc-comment-in-generic-tuple-type.rs index 929777db6e4c0..d62a5698c3a96 100644 --- a/tests/ui/parser/doc-comment-in-generic-tuple-type.rs +++ b/tests/ui/parser/doc-comment-in-generic-tuple-type.rs @@ -2,7 +2,7 @@ struct Foo { a: Vec<( /// Docstring - //~^ ERROR expected type, found doc comment + //~^ ERROR doc comments cannot be applied to types f32, f32, )>, diff --git a/tests/ui/parser/doc-comment-in-generic-tuple-type.stderr b/tests/ui/parser/doc-comment-in-generic-tuple-type.stderr index fc718dae8e511..c432bb95cc0bb 100644 --- a/tests/ui/parser/doc-comment-in-generic-tuple-type.stderr +++ b/tests/ui/parser/doc-comment-in-generic-tuple-type.stderr @@ -1,11 +1,8 @@ -error: expected type, found doc comment `/// Docstring` +error: doc comments cannot be applied to types --> $DIR/doc-comment-in-generic-tuple-type.rs:4:9 | -LL | struct Foo { - | --- while parsing this struct -LL | a: Vec<( LL | /// Docstring - | ^^^^^^^^^^^^^ expected type + | ^^^^^^^^^^^^^ doc comments are not allowed here error: aborting due to 1 previous error From 9875dda2103aed500ac58cc8f487c3cdbec568d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Fri, 4 Sep 2026 01:23:35 +0200 Subject: [PATCH 5/9] Remove unreachable condition from recovery code In commit 05641688953731fa5fa5988d327ca5db9d670d3e, the check for doc comments was added to prevent us from trying to reparse the type as a const expression and suggesting to wrap it in `{` `}` if successful as that's unlikely what the user meant. However, now that we always try to parse outer attributes (including doc comments) on types to provide better diagnostics, this case in `parse_generic_arg` can't be reached anymore. --- compiler/rustc_parse/src/parser/path.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/compiler/rustc_parse/src/parser/path.rs b/compiler/rustc_parse/src/parser/path.rs index d6eb013acda99..a4aa6cc65980b 100644 --- a/compiler/rustc_parse/src/parser/path.rs +++ b/compiler/rustc_parse/src/parser/path.rs @@ -941,10 +941,7 @@ impl<'a> Parser<'a> { match self.parse_ty() { Ok(ty) => GenericArg::Type(ty), Err(err) => { - let stopped_at_doc_comment = matches!(self.token.kind, token::DocComment(..)); - - if !stopped_at_doc_comment - && let Some(snapshot) = snapshot + if let Some(snapshot) = snapshot && let Some(expr) = self.recover_unbraced_const_arg_that_can_begin_ty(snapshot) { From 712a16edb1e2f38ca5642422be5af229966454fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Fri, 4 Sep 2026 01:34:01 +0200 Subject: [PATCH 6/9] Remove specialized recovery from outer attributes on parameter types Given we now recover from outer attributes on any type, this specialized logic is no longer necessary. Given we now recover from outer attributes on any type, this specialized logic is no longer necessary. --- compiler/rustc_parse/src/diagnostics.rs | 16 ------------- .../rustc_parse/src/parser/diagnostics.rs | 23 +++---------------- compiler/rustc_parse/src/parser/function.rs | 2 -- tests/ui/parser/attribute-on-empty.rs | 2 +- tests/ui/parser/attribute-on-empty.stderr | 2 +- tests/ui/parser/fn-arg-doc-comment.rs | 2 +- tests/ui/parser/fn-arg-doc-comment.stderr | 2 +- 7 files changed, 7 insertions(+), 42 deletions(-) diff --git a/compiler/rustc_parse/src/diagnostics.rs b/compiler/rustc_parse/src/diagnostics.rs index b52e2a6a4e3ce..d628e9dfb85a9 100644 --- a/compiler/rustc_parse/src/diagnostics.rs +++ b/compiler/rustc_parse/src/diagnostics.rs @@ -1826,22 +1826,6 @@ pub(crate) struct ParenthesesInMatchPatSugg { pub right: Span, } -#[derive(Diagnostic)] -#[diag("documentation comments cannot be applied to a function parameter's type")] -pub(crate) struct DocCommentOnParamType { - #[primary_span] - #[label("doc comments are not allowed here")] - pub span: Span, -} - -#[derive(Diagnostic)] -#[diag("attributes cannot be applied to a function parameter's type")] -pub(crate) struct AttributeOnParamType { - #[primary_span] - #[label("attributes are not allowed here")] - pub span: Span, -} - #[derive(Diagnostic)] #[diag("attributes cannot be applied to generic arguments")] pub(crate) struct AttributeOnGenericArg { diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index 0b91828639d36..6d4a0215eb7b3 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -26,10 +26,9 @@ use super::{ SeqSep, TokenType, }; use crate::diagnostics::{ - AddParen, AmbiguousPlus, AsyncMoveBlockIn2015, AsyncUseBlockIn2015, AttributeOnParamType, - AwaitSuggestion, BadQPathStage2, BadTypePlus, BadTypePlusSub, ColonAsSemi, - ComparisonOperatorsCannotBeChained, ComparisonOperatorsCannotBeChainedSugg, - DocCommentDoesNotDocumentAnything, DocCommentOnParamType, DoubleColonInBound, + AddParen, AmbiguousPlus, AsyncMoveBlockIn2015, AsyncUseBlockIn2015, AwaitSuggestion, + BadQPathStage2, BadTypePlus, BadTypePlusSub, ColonAsSemi, ComparisonOperatorsCannotBeChained, + ComparisonOperatorsCannotBeChainedSugg, DocCommentDoesNotDocumentAnything, DoubleColonInBound, ExpectedIdentifier, ExpectedSemi, ExpectedSemiSugg, ExprParenthesesNeeded, FoundPathInGenerics, GenericParamsWithoutAngleBrackets, GenericParamsWithoutAngleBracketsSugg, HelpIdentifierStartsWithNumber, HelpUseLatestEdition, InInTypo, IncorrectAwait, @@ -2216,22 +2215,6 @@ impl<'a> Parser<'a> { } } - pub(super) fn eat_incorrect_doc_comment_for_param_type(&mut self) { - if let token::DocComment(..) = self.token.kind { - self.dcx().emit_err(DocCommentOnParamType { span: self.token.span }); - self.bump(); - } else if self.token == token::Pound && self.look_ahead(1, |t| *t == token::OpenBracket) { - let lo = self.token.span; - // Skip every token until next possible arg. - while self.token != token::CloseBracket { - self.bump(); - } - let sp = lo.to(self.token.span); - self.bump(); - self.dcx().emit_err(AttributeOnParamType { span: sp }); - } - } - pub(super) fn parameter_without_type( &mut self, err: &mut Diag<'_>, diff --git a/compiler/rustc_parse/src/parser/function.rs b/compiler/rustc_parse/src/parser/function.rs index 72b8507ccef0f..57fe19226066c 100644 --- a/compiler/rustc_parse/src/parser/function.rs +++ b/compiler/rustc_parse/src/parser/function.rs @@ -778,12 +778,10 @@ impl<'a> Parser<'a> { }; } - this.eat_incorrect_doc_comment_for_param_type(); (pat, this.parse_ty_for_param()?) } else { debug!("parse_param_general ident_to_pat"); let parser_snapshot_before_ty = this.create_snapshot_for_diagnostic(); - this.eat_incorrect_doc_comment_for_param_type(); let mut ty = this.parse_ty_for_param(); if let Ok(t) = &ty { diff --git a/tests/ui/parser/attribute-on-empty.rs b/tests/ui/parser/attribute-on-empty.rs index ddcd8eb1e4f6b..e9170268f6527 100644 --- a/tests/ui/parser/attribute-on-empty.rs +++ b/tests/ui/parser/attribute-on-empty.rs @@ -8,7 +8,7 @@ fn f() { } fn g(_param: #[attr]) {} -//~^ ERROR attributes cannot be applied to a function parameter's type +//~^ ERROR attributes cannot be applied to types //~| ERROR expected type, found `)` fn barrier0() { diff --git a/tests/ui/parser/attribute-on-empty.stderr b/tests/ui/parser/attribute-on-empty.stderr index 21ff0beac53f1..2442ca084e38b 100644 --- a/tests/ui/parser/attribute-on-empty.stderr +++ b/tests/ui/parser/attribute-on-empty.stderr @@ -6,7 +6,7 @@ LL | let _: Baz<#[cfg(false)]> = todo!(); | | | while parsing the type for `_` -error: attributes cannot be applied to a function parameter's type +error: attributes cannot be applied to types --> $DIR/attribute-on-empty.rs:10:14 | LL | fn g(_param: #[attr]) {} diff --git a/tests/ui/parser/fn-arg-doc-comment.rs b/tests/ui/parser/fn-arg-doc-comment.rs index 57a4d15fa2564..962c5fb6dddc1 100644 --- a/tests/ui/parser/fn-arg-doc-comment.rs +++ b/tests/ui/parser/fn-arg-doc-comment.rs @@ -10,7 +10,7 @@ pub fn f( //~ NOTE function defined here ) {} fn bar(id: #[allow(dead_code)] i32) {} -//~^ ERROR attributes cannot be applied to a function parameter's type +//~^ ERROR attributes cannot be applied to types //~| NOTE attributes are not allowed here //~| NOTE function defined here diff --git a/tests/ui/parser/fn-arg-doc-comment.stderr b/tests/ui/parser/fn-arg-doc-comment.stderr index 84c8bb3c2d094..62ce856009490 100644 --- a/tests/ui/parser/fn-arg-doc-comment.stderr +++ b/tests/ui/parser/fn-arg-doc-comment.stderr @@ -1,4 +1,4 @@ -error: attributes cannot be applied to a function parameter's type +error: attributes cannot be applied to types --> $DIR/fn-arg-doc-comment.rs:12:12 | LL | fn bar(id: #[allow(dead_code)] i32) {} From 3d5b7aaa0101d1af50704a3a9ce4a5ae0b92e4d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Fri, 4 Sep 2026 02:04:19 +0200 Subject: [PATCH 7/9] Simplify recovery from outer attributes on generic args Use our new routine. Again, this means we can now also recover from leading doc comments and inner attributes. Moreover, we now actually store type & const arguments that have outer attributes in the AST instead of using `{type error}` & `{const error}` for them, respectively. --- compiler/rustc_parse/src/diagnostics.rs | 23 ------------ compiler/rustc_parse/src/parser/path.rs | 35 +++---------------- tests/ui/parser/attribute-on-empty.rs | 2 +- tests/ui/parser/attribute-on-empty.stderr | 6 ++-- ...xed => attribute-on-type-or-gen-arg.fixed} | 0 ...ype.rs => attribute-on-type-or-gen-arg.rs} | 0 ...rr => attribute-on-type-or-gen-arg.stderr} | 30 ++++++++-------- tests/ui/parser/issues/issue-103143.rs | 2 ++ tests/ui/parser/issues/issue-103143.stderr | 14 +++++++- 9 files changed, 38 insertions(+), 74 deletions(-) rename tests/ui/parser/{attribute-on-type.fixed => attribute-on-type-or-gen-arg.fixed} (100%) rename tests/ui/parser/{attribute-on-type.rs => attribute-on-type-or-gen-arg.rs} (100%) rename tests/ui/parser/{attribute-on-type.stderr => attribute-on-type-or-gen-arg.stderr} (78%) diff --git a/compiler/rustc_parse/src/diagnostics.rs b/compiler/rustc_parse/src/diagnostics.rs index d628e9dfb85a9..1dc2d625fe0e0 100644 --- a/compiler/rustc_parse/src/diagnostics.rs +++ b/compiler/rustc_parse/src/diagnostics.rs @@ -1826,29 +1826,6 @@ pub(crate) struct ParenthesesInMatchPatSugg { pub right: Span, } -#[derive(Diagnostic)] -#[diag("attributes cannot be applied to generic arguments")] -pub(crate) struct AttributeOnGenericArg { - #[primary_span] - #[label("attributes are not allowed here")] - pub span: Span, - #[suggestion( - "remove attribute from here", - code = "", - applicability = "machine-applicable", - style = "tool-only" - )] - pub fix_span: Span, -} - -#[derive(Diagnostic)] -#[diag("attributes cannot be applied here")] -pub(crate) struct AttributeOnEmptyType { - #[primary_span] - #[label("attributes are not allowed here")] - pub span: Span, -} - #[derive(Diagnostic)] #[diag("patterns aren't allowed in {$target}", code = E0642)] pub(crate) struct PatternMethodParamWithoutBody { diff --git a/compiler/rustc_parse/src/parser/path.rs b/compiler/rustc_parse/src/parser/path.rs index a4aa6cc65980b..cbd0891c7fe9a 100644 --- a/compiler/rustc_parse/src/parser/path.rs +++ b/compiler/rustc_parse/src/parser/path.rs @@ -16,14 +16,11 @@ use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign}; use super::{Parser, Restrictions, TokenType}; use crate::ast::{PatKind, TyKind}; use crate::diagnostics::{ - self, AttributeOnEmptyType, AttributeOnGenericArg, ConstGenericWithoutBraces, - ConstGenericWithoutBracesSugg, PathFoundAttributeInParams, PathFoundCVariadicParams, - PathSingleColon, PathTripleColon, + self, ConstGenericWithoutBraces, ConstGenericWithoutBracesSugg, PathFoundAttributeInParams, + PathFoundCVariadicParams, PathSingleColon, PathTripleColon, }; use crate::exp; -use crate::parser::{ - CommaRecoveryMode, Expr, ExprKind, FnContext, FnParseMode, RecoverColon, RecoverComma, -}; +use crate::parser::{CommaRecoveryMode, Expr, FnContext, FnParseMode, RecoverColon, RecoverComma}; /// Specifies how to parse a path. #[derive(Copy, Clone, PartialEq)] @@ -914,12 +911,8 @@ impl<'a> Parser<'a> { &mut self, ty_generics: Option<&Generics>, ) -> PResult<'a, Option> { - let mut attr_span: Option = None; - if self.token == token::Pound && self.look_ahead(1, |t| *t == token::OpenBracket) { - let attrs_wrapper = self.parse_outer_attributes()?; - let raw_attrs = attrs_wrapper.take_for_recovery(self.psess); - attr_span = Some(raw_attrs[0].span.to(raw_attrs.last().unwrap().span)); - } + self.recover_from_outer_attributes("generic arguments")?; + let start = self.token.span; let arg = if self.check_lifetime() && self.look_ahead(1, |t| !t.is_like_plus()) { // Parse lifetime argument. @@ -953,9 +946,6 @@ impl<'a> Parser<'a> { } } else if self.token.is_keyword(kw::Const) { return self.recover_const_param_declaration(ty_generics); - } else if let Some(attr_span) = attr_span { - let diag = self.dcx().create_err(AttributeOnEmptyType { span: attr_span }); - return Err(diag); } else { // Fall back by trying to parse a const-expr expression. If we successfully do so, // then we should report an error that it needs to be wrapped in braces. @@ -975,21 +965,6 @@ impl<'a> Parser<'a> { } }; - if let Some(attr_span) = attr_span { - let guar = self.dcx().emit_err(AttributeOnGenericArg { - span: attr_span, - fix_span: attr_span.until(arg.span()), - }); - return Ok(Some(match arg { - GenericArg::Type(_) => GenericArg::Type(self.mk_ty(attr_span, TyKind::Err(guar))), - GenericArg::Const(_) => { - let error_expr = self.mk_expr(attr_span, ExprKind::Err(guar)); - GenericArg::Const(AnonConst { id: ast::DUMMY_NODE_ID, value: error_expr }) - } - GenericArg::Lifetime(lt) => GenericArg::Lifetime(lt), - })); - } - Ok(Some(arg)) } diff --git a/tests/ui/parser/attribute-on-empty.rs b/tests/ui/parser/attribute-on-empty.rs index e9170268f6527..135929a9fa397 100644 --- a/tests/ui/parser/attribute-on-empty.rs +++ b/tests/ui/parser/attribute-on-empty.rs @@ -4,7 +4,7 @@ struct Baz(i32); fn f() { let _: Baz<#[cfg(false)]> = todo!(); - //~^ ERROR attributes cannot be applied here + //~^ ERROR attributes cannot be applied to generic arguments } fn g(_param: #[attr]) {} diff --git a/tests/ui/parser/attribute-on-empty.stderr b/tests/ui/parser/attribute-on-empty.stderr index 2442ca084e38b..5376e3e472cdd 100644 --- a/tests/ui/parser/attribute-on-empty.stderr +++ b/tests/ui/parser/attribute-on-empty.stderr @@ -1,10 +1,8 @@ -error: attributes cannot be applied here +error: attributes cannot be applied to generic arguments --> $DIR/attribute-on-empty.rs:6:16 | LL | let _: Baz<#[cfg(false)]> = todo!(); - | - ^^^^^^^^^^^^^ attributes are not allowed here - | | - | while parsing the type for `_` + | ^^^^^^^^^^^^^ attributes are not allowed here error: attributes cannot be applied to types --> $DIR/attribute-on-empty.rs:10:14 diff --git a/tests/ui/parser/attribute-on-type.fixed b/tests/ui/parser/attribute-on-type-or-gen-arg.fixed similarity index 100% rename from tests/ui/parser/attribute-on-type.fixed rename to tests/ui/parser/attribute-on-type-or-gen-arg.fixed diff --git a/tests/ui/parser/attribute-on-type.rs b/tests/ui/parser/attribute-on-type-or-gen-arg.rs similarity index 100% rename from tests/ui/parser/attribute-on-type.rs rename to tests/ui/parser/attribute-on-type-or-gen-arg.rs diff --git a/tests/ui/parser/attribute-on-type.stderr b/tests/ui/parser/attribute-on-type-or-gen-arg.stderr similarity index 78% rename from tests/ui/parser/attribute-on-type.stderr rename to tests/ui/parser/attribute-on-type-or-gen-arg.stderr index 316620325c04c..f565b0b81ac90 100644 --- a/tests/ui/parser/attribute-on-type.stderr +++ b/tests/ui/parser/attribute-on-type-or-gen-arg.stderr @@ -1,89 +1,89 @@ error: attributes cannot be applied to generic arguments - --> $DIR/attribute-on-type.rs:13:18 + --> $DIR/attribute-on-type-or-gen-arg.rs:13:18 | LL | let foo: Foo<#[cfg(not(wrong))] i32> = Foo(2i32); | ^^^^^^^^^^^^^^^^^^ attributes are not allowed here error: attributes cannot be applied to types - --> $DIR/attribute-on-type.rs:16:12 + --> $DIR/attribute-on-type-or-gen-arg.rs:16:12 | LL | let _: #[attr] &'static str = "123"; | ^^^^^^^ attributes are not allowed here error: attributes cannot be applied to generic arguments - --> $DIR/attribute-on-type.rs:19:16 + --> $DIR/attribute-on-type-or-gen-arg.rs:19:16 | LL | let _: Bar<#[cfg(false)] 'static> = Bar(&123); | ^^^^^^^^^^^^^ attributes are not allowed here error: attributes cannot be applied to generic arguments - --> $DIR/attribute-on-type.rs:22:16 + --> $DIR/attribute-on-type-or-gen-arg.rs:22:16 | LL | let _: Baz<#[cfg(false)] 42> = Baz(42); | ^^^^^^^^^^^^^ attributes are not allowed here error: attributes cannot be applied to generic arguments - --> $DIR/attribute-on-type.rs:25:16 + --> $DIR/attribute-on-type-or-gen-arg.rs:25:16 | LL | let _: Foo<#[cfg(not(wrong))]String> = Foo(String::new()); | ^^^^^^^^^^^^^^^^^^ attributes are not allowed here error: attributes cannot be applied to generic arguments - --> $DIR/attribute-on-type.rs:28:16 + --> $DIR/attribute-on-type-or-gen-arg.rs:28:16 | LL | let _: Bar<#[cfg(false)] 'static> = Bar(&456); | ^^^^^^^^^^^^^ attributes are not allowed here error: attributes cannot be applied to generic arguments - --> $DIR/attribute-on-type.rs:31:23 + --> $DIR/attribute-on-type-or-gen-arg.rs:31:23 | LL | let _generic: Box<#[attr] i32> = Box::new(1); | ^^^^^^^ attributes are not allowed here error: attributes cannot be applied to types - --> $DIR/attribute-on-type.rs:34:22 + --> $DIR/attribute-on-type-or-gen-arg.rs:34:22 | LL | let _assignment: #[attr] i32 = *Box::new(1); | ^^^^^^^ attributes are not allowed here error: attributes cannot be applied to generic arguments - --> $DIR/attribute-on-type.rs:37:23 + --> $DIR/attribute-on-type-or-gen-arg.rs:37:23 | LL | let _complex: Vec<#[derive(Debug)] String> = vec![]; | ^^^^^^^^^^^^^^^^ attributes are not allowed here error: attributes cannot be applied to generic arguments - --> $DIR/attribute-on-type.rs:40:26 + --> $DIR/attribute-on-type-or-gen-arg.rs:40:26 | LL | let _nested: Box> = Box::new(vec![]); | ^^^^^^^^^^^^^^^^^^^^^^^^ attributes are not allowed here error: attributes cannot be applied to types - --> $DIR/attribute-on-type.rs:44:11 + --> $DIR/attribute-on-type-or-gen-arg.rs:44:11 | LL | fn g() -> #[attr] i32 { 0 } | ^^^^^^^ attributes are not allowed here error: attributes cannot be applied to types - --> $DIR/attribute-on-type.rs:48:12 + --> $DIR/attribute-on-type-or-gen-arg.rs:48:12 | LL | field: #[attr] i32, | ^^^^^^^ attributes are not allowed here error: attributes cannot be applied to types - --> $DIR/attribute-on-type.rs:50:14 + --> $DIR/attribute-on-type-or-gen-arg.rs:50:14 | LL | field1: (#[attr] i32, i32), | ^^^^^^^ attributes are not allowed here error: attributes cannot be applied to types - --> $DIR/attribute-on-type.rs:54:15 + --> $DIR/attribute-on-type-or-gen-arg.rs:54:15 | LL | type Tuple = (#[attr] i32, String); | ^^^^^^^ attributes are not allowed here error: attributes cannot be applied to types - --> $DIR/attribute-on-type.rs:57:6 + --> $DIR/attribute-on-type-or-gen-arg.rs:57:6 | LL | impl #[attr] S {} | ^^^^^^^ attributes are not allowed here diff --git a/tests/ui/parser/issues/issue-103143.rs b/tests/ui/parser/issues/issue-103143.rs index 90f10fc1a089c..a1cb73df798fe 100644 --- a/tests/ui/parser/issues/issue-103143.rs +++ b/tests/ui/parser/issues/issue-103143.rs @@ -2,4 +2,6 @@ fn main() { x::<#[a]y::> //~^ ERROR attributes cannot be applied to generic arguments //~| ERROR cannot find value `x` in this scope + //~| ERROR cannot find type `y` in this scope + //~| ERROR cannot find type `z` in this scope } diff --git a/tests/ui/parser/issues/issue-103143.stderr b/tests/ui/parser/issues/issue-103143.stderr index 168a2077396c7..6272ecc8526a9 100644 --- a/tests/ui/parser/issues/issue-103143.stderr +++ b/tests/ui/parser/issues/issue-103143.stderr @@ -10,6 +10,18 @@ error[E0425]: cannot find value `x` in this scope LL | x::<#[a]y::> | ^ not found in this scope -error: aborting due to 2 previous errors +error[E0425]: cannot find type `y` in this scope + --> $DIR/issue-103143.rs:2:13 + | +LL | x::<#[a]y::> + | ^ not found in this scope + +error[E0425]: cannot find type `z` in this scope + --> $DIR/issue-103143.rs:2:17 + | +LL | x::<#[a]y::> + | ^ not found in this scope + +error: aborting due to 4 previous errors For more information about this error, try `rustc --explain E0425`. From 4c139efaa50518df61ce42c95565d689c1e145b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Fri, 4 Sep 2026 02:29:25 +0200 Subject: [PATCH 8/9] Simplify recovery from outer attributes on `cfg_select` branches Admittedly, this slightly regresses diagnostic quality in certain scenarios but this can be improved in the future *for all* users of `recover_from_outer_attributes`. For example, it's a bit contradictory to say "expected outer doc comment" followed by "doc comments cannot be applied to XY". We should probably only emit a single error saying "doc comments cannot be applied to XY" but that needs modifications to `parse_outer_attributes` or something similar. --- .../src/attributes/cfg_select.rs | 30 +--------- compiler/rustc_parse/src/parser/attr.rs | 2 +- compiler/rustc_parse/src/parser/cfg_select.rs | 32 +---------- tests/ui/macros/cfg_select.rs | 11 ++-- tests/ui/macros/cfg_select.stderr | 55 +++++++++++-------- 5 files changed, 41 insertions(+), 89 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs b/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs index 4333559afb1c0..53d7044521e5a 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs @@ -4,7 +4,7 @@ use rustc_ast::{AttrStyle, NodeId, token}; use rustc_attr_ir::target::Target; use rustc_attr_ir::{AttrPath, CfgEntry}; use rustc_data_structures::fx::FxHashMap; -use rustc_errors::{Diagnostic, MultiSpan}; +use rustc_errors::Diagnostic; use rustc_feature::Features; use rustc_lint_defs::builtin::UNREACHABLE_CFG_SELECT_PREDICATES; use rustc_parse::exp; @@ -87,10 +87,9 @@ pub fn parse_cfg_select( lint_node_id: NodeId, ) -> Result { let mut branches = CfgSelectBranches::default(); - let mut branch_attr_error: Option = None; while p.token != token::Eof { - reject_branch_outer_attrs(p, &mut branch_attr_error)?; + p.recover_from_outer_attributes("`cfg_select` branches").map_err(|e| e.emit())?; if p.eat_keyword(exp!(Underscore)) { let underscore = p.prev_token; @@ -145,10 +144,6 @@ pub fn parse_cfg_select( } } - if let Some(guar) = branch_attr_error { - return Err(guar); - } - let it = branches .reachable .iter() @@ -161,27 +156,6 @@ pub fn parse_cfg_select( Ok(branches) } -fn reject_branch_outer_attrs( - p: &mut Parser<'_>, - branch_attr_error: &mut Option, -) -> Result<(), ErrorGuaranteed> { - let Some(spans) = p.parse_cfg_select_branch_outer_attrs().map_err(|e| e.emit())? else { - return Ok(()); - }; - - for (spans, msg) in [ - (spans.doc_comments, "doc comments are not allowed on `cfg_select` branches"), - (spans.attrs, "attributes are not allowed on `cfg_select` branches"), - ] { - if !spans.is_empty() { - branch_attr_error - .get_or_insert(p.dcx().struct_span_err(MultiSpan::from_spans(spans), msg).emit()); - } - } - - Ok(()) -} - fn lint_unreachable( p: &mut Parser<'_>, predicates: impl Iterator, diff --git a/compiler/rustc_parse/src/parser/attr.rs b/compiler/rustc_parse/src/parser/attr.rs index 091d01f658b51..4d6255aa08f8f 100644 --- a/compiler/rustc_parse/src/parser/attr.rs +++ b/compiler/rustc_parse/src/parser/attr.rs @@ -509,7 +509,7 @@ impl<'a> Parser<'a> { } /// Recover from outer attributes in places where none were expected. - pub(super) fn recover_from_outer_attributes(&mut self, target: &str) -> PResult<'a, ()> { + pub fn recover_from_outer_attributes(&mut self, target: &str) -> PResult<'a, ()> { // We check the token ourselves first to prevent `#` // from getting added to the set of expected tokens. if !self.may_recover() || !matches!(self.token.kind, token::Pound | token::DocComment(..)) { diff --git a/compiler/rustc_parse/src/parser/cfg_select.rs b/compiler/rustc_parse/src/parser/cfg_select.rs index 3d89cabbbc655..cf1ef62e56d5a 100644 --- a/compiler/rustc_parse/src/parser/cfg_select.rs +++ b/compiler/rustc_parse/src/parser/cfg_select.rs @@ -1,6 +1,6 @@ +use rustc_ast::token; use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_ast::util::classify; -use rustc_ast::{AttrKind, token}; use rustc_errors::PResult; use rustc_span::Span; @@ -48,34 +48,4 @@ impl<'a> Parser<'a> { } Ok(TokenStream::from_ast(&expr)) } - - /// Parses outer attributes before a `cfg_select!` branch for recovery. - pub fn parse_cfg_select_branch_outer_attrs( - &mut self, - ) -> PResult<'a, Option> { - let attrs = self.parse_outer_attributes()?; - if attrs.is_empty() { - return Ok(None); - } - - let mut spans = CfgSelectBranchAttrSpans::default(); - for attr in attrs.take_for_recovery(self.psess) { - match attr.kind { - AttrKind::Normal(..) => spans.attrs.push(attr.span), - AttrKind::Synthetic(..) => unreachable!(), - // `parse_outer_attributes` already emitted E0753 for inner doc comments before - // recovering them as outer doc-comment attributes. - AttrKind::DocComment(comment_kind, _) - if self.span_to_snippet(attr.span).ok().is_some_and( - |snippet| match comment_kind { - token::CommentKind::Line => snippet.starts_with("//!"), - token::CommentKind::Block => snippet.starts_with("/*!"), - }, - ) => {} - AttrKind::DocComment(..) => spans.doc_comments.push(attr.span), - } - } - - Ok(Some(spans)) - } } diff --git a/tests/ui/macros/cfg_select.rs b/tests/ui/macros/cfg_select.rs index 0d5fcec971550..10d6355b11c03 100644 --- a/tests/ui/macros/cfg_select.rs +++ b/tests/ui/macros/cfg_select.rs @@ -207,16 +207,16 @@ cfg_select! { // Regression test for https://github.com/rust-lang/rust/issues/155701. cfg_select! { /// doc comment - //~^ ERROR doc comments are not allowed on `cfg_select` branches + //~^ ERROR doc comments cannot be applied to `cfg_select` branches debug_assertions => {} /// doc comment - //~^ ERROR doc comments are not allowed on `cfg_select` branches + //~^ ERROR doc comments cannot be applied to `cfg_select` branches _ => {} } cfg_select! { #[cfg(false)] - //~^ ERROR attributes are not allowed on `cfg_select` branches + //~^ ERROR attributes cannot be applied to `cfg_select` branches debug_assertions => {} _ => {} } @@ -231,6 +231,7 @@ cfg_select! { cfg_select! { //! inner doc comment //~^ ERROR expected outer doc comment + //~| ERROR doc comments cannot be applied to `cfg_select` branches debug_assertions => {} _ => {} } @@ -238,7 +239,7 @@ cfg_select! { cfg_select! { debug_assertions => {} /// line1 - //~^ ERROR doc comments are not allowed on `cfg_select` branches + //~^ ERROR doc comments cannot be applied to `cfg_select` branches // line2 /// line3 _ => {} @@ -246,7 +247,7 @@ cfg_select! { cfg_select! { /// outer doc comment - //~^ ERROR doc comments are not allowed on `cfg_select` branches + //~^ ERROR doc comments cannot be applied to `cfg_select` branches //! inner doc comment //~^ ERROR expected outer doc comment debug_assertions => {} diff --git a/tests/ui/macros/cfg_select.stderr b/tests/ui/macros/cfg_select.stderr index 9af74456367f2..7c963785db289 100644 --- a/tests/ui/macros/cfg_select.stderr +++ b/tests/ui/macros/cfg_select.stderr @@ -55,23 +55,23 @@ error: expected one of `(`, `::`, `=>`, or `=`, found `!` LL | cfg!() => {} | ^ expected one of `(`, `::`, `=>`, or `=` -error: doc comments are not allowed on `cfg_select` branches +error: doc comments cannot be applied to `cfg_select` branches --> $DIR/cfg_select.rs:209:5 | LL | /// doc comment - | ^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ doc comments are not allowed here -error: doc comments are not allowed on `cfg_select` branches +error: doc comments cannot be applied to `cfg_select` branches --> $DIR/cfg_select.rs:212:5 | LL | /// doc comment - | ^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ doc comments are not allowed here -error: attributes are not allowed on `cfg_select` branches +error: attributes cannot be applied to `cfg_select` branches --> $DIR/cfg_select.rs:218:5 | LL | #[cfg(false)] - | ^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^ attributes are not allowed here error: an inner attribute is not permitted in this context --> $DIR/cfg_select.rs:225:5 @@ -95,17 +95,22 @@ LL - //! inner doc comment LL + // inner doc comment | -error: doc comments are not allowed on `cfg_select` branches - --> $DIR/cfg_select.rs:240:5 +error: doc comments cannot be applied to `cfg_select` branches + --> $DIR/cfg_select.rs:232:5 + | +LL | //! inner doc comment + | ^^^^^^^^^^^^^^^^^^^^^ doc comments are not allowed here + +error: doc comments cannot be applied to `cfg_select` branches + --> $DIR/cfg_select.rs:241:5 | -LL | /// line1 - | ^^^^^^^^^ -... -LL | /// line3 - | ^^^^^^^^^ +LL | / /// line1 +... | +LL | | /// line3 + | |_____________^ doc comments are not allowed here error[E0753]: expected outer doc comment - --> $DIR/cfg_select.rs:250:5 + --> $DIR/cfg_select.rs:251:5 | LL | //! inner doc comment | ^^^^^^^^^^^^^^^^^^^^^ @@ -117,20 +122,22 @@ LL - //! inner doc comment LL + // inner doc comment | -error: doc comments are not allowed on `cfg_select` branches - --> $DIR/cfg_select.rs:248:5 +error: doc comments cannot be applied to `cfg_select` branches + --> $DIR/cfg_select.rs:249:5 | -LL | /// outer doc comment - | ^^^^^^^^^^^^^^^^^^^^^ +LL | / /// outer doc comment +LL | | +LL | | //! inner doc comment + | |_________________________^ doc comments are not allowed here error[E0425]: cannot find type `Thing1` in this scope - --> $DIR/cfg_select.rs:278:13 + --> $DIR/cfg_select.rs:279:13 | LL | let t1: Thing1; | ^^^^^^ not found in this scope | note: found an item that was configured out - --> $DIR/cfg_select.rs:258:16 + --> $DIR/cfg_select.rs:259:16 | LL | all(true, false) => { | ----- the item is gated here @@ -138,20 +145,20 @@ LL | struct Thing1; | ^^^^^^ error[E0425]: cannot find type `Thing2` in this scope - --> $DIR/cfg_select.rs:280:13 + --> $DIR/cfg_select.rs:281:13 | LL | let t2: Thing2; | ^^^^^^ not found in this scope | note: found an item that was configured out - --> $DIR/cfg_select.rs:265:16 + --> $DIR/cfg_select.rs:266:16 | LL | feature = "meow" => { | ---------------- the item is gated behind the `meow` feature LL | struct Thing2; | ^^^^^^ note: found an item that was configured out - --> $DIR/cfg_select.rs:272:16 + --> $DIR/cfg_select.rs:273:16 | LL | all(true, feature = "meow") => { | ---------------- the item is gated behind the `meow` feature @@ -218,7 +225,7 @@ LL | cfg!() => {} = help: to expect this configuration use `--check-cfg=cfg(cfg)` = note: see for more information about checking conditional configuration -error: aborting due to 19 previous errors; 7 warnings emitted +error: aborting due to 20 previous errors; 7 warnings emitted Some errors have detailed explanations: E0425, E0539, E0565, E0753. For more information about an error, try `rustc --explain E0425`. From 8a845e5d154975b56a6972bd7946db8f91bfc214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Fri, 4 Sep 2026 03:46:46 +0200 Subject: [PATCH 9/9] Clarify the code responsible for parsing bare trait object types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Rename `parse_ty_tuple_or_parens` to `parse_paren_start_ty` since it doesn't just parse parenthesized types and tuple types, it also parses bare trait object types where the first bound is parenthesized. Naming is inspired by `parse_path_start_ty`. 2. Don't use `parse_remaining_bounds_path` ("finish parsing bare trait object type") when recovering from `for<…> impl …` / `for<…> impl …` since we're in fact not parsing a bare trait object type but either an impl-Trait type or a dyn-Trait type. This means we no longer need to match on the `TyKind` to extract the `bounds`. 3. Rename `parse_remaining_bounds_path` to `finish_parsing_bare_trait_object_ty` since the previous name really didn't make it clear that it's specific to bare trait object types. 4. Add a bunch of comments explaining what we're doing in (now) `parse_paren_start_ty` to successfully parse a bare trait object type where the first bound is parenthesized. 5. Inline `parse_remaining_bounds` since it didn't really have any reason to exist. The callsites look just fine or even better with the inlined body. --- compiler/rustc_parse/src/parser/ty.rs | 139 ++++++++++++++------------ 1 file changed, 77 insertions(+), 62 deletions(-) diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs index ff619f36448c6..f62f8f1765652 100644 --- a/compiler/rustc_parse/src/parser/ty.rs +++ b/compiler/rustc_parse/src/parser/ty.rs @@ -292,7 +292,7 @@ impl<'a> Parser<'a> { let lo = self.token.span; let mut impl_dyn_multi = false; let kind = if self.check(exp!(OpenParen)) { - self.parse_ty_tuple_or_parens(lo, allow_plus)? + self.parse_paren_start_ty(lo, allow_plus)? } else if self.eat(exp!(Bang)) { // Never type `!` TyKind::Never @@ -334,15 +334,18 @@ impl<'a> Parser<'a> { let kw = self.prev_token.ident().unwrap().0; let removal_span = kw.span.with_hi(self.token.span.lo()); let path = self.parse_path(PathStyle::Type)?; - let parse_plus = allow_plus == AllowPlus::Yes && self.check_plus(); - let kind = self.parse_remaining_bounds_path( + let mut bounds = thin_vec![GenericBound::Trait(PolyTraitRef::new( bound_vars, path, - lo, - parse_plus, + TraitBoundModifiers::NONE, + lo.to(self.prev_token.span), ast::Parens::No, - )?; - let err = self.dcx().create_err(diagnostics::TransposeDynOrImpl { + ))]; + if allow_plus == AllowPlus::Yes && self.check_plus() { + self.eat_plus(); + bounds.append(&mut self.parse_generic_bounds()?); + } + self.dcx().emit_err(diagnostics::TransposeDynOrImpl { span: kw.span, kw: kw.name.as_str(), sugg: diagnostics::TransposeDynOrImplSugg { @@ -351,24 +354,15 @@ impl<'a> Parser<'a> { kw: kw.name.as_str(), }, }); - - // Take the parsed bare trait object and turn it either - // into a `dyn` object or an `impl Trait`. - let kind = match (kind, kw.name) { - (TyKind::TraitObject(bounds, _), kw::Dyn) => { - TyKind::TraitObject(bounds, TraitObjectSyntax::Dyn) - } - (TyKind::TraitObject(bounds, _), kw::Impl) => { - TyKind::ImplTrait(ast::DUMMY_NODE_ID, bounds) - } - _ => return Err(err), - }; - err.emit(); - kind + match kw.name { + kw::Dyn => TyKind::TraitObject(bounds, TraitObjectSyntax::Dyn), + kw::Impl => TyKind::ImplTrait(ast::DUMMY_NODE_ID, bounds), + _ => unreachable!(), + } } else { let path = self.parse_path(PathStyle::Type)?; let parse_plus = allow_plus == AllowPlus::Yes && self.check_plus(); - self.parse_remaining_bounds_path( + self.finish_parsing_bare_trait_object_ty( bound_vars, path, lo, @@ -392,7 +386,7 @@ impl<'a> Parser<'a> { } else if self.check_path() { self.parse_path_start_ty(lo, allow_plus, ty_generics)? } else if self.can_begin_bound() { - self.parse_bare_trait_object(lo, allow_plus)? + self.parse_bare_trait_object_ty(lo, allow_plus)? } else if self.eat(exp!(DotDotDot)) { match allow_c_variadic { AllowCVariadic::Yes => TyKind::CVarArgs, @@ -442,10 +436,14 @@ impl<'a> Parser<'a> { Ok(TyKind::UnsafeBinder(Box::new(UnsafeBinderTy { generic_params, inner_ty }))) } - /// Parses either: - /// - `(TYPE)`, a parenthesized type. - /// - `(TYPE,)`, a tuple with a single field of type TYPE. - fn parse_ty_tuple_or_parens(&mut self, lo: Span, allow_plus: AllowPlus) -> PResult<'a, TyKind> { + /// Parse a type that begins with an opening parenthesis `(`. + /// + /// More specifically, it parses one of the following: + /// + /// 1. parenthesized type + /// 2. tuple type + /// 3. bare trait object type where the first trait bound is parenthesized + fn parse_paren_start_ty(&mut self, lo: Span, allow_plus: AllowPlus) -> PResult<'a, TyKind> { let mut trailing_plus = false; let (ts, trailing) = self.parse_paren_comma_seq(|p| { let ty = p.parse_ty()?; @@ -455,25 +453,41 @@ impl<'a> Parser<'a> { if ts.len() == 1 && matches!(trailing, Trailing::No) { let ty = ts.into_iter().next().unwrap(); + + // Let's check if we actually have a bare trait object type where the first trait bound + // is parenthesized. That's the case if the parentheses are followed by a `+` and if + // what's contained between the parentheses resembles a *BareTraitBound*. + // + // For context, looking at bounds in general (see *Bound*), only trait bounds are + // allowed to be wrapped in parentheses, not however lifetime and use bounds. let maybe_bounds = allow_plus == AllowPlus::Yes && self.token.is_like_plus(); match ty.kind { - // `"(" BareTraitBound ")" "+" Bound "+" ...`. - TyKind::Path(None, path) if maybe_bounds => self.parse_remaining_bounds_path( - ThinVec::new(), - path, - lo, - true, - ast::Parens::Yes, - ), - // For `('a) + …`, we know that `'a` in type position already lead to an error being - // emitted. To reduce output, let's indirectly suppress E0178 (bad `+` in type) and - // other irrelevant consequential errors. - TyKind::TraitObject(bounds, TraitObjectSyntax::None) + // `"(" TypePath ")" "+"` + TyKind::Path(None, path) if maybe_bounds => self + .finish_parsing_bare_trait_object_ty( + ThinVec::new(), + path, + lo, + true, + ast::Parens::Yes, + ), + // `"(" BareTraitBound\TypePath | UseBound ")" "+"` + // + // * FIXME: As alluded to above, only trait bounds are meant to allow parens. + // Arguably, it's an accident that we're permitting *UseBound*s and thus types + // like `(use<>)+`. Might need a T-lang FCP to change this. + // * We're checking `!trailing_plus` to prevent us from accepting code like + // `(T+)+` or `('a+)+`. + // * While we could be looking at `('a)+` which we don't want to accept, we + // know that the `parse_ty` above has already emitted an error since the + // lifetime isn't immediately followed by a `+`. + TyKind::TraitObject(mut bounds, TraitObjectSyntax::None) if maybe_bounds && bounds.len() == 1 && !trailing_plus => { - self.parse_remaining_bounds(bounds, true) + self.eat_plus(); + bounds.append(&mut self.parse_generic_bounds()?); + Ok(TyKind::TraitObject(bounds, TraitObjectSyntax::None)) } - // `(TYPE)` _ => Ok(TyKind::Paren(ty)), } } else { @@ -481,7 +495,11 @@ impl<'a> Parser<'a> { } } - fn parse_bare_trait_object(&mut self, lo: Span, allow_plus: AllowPlus) -> PResult<'a, TyKind> { + fn parse_bare_trait_object_ty( + &mut self, + lo: Span, + allow_plus: AllowPlus, + ) -> PResult<'a, TyKind> { // A lifetime only begins a bare trait object type if it is followed by `+`! if self.token.is_lifetime() && !self.look_ahead(1, |t| t.is_like_plus()) { // In Rust 2021 and beyond, we assume that the user didn't intend to write a bare trait @@ -538,7 +556,7 @@ impl<'a> Parser<'a> { } } - fn parse_remaining_bounds_path( + fn finish_parsing_bare_trait_object_ty( &mut self, generic_params: ThinVec, path: ast::Path, @@ -546,25 +564,15 @@ impl<'a> Parser<'a> { parse_plus: bool, parens: ast::Parens, ) -> PResult<'a, TyKind> { - let poly_trait_ref = PolyTraitRef::new( + let mut bounds = thin_vec![GenericBound::Trait(PolyTraitRef::new( generic_params, path, TraitBoundModifiers::NONE, lo.to(self.prev_token.span), parens, - ); - let bounds = thin_vec![GenericBound::Trait(poly_trait_ref)]; - self.parse_remaining_bounds(bounds, parse_plus) - } - - /// Parse the remainder of a bare trait object type given an already parsed list. - fn parse_remaining_bounds( - &mut self, - mut bounds: GenericBounds, - plus: bool, - ) -> PResult<'a, TyKind> { - if plus { - self.eat_plus(); // `+`, or `+=` gets split and `+` is discarded + ))]; + if parse_plus { + self.eat_plus(); bounds.append(&mut self.parse_generic_bounds()?); } Ok(TyKind::TraitObject(bounds, TraitObjectSyntax::None)) @@ -990,11 +998,11 @@ impl<'a> Parser<'a> { Ok(TyKind::TraitObject(bounds, TraitObjectSyntax::Dyn)) } - /// Parses a type starting with a path. + /// Parse a type that begins with a path. /// /// This can be: /// 1. a type macro, `mac!(...)`, - /// 2. a bare trait object, `B0 + ... + Bn`, + /// 2. a bare trait object type, `B0 + ... + Bn`, /// 3. or a path, `path::to::MyType`. fn parse_path_start_ty( &mut self, @@ -1009,7 +1017,13 @@ impl<'a> Parser<'a> { Ok(TyKind::MacCall(Box::new(MacCall { path, args: self.parse_delim_args()? }))) } else if allow_plus == AllowPlus::Yes && self.check_plus() { // `Trait1 + Trait2 + 'a` - self.parse_remaining_bounds_path(ThinVec::new(), path, lo, true, ast::Parens::No) + self.finish_parsing_bare_trait_object_ty( + ThinVec::new(), + path, + lo, + true, + ast::Parens::No, + ) } else { // Just a type path. Ok(TyKind::Path(None, path)) @@ -1385,9 +1399,10 @@ impl<'a> Parser<'a> { // Someone has written something like `&dyn (Trait + Other)`. The correct code // would be `&(dyn Trait + Other)` if self.token.is_like_plus() && leading_token.is_keyword(kw::Dyn) { - let bounds = thin_vec![]; - self.parse_remaining_bounds(bounds, true)?; + self.eat_plus(); + self.parse_generic_bounds()?; self.expect(exp!(CloseParen))?; + self.dcx().emit_err(diagnostics::IncorrectParensTraitBounds { span: vec![lo, self.prev_token.span], sugg: diagnostics::IncorrectParensTraitBoundsSugg {