Skip to content

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

Merged
charliermarsh merged 6 commits intomainfrom
charlie/self3
Mar 25, 2026
Merged

[ty] Disallow Self in metaclass and static methods#23231
charliermarsh merged 6 commits intomainfrom
charlie/self3

Conversation

@charliermarsh
Copy link
Copy Markdown
Member

@charliermarsh charliermarsh commented Feb 11, 2026

Summary

Closes astral-sh/ty#1897.

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

astral-sh-bot bot commented Feb 11, 2026

Typing conformance results improved 🎉

The percentage of diagnostics emitted that were expected errors increased from 86.32% to 86.38%. The percentage of expected errors that received a diagnostic increased from 80.30% to 80.68%. The number of fully passing files held steady at 67/133.

Summary

How are test cases classified?

Each test case represents one expected error annotation or a group of annotations sharing a tag. Counts are per test case, not per diagnostic — multiple diagnostics on the same line count as one. Required annotations (E) are true positives when ty flags the expected location and false negatives when it does not. Optional annotations (E?) are true positives when flagged but true negatives (not false negatives) when not. Tagged annotations (E[tag]) require ty to flag exactly one of the tagged lines; tagged multi-annotations (E[tag+]) allow any number up to the tag count. Flagging unexpected locations counts as a false positive.

Metric Old New Diff Outcome
True Positives 852 856 +4 ⏫ (✅)
False Positives 135 135 +0
False Negatives 209 205 -4 ⏬ (✅)
Total Diagnostics 1045 1050 +5
Precision 86.32% 86.38% +0.06% ⏫ (✅)
Recall 80.30% 80.68% +0.38% ⏫ (✅)
Passing Files 67/133 67/133 +0

Test file breakdown

1 file altered
File True Positives False Positives False Negatives Status
generics_self_usage.py 10 (+4) ✅ 0 1 (-4) ✅ 📈 Improving
Total (all files) 856 (+4) ✅ 135 205 (-4) ✅ 67/133

True positives added (4)

4 diagnostics
Test case Diff

generics_self_usage.py:113

+error[invalid-type-form] `Self` cannot be used in a static method

generics_self_usage.py:118

+error[invalid-type-form] `Self` cannot be used in a static method
+error[invalid-type-form] `Self` cannot be used in a static method

generics_self_usage.py:123

+error[invalid-type-form] `Self` cannot be used in a metaclass

generics_self_usage.py:127

+error[invalid-type-form] `Self` cannot be used in a metaclass

@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot bot commented Feb 11, 2026

mypy_primer results

