diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/bad_string_format_type.py b/crates/ruff_linter/resources/test/fixtures/pylint/bad_string_format_type.py index 3fe6722401507..e95b8ed9a6ded 100644 --- a/crates/ruff_linter/resources/test/fixtures/pylint/bad_string_format_type.py +++ b/crates/ruff_linter/resources/test/fixtures/pylint/bad_string_format_type.py @@ -57,3 +57,9 @@ '(%r, %r, %r, %r)' % (hostname, address, username, '$PASSWORD') '%r' % ({'server_school_roles': server_school_roles, 'is_school_multiserver_domain': is_school_multiserver_domain}, ) "%d" % (1 if x > 0 else 2) + +# Special cases for %c allowing single character strings +# https://github.com/astral-sh/ruff/issues/8406 +"%c" % ("x",) +"%c" % "x" +"%c" % "œ" diff --git a/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_type.rs b/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_type.rs index 0ea6884da096a..4d62c785de583 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_type.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/bad_string_format_type.rs @@ -119,12 +119,23 @@ fn collect_specs(formats: &[CFormatStrOrBytes]) -> Vec<&CFormatSpec> { /// Return `true` if the format string is equivalent to the constant type fn equivalent(format: &CFormatSpec, value: &Expr) -> bool { - let format = FormatType::from(format.format_char); + let format_type = FormatType::from(format.format_char); match ResolvedPythonType::from(value) { - ResolvedPythonType::Atom(atom) => format.is_compatible_with(atom), - ResolvedPythonType::Union(atoms) => { - atoms.iter().all(|atom| format.is_compatible_with(*atom)) + ResolvedPythonType::Atom(atom) => { + // Special case where `%c` allows single character strings to be formatted + if format.format_char == 'c' { + if let Expr::StringLiteral(string) = value { + let mut chars = string.chars(); + if chars.next().is_some() && chars.next().is_none() { + return true; + } + } + } + format_type.is_compatible_with(atom) } + ResolvedPythonType::Union(atoms) => atoms + .iter() + .all(|atom| format_type.is_compatible_with(*atom)), ResolvedPythonType::Unknown => true, ResolvedPythonType::TypeError => true, }