From c535f9446ddc26ba715e6899cd978a31bf744269 Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Thu, 7 May 2026 11:30:05 +0300 Subject: [PATCH 1/7] Add ternary to TUTORIAL.md --- docs/TUTORIAL.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/TUTORIAL.md b/docs/TUTORIAL.md index 8c3d473..aa62606 100644 --- a/docs/TUTORIAL.md +++ b/docs/TUTORIAL.md @@ -21,6 +21,7 @@ - [Comparison Operators](#comparison-operators) - [Logical Operators](#logical-operators) - [Bitwise Operators](#bitwise-operators) + - [Ternary Operator](#ternary-operator) 6. [Control Flow](#control-flow) - [If Statements](#if-statements) - [Require Statements](#require-statements) @@ -403,6 +404,23 @@ int bitOr = x | y; // 0xFF (bitwise OR) int bitXor = x ^ y; // 0xFF (bitwise XOR) ``` +### Ternary Operator + +Use the ternary operator to choose between two expressions: + +```javascript +int value = condition ? thenValue : elseValue; +``` + +The condition must evaluate to `bool`, and both result branches must have the same type. The ternary expression's result must also match the declared type where it is assigned or returned: + +```javascript +entrypoint function example(int amount, bool useBonus) { + int payout = useBonus ? amount + 100 : amount; + require(payout >= amount); +} +``` + --- ## Control Flow From 619dc39c82481a20a0cbef43037dc850cdbc294c Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Thu, 7 May 2026 12:06:52 +0300 Subject: [PATCH 2/7] Add ternary to TUTORIAL.md --- docs/TUTORIAL.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/TUTORIAL.md b/docs/TUTORIAL.md index aa62606..6696471 100644 --- a/docs/TUTORIAL.md +++ b/docs/TUTORIAL.md @@ -409,6 +409,9 @@ int bitXor = x ^ y; // 0xFF (bitwise XOR) Use the ternary operator to choose between two expressions: ```javascript +bool condition = true; +int thenValue = 100; +int elseValue = 50; int value = condition ? thenValue : elseValue; ``` From b70843df424045f3a99a409b78abab9f07704f4e Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Thu, 7 May 2026 12:30:28 +0300 Subject: [PATCH 3/7] Infer array size before static check --- silverscript-lang/src/compiler/compile.rs | 8 +++++--- silverscript-lang/src/compiler/mod.rs | 4 +--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/silverscript-lang/src/compiler/compile.rs b/silverscript-lang/src/compiler/compile.rs index 3ae8e5e..0e8e1f3 100644 --- a/silverscript-lang/src/compiler/compile.rs +++ b/silverscript-lang/src/compiler/compile.rs @@ -5,6 +5,7 @@ use super::infer_array::lower_inferred_array_sizes; use super::inline_functions::lower_inline_functions; use super::locals::lower_local_aliases; use super::stack_bindings::StackBindings; +use super::static_check::static_check_contract; use super::*; use kaspa_txscript::opcodes::codes::*; use kaspa_txscript::script_builder::ScriptBuilder; @@ -98,14 +99,15 @@ pub(super) fn compile_contract_impl<'i>( } let mut debug_recorder = DebugRecorder::new(options, contract)?; - let covenant_lowered_contract = lower_covenant_declarations(contract, &constants)?; + let inferred_lowered_contract = lower_inferred_array_sizes(contract, &constants)?; + static_check_contract(&inferred_lowered_contract, constructor_args, options)?; + let covenant_lowered_contract = lower_covenant_declarations(&inferred_lowered_contract, &constants)?; let inline_lowered_contract = lower_inline_functions(&covenant_lowered_contract, &mut debug_recorder)?; let structs = build_struct_registry(&inline_lowered_contract)?; let struct_lowered_contract = lower_structs_contract(&inline_lowered_contract, &structs, &constants)?; let append_lowered_contract = lower_array_appends(&struct_lowered_contract)?; let for_lowered_contract = lower_for_loops(&append_lowered_contract, &constants)?; - let lowered_contract = lower_inferred_array_sizes(&for_lowered_contract, &constants)?; - let lowered_contract = if options.record_debug_infos { lowered_contract } else { lower_local_aliases(&lowered_contract)? }; + let lowered_contract = if options.record_debug_infos { for_lowered_contract } else { lower_local_aliases(&for_lowered_contract)? }; let mut lowered_constants = flatten_constructor_args_env(&covenant_lowered_contract.params, constructor_args, &structs)?; lowered_constants.extend(lowered_contract.constants.iter().map(|constant| (constant.name.clone(), constant.expr.clone()))); diff --git a/silverscript-lang/src/compiler/mod.rs b/silverscript-lang/src/compiler/mod.rs index 710352e..eb6fd9d 100644 --- a/silverscript-lang/src/compiler/mod.rs +++ b/silverscript-lang/src/compiler/mod.rs @@ -31,7 +31,7 @@ pub use compile::{compile_debug_expr, function_branch_index}; pub(crate) use debug_recording::DebugRecorder; use r#for::lower_for_loops; pub(crate) use static_check::expr_matches_declared_type_ref; -use static_check::{static_check_contract, value_matches_type_ref}; +use static_check::value_matches_type_ref; pub use structs::flattened_struct_name; pub(super) use structs::{ StructFieldSpec, StructRegistry, build_struct_registry, ensure_known_or_builtin_type, flatten_constructor_args_env, @@ -106,7 +106,6 @@ pub fn compile_contract<'i>( options: CompileOptions, ) -> Result, CompilerError> { let contract = parse_contract_ast(source)?; - static_check_contract(&contract, constructor_args, options)?; compile_contract_impl(&contract, constructor_args, options, Some(source)) } @@ -115,7 +114,6 @@ pub fn compile_contract_ast<'i>( constructor_args: &[Expr<'i>], options: CompileOptions, ) -> Result, CompilerError> { - static_check_contract(contract, constructor_args, options)?; compile_contract_impl(contract, constructor_args, options, None) } From 6883569c7d3b6e924b31fc1e87f2a4389b7a3476 Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Sun, 10 May 2026 09:51:32 +0300 Subject: [PATCH 4/7] Add rejects_inferred_array_size_when_initializer_cannot_provide_matching_fixed_array_type --- silverscript-lang/tests/compiler_tests.rs | 55 +++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/silverscript-lang/tests/compiler_tests.rs b/silverscript-lang/tests/compiler_tests.rs index dd559b6..1b98bb8 100644 --- a/silverscript-lang/tests/compiler_tests.rs +++ b/silverscript-lang/tests/compiler_tests.rs @@ -1288,6 +1288,61 @@ fn rejects_comparing_inferred_and_fixed_byte_arrays_when_sizes_differ() { ); } +#[test] +fn rejects_inferred_array_size_when_initializer_cannot_provide_matching_fixed_array_type() { + let cases = [ + ( + "literal values do not match declared element type", + r#" + int[_] x = [1, true]; + "#, + ), + ( + "identifier is unknown", + r#" + int[_] x = y; + "#, + ), + ( + "identifier is not an array", + r#" + int y = 1; + int[_] x = y; + "#, + ), + ( + "identifier has a different array element type", + r#" + bool[2] y = [true, false]; + int[_] x = y; + "#, + ), + ( + "identifier has a dynamic array size", + r#" + int[] y = [1, 2]; + int[_] x = y; + "#, + ), + ]; + + for (name, body) in cases { + let source = format!( + r#" + contract Arrays() {{ + entrypoint function main() {{ + {body} + require(true); + }} + }} + "# + ); + + let err = compile_contract(&source, &[], CompileOptions::default()).expect_err(&format!("{name} should fail")); + assert!(err.to_string().contains("cannot infer fixed array size from variable 'x'"), "{name}: unexpected error: {err}"); + } +} + #[test] fn infers_fixed_sizes_for_multiple_array_element_types() { let source = r#" From 7f81e4975398ec47fde9f63cebd26761a941bece Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Sun, 10 May 2026 10:03:18 +0300 Subject: [PATCH 5/7] Remove type check from infer_array.rs --- silverscript-lang/src/compiler/infer_array.rs | 4 ++-- silverscript-lang/tests/compiler_tests.rs | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/silverscript-lang/src/compiler/infer_array.rs b/silverscript-lang/src/compiler/infer_array.rs index 63a37d0..557c642 100644 --- a/silverscript-lang/src/compiler/infer_array.rs +++ b/silverscript-lang/src/compiler/infer_array.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use super::compile::{array_literal_matches_type_with_env_ref, type_name_from_ref}; +use super::compile::type_name_from_ref; use super::*; use crate::ast::{ArrayDim, ConstantAst, ContractAst, ContractFieldAst, FunctionAst, ParamAst, Statement, TypeRef}; @@ -59,7 +59,7 @@ pub(super) fn infer_fixed_array_type_from_initializer_ref<'i>( ExprKind::Array(values) => { let mut inferred = element_type.clone(); inferred.array_dims.push(ArrayDim::Fixed(values.len())); - if array_literal_matches_type_with_env_ref(values, &inferred, types, constants) { Some(inferred) } else { None } + Some(inferred) } ExprKind::Identifier(name) => { let other_type = parse_type_ref(types.get(name)?).ok()?; diff --git a/silverscript-lang/tests/compiler_tests.rs b/silverscript-lang/tests/compiler_tests.rs index 1b98bb8..021a9dd 100644 --- a/silverscript-lang/tests/compiler_tests.rs +++ b/silverscript-lang/tests/compiler_tests.rs @@ -1296,12 +1296,14 @@ fn rejects_inferred_array_size_when_initializer_cannot_provide_matching_fixed_ar r#" int[_] x = [1, true]; "#, + "array element type mismatch", ), ( "identifier is unknown", r#" int[_] x = y; "#, + "cannot infer fixed array size from variable 'x'", ), ( "identifier is not an array", @@ -1309,6 +1311,7 @@ fn rejects_inferred_array_size_when_initializer_cannot_provide_matching_fixed_ar int y = 1; int[_] x = y; "#, + "cannot infer fixed array size from variable 'x'", ), ( "identifier has a different array element type", @@ -1316,6 +1319,7 @@ fn rejects_inferred_array_size_when_initializer_cannot_provide_matching_fixed_ar bool[2] y = [true, false]; int[_] x = y; "#, + "cannot infer fixed array size from variable 'x'", ), ( "identifier has a dynamic array size", @@ -1323,10 +1327,11 @@ fn rejects_inferred_array_size_when_initializer_cannot_provide_matching_fixed_ar int[] y = [1, 2]; int[_] x = y; "#, + "cannot infer fixed array size from variable 'x'", ), ]; - for (name, body) in cases { + for (name, body, expected_error) in cases { let source = format!( r#" contract Arrays() {{ @@ -1339,7 +1344,7 @@ fn rejects_inferred_array_size_when_initializer_cannot_provide_matching_fixed_ar ); let err = compile_contract(&source, &[], CompileOptions::default()).expect_err(&format!("{name} should fail")); - assert!(err.to_string().contains("cannot infer fixed array size from variable 'x'"), "{name}: unexpected error: {err}"); + assert!(err.to_string().contains(expected_error), "{name}: expected error containing '{expected_error}', got: {err}"); } } From 1a083668e55d7cf2940c26d966f90a8419dd431e Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Sun, 10 May 2026 10:18:25 +0300 Subject: [PATCH 6/7] Add array infer tests --- silverscript-lang/tests/compiler_tests.rs | 65 +++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/silverscript-lang/tests/compiler_tests.rs b/silverscript-lang/tests/compiler_tests.rs index 021a9dd..dd945f2 100644 --- a/silverscript-lang/tests/compiler_tests.rs +++ b/silverscript-lang/tests/compiler_tests.rs @@ -1378,6 +1378,71 @@ fn infers_fixed_sizes_for_multiple_array_element_types() { ); } +#[test] +fn infers_fixed_array_size_from_function_call_initializer_expression() { + let source = r#" + contract Arrays() { + function makeArray(): int[3] { + return [1, 2, 3]; + } + + entrypoint function main() { + int[_] x = makeArray(); + require(x.length == 3); + } + } + "#; + + compile_contract(source, &[], CompileOptions::default()).expect("int[_] x should infer from function call returning int[3]"); +} + +#[test] +fn infers_fixed_array_size_from_array_concat_initializer_expression() { + let source = r#" + contract Arrays() { + entrypoint function main() { + int[2] left = [1, 2]; + int[1] right = [3]; + int[_] x = left + right; + require(x.length == 3); + } + } + "#; + + compile_contract(source, &[], CompileOptions::default()).expect("int[_] x should infer from int[2] + int[1]"); +} + +#[test] +fn infers_fixed_array_size_from_ternary_initializer_expression() { + let source = r#" + contract Arrays() { + entrypoint function main(bool flag) { + int[3] left = [1, 2, 3]; + int[3] right = [4, 5, 6]; + int[_] x = flag ? left : right; + require(x.length == 3); + } + } + "#; + + compile_contract(source, &[], CompileOptions::default()).expect("int[_] x should infer from ternary branches typed int[3]"); +} + +#[test] +fn recursively_infers_fixed_array_size_from_inferred_array_identifier() { + let source = r#" + contract Arrays() { + entrypoint function main() { + int[_] x = [1, 2, 3]; + int[_] y = x; + require(y.length == 3); + } + } + "#; + + compile_contract(source, &[], CompileOptions::default()).expect("int[_] y should infer from previously inferred int[_] x"); +} + #[test] fn rejects_comparing_dynamic_and_fixed_arrays_without_cast_in_function_scope() { let source = r#" From 4710ff5e2b965f7e092293df41c9a9478c0a23aa Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Sun, 10 May 2026 11:01:16 +0300 Subject: [PATCH 7/7] Better array infer --- silverscript-lang/src/compiler/infer_array.rs | 136 +++++++++++++----- 1 file changed, 104 insertions(+), 32 deletions(-) diff --git a/silverscript-lang/src/compiler/infer_array.rs b/silverscript-lang/src/compiler/infer_array.rs index 557c642..f3217cb 100644 --- a/silverscript-lang/src/compiler/infer_array.rs +++ b/silverscript-lang/src/compiler/infer_array.rs @@ -8,6 +8,8 @@ pub(super) fn lower_inferred_array_sizes<'i>( contract: &ContractAst<'i>, contract_constants: &HashMap>, ) -> Result, CompilerError> { + let functions_by_name: HashMap> = + contract.functions.iter().map(|function| (function.name.clone(), function)).collect(); let mut top_level_types = HashMap::new(); for param in &contract.params { top_level_types.insert(param.name.clone(), type_name_from_ref(¶m.type_ref)); @@ -16,17 +18,17 @@ pub(super) fn lower_inferred_array_sizes<'i>( let constants = contract .constants .iter() - .map(|constant| lower_constant(constant, &mut top_level_types, contract_constants)) + .map(|constant| lower_constant(constant, &mut top_level_types, contract_constants, &functions_by_name)) .collect::, _>>()?; let fields = contract .fields .iter() - .map(|field| lower_field(field, &mut top_level_types, contract_constants)) + .map(|field| lower_field(field, &mut top_level_types, contract_constants, &functions_by_name)) .collect::, _>>()?; let functions = contract .functions .iter() - .map(|function| lower_function(function, &top_level_types, contract_constants)) + .map(|function| lower_function(function, &top_level_types, contract_constants, &functions_by_name)) .collect::, _>>()?; Ok(ContractAst { @@ -42,11 +44,12 @@ pub(super) fn lower_inferred_array_sizes<'i>( }) } -pub(super) fn infer_fixed_array_type_from_initializer_ref<'i>( +fn infer_fixed_array_type_from_initializer_ref<'i>( declared_type: &TypeRef, initializer: Option<&Expr<'i>>, types: &HashMap, constants: &HashMap>, + functions: &HashMap>, ) -> Option { if !matches!(declared_type.array_size(), Some(ArrayDim::Inferred)) { return None; @@ -54,33 +57,25 @@ pub(super) fn infer_fixed_array_type_from_initializer_ref<'i>( let element_type = declared_type.element_type()?; let init = initializer?; + let init_type = infer_expr_type_ref(init, types, constants, functions, Some(&element_type))?; - match &init.kind { - ExprKind::Array(values) => { - let mut inferred = element_type.clone(); - inferred.array_dims.push(ArrayDim::Fixed(values.len())); - Some(inferred) - } - ExprKind::Identifier(name) => { - let other_type = parse_type_ref(types.get(name)?).ok()?; - if !other_type.is_array() || other_type.element_type() != Some(element_type.clone()) { - return None; - } - let size = array_size_with_constants_ref(&other_type, constants)?; - let mut inferred = element_type; - inferred.array_dims.push(ArrayDim::Fixed(size)); - Some(inferred) - } - _ => None, + if !init_type.is_array() || init_type.element_type() != Some(element_type.clone()) { + return None; } + + let size = array_size_with_constants_ref(&init_type, constants)?; + let mut inferred = element_type; + inferred.array_dims.push(ArrayDim::Fixed(size)); + Some(inferred) } fn lower_constant<'i>( constant: &ConstantAst<'i>, types: &mut HashMap, constants: &HashMap>, + functions: &HashMap>, ) -> Result, CompilerError> { - let type_ref = infer_type_ref(&constant.type_ref, Some(&constant.expr), types, constants) + let type_ref = infer_type_ref(&constant.type_ref, Some(&constant.expr), types, constants, functions) .ok_or_else(|| CompilerError::Unsupported(format!("cannot infer fixed array size from constant '{}'", constant.name)))?; types.insert(constant.name.clone(), type_name_from_ref(&type_ref)); Ok(ConstantAst { type_ref, ..constant.clone() }) @@ -90,8 +85,9 @@ fn lower_field<'i>( field: &ContractFieldAst<'i>, types: &mut HashMap, constants: &HashMap>, + functions: &HashMap>, ) -> Result, CompilerError> { - let type_ref = infer_type_ref(&field.type_ref, Some(&field.expr), types, constants) + let type_ref = infer_type_ref(&field.type_ref, Some(&field.expr), types, constants, functions) .ok_or_else(|| CompilerError::Unsupported(format!("cannot infer fixed array size from contract field '{}'", field.name)))?; types.insert(field.name.clone(), type_name_from_ref(&type_ref)); Ok(ContractFieldAst { type_ref, ..field.clone() }) @@ -101,12 +97,13 @@ fn lower_function<'i>( function: &FunctionAst<'i>, top_level_types: &HashMap, constants: &HashMap>, + functions: &HashMap>, ) -> Result, CompilerError> { let mut types = top_level_types.clone(); for param in &function.params { types.insert(param.name.clone(), type_name_from_ref(¶m.type_ref)); } - let body = lower_block(&function.body, &mut types, constants)?; + let body = lower_block(&function.body, &mut types, constants, functions)?; Ok(FunctionAst { body, ..function.clone() }) } @@ -114,10 +111,11 @@ fn lower_block<'i>( statements: &[Statement<'i>], types: &mut HashMap, constants: &HashMap>, + functions: &HashMap>, ) -> Result>, CompilerError> { let mut lowered = Vec::with_capacity(statements.len()); for statement in statements { - lowered.push(lower_statement(statement, types, constants)?); + lowered.push(lower_statement(statement, types, constants, functions)?); } Ok(lowered) } @@ -126,10 +124,11 @@ fn lower_statement<'i>( statement: &Statement<'i>, types: &mut HashMap, constants: &HashMap>, + functions: &HashMap>, ) -> Result, CompilerError> { match statement { Statement::VariableDefinition { type_ref, name, expr, .. } => { - let lowered_type = infer_type_ref(type_ref, expr.as_ref(), types, constants) + let lowered_type = infer_type_ref(type_ref, expr.as_ref(), types, constants, functions) .ok_or_else(|| CompilerError::Unsupported(format!("cannot infer fixed array size from variable '{}'", name)))?; types.insert(name.clone(), type_name_from_ref(&lowered_type)); Ok(match statement { @@ -152,7 +151,7 @@ fn lower_statement<'i>( let lowered_bindings = bindings .iter() .map(|binding| { - let lowered_type = infer_type_ref(&binding.type_ref, None, types, constants).ok_or_else(|| { + let lowered_type = infer_type_ref(&binding.type_ref, None, types, constants, functions).ok_or_else(|| { CompilerError::Unsupported(format!("cannot infer fixed array size from binding '{}'", binding.name)) })?; types.insert(binding.name.clone(), type_name_from_ref(&lowered_type)); @@ -169,15 +168,15 @@ fn lower_statement<'i>( } Statement::Block { body, span } => { let mut block_types = types.clone(); - let lowered_body = lower_block(body, &mut block_types, constants)?; + let lowered_body = lower_block(body, &mut block_types, constants, functions)?; Ok(Statement::Block { body: lowered_body, span: *span }) } Statement::If { condition, then_branch, else_branch, span, then_span, else_span } => { let mut then_types = types.clone(); - let lowered_then = lower_block(then_branch, &mut then_types, constants)?; + let lowered_then = lower_block(then_branch, &mut then_types, constants, functions)?; let (lowered_else, merged_types) = if let Some(else_branch) = else_branch { let mut else_types = types.clone(); - let lowered_else = lower_block(else_branch, &mut else_types, constants)?; + let lowered_else = lower_block(else_branch, &mut else_types, constants, functions)?; let mut merged = then_types; merged.extend(else_types); (Some(lowered_else), merged) @@ -197,7 +196,7 @@ fn lower_statement<'i>( Statement::For { ident, start, end, max_iterations, body, span, ident_span, body_span } => { let mut body_types = types.clone(); body_types.insert(ident.clone(), "int".to_string()); - let lowered_body = lower_block(body, &mut body_types, constants)?; + let lowered_body = lower_block(body, &mut body_types, constants, functions)?; Ok(Statement::For { ident: ident.clone(), start: start.clone(), @@ -218,14 +217,87 @@ fn infer_type_ref<'i>( initializer: Option<&Expr<'i>>, types: &HashMap, constants: &HashMap>, + functions: &HashMap>, ) -> Option { if matches!(declared_type.array_size(), Some(ArrayDim::Inferred)) { - infer_fixed_array_type_from_initializer_ref(declared_type, initializer, types, constants) + infer_fixed_array_type_from_initializer_ref(declared_type, initializer, types, constants, functions) } else { Some(declared_type.clone()) } } +fn infer_expr_type_ref<'i>( + expr: &Expr<'i>, + types: &HashMap, + constants: &HashMap>, + functions: &HashMap>, + array_literal_element_type: Option<&TypeRef>, +) -> Option { + match &expr.kind { + ExprKind::Identifier(name) => parse_type_ref(types.get(name)?).ok(), + ExprKind::Array(values) => { + let mut inferred = array_literal_element_type + .cloned() + .or_else(|| infer_array_literal_element_type(values, types, constants, functions))?; + inferred.array_dims.push(ArrayDim::Fixed(values.len())); + Some(inferred) + } + ExprKind::Call { name, .. } => { + if let Some(function) = functions.get(name) { + if function.entrypoint || function.return_types.len() != 1 { + return None; + } + return Some(function.return_types[0].clone()); + } + parse_type_ref(name).ok() + } + ExprKind::Binary { op: BinaryOp::Add, left, right } => { + let left_type = infer_expr_type_ref(left, types, constants, functions, None)?; + let right_type = infer_expr_type_ref(right, types, constants, functions, None)?; + let left_element = left_type.element_type()?; + if right_type.element_type() != Some(left_element.clone()) { + return None; + } + let left_size = array_size_with_constants_ref(&left_type, constants)?; + let right_size = array_size_with_constants_ref(&right_type, constants)?; + let mut inferred = left_element; + inferred.array_dims.push(ArrayDim::Fixed(left_size.checked_add(right_size)?)); + Some(inferred) + } + ExprKind::IfElse { then_expr, else_expr, .. } => { + let then_type = infer_expr_type_ref(then_expr, types, constants, functions, None)?; + let else_type = infer_expr_type_ref(else_expr, types, constants, functions, None)?; + (then_type == else_type).then_some(then_type) + } + ExprKind::Append { source, args, .. } => { + let source_type = infer_expr_type_ref(source, types, constants, functions, None)?; + let element_type = source_type.element_type()?; + let source_size = array_size_with_constants_ref(&source_type, constants)?; + let mut inferred = element_type; + inferred.array_dims.push(ArrayDim::Fixed(source_size.checked_add(args.len())?)); + Some(inferred) + } + ExprKind::UnarySuffix { source, kind: UnarySuffixKind::Reverse, .. } => { + infer_expr_type_ref(source, types, constants, functions, None) + } + _ => None, + } +} + +fn infer_array_literal_element_type<'i>( + values: &[Expr<'i>], + types: &HashMap, + constants: &HashMap>, + functions: &HashMap>, +) -> Option { + let first_type = infer_expr_type_ref(values.first()?, types, constants, functions, None)?; + if values.iter().skip(1).all(|value| infer_expr_type_ref(value, types, constants, functions, None).as_ref() == Some(&first_type)) { + Some(first_type) + } else { + None + } +} + fn array_size_with_constants_ref<'i>(type_ref: &TypeRef, constants: &HashMap>) -> Option { match type_ref.array_size()? { ArrayDim::Fixed(size) => Some(*size),