Changes were detected when running on open source projects
prefect (https://github.com/PrefectHQ/prefect)
- src/prefect/deployments/runner.py:1017:70: error[unresolved-attribute] Attribute `__name__` is not defined on `(...) -> Any` in union `Unknown | ((...) -> Any)`
+ src/prefect/deployments/runner.py:1017:70: error[unresolved-attribute] Attribute `__name__` is not defined on `((...) -> Any) & ((*args: object, **kwargs: object) -> object)` in union `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/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__`
- Found 5888 diagnostics
+ Found 5886 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 58 diagnostics
+ Found 57 diagnostics

@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot bot commented Feb 11, 2026

Memory usage report

Summary

Project Old New Diff Outcome
prefect 704.59MB 711.72MB +1.01% (7.13MB)
sphinx 262.64MB 264.85MB +0.84% (2.21MB)
trio 115.82MB 116.70MB +0.76% (901.37kB)
flake8 48.13MB 48.41MB +0.58% (287.55kB)

Significant changes

Click to expand detailed breakdown

prefect

Name Old New Diff Outcome
function_known_decorators 0.00B 8.07MB +8.07MB (new)
infer_definition_types 90.61MB 89.62MB -1.09% (1013.61kB)
infer_deferred_types 14.57MB 14.60MB +0.21% (32.03kB)
infer_expression_types_impl 55.93MB 55.93MB +0.01% (6.05kB)
infer_expression_type_impl 13.91MB 13.92MB +0.04% (5.30kB)
StaticClassLiteral<'db>::implicit_attribute_inner_ 10.03MB 10.04MB +0.03% (3.23kB)
Type<'db>::member_lookup_with_policy_ 16.25MB 16.25MB +0.01% (2.34kB)
Type<'db>::class_member_with_policy_ 17.73MB 17.73MB +0.01% (1.39kB)
all_narrowing_constraints_for_expression 7.39MB 7.39MB +0.02% (1.21kB)
cached_protocol_interface 402.80kB 402.15kB -0.16% (660.00B)
is_equivalent_to_object_inner 105.51kB 104.86kB -0.61% (660.00B)
all_negative_narrowing_constraints_for_expression 2.79MB 2.79MB +0.02% (504.00B)
StaticClassLiteral<'db>::variance_of_ 177.37kB 177.49kB +0.07% (120.00B)
infer_scope_types_impl 53.59MB 53.59MB +0.00% (120.00B)
FunctionType<'db>::signature_ 3.94MB 3.94MB -0.00% (108.00B)
... 1 more

sphinx

Name Old New Diff Outcome
function_known_decorators 0.00B 2.46MB +2.46MB (new)
infer_definition_types 24.34MB 24.08MB -1.04% (259.32kB)
infer_deferred_types 5.60MB 5.61MB +0.17% (9.63kB)
StaticClassLiteral<'db>::try_mro_ 2.06MB 2.06MB +0.01% (196.00B)
cached_protocol_interface 187.52kB 187.42kB -0.06% (108.00B)
is_equivalent_to_object_inner 49.36kB 49.25kB -0.21% (108.00B)
StaticClassLiteral<'db>::try_mro_::interned_arguments 476.23kB 476.30kB +0.01% (72.00B)

trio

Name Old New Diff Outcome
function_known_decorators 0.00B 921.32kB +921.32kB (new)
infer_definition_types 7.50MB 7.47MB -0.37% (28.66kB)
infer_deferred_types 2.36MB 2.37MB +0.41% (9.97kB)
cached_protocol_interface 127.43kB 126.38kB -0.83% (1.05kB)
is_equivalent_to_object_inner 32.34kB 31.29kB -3.26% (1.05kB)
StaticClassLiteral<'db>::try_mro_ 821.97kB 822.67kB +0.09% (716.00B)
StaticClassLiteral<'db>::try_mro_::interned_arguments 191.67kB 191.81kB +0.07% (144.00B)
FunctionType<'db>::signature_ 1.07MB 1.07MB +0.00% (12.00B)

flake8

Name Old New Diff Outcome
function_known_decorators 0.00B 316.92kB +316.92kB (new)
infer_definition_types 1.94MB 1.91MB -1.55% (30.84kB)
infer_deferred_types 690.77kB 692.43kB +0.24% (1.66kB)
cached_protocol_interface 43.70kB 43.61kB -0.21% (96.00B)
is_equivalent_to_object_inner 11.41kB 11.32kB -0.82% (96.00B)

@charliermarsh charliermarsh changed the title [ty] Third try at detecting Self [ty] Disallow Self in metaclass and static methods Feb 11, 2026
@charliermarsh charliermarsh marked this pull request as ready for review February 11, 2026 17:07
@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot bot commented Feb 14, 2026

ecosystem-analyzer results

No diagnostic changes detected ✅

Full report with detailed diff (timing results)

Copy link
Copy Markdown
Contributor

@carljm carljm left a comment

Choose a reason for hiding this comment

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

Thanks, this is surprisingly tricky! (As I'm sure you already know, given it looks like this was already the third try...)

return false;
};

infer_definition_types(db, binding_definition)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This means we will create a Salsa cycle if we ever have to evaluate annotations while still within infer_definition_types for the function itself, and one of those annotations is Self. That can be triggered by another stacked decorator that takes and returns Callable[P, R], because that requires evaluating the signature of the original function, which means evaluating its annotations.

It's hard to say right now if this cycle causes correctness problems or not, because the .as_function_literal() issue mentioned below would always mask it in those same stacked-decorator cases. But either way it's not great for perf to introduce more cycles.

But I'm also not sure what to do to avoid this cycle. I think probably the right answer is to factor out a separate query specifically for inferring decorator types on a function. Then we could call that query here with no cycle risk. That's very doable; could try it in this PR or as a follow-up if it seems like the cycle isn't causing any immediate breakage here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Gave this a shot!

@carljm carljm self-assigned this Feb 14, 2026
@charliermarsh charliermarsh marked this pull request as draft February 15, 2026 19:33
@charliermarsh charliermarsh force-pushed the charlie/self3 branch 2 times, most recently from 1c05450 to 609397d Compare February 15, 2026 20:49
@charliermarsh charliermarsh marked this pull request as ready for review February 15, 2026 20:59
@carljm carljm self-requested a review February 16, 2026 07:38
@charliermarsh charliermarsh force-pushed the charlie/self3 branch 2 times, most recently from 9e93ebe to aaf46b3 Compare February 25, 2026 03:18
@charliermarsh charliermarsh force-pushed the charlie/self3 branch 2 times, most recently from 9533793 to 01c4a2e Compare March 4, 2026 14:00
@charliermarsh charliermarsh requested a review from carljm March 4, 2026 14:04
Comment on lines +9004 to +9006
let InferenceRegion::Definition(definition) = self.region else {
panic!("Expected Definition region");
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think it will make the code easier to follow if we stick to the existing pattern of all the other inference kinds. This would imply a new InferenceRegion::FunctionDecorators(definition) to make it explicit that we are, in fact, inferring a more specific region than the entire definition. And then we'd just call infer_region method to do the actual inference here, with no need for the unreachable panic.

Comment on lines +186 to +193
let decorator_inference = function_known_decorators(db, definition);
self.context.extend(decorator_inference.diagnostics());
self.expressions.extend(
decorator_inference
.expression_types()
.iter()
.map(|(expression, ty)| (*expression, *ty)),
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not sure if it currently has any visible impact (it seems like, independently, our called_functions tracking for check_overloaded_function isn't quite working as I think it should, even on main), but I think we should also copy called_functions over, otherwise we will miss recording functions that are called as decorators.

Not sure if we might also need to copy over definitions, in case of walrus expressions inside a decorator expression?

@AlexWaygood AlexWaygood removed their request for review March 25, 2026 09:29
@charliermarsh charliermarsh merged commit 2915824 into main Mar 25, 2026
49 checks passed
@charliermarsh charliermarsh deleted the charlie/self3 branch March 25, 2026 14:55
carljm added a commit that referenced this pull request Mar 25, 2026
* main:
  [ty] make `test-case` a dev-dependency (#24187)
  [ty] implement cycle normalization for more types to prevent too-many-cycle panics (#24061)
  [ty] Silence all diagnostics in unreachable code (#24179)
  [ty] Intern `InferableTypeVars` (#24161)
  Implement unnecessary-if (RUF050) (#24114)
  Recognize `Self` annotation and `self` assignment in SLF001 (#24144)
  Bump the npm version before publish (#24178)
  [ty] Disallow Self in metaclass and static methods (#23231)
  Use trusted publishing for NPM packages (#24171)
  [ty] Respect non-explicitly defined dataclass params (#24170)
  Add RUF072: warn when using  operator on an f-string (#24162)
  [ty] Check return type of generator functions (#24026)
  Implement useless-finally (RUF-072) (#24165)
  [ty] Add test for a dataclass with a default field converter (#24169)
  [ty] Dataclass field converters (#23088)
  [flake8-bandit] Treat sys.executable as trusted input in S603 (#24106)
  [ty] Add support for `typing.Concatenate` (#23689)
  `ASYNC115`: autofix to use full qualified `anyio.lowlevel` import (#24166)
  [ty] Disallow read-only fields in TypedDict updates (#24128)
  Speed up diagnostic rendering (#24146)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ecosystem-analyzer 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