11use ruff_python_ast:: name:: Name ;
2- use ruff_python_ast:: { self as ast, NodeIndex } ;
2+ use ruff_python_ast:: { self as ast, NodeIndex , PythonVersion } ;
33
44use crate :: {
5- Db ,
5+ Db , Program ,
66 semantic_index:: definition:: Definition ,
77 types:: {
88 ClassLiteral , KnownClass , Type , TypeContext ,
99 class:: { DynamicEnumAnchor , DynamicEnumLiteral , EnumSpec } ,
10+ diagnostic:: { INVALID_ARGUMENT_TYPE , TOO_MANY_POSITIONAL_ARGUMENTS , UNKNOWN_ARGUMENT } ,
1011 infer:: TypeInferenceBuilder ,
1112 subclass_of:: SubclassOfType ,
1213 } ,
@@ -43,9 +44,24 @@ fn enum_auto_value<'db>(
4344 match base_class {
4445 KnownClass :: StrEnum => Type :: string_literal ( db, & name. to_lowercase ( ) ) ,
4546 KnownClass :: Flag | KnownClass :: IntFlag => {
46- Type :: int_literal ( start << i64:: try_from ( index) . unwrap_or ( 0 ) )
47+ let shift = i64:: try_from ( index) . ok ( ) ;
48+ let headroom = if start >= 0 {
49+ start. leading_zeros ( ) . saturating_sub ( 1 )
50+ } else {
51+ start. leading_ones ( ) . saturating_sub ( 1 )
52+ } ;
53+ shift
54+ . and_then ( |s| u32:: try_from ( s) . ok ( ) )
55+ . filter ( |& s| s <= headroom)
56+ . and_then ( |s| start. checked_shl ( s) )
57+ . map ( Type :: int_literal)
58+ . unwrap_or_else ( || KnownClass :: Int . to_instance ( db) )
4759 }
48- _ => Type :: int_literal ( start + i64:: try_from ( index) . unwrap_or ( 0 ) ) ,
60+ _ => i64:: try_from ( index)
61+ . ok ( )
62+ . and_then ( |i| start. checked_add ( i) )
63+ . map ( Type :: int_literal)
64+ . unwrap_or_else ( || KnownClass :: Int . to_instance ( db) ) ,
4965 }
5066}
5167
@@ -69,10 +85,17 @@ fn dict_auto_value<'db>(
6985 if last_int_value <= 0 {
7086 Type :: int_literal ( 1 )
7187 } else {
72- Type :: int_literal ( 1 << ( i64:: BITS - last_int_value. leading_zeros ( ) ) )
88+ let shift = i64:: BITS - last_int_value. leading_zeros ( ) ;
89+ 1_i64
90+ . checked_shl ( shift)
91+ . map ( Type :: int_literal)
92+ . unwrap_or_else ( || KnownClass :: Int . to_instance ( db) )
7393 }
7494 }
75- _ => Type :: int_literal ( last_int_value + 1 ) ,
95+ _ => last_int_value
96+ . checked_add ( 1 )
97+ . map ( Type :: int_literal)
98+ . unwrap_or_else ( || KnownClass :: Int . to_instance ( db) ) ,
7699 }
77100}
78101
@@ -87,17 +110,24 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
87110 let args = & call_expr. arguments . args ;
88111 let keywords = & call_expr. arguments . keywords ;
89112
90- // bail out on unknown keywords so normal overload resolution can diagnose them
91- let has_unknown_keyword = keywords. iter ( ) . any ( |kw| {
92- kw. arg . as_ref ( ) . is_some_and ( |name| {
93- !matches ! (
113+ let base_name = base_class. name ( db) ;
114+ let python_version = Program :: get ( db) . python_version ( db) ;
115+
116+ for kw in keywords {
117+ if let Some ( name) = & kw. arg {
118+ let is_valid_keyword = matches ! (
94119 name. as_str( ) ,
95- "value" | "names" | "start" | "type" | "module" | "qualname" | "boundary"
96- )
97- } )
98- } ) ;
99- if has_unknown_keyword {
100- return None ;
120+ "value" | "names" | "start" | "type" | "module" | "qualname"
121+ ) || ( name. as_str ( ) == "boundary"
122+ && python_version >= PythonVersion :: PY311 ) ;
123+ if !is_valid_keyword {
124+ if let Some ( builder) = self . context . report_lint ( & UNKNOWN_ARGUMENT , kw) {
125+ builder. into_diagnostic ( format_args ! (
126+ "Argument `{name}` does not match any known parameter of function `{base_name}`" ,
127+ ) ) ;
128+ }
129+ }
130+ }
101131 }
102132
103133 let value_kw = call_expr. arguments . find_keyword ( "value" ) ;
@@ -131,6 +161,18 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
131161 // Non-literal name: return type[base_class] without creating a
132162 // DynamicEnumLiteral. This matches the typeshed overload return type.
133163 if !name_arg. is_string_literal_expr ( ) {
164+ let name_type = self . expression_type ( name_arg) ;
165+ if !name_type. is_assignable_to ( db, KnownClass :: Str . to_instance ( db) )
166+ && let Some ( builder) = self . context . report_lint ( & INVALID_ARGUMENT_TYPE , name_arg)
167+ {
168+ let mut diagnostic = builder. into_diagnostic ( format_args ! (
169+ "Invalid argument to parameter `value` of `{base_name}()`"
170+ ) ) ;
171+ diagnostic. set_primary_message ( format_args ! (
172+ "Expected `str`, found `{}`" ,
173+ name_type. display( db)
174+ ) ) ;
175+ }
134176 return SubclassOfType :: try_from_type ( db, base_class. to_class_literal ( db) ) ;
135177 }
136178
@@ -151,6 +193,17 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
151193 // Only 1 extra positional arg is allowed (the `names` parameter).
152194 // `Enum("Color", "RED", "GREEN")` is invalid at runtime.
153195 let has_too_many_positional = args. len ( ) > 2 ;
196+ if has_too_many_positional {
197+ if let Some ( builder) = self
198+ . context
199+ . report_lint ( & TOO_MANY_POSITIONAL_ARGUMENTS , & args[ 2 ] )
200+ {
201+ builder. into_diagnostic ( format_args ! (
202+ "Too many positional arguments to function `{base_name}`: expected 2, got {}" ,
203+ args. len( ) ,
204+ ) ) ;
205+ }
206+ }
154207
155208 // without `names`, this is a value-lookup call, not functional enum creation
156209 let names_arg = names_arg?;
0 commit comments