From f8f7c27d4f723913a6929e591c612865a7b62f70 Mon Sep 17 00:00:00 2001 From: León Orell Valerian Liehr Date: Fri, 15 Aug 2025 23:01:32 +0200 Subject: Clean up parsers related to generic bounds --- compiler/rustc_parse/src/errors.rs | 21 --------------------- 1 file changed, 21 deletions(-) (limited to 'compiler/rustc_parse/src/errors.rs') diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index ddb2c545c78..6690025ef89 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -3136,27 +3136,6 @@ pub(crate) struct ModifierLifetime { pub modifier: &'static str, } -#[derive(Subdiagnostic)] -#[multipart_suggestion( - parse_parenthesized_lifetime_suggestion, - applicability = "machine-applicable" -)] -pub(crate) struct RemoveParens { - #[suggestion_part(code = "")] - pub lo: Span, - #[suggestion_part(code = "")] - pub hi: Span, -} - -#[derive(Diagnostic)] -#[diag(parse_parenthesized_lifetime)] -pub(crate) struct ParenthesizedLifetime { - #[primary_span] - pub span: Span, - #[subdiagnostic] - pub sugg: RemoveParens, -} - #[derive(Diagnostic)] #[diag(parse_underscore_literal_suffix)] pub(crate) struct UnderscoreLiteralSuffix { -- cgit 1.4.1-3-g733a5 From 30bb7045d6b80b979b235396626f3b405cfec160 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Mon, 4 Aug 2025 09:42:27 +0000 Subject: don't print invalid labels with `r#` --- compiler/rustc_parse/src/errors.rs | 2 +- compiler/rustc_parse/src/parser/expr.rs | 8 +++++++- tests/ui/closures/issue-52437.rs | 2 +- tests/ui/closures/issue-52437.stderr | 2 +- tests/ui/issues/issue-46311.rs | 2 +- tests/ui/issues/issue-46311.stderr | 2 +- tests/ui/label/label-static.rs | 4 ++-- tests/ui/label/label-static.stderr | 4 ++-- tests/ui/label/label-underscore.rs | 4 ++-- tests/ui/label/label-underscore.stderr | 4 ++-- tests/ui/parser/require-parens-for-chained-comparison.stderr | 4 ++-- 11 files changed, 22 insertions(+), 16 deletions(-) (limited to 'compiler/rustc_parse/src/errors.rs') diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index 2c046329e33..7d84fc2a2f9 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -2232,7 +2232,7 @@ pub(crate) struct KeywordLifetime { pub(crate) struct InvalidLabel { #[primary_span] pub span: Span, - pub name: Symbol, + pub name: String, } #[derive(Diagnostic)] diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index cb7f755f524..effcfd54a8f 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -3094,7 +3094,13 @@ impl<'a> Parser<'a> { if let Some((ident, is_raw)) = self.token.lifetime() { // Disallow `'fn`, but with a better error message than `expect_lifetime`. if matches!(is_raw, IdentIsRaw::No) && ident.without_first_quote().is_reserved() { - self.dcx().emit_err(errors::InvalidLabel { span: ident.span, name: ident.name }); + self.dcx().emit_err(errors::InvalidLabel { + span: ident.span, + // `IntoDiagArg` prints the symbol as if it was an ident, + // so `'break` is printed as `r#break`. We don't want that + // here so convert to string eagerly. + name: ident.without_first_quote().name.to_string(), + }); } self.bump(); diff --git a/tests/ui/closures/issue-52437.rs b/tests/ui/closures/issue-52437.rs index 6ac5380a5aa..0655eac517b 100644 --- a/tests/ui/closures/issue-52437.rs +++ b/tests/ui/closures/issue-52437.rs @@ -1,5 +1,5 @@ fn main() { [(); &(&'static: loop { |x| {}; }) as *const _ as usize] - //~^ ERROR: invalid label name `'static` + //~^ ERROR: invalid label name `static` //~| ERROR: type annotations needed } diff --git a/tests/ui/closures/issue-52437.stderr b/tests/ui/closures/issue-52437.stderr index 9ba24c7a886..e8832f42f10 100644 --- a/tests/ui/closures/issue-52437.stderr +++ b/tests/ui/closures/issue-52437.stderr @@ -1,4 +1,4 @@ -error: invalid label name `'static` +error: invalid label name `static` --> $DIR/issue-52437.rs:2:13 | LL | [(); &(&'static: loop { |x| {}; }) as *const _ as usize] diff --git a/tests/ui/issues/issue-46311.rs b/tests/ui/issues/issue-46311.rs index 1233a49c582..69826d78e2d 100644 --- a/tests/ui/issues/issue-46311.rs +++ b/tests/ui/issues/issue-46311.rs @@ -1,4 +1,4 @@ fn main() { - 'break: loop { //~ ERROR invalid label name `'break` + 'break: loop { //~ ERROR invalid label name `break` } } diff --git a/tests/ui/issues/issue-46311.stderr b/tests/ui/issues/issue-46311.stderr index 86a3602899a..e2fe3130d72 100644 --- a/tests/ui/issues/issue-46311.stderr +++ b/tests/ui/issues/issue-46311.stderr @@ -1,4 +1,4 @@ -error: invalid label name `'break` +error: invalid label name `break` --> $DIR/issue-46311.rs:2:5 | LL | 'break: loop { diff --git a/tests/ui/label/label-static.rs b/tests/ui/label/label-static.rs index 95e764d0187..46fa6ac81d8 100644 --- a/tests/ui/label/label-static.rs +++ b/tests/ui/label/label-static.rs @@ -1,5 +1,5 @@ fn main() { - 'static: loop { //~ ERROR invalid label name `'static` - break 'static //~ ERROR invalid label name `'static` + 'static: loop { //~ ERROR invalid label name `static` + break 'static //~ ERROR invalid label name `static` } } diff --git a/tests/ui/label/label-static.stderr b/tests/ui/label/label-static.stderr index 1d3251d1b5f..890a7b12c61 100644 --- a/tests/ui/label/label-static.stderr +++ b/tests/ui/label/label-static.stderr @@ -1,10 +1,10 @@ -error: invalid label name `'static` +error: invalid label name `static` --> $DIR/label-static.rs:2:5 | LL | 'static: loop { | ^^^^^^^ -error: invalid label name `'static` +error: invalid label name `static` --> $DIR/label-static.rs:3:15 | LL | break 'static diff --git a/tests/ui/label/label-underscore.rs b/tests/ui/label/label-underscore.rs index de67f3d2c3e..ed44957d1d8 100644 --- a/tests/ui/label/label-underscore.rs +++ b/tests/ui/label/label-underscore.rs @@ -1,5 +1,5 @@ fn main() { - '_: loop { //~ ERROR invalid label name `'_` - break '_ //~ ERROR invalid label name `'_` + '_: loop { //~ ERROR invalid label name `_` + break '_ //~ ERROR invalid label name `_` } } diff --git a/tests/ui/label/label-underscore.stderr b/tests/ui/label/label-underscore.stderr index 4558ec4cb41..31c962b6ae5 100644 --- a/tests/ui/label/label-underscore.stderr +++ b/tests/ui/label/label-underscore.stderr @@ -1,10 +1,10 @@ -error: invalid label name `'_` +error: invalid label name `_` --> $DIR/label-underscore.rs:2:5 | LL | '_: loop { | ^^ -error: invalid label name `'_` +error: invalid label name `_` --> $DIR/label-underscore.rs:3:15 | LL | break '_ diff --git a/tests/ui/parser/require-parens-for-chained-comparison.stderr b/tests/ui/parser/require-parens-for-chained-comparison.stderr index 857c4a55788..f7a7cb848c0 100644 --- a/tests/ui/parser/require-parens-for-chained-comparison.stderr +++ b/tests/ui/parser/require-parens-for-chained-comparison.stderr @@ -53,7 +53,7 @@ help: use `::<...>` instead of `<...>` to specify lifetime, type, or const argum LL | let _ = f::(); | ++ -error: invalid label name `'_` +error: invalid label name `_` --> $DIR/require-parens-for-chained-comparison.rs:22:15 | LL | let _ = f<'_, i8>(); @@ -81,7 +81,7 @@ help: use `::<...>` instead of `<...>` to specify lifetime, type, or const argum LL | let _ = f::<'_, i8>(); | ++ -error: invalid label name `'_` +error: invalid label name `_` --> $DIR/require-parens-for-chained-comparison.rs:29:7 | LL | f<'_>(); -- cgit 1.4.1-3-g733a5 From 4970127c33a2cfd7b7035daedba7cac512a2e201 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Fri, 22 Aug 2025 13:06:49 +0800 Subject: address review comments --- compiler/rustc_parse/messages.ftl | 5 ++--- compiler/rustc_parse/src/errors.rs | 5 ++--- compiler/rustc_parse/src/parser/expr.rs | 8 +------- compiler/rustc_parse/src/parser/ty.rs | 3 +-- compiler/rustc_span/src/symbol.rs | 12 ++++++++---- tests/ui/closures/issue-52437.rs | 2 +- tests/ui/closures/issue-52437.stderr | 2 +- tests/ui/issues/issue-46311.rs | 2 +- tests/ui/issues/issue-46311.stderr | 2 +- tests/ui/label/label-static.rs | 4 ++-- tests/ui/label/label-static.stderr | 4 ++-- tests/ui/label/label-underscore.rs | 4 ++-- tests/ui/label/label-underscore.stderr | 4 ++-- tests/ui/parser/require-parens-for-chained-comparison.rs | 4 ++-- tests/ui/parser/require-parens-for-chained-comparison.stderr | 4 ++-- 15 files changed, 30 insertions(+), 35 deletions(-) (limited to 'compiler/rustc_parse/src/errors.rs') diff --git a/compiler/rustc_parse/messages.ftl b/compiler/rustc_parse/messages.ftl index a107a682184..40df0d38848 100644 --- a/compiler/rustc_parse/messages.ftl +++ b/compiler/rustc_parse/messages.ftl @@ -468,9 +468,6 @@ parse_invalid_dyn_keyword = invalid `dyn` keyword parse_invalid_expression_in_let_else = a `{$operator}` expression cannot be directly assigned in `let...else` parse_invalid_identifier_with_leading_number = identifiers cannot start with a number -parse_invalid_label = - invalid label name `{$name}` - parse_invalid_literal_suffix_on_tuple_index = suffixes on a tuple index are invalid .label = invalid suffix `{$suffix}` .tuple_exception_line_1 = `{$suffix}` is *temporarily* accepted on tuple index fields as it was incorrectly accepted on stable for a few releases @@ -500,6 +497,8 @@ parse_invalid_unicode_escape = invalid unicode character escape parse_invalid_variable_declaration = invalid variable declaration +parse_keyword_label = labels cannot use keyword names + parse_keyword_lifetime = lifetimes cannot use keyword names diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index 7d84fc2a2f9..4cc53f1e518 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -2228,11 +2228,10 @@ pub(crate) struct KeywordLifetime { } #[derive(Diagnostic)] -#[diag(parse_invalid_label)] -pub(crate) struct InvalidLabel { +#[diag(parse_keyword_label)] +pub(crate) struct KeywordLabel { #[primary_span] pub span: Span, - pub name: String, } #[derive(Diagnostic)] diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index effcfd54a8f..4ac5e1a578c 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -3094,13 +3094,7 @@ impl<'a> Parser<'a> { if let Some((ident, is_raw)) = self.token.lifetime() { // Disallow `'fn`, but with a better error message than `expect_lifetime`. if matches!(is_raw, IdentIsRaw::No) && ident.without_first_quote().is_reserved() { - self.dcx().emit_err(errors::InvalidLabel { - span: ident.span, - // `IntoDiagArg` prints the symbol as if it was an ident, - // so `'break` is printed as `r#break`. We don't want that - // here so convert to string eagerly. - name: ident.without_first_quote().name.to_string(), - }); + self.dcx().emit_err(errors::KeywordLabel { span: ident.span }); } self.bump(); diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs index 899a43955ab..109c77b9bab 100644 --- a/compiler/rustc_parse/src/parser/ty.rs +++ b/compiler/rustc_parse/src/parser/ty.rs @@ -1479,8 +1479,7 @@ impl<'a> Parser<'a> { pub(super) fn expect_lifetime(&mut self) -> Lifetime { if let Some((ident, is_raw)) = self.token.lifetime() { if matches!(is_raw, IdentIsRaw::No) - && ident.without_first_quote().is_reserved() - && ![kw::UnderscoreLifetime, kw::StaticLifetime].contains(&ident.name) + && ident.without_first_quote().is_reserved_lifetime() { self.dcx().emit_err(errors::KeywordLifetime { span: ident.span }); } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index d6acd902c7c..744a4771aae 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -3045,13 +3045,17 @@ impl Ident { self.name.can_be_raw() && self.is_reserved() } + /// Given the name of a lifetime without the first quote (`'`), + /// returns whether the lifetime name is reserved (therefore invalid) + pub fn is_reserved_lifetime(self) -> bool { + self.is_reserved() && ![kw::Underscore, kw::Static].contains(&self.name) + } + pub fn is_raw_lifetime_guess(self) -> bool { - // this should be kept consistent with `Parser::expect_lifetime` found under - // compiler/rustc_parse/src/parser/ty.rs let name_without_apostrophe = self.without_first_quote(); name_without_apostrophe.name != self.name - && ![kw::UnderscoreLifetime, kw::StaticLifetime].contains(&self.name) - && name_without_apostrophe.is_raw_guess() + && name_without_apostrophe.name.can_be_raw() + && name_without_apostrophe.is_reserved_lifetime() } pub fn guess_print_mode(self) -> IdentPrintMode { diff --git a/tests/ui/closures/issue-52437.rs b/tests/ui/closures/issue-52437.rs index 0655eac517b..98b04d179af 100644 --- a/tests/ui/closures/issue-52437.rs +++ b/tests/ui/closures/issue-52437.rs @@ -1,5 +1,5 @@ fn main() { [(); &(&'static: loop { |x| {}; }) as *const _ as usize] - //~^ ERROR: invalid label name `static` + //~^ ERROR: labels cannot use keyword names //~| ERROR: type annotations needed } diff --git a/tests/ui/closures/issue-52437.stderr b/tests/ui/closures/issue-52437.stderr index e8832f42f10..8c6fa097ec5 100644 --- a/tests/ui/closures/issue-52437.stderr +++ b/tests/ui/closures/issue-52437.stderr @@ -1,4 +1,4 @@ -error: invalid label name `static` +error: labels cannot use keyword names --> $DIR/issue-52437.rs:2:13 | LL | [(); &(&'static: loop { |x| {}; }) as *const _ as usize] diff --git a/tests/ui/issues/issue-46311.rs b/tests/ui/issues/issue-46311.rs index 69826d78e2d..24435665501 100644 --- a/tests/ui/issues/issue-46311.rs +++ b/tests/ui/issues/issue-46311.rs @@ -1,4 +1,4 @@ fn main() { - 'break: loop { //~ ERROR invalid label name `break` + 'break: loop { //~ ERROR labels cannot use keyword names } } diff --git a/tests/ui/issues/issue-46311.stderr b/tests/ui/issues/issue-46311.stderr index e2fe3130d72..f040ba2c026 100644 --- a/tests/ui/issues/issue-46311.stderr +++ b/tests/ui/issues/issue-46311.stderr @@ -1,4 +1,4 @@ -error: invalid label name `break` +error: labels cannot use keyword names --> $DIR/issue-46311.rs:2:5 | LL | 'break: loop { diff --git a/tests/ui/label/label-static.rs b/tests/ui/label/label-static.rs index 46fa6ac81d8..ef06658506a 100644 --- a/tests/ui/label/label-static.rs +++ b/tests/ui/label/label-static.rs @@ -1,5 +1,5 @@ fn main() { - 'static: loop { //~ ERROR invalid label name `static` - break 'static //~ ERROR invalid label name `static` + 'static: loop { //~ ERROR labels cannot use keyword names + break 'static //~ ERROR labels cannot use keyword names } } diff --git a/tests/ui/label/label-static.stderr b/tests/ui/label/label-static.stderr index 890a7b12c61..f6ae8b44c08 100644 --- a/tests/ui/label/label-static.stderr +++ b/tests/ui/label/label-static.stderr @@ -1,10 +1,10 @@ -error: invalid label name `static` +error: labels cannot use keyword names --> $DIR/label-static.rs:2:5 | LL | 'static: loop { | ^^^^^^^ -error: invalid label name `static` +error: labels cannot use keyword names --> $DIR/label-static.rs:3:15 | LL | break 'static diff --git a/tests/ui/label/label-underscore.rs b/tests/ui/label/label-underscore.rs index ed44957d1d8..8f1f90fe7c0 100644 --- a/tests/ui/label/label-underscore.rs +++ b/tests/ui/label/label-underscore.rs @@ -1,5 +1,5 @@ fn main() { - '_: loop { //~ ERROR invalid label name `_` - break '_ //~ ERROR invalid label name `_` + '_: loop { //~ ERROR labels cannot use keyword names + break '_ //~ ERROR labels cannot use keyword names } } diff --git a/tests/ui/label/label-underscore.stderr b/tests/ui/label/label-underscore.stderr index 31c962b6ae5..c0eb178d0f0 100644 --- a/tests/ui/label/label-underscore.stderr +++ b/tests/ui/label/label-underscore.stderr @@ -1,10 +1,10 @@ -error: invalid label name `_` +error: labels cannot use keyword names --> $DIR/label-underscore.rs:2:5 | LL | '_: loop { | ^^ -error: invalid label name `_` +error: labels cannot use keyword names --> $DIR/label-underscore.rs:3:15 | LL | break '_ diff --git a/tests/ui/parser/require-parens-for-chained-comparison.rs b/tests/ui/parser/require-parens-for-chained-comparison.rs index 21a908923f2..6152fff6c03 100644 --- a/tests/ui/parser/require-parens-for-chained-comparison.rs +++ b/tests/ui/parser/require-parens-for-chained-comparison.rs @@ -24,14 +24,14 @@ fn main() { //~| HELP use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments //~| ERROR expected //~| HELP add `'` to close the char literal - //~| ERROR invalid label name + //~| ERROR labels cannot use keyword names f<'_>(); //~^ ERROR comparison operators cannot be chained //~| HELP use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments //~| ERROR expected //~| HELP add `'` to close the char literal - //~| ERROR invalid label name + //~| ERROR labels cannot use keyword names let _ = f; //~^ ERROR comparison operators cannot be chained diff --git a/tests/ui/parser/require-parens-for-chained-comparison.stderr b/tests/ui/parser/require-parens-for-chained-comparison.stderr index f7a7cb848c0..9edfae36250 100644 --- a/tests/ui/parser/require-parens-for-chained-comparison.stderr +++ b/tests/ui/parser/require-parens-for-chained-comparison.stderr @@ -53,7 +53,7 @@ help: use `::<...>` instead of `<...>` to specify lifetime, type, or const argum LL | let _ = f::(); | ++ -error: invalid label name `_` +error: labels cannot use keyword names --> $DIR/require-parens-for-chained-comparison.rs:22:15 | LL | let _ = f<'_, i8>(); @@ -81,7 +81,7 @@ help: use `::<...>` instead of `<...>` to specify lifetime, type, or const argum LL | let _ = f::<'_, i8>(); | ++ -error: invalid label name `_` +error: labels cannot use keyword names --> $DIR/require-parens-for-chained-comparison.rs:29:7 | LL | f<'_>(); -- cgit 1.4.1-3-g733a5 From 21d31897794ed7fc7990de8e664d3c4ec511da7d Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Tue, 29 Jul 2025 16:35:22 +0200 Subject: Move validate_attr to `rustc_attr_parsing` --- Cargo.lock | 3 +- compiler/rustc_ast_passes/Cargo.toml | 1 - compiler/rustc_ast_passes/src/ast_validation.rs | 2 +- compiler/rustc_attr_parsing/Cargo.toml | 1 + compiler/rustc_attr_parsing/messages.ftl | 12 + compiler/rustc_attr_parsing/src/lib.rs | 1 + .../rustc_attr_parsing/src/session_diagnostics.rs | 55 +++- compiler/rustc_attr_parsing/src/validate_attr.rs | 340 ++++++++++++++++++++ .../rustc_builtin_macros/src/cfg_accessible.rs | 2 +- compiler/rustc_builtin_macros/src/derive.rs | 2 +- compiler/rustc_builtin_macros/src/util.rs | 3 +- compiler/rustc_expand/src/config.rs | 4 +- compiler/rustc_expand/src/expand.rs | 3 +- compiler/rustc_expand/src/module.rs | 3 +- compiler/rustc_interface/src/passes.rs | 5 +- compiler/rustc_interface/src/util.rs | 2 +- compiler/rustc_parse/Cargo.toml | 1 - compiler/rustc_parse/messages.ftl | 10 - compiler/rustc_parse/src/errors.rs | 41 --- compiler/rustc_parse/src/lib.rs | 17 +- compiler/rustc_parse/src/parser/attr.rs | 2 +- compiler/rustc_parse/src/validate_attr.rs | 350 --------------------- 22 files changed, 437 insertions(+), 423 deletions(-) create mode 100644 compiler/rustc_attr_parsing/src/validate_attr.rs delete mode 100644 compiler/rustc_parse/src/validate_attr.rs (limited to 'compiler/rustc_parse/src/errors.rs') diff --git a/Cargo.lock b/Cargo.lock index 91528a4135e..8eb77165cf2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3459,7 +3459,6 @@ dependencies = [ "rustc_feature", "rustc_fluent_macro", "rustc_macros", - "rustc_parse", "rustc_session", "rustc_span", "rustc_target", @@ -3490,6 +3489,7 @@ dependencies = [ "rustc_hir", "rustc_lexer", "rustc_macros", + "rustc_parse", "rustc_session", "rustc_span", "thin-vec", @@ -4355,7 +4355,6 @@ dependencies = [ "rustc-literal-escaper", "rustc_ast", "rustc_ast_pretty", - "rustc_attr_parsing", "rustc_data_structures", "rustc_errors", "rustc_feature", diff --git a/compiler/rustc_ast_passes/Cargo.toml b/compiler/rustc_ast_passes/Cargo.toml index 1940628b44a..3e04f8b11ec 100644 --- a/compiler/rustc_ast_passes/Cargo.toml +++ b/compiler/rustc_ast_passes/Cargo.toml @@ -15,7 +15,6 @@ rustc_errors = { path = "../rustc_errors" } rustc_feature = { path = "../rustc_feature" } rustc_fluent_macro = { path = "../rustc_fluent_macro" } rustc_macros = { path = "../rustc_macros" } -rustc_parse = { path = "../rustc_parse" } rustc_session = { path = "../rustc_session" } rustc_span = { path = "../rustc_span" } rustc_target = { path = "../rustc_target" } diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index ef4410566c5..508eb083672 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -25,10 +25,10 @@ use rustc_abi::{CanonAbi, ExternAbi, InterruptKind}; use rustc_ast::visit::{AssocCtxt, BoundKind, FnCtxt, FnKind, Visitor, walk_list}; use rustc_ast::*; use rustc_ast_pretty::pprust::{self, State}; +use rustc_attr_parsing::validate_attr; use rustc_data_structures::fx::FxIndexMap; use rustc_errors::DiagCtxtHandle; use rustc_feature::Features; -use rustc_parse::validate_attr; use rustc_session::Session; use rustc_session::lint::builtin::{ DEPRECATED_WHERE_CLAUSE_LOCATION, MISSING_ABI, MISSING_UNSAFE_ON_EXTERN, diff --git a/compiler/rustc_attr_parsing/Cargo.toml b/compiler/rustc_attr_parsing/Cargo.toml index cec9d62e656..fd8f7ffb2ed 100644 --- a/compiler/rustc_attr_parsing/Cargo.toml +++ b/compiler/rustc_attr_parsing/Cargo.toml @@ -14,6 +14,7 @@ rustc_fluent_macro = { path = "../rustc_fluent_macro" } rustc_hir = { path = "../rustc_hir" } rustc_lexer = { path = "../rustc_lexer" } rustc_macros = { path = "../rustc_macros" } +rustc_parse = { path = "../rustc_parse" } rustc_session = { path = "../rustc_session" } rustc_span = { path = "../rustc_span" } thin-vec = "0.2.12" diff --git a/compiler/rustc_attr_parsing/messages.ftl b/compiler/rustc_attr_parsing/messages.ftl index 067d95a0f48..1a2815c54ec 100644 --- a/compiler/rustc_attr_parsing/messages.ftl +++ b/compiler/rustc_attr_parsing/messages.ftl @@ -170,3 +170,15 @@ attr_parsing_unused_multiple = -attr_parsing_previously_accepted = this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + +attr_parsing_meta_bad_delim = wrong meta list delimiters +attr_parsing_meta_bad_delim_suggestion = the delimiters should be `(` and `)` + +attr_parsing_unsafe_attr_outside_unsafe = unsafe attribute used without unsafe + .label = usage of unsafe attribute +attr_parsing_unsafe_attr_outside_unsafe_suggestion = wrap the attribute in `unsafe(...)` + +attr_parsing_invalid_attr_unsafe = `{$name}` is not an unsafe attribute + .label = this is not an unsafe attribute + .suggestion = remove the `unsafe(...)` + .note = extraneous unsafe is not allowed in attributes diff --git a/compiler/rustc_attr_parsing/src/lib.rs b/compiler/rustc_attr_parsing/src/lib.rs index 99842cd9687..969c24a4f89 100644 --- a/compiler/rustc_attr_parsing/src/lib.rs +++ b/compiler/rustc_attr_parsing/src/lib.rs @@ -102,6 +102,7 @@ pub mod parser; mod lints; mod session_diagnostics; mod target_checking; +pub mod validate_attr; pub use attributes::cfg::{CFG_TEMPLATE, EvalConfigResult, eval_config_entry, parse_cfg_attr}; pub use attributes::cfg_old::*; diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 2993881f717..b877caa143a 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -1,6 +1,6 @@ use std::num::IntErrorKind; -use rustc_ast::{self as ast, AttrStyle}; +use rustc_ast::{self as ast, AttrStyle, Path}; use rustc_errors::codes::*; use rustc_errors::{ Applicability, Diag, DiagArgValue, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level, @@ -737,3 +737,56 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError { diag } } + +#[derive(Diagnostic)] +#[diag(attr_parsing_invalid_attr_unsafe)] +#[note] +pub(crate) struct InvalidAttrUnsafe { + #[primary_span] + #[label] + pub span: Span, + pub name: Path, +} + +#[derive(Diagnostic)] +#[diag(attr_parsing_unsafe_attr_outside_unsafe)] +pub(crate) struct UnsafeAttrOutsideUnsafe { + #[primary_span] + #[label] + pub span: Span, + #[subdiagnostic] + pub suggestion: UnsafeAttrOutsideUnsafeSuggestion, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion( + attr_parsing_unsafe_attr_outside_unsafe_suggestion, + applicability = "machine-applicable" +)] +pub(crate) struct UnsafeAttrOutsideUnsafeSuggestion { + #[suggestion_part(code = "unsafe(")] + pub left: Span, + #[suggestion_part(code = ")")] + pub right: Span, +} + +#[derive(Diagnostic)] +#[diag(attr_parsing_meta_bad_delim)] +pub(crate) struct MetaBadDelim { + #[primary_span] + pub span: Span, + #[subdiagnostic] + pub sugg: MetaBadDelimSugg, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion( + attr_parsing_meta_bad_delim_suggestion, + applicability = "machine-applicable" +)] +pub(crate) struct MetaBadDelimSugg { + #[suggestion_part(code = "(")] + pub open: Span, + #[suggestion_part(code = ")")] + pub close: Span, +} diff --git a/compiler/rustc_attr_parsing/src/validate_attr.rs b/compiler/rustc_attr_parsing/src/validate_attr.rs new file mode 100644 index 00000000000..da3a1cbe016 --- /dev/null +++ b/compiler/rustc_attr_parsing/src/validate_attr.rs @@ -0,0 +1,340 @@ +//! Meta-syntax validation logic of attributes for post-expansion. + +use std::slice; + +use rustc_ast::token::Delimiter; +use rustc_ast::tokenstream::DelimSpan; +use rustc_ast::{ + self as ast, AttrArgs, Attribute, DelimArgs, MetaItem, MetaItemInner, MetaItemKind, NodeId, + Path, Safety, +}; +use rustc_errors::{Applicability, DiagCtxtHandle, FatalError, PResult}; +use rustc_feature::{AttributeSafety, AttributeTemplate, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute}; +use rustc_parse::parse_in; +use rustc_session::errors::report_lit_error; +use rustc_session::lint::BuiltinLintDiag; +use rustc_session::lint::builtin::{ILL_FORMED_ATTRIBUTE_INPUT, UNSAFE_ATTR_OUTSIDE_UNSAFE}; +use rustc_session::parse::ParseSess; +use rustc_span::{Span, Symbol, sym}; + +use crate::{AttributeParser, Late, session_diagnostics as errors}; + +pub fn check_attr(psess: &ParseSess, attr: &Attribute, id: NodeId) { + if attr.is_doc_comment() || attr.has_name(sym::cfg_trace) || attr.has_name(sym::cfg_attr_trace) + { + return; + } + + let builtin_attr_info = attr.ident().and_then(|ident| BUILTIN_ATTRIBUTE_MAP.get(&ident.name)); + + let builtin_attr_safety = builtin_attr_info.map(|x| x.safety); + check_attribute_safety(psess, builtin_attr_safety, attr, id); + + // Check input tokens for built-in and key-value attributes. + match builtin_attr_info { + // `rustc_dummy` doesn't have any restrictions specific to built-in attributes. + Some(BuiltinAttribute { name, template, .. }) if *name != sym::rustc_dummy => { + match parse_meta(psess, attr) { + // Don't check safety again, we just did that + Ok(meta) => { + check_builtin_meta_item(psess, &meta, attr.style, *name, *template, false) + } + Err(err) => { + err.emit(); + } + } + } + _ => { + let attr_item = attr.get_normal_item(); + if let AttrArgs::Eq { .. } = attr_item.args { + // All key-value attributes are restricted to meta-item syntax. + match parse_meta(psess, attr) { + Ok(_) => {} + Err(err) => { + err.emit(); + } + } + } + } + } +} + +pub fn parse_meta<'a>(psess: &'a ParseSess, attr: &Attribute) -> PResult<'a, MetaItem> { + let item = attr.get_normal_item(); + Ok(MetaItem { + unsafety: item.unsafety, + span: attr.span, + path: item.path.clone(), + kind: match &item.args { + AttrArgs::Empty => MetaItemKind::Word, + AttrArgs::Delimited(DelimArgs { dspan, delim, tokens }) => { + check_meta_bad_delim(psess, *dspan, *delim); + let nmis = + parse_in(psess, tokens.clone(), "meta list", |p| p.parse_meta_seq_top())?; + MetaItemKind::List(nmis) + } + AttrArgs::Eq { expr, .. } => { + if let ast::ExprKind::Lit(token_lit) = expr.kind { + let res = ast::MetaItemLit::from_token_lit(token_lit, expr.span); + let res = match res { + Ok(lit) => { + if token_lit.suffix.is_some() { + let mut err = psess.dcx().struct_span_err( + expr.span, + "suffixed literals are not allowed in attributes", + ); + err.help( + "instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), \ + use an unsuffixed version (`1`, `1.0`, etc.)", + ); + return Err(err); + } else { + MetaItemKind::NameValue(lit) + } + } + Err(err) => { + let guar = report_lit_error(psess, err, token_lit, expr.span); + let lit = ast::MetaItemLit { + symbol: token_lit.symbol, + suffix: token_lit.suffix, + kind: ast::LitKind::Err(guar), + span: expr.span, + }; + MetaItemKind::NameValue(lit) + } + }; + res + } else { + // Example cases: + // - `#[foo = 1+1]`: results in `ast::ExprKind::Binary`. + // - `#[foo = include_str!("nonexistent-file.rs")]`: + // results in `ast::ExprKind::Err`. In that case we delay + // the error because an earlier error will have already + // been reported. + let msg = "attribute value must be a literal"; + let mut err = psess.dcx().struct_span_err(expr.span, msg); + if let ast::ExprKind::Err(_) = expr.kind { + err.downgrade_to_delayed_bug(); + } + return Err(err); + } + } + }, + }) +} + +fn check_meta_bad_delim(psess: &ParseSess, span: DelimSpan, delim: Delimiter) { + if let Delimiter::Parenthesis = delim { + return; + } + psess.dcx().emit_err(errors::MetaBadDelim { + span: span.entire(), + sugg: errors::MetaBadDelimSugg { open: span.open, close: span.close }, + }); +} + +/// Checks that the given meta-item is compatible with this `AttributeTemplate`. +fn is_attr_template_compatible(template: &AttributeTemplate, meta: &ast::MetaItemKind) -> bool { + let is_one_allowed_subword = |items: &[MetaItemInner]| match items { + [item] => item.is_word() && template.one_of.iter().any(|&word| item.has_name(word)), + _ => false, + }; + match meta { + MetaItemKind::Word => template.word, + MetaItemKind::List(items) => template.list.is_some() || is_one_allowed_subword(items), + MetaItemKind::NameValue(lit) if lit.kind.is_str() => template.name_value_str.is_some(), + MetaItemKind::NameValue(..) => false, + } +} + +pub fn check_attribute_safety( + psess: &ParseSess, + builtin_attr_safety: Option, + attr: &Attribute, + id: NodeId, +) { + let attr_item = attr.get_normal_item(); + match (builtin_attr_safety, attr_item.unsafety) { + // - Unsafe builtin attribute + // - User wrote `#[unsafe(..)]`, which is permitted on any edition + (Some(AttributeSafety::Unsafe { .. }), Safety::Unsafe(..)) => { + // OK + } + + // - Unsafe builtin attribute + // - User did not write `#[unsafe(..)]` + (Some(AttributeSafety::Unsafe { unsafe_since }), Safety::Default) => { + let path_span = attr_item.path.span; + + // If the `attr_item`'s span is not from a macro, then just suggest + // wrapping it in `unsafe(...)`. Otherwise, we suggest putting the + // `unsafe(`, `)` right after and right before the opening and closing + // square bracket respectively. + let diag_span = attr_item.span(); + + // Attributes can be safe in earlier editions, and become unsafe in later ones. + // + // Use the span of the attribute's name to determine the edition: the span of the + // attribute as a whole may be inaccurate if it was emitted by a macro. + // + // See https://github.com/rust-lang/rust/issues/142182. + let emit_error = match unsafe_since { + None => true, + Some(unsafe_since) => path_span.edition() >= unsafe_since, + }; + + if emit_error { + psess.dcx().emit_err(errors::UnsafeAttrOutsideUnsafe { + span: path_span, + suggestion: errors::UnsafeAttrOutsideUnsafeSuggestion { + left: diag_span.shrink_to_lo(), + right: diag_span.shrink_to_hi(), + }, + }); + } else { + psess.buffer_lint( + UNSAFE_ATTR_OUTSIDE_UNSAFE, + path_span, + id, + BuiltinLintDiag::UnsafeAttrOutsideUnsafe { + attribute_name_span: path_span, + sugg_spans: (diag_span.shrink_to_lo(), diag_span.shrink_to_hi()), + }, + ); + } + } + + // - Normal builtin attribute, or any non-builtin attribute + // - All non-builtin attributes are currently considered safe; writing `#[unsafe(..)]` is + // not permitted on non-builtin attributes or normal builtin attributes + (Some(AttributeSafety::Normal) | None, Safety::Unsafe(unsafe_span)) => { + psess.dcx().emit_err(errors::InvalidAttrUnsafe { + span: unsafe_span, + name: attr_item.path.clone(), + }); + } + + // - Normal builtin attribute + // - No explicit `#[unsafe(..)]` written. + (Some(AttributeSafety::Normal), Safety::Default) => { + // OK + } + + // - Non-builtin attribute + // - No explicit `#[unsafe(..)]` written. + (None, Safety::Default) => { + // OK + } + + ( + Some(AttributeSafety::Unsafe { .. } | AttributeSafety::Normal) | None, + Safety::Safe(..), + ) => { + psess.dcx().span_delayed_bug( + attr_item.span(), + "`check_attribute_safety` does not expect `Safety::Safe` on attributes", + ); + } + } +} + +// Called by `check_builtin_meta_item` and code that manually denies +// `unsafe(...)` in `cfg` +pub fn deny_builtin_meta_unsafety(diag: DiagCtxtHandle<'_>, unsafety: Safety, name: &Path) { + // This only supports denying unsafety right now - making builtin attributes + // support unsafety will requite us to thread the actual `Attribute` through + // for the nice diagnostics. + if let Safety::Unsafe(unsafe_span) = unsafety { + diag.emit_err(errors::InvalidAttrUnsafe { span: unsafe_span, name: name.clone() }); + } +} + +pub fn check_builtin_meta_item( + psess: &ParseSess, + meta: &MetaItem, + style: ast::AttrStyle, + name: Symbol, + template: AttributeTemplate, + deny_unsafety: bool, +) { + if !is_attr_template_compatible(&template, &meta.kind) { + // attrs with new parsers are locally validated so excluded here + if AttributeParser::::is_parsed_attribute(slice::from_ref(&name)) { + return; + } + emit_malformed_attribute(psess, style, meta.span, name, template); + } + + if deny_unsafety { + deny_builtin_meta_unsafety(psess.dcx(), meta.unsafety, &meta.path); + } +} + +fn emit_malformed_attribute( + psess: &ParseSess, + style: ast::AttrStyle, + span: Span, + name: Symbol, + template: AttributeTemplate, +) { + // Some of previously accepted forms were used in practice, + // report them as warnings for now. + let should_warn = |name| matches!(name, sym::doc | sym::link | sym::test | sym::bench); + + let error_msg = format!("malformed `{name}` attribute input"); + let mut suggestions = vec![]; + let inner = if style == ast::AttrStyle::Inner { "!" } else { "" }; + if template.word { + suggestions.push(format!("#{inner}[{name}]")); + } + if let Some(descr) = template.list { + for descr in descr { + suggestions.push(format!("#{inner}[{name}({descr})]")); + } + } + suggestions.extend(template.one_of.iter().map(|&word| format!("#{inner}[{name}({word})]"))); + if let Some(descr) = template.name_value_str { + for descr in descr { + suggestions.push(format!("#{inner}[{name} = \"{descr}\"]")); + } + } + if should_warn(name) { + psess.buffer_lint( + ILL_FORMED_ATTRIBUTE_INPUT, + span, + ast::CRATE_NODE_ID, + BuiltinLintDiag::IllFormedAttributeInput { + suggestions: suggestions.clone(), + docs: template.docs, + }, + ); + } else { + suggestions.sort(); + let mut err = psess.dcx().struct_span_err(span, error_msg).with_span_suggestions( + span, + if suggestions.len() == 1 { + "must be of the form" + } else { + "the following are the possible correct uses" + }, + suggestions, + Applicability::HasPlaceholders, + ); + if let Some(link) = template.docs { + err.note(format!("for more information, visit <{link}>")); + } + err.emit(); + } +} + +pub fn emit_fatal_malformed_builtin_attribute( + psess: &ParseSess, + attr: &Attribute, + name: Symbol, +) -> ! { + let template = BUILTIN_ATTRIBUTE_MAP.get(&name).expect("builtin attr defined").template; + emit_malformed_attribute(psess, attr.style, attr.span, name, template); + // This is fatal, otherwise it will likely cause a cascade of other errors + // (and an error here is expected to be very rare). + FatalError.raise() +} diff --git a/compiler/rustc_builtin_macros/src/cfg_accessible.rs b/compiler/rustc_builtin_macros/src/cfg_accessible.rs index f7d8f4aa783..48d80004cdd 100644 --- a/compiler/rustc_builtin_macros/src/cfg_accessible.rs +++ b/compiler/rustc_builtin_macros/src/cfg_accessible.rs @@ -1,9 +1,9 @@ //! Implementation of the `#[cfg_accessible(path)]` attribute macro. use rustc_ast as ast; +use rustc_attr_parsing::validate_attr; use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, Indeterminate, MultiItemModifier}; use rustc_feature::AttributeTemplate; -use rustc_parse::validate_attr; use rustc_span::{Span, sym}; use crate::errors; diff --git a/compiler/rustc_builtin_macros/src/derive.rs b/compiler/rustc_builtin_macros/src/derive.rs index a33eca43de5..09d827b0635 100644 --- a/compiler/rustc_builtin_macros/src/derive.rs +++ b/compiler/rustc_builtin_macros/src/derive.rs @@ -1,10 +1,10 @@ use rustc_ast as ast; use rustc_ast::{GenericParamKind, ItemKind, MetaItemInner, MetaItemKind, StmtKind}; +use rustc_attr_parsing::validate_attr; use rustc_expand::base::{ Annotatable, DeriveResolution, ExpandResult, ExtCtxt, Indeterminate, MultiItemModifier, }; use rustc_feature::AttributeTemplate; -use rustc_parse::validate_attr; use rustc_session::Session; use rustc_span::{ErrorGuaranteed, Ident, Span, sym}; diff --git a/compiler/rustc_builtin_macros/src/util.rs b/compiler/rustc_builtin_macros/src/util.rs index f00c170e485..3a4585d5be9 100644 --- a/compiler/rustc_builtin_macros/src/util.rs +++ b/compiler/rustc_builtin_macros/src/util.rs @@ -1,12 +1,13 @@ use rustc_ast::tokenstream::TokenStream; use rustc_ast::{self as ast, AttrStyle, Attribute, MetaItem, attr, token}; +use rustc_attr_parsing::validate_attr; use rustc_errors::{Applicability, Diag, ErrorGuaranteed}; use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt}; use rustc_expand::expand::AstFragment; use rustc_feature::AttributeTemplate; use rustc_lint_defs::BuiltinLintDiag; use rustc_lint_defs::builtin::DUPLICATE_MACRO_ATTRIBUTES; -use rustc_parse::{exp, parser, validate_attr}; +use rustc_parse::{exp, parser}; use rustc_session::errors::report_lit_error; use rustc_span::{BytePos, Span, Symbol}; diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs index 83a8d601afe..db5b45ff3d5 100644 --- a/compiler/rustc_expand/src/config.rs +++ b/compiler/rustc_expand/src/config.rs @@ -11,8 +11,10 @@ use rustc_ast::{ NodeId, NormalAttr, }; use rustc_attr_parsing as attr; +use rustc_attr_parsing::validate_attr::deny_builtin_meta_unsafety; use rustc_attr_parsing::{ AttributeParser, CFG_TEMPLATE, EvalConfigResult, ShouldEmit, eval_config_entry, parse_cfg_attr, + validate_attr, }; use rustc_data_structures::flat_map_in_place::FlatMapInPlace; use rustc_feature::{ @@ -20,8 +22,6 @@ use rustc_feature::{ REMOVED_LANG_FEATURES, UNSTABLE_LANG_FEATURES, }; use rustc_lint_defs::BuiltinLintDiag; -use rustc_parse::validate_attr; -use rustc_parse::validate_attr::deny_builtin_meta_unsafety; use rustc_session::Session; use rustc_session::parse::feature_err; use rustc_span::{STDLIB_STABLE_CRATES, Span, Symbol, sym}; diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 755275d3cda..7bc380f0939 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -12,7 +12,7 @@ use rustc_ast::{ MetaItemKind, ModKind, NodeId, PatKind, StmtKind, TyKind, token, }; use rustc_ast_pretty::pprust; -use rustc_attr_parsing::{EvalConfigResult, ShouldEmit}; +use rustc_attr_parsing::{EvalConfigResult, ShouldEmit, validate_attr}; use rustc_data_structures::flat_map_in_place::FlatMapInPlace; use rustc_errors::PResult; use rustc_feature::Features; @@ -21,7 +21,6 @@ use rustc_parse::parser::{ AttemptLocalParseRecovery, CommaRecoveryMode, ForceCollect, Parser, RecoverColon, RecoverComma, token_descr, }; -use rustc_parse::validate_attr; use rustc_session::lint::BuiltinLintDiag; use rustc_session::lint::builtin::{UNUSED_ATTRIBUTES, UNUSED_DOC_COMMENTS}; use rustc_session::parse::feature_err; diff --git a/compiler/rustc_expand/src/module.rs b/compiler/rustc_expand/src/module.rs index 6666ea33cd3..19f3cdbc549 100644 --- a/compiler/rustc_expand/src/module.rs +++ b/compiler/rustc_expand/src/module.rs @@ -2,8 +2,9 @@ use std::iter::once; use std::path::{self, Path, PathBuf}; use rustc_ast::{AttrVec, Attribute, Inline, Item, ModSpans}; +use rustc_attr_parsing::validate_attr; use rustc_errors::{Diag, ErrorGuaranteed}; -use rustc_parse::{exp, new_parser_from_file, unwrap_or_emit_fatal, validate_attr}; +use rustc_parse::{exp, new_parser_from_file, unwrap_or_emit_fatal}; use rustc_session::Session; use rustc_session::parse::ParseSess; use rustc_span::{Ident, Span, sym}; diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 3ba224723e3..424cba2dae8 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -6,6 +6,7 @@ use std::sync::{Arc, LazyLock, OnceLock}; use std::{env, fs, iter}; use rustc_ast as ast; +use rustc_attr_parsing::validate_attr; use rustc_codegen_ssa::traits::CodegenBackend; use rustc_data_structures::jobserver::Proxy; use rustc_data_structures::steal::Steal; @@ -25,9 +26,7 @@ use rustc_middle::arena::Arena; use rustc_middle::dep_graph::DepsType; use rustc_middle::ty::{self, CurrentGcx, GlobalCtxt, RegisteredTools, TyCtxt}; use rustc_middle::util::Providers; -use rustc_parse::{ - new_parser_from_file, new_parser_from_source_str, unwrap_or_emit_fatal, validate_attr, -}; +use rustc_parse::{new_parser_from_file, new_parser_from_source_str, unwrap_or_emit_fatal}; use rustc_passes::{abi_test, input_stats, layout_test}; use rustc_resolve::{Resolver, ResolverOutputs}; use rustc_session::config::{CrateType, Input, OutFileName, OutputFilenames, OutputType}; diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs index 0ca4fcc66ca..49ac3b7a10d 100644 --- a/compiler/rustc_interface/src/util.rs +++ b/compiler/rustc_interface/src/util.rs @@ -5,12 +5,12 @@ use std::sync::{Arc, OnceLock}; use std::{env, thread}; use rustc_ast as ast; +use rustc_attr_parsing::validate_attr; use rustc_codegen_ssa::traits::CodegenBackend; use rustc_data_structures::jobserver::Proxy; use rustc_data_structures::sync; use rustc_metadata::{DylibError, load_symbol_from_dylib}; use rustc_middle::ty::CurrentGcx; -use rustc_parse::validate_attr; use rustc_session::config::{Cfg, OutFileName, OutputFilenames, OutputTypes, Sysroot, host_tuple}; use rustc_session::lint::{self, BuiltinLintDiag, LintBuffer}; use rustc_session::output::{CRATE_TYPES, categorize_crate_type}; diff --git a/compiler/rustc_parse/Cargo.toml b/compiler/rustc_parse/Cargo.toml index 0ae0b613fa2..6d738a10371 100644 --- a/compiler/rustc_parse/Cargo.toml +++ b/compiler/rustc_parse/Cargo.toml @@ -9,7 +9,6 @@ bitflags = "2.4.1" rustc-literal-escaper = "0.0.5" rustc_ast = { path = "../rustc_ast" } rustc_ast_pretty = { path = "../rustc_ast_pretty" } -rustc_attr_parsing = { path = "../rustc_attr_parsing" } rustc_data_structures = { path = "../rustc_data_structures" } rustc_errors = { path = "../rustc_errors" } rustc_feature = { path = "../rustc_feature" } diff --git a/compiler/rustc_parse/messages.ftl b/compiler/rustc_parse/messages.ftl index a107a682184..1c09cab53b8 100644 --- a/compiler/rustc_parse/messages.ftl +++ b/compiler/rustc_parse/messages.ftl @@ -436,11 +436,6 @@ parse_inner_doc_comment_not_permitted = expected outer doc comment .label_does_not_annotate_this = the inner doc comment doesn't annotate this {$item} .sugg_change_inner_to_outer = to annotate the {$item}, change the doc comment from inner to outer style -parse_invalid_attr_unsafe = `{$name}` is not an unsafe attribute - .label = this is not an unsafe attribute - .suggestion = remove the `unsafe(...)` - .note = extraneous unsafe is not allowed in attributes - parse_invalid_block_macro_segment = cannot use a `block` macro fragment here .label = the `block` fragment is within this context .suggestion = wrap this in another block @@ -601,7 +596,6 @@ parse_maybe_report_ambiguous_plus = ambiguous `+` in a type .suggestion = use parentheses to disambiguate -parse_meta_bad_delim = wrong meta list delimiters parse_meta_bad_delim_suggestion = the delimiters should be `(` and `)` parse_mismatched_closing_delimiter = mismatched closing delimiter: `{$delimiter}` @@ -990,10 +984,6 @@ parse_unmatched_angle_brackets = {$num_extra_brackets -> *[other] remove extra angle brackets } -parse_unsafe_attr_outside_unsafe = unsafe attribute used without unsafe - .label = usage of unsafe attribute -parse_unsafe_attr_outside_unsafe_suggestion = wrap the attribute in `unsafe(...)` - parse_unskipped_whitespace = whitespace symbol '{$ch}' is not skipped .label = {parse_unskipped_whitespace} diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index 2c046329e33..dfd4f38cf03 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -3345,15 +3345,6 @@ pub(crate) struct KwBadCase<'a> { pub kw: &'a str, } -#[derive(Diagnostic)] -#[diag(parse_meta_bad_delim)] -pub(crate) struct MetaBadDelim { - #[primary_span] - pub span: Span, - #[subdiagnostic] - pub sugg: MetaBadDelimSugg, -} - #[derive(Diagnostic)] #[diag(parse_cfg_attr_bad_delim)] pub(crate) struct CfgAttrBadDelim { @@ -3493,38 +3484,6 @@ pub(crate) struct DotDotRangeAttribute { pub span: Span, } -#[derive(Diagnostic)] -#[diag(parse_invalid_attr_unsafe)] -#[note] -pub(crate) struct InvalidAttrUnsafe { - #[primary_span] - #[label] - pub span: Span, - pub name: Path, -} - -#[derive(Diagnostic)] -#[diag(parse_unsafe_attr_outside_unsafe)] -pub(crate) struct UnsafeAttrOutsideUnsafe { - #[primary_span] - #[label] - pub span: Span, - #[subdiagnostic] - pub suggestion: UnsafeAttrOutsideUnsafeSuggestion, -} - -#[derive(Subdiagnostic)] -#[multipart_suggestion( - parse_unsafe_attr_outside_unsafe_suggestion, - applicability = "machine-applicable" -)] -pub(crate) struct UnsafeAttrOutsideUnsafeSuggestion { - #[suggestion_part(code = "unsafe(")] - pub left: Span, - #[suggestion_part(code = ")")] - pub right: Span, -} - #[derive(Diagnostic)] #[diag(parse_binder_before_modifiers)] pub(crate) struct BinderBeforeModifiers { diff --git a/compiler/rustc_parse/src/lib.rs b/compiler/rustc_parse/src/lib.rs index adad5751871..48289b2e8ab 100644 --- a/compiler/rustc_parse/src/lib.rs +++ b/compiler/rustc_parse/src/lib.rs @@ -16,7 +16,7 @@ use std::str::Utf8Error; use std::sync::Arc; use rustc_ast as ast; -use rustc_ast::tokenstream::TokenStream; +use rustc_ast::tokenstream::{DelimSpan, TokenStream}; use rustc_ast::{AttrItem, Attribute, MetaItemInner, token}; use rustc_ast_pretty::pprust; use rustc_errors::{Diag, EmissionGuarantee, FatalError, PResult, pluralize}; @@ -31,8 +31,9 @@ pub const MACRO_ARGUMENTS: Option<&str> = Some("macro arguments"); #[macro_use] pub mod parser; use parser::Parser; +use rustc_ast::token::Delimiter; + pub mod lexer; -pub mod validate_attr; mod errors; @@ -235,7 +236,7 @@ pub fn parse_cfg_attr( ast::AttrArgs::Delimited(ast::DelimArgs { dspan, delim, ref tokens }) if !tokens.is_empty() => { - crate::validate_attr::check_cfg_attr_bad_delim(psess, dspan, delim); + check_cfg_attr_bad_delim(psess, dspan, delim); match parse_in(psess, tokens.clone(), "`cfg_attr` input", |p| p.parse_cfg_attr()) { Ok(r) => return Some(r), Err(e) => { @@ -254,3 +255,13 @@ pub fn parse_cfg_attr( } None } + +fn check_cfg_attr_bad_delim(psess: &ParseSess, span: DelimSpan, delim: Delimiter) { + if let Delimiter::Parenthesis = delim { + return; + } + psess.dcx().emit_err(errors::CfgAttrBadDelim { + span: span.entire(), + sugg: errors::MetaBadDelimSugg { open: span.open, close: span.close }, + }); +} diff --git a/compiler/rustc_parse/src/parser/attr.rs b/compiler/rustc_parse/src/parser/attr.rs index 7f6afeba28c..acd338156ce 100644 --- a/compiler/rustc_parse/src/parser/attr.rs +++ b/compiler/rustc_parse/src/parser/attr.rs @@ -399,7 +399,7 @@ impl<'a> Parser<'a> { } /// Matches `COMMASEP(meta_item_inner)`. - pub(crate) fn parse_meta_seq_top(&mut self) -> PResult<'a, ThinVec> { + pub fn parse_meta_seq_top(&mut self) -> PResult<'a, ThinVec> { // Presumably, the majority of the time there will only be one attr. let mut nmis = ThinVec::with_capacity(1); while self.token != token::Eof { diff --git a/compiler/rustc_parse/src/validate_attr.rs b/compiler/rustc_parse/src/validate_attr.rs deleted file mode 100644 index 68ef6d6f32c..00000000000 --- a/compiler/rustc_parse/src/validate_attr.rs +++ /dev/null @@ -1,350 +0,0 @@ -//! Meta-syntax validation logic of attributes for post-expansion. - -use std::slice; - -use rustc_ast::token::Delimiter; -use rustc_ast::tokenstream::DelimSpan; -use rustc_ast::{ - self as ast, AttrArgs, Attribute, DelimArgs, MetaItem, MetaItemInner, MetaItemKind, NodeId, - Path, Safety, -}; -use rustc_attr_parsing::{AttributeParser, Late}; -use rustc_errors::{Applicability, DiagCtxtHandle, FatalError, PResult}; -use rustc_feature::{AttributeSafety, AttributeTemplate, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute}; -use rustc_session::errors::report_lit_error; -use rustc_session::lint::BuiltinLintDiag; -use rustc_session::lint::builtin::{ILL_FORMED_ATTRIBUTE_INPUT, UNSAFE_ATTR_OUTSIDE_UNSAFE}; -use rustc_session::parse::ParseSess; -use rustc_span::{Span, Symbol, sym}; - -use crate::{errors, parse_in}; - -pub fn check_attr(psess: &ParseSess, attr: &Attribute, id: NodeId) { - if attr.is_doc_comment() || attr.has_name(sym::cfg_trace) || attr.has_name(sym::cfg_attr_trace) - { - return; - } - - let builtin_attr_info = attr.ident().and_then(|ident| BUILTIN_ATTRIBUTE_MAP.get(&ident.name)); - - let builtin_attr_safety = builtin_attr_info.map(|x| x.safety); - check_attribute_safety(psess, builtin_attr_safety, attr, id); - - // Check input tokens for built-in and key-value attributes. - match builtin_attr_info { - // `rustc_dummy` doesn't have any restrictions specific to built-in attributes. - Some(BuiltinAttribute { name, template, .. }) if *name != sym::rustc_dummy => { - match parse_meta(psess, attr) { - // Don't check safety again, we just did that - Ok(meta) => { - check_builtin_meta_item(psess, &meta, attr.style, *name, *template, false) - } - Err(err) => { - err.emit(); - } - } - } - _ => { - let attr_item = attr.get_normal_item(); - if let AttrArgs::Eq { .. } = attr_item.args { - // All key-value attributes are restricted to meta-item syntax. - match parse_meta(psess, attr) { - Ok(_) => {} - Err(err) => { - err.emit(); - } - } - } - } - } -} - -pub fn parse_meta<'a>(psess: &'a ParseSess, attr: &Attribute) -> PResult<'a, MetaItem> { - let item = attr.get_normal_item(); - Ok(MetaItem { - unsafety: item.unsafety, - span: attr.span, - path: item.path.clone(), - kind: match &item.args { - AttrArgs::Empty => MetaItemKind::Word, - AttrArgs::Delimited(DelimArgs { dspan, delim, tokens }) => { - check_meta_bad_delim(psess, *dspan, *delim); - let nmis = - parse_in(psess, tokens.clone(), "meta list", |p| p.parse_meta_seq_top())?; - MetaItemKind::List(nmis) - } - AttrArgs::Eq { expr, .. } => { - if let ast::ExprKind::Lit(token_lit) = expr.kind { - let res = ast::MetaItemLit::from_token_lit(token_lit, expr.span); - let res = match res { - Ok(lit) => { - if token_lit.suffix.is_some() { - let mut err = psess.dcx().struct_span_err( - expr.span, - "suffixed literals are not allowed in attributes", - ); - err.help( - "instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), \ - use an unsuffixed version (`1`, `1.0`, etc.)", - ); - return Err(err); - } else { - MetaItemKind::NameValue(lit) - } - } - Err(err) => { - let guar = report_lit_error(psess, err, token_lit, expr.span); - let lit = ast::MetaItemLit { - symbol: token_lit.symbol, - suffix: token_lit.suffix, - kind: ast::LitKind::Err(guar), - span: expr.span, - }; - MetaItemKind::NameValue(lit) - } - }; - res - } else { - // Example cases: - // - `#[foo = 1+1]`: results in `ast::ExprKind::Binary`. - // - `#[foo = include_str!("nonexistent-file.rs")]`: - // results in `ast::ExprKind::Err`. In that case we delay - // the error because an earlier error will have already - // been reported. - let msg = "attribute value must be a literal"; - let mut err = psess.dcx().struct_span_err(expr.span, msg); - if let ast::ExprKind::Err(_) = expr.kind { - err.downgrade_to_delayed_bug(); - } - return Err(err); - } - } - }, - }) -} - -fn check_meta_bad_delim(psess: &ParseSess, span: DelimSpan, delim: Delimiter) { - if let Delimiter::Parenthesis = delim { - return; - } - psess.dcx().emit_err(errors::MetaBadDelim { - span: span.entire(), - sugg: errors::MetaBadDelimSugg { open: span.open, close: span.close }, - }); -} - -pub(super) fn check_cfg_attr_bad_delim(psess: &ParseSess, span: DelimSpan, delim: Delimiter) { - if let Delimiter::Parenthesis = delim { - return; - } - psess.dcx().emit_err(errors::CfgAttrBadDelim { - span: span.entire(), - sugg: errors::MetaBadDelimSugg { open: span.open, close: span.close }, - }); -} - -/// Checks that the given meta-item is compatible with this `AttributeTemplate`. -fn is_attr_template_compatible(template: &AttributeTemplate, meta: &ast::MetaItemKind) -> bool { - let is_one_allowed_subword = |items: &[MetaItemInner]| match items { - [item] => item.is_word() && template.one_of.iter().any(|&word| item.has_name(word)), - _ => false, - }; - match meta { - MetaItemKind::Word => template.word, - MetaItemKind::List(items) => template.list.is_some() || is_one_allowed_subword(items), - MetaItemKind::NameValue(lit) if lit.kind.is_str() => template.name_value_str.is_some(), - MetaItemKind::NameValue(..) => false, - } -} - -pub fn check_attribute_safety( - psess: &ParseSess, - builtin_attr_safety: Option, - attr: &Attribute, - id: NodeId, -) { - let attr_item = attr.get_normal_item(); - match (builtin_attr_safety, attr_item.unsafety) { - // - Unsafe builtin attribute - // - User wrote `#[unsafe(..)]`, which is permitted on any edition - (Some(AttributeSafety::Unsafe { .. }), Safety::Unsafe(..)) => { - // OK - } - - // - Unsafe builtin attribute - // - User did not write `#[unsafe(..)]` - (Some(AttributeSafety::Unsafe { unsafe_since }), Safety::Default) => { - let path_span = attr_item.path.span; - - // If the `attr_item`'s span is not from a macro, then just suggest - // wrapping it in `unsafe(...)`. Otherwise, we suggest putting the - // `unsafe(`, `)` right after and right before the opening and closing - // square bracket respectively. - let diag_span = attr_item.span(); - - // Attributes can be safe in earlier editions, and become unsafe in later ones. - // - // Use the span of the attribute's name to determine the edition: the span of the - // attribute as a whole may be inaccurate if it was emitted by a macro. - // - // See https://github.com/rust-lang/rust/issues/142182. - let emit_error = match unsafe_since { - None => true, - Some(unsafe_since) => path_span.edition() >= unsafe_since, - }; - - if emit_error { - psess.dcx().emit_err(errors::UnsafeAttrOutsideUnsafe { - span: path_span, - suggestion: errors::UnsafeAttrOutsideUnsafeSuggestion { - left: diag_span.shrink_to_lo(), - right: diag_span.shrink_to_hi(), - }, - }); - } else { - psess.buffer_lint( - UNSAFE_ATTR_OUTSIDE_UNSAFE, - path_span, - id, - BuiltinLintDiag::UnsafeAttrOutsideUnsafe { - attribute_name_span: path_span, - sugg_spans: (diag_span.shrink_to_lo(), diag_span.shrink_to_hi()), - }, - ); - } - } - - // - Normal builtin attribute, or any non-builtin attribute - // - All non-builtin attributes are currently considered safe; writing `#[unsafe(..)]` is - // not permitted on non-builtin attributes or normal builtin attributes - (Some(AttributeSafety::Normal) | None, Safety::Unsafe(unsafe_span)) => { - psess.dcx().emit_err(errors::InvalidAttrUnsafe { - span: unsafe_span, - name: attr_item.path.clone(), - }); - } - - // - Normal builtin attribute - // - No explicit `#[unsafe(..)]` written. - (Some(AttributeSafety::Normal), Safety::Default) => { - // OK - } - - // - Non-builtin attribute - // - No explicit `#[unsafe(..)]` written. - (None, Safety::Default) => { - // OK - } - - ( - Some(AttributeSafety::Unsafe { .. } | AttributeSafety::Normal) | None, - Safety::Safe(..), - ) => { - psess.dcx().span_delayed_bug( - attr_item.span(), - "`check_attribute_safety` does not expect `Safety::Safe` on attributes", - ); - } - } -} - -// Called by `check_builtin_meta_item` and code that manually denies -// `unsafe(...)` in `cfg` -pub fn deny_builtin_meta_unsafety(diag: DiagCtxtHandle<'_>, unsafety: Safety, name: &Path) { - // This only supports denying unsafety right now - making builtin attributes - // support unsafety will requite us to thread the actual `Attribute` through - // for the nice diagnostics. - if let Safety::Unsafe(unsafe_span) = unsafety { - diag.emit_err(errors::InvalidAttrUnsafe { span: unsafe_span, name: name.clone() }); - } -} - -pub fn check_builtin_meta_item( - psess: &ParseSess, - meta: &MetaItem, - style: ast::AttrStyle, - name: Symbol, - template: AttributeTemplate, - deny_unsafety: bool, -) { - if !is_attr_template_compatible(&template, &meta.kind) { - // attrs with new parsers are locally validated so excluded here - if AttributeParser::::is_parsed_attribute(slice::from_ref(&name)) { - return; - } - emit_malformed_attribute(psess, style, meta.span, name, template); - } - - if deny_unsafety { - deny_builtin_meta_unsafety(psess.dcx(), meta.unsafety, &meta.path); - } -} - -fn emit_malformed_attribute( - psess: &ParseSess, - style: ast::AttrStyle, - span: Span, - name: Symbol, - template: AttributeTemplate, -) { - // Some of previously accepted forms were used in practice, - // report them as warnings for now. - let should_warn = |name| matches!(name, sym::doc | sym::link | sym::test | sym::bench); - - let error_msg = format!("malformed `{name}` attribute input"); - let mut suggestions = vec![]; - let inner = if style == ast::AttrStyle::Inner { "!" } else { "" }; - if template.word { - suggestions.push(format!("#{inner}[{name}]")); - } - if let Some(descr) = template.list { - for descr in descr { - suggestions.push(format!("#{inner}[{name}({descr})]")); - } - } - suggestions.extend(template.one_of.iter().map(|&word| format!("#{inner}[{name}({word})]"))); - if let Some(descr) = template.name_value_str { - for descr in descr { - suggestions.push(format!("#{inner}[{name} = \"{descr}\"]")); - } - } - if should_warn(name) { - psess.buffer_lint( - ILL_FORMED_ATTRIBUTE_INPUT, - span, - ast::CRATE_NODE_ID, - BuiltinLintDiag::IllFormedAttributeInput { - suggestions: suggestions.clone(), - docs: template.docs, - }, - ); - } else { - suggestions.sort(); - let mut err = psess.dcx().struct_span_err(span, error_msg).with_span_suggestions( - span, - if suggestions.len() == 1 { - "must be of the form" - } else { - "the following are the possible correct uses" - }, - suggestions, - Applicability::HasPlaceholders, - ); - if let Some(link) = template.docs { - err.note(format!("for more information, visit <{link}>")); - } - err.emit(); - } -} - -pub fn emit_fatal_malformed_builtin_attribute( - psess: &ParseSess, - attr: &Attribute, - name: Symbol, -) -> ! { - let template = BUILTIN_ATTRIBUTE_MAP.get(&name).expect("builtin attr defined").template; - emit_malformed_attribute(psess, attr.style, attr.span, name, template); - // This is fatal, otherwise it will likely cause a cascade of other errors - // (and an error here is expected to be very rare). - FatalError.raise() -} -- cgit 1.4.1-3-g733a5 From 52fadd8f9623589143a4c2e5725c3b3c91e160c4 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Fri, 22 Aug 2025 03:01:35 -0700 Subject: Migrate `BuiltinLintDiag::HiddenUnicodeCodepoints` to use `LintDiagnostic` directly --- compiler/rustc_lint/messages.ftl | 13 ----- compiler/rustc_lint/src/early/diagnostics.rs | 21 -------- compiler/rustc_lint/src/lints.rs | 74 --------------------------- compiler/rustc_lint_defs/src/lib.rs | 8 --- compiler/rustc_parse/messages.ftl | 14 +++++ compiler/rustc_parse/src/errors.rs | 76 +++++++++++++++++++++++++++- compiler/rustc_parse/src/lexer/mod.rs | 18 +++---- 7 files changed, 98 insertions(+), 126 deletions(-) (limited to 'compiler/rustc_parse/src/errors.rs') diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index fc3b0cf2c56..940a07c94df 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -288,19 +288,6 @@ lint_hidden_glob_reexport = private item shadows public glob re-export lint_hidden_lifetime_parameters = hidden lifetime parameters in types are deprecated -lint_hidden_unicode_codepoints = unicode codepoint changing visible direction of text present in {$label} - .label = this {$label} contains {$count -> - [one] an invisible - *[other] invisible - } unicode text flow control {$count -> - [one] codepoint - *[other] codepoints - } - .note = these kind of unicode codepoints change the way text flows on applications that support them, but can cause confusion because they change the order of characters on the screen - .suggestion_remove = if their presence wasn't intentional, you can remove them - .suggestion_escape = if you want to keep them but make them visible in your source code, you can escape them - .no_suggestion_note_escape = if you want to keep them but make them visible in your source code, you can escape them: {$escaped} - lint_identifier_non_ascii_char = identifier contains non-ASCII characters lint_identifier_uncommon_codepoints = identifier contains {$codepoints_len -> diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index 09e474963ff..7300490b838 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -183,27 +183,6 @@ pub fn decorate_builtin_lint( lints::ReservedMultihash { suggestion }.decorate_lint(diag); } } - BuiltinLintDiag::HiddenUnicodeCodepoints { - label, - count, - span_label, - labels, - escape, - spans, - } => { - lints::HiddenUnicodeCodepointsDiag { - label: &label, - count, - span_label, - labels: labels.map(|spans| lints::HiddenUnicodeCodepointsDiagLabels { spans }), - sub: if escape { - lints::HiddenUnicodeCodepointsDiagSub::Escape { spans } - } else { - lints::HiddenUnicodeCodepointsDiagSub::NoEscape { spans } - }, - } - .decorate_lint(diag); - } BuiltinLintDiag::UnusedBuiltinAttribute { attr_name, macro_name, diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 32bd7e263b0..6c509734626 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -815,80 +815,6 @@ pub(crate) enum InvalidReferenceCastingDiag<'tcx> { }, } -// hidden_unicode_codepoints.rs -#[derive(LintDiagnostic)] -#[diag(lint_hidden_unicode_codepoints)] -#[note] -pub(crate) struct HiddenUnicodeCodepointsDiag<'a> { - pub label: &'a str, - pub count: usize, - #[label] - pub span_label: Span, - #[subdiagnostic] - pub labels: Option, - #[subdiagnostic] - pub sub: HiddenUnicodeCodepointsDiagSub, -} - -pub(crate) struct HiddenUnicodeCodepointsDiagLabels { - pub spans: Vec<(char, Span)>, -} - -impl Subdiagnostic for HiddenUnicodeCodepointsDiagLabels { - fn add_to_diag(self, diag: &mut Diag<'_, G>) { - for (c, span) in self.spans { - diag.span_label(span, format!("{c:?}")); - } - } -} - -pub(crate) enum HiddenUnicodeCodepointsDiagSub { - Escape { spans: Vec<(char, Span)> }, - NoEscape { spans: Vec<(char, Span)> }, -} - -// Used because of multiple multipart_suggestion and note -impl Subdiagnostic for HiddenUnicodeCodepointsDiagSub { - fn add_to_diag(self, diag: &mut Diag<'_, G>) { - match self { - HiddenUnicodeCodepointsDiagSub::Escape { spans } => { - diag.multipart_suggestion_with_style( - fluent::lint_suggestion_remove, - spans.iter().map(|(_, span)| (*span, "".to_string())).collect(), - Applicability::MachineApplicable, - SuggestionStyle::HideCodeAlways, - ); - diag.multipart_suggestion( - fluent::lint_suggestion_escape, - spans - .into_iter() - .map(|(c, span)| { - let c = format!("{c:?}"); - (span, c[1..c.len() - 1].to_string()) - }) - .collect(), - Applicability::MachineApplicable, - ); - } - HiddenUnicodeCodepointsDiagSub::NoEscape { spans } => { - // FIXME: in other suggestions we've reversed the inner spans of doc comments. We - // should do the same here to provide the same good suggestions as we do for - // literals above. - diag.arg( - "escaped", - spans - .into_iter() - .map(|(c, _)| format!("{c:?}")) - .collect::>() - .join(", "), - ); - diag.note(fluent::lint_suggestion_remove); - diag.note(fluent::lint_no_suggestion_note_escape); - } - } - } -} - // map_unit_fn.rs #[derive(LintDiagnostic)] #[diag(lint_map_unit_fn)] diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 9a91d2d222b..2e84233e5a5 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -668,14 +668,6 @@ pub enum BuiltinLintDiag { is_string: bool, suggestion: Span, }, - HiddenUnicodeCodepoints { - label: String, - count: usize, - span_label: Span, - labels: Option>, - escape: bool, - spans: Vec<(char, Span)>, - }, TrailingMacro(bool, Ident), BreakWithLabelAndLoop(Span), UnicodeTextFlow(Span, String), diff --git a/compiler/rustc_parse/messages.ftl b/compiler/rustc_parse/messages.ftl index a107a682184..9f55db246d0 100644 --- a/compiler/rustc_parse/messages.ftl +++ b/compiler/rustc_parse/messages.ftl @@ -359,6 +359,20 @@ parse_generics_in_path = unexpected generic arguments in path parse_help_set_edition_cargo = set `edition = "{$edition}"` in `Cargo.toml` parse_help_set_edition_standalone = pass `--edition {$edition}` to `rustc` + +parse_hidden_unicode_codepoints = unicode codepoint changing visible direction of text present in {$label} + .label = this {$label} contains {$count -> + [one] an invisible + *[other] invisible + } unicode text flow control {$count -> + [one] codepoint + *[other] codepoints + } + .note = these kind of unicode codepoints change the way text flows on applications that support them, but can cause confusion because they change the order of characters on the screen + .suggestion_remove = if their presence wasn't intentional, you can remove them + .suggestion_escape = if you want to keep them but make them visible in your source code, you can escape them + .no_suggestion_note_escape = if you want to keep them but make them visible in your source code, you can escape them: {$escaped} + parse_if_expression_missing_condition = missing condition for `if` expression .condition_label = expected condition here .block_label = if this block is the condition of the `if` expression, then it must be followed by another block diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index 2c046329e33..1966664aca3 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -8,8 +8,9 @@ use rustc_ast::{Path, Visibility}; use rustc_errors::codes::*; use rustc_errors::{ Applicability, Diag, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level, Subdiagnostic, + SuggestionStyle, }; -use rustc_macros::{Diagnostic, Subdiagnostic}; +use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic}; use rustc_session::errors::ExprParenthesesNeeded; use rustc_span::edition::{Edition, LATEST_STABLE_EDITION}; use rustc_span::{Ident, Span, Symbol}; @@ -3643,3 +3644,76 @@ pub(crate) struct ExpectedRegisterClassOrExplicitRegister { #[primary_span] pub(crate) span: Span, } + +#[derive(LintDiagnostic)] +#[diag(parse_hidden_unicode_codepoints)] +#[note] +pub(crate) struct HiddenUnicodeCodepointsDiag { + pub label: String, + pub count: usize, + #[label] + pub span_label: Span, + #[subdiagnostic] + pub labels: Option, + #[subdiagnostic] + pub sub: HiddenUnicodeCodepointsDiagSub, +} + +pub(crate) struct HiddenUnicodeCodepointsDiagLabels { + pub spans: Vec<(char, Span)>, +} + +impl Subdiagnostic for HiddenUnicodeCodepointsDiagLabels { + fn add_to_diag(self, diag: &mut Diag<'_, G>) { + for (c, span) in self.spans { + diag.span_label(span, format!("{c:?}")); + } + } +} + +pub(crate) enum HiddenUnicodeCodepointsDiagSub { + Escape { spans: Vec<(char, Span)> }, + NoEscape { spans: Vec<(char, Span)> }, +} + +// Used because of multiple multipart_suggestion and note +impl Subdiagnostic for HiddenUnicodeCodepointsDiagSub { + fn add_to_diag(self, diag: &mut Diag<'_, G>) { + match self { + HiddenUnicodeCodepointsDiagSub::Escape { spans } => { + diag.multipart_suggestion_with_style( + fluent::parse_suggestion_remove, + spans.iter().map(|(_, span)| (*span, "".to_string())).collect(), + Applicability::MachineApplicable, + SuggestionStyle::HideCodeAlways, + ); + diag.multipart_suggestion( + fluent::parse_suggestion_escape, + spans + .into_iter() + .map(|(c, span)| { + let c = format!("{c:?}"); + (span, c[1..c.len() - 1].to_string()) + }) + .collect(), + Applicability::MachineApplicable, + ); + } + HiddenUnicodeCodepointsDiagSub::NoEscape { spans } => { + // FIXME: in other suggestions we've reversed the inner spans of doc comments. We + // should do the same here to provide the same good suggestions as we do for + // literals above. + diag.arg( + "escaped", + spans + .into_iter() + .map(|(c, _)| format!("{c:?}")) + .collect::>() + .join(", "), + ); + diag.note(fluent::parse_suggestion_remove); + diag.note(fluent::parse_no_suggestion_note_escape); + } + } + } +} diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs index 7c7e7e50b27..9792240a548 100644 --- a/compiler/rustc_parse/src/lexer/mod.rs +++ b/compiler/rustc_parse/src/lexer/mod.rs @@ -543,21 +543,21 @@ impl<'psess, 'src> Lexer<'psess, 'src> { }) .collect(); + let label = label.to_string(); let count = spans.len(); - let labels = point_at_inner_spans.then_some(spans.clone()); + let labels = point_at_inner_spans + .then_some(errors::HiddenUnicodeCodepointsDiagLabels { spans: spans.clone() }); + let sub = if point_at_inner_spans && !spans.is_empty() { + errors::HiddenUnicodeCodepointsDiagSub::Escape { spans } + } else { + errors::HiddenUnicodeCodepointsDiagSub::NoEscape { spans } + }; self.psess.buffer_lint( TEXT_DIRECTION_CODEPOINT_IN_LITERAL, span, ast::CRATE_NODE_ID, - BuiltinLintDiag::HiddenUnicodeCodepoints { - label: label.to_string(), - count, - span_label: span, - labels, - escape: point_at_inner_spans && !spans.is_empty(), - spans, - }, + errors::HiddenUnicodeCodepointsDiag { label, count, span_label: span, labels, sub }, ); } -- cgit 1.4.1-3-g733a5