From c9ec15a043997b31ad201a0ffef96ffc306d7490 Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Wed, 6 May 2026 18:19:40 +0300 Subject: [PATCH 1/2] Minor changes in inline_functions.rs --- .../src/compiler/inline_functions.rs | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/silverscript-lang/src/compiler/inline_functions.rs b/silverscript-lang/src/compiler/inline_functions.rs index a750227..a910d10 100644 --- a/silverscript-lang/src/compiler/inline_functions.rs +++ b/silverscript-lang/src/compiler/inline_functions.rs @@ -357,7 +357,7 @@ impl<'i, 'd> Inliner<'i, 'd> { } fn inline_target(&self, name: &str) -> Option> { - self.functions.get(name).cloned().filter(|function| !function.entrypoint) + self.functions.get(name).cloned().filter(|function| !function.entrypoint) // TODO: Store this information in a separate set for efficiency } fn inline_call( @@ -410,36 +410,32 @@ impl<'i, 'd> Inliner<'i, 'd> { None => (&[][..], None), }; - let lowered_result = (|| -> Result<(), CompilerError> { - for statement in callee_body { - lowered.extend(self.lower_statement(statement, &mut local_scope, visited_functions)?); - } + for statement in callee_body { + lowered.extend(self.lower_statement(statement, &mut local_scope, visited_functions)?); + } - if let (Some(bindings), Some(return_exprs)) = (bindings, return_exprs) { - for (binding, expr) in bindings.iter().zip(return_exprs.iter()) { - let (prelude, renamed_expr) = self.lower_expr(expr, &local_scope, visited_functions)?; - lowered.extend(prelude); - self.push_lowered_statement( - &mut lowered, - Statement::VariableDefinition { - type_ref: binding.type_ref.clone(), - modifiers: Vec::new(), - name: binding.name.clone(), - expr: Some(renamed_expr), - span, - type_span: binding.type_span, - modifier_spans: Vec::new(), - name_span: binding.name_span, - }, - ); - } + if let (Some(bindings), Some(return_exprs)) = (bindings, return_exprs) { + for (binding, expr) in bindings.iter().zip(return_exprs.iter()) { + let (prelude, renamed_expr) = self.lower_expr(expr, &local_scope, visited_functions)?; + lowered.extend(prelude); + self.push_lowered_statement( + &mut lowered, + Statement::VariableDefinition { + type_ref: binding.type_ref.clone(), + modifiers: Vec::new(), + name: binding.name.clone(), + expr: Some(renamed_expr), + span, + type_span: binding.type_span, + modifier_spans: Vec::new(), + name_span: binding.name_span, + }, + ); } + } - Ok(()) - })(); let body_end_statement_index = self.debug_recorder.current_source_statement_index(); visited_functions.remove(&function.name); - lowered_result?; self.debug_recorder.finish_inline_source_call(body_end_statement_index); Ok(lowered) } From 1298a205af875f6fee912d80283720080086281d Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Thu, 7 May 2026 10:32:41 +0300 Subject: [PATCH 2/2] Add ternary --- silverscript-lang/src/ast/mod.rs | 27 +++++- .../src/compiler/static_check.rs | 71 +++++++++++++- silverscript-lang/src/silverscript.pest | 3 +- silverscript-lang/tests/compiler_tests.rs | 95 ++++++++++++++++++- 4 files changed, 190 insertions(+), 6 deletions(-) diff --git a/silverscript-lang/src/ast/mod.rs b/silverscript-lang/src/ast/mod.rs index 76d7b27..0d5b087 100644 --- a/silverscript-lang/src/ast/mod.rs +++ b/silverscript-lang/src/ast/mod.rs @@ -989,9 +989,12 @@ fn format_expr_with_prec(expr: &Expr<'_>, parent_prec: u8, right_child: bool) -> binary_op_str(*op), format_expr_with_prec(right, binary_precedence(*op), true) ), - ExprKind::IfElse { condition, then_expr, else_expr } => { - format!("ifElse({}, {}, {})", format_expr(condition), format_expr(then_expr), format_expr(else_expr)) - } + ExprKind::IfElse { condition, then_expr, else_expr } => format!( + "{} ? {} : {}", + format_expr_with_prec(condition, binary_precedence(BinaryOp::Or), false), + format_expr(then_expr), + format_expr_with_prec(else_expr, expr_precedence(&expr.kind), false) + ), ExprKind::Nullary(op) => nullary_op_str(*op).to_string(), ExprKind::Introspection { kind, index, .. } => { format!("{}[{}]{}", introspection_root(*kind), format_expr(index), introspection_field(*kind)) @@ -1796,6 +1799,7 @@ fn parse_state_typed_binding<'i>(pair: Pair<'i, Rule>) -> Result(pair: Pair<'i, Rule>) -> Result, CompilerError> { match pair.as_rule() { Rule::expression => parse_expression(single_inner(pair)?), + Rule::conditional => parse_conditional(pair), Rule::logical_or => parse_infix(pair, parse_expression, map_logical_or), Rule::logical_and => parse_infix(pair, parse_expression, map_logical_and), Rule::bit_or => parse_infix(pair, parse_expression, map_bit_or), @@ -1839,6 +1843,23 @@ fn parse_expression<'i>(pair: Pair<'i, Rule>) -> Result, CompilerError> } } +fn parse_conditional<'i>(pair: Pair<'i, Rule>) -> Result, CompilerError> { + let span = Span::from(pair.as_span()); + let mut inner = pair.into_inner(); + let condition_pair = inner.next().ok_or_else(|| CompilerError::Unsupported("missing conditional condition".to_string()))?; + let condition = parse_expression(condition_pair)?; + let Some(then_pair) = inner.next() else { + return Ok(condition); + }; + let then_expr = parse_expression(then_pair)?; + let else_pair = inner.next().ok_or_else(|| CompilerError::Unsupported("missing conditional else expression".to_string()))?; + let else_expr = parse_expression(else_pair)?; + Ok(Expr::new( + ExprKind::IfElse { condition: Box::new(condition), then_expr: Box::new(then_expr), else_expr: Box::new(else_expr) }, + span, + )) +} + fn parse_unary<'i>(pair: Pair<'i, Rule>) -> Result, CompilerError> { let span = Span::from(pair.as_span()); let mut inner = pair.into_inner(); diff --git a/silverscript-lang/src/compiler/static_check.rs b/silverscript-lang/src/compiler/static_check.rs index b6896a5..0c4b2e1 100644 --- a/silverscript-lang/src/compiler/static_check.rs +++ b/silverscript-lang/src/compiler/static_check.rs @@ -811,7 +811,35 @@ fn validate_expr_semantics<'i>( ExprKind::IfElse { condition, then_expr, else_expr } => { validate_expr_semantics(condition, env, prefer_env_for_comparison, types, structs, functions, contract_fields)?; validate_expr_semantics(then_expr, env, prefer_env_for_comparison, types, structs, functions, contract_fields)?; - validate_expr_semantics(else_expr, env, prefer_env_for_comparison, types, structs, functions, contract_fields) + validate_expr_semantics(else_expr, env, prefer_env_for_comparison, types, structs, functions, contract_fields)?; + let then_type = infer_expr_type_ref_for_comparison_ref( + then_expr, + env, + prefer_env_for_comparison, + types, + structs, + functions, + contract_fields, + ); + let else_type = infer_expr_type_ref_for_comparison_ref( + else_expr, + env, + prefer_env_for_comparison, + types, + structs, + functions, + contract_fields, + ); + if let (Some(then_type), Some(else_type)) = (then_type, else_type) + && then_type != else_type + { + return Err(CompilerError::Unsupported(format!( + "ternary branch type mismatch: then expression is {}, else expression is {}", + type_name_from_ref(&then_type), + type_name_from_ref(&else_type) + ))); + } + Ok(()) } ExprKind::Array(values) => { for value in values { @@ -946,6 +974,27 @@ fn infer_expr_type_ref_for_comparison_ref<'i>( ExprKind::Append { source, .. } => { infer_expr_type_ref_for_comparison_ref(source, env, prefer_env_for_comparison, types, structs, functions, contract_fields) } + ExprKind::IfElse { then_expr, else_expr, .. } => { + let then_type = infer_expr_type_ref_for_comparison_ref( + then_expr, + env, + prefer_env_for_comparison, + types, + structs, + functions, + contract_fields, + )?; + let else_type = infer_expr_type_ref_for_comparison_ref( + else_expr, + env, + prefer_env_for_comparison, + types, + structs, + functions, + contract_fields, + )?; + (then_type == else_type).then_some(then_type) + } ExprKind::Call { name, .. } if name == "readInputState" && !contract_fields.is_empty() => { Some(TypeRef { base: TypeBase::Custom("State".to_string()), array_dims: Vec::new() }) } @@ -1176,6 +1225,17 @@ fn validate_expr_assignable_to_type<'i>( return Ok(()); } + if let ExprKind::IfElse { .. } = &expr.kind + && let Some(actual_type) = + infer_expr_type_ref_for_comparison_ref(expr, &HashMap::new(), &HashSet::new(), types, structs, functions, contract_fields) + { + return if is_type_assignable_ref(&actual_type, type_ref, constants) { + Ok(()) + } else { + Err(CompilerError::Unsupported("type mismatch".to_string())) + }; + } + if type_ref.is_array() && let ExprKind::Array(values) = &expr.kind { @@ -1451,6 +1511,15 @@ pub(super) fn expr_matches_return_type_ref<'i>( } match &expr.kind { + ExprKind::IfElse { .. } => { + if let Ok(actual_type_name) = + super::debug_value_types::infer_debug_expr_value_type(expr, &HashMap::new(), types, &mut HashSet::new()) + && let Ok(actual_type) = parse_type_ref(&actual_type_name) + { + return is_type_assignable_ref(&actual_type, type_ref, constants); + } + false + } ExprKind::Array(values) => { expr_matches_declared_type_ref(expr, type_ref, structs) || (is_array_type_ref(type_ref) && array_literal_matches_type_ref(values, type_ref)) diff --git a/silverscript-lang/src/silverscript.pest b/silverscript-lang/src/silverscript.pest index e05725d..f3eb6af 100644 --- a/silverscript-lang/src/silverscript.pest +++ b/silverscript-lang/src/silverscript.pest @@ -68,8 +68,9 @@ console_parameter_list = { "(" ~ (expression ~ ("," ~ expression)* ~ ","?)? ~ ") function_call = { Identifier ~ expression_list } expression_list = { "(" ~ (expression ~ ("," ~ expression)* ~ ","?)? ~ ")" } -expression = _{ logical_or } +expression = _{ conditional } +conditional = { logical_or ~ ("?" ~ expression ~ ":" ~ conditional)? } logical_or = { logical_and ~ (logical_or_op ~ logical_and)* } logical_and = { bit_or ~ (logical_and_op ~ bit_or)* } bit_or = { bit_xor ~ (bit_or_op ~ bit_xor)* } diff --git a/silverscript-lang/tests/compiler_tests.rs b/silverscript-lang/tests/compiler_tests.rs index 9ef940d..dd559b6 100644 --- a/silverscript-lang/tests/compiler_tests.rs +++ b/silverscript-lang/tests/compiler_tests.rs @@ -16,7 +16,7 @@ use kaspa_txscript::{ EngineCtx, EngineFlags, SeqCommitAccessor, TxScriptEngine, parse_script, pay_to_address_script, pay_to_script_hash_script, pay_to_script_hash_signature_script, script_to_str, serialize_i64, }; -use silverscript_lang::ast::{Expr, ExprKind, format_contract_ast, parse_contract_ast}; +use silverscript_lang::ast::{Expr, ExprKind, Statement, format_contract_ast, parse_contract_ast}; use silverscript_lang::compiler::{ CompileOptions, CompiledContract, CovenantDeclCallOptions, FunctionAbiEntry, FunctionInputAbi, compile_contract, compile_contract_ast, function_branch_index, struct_object, @@ -8590,6 +8590,99 @@ fn function_param_shadows_constructor_constant_with_same_name() { assert!(result_wrong.is_err(), "require(3==4) should fail, proving the param value matters"); } +#[test] +fn ternary_syntax_lowers_to_if_else_expr() { + let source = r#" + contract TernaryAst() { + entrypoint function main(bool flag) { + int value = flag ? 7 : 11; + require(value > 0); + } + } + "#; + + let contract = parse_contract_ast(source).expect("contract parses"); + let Statement::VariableDefinition { expr: Some(expr), .. } = &contract.functions[0].body[0] else { + panic!("expected variable definition"); + }; + assert!(matches!(&expr.kind, ExprKind::IfElse { .. }), "ternary should lower to ExprKind::IfElse: {expr:?}"); +} + +#[test] +fn ternary_expression_executes_selected_branch() { + let source = r#" + contract TernaryRuntime() { + entrypoint function main(int selector, int expected) { + int value = selector > 0 ? 7 : 11; + require(value == expected); + } + } + "#; + + let compiled = compile_contract(source, &[], CompileOptions::default()).expect("ternary contract should compile"); + + let sigscript_then = compiled.build_sig_script("main", vec![Expr::int(1), Expr::int(7)]).expect("sigscript builds"); + let result_then = run_script_with_sigscript(compiled.script.clone(), sigscript_then); + assert!(result_then.is_ok(), "then branch should execute successfully: {}", result_then.unwrap_err()); + + let sigscript_else = compiled.build_sig_script("main", vec![Expr::int(0), Expr::int(11)]).expect("sigscript builds"); + let result_else = run_script_with_sigscript(compiled.script.clone(), sigscript_else); + assert!(result_else.is_ok(), "else branch should execute successfully: {}", result_else.unwrap_err()); + + let sigscript_wrong = compiled.build_sig_script("main", vec![Expr::int(0), Expr::int(7)]).expect("sigscript builds"); + let result_wrong = run_script_with_sigscript(compiled.script, sigscript_wrong); + assert!(result_wrong.is_err(), "else branch should not produce the then value"); +} + +#[test] +fn ternary_expression_rejects_mismatched_branch_types() { + let source = r#" + contract TernaryTypes() { + entrypoint function main(bool flag) { + int value = flag ? 7 : false; + require(value > 0); + } + } + "#; + + let err = compile_contract(source, &[], CompileOptions::default()).expect_err("mismatched ternary branches should fail"); + assert!(err.to_string().contains("ternary branch type mismatch"), "unexpected error: {err}"); +} + +#[test] +fn ternary_expression_rejects_branch_type_that_does_not_match_declared_variable_type() { + let source = r#" + contract TernaryDeclaredType() { + entrypoint function main(bool cond, bool y, bool z) { + int x = cond ? y : z; + require(x > 0); + } + } + "#; + + let err = compile_contract(source, &[], CompileOptions::default()).expect_err("ternary result type should match declared type"); + assert!(err.to_string().contains("variable 'x' expects int"), "unexpected error: {err}"); +} + +#[test] +fn ternary_expression_rejects_branch_type_that_does_not_match_function_return_type() { + let source = r#" + contract TernaryReturnType() { + function choose(bool cond, bool y, bool z): int { + return cond ? y : z; + } + + entrypoint function main(bool cond, bool y, bool z) { + int value = choose(cond, y, z); + require(value > 0); + } + } + "#; + + let err = compile_contract(source, &[], CompileOptions::default()).expect_err("ternary result type should match return type"); + assert!(err.to_string().contains("return value expects int"), "unexpected error: {err}"); +} + #[test] fn nested_inline_calls_with_args_compile_and_execute() { // Nested inline calls must propagate synthetic __arg_ bindings so that