Skip to content

[ty] Support enum member access through enum instances and members#23772

Merged
charliermarsh merged 4 commits intomainfrom
charlie/mem
Mar 7, 2026
Merged

[ty] Support enum member access through enum instances and members#23772
charliermarsh merged 4 commits intomainfrom
charlie/mem

Conversation

@charliermarsh
Copy link
Member

Summary

Closes astral-sh/ty#2977.

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

astral-sh-bot bot commented Mar 7, 2026

Typing conformance results

No changes detected ✅

Current numbers
The percentage of diagnostics emitted that were expected errors held steady at 87.10%. The percentage of expected errors that received a diagnostic held steady at 77.81%.

@astral-sh-bot
Copy link

astral-sh-bot bot commented Mar 7, 2026

mypy_primer results

No ecosystem changes detected ✅

@astral-sh-bot
Copy link

astral-sh-bot bot commented Mar 7, 2026

Memory usage report

Summary

Project Old New Diff Outcome
flake8 47.92MB 47.90MB -0.04% (20.95kB) ⬇️
sphinx 265.49MB 265.40MB -0.03% (93.33kB) ⬇️
trio 118.08MB 117.86MB -0.19% (225.17kB) ⬇️
prefect 694.58MB 693.71MB -0.12% (886.70kB) ⬇️

Significant changes

Click to expand detailed breakdown

flake8

Name Old New Diff Outcome
Type<'db>::class_member_with_policy_ 551.86kB 547.17kB -0.85% (4.69kB) ⬇️
is_redundant_with_impl::interned_arguments 143.77kB 141.37kB -1.67% (2.41kB) ⬇️
Type<'db>::member_lookup_with_policy_ 411.20kB 409.34kB -0.45% (1.85kB) ⬇️
UnionType<'db>::from_two_elements_ 82.56kB 80.74kB -2.20% (1.82kB) ⬇️
enum_metadata 66.04kB 67.60kB +2.36% (1.56kB) ⬇️
is_redundant_with_impl 142.49kB 140.93kB -1.09% (1.56kB) ⬇️
StaticClassLiteral<'db>::implicit_attribute_inner_ 295.69kB 294.16kB -0.52% (1.53kB) ⬇️
IntersectionType 74.27kB 72.80kB -1.99% (1.48kB) ⬇️
StaticClassLiteral<'db>::implicit_attribute_inner_::interned_arguments 240.09kB 238.78kB -0.55% (1.31kB) ⬇️
UnionType 104.89kB 103.64kB -1.19% (1.25kB) ⬇️
Type<'db>::class_member_with_policy_::interned_arguments 301.54kB 300.32kB -0.40% (1.22kB) ⬇️
UnionType<'db>::from_two_elements_::interned_arguments 48.98kB 47.95kB -2.11% (1.03kB) ⬇️
Type<'db>::try_call_dunder_get_ 374.75kB 373.83kB -0.24% (936.00B) ⬇️
Type<'db>::try_call_dunder_get_::interned_arguments 84.80kB 83.89kB -1.08% (936.00B) ⬇️
place_by_id 143.93kB 143.13kB -0.55% (816.00B) ⬇️
... 3 more

sphinx

Name Old New Diff Outcome
Type<'db>::member_lookup_with_policy_ 6.11MB 6.10MB -0.18% (11.37kB) ⬇️
StaticClassLiteral<'db>::implicit_attribute_inner_ 2.39MB 2.38MB -0.41% (9.95kB) ⬇️
Type<'db>::class_member_with_policy_ 7.56MB 7.55MB -0.12% (9.45kB) ⬇️
is_redundant_with_impl::interned_arguments 2.08MB 2.07MB -0.44% (9.45kB) ⬇️
StaticClassLiteral<'db>::implicit_attribute_inner_::interned_arguments 1.91MB 1.91MB -0.44% (8.53kB) ⬇️
UnionType<'db>::from_two_elements_ 1.36MB 1.35MB -0.48% (6.67kB) ⬇️
is_redundant_with_impl 1.81MB 1.81MB -0.35% (6.55kB) ⬇️
IntersectionType 902.06kB 895.83kB -0.69% (6.23kB) ⬇️
Type<'db>::try_call_dunder_get_ 4.94MB 4.94MB -0.09% (4.75kB) ⬇️
Type<'db>::try_call_dunder_get_::interned_arguments 1.22MB 1.21MB -0.38% (4.67kB) ⬇️
place_by_id 1.37MB 1.37MB -0.33% (4.65kB) ⬇️
UnionType 1.27MB 1.27MB -0.35% (4.53kB) ⬇️
Type<'db>::class_member_with_policy_::interned_arguments 3.99MB 3.99MB -0.10% (4.16kB) ⬇️
UnionType<'db>::from_two_elements_::interned_arguments 744.82kB 741.47kB -0.45% (3.35kB) ⬇️
place_by_id::interned_arguments 1.04MB 1.03MB -0.23% (2.46kB) ⬇️
... 2 more

trio

