From 50c557419e9d7066af7bdb5f3d29280587103a1e Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 8 Aug 2025 15:05:59 +0900 Subject: [PATCH 1/8] more match pattern --- compiler/codegen/src/compile.rs | 277 +++++++++++++++------------ extra_tests/snippets/syntax_match.py | 43 ++++- vm/src/frame.rs | 164 +++++++++++++--- 3 files changed, 331 insertions(+), 153 deletions(-) diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index 67a93b4650a..5815f37ec9c 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -36,7 +36,7 @@ use rustpython_compiler_core::{ Mode, OneIndexed, SourceFile, SourceLocation, bytecode::{ self, Arg as OpArgMarker, BinaryOperator, CodeObject, ComparisonOperator, ConstantData, - Instruction, OpArg, OpArgType, UnpackExArgs, + Instruction, OpArg, OpArgType, TestOperator, UnpackExArgs, }, }; use rustpython_wtf8::Wtf8Buf; @@ -3291,7 +3291,7 @@ impl Compiler { // index = len(subject) - (size - i) emit!(self, Instruction::GetLen); self.emit_load_const(ConstantData::Integer { - value: (patterns.len() - 1).into(), + value: (patterns.len() - i).into(), }); // Subtract to compute the correct index. emit!( @@ -3484,12 +3484,23 @@ impl Compiler { // Process each sub-pattern. for subpattern in patterns.iter().chain(kwd_patterns.iter()) { - // Decrement the on_top counter as each sub-pattern is processed - // (on_top should be zero at the end of the algorithm as a sanity check). + // Decrement the on_top counter BEFORE processing each sub-pattern pc.on_top -= 1; - if subpattern.is_wildcard() { + + // Check if this is a true wildcard (underscore pattern without name binding) + let is_true_wildcard = match subpattern { + Pattern::MatchAs(match_as) => { + // Only consider it wildcard if both pattern and name are None (i.e., "_") + match_as.pattern.is_none() && match_as.name.is_none() + } + _ => subpattern.is_wildcard(), + }; + + if is_true_wildcard { emit!(self, Instruction::Pop); + continue; // Don't compile wildcard patterns } + // Compile the subpattern without irrefutability checks. self.compile_pattern_subpattern(subpattern, pc)?; } @@ -3505,172 +3516,195 @@ impl Compiler { let keys = &mapping.keys; let patterns = &mapping.patterns; let size = keys.len(); - let n_patterns = patterns.len(); + let star_target = &mapping.rest; - if size != n_patterns { + // Validate pattern count matches key count + if keys.len() != patterns.len() { return Err(self.error(CodegenErrorType::SyntaxError(format!( - "keys ({size}) / patterns ({n_patterns}) length mismatch in mapping pattern" + "keys ({}) / patterns ({}) length mismatch in mapping pattern", + keys.len(), + patterns.len() )))); } - let star_target = &mapping.rest; + // Validate rest pattern: '_' cannot be used as a rest target + if let Some(rest) = star_target { + if rest.as_str() == "_" { + return Err(self.error(CodegenErrorType::SyntaxError("invalid syntax".to_string()))); + } + } // Step 1: Check if subject is a mapping // Stack: [subject] - // We need to keep the subject on top during the mapping and length checks pc.on_top += 1; emit!(self, Instruction::MatchMapping); - // Stack: [subject, bool] + // Stack: [subject, is_mapping] - // If not a mapping, fail (jump and pop subject) self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse)?; - // Stack: [subject] (on success) + // Stack: [subject] - // Step 2: If empty pattern with no star, consume subject + // Special case: empty pattern {} with no rest if size == 0 && star_target.is_none() { - // If the pattern is just "{}", we're done! Pop the subject: + // If the pattern is just "{}", we're done! Pop the subject pc.on_top -= 1; emit!(self, Instruction::Pop); return Ok(()); } - // Step 3: Check mapping has enough keys - if size != 0 { - // Stack: [subject] + // Length check for patterns with keys + if size > 0 { + // Check if the mapping has at least 'size' keys emit!(self, Instruction::GetLen); - // Stack: [subject, len] self.emit_load_const(ConstantData::Integer { value: size.into() }); - // Stack: [subject, len, size] emit!( self, Instruction::CompareOperation { op: ComparisonOperator::GreaterOrEqual } ); - // Stack: [subject, bool] - - // If not enough keys, fail self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse)?; - // Stack: [subject] } - // Step 4: Build keys tuple - if size.saturating_sub(1) > (i32::MAX as usize) { + // Check for overflow (INT_MAX < size - 1) + if size > (i32::MAX as usize + 1) { return Err(self.error(CodegenErrorType::SyntaxError( "too many sub-patterns in mapping pattern".to_string(), ))); } + #[allow(clippy::cast_possible_truncation)] + let size = size as u32; // checked right before - // Validate and compile keys - // NOTE: RustPython difference - using HashSet for duplicate checking - // CPython uses PySet with actual Python objects - let mut seen = std::collections::HashSet::new(); - - for key in keys.iter() { - // Validate key - let is_constant = matches!( - key, - Expr::NumberLiteral(_) - | Expr::StringLiteral(_) - | Expr::BytesLiteral(_) - | Expr::BooleanLiteral(_) - | Expr::NoneLiteral(_) - ); - let is_attribute = matches!(key, Expr::Attribute(_)); + // Step 2: If we have keys to match + if size > 0 { + // Validate and compile keys + let mut seen = std::collections::HashSet::new(); + for key in keys { + let is_attribute = matches!(key, Expr::Attribute(_)); + let key_repr = if let Expr::NumberLiteral(_) + | Expr::StringLiteral(_) + | Expr::BooleanLiteral(_) = key + { + format!("{:?}", key) + } else if is_attribute { + String::new() + } else { + return Err(self.error(CodegenErrorType::SyntaxError( + "mapping pattern keys may only match literals and attribute lookups" + .to_string(), + ))); + }; - if is_constant { - let key_repr = format!("{key:?}"); - if seen.contains(&key_repr) { + if !key_repr.is_empty() && seen.contains(&key_repr) { return Err(self.error(CodegenErrorType::SyntaxError(format!( - "mapping pattern checks duplicate key: {key_repr:?}" + "mapping pattern checks duplicate key ({key_repr})" )))); } - seen.insert(key_repr); - } else if !is_attribute { - return Err(self.error(CodegenErrorType::SyntaxError( - "mapping pattern keys may only match literals and attribute lookups" - .to_string(), - ))); - } + if !key_repr.is_empty() { + seen.insert(key_repr); + } - // Compile key expression - self.compile_expression(key)?; + self.compile_expression(key)?; + } + // Stack: [subject, key1, key2, ..., keyn] } - // Stack: [subject, key1, key2, ...] + // Stack: [subject] if size==0, or [subject, key1, ..., keyn] if size>0 - // Build tuple of keys - emit!( - self, - Instruction::BuildTuple { - size: u32::try_from(size).expect("too many keys in mapping pattern") - } - ); + // Build tuple of keys (empty tuple if size==0) + emit!(self, Instruction::BuildTuple { size }); // Stack: [subject, keys_tuple] - // Step 5: Extract values using MATCH_KEYS + // Match keys emit!(self, Instruction::MatchKeys); - // Stack: [subject, keys_or_none, values_or_none] - // There's now a tuple of keys and a tuple of values on top of the subject - pc.on_top += 2; + // Stack: [subject, keys_tuple, values_or_none] + pc.on_top += 2; // subject and keys_tuple are underneath - emit!(self, Instruction::CopyItem { index: 1_u32 }); // Copy values_or_none (TOS) - // Stack: [subject, keys_or_none, values_or_none, values_or_none] + // Check if match succeeded + emit!(self, Instruction::CopyItem { index: 1_u32 }); + // Stack: [subject, keys_tuple, values_tuple, values_tuple_copy] + // Check if copy is None (consumes the copy like POP_JUMP_IF_NONE) self.emit_load_const(ConstantData::None); - // Stack: [subject, keys_or_none, values_or_none, values_or_none, None] - emit!( self, Instruction::TestOperation { - op: bytecode::TestOperator::IsNot + op: TestOperator::IsNot } ); - // Stack: [subject, keys_or_none, values_or_none, bool] - - // If values_or_none is None, fail (need to clean up 3 items: values_or_none, keys_or_none, subject) + // Stack: [subject, keys_tuple, values_tuple, bool] self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse)?; - // Stack: [subject, keys_or_none, values_or_none] (on success) + // Stack: [subject, keys_tuple, values_tuple] - // Step 7: Process patterns - if size > 0 { - // Unpack values tuple - emit!( - self, - Instruction::UnpackSequence { - size: u32::try_from(size).expect("too many values in mapping pattern") - } - ); - // Stack: [subject, keys_or_none, value_n, ..., value_1] - // After UNPACK_SEQUENCE, we have size values on the stack - pc.on_top += size - 1; + // Unpack values (the original values_tuple) + emit!(self, Instruction::UnpackSequence { size }); + // Stack after unpack: [subject, keys_tuple, ...unpacked values...] + pc.on_top += size as usize; // Unpacked size values, tuple replaced by values + pc.on_top -= 1; - // Process each pattern with compile_pattern_subpattern - for pattern in patterns.iter() { - pc.on_top -= 1; - self.compile_pattern_subpattern(pattern, pc)?; - } + // Step 3: Process matched values + for i in 0..size { + pc.on_top -= 1; + self.compile_pattern_subpattern(&patterns[i as usize], pc)?; } - // After all patterns processed, adjust on_top for subject and keys_or_none + // After processing subpatterns, adjust on_top + // CPython: "Whatever happens next should consume the tuple of keys and the subject" + // Stack currently: [subject, keys_tuple, ...any captured values...] pc.on_top -= 2; - // Step 8: Clean up - // If we get this far, it's a match! Whatever happens next should consume - // the tuple of keys and the subject + // Step 4: Handle rest pattern or cleanup + if let Some(rest_name) = star_target { + // Build rest dict for **rest pattern + // Stack: [subject, keys_tuple] + + // Build rest dict exactly + emit!(self, Instruction::BuildMap { size: 0 }); + // Stack: [subject, keys_tuple, {}] + emit!(self, Instruction::Swap { index: 3 }); + // Stack: [{}, keys_tuple, subject] + emit!(self, Instruction::DictUpdate { index: 2 }); + // Stack after DICT_UPDATE: [rest_dict, keys_tuple] + // DICT_UPDATE consumes source (subject) and leaves dict in place + + // Unpack keys and delete from rest_dict + emit!(self, Instruction::UnpackSequence { size }); + // Stack: [rest_dict, k1, k2, ..., kn] (if size==0, nothing pushed) + + // Delete each key from rest_dict (skipped when size==0) + // while (size) { COPY(1 + size--); SWAP(2); DELETE_SUBSCR } + let mut remaining = size; + while remaining > 0 { + // Copy rest_dict which is at position (1 + remaining) from TOS + emit!( + self, + Instruction::CopyItem { + index: 1 + remaining + } + ); + // Stack: [rest_dict, k1, ..., kn, rest_dict] + emit!(self, Instruction::Swap { index: 2 }); + // Stack: [rest_dict, k1, ..., kn-1, rest_dict, kn] + emit!(self, Instruction::DeleteSubscript); + // Stack: [rest_dict, k1, ..., kn-1] (removed kn from rest_dict) + remaining -= 1; + } + // Stack: [rest_dict] (plus any previously stored values) + // pattern_helper_store_name will handle the rotation correctly - if let Some(_star_target) = star_target { - // TODO: Implement **rest pattern support - // This would involve BUILD_MAP, DICT_UPDATE, etc. - return Err(self.error(CodegenErrorType::SyntaxError( - "**rest pattern in mapping not yet implemented".to_string(), - ))); + // Store the rest dict + self.pattern_helper_store_name(Some(rest_name), pc)?; + + // After storing all values, pc.on_top should be 0 + // The values are rotated to the bottom for later storage + pc.on_top = 0; } else { - // Pop the tuple of keys - emit!(self, Instruction::Pop); - // Pop the subject - emit!(self, Instruction::Pop); + // Non-rest pattern: just clean up the stack + + // Pop them as we're not using them + emit!(self, Instruction::Pop); // Pop keys_tuple + emit!(self, Instruction::Pop); // Pop subject } + Ok(()) } @@ -3890,11 +3924,12 @@ impl Compiler { Singleton::False => ConstantData::Boolean { value: false }, Singleton::True => ConstantData::Boolean { value: true }, }); - // Compare using the "Is" operator. + // Compare using the "Is" operator (identity check, not equality). + // This is important: False should not match 0, even though False == 0 emit!( self, - Instruction::CompareOperation { - op: bytecode::ComparisonOperator::Equal + Instruction::TestOperation { + op: bytecode::TestOperator::Is } ); // Jump to the failure label if the comparison is false. @@ -3967,12 +4002,17 @@ impl Compiler { self.compile_name(name, NameUsage::Store)?; } - if let Some(ref _guard) = m.guard { + if let Some(ref guard) = m.guard { self.ensure_fail_pop(pattern_context, 0)?; - // TODO: Fix compile jump if call - return Err(self.error(CodegenErrorType::NotImplementedYet)); - // Jump if the guard fails. We assume that patter_context.fail_pop[0] is the jump target. - // self.compile_jump_if(&m.pattern, &guard, pattern_context.fail_pop[0])?; + // Compile the guard expression + self.compile_expression(guard)?; + // If guard is false, jump to fail_pop[0] + emit!( + self, + Instruction::JumpIfFalseOrPop { + target: pattern_context.fail_pop[0] + } + ); } if i != case_count - 1 { @@ -3991,9 +4031,10 @@ impl Compiler { } else { emit!(self, Instruction::Nop); } - if let Some(ref _guard) = m.guard { - // TODO: Fix compile jump if call - return Err(self.error(CodegenErrorType::NotImplementedYet)); + if let Some(ref guard) = m.guard { + // Compile guard and jump to end if false + self.compile_expression(guard)?; + emit!(self, Instruction::JumpIfFalseOrPop { target: end }); } self.compile_statements(&m.body)?; } diff --git a/extra_tests/snippets/syntax_match.py b/extra_tests/snippets/syntax_match.py index be1a94eadb6..8868cb93c38 100644 --- a/extra_tests/snippets/syntax_match.py +++ b/extra_tests/snippets/syntax_match.py @@ -64,18 +64,46 @@ def test_or(i): match data: case {"a": x, "b": y}: + assert x == 1, x + assert y == 2, y + case _: + assert False + +# test mapping with rest +match data: + case {"a": x, **rest}: + assert x == 1 + assert rest == {"b": 2} + case _: + assert False + +# test empty rest +data2 = {"a": 1} +match data2: + case {"a": x, **rest}: + assert x == 1 + assert rest == {} + case _: + assert False + +# test rest with multiple keys +data3 = {"a": 1, "b": 2, "c": 3, "d": 4} +match data3: + case {"a": x, "b": y, **rest}: assert x == 1 assert y == 2 + assert rest == {"c": 3, "d": 4} case _: assert False -# test mapping with rest (TODO: implement **rest pattern) -# match data: -# case {"a": x, **rest}: -# assert x == 1 -# assert rest == {"b": 2} -# case _: -# assert False +match data3: + case {"a": x, "b": y, "c": z, **rest}: + assert x == 1 + assert y == 2 + assert z == 3 + assert rest == {"d": 4} + case _: + assert False # test mapping pattern with wildcard fallback (reproduces wheelinfo.py issue) test_dict = {"sha256": "abc123"} @@ -118,7 +146,6 @@ def test_mapping_comprehensive(): cap_x = cap_y = None assert cap_x == 1, f"Expected x=1, got {cap_x}" assert cap_y == 2, f"Expected y=2, got {cap_y}" - print("Comprehensive mapping tests passed!") test_mapping_comprehensive() diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 14e4fabd6d9..d664804eb25 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -1,5 +1,6 @@ use crate::common::{boxvec::BoxVec, lock::PyMutex}; use crate::protocol::PyMapping; +use crate::types::PyTypeFlags; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, builtins::{ @@ -701,10 +702,19 @@ impl ExecutingFrame<'_> { } bytecode::Instruction::Swap { index } => { let len = self.state.stack.len(); + if len == 0 { + return Err(vm.new_runtime_error("stack underflow in SWAP".to_string())); + } let i = len - 1; // TOS index let index_val = index.get(arg) as usize; // CPython: SWAP(n) swaps TOS with PEEK(n) where PEEK(n) = stack_pointer[-n] // This means swap TOS with the element at index (len - n) + if index_val > len { + return Err(vm.new_runtime_error(format!( + "SWAP index {} exceeds stack size {}", + index_val, len + ))); + } let j = len - index_val; self.state.stack.swap(i, j); Ok(None) @@ -1296,7 +1306,10 @@ impl ExecutingFrame<'_> { bytecode::Instruction::MatchMapping => { // Pop and push back the subject to keep it on stack let subject = self.pop_value(); - let is_mapping = PyMapping::check(&subject); + + // Check if the type has the MAPPING flag + let is_mapping = subject.class().slots.flags.contains(PyTypeFlags::MAPPING); + self.push_value(subject); self.push_value(vm.ctx.new_bool(is_mapping).into()); Ok(None) @@ -1304,18 +1317,18 @@ impl ExecutingFrame<'_> { bytecode::Instruction::MatchSequence => { // Pop and push back the subject to keep it on stack let subject = self.pop_value(); - let is_sequence = subject.to_sequence().check(); + + // Check if the type has the SEQUENCE flag + let is_sequence = subject.class().slots.flags.contains(PyTypeFlags::SEQUENCE); + self.push_value(subject); self.push_value(vm.ctx.new_bool(is_sequence).into()); Ok(None) } bytecode::Instruction::MatchKeys => { - // Pop keys tuple and subject - let keys_tuple = self.pop_value(); - let subject = self.pop_value(); - - // Push the subject back first - self.push_value(subject.clone()); + // MATCH_KEYS doesn't pop subject and keys, only reads them + let keys_tuple = self.top_value(); // stack[-1] + let subject = self.nth_value(1); // stack[-2] // Check if subject is a mapping and extract values for keys if PyMapping::check(&subject) { @@ -1334,42 +1347,139 @@ impl ExecutingFrame<'_> { } if all_match { - // Push keys_or_none (the original keys) and values_or_none - // Keys should remain as they were for potential **rest handling - self.push_value(keys_tuple); // keys_or_none - self.push_value(vm.ctx.new_tuple(values).into()); // values_or_none + // Push values tuple on successful match + self.push_value(vm.ctx.new_tuple(values).into()); } else { - // No match - push None twice - self.push_value(vm.ctx.none()); + // No match - push None self.push_value(vm.ctx.none()); } } else { - // Not a mapping - push None twice - self.push_value(vm.ctx.none()); + // Not a mapping - push None self.push_value(vm.ctx.none()); } Ok(None) } - bytecode::Instruction::MatchClass(_arg) => { + bytecode::Instruction::MatchClass(nargs) => { // STACK[-1] is a tuple of keyword attribute names, STACK[-2] is the class being matched against, and STACK[-3] is the match subject. - // count is the number of positional sub-patterns. - // Pop STACK[-1], STACK[-2], and STACK[-3]. - let names = self.pop_value(); - let names = names.downcast_ref::().unwrap(); + // nargs is the number of positional sub-patterns. + let kwd_attrs = self.pop_value(); + let kwd_attrs = kwd_attrs.downcast_ref::().unwrap(); let cls = self.pop_value(); let subject = self.pop_value(); - // If STACK[-3] is an instance of STACK[-2] and has the positional and keyword attributes required by count and STACK[-1], - // push a tuple of extracted attributes. + let nargs_val = nargs.get(arg) as usize; + + // Check if subject is an instance of cls if subject.is_instance(cls.as_ref(), vm)? { let mut extracted = vec![]; - for name in names { + + // Get __match_args__ for positional arguments if nargs > 0 + if nargs_val > 0 { + // Get __match_args__ from the class + let match_args = + vm.get_attribute_opt(cls.clone(), identifier!(vm, __match_args__))?; + + if let Some(match_args) = match_args { + // Convert to tuple + let match_args = match match_args.downcast_exact::(vm) { + Ok(tuple) => tuple, + Err(match_args) => { + // __match_args__ must be a tuple + // Get type names for error message + let type_name = cls + .downcast::() + .map(|t| t.__name__(vm).as_str().to_owned()) + .unwrap_or_else(|_| String::from("?")); + let match_args_type_name = match_args.class().__name__(vm); + return Err(vm.new_type_error(format!( + "{}.__match_args__ must be a tuple (got {})", + type_name, match_args_type_name + ))); + } + }; + + // Check if we have enough match args + if match_args.len() < nargs_val { + return Err(vm.new_type_error(format!( + "class pattern accepts at most {} positional sub-patterns ({} given)", + match_args.len(), + nargs_val + ))); + } + + // Extract positional attributes + for i in 0..nargs_val { + let attr_name = &match_args[i]; + let attr_name_str = match attr_name.downcast_ref::() { + Some(s) => s, + None => { + return Err(vm.new_type_error( + "__match_args__ elements must be strings".to_string(), + )); + } + }; + match subject.get_attr(attr_name_str, vm) { + Ok(value) => extracted.push(value), + Err(e) + if e.fast_isinstance(vm.ctx.exceptions.attribute_error) => + { + // Missing attribute → non-match + self.push_value(vm.ctx.none()); + return Ok(None); + } + Err(e) => return Err(e), + } + } + } else { + // No __match_args__, check if this is a type with MATCH_SELF behavior + // For built-in types like bool, int, str, list, tuple, dict, etc. + // they match the subject itself as the single positional argument + let is_match_self_type = cls.is(vm.ctx.types.bool_type) + || cls.is(vm.ctx.types.int_type) + || cls.is(vm.ctx.types.float_type) + || cls.is(vm.ctx.types.str_type) + || cls.is(vm.ctx.types.bytes_type) + || cls.is(vm.ctx.types.bytearray_type) + || cls.is(vm.ctx.types.list_type) + || cls.is(vm.ctx.types.tuple_type) + || cls.is(vm.ctx.types.dict_type) + || cls.is(vm.ctx.types.set_type) + || cls.is(vm.ctx.types.frozenset_type); + + if is_match_self_type { + if nargs_val == 1 { + // Match the subject itself as the single positional argument + extracted.push(subject.clone()); + } else if nargs_val > 1 { + // Too many positional arguments for MATCH_SELF + self.push_value(vm.ctx.none()); + return Ok(None); + } + } else { + // No __match_args__ and not a MATCH_SELF type + if nargs_val > 0 { + self.push_value(vm.ctx.none()); + return Ok(None); + } + } + } + } + + // Extract keyword attributes + for name in kwd_attrs { let name_str = name.downcast_ref::().unwrap(); - let value = subject.get_attr(name_str, vm)?; - extracted.push(value); + match subject.get_attr(name_str, vm) { + Ok(value) => extracted.push(value), + Err(_) => { + // Attribute doesn't exist + self.push_value(vm.ctx.none()); + return Ok(None); + } + } } + self.push_value(vm.ctx.new_tuple(extracted).into()); } else { - // Otherwise, push None. + // Not an instance, push None self.push_value(vm.ctx.none()); } Ok(None) From 8ae2dc75f6d6720b252a462e0cf91ddc1524db36 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 26 Aug 2025 12:15:45 +0900 Subject: [PATCH 2/8] MATCH_SELF --- compiler/codegen/src/compile.rs | 14 +++++++------- vm/src/builtins/bool.rs | 2 +- vm/src/builtins/bytearray.rs | 2 +- vm/src/builtins/bytes.rs | 2 +- vm/src/builtins/dict.rs | 2 +- vm/src/builtins/float.rs | 2 +- vm/src/builtins/int.rs | 2 +- vm/src/builtins/list.rs | 2 +- vm/src/builtins/set.rs | 4 ++-- vm/src/builtins/str.rs | 2 +- vm/src/builtins/tuple.rs | 2 +- vm/src/frame.rs | 30 ++++++++++-------------------- vm/src/types/slot.rs | 4 ++++ 13 files changed, 32 insertions(+), 38 deletions(-) diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index 5815f37ec9c..fe72b523870 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -36,7 +36,7 @@ use rustpython_compiler_core::{ Mode, OneIndexed, SourceFile, SourceLocation, bytecode::{ self, Arg as OpArgMarker, BinaryOperator, CodeObject, ComparisonOperator, ConstantData, - Instruction, OpArg, OpArgType, TestOperator, UnpackExArgs, + Instruction, OpArg, OpArgType, UnpackExArgs, }, }; use rustpython_wtf8::Wtf8Buf; @@ -3484,7 +3484,7 @@ impl Compiler { // Process each sub-pattern. for subpattern in patterns.iter().chain(kwd_patterns.iter()) { - // Decrement the on_top counter BEFORE processing each sub-pattern + // Decrement the on_top counter before processing each sub-pattern pc.on_top -= 1; // Check if this is a true wildcard (underscore pattern without name binding) @@ -3557,6 +3557,7 @@ impl Compiler { // Check if the mapping has at least 'size' keys emit!(self, Instruction::GetLen); self.emit_load_const(ConstantData::Integer { value: size.into() }); + // Stack: [subject, len, size] emit!( self, Instruction::CompareOperation { @@ -3564,6 +3565,7 @@ impl Compiler { } ); self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse)?; + // Stack: [subject] } // Check for overflow (INT_MAX < size - 1) @@ -3606,9 +3608,8 @@ impl Compiler { self.compile_expression(key)?; } - // Stack: [subject, key1, key2, ..., keyn] } - // Stack: [subject] if size==0, or [subject, key1, ..., keyn] if size>0 + // Stack: [subject, key1, key2, ..., key_n] // Build tuple of keys (empty tuple if size==0) emit!(self, Instruction::BuildTuple { size }); @@ -3628,7 +3629,7 @@ impl Compiler { emit!( self, Instruction::TestOperation { - op: TestOperator::IsNot + op: bytecode::TestOperator::IsNot } ); // Stack: [subject, keys_tuple, values_tuple, bool] @@ -3924,8 +3925,7 @@ impl Compiler { Singleton::False => ConstantData::Boolean { value: false }, Singleton::True => ConstantData::Boolean { value: true }, }); - // Compare using the "Is" operator (identity check, not equality). - // This is important: False should not match 0, even though False == 0 + // Compare using the "Is" operator. emit!( self, Instruction::TestOperation { diff --git a/vm/src/builtins/bool.rs b/vm/src/builtins/bool.rs index a7569e2f889..a20e821d2d1 100644 --- a/vm/src/builtins/bool.rs +++ b/vm/src/builtins/bool.rs @@ -109,7 +109,7 @@ impl Constructor for PyBool { } } -#[pyclass(with(Constructor, AsNumber, Representable))] +#[pyclass(with(Constructor, AsNumber, Representable), flags(_MATCH_SELF))] impl PyBool { #[pymethod] fn __format__(obj: PyObjectRef, spec: PyStrRef, vm: &VirtualMachine) -> PyResult { diff --git a/vm/src/builtins/bytearray.rs b/vm/src/builtins/bytearray.rs index 2d3bd1ebfc3..32eaa2b3e27 100644 --- a/vm/src/builtins/bytearray.rs +++ b/vm/src/builtins/bytearray.rs @@ -170,7 +170,7 @@ impl PyByteArray { } #[pyclass( - flags(BASETYPE), + flags(BASETYPE, _MATCH_SELF), with( Py, PyRef, diff --git a/vm/src/builtins/bytes.rs b/vm/src/builtins/bytes.rs index 6d77a3734a5..a06588cc8f2 100644 --- a/vm/src/builtins/bytes.rs +++ b/vm/src/builtins/bytes.rs @@ -132,7 +132,7 @@ impl PyRef { } #[pyclass( - flags(BASETYPE), + flags(BASETYPE, _MATCH_SELF), with( Py, PyRef, diff --git a/vm/src/builtins/dict.rs b/vm/src/builtins/dict.rs index 2f1c91323c5..f9911ce033b 100644 --- a/vm/src/builtins/dict.rs +++ b/vm/src/builtins/dict.rs @@ -176,7 +176,7 @@ impl PyDict { AsMapping, Representable ), - flags(BASETYPE, MAPPING) + flags(BASETYPE, MAPPING, _MATCH_SELF) )] impl PyDict { #[pyclassmethod] diff --git a/vm/src/builtins/float.rs b/vm/src/builtins/float.rs index b213be3a683..ad18db36a43 100644 --- a/vm/src/builtins/float.rs +++ b/vm/src/builtins/float.rs @@ -201,7 +201,7 @@ fn float_from_string(val: PyObjectRef, vm: &VirtualMachine) -> PyResult { } #[pyclass( - flags(BASETYPE), + flags(BASETYPE, _MATCH_SELF), with(Comparable, Hashable, Constructor, AsNumber, Representable) )] impl PyFloat { diff --git a/vm/src/builtins/int.rs b/vm/src/builtins/int.rs index 9b38de49951..70fd9bad9b5 100644 --- a/vm/src/builtins/int.rs +++ b/vm/src/builtins/int.rs @@ -317,7 +317,7 @@ impl PyInt { } #[pyclass( - flags(BASETYPE), + flags(BASETYPE, _MATCH_SELF), with(PyRef, Comparable, Hashable, Constructor, AsNumber, Representable) )] impl PyInt { diff --git a/vm/src/builtins/list.rs b/vm/src/builtins/list.rs index 14c8341e44d..13f29de401b 100644 --- a/vm/src/builtins/list.rs +++ b/vm/src/builtins/list.rs @@ -109,7 +109,7 @@ pub type PyListRef = PyRef; AsSequence, Representable ), - flags(BASETYPE, SEQUENCE) + flags(BASETYPE, SEQUENCE, _MATCH_SELF) )] impl PyList { #[pymethod] diff --git a/vm/src/builtins/set.rs b/vm/src/builtins/set.rs index b68a51cb33e..53ebc931a74 100644 --- a/vm/src/builtins/set.rs +++ b/vm/src/builtins/set.rs @@ -529,7 +529,7 @@ fn reduce_set( AsNumber, Representable ), - flags(BASETYPE) + flags(BASETYPE, _MATCH_SELF) )] impl PySet { #[pymethod] @@ -946,7 +946,7 @@ impl Constructor for PyFrozenSet { } #[pyclass( - flags(BASETYPE), + flags(BASETYPE, _MATCH_SELF), with( Constructor, AsSequence, diff --git a/vm/src/builtins/str.rs b/vm/src/builtins/str.rs index 641bf1e428b..ddf04e6deb2 100644 --- a/vm/src/builtins/str.rs +++ b/vm/src/builtins/str.rs @@ -521,7 +521,7 @@ impl Py { } #[pyclass( - flags(BASETYPE), + flags(BASETYPE, _MATCH_SELF), with( PyRef, AsMapping, diff --git a/vm/src/builtins/tuple.rs b/vm/src/builtins/tuple.rs index abd7bf71de9..f6a6dde7914 100644 --- a/vm/src/builtins/tuple.rs +++ b/vm/src/builtins/tuple.rs @@ -244,7 +244,7 @@ impl PyTuple> { } #[pyclass( - flags(BASETYPE, SEQUENCE), + flags(BASETYPE, SEQUENCE, _MATCH_SELF), with( AsMapping, AsSequence, diff --git a/vm/src/frame.rs b/vm/src/frame.rs index d664804eb25..7f43625d506 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -702,19 +702,17 @@ impl ExecutingFrame<'_> { } bytecode::Instruction::Swap { index } => { let len = self.state.stack.len(); - if len == 0 { - return Err(vm.new_runtime_error("stack underflow in SWAP".to_string())); - } + debug_assert!(len > 0, "stack underflow in SWAP"); let i = len - 1; // TOS index let index_val = index.get(arg) as usize; // CPython: SWAP(n) swaps TOS with PEEK(n) where PEEK(n) = stack_pointer[-n] // This means swap TOS with the element at index (len - n) - if index_val > len { - return Err(vm.new_runtime_error(format!( - "SWAP index {} exceeds stack size {}", - index_val, len - ))); - } + debug_assert!( + index_val <= len, + "SWAP index {} exceeds stack size {}", + index_val, + len + ); let j = len - index_val; self.state.stack.swap(i, j); Ok(None) @@ -1433,17 +1431,9 @@ impl ExecutingFrame<'_> { // No __match_args__, check if this is a type with MATCH_SELF behavior // For built-in types like bool, int, str, list, tuple, dict, etc. // they match the subject itself as the single positional argument - let is_match_self_type = cls.is(vm.ctx.types.bool_type) - || cls.is(vm.ctx.types.int_type) - || cls.is(vm.ctx.types.float_type) - || cls.is(vm.ctx.types.str_type) - || cls.is(vm.ctx.types.bytes_type) - || cls.is(vm.ctx.types.bytearray_type) - || cls.is(vm.ctx.types.list_type) - || cls.is(vm.ctx.types.tuple_type) - || cls.is(vm.ctx.types.dict_type) - || cls.is(vm.ctx.types.set_type) - || cls.is(vm.ctx.types.frozenset_type); + let is_match_self_type = cls + .downcast::() + .is_ok_and(|t| t.slots.flags.contains(PyTypeFlags::_MATCH_SELF)); if is_match_self_type { if nargs_val == 1 { diff --git a/vm/src/types/slot.rs b/vm/src/types/slot.rs index 182f902bc7d..44e46fdb5ab 100644 --- a/vm/src/types/slot.rs +++ b/vm/src/types/slot.rs @@ -129,6 +129,10 @@ bitflags! { const HEAPTYPE = 1 << 9; const BASETYPE = 1 << 10; const METHOD_DESCRIPTOR = 1 << 17; + // For built-in types that match the subject itself in pattern matching + // (bool, int, float, str, bytes, bytearray, list, tuple, dict, set, frozenset) + // This is not a stable API + const _MATCH_SELF = 1 << 22; const HAS_DICT = 1 << 40; #[cfg(debug_assertions)] From a109a596c8703d7350fc448fa5ab6cf9b71483ed Mon Sep 17 00:00:00 2001 From: CPython Developers <> Date: Tue, 26 Aug 2025 12:22:22 +0900 Subject: [PATCH 3/8] Import test_patma from CPython 3.13.7 --- Lib/test/test_patma.py | 3550 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3550 insertions(+) create mode 100644 Lib/test/test_patma.py diff --git a/Lib/test/test_patma.py b/Lib/test/test_patma.py new file mode 100644 index 00000000000..8325b83a593 --- /dev/null +++ b/Lib/test/test_patma.py @@ -0,0 +1,3550 @@ +import array +import collections +import dataclasses +import dis +import enum +import inspect +import sys +import unittest + + +@dataclasses.dataclass +class Point: + x: int + y: int + + +class TestCompiler(unittest.TestCase): + + def test_refleaks(self): + # Hunting for leaks using -R doesn't catch leaks in the compiler itself, + # just the code under test. This test ensures that if there are leaks in + # the pattern compiler, those runs will fail: + with open(__file__) as file: + compile(file.read(), __file__, "exec") + + +class TestInheritance(unittest.TestCase): + + @staticmethod + def check_sequence_then_mapping(x): + match x: + case [*_]: + return "seq" + case {}: + return "map" + + @staticmethod + def check_mapping_then_sequence(x): + match x: + case {}: + return "map" + case [*_]: + return "seq" + + def test_multiple_inheritance_mapping(self): + class C: + pass + class M1(collections.UserDict, collections.abc.Sequence): + pass + class M2(C, collections.UserDict, collections.abc.Sequence): + pass + class M3(collections.UserDict, C, list): + pass + class M4(dict, collections.abc.Sequence, C): + pass + self.assertEqual(self.check_sequence_then_mapping(M1()), "map") + self.assertEqual(self.check_sequence_then_mapping(M2()), "map") + self.assertEqual(self.check_sequence_then_mapping(M3()), "map") + self.assertEqual(self.check_sequence_then_mapping(M4()), "map") + self.assertEqual(self.check_mapping_then_sequence(M1()), "map") + self.assertEqual(self.check_mapping_then_sequence(M2()), "map") + self.assertEqual(self.check_mapping_then_sequence(M3()), "map") + self.assertEqual(self.check_mapping_then_sequence(M4()), "map") + + def test_multiple_inheritance_sequence(self): + class C: + pass + class S1(collections.UserList, collections.abc.Mapping): + pass + class S2(C, collections.UserList, collections.abc.Mapping): + pass + class S3(list, C, collections.abc.Mapping): + pass + class S4(collections.UserList, dict, C): + pass + self.assertEqual(self.check_sequence_then_mapping(S1()), "seq") + self.assertEqual(self.check_sequence_then_mapping(S2()), "seq") + self.assertEqual(self.check_sequence_then_mapping(S3()), "seq") + self.assertEqual(self.check_sequence_then_mapping(S4()), "seq") + self.assertEqual(self.check_mapping_then_sequence(S1()), "seq") + self.assertEqual(self.check_mapping_then_sequence(S2()), "seq") + self.assertEqual(self.check_mapping_then_sequence(S3()), "seq") + self.assertEqual(self.check_mapping_then_sequence(S4()), "seq") + + def test_late_registration_mapping(self): + class Parent: + pass + class ChildPre(Parent): + pass + class GrandchildPre(ChildPre): + pass + collections.abc.Mapping.register(Parent) + class ChildPost(Parent): + pass + class GrandchildPost(ChildPost): + pass + self.assertEqual(self.check_sequence_then_mapping(Parent()), "map") + self.assertEqual(self.check_sequence_then_mapping(ChildPre()), "map") + self.assertEqual(self.check_sequence_then_mapping(GrandchildPre()), "map") + self.assertEqual(self.check_sequence_then_mapping(ChildPost()), "map") + self.assertEqual(self.check_sequence_then_mapping(GrandchildPost()), "map") + self.assertEqual(self.check_mapping_then_sequence(Parent()), "map") + self.assertEqual(self.check_mapping_then_sequence(ChildPre()), "map") + self.assertEqual(self.check_mapping_then_sequence(GrandchildPre()), "map") + self.assertEqual(self.check_mapping_then_sequence(ChildPost()), "map") + self.assertEqual(self.check_mapping_then_sequence(GrandchildPost()), "map") + + def test_late_registration_sequence(self): + class Parent: + pass + class ChildPre(Parent): + pass + class GrandchildPre(ChildPre): + pass + collections.abc.Sequence.register(Parent) + class ChildPost(Parent): + pass + class GrandchildPost(ChildPost): + pass + self.assertEqual(self.check_sequence_then_mapping(Parent()), "seq") + self.assertEqual(self.check_sequence_then_mapping(ChildPre()), "seq") + self.assertEqual(self.check_sequence_then_mapping(GrandchildPre()), "seq") + self.assertEqual(self.check_sequence_then_mapping(ChildPost()), "seq") + self.assertEqual(self.check_sequence_then_mapping(GrandchildPost()), "seq") + self.assertEqual(self.check_mapping_then_sequence(Parent()), "seq") + self.assertEqual(self.check_mapping_then_sequence(ChildPre()), "seq") + self.assertEqual(self.check_mapping_then_sequence(GrandchildPre()), "seq") + self.assertEqual(self.check_mapping_then_sequence(ChildPost()), "seq") + self.assertEqual(self.check_mapping_then_sequence(GrandchildPost()), "seq") + + +class TestPatma(unittest.TestCase): + + def test_patma_000(self): + match 0: + case 0: + x = True + self.assertIs(x, True) + + def test_patma_001(self): + match 0: + case 0 if False: + x = False + case 0 if True: + x = True + self.assertIs(x, True) + + def test_patma_002(self): + match 0: + case 0: + x = True + case 0: + x = False + self.assertIs(x, True) + + def test_patma_003(self): + x = False + match 0: + case 0 | 1 | 2 | 3: + x = True + self.assertIs(x, True) + + def test_patma_004(self): + x = False + match 1: + case 0 | 1 | 2 | 3: + x = True + self.assertIs(x, True) + + def test_patma_005(self): + x = False + match 2: + case 0 | 1 | 2 | 3: + x = True + self.assertIs(x, True) + + def test_patma_006(self): + x = False + match 3: + case 0 | 1 | 2 | 3: + x = True + self.assertIs(x, True) + + def test_patma_007(self): + x = False + match 4: + case 0 | 1 | 2 | 3: + x = True + self.assertIs(x, False) + + def test_patma_008(self): + x = 0 + class A: + y = 1 + match x: + case A.y as z: + pass + self.assertEqual(x, 0) + self.assertEqual(A.y, 1) + + def test_patma_009(self): + class A: + B = 0 + match 0: + case x if x: + z = 0 + case _ as y if y == x and y: + z = 1 + case A.B: + z = 2 + self.assertEqual(A.B, 0) + self.assertEqual(x, 0) + self.assertEqual(y, 0) + self.assertEqual(z, 2) + + def test_patma_010(self): + match (): + case []: + x = 0 + self.assertEqual(x, 0) + + def test_patma_011(self): + match (0, 1, 2): + case [*x]: + y = 0 + self.assertEqual(x, [0, 1, 2]) + self.assertEqual(y, 0) + + def test_patma_012(self): + match (0, 1, 2): + case [0, *x]: + y = 0 + self.assertEqual(x, [1, 2]) + self.assertEqual(y, 0) + + def test_patma_013(self): + match (0, 1, 2): + case [0, 1, *x,]: + y = 0 + self.assertEqual(x, [2]) + self.assertEqual(y, 0) + + def test_patma_014(self): + match (0, 1, 2): + case [0, 1, 2, *x]: + y = 0 + self.assertEqual(x, []) + self.assertEqual(y, 0) + + def test_patma_015(self): + match (0, 1, 2): + case [*x, 2,]: + y = 0 + self.assertEqual(x, [0, 1]) + self.assertEqual(y, 0) + + def test_patma_016(self): + match (0, 1, 2): + case [*x, 1, 2]: + y = 0 + self.assertEqual(x, [0]) + self.assertEqual(y, 0) + + def test_patma_017(self): + match (0, 1, 2): + case [*x, 0, 1, 2,]: + y = 0 + self.assertEqual(x, []) + self.assertEqual(y, 0) + + def test_patma_018(self): + match (0, 1, 2): + case [0, *x, 2]: + y = 0 + self.assertEqual(x, [1]) + self.assertEqual(y, 0) + + def test_patma_019(self): + match (0, 1, 2): + case [0, 1, *x, 2,]: + y = 0 + self.assertEqual(x, []) + self.assertEqual(y, 0) + + def test_patma_020(self): + match (0, 1, 2): + case [0, *x, 1, 2]: + y = 0 + self.assertEqual(x, []) + self.assertEqual(y, 0) + + def test_patma_021(self): + match (0, 1, 2): + case [*x,]: + y = 0 + self.assertEqual(x, [0, 1, 2]) + self.assertEqual(y, 0) + + def test_patma_022(self): + x = {} + match x: + case {}: + y = 0 + self.assertEqual(x, {}) + self.assertEqual(y, 0) + + def test_patma_023(self): + x = {0: 0} + match x: + case {}: + y = 0 + self.assertEqual(x, {0: 0}) + self.assertEqual(y, 0) + + def test_patma_024(self): + x = {} + y = None + match x: + case {0: 0}: + y = 0 + self.assertEqual(x, {}) + self.assertIs(y, None) + + def test_patma_025(self): + x = {0: 0} + match x: + case {0: (0 | 1 | 2 as z)}: + y = 0 + self.assertEqual(x, {0: 0}) + self.assertEqual(y, 0) + self.assertEqual(z, 0) + + def test_patma_026(self): + x = {0: 1} + match x: + case {0: (0 | 1 | 2 as z)}: + y = 0 + self.assertEqual(x, {0: 1}) + self.assertEqual(y, 0) + self.assertEqual(z, 1) + + def test_patma_027(self): + x = {0: 2} + match x: + case {0: (0 | 1 | 2 as z)}: + y = 0 + self.assertEqual(x, {0: 2}) + self.assertEqual(y, 0) + self.assertEqual(z, 2) + + def test_patma_028(self): + x = {0: 3} + y = None + match x: + case {0: (0 | 1 | 2 as z)}: + y = 0 + self.assertEqual(x, {0: 3}) + self.assertIs(y, None) + + def test_patma_029(self): + x = {} + y = None + match x: + case {0: [1, 2, {}]}: + y = 0 + case {0: [1, 2, {}], 1: [[]]}: + y = 1 + case []: + y = 2 + self.assertEqual(x, {}) + self.assertIs(y, None) + + def test_patma_030(self): + x = {False: (True, 2.0, {})} + match x: + case {0: [1, 2, {}]}: + y = 0 + case {0: [1, 2, {}], 1: [[]]}: + y = 1 + case []: + y = 2 + self.assertEqual(x, {False: (True, 2.0, {})}) + self.assertEqual(y, 0) + + def test_patma_031(self): + x = {False: (True, 2.0, {}), 1: [[]], 2: 0} + match x: + case {0: [1, 2, {}]}: + y = 0 + case {0: [1, 2, {}], 1: [[]]}: + y = 1 + case []: + y = 2 + self.assertEqual(x, {False: (True, 2.0, {}), 1: [[]], 2: 0}) + self.assertEqual(y, 0) + + def test_patma_032(self): + x = {False: (True, 2.0, {}), 1: [[]], 2: 0} + match x: + case {0: [1, 2]}: + y = 0 + case {0: [1, 2, {}], 1: [[]]}: + y = 1 + case []: + y = 2 + self.assertEqual(x, {False: (True, 2.0, {}), 1: [[]], 2: 0}) + self.assertEqual(y, 1) + + def test_patma_033(self): + x = [] + match x: + case {0: [1, 2, {}]}: + y = 0 + case {0: [1, 2, {}], 1: [[]]}: + y = 1 + case []: + y = 2 + self.assertEqual(x, []) + self.assertEqual(y, 2) + + def test_patma_034(self): + x = {0: 0} + match x: + case {0: [1, 2, {}]}: + y = 0 + case {0: ([1, 2, {}] | False)} | {1: [[]]} | {0: [1, 2, {}]} | [] | "X" | {}: + y = 1 + case []: + y = 2 + self.assertEqual(x, {0: 0}) + self.assertEqual(y, 1) + + def test_patma_035(self): + x = {0: 0} + match x: + case {0: [1, 2, {}]}: + y = 0 + case {0: [1, 2, {}] | True} | {1: [[]]} | {0: [1, 2, {}]} | [] | "X" | {}: + y = 1 + case []: + y = 2 + self.assertEqual(x, {0: 0}) + self.assertEqual(y, 1) + + def test_patma_036(self): + x = 0 + match x: + case 0 | 1 | 2: + y = 0 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + + def test_patma_037(self): + x = 1 + match x: + case 0 | 1 | 2: + y = 0 + self.assertEqual(x, 1) + self.assertEqual(y, 0) + + def test_patma_038(self): + x = 2 + match x: + case 0 | 1 | 2: + y = 0 + self.assertEqual(x, 2) + self.assertEqual(y, 0) + + def test_patma_039(self): + x = 3 + y = None + match x: + case 0 | 1 | 2: + y = 0 + self.assertEqual(x, 3) + self.assertIs(y, None) + + def test_patma_040(self): + x = 0 + match x: + case (0 as z) | (1 as z) | (2 as z) if z == x % 2: + y = 0 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + self.assertEqual(z, 0) + + def test_patma_041(self): + x = 1 + match x: + case (0 as z) | (1 as z) | (2 as z) if z == x % 2: + y = 0 + self.assertEqual(x, 1) + self.assertEqual(y, 0) + self.assertEqual(z, 1) + + def test_patma_042(self): + x = 2 + y = None + match x: + case (0 as z) | (1 as z) | (2 as z) if z == x % 2: + y = 0 + self.assertEqual(x, 2) + self.assertIs(y, None) + self.assertEqual(z, 2) + + def test_patma_043(self): + x = 3 + y = None + match x: + case (0 as z) | (1 as z) | (2 as z) if z == x % 2: + y = 0 + self.assertEqual(x, 3) + self.assertIs(y, None) + + def test_patma_044(self): + x = () + match x: + case []: + y = 0 + self.assertEqual(x, ()) + self.assertEqual(y, 0) + + def test_patma_045(self): + x = () + match x: + case (): + y = 0 + self.assertEqual(x, ()) + self.assertEqual(y, 0) + + def test_patma_046(self): + x = (0,) + match x: + case [0]: + y = 0 + self.assertEqual(x, (0,)) + self.assertEqual(y, 0) + + def test_patma_047(self): + x = ((),) + match x: + case [[]]: + y = 0 + self.assertEqual(x, ((),)) + self.assertEqual(y, 0) + + def test_patma_048(self): + x = [0, 1] + match x: + case [0, 1] | [1, 0]: + y = 0 + self.assertEqual(x, [0, 1]) + self.assertEqual(y, 0) + + def test_patma_049(self): + x = [1, 0] + match x: + case [0, 1] | [1, 0]: + y = 0 + self.assertEqual(x, [1, 0]) + self.assertEqual(y, 0) + + def test_patma_050(self): + x = [0, 0] + y = None + match x: + case [0, 1] | [1, 0]: + y = 0 + self.assertEqual(x, [0, 0]) + self.assertIs(y, None) + + def test_patma_051(self): + w = None + x = [1, 0] + match x: + case [(0 as w)]: + y = 0 + case [z] | [1, (0 | 1 as z)] | [z]: + y = 1 + self.assertIs(w, None) + self.assertEqual(x, [1, 0]) + self.assertEqual(y, 1) + self.assertEqual(z, 0) + + def test_patma_052(self): + x = [1, 0] + match x: + case [0]: + y = 0 + case [1, 0] if (x := x[:0]): + y = 1 + case [1, 0]: + y = 2 + self.assertEqual(x, []) + self.assertEqual(y, 2) + + def test_patma_053(self): + x = {0} + y = None + match x: + case [0]: + y = 0 + self.assertEqual(x, {0}) + self.assertIs(y, None) + + def test_patma_054(self): + x = set() + y = None + match x: + case []: + y = 0 + self.assertEqual(x, set()) + self.assertIs(y, None) + + def test_patma_055(self): + x = iter([1, 2, 3]) + y = None + match x: + case []: + y = 0 + self.assertEqual([*x], [1, 2, 3]) + self.assertIs(y, None) + + def test_patma_056(self): + x = {} + y = None + match x: + case []: + y = 0 + self.assertEqual(x, {}) + self.assertIs(y, None) + + def test_patma_057(self): + x = {0: False, 1: True} + y = None + match x: + case [0, 1]: + y = 0 + self.assertEqual(x, {0: False, 1: True}) + self.assertIs(y, None) + + def test_patma_058(self): + x = 0 + match x: + case 0: + y = 0 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + + def test_patma_059(self): + x = 0 + y = None + match x: + case False: + y = 0 + self.assertEqual(x, 0) + self.assertEqual(y, None) + + def test_patma_060(self): + x = 0 + y = None + match x: + case 1: + y = 0 + self.assertEqual(x, 0) + self.assertIs(y, None) + + def test_patma_061(self): + x = 0 + y = None + match x: + case None: + y = 0 + self.assertEqual(x, 0) + self.assertIs(y, None) + + def test_patma_062(self): + x = 0 + match x: + case 0: + y = 0 + case 0: + y = 1 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + + def test_patma_063(self): + x = 0 + y = None + match x: + case 1: + y = 0 + case 1: + y = 1 + self.assertEqual(x, 0) + self.assertIs(y, None) + + def test_patma_064(self): + x = "x" + match x: + case "x": + y = 0 + case "y": + y = 1 + self.assertEqual(x, "x") + self.assertEqual(y, 0) + + def test_patma_065(self): + x = "x" + match x: + case "y": + y = 0 + case "x": + y = 1 + self.assertEqual(x, "x") + self.assertEqual(y, 1) + + def test_patma_066(self): + x = "x" + match x: + case "": + y = 0 + case "x": + y = 1 + self.assertEqual(x, "x") + self.assertEqual(y, 1) + + def test_patma_067(self): + x = b"x" + match x: + case b"y": + y = 0 + case b"x": + y = 1 + self.assertEqual(x, b"x") + self.assertEqual(y, 1) + + def test_patma_068(self): + x = 0 + match x: + case 0 if False: + y = 0 + case 0: + y = 1 + self.assertEqual(x, 0) + self.assertEqual(y, 1) + + def test_patma_069(self): + x = 0 + y = None + match x: + case 0 if 0: + y = 0 + case 0 if 0: + y = 1 + self.assertEqual(x, 0) + self.assertIs(y, None) + + def test_patma_070(self): + x = 0 + match x: + case 0 if True: + y = 0 + case 0 if True: + y = 1 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + + def test_patma_071(self): + x = 0 + match x: + case 0 if 1: + y = 0 + case 0 if 1: + y = 1 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + + def test_patma_072(self): + x = 0 + match x: + case 0 if True: + y = 0 + case 0 if True: + y = 1 + y = 2 + self.assertEqual(x, 0) + self.assertEqual(y, 2) + + def test_patma_073(self): + x = 0 + match x: + case 0 if 0: + y = 0 + case 0 if 1: + y = 1 + y = 2 + self.assertEqual(x, 0) + self.assertEqual(y, 2) + + def test_patma_074(self): + x = 0 + y = None + match x: + case 0 if not (x := 1): + y = 0 + case 1: + y = 1 + self.assertEqual(x, 1) + self.assertIs(y, None) + + def test_patma_075(self): + x = "x" + match x: + case ["x"]: + y = 0 + case "x": + y = 1 + self.assertEqual(x, "x") + self.assertEqual(y, 1) + + def test_patma_076(self): + x = b"x" + match x: + case [b"x"]: + y = 0 + case ["x"]: + y = 1 + case [120]: + y = 2 + case b"x": + y = 4 + self.assertEqual(x, b"x") + self.assertEqual(y, 4) + + def test_patma_077(self): + x = bytearray(b"x") + y = None + match x: + case [120]: + y = 0 + case 120: + y = 1 + self.assertEqual(x, b"x") + self.assertIs(y, None) + + def test_patma_078(self): + x = "" + match x: + case []: + y = 0 + case [""]: + y = 1 + case "": + y = 2 + self.assertEqual(x, "") + self.assertEqual(y, 2) + + def test_patma_079(self): + x = "xxx" + match x: + case ["x", "x", "x"]: + y = 0 + case ["xxx"]: + y = 1 + case "xxx": + y = 2 + self.assertEqual(x, "xxx") + self.assertEqual(y, 2) + + def test_patma_080(self): + x = b"xxx" + match x: + case [120, 120, 120]: + y = 0 + case [b"xxx"]: + y = 1 + case b"xxx": + y = 2 + self.assertEqual(x, b"xxx") + self.assertEqual(y, 2) + + def test_patma_081(self): + x = 0 + match x: + case 0 if not (x := 1): + y = 0 + case (0 as z): + y = 1 + self.assertEqual(x, 1) + self.assertEqual(y, 1) + self.assertEqual(z, 0) + + def test_patma_082(self): + x = 0 + match x: + case (1 as z) if not (x := 1): + y = 0 + case 0: + y = 1 + self.assertEqual(x, 0) + self.assertEqual(y, 1) + + def test_patma_083(self): + x = 0 + match x: + case (0 as z): + y = 0 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + self.assertEqual(z, 0) + + def test_patma_084(self): + x = 0 + y = None + match x: + case (1 as z): + y = 0 + self.assertEqual(x, 0) + self.assertIs(y, None) + + def test_patma_085(self): + x = 0 + y = None + match x: + case (0 as z) if (w := 0): + y = 0 + self.assertEqual(w, 0) + self.assertEqual(x, 0) + self.assertIs(y, None) + self.assertEqual(z, 0) + + def test_patma_086(self): + x = 0 + match x: + case ((0 as w) as z): + y = 0 + self.assertEqual(w, 0) + self.assertEqual(x, 0) + self.assertEqual(y, 0) + self.assertEqual(z, 0) + + def test_patma_087(self): + x = 0 + match x: + case (0 | 1) | 2: + y = 0 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + + def test_patma_088(self): + x = 1 + match x: + case (0 | 1) | 2: + y = 0 + self.assertEqual(x, 1) + self.assertEqual(y, 0) + + def test_patma_089(self): + x = 2 + match x: + case (0 | 1) | 2: + y = 0 + self.assertEqual(x, 2) + self.assertEqual(y, 0) + + def test_patma_090(self): + x = 3 + y = None + match x: + case (0 | 1) | 2: + y = 0 + self.assertEqual(x, 3) + self.assertIs(y, None) + + def test_patma_091(self): + x = 0 + match x: + case 0 | (1 | 2): + y = 0 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + + def test_patma_092(self): + x = 1 + match x: + case 0 | (1 | 2): + y = 0 + self.assertEqual(x, 1) + self.assertEqual(y, 0) + + def test_patma_093(self): + x = 2 + match x: + case 0 | (1 | 2): + y = 0 + self.assertEqual(x, 2) + self.assertEqual(y, 0) + + def test_patma_094(self): + x = 3 + y = None + match x: + case 0 | (1 | 2): + y = 0 + self.assertEqual(x, 3) + self.assertIs(y, None) + + def test_patma_095(self): + x = 0 + match x: + case -0: + y = 0 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + + def test_patma_096(self): + x = 0 + match x: + case -0.0: + y = 0 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + + def test_patma_097(self): + x = 0 + match x: + case -0j: + y = 0 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + + def test_patma_098(self): + x = 0 + match x: + case -0.0j: + y = 0 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + + def test_patma_099(self): + x = -1 + match x: + case -1: + y = 0 + self.assertEqual(x, -1) + self.assertEqual(y, 0) + + def test_patma_100(self): + x = -1.5 + match x: + case -1.5: + y = 0 + self.assertEqual(x, -1.5) + self.assertEqual(y, 0) + + def test_patma_101(self): + x = -1j + match x: + case -1j: + y = 0 + self.assertEqual(x, -1j) + self.assertEqual(y, 0) + + def test_patma_102(self): + x = -1.5j + match x: + case -1.5j: + y = 0 + self.assertEqual(x, -1.5j) + self.assertEqual(y, 0) + + def test_patma_103(self): + x = 0 + match x: + case 0 + 0j: + y = 0 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + + def test_patma_104(self): + x = 0 + match x: + case 0 - 0j: + y = 0 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + + def test_patma_105(self): + x = 0 + match x: + case -0 + 0j: + y = 0 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + + def test_patma_106(self): + x = 0 + match x: + case -0 - 0j: + y = 0 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + + def test_patma_107(self): + x = 0.25 + 1.75j + match x: + case 0.25 + 1.75j: + y = 0 + self.assertEqual(x, 0.25 + 1.75j) + self.assertEqual(y, 0) + + def test_patma_108(self): + x = 0.25 - 1.75j + match x: + case 0.25 - 1.75j: + y = 0 + self.assertEqual(x, 0.25 - 1.75j) + self.assertEqual(y, 0) + + def test_patma_109(self): + x = -0.25 + 1.75j + match x: + case -0.25 + 1.75j: + y = 0 + self.assertEqual(x, -0.25 + 1.75j) + self.assertEqual(y, 0) + + def test_patma_110(self): + x = -0.25 - 1.75j + match x: + case -0.25 - 1.75j: + y = 0 + self.assertEqual(x, -0.25 - 1.75j) + self.assertEqual(y, 0) + + def test_patma_111(self): + class A: + B = 0 + x = 0 + match x: + case A.B: + y = 0 + self.assertEqual(A.B, 0) + self.assertEqual(x, 0) + self.assertEqual(y, 0) + + def test_patma_112(self): + class A: + class B: + C = 0 + x = 0 + match x: + case A.B.C: + y = 0 + self.assertEqual(A.B.C, 0) + self.assertEqual(x, 0) + self.assertEqual(y, 0) + + def test_patma_113(self): + class A: + class B: + C = 0 + D = 1 + x = 1 + match x: + case A.B.C: + y = 0 + case A.B.D: + y = 1 + self.assertEqual(A.B.C, 0) + self.assertEqual(A.B.D, 1) + self.assertEqual(x, 1) + self.assertEqual(y, 1) + + def test_patma_114(self): + class A: + class B: + class C: + D = 0 + x = 0 + match x: + case A.B.C.D: + y = 0 + self.assertEqual(A.B.C.D, 0) + self.assertEqual(x, 0) + self.assertEqual(y, 0) + + def test_patma_115(self): + class A: + class B: + class C: + D = 0 + E = 1 + x = 1 + match x: + case A.B.C.D: + y = 0 + case A.B.C.E: + y = 1 + self.assertEqual(A.B.C.D, 0) + self.assertEqual(A.B.C.E, 1) + self.assertEqual(x, 1) + self.assertEqual(y, 1) + + def test_patma_116(self): + match = case = 0 + match match: + case case: + x = 0 + self.assertEqual(match, 0) + self.assertEqual(case, 0) + self.assertEqual(x, 0) + + def test_patma_117(self): + match = case = 0 + match case: + case match: + x = 0 + self.assertEqual(match, 0) + self.assertEqual(case, 0) + self.assertEqual(x, 0) + + def test_patma_118(self): + x = [] + match x: + case [*_, _]: + y = 0 + case []: + y = 1 + self.assertEqual(x, []) + self.assertEqual(y, 1) + + def test_patma_119(self): + x = collections.defaultdict(int) + match x: + case {0: 0}: + y = 0 + case {}: + y = 1 + self.assertEqual(x, {}) + self.assertEqual(y, 1) + + def test_patma_120(self): + x = collections.defaultdict(int) + match x: + case {0: 0}: + y = 0 + case {**z}: + y = 1 + self.assertEqual(x, {}) + self.assertEqual(y, 1) + self.assertEqual(z, {}) + + def test_patma_121(self): + match (): + case (): + x = 0 + self.assertEqual(x, 0) + + def test_patma_122(self): + match (0, 1, 2): + case (*x,): + y = 0 + self.assertEqual(x, [0, 1, 2]) + self.assertEqual(y, 0) + + def test_patma_123(self): + match (0, 1, 2): + case 0, *x: + y = 0 + self.assertEqual(x, [1, 2]) + self.assertEqual(y, 0) + + def test_patma_124(self): + match (0, 1, 2): + case (0, 1, *x,): + y = 0 + self.assertEqual(x, [2]) + self.assertEqual(y, 0) + + def test_patma_125(self): + match (0, 1, 2): + case 0, 1, 2, *x: + y = 0 + self.assertEqual(x, []) + self.assertEqual(y, 0) + + def test_patma_126(self): + match (0, 1, 2): + case *x, 2,: + y = 0 + self.assertEqual(x, [0, 1]) + self.assertEqual(y, 0) + + def test_patma_127(self): + match (0, 1, 2): + case (*x, 1, 2): + y = 0 + self.assertEqual(x, [0]) + self.assertEqual(y, 0) + + def test_patma_128(self): + match (0, 1, 2): + case *x, 0, 1, 2,: + y = 0 + self.assertEqual(x, []) + self.assertEqual(y, 0) + + def test_patma_129(self): + match (0, 1, 2): + case (0, *x, 2): + y = 0 + self.assertEqual(x, [1]) + self.assertEqual(y, 0) + + def test_patma_130(self): + match (0, 1, 2): + case 0, 1, *x, 2,: + y = 0 + self.assertEqual(x, []) + self.assertEqual(y, 0) + + def test_patma_131(self): + match (0, 1, 2): + case (0, *x, 1, 2): + y = 0 + self.assertEqual(x, []) + self.assertEqual(y, 0) + + def test_patma_132(self): + match (0, 1, 2): + case *x,: + y = 0 + self.assertEqual(x, [0, 1, 2]) + self.assertEqual(y, 0) + + def test_patma_133(self): + x = collections.defaultdict(int, {0: 1}) + match x: + case {1: 0}: + y = 0 + case {0: 0}: + y = 1 + case {}: + y = 2 + self.assertEqual(x, {0: 1}) + self.assertEqual(y, 2) + + def test_patma_134(self): + x = collections.defaultdict(int, {0: 1}) + match x: + case {1: 0}: + y = 0 + case {0: 0}: + y = 1 + case {**z}: + y = 2 + self.assertEqual(x, {0: 1}) + self.assertEqual(y, 2) + self.assertEqual(z, {0: 1}) + + def test_patma_135(self): + x = collections.defaultdict(int, {0: 1}) + match x: + case {1: 0}: + y = 0 + case {0: 0}: + y = 1 + case {0: _, **z}: + y = 2 + self.assertEqual(x, {0: 1}) + self.assertEqual(y, 2) + self.assertEqual(z, {}) + + def test_patma_136(self): + x = {0: 1} + match x: + case {1: 0}: + y = 0 + case {0: 0}: + y = 0 + case {}: + y = 1 + self.assertEqual(x, {0: 1}) + self.assertEqual(y, 1) + + def test_patma_137(self): + x = {0: 1} + match x: + case {1: 0}: + y = 0 + case {0: 0}: + y = 0 + case {**z}: + y = 1 + self.assertEqual(x, {0: 1}) + self.assertEqual(y, 1) + self.assertEqual(z, {0: 1}) + + def test_patma_138(self): + x = {0: 1} + match x: + case {1: 0}: + y = 0 + case {0: 0}: + y = 0 + case {0: _, **z}: + y = 1 + self.assertEqual(x, {0: 1}) + self.assertEqual(y, 1) + self.assertEqual(z, {}) + + def test_patma_139(self): + x = False + match x: + case bool(z): + y = 0 + self.assertIs(x, False) + self.assertEqual(y, 0) + self.assertIs(z, x) + + def test_patma_140(self): + x = True + match x: + case bool(z): + y = 0 + self.assertIs(x, True) + self.assertEqual(y, 0) + self.assertIs(z, x) + + def test_patma_141(self): + x = bytearray() + match x: + case bytearray(z): + y = 0 + self.assertEqual(x, bytearray()) + self.assertEqual(y, 0) + self.assertIs(z, x) + + def test_patma_142(self): + x = b"" + match x: + case bytes(z): + y = 0 + self.assertEqual(x, b"") + self.assertEqual(y, 0) + self.assertIs(z, x) + + def test_patma_143(self): + x = {} + match x: + case dict(z): + y = 0 + self.assertEqual(x, {}) + self.assertEqual(y, 0) + self.assertIs(z, x) + + def test_patma_144(self): + x = 0.0 + match x: + case float(z): + y = 0 + self.assertEqual(x, 0.0) + self.assertEqual(y, 0) + self.assertIs(z, x) + + def test_patma_145(self): + x = frozenset() + match x: + case frozenset(z): + y = 0 + self.assertEqual(x, frozenset()) + self.assertEqual(y, 0) + self.assertIs(z, x) + + def test_patma_146(self): + x = 0 + match x: + case int(z): + y = 0 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + self.assertIs(z, x) + + def test_patma_147(self): + x = [] + match x: + case list(z): + y = 0 + self.assertEqual(x, []) + self.assertEqual(y, 0) + self.assertIs(z, x) + + def test_patma_148(self): + x = set() + match x: + case set(z): + y = 0 + self.assertEqual(x, set()) + self.assertEqual(y, 0) + self.assertIs(z, x) + + def test_patma_149(self): + x = "" + match x: + case str(z): + y = 0 + self.assertEqual(x, "") + self.assertEqual(y, 0) + self.assertIs(z, x) + + def test_patma_150(self): + x = () + match x: + case tuple(z): + y = 0 + self.assertEqual(x, ()) + self.assertEqual(y, 0) + self.assertIs(z, x) + + def test_patma_151(self): + x = 0 + match x,: + case y,: + z = 0 + self.assertEqual(x, 0) + self.assertIs(y, x) + self.assertIs(z, 0) + + def test_patma_152(self): + w = 0 + x = 0 + match w, x: + case y, z: + v = 0 + self.assertEqual(w, 0) + self.assertEqual(x, 0) + self.assertIs(y, w) + self.assertIs(z, x) + self.assertEqual(v, 0) + + def test_patma_153(self): + x = 0 + match w := x,: + case y as v,: + z = 0 + self.assertEqual(x, 0) + self.assertIs(y, x) + self.assertEqual(z, 0) + self.assertIs(w, x) + self.assertIs(v, y) + + def test_patma_154(self): + x = 0 + y = None + match x: + case 0 if x: + y = 0 + self.assertEqual(x, 0) + self.assertIs(y, None) + + def test_patma_155(self): + x = 0 + y = None + match x: + case 1e1000: + y = 0 + self.assertEqual(x, 0) + self.assertIs(y, None) + + def test_patma_156(self): + x = 0 + match x: + case z: + y = 0 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + self.assertIs(z, x) + + def test_patma_157(self): + x = 0 + y = None + match x: + case _ if x: + y = 0 + self.assertEqual(x, 0) + self.assertIs(y, None) + + def test_patma_158(self): + x = 0 + match x: + case -1e1000: + y = 0 + case 0: + y = 1 + self.assertEqual(x, 0) + self.assertEqual(y, 1) + + def test_patma_159(self): + x = 0 + match x: + case 0 if not x: + y = 0 + case 1: + y = 1 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + + def test_patma_160(self): + x = 0 + z = None + match x: + case 0: + y = 0 + case z if x: + y = 1 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + self.assertIs(z, None) + + def test_patma_161(self): + x = 0 + match x: + case 0: + y = 0 + case _: + y = 1 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + + def test_patma_162(self): + x = 0 + match x: + case 1 if x: + y = 0 + case 0: + y = 1 + self.assertEqual(x, 0) + self.assertEqual(y, 1) + + def test_patma_163(self): + x = 0 + y = None + match x: + case 1: + y = 0 + case 1 if not x: + y = 1 + self.assertEqual(x, 0) + self.assertIs(y, None) + + def test_patma_164(self): + x = 0 + match x: + case 1: + y = 0 + case z: + y = 1 + self.assertEqual(x, 0) + self.assertEqual(y, 1) + self.assertIs(z, x) + + def test_patma_165(self): + x = 0 + match x: + case 1 if x: + y = 0 + case _: + y = 1 + self.assertEqual(x, 0) + self.assertEqual(y, 1) + + def test_patma_166(self): + x = 0 + match x: + case z if not z: + y = 0 + case 0 if x: + y = 1 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + self.assertIs(z, x) + + def test_patma_167(self): + x = 0 + match x: + case z if not z: + y = 0 + case 1: + y = 1 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + self.assertIs(z, x) + + def test_patma_168(self): + x = 0 + match x: + case z if not x: + y = 0 + case z: + y = 1 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + self.assertIs(z, x) + + def test_patma_169(self): + x = 0 + match x: + case z if not z: + y = 0 + case _ if x: + y = 1 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + self.assertIs(z, x) + + def test_patma_170(self): + x = 0 + match x: + case _ if not x: + y = 0 + case 0: + y = 1 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + + def test_patma_171(self): + x = 0 + y = None + match x: + case _ if x: + y = 0 + case 1: + y = 1 + self.assertEqual(x, 0) + self.assertIs(y, None) + + def test_patma_172(self): + x = 0 + z = None + match x: + case _ if not x: + y = 0 + case z if not x: + y = 1 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + self.assertIs(z, None) + + def test_patma_173(self): + x = 0 + match x: + case _ if not x: + y = 0 + case _: + y = 1 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + + def test_patma_174(self): + def http_error(status): + match status: + case 400: + return "Bad request" + case 401: + return "Unauthorized" + case 403: + return "Forbidden" + case 404: + return "Not found" + case 418: + return "I'm a teapot" + case _: + return "Something else" + self.assertEqual(http_error(400), "Bad request") + self.assertEqual(http_error(401), "Unauthorized") + self.assertEqual(http_error(403), "Forbidden") + self.assertEqual(http_error(404), "Not found") + self.assertEqual(http_error(418), "I'm a teapot") + self.assertEqual(http_error(123), "Something else") + self.assertEqual(http_error("400"), "Something else") + self.assertEqual(http_error(401 | 403 | 404), "Something else") # 407 + + def test_patma_175(self): + def http_error(status): + match status: + case 400: + return "Bad request" + case 401 | 403 | 404: + return "Not allowed" + case 418: + return "I'm a teapot" + self.assertEqual(http_error(400), "Bad request") + self.assertEqual(http_error(401), "Not allowed") + self.assertEqual(http_error(403), "Not allowed") + self.assertEqual(http_error(404), "Not allowed") + self.assertEqual(http_error(418), "I'm a teapot") + self.assertIs(http_error(123), None) + self.assertIs(http_error("400"), None) + self.assertIs(http_error(401 | 403 | 404), None) # 407 + + def test_patma_176(self): + def whereis(point): + match point: + case (0, 0): + return "Origin" + case (0, y): + return f"Y={y}" + case (x, 0): + return f"X={x}" + case (x, y): + return f"X={x}, Y={y}" + case _: + return "Not a point" + self.assertEqual(whereis((0, 0)), "Origin") + self.assertEqual(whereis((0, -1.0)), "Y=-1.0") + self.assertEqual(whereis(("X", 0)), "X=X") + self.assertEqual(whereis((None, 1j)), "X=None, Y=1j") + self.assertEqual(whereis(42), "Not a point") + + def test_patma_177(self): + def whereis(point): + match point: + case Point(0, 0): + return "Origin" + case Point(0, y): + return f"Y={y}" + case Point(x, 0): + return f"X={x}" + case Point(): + return "Somewhere else" + case _: + return "Not a point" + self.assertEqual(whereis(Point(1, 0)), "X=1") + self.assertEqual(whereis(Point(0, 0)), "Origin") + self.assertEqual(whereis(10), "Not a point") + self.assertEqual(whereis(Point(False, False)), "Origin") + self.assertEqual(whereis(Point(0, -1.0)), "Y=-1.0") + self.assertEqual(whereis(Point("X", 0)), "X=X") + self.assertEqual(whereis(Point(None, 1j)), "Somewhere else") + self.assertEqual(whereis(Point), "Not a point") + self.assertEqual(whereis(42), "Not a point") + + def test_patma_178(self): + def whereis(point): + match point: + case Point(1, var): + return var + self.assertEqual(whereis(Point(1, 0)), 0) + self.assertIs(whereis(Point(0, 0)), None) + + def test_patma_179(self): + def whereis(point): + match point: + case Point(1, y=var): + return var + self.assertEqual(whereis(Point(1, 0)), 0) + self.assertIs(whereis(Point(0, 0)), None) + + def test_patma_180(self): + def whereis(point): + match point: + case Point(x=1, y=var): + return var + self.assertEqual(whereis(Point(1, 0)), 0) + self.assertIs(whereis(Point(0, 0)), None) + + def test_patma_181(self): + def whereis(point): + match point: + case Point(y=var, x=1): + return var + self.assertEqual(whereis(Point(1, 0)), 0) + self.assertIs(whereis(Point(0, 0)), None) + + def test_patma_182(self): + def whereis(points): + match points: + case []: + return "No points" + case [Point(0, 0)]: + return "The origin" + case [Point(x, y)]: + return f"Single point {x}, {y}" + case [Point(0, y1), Point(0, y2)]: + return f"Two on the Y axis at {y1}, {y2}" + case _: + return "Something else" + self.assertEqual(whereis([]), "No points") + self.assertEqual(whereis([Point(0, 0)]), "The origin") + self.assertEqual(whereis([Point(0, 1)]), "Single point 0, 1") + self.assertEqual(whereis([Point(0, 0), Point(0, 0)]), "Two on the Y axis at 0, 0") + self.assertEqual(whereis([Point(0, 1), Point(0, 1)]), "Two on the Y axis at 1, 1") + self.assertEqual(whereis([Point(0, 0), Point(1, 0)]), "Something else") + self.assertEqual(whereis([Point(0, 0), Point(0, 0), Point(0, 0)]), "Something else") + self.assertEqual(whereis([Point(0, 1), Point(0, 1), Point(0, 1)]), "Something else") + + def test_patma_183(self): + def whereis(point): + match point: + case Point(x, y) if x == y: + return f"Y=X at {x}" + case Point(x, y): + return "Not on the diagonal" + self.assertEqual(whereis(Point(0, 0)), "Y=X at 0") + self.assertEqual(whereis(Point(0, False)), "Y=X at 0") + self.assertEqual(whereis(Point(False, 0)), "Y=X at False") + self.assertEqual(whereis(Point(-1 - 1j, -1 - 1j)), "Y=X at (-1-1j)") + self.assertEqual(whereis(Point("X", "X")), "Y=X at X") + self.assertEqual(whereis(Point("X", "x")), "Not on the diagonal") + + def test_patma_184(self): + class Seq(collections.abc.Sequence): + __getitem__ = None + def __len__(self): + return 0 + match Seq(): + case []: + y = 0 + self.assertEqual(y, 0) + + def test_patma_185(self): + class Seq(collections.abc.Sequence): + __getitem__ = None + def __len__(self): + return 42 + match Seq(): + case [*_]: + y = 0 + self.assertEqual(y, 0) + + def test_patma_186(self): + class Seq(collections.abc.Sequence): + def __getitem__(self, i): + return i + def __len__(self): + return 42 + match Seq(): + case [x, *_, y]: + z = 0 + self.assertEqual(x, 0) + self.assertEqual(y, 41) + self.assertEqual(z, 0) + + def test_patma_187(self): + w = range(10) + match w: + case [x, y, *rest]: + z = 0 + self.assertEqual(w, range(10)) + self.assertEqual(x, 0) + self.assertEqual(y, 1) + self.assertEqual(z, 0) + self.assertEqual(rest, list(range(2, 10))) + + def test_patma_188(self): + w = range(100) + match w: + case (x, y, *rest): + z = 0 + self.assertEqual(w, range(100)) + self.assertEqual(x, 0) + self.assertEqual(y, 1) + self.assertEqual(z, 0) + self.assertEqual(rest, list(range(2, 100))) + + def test_patma_189(self): + w = range(1000) + match w: + case x, y, *rest: + z = 0 + self.assertEqual(w, range(1000)) + self.assertEqual(x, 0) + self.assertEqual(y, 1) + self.assertEqual(z, 0) + self.assertEqual(rest, list(range(2, 1000))) + + def test_patma_190(self): + w = range(1 << 10) + match w: + case [x, y, *_]: + z = 0 + self.assertEqual(w, range(1 << 10)) + self.assertEqual(x, 0) + self.assertEqual(y, 1) + self.assertEqual(z, 0) + + def test_patma_191(self): + w = range(1 << 20) + match w: + case (x, y, *_): + z = 0 + self.assertEqual(w, range(1 << 20)) + self.assertEqual(x, 0) + self.assertEqual(y, 1) + self.assertEqual(z, 0) + + def test_patma_192(self): + w = range(1 << 30) + match w: + case x, y, *_: + z = 0 + self.assertEqual(w, range(1 << 30)) + self.assertEqual(x, 0) + self.assertEqual(y, 1) + self.assertEqual(z, 0) + + def test_patma_193(self): + x = {"bandwidth": 0, "latency": 1} + match x: + case {"bandwidth": b, "latency": l}: + y = 0 + self.assertEqual(x, {"bandwidth": 0, "latency": 1}) + self.assertIs(b, x["bandwidth"]) + self.assertIs(l, x["latency"]) + self.assertEqual(y, 0) + + def test_patma_194(self): + x = {"bandwidth": 0, "latency": 1, "key": "value"} + match x: + case {"latency": l, "bandwidth": b}: + y = 0 + self.assertEqual(x, {"bandwidth": 0, "latency": 1, "key": "value"}) + self.assertIs(l, x["latency"]) + self.assertIs(b, x["bandwidth"]) + self.assertEqual(y, 0) + + def test_patma_195(self): + x = {"bandwidth": 0, "latency": 1, "key": "value"} + match x: + case {"bandwidth": b, "latency": l, **rest}: + y = 0 + self.assertEqual(x, {"bandwidth": 0, "latency": 1, "key": "value"}) + self.assertIs(b, x["bandwidth"]) + self.assertIs(l, x["latency"]) + self.assertEqual(rest, {"key": "value"}) + self.assertEqual(y, 0) + + def test_patma_196(self): + x = {"bandwidth": 0, "latency": 1} + match x: + case {"latency": l, "bandwidth": b, **rest}: + y = 0 + self.assertEqual(x, {"bandwidth": 0, "latency": 1}) + self.assertIs(l, x["latency"]) + self.assertIs(b, x["bandwidth"]) + self.assertEqual(rest, {}) + self.assertEqual(y, 0) + + def test_patma_197(self): + w = [Point(-1, 0), Point(1, 2)] + match w: + case (Point(x1, y1), Point(x2, y2) as p2): + z = 0 + self.assertEqual(w, [Point(-1, 0), Point(1, 2)]) + self.assertIs(x1, w[0].x) + self.assertIs(y1, w[0].y) + self.assertIs(p2, w[1]) + self.assertIs(x2, w[1].x) + self.assertIs(y2, w[1].y) + self.assertIs(z, 0) + + def test_patma_198(self): + class Color(enum.Enum): + RED = 0 + GREEN = 1 + BLUE = 2 + def f(color): + match color: + case Color.RED: + return "I see red!" + case Color.GREEN: + return "Grass is green" + case Color.BLUE: + return "I'm feeling the blues :(" + self.assertEqual(f(Color.RED), "I see red!") + self.assertEqual(f(Color.GREEN), "Grass is green") + self.assertEqual(f(Color.BLUE), "I'm feeling the blues :(") + self.assertIs(f(Color), None) + self.assertIs(f(0), None) + self.assertIs(f(1), None) + self.assertIs(f(2), None) + self.assertIs(f(3), None) + self.assertIs(f(False), None) + self.assertIs(f(True), None) + self.assertIs(f(2+0j), None) + self.assertIs(f(3.0), None) + + def test_patma_199(self): + class Color(int, enum.Enum): + RED = 0 + GREEN = 1 + BLUE = 2 + def f(color): + match color: + case Color.RED: + return "I see red!" + case Color.GREEN: + return "Grass is green" + case Color.BLUE: + return "I'm feeling the blues :(" + self.assertEqual(f(Color.RED), "I see red!") + self.assertEqual(f(Color.GREEN), "Grass is green") + self.assertEqual(f(Color.BLUE), "I'm feeling the blues :(") + self.assertIs(f(Color), None) + self.assertEqual(f(0), "I see red!") + self.assertEqual(f(1), "Grass is green") + self.assertEqual(f(2), "I'm feeling the blues :(") + self.assertIs(f(3), None) + self.assertEqual(f(False), "I see red!") + self.assertEqual(f(True), "Grass is green") + self.assertEqual(f(2+0j), "I'm feeling the blues :(") + self.assertIs(f(3.0), None) + + def test_patma_200(self): + class Class: + __match_args__ = ("a", "b") + c = Class() + c.a = 0 + c.b = 1 + match c: + case Class(x, y): + z = 0 + self.assertIs(x, c.a) + self.assertIs(y, c.b) + self.assertEqual(z, 0) + + def test_patma_201(self): + class Class: + __match_args__ = ("a", "b") + c = Class() + c.a = 0 + c.b = 1 + match c: + case Class(x, b=y): + z = 0 + self.assertIs(x, c.a) + self.assertIs(y, c.b) + self.assertEqual(z, 0) + + def test_patma_202(self): + class Parent: + __match_args__ = "a", "b" + class Child(Parent): + __match_args__ = ("c", "d") + c = Child() + c.a = 0 + c.b = 1 + match c: + case Parent(x, y): + z = 0 + self.assertIs(x, c.a) + self.assertIs(y, c.b) + self.assertEqual(z, 0) + + def test_patma_203(self): + class Parent: + __match_args__ = ("a", "b") + class Child(Parent): + __match_args__ = "c", "d" + c = Child() + c.a = 0 + c.b = 1 + match c: + case Parent(x, b=y): + z = 0 + self.assertIs(x, c.a) + self.assertIs(y, c.b) + self.assertEqual(z, 0) + + def test_patma_204(self): + def f(w): + match w: + case 42: + out = locals() + del out["w"] + return out + self.assertEqual(f(42), {}) + self.assertIs(f(0), None) + self.assertEqual(f(42.0), {}) + self.assertIs(f("42"), None) + + def test_patma_205(self): + def f(w): + match w: + case 42.0: + out = locals() + del out["w"] + return out + self.assertEqual(f(42.0), {}) + self.assertEqual(f(42), {}) + self.assertIs(f(0.0), None) + self.assertIs(f(0), None) + + def test_patma_206(self): + def f(w): + match w: + case 1 | 2 | 3: + out = locals() + del out["w"] + return out + self.assertEqual(f(1), {}) + self.assertEqual(f(2), {}) + self.assertEqual(f(3), {}) + self.assertEqual(f(3.0), {}) + self.assertIs(f(0), None) + self.assertIs(f(4), None) + self.assertIs(f("1"), None) + + def test_patma_207(self): + def f(w): + match w: + case [1, 2] | [3, 4]: + out = locals() + del out["w"] + return out + self.assertEqual(f([1, 2]), {}) + self.assertEqual(f([3, 4]), {}) + self.assertIs(f(42), None) + self.assertIs(f([2, 3]), None) + self.assertIs(f([1, 2, 3]), None) + self.assertEqual(f([1, 2.0]), {}) + + def test_patma_208(self): + def f(w): + match w: + case x: + out = locals() + del out["w"] + return out + self.assertEqual(f(42), {"x": 42}) + self.assertEqual(f((1, 2)), {"x": (1, 2)}) + self.assertEqual(f(None), {"x": None}) + + def test_patma_209(self): + def f(w): + match w: + case _: + out = locals() + del out["w"] + return out + self.assertEqual(f(42), {}) + self.assertEqual(f(None), {}) + self.assertEqual(f((1, 2)), {}) + + def test_patma_210(self): + def f(w): + match w: + case (x, y, z): + out = locals() + del out["w"] + return out + self.assertEqual(f((1, 2, 3)), {"x": 1, "y": 2, "z": 3}) + self.assertIs(f((1, 2)), None) + self.assertIs(f((1, 2, 3, 4)), None) + self.assertIs(f(123), None) + self.assertIs(f("abc"), None) + self.assertIs(f(b"abc"), None) + self.assertEqual(f(array.array("b", b"abc")), {'x': 97, 'y': 98, 'z': 99}) + self.assertEqual(f(memoryview(b"abc")), {"x": 97, "y": 98, "z": 99}) + self.assertIs(f(bytearray(b"abc")), None) + + def test_patma_211(self): + def f(w): + match w: + case {"x": x, "y": "y", "z": z}: + out = locals() + del out["w"] + return out + self.assertEqual(f({"x": "x", "y": "y", "z": "z"}), {"x": "x", "z": "z"}) + self.assertEqual(f({"x": "x", "y": "y", "z": "z", "a": "a"}), {"x": "x", "z": "z"}) + self.assertIs(f(({"x": "x", "y": "yy", "z": "z", "a": "a"})), None) + self.assertIs(f(({"x": "x", "y": "y"})), None) + + def test_patma_212(self): + def f(w): + match w: + case Point(int(xx), y="hello"): + out = locals() + del out["w"] + return out + self.assertEqual(f(Point(42, "hello")), {"xx": 42}) + + def test_patma_213(self): + def f(w): + match w: + case (p, q) as x: + out = locals() + del out["w"] + return out + self.assertEqual(f((1, 2)), {"p": 1, "q": 2, "x": (1, 2)}) + self.assertEqual(f([1, 2]), {"p": 1, "q": 2, "x": [1, 2]}) + self.assertIs(f(12), None) + self.assertIs(f((1, 2, 3)), None) + + def test_patma_214(self): + def f(): + match 42: + case 42: + return locals() + self.assertEqual(set(f()), set()) + + def test_patma_215(self): + def f(): + match 1: + case 1 | 2 | 3: + return locals() + self.assertEqual(set(f()), set()) + + def test_patma_216(self): + def f(): + match ...: + case _: + return locals() + self.assertEqual(set(f()), set()) + + def test_patma_217(self): + def f(): + match ...: + case abc: + return locals() + self.assertEqual(set(f()), {"abc"}) + + def test_patma_218(self): + def f(): + match ..., ...: + case a, b: + return locals() + self.assertEqual(set(f()), {"a", "b"}) + + def test_patma_219(self): + def f(): + match {"k": ..., "l": ...}: + case {"k": a, "l": b}: + return locals() + self.assertEqual(set(f()), {"a", "b"}) + + def test_patma_220(self): + def f(): + match Point(..., ...): + case Point(x, y=y): + return locals() + self.assertEqual(set(f()), {"x", "y"}) + + def test_patma_221(self): + def f(): + match ...: + case b as a: + return locals() + self.assertEqual(set(f()), {"a", "b"}) + + def test_patma_222(self): + def f(x): + match x: + case _: + return 0 + self.assertEqual(f(0), 0) + self.assertEqual(f(1), 0) + self.assertEqual(f(2), 0) + self.assertEqual(f(3), 0) + + def test_patma_223(self): + def f(x): + match x: + case 0: + return 0 + self.assertEqual(f(0), 0) + self.assertIs(f(1), None) + self.assertIs(f(2), None) + self.assertIs(f(3), None) + + def test_patma_224(self): + def f(x): + match x: + case 0: + return 0 + case _: + return 1 + self.assertEqual(f(0), 0) + self.assertEqual(f(1), 1) + self.assertEqual(f(2), 1) + self.assertEqual(f(3), 1) + + def test_patma_225(self): + def f(x): + match x: + case 0: + return 0 + case 1: + return 1 + self.assertEqual(f(0), 0) + self.assertEqual(f(1), 1) + self.assertIs(f(2), None) + self.assertIs(f(3), None) + + def test_patma_226(self): + def f(x): + match x: + case 0: + return 0 + case 1: + return 1 + case _: + return 2 + self.assertEqual(f(0), 0) + self.assertEqual(f(1), 1) + self.assertEqual(f(2), 2) + self.assertEqual(f(3), 2) + + def test_patma_227(self): + def f(x): + match x: + case 0: + return 0 + case 1: + return 1 + case 2: + return 2 + self.assertEqual(f(0), 0) + self.assertEqual(f(1), 1) + self.assertEqual(f(2), 2) + self.assertIs(f(3), None) + + def test_patma_228(self): + match(): + case(): + x = 0 + self.assertEqual(x, 0) + + def test_patma_229(self): + x = 0 + match(x): + case(x): + y = 0 + self.assertEqual(x, 0) + self.assertEqual(y, 0) + + def test_patma_230(self): + x = 0 + match x: + case False: + y = 0 + case 0: + y = 1 + self.assertEqual(x, 0) + self.assertEqual(y, 1) + + def test_patma_231(self): + x = 1 + match x: + case True: + y = 0 + case 1: + y = 1 + self.assertEqual(x, 1) + self.assertEqual(y, 1) + + def test_patma_232(self): + class Eq: + def __eq__(self, other): + return True + x = eq = Eq() + # None + y = None + match x: + case None: + y = 0 + self.assertIs(x, eq) + self.assertEqual(y, None) + # True + y = None + match x: + case True: + y = 0 + self.assertIs(x, eq) + self.assertEqual(y, None) + # False + y = None + match x: + case False: + y = 0 + self.assertIs(x, eq) + self.assertEqual(y, None) + + def test_patma_233(self): + x = False + match x: + case False: + y = 0 + self.assertIs(x, False) + self.assertEqual(y, 0) + + def test_patma_234(self): + x = True + match x: + case True: + y = 0 + self.assertIs(x, True) + self.assertEqual(y, 0) + + def test_patma_235(self): + x = None + match x: + case None: + y = 0 + self.assertIs(x, None) + self.assertEqual(y, 0) + + def test_patma_236(self): + x = 0 + match x: + case (0 as w) as z: + y = 0 + self.assertEqual(w, 0) + self.assertEqual(x, 0) + self.assertEqual(y, 0) + self.assertEqual(z, 0) + + def test_patma_237(self): + x = 0 + match x: + case (0 as w) as z: + y = 0 + self.assertEqual(w, 0) + self.assertEqual(x, 0) + self.assertEqual(y, 0) + self.assertEqual(z, 0) + + def test_patma_238(self): + x = ((0, 1), (2, 3)) + match x: + case ((a as b, c as d) as e) as w, ((f as g, h) as i) as z: + y = 0 + self.assertEqual(a, 0) + self.assertEqual(b, 0) + self.assertEqual(c, 1) + self.assertEqual(d, 1) + self.assertEqual(e, (0, 1)) + self.assertEqual(f, 2) + self.assertEqual(g, 2) + self.assertEqual(h, 3) + self.assertEqual(i, (2, 3)) + self.assertEqual(w, (0, 1)) + self.assertEqual(x, ((0, 1), (2, 3))) + self.assertEqual(y, 0) + self.assertEqual(z, (2, 3)) + + def test_patma_239(self): + x = collections.UserDict({0: 1, 2: 3}) + match x: + case {2: 3}: + y = 0 + self.assertEqual(x, {0: 1, 2: 3}) + self.assertEqual(y, 0) + + def test_patma_240(self): + x = collections.UserDict({0: 1, 2: 3}) + match x: + case {2: 3, **z}: + y = 0 + self.assertEqual(x, {0: 1, 2: 3}) + self.assertEqual(y, 0) + self.assertEqual(z, {0: 1}) + + def test_patma_241(self): + x = [[{0: 0}]] + match x: + case list([({-0-0j: int(real=0+0j, imag=0-0j) | (1) as z},)]): + y = 0 + self.assertEqual(x, [[{0: 0}]]) + self.assertEqual(y, 0) + self.assertEqual(z, 0) + + def test_patma_242(self): + x = range(3) + match x: + case [y, *_, z]: + w = 0 + self.assertEqual(w, 0) + self.assertEqual(x, range(3)) + self.assertEqual(y, 0) + self.assertEqual(z, 2) + + def test_patma_243(self): + x = range(3) + match x: + case [_, *_, y]: + z = 0 + self.assertEqual(x, range(3)) + self.assertEqual(y, 2) + self.assertEqual(z, 0) + + def test_patma_244(self): + x = range(3) + match x: + case [*_, y]: + z = 0 + self.assertEqual(x, range(3)) + self.assertEqual(y, 2) + self.assertEqual(z, 0) + + def test_patma_245(self): + x = {"y": 1} + match x: + case {"y": (0 as y) | (1 as y)}: + z = 0 + self.assertEqual(x, {"y": 1}) + self.assertEqual(y, 1) + self.assertEqual(z, 0) + + def test_patma_246(self): + def f(x): + match x: + case ((a, b, c, d, e, f, g, h, i, 9) | + (h, g, i, a, b, d, e, c, f, 10) | + (g, b, a, c, d, -5, e, h, i, f) | + (-1, d, f, b, g, e, i, a, h, c)): + w = 0 + out = locals() + del out["x"] + return out + alts = [ + dict(a=0, b=1, c=2, d=3, e=4, f=5, g=6, h=7, i=8, w=0), + dict(h=1, g=2, i=3, a=4, b=5, d=6, e=7, c=8, f=9, w=0), + dict(g=0, b=-1, a=-2, c=-3, d=-4, e=-6, h=-7, i=-8, f=-9, w=0), + dict(d=-2, f=-3, b=-4, g=-5, e=-6, i=-7, a=-8, h=-9, c=-10, w=0), + dict(), + ] + self.assertEqual(f(range(10)), alts[0]) + self.assertEqual(f(range(1, 11)), alts[1]) + self.assertEqual(f(range(0, -10, -1)), alts[2]) + self.assertEqual(f(range(-1, -11, -1)), alts[3]) + self.assertEqual(f(range(10, 20)), alts[4]) + + def test_patma_247(self): + def f(x): + match x: + case [y, (a, b, c, d, e, f, g, h, i, 9) | + (h, g, i, a, b, d, e, c, f, 10) | + (g, b, a, c, d, -5, e, h, i, f) | + (-1, d, f, b, g, e, i, a, h, c), z]: + w = 0 + out = locals() + del out["x"] + return out + alts = [ + dict(a=0, b=1, c=2, d=3, e=4, f=5, g=6, h=7, i=8, w=0, y=False, z=True), + dict(h=1, g=2, i=3, a=4, b=5, d=6, e=7, c=8, f=9, w=0, y=False, z=True), + dict(g=0, b=-1, a=-2, c=-3, d=-4, e=-6, h=-7, i=-8, f=-9, w=0, y=False, z=True), + dict(d=-2, f=-3, b=-4, g=-5, e=-6, i=-7, a=-8, h=-9, c=-10, w=0, y=False, z=True), + dict(), + ] + self.assertEqual(f((False, range(10), True)), alts[0]) + self.assertEqual(f((False, range(1, 11), True)), alts[1]) + self.assertEqual(f((False, range(0, -10, -1), True)), alts[2]) + self.assertEqual(f((False, range(-1, -11, -1), True)), alts[3]) + self.assertEqual(f((False, range(10, 20), True)), alts[4]) + + def test_patma_248(self): + class C(dict): + @staticmethod + def get(key, default=None): + return 'bar' + + x = C({'foo': 'bar'}) + match x: + case {'foo': bar}: + y = bar + + self.assertEqual(y, 'bar') + + def test_patma_249(self): + class C: + __attr = "eggs" # mangled to _C__attr + _Outer__attr = "bacon" + class Outer: + def f(self, x): + match x: + # looks up __attr, not _C__attr or _Outer__attr + case C(__attr=y): + return y + c = C() + setattr(c, "__attr", "spam") # setattr is needed because we're in a class scope + self.assertEqual(Outer().f(c), "spam") + + def test_patma_250(self): + def f(x): + match x: + case {"foo": y} if y >= 0: + return True + case {"foo": y} if y < 0: + return False + + self.assertIs(f({"foo": 1}), True) + self.assertIs(f({"foo": -1}), False) + + def test_patma_251(self): + def f(v, x): + match v: + case x.attr if x.attr >= 0: + return True + case x.attr if x.attr < 0: + return False + case _: + return None + + class X: + def __init__(self, attr): + self.attr = attr + + self.assertIs(f(1, X(1)), True) + self.assertIs(f(-1, X(-1)), False) + self.assertIs(f(1, X(-1)), None) + + def test_patma_252(self): + # Side effects must be possible in guards: + effects = [] + def lt(x, y): + effects.append((x, y)) + return x < y + + res = None + match {"foo": 1}: + case {"foo": x} if lt(x, 0): + res = 0 + case {"foo": x} if lt(x, 1): + res = 1 + case {"foo": x} if lt(x, 2): + res = 2 + + self.assertEqual(res, 2) + self.assertEqual(effects, [(1, 0), (1, 1), (1, 2)]) + + def test_patma_253(self): + def f(v): + match v: + case [x] | x: + return x + + self.assertEqual(f(1), 1) + self.assertEqual(f([1]), 1) + + def test_patma_254(self): + def f(v): + match v: + case {"x": x} | x: + return x + + self.assertEqual(f(1), 1) + self.assertEqual(f({"x": 1}), 1) + + def test_patma_255(self): + x = [] + match x: + case [] as z if z.append(None): + y = 0 + case [None]: + y = 1 + self.assertEqual(x, [None]) + self.assertEqual(y, 1) + self.assertIs(z, x) + + def test_patma_runtime_checkable_protocol(self): + # Runtime-checkable protocol + from typing import Protocol, runtime_checkable + + @runtime_checkable + class P(Protocol): + x: int + y: int + + class A: + def __init__(self, x: int, y: int): + self.x = x + self.y = y + + class B(A): ... + + for cls in (A, B): + with self.subTest(cls=cls.__name__): + inst = cls(1, 2) + w = 0 + match inst: + case P() as p: + self.assertIsInstance(p, cls) + self.assertEqual(p.x, 1) + self.assertEqual(p.y, 2) + w = 1 + self.assertEqual(w, 1) + + q = 0 + match inst: + case P(x=x, y=y): + self.assertEqual(x, 1) + self.assertEqual(y, 2) + q = 1 + self.assertEqual(q, 1) + + + def test_patma_generic_protocol(self): + # Runtime-checkable generic protocol + from typing import Generic, TypeVar, Protocol, runtime_checkable + + T = TypeVar('T') # not using PEP695 to be able to backport changes + + @runtime_checkable + class P(Protocol[T]): + a: T + b: T + + class A: + def __init__(self, x: int, y: int): + self.x = x + self.y = y + + class G(Generic[T]): + def __init__(self, x: T, y: T): + self.x = x + self.y = y + + for cls in (A, G): + with self.subTest(cls=cls.__name__): + inst = cls(1, 2) + w = 0 + match inst: + case P(): + w = 1 + self.assertEqual(w, 0) + + def test_patma_protocol_with_match_args(self): + # Runtime-checkable protocol with `__match_args__` + from typing import Protocol, runtime_checkable + + # Used to fail before + # https://github.com/python/cpython/issues/110682 + @runtime_checkable + class P(Protocol): + __match_args__ = ('x', 'y') + x: int + y: int + + class A: + def __init__(self, x: int, y: int): + self.x = x + self.y = y + + class B(A): ... + + for cls in (A, B): + with self.subTest(cls=cls.__name__): + inst = cls(1, 2) + w = 0 + match inst: + case P() as p: + self.assertIsInstance(p, cls) + self.assertEqual(p.x, 1) + self.assertEqual(p.y, 2) + w = 1 + self.assertEqual(w, 1) + + q = 0 + match inst: + case P(x=x, y=y): + self.assertEqual(x, 1) + self.assertEqual(y, 2) + q = 1 + self.assertEqual(q, 1) + + j = 0 + match inst: + case P(x=1, y=2): + j = 1 + self.assertEqual(j, 1) + + g = 0 + match inst: + case P(x, y): + self.assertEqual(x, 1) + self.assertEqual(y, 2) + g = 1 + self.assertEqual(g, 1) + + h = 0 + match inst: + case P(1, 2): + h = 1 + self.assertEqual(h, 1) + + +class TestSyntaxErrors(unittest.TestCase): + + def assert_syntax_error(self, code: str): + with self.assertRaises(SyntaxError): + compile(inspect.cleandoc(code), "", "exec") + + def test_alternative_patterns_bind_different_names_0(self): + self.assert_syntax_error(""" + match ...: + case "a" | a: + pass + """) + + def test_alternative_patterns_bind_different_names_1(self): + self.assert_syntax_error(""" + match ...: + case [a, [b] | [c] | [d]]: + pass + """) + + + def test_attribute_name_repeated_in_class_pattern(self): + self.assert_syntax_error(""" + match ...: + case Class(a=_, a=_): + pass + """) + + def test_imaginary_number_required_in_complex_literal_0(self): + self.assert_syntax_error(""" + match ...: + case 0+0: + pass + """) + + def test_imaginary_number_required_in_complex_literal_1(self): + self.assert_syntax_error(""" + match ...: + case {0+0: _}: + pass + """) + + def test_invalid_syntax_0(self): + self.assert_syntax_error(""" + match ...: + case {**rest, "key": value}: + pass + """) + + def test_invalid_syntax_1(self): + self.assert_syntax_error(""" + match ...: + case {"first": first, **rest, "last": last}: + pass + """) + + def test_invalid_syntax_2(self): + self.assert_syntax_error(""" + match ...: + case {**_}: + pass + """) + + def test_invalid_syntax_3(self): + self.assert_syntax_error(""" + match ...: + case 42 as _: + pass + """) + + def test_len1_tuple_sequence_pattern_comma(self): + # correct syntax would be `case(*x,):` + self.assert_syntax_error(""" + match ...: + case (*x): + pass + """) + + def test_mapping_pattern_keys_may_only_match_literals_and_attribute_lookups(self): + self.assert_syntax_error(""" + match ...: + case {f"": _}: + pass + """) + + def test_multiple_assignments_to_name_in_pattern_0(self): + self.assert_syntax_error(""" + match ...: + case a, a: + pass + """) + + def test_multiple_assignments_to_name_in_pattern_1(self): + self.assert_syntax_error(""" + match ...: + case {"k": a, "l": a}: + pass + """) + + def test_multiple_assignments_to_name_in_pattern_2(self): + self.assert_syntax_error(""" + match ...: + case MyClass(x, x): + pass + """) + + def test_multiple_assignments_to_name_in_pattern_3(self): + self.assert_syntax_error(""" + match ...: + case MyClass(x=x, y=x): + pass + """) + + def test_multiple_assignments_to_name_in_pattern_4(self): + self.assert_syntax_error(""" + match ...: + case MyClass(x, y=x): + pass + """) + + def test_multiple_assignments_to_name_in_pattern_5(self): + self.assert_syntax_error(""" + match ...: + case a as a: + pass + """) + + def test_multiple_starred_names_in_sequence_pattern_0(self): + self.assert_syntax_error(""" + match ...: + case *a, b, *c, d, *e: + pass + """) + + def test_multiple_starred_names_in_sequence_pattern_1(self): + self.assert_syntax_error(""" + match ...: + case a, *b, c, *d, e: + pass + """) + + def test_name_capture_makes_remaining_patterns_unreachable_0(self): + self.assert_syntax_error(""" + match ...: + case a | "a": + pass + """) + + def test_name_capture_makes_remaining_patterns_unreachable_1(self): + self.assert_syntax_error(""" + match 42: + case x: + pass + case y: + pass + """) + + def test_name_capture_makes_remaining_patterns_unreachable_2(self): + self.assert_syntax_error(""" + match ...: + case x | [_ as x] if x: + pass + """) + + def test_name_capture_makes_remaining_patterns_unreachable_3(self): + self.assert_syntax_error(""" + match ...: + case x: + pass + case [x] if x: + pass + """) + + def test_name_capture_makes_remaining_patterns_unreachable_4(self): + self.assert_syntax_error(""" + match ...: + case x: + pass + case _: + pass + """) + + def test_patterns_may_only_match_literals_and_attribute_lookups_0(self): + self.assert_syntax_error(""" + match ...: + case f"": + pass + """) + + def test_patterns_may_only_match_literals_and_attribute_lookups_1(self): + self.assert_syntax_error(""" + match ...: + case f"{x}": + pass + """) + + def test_real_number_required_in_complex_literal_0(self): + self.assert_syntax_error(""" + match ...: + case 0j+0: + pass + """) + + def test_real_number_required_in_complex_literal_1(self): + self.assert_syntax_error(""" + match ...: + case 0j+0j: + pass + """) + + def test_real_number_required_in_complex_literal_2(self): + self.assert_syntax_error(""" + match ...: + case {0j+0: _}: + pass + """) + + def test_real_number_required_in_complex_literal_3(self): + self.assert_syntax_error(""" + match ...: + case {0j+0j: _}: + pass + """) + + def test_real_number_multiple_ops(self): + self.assert_syntax_error(""" + match ...: + case 0 + 0j + 0: + pass + """) + + def test_real_number_wrong_ops(self): + for op in ["*", "/", "@", "**", "%", "//"]: + with self.subTest(op=op): + self.assert_syntax_error(f""" + match ...: + case 0 {op} 0j: + pass + """) + self.assert_syntax_error(f""" + match ...: + case 0j {op} 0: + pass + """) + self.assert_syntax_error(f""" + match ...: + case -0j {op} 0: + pass + """) + self.assert_syntax_error(f""" + match ...: + case 0j {op} -0: + pass + """) + + def test_wildcard_makes_remaining_patterns_unreachable_0(self): + self.assert_syntax_error(""" + match ...: + case _ | _: + pass + """) + + def test_wildcard_makes_remaining_patterns_unreachable_1(self): + self.assert_syntax_error(""" + match ...: + case (_ as x) | [x]: + pass + """) + + def test_wildcard_makes_remaining_patterns_unreachable_2(self): + self.assert_syntax_error(""" + match ...: + case _ | _ if condition(): + pass + """) + + def test_wildcard_makes_remaining_patterns_unreachable_3(self): + self.assert_syntax_error(""" + match ...: + case _: + pass + case None: + pass + """) + + def test_wildcard_makes_remaining_patterns_unreachable_4(self): + self.assert_syntax_error(""" + match ...: + case (None | _) | _: + pass + """) + + def test_wildcard_makes_remaining_patterns_unreachable_5(self): + self.assert_syntax_error(""" + match ...: + case _ | (True | False): + pass + """) + + def test_mapping_pattern_duplicate_key(self): + self.assert_syntax_error(""" + match ...: + case {"a": _, "a": _}: + pass + """) + + def test_mapping_pattern_duplicate_key_edge_case0(self): + self.assert_syntax_error(""" + match ...: + case {0: _, False: _}: + pass + """) + + def test_mapping_pattern_duplicate_key_edge_case1(self): + self.assert_syntax_error(""" + match ...: + case {0: _, 0.0: _}: + pass + """) + + def test_mapping_pattern_duplicate_key_edge_case2(self): + self.assert_syntax_error(""" + match ...: + case {0: _, -0: _}: + pass + """) + + def test_mapping_pattern_duplicate_key_edge_case3(self): + self.assert_syntax_error(""" + match ...: + case {0: _, 0j: _}: + pass + """) + +class TestTypeErrors(unittest.TestCase): + + def test_accepts_positional_subpatterns_0(self): + class Class: + __match_args__ = () + x = Class() + y = z = None + with self.assertRaises(TypeError): + match x: + case Class(y): + z = 0 + self.assertIs(y, None) + self.assertIs(z, None) + + def test_accepts_positional_subpatterns_1(self): + x = range(10) + y = None + with self.assertRaises(TypeError): + match x: + case range(10): + y = 0 + self.assertEqual(x, range(10)) + self.assertIs(y, None) + + def test_got_multiple_subpatterns_for_attribute_0(self): + class Class: + __match_args__ = ("a", "a") + a = None + x = Class() + w = y = z = None + with self.assertRaises(TypeError): + match x: + case Class(y, z): + w = 0 + self.assertIs(w, None) + self.assertIs(y, None) + self.assertIs(z, None) + + def test_got_multiple_subpatterns_for_attribute_1(self): + class Class: + __match_args__ = ("a",) + a = None + x = Class() + w = y = z = None + with self.assertRaises(TypeError): + match x: + case Class(y, a=z): + w = 0 + self.assertIs(w, None) + self.assertIs(y, None) + self.assertIs(z, None) + + def test_match_args_elements_must_be_strings(self): + class Class: + __match_args__ = (None,) + x = Class() + y = z = None + with self.assertRaises(TypeError): + match x: + case Class(y): + z = 0 + self.assertIs(y, None) + self.assertIs(z, None) + + def test_match_args_must_be_a_tuple_0(self): + class Class: + __match_args__ = None + x = Class() + y = z = None + with self.assertRaises(TypeError): + match x: + case Class(y): + z = 0 + self.assertIs(y, None) + self.assertIs(z, None) + + def test_match_args_must_be_a_tuple_1(self): + class Class: + __match_args__ = "XYZ" + x = Class() + y = z = None + with self.assertRaises(TypeError): + match x: + case Class(y): + z = 0 + self.assertIs(y, None) + self.assertIs(z, None) + + def test_match_args_must_be_a_tuple_2(self): + class Class: + __match_args__ = ["spam", "eggs"] + spam = 0 + eggs = 1 + x = Class() + w = y = z = None + with self.assertRaises(TypeError): + match x: + case Class(y, z): + w = 0 + self.assertIs(w, None) + self.assertIs(y, None) + self.assertIs(z, None) + + def test_class_pattern_not_type(self): + w = None + with self.assertRaises(TypeError): + match 1: + case max(0, 1): + w = 0 + self.assertIsNone(w) + + def test_regular_protocol(self): + from typing import Protocol + class P(Protocol): ... + msg = ( + 'Instance and class checks can only be used ' + 'with @runtime_checkable protocols' + ) + w = None + with self.assertRaisesRegex(TypeError, msg): + match 1: + case P(): + w = 0 + self.assertIsNone(w) + + def test_positional_patterns_with_regular_protocol(self): + from typing import Protocol + class P(Protocol): + x: int # no `__match_args__` + y: int + class A: + x = 1 + y = 2 + w = None + with self.assertRaises(TypeError): + match A(): + case P(x, y): + w = 0 + self.assertIsNone(w) + + +class TestValueErrors(unittest.TestCase): + + def test_mapping_pattern_checks_duplicate_key_1(self): + class Keys: + KEY = "a" + x = {"a": 0, "b": 1} + w = y = z = None + with self.assertRaises(ValueError): + match x: + case {Keys.KEY: y, "a": z}: + w = 0 + self.assertIs(w, None) + self.assertIs(y, None) + self.assertIs(z, None) + +class TestSourceLocations(unittest.TestCase): + def test_jump_threading(self): + # See gh-123048 + def f(): + x = 0 + v = 1 + match v: + case 1: + if x < 0: + x = 1 + case 2: + if x < 0: + x = 1 + x += 1 + + for inst in dis.get_instructions(f): + if inst.opcode in dis.hasjump: + self.assertIsNotNone(inst.positions.lineno, "jump without location") + +class TestTracing(unittest.TestCase): + + @staticmethod + def _trace(func, *args, **kwargs): + actual_linenos = [] + + def trace(frame, event, arg): + if event == "line" and frame.f_code.co_name == func.__name__: + assert arg is None + relative_lineno = frame.f_lineno - func.__code__.co_firstlineno + actual_linenos.append(relative_lineno) + return trace + + old_trace = sys.gettrace() + sys.settrace(trace) + try: + func(*args, **kwargs) + finally: + sys.settrace(old_trace) + return actual_linenos + + def test_default_wildcard(self): + def f(command): # 0 + match command.split(): # 1 + case ["go", direction] if direction in "nesw": # 2 + return f"go {direction}" # 3 + case ["go", _]: # 4 + return "no go" # 5 + case _: # 6 + return "default" # 7 + + self.assertListEqual(self._trace(f, "go n"), [1, 2, 3]) + self.assertListEqual(self._trace(f, "go x"), [1, 2, 4, 5]) + self.assertListEqual(self._trace(f, "spam"), [1, 2, 4, 6, 7]) + + def test_default_capture(self): + def f(command): # 0 + match command.split(): # 1 + case ["go", direction] if direction in "nesw": # 2 + return f"go {direction}" # 3 + case ["go", _]: # 4 + return "no go" # 5 + case x: # 6 + return x # 7 + + self.assertListEqual(self._trace(f, "go n"), [1, 2, 3]) + self.assertListEqual(self._trace(f, "go x"), [1, 2, 4, 5]) + self.assertListEqual(self._trace(f, "spam"), [1, 2, 4, 6, 7]) + + def test_no_default(self): + def f(command): # 0 + match command.split(): # 1 + case ["go", direction] if direction in "nesw": # 2 + return f"go {direction}" # 3 + case ["go", _]: # 4 + return "no go" # 5 + + self.assertListEqual(self._trace(f, "go n"), [1, 2, 3]) + self.assertListEqual(self._trace(f, "go x"), [1, 2, 4, 5]) + self.assertListEqual(self._trace(f, "spam"), [1, 2, 4]) + + def test_only_default_wildcard(self): + def f(command): # 0 + match command.split(): # 1 + case _: # 2 + return "default" # 3 + + self.assertListEqual(self._trace(f, "go n"), [1, 2, 3]) + self.assertListEqual(self._trace(f, "go x"), [1, 2, 3]) + self.assertListEqual(self._trace(f, "spam"), [1, 2, 3]) + + def test_only_default_capture(self): + def f(command): # 0 + match command.split(): # 1 + case x: # 2 + return x # 3 + + self.assertListEqual(self._trace(f, "go n"), [1, 2, 3]) + self.assertListEqual(self._trace(f, "go x"), [1, 2, 3]) + self.assertListEqual(self._trace(f, "spam"), [1, 2, 3]) + + def test_unreachable_code(self): + def f(command): # 0 + match command: # 1 + case 1: # 2 + if False: # 3 + return 1 # 4 + case _: # 5 + if False: # 6 + return 0 # 7 + + self.assertListEqual(self._trace(f, 1), [1, 2, 3]) + self.assertListEqual(self._trace(f, 0), [1, 2, 5, 6]) + + def test_parser_deeply_nested_patterns(self): + # Deeply nested patterns can cause exponential backtracking when parsing. + # See gh-93671 for more information. + + levels = 100 + + patterns = [ + "A" + "(" * levels + ")" * levels, + "{1:" * levels + "1" + "}" * levels, + "[" * levels + "1" + "]" * levels, + ] + + for pattern in patterns: + with self.subTest(pattern): + code = inspect.cleandoc(""" + match None: + case {}: + pass + """.format(pattern)) + compile(code, "", "exec") + + +if __name__ == "__main__": + """ + # From inside environment using this Python, with pyperf installed: + sudo $(which pyperf) system tune && \ + $(which python) -m test.test_patma --rigorous; \ + sudo $(which pyperf) system reset + """ + import pyperf + + + class PerfPatma(TestPatma): + + def assertEqual(*_, **__): + pass + + def assertIs(*_, **__): + pass + + def assertRaises(*_, **__): + assert False, "this test should be a method of a different class!" + + def run_perf(self, count): + tests = [] + for attr in vars(TestPatma): + if attr.startswith("test_"): + tests.append(getattr(self, attr)) + tests *= count + start = pyperf.perf_counter() + for test in tests: + test() + return pyperf.perf_counter() - start + + + runner = pyperf.Runner() + runner.bench_time_func("patma", PerfPatma().run_perf) From 4b638011bb3c6e7198c5eab2ed725b2172b25d89 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 26 Aug 2025 12:23:12 +0900 Subject: [PATCH 4/8] Add failing test markers --- Lib/test/test_patma.py | 51 +++++++++++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_patma.py b/Lib/test/test_patma.py index 8325b83a593..9fd745b97d2 100644 --- a/Lib/test/test_patma.py +++ b/Lib/test/test_patma.py @@ -42,6 +42,7 @@ def check_mapping_then_sequence(x): case [*_]: return "seq" + @unittest.expectedFailure # TODO: RUSTPYTHON def test_multiple_inheritance_mapping(self): class C: pass @@ -62,6 +63,7 @@ class M4(dict, collections.abc.Sequence, C): self.assertEqual(self.check_mapping_then_sequence(M3()), "map") self.assertEqual(self.check_mapping_then_sequence(M4()), "map") + @unittest.expectedFailure # TODO: RUSTPYTHON def test_multiple_inheritance_sequence(self): class C: pass @@ -82,6 +84,7 @@ class S4(collections.UserList, dict, C): self.assertEqual(self.check_mapping_then_sequence(S3()), "seq") self.assertEqual(self.check_mapping_then_sequence(S4()), "seq") + @unittest.expectedFailure # TODO: RUSTPYTHON def test_late_registration_mapping(self): class Parent: pass @@ -105,6 +108,7 @@ class GrandchildPost(ChildPost): self.assertEqual(self.check_mapping_then_sequence(ChildPost()), "map") self.assertEqual(self.check_mapping_then_sequence(GrandchildPost()), "map") + @unittest.expectedFailure # TODO: RUSTPYTHON def test_late_registration_sequence(self): class Parent: pass @@ -582,6 +586,7 @@ def test_patma_051(self): self.assertEqual(y, 1) self.assertEqual(z, 0) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_patma_052(self): x = [1, 0] match x: @@ -1335,6 +1340,7 @@ def test_patma_132(self): self.assertEqual(x, [0, 1, 2]) self.assertEqual(y, 0) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_patma_133(self): x = collections.defaultdict(int, {0: 1}) match x: @@ -1347,6 +1353,7 @@ def test_patma_133(self): self.assertEqual(x, {0: 1}) self.assertEqual(y, 2) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_patma_134(self): x = collections.defaultdict(int, {0: 1}) match x: @@ -1360,6 +1367,7 @@ def test_patma_134(self): self.assertEqual(y, 2) self.assertEqual(z, {0: 1}) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_patma_135(self): x = collections.defaultdict(int, {0: 1}) match x: @@ -1896,6 +1904,7 @@ def whereis(points): self.assertEqual(whereis([Point(0, 0), Point(0, 0), Point(0, 0)]), "Something else") self.assertEqual(whereis([Point(0, 1), Point(0, 1), Point(0, 1)]), "Something else") + @unittest.expectedFailure # TODO: RUSTPYTHON def test_patma_183(self): def whereis(point): match point: @@ -2244,6 +2253,7 @@ def f(w): self.assertEqual(f(None), {}) self.assertEqual(f((1, 2)), {}) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_patma_210(self): def f(w): match w: @@ -2563,14 +2573,15 @@ def test_patma_240(self): self.assertEqual(y, 0) self.assertEqual(z, {0: 1}) - def test_patma_241(self): - x = [[{0: 0}]] - match x: - case list([({-0-0j: int(real=0+0j, imag=0-0j) | (1) as z},)]): - y = 0 - self.assertEqual(x, [[{0: 0}]]) - self.assertEqual(y, 0) - self.assertEqual(z, 0) + # TODO: RUSTPYTHON + # def test_patma_241(self): + # x = [[{0: 0}]] + # match x: + # case list([({-0-0j: int(real=0+0j, imag=0-0j) | (1) as z},)]): + # y = 0 + # self.assertEqual(x, [[{0: 0}]]) + # self.assertEqual(y, 0) + # self.assertEqual(z, 0) def test_patma_242(self): x = range(3) @@ -2684,6 +2695,7 @@ def f(self, x): setattr(c, "__attr", "spam") # setattr is needed because we're in a class scope self.assertEqual(Outer().f(c), "spam") + @unittest.expectedFailure # TODO: RUSTPYTHON def test_patma_250(self): def f(x): match x: @@ -2695,6 +2707,7 @@ def f(x): self.assertIs(f({"foo": 1}), True) self.assertIs(f({"foo": -1}), False) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_patma_251(self): def f(v, x): match v: @@ -2713,6 +2726,7 @@ def __init__(self, attr): self.assertIs(f(-1, X(-1)), False) self.assertIs(f(1, X(-1)), None) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_patma_252(self): # Side effects must be possible in guards: effects = [] @@ -2750,6 +2764,7 @@ def f(v): self.assertEqual(f(1), 1) self.assertEqual(f({"x": 1}), 1) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_patma_255(self): x = [] match x: @@ -2951,6 +2966,7 @@ def test_invalid_syntax_2(self): pass """) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_invalid_syntax_3(self): self.assert_syntax_error(""" match ...: @@ -3070,6 +3086,7 @@ def test_name_capture_makes_remaining_patterns_unreachable_4(self): pass """) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_patterns_may_only_match_literals_and_attribute_lookups_0(self): self.assert_syntax_error(""" match ...: @@ -3077,6 +3094,7 @@ def test_patterns_may_only_match_literals_and_attribute_lookups_0(self): pass """) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_patterns_may_only_match_literals_and_attribute_lookups_1(self): self.assert_syntax_error(""" match ...: @@ -3119,6 +3137,7 @@ def test_real_number_multiple_ops(self): pass """) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_real_number_wrong_ops(self): for op in ["*", "/", "@", "**", "%", "//"]: with self.subTest(op=op): @@ -3187,6 +3206,7 @@ def test_wildcard_makes_remaining_patterns_unreachable_5(self): pass """) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_mapping_pattern_duplicate_key(self): self.assert_syntax_error(""" match ...: @@ -3194,6 +3214,7 @@ def test_mapping_pattern_duplicate_key(self): pass """) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_mapping_pattern_duplicate_key_edge_case0(self): self.assert_syntax_error(""" match ...: @@ -3201,6 +3222,7 @@ def test_mapping_pattern_duplicate_key_edge_case0(self): pass """) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_mapping_pattern_duplicate_key_edge_case1(self): self.assert_syntax_error(""" match ...: @@ -3215,6 +3237,8 @@ def test_mapping_pattern_duplicate_key_edge_case2(self): pass """) + + @unittest.expectedFailure # TODO: RUSTPYTHON def test_mapping_pattern_duplicate_key_edge_case3(self): self.assert_syntax_error(""" match ...: @@ -3236,6 +3260,7 @@ class Class: self.assertIs(y, None) self.assertIs(z, None) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_accepts_positional_subpatterns_1(self): x = range(10) y = None @@ -3246,6 +3271,7 @@ def test_accepts_positional_subpatterns_1(self): self.assertEqual(x, range(10)) self.assertIs(y, None) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_got_multiple_subpatterns_for_attribute_0(self): class Class: __match_args__ = ("a", "a") @@ -3260,6 +3286,7 @@ class Class: self.assertIs(y, None) self.assertIs(z, None) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_got_multiple_subpatterns_for_attribute_1(self): class Class: __match_args__ = ("a",) @@ -3365,6 +3392,7 @@ class A: class TestValueErrors(unittest.TestCase): + @unittest.expectedFailure # TODO: RUSTPYTHON def test_mapping_pattern_checks_duplicate_key_1(self): class Keys: KEY = "a" @@ -3379,6 +3407,7 @@ class Keys: self.assertIs(z, None) class TestSourceLocations(unittest.TestCase): + @unittest.expectedFailure # TODO: RUSTPYTHON def test_jump_threading(self): # See gh-123048 def f(): @@ -3418,6 +3447,7 @@ def trace(frame, event, arg): sys.settrace(old_trace) return actual_linenos + @unittest.expectedFailure # TODO: RUSTPYTHON def test_default_wildcard(self): def f(command): # 0 match command.split(): # 1 @@ -3432,6 +3462,7 @@ def f(command): # 0 self.assertListEqual(self._trace(f, "go x"), [1, 2, 4, 5]) self.assertListEqual(self._trace(f, "spam"), [1, 2, 4, 6, 7]) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_default_capture(self): def f(command): # 0 match command.split(): # 1 @@ -3446,6 +3477,7 @@ def f(command): # 0 self.assertListEqual(self._trace(f, "go x"), [1, 2, 4, 5]) self.assertListEqual(self._trace(f, "spam"), [1, 2, 4, 6, 7]) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_no_default(self): def f(command): # 0 match command.split(): # 1 @@ -3458,6 +3490,7 @@ def f(command): # 0 self.assertListEqual(self._trace(f, "go x"), [1, 2, 4, 5]) self.assertListEqual(self._trace(f, "spam"), [1, 2, 4]) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_only_default_wildcard(self): def f(command): # 0 match command.split(): # 1 @@ -3468,6 +3501,7 @@ def f(command): # 0 self.assertListEqual(self._trace(f, "go x"), [1, 2, 3]) self.assertListEqual(self._trace(f, "spam"), [1, 2, 3]) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_only_default_capture(self): def f(command): # 0 match command.split(): # 1 @@ -3478,6 +3512,7 @@ def f(command): # 0 self.assertListEqual(self._trace(f, "go x"), [1, 2, 3]) self.assertListEqual(self._trace(f, "spam"), [1, 2, 3]) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_unreachable_code(self): def f(command): # 0 match command: # 1 From 21fb4aafcfa31f44d1b7641e4203dadb5c6036a3 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 26 Aug 2025 13:49:32 +0900 Subject: [PATCH 5/8] apply review --- Lib/test/test_patma.py | 1 - compiler/codegen/src/compile.rs | 21 +++++++++++++-------- vm/src/frame.rs | 11 +++++------ 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/Lib/test/test_patma.py b/Lib/test/test_patma.py index 9fd745b97d2..aa07c7ed9c1 100644 --- a/Lib/test/test_patma.py +++ b/Lib/test/test_patma.py @@ -3206,7 +3206,6 @@ def test_wildcard_makes_remaining_patterns_unreachable_5(self): pass """) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_mapping_pattern_duplicate_key(self): self.assert_syntax_error(""" match ...: diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index fe72b523870..8bcb133cc07 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -3484,9 +3484,6 @@ impl Compiler { // Process each sub-pattern. for subpattern in patterns.iter().chain(kwd_patterns.iter()) { - // Decrement the on_top counter before processing each sub-pattern - pc.on_top -= 1; - // Check if this is a true wildcard (underscore pattern without name binding) let is_true_wildcard = match subpattern { Pattern::MatchAs(match_as) => { @@ -3496,6 +3493,9 @@ impl Compiler { _ => subpattern.is_wildcard(), }; + // Decrement the on_top counter for each sub-pattern + pc.on_top -= 1; + if is_true_wildcard { emit!(self, Instruction::Pop); continue; // Don't compile wildcard patterns @@ -3583,11 +3583,16 @@ impl Compiler { let mut seen = std::collections::HashSet::new(); for key in keys { let is_attribute = matches!(key, Expr::Attribute(_)); - let key_repr = if let Expr::NumberLiteral(_) - | Expr::StringLiteral(_) - | Expr::BooleanLiteral(_) = key - { - format!("{:?}", key) + let is_literal = matches!( + key, + Expr::NumberLiteral(_) + | Expr::StringLiteral(_) + | Expr::BytesLiteral(_) + | Expr::BooleanLiteral(_) + | Expr::NoneLiteral(_) + ); + let key_repr = if is_literal { + unparse_expr(key, &self.source_file).to_string() } else if is_attribute { String::new() } else { diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 7f43625d506..45bc4f59216 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -1,6 +1,3 @@ -use crate::common::{boxvec::BoxVec, lock::PyMutex}; -use crate::protocol::PyMapping; -use crate::types::PyTypeFlags; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, builtins::{ @@ -18,11 +15,12 @@ use crate::{ protocol::{PyIter, PyIterReturn}, scope::Scope, stdlib::{builtins, typing}, + types::PyTypeFlags, vm::{Context, PyMethod}, }; use indexmap::IndexMap; use itertools::Itertools; -use rustpython_common::wtf8::Wtf8Buf; +use rustpython_common::{boxvec::BoxVec, lock::PyMutex, wtf8::Wtf8Buf}; use rustpython_compiler_core::SourceLocation; #[cfg(feature = "threading")] use std::sync::atomic; @@ -1329,7 +1327,7 @@ impl ExecutingFrame<'_> { let subject = self.nth_value(1); // stack[-2] // Check if subject is a mapping and extract values for keys - if PyMapping::check(&subject) { + if subject.class().slots.flags.contains(PyTypeFlags::MAPPING) { let keys = keys_tuple.downcast_ref::().unwrap(); let mut values = Vec::new(); let mut all_match = true; @@ -1337,10 +1335,11 @@ impl ExecutingFrame<'_> { for key in keys { match subject.get_item(key.as_object(), vm) { Ok(value) => values.push(value), - Err(_) => { + Err(e) if e.fast_isinstance(vm.ctx.exceptions.key_error) => { all_match = false; break; } + Err(e) => return Err(e), } } From b807bc7fc4891613fbd0ab41b74b9be21b1ec063 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 26 Aug 2025 15:12:35 +0900 Subject: [PATCH 6/8] Fix patma guard --- Lib/test/test_patma.py | 7 ------- compiler/codegen/src/compile.rs | 4 ++-- vm/src/frame.rs | 16 ++++++++++------ 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/Lib/test/test_patma.py b/Lib/test/test_patma.py index aa07c7ed9c1..5b2755f838c 100644 --- a/Lib/test/test_patma.py +++ b/Lib/test/test_patma.py @@ -586,7 +586,6 @@ def test_patma_051(self): self.assertEqual(y, 1) self.assertEqual(z, 0) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_patma_052(self): x = [1, 0] match x: @@ -1904,7 +1903,6 @@ def whereis(points): self.assertEqual(whereis([Point(0, 0), Point(0, 0), Point(0, 0)]), "Something else") self.assertEqual(whereis([Point(0, 1), Point(0, 1), Point(0, 1)]), "Something else") - @unittest.expectedFailure # TODO: RUSTPYTHON def test_patma_183(self): def whereis(point): match point: @@ -2695,7 +2693,6 @@ def f(self, x): setattr(c, "__attr", "spam") # setattr is needed because we're in a class scope self.assertEqual(Outer().f(c), "spam") - @unittest.expectedFailure # TODO: RUSTPYTHON def test_patma_250(self): def f(x): match x: @@ -2707,7 +2704,6 @@ def f(x): self.assertIs(f({"foo": 1}), True) self.assertIs(f({"foo": -1}), False) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_patma_251(self): def f(v, x): match v: @@ -2726,7 +2722,6 @@ def __init__(self, attr): self.assertIs(f(-1, X(-1)), False) self.assertIs(f(1, X(-1)), None) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_patma_252(self): # Side effects must be possible in guards: effects = [] @@ -2764,7 +2759,6 @@ def f(v): self.assertEqual(f(1), 1) self.assertEqual(f({"x": 1}), 1) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_patma_255(self): x = [] match x: @@ -3259,7 +3253,6 @@ class Class: self.assertIs(y, None) self.assertIs(z, None) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_accepts_positional_subpatterns_1(self): x = range(10) y = None diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index 8bcb133cc07..41a4b6e82b6 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -4011,10 +4011,10 @@ impl Compiler { self.ensure_fail_pop(pattern_context, 0)?; // Compile the guard expression self.compile_expression(guard)?; - // If guard is false, jump to fail_pop[0] + emit!(self, Instruction::ToBool); emit!( self, - Instruction::JumpIfFalseOrPop { + Instruction::PopJumpIfFalse { target: pattern_context.fail_pop[0] } ); diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 45bc4f59216..ec03eef2249 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -1440,14 +1440,18 @@ impl ExecutingFrame<'_> { extracted.push(subject.clone()); } else if nargs_val > 1 { // Too many positional arguments for MATCH_SELF - self.push_value(vm.ctx.none()); - return Ok(None); + return Err(vm.new_type_error( + "class pattern accepts at most 1 positional sub-pattern for MATCH_SELF types" + .to_string(), + )); } } else { // No __match_args__ and not a MATCH_SELF type if nargs_val > 0 { - self.push_value(vm.ctx.none()); - return Ok(None); + return Err(vm.new_type_error( + "class pattern defines no positional sub-patterns (__match_args__ missing)" + .to_string(), + )); } } } @@ -1458,11 +1462,11 @@ impl ExecutingFrame<'_> { let name_str = name.downcast_ref::().unwrap(); match subject.get_attr(name_str, vm) { Ok(value) => extracted.push(value), - Err(_) => { - // Attribute doesn't exist + Err(e) if e.fast_isinstance(vm.ctx.exceptions.attribute_error) => { self.push_value(vm.ctx.none()); return Ok(None); } + Err(e) => return Err(e), } } From be54bc0dfd80788e5a63ce5f39ca4f4fa9c156c0 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 26 Aug 2025 16:56:54 +0900 Subject: [PATCH 7/8] Fix multiple inheritance --- Lib/test/test_patma.py | 2 -- vm/src/builtins/type.rs | 27 ++++++++++++++++++++------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_patma.py b/Lib/test/test_patma.py index 5b2755f838c..269e5f87edf 100644 --- a/Lib/test/test_patma.py +++ b/Lib/test/test_patma.py @@ -42,7 +42,6 @@ def check_mapping_then_sequence(x): case [*_]: return "seq" - @unittest.expectedFailure # TODO: RUSTPYTHON def test_multiple_inheritance_mapping(self): class C: pass @@ -63,7 +62,6 @@ class M4(dict, collections.abc.Sequence, C): self.assertEqual(self.check_mapping_then_sequence(M3()), "map") self.assertEqual(self.check_mapping_then_sequence(M4()), "map") - @unittest.expectedFailure # TODO: RUSTPYTHON def test_multiple_inheritance_sequence(self): class C: pass diff --git a/vm/src/builtins/type.rs b/vm/src/builtins/type.rs index 01ac2c1d6f1..cce58089479 100644 --- a/vm/src/builtins/type.rs +++ b/vm/src/builtins/type.rs @@ -232,13 +232,25 @@ impl PyType { linearise_mro(mros) } - /// Inherit SEQUENCE and MAPPING flags from base class (CPython: inherit_patma_flags) - fn inherit_patma_flags(slots: &mut PyTypeSlots, base: &PyRef) { + /// Inherit SEQUENCE and MAPPING flags from base classes + /// Check all bases in order and inherit the first SEQUENCE or MAPPING flag found + fn inherit_patma_flags(slots: &mut PyTypeSlots, bases: &[PyRef]) { const COLLECTION_FLAGS: PyTypeFlags = PyTypeFlags::from_bits_truncate( PyTypeFlags::SEQUENCE.bits() | PyTypeFlags::MAPPING.bits(), ); - if !slots.flags.intersects(COLLECTION_FLAGS) { - slots.flags |= base.slots.flags & COLLECTION_FLAGS; + + // If flags are already set, don't override + if slots.flags.intersects(COLLECTION_FLAGS) { + return; + } + + // Check each base in order and inherit the first collection flag found + for base in bases { + let base_flags = base.slots.flags & COLLECTION_FLAGS; + if !base_flags.is_empty() { + slots.flags |= base_flags; + return; + } } } @@ -300,8 +312,8 @@ impl PyType { slots.flags |= PyTypeFlags::HAS_DICT } - // Inherit SEQUENCE and MAPPING flags from base class - Self::inherit_patma_flags(&mut slots, &base); + // Inherit SEQUENCE and MAPPING flags from base classes + Self::inherit_patma_flags(&mut slots, &bases); // Check for __abc_tpflags__ from ABCMeta (for collections.abc.Sequence, Mapping, etc.) Self::check_abc_tpflags(&mut slots, &attrs, &bases, ctx); @@ -359,7 +371,8 @@ impl PyType { } // Inherit SEQUENCE and MAPPING flags from base class - Self::inherit_patma_flags(&mut slots, &base); + // For static types, we only have a single base + Self::inherit_patma_flags(&mut slots, std::slice::from_ref(&base)); if slots.basicsize == 0 { slots.basicsize = base.slots.basicsize; From f4543f5f51c3e07bfb15817906f08998df1015e2 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 26 Aug 2025 17:18:05 +0900 Subject: [PATCH 8/8] Fix defaultdict --- Lib/test/test_patma.py | 3 --- vm/src/frame.rs | 47 +++++++++++++++++++++++++++++++++++------- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_patma.py b/Lib/test/test_patma.py index 269e5f87edf..847ca001e43 100644 --- a/Lib/test/test_patma.py +++ b/Lib/test/test_patma.py @@ -1337,7 +1337,6 @@ def test_patma_132(self): self.assertEqual(x, [0, 1, 2]) self.assertEqual(y, 0) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_patma_133(self): x = collections.defaultdict(int, {0: 1}) match x: @@ -1350,7 +1349,6 @@ def test_patma_133(self): self.assertEqual(x, {0: 1}) self.assertEqual(y, 2) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_patma_134(self): x = collections.defaultdict(int, {0: 1}) match x: @@ -1364,7 +1362,6 @@ def test_patma_134(self): self.assertEqual(y, 2) self.assertEqual(z, {0: 1}) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_patma_135(self): x = collections.defaultdict(int, {0: 1}) match x: diff --git a/vm/src/frame.rs b/vm/src/frame.rs index ec03eef2249..ba9abffc6e7 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -1332,14 +1332,47 @@ impl ExecutingFrame<'_> { let mut values = Vec::new(); let mut all_match = true; - for key in keys { - match subject.get_item(key.as_object(), vm) { - Ok(value) => values.push(value), - Err(e) if e.fast_isinstance(vm.ctx.exceptions.key_error) => { - all_match = false; - break; + // We use the two argument form of map.get(key, default) for two reasons: + // - Atomically check for a key and get its value without error handling. + // - Don't cause key creation or resizing in dict subclasses like + // collections.defaultdict that define __missing__ (or similar). + // See CPython's _PyEval_MatchKeys + + if let Some(get_method) = vm + .get_method(subject.to_owned(), vm.ctx.intern_str("get")) + .transpose()? + { + // dummy = object() + // CPython: dummy = _PyObject_CallNoArgs((PyObject *)&PyBaseObject_Type); + let dummy = vm + .ctx + .new_base_object(vm.ctx.types.object_type.to_owned(), None); + + for key in keys { + // value = map.get(key, dummy) + match get_method.call((key.as_object(), dummy.clone()), vm) { + Ok(value) => { + // if value == dummy: key not in map! + if value.is(&dummy) { + all_match = false; + break; + } + values.push(value); + } + Err(e) => return Err(e), + } + } + } else { + // Fallback if .get() method is not available (shouldn't happen for mappings) + for key in keys { + match subject.get_item(key.as_object(), vm) { + Ok(value) => values.push(value), + Err(e) if e.fast_isinstance(vm.ctx.exceptions.key_error) => { + all_match = false; + break; + } + Err(e) => return Err(e), } - Err(e) => return Err(e), } }