[ty] Use let-chains more (#22580)

## Summary

just a little refactor.

Edit: okay, I removed a period at the end of a diagnostic message, which
I guess changes a _lot_ of diagnostic messages.
This commit is contained in:
Alex Waygood
2026-01-14 19:56:07 +00:00
committed by GitHub
parent 7e8eba2572
commit b79e9bac14
4 changed files with 358 additions and 387 deletions

View File

@@ -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"
```

View File

@@ -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()

View File

@@ -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) {

View File

@@ -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[..] {