mirror of
				https://github.com/astral-sh/ruff.git
				synced 2025-10-31 03:55:09 +00:00 
			
		
		
		
	[ty] Detect illegal non-enum attribute accesses in Literal annotation (#19477)
## Summary Detect illegal attribute accesses in `Literal[X.Y]` annotations if `X` is not an enum class. ## Test Plan New Markdown test
This commit is contained in:
		
							parent
							
								
									5e29278aa2
								
							
						
					
					
						commit
						215a1c55d4
					
				
					 3 changed files with 34 additions and 8 deletions
				
			
		|  | @ -25,6 +25,9 @@ class Color(Enum): | ||||||
| 
 | 
 | ||||||
| b1: Literal[Color.RED] | b1: Literal[Color.RED] | ||||||
| 
 | 
 | ||||||
|  | MissingT = Enum("MissingT", {"MISSING": "MISSING"}) | ||||||
|  | b2: Literal[MissingT.MISSING] | ||||||
|  | 
 | ||||||
| def f(): | def f(): | ||||||
|     reveal_type(mode)  # revealed: Literal["w", "r"] |     reveal_type(mode)  # revealed: Literal["w", "r"] | ||||||
|     reveal_type(a1)  # revealed: Literal[26] |     reveal_type(a1)  # revealed: Literal[26] | ||||||
|  | @ -51,6 +54,12 @@ 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] | ||||||
|  | invalid5: Literal[NotAnEnum.x] | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ## Shortening unions of literals | ## Shortening unions of literals | ||||||
|  |  | ||||||
|  | @ -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, | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -104,6 +104,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::is_enum_class; | ||||||
| use crate::types::function::{ | use crate::types::function::{ | ||||||
|     FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral, |     FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral, | ||||||
| }; | }; | ||||||
|  | @ -10414,7 +10415,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 | ||||||
|  | @ -10422,6 +10424,14 @@ 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()); | ||||||
|  |                     if value_ty.is_todo() { | ||||||
|  |                         value_ty | ||||||
|  |                     } else { | ||||||
|  |                         return Err(vec![parameters]); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|             // for negative and positive numbers
 |             // for negative and positive numbers
 | ||||||
|             ast::Expr::UnaryOp(u) |             ast::Expr::UnaryOp(u) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 David Peter
						David Peter