[ty] Format conflicting types as an enumeration (#18956)

## Summary

Format conflicting declared types as
```
`str`, `int` and `bytes`
```

Thanks to @AlexWaygood for the initial draft.

@dcreager, looking forward to your one-character follow-up PR.
This commit is contained in:
David Peter 2025-06-26 14:29:33 +02:00 committed by GitHub
parent c0beb3412f
commit 86fd9b634e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 90 additions and 83 deletions

View file

@ -17,6 +17,7 @@ use crate::types::string_annotation::{
};
use crate::types::tuple::TupleType;
use crate::types::{SpecialFormType, Type, protocol_class::ProtocolClassLiteral};
use crate::util::diagnostics::format_enumeration;
use crate::{Db, FxIndexMap, Module, ModuleName, Program, declare_lint};
use itertools::Itertools;
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic};
@ -2038,29 +2039,9 @@ impl<'db> IncompatibleBases<'db> {
/// List the problematic class bases in a human-readable format.
fn describe_problematic_class_bases(&self, db: &dyn Db) -> String {
let num_bases = self.len();
debug_assert!(num_bases >= 2);
let bad_base_names = self.0.values().map(|info| info.originating_base.name(db));
let mut bad_base_names = self.0.values().map(|info| info.originating_base.name(db));
let final_base = bad_base_names.next_back().unwrap();
let penultimate_base = bad_base_names.next_back().unwrap();
let mut buffer = String::new();
for base_name in bad_base_names {
buffer.push('`');
buffer.push_str(base_name);
buffer.push_str("`, ");
}
buffer.push('`');
buffer.push_str(penultimate_base);
buffer.push_str("` and `");
buffer.push_str(final_base);
buffer.push('`');
buffer
format_enumeration(bad_base_names)
}
pub(super) fn len(&self) -> usize {

View file

@ -106,6 +106,7 @@ use crate::types::{
todo_type,
};
use crate::unpack::{Unpack, UnpackPosition};
use crate::util::diagnostics::format_enumeration;
use crate::util::subscript::{PyIndex, PySlice};
use crate::{Db, FxOrderSet, Program};
@ -1643,7 +1644,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
if let Some(builder) = self.context.report_lint(&CONFLICTING_DECLARATIONS, node) {
builder.into_diagnostic(format_args!(
"Conflicting declared types for `{place}`: {}",
conflicting.iter().map(|ty| ty.display(db)).join(", ")
format_enumeration(conflicting.iter().map(|ty| ty.display(db)))
));
}
ty.inner_type()

View file

@ -1,5 +1,6 @@
use crate::{Db, Program, PythonVersionWithSource};
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic};
use std::fmt::Write;
/// Add a subdiagnostic to `diagnostic` that explains why a certain Python version was inferred.
///
@ -87,3 +88,27 @@ pub fn add_inferred_python_version_hint_to_diagnostic(
}
}
}
/// Format a list of elements as a human-readable enumeration.
///
/// Encloses every element in backticks (`1`, `2` and `3`).
pub(crate) fn format_enumeration<I, IT, D>(elements: I) -> String
where
I: IntoIterator<IntoIter = IT>,
IT: ExactSizeIterator<Item = D> + DoubleEndedIterator,
D: std::fmt::Display,
{
let mut elements = elements.into_iter();
debug_assert!(elements.len() >= 2);
let final_element = elements.next_back().unwrap();
let penultimate_element = elements.next_back().unwrap();
let mut buffer = String::new();
for element in elements {
write!(&mut buffer, "`{element}`, ").ok();
}
write!(&mut buffer, "`{penultimate_element}` and `{final_element}`").ok();
buffer
}