Skip to content

[ty] Treat declared dataclass fields as instance attributes in own_instance_member#22965

Merged
charliermarsh merged 6 commits intomainfrom
charlie/imp
Jan 30, 2026
Merged

[ty] Treat declared dataclass fields as instance attributes in own_instance_member#22965
charliermarsh merged 6 commits intomainfrom
charlie/imp

Conversation

@charliermarsh
Copy link
Member

@charliermarsh charliermarsh commented Jan 30, 2026

Summary

The PR adds a check in own_instance_member to avoid treating dataclass fields with defaults as Member::unbound().

I've also fixed cases in which KW_ONLY attributes are shadowed, as in:

from dataclasses import dataclass, KW_ONLY

@dataclass
class Parent:
    _: int

@dataclass
class Child(Parent):
    _: KW_ONLY
    name: str

We now model the correct constructor (def __init__(self, _: int, *, name: str) -> None: ...).

Closes astral-sh/ty#2636.

@astral-sh-bot
Copy link

astral-sh-bot bot commented Jan 30, 2026

Typing conformance results

No changes detected ✅

@astral-sh-bot
Copy link

astral-sh-bot bot commented Jan 30, 2026

mypy_primer results

Changes were detected when running on open source projects
pylox (https://github.com/sco1/pylox)
+ pylox/containers/array.py:25:9: error[unresolved-attribute] Object of type `dict[Unknown, Unknown]` has no attribute `append`
+ pylox/containers/array.py:38:9: error[unresolved-attribute] Object of type `dict[Unknown, Unknown]` has no attribute `appendleft`
- pylox/containers/array.py:25:9: warning[possibly-missing-attribute] Attribute `append` may be missing on object of type `dict[Unknown, Unknown] | Unknown | deque[Unknown | None]`
- pylox/containers/array.py:38:9: warning[possibly-missing-attribute] Attribute `appendleft` may be missing on object of type `dict[Unknown, Unknown] | Unknown | deque[Unknown | None]`
- pylox/containers/array.py:69:16: error[no-matching-overload] No overload of bound method `join` matches arguments
- pylox/containers/array.py:99:20: warning[possibly-missing-attribute] Attribute `popleft` may be missing on object of type `dict[Unknown, Unknown] | Unknown | deque[Unknown | None]`
- pylox/containers/array.py:114:9: warning[possibly-missing-attribute] Attribute `reverse` may be missing on object of type `dict[Unknown, Unknown] | Unknown | deque[Unknown | None]`
+ pylox/containers/array.py:99:20: error[unresolved-attribute] Object of type `dict[Unknown, Unknown]` has no attribute `popleft`
+ pylox/containers/array.py:114:9: error[unresolved-attribute] Object of type `dict[Unknown, Unknown]` has no attribute `reverse`
- Found 48 diagnostics
+ Found 47 diagnostics

pwndbg (https://github.com/pwndbg/pwndbg)
- pwndbg/aglib/kernel/__init__.py:264:20: error[unsupported-operator] Operator `+` is not supported between objects of type `None | Unknown` and `Literal[1]`
+ pwndbg/aglib/kernel/__init__.py:264:20: error[unsupported-operator] Operator `+` is not supported between objects of type `Unknown | None` and `Literal[1]`
- pwndbg/aglib/kernel/__init__.py:270:22: error[unsupported-operator] Operator `+` is not supported between objects of type `None | Unknown` and `int`
+ pwndbg/aglib/kernel/__init__.py:270:22: error[unsupported-operator] Operator `+` is not supported between objects of type `Unknown | None` and `int`

prefect (https://github.com/PrefectHQ/prefect)
- src/integrations/prefect-dbt/prefect_dbt/core/settings.py:94:28: error[invalid-assignment] Object of type `dict[Any, Any] | int | dict[str, Any] | ... omitted 4 union elements` is not assignable to `dict[str, Any]`
- src/integrations/prefect-dbt/prefect_dbt/core/settings.py:99:28: error[invalid-assignment] Object of type `int | dict[Any, Any] | float | ... omitted 3 union elements` is not assignable to `dict[str, Any]`
- src/prefect/cli/deploy/_core.py:86:21: error[invalid-assignment] Object of type `dict[Any, Any] | int | dict[str, Any] | ... omitted 4 union elements` is not assignable to `dict[str, Any]`
- src/prefect/cli/deploy/_core.py:87:21: error[invalid-assignment] Object of type `int | dict[Any, Any] | float | ... omitted 3 union elements` is not assignable to `dict[str, Any]`
- src/prefect/deployments/steps/core.py:137:38: error[invalid-argument-type] Argument is incorrect: Argument type `dict[Any, Any] | int | dict[str, Any] | ... omitted 4 union elements` does not satisfy constraints (`str`, `int`, `int | float`, `bool`, `dict[Any, Any]`, `list[Any]`, `None`) of type variable `T`
- src/prefect/flow_engine.py:535:20: error[invalid-return-type] Return type does not match returned value: expected `R@FlowRunEngine | State[Any] | None`, found `(R@FlowRunEngine & ~<class 'NotSet'> & ~Top[State[Any]] & ~Coroutine[object, Never, object]) | (type[NotSet] & ~<class 'NotSet'> & ~Coroutine[object, Never, object]) | Unknown | (PrefectFuture[Unknown] & ~Top[State[Any]] & ~Coroutine[object, Never, object]) | None`
+ src/prefect/flow_engine.py:535:20: error[invalid-return-type] Return type does not match returned value: expected `R@FlowRunEngine | State[Any] | None`, found `(R@FlowRunEngine & ~<class 'NotSet'> & ~Top[State[Any]] & ~Coroutine[object, Never, object]) | (type[NotSet] & ~<class 'NotSet'> & ~Coroutine[object, Never, object]) | Unknown`
- src/prefect/flow_engine.py:539:23: error[invalid-raise] Cannot raise object of type `BaseException | (type[NotSet] & ~<class 'NotSet'>) | (Unknown & ~<class 'NotSet'>)`: Not an instance or subclass of `BaseException`
+ src/prefect/flow_engine.py:539:23: error[invalid-raise] Cannot raise object of type `Exception | (type[NotSet] & ~<class 'NotSet'>)`: Not an instance or subclass of `BaseException`
- src/prefect/flow_engine.py:540:20: error[invalid-return-type] Return type does not match returned value: expected `R@FlowRunEngine | State[Any] | None`, found `BaseException | (type[NotSet] & ~<class 'NotSet'>) | (Unknown & ~<class 'NotSet'>)`
+ src/prefect/flow_engine.py:540:20: error[invalid-return-type] Return type does not match returned value: expected `R@FlowRunEngine | State[Any] | None`, found `Exception | (type[NotSet] & ~<class 'NotSet'>)`
- src/prefect/flow_engine.py:1128:23: error[invalid-raise] Cannot raise object of type `BaseException | (type[NotSet] & ~<class 'NotSet'>) | (Unknown & ~<class 'NotSet'>)`: Not an instance or subclass of `BaseException`
+ src/prefect/flow_engine.py:1128:23: error[invalid-raise] Cannot raise object of type `Exception | (type[NotSet] & ~<class 'NotSet'>)`: Not an instance or subclass of `BaseException`
- src/prefect/flow_engine.py:1129:20: error[invalid-return-type] Return type does not match returned value: expected `R@AsyncFlowRunEngine | State[Any] | None`, found `BaseException | (type[NotSet] & ~<class 'NotSet'>) | (Unknown & ~<class 'NotSet'>)`
+ src/prefect/flow_engine.py:1129:20: error[invalid-return-type] Return type does not match returned value: expected `R@AsyncFlowRunEngine | State[Any] | None`, found `Exception | (type[NotSet] & ~<class 'NotSet'>)`
- src/prefect/task_engine.py:605:20: error[invalid-return-type] Return type does not match returned value: expected `R@SyncTaskRunEngine | State[Any] | None`, found `(R@SyncTaskRunEngine & ~<class 'NotSet'> & ~Top[ResultRecord[Unknown]]) | (type[NotSet] & ~<class 'NotSet'>) | (Unknown & ~<class 'NotSet'> & ~Top[ResultRecord[Unknown]])`
+ src/prefect/task_engine.py:605:20: error[invalid-return-type] Return type does not match returned value: expected `R@SyncTaskRunEngine | State[Any] | None`, found `(R@SyncTaskRunEngine & ~<class 'NotSet'> & ~Top[ResultRecord[Unknown]]) | (type[NotSet] & ~<class 'NotSet'>)`
- src/prefect/task_engine.py:610:23: error[invalid-raise] Cannot raise object of type `BaseException | (type[NotSet] & ~<class 'NotSet'>) | (Unknown & ~<class 'NotSet'>)`: Not an instance or subclass of `BaseException`
+ src/prefect/task_engine.py:610:23: error[invalid-raise] Cannot raise object of type `BaseException | (type[NotSet] & ~<class 'NotSet'>)`: Not an instance or subclass of `BaseException`
- src/prefect/task_engine.py:613:20: error[invalid-return-type] Return type does not match returned value: expected `R@SyncTaskRunEngine | State[Any] | None`, found `BaseException | (type[NotSet] & ~<class 'NotSet'>) | (Unknown & ~<class 'NotSet'>)`
+ src/prefect/task_engine.py:613:20: error[invalid-return-type] Return type does not match returned value: expected `R@SyncTaskRunEngine | State[Any] | None`, found `BaseException | (type[NotSet] & ~<class 'NotSet'>)`
- src/prefect/task_engine.py:1224:20: error[invalid-return-type] Return type does not match returned value: expected `R@AsyncTaskRunEngine | State[Any] | None`, found `(R@AsyncTaskRunEngine & ~<class 'NotSet'> & ~Top[ResultRecord[Unknown]]) | (type[NotSet] & ~<class 'NotSet'>) | (Unknown & ~<class 'NotSet'> & ~Top[ResultRecord[Unknown]])`
+ src/prefect/task_engine.py:1224:20: error[invalid-return-type] Return type does not match returned value: expected `R@AsyncTaskRunEngine | State[Any] | None`, found `(R@AsyncTaskRunEngine & ~<class 'NotSet'> & ~Top[ResultRecord[Unknown]]) | (type[NotSet] & ~<class 'NotSet'>)`
- src/prefect/task_engine.py:1229:23: error[invalid-raise] Cannot raise object of type `BaseException | (type[NotSet] & ~<class 'NotSet'>) | (Unknown & ~<class 'NotSet'>)`: Not an instance or subclass of `BaseException`
+ src/prefect/task_engine.py:1229:23: error[invalid-raise] Cannot raise object of type `BaseException | (type[NotSet] & ~<class 'NotSet'>)`: Not an instance or subclass of `BaseException`
- src/prefect/task_engine.py:1232:20: error[invalid-return-type] Return type does not match returned value: expected `R@AsyncTaskRunEngine | State[Any] | None`, found `BaseException | (type[NotSet] & ~<class 'NotSet'>) | (Unknown & ~<class 'NotSet'>)`
+ src/prefect/task_engine.py:1232:20: error[invalid-return-type] Return type does not match returned value: expected `R@AsyncTaskRunEngine | State[Any] | None`, found `BaseException | (type[NotSet] & ~<class 'NotSet'>)`
- src/prefect/utilities/templating.py:320:13: error[invalid-assignment] Invalid subscript assignment with key of type `object` and value of type `Unknown | int | dict[str, Any] | ... omitted 4 union elements` on object of type `dict[str, Any]`
+ src/prefect/utilities/templating.py:320:13: error[invalid-assignment] Invalid subscript assignment with key of type `object` and value of type `Unknown | dict[str, Any]` on object of type `dict[str, Any]`
- src/prefect/utilities/templating.py:323:16: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_block_document_references | dict[str, Any]`, found `list[Unknown | int | dict[str, Any] | ... omitted 4 union elements]`
+ src/prefect/utilities/templating.py:323:16: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_block_document_references | dict[str, Any]`, found `list[Unknown | dict[str, Any]]`
- src/prefect/utilities/templating.py:437:16: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_variables`, found `dict[object, Unknown | int | float | ... omitted 4 union elements]`
+ src/prefect/utilities/templating.py:437:16: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_variables`, found `dict[object, Unknown]`
- src/prefect/utilities/templating.py:442:16: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_variables`, found `list[Unknown | int | float | ... omitted 4 union elements]`
+ src/prefect/utilities/templating.py:442:16: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_variables`, found `list[Unknown]`
+ src/prefect/workers/base.py:232:13: error[invalid-argument-type] Argument is incorrect: Argument type `str | dict[str, Any]` does not satisfy constraints (`str`, `int`, `int | float`, `bool`, `dict[Any, Any]`, `list[Any]`, `None`) of type variable `T`
- src/prefect/workers/base.py:232:13: error[invalid-argument-type] Argument is incorrect: Argument type `str | int | dict[str, Any] | ... omitted 3 union elements` does not satisfy constraints (`str`, `int`, `int | float`, `bool`, `dict[Any, Any]`, `list[Any]`, `None`) of type variable `T`
- src/prefect/workers/base.py:234:22: error[invalid-argument-type] Argument expression after ** must be a mapping type: Found `int | Unknown | float | ... omitted 4 union elements`
- Found 5375 diagnostics
+ Found 5369 diagnostics

egglog-python (https://github.com/egraphs-good/egglog-python)
+ python/egglog/runtime.py:745:9: error[invalid-assignment] Object of type `() -> Declarations` is not assignable to attribute `__egg_decls_thunk__` of type `() -> Declarations`
- Found 1483 diagnostics
+ Found 1484 diagnostics

core (https://github.com/home-assistant/core)
- homeassistant/util/variance.py:47:12: error[invalid-return-type] Return type does not match returned value: expected `(**_P@ignore_variance) -> _R@ignore_variance`, found `_Wrapped[_P@ignore_variance, int | _R@ignore_variance | float | datetime, _P@ignore_variance, _R@ignore_variance | int | float | datetime]`
- Found 14516 diagnostics
+ Found 14515 diagnostics

Memory usage changes were detected when running on open source projects
trio (https://github.com/python-trio/trio)
-     memo metadata = ~31MB
+     memo metadata = ~33MB

sphinx (https://github.com/sphinx-doc/sphinx)
- TOTAL MEMORY USAGE: ~287MB
+ TOTAL MEMORY USAGE: ~301MB

@charliermarsh charliermarsh changed the title [ty] Treat declared dataclass fields as implicitly defined attributes [ty] Treat declared dataclass fields as instance attributes in own_instance_member Jan 30, 2026
@charliermarsh charliermarsh added the ty Multi-file analysis & type inference label Jan 30, 2026
@charliermarsh charliermarsh marked this pull request as ready for review January 30, 2026 02:34
Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, this looks good

return None;
}

let fields = self.own_fields(db, None, field_policy);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The own_fields() call here is probably somewhat expensive, since it isn't a Salsa-tracked method (ClassType::fields() is currently, but not ClassType::own_fields()). This may be the cause of the 2% perf regression on the tanjun benchmark on Codspeed. We could consider adding Salsa caching to that method and see if it gets rid of the regression -- though the regression is small enough that I don't think it should be blocking; we could try adding Salsa caching as a separate, standalone change.

}
)
.then_some(field.clone())
.filter(|field| !field.is_kw_only_sentinel(db))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thsi looks correct, but no tests fail if I make this change

Suggested change
.filter(|field| !field.is_kw_only_sentinel(db))

is it possible to improve the test coverage here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Looking at this.)

@charliermarsh charliermarsh marked this pull request as draft January 30, 2026 14:19
@charliermarsh charliermarsh marked this pull request as ready for review January 30, 2026 17:51
Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

@charliermarsh charliermarsh merged commit c2f7cef into main Jan 30, 2026
48 checks passed
@charliermarsh charliermarsh deleted the charlie/imp branch January 30, 2026 18:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Type inference error in frozen dataclasses inheritance

2 participants