mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-28 21:05:08 +00:00
ty_python_semantic: improve failed overloaded function call
The diagnostic now includes a pointer to the implementation definition along with each possible overload. This doesn't include information about *why* each overload failed. But given the emphasis on concise output (since there can be *many* unmatched overloads), it's not totally clear how to include that additional information. Fixes #274
This commit is contained in:
parent
451c5db7a3
commit
faf54c0181
8 changed files with 921 additions and 79 deletions
|
@ -5350,36 +5350,6 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a tuple of two spans. The first is
|
||||
/// the span for the identifier of the function
|
||||
/// definition for `self`. The second is
|
||||
/// the span for the return type in the function
|
||||
/// definition for `self`.
|
||||
///
|
||||
/// If there are no meaningful spans, then this
|
||||
/// returns `None`. For example, when this type
|
||||
/// isn't callable or if the function has no
|
||||
/// declared return type.
|
||||
///
|
||||
/// # Performance
|
||||
///
|
||||
/// Note that this may introduce cross-module
|
||||
/// dependencies. This can have an impact on
|
||||
/// the effectiveness of incremental caching
|
||||
/// and should therefore be used judiciously.
|
||||
///
|
||||
/// An example of a good use case is to improve
|
||||
/// a diagnostic.
|
||||
fn return_type_span(&self, db: &'db dyn Db) -> Option<(Span, Span)> {
|
||||
match *self {
|
||||
Type::FunctionLiteral(function) => function.return_type_span(db),
|
||||
Type::BoundMethod(bound_method) => {
|
||||
Type::FunctionLiteral(bound_method.function(db)).return_type_span(db)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a tuple of two spans. The first is
|
||||
/// the span for the identifier of the function
|
||||
/// definition for `self`. The second is
|
||||
|
@ -5410,9 +5380,34 @@ impl<'db> Type<'db> {
|
|||
) -> Option<(Span, Span)> {
|
||||
match *self {
|
||||
Type::FunctionLiteral(function) => function.parameter_span(db, parameter_index),
|
||||
Type::BoundMethod(bound_method) => {
|
||||
Type::FunctionLiteral(bound_method.function(db)).parameter_span(db, parameter_index)
|
||||
}
|
||||
Type::BoundMethod(bound_method) => bound_method
|
||||
.function(db)
|
||||
.parameter_span(db, parameter_index),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a collection of useful spans for a
|
||||
/// function signature. These are useful for
|
||||
/// creating annotations on diagnostics.
|
||||
///
|
||||
/// If there are no meaningful spans, then this
|
||||
/// returns `None`. For example, when this type
|
||||
/// isn't callable.
|
||||
///
|
||||
/// # Performance
|
||||
///
|
||||
/// Note that this may introduce cross-module
|
||||
/// dependencies. This can have an impact on
|
||||
/// the effectiveness of incremental caching
|
||||
/// and should therefore be used judiciously.
|
||||
///
|
||||
/// An example of a good use case is to improve
|
||||
/// a diagnostic.
|
||||
fn function_spans(&self, db: &'db dyn Db) -> Option<FunctionSpans> {
|
||||
match *self {
|
||||
Type::FunctionLiteral(function) => function.spans(db),
|
||||
Type::BoundMethod(bound_method) => bound_method.function(db).spans(db),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -6297,7 +6292,8 @@ impl<'db> BoolError<'db> {
|
|||
.member(context.db(), "__bool__")
|
||||
.into_lookup_result()
|
||||
.ok()
|
||||
.and_then(|quals| quals.inner_type().return_type_span(context.db()))
|
||||
.and_then(|quals| quals.inner_type().function_spans(context.db()))
|
||||
.and_then(|spans| Some((spans.name, spans.return_type?)))
|
||||
{
|
||||
sub.annotate(
|
||||
Annotation::primary(return_type_span).message("Incorrect return type"),
|
||||
|
@ -6894,37 +6890,6 @@ impl<'db> FunctionType<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a tuple of two spans. The first is
|
||||
/// the span for the identifier of the function
|
||||
/// definition for `self`. The second is
|
||||
/// the span for the return type in the function
|
||||
/// definition for `self`.
|
||||
///
|
||||
/// If there are no meaningful spans, then this
|
||||
/// returns `None`. For example, when this type
|
||||
/// isn't callable or if the function has no
|
||||
/// declared return type.
|
||||
///
|
||||
/// # Performance
|
||||
///
|
||||
/// Note that this may introduce cross-module
|
||||
/// dependencies. This can have an impact on
|
||||
/// the effectiveness of incremental caching
|
||||
/// and should therefore be used judiciously.
|
||||
///
|
||||
/// An example of a good use case is to improve
|
||||
/// a diagnostic.
|
||||
fn return_type_span(&self, db: &'db dyn Db) -> Option<(Span, Span)> {
|
||||
let function_scope = self.body_scope(db);
|
||||
let span = Span::from(function_scope.file(db));
|
||||
let node = function_scope.node(db);
|
||||
let func_def = node.as_function()?;
|
||||
let return_type_range = func_def.returns.as_ref()?.range();
|
||||
let name_span = span.clone().with_range(func_def.name.range);
|
||||
let return_type_span = span.with_range(return_type_range);
|
||||
Some((name_span, return_type_span))
|
||||
}
|
||||
|
||||
/// Returns a tuple of two spans. The first is
|
||||
/// the span for the identifier of the function
|
||||
/// definition for `self`. The second is
|
||||
|
@ -6949,7 +6914,7 @@ impl<'db> FunctionType<'db> {
|
|||
/// An example of a good use case is to improve
|
||||
/// a diagnostic.
|
||||
fn parameter_span(
|
||||
&self,
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
parameter_index: Option<usize>,
|
||||
) -> Option<(Span, Span)> {
|
||||
|
@ -6970,6 +6935,55 @@ impl<'db> FunctionType<'db> {
|
|||
let parameter_span = span.with_range(range);
|
||||
Some((name_span, parameter_span))
|
||||
}
|
||||
|
||||
/// Returns a collection of useful spans for a
|
||||
/// function signature. These are useful for
|
||||
/// creating annotations on diagnostics.
|
||||
///
|
||||
/// # Performance
|
||||
///
|
||||
/// Note that this may introduce cross-module
|
||||
/// dependencies. This can have an impact on
|
||||
/// the effectiveness of incremental caching
|
||||
/// and should therefore be used judiciously.
|
||||
///
|
||||
/// An example of a good use case is to improve
|
||||
/// a diagnostic.
|
||||
fn spans(self, db: &'db dyn Db) -> Option<FunctionSpans> {
|
||||
let function_scope = self.body_scope(db);
|
||||
let span = Span::from(function_scope.file(db));
|
||||
let node = function_scope.node(db);
|
||||
let func_def = node.as_function()?;
|
||||
let return_type_range = func_def.returns.as_ref().map(|returns| returns.range());
|
||||
let mut signature = func_def.name.range.cover(func_def.parameters.range);
|
||||
if let Some(return_type_range) = return_type_range {
|
||||
signature = signature.cover(return_type_range);
|
||||
}
|
||||
Some(FunctionSpans {
|
||||
signature: span.clone().with_range(signature),
|
||||
name: span.clone().with_range(func_def.name.range),
|
||||
parameters: span.clone().with_range(func_def.parameters.range),
|
||||
return_type: return_type_range.map(|range| span.clone().with_range(range)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of useful spans for annotating functions.
|
||||
///
|
||||
/// This can be retrieved via `FunctionType::spans` or
|
||||
/// `Type::function_spans`.
|
||||
struct FunctionSpans {
|
||||
/// The span of the entire function "signature." This includes
|
||||
/// the name, parameter list and return type (if present).
|
||||
signature: Span,
|
||||
/// The span of the function name. i.e., `foo` in `def foo(): ...`.
|
||||
name: Span,
|
||||
/// The span of the parameter list, including the opening and
|
||||
/// closing parentheses.
|
||||
#[expect(dead_code)]
|
||||
parameters: Span,
|
||||
/// The span of the annotated return type, if present.
|
||||
return_type: Option<Span>,
|
||||
}
|
||||
|
||||
fn signature_cycle_recover<'db>(
|
||||
|
|
|
@ -1100,6 +1100,13 @@ impl<'db> CallableBinding<'db> {
|
|||
);
|
||||
}
|
||||
_overloads => {
|
||||
// When the number of unmatched overloads exceeds this number, we stop
|
||||
// printing them to avoid excessive output.
|
||||
//
|
||||
// An example of a routine with many many overloads:
|
||||
// https://github.com/henribru/google-api-python-client-stubs/blob/master/googleapiclient-stubs/discovery.pyi
|
||||
const MAXIMUM_OVERLOADS: usize = 50;
|
||||
|
||||
let Some(builder) = context.report_lint(&NO_MATCHING_OVERLOAD, node) else {
|
||||
return;
|
||||
};
|
||||
|
@ -1113,6 +1120,48 @@ impl<'db> CallableBinding<'db> {
|
|||
String::new()
|
||||
}
|
||||
));
|
||||
if let Some(function) = self.signature_type.into_function_literal() {
|
||||
if let Some(overloaded_function) = function.to_overloaded(context.db()) {
|
||||
if let Some(spans) = overloaded_function
|
||||
.overloads
|
||||
.first()
|
||||
.and_then(|overload| overload.spans(context.db()))
|
||||
{
|
||||
let mut sub =
|
||||
SubDiagnostic::new(Severity::Info, "First overload defined here");
|
||||
sub.annotate(Annotation::primary(spans.signature));
|
||||
diag.sub(sub);
|
||||
}
|
||||
|
||||
diag.info(format_args!(
|
||||
"Possible overloads for function `{}`:",
|
||||
function.name(context.db())
|
||||
));
|
||||
|
||||
let overloads = &function.signature(context.db()).overloads.overloads;
|
||||
for overload in overloads.iter().take(MAXIMUM_OVERLOADS) {
|
||||
diag.info(format_args!(" {}", overload.display(context.db())));
|
||||
}
|
||||
if overloads.len() > MAXIMUM_OVERLOADS {
|
||||
diag.info(format_args!(
|
||||
"... omitted {remaining} overloads",
|
||||
remaining = overloads.len() - MAXIMUM_OVERLOADS
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(spans) = overloaded_function
|
||||
.implementation
|
||||
.and_then(|function| function.spans(context.db()))
|
||||
{
|
||||
let mut sub = SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
"Overload implementation defined here",
|
||||
);
|
||||
sub.annotate(Annotation::primary(spans.signature));
|
||||
diag.sub(sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(union_diag) = union_diag {
|
||||
union_diag.add_union_context(context.db(), &mut diag);
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ pub(crate) struct CallableSignature<'db> {
|
|||
///
|
||||
/// By using `SmallVec`, we avoid an extra heap allocation for the common case of a
|
||||
/// non-overloaded callable.
|
||||
overloads: SmallVec<[Signature<'db>; 1]>,
|
||||
pub(crate) overloads: SmallVec<[Signature<'db>; 1]>,
|
||||
}
|
||||
|
||||
impl<'db> CallableSignature<'db> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue