Skip to content

[ty] Preserve recursively-defined flag during normalization#24276

Open
charliermarsh wants to merge 1 commit intomainfrom
charlie/recursively-defined
Open

[ty] Preserve recursively-defined flag during normalization#24276
charliermarsh wants to merge 1 commit intomainfrom
charlie/recursively-defined

Conversation

@charliermarsh
Copy link
Copy Markdown
Member

@charliermarsh charliermarsh commented Mar 28, 2026

Summary

This PR allows the Unknown variant to retain a recursively-normalized flag so that it's not dropped during normalization.

Consider this motivating example:

class Count:
    def __init__(self):
        self.i = 0

class PositiveCount(Count):
    def increment(self):
        self.i = self.i + 1

    def decrement(self):
        if self.i != 0:
            self.i = self.i - 1

PositiveCount.i is initially inferred as Unknown in the subclass (the subclass doesn't see self.i = 0 upfront, because implicit_attribute_inner operates on a single class). Instead, it sees the self.i = self.i + 1 and self.i = self.i - 1 statements, which adds Divergent to the type. Recursive normalization drops the Divergent arms and marks the result recursive... but that flag doesn't have anywhere to go. Then, when evaluating self.i + 1, it walks the MRO and adds the Literal[0] from self.i = 0, so we have Unknown | Literal[0], but we've already "lost" the recursively-normalized flag.

I also considered #24272 which is more targeted, but this felt more structurally correct and less targeted.

Closes astral-sh/ty#3149.

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

astral-sh-bot bot commented Mar 28, 2026

Typing conformance results

No changes detected ✅

Current numbers
The percentage of diagnostics emitted that were expected errors held steady at 86.60%. The percentage of expected errors that received a diagnostic held steady at 81.47%. The number of fully passing files held steady at 69/132.

@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot bot commented Mar 28, 2026

Memory usage report

Summary

Project Old New Diff Outcome
prefect 716.74MB 716.94MB +0.03% (202.91kB)
sphinx 265.21MB 265.34MB +0.05% (132.93kB)
trio 117.99MB 118.02MB +0.03% (31.84kB)
flake8 48.07MB 48.08MB +0.01% (4.21kB)

Significant changes

Click to expand detailed breakdown

prefect

Name Old New Diff Outcome
UnionType<'db>::from_two_elements_ 5.24MB 5.37MB +2.51% (134.76kB)
UnionType 3.52MB 3.53MB +0.56% (20.09kB)
infer_definition_types 89.72MB 89.74MB +0.02% (14.68kB)
infer_expression_types_impl 61.76MB 61.77MB +0.01% (8.58kB)
Type<'db>::member_lookup_with_policy_ 16.19MB 16.19MB +0.03% (5.16kB)
Type<'db>::member_lookup_with_policy_::interned_arguments 5.76MB 5.77MB +0.08% (4.67kB)
infer_deferred_types 14.72MB 14.73MB +0.03% (3.84kB)
is_redundant_with_impl::interned_arguments 5.42MB 5.42MB -0.07% (3.78kB)
infer_expression_type_impl 13.82MB 13.82MB +0.02% (3.52kB)
loop_header_reachability 434.99kB 438.22kB +0.74% (3.23kB)
StaticClassLiteral<'db>::try_mro_ 5.86MB 5.87MB +0.05% (2.83kB)
is_redundant_with_impl 5.51MB 5.51MB -0.05% (2.71kB)
all_negative_narrowing_constraints_for_expression 2.62MB 2.62MB -0.09% (2.45kB)
all_narrowing_constraints_for_expression 7.13MB 7.13MB -0.03% (2.29kB)
StaticClassLiteral<'db>::implicit_attribute_inner_ 9.93MB 9.94MB +0.02% (1.73kB)
... 25 more

sphinx

Name Old New Diff Outcome
UnionType<'db>::from_two_elements_ 1.38MB 1.41MB +2.56% (36.09kB)
Type<'db>::member_lookup_with_policy_ 6.51MB 6.53MB +0.35% (23.17kB)
UnionType 1.24MB 1.26MB +1.49% (18.97kB)
infer_expression_types_impl 21.48MB 21.49MB +0.06% (13.66kB)
infer_definition_types 23.97MB 23.98MB +0.05% (12.64kB)
infer_expression_type_impl 2.95MB 2.96MB +0.20% (6.04kB)
is_redundant_with_impl::interned_arguments 2.02MB 2.02MB +0.15% (3.01kB)
is_redundant_with_impl 1.78MB 1.78MB +0.16% (2.95kB)
IntersectionType 880.59kB 883.23kB +0.30% (2.65kB)
loop_header_reachability 378.58kB 381.22kB +0.70% (2.64kB)
Type<'db>::member_lookup_with_policy_::interned_arguments 2.65MB 2.65MB +0.09% (2.44kB)
TupleType<'db>::to_class_type_ 161.37kB 163.07kB +1.05% (1.70kB)
UnionType<'db>::from_two_elements_::interned_arguments 780.74kB 782.29kB +0.20% (1.55kB)
infer_deferred_types 5.61MB 5.61MB +0.02% (1.20kB)
infer_scope_types_impl 15.49MB 15.50MB +0.01% (924.00B)
... 11 more

trio

Name Old New Diff Outcome
infer_definition_types 7.73MB 7.73MB +0.08% (6.33kB)
UnionType<'db>::from_two_elements_ 281.39kB 287.30kB +2.10% (5.90kB)
UnionType 304.84kB 308.94kB +1.34% (4.09kB)
infer_expression_types_impl 7.08MB 7.08MB +0.04% (3.00kB)
infer_expression_type_impl 1.32MB 1.32MB +0.18% (2.37kB)
Type<'db>::member_lookup_with_policy_ 1.81MB 1.81MB +0.12% (2.29kB)
StaticClassLiteral<'db>::try_mro_ 822.56kB 823.75kB +0.14% (1.19kB)
Type<'db>::member_lookup_with_policy_::interned_arguments 909.39kB 910.30kB +0.10% (936.00B)
loop_header_reachability 133.23kB 134.12kB +0.67% (912.00B)
Type<'db>::class_member_with_policy_ 2.00MB 2.00MB +0.04% (776.00B)
all_narrowing_constraints_for_expression 589.21kB 589.80kB +0.10% (600.00B)
infer_deferred_types 2.38MB 2.38MB +0.02% (588.00B)
Specialization 462.36kB 462.86kB +0.11% (512.00B)
Type<'db>::try_call_dunder_get_ 1.37MB 1.37MB +0.04% (508.00B)
TupleType<'db>::to_class_type_ 51.62kB 52.04kB +0.82% (432.00B)
... 14 more

flake8

Name Old New Diff Outcome
UnionType<'db>::from_two_elements_ 83.43kB 85.64kB +2.65% (2.21kB)
UnionType 101.28kB 101.94kB +0.65% (672.00B)
infer_definition_types 1.87MB 1.87MB +0.02% (312.00B)
Type<'db>::class_member_with_policy_ 568.68kB 568.97kB +0.05% (292.00B)
infer_expression_types_impl 1.07MB 1.07MB +0.02% (216.00B)
StaticClassLiteral<'db>::implicit_attribute_inner_ 313.34kB 313.51kB +0.06% (180.00B)
is_redundant_with_impl::interned_arguments 139.56kB 139.39kB -0.12% (176.00B)
IntersectionType 68.56kB 68.40kB -0.24% (168.00B)
Type<'db>::member_lookup_with_policy_ 487.16kB 487.31kB +0.03% (160.00B)
infer_expression_type_impl 141.97kB 142.12kB +0.11% (156.00B)
TupleType<'db>::to_class_type_ 15.58kB 15.73kB +0.98% (156.00B)
Type<'db>::member_lookup_with_policy_::interned_arguments 228.11kB 228.21kB +0.04% (104.00B)
Type<'db>::class_member_with_policy_::interned_arguments 306.41kB 306.52kB +0.03% (104.00B)
is_redundant_with_impl 136.72kB 136.63kB -0.07% (96.00B)
infer_deferred_types 692.50kB 692.54kB +0.01% (36.00B)
... 5 more

@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot bot commented Mar 28, 2026

ecosystem-analyzer results

No diagnostic changes detected ✅

Full report with detailed diff (timing results)

@charliermarsh charliermarsh marked this pull request as ready for review March 29, 2026 00:48
Copy link
Copy Markdown
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.

Can we add a regression test for this?

@charliermarsh charliermarsh force-pushed the charlie/recursively-defined branch from 53d50ff to 6a18c97 Compare March 29, 2026 01:06
@charliermarsh
Copy link
Copy Markdown
Member Author

Added, sorry. (This was present in https://github.com/astral-sh/ruff/pull/24272/changes#diff-19ac4a4d9337821d0bbfb544ab4e3fda87df59c6ea06065b77d3fcf5cf682ec2R2809 but forgot to carry it over.)

@charliermarsh charliermarsh force-pushed the charlie/recursively-defined branch from 6a18c97 to 0d7584e Compare March 30, 2026 02:00
@carljm carljm removed their request for review March 30, 2026 17:13
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.

panic: 'too many cycle iterations' with inherited attribute

3 participants