[ty] Detect illegal non-enum attribute accesses in Literal annotation

This commit is contained in:
David Peter 2025-07-22 09:18:50 +02:00
parent 897889d1ce
commit cbc8c08016
3 changed files with 28 additions and 8 deletions

View file

@ -51,6 +51,13 @@ invalid4: Literal[
hello, # error: [invalid-type-form] hello, # error: [invalid-type-form]
(1, 2, 3), # error: [invalid-type-form] (1, 2, 3), # error: [invalid-type-form]
] ]
class NotAnEnum:
x: int = 1
# error: [invalid-type-form]
def _(invalid: Literal[NotAnEnum.x]) -> None:
reveal_type(invalid) # revealed: Unknown
``` ```
## Shortening unions of literals ## Shortening unions of literals

View file

@ -240,3 +240,10 @@ pub(crate) fn enum_member_literals<'a, 'db: 'a>(
pub(crate) fn is_single_member_enum<'db>(db: &'db dyn Db, class: ClassLiteral<'db>) -> bool { pub(crate) fn is_single_member_enum<'db>(db: &'db dyn Db, class: ClassLiteral<'db>) -> bool {
enum_metadata(db, class).is_some_and(|metadata| metadata.members.len() == 1) enum_metadata(db, class).is_some_and(|metadata| metadata.members.len() == 1)
} }
pub(crate) fn is_enum_class<'db>(db: &'db dyn Db, ty: Type<'db>) -> bool {
match ty {
Type::ClassLiteral(class_literal) => enum_metadata(db, class_literal).is_some(),
_ => false,
}
}

View file

@ -102,6 +102,7 @@ use crate::types::diagnostic::{
report_invalid_generator_function_return_type, report_invalid_return_type, report_invalid_generator_function_return_type, report_invalid_return_type,
report_possibly_unbound_attribute, report_possibly_unbound_attribute,
}; };
use crate::types::enums::{enum_metadata, is_enum_class};
use crate::types::function::{ use crate::types::function::{
FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral, FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral,
}; };
@ -10033,7 +10034,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
// For enum values // For enum values
ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => { ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => {
let value_ty = self.infer_expression(value); let value_ty = self.infer_expression(value);
// TODO: Check that value type is enum otherwise return None
if is_enum_class(self.db(), value_ty) {
let ty = value_ty let ty = value_ty
.member(self.db(), &attr.id) .member(self.db(), &attr.id)
.place .place
@ -10041,6 +10043,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
.unwrap_or(Type::unknown()); .unwrap_or(Type::unknown());
self.store_expression_type(parameters, ty); self.store_expression_type(parameters, ty);
ty ty
} else {
self.store_expression_type(parameters, Type::unknown());
return Err(vec![parameters]);
}
} }
// for negative and positive numbers // for negative and positive numbers
ast::Expr::UnaryOp(u) ast::Expr::UnaryOp(u)