Skip to content

Commit e36e3ce

Browse files
committed
[ty] Detect illegal non-enum attribute accesses in Literal annotation
1 parent 30683e3 commit e36e3ce

File tree

3 files changed

+34
-8
lines changed

3 files changed

+34
-8
lines changed

crates/ty_python_semantic/resources/mdtest/annotations/literal.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ class Color(Enum):
2525

2626
b1: Literal[Color.RED]
2727

28+
MissingT = Enum("MissingT", {"MISSING": "MISSING"})
29+
b2: Literal[MissingT.MISSING]
30+
2831
def f():
2932
reveal_type(mode) # revealed: Literal["w", "r"]
3033
reveal_type(a1) # revealed: Literal[26]
@@ -51,6 +54,12 @@ invalid4: Literal[
5154
hello, # error: [invalid-type-form]
5255
(1, 2, 3), # error: [invalid-type-form]
5356
]
57+
58+
class NotAnEnum:
59+
x: int = 1
60+
61+
# error: [invalid-type-form]
62+
invalid5: Literal[NotAnEnum.x]
5463
```
5564

5665
## Shortening unions of literals

crates/ty_python_semantic/src/types/enums.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,3 +240,10 @@ pub(crate) fn enum_member_literals<'a, 'db: 'a>(
240240
pub(crate) fn is_single_member_enum<'db>(db: &'db dyn Db, class: ClassLiteral<'db>) -> bool {
241241
enum_metadata(db, class).is_some_and(|metadata| metadata.members.len() == 1)
242242
}
243+
244+
pub(crate) fn is_enum_class<'db>(db: &'db dyn Db, ty: Type<'db>) -> bool {
245+
match ty {
246+
Type::ClassLiteral(class_literal) => enum_metadata(db, class_literal).is_some(),
247+
_ => false,
248+
}
249+
}

crates/ty_python_semantic/src/types/infer.rs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ use crate::types::diagnostic::{
102102
report_invalid_generator_function_return_type, report_invalid_return_type,
103103
report_possibly_unbound_attribute,
104104
};
105+
use crate::types::enums::is_enum_class;
105106
use crate::types::function::{
106107
FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral,
107108
};
@@ -10033,14 +10034,23 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
1003310034
// For enum values
1003410035
ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => {
1003510036
let value_ty = self.infer_expression(value);
10036-
// TODO: Check that value type is enum otherwise return None
10037-
let ty = value_ty
10038-
.member(self.db(), &attr.id)
10039-
.place
10040-
.ignore_possibly_unbound()
10041-
.unwrap_or(Type::unknown());
10042-
self.store_expression_type(parameters, ty);
10043-
ty
10037+
10038+
if is_enum_class(self.db(), value_ty) {
10039+
let ty = value_ty
10040+
.member(self.db(), &attr.id)
10041+
.place
10042+
.ignore_possibly_unbound()
10043+
.unwrap_or(Type::unknown());
10044+
self.store_expression_type(parameters, ty);
10045+
ty
10046+
} else {
10047+
self.store_expression_type(parameters, Type::unknown());
10048+
if value_ty.is_todo() {
10049+
value_ty
10050+
} else {
10051+
return Err(vec![parameters]);
10052+
}
10053+
}
1004410054
}
1004510055
// for negative and positive numbers
1004610056
ast::Expr::UnaryOp(u)

0 commit comments

Comments
 (0)