[ty] Fix attribute writes to unions/intersections including modules (#18313)

## Summary

Fix a bug that involved writes to attributes on union/intersection types
that included modules as elements.

This is a prerequisite to avoid some ecosystem false positives in
https://github.com/astral-sh/ruff/pull/18312

## Test Plan

Added regression test
This commit is contained in:
David Peter 2025-05-26 11:41:03 +02:00 committed by GitHub
parent fbaf826a9d
commit 7eca6f96e3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 53 additions and 16 deletions

View file

@ -1698,6 +1698,36 @@ reveal_type(outer.nested.inner.Outer.Nested.Inner.attr) # revealed: int
outer.nested.inner.Outer.Nested.Inner.attr = "a"
```
### Unions of module attributes
`mod1.py`:
```py
global_symbol: str = "a"
```
`mod2.py`:
```py
global_symbol: str = "a"
```
```py
import mod1
import mod2
def _(flag: bool):
if flag:
mod = mod1
else:
mod = mod2
mod.global_symbol = "b"
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to attribute `global_symbol` on type `<module 'mod1'> | <module 'mod2'>`"
mod.global_symbol = 1
```
## Literal types
### Function-literal attributes

View file

@ -3406,24 +3406,31 @@ impl<'db> TypeInferenceBuilder<'db> {
Type::ModuleLiteral(module) => {
if let Symbol::Type(attr_ty, _) = module.static_member(db, attribute) {
let assignable = value_ty.is_assignable_to(db, attr_ty);
if !assignable {
report_invalid_attribute_assignment(
&self.context,
target.into(),
attr_ty,
value_ty,
attribute,
);
if assignable {
true
} else {
if emit_diagnostics {
report_invalid_attribute_assignment(
&self.context,
target.into(),
attr_ty,
value_ty,
attribute,
);
}
false
}
false
} else {
if let Some(builder) = self.context.report_lint(&UNRESOLVED_ATTRIBUTE, target) {
builder.into_diagnostic(format_args!(
"Unresolved attribute `{}` on type `{}`.",
attribute,
object_ty.display(db)
));
if emit_diagnostics {
if let Some(builder) =
self.context.report_lint(&UNRESOLVED_ATTRIBUTE, target)
{
builder.into_diagnostic(format_args!(
"Unresolved attribute `{}` on type `{}`.",
attribute,
object_ty.display(db)
));
}
}
false