@@ -2,17 +2,23 @@ use cairo_lang_debug::DebugWithDb;
22use cairo_lang_defs:: ids:: NamedLanguageElementId ;
33use cairo_lang_diagnostics:: { DiagnosticNote , Maybe } ;
44use cairo_lang_filesystem:: flag:: FlagsGroup ;
5- use cairo_lang_semantic:: corelib:: { CorelibSemantic , validate_literal} ;
5+ use cairo_lang_filesystem:: ids:: SmolStrId ;
6+ use cairo_lang_semantic:: corelib:: {
7+ CorelibSemantic , get_usize_ty, try_get_core_ty_by_name, validate_literal,
8+ } ;
69use cairo_lang_semantic:: expr:: compute:: unwrap_pattern_type;
10+ use cairo_lang_semantic:: items:: constant:: ConstValue ;
711use cairo_lang_semantic:: items:: enm:: SemanticEnumEx ;
812use cairo_lang_semantic:: items:: structure:: StructSemantic ;
13+ use cairo_lang_semantic:: types:: wrap_in_snapshots;
914use cairo_lang_semantic:: {
1015 self as semantic, ConcreteEnumId , ConcreteStructId , ConcreteTypeId , ExprNumericLiteral ,
11- PatternEnumVariant , PatternLiteral , PatternStruct , PatternTuple , PatternWrappingInfo , TypeId ,
12- TypeLongId , corelib,
16+ GenericArgumentId , PatternEnumVariant , PatternLiteral , PatternStruct , PatternTuple ,
17+ PatternWrappingInfo , TypeId , TypeLongId , corelib,
1318} ;
1419use cairo_lang_syntax:: node:: TypedStablePtr ;
1520use cairo_lang_syntax:: node:: ast:: ExprPtr ;
21+ use cairo_lang_utils:: Intern ;
1622use cairo_lang_utils:: ordered_hash_map:: OrderedHashMap ;
1723use itertools:: { Itertools , zip_eq} ;
1824use num_bigint:: BigInt ;
@@ -26,7 +32,9 @@ use super::filtered_patterns::{Bindings, FilteredPatterns};
2632use crate :: diagnostic:: { LoweringDiagnosticKind , MatchDiagnostic , MatchError } ;
2733use crate :: ids:: LocationId ;
2834use crate :: lower:: context:: LoweringContext ;
29- use crate :: lower:: flow_control:: graph:: { Downcast , EqualsLiteral , Upcast , ValueMatch } ;
35+ use crate :: lower:: flow_control:: graph:: {
36+ Downcast , EqualsLiteral , SliceDestructure , Upcast , ValueMatch ,
37+ } ;
3038
3139/// A callback that gets a [FilteredPatterns] and constructs a node that continues the pattern
3240/// matching restricted to the filtered patterns.
@@ -150,21 +158,6 @@ pub fn create_node_for_patterns<'db>(
150158 create_node_for_enum ( params, input_var, concrete_enum_id, wrapping_info)
151159 }
152160 TypeLongId :: Concrete ( ConcreteTypeId :: Struct ( concrete_struct_id) ) => {
153- // Check if any non-any pattern is a FixedSizeArray (i.e. Span destructure).
154- // Span destructuring in match/if-let is not yet supported in lowering.
155- let has_fixed_size_array_pattern = patterns
156- . iter ( )
157- . flatten ( )
158- . any ( |p| matches ! ( p, semantic:: Pattern :: FixedSizeArray ( ..) ) ) ;
159- if has_fixed_size_array_pattern {
160- return graph. report_with_missing_node (
161- first_non_any_pattern. stable_ptr ( ) ,
162- LoweringDiagnosticKind :: MatchError ( MatchError {
163- kind : graph. kind ( ) ,
164- error : MatchDiagnostic :: UnsupportedMatchedType ( long_ty. format ( ctx. db ) ) ,
165- } ) ,
166- ) ;
167- }
168161 create_node_for_struct ( params, input_var, concrete_struct_id, wrapping_info)
169162 }
170163 TypeLongId :: Tuple ( types) => create_node_for_tuple ( params, input_var, & types, wrapping_info) ,
@@ -354,6 +347,19 @@ fn create_node_for_struct<'db>(
354347) -> NodeId {
355348 let CreateNodeParams { ctx, graph, patterns, build_node_callback, location } = params;
356349
350+ if let Some ( node) = try_create_slice_destructure_chain (
351+ ctx,
352+ graph,
353+ patterns,
354+ build_node_callback,
355+ location,
356+ input_var,
357+ concrete_struct_id,
358+ wrapping_info,
359+ ) {
360+ return node;
361+ }
362+
357363 let members = match ctx. db . concrete_struct_members ( concrete_struct_id) {
358364 Ok ( members) => members,
359365 Err ( diag_added) => return graph. add_node ( FlowControlNode :: Missing ( diag_added) ) ,
@@ -390,6 +396,158 @@ fn create_node_for_struct<'db>(
390396 } ) )
391397}
392398
399+ /// Tries to create a chain of [`SliceDestructure`] nodes for matching a `Span<T>` against
400+ /// fixed-size array patterns with different sizes.
401+ ///
402+ /// Returns `None` if no `FixedSizeArray` patterns are present or the struct is not a `Span`.
403+ /// Each size is tried in order. On failure, the next size is attempted. If all sizes fail,
404+ /// the wildcard/otherwise patterns are used.
405+ #[ allow( clippy:: too_many_arguments) ]
406+ fn try_create_slice_destructure_chain < ' db > (
407+ ctx : & LoweringContext < ' db , ' _ > ,
408+ graph : & mut FlowControlGraphBuilder < ' db > ,
409+ patterns : & [ PatternOption < ' _ , ' db > ] ,
410+ build_node_callback : BuildNodeCallback < ' db , ' _ > ,
411+ location : LocationId < ' db > ,
412+ input_var : FlowControlVar ,
413+ concrete_struct_id : ConcreteStructId < ' db > ,
414+ wrapping_info : PatternWrappingInfo ,
415+ ) -> Option < NodeId > {
416+ if !patterns. iter ( ) . any ( |p| matches ! ( p, Some ( semantic:: Pattern :: FixedSizeArray ( ..) ) ) ) {
417+ return None ;
418+ }
419+ let [ GenericArgumentId :: Type ( elem_ty) ] = concrete_struct_id. long ( ctx. db ) . generic_args [ ..]
420+ else {
421+ return None ;
422+ } ;
423+ if try_get_core_ty_by_name (
424+ ctx. db ,
425+ SmolStrId :: from ( ctx. db , "Span" ) ,
426+ vec ! [ GenericArgumentId :: Type ( elem_ty) ] ,
427+ )
428+ . is_err ( )
429+ {
430+ // Not a Span - report error on the first FixedSizeArray pattern.
431+ let first_fsa = patterns. iter ( ) . find_map ( |p| match p {
432+ Some ( semantic:: Pattern :: FixedSizeArray ( p) ) => Some ( p) ,
433+ _ => None ,
434+ } ) ;
435+ return Some ( graph. report_with_missing_node (
436+ first_fsa. unwrap ( ) . stable_ptr . untyped ( ) ,
437+ LoweringDiagnosticKind :: UnexpectedError ,
438+ ) ) ;
439+ }
440+ // Deconstruct Span<T> to get its single member @Array<T>.
441+ let members = ctx. db . concrete_struct_members ( concrete_struct_id) . ok ( ) ?;
442+ let snapshot_array_ty = members. iter ( ) . next ( ) . unwrap ( ) . 1 . ty ;
443+ let snapshot_array_var = graph. new_var ( snapshot_array_ty, location) ;
444+
445+ // Group patterns by array size. Wildcards/otherwise are added to all groups.
446+ // Use an OrderedHashMap to preserve insertion order (first-seen size first).
447+ let mut size_groups: OrderedHashMap < usize , SizeGroupInfo < ' _ , ' _ > > = OrderedHashMap :: default ( ) ;
448+ let mut wildcard_filter = FilteredPatterns :: default ( ) ;
449+ // Track accumulated wildcards so newly created size groups inherit earlier wildcards.
450+ let mut accumulated_wildcards: Vec < usize > = Vec :: new ( ) ;
451+
452+ for ( idx, pattern) in patterns. iter ( ) . enumerate ( ) {
453+ match pattern {
454+ Some ( semantic:: Pattern :: FixedSizeArray ( p) ) => {
455+ let n = p. elements_patterns . len ( ) ;
456+ let is_new = !size_groups. contains_key ( & n) ;
457+ if is_new {
458+ size_groups. insert ( n, SizeGroupInfo :: default ( ) ) ;
459+ }
460+ let group = size_groups. get_mut ( & n) . unwrap ( ) ;
461+ if is_new {
462+ // Seed new size group with all previously accumulated wildcards.
463+ for & wc_idx in & accumulated_wildcards {
464+ group. filter . add ( wc_idx) ;
465+ group. patterns . push ( None ) ;
466+ }
467+ }
468+ group. filter . add ( idx) ;
469+ group. patterns . push ( * pattern) ;
470+ }
471+ Some ( semantic:: Pattern :: Otherwise ( ..) ) | None => {
472+ wildcard_filter. add ( idx) ;
473+ accumulated_wildcards. push ( idx) ;
474+ for group in size_groups. values_mut ( ) {
475+ group. filter . add ( idx) ;
476+ group. patterns . push ( None ) ;
477+ }
478+ }
479+ _ => unreachable ! ( "Non-FixedSizeArray/Otherwise pattern in slice destructure chain" ) ,
480+ }
481+ }
482+
483+ let sizes: Vec < usize > = size_groups. keys ( ) . copied ( ) . collect ( ) ;
484+
485+ // Build the chain from back to front. The final fallback is the wildcard-only callback.
486+ let mut failure_node = build_node_callback ( graph, wildcard_filter, "[slice_no_match]" . into ( ) ) ;
487+
488+ for & size in sizes. iter ( ) . rev ( ) {
489+ let group = size_groups. swap_remove ( & size) . unwrap ( ) ;
490+ let n = size;
491+ let types = vec ! [ wrap_in_snapshots( ctx. db, elem_ty, 1 ) ; n] ;
492+ let inner_vars = types
493+ . iter ( )
494+ . map ( |ty| graph. new_var ( wrapping_info. wrap ( ctx. db , * ty) , location) )
495+ . collect_vec ( ) ;
496+
497+ // Build the success path: process element patterns within this size group.
498+ let group_filter = group. filter ;
499+ let group_patterns: Vec < PatternOption < ' _ , ' db > > = group. patterns ;
500+ let success = create_node_for_tuple_inner (
501+ CreateNodeParams {
502+ ctx,
503+ graph,
504+ patterns : & group_patterns,
505+ build_node_callback : & mut |graph, pattern_indices, path| {
506+ build_node_callback (
507+ graph,
508+ pattern_indices. lift ( & group_filter) ,
509+ format ! ( "[{path}]" ) ,
510+ )
511+ } ,
512+ location,
513+ } ,
514+ & inner_vars,
515+ & types,
516+ 0 ,
517+ None ,
518+ ) ;
519+
520+ let fixed_array_ty = TypeLongId :: FixedSizeArray {
521+ type_id : elem_ty,
522+ size : ConstValue :: Int ( n. into ( ) , get_usize_ty ( ctx. db ) ) . intern ( ctx. db ) ,
523+ }
524+ . intern ( ctx. db ) ;
525+
526+ failure_node = graph. add_node ( FlowControlNode :: SliceDestructure ( SliceDestructure {
527+ input : snapshot_array_var,
528+ fixed_array_ty,
529+ outputs : inner_vars,
530+ success,
531+ failure : failure_node,
532+ } ) ) ;
533+ }
534+
535+ // Wrap in a Deconstruct to extract @Array<T> from Span<T> once.
536+ let chain = graph. add_node ( FlowControlNode :: Deconstruct ( Deconstruct {
537+ input : input_var,
538+ outputs : vec ! [ snapshot_array_var] ,
539+ next : failure_node,
540+ } ) ) ;
541+
542+ Some ( chain)
543+ }
544+
545+ #[ derive( Default ) ]
546+ struct SizeGroupInfo < ' a , ' db > {
547+ filter : FilteredPatterns ,
548+ patterns : Vec < PatternOption < ' a , ' db > > ,
549+ }
550+
393551/// Helper function for [create_node_for_tuple].
394552///
395553/// `item_idx` is the index of the current member that is being processed in the tuple.
@@ -444,6 +602,13 @@ fn create_node_for_tuple_inner<'db>(
444602 patterns_on_current_item. push ( Some ( inner_pattern) )
445603 }
446604 }
605+ Some ( semantic:: Pattern :: FixedSizeArray ( semantic:: PatternFixedSizeArray {
606+ elements_patterns,
607+ ..
608+ } ) ) if current_member. is_none ( ) => {
609+ patterns_on_current_item
610+ . push ( Some ( get_pattern ( ctx, elements_patterns[ item_idx] ) . clone ( ) ) ) ;
611+ }
447612 Some (
448613 pattern @ ( semantic:: Pattern :: StringLiteral ( ..)
449614 | semantic:: Pattern :: EnumVariant ( ..)
0 commit comments