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 119328f80f9..3b7d1ea6bcc 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 @@ -150,6 +150,21 @@ 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), diff --git a/crates/cairo-lang-semantic/src/diagnostic.rs b/crates/cairo-lang-semantic/src/diagnostic.rs index b47c855c6ac..0b4bf69bf12 100644 --- a/crates/cairo-lang-semantic/src/diagnostic.rs +++ b/crates/cairo-lang-semantic/src/diagnostic.rs @@ -741,7 +741,7 @@ impl<'db> DiagnosticEntry<'db> for SemanticDiagnostic<'db> { SemanticDiagnosticKind::UnexpectedFixedSizeArrayPattern(ty) => { format!( "Unexpected type for fixed size array pattern. \"{}\" is not a fixed size \ - array.", + array or a span.", ty.format(db), ) } diff --git a/crates/cairo-lang-semantic/src/expr/compute.rs b/crates/cairo-lang-semantic/src/expr/compute.rs index 6f5acc73448..54e06bdc211 100644 --- a/crates/cairo-lang-semantic/src/expr/compute.rs +++ b/crates/cairo-lang-semantic/src/expr/compute.rs @@ -3082,6 +3082,50 @@ fn maybe_compute_pattern_semantic<'db>( Ok(pattern) } +/// Tries to get the long type of the matched expression for a tuple-like pattern. +/// Returns `None` if the pattern and type are incompatible. +fn try_get_match_expr_long_ty<'db>( + ctx: &mut ComputationContext<'db, '_>, + pattern_syntax: &ast::Pattern<'db>, + long_ty: &TypeLongId<'db>, + ty: TypeId<'db>, + num_patterns: usize, +) -> Option> { + let db = ctx.db; + match (pattern_syntax, long_ty) { + (_, TypeLongId::Var(_) | TypeLongId::Missing(_)) + | (ast::Pattern::Tuple(_), TypeLongId::Tuple(_)) + | (ast::Pattern::FixedSizeArray(_), TypeLongId::FixedSizeArray { .. }) => { + Some(long_ty.clone()) + } + ( + ast::Pattern::FixedSizeArray(_), + TypeLongId::Concrete(ConcreteTypeId::Struct(concrete_struct_id)), + ) => { + let [GenericArgumentId::Type(inner_ty)] = concrete_struct_id.long(db).generic_args[..] + else { + return None; + }; + let span_ty = try_get_core_ty_by_name( + db, + SmolStrId::from(db, "Span"), + vec![GenericArgumentId::Type(inner_ty)], + ) + .ok()?; + if let Err(err_set) = ctx.resolver.inference().conform_ty(ty, span_ty) { + // The caller is going to report a more accurate error message. + ctx.resolver.inference().consume_error_without_reporting(err_set); + return None; + } + Some(TypeLongId::FixedSizeArray { + type_id: wrap_in_snapshots(db, inner_ty, 1), + size: ConstValue::Int(num_patterns.into(), get_usize_ty(db)).intern(db), + }) + } + _ => None, + } +} + /// Computes the semantic model of a pattern of a tuple or a fixed size array. Assumes that the /// pattern is one of these types. fn compute_tuple_like_pattern_semantic<'db>( @@ -3093,7 +3137,7 @@ fn compute_tuple_like_pattern_semantic<'db>( wrong_number_of_elements: fn(usize, usize) -> SemanticDiagnosticKind<'db>, ) -> Pattern<'db> { let db = ctx.db; - let (wrapping_info, mut long_ty) = + let (wrapping_info, long_ty) = match extract_tuple_like_from_pattern_and_validate(ctx, pattern_syntax, ty) { Ok(value) => value, Err(diag_added) => ( @@ -3101,24 +3145,27 @@ fn compute_tuple_like_pattern_semantic<'db>( TypeLongId::Missing(diag_added), ), }; - // Assert that the pattern is of the same type as the expr. - match (pattern_syntax, &long_ty) { - (_, TypeLongId::Var(_) | TypeLongId::Missing(_)) - | (ast::Pattern::Tuple(_), TypeLongId::Tuple(_)) - | (ast::Pattern::FixedSizeArray(_), TypeLongId::FixedSizeArray { .. }) => {} - _ => { - long_ty = TypeLongId::Missing( - ctx.diagnostics.report(pattern_syntax.stable_ptr(db), unexpected_pattern(ty)), - ); - } - }; - let patterns_syntax = match pattern_syntax { + let patterns_syntax_elements = match pattern_syntax { ast::Pattern::Tuple(pattern_tuple) => pattern_tuple.patterns(db).elements_vec(db), ast::Pattern::FixedSizeArray(pattern_fixed_size_array) => { pattern_fixed_size_array.patterns(db).elements_vec(db) } _ => unreachable!(), }; + + // Assert that the pattern is of the same type as the expr. + let long_ty = try_get_match_expr_long_ty( + ctx, + pattern_syntax, + &long_ty, + ty, + patterns_syntax_elements.len(), + ) + .unwrap_or_else(|| { + TypeLongId::Missing( + ctx.diagnostics.report(pattern_syntax.stable_ptr(db), unexpected_pattern(ty)), + ) + }); let mut inner_tys = match long_ty { TypeLongId::Tuple(inner_tys) => inner_tys, TypeLongId::FixedSizeArray { type_id: inner_ty, size } => { @@ -3127,7 +3174,8 @@ fn compute_tuple_like_pattern_semantic<'db>( } else { let inference = &mut ctx.resolver.inference(); let expected_size = - ConstValue::Int(patterns_syntax.len().into(), get_usize_ty(db)).intern(db); + ConstValue::Int(patterns_syntax_elements.len().into(), get_usize_ty(db)) + .intern(db); if let Err(err) = inference.conform_const(size, expected_size) { let _ = inference.report_on_pending_error( err, @@ -3135,7 +3183,7 @@ fn compute_tuple_like_pattern_semantic<'db>( pattern_syntax.stable_ptr(db).untyped(), ); } - patterns_syntax.len() + patterns_syntax_elements.len() }; [inner_ty].repeat(size) @@ -3143,7 +3191,7 @@ fn compute_tuple_like_pattern_semantic<'db>( TypeLongId::Var(_) => { let inference = &mut ctx.resolver.inference(); let (inner_tys, tuple_like_ty) = if matches!(pattern_syntax, ast::Pattern::Tuple(_)) { - let inner_tys: Vec<_> = patterns_syntax + let inner_tys: Vec<_> = patterns_syntax_elements .iter() .map(|e| inference.new_type_var(Some(e.stable_ptr(db).untyped()))) .collect(); @@ -3151,11 +3199,14 @@ fn compute_tuple_like_pattern_semantic<'db>( } else { let var = inference.new_type_var(Some(pattern_syntax.stable_ptr(db).untyped())); ( - vec![var; patterns_syntax.len()], + vec![var; patterns_syntax_elements.len()], TypeLongId::FixedSizeArray { type_id: var, - size: ConstValue::Int(patterns_syntax.len().into(), get_usize_ty(db)) - .intern(db), + size: ConstValue::Int( + patterns_syntax_elements.len().into(), + get_usize_ty(db), + ) + .intern(db), }, ) }; @@ -3167,21 +3218,21 @@ fn compute_tuple_like_pattern_semantic<'db>( } TypeLongId::Missing(diag_added) => { let missing = TypeId::missing(db, diag_added); - vec![missing; patterns_syntax.len()] + vec![missing; patterns_syntax_elements.len()] } _ => unreachable!(), }; let size = inner_tys.len(); - if size != patterns_syntax.len() { + if size != patterns_syntax_elements.len() { let diag_added = ctx.diagnostics.report( pattern_syntax.stable_ptr(db), - wrong_number_of_elements(size, patterns_syntax.len()), + wrong_number_of_elements(size, patterns_syntax_elements.len()), ); let missing = TypeId::missing(db, diag_added); - inner_tys = vec![missing; patterns_syntax.len()]; + inner_tys = vec![missing; patterns_syntax_elements.len()]; } - let subpatterns = zip_eq(patterns_syntax, inner_tys) + let subpatterns = zip_eq(patterns_syntax_elements, inner_tys) .map(|(pattern_ast, ty)| { let ty = wrapping_info.wrap(db, ty); compute_pattern_semantic(ctx, &pattern_ast, ty, or_pattern_variables_map).id diff --git a/crates/cairo-lang-semantic/src/expr/test_data/let_statement b/crates/cairo-lang-semantic/src/expr/test_data/let_statement index d5ab6a73bff..965ed5b05d0 100644 --- a/crates/cairo-lang-semantic/src/expr/test_data/let_statement +++ b/crates/cairo-lang-semantic/src/expr/test_data/let_statement @@ -153,7 +153,7 @@ error[E0006]: Function not found. let (a, b, c) = undefined(); ^^^^^^^^^ -error[E2106]: Unexpected type for fixed size array pattern. "(, , )" is not a fixed size array. +error[E2106]: Unexpected type for fixed size array pattern. "(, , )" is not a fixed size array or a span. --> lib.cairo:3:9 let [d, e, f] = (a, b, c); ^^^^^^^^^ diff --git a/crates/cairo-lang-semantic/src/expr/test_data/pattern b/crates/cairo-lang-semantic/src/expr/test_data/pattern index 78181526318..006a9223feb 100644 --- a/crates/cairo-lang-semantic/src/expr/test_data/pattern +++ b/crates/cairo-lang-semantic/src/expr/test_data/pattern @@ -203,7 +203,7 @@ fn foo(s: (felt252, felt252, felt252)) { foo //! > expected_diagnostics -error[E2106]: Unexpected type for fixed size array pattern. "(core::felt252, core::felt252, core::felt252)" is not a fixed size array. +error[E2106]: Unexpected type for fixed size array pattern. "(core::felt252, core::felt252, core::felt252)" is not a fixed size array or a span. --> lib.cairo:2:9 let [_a, _b, _c] = s; ^^^^^^^^^^^^ @@ -313,3 +313,129 @@ error[E2049]: Redefinition of variable name "_b" in pattern. --> lib.cairo:7:19 ((_b, _), _b) => (), ^^ + +//! > ========================================================================== + +//! > Test span into fixed size array pattern. + +//! > test_runner_name +test_function_diagnostics(expect_diagnostics: false) + +//! > function_code +fn foo(s: Span) -> u32 { + let [a, b] = s else { + return 0; + }; + *a + *b +} + +//! > function_name +foo + +//! > module_code + +//! > expected_diagnostics + +//! > ========================================================================== + +//! > Test type mismatch in span pattern arm surfaces as confusing ToSpanTrait error. + +//! > test_runner_name +test_function_diagnostics(expect_diagnostics: true) + +//! > function_code +fn foo() { + let a: Array = array![1, 2, 3]; + let c: u16 = 0; + match a.span() { + [x, y, z] => c + *x + *y + *z, + _ => 0, + }; +} + +//! > function_name +foo + +//! > module_code + +//! > expected_diagnostics +error[E2311]: Trait has no implementation in context: core::array::ToSpanTrait::, core::integer::u16>. + --> lib.cairo:4:13 + match a.span() { + ^^^^ + +//! > ========================================================================== + +//! > Test array pattern on non-Span generic struct. + +//! > test_runner_name +test_function_diagnostics(expect_diagnostics: true) + +//! > function_code +fn foo(s: MyStruct) { + let [_x, _y] = s; +} + +//! > function_name +foo + +//! > module_code +struct MyStruct { + value: T, +} + +//! > expected_diagnostics +error[E2106]: Unexpected type for fixed size array pattern. "test::MyStruct::" is not a fixed size array or a span. + --> lib.cairo:5:9 + let [_x, _y] = s; + ^^^^^^^^ + +//! > ========================================================================== + +//! > Test array pattern on Array instead of Span. + +//! > test_runner_name +test_function_diagnostics(expect_diagnostics: true) + +//! > function_code +fn foo() { + let a: Array = array![1, 2, 3]; + match a { + [_x, _y, _z] => {}, + _ => {}, + }; +} + +//! > function_name +foo + +//! > module_code + +//! > expected_diagnostics +error[E2106]: Unexpected type for fixed size array pattern. "core::array::Array::" is not a fixed size array or a span. + --> lib.cairo:4:9 + [_x, _y, _z] => {}, + ^^^^^^^^^^^^ + +//! > ========================================================================== + +//! > Test array pattern on fixed size array value. + +//! > test_runner_name +test_function_diagnostics(expect_diagnostics: false) + +//! > function_code +fn foo() { + let a: [u8; 3] = [1, 2, 3]; + match a { + [_x, _y, _z] => {}, + _ => {}, + }; +} + +//! > function_name +foo + +//! > module_code + +//! > expected_diagnostics