Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/rustc_lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
80 changes: 68 additions & 12 deletions compiler/rustc_lint/src/non_ascii_idents.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand Down Expand Up @@ -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! {
Expand Down Expand Up @@ -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<UnstableLintExpectationId>)>,
}

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('\'') {

@estebank estebank Jun 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is there a reason we're ignoring lifetime names here? I would have assumed that lifetimes should also be handled by this lint (although a quick check of the current logic it does seem like we don't?)

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'd be happy to include lifetime names here too if you think that's appropriate.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Doing so might cause us to have to delay landing this PR (as we'll have to run crater to make sure we don't suddenly regress a lot of crates). Would you mind doing that as a follow up though?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sure. Is there a standard or blessed way of stacking PRs in this repo? Or should I just remember to push a new PR once this one has merged?

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;
}
Expand All @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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() {}
Original file line number Diff line number Diff line change
@@ -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

8 changes: 8 additions & 0 deletions tests/ui/proc-macro/auxiliary/non-ascii-idents-derive.rs
Original file line number Diff line number Diff line change
@@ -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()
}
17 changes: 17 additions & 0 deletions tests/ui/proc-macro/non-ascii-idents-derive.rs
Original file line number Diff line number Diff line change
@@ -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() {}
Loading