Skip to content
Merged
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
6 changes: 3 additions & 3 deletions crates/ty_python_semantic/src/types/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use ruff_db::diagnostic::{Annotation, Diagnostic, Span, SubDiagnostic, SubDiagno
use ruff_db::source::source_text;
use ruff_python_ast::name::Name;
use ruff_python_ast::parenthesize::parentheses_iterator;
use ruff_python_ast::{self as ast, AnyNodeRef, Identifier};
use ruff_python_ast::{self as ast, AnyNodeRef};
use ruff_python_trivia::CommentRanges;
use ruff_text_size::{Ranged, TextRange};
use rustc_hash::FxHashSet;
Expand Down Expand Up @@ -3427,7 +3427,7 @@ pub(super) fn hint_if_stdlib_attribute_exists_on_other_versions(
db: &dyn Db,
mut diagnostic: LintDiagnosticGuard,
value_type: &Type,
attr: &Identifier,
attr: &str,
) {
// Currently we limit this analysis to attributes of stdlib modules,
// as this covers the most important cases while not being too noisy
Expand Down Expand Up @@ -3461,6 +3461,6 @@ pub(super) fn hint_if_stdlib_attribute_exists_on_other_versions(
add_inferred_python_version_hint_to_diagnostic(
db,
&mut diagnostic,
&format!("accessing `{}`", attr.id),
&format!("accessing `{attr}`"),
);
}
165 changes: 86 additions & 79 deletions crates/ty_python_semantic/src/types/infer/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8988,7 +8988,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
assigned_type = Some(ty);
}
}
let fallback_place = value_type.member(db, &attr.id);
let mut fallback_place = value_type.member(db, &attr.id);
// Exclude non-definitely-bound places for purposes of reachability
// analysis. We currently do not perform boundness analysis for implicit
// instance attributes, so we exclude them here as well.
Expand All @@ -9000,91 +9000,98 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.all_definitely_bound = false;
}

let resolved_type =
fallback_place.map_type(|ty| {
fallback_place = fallback_place.map_type(|ty| {
self.narrow_expr_with_applicable_constraints(attribute, ty, &constraint_keys)
}).unwrap_with_diagnostic(|lookup_error| match lookup_error {
LookupError::Undefined(_) => {
let report_unresolved_attribute = self.is_reachable(attribute);

if report_unresolved_attribute {
let bound_on_instance = match value_type {
Type::ClassLiteral(class) => {
!class.instance_member(db, None, attr).is_undefined()
}
Type::SubclassOf(subclass_of @ SubclassOfType { .. }) => {
match subclass_of.subclass_of() {
SubclassOfInner::Class(class) => {
!class.instance_member(db, attr).is_undefined()
}
SubclassOfInner::Dynamic(_) => unreachable!(
"Attribute lookup on a dynamic `SubclassOf` type should always return a bound symbol"
),
}
}
_ => false,
};
});

if let Some(builder) = self
.context
.report_lint(&UNRESOLVED_ATTRIBUTE, attribute)
{
if bound_on_instance {
builder.into_diagnostic(
format_args!(
"Attribute `{}` can only be accessed on instances, \
not on the class object `{}` itself.",
attr.id,
value_type.display(db)
),
);
} else {
let diagnostic = match value_type {
Type::ModuleLiteral(module) => builder.into_diagnostic(format_args!(
"Module `{}` has no member `{}`",
module.module(db).name(db),
&attr.id
)),
Type::ClassLiteral(class) => builder.into_diagnostic(format_args!(
"Class `{}` has no attribute `{}`",
class.name(db),
&attr.id
)),
Type::GenericAlias(alias) => builder.into_diagnostic(format_args!(
"Class `{}` has no attribute `{}`",
alias.display(db),
&attr.id
)),
Type::FunctionLiteral(function) => builder.into_diagnostic(format_args!(
"Function `{}` has no attribute `{}`",
function.name(db),
&attr.id
)),
_ => builder.into_diagnostic(format_args!(
"Object of type `{}` has no attribute `{}`",
value_type.display(db),
&attr.id
)),
};
hint_if_stdlib_attribute_exists_on_other_versions(db, diagnostic, &value_type, attr);
let attr_name = &attr.id;

let resolved_type = fallback_place.unwrap_with_diagnostic(|lookup_err| match lookup_err {
LookupError::Undefined(_) => {
let fallback = || {
TypeAndQualifiers::new(
Type::unknown(),
TypeOrigin::Inferred,
TypeQualifiers::empty(),
)
};

if !self.is_reachable(attribute) {
return fallback();
}

let bound_on_instance = match value_type {
Type::ClassLiteral(class) => {
!class.instance_member(db, None, attr).is_undefined()
}
Type::SubclassOf(subclass_of @ SubclassOfType { .. }) => {
match subclass_of.subclass_of() {
SubclassOfInner::Class(class) => {
!class.instance_member(db, attr).is_undefined()
}
SubclassOfInner::Dynamic(_) => unreachable!(
"Attribute lookup on a dynamic `SubclassOf` type \
should always return a bound symbol"
),
}
}
_ => false,
};

TypeAndQualifiers::new(Type::unknown(), TypeOrigin::Inferred, TypeQualifiers::empty())
}
LookupError::PossiblyUndefined(type_when_bound) => {
report_possibly_missing_attribute(
&self.context,
attribute,
&attr.id,
value_type,
);
let Some(builder) = self.context.report_lint(&UNRESOLVED_ATTRIBUTE, attribute)
else {
return fallback();
};

type_when_bound
}
})
.inner_type();
if bound_on_instance {
builder.into_diagnostic(format_args!(
"Attribute `{attr_name}` can only be accessed on instances, \
not on the class object `{}` itself.",
value_type.display(db)
));
return fallback();
}

let diagnostic = match value_type {
Type::ModuleLiteral(module) => builder.into_diagnostic(format_args!(
"Module `{}` has no member `{attr_name}`",
module.module(db).name(db),
)),
Type::ClassLiteral(class) => builder.into_diagnostic(format_args!(
"Class `{}` has no attribute `{attr_name}`",
class.name(db),
)),
Type::GenericAlias(alias) => builder.into_diagnostic(format_args!(
"Class `{}` has no attribute `{attr_name}`",
alias.display(db),
)),
Type::FunctionLiteral(function) => builder.into_diagnostic(format_args!(
"Function `{}` has no attribute `{attr_name}`",
function.name(db),
)),
_ => builder.into_diagnostic(format_args!(
"Object of type `{}` has no attribute `{attr_name}`",
value_type.display(db),
)),
};

hint_if_stdlib_attribute_exists_on_other_versions(
db,
diagnostic,
&value_type,
attr_name,
);

fallback()
}
LookupError::PossiblyUndefined(type_when_bound) => {
report_possibly_missing_attribute(&self.context, attribute, &attr.id, value_type);

type_when_bound
}
});

let resolved_type = resolved_type.inner_type();

self.check_deprecated(attr, resolved_type);

Expand Down
Loading