Collect all attributes declared in the dataclass and its parents. All assignments of the form a: SomeType b: SomeOtherType = ... are collected. Return None if some dataclass base class hasn't been processed yet and thus we'll need to ask for an
(self)
| 511 | yield from self._get_assignment_statements_from_if_statement(stmt) |
| 512 | |
| 513 | def collect_attributes(self) -> list[DataclassAttribute] | None: |
| 514 | """Collect all attributes declared in the dataclass and its parents. |
| 515 | |
| 516 | All assignments of the form |
| 517 | |
| 518 | a: SomeType |
| 519 | b: SomeOtherType = ... |
| 520 | |
| 521 | are collected. |
| 522 | |
| 523 | Return None if some dataclass base class hasn't been processed |
| 524 | yet and thus we'll need to ask for another pass. |
| 525 | """ |
| 526 | cls = self._cls |
| 527 | |
| 528 | # First, collect attributes belonging to any class in the MRO, ignoring duplicates. |
| 529 | # |
| 530 | # We iterate through the MRO in reverse because attrs defined in the parent must appear |
| 531 | # earlier in the attributes list than attrs defined in the child. See: |
| 532 | # https://docs.python.org/3/library/dataclasses.html#inheritance |
| 533 | # |
| 534 | # However, we also want attributes defined in the subtype to override ones defined |
| 535 | # in the parent. We can implement this via a dict without disrupting the attr order |
| 536 | # because dicts preserve insertion order in Python 3.7+. |
| 537 | found_attrs: dict[str, DataclassAttribute] = {} |
| 538 | for info in reversed(cls.info.mro[1:-1]): |
| 539 | if "dataclass_tag" in info.metadata and "dataclass" not in info.metadata: |
| 540 | # We haven't processed the base class yet. Need another pass. |
| 541 | return None |
| 542 | if "dataclass" not in info.metadata: |
| 543 | continue |
| 544 | |
| 545 | # Each class depends on the set of attributes in its dataclass ancestors. |
| 546 | self._api.add_plugin_dependency(make_wildcard_trigger(info.fullname)) |
| 547 | |
| 548 | for data in info.metadata["dataclass"]["attributes"]: |
| 549 | name: str = data["name"] |
| 550 | |
| 551 | attr = DataclassAttribute.deserialize(info, data, self._api) |
| 552 | # TODO: We shouldn't be performing type operations during the main |
| 553 | # semantic analysis pass, since some TypeInfo attributes might |
| 554 | # still be in flux. This should be performed in a later phase. |
| 555 | attr.expand_typevar_from_subtype(cls.info) |
| 556 | found_attrs[name] = attr |
| 557 | |
| 558 | sym_node = cls.info.names.get(name) |
| 559 | if sym_node and sym_node.node and not isinstance(sym_node.node, Var): |
| 560 | self._api.fail( |
| 561 | "Dataclass attribute may only be overridden by another attribute", |
| 562 | sym_node.node, |
| 563 | ) |
| 564 | |
| 565 | # Second, collect attributes belonging to the current class. |
| 566 | current_attr_names: set[str] = set() |
| 567 | kw_only = self._get_bool_arg("kw_only", self._spec.kw_only_default) |
| 568 | for stmt in self._get_assignment_statements_from_block(cls.defs): |
| 569 | # Any assignment that doesn't use the new type declaration |
| 570 | # syntax can be ignored out of hand. |
no test coverage detected