@@ -3010,13 +3010,54 @@ impl ExecutingFrame<'_> {
30103010 Ok ( None )
30113011 }
30123012
3013+ /// _PyEval_UnpackIterableStackRef
30133014 fn unpack_sequence ( & mut self , size : u32 , vm : & VirtualMachine ) -> FrameResult {
30143015 let value = self . pop_value ( ) ;
3016+ let size = size as usize ;
3017+
3018+ // Fast path for exact tuple/list types (not subclasses) — check
3019+ // length directly without creating an iterator, matching
3020+ // UNPACK_SEQUENCE_TUPLE / UNPACK_SEQUENCE_LIST specializations.
3021+ let cls = value. class ( ) ;
3022+ let fast_elements: Option < Vec < PyObjectRef > > = if cls. is ( vm. ctx . types . tuple_type ) {
3023+ Some ( value. downcast_ref :: < PyTuple > ( ) . unwrap ( ) . as_slice ( ) . to_vec ( ) )
3024+ } else if cls. is ( vm. ctx . types . list_type ) {
3025+ Some (
3026+ value
3027+ . downcast_ref :: < PyList > ( )
3028+ . unwrap ( )
3029+ . borrow_vec ( )
3030+ . to_vec ( ) ,
3031+ )
3032+ } else {
3033+ None
3034+ } ;
3035+ if let Some ( elements) = fast_elements {
3036+ return match elements. len ( ) . cmp ( & size) {
3037+ core:: cmp:: Ordering :: Equal => {
3038+ self . state
3039+ . stack
3040+ . extend ( elements. into_iter ( ) . rev ( ) . map ( Some ) ) ;
3041+ Ok ( None )
3042+ }
3043+ core:: cmp:: Ordering :: Greater => Err ( vm. new_value_error ( format ! (
3044+ "too many values to unpack (expected {size}, got {})" ,
3045+ elements. len( )
3046+ ) ) ) ,
3047+ core:: cmp:: Ordering :: Less => Err ( vm. new_value_error ( format ! (
3048+ "not enough values to unpack (expected {size}, got {})" ,
3049+ elements. len( )
3050+ ) ) ) ,
3051+ } ;
3052+ }
3053+
3054+ // General path — iterate up to `size + 1` elements to avoid
3055+ // consuming the entire iterator (fixes hang on infinite sequences).
30153056 let not_iterable = value. class ( ) . slots . iter . load ( ) . is_none ( )
30163057 && value
30173058 . get_class_attr ( vm. ctx . intern_str ( "__getitem__" ) )
30183059 . is_none ( ) ;
3019- let elements : Vec < _ > = value. try_to_value ( vm ) . map_err ( |e| {
3060+ let iter = PyIter :: try_from_object ( vm , value. clone ( ) ) . map_err ( |e| {
30203061 if not_iterable && e. class ( ) . is ( vm. ctx . exceptions . type_error ) {
30213062 vm. new_type_error ( format ! (
30223063 "cannot unpack non-iterable {} object" ,
@@ -3026,24 +3067,48 @@ impl ExecutingFrame<'_> {
30263067 e
30273068 }
30283069 } ) ?;
3029- let msg = match elements. len ( ) . cmp ( & ( size as usize ) ) {
3030- core:: cmp:: Ordering :: Equal => {
3031- // Wrap each element in Some() for Option<PyObjectRef> stack
3070+
3071+ let mut elements = Vec :: with_capacity ( size) ;
3072+ for _ in 0 ..size {
3073+ match iter. next ( vm) ? {
3074+ PyIterReturn :: Return ( item) => elements. push ( item) ,
3075+ PyIterReturn :: StopIteration ( _) => {
3076+ return Err ( vm. new_value_error ( format ! (
3077+ "not enough values to unpack (expected {size}, got {})" ,
3078+ elements. len( )
3079+ ) ) ) ;
3080+ }
3081+ }
3082+ }
3083+
3084+ // Check that the iterator is exhausted.
3085+ match iter. next ( vm) ? {
3086+ PyIterReturn :: Return ( _) => {
3087+ // For exact dict types, show "got N" using the container's
3088+ // size (PyDict_Size). Exact tuple/list are handled by the
3089+ // fast path above and never reach here.
3090+ let msg = if value. class ( ) . is ( vm. ctx . types . dict_type ) {
3091+ if let Ok ( got) = value. length ( vm) {
3092+ if got > size {
3093+ format ! ( "too many values to unpack (expected {size}, got {got})" )
3094+ } else {
3095+ format ! ( "too many values to unpack (expected {size})" )
3096+ }
3097+ } else {
3098+ format ! ( "too many values to unpack (expected {size})" )
3099+ }
3100+ } else {
3101+ format ! ( "too many values to unpack (expected {size})" )
3102+ } ;
3103+ Err ( vm. new_value_error ( msg) )
3104+ }
3105+ PyIterReturn :: StopIteration ( _) => {
30323106 self . state
30333107 . stack
30343108 . extend ( elements. into_iter ( ) . rev ( ) . map ( Some ) ) ;
3035- return Ok ( None ) ;
3036- }
3037- core:: cmp:: Ordering :: Greater => {
3038- format ! ( "too many values to unpack (expected {size})" )
3109+ Ok ( None )
30393110 }
3040- core:: cmp:: Ordering :: Less => format ! (
3041- "not enough values to unpack (expected {}, got {})" ,
3042- size,
3043- elements. len( )
3044- ) ,
3045- } ;
3046- Err ( vm. new_value_error ( msg) )
3111+ }
30473112 }
30483113
30493114 fn convert_value (
0 commit comments