[ty] Rename "possibly unbound" diagnostics to "possibly missing" (#20492)

Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
This commit is contained in:
Renkai Ge 2025-09-23 22:26:55 +08:00 committed by GitHub
parent 4ed8c65d29
commit bf38e69870
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 213 additions and 194 deletions

View file

@ -8,7 +8,7 @@ use bitflags::bitflags;
use call::{CallDunderError, CallError, CallErrorKind};
use context::InferContext;
use diagnostic::{
INVALID_CONTEXT_MANAGER, INVALID_SUPER_ARGUMENT, NOT_ITERABLE, POSSIBLY_UNBOUND_IMPLICIT_CALL,
INVALID_CONTEXT_MANAGER, INVALID_SUPER_ARGUMENT, NOT_ITERABLE, POSSIBLY_MISSING_IMPLICIT_CALL,
UNAVAILABLE_IMPLICIT_SUPER_ARGUMENTS,
};
use ruff_db::diagnostic::{Annotation, Diagnostic, Span, SubDiagnostic, SubDiagnosticSeverity};
@ -3830,7 +3830,7 @@ impl<'db> Type<'db> {
});
}
// Don't trust possibly unbound `__bool__` method.
// Don't trust possibly missing `__bool__` method.
Ok(Truthiness::Ambiguous)
}
@ -8148,7 +8148,7 @@ impl<'db> AwaitError<'db> {
}
}
Self::Call(CallDunderError::PossiblyUnbound(bindings)) => {
diag.info("`__await__` is possibly unbound");
diag.info("`__await__` may be missing");
if let Some(definition_spans) = bindings.callable_type().function_spans(db) {
diag.annotate(
Annotation::secondary(definition_spans.signature)
@ -8255,7 +8255,7 @@ impl<'db> ContextManagerError<'db> {
match call_dunder_error {
CallDunderError::MethodNotAvailable => format!("it does not implement `{name}`"),
CallDunderError::PossiblyUnbound(_) => {
format!("the method `{name}` is possibly unbound")
format!("the method `{name}` may be missing")
}
// TODO: Use more specific error messages for the different error cases.
// E.g. hint toward the union variant that doesn't correctly implement enter,
@ -8272,7 +8272,7 @@ impl<'db> ContextManagerError<'db> {
name_b: &str| {
match (error_a, error_b) {
(CallDunderError::PossiblyUnbound(_), CallDunderError::PossiblyUnbound(_)) => {
format!("the methods `{name_a}` and `{name_b}` are possibly unbound")
format!("the methods `{name_a}` and `{name_b}` are possibly missing")
}
(CallDunderError::MethodNotAvailable, CallDunderError::MethodNotAvailable) => {
format!("it does not implement `{name_a}` and `{name_b}`")
@ -8821,7 +8821,7 @@ pub(super) enum BoolError<'db> {
/// Any other reason why the type can't be converted to a bool.
/// E.g. because calling `__bool__` returns in a union type and not all variants support `__bool__` or
/// because `__bool__` points to a type that has a possibly unbound `__call__` method.
/// because `__bool__` points to a type that has a possibly missing `__call__` method.
Other { not_boolable_type: Type<'db> },
}
@ -8994,7 +8994,7 @@ impl<'db> ConstructorCallError<'db> {
let report_init_error = |call_dunder_error: &CallDunderError<'db>| match call_dunder_error {
CallDunderError::MethodNotAvailable => {
if let Some(builder) =
context.report_lint(&POSSIBLY_UNBOUND_IMPLICIT_CALL, context_expression_node)
context.report_lint(&POSSIBLY_MISSING_IMPLICIT_CALL, context_expression_node)
{
// If we are using vendored typeshed, it should be impossible to have missing
// or unbound `__init__` method on a class, as all classes have `object` in MRO.
@ -9008,10 +9008,10 @@ impl<'db> ConstructorCallError<'db> {
}
CallDunderError::PossiblyUnbound(bindings) => {
if let Some(builder) =
context.report_lint(&POSSIBLY_UNBOUND_IMPLICIT_CALL, context_expression_node)
context.report_lint(&POSSIBLY_MISSING_IMPLICIT_CALL, context_expression_node)
{
builder.into_diagnostic(format_args!(
"Method `__init__` on type `{}` is possibly unbound.",
"Method `__init__` on type `{}` may be missing.",
context_expression_type.display(context.db()),
));
}
@ -9026,10 +9026,10 @@ impl<'db> ConstructorCallError<'db> {
let report_new_error = |error: &DunderNewCallError<'db>| match error {
DunderNewCallError::PossiblyUnbound(call_error) => {
if let Some(builder) =
context.report_lint(&POSSIBLY_UNBOUND_IMPLICIT_CALL, context_expression_node)
context.report_lint(&POSSIBLY_MISSING_IMPLICIT_CALL, context_expression_node)
{
builder.into_diagnostic(format_args!(
"Method `__new__` on type `{}` is possibly unbound.",
"Method `__new__` on type `{}` may be missing.",
context_expression_type.display(context.db()),
));
}

View file

@ -1758,7 +1758,7 @@ impl<'db> CallableBinding<'db> {
if self.dunder_call_is_possibly_unbound {
if let Some(builder) = context.report_lint(&CALL_NON_CALLABLE, node) {
let mut diag = builder.into_diagnostic(format_args!(
"Object of type `{}` is not callable (possibly unbound `__call__` method)",
"Object of type `{}` is not callable (possibly missing `__call__` method)",
self.callable_type.display(context.db()),
));
if let Some(union_diag) = union_diag {

View file

@ -38,7 +38,7 @@ use std::fmt::Formatter;
pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
registry.register_lint(&AMBIGUOUS_PROTOCOL_MEMBER);
registry.register_lint(&CALL_NON_CALLABLE);
registry.register_lint(&POSSIBLY_UNBOUND_IMPLICIT_CALL);
registry.register_lint(&POSSIBLY_MISSING_IMPLICIT_CALL);
registry.register_lint(&CONFLICTING_ARGUMENT_FORMS);
registry.register_lint(&CONFLICTING_DECLARATIONS);
registry.register_lint(&CONFLICTING_METACLASS);
@ -80,8 +80,8 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
registry.register_lint(&NOT_ITERABLE);
registry.register_lint(&UNSUPPORTED_BOOL_CONVERSION);
registry.register_lint(&PARAMETER_ALREADY_ASSIGNED);
registry.register_lint(&POSSIBLY_UNBOUND_ATTRIBUTE);
registry.register_lint(&POSSIBLY_UNBOUND_IMPORT);
registry.register_lint(&POSSIBLY_MISSING_ATTRIBUTE);
registry.register_lint(&POSSIBLY_MISSING_IMPORT);
registry.register_lint(&POSSIBLY_UNRESOLVED_REFERENCE);
registry.register_lint(&SUBCLASS_OF_FINAL_CLASS);
registry.register_lint(&TYPE_ASSERTION_FAILURE);
@ -131,12 +131,12 @@ declare_lint! {
declare_lint! {
/// ## What it does
/// Checks for implicit calls to possibly unbound methods.
/// Checks for implicit calls to possibly missing methods.
///
/// ## Why is this bad?
/// Expressions such as `x[y]` and `x * y` call methods
/// under the hood (`__getitem__` and `__mul__` respectively).
/// Calling an unbound method will raise an `AttributeError` at runtime.
/// Calling a missing method will raise an `AttributeError` at runtime.
///
/// ## Examples
/// ```python
@ -148,8 +148,8 @@ declare_lint! {
///
/// A()[0] # TypeError: 'A' object is not subscriptable
/// ```
pub(crate) static POSSIBLY_UNBOUND_IMPLICIT_CALL = {
summary: "detects implicit calls to possibly unbound methods",
pub(crate) static POSSIBLY_MISSING_IMPLICIT_CALL = {
summary: "detects implicit calls to possibly missing methods",
status: LintStatus::preview("1.0.0"),
default_level: Level::Warn,
}
@ -1327,10 +1327,10 @@ declare_lint! {
declare_lint! {
/// ## What it does
/// Checks for possibly unbound attributes.
/// Checks for possibly missing attributes.
///
/// ## Why is this bad?
/// Attempting to access an unbound attribute will raise an `AttributeError` at runtime.
/// Attempting to access a missing attribute will raise an `AttributeError` at runtime.
///
/// ## Examples
/// ```python
@ -1340,8 +1340,8 @@ declare_lint! {
///
/// A.c # AttributeError: type object 'A' has no attribute 'c'
/// ```
pub(crate) static POSSIBLY_UNBOUND_ATTRIBUTE = {
summary: "detects references to possibly unbound attributes",
pub(crate) static POSSIBLY_MISSING_ATTRIBUTE = {
summary: "detects references to possibly missing attributes",
status: LintStatus::preview("1.0.0"),
default_level: Level::Warn,
}
@ -1349,10 +1349,10 @@ declare_lint! {
declare_lint! {
/// ## What it does
/// Checks for imports of symbols that may be unbound.
/// Checks for imports of symbols that may be missing.
///
/// ## Why is this bad?
/// Importing an unbound module or name will raise a `ModuleNotFoundError`
/// Importing a missing module or name will raise a `ModuleNotFoundError`
/// or `ImportError` at runtime.
///
/// ## Examples
@ -1366,8 +1366,8 @@ declare_lint! {
/// # main.py
/// from module import a # ImportError: cannot import name 'a' from 'module'
/// ```
pub(crate) static POSSIBLY_UNBOUND_IMPORT = {
summary: "detects possibly unbound imports",
pub(crate) static POSSIBLY_MISSING_IMPORT = {
summary: "detects possibly missing imports",
status: LintStatus::preview("1.0.0"),
default_level: Level::Warn,
}
@ -2197,17 +2197,17 @@ pub(super) fn report_possibly_unresolved_reference(
builder.into_diagnostic(format_args!("Name `{id}` used when possibly not defined"));
}
pub(super) fn report_possibly_unbound_attribute(
pub(super) fn report_possibly_missing_attribute(
context: &InferContext,
target: &ast::ExprAttribute,
attribute: &str,
object_ty: Type,
) {
let Some(builder) = context.report_lint(&POSSIBLY_UNBOUND_ATTRIBUTE, target) else {
let Some(builder) = context.report_lint(&POSSIBLY_MISSING_ATTRIBUTE, target) else {
return;
};
builder.into_diagnostic(format_args!(
"Attribute `{attribute}` on type `{}` is possibly unbound",
"Attribute `{attribute}` on type `{}` may be missing",
object_ty.display(context.db()),
));
}
@ -2793,7 +2793,7 @@ pub(crate) fn report_invalid_or_unsupported_base(
CallDunderError::PossiblyUnbound(_) => {
explain_mro_entries(&mut diagnostic);
diagnostic.info(format_args!(
"Type `{}` has an `__mro_entries__` attribute, but it is possibly unbound",
"Type `{}` may have an `__mro_entries__` attribute, but it may be missing",
base_type.display(db)
));
}

View file

@ -52,7 +52,7 @@ use crate::types::diagnostic::{
INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, INVALID_DECLARATION,
INVALID_GENERIC_CLASS, INVALID_KEY, INVALID_NAMED_TUPLE, INVALID_PARAMETER_DEFAULT,
INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL, INVALID_TYPE_VARIABLE_CONSTRAINTS,
IncompatibleBases, NON_SUBSCRIPTABLE, POSSIBLY_UNBOUND_IMPLICIT_CALL, POSSIBLY_UNBOUND_IMPORT,
IncompatibleBases, NON_SUBSCRIPTABLE, POSSIBLY_MISSING_IMPLICIT_CALL, POSSIBLY_MISSING_IMPORT,
UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL, UNRESOLVED_IMPORT,
UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, report_bad_dunder_set_call,
report_cannot_pop_required_field_on_typed_dict, report_implicit_return_type,
@ -60,7 +60,7 @@ use crate::types::diagnostic::{
report_invalid_attribute_assignment, report_invalid_generator_function_return_type,
report_invalid_key_on_typed_dict, report_invalid_return_type,
report_namedtuple_field_without_default_after_field_with_default,
report_possibly_unbound_attribute,
report_possibly_missing_attribute,
};
use crate::types::diagnostic::{
INVALID_METACLASS, INVALID_OVERLOAD, INVALID_PROTOCOL, SUBCLASS_OF_FINAL_CLASS,
@ -3222,10 +3222,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
Err(err) => match err {
CallDunderError::PossiblyUnbound { .. } => {
if let Some(builder) =
context.report_lint(&POSSIBLY_UNBOUND_IMPLICIT_CALL, &**value)
context.report_lint(&POSSIBLY_MISSING_IMPLICIT_CALL, &**value)
{
builder.into_diagnostic(format_args!(
"Method `__setitem__` of type `{}` is possibly unbound",
"Method `__setitem__` of type `{}` may be missing",
value_ty.display(db),
));
}
@ -3306,7 +3306,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
if let Some(builder) = context.report_lint(&CALL_NON_CALLABLE, &**value)
{
builder.into_diagnostic(format_args!(
"Method `__setitem__` of type `{}` is possibly not \
"Method `__setitem__` of type `{}` may not be \
callable on object of type `{}`",
bindings.callable_type().display(db),
value_ty.display(db),
@ -3642,7 +3642,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
};
if boundness == Boundness::PossiblyUnbound {
report_possibly_unbound_attribute(
report_possibly_missing_attribute(
&self.context,
target,
attribute,
@ -3672,7 +3672,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
if instance_attr_boundness == Boundness::PossiblyUnbound {
report_possibly_unbound_attribute(
report_possibly_missing_attribute(
&self.context,
target,
attribute,
@ -3752,7 +3752,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
};
if boundness == Boundness::PossiblyUnbound {
report_possibly_unbound_attribute(
report_possibly_missing_attribute(
&self.context,
target,
attribute,
@ -3783,7 +3783,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
if class_attr_boundness == Boundness::PossiblyUnbound {
report_possibly_unbound_attribute(
report_possibly_missing_attribute(
&self.context,
target,
attribute,
@ -4680,10 +4680,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// together if the attribute exists but is possibly-unbound.
if let Some(builder) = self
.context
.report_lint(&POSSIBLY_UNBOUND_IMPORT, AnyNodeRef::Alias(alias))
.report_lint(&POSSIBLY_MISSING_IMPORT, AnyNodeRef::Alias(alias))
{
builder.into_diagnostic(format_args!(
"Member `{name}` of module `{module_name}` is possibly unbound",
"Member `{name}` of module `{module_name}` may be missing",
));
}
}
@ -6804,7 +6804,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
Type::unknown().into()
}
LookupError::PossiblyUnbound(type_when_bound) => {
report_possibly_unbound_attribute(
report_possibly_missing_attribute(
&self.context,
attribute,
&attr.id,
@ -8757,10 +8757,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
Err(err @ CallDunderError::PossiblyUnbound { .. }) => {
if let Some(builder) =
context.report_lint(&POSSIBLY_UNBOUND_IMPLICIT_CALL, value_node)
context.report_lint(&POSSIBLY_MISSING_IMPLICIT_CALL, value_node)
{
builder.into_diagnostic(format_args!(
"Method `__getitem__` of type `{}` is possibly unbound",
"Method `__getitem__` of type `{}` may be missing",
value_ty.display(db),
));
}
@ -8808,7 +8808,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
CallErrorKind::PossiblyNotCallable => {
if let Some(builder) = context.report_lint(&CALL_NON_CALLABLE, value_node) {
builder.into_diagnostic(format_args!(
"Method `__getitem__` of type `{}` is possibly not callable on object of type `{}`",
"Method `__getitem__` of type `{}` may not be callable on object of type `{}`",
bindings.callable_type().display(db),
value_ty.display(db),
));
@ -8840,11 +8840,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
Place::Type(ty, boundness) => {
if boundness == Boundness::PossiblyUnbound {
if let Some(builder) =
context.report_lint(&POSSIBLY_UNBOUND_IMPLICIT_CALL, value_node)
context.report_lint(&POSSIBLY_MISSING_IMPLICIT_CALL, value_node)
{
builder.into_diagnostic(format_args!(
"Method `__class_getitem__` of type `{}` \
is possibly unbound",
"Method `__class_getitem__` of type `{}` may be missing",
value_ty.display(db),
));
}