diff --git a/corelib/src/test/language_features/match_test.cairo b/corelib/src/test/language_features/match_test.cairo index eeb03ae71b8..d0ab894513d 100644 --- a/corelib/src/test/language_features/match_test.cairo +++ b/corelib/src/test/language_features/match_test.cairo @@ -12,6 +12,29 @@ fn test_match_multienum_binding() { panic!("Match expression did not return - this should be unreachable"); } +#[test] +fn test_match_span_to_fixed_size_array() { + let span: Span = array![10, 20, 30].span(); + + match span { + [_a, _b, _c, _d] => { panic!("Expected 3 elements, but got 4"); }, + [_a, _b] => { panic!("Expected 3 elements, but got 2"); }, + [a, b, c] => { assert_eq!((*a, *b, *c), (10, 20, 30)); }, + _ => panic!("Expected 3 elements, but got a different pattern"), + } +} + +#[test] +fn test_match_span_empty_pattern() { + let span: Span = array![].span(); + + match span { + [_a] => { panic!("Expected 0 elements, but got 1"); }, + [] => {}, + _ => panic!("Expected 0 elements, but got a different count"), + } +} + #[test] fn test_match_extern_multilevel() { if true { @@ -24,3 +47,59 @@ fn test_match_extern_multilevel() { } panic!("Match expression did not return - this should be unreachable"); } + + +#[test] +fn test_match_span_inner_pattern_mismatch() { + let matcher = |s: Array>| match s.span() { + [Some(_)] => 1, + [None] => 2, + _ => 0, + }; + + assert_eq!(matcher(array![Some(42)]), 1); + assert_eq!(matcher(array![None]), 2); + assert_eq!(matcher(array![Some(1), Some(2)]), 0); +} + +#[test] +fn test_match_span_fsa_with_struct_catch_all() { + let matcher = |s: Span| match s { + [a] => *a, + Span { snapshot: inner } => inner.len(), + }; + + assert_eq!(matcher(array![42].span()), 42); + assert_eq!(matcher(array![].span()), 0); + assert_eq!(matcher(array![1, 2, 3].span()), 3); +} + +#[test] +fn test_match_span_fsa_struct_between_sizes() { + let matcher = |s: Span, val: u32| match (s, val) { + ([a], v) => *a + v, + (Span { snapshot: inner }, 5) => 2 * inner.len(), + ([a, b], v) => *a + *b + v, + ([a, b, c], v) => *a + *b + *c + v, + _ => 0, + }; + + // `[a]` arm wins for size 1 regardless of `val`. + assert_eq!(matcher(array![10].span(), 7), 17); + assert_eq!(matcher(array![10].span(), 5), 15); + + // Struct arm wins when `val == 5` and size != 1 (covers sizes outside `[a, b]` / `[a, b, c]` + // too). + assert_eq!(matcher(array![].span(), 5), 0); + assert_eq!(matcher(array![1, 2].span(), 5), 4); + assert_eq!(matcher(array![1, 2, 3].span(), 5), 6); + assert_eq!(matcher(array![1, 2, 3, 4].span(), 5), 8); + + // `[a, b]` and `[a, b, c]` arms win for their sizes when `val != 5`. + assert_eq!(matcher(array![10, 20].span(), 7), 37); + assert_eq!(matcher(array![10, 20, 30].span(), 7), 67); + + // Wildcard arm — `val != 5` and size not in {1, 2, 3}. + assert_eq!(matcher(array![].span(), 7), 0); + assert_eq!(matcher(array![1, 2, 3, 4].span(), 7), 0); +} diff --git a/crates/cairo-lang-lowering/src/lower/flow_control/create_graph/patterns.rs b/crates/cairo-lang-lowering/src/lower/flow_control/create_graph/patterns.rs index 3b7d1ea6bcc..8104aec3c33 100644 --- a/crates/cairo-lang-lowering/src/lower/flow_control/create_graph/patterns.rs +++ b/crates/cairo-lang-lowering/src/lower/flow_control/create_graph/patterns.rs @@ -6,10 +6,11 @@ use cairo_lang_semantic::corelib::{CorelibSemantic, validate_literal}; use cairo_lang_semantic::expr::compute::unwrap_pattern_type; use cairo_lang_semantic::items::enm::SemanticEnumEx; use cairo_lang_semantic::items::structure::StructSemantic; +use cairo_lang_semantic::types::wrap_in_snapshots; use cairo_lang_semantic::{ self as semantic, ConcreteEnumId, ConcreteStructId, ConcreteTypeId, ExprNumericLiteral, - PatternEnumVariant, PatternLiteral, PatternStruct, PatternTuple, PatternWrappingInfo, TypeId, - TypeLongId, corelib, + GenericArgumentId, PatternEnumVariant, PatternLiteral, PatternStruct, PatternTuple, + PatternWrappingInfo, TypeId, TypeLongId, corelib, }; use cairo_lang_syntax::node::TypedStablePtr; use cairo_lang_syntax::node::ast::ExprPtr; @@ -26,7 +27,9 @@ use super::filtered_patterns::{Bindings, FilteredPatterns}; use crate::diagnostic::{LoweringDiagnosticKind, MatchDiagnostic, MatchError}; use crate::ids::LocationId; use crate::lower::context::LoweringContext; -use crate::lower::flow_control::graph::{Downcast, EqualsLiteral, Upcast, ValueMatch}; +use crate::lower::flow_control::graph::{ + Downcast, EqualsLiteral, SliceDestructure, Upcast, ValueMatch, +}; /// A callback that gets a [FilteredPatterns] and constructs a node that continues the pattern /// matching restricted to the filtered patterns. @@ -150,21 +153,6 @@ pub fn create_node_for_patterns<'db>( create_node_for_enum(params, input_var, concrete_enum_id, wrapping_info) } TypeLongId::Concrete(ConcreteTypeId::Struct(concrete_struct_id)) => { - // Check if any non-any pattern is a FixedSizeArray (i.e. Span destructure). - // Span destructuring in match/if-let is not yet supported in lowering. - let has_fixed_size_array_pattern = patterns - .iter() - .flatten() - .any(|p| matches!(p, semantic::Pattern::FixedSizeArray(..))); - if has_fixed_size_array_pattern { - return graph.report_with_missing_node( - first_non_any_pattern.stable_ptr(), - LoweringDiagnosticKind::MatchError(MatchError { - kind: graph.kind(), - error: MatchDiagnostic::UnsupportedMatchedType(long_ty.format(ctx.db)), - }), - ); - } create_node_for_struct(params, input_var, concrete_struct_id, wrapping_info) } TypeLongId::Tuple(types) => create_node_for_tuple(params, input_var, &types, wrapping_info), @@ -352,6 +340,15 @@ fn create_node_for_struct<'db>( concrete_struct_id: ConcreteStructId<'db>, wrapping_info: PatternWrappingInfo, ) -> NodeId { + if let Some(elem_ty) = should_create_slice_destructure_chain( + params.ctx.db, + params.patterns, + concrete_struct_id, + wrapping_info, + ) { + return create_slice_destructure_chain(params, input_var, concrete_struct_id, elem_ty); + } + let CreateNodeParams { ctx, graph, patterns, build_node_callback, location } = params; let members = match ctx.db.concrete_struct_members(concrete_struct_id) { @@ -390,6 +387,245 @@ fn create_node_for_struct<'db>( })) } +/// Decides whether [create_slice_destructure_chain] should be used for the given patterns and +/// struct type. Returns `Some(elem_ty)` (the `T` of `Span`) when slice destructure should be +/// attempted, `None` to fall back to ordinary struct destructure. +fn should_create_slice_destructure_chain<'db>( + db: &'db dyn Database, + patterns: &[PatternOption<'_, 'db>], + concrete_struct_id: ConcreteStructId<'db>, + wrapping_info: PatternWrappingInfo, +) -> Option> { + if !patterns.iter().any(|p| matches!(p, Some(semantic::Pattern::FixedSizeArray(..)))) { + return None; + } + // Semantic rejects matching on `@Span` / `Box>` with fixed-size array patterns + // (see the `conform_ty(ty, Span<_>)` check in `try_get_match_expr_long_ty`), so by the time + // this function is entered `wrapping_info` is always trivial. `lower_slice_destructure` and + // the `StructConstruct(Span)` it emits rely on this: `snapshot_array_var` below must be + // exactly `@Array`, not a wrapped variant. + if wrapping_info.n_outer_snapshots != 0 || wrapping_info.n_boxed_inner_snapshots.is_some() { + return None; + } + // Semantic also guarantees that a `FixedSizeArray` pattern against a struct type has been + // validated to be `Span`, so `concrete_struct_id` here is always `Span`. + let [GenericArgumentId::Type(elem_ty)] = concrete_struct_id.long(db).generic_args[..] else { + return None; + }; + Some(elem_ty) +} + +/// Creates a chain of [`SliceDestructure`] nodes for matching a `Span` against fixed-size +/// array patterns with different sizes. +/// +/// Each size is tried in order. On failure, the next size is attempted. If all sizes fail, +/// the wildcard/otherwise patterns are used. +/// +/// Caller must have verified via [should_create_slice_destructure_chain] that this is the right +/// path and supplied the corresponding `elem_ty`. +fn create_slice_destructure_chain<'db>( + params: CreateNodeParams<'db, '_, '_>, + input_var: FlowControlVar, + concrete_struct_id: ConcreteStructId<'db>, + elem_ty: TypeId<'db>, +) -> NodeId { + let CreateNodeParams { ctx, graph, patterns, build_node_callback, location } = params; + // Deconstruct Span to get its single member @Array. + let members = match ctx.db.concrete_struct_members(concrete_struct_id) { + Ok(members) => members, + Err(diag_added) => return graph.add_node(FlowControlNode::Missing(diag_added)), + }; + let (snapshot_member_id, snapshot_array_ty) = match members.iter().exactly_one() { + Ok((_, member)) => (member.id, member.ty), + Err(e) => panic!("Got wrong number of `Span` members: `{e:?}`."), + }; + let snapshot_array_var = graph.new_var(snapshot_array_ty, location); + + // Group patterns by array size. Each key `n` holds the arms that match a span of length + // `n`. The fallback group — used when no size-specific arm matches — is kept separately in + // `fallback_group` and turned into the initial failure node of the chain below. + let mut size_groups: OrderedHashMap> = OrderedHashMap::default(); + let mut fallback_group = SizeGroupInfo::default(); + // Per-arm [Bindings], aligned with the original patterns list. Mostly `Bindings::default()`; + // a `Pattern::Struct` arm (`Span { snapshot: inner }`) sets its slot to + // `Bindings::single(snapshot_array_var, inner_pattern_var)` so that arm-selection via any + // chain link (size group or fallback) attaches `inner`'s binding after lifting back to the + // original pattern index. + let mut bindings: Vec = vec![Bindings::default(); patterns.len()]; + // Whether any `Pattern::Struct` arm contributes to the fallback. Used to decide between the + // plain `"_"` non-exhaustive path string and a more descriptive `"Span { snapshot: ... }"` + // path when struct patterns are present. + let mut has_struct_arm = false; + + for (idx, pattern) in patterns.iter().enumerate() { + match pattern { + Some(semantic::Pattern::FixedSizeArray(p)) => { + let n = p.elements_patterns.len(); + // When a new size is introduced, seed from the fallback so prior wildcards and + // struct arms are included (`inner_patterns` carries `None`s from those arms). + let group = size_groups.entry(n).or_insert_with(|| fallback_group.clone()); + group.filter.add(idx); + group.inner_patterns.push(*pattern); + } + Some(semantic::Pattern::Struct(PatternStruct { field_patterns, .. })) => { + // `should_create_slice_destructure_chain` has already ensured the matched type is + // `Span`, so this struct pattern is a `Span<...>` pattern. Since `Span` has a + // single `snapshot: @Array` member, the struct pattern acts as a catch-all for + // the fixed-size-array chain — it always matches *some* `Span` — and we only + // need to capture the inner pattern for the snapshot field. + let snapshot_field_pattern = field_patterns + .iter() + .find(|(_, member)| member.id == snapshot_member_id) + .map(|(p, _)| get_pattern(ctx, *p)); + // Only Variable/Otherwise (or missing) inner patterns are supported here. More + // complex patterns would require dispatching them against a fixed-size-array + // path inside the chain, which isn't implemented. Falling back to the regular + // struct-destructure route is not possible once we've committed to the chain, + // so report an error. + match snapshot_field_pattern { + None + | Some(semantic::Pattern::Variable(..)) + | Some(semantic::Pattern::Otherwise(..)) => {} + Some(other) => { + return graph.report_with_missing_node( + other.stable_ptr().untyped(), + LoweringDiagnosticKind::UnexpectedError, + ); + } + } + has_struct_arm = true; + // Register `snapshot`'s `Variable` (if any) and record the binding at this arm's + // original-pattern index in `bindings`. Every chain path lifts back to the + // original index, so the binding fires from whichever link matches. + if let Some(semantic::Pattern::Variable(pv)) = snapshot_field_pattern { + let pattern_var = graph.register_pattern_var(pv.clone()); + bindings[idx] = Bindings::single(snapshot_array_var, pattern_var); + } + // The struct arm is a catch-all for the chain — add it to every group with a + // wildcard inner pattern (`None`). We store `None` rather than the `Variable` + // so element-level dispatch doesn't re-register the pattern var; the binding + // lives in `bindings` instead. + for group in size_groups.values_mut() { + group.filter.add(idx); + group.inner_patterns.push(None); + } + fallback_group.filter.add(idx); + fallback_group.inner_patterns.push(None); + } + Some(semantic::Pattern::Otherwise(..)) | None => { + for group in size_groups.values_mut() { + group.filter.add(idx); + group.inner_patterns.push(None); + } + fallback_group.filter.add(idx); + fallback_group.inner_patterns.push(None); + } + Some(pattern) => { + // This should not be reachable without getting a semantic error. + return graph.report_with_missing_node( + pattern.stable_ptr().untyped(), + LoweringDiagnosticKind::UnexpectedError, + ); + } + } + } + + // Build the chain back-to-front, starting with the fallback — the terminal arm-selection + // reached when no size-specific link succeeds. The fallback's `inner_patterns` is all-`None` + // (struct arms' `inner → snapshot_array_var` binding lives in the shared `bindings`), so + // there's nothing to dispatch on: an all-accepting filter is lifted to the original indices + // and has the top-level `bindings` attached before being handed to the outer callback. The + // path string is `"_"` because the match has no `[..]` syntax to name the fallback case. + let mut current_node = { + let path = if has_struct_arm { + let struct_name = concrete_struct_id.struct_id(ctx.db).name(ctx.db).long(ctx.db); + format!("{struct_name} {{ snapshot: _ }}") + } else { + "_".into() + }; + let filter = FilteredPatterns::all(fallback_group.inner_patterns.len()) + .lift(&fallback_group.filter) + .add_bindings(&bindings); + build_node_callback(graph, filter, path) + }; + + // Wrap the fallback in `SliceDestructure` links for each size, in reverse insertion order. + for (size, group) in size_groups.into_iter().rev() { + let types = vec![wrap_in_snapshots(ctx.db, elem_ty, 1); size]; + let inner_vars = types.iter().map(|ty| graph.new_var(*ty, location)).collect_vec(); + + // Build the success path: process element patterns within this size group. + let group_filter = group.filter; + let group_patterns: Vec> = group.inner_patterns; + let success = create_node_for_tuple_inner( + CreateNodeParams { + ctx, + graph, + patterns: &group_patterns, + build_node_callback: &mut |graph, pattern_indices, path| { + build_node_callback( + graph, + pattern_indices.lift(&group_filter).add_bindings(&bindings), + format!("[{path}]"), + ) + }, + location, + }, + &inner_vars, + &types, + 0, + None, + ); + + // Optimization: if the success and failure branches lead to the same node and the + // destructured elements are unused, skip this `SliceDestructure`. This keeps the graph + // tight when a fixed-size-array arm is unreachable (e.g. appearing after a wildcard), + // which would otherwise emit a runtime `try_into` whose outcome doesn't matter. + current_node = + if success == current_node && inner_vars.iter().all(|v| !graph.is_var_used(*v)) { + success + } else { + graph.add_node(FlowControlNode::SliceDestructure(SliceDestructure { + input: snapshot_array_var, + element_ty: elem_ty, + outputs: inner_vars, + success, + failure: current_node, + })) + }; + } + + // Wrap in a Deconstruct to extract @Array from Span once. + graph.add_node(FlowControlNode::Deconstruct(Deconstruct { + input: input_var, + outputs: vec![snapshot_array_var], + next: current_node, + })) +} + +/// Information accumulated for a single array-size group while lowering a `Span` match. +/// +/// When matching a `Span` against multiple fixed-size array patterns (e.g. `[a, b]`, +/// `[a, b, c]`), the patterns are partitioned by their length. Each distinct length gets its +/// own `SizeGroupInfo`. +#[derive(Clone, Default)] +struct SizeGroupInfo<'a, 'db> { + /// The indices (into the original list of match arms) of the patterns that belong to this + /// size group — including any trailing wildcard/`_` patterns, which apply to every group. + filter: FilteredPatterns, + /// The per-arm patterns to dispatch on once the span has been destructured into a + /// fixed-size array of this length. Wildcards appear as `None`. + /// + /// Note: unlike [VariantInfo::inner_patterns], which stores the inner pattern of each + /// `EnumVariant` arm, here we store the whole `FixedSizeArray` pattern. The next stage — + /// [create_node_for_tuple_inner] — walks elements by index and extracts + /// `elements_patterns[item_idx]` itself (see the `FixedSizeArray` arm in that function), so + /// pre-unpacking would just force us to duplicate or undo that logic. Enum variants have a + /// single inner pattern and feed into [create_node_for_patterns], which wants it already + /// unwrapped, hence the asymmetry. + inner_patterns: Vec>, +} + /// Helper function for [create_node_for_tuple]. /// /// `item_idx` is the index of the current member that is being processed in the tuple. @@ -444,6 +680,13 @@ fn create_node_for_tuple_inner<'db>( patterns_on_current_item.push(Some(inner_pattern)) } } + Some(semantic::Pattern::FixedSizeArray(semantic::PatternFixedSizeArray { + elements_patterns, + .. + })) if current_member.is_none() => { + patterns_on_current_item + .push(Some(get_pattern(ctx, elements_patterns[item_idx]).clone())); + } Some( pattern @ (semantic::Pattern::StringLiteral(..) | semantic::Pattern::EnumVariant(..) diff --git a/crates/cairo-lang-lowering/src/lower/flow_control/graph.rs b/crates/cairo-lang-lowering/src/lower/flow_control/graph.rs index 1b86da564d1..e7a5bf38d48 100644 --- a/crates/cairo-lang-lowering/src/lower/flow_control/graph.rs +++ b/crates/cairo-lang-lowering/src/lower/flow_control/graph.rs @@ -224,6 +224,36 @@ pub struct Downcast { pub out_of_range: NodeId, } +/// Destructures a `@Array` into a fixed-size array `[T; N]` via `TryInto, @Box<[T; +/// N]>>`. +/// +/// On success, the array has exactly `N` elements and the output variables are bound to them. +/// On failure (wrong number of elements), execution continues to the `failure` node. +pub struct SliceDestructure<'db> { + /// The input `@Array` variable (already extracted from the Span). + pub input: FlowControlVar, + /// The element type `T`. The array size `N` is `outputs.len()`. + pub element_ty: semantic::TypeId<'db>, + /// The output element variables (if the slice has the right size). + pub outputs: Vec, + /// The next node if the slice has the right number of elements. + pub success: NodeId, + /// The next node if the slice doesn't have the right number of elements. + pub failure: NodeId, +} + +impl<'db> std::fmt::Debug for SliceDestructure<'db> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Ignore the element type for the debug output, it requires the db for formatting and is + // not interesting enough. + let SliceDestructure { input, element_ty: _, outputs, success, failure } = self; + write!( + f, + "SliceDestructure {{ input: {:?}, outputs: {:?}, success: {:?}, failure: {:?}}}", + input, outputs, success, failure, + ) + } +} /// An arm (final node) that returns a tuple of bound variables for the let-else success arm. /// /// See [crate::lower::lower_let_else::lower_let_else] for more details. @@ -257,6 +287,9 @@ pub enum FlowControlNode<'db> { Upcast(Upcast), /// Downcasts a value to a smaller type. Downcast(Downcast), + /// Unpacks an `@Array` (already extracted from a `Span`) into a fixed-size array + /// `[T; N]`. + SliceDestructure(SliceDestructure<'db>), /// An arm (final node) that returns a tuple of bound variables for the let-else success arm. LetElseSuccess(LetElseSuccess<'db>), /// An arm (final node) that returns a unit value - `()`. @@ -285,6 +318,7 @@ impl<'db> FlowControlNode<'db> { FlowControlNode::BindVar(node) => Some(node.input), FlowControlNode::Upcast(node) => Some(node.input), FlowControlNode::Downcast(node) => Some(node.input), + FlowControlNode::SliceDestructure(node) => Some(node.input), FlowControlNode::LetElseSuccess(..) => None, FlowControlNode::UnitResult => None, FlowControlNode::Missing(_) => None, @@ -306,6 +340,7 @@ impl<'db> Debug for FlowControlNode<'db> { FlowControlNode::BindVar(node) => node.fmt(f), FlowControlNode::Upcast(node) => node.fmt(f), FlowControlNode::Downcast(node) => node.fmt(f), + FlowControlNode::SliceDestructure(node) => node.fmt(f), FlowControlNode::LetElseSuccess(node) => node.fmt(f), FlowControlNode::UnitResult => write!(f, "UnitResult"), FlowControlNode::Missing(_) => write!(f, "Missing"), diff --git a/crates/cairo-lang-lowering/src/lower/flow_control/lower_graph/lower_node.rs b/crates/cairo-lang-lowering/src/lower/flow_control/lower_graph/lower_node.rs index 02bbe0ce34f..2c6d89a8738 100644 --- a/crates/cairo-lang-lowering/src/lower/flow_control/lower_graph/lower_node.rs +++ b/crates/cairo-lang-lowering/src/lower/flow_control/lower_graph/lower_node.rs @@ -1,11 +1,15 @@ //! Functions for lowering nodes of a [super::FlowControlGraph]. use cairo_lang_diagnostics::Maybe; -use cairo_lang_semantic::corelib::{CorelibSemantic, unit_ty}; +use cairo_lang_filesystem::ids::SmolStrId; +use cairo_lang_semantic::corelib::{CorelibSemantic, get_usize_ty, unit_ty}; +use cairo_lang_semantic::items::constant::ConstValue; +use cairo_lang_semantic::items::functions::{GenericFunctionId, ImplGenericFunctionId}; use cairo_lang_semantic::{ self as semantic, GenericArgumentId, MatchArmSelector, ValueSelectorArm, corelib, }; use cairo_lang_syntax::node::TypedStablePtr; +use cairo_lang_utils::Intern; use itertools::{Itertools, zip_eq}; use num_bigint::BigInt; @@ -19,7 +23,7 @@ use crate::lower::context::{LoweredExpr, LoweredExprExternEnum, VarRequest}; use crate::lower::external::extern_facade_expr; use crate::lower::flow_control::graph::{ ArmExpr, BindVar, BooleanIf, Deconstruct, Downcast, EnumMatch, EqualsLiteral, EvaluateExpr, - FlowControlNode, LetElseSuccess, NodeId, Upcast, ValueMatch, WhileBody, + FlowControlNode, LetElseSuccess, NodeId, SliceDestructure, Upcast, ValueMatch, WhileBody, }; use crate::lower::lower_let_else::lower_success_arm_body; use crate::lower::{ @@ -59,6 +63,7 @@ pub fn lower_node(ctx: &mut LowerGraphContext<'_, '_, '_>, id: NodeId) -> Maybe< FlowControlNode::Deconstruct(node) => lower_deconstruct(ctx, id, node, builder), FlowControlNode::Upcast(node) => lower_upcast(ctx, id, node, builder), FlowControlNode::Downcast(node) => lower_downcast(ctx, id, node, builder), + FlowControlNode::SliceDestructure(node) => lower_slice_destructure(ctx, id, node, builder), FlowControlNode::LetElseSuccess(node) => lower_let_else_success(ctx, id, node, builder), FlowControlNode::Missing(diag_added) => Err(*diag_added), } @@ -526,6 +531,154 @@ fn lower_downcast<'db>( Ok(()) } +/// Lowers a [`SliceDestructure`] node. +/// +/// The input is `@Array` (already extracted from the Span by a preceding `Deconstruct`). +/// +/// Generates: +/// 1. Reconstruct `Span` from `@Array` via `StructConstruct`. +/// 2. Call `TryInto::, @Box<[T; N]>>::try_into(span)` → `Option<@Box<[T; N]>>`. +/// 3. Match on the `Option`: +/// - `Some(@Box<[T; N]>)` → `box_forward_snapshot` → unbox → destructure into N elements +/// - `None` → continue to failure node +fn lower_slice_destructure<'db>( + ctx: &mut LowerGraphContext<'db, '_, '_>, + id: NodeId, + node: &SliceDestructure<'db>, + mut builder: BlockBuilder<'db>, +) -> Maybe<()> { + let db = ctx.ctx.db; + let location = ctx.location; + let snapshot_array_usage = ctx.var_usage(node.input); + let elem_ty = node.element_ty; + let fixed_array_ty = semantic::TypeLongId::FixedSizeArray { + type_id: elem_ty, + size: ConstValue::Int(node.outputs.len().into(), get_usize_ty(db)).intern(db), + } + .intern(db); + let snapshot_box_ty = + semantic::TypeLongId::Snapshot(corelib::core_box_ty(db, fixed_array_ty)).intern(db); + + let info = db.core_info(); + + // Reconstruct Span from @Array. + let span_ty = corelib::try_get_core_ty_by_name( + db, + SmolStrId::from(db, "Span"), + vec![GenericArgumentId::Type(elem_ty)], + ) + .expect("Span type must exist"); + let span_usage = + generators::StructConstruct { inputs: vec![snapshot_array_usage], ty: span_ty, location } + .add(ctx.ctx, &mut builder.statements); + + // Resolve TryInto, @Box<[T; SIZE]>> impl. + let concrete_trait = semantic::ConcreteTraitLongId { + trait_id: info.try_into_trt, + generic_args: vec![ + GenericArgumentId::Type(span_ty), + GenericArgumentId::Type(snapshot_box_ty), + ], + } + .intern(db); + let lookup_context = ctx.ctx.variables.lookup_context; + let impl_id = semantic::types::get_impl_at_context(db, lookup_context, concrete_trait, None) + .expect("TryInto, @Box<[T; SIZE]>> impl must exist"); + + // Call TryInto::try_into(span) -> Option<@Box<[T; SIZE]>>. + let generic_function = + GenericFunctionId::Impl(ImplGenericFunctionId { impl_id, function: info.try_into_fn }); + let function = semantic::FunctionLongId { + function: semantic::ConcreteFunction { generic_function, generic_args: vec![] }, + } + .intern(db); + let function_id = function.lowered(db); + + let option_ty = corelib::core_option_ty(db, snapshot_box_ty); + let call_result = generators::Call { + function: function_id, + inputs: vec![span_usage], + coupon_input: None, + extra_ret_tys: vec![], + ret_tys: vec![option_ty], + location, + } + .add(ctx.ctx, &mut builder.statements); + let option_usage = call_result.returns.into_iter().next().unwrap(); + + // Success branch: receives @Box<[T; N]>, then forward snapshot + unbox + destructure. + let mut success_builder = ctx.create_child_builder(&builder); + + let boxed_var = ctx.ctx.new_var(VarRequest { ty: snapshot_box_ty, location }); + + // box_forward_snapshot: @Box<[T; N]> -> Box<@[T; N]> + let snapshot_inner_ty = semantic::TypeLongId::Snapshot(fixed_array_ty).intern(db); + let box_of_snapshot_ty = corelib::core_box_ty(db, snapshot_inner_ty); + let fwd_fn = GenericFunctionId::Extern(info.box_forward_snapshot) + .concretize(db, vec![GenericArgumentId::Type(fixed_array_ty)]) + .lowered(db); + let fwd_result = generators::Call { + function: fwd_fn, + inputs: vec![VarUsage { var_id: boxed_var, location }], + coupon_input: None, + extra_ret_tys: vec![], + ret_tys: vec![box_of_snapshot_ty], + location, + } + .add(ctx.ctx, &mut success_builder.statements); + let box_of_snapshot_usage = fwd_result.returns.into_iter().next().unwrap(); + + // Unbox: Box<@[T; N]> -> @[T; N] + let snapshot_fixed_array_usage = generators::Unbox { input: box_of_snapshot_usage, location } + .add(ctx.ctx, &mut success_builder.statements); + + // Destructure @[T; N] -> (@v0, @v1, ..., @vN-1) + let elem_var_reqs: Vec<_> = node + .outputs + .iter() + .map(|output| VarRequest { ty: output.ty(ctx.graph), location: output.location(ctx.graph) }) + .collect(); + let elem_var_ids = generators::StructDestructure { + input: snapshot_fixed_array_usage, + var_reqs: elem_var_reqs, + } + .add(ctx.ctx, &mut success_builder.statements); + + for (output, var_id) in zip_eq(&node.outputs, elem_var_ids) { + ctx.register_var(*output, var_id); + } + + let success_block_id = ctx.register_child_builder(node.success, success_builder); + + // Failure branch: no outputs. Option::None wraps unit. + let failure_block_id = ctx.assign_child_block_id(node.failure, &builder); + let none_var = ctx.ctx.new_var(VarRequest { ty: unit_ty(db), location }); + + let some_variant = corelib::option_some_variant(db, snapshot_box_ty); + let none_variant = corelib::option_none_variant(db, snapshot_box_ty); + + let match_info = MatchInfo::Enum(MatchEnumInfo { + concrete_enum_id: some_variant.concrete_enum_id, + input: option_usage, + arms: vec![ + MatchArm { + arm_selector: MatchArmSelector::VariantId(some_variant), + block_id: success_block_id, + var_ids: vec![boxed_var], + }, + MatchArm { + arm_selector: MatchArmSelector::VariantId(none_variant), + block_id: failure_block_id, + var_ids: vec![none_var], + }, + ], + location, + }); + + ctx.finalize_with_match(id, builder, match_info); + Ok(()) +} + /// Lowers a [LetElseSuccess] node. fn lower_let_else_success<'db>( ctx: &mut LowerGraphContext<'db, '_, '_>, diff --git a/crates/cairo-lang-lowering/src/lower/flow_control/test_data/match b/crates/cairo-lang-lowering/src/lower/flow_control/test_data/match index dda60808cc9..943bc83e829 100644 --- a/crates/cairo-lang-lowering/src/lower/flow_control/test_data/match +++ b/crates/cairo-lang-lowering/src/lower/flow_control/test_data/match @@ -2341,3 +2341,1339 @@ blk8: Statements: End: Return(v11) + +//! > ========================================================================== + +//! > Span to fixed size array. + +//! > test_runner_name +test_create_graph(expect_diagnostics: false) + +//! > function_code +fn foo(s: Span) -> u32 { + match s { + [a, b] => *a + *b, + [a, b, c] => *a + *b + *c, + s => s.len(), + } +} + +//! > graph +Root: 12 +12 EvaluateExpr { expr: ExprId(0), var_id: v0, next: NodeId(11) } +11 Deconstruct { input: v0, outputs: [v1], next: NodeId(10) } +10 SliceDestructure { input: v1, outputs: [v5, v6], success: NodeId(9), failure: NodeId(7)} +9 BindVar { input: v5, output: PatternVarId(4), next: NodeId(8) } +8 BindVar { input: v6, output: PatternVarId(5), next: NodeId(0) } +7 SliceDestructure { input: v1, outputs: [v2, v3, v4], success: NodeId(6), failure: NodeId(3)} +6 BindVar { input: v2, output: PatternVarId(1), next: NodeId(5) } +5 BindVar { input: v3, output: PatternVarId(2), next: NodeId(4) } +4 BindVar { input: v4, output: PatternVarId(3), next: NodeId(1) } +3 BindVar { input: v0, output: PatternVarId(0), next: NodeId(2) } +2 ArmExpr { expr: ExprId(15) } +1 ArmExpr { expr: ExprId(13) } +0 ArmExpr { expr: ExprId(5) } + +//! > semantic_diagnostics + +//! > lowering_diagnostics + +//! > lowered +Parameters: v0: core::array::Span:: +blk0 (root): +Statements: + (v1: @core::array::Array::) <- struct_destructure(v0) + (v2: core::array::Span::) <- struct_construct(v1) + (v3: core::option::Option::<@core::box::Box::<[core::integer::u32; 2]>>) <- core::array::SpanTryIntoFixedSizedArray::::try_into(v2) +End: + Match(match_enum(v3) { + Option::Some(v4) => blk1, + Option::None(v9) => blk2, + }) + +blk1: +Statements: + (v5: core::box::Box::<@[core::integer::u32; 2]>) <- core::box::box_forward_snapshot::<[core::integer::u32; 2]>(v4) + (v6: @[core::integer::u32; 2]) <- unbox(v5) + (v7: @core::integer::u32, v8: @core::integer::u32) <- struct_destructure(v6) + (v25: core::integer::u32) <- desnap(v7) + (v26: core::integer::u32) <- desnap(v8) + (v27: core::integer::u32) <- core::integer::U32Add::add(v25, v26) +End: + Goto(blk5, {v27 -> v28}) + +blk2: +Statements: + (v10: core::array::Span::) <- struct_construct(v1) + (v11: core::option::Option::<@core::box::Box::<[core::integer::u32; 3]>>) <- core::array::SpanTryIntoFixedSizedArray::::try_into(v10) +End: + Match(match_enum(v11) { + Option::Some(v12) => blk3, + Option::None(v18) => blk4, + }) + +blk3: +Statements: + (v13: core::box::Box::<@[core::integer::u32; 3]>) <- core::box::box_forward_snapshot::<[core::integer::u32; 3]>(v12) + (v14: @[core::integer::u32; 3]) <- unbox(v13) + (v15: @core::integer::u32, v16: @core::integer::u32, v17: @core::integer::u32) <- struct_destructure(v14) + (v20: core::integer::u32) <- desnap(v15) + (v21: core::integer::u32) <- desnap(v16) + (v22: core::integer::u32) <- core::integer::U32Add::add(v20, v21) + (v23: core::integer::u32) <- desnap(v17) + (v24: core::integer::u32) <- core::integer::U32Add::add(v22, v23) +End: + Goto(blk5, {v24 -> v28}) + +blk4: +Statements: + (v19: core::integer::u32) <- core::array::SpanImpl::::len(v0) +End: + Goto(blk5, {v19 -> v28}) + +blk5: +Statements: +End: + Return(v28) + +//! > ========================================================================== + +//! > Span to fixed size array with empty pattern. + +//! > test_runner_name +test_create_graph(expect_diagnostics: false) + +//! > function_code +fn foo(s: Span) -> u32 { + match s { + [a] => *a, + [] => 0, + _ => s.len(), + } +} + +//! > graph +Root: 7 +7 EvaluateExpr { expr: ExprId(0), var_id: v0, next: NodeId(6) } +6 Deconstruct { input: v0, outputs: [v1], next: NodeId(5) } +5 SliceDestructure { input: v1, outputs: [v2], success: NodeId(4), failure: NodeId(3)} +4 BindVar { input: v2, output: PatternVarId(0), next: NodeId(0) } +3 SliceDestructure { input: v1, outputs: [], success: NodeId(1), failure: NodeId(2)} +2 ArmExpr { expr: ExprId(5) } +1 ArmExpr { expr: ExprId(3) } +0 ArmExpr { expr: ExprId(2) } + +//! > semantic_diagnostics + +//! > lowering_diagnostics + +//! > lowered +Parameters: v0: core::array::Span:: +blk0 (root): +Statements: + (v1: @core::array::Array::) <- struct_destructure(v0) + (v2: core::array::Span::) <- struct_construct(v1) + (v3: core::option::Option::<@core::box::Box::<[core::integer::u32; 1]>>) <- core::array::SpanTryIntoFixedSizedArray::::try_into(v2) +End: + Match(match_enum(v3) { + Option::Some(v4) => blk1, + Option::None(v8) => blk2, + }) + +blk1: +Statements: + (v5: core::box::Box::<@[core::integer::u32; 1]>) <- core::box::box_forward_snapshot::<[core::integer::u32; 1]>(v4) + (v6: @[core::integer::u32; 1]) <- unbox(v5) + (v7: @core::integer::u32) <- struct_destructure(v6) + (v17: core::integer::u32) <- desnap(v7) +End: + Goto(blk5, {v17 -> v18}) + +blk2: +Statements: + (v9: core::array::Span::) <- struct_construct(v1) + (v10: core::option::Option::<@core::box::Box::<[core::integer::u32; 0]>>) <- core::array::SpanTryIntoEmptyFixedSizedArray::::try_into(v9) +End: + Match(match_enum(v10) { + Option::Some(v11) => blk3, + Option::None(v14) => blk4, + }) + +blk3: +Statements: + (v12: core::box::Box::<@[core::integer::u32; 0]>) <- core::box::box_forward_snapshot::<[core::integer::u32; 0]>(v11) + (v13: @[core::integer::u32; 0]) <- unbox(v12) + () <- struct_destructure(v13) + (v16: core::integer::u32) <- 0 +End: + Goto(blk5, {v16 -> v18}) + +blk4: +Statements: + (v15: core::integer::u32) <- core::array::SpanImpl::::len(v0) +End: + Goto(blk5, {v15 -> v18}) + +blk5: +Statements: +End: + Return(v18) + +//! > ========================================================================== + +//! > Span to fixed size array with wildcard between sizes. + +//! > test_runner_name +test_create_graph(expect_diagnostics: true) + +//! > function_code +fn foo(s: Span) -> u32 { + match s { + [a, b] => *a + *b, + _ => 0, + [a, b, c] => *a + *b + *c, + } +} + +//! > graph +Root: 7 +7 EvaluateExpr { expr: ExprId(0), var_id: v0, next: NodeId(6) } +6 Deconstruct { input: v0, outputs: [v1], next: NodeId(5) } +5 SliceDestructure { input: v1, outputs: [v5, v6], success: NodeId(4), failure: NodeId(1)} +4 BindVar { input: v5, output: PatternVarId(3), next: NodeId(3) } +3 BindVar { input: v6, output: PatternVarId(4), next: NodeId(0) } +2 ArmExpr { expr: ExprId(14) } +1 ArmExpr { expr: ExprId(6) } +0 ArmExpr { expr: ExprId(5) } + +//! > semantic_diagnostics + +//! > lowering_diagnostics +warning[E3004]: Unreachable pattern arm. + --> lib.cairo:5:22 + [a, b, c] => *a + *b + *c, + ^^^^^^^^^^^^ + +//! > lowered +Parameters: v0: core::array::Span:: +blk0 (root): +Statements: + (v1: @core::array::Array::) <- struct_destructure(v0) + (v2: core::array::Span::) <- struct_construct(v1) + (v3: core::option::Option::<@core::box::Box::<[core::integer::u32; 2]>>) <- core::array::SpanTryIntoFixedSizedArray::::try_into(v2) +End: + Match(match_enum(v3) { + Option::Some(v4) => blk1, + Option::None(v9) => blk2, + }) + +blk1: +Statements: + (v5: core::box::Box::<@[core::integer::u32; 2]>) <- core::box::box_forward_snapshot::<[core::integer::u32; 2]>(v4) + (v6: @[core::integer::u32; 2]) <- unbox(v5) + (v7: @core::integer::u32, v8: @core::integer::u32) <- struct_destructure(v6) + (v11: core::integer::u32) <- desnap(v7) + (v12: core::integer::u32) <- desnap(v8) + (v13: core::integer::u32) <- core::integer::U32Add::add(v11, v12) +End: + Goto(blk3, {v13 -> v14}) + +blk2: +Statements: + (v10: core::integer::u32) <- 0 +End: + Goto(blk3, {v10 -> v14}) + +blk3: +Statements: +End: + Return(v14) + +//! > ========================================================================== + +//! > Span to fixed size array with duplicate size. + +//! > test_runner_name +test_create_graph(expect_diagnostics: true) + +//! > function_code +fn foo(s: Span) -> u32 { + match s { + [a, b] => *a + *b, + [a, b] => *a + *b, + _ => 0, + } +} + +//! > graph +Root: 7 +7 EvaluateExpr { expr: ExprId(0), var_id: v0, next: NodeId(6) } +6 Deconstruct { input: v0, outputs: [v1], next: NodeId(5) } +5 SliceDestructure { input: v1, outputs: [v2, v3], success: NodeId(4), failure: NodeId(2)} +4 BindVar { input: v2, output: PatternVarId(0), next: NodeId(3) } +3 BindVar { input: v3, output: PatternVarId(2), next: NodeId(0) } +2 ArmExpr { expr: ExprId(11) } +1 ArmExpr { expr: ExprId(10) } +0 ArmExpr { expr: ExprId(5) } + +//! > semantic_diagnostics + +//! > lowering_diagnostics +warning[E3004]: Unreachable pattern arm. + --> lib.cairo:4:19 + [a, b] => *a + *b, + ^^^^^^^ + +//! > lowered +Parameters: v0: core::array::Span:: +blk0 (root): +Statements: + (v1: @core::array::Array::) <- struct_destructure(v0) + (v2: core::array::Span::) <- struct_construct(v1) + (v3: core::option::Option::<@core::box::Box::<[core::integer::u32; 2]>>) <- core::array::SpanTryIntoFixedSizedArray::::try_into(v2) +End: + Match(match_enum(v3) { + Option::Some(v4) => blk1, + Option::None(v9) => blk2, + }) + +blk1: +Statements: + (v5: core::box::Box::<@[core::integer::u32; 2]>) <- core::box::box_forward_snapshot::<[core::integer::u32; 2]>(v4) + (v6: @[core::integer::u32; 2]) <- unbox(v5) + (v7: @core::integer::u32, v8: @core::integer::u32) <- struct_destructure(v6) + (v11: core::integer::u32) <- desnap(v7) + (v12: core::integer::u32) <- desnap(v8) + (v13: core::integer::u32) <- core::integer::U32Add::add(v11, v12) +End: + Goto(blk3, {v13 -> v14}) + +blk2: +Statements: + (v10: core::integer::u32) <- 0 +End: + Goto(blk3, {v10 -> v14}) + +blk3: +Statements: +End: + Return(v14) + +//! > ========================================================================== + +//! > Two pattern with the same arm. + +//! > test_runner_name +test_create_graph(expect_diagnostics: false) + +//! > function_code +fn foo(s: Span) -> u32 { + match s { + [a, b] | [a, b, _] => *a + *b, + _ => 0, + } +} + +//! > graph +Root: 9 +9 EvaluateExpr { expr: ExprId(0), var_id: v0, next: NodeId(8) } +8 Deconstruct { input: v0, outputs: [v1], next: NodeId(7) } +7 SliceDestructure { input: v1, outputs: [v5, v6], success: NodeId(6), failure: NodeId(4)} +6 BindVar { input: v5, output: PatternVarId(2), next: NodeId(5) } +5 BindVar { input: v6, output: PatternVarId(3), next: NodeId(0) } +4 SliceDestructure { input: v1, outputs: [v2, v3, v4], success: NodeId(3), failure: NodeId(1)} +3 BindVar { input: v2, output: PatternVarId(0), next: NodeId(2) } +2 BindVar { input: v3, output: PatternVarId(1), next: NodeId(0) } +1 ArmExpr { expr: ExprId(6) } +0 ArmExpr { expr: ExprId(5) } + +//! > semantic_diagnostics + +//! > lowering_diagnostics + +//! > lowered +Parameters: v0: core::array::Span:: +blk0 (root): +Statements: + (v1: @core::array::Array::) <- struct_destructure(v0) + (v2: core::array::Span::) <- struct_construct(v1) + (v3: core::option::Option::<@core::box::Box::<[core::integer::u32; 2]>>) <- core::array::SpanTryIntoFixedSizedArray::::try_into(v2) +End: + Match(match_enum(v3) { + Option::Some(v4) => blk1, + Option::None(v9) => blk2, + }) + +blk1: +Statements: + (v5: core::box::Box::<@[core::integer::u32; 2]>) <- core::box::box_forward_snapshot::<[core::integer::u32; 2]>(v4) + (v6: @[core::integer::u32; 2]) <- unbox(v5) + (v7: @core::integer::u32, v8: @core::integer::u32) <- struct_destructure(v6) +End: + Goto(blk5, {v7 -> v20, v8 -> v21}) + +blk2: +Statements: + (v10: core::array::Span::) <- struct_construct(v1) + (v11: core::option::Option::<@core::box::Box::<[core::integer::u32; 3]>>) <- core::array::SpanTryIntoFixedSizedArray::::try_into(v10) +End: + Match(match_enum(v11) { + Option::Some(v12) => blk3, + Option::None(v18) => blk4, + }) + +blk3: +Statements: + (v13: core::box::Box::<@[core::integer::u32; 3]>) <- core::box::box_forward_snapshot::<[core::integer::u32; 3]>(v12) + (v14: @[core::integer::u32; 3]) <- unbox(v13) + (v15: @core::integer::u32, v16: @core::integer::u32, v17: @core::integer::u32) <- struct_destructure(v14) +End: + Goto(blk5, {v15 -> v20, v16 -> v21}) + +blk4: +Statements: + (v19: core::integer::u32) <- 0 +End: + Goto(blk6, {v19 -> v25}) + +blk5: +Statements: + (v22: core::integer::u32) <- desnap(v20) + (v23: core::integer::u32) <- desnap(v21) + (v24: core::integer::u32) <- core::integer::U32Add::add(v22, v23) +End: + Goto(blk6, {v24 -> v25}) + +blk6: +Statements: +End: + Return(v25) + +//! > ========================================================================== + +//! > Span to fixed size array with inner pattern mismatch. + +//! > test_runner_name +test_create_graph(expect_diagnostics: false) + +//! > function_code +fn foo(s: Span) -> felt252 { + match s { + [true, true] => 1, + [true, false] => 2, + _ => 0, + } +} + +//! > graph +Root: 7 +7 EvaluateExpr { expr: ExprId(0), var_id: v0, next: NodeId(6) } +6 Deconstruct { input: v0, outputs: [v1], next: NodeId(5) } +5 SliceDestructure { input: v1, outputs: [v2, v3], success: NodeId(4), failure: NodeId(2)} +4 EnumMatch { matched_var: v2, variants: (NodeId(2), v4), (NodeId(3), v5)} +3 EnumMatch { matched_var: v3, variants: (NodeId(1), v6), (NodeId(0), v7)} +2 ArmExpr { expr: ExprId(7) } +1 ArmExpr { expr: ExprId(6) } +0 ArmExpr { expr: ExprId(3) } + +//! > semantic_diagnostics + +//! > lowering_diagnostics + +//! > lowered +Parameters: v0: core::array::Span:: +blk0 (root): +Statements: + (v1: @core::array::Array::) <- struct_destructure(v0) + (v2: core::array::Span::) <- struct_construct(v1) + (v3: core::option::Option::<@core::box::Box::<[core::bool; 2]>>) <- core::array::SpanTryIntoFixedSizedArray::::try_into(v2) +End: + Match(match_enum(v3) { + Option::Some(v4) => blk1, + Option::None(v9) => blk2, + }) + +blk1: +Statements: + (v5: core::box::Box::<@[core::bool; 2]>) <- core::box::box_forward_snapshot::<[core::bool; 2]>(v4) + (v6: @[core::bool; 2]) <- unbox(v5) + (v7: @core::bool, v8: @core::bool) <- struct_destructure(v6) +End: + Match(match_enum(v7) { + bool::False(v10) => blk3, + bool::True(v11) => blk4, + }) + +blk2: +Statements: +End: + Goto(blk7, {}) + +blk3: +Statements: +End: + Goto(blk7, {}) + +blk4: +Statements: +End: + Match(match_enum(v8) { + bool::False(v12) => blk5, + bool::True(v13) => blk6, + }) + +blk5: +Statements: + (v15: core::felt252) <- 2 +End: + Goto(blk8, {v15 -> v17}) + +blk6: +Statements: + (v16: core::felt252) <- 1 +End: + Goto(blk8, {v16 -> v17}) + +blk7: +Statements: + (v14: core::felt252) <- 0 +End: + Goto(blk8, {v14 -> v17}) + +blk8: +Statements: +End: + Return(v17) + +//! > ========================================================================== + +//! > Span to fixed size array with non-exhaustive sizes. + +//! > test_runner_name +test_create_graph(expect_diagnostics: true) + +//! > function_code +fn foo(s: Span) -> u32 { + match s { + [a] => *a, + [a, b] => *a + *b, + } +} + +//! > graph +Root: 9 +9 EvaluateExpr { expr: ExprId(0), var_id: v0, next: NodeId(8) } +8 Deconstruct { input: v0, outputs: [v1], next: NodeId(7) } +7 SliceDestructure { input: v1, outputs: [v4], success: NodeId(6), failure: NodeId(5)} +6 BindVar { input: v4, output: PatternVarId(2), next: NodeId(0) } +5 SliceDestructure { input: v1, outputs: [v2, v3], success: NodeId(4), failure: NodeId(2)} +4 BindVar { input: v2, output: PatternVarId(0), next: NodeId(3) } +3 BindVar { input: v3, output: PatternVarId(1), next: NodeId(1) } +2 Missing +1 ArmExpr { expr: ExprId(7) } +0 ArmExpr { expr: ExprId(2) } + +//! > semantic_diagnostics + +//! > lowering_diagnostics +error[E3004]: Match is non-exhaustive: `_` not covered. + --> lib.cairo:2:5-5:5 + match s { + _____^ +| ... +| } +|_____^ + +//! > lowered +Parameters: v0: core::array::Span:: + +//! > ========================================================================== + +//! > Span to fixed size array with size after wildcard. + +//! > test_runner_name +test_create_graph(expect_diagnostics: true) + +//! > function_code +fn foo(s: Span) -> u32 { + match s { + [a] => *a, + _ => 0, + [a, b] => *a + *b, + } +} + +//! > graph +Root: 6 +6 EvaluateExpr { expr: ExprId(0), var_id: v0, next: NodeId(5) } +5 Deconstruct { input: v0, outputs: [v1], next: NodeId(4) } +4 SliceDestructure { input: v1, outputs: [v4], success: NodeId(3), failure: NodeId(1)} +3 BindVar { input: v4, output: PatternVarId(2), next: NodeId(0) } +2 ArmExpr { expr: ExprId(8) } +1 ArmExpr { expr: ExprId(3) } +0 ArmExpr { expr: ExprId(2) } + +//! > semantic_diagnostics + +//! > lowering_diagnostics +warning[E3004]: Unreachable pattern arm. + --> lib.cairo:5:19 + [a, b] => *a + *b, + ^^^^^^^ + +//! > lowered +Parameters: v0: core::array::Span:: +blk0 (root): +Statements: + (v1: @core::array::Array::) <- struct_destructure(v0) + (v2: core::array::Span::) <- struct_construct(v1) + (v3: core::option::Option::<@core::box::Box::<[core::integer::u32; 1]>>) <- core::array::SpanTryIntoFixedSizedArray::::try_into(v2) +End: + Match(match_enum(v3) { + Option::Some(v4) => blk1, + Option::None(v8) => blk2, + }) + +blk1: +Statements: + (v5: core::box::Box::<@[core::integer::u32; 1]>) <- core::box::box_forward_snapshot::<[core::integer::u32; 1]>(v4) + (v6: @[core::integer::u32; 1]) <- unbox(v5) + (v7: @core::integer::u32) <- struct_destructure(v6) + (v10: core::integer::u32) <- desnap(v7) +End: + Goto(blk3, {v10 -> v11}) + +blk2: +Statements: + (v9: core::integer::u32) <- 0 +End: + Goto(blk3, {v9 -> v11}) + +blk3: +Statements: +End: + Return(v11) + +//! > ========================================================================== + +//! > Span to fixed size array followed by struct destructure pattern. + +//! > test_runner_name +test_create_graph(expect_diagnostics: true) + +//! > function_code +fn foo(s: Span) -> u32 { + match s { + [a] => *a, + Span { snapshot: inner } => inner.len(), + [a, b] => *a + *b, + Span { snapshot: inner } => 2 * inner.len(), + } +} + +//! > graph +Root: 8 +8 EvaluateExpr { expr: ExprId(0), var_id: v0, next: NodeId(7) } +7 Deconstruct { input: v0, outputs: [v1], next: NodeId(6) } +6 SliceDestructure { input: v1, outputs: [v4], success: NodeId(5), failure: NodeId(4)} +5 BindVar { input: v4, output: PatternVarId(4), next: NodeId(0) } +4 BindVar { input: v1, output: PatternVarId(0), next: NodeId(1) } +3 ArmExpr { expr: ExprId(13) } +2 ArmExpr { expr: ExprId(9) } +1 ArmExpr { expr: ExprId(4) } +0 ArmExpr { expr: ExprId(2) } + +//! > semantic_diagnostics + +//! > lowering_diagnostics +warning[E3004]: Unreachable pattern arm. + --> lib.cairo:6:37 + Span { snapshot: inner } => 2 * inner.len(), + ^^^^^^^^^^^^^^^ + +warning[E3004]: Unreachable pattern arm. + --> lib.cairo:5:19 + [a, b] => *a + *b, + ^^^^^^^ + +//! > lowered +Parameters: v0: core::array::Span:: +blk0 (root): +Statements: + (v1: @core::array::Array::) <- struct_destructure(v0) + (v2: core::array::Span::) <- struct_construct(v1) + (v3: core::option::Option::<@core::box::Box::<[core::integer::u32; 1]>>) <- core::array::SpanTryIntoFixedSizedArray::::try_into(v2) +End: + Match(match_enum(v3) { + Option::Some(v4) => blk1, + Option::None(v8) => blk2, + }) + +blk1: +Statements: + (v5: core::box::Box::<@[core::integer::u32; 1]>) <- core::box::box_forward_snapshot::<[core::integer::u32; 1]>(v4) + (v6: @[core::integer::u32; 1]) <- unbox(v5) + (v7: @core::integer::u32) <- struct_destructure(v6) + (v10: core::integer::u32) <- desnap(v7) +End: + Goto(blk3, {v10 -> v11}) + +blk2: +Statements: + (v9: core::integer::u32) <- core::array::ArrayImpl::::len(v1) +End: + Goto(blk3, {v9 -> v11}) + +blk3: +Statements: +End: + Return(v11) + +//! > ========================================================================== + +//! > Fixed size patterns after struct pattern. + +//! > test_runner_name +test_create_graph(expect_diagnostics: *) + +//! > function_code +fn foo(s: Span, val: u32) -> u32 { + match (s, val) { + ([a], v) => *a + v, + (Span { snapshot: inner }, 5) => 2 * inner.len(), + ([a, b], v) => *a + *b + v, + ([a, b, c], v) => *a + *b + *c + v, + _ => 0, + } +} + +//! > graph +Root: 27 +27 EvaluateExpr { expr: ExprId(2), var_id: v0, next: NodeId(26) } +26 Deconstruct { input: v0, outputs: [v1, v2], next: NodeId(25) } +25 Deconstruct { input: v1, outputs: [v3], next: NodeId(24) } +24 SliceDestructure { input: v3, outputs: [v12], success: NodeId(23), failure: NodeId(20)} +23 Upcast { input: v2, output: v13, next: NodeId(22) } +22 BindVar { input: v12, output: PatternVarId(8), next: NodeId(21) } +21 BindVar { input: v2, output: PatternVarId(9), next: NodeId(0) } +20 SliceDestructure { input: v3, outputs: [v9, v10], success: NodeId(19), failure: NodeId(14)} +19 Upcast { input: v2, output: v11, next: NodeId(18) } +18 EqualsLiteral { input: v11, literal: 5, true_branch: NodeId(5), false_branch: NodeId(17) } +17 BindVar { input: v9, output: PatternVarId(5), next: NodeId(16) } +16 BindVar { input: v10, output: PatternVarId(6), next: NodeId(15) } +15 BindVar { input: v2, output: PatternVarId(7), next: NodeId(2) } +14 SliceDestructure { input: v3, outputs: [v5, v6, v7], success: NodeId(13), failure: NodeId(7)} +13 Upcast { input: v2, output: v8, next: NodeId(12) } +12 EqualsLiteral { input: v8, literal: 5, true_branch: NodeId(5), false_branch: NodeId(11) } +11 BindVar { input: v5, output: PatternVarId(1), next: NodeId(10) } +10 BindVar { input: v6, output: PatternVarId(2), next: NodeId(9) } +9 BindVar { input: v7, output: PatternVarId(3), next: NodeId(8) } +8 BindVar { input: v2, output: PatternVarId(4), next: NodeId(3) } +7 Upcast { input: v2, output: v4, next: NodeId(6) } +6 EqualsLiteral { input: v4, literal: 5, true_branch: NodeId(5), false_branch: NodeId(4) } +5 BindVar { input: v3, output: PatternVarId(0), next: NodeId(1) } +4 ArmExpr { expr: ExprId(28) } +3 ArmExpr { expr: ExprId(27) } +2 ArmExpr { expr: ExprId(17) } +1 ArmExpr { expr: ExprId(10) } +0 ArmExpr { expr: ExprId(6) } + +//! > semantic_diagnostics + +//! > lowering_diagnostics + +//! > lowered +Parameters: v0: core::array::Span::, v1: core::integer::u32 +blk0 (root): +Statements: + (v2: (core::array::Span::, core::integer::u32)) <- struct_construct(v0, v1) + (v3: core::array::Span::, v4: core::integer::u32) <- struct_destructure(v2) + (v5: @core::array::Array::) <- struct_destructure(v3) + (v6: core::array::Span::) <- struct_construct(v5) + (v7: core::option::Option::<@core::box::Box::<[core::integer::u32; 1]>>) <- core::array::SpanTryIntoFixedSizedArray::::try_into(v6) +End: + Match(match_enum(v7) { + Option::Some(v8) => blk1, + Option::None(v12) => blk2, + }) + +blk1: +Statements: + (v9: core::box::Box::<@[core::integer::u32; 1]>) <- core::box::box_forward_snapshot::<[core::integer::u32; 1]>(v8) + (v10: @[core::integer::u32; 1]) <- unbox(v9) + (v11: @core::integer::u32) <- struct_destructure(v10) + (v13: core::felt252) <- core::internal::bounded_int::upcast::(v4) + (v57: core::integer::u32) <- desnap(v11) + (v58: core::integer::u32) <- core::integer::U32Add::add(v57, v4) +End: + Goto(blk14, {v58 -> v59}) + +blk2: +Statements: + (v14: core::array::Span::) <- struct_construct(v5) + (v15: core::option::Option::<@core::box::Box::<[core::integer::u32; 2]>>) <- core::array::SpanTryIntoFixedSizedArray::::try_into(v14) +End: + Match(match_enum(v15) { + Option::Some(v16) => blk3, + Option::None(v21) => blk4, + }) + +blk3: +Statements: + (v17: core::box::Box::<@[core::integer::u32; 2]>) <- core::box::box_forward_snapshot::<[core::integer::u32; 2]>(v16) + (v18: @[core::integer::u32; 2]) <- unbox(v17) + (v19: @core::integer::u32, v20: @core::integer::u32) <- struct_destructure(v18) + (v22: core::felt252) <- core::internal::bounded_int::upcast::(v4) + (v23: core::felt252) <- 5 + (v24: core::felt252) <- core::Felt252Sub::sub(v22, v23) +End: + Match(match core::felt252_is_zero(v24) { + IsZeroResult::Zero => blk5, + IsZeroResult::NonZero(v25) => blk6, + }) + +blk4: +Statements: + (v26: core::array::Span::) <- struct_construct(v5) + (v27: core::option::Option::<@core::box::Box::<[core::integer::u32; 3]>>) <- core::array::SpanTryIntoFixedSizedArray::::try_into(v26) +End: + Match(match_enum(v27) { + Option::Some(v28) => blk7, + Option::None(v34) => blk8, + }) + +blk5: +Statements: +End: + Goto(blk13, {}) + +blk6: +Statements: + (v50: core::integer::u32) <- desnap(v19) + (v51: core::integer::u32) <- desnap(v20) + (v52: core::integer::u32) <- core::integer::U32Add::add(v50, v51) + (v53: core::integer::u32) <- core::integer::U32Add::add(v52, v4) +End: + Goto(blk14, {v53 -> v59}) + +blk7: +Statements: + (v29: core::box::Box::<@[core::integer::u32; 3]>) <- core::box::box_forward_snapshot::<[core::integer::u32; 3]>(v28) + (v30: @[core::integer::u32; 3]) <- unbox(v29) + (v31: @core::integer::u32, v32: @core::integer::u32, v33: @core::integer::u32) <- struct_destructure(v30) + (v35: core::felt252) <- core::internal::bounded_int::upcast::(v4) + (v36: core::felt252) <- 5 + (v37: core::felt252) <- core::Felt252Sub::sub(v35, v36) +End: + Match(match core::felt252_is_zero(v37) { + IsZeroResult::Zero => blk9, + IsZeroResult::NonZero(v38) => blk10, + }) + +blk8: +Statements: + (v39: core::felt252) <- core::internal::bounded_int::upcast::(v4) + (v40: core::felt252) <- 5 + (v41: core::felt252) <- core::Felt252Sub::sub(v39, v40) +End: + Match(match core::felt252_is_zero(v41) { + IsZeroResult::Zero => blk11, + IsZeroResult::NonZero(v42) => blk12, + }) + +blk9: +Statements: +End: + Goto(blk13, {}) + +blk10: +Statements: + (v44: core::integer::u32) <- desnap(v31) + (v45: core::integer::u32) <- desnap(v32) + (v46: core::integer::u32) <- core::integer::U32Add::add(v44, v45) + (v47: core::integer::u32) <- desnap(v33) + (v48: core::integer::u32) <- core::integer::U32Add::add(v46, v47) + (v49: core::integer::u32) <- core::integer::U32Add::add(v48, v4) +End: + Goto(blk14, {v49 -> v59}) + +blk11: +Statements: +End: + Goto(blk13, {}) + +blk12: +Statements: + (v43: core::integer::u32) <- 0 +End: + Goto(blk14, {v43 -> v59}) + +blk13: +Statements: + (v54: core::integer::u32) <- 2 + (v55: core::integer::u32) <- core::array::ArrayImpl::::len(v5) + (v56: core::integer::u32) <- core::integer::U32Mul::mul(v54, v55) +End: + Goto(blk14, {v56 -> v59}) + +blk14: +Statements: +End: + Return(v59) + +//! > ========================================================================== + +//! > Span match with two struct arms binding different variable names. + +//! > test_runner_name +test_create_graph(expect_diagnostics: *) + +//! > function_code +fn foo(s: Span) -> u32 { + match s { + Span { snapshot: x } => x.len(), + Span { snapshot: y } => y.len() + 1, + } +} + +//! > graph +Root: 4 +4 EvaluateExpr { expr: ExprId(0), var_id: v0, next: NodeId(3) } +3 Deconstruct { input: v0, outputs: [v1], next: NodeId(2) } +2 BindVar { input: v1, output: PatternVarId(0), next: NodeId(0) } +1 ArmExpr { expr: ExprId(6) } +0 ArmExpr { expr: ExprId(2) } + +//! > semantic_diagnostics + +//! > lowering_diagnostics +warning[E3004]: Unreachable pattern arm. + --> lib.cairo:4:33 + Span { snapshot: y } => y.len() + 1, + ^^^^^^^^^^^ + +//! > lowered +Parameters: v0: core::array::Span:: +blk0 (root): +Statements: + (v1: @core::array::Array::) <- struct_destructure(v0) + (v2: core::integer::u32) <- core::array::ArrayImpl::::len(v1) +End: + Return(v2) + +//! > ========================================================================== + +//! > Tuple with two Span struct arms binding different variable names. + +//! > test_runner_name +test_create_graph(expect_diagnostics: *) + +//! > function_code +fn foo(s: Span, v: u32) -> u32 { + match (s, v) { + ([a], _) => *a, + (Span { snapshot: x }, 5) => x.len(), + (Span { snapshot: y }, 7) => y.len() + 1, + _ => 0, + } +} + +//! > graph +Root: 14 +14 EvaluateExpr { expr: ExprId(2), var_id: v0, next: NodeId(13) } +13 Deconstruct { input: v0, outputs: [v1, v2], next: NodeId(12) } +12 Deconstruct { input: v1, outputs: [v3], next: NodeId(11) } +11 SliceDestructure { input: v3, outputs: [v5], success: NodeId(10), failure: NodeId(8)} +10 Upcast { input: v2, output: v6, next: NodeId(9) } +9 BindVar { input: v5, output: PatternVarId(2), next: NodeId(0) } +8 Upcast { input: v2, output: v4, next: NodeId(7) } +7 EqualsLiteral { input: v4, literal: 5, true_branch: NodeId(6), false_branch: NodeId(5) } +6 BindVar { input: v3, output: PatternVarId(0), next: NodeId(1) } +5 EqualsLiteral { input: v4, literal: 7, true_branch: NodeId(4), false_branch: NodeId(3) } +4 BindVar { input: v3, output: PatternVarId(1), next: NodeId(2) } +3 ArmExpr { expr: ExprId(11) } +2 ArmExpr { expr: ExprId(10) } +1 ArmExpr { expr: ExprId(6) } +0 ArmExpr { expr: ExprId(4) } + +//! > semantic_diagnostics + +//! > lowering_diagnostics + +//! > lowered +Parameters: v0: core::array::Span::, v1: core::integer::u32 +blk0 (root): +Statements: + (v2: (core::array::Span::, core::integer::u32)) <- struct_construct(v0, v1) + (v3: core::array::Span::, v4: core::integer::u32) <- struct_destructure(v2) + (v5: @core::array::Array::) <- struct_destructure(v3) + (v6: core::array::Span::) <- struct_construct(v5) + (v7: core::option::Option::<@core::box::Box::<[core::integer::u32; 1]>>) <- core::array::SpanTryIntoFixedSizedArray::::try_into(v6) +End: + Match(match_enum(v7) { + Option::Some(v8) => blk1, + Option::None(v12) => blk2, + }) + +blk1: +Statements: + (v9: core::box::Box::<@[core::integer::u32; 1]>) <- core::box::box_forward_snapshot::<[core::integer::u32; 1]>(v8) + (v10: @[core::integer::u32; 1]) <- unbox(v9) + (v11: @core::integer::u32) <- struct_destructure(v10) + (v13: core::felt252) <- core::internal::bounded_int::upcast::(v4) + (v26: core::integer::u32) <- desnap(v11) +End: + Goto(blk7, {v26 -> v27}) + +blk2: +Statements: + (v14: core::felt252) <- core::internal::bounded_int::upcast::(v4) + (v15: core::felt252) <- 5 + (v16: core::felt252) <- core::Felt252Sub::sub(v14, v15) +End: + Match(match core::felt252_is_zero(v16) { + IsZeroResult::Zero => blk3, + IsZeroResult::NonZero(v17) => blk4, + }) + +blk3: +Statements: + (v25: core::integer::u32) <- core::array::ArrayImpl::::len(v5) +End: + Goto(blk7, {v25 -> v27}) + +blk4: +Statements: + (v18: core::felt252) <- 7 + (v19: core::felt252) <- core::Felt252Sub::sub(v14, v18) +End: + Match(match core::felt252_is_zero(v19) { + IsZeroResult::Zero => blk5, + IsZeroResult::NonZero(v20) => blk6, + }) + +blk5: +Statements: + (v22: core::integer::u32) <- core::array::ArrayImpl::::len(v5) + (v23: core::integer::u32) <- 1 + (v24: core::integer::u32) <- core::integer::U32Add::add(v22, v23) +End: + Goto(blk7, {v24 -> v27}) + +blk6: +Statements: + (v21: core::integer::u32) <- 0 +End: + Goto(blk7, {v21 -> v27}) + +blk7: +Statements: +End: + Return(v27) + +//! > ========================================================================== + +//! > Tuple with wildcard between FSA arms of the same size. + +//! > test_runner_name +test_create_graph(expect_diagnostics: *) + +//! > function_code +fn foo(s: Span, v: u32) -> u32 { + match (s, v) { + ([true], _) => 1, + (_, 0) => 0, + ([false], _) => 2, + _ => 3, + } +} + +//! > graph +Root: 13 +13 EvaluateExpr { expr: ExprId(2), var_id: v0, next: NodeId(12) } +12 Deconstruct { input: v0, outputs: [v1, v2], next: NodeId(11) } +11 Deconstruct { input: v1, outputs: [v3], next: NodeId(10) } +10 SliceDestructure { input: v3, outputs: [v5], success: NodeId(9), failure: NodeId(5)} +9 EnumMatch { matched_var: v5, variants: (NodeId(7), v6), (NodeId(8), v8)} +8 Upcast { input: v2, output: v9, next: NodeId(0) } +7 Upcast { input: v2, output: v7, next: NodeId(6) } +6 EqualsLiteral { input: v7, literal: 0, true_branch: NodeId(1), false_branch: NodeId(2) } +5 Upcast { input: v2, output: v4, next: NodeId(4) } +4 EqualsLiteral { input: v4, literal: 0, true_branch: NodeId(1), false_branch: NodeId(3) } +3 ArmExpr { expr: ExprId(8) } +2 ArmExpr { expr: ExprId(7) } +1 ArmExpr { expr: ExprId(5) } +0 ArmExpr { expr: ExprId(4) } + +//! > semantic_diagnostics + +//! > lowering_diagnostics + +//! > lowered +Parameters: v0: core::array::Span::, v1: core::integer::u32 +blk0 (root): +Statements: + (v2: (core::array::Span::, core::integer::u32)) <- struct_construct(v0, v1) + (v3: core::array::Span::, v4: core::integer::u32) <- struct_destructure(v2) + (v5: @core::array::Array::) <- struct_destructure(v3) + (v6: core::array::Span::) <- struct_construct(v5) + (v7: core::option::Option::<@core::box::Box::<[core::bool; 1]>>) <- core::array::SpanTryIntoFixedSizedArray::::try_into(v6) +End: + Match(match_enum(v7) { + Option::Some(v8) => blk1, + Option::None(v12) => blk2, + }) + +blk1: +Statements: + (v9: core::box::Box::<@[core::bool; 1]>) <- core::box::box_forward_snapshot::<[core::bool; 1]>(v8) + (v10: @[core::bool; 1]) <- unbox(v9) + (v11: @core::bool) <- struct_destructure(v10) +End: + Match(match_enum(v11) { + bool::False(v13) => blk3, + bool::True(v14) => blk4, + }) + +blk2: +Statements: + (v18: core::felt252) <- core::internal::bounded_int::upcast::(v4) +End: + Match(match core::felt252_is_zero(v18) { + IsZeroResult::Zero => blk7, + IsZeroResult::NonZero(v19) => blk8, + }) + +blk3: +Statements: + (v16: core::felt252) <- core::internal::bounded_int::upcast::(v4) +End: + Match(match core::felt252_is_zero(v16) { + IsZeroResult::Zero => blk5, + IsZeroResult::NonZero(v17) => blk6, + }) + +blk4: +Statements: + (v15: core::felt252) <- core::internal::bounded_int::upcast::(v4) + (v23: core::integer::u32) <- 1 +End: + Goto(blk10, {v23 -> v24}) + +blk5: +Statements: +End: + Goto(blk9, {}) + +blk6: +Statements: + (v21: core::integer::u32) <- 2 +End: + Goto(blk10, {v21 -> v24}) + +blk7: +Statements: +End: + Goto(blk9, {}) + +blk8: +Statements: + (v20: core::integer::u32) <- 3 +End: + Goto(blk10, {v20 -> v24}) + +blk9: +Statements: + (v22: core::integer::u32) <- 0 +End: + Goto(blk10, {v22 -> v24}) + +blk10: +Statements: +End: + Return(v24) + +//! > ========================================================================== + +//! > Option of Span to fixed size array. + +//! > test_runner_name +test_create_graph(expect_diagnostics: false) + +//! > function_code +fn foo(s: Option>) -> u32 { + match s { + Some([a]) => *a, + Some([a, b]) => *a + *b, + _ => 0, + } +} + +//! > graph +Root: 10 +10 EvaluateExpr { expr: ExprId(0), var_id: v0, next: NodeId(9) } +9 EnumMatch { matched_var: v0, variants: (NodeId(8), v1), (NodeId(2), v6)} +8 Deconstruct { input: v1, outputs: [v2], next: NodeId(7) } +7 SliceDestructure { input: v2, outputs: [v5], success: NodeId(6), failure: NodeId(5)} +6 BindVar { input: v5, output: PatternVarId(2), next: NodeId(0) } +5 SliceDestructure { input: v2, outputs: [v3, v4], success: NodeId(4), failure: NodeId(2)} +4 BindVar { input: v3, output: PatternVarId(0), next: NodeId(3) } +3 BindVar { input: v4, output: PatternVarId(1), next: NodeId(1) } +2 ArmExpr { expr: ExprId(8) } +1 ArmExpr { expr: ExprId(7) } +0 ArmExpr { expr: ExprId(2) } + +//! > semantic_diagnostics + +//! > lowering_diagnostics + +//! > lowered +Parameters: v0: core::option::Option::> +blk0 (root): +Statements: +End: + Match(match_enum(v0) { + Option::Some(v1) => blk1, + Option::None(v2) => blk2, + }) + +blk1: +Statements: + (v3: @core::array::Array::) <- struct_destructure(v1) + (v4: core::array::Span::) <- struct_construct(v3) + (v5: core::option::Option::<@core::box::Box::<[core::integer::u32; 1]>>) <- core::array::SpanTryIntoFixedSizedArray::::try_into(v4) +End: + Match(match_enum(v5) { + Option::Some(v6) => blk3, + Option::None(v10) => blk4, + }) + +blk2: +Statements: +End: + Goto(blk7, {}) + +blk3: +Statements: + (v7: core::box::Box::<@[core::integer::u32; 1]>) <- core::box::box_forward_snapshot::<[core::integer::u32; 1]>(v6) + (v8: @[core::integer::u32; 1]) <- unbox(v7) + (v9: @core::integer::u32) <- struct_destructure(v8) + (v23: core::integer::u32) <- desnap(v9) +End: + Goto(blk8, {v23 -> v24}) + +blk4: +Statements: + (v11: core::array::Span::) <- struct_construct(v3) + (v12: core::option::Option::<@core::box::Box::<[core::integer::u32; 2]>>) <- core::array::SpanTryIntoFixedSizedArray::::try_into(v11) +End: + Match(match_enum(v12) { + Option::Some(v13) => blk5, + Option::None(v18) => blk6, + }) + +blk5: +Statements: + (v14: core::box::Box::<@[core::integer::u32; 2]>) <- core::box::box_forward_snapshot::<[core::integer::u32; 2]>(v13) + (v15: @[core::integer::u32; 2]) <- unbox(v14) + (v16: @core::integer::u32, v17: @core::integer::u32) <- struct_destructure(v15) + (v20: core::integer::u32) <- desnap(v16) + (v21: core::integer::u32) <- desnap(v17) + (v22: core::integer::u32) <- core::integer::U32Add::add(v20, v21) +End: + Goto(blk8, {v22 -> v24}) + +blk6: +Statements: +End: + Goto(blk7, {}) + +blk7: +Statements: + (v19: core::integer::u32) <- 0 +End: + Goto(blk8, {v19 -> v24}) + +blk8: +Statements: +End: + Return(v24) + +//! > ========================================================================== + +//! > Option of Span to fixed size array without wildcard. + +//! > test_runner_name +test_create_graph(expect_diagnostics: true) + +//! > function_code +fn foo(s: Option>) -> u32 { + match s { + Some([a]) => *a, + Some([a, b]) => *a + *b, + None => 0, + // Some(Span { snapshot: inner }) => inner.len(), + } +} + +//! > graph +Root: 11 +11 EvaluateExpr { expr: ExprId(0), var_id: v0, next: NodeId(10) } +10 EnumMatch { matched_var: v0, variants: (NodeId(9), v1), (NodeId(2), v6)} +9 Deconstruct { input: v1, outputs: [v2], next: NodeId(8) } +8 SliceDestructure { input: v2, outputs: [v5], success: NodeId(7), failure: NodeId(6)} +7 BindVar { input: v5, output: PatternVarId(2), next: NodeId(0) } +6 SliceDestructure { input: v2, outputs: [v3, v4], success: NodeId(5), failure: NodeId(3)} +5 BindVar { input: v3, output: PatternVarId(0), next: NodeId(4) } +4 BindVar { input: v4, output: PatternVarId(1), next: NodeId(1) } +3 Missing +2 ArmExpr { expr: ExprId(8) } +1 ArmExpr { expr: ExprId(7) } +0 ArmExpr { expr: ExprId(2) } + +//! > semantic_diagnostics + +//! > lowering_diagnostics +error[E3004]: Match is non-exhaustive: `Some(_)` not covered. + --> lib.cairo:2:5-7:5 + match s { + _____^ +| ... +| } +|_____^ + +//! > lowered +Parameters: v0: core::option::Option::> + +//! > ========================================================================== + +//! > Fixed-size array or-pattern with wildcard — SliceDestructure is skipped. + +//! > test_runner_name +test_create_graph(expect_diagnostics: false) + +//! > function_code +fn foo(s: Span) -> u32 { + match s { + [_, _] | _ => 0, + } +} + +//! > graph +Root: 2 +2 EvaluateExpr { expr: ExprId(0), var_id: v0, next: NodeId(1) } +1 Deconstruct { input: v0, outputs: [v1], next: NodeId(0) } +0 ArmExpr { expr: ExprId(1) } + +//! > semantic_diagnostics + +//! > lowering_diagnostics + +//! > lowered +Parameters: v0: core::array::Span:: +blk0 (root): +Statements: + (v1: @core::array::Array::) <- struct_destructure(v0) + (v2: core::integer::u32) <- 0 +End: + Return(v2) diff --git a/crates/cairo-lang-semantic/src/corelib.rs b/crates/cairo-lang-semantic/src/corelib.rs index 797d75128f7..52ff83cd871 100644 --- a/crates/cairo-lang-semantic/src/corelib.rs +++ b/crates/cairo-lang-semantic/src/corelib.rs @@ -970,6 +970,7 @@ pub struct CoreInfo<'db> { pub iterator_trt: TraitId<'db>, pub fn_trt: TraitId<'db>, pub fn_once_trt: TraitId<'db>, + pub try_into_trt: TraitId<'db>, pub type_eq_trt: TraitId<'db>, pub felt252_dict_value_trt: TraitId<'db>, pub box_trt: TraitId<'db>, @@ -1007,11 +1008,13 @@ pub struct CoreInfo<'db> { pub next_fn: TraitFunctionId<'db>, pub call_fn: TraitFunctionId<'db>, pub call_once_fn: TraitFunctionId<'db>, + pub try_into_fn: TraitFunctionId<'db>, pub box_new_fn: TraitFunctionId<'db>, pub upcast_fn: GenericFunctionId<'db>, pub downcast_fn: GenericFunctionId<'db>, pub into_box: ExternFunctionId<'db>, pub unbox: ExternFunctionId<'db>, + pub box_forward_snapshot: ExternFunctionId<'db>, pub tuple_submodule: ModuleId<'db>, pub fixed_size_array_submodule: ModuleId<'db>, pub keyword_docs_submodule: ModuleId<'db>, @@ -1118,9 +1121,11 @@ impl<'db> CoreInfo<'db> { iterator_trt, fn_trt, fn_once_trt, + try_into_trt: traits.trait_id("TryInto"), type_eq_trt: core.submodule("metaprogramming").trait_id("TypeEqual"), felt252_dict_value_trt: traits.trait_id("Felt252DictValue"), box_trt, + try_into_fn: trait_fn(traits.trait_id("TryInto"), "try_into"), deref_fn: trait_fn(deref_trt, "deref"), deref_mut_fn: trait_fn(deref_mut_trt, "deref_mut"), destruct_fn: trait_fn(destruct_trt, "destruct"), @@ -1159,6 +1164,7 @@ impl<'db> CoreInfo<'db> { downcast_fn: bounded_int.generic_function_id("downcast"), into_box: box_module.extern_function_id("into_box"), unbox: box_module.extern_function_id("unbox"), + box_forward_snapshot: box_module.extern_function_id("box_forward_snapshot"), tuple_submodule, fixed_size_array_submodule, keyword_docs_submodule: core.submodule("keyword_docs").id,