diff --git a/silverscript-lang/src/compiler/compile/helpers.rs b/silverscript-lang/src/compiler/compile/helpers.rs index bd47903a..914d2aaf 100644 --- a/silverscript-lang/src/compiler/compile/helpers.rs +++ b/silverscript-lang/src/compiler/compile/helpers.rs @@ -16,27 +16,29 @@ pub(super) fn compile_contract_fields<'i>( let mut field_types = HashMap::new(); let mut builder = script_builder(); let stack_bindings = StackBindings::default(); + let mut current_constants = base_constants.clone(); for field in fields { let mut resolve_visiting = HashSet::new(); - let resolved = resolve_constant_references(field.expr.clone(), base_constants, &mut resolve_visiting)?; + let resolved = resolve_constant_references(field.expr.clone(), ¤t_constants, &mut resolve_visiting)?; - if fixed_type_size(&field.type_ref, base_constants)?.is_some() { - let encoded = encode_value_with_constant_size(&resolved, &field.type_ref, base_constants)?; + if fixed_type_size(&field.type_ref, ¤t_constants)?.is_some() { + let encoded = encode_value_with_constant_size(&resolved, &field.type_ref, ¤t_constants)?; builder.add_data_with_push_opcode(&encoded)?; } else { let env = ExprEnv { - constants: base_constants, + constants: ¤t_constants, stack_bindings: &stack_bindings, types: &field_types, bytecode_size, - contract_constants: base_constants, + contract_constants: ¤t_constants, }; let mut emitter = ScriptEmitter::new(&mut builder, 0); compile_expr(&resolved, Some(&field.type_ref), &env, &mut emitter)?; } - field_values.insert(field.name.clone(), resolved); + field_values.insert(field.name.clone(), resolved.clone()); + current_constants.insert(field.name.clone(), resolved); field_types.insert(field.name.clone(), field.type_ref.clone()); } diff --git a/silverscript-lang/tests/compiler_tests.rs b/silverscript-lang/tests/compiler_tests.rs index c98474e5..daf04f0b 100644 --- a/silverscript-lang/tests/compiler_tests.rs +++ b/silverscript-lang/tests/compiler_tests.rs @@ -17822,3 +17822,74 @@ fn signature_script_builder_requires_explicit_byte_values() { encode_entry_sig_script(&compiled, "array", &[ArtifactValue::Bytes(vec![1])]).expect("explicit byte elements build"); run_bytecode_with_sigscript(bytecode(&compiled), sigscript).expect("the explicit byte-array invocation executes"); } + +#[test] +fn contract_field_initializer_accepts_a_reference_to_an_earlier_field() { + let source = r#" + contract PriorFieldReference(int initial) { + int amount = initial; + int mirrored = amount; + + entry main() { + require(mirrored == amount); + } + } + "#; + + let compiled = compile_contract(source, &[42.into()], CompileOptions::default()); + assert!(compiled.is_ok(), "contract with prior field reference should compile successfully: {:?}", compiled.err()); +} + +#[test] +fn contract_field_initializer_rejects_forward_and_undefined_references() { + let source_forward = r#" + contract ForwardFieldReference(int initial) { + int mirrored = amount; + int amount = initial; + + entry main() { + require(mirrored == amount); + } + } + "#; + let err_forward = compile_contract(source_forward, &[42.into()], CompileOptions::default()).expect_err("forward reference should fail"); + assert!( + err_forward.to_string().contains("UndefinedIdentifier") || err_forward.to_string().contains("undefined"), + "unexpected error: {err_forward}" + ); + + let source_undefined = r#" + contract UndefinedFieldReference(int initial) { + int amount = undefined_var; + + entry main() { + require(amount == initial); + } + } + "#; + let err_undefined = compile_contract(source_undefined, &[42.into()], CompileOptions::default()).expect_err("undefined reference should fail"); + assert!( + err_undefined.to_string().contains("UndefinedIdentifier") || err_undefined.to_string().contains("undefined"), + "unexpected error: {err_undefined}" + ); +} + +#[test] +fn contract_field_initializer_rejects_cyclic_references() { + let source_cyclic = r#" + contract CyclicFieldReference(int initial) { + int amount = mirrored; + int mirrored = amount; + + entry main() { + require(amount == initial); + } + } + "#; + let err_cyclic = compile_contract(source_cyclic, &[42.into()], CompileOptions::default()).expect_err("cyclic reference should fail"); + assert!( + err_cyclic.to_string().contains("UndefinedIdentifier") || err_cyclic.to_string().contains("undefined"), + "unexpected error: {err_cyclic}" + ); +} +