diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/callable.md b/crates/ty_python_semantic/resources/mdtest/annotations/callable.md index 34dd6e7d5d..e65ec2767c 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/callable.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/callable.md @@ -367,7 +367,7 @@ def f_wrong(c: Callable[[], None]): # error: [unresolved-attribute] "Object of type `() -> None` has no attribute `__qualname__`" c.__qualname__ - # error: [unresolved-attribute] "Unresolved attribute `__qualname__` on type `() -> None`." + # error: [unresolved-attribute] "Unresolved attribute `__qualname__` on type `() -> None`" c.__qualname__ = "my_callable" ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment…_-_Attribute_assignment_-_Unknown_attributes_(368ba83a71ef2120).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment…_-_Attribute_assignment_-_Unknown_attributes_(368ba83a71ef2120).snap index 02eb6f9eff..ead3379c62 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment…_-_Attribute_assignment_-_Unknown_attributes_(368ba83a71ef2120).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment…_-_Attribute_assignment_-_Unknown_attributes_(368ba83a71ef2120).snap @@ -39,7 +39,7 @@ info: rule `unresolved-attribute` is enabled by default ``` ``` -error[unresolved-attribute]: Unresolved attribute `non_existent` on type `C`. +error[unresolved-attribute]: Unresolved attribute `non_existent` on type `C` --> src/mdtest_snippet.py:6:1 | 5 | instance = C() diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 4a54a12f55..4622cce87a 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -3384,17 +3384,16 @@ impl<'db> Type<'db> { .map(|class| class.class_literal(db)), _ => None, }; - if let Some(enum_class) = enum_class { - if let Some(metadata) = enum_metadata(db, enum_class) { - if let Some(resolved_name) = metadata.resolve_member(&name) { - return Place::bound(Type::EnumLiteral(EnumLiteralType::new( - db, - enum_class, - resolved_name, - ))) - .into(); - } - } + if let Some(enum_class) = enum_class + && let Some(metadata) = enum_metadata(db, enum_class) + && let Some(resolved_name) = metadata.resolve_member(&name) + { + return Place::bound(Type::EnumLiteral(EnumLiteralType::new( + db, + enum_class, + resolved_name, + ))) + .into(); } let class_attr_plain = self.find_name_in_mro_with_policy(db, name_str, policy).expect( @@ -5063,16 +5062,15 @@ impl<'db> Type<'db> { let from_class_base = |base: ClassBase<'db>| { let class = base.into_class()?; - if class.is_known(db, KnownClass::Generator) { - if let Some((_, Some(specialization))) = + if class.is_known(db, KnownClass::Generator) + && let Some((_, Some(specialization))) = class.static_class_literal_specialized(db, None) - { - if let [_, _, return_ty] = specialization.types(db) { - return Some(*return_ty); - } - } + && let [_, _, return_ty] = specialization.types(db) + { + Some(*return_ty) + } else { + None } - None }; match self { @@ -8054,15 +8052,10 @@ impl<'db> TypeVarInstance<'db> { let typevar_node = typevar.node(&module); let bound = definition_expression_type(db, definition, typevar_node.bound.as_ref()?); - let constraints = if let Some(tuple) = bound - .as_nominal_instance() - .and_then(|instance| instance.tuple_spec(db)) + let constraints = if let Some(tuple) = bound.tuple_instance_spec(db) + && let Tuple::Fixed(tuple) = tuple.into_owned() { - if let Tuple::Fixed(tuple) = tuple.into_owned() { - tuple.owned_elements() - } else { - vec![Type::unknown()].into_boxed_slice() - } + tuple.owned_elements() } else { vec![Type::unknown()].into_boxed_slice() }; @@ -9140,13 +9133,13 @@ impl<'db> AwaitError<'db> { "" }; diag.info(format_args!("`__await__` is{possibly} not callable")); - if let Some(definition) = bindings.callable_type().definition(db) { - if let Some(definition_range) = definition.focus_range(db) { - diag.annotate( - Annotation::secondary(definition_range.into()) - .message("attribute defined here"), - ); - } + if let Some(definition) = bindings.callable_type().definition(db) + && let Some(definition_range) = definition.focus_range(db) + { + diag.annotate( + Annotation::secondary(definition_range.into()) + .message("attribute defined here"), + ); } } Self::Call(CallDunderError::PossiblyUnbound(bindings)) => { @@ -9160,13 +9153,12 @@ impl<'db> AwaitError<'db> { } Self::Call(CallDunderError::MethodNotAvailable) => { diag.info("`__await__` is missing"); - if let Some(type_definition) = context_expression_type.definition(db) { - if let Some(definition_range) = type_definition.focus_range(db) { - diag.annotate( - Annotation::secondary(definition_range.into()) - .message("type defined here"), - ); - } + if let Some(type_definition) = context_expression_type.definition(db) + && let Some(definition_range) = type_definition.focus_range(db) + { + diag.annotate( + Annotation::secondary(definition_range.into()).message("type defined here"), + ); } } Self::InvalidReturnType(return_type, bindings) => { @@ -11352,20 +11344,20 @@ impl<'db> ModuleLiteralType<'db> { // if it exists. First, we need to look up the `__getattr__` function in the module's scope. if let Some(file) = self.module(db).file(db) { let getattr_symbol = imported_symbol(db, file, "__getattr__", None); - if let Place::Defined(place) = getattr_symbol.place { - // If we found a __getattr__ function, try to call it with the name argument - if let Ok(outcome) = place.ty.try_call( + // If we found a __getattr__ function, try to call it with the name argument + if let Place::Defined(place) = getattr_symbol.place + && let Ok(outcome) = place.ty.try_call( db, &CallArguments::positional([Type::string_literal(db, name)]), - ) { - return PlaceAndQualifiers { - place: Place::Defined(DefinedPlace { - ty: outcome.return_type(db), - ..place - }), - qualifiers: TypeQualifiers::FROM_MODULE_GETATTR, - }; - } + ) + { + return PlaceAndQualifiers { + place: Place::Defined(DefinedPlace { + ty: outcome.return_type(db), + ..place + }), + qualifiers: TypeQualifiers::FROM_MODULE_GETATTR, + }; } } @@ -11391,10 +11383,10 @@ impl<'db> ModuleLiteralType<'db> { // the parent module's `__init__.py` file being evaluated. That said, we have // chosen to always have the submodule take priority. (This matches pyright's // current behavior, but is the opposite of mypy's current behavior.) - if self.available_submodule_attributes(db).contains(name) { - if let Some(submodule) = self.resolve_submodule(db, name) { - return Place::bound(submodule).into(); - } + if self.available_submodule_attributes(db).contains(name) + && let Some(submodule) = self.resolve_submodule(db, name) + { + return Place::bound(submodule).into(); } let place_and_qualifiers = self @@ -12093,17 +12085,11 @@ impl<'db> UnionType<'db> { let mut has_float = false; let mut has_complex = false; for element in self.elements(db) { - if let Type::NominalInstance(nominal) = element - && let Some(known) = nominal.known_class(db) - { - match known { - KnownClass::Int => has_int = true, - KnownClass::Float => has_float = true, - KnownClass::Complex => has_complex = true, - _ => return None, - } - } else { - return None; + match element.as_nominal_instance()?.known_class(db)? { + KnownClass::Int => has_int = true, + KnownClass::Float => has_float = true, + KnownClass::Complex => has_complex = true, + _ => return None, } } match (has_int, has_float, has_complex) { diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index 496468daaa..351c8cc3a1 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -628,17 +628,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { for (class, class_node) in class_definitions { // (1) Check that the class does not have a cyclic definition if let Some(inheritance_cycle) = class.inheritance_cycle(self.db()) { - if inheritance_cycle.is_participant() { - if let Some(builder) = self + if inheritance_cycle.is_participant() + && let Some(builder) = self .context .report_lint(&CYCLIC_CLASS_DEFINITION, class_node) - { - builder.into_diagnostic(format_args!( - "Cyclic definition of `{}` (class cannot inherit from itself)", - class.name(self.db()) - )); - } + { + builder.into_diagnostic(format_args!( + "Cyclic definition of `{}` (class cannot inherit from itself)", + class.name(self.db()) + )); } + // If a class is cyclically defined, that's a sufficient error to report; the // following checks (which are all inheritance-based) aren't even relevant. continue; @@ -1026,15 +1026,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { class.legacy_generic_context(self.db()), class.inherited_legacy_generic_context(self.db()), ) { - if !inherited.is_subset_of(self.db(), legacy) { - if let Some(builder) = + if !inherited.is_subset_of(self.db(), legacy) + && let Some(builder) = self.context.report_lint(&INVALID_GENERIC_CLASS, class_node) - { - builder.into_diagnostic( - "`Generic` base class must include all type \ - variables used in other base classes", - ); - } + { + builder.into_diagnostic( + "`Generic` base class must include all type \ + variables used in other base classes", + ); } } @@ -1155,16 +1154,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // Annotated assignments are allowed (that's the whole point), but they're // not allowed to have a value. ast::Stmt::AnnAssign(ann_assign) => { - if let Some(value) = &ann_assign.value { - if let Some(builder) = self + if let Some(value) = &ann_assign.value + && let Some(builder) = self .context .report_lint(&INVALID_TYPED_DICT_STATEMENT, &**value) - { - builder.into_diagnostic(format_args!( - "TypedDict item cannot have a value" - )); - } + { + builder.into_diagnostic("TypedDict item cannot have a value"); } + continue; } // Pass statements are allowed. @@ -1832,17 +1829,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } // Fall back to implicit module globals for (possibly) unbound names - if !place_and_quals.place.is_definitely_bound() { - if let PlaceExprRef::Symbol(symbol) = place { - let symbol_id = place_id.expect_symbol(); + if !place_and_quals.place.is_definitely_bound() + && let PlaceExprRef::Symbol(symbol) = place + { + let symbol_id = place_id.expect_symbol(); - if self.skip_non_global_scopes(file_scope_id, symbol_id) - || self.scope.file_scope_id(self.db()).is_global() - { - place_and_quals = place_and_quals.or_fall_back_to(self.db(), || { - module_type_implicit_global_declaration(self.db(), symbol.name()) - }); - } + if self.skip_non_global_scopes(file_scope_id, symbol_id) + || self.scope.file_scope_id(self.db()).is_global() + { + place_and_quals = place_and_quals.or_fall_back_to(self.db(), || { + module_type_implicit_global_declaration(self.db(), symbol.name()) + }); } } @@ -2621,25 +2618,26 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { returns: Option<&ast::Expr>, deferred_expression_state: DeferredExpressionState, ) { - if let Some(returns) = returns { - let annotated = self.infer_annotation_expression(returns, deferred_expression_state); + let Some(returns) = returns else { + return; + }; + let annotated = self.infer_annotation_expression(returns, deferred_expression_state); - if !annotated.qualifiers.is_empty() { - for qualifier in [ - TypeQualifiers::FINAL, - TypeQualifiers::CLASS_VAR, - TypeQualifiers::INIT_VAR, - ] { - if annotated.qualifiers.contains(qualifier) { - if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, returns) - { - builder.into_diagnostic(format!( - "`{name}` is not allowed in function return type annotations", - name = qualifier.name() - )); - } - } - } + if annotated.qualifiers.is_empty() { + return; + } + for qualifier in [ + TypeQualifiers::FINAL, + TypeQualifiers::CLASS_VAR, + TypeQualifiers::INIT_VAR, + ] { + if annotated.qualifiers.contains(qualifier) + && let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, returns) + { + builder.into_diagnostic(format!( + "`{name}` is not allowed in function return type annotations", + name = qualifier.name() + )); } } } @@ -2679,24 +2677,28 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { self.defer_annotations().into(), ); - if let Some(qualifiers) = annotated.map(|annotated| annotated.qualifiers) { - if !qualifiers.is_empty() { - for qualifier in [ - TypeQualifiers::FINAL, - TypeQualifiers::CLASS_VAR, - TypeQualifiers::INIT_VAR, - ] { - if qualifiers.contains(qualifier) { - if let Some(builder) = - self.context.report_lint(&INVALID_TYPE_FORM, parameter) - { - builder.into_diagnostic(format!( - "`{name}` is not allowed in function parameter annotations", - name = qualifier.name() - )); - } - } - } + let Some(annotated) = annotated else { + return; + }; + + let qualifiers = annotated.qualifiers; + + if qualifiers.is_empty() { + return; + } + + for qualifier in [ + TypeQualifiers::FINAL, + TypeQualifiers::CLASS_VAR, + TypeQualifiers::INIT_VAR, + ] { + if qualifiers.contains(qualifier) + && let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, parameter) + { + builder.into_diagnostic(format!( + "`{name}` is not allowed in function parameter annotations", + name = qualifier.name() + )); } } } @@ -4545,15 +4547,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } // If none are valid, emit a diagnostic for the first failing element - if !any_valid { - if let Some(element_ty) = intersection.positive(db).first() { - self.validate_subscript_deletion_impl( - target, - full_object_ty.or(Some(object_ty)), - *element_ty, - slice_ty, - ); - } + if !any_valid && let Some(element_ty) = intersection.positive(db).first() { + self.validate_subscript_deletion_impl( + target, + full_object_ty.or(Some(object_ty)), + *element_ty, + slice_ty, + ); } } @@ -4761,13 +4761,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { }; let emit_invalid_final = |builder: &Self| { - if emit_diagnostics { - if let Some(builder) = builder.context.report_lint(&INVALID_ASSIGNMENT, target) { - builder.into_diagnostic(format_args!( - "Cannot assign to final attribute `{attribute}` on type `{}`", - object_ty.display(db) - )); - } + if emit_diagnostics + && let Some(builder) = builder.context.report_lint(&INVALID_ASSIGNMENT, target) + { + builder.into_diagnostic(format_args!( + "Cannot assign to final attribute `{attribute}` on type `{}`", + object_ty.display(db) + )); } }; @@ -4811,20 +4811,20 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let class_scope_id = class_literal.body_scope(db).file_scope_id(db); let place_table = builder.index.place_table(class_scope_id); - if let Some(symbol) = place_table.symbol_by_name(attribute) { - if symbol.is_bound() { - if emit_diagnostics { - if let Some(diag_builder) = - builder.context.report_lint(&INVALID_ASSIGNMENT, target) - { - diag_builder.into_diagnostic(format_args!( - "Cannot assign to final attribute `{attribute}` in `__init__` \ - because it already has a value at class level" - )); - } - } - return true; + if let Some(symbol) = place_table.symbol_by_name(attribute) + && symbol.is_bound() + { + if emit_diagnostics + && let Some(diag_builder) = + builder.context.report_lint(&INVALID_ASSIGNMENT, target) + { + diag_builder.into_diagnostic(format_args!( + "Cannot assign to final attribute `{attribute}` in `__init__` \ + because it already has a value at class level" + )); } + + return true; } } @@ -4851,16 +4851,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } else { // TODO: This is not a very helpful error message, as it does not include the underlying reason // why the assignment is invalid. This would be a good use case for sub-diagnostics. - if emit_diagnostics { - if let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) - { - builder.into_diagnostic(format_args!( - "Object of type `{}` is not assignable \ + if emit_diagnostics + && let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) + { + builder.into_diagnostic(format_args!( + "Object of type `{}` is not assignable \ to attribute `{attribute}` on type `{}`", - value_ty.display(self.db()), - object_ty.display(self.db()), - )); - } + value_ty.display(self.db()), + object_ty.display(self.db()), + )); } false @@ -4884,18 +4883,18 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { }) { true } else { - if emit_diagnostics { - if let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) - { - // TODO: same here, see above - builder.into_diagnostic(format_args!( - "Object of type `{}` is not assignable \ + if emit_diagnostics + && let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) + { + // TODO: same here, see above + builder.into_diagnostic(format_args!( + "Object of type `{}` is not assignable \ to attribute `{attribute}` on type `{}`", - value_ty.display(self.db()), - object_ty.display(self.db()), - )); - } + value_ty.display(self.db()), + object_ty.display(self.db()), + )); } + false } } @@ -4912,26 +4911,27 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { Type::NominalInstance(instance) if instance.has_known_class(db, KnownClass::Super) => { infer_value_ty(self, TypeContext::default()); - if emit_diagnostics { - if let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) { - builder.into_diagnostic(format_args!( - "Cannot assign to attribute `{attribute}` on type `{}`", - object_ty.display(self.db()), - )); - } + if emit_diagnostics + && let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) + { + builder.into_diagnostic(format_args!( + "Cannot assign to attribute `{attribute}` on type `{}`", + object_ty.display(self.db()), + )); } + false } Type::BoundSuper(_) => { infer_value_ty(self, TypeContext::default()); - if emit_diagnostics { - if let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) { - builder.into_diagnostic(format_args!( - "Cannot assign to attribute `{attribute}` on type `{}`", - object_ty.display(self.db()), - )); - } + if emit_diagnostics + && let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) + { + builder.into_diagnostic(format_args!( + "Cannot assign to attribute `{attribute}` on type `{}`", + object_ty.display(self.db()), + )); } false } @@ -5036,16 +5036,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // Only fall back to `__setattr__` when no explicit attribute is found. match object_ty.class_member(db, attribute.into()) { meta_attr @ PlaceAndQualifiers { .. } if meta_attr.is_class_var() => { - if emit_diagnostics { - if let Some(builder) = + if emit_diagnostics + && let Some(builder) = self.context.report_lint(&INVALID_ATTRIBUTE_ACCESS, target) - { - builder.into_diagnostic(format_args!( - "Cannot assign to ClassVar `{attribute}` \ - from an instance of type `{ty}`", - ty = object_ty.display(self.db()), - )); - } + { + builder.into_diagnostic(format_args!( + "Cannot assign to ClassVar `{attribute}` \ + from an instance of type `{ty}`", + ty = object_ty.display(self.db()), + )); } false } @@ -5075,16 +5074,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { &CallArguments::positional([meta_attr_ty, object_ty, value_ty]), ); - if emit_diagnostics { - if let Err(dunder_set_failure) = dunder_set_result.as_ref() { - report_bad_dunder_set_call( - &self.context, - dunder_set_failure, - attribute, - object_ty, - target, - ); - } + if emit_diagnostics + && let Err(dunder_set_failure) = dunder_set_result.as_ref() + { + report_bad_dunder_set_call( + &self.context, + dunder_set_failure, + attribute, + object_ty, + target, + ); } dunder_set_result.is_ok() @@ -5177,32 +5176,30 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // If __setattr__ succeeded, allow the assignment. Ok(_) | Err(CallDunderError::PossiblyUnbound(_)) => true, Err(CallDunderError::CallError(..)) => { - if emit_diagnostics { - if let Some(builder) = + if emit_diagnostics + && let Some(builder) = self.context.report_lint(&UNRESOLVED_ATTRIBUTE, target) - { - builder.into_diagnostic(format_args!( - "Cannot assign object of type `{}` to attribute \ - `{attribute}` on type `{}` with \ - custom `__setattr__` method.", - value_ty.display(db), - object_ty.display(db) - )); - } + { + builder.into_diagnostic(format_args!( + "Cannot assign object of type `{}` to attribute \ + `{attribute}` on type `{}` with \ + custom `__setattr__` method.", + value_ty.display(db), + object_ty.display(db) + )); } false } Err(CallDunderError::MethodNotAvailable) => { - if emit_diagnostics { - if let Some(builder) = + if emit_diagnostics + && let Some(builder) = self.context.report_lint(&UNRESOLVED_ATTRIBUTE, target) - { - builder.into_diagnostic(format_args!( - "Unresolved attribute `{}` on type `{}`.", - attribute, - object_ty.display(db) - )); - } + { + builder.into_diagnostic(format_args!( + "Unresolved attribute `{}` on type `{}`", + attribute, + object_ty.display(db) + )); } false } @@ -5244,16 +5241,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { &CallArguments::positional([meta_attr_ty, object_ty, value_ty]), ); - if emit_diagnostics { - if let Err(dunder_set_failure) = dunder_set_result.as_ref() { - report_bad_dunder_set_call( - &self.context, - dunder_set_failure, - attribute, - object_ty, - target, - ); - } + if emit_diagnostics + && let Err(dunder_set_failure) = dunder_set_result.as_ref() + { + report_bad_dunder_set_call( + &self.context, + dunder_set_failure, + attribute, + object_ty, + target, + ); } dunder_set_result.is_ok() @@ -5406,16 +5403,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } else { infer_value_ty(self, TypeContext::default()); - if emit_diagnostics { - if let Some(builder) = + if emit_diagnostics + && let Some(builder) = self.context.report_lint(&UNRESOLVED_ATTRIBUTE, target) - { - builder.into_diagnostic(format_args!( - "Unresolved attribute `{}` on type `{}`.", - attribute, - object_ty.display(db) - )); - } + { + builder.into_diagnostic(format_args!( + "Unresolved attribute `{}` on type `{}`.", + attribute, + object_ty.display(db) + )); } false @@ -7283,16 +7279,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { if !annotated.qualifiers.is_empty() { for qualifier in [TypeQualifiers::CLASS_VAR, TypeQualifiers::INIT_VAR] { - if annotated.qualifiers.contains(qualifier) { - if let Some(builder) = self + if annotated.qualifiers.contains(qualifier) + && let Some(builder) = self .context .report_lint(&INVALID_TYPE_FORM, annotation.as_ref()) - { - builder.into_diagnostic(format_args!( - "`{name}` annotations are not allowed for non-name targets", - name = qualifier.name() - )); - } + { + builder.into_diagnostic(format_args!( + "`{name}` annotations are not allowed for non-name targets", + name = qualifier.name() + )); } } } @@ -7336,15 +7331,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let current_scope = self.index.scope(current_scope_id); if current_scope.kind() != ScopeKind::Class { for qualifier in [TypeQualifiers::CLASS_VAR, TypeQualifiers::INIT_VAR] { - if declared.qualifiers.contains(qualifier) { - if let Some(builder) = + if declared.qualifiers.contains(qualifier) + && let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, annotation) - { - builder.into_diagnostic(format_args!( - "`{name}` annotations are only allowed in class-body scopes", - name = qualifier.name() - )); - } + { + builder.into_diagnostic(format_args!( + "`{name}` annotations are only allowed in class-body scopes", + name = qualifier.name() + )); } } } @@ -7383,12 +7377,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let is_pep_613_type_alias = declared.inner_type().is_typealias_special_form(); // Handle various singletons. - if let Some(name_expr) = target.as_name_expr() { - if let Some(special_form) = + if let Some(name_expr) = target.as_name_expr() + && let Some(special_form) = SpecialFormType::try_from_file_and_name(self.db(), self.file(), &name_expr.id) - { - declared.inner = Type::SpecialForm(special_form); - } + { + declared.inner = Type::SpecialForm(special_form); } // If the target of an assignment is not one of the place expressions we support, @@ -9699,10 +9692,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // Simplify the inference based on a non-covariant declared type. if let Some(elt_tcx) = elt_tcx.filter(|_| !elt_tcx_variance[&elt_ty_identity].is_covariant()) + && inferred_elt_ty.is_assignable_to(self.db(), elt_tcx) { - if inferred_elt_ty.is_assignable_to(self.db(), elt_tcx) { - continue; - } + continue; } // Convert any element literals to their promoted type form to avoid excessively large @@ -10318,50 +10310,50 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // Special handling for `TypedDict` method calls if let ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = func.as_ref() { let value_type = self.expression_type(value); - if let Type::TypedDict(typed_dict_ty) = value_type { - if matches!(attr.id.as_str(), "pop" | "setdefault") && !arguments.args.is_empty() { - // Validate the key argument for `TypedDict` methods - if let Some(first_arg) = arguments.args.first() { - if let ast::Expr::StringLiteral(ast::ExprStringLiteral { - value: key_literal, - .. - }) = first_arg - { - let key = key_literal.to_str(); - let items = typed_dict_ty.items(self.db()); - // Check if key exists - if let Some((_, field)) = items - .iter() - .find(|(field_name, _)| field_name.as_str() == key) - { - // Key exists - check if it's a `pop()` on a required field - if attr.id.as_str() == "pop" && field.is_required() { - report_cannot_pop_required_field_on_typed_dict( - &self.context, - first_arg.into(), - Type::TypedDict(typed_dict_ty), - key, - ); - return Type::unknown(); - } - } else { - // Key not found, report error with suggestion and return early - let key_ty = Type::string_literal(self.db(), key); - report_invalid_key_on_typed_dict( - &self.context, - first_arg.into(), - first_arg.into(), - Type::TypedDict(typed_dict_ty), - None, - key_ty, - items, - ); - // Return `Unknown` to prevent the overload system from generating its own error - return Type::unknown(); - } - } + if let Type::TypedDict(typed_dict_ty) = value_type + && matches!(attr.id.as_str(), "pop" | "setdefault") + && !arguments.args.is_empty() + + // Validate the key argument for `TypedDict` methods + && let Some(first_arg) = arguments.args.first() + && let ast::Expr::StringLiteral(ast::ExprStringLiteral { + value: key_literal, + .. + }) = first_arg + { + let key = key_literal.to_str(); + let items = typed_dict_ty.items(self.db()); + + // Check if key exists + if let Some((_, field)) = items + .iter() + .find(|(field_name, _)| field_name.as_str() == key) + { + // Key exists - check if it's a `pop()` on a required field + if attr.id.as_str() == "pop" && field.is_required() { + report_cannot_pop_required_field_on_typed_dict( + &self.context, + first_arg.into(), + Type::TypedDict(typed_dict_ty), + key, + ); + return Type::unknown(); } + } else { + // Key not found, report error with suggestion and return early + let key_ty = Type::string_literal(self.db(), key); + report_invalid_key_on_typed_dict( + &self.context, + first_arg.into(), + first_arg.into(), + Type::TypedDict(typed_dict_ty), + None, + key_ty, + items, + ); + // Return `Unknown` to prevent the overload system from generating its own error + return Type::unknown(); } } } @@ -10538,18 +10530,18 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ); // Validate `TypedDict` constructor calls after argument type inference - if let Some(class_literal) = callable_type.as_class_literal() { - if class_literal.is_typed_dict(self.db()) { - let typed_dict_type = Type::typed_dict(ClassType::NonGeneric(class_literal)); - if let Some(typed_dict) = typed_dict_type.as_typed_dict() { - validate_typed_dict_constructor( - &self.context, - typed_dict, - arguments, - func.as_ref().into(), - |expr| self.expression_type(expr), - ); - } + if let Type::ClassLiteral(class_literal) = callable_type + && class_literal.is_typed_dict(self.db()) + { + let typed_dict_type = Type::typed_dict(ClassType::NonGeneric(class_literal)); + if let Some(typed_dict) = typed_dict_type.as_typed_dict() { + validate_typed_dict_constructor( + &self.context, + typed_dict, + arguments, + func.as_ref().into(), + |expr| self.expression_type(expr), + ); } } @@ -10962,26 +10954,24 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let place = PlaceAndQualifiers::from(local_scope_place).or_fall_back_to(db, || { let mut symbol_resolves_locally = false; - if let Some(symbol) = place_expr.as_symbol() { - if let Some(symbol_id) = place_table.symbol_id(symbol.name()) { - // Footgun: `place_expr` and `symbol` were probably constructed with all-zero - // flags. We need to read the place table to get correct flags. - symbol_resolves_locally = place_table.symbol(symbol_id).is_local(); - // If we try to access a variable in a class before it has been defined, the - // lookup will fall back to global. See the comment on `Symbol::is_local`. - let fallback_to_global = - scope.node(db).scope_kind().is_class() && symbol_resolves_locally; - if self.skip_non_global_scopes(file_scope_id, symbol_id) || fallback_to_global { - return global_symbol(self.db(), self.file(), symbol.name()).map_type( - |ty| { - self.narrow_place_with_applicable_constraints( - place_expr, - ty, - &constraint_keys, - ) - }, - ); - } + if let Some(symbol) = place_expr.as_symbol() + && let Some(symbol_id) = place_table.symbol_id(symbol.name()) + { + // Footgun: `place_expr` and `symbol` were probably constructed with all-zero + // flags. We need to read the place table to get correct flags. + symbol_resolves_locally = place_table.symbol(symbol_id).is_local(); + // If we try to access a variable in a class before it has been defined, the + // lookup will fall back to global. See the comment on `Symbol::is_local`. + let fallback_to_global = + scope.node(db).scope_kind().is_class() && symbol_resolves_locally; + if self.skip_non_global_scopes(file_scope_id, symbol_id) || fallback_to_global { + return global_symbol(self.db(), self.file(), symbol.name()).map_type(|ty| { + self.narrow_place_with_applicable_constraints( + place_expr, + ty, + &constraint_keys, + ) + }); } } @@ -12078,15 +12068,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | (Type::IntLiteral(n), Type::StringLiteral(s), ast::Operator::Mult) => { let ty = if n < 1 { Type::string_literal(self.db(), "") - } else if let Ok(n) = usize::try_from(n) { - if n.checked_mul(s.value(self.db()).len()) + } else if let Ok(n) = usize::try_from(n) + && n.checked_mul(s.value(self.db()).len()) .is_some_and(|new_length| new_length <= Self::MAX_STRING_LITERAL_SIZE) - { - let new_literal = s.value(self.db()).repeat(n); - Type::string_literal(self.db(), &new_literal) - } else { - Type::LiteralString - } + { + let new_literal = s.value(self.db()).repeat(n); + Type::string_literal(self.db(), &new_literal) } else { Type::LiteralString }; @@ -13521,12 +13508,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ))); } Type::SpecialForm(SpecialFormType::Optional) => { - if matches!(**slice, ast::Expr::Tuple(_)) { - if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { - builder.into_diagnostic(format_args!( - "`typing.Optional` requires exactly one argument" - )); - } + if matches!(**slice, ast::Expr::Tuple(_)) + && let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) + { + builder.into_diagnostic(format_args!( + "`typing.Optional` requires exactly one argument" + )); } let ty = self.infer_expression(slice, TypeContext::default()); @@ -13563,14 +13550,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ), )); - if is_empty { - if let Some(builder) = + if is_empty + && let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) - { - builder.into_diagnostic( - "`typing.Union` requires at least one type argument", - ); - } + { + builder.into_diagnostic( + "`typing.Union` requires at least one type argument", + ); } return union_type; @@ -13688,16 +13674,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { .. }) = **slice { - if arguments.len() != 2 { - if let Some(builder) = + if arguments.len() != 2 + && let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) - { - builder.into_diagnostic(format_args!( - "`typing.{}` requires exactly two arguments, got {}", - special_form.name(), - arguments.len() - )); - } + { + builder.into_diagnostic(format_args!( + "`typing.{}` requires exactly two arguments, got {}", + special_form.name(), + arguments.len() + )); } if let [first_expr, second_expr] = &arguments[..] {