From 4f0dd465cf7758545a92c7747bbcf4e20ccbad8d Mon Sep 17 00:00:00 2001 From: Joshua Liebow-Feeser Date: Fri, 5 Jun 2026 17:36:45 +0000 Subject: [PATCH] rustc_lint: Allow scoped `non_ascii_idents` lint levels `non_ascii_idents` was marked `crate_level_only`, and its diagnostic was emitted from the crate-wide symbol gallery. Move the lint into the scoped early lint visitor, checking both AST idents and macro token streams. Keep the one-diagnostic-per-ident behavior by deduplicating by ident and effective lint level/expectation, and leave the other Unicode security lints crate-level-only. --- compiler/rustc_lint/src/lib.rs | 2 +- compiler/rustc_lint/src/non_ascii_idents.rs | 80 ++++++++++++++++--- .../lint-non-ascii-idents-nested.rs | 31 +++++++ .../lint-non-ascii-idents-nested.stderr | 20 +++++ .../auxiliary/non-ascii-idents-derive.rs | 8 ++ .../ui/proc-macro/non-ascii-idents-derive.rs | 17 ++++ 6 files changed, 145 insertions(+), 13 deletions(-) create mode 100644 tests/ui/lint/rfc-2457-non-ascii-idents/lint-non-ascii-idents-nested.rs create mode 100644 tests/ui/lint/rfc-2457-non-ascii-idents/lint-non-ascii-idents-nested.stderr create mode 100644 tests/ui/proc-macro/auxiliary/non-ascii-idents-derive.rs create mode 100644 tests/ui/proc-macro/non-ascii-idents-derive.rs diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 1efc8b70ef22d..6436abd93de3d 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -178,7 +178,7 @@ early_lint_methods!( EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns::default(), NonCamelCaseTypes: NonCamelCaseTypes, WhileTrue: WhileTrue, - NonAsciiIdents: NonAsciiIdents, + NonAsciiIdents: NonAsciiIdents::default(), IncompleteInternalFeatures: IncompleteInternalFeatures, RedundantSemicolons: RedundantSemicolons, UnusedDocComment: UnusedDocComment, diff --git a/compiler/rustc_lint/src/non_ascii_idents.rs b/compiler/rustc_lint/src/non_ascii_idents.rs index 42aba3ffcfe24..475c38f7cf6de 100644 --- a/compiler/rustc_lint/src/non_ascii_idents.rs +++ b/compiler/rustc_lint/src/non_ascii_idents.rs @@ -1,8 +1,10 @@ use rustc_ast as ast; -use rustc_data_structures::fx::FxIndexMap; +use rustc_ast::tokenstream::{TokenStream, TokenTree}; +use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; use rustc_data_structures::unord::UnordMap; -use rustc_session::{declare_lint, declare_lint_pass}; -use rustc_span::Symbol; +use rustc_session::lint::{Level, UnstableLintExpectationId}; +use rustc_session::{declare_lint, impl_lint_pass}; +use rustc_span::{Ident, Symbol}; use unicode_security::general_security_profile::IdentifierType; use crate::lints::{ @@ -36,8 +38,7 @@ declare_lint! { /// [RFC 2457]: https://github.com/rust-lang/rfcs/blob/master/text/2457-non-ascii-idents.md pub NON_ASCII_IDENTS, Allow, - "detects non-ASCII identifiers", - crate_level_only + "detects non-ASCII identifiers" } declare_lint! { @@ -149,25 +150,81 @@ declare_lint! { crate_level_only } -declare_lint_pass!(NonAsciiIdents => [NON_ASCII_IDENTS, UNCOMMON_CODEPOINTS, CONFUSABLE_IDENTS, MIXED_SCRIPT_CONFUSABLES]); +#[derive(Default)] +pub(crate) struct NonAsciiIdents { + seen_non_ascii_idents: FxHashSet<(Symbol, Level, Option)>, +} + +impl_lint_pass!( + NonAsciiIdents => [ + NON_ASCII_IDENTS, + UNCOMMON_CODEPOINTS, + CONFUSABLE_IDENTS, + MIXED_SCRIPT_CONFUSABLES, + ] +); + +impl NonAsciiIdents { + fn check_token_stream(&mut self, cx: &EarlyContext<'_>, tokens: &TokenStream) { + for tt in tokens.iter() { + match tt { + TokenTree::Token(token, _) => { + if let Some((ident, _)) = token.ident() { + self.check_ident_token(cx, ident); + } + } + TokenTree::Delimited(.., tts) => self.check_token_stream(cx, tts), + } + } + } + + fn check_ident_token(&mut self, cx: &EarlyContext<'_>, ident: Ident) { + let symbol = ident.name; + let symbol_str = symbol.as_str(); + if symbol_str.is_ascii() || symbol_str.starts_with('\'') { + return; + } + + let level_spec = cx.builder.lint_level_spec(NON_ASCII_IDENTS); + if level_spec.is_allow() + || !self.seen_non_ascii_idents.insert(( + symbol, + level_spec.level(), + level_spec.lint_id(), + )) + { + return; + } + + cx.emit_span_lint(NON_ASCII_IDENTS, ident.span, IdentifierNonAsciiChar); + } +} impl EarlyLintPass for NonAsciiIdents { + fn check_mac_def(&mut self, cx: &EarlyContext<'_>, mac_def: &ast::MacroDef) { + self.check_token_stream(cx, &mac_def.body.tokens); + } + + fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &ast::MacCall) { + self.check_token_stream(cx, &mac.args.tokens); + } + + fn check_ident(&mut self, cx: &EarlyContext<'_>, ident: &Ident) { + self.check_ident_token(cx, *ident); + } + fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) { use std::collections::BTreeMap; use rustc_span::Span; use unicode_security::GeneralSecurityProfile; - let check_non_ascii_idents = !cx.builder.lint_level_spec(NON_ASCII_IDENTS).is_allow(); let check_uncommon_codepoints = !cx.builder.lint_level_spec(UNCOMMON_CODEPOINTS).is_allow(); let check_confusable_idents = !cx.builder.lint_level_spec(CONFUSABLE_IDENTS).is_allow(); let check_mixed_script_confusables = !cx.builder.lint_level_spec(MIXED_SCRIPT_CONFUSABLES).is_allow(); - if !check_non_ascii_idents - && !check_uncommon_codepoints - && !check_confusable_idents - && !check_mixed_script_confusables + if !check_uncommon_codepoints && !check_confusable_idents && !check_mixed_script_confusables { return; } @@ -187,7 +244,6 @@ impl EarlyLintPass for NonAsciiIdents { continue; } has_non_ascii_idents = true; - cx.emit_span_lint(NON_ASCII_IDENTS, sp, IdentifierNonAsciiChar); if check_uncommon_codepoints && !symbol_str.chars().all(GeneralSecurityProfile::identifier_allowed) { diff --git a/tests/ui/lint/rfc-2457-non-ascii-idents/lint-non-ascii-idents-nested.rs b/tests/ui/lint/rfc-2457-non-ascii-idents/lint-non-ascii-idents-nested.rs new file mode 100644 index 0000000000000..78057858725aa --- /dev/null +++ b/tests/ui/lint/rfc-2457-non-ascii-idents/lint-non-ascii-idents-nested.rs @@ -0,0 +1,31 @@ +// Nested lint levels should control `non_ascii_idents` at the annotated scope. + +#![allow(dead_code, unused_macros)] +#![deny(non_ascii_idents, unused_attributes)] + +#[allow(non_ascii_idents)] +fn föö() {} + +mod allowed { + #![allow(non_ascii_idents)] + + fn bår() {} + + macro_rules! allowed_macro_tokens { + () => { + let quúx = 0; + }; + } +} + +fn bår() {} +//~^ ERROR identifier contains non-ASCII characters + +macro_rules! denied_macro_tokens { + () => { + let bazé = 0; + //~^ ERROR identifier contains non-ASCII characters + }; +} + +fn main() {} diff --git a/tests/ui/lint/rfc-2457-non-ascii-idents/lint-non-ascii-idents-nested.stderr b/tests/ui/lint/rfc-2457-non-ascii-idents/lint-non-ascii-idents-nested.stderr new file mode 100644 index 0000000000000..b5d1dbdc97d67 --- /dev/null +++ b/tests/ui/lint/rfc-2457-non-ascii-idents/lint-non-ascii-idents-nested.stderr @@ -0,0 +1,20 @@ +error: identifier contains non-ASCII characters + --> $DIR/lint-non-ascii-idents-nested.rs:21:4 + | +LL | fn bår() {} + | ^^^ + | +note: the lint level is defined here + --> $DIR/lint-non-ascii-idents-nested.rs:4:9 + | +LL | #![deny(non_ascii_idents, unused_attributes)] + | ^^^^^^^^^^^^^^^^ + +error: identifier contains non-ASCII characters + --> $DIR/lint-non-ascii-idents-nested.rs:26:13 + | +LL | let bazé = 0; + | ^^^^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/proc-macro/auxiliary/non-ascii-idents-derive.rs b/tests/ui/proc-macro/auxiliary/non-ascii-idents-derive.rs new file mode 100644 index 0000000000000..9a5c493811371 --- /dev/null +++ b/tests/ui/proc-macro/auxiliary/non-ascii-idents-derive.rs @@ -0,0 +1,8 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; + +#[proc_macro_derive(NonAsciiIdent)] +pub fn derive_non_ascii_ident(_: TokenStream) -> TokenStream { + "#[allow(non_ascii_idents)] const föö: () = ();".parse().unwrap() +} diff --git a/tests/ui/proc-macro/non-ascii-idents-derive.rs b/tests/ui/proc-macro/non-ascii-idents-derive.rs new file mode 100644 index 0000000000000..58172e7cf826f --- /dev/null +++ b/tests/ui/proc-macro/non-ascii-idents-derive.rs @@ -0,0 +1,17 @@ +// Regression test for #151025: derive-generated `#[allow(non_ascii_idents)]` +// should not be rejected as an unused attribute. + +//@ check-pass +//@ proc-macro: non-ascii-idents-derive.rs + +#![allow(dead_code)] +#![deny(non_ascii_idents, unused_attributes)] + +extern crate non_ascii_idents_derive; + +use non_ascii_idents_derive::NonAsciiIdent; + +#[derive(NonAsciiIdent)] +struct S; + +fn main() {}