diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index ed8b1be1ab48e..a91048f54d465 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -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; @@ -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 @@ -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}`"), ); } diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index cd93f59526df4..c691d00be0cb9 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -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. @@ -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);