Skip to content

[ty] Disallow Self in metaclass and static methods#22755

Closed
charliermarsh wants to merge 1 commit intomainfrom
charlie/self2
Closed

[ty] Disallow Self in metaclass and static methods#22755
charliermarsh wants to merge 1 commit intomainfrom
charlie/self2

Conversation

@charliermarsh
Copy link
Copy Markdown
Member

Summary

Closes astral-sh/ty#1897.

@charliermarsh charliermarsh added the ty Multi-file analysis & type inference label Jan 20, 2026
@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot bot commented Jan 20, 2026

Typing conformance results improved 🎉

The percentage of diagnostics emitted that were expected errors increased from 83.14% to 83.23%. The percentage of expected errors that received a diagnostic increased from 74.24% to 74.63%.

Summary

Metric Old New Diff Outcome
True Positives 804 809 +5 ⏫ (✅)
False Positives 163 163 +0
False Negatives 279 275 -4 ⏬ (✅)
Total Diagnostics 967 972 +5
Precision 83.14% 83.23% +0.09% ⏫ (✅)
Recall 74.24% 74.63% +0.39% ⏫ (✅)

True positives added

Details
Location Name Message
generics_self_usage.py:113:19 invalid-type-form Self cannot be used in a static method
generics_self_usage.py:118:31
generics_self_usage.py:118:40
invalid-type-form
invalid-type-form
Self cannot be used in a static method
Self cannot be used in a static method
generics_self_usage.py:123:37 invalid-type-form Self cannot be used in a metaclass
generics_self_usage.py:127:42 invalid-type-form Self cannot be used in a metaclass

@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot bot commented Jan 20, 2026

mypy_primer results