Name Old New Diff Outcome
StaticClassLiteral<'db>::implicit_attribute_inner_ 774.14kB 730.06kB -5.69% (44.08kB) ⬇️
StaticClassLiteral<'db>::implicit_attribute_inner_::interned_arguments 619.12kB 581.34kB -6.10% (37.78kB) ⬇️
Type<'db>::member_lookup_with_policy_ 1.70MB 1.67MB -1.74% (30.26kB) ⬇️
Type<'db>::class_member_with_policy_ 2.01MB 1.98MB -1.40% (28.85kB) ⬇️
Type<'db>::class_member_with_policy_::interned_arguments 1.11MB 1.10MB -1.09% (12.39kB) ⬇️
is_redundant_with_impl::interned_arguments 556.27kB 544.41kB -2.13% (11.86kB) ⬇️
place_by_id 560.98kB 550.75kB -1.82% (10.23kB) ⬇️
UnionType<'db>::from_two_elements_ 281.39kB 272.48kB -3.17% (8.91kB) ⬇️
IntersectionType 240.16kB 231.30kB -3.69% (8.86kB) ⬇️
Type<'db>::try_call_dunder_get_::interned_arguments 358.11kB 349.68kB -2.35% (8.43kB) ⬇️
Type<'db>::try_call_dunder_get_ 1.38MB 1.37MB -0.54% (7.65kB) ⬇️
is_redundant_with_impl 489.20kB 481.83kB -1.51% (7.37kB) ⬇️
UnionType 326.19kB 319.56kB -2.03% (6.62kB) ⬇️
EnumLiteralType 8.16kB 13.94kB +70.97% (5.79kB) ⬇️
place_by_id::interned_arguments 415.12kB 409.71kB -1.30% (5.41kB) ⬇️
... 5 more

prefect

Name Old New Diff Outcome
Type<'db>::class_member_with_policy_ 17.11MB 16.95MB -0.91% (160.00kB) ⬇️
StaticClassLiteral<'db>::implicit_attribute_inner_ 9.72MB 9.58MB -1.40% (139.50kB) ⬇️
StaticClassLiteral<'db>::implicit_attribute_inner_::interned_arguments 5.13MB 5.01MB -2.28% (119.91kB) ⬇️
Type<'db>::member_lookup_with_policy_ 15.27MB 15.17MB -0.66% (103.52kB) ⬇️
is_redundant_with_impl::interned_arguments 5.36MB 5.30MB -1.07% (58.95kB) ⬇️
IntersectionType 2.33MB 2.28MB -1.87% (44.62kB) ⬇️
Type<'db>::class_member_with_policy_::interned_arguments 9.20MB 9.15MB -0.47% (44.28kB) ⬇️
UnionType<'db>::from_two_elements_ 5.14MB 5.10MB -0.82% (43.14kB) ⬇️
is_redundant_with_impl 5.54MB 5.50MB -0.65% (36.75kB) ⬇️
UnionType 3.50MB 3.47MB -0.91% (32.77kB) ⬇️
place_by_id 4.50MB 4.47MB -0.70% (32.14kB) ⬇️
Type<'db>::try_call_dunder_get_::interned_arguments 2.98MB 2.95MB -0.98% (29.86kB) ⬇️
Type<'db>::try_call_dunder_get_ 10.29MB 10.26MB -0.26% (27.89kB) ⬇️
UnionType<'db>::from_two_elements_::interned_arguments 2.38MB 2.35MB -1.05% (25.61kB) ⬇️
EnumLiteralType 7.50kB 25.12kB +235.00% (17.62kB) ⬇️
... 9 more

@charliermarsh charliermarsh marked this pull request as ready for review March 7, 2026 01:09
@charliermarsh charliermarsh marked this pull request as draft March 7, 2026 01:10
@codspeed-hq
Copy link

codspeed-hq bot commented Mar 7, 2026

Merging this PR will improve performance by 31.95%

⚡ 1 improved benchmark
✅ 25 untouched benchmarks
⏩ 30 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation ty_micro[many_enum_members] 131.4 ms 99.6 ms +31.95%

Comparing charlie/mem (fac85b5) with main (2185bec)

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 marked this pull request as ready for review March 7, 2026 01:22
@charliermarsh charliermarsh marked this pull request as draft March 7, 2026 02:08
Comment on lines +3206 to +3210
Type::LiteralValue(literal) => literal
.as_enum()
.map(|enum_literal| enum_literal.enum_class(db)),
Type::NominalInstance(instance) => Some(instance.class_literal(db)),
_ => None,
Copy link
Contributor

Choose a reason for hiding this comment

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

Kinda unfortunate that we have to re-match on self here inside this match arm, where a bunch of types will never match here. But I guess it's hard to structure this otherwise without if let guards, which are still experimental. We could extract the "fallback" handling below into a function/method/closure so we could easily reuse it in two different branches, but maybe that's not worth it.

@carljm
Copy link
Contributor

carljm commented Mar 7, 2026

I'll note for posterity that privileging enum members over other attributes results in behavior that doesn't match runtime in the weird case where you have a mixin enum type (like IntEnum, where members are also subclasses of int), and then you also define an enum member that overrides a real attribute of the mixin class (e.g. an IntEnum with a member named numerator or denominator). In this case at runtime MyWeirdIntEnum.numerator.numerator actually accesses the real int attribute numerator on the enum value MyWeirdIntEnum.numerator, whereas in this PR you'd get back the enum member again.

But a) this case is invalid, and once we improve our Liskov checking we should emit a Liskov violation on it, and b) other type checkers all do the same. So I think this behavior is fine.

@charliermarsh charliermarsh merged commit f8df8ea into main Mar 7, 2026
51 checks passed
@charliermarsh charliermarsh deleted the charlie/mem branch March 7, 2026 02:59
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.

Support accessing enum members off enum members/instances

3 participants