@@ -36,7 +36,7 @@ use rustpython_compiler_core::{
3636 Mode , OneIndexed , SourceFile , SourceLocation ,
3737 bytecode:: {
3838 self , Arg as OpArgMarker , BinaryOperator , CodeObject , ComparisonOperator , ConstantData ,
39- Instruction , OpArg , OpArgType , UnpackExArgs ,
39+ Instruction , OpArg , OpArgType , TestOperator , UnpackExArgs ,
4040 } ,
4141} ;
4242use rustpython_wtf8:: Wtf8Buf ;
@@ -3291,7 +3291,7 @@ impl Compiler {
32913291 // index = len(subject) - (size - i)
32923292 emit ! ( self , Instruction :: GetLen ) ;
32933293 self . emit_load_const ( ConstantData :: Integer {
3294- value : ( patterns. len ( ) - 1 ) . into ( ) ,
3294+ value : ( patterns. len ( ) - i ) . into ( ) ,
32953295 } ) ;
32963296 // Subtract to compute the correct index.
32973297 emit ! (
@@ -3484,12 +3484,23 @@ impl Compiler {
34843484
34853485 // Process each sub-pattern.
34863486 for subpattern in patterns. iter ( ) . chain ( kwd_patterns. iter ( ) ) {
3487- // Decrement the on_top counter as each sub-pattern is processed
3488- // (on_top should be zero at the end of the algorithm as a sanity check).
3487+ // Decrement the on_top counter BEFORE processing each sub-pattern
34893488 pc. on_top -= 1 ;
3490- if subpattern. is_wildcard ( ) {
3489+
3490+ // Check if this is a true wildcard (underscore pattern without name binding)
3491+ let is_true_wildcard = match subpattern {
3492+ Pattern :: MatchAs ( match_as) => {
3493+ // Only consider it wildcard if both pattern and name are None (i.e., "_")
3494+ match_as. pattern . is_none ( ) && match_as. name . is_none ( )
3495+ }
3496+ _ => subpattern. is_wildcard ( ) ,
3497+ } ;
3498+
3499+ if is_true_wildcard {
34913500 emit ! ( self , Instruction :: Pop ) ;
3501+ continue ; // Don't compile wildcard patterns
34923502 }
3503+
34933504 // Compile the subpattern without irrefutability checks.
34943505 self . compile_pattern_subpattern ( subpattern, pc) ?;
34953506 }
@@ -3505,172 +3516,195 @@ impl Compiler {
35053516 let keys = & mapping. keys ;
35063517 let patterns = & mapping. patterns ;
35073518 let size = keys. len ( ) ;
3508- let n_patterns = patterns . len ( ) ;
3519+ let star_target = & mapping . rest ;
35093520
3510- if size != n_patterns {
3521+ // Validate pattern count matches key count
3522+ if keys. len ( ) != patterns. len ( ) {
35113523 return Err ( self . error ( CodegenErrorType :: SyntaxError ( format ! (
3512- "keys ({size}) / patterns ({n_patterns}) length mismatch in mapping pattern"
3524+ "keys ({}) / patterns ({}) length mismatch in mapping pattern" ,
3525+ keys. len( ) ,
3526+ patterns. len( )
35133527 ) ) ) ) ;
35143528 }
35153529
3516- let star_target = & mapping. rest ;
3530+ // Validate rest pattern: '_' cannot be used as a rest target
3531+ if let Some ( rest) = star_target {
3532+ if rest. as_str ( ) == "_" {
3533+ return Err ( self . error ( CodegenErrorType :: SyntaxError ( "invalid syntax" . to_string ( ) ) ) ) ;
3534+ }
3535+ }
35173536
35183537 // Step 1: Check if subject is a mapping
35193538 // Stack: [subject]
3520- // We need to keep the subject on top during the mapping and length checks
35213539 pc. on_top += 1 ;
35223540
35233541 emit ! ( self , Instruction :: MatchMapping ) ;
3524- // Stack: [subject, bool ]
3542+ // Stack: [subject, is_mapping ]
35253543
3526- // If not a mapping, fail (jump and pop subject)
35273544 self . jump_to_fail_pop ( pc, JumpOp :: PopJumpIfFalse ) ?;
3528- // Stack: [subject] (on success)
3545+ // Stack: [subject]
35293546
3530- // Step 2: If empty pattern with no star, consume subject
3547+ // Special case: empty pattern {} with no rest
35313548 if size == 0 && star_target. is_none ( ) {
3532- // If the pattern is just "{}", we're done! Pop the subject:
3549+ // If the pattern is just "{}", we're done! Pop the subject
35333550 pc. on_top -= 1 ;
35343551 emit ! ( self , Instruction :: Pop ) ;
35353552 return Ok ( ( ) ) ;
35363553 }
35373554
3538- // Step 3: Check mapping has enough keys
3539- if size != 0 {
3540- // Stack: [subject]
3555+ // Length check for patterns with keys
3556+ if size > 0 {
3557+ // Check if the mapping has at least 'size' keys
35413558 emit ! ( self , Instruction :: GetLen ) ;
3542- // Stack: [subject, len]
35433559 self . emit_load_const ( ConstantData :: Integer { value : size. into ( ) } ) ;
3544- // Stack: [subject, len, size]
35453560 emit ! (
35463561 self ,
35473562 Instruction :: CompareOperation {
35483563 op: ComparisonOperator :: GreaterOrEqual
35493564 }
35503565 ) ;
3551- // Stack: [subject, bool]
3552-
3553- // If not enough keys, fail
35543566 self . jump_to_fail_pop ( pc, JumpOp :: PopJumpIfFalse ) ?;
3555- // Stack: [subject]
35563567 }
35573568
3558- // Step 4: Build keys tuple
3559- if size. saturating_sub ( 1 ) > ( i32:: MAX as usize ) {
3569+ // Check for overflow (INT_MAX < size - 1)
3570+ if size > ( i32:: MAX as usize + 1 ) {
35603571 return Err ( self . error ( CodegenErrorType :: SyntaxError (
35613572 "too many sub-patterns in mapping pattern" . to_string ( ) ,
35623573 ) ) ) ;
35633574 }
3575+ #[ allow( clippy:: cast_possible_truncation) ]
3576+ let size = size as u32 ; // checked right before
35643577
3565- // Validate and compile keys
3566- // NOTE: RustPython difference - using HashSet<String> for duplicate checking
3567- // CPython uses PySet with actual Python objects
3568- let mut seen = std:: collections:: HashSet :: new ( ) ;
3569-
3570- for key in keys. iter ( ) {
3571- // Validate key
3572- let is_constant = matches ! (
3573- key,
3574- Expr :: NumberLiteral ( _)
3575- | Expr :: StringLiteral ( _)
3576- | Expr :: BytesLiteral ( _)
3577- | Expr :: BooleanLiteral ( _)
3578- | Expr :: NoneLiteral ( _)
3579- ) ;
3580- let is_attribute = matches ! ( key, Expr :: Attribute ( _) ) ;
3578+ // Step 2: If we have keys to match
3579+ if size > 0 {
3580+ // Validate and compile keys
3581+ let mut seen = std:: collections:: HashSet :: new ( ) ;
3582+ for key in keys {
3583+ let is_attribute = matches ! ( key, Expr :: Attribute ( _) ) ;
3584+ let key_repr = if let Expr :: NumberLiteral ( _)
3585+ | Expr :: StringLiteral ( _)
3586+ | Expr :: BooleanLiteral ( _) = key
3587+ {
3588+ format ! ( "{:?}" , key)
3589+ } else if is_attribute {
3590+ String :: new ( )
3591+ } else {
3592+ return Err ( self . error ( CodegenErrorType :: SyntaxError (
3593+ "mapping pattern keys may only match literals and attribute lookups"
3594+ . to_string ( ) ,
3595+ ) ) ) ;
3596+ } ;
35813597
3582- if is_constant {
3583- let key_repr = format ! ( "{key:?}" ) ;
3584- if seen. contains ( & key_repr) {
3598+ if !key_repr. is_empty ( ) && seen. contains ( & key_repr) {
35853599 return Err ( self . error ( CodegenErrorType :: SyntaxError ( format ! (
3586- "mapping pattern checks duplicate key: {key_repr:?} "
3600+ "mapping pattern checks duplicate key ( {key_repr}) "
35873601 ) ) ) ) ;
35883602 }
3589- seen. insert ( key_repr) ;
3590- } else if !is_attribute {
3591- return Err ( self . error ( CodegenErrorType :: SyntaxError (
3592- "mapping pattern keys may only match literals and attribute lookups"
3593- . to_string ( ) ,
3594- ) ) ) ;
3595- }
3603+ if !key_repr. is_empty ( ) {
3604+ seen. insert ( key_repr) ;
3605+ }
35963606
3597- // Compile key expression
3598- self . compile_expression ( key) ?;
3607+ self . compile_expression ( key) ?;
3608+ }
3609+ // Stack: [subject, key1, key2, ..., keyn]
35993610 }
3600- // Stack: [subject, key1, key2 , ...]
3611+ // Stack: [subject] if size==0, or [subject, key1 , ..., keyn] if size>0
36013612
3602- // Build tuple of keys
3603- emit ! (
3604- self ,
3605- Instruction :: BuildTuple {
3606- size: u32 :: try_from( size) . expect( "too many keys in mapping pattern" )
3607- }
3608- ) ;
3613+ // Build tuple of keys (empty tuple if size==0)
3614+ emit ! ( self , Instruction :: BuildTuple { size } ) ;
36093615 // Stack: [subject, keys_tuple]
36103616
3611- // Step 5: Extract values using MATCH_KEYS
3617+ // Match keys
36123618 emit ! ( self , Instruction :: MatchKeys ) ;
3613- // Stack: [subject, keys_or_none, values_or_none]
3614- // There's now a tuple of keys and a tuple of values on top of the subject
3615- pc. on_top += 2 ;
3619+ // Stack: [subject, keys_tuple, values_or_none]
3620+ pc. on_top += 2 ; // subject and keys_tuple are underneath
36163621
3617- emit ! ( self , Instruction :: CopyItem { index: 1_u32 } ) ; // Copy values_or_none (TOS)
3618- // Stack: [subject, keys_or_none, values_or_none, values_or_none]
3622+ // Check if match succeeded
3623+ emit ! ( self , Instruction :: CopyItem { index: 1_u32 } ) ;
3624+ // Stack: [subject, keys_tuple, values_tuple, values_tuple_copy]
36193625
3626+ // Check if copy is None (consumes the copy like POP_JUMP_IF_NONE)
36203627 self . emit_load_const ( ConstantData :: None ) ;
3621- // Stack: [subject, keys_or_none, values_or_none, values_or_none, None]
3622-
36233628 emit ! (
36243629 self ,
36253630 Instruction :: TestOperation {
3626- op: bytecode :: TestOperator :: IsNot
3631+ op: TestOperator :: IsNot
36273632 }
36283633 ) ;
3629- // Stack: [subject, keys_or_none, values_or_none, bool]
3630-
3631- // If values_or_none is None, fail (need to clean up 3 items: values_or_none, keys_or_none, subject)
3634+ // Stack: [subject, keys_tuple, values_tuple, bool]
36323635 self . jump_to_fail_pop ( pc, JumpOp :: PopJumpIfFalse ) ?;
3633- // Stack: [subject, keys_or_none, values_or_none] (on success)
3636+ // Stack: [subject, keys_tuple, values_tuple]
36343637
3635- // Step 7: Process patterns
3636- if size > 0 {
3637- // Unpack values tuple
3638- emit ! (
3639- self ,
3640- Instruction :: UnpackSequence {
3641- size: u32 :: try_from( size) . expect( "too many values in mapping pattern" )
3642- }
3643- ) ;
3644- // Stack: [subject, keys_or_none, value_n, ..., value_1]
3645- // After UNPACK_SEQUENCE, we have size values on the stack
3646- pc. on_top += size - 1 ;
3638+ // Unpack values (the original values_tuple)
3639+ emit ! ( self , Instruction :: UnpackSequence { size } ) ;
3640+ // Stack after unpack: [subject, keys_tuple, ...unpacked values...]
3641+ pc. on_top += size as usize ; // Unpacked size values, tuple replaced by values
3642+ pc. on_top -= 1 ;
36473643
3648- // Process each pattern with compile_pattern_subpattern
3649- for pattern in patterns. iter ( ) {
3650- pc. on_top -= 1 ;
3651- self . compile_pattern_subpattern ( pattern, pc) ?;
3652- }
3644+ // Step 3: Process matched values
3645+ for i in 0 ..size {
3646+ pc. on_top -= 1 ;
3647+ self . compile_pattern_subpattern ( & patterns[ i as usize ] , pc) ?;
36533648 }
36543649
3655- // After all patterns processed, adjust on_top for subject and keys_or_none
3650+ // After processing subpatterns, adjust on_top
3651+ // CPython: "Whatever happens next should consume the tuple of keys and the subject"
3652+ // Stack currently: [subject, keys_tuple, ...any captured values...]
36563653 pc. on_top -= 2 ;
36573654
3658- // Step 8: Clean up
3659- // If we get this far, it's a match! Whatever happens next should consume
3660- // the tuple of keys and the subject
3655+ // Step 4: Handle rest pattern or cleanup
3656+ if let Some ( rest_name) = star_target {
3657+ // Build rest dict for **rest pattern
3658+ // Stack: [subject, keys_tuple]
3659+
3660+ // Build rest dict exactly
3661+ emit ! ( self , Instruction :: BuildMap { size: 0 } ) ;
3662+ // Stack: [subject, keys_tuple, {}]
3663+ emit ! ( self , Instruction :: Swap { index: 3 } ) ;
3664+ // Stack: [{}, keys_tuple, subject]
3665+ emit ! ( self , Instruction :: DictUpdate { index: 2 } ) ;
3666+ // Stack after DICT_UPDATE: [rest_dict, keys_tuple]
3667+ // DICT_UPDATE consumes source (subject) and leaves dict in place
3668+
3669+ // Unpack keys and delete from rest_dict
3670+ emit ! ( self , Instruction :: UnpackSequence { size } ) ;
3671+ // Stack: [rest_dict, k1, k2, ..., kn] (if size==0, nothing pushed)
3672+
3673+ // Delete each key from rest_dict (skipped when size==0)
3674+ // while (size) { COPY(1 + size--); SWAP(2); DELETE_SUBSCR }
3675+ let mut remaining = size;
3676+ while remaining > 0 {
3677+ // Copy rest_dict which is at position (1 + remaining) from TOS
3678+ emit ! (
3679+ self ,
3680+ Instruction :: CopyItem {
3681+ index: 1 + remaining
3682+ }
3683+ ) ;
3684+ // Stack: [rest_dict, k1, ..., kn, rest_dict]
3685+ emit ! ( self , Instruction :: Swap { index: 2 } ) ;
3686+ // Stack: [rest_dict, k1, ..., kn-1, rest_dict, kn]
3687+ emit ! ( self , Instruction :: DeleteSubscript ) ;
3688+ // Stack: [rest_dict, k1, ..., kn-1] (removed kn from rest_dict)
3689+ remaining -= 1 ;
3690+ }
3691+ // Stack: [rest_dict] (plus any previously stored values)
3692+ // pattern_helper_store_name will handle the rotation correctly
36613693
3662- if let Some ( _star_target ) = star_target {
3663- // TODO: Implement **rest pattern support
3664- // This would involve BUILD_MAP, DICT_UPDATE, etc.
3665- return Err ( self . error ( CodegenErrorType :: SyntaxError (
3666- "**rest pattern in mapping not yet implemented" . to_string ( ) ,
3667- ) ) ) ;
3694+ // Store the rest dict
3695+ self . pattern_helper_store_name ( Some ( rest_name ) , pc ) ? ;
3696+
3697+ // After storing all values, pc.on_top should be 0
3698+ // The values are rotated to the bottom for later storage
3699+ pc . on_top = 0 ;
36683700 } else {
3669- // Pop the tuple of keys
3670- emit ! ( self , Instruction :: Pop ) ;
3671- // Pop the subject
3672- emit ! ( self , Instruction :: Pop ) ;
3701+ // Non-rest pattern: just clean up the stack
3702+
3703+ // Pop them as we're not using them
3704+ emit ! ( self , Instruction :: Pop ) ; // Pop keys_tuple
3705+ emit ! ( self , Instruction :: Pop ) ; // Pop subject
36733706 }
3707+
36743708 Ok ( ( ) )
36753709 }
36763710
@@ -3890,11 +3924,12 @@ impl Compiler {
38903924 Singleton :: False => ConstantData :: Boolean { value : false } ,
38913925 Singleton :: True => ConstantData :: Boolean { value : true } ,
38923926 } ) ;
3893- // Compare using the "Is" operator.
3927+ // Compare using the "Is" operator (identity check, not equality).
3928+ // This is important: False should not match 0, even though False == 0
38943929 emit ! (
38953930 self ,
3896- Instruction :: CompareOperation {
3897- op: bytecode:: ComparisonOperator :: Equal
3931+ Instruction :: TestOperation {
3932+ op: bytecode:: TestOperator :: Is
38983933 }
38993934 ) ;
39003935 // Jump to the failure label if the comparison is false.
@@ -3967,12 +4002,17 @@ impl Compiler {
39674002 self . compile_name ( name, NameUsage :: Store ) ?;
39684003 }
39694004
3970- if let Some ( ref _guard ) = m. guard {
4005+ if let Some ( ref guard ) = m. guard {
39714006 self . ensure_fail_pop ( pattern_context, 0 ) ?;
3972- // TODO: Fix compile jump if call
3973- return Err ( self . error ( CodegenErrorType :: NotImplementedYet ) ) ;
3974- // Jump if the guard fails. We assume that patter_context.fail_pop[0] is the jump target.
3975- // self.compile_jump_if(&m.pattern, &guard, pattern_context.fail_pop[0])?;
4007+ // Compile the guard expression
4008+ self . compile_expression ( guard) ?;
4009+ // If guard is false, jump to fail_pop[0]
4010+ emit ! (
4011+ self ,
4012+ Instruction :: JumpIfFalseOrPop {
4013+ target: pattern_context. fail_pop[ 0 ]
4014+ }
4015+ ) ;
39764016 }
39774017
39784018 if i != case_count - 1 {
@@ -3991,9 +4031,10 @@ impl Compiler {
39914031 } else {
39924032 emit ! ( self , Instruction :: Nop ) ;
39934033 }
3994- if let Some ( ref _guard) = m. guard {
3995- // TODO: Fix compile jump if call
3996- return Err ( self . error ( CodegenErrorType :: NotImplementedYet ) ) ;
4034+ if let Some ( ref guard) = m. guard {
4035+ // Compile guard and jump to end if false
4036+ self . compile_expression ( guard) ?;
4037+ emit ! ( self , Instruction :: JumpIfFalseOrPop { target: end } ) ;
39974038 }
39984039 self . compile_statements ( & m. body ) ?;
39994040 }
0 commit comments