Skip to content

Commit b605c3e

Browse files
authored
[ty] Normalize single-member enums to their instance type (#19502)
## Summary Fixes astral-sh/ty#874 Labeling this as `internal`, since we haven't released the enum-expansion feature. ## Test Plan New Markdown tests
1 parent c281891 commit b605c3e

File tree

3 files changed

+30
-1
lines changed

3 files changed

+30
-1
lines changed

crates/ty_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ materializations of `B`, and all materializations of `B` are also materializatio
1313
### Fully static
1414

1515
```py
16-
from typing_extensions import Literal, LiteralString, Never
16+
from typing_extensions import Literal, LiteralString, Protocol, Never
1717
from ty_extensions import Unknown, is_equivalent_to, static_assert, TypeOf, AlwaysTruthy, AlwaysFalsy
1818
from enum import Enum
1919

@@ -43,6 +43,16 @@ static_assert(is_equivalent_to(Literal[Single.VALUE], Single))
4343
static_assert(is_equivalent_to(Single, Literal[Single.VALUE]))
4444
static_assert(is_equivalent_to(Literal[Single.VALUE], Literal[Single.VALUE]))
4545

46+
static_assert(is_equivalent_to(tuple[Single] | int | str, str | int | tuple[Literal[Single.VALUE]]))
47+
48+
class Protocol1(Protocol):
49+
a: Single
50+
51+
class Protocol2(Protocol):
52+
a: Literal[Single.VALUE]
53+
54+
static_assert(is_equivalent_to(Protocol1, Protocol2))
55+
4656
static_assert(is_equivalent_to(Never, Never))
4757
static_assert(is_equivalent_to(AlwaysTruthy, AlwaysTruthy))
4858
static_assert(is_equivalent_to(AlwaysFalsy, AlwaysFalsy))

crates/ty_python_semantic/src/types.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,6 +1062,12 @@ impl<'db> Type<'db> {
10621062
type_is.with_type(db, type_is.return_type(db).normalized_impl(db, v))
10631063
}),
10641064
Type::Dynamic(dynamic) => Type::Dynamic(dynamic.normalized()),
1065+
Type::EnumLiteral(enum_literal)
1066+
if is_single_member_enum(db, enum_literal.enum_class(db)) =>
1067+
{
1068+
// Always normalize single-member enums to their class instance (`Literal[Single.VALUE]` => `Single`)
1069+
enum_literal.enum_class_instance(db)
1070+
}
10651071
Type::LiteralString
10661072
| Type::AlwaysFalsy
10671073
| Type::AlwaysTruthy

crates/ty_python_semantic/src/types/property_tests/type_generation.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::db::tests::TestDb;
22
use crate::place::{builtins_symbol, known_module_symbol};
3+
use crate::types::enums::is_single_member_enum;
34
use crate::types::tuple::TupleType;
45
use crate::types::{
56
BoundMethodType, CallableType, EnumLiteralType, IntersectionBuilder, KnownClass, Parameter,
@@ -27,6 +28,8 @@ pub(crate) enum Ty {
2728
BytesLiteral(&'static str),
2829
// An enum literal variant, using `uuid.SafeUUID` as base
2930
EnumLiteral(&'static str),
31+
// A single-member enum literal, using `dataclasses.MISSING`
32+
SingleMemberEnumLiteral,
3033
// BuiltinInstance("str") corresponds to an instance of the builtin `str` class
3134
BuiltinInstance(&'static str),
3235
/// Members of the `abc` stdlib module
@@ -145,6 +148,15 @@ impl Ty {
145148
.expect_class_literal(),
146149
Name::new(name),
147150
)),
151+
Ty::SingleMemberEnumLiteral => {
152+
let ty = known_module_symbol(db, KnownModule::Dataclasses, "MISSING")
153+
.place
154+
.expect_type();
155+
debug_assert!(
156+
matches!(ty, Type::NominalInstance(instance) if is_single_member_enum(db, instance.class.class_literal(db).0))
157+
);
158+
ty
159+
}
148160
Ty::BuiltinInstance(s) => builtins_symbol(db, s)
149161
.place
150162
.expect_type()
@@ -265,6 +277,7 @@ fn arbitrary_core_type(g: &mut Gen, fully_static: bool) -> Ty {
265277
Ty::EnumLiteral("safe"),
266278
Ty::EnumLiteral("unsafe"),
267279
Ty::EnumLiteral("unknown"),
280+
Ty::SingleMemberEnumLiteral,
268281
Ty::KnownClassInstance(KnownClass::Object),
269282
Ty::KnownClassInstance(KnownClass::Str),
270283
Ty::KnownClassInstance(KnownClass::Int),

0 commit comments

Comments
 (0)