[ty] Infer class attributes assigned by metaclass initialization#25342
Conversation
Typing conformance resultsNo changes detected ✅Current numbersThe percentage of diagnostics emitted that were expected errors held steady at 89.70%. The percentage of expected errors that received a diagnostic held steady at 86.99%. The number of fully passing files held steady at 91/134. |
Memory usage reportSummary
Significant changesClick to expand detailed breakdownprefect
sphinx
trio
flake8
|
|
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
invalid-assignment |
0 | 0 | 1 |
| Total | 0 | 0 | 1 |
Raw diff:
core (https://github.com/home-assistant/core)
- homeassistant/components/bluetooth/passive_update_processor.py:97:30 error[invalid-assignment] Object of type `object` is not assignable to `type[EntityDescription]`
+ homeassistant/components/bluetooth/passive_update_processor.py:97:30 error[invalid-assignment] Object of type `type` is not assignable to `type[EntityDescription]`| self.extend_with_class_members(db, ty, metaclass.class_literal(db)); | ||
| if let Some((metaclass, _)) = metaclass.static_class_literal(db) { | ||
| self.extend_with_instance_members(db, ty, metaclass); | ||
| } |
There was a problem hiding this comment.
This here adds instance members defined during metaclass initialization, like:
class Meta(type):
def __init__(cls, ...):
cls.generated = 1(Which wasn't happening before.)
d9067e7 to
59ca3d2
Compare
Merging this PR will not alter performance
Comparing Footnotes
|
| ); | ||
|
|
||
| if let Some(metaclass_instance) = self.to_meta_type(db).to_instance(db) { | ||
| let metaclass_attr = metaclass_instance.instance_member(db, name); |
There was a problem hiding this comment.
Hm, interesting. I guess I never thought about it that way, but I guess it all makes sense: this works because the cls in the metaclasses' __init__ is recognized as the self attribute.
class Meta(type):
def __init__(cls, name: str, bases: tuple[type, ...], namespace: dict[str, object]) -> None:
cls.attr: int = 1| .. | ||
| }) | ||
| ) { | ||
| class_attr.or_fall_back_to(db, || metaclass_attr) |
There was a problem hiding this comment.
Can the fallback ever be relevant here if we match on Place::Defined above? If not, can we simplify this by skipping the metaclass instance member lookup entirely?
There was a problem hiding this comment.
I think it is possible to be Place::Defined but Definedness::PossiblyUndefined, as in;
class Meta(type):
def __init__(cls, ...) -> None:
cls.attr: int | str = 1
def _(flag: bool):
class C(metaclass=Meta):
if flag:
attr: str = "class value"
reveal_type(C.attr) # int | str| metaclass_attr.or_fall_back_to(db, || class_attr) | ||
| } | ||
| } else { | ||
| class_attr |
There was a problem hiding this comment.
What if the metaclass_attr is possibly defined? Shouldn't we create a union type in that case?
53f9e7f to
fa21e9f
Compare
fa21e9f to
64433d5
Compare
64433d5 to
69ac292
Compare
…ral-sh#25342) ## Summary Prior to this change, we didn't recognize class-object attributes populated by a metaclass during class initialization. For example: ```python class Meta(type): def __init__(cls, name: str, bases: tuple[type, ...], namespace: dict[str, object]) -> None: cls.attr: int = 1 class C(metaclass=Meta): ... reveal_type(C.attr) # revealed: int ``` We now recognize attributes that a metaclass adds to a class while creating it. If the metaclass overwrites an existing class-body value, we use the overwritten value's type. If the class provides an annotation for the generated attribute, we preserve that annotation as its public type. For example, if the class body initially gives attr a value, the metaclass assignment happens later at runtime and overwrites it: ```python class Meta(type): def __init__(cls, name, bases, namespace): cls.attr: int = 1 class C(metaclass=Meta): attr = "initial value" reveal_type(C.attr) # int ``` Closes astral-sh/ty#1138.
Summary
Prior to this change, we didn't recognize class-object attributes populated by a metaclass during class initialization. For example:
We now recognize attributes that a metaclass adds to a class while creating it. If the metaclass overwrites an existing class-body value, we use the overwritten value's type. If the class provides an annotation for the generated attribute, we preserve that annotation as its public type.
For example, if the class body initially gives attr a value, the metaclass assignment happens later at runtime and overwrites it:
Closes astral-sh/ty#1138.