@@ -2,14 +2,16 @@ 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:: { CorelibSemantic , try_get_core_ty_by_name, validate_literal} ;
67use cairo_lang_semantic:: expr:: compute:: unwrap_pattern_type;
78use cairo_lang_semantic:: items:: enm:: SemanticEnumEx ;
89use cairo_lang_semantic:: items:: structure:: StructSemantic ;
10+ use cairo_lang_semantic:: types:: wrap_in_snapshots;
911use cairo_lang_semantic:: {
1012 self as semantic, ConcreteEnumId , ConcreteStructId , ConcreteTypeId , ExprNumericLiteral ,
11- PatternEnumVariant , PatternLiteral , PatternStruct , PatternTuple , PatternWrappingInfo , TypeId ,
12- TypeLongId , corelib,
13+ GenericArgumentId , PatternEnumVariant , PatternLiteral , PatternStruct , PatternTuple ,
14+ PatternWrappingInfo , TypeId , TypeLongId , corelib,
1315} ;
1416use cairo_lang_syntax:: node:: TypedStablePtr ;
1517use cairo_lang_syntax:: node:: ast:: ExprPtr ;
@@ -26,7 +28,9 @@ use super::filtered_patterns::{Bindings, FilteredPatterns};
2628use crate :: diagnostic:: { LoweringDiagnosticKind , MatchDiagnostic , MatchError } ;
2729use crate :: ids:: LocationId ;
2830use crate :: lower:: context:: LoweringContext ;
29- use crate :: lower:: flow_control:: graph:: { Downcast , EqualsLiteral , Upcast , ValueMatch } ;
31+ use crate :: lower:: flow_control:: graph:: {
32+ Downcast , EqualsLiteral , SliceDestructure , Upcast , ValueMatch ,
33+ } ;
3034
3135/// A callback that gets a [FilteredPatterns] and constructs a node that continues the pattern
3236/// matching restricted to the filtered patterns.
@@ -150,21 +154,6 @@ pub fn create_node_for_patterns<'db>(
150154 create_node_for_enum ( params, input_var, concrete_enum_id, wrapping_info)
151155 }
152156 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- }
168157 create_node_for_struct ( params, input_var, concrete_struct_id, wrapping_info)
169158 }
170159 TypeLongId :: Tuple ( types) => create_node_for_tuple ( params, input_var, & types, wrapping_info) ,
@@ -354,6 +343,19 @@ fn create_node_for_struct<'db>(
354343) -> NodeId {
355344 let CreateNodeParams { ctx, graph, patterns, build_node_callback, location } = params;
356345
346+ if let Some ( node) = try_create_slice_destructure_chain (
347+ ctx,
348+ graph,
349+ patterns,
350+ build_node_callback,
351+ location,
352+ input_var,
353+ concrete_struct_id,
354+ wrapping_info,
355+ ) {
356+ return node;
357+ }
358+
357359 let members = match ctx. db . concrete_struct_members ( concrete_struct_id) {
358360 Ok ( members) => members,
359361 Err ( diag_added) => return graph. add_node ( FlowControlNode :: Missing ( diag_added) ) ,
@@ -390,6 +392,163 @@ fn create_node_for_struct<'db>(
390392 } ) )
391393}
392394
395+ /// Tries to create a chain of [`SliceDestructure`] nodes for matching a `Span<T>` against
396+ /// fixed-size array patterns with different sizes.
397+ ///
398+ /// Returns `None` if no `FixedSizeArray` patterns are present or the struct is not a `Span`.
399+ /// Each size is tried in order. On failure, the next size is attempted. If all sizes fail,
400+ /// the wildcard/otherwise patterns are used.
401+ #[ allow( clippy:: too_many_arguments) ]
402+ fn try_create_slice_destructure_chain < ' db > (
403+ ctx : & LoweringContext < ' db , ' _ > ,
404+ graph : & mut FlowControlGraphBuilder < ' db > ,
405+ patterns : & [ PatternOption < ' _ , ' db > ] ,
406+ build_node_callback : BuildNodeCallback < ' db , ' _ > ,
407+ location : LocationId < ' db > ,
408+ input_var : FlowControlVar ,
409+ concrete_struct_id : ConcreteStructId < ' db > ,
410+ wrapping_info : PatternWrappingInfo ,
411+ ) -> Option < NodeId > {
412+ if !patterns. iter ( ) . any ( |p| matches ! ( p, Some ( semantic:: Pattern :: FixedSizeArray ( ..) ) ) ) {
413+ return None ;
414+ }
415+ let [ GenericArgumentId :: Type ( elem_ty) ] = concrete_struct_id. long ( ctx. db ) . generic_args [ ..]
416+ else {
417+ return None ;
418+ } ;
419+ if try_get_core_ty_by_name (
420+ ctx. db ,
421+ SmolStrId :: from ( ctx. db , "Span" ) ,
422+ vec ! [ GenericArgumentId :: Type ( elem_ty) ] ,
423+ )
424+ . is_err ( )
425+ {
426+ // Not a Span - report error on the first FixedSizeArray pattern.
427+ let first_fsa = patterns. iter ( ) . find_map ( |p| match p {
428+ Some ( semantic:: Pattern :: FixedSizeArray ( p) ) => Some ( p) ,
429+ _ => None ,
430+ } ) ;
431+ return Some ( graph. report_with_missing_node (
432+ first_fsa. unwrap ( ) . stable_ptr . untyped ( ) ,
433+ LoweringDiagnosticKind :: UnexpectedError ,
434+ ) ) ;
435+ }
436+ // Deconstruct Span<T> to get its single member @Array<T>.
437+ let members = ctx. db . concrete_struct_members ( concrete_struct_id) . ok ( ) ?;
438+ let snapshot_array_ty = members. iter ( ) . next ( ) . unwrap ( ) . 1 . ty ;
439+ let snapshot_array_var = graph. new_var ( snapshot_array_ty, location) ;
440+
441+ // Group patterns by array size. Wildcards/otherwise are added to all groups.
442+ // Use an OrderedHashMap to preserve insertion order (first-seen size first).
443+ let mut size_groups: OrderedHashMap < usize , SizeGroupInfo < ' _ , ' _ > > = OrderedHashMap :: default ( ) ;
444+ let mut wildcard_filter = FilteredPatterns :: default ( ) ;
445+
446+ for ( idx, pattern) in patterns. iter ( ) . enumerate ( ) {
447+ match pattern {
448+ Some ( semantic:: Pattern :: FixedSizeArray ( p) ) => {
449+ let n = p. elements_patterns . len ( ) ;
450+ let group = size_groups. entry ( n) . or_default ( ) ;
451+
452+ group. filter . add ( idx) ;
453+ group. patterns . push ( * pattern) ;
454+ }
455+ Some ( semantic:: Pattern :: Otherwise ( ..) ) | None => {
456+ wildcard_filter. add ( idx) ;
457+ for group in size_groups. values_mut ( ) {
458+ group. filter . add ( idx) ;
459+ group. patterns . push ( None ) ;
460+ }
461+ // Patterns after the wildcard pattern are unreachable, so we can break.
462+ break ;
463+ }
464+ Some ( pattern) => {
465+ // This should not be reachable without getting a semantic error.
466+ return Some ( graph. report_with_missing_node (
467+ pattern. stable_ptr ( ) . untyped ( ) ,
468+ LoweringDiagnosticKind :: UnexpectedError ,
469+ ) ) ;
470+ }
471+ }
472+ }
473+
474+ // Build the chain from back to front, since we need the fallback node before we can create a
475+ // node. The final fallback is the wildcard-only callback.
476+ // Note: `"_"` is used as the pattern for the non-exhaustive diagnostic, as there is no `[..]`
477+ // pattern.
478+ let mut failure_node = build_node_callback ( graph, wildcard_filter, "_" . into ( ) ) ;
479+
480+ for ( size, group) in size_groups. into_iter ( ) . rev ( ) {
481+ let types = vec ! [ wrap_in_snapshots( ctx. db, elem_ty, 1 ) ; size] ;
482+ let inner_vars = types
483+ . iter ( )
484+ . map ( |ty| graph. new_var ( wrapping_info. wrap ( ctx. db , * ty) , location) )
485+ . collect_vec ( ) ;
486+
487+ // Build the success path: process element patterns within this size group.
488+ let group_filter = group. filter ;
489+ let group_patterns: Vec < PatternOption < ' _ , ' db > > = group. patterns ;
490+ let success = create_node_for_tuple_inner (
491+ CreateNodeParams {
492+ ctx,
493+ graph,
494+ patterns : & group_patterns,
495+ build_node_callback : & mut |graph, pattern_indices, path| {
496+ build_node_callback (
497+ graph,
498+ pattern_indices. lift ( & group_filter) ,
499+ format ! ( "[{path}]" ) ,
500+ )
501+ } ,
502+ location,
503+ } ,
504+ & inner_vars,
505+ & types,
506+ 0 ,
507+ None ,
508+ ) ;
509+
510+ failure_node = graph. add_node ( FlowControlNode :: SliceDestructure ( SliceDestructure {
511+ input : snapshot_array_var,
512+ element_ty : elem_ty,
513+ outputs : inner_vars,
514+ success,
515+ failure : failure_node,
516+ } ) ) ;
517+ }
518+
519+ // Wrap in a Deconstruct to extract @Array<T> from Span<T> once.
520+ let chain = graph. add_node ( FlowControlNode :: Deconstruct ( Deconstruct {
521+ input : input_var,
522+ outputs : vec ! [ snapshot_array_var] ,
523+ next : failure_node,
524+ } ) ) ;
525+
526+ Some ( chain)
527+ }
528+
529+ /// Information accumulated for a single array-size group while lowering a `Span<T>` match.
530+ ///
531+ /// When matching a `Span<T>` against multiple fixed-size array patterns (e.g. `[a, b]`,
532+ /// `[a, b, c]`), the patterns are partitioned by their length. Each distinct length gets its
533+ /// own `SizeGroupInfo`.
534+ #[ derive( Default ) ]
535+ struct SizeGroupInfo < ' a , ' db > {
536+ /// The indices (into the original list of match arms) of the patterns that belong to this
537+ /// size group — including any trailing wildcard/`_` patterns, which apply to every group.
538+ filter : FilteredPatterns ,
539+ /// The per-arm patterns to dispatch on once the span has been destructured into a
540+ /// fixed-size array of this length. Wildcards appear as `None`.
541+ ///
542+ /// Note: unlike [VariantInfo], which stores the inner pattern of each `EnumVariant` arm,
543+ /// here we store the whole `FixedSizeArray` pattern. The next stage —
544+ /// [create_node_for_tuple_inner] — walks elements by index and extracts
545+ /// `elements_patterns[item_idx]` itself (see the `FixedSizeArray` arm in that function), so
546+ /// pre-unpacking would just force us to duplicate or undo that logic. Enum variants have a
547+ /// single inner pattern and feed into [create_node_for_patterns], which wants it already
548+ /// unwrapped, hence the asymmetry.
549+ patterns : Vec < PatternOption < ' a , ' db > > ,
550+ }
551+
393552/// Helper function for [create_node_for_tuple].
394553///
395554/// `item_idx` is the index of the current member that is being processed in the tuple.
@@ -444,6 +603,13 @@ fn create_node_for_tuple_inner<'db>(
444603 patterns_on_current_item. push ( Some ( inner_pattern) )
445604 }
446605 }
606+ Some ( semantic:: Pattern :: FixedSizeArray ( semantic:: PatternFixedSizeArray {
607+ elements_patterns,
608+ ..
609+ } ) ) if current_member. is_none ( ) => {
610+ patterns_on_current_item
611+ . push ( Some ( get_pattern ( ctx, elements_patterns[ item_idx] ) . clone ( ) ) ) ;
612+ }
447613 Some (
448614 pattern @ ( semantic:: Pattern :: StringLiteral ( ..)
449615 | semantic:: Pattern :: EnumVariant ( ..)
0 commit comments