Changes were detected when running on open source projects
porcupine (https://github.com/Akuli/porcupine)
+ porcupine/pluginmanager.py:133:49: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[Never]`, found `Unknown | str`
- Found 24 diagnostics
+ Found 25 diagnostics

setuptools (https://github.com/pypa/setuptools)
- setuptools/_distutils/command/install.py:719:42: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[Never]`, found `map[str]`
- Found 1127 diagnostics
+ Found 1126 diagnostics

prefect (https://github.com/PrefectHQ/prefect)
- src/prefect/deployments/runner.py:1017:70: warning[possibly-missing-attribute] Attribute `__name__` may be missing on object of type `Unknown | ((...) -> Any)`
+ src/prefect/deployments/runner.py:1017:70: warning[possibly-missing-attribute] Attribute `__name__` may be missing on object of type `Unknown | (((...) -> Any) & ((*args: object, **kwargs: object) -> object))`
- src/prefect/flow_engine.py:1004:32: error[invalid-await] `Unknown | R@FlowRunEngine | Coroutine[Any, Any, R@FlowRunEngine]` is not awaitable
- src/prefect/flow_engine.py:1610:24: error[invalid-await] `Unknown | R@AsyncFlowRunEngine | Coroutine[Any, Any, R@AsyncFlowRunEngine]` is not awaitable
- src/prefect/flow_engine.py:1691:43: error[invalid-argument-type] Argument to function `next` is incorrect: Expected `SupportsNext[Unknown]`, found `Unknown | R@run_generator_flow_sync`
- src/prefect/flow_engine.py:1699:21: warning[possibly-missing-attribute] Attribute `throw` may be missing on object of type `Unknown | R@run_generator_flow_sync`
- src/prefect/flow_engine.py:1733:44: warning[possibly-missing-attribute] Attribute `__anext__` may be missing on object of type `Unknown | R@run_generator_flow_async`
- src/prefect/flow_engine.py:1740:25: warning[possibly-missing-attribute] Attribute `throw` may be missing on object of type `Unknown | R@run_generator_flow_async`
- src/prefect/flows.py:286:34: error[unresolved-attribute] Object of type `(**P@Flow) -> R@Flow` has no attribute `__name__`
+ src/prefect/flows.py:286:34: error[unresolved-attribute] Object of type `((**P@Flow) -> R@Flow) & ((*args: object, **kwargs: object) -> object)` has no attribute `__name__`
- src/prefect/flows.py:404:68: error[unresolved-attribute] Object of type `(**P@Flow) -> R@Flow` has no attribute `__name__`
+ src/prefect/flows.py:404:68: error[unresolved-attribute] Object of type `((**P@Flow) -> R@Flow) & ((*args: object, **kwargs: object) -> object)` has no attribute `__name__`
- src/prefect/flows.py:1946:21: error[no-matching-overload] No overload of function `run_coro_as_sync` matches arguments
+ src/prefect/flows.py:1886:53: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- Found 5463 diagnostics
+ Found 5457 diagnostics

scikit-build-core (https://github.com/scikit-build/scikit-build-core)
+ src/scikit_build_core/build/wheel.py:99:20: error[no-matching-overload] No overload of bound method `__init__` matches arguments
- Found 49 diagnostics
+ Found 50 diagnostics

@charliermarsh
Copy link
Copy Markdown
Member Author

(Needs refinement, not ready for review!)

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Jan 20, 2026

Merging this PR will not alter performance

✅ 24 untouched benchmarks
⏩ 30 skipped benchmarks1


Comparing charlie/self2 (37d02cb) with main (c685810)

Open in CodSpeed

Footnotes

  1. 30 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@charliermarsh charliermarsh force-pushed the charlie/self2 branch 2 times, most recently from 5d54ca3 to 3612413 Compare January 20, 2026 15:38
@charliermarsh charliermarsh marked this pull request as ready for review January 20, 2026 15:54
@charliermarsh charliermarsh marked this pull request as draft January 20, 2026 16:59
@charliermarsh charliermarsh marked this pull request as ready for review January 20, 2026 19:15
@charliermarsh charliermarsh marked this pull request as draft January 21, 2026 03:35
@charliermarsh charliermarsh marked this pull request as ready for review January 21, 2026 04:12
///
/// This is a cheap check that doesn't trigger type inference. A class with no
/// explicit bases only inherits from `object`.
pub(super) fn has_explicit_bases(self, db: &'db dyn Db) -> bool {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Because this method directly accessed the AST, it needs to be Salsa-tracked to avoid overly eager cache invalidation if it's accessed while inferring types for a different module to the one the class is defined in. But we try not to have too many Salsa-tracked functions/methods, because they increase our memory-usage, so it may be better to use the existing explicit_bases method unless this buys us a lot in terms of performance

pass
```

## `__new__` is not a static method
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The behaviour demonstrated in this test is correct, but the prose describing it seems pretty confused. __new__ is a staticmethod, even if isn't decorated with @staticmethod. But using Self in __new__ is allowed, partly because of the fact that at runtime it is heavily special-cased by the interpreter and behaves in many ways more like you'd usually expect a classmethod to behave. It always receives a cls argument with type type[Self] first, and it often returns an object of type Self.

}

/// Check if a function definition is decorated with `@staticmethod`.
pub(crate) fn is_function_staticmethod<'db>(db: &'db dyn Db, definition: Definition<'db>) -> bool {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Uff, I think we already have 3 methods for determining if a function is a staticmethod — I'd prefer not to add a fourth 😄

@charliermarsh charliermarsh force-pushed the charlie/self2 branch 3 times, most recently from 65ed42e to 443853e Compare January 21, 2026 14:51
@charliermarsh charliermarsh marked this pull request as draft January 21, 2026 15:02
@charliermarsh charliermarsh marked this pull request as ready for review January 21, 2026 16:01
@charliermarsh charliermarsh marked this pull request as draft January 21, 2026 16:16
@charliermarsh charliermarsh force-pushed the charlie/self2 branch 3 times, most recently from bfcdb75 to fbadee6 Compare January 21, 2026 23:00
@charliermarsh charliermarsh marked this pull request as draft January 24, 2026 18:06
@charliermarsh charliermarsh force-pushed the charlie/self2 branch 7 times, most recently from 72fbc2e to fea3526 Compare January 29, 2026 21:41
@charliermarsh charliermarsh marked this pull request as ready for review January 29, 2026 21:41
@charliermarsh
Copy link
Copy Markdown
Member Author

I re-did this to track state during traversal rather than re-detecting static methods et al after the fact, because I had a lot of trouble shaking the regression on Altair. I suspect people will have qualms with the approach, open to feedback 😭

@charliermarsh charliermarsh marked this pull request as draft January 29, 2026 22:01
@charliermarsh charliermarsh force-pushed the charlie/self2 branch 2 times, most recently from 6867e2c to f7d022d Compare January 30, 2026 00:03
@charliermarsh charliermarsh marked this pull request as ready for review January 30, 2026 00:18
@charliermarsh charliermarsh marked this pull request as draft January 30, 2026 00:19
@charliermarsh charliermarsh marked this pull request as ready for review February 10, 2026 21:52
@charliermarsh charliermarsh marked this pull request as draft February 10, 2026 21:56
@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot bot commented Feb 10, 2026

Memory usage report

Summary

Project Old New Diff Outcome
prefect 679.32MB 683.99MB +0.7% (4.67MB)
sphinx 270.11MB 271.50MB +0.5% (1.39MB)
trio 118.31MB 118.58MB +0.2% (273.00kB)
flake8 49.45MB 49.53MB +0.2% (78.55kB)

Significant changes

Click to expand detailed breakdown

prefect

Name Old New Diff Outcome
infer_definition_types 82.75MB 84.72MB +2.4% (1.97MB)
infer_expression_types_impl 50.97MB 52.16MB +2.3% (1.19MB)
infer_scope_types_impl 57.19MB 57.91MB +1.2% (731.62kB)
infer_deferred_types 14.29MB 14.74MB +3.1% (457.31kB)
StaticClassLiteral<'db>::is_metaclass_ 0.00B 335.51kB +335.51kB (new)
infer_expression_type_impl 10.30MB 10.30MB +0.1% (9.26kB)
StaticClassLiteral<'db>::implicit_attribute_inner_ 9.79MB 9.80MB +0.0% (4.25kB)
Type<'db>::member_lookup_with_policy_ 15.25MB 15.25MB +0.0% (3.50kB)
all_narrowing_constraints_for_expression 3.48MB 3.48MB +0.0% (1.29kB)
all_negative_narrowing_constraints_for_expression 1.51MB 1.51MB +0.1% (804.00B)
Type<'db>::class_member_with_policy_ 16.87MB 16.87MB +0.0% (456.00B)
GenericAlias<'db>::variance_of_ 545.19kB 545.50kB +0.1% (312.00B)
FunctionType<'db>::signature_ 4.07MB 4.07MB +0.0% (180.00B)
cached_protocol_interface 523.76kB 523.90kB +0.0% (144.00B)
infer_unpack_types 778.97kB 779.11kB +0.0% (144.00B)
... 6 more

sphinx

Name Old New Diff Outcome
infer_definition_types 23.34MB 23.90MB +2.4% (571.17kB)
infer_expression_types_impl 21.26MB 21.72MB +2.2% (473.71kB)
infer_scope_types_impl 17.47MB 17.60MB +0.8% (140.39kB)
infer_deferred_types 5.82MB 5.95MB +2.2% (131.16kB)
StaticClassLiteral<'db>::is_metaclass_ 0.00B 96.77kB +96.77kB (new)
infer_expression_type_impl 2.23MB 2.23MB +0.1% (2.37kB)
Type<'db>::member_lookup_with_policy_ 6.48MB 6.48MB +0.0% (1.31kB)
StaticClassLiteral<'db>::implicit_attribute_inner_ 2.53MB 2.54MB +0.0% (816.00B)
all_narrowing_constraints_for_expression 1.07MB 1.07MB +0.0% (360.00B)
StaticClassLiteral<'db>::try_mro_ 2.12MB 2.12MB +0.0% (60.00B)
StaticClassLiteral<'db>::is_typed_dict_ 182.19kB 182.25kB +0.0% (60.00B)
all_negative_narrowing_constraints_for_expression 649.64kB 649.68kB +0.0% (48.00B)
cached_protocol_interface 200.14kB 200.17kB +0.0% (24.00B)
is_equivalent_to_object_inner 105.09kB 105.11kB +0.0% (24.00B)
StaticClassLiteral<'db>::try_metaclass_ 351.58kB 351.59kB +0.0% (12.00B)
... 1 more

trio

Name Old New Diff Outcome
infer_definition_types 7.19MB 7.30MB +1.5% (107.24kB)
infer_expression_types_impl 5.60MB 5.67MB +1.1% (64.31kB)
infer_deferred_types 2.44MB 2.48MB +1.5% (36.30kB)
StaticClassLiteral<'db>::is_metaclass_ 0.00B 33.62kB +33.62kB (new)
infer_scope_types_impl 5.56MB 5.59MB +0.5% (28.75kB)
infer_expression_type_impl 709.86kB 711.16kB +0.2% (1.30kB)
Type<'db>::member_lookup_with_policy_ 1.75MB 1.75MB +0.0% (408.00B)
StaticClassLiteral<'db>::implicit_attribute_inner_ 816.06kB 816.40kB +0.0% (348.00B)
cached_protocol_interface 153.93kB 154.16kB +0.2% (240.00B)
is_equivalent_to_object_inner 66.01kB 66.24kB +0.4% (240.00B)
all_narrowing_constraints_for_expression 251.89kB 252.06kB +0.1% (168.00B)
all_negative_narrowing_constraints_for_expression 111.44kB 111.51kB +0.1% (72.00B)
infer_unpack_types 163.69kB 163.71kB +0.0% (24.00B)
FunctionType<'db>::signature_ 1.11MB 1.11MB +0.0% (12.00B)

flake8

Name Old New Diff Outcome
infer_definition_types 1.85MB 1.88MB +1.5% (28.23kB)
infer_expression_types_impl 1.06MB 1.08MB +1.8% (19.96kB)
StaticClassLiteral<'db>::is_metaclass_ 0.00B 11.19kB +11.19kB (new)
infer_deferred_types 717.88kB 728.28kB +1.4% (10.39kB)
infer_scope_types_impl 1.09MB 1.10MB +0.7% (8.29kB)
infer_expression_type_impl 119.70kB 119.89kB +0.2% (192.00B)
StaticClassLiteral<'db>::implicit_attribute_inner_ 313.54kB 313.66kB +0.0% (120.00B)
Type<'db>::member_lookup_with_policy_ 422.87kB 422.99kB +0.0% (120.00B)
cached_protocol_interface 40.96kB 40.98kB +0.1% (24.00B)
is_equivalent_to_object_inner 23.33kB 23.35kB +0.1% (24.00B)
all_narrowing_constraints_for_expression 37.64kB 37.66kB +0.1% (24.00B)

@charliermarsh charliermarsh marked this pull request as ready for review February 11, 2026 00:35
@sharkdp sharkdp removed their request for review February 11, 2026 08:39
@charliermarsh charliermarsh marked this pull request as draft February 11, 2026 16:01
@charliermarsh
Copy link
Copy Markdown
Member Author

Closing in favor of #23231 which is simpler.

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.

typing.Self in staticmethods and metaclasses should be rejected

2 participants