Skip to content

Commit 50c5574

Browse files
committed
more match pattern
1 parent c6d1a57 commit 50c5574

File tree

3 files changed

+331
-153
lines changed

3 files changed

+331
-153
lines changed

compiler/codegen/src/compile.rs

Lines changed: 159 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -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
};
4242
use 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

Comments
 (0)