diff --git a/.gitignore b/.gitignore index 4433574..23159c3 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,7 @@ __pycache__ # IDEs .vscode + +# Rust / Cargo +**/rust/Cargo.lock +**/rust/target/ diff --git a/compiler/back_end/experimental/rust/emboss_codegen_rust.py b/compiler/back_end/experimental/rust/emboss_codegen_rust.py index 89a4ec7..a8d95fd 100644 --- a/compiler/back_end/experimental/rust/emboss_codegen_rust.py +++ b/compiler/back_end/experimental/rust/emboss_codegen_rust.py @@ -272,7 +272,10 @@ def _generate_expression( expr, ir, module, generated_fields, templates, self_ref="self" ): if ir_util.is_constant(expr): - return str(ir_util.constant_value(expr)) + val = ir_util.constant_value(expr) + if isinstance(val, bool): + return "true" if val else "false" + return str(val) if expr.has_field("boolean_constant"): return "true" if expr.boolean_constant.value else "false" @@ -315,9 +318,12 @@ def _generate_expression( templates.expr_multiplication, left=args[0], right=args[1] ) if func == ir_data.FunctionMapping.MAXIMUM.value: - return code_template.format_template( - templates.expr_maximum, left=args[0], right=args[1] - ) + res = args[0] + for a in args[1:]: + res = code_template.format_template( + templates.expr_maximum, left=res, right=a + ) + return res if func == ir_data.FunctionMapping.CHOICE.value: return code_template.format_template( templates.expr_choice, @@ -907,11 +913,25 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) - generated_fields.add(field_name) + # Extract the frontend-synthesized $size_in_bytes expression tree to compute the + # dynamic layout completeness of the structure in CheckComplete::check_complete(). + size_in_bytes = "0" + for field in type_ir.structure.field: + if field.name.name.text == "$size_in_bytes": + size_in_bytes = ( + _generate_expression( + field.read_transform, ir, module, generated_fields, templates + ) + or "0" + ) + break + main_struct_def = code_template.format_template( templates.struct_view, struct_name=struct_name, field_accessors="".join(field_accessors), mut_field_accessors="".join(mut_field_accessors), + size_in_bytes=size_in_bytes, ) return "".join(generated_nested_types) + main_struct_def diff --git a/compiler/back_end/experimental/rust/generated_code_templates b/compiler/back_end/experimental/rust/generated_code_templates index 3346a2c..78885d1 100644 --- a/compiler/back_end/experimental/rust/generated_code_templates +++ b/compiler/back_end/experimental/rust/generated_code_templates @@ -16,38 +16,93 @@ // Generated by the Emboss compiler. DO NOT EDIT! #![allow(dead_code, unused_imports, unused_variables, non_camel_case_types, unused_parens)] -use emboss_runtime::prelude::*; -pub use emboss_runtime::{Error, Storage, MutStorage, TryRead, TryWrite, UInt, Int, VirtualField}; +pub use emboss_runtime::{Error, Int, MutStorage, Storage, TryRead, TryWrite, UInt, VirtualField}; ${imports_list} ${struct_definitions} // ** struct_view ** // -pub struct ${struct_name} { +pub struct ${struct_name} { storage: S, + _marker: core::marker::PhantomData, } -impl ${struct_name} { +impl ${struct_name} { pub fn new(storage: S) -> Self { - Self { storage } + Self { + storage, + _marker: core::marker::PhantomData, + } + } +} + +impl ${struct_name} { + /// Constructs a view in a specific typestate. + /// + /// # Safety + /// + /// The caller must ensure that `storage` satisfies the layout invariants for `ST`. + pub unsafe fn new_in_state(storage: S) -> Self { + Self { + storage, + _marker: core::marker::PhantomData, + } } ${field_accessors} } -pub struct ${struct_name}Mut { +pub struct ${struct_name}Mut { storage: S, + _marker: core::marker::PhantomData, } -impl ${struct_name}Mut { +impl ${struct_name}Mut { pub fn new(storage: S) -> Self { - Self { storage } + Self { + storage, + _marker: core::marker::PhantomData, + } + } +} + +impl ${struct_name}Mut { + /// Constructs a mutable view in a specific typestate. + /// + /// # Safety + /// + /// The caller must ensure that `storage` satisfies the layout invariants for `ST`. + pub unsafe fn new_in_state(storage: S) -> Self { + Self { + storage, + _marker: core::marker::PhantomData, + } } ${mut_field_accessors} } +impl emboss_runtime::CheckComplete for ${struct_name} { + type Completed = ${struct_name}; + fn check_complete(self) -> core::result::Result { + let size = (${size_in_bytes}) as usize; + let _ = self.storage.slice(0, size)?; + // SAFETY: self.storage has been validated to contain the complete byte length required by the struct layout. + core::result::Result::Ok(unsafe { ${struct_name}::new_in_state(self.storage) }) + } +} + +impl emboss_runtime::CheckComplete for ${struct_name}Mut { + type Completed = ${struct_name}Mut; + fn check_complete(mut self) -> core::result::Result { + let size = (${size_in_bytes}) as usize; + let _ = self.storage.slice_mut(0, size)?; + // SAFETY: self.storage has been validated to contain the complete byte length required by the struct layout. + core::result::Result::Ok(unsafe { ${struct_name}Mut::new_in_state(self.storage) }) + } +} + // ** bit_external_field_accessor ** // - pub fn ${field_name}(&self) -> emboss_runtime::Bit${type_name}<${bits}, ${bit_offset}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>> { + pub fn ${field_name}(&self) -> emboss_runtime::Bit${type_name}<${bits}, ${bit_offset}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>, ST> { let offset_res = (|| -> core::result::Result<(usize, usize), emboss_runtime::Error> { let offset = 0; let len = (${byte_length}) as usize; // Total struct size in bytes @@ -57,11 +112,11 @@ ${mut_field_accessors} core::result::Result::Ok((offset, len)) => self.storage.slice(offset, len), core::result::Result::Err(e) => core::result::Result::Err(e), }; - emboss_runtime::Bit${type_name}::<${bits}, ${bit_offset}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>>::new(storage) + emboss_runtime::Bit${type_name}::<${bits}, ${bit_offset}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>, ST>::new(storage) } // ** bit_external_mut_field_accessor ** // - pub fn ${field_name}(&mut self) -> emboss_runtime::Bit${type_name}<${bits}, ${bit_offset}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>> { + pub fn ${field_name}(&mut self) -> emboss_runtime::Bit${type_name}<${bits}, ${bit_offset}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>, ST> { let offset_res = (|| -> core::result::Result<(usize, usize), emboss_runtime::Error> { let offset = 0; let len = (${byte_length}) as usize; @@ -71,11 +126,11 @@ ${mut_field_accessors} core::result::Result::Ok((offset, len)) => self.storage.slice_mut(offset, len), core::result::Result::Err(e) => core::result::Result::Err(e), }; - emboss_runtime::Bit${type_name}::<${bits}, ${bit_offset}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>>::new(storage) + emboss_runtime::Bit${type_name}::<${bits}, ${bit_offset}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>, ST>::new(storage) } // ** external_field_accessor ** // - pub fn ${field_name}(&self) -> ${type_name}<${bits}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>> { + pub fn ${field_name}(&self) -> emboss_runtime::${type_name}<${bits}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>, ST> { let offset_res = (|| -> core::result::Result<(usize, usize), emboss_runtime::Error> { let offset = (${byte_offset}) as usize; let len = (${byte_length}) as usize; @@ -85,11 +140,11 @@ ${mut_field_accessors} core::result::Result::Ok((offset, len)) => self.storage.slice(offset, len), core::result::Result::Err(e) => core::result::Result::Err(e), }; - ${type_name}::<${bits}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>>::new(storage) + emboss_runtime::${type_name}::<${bits}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>, ST>::new(storage) } // ** external_mut_field_accessor ** // - pub fn ${field_name}(&mut self) -> ${type_name}<${bits}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>> { + pub fn ${field_name}(&mut self) -> emboss_runtime::${type_name}<${bits}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>, ST> { let offset_res = (|| -> core::result::Result<(usize, usize), emboss_runtime::Error> { let offset = (${byte_offset}) as usize; let len = (${byte_length}) as usize; @@ -99,11 +154,11 @@ ${mut_field_accessors} core::result::Result::Ok((offset, len)) => self.storage.slice_mut(offset, len), core::result::Result::Err(e) => core::result::Result::Err(e), }; - ${type_name}::<${bits}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>>::new(storage) + emboss_runtime::${type_name}::<${bits}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>, ST>::new(storage) } // ** struct_field_accessor ** // - pub fn ${field_name}(&self) -> ${type_name}, emboss_runtime::Error>> { + pub fn ${field_name}(&self) -> ${type_name}, emboss_runtime::Error>, emboss_runtime::UncheckedState> { let offset_res = (|| -> core::result::Result<(usize, usize), emboss_runtime::Error> { let offset = (${byte_offset}) as usize; let len = (${byte_length}) as usize; @@ -113,11 +168,12 @@ ${mut_field_accessors} core::result::Result::Ok((offset, len)) => self.storage.slice(offset, len), core::result::Result::Err(e) => core::result::Result::Err(e), }; - ${type_name}::new(storage) + // SAFETY: The inner struct view is created in UncheckedState, which requires explicit verification before access. + unsafe { ${type_name}::, emboss_runtime::Error>, emboss_runtime::UncheckedState>::new_in_state(storage) } } // ** struct_mut_field_accessor ** // - pub fn ${field_name}(&mut self) -> ${type_name}Mut, emboss_runtime::Error>> { + pub fn ${field_name}(&mut self) -> ${type_name}Mut, emboss_runtime::Error>, emboss_runtime::UncheckedState> { let offset_res = (|| -> core::result::Result<(usize, usize), emboss_runtime::Error> { let offset = (${byte_offset}) as usize; let len = (${byte_length}) as usize; @@ -127,7 +183,8 @@ ${mut_field_accessors} core::result::Result::Ok((offset, len)) => self.storage.slice_mut(offset, len), core::result::Result::Err(e) => core::result::Result::Err(e), }; - ${type_name}Mut::new(storage) + // SAFETY: The inner struct view is created in UncheckedState, which requires explicit verification before access. + unsafe { ${type_name}Mut::, emboss_runtime::Error>, emboss_runtime::UncheckedState>::new_in_state(storage) } } // ** enum_definition ** // @@ -195,7 +252,7 @@ core::cmp::min((${left}) as i64, (${right}) as i64) // ** enum_field_accessor ** // - pub fn ${field_name}(&self) -> emboss_runtime::EnumView<${enum_name}, emboss_runtime::UInt<${bits}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>>> { + pub fn ${field_name}(&self) -> emboss_runtime::EnumView<${enum_name}, emboss_runtime::UInt<${bits}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>, ST>> { let offset_res = (|| -> core::result::Result<(usize, usize), emboss_runtime::Error> { let offset = (${byte_offset}) as usize; let len = (${byte_length}) as usize; @@ -209,7 +266,7 @@ core::cmp::min((${left}) as i64, (${right}) as i64) } // ** enum_mut_field_accessor ** // - pub fn ${field_name}(&mut self) -> emboss_runtime::EnumViewMut<${enum_name}, emboss_runtime::UInt<${bits}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>>> { + pub fn ${field_name}(&mut self) -> emboss_runtime::EnumViewMut<${enum_name}, emboss_runtime::UInt<${bits}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>, ST>> { let offset_res = (|| -> core::result::Result<(usize, usize), emboss_runtime::Error> { let offset = (${byte_offset}) as usize; let len = (${byte_length}) as usize; @@ -371,7 +428,7 @@ emboss_runtime::EnumViewMut::new(emboss_runtime::UInt::new(storage)) } // ** bit_enum_field_accessor ** // - pub fn ${field_name}(&self) -> emboss_runtime::EnumView<${enum_name}, emboss_runtime::BitUInt<${bits}, ${bit_offset}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>>> { + pub fn ${field_name}(&self) -> emboss_runtime::EnumView<${enum_name}, emboss_runtime::BitUInt<${bits}, ${bit_offset}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>, ST>> { let offset_res = (|| -> core::result::Result<(usize, usize), emboss_runtime::Error> { let offset = 0; let len = (${byte_length}) as usize; @@ -385,7 +442,7 @@ emboss_runtime::EnumViewMut::new(emboss_runtime::UInt::new(storage)) } // ** bit_enum_mut_field_accessor ** // - pub fn ${field_name}(&mut self) -> emboss_runtime::EnumViewMut<${enum_name}, emboss_runtime::BitUInt<${bits}, ${bit_offset}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>>> { + pub fn ${field_name}(&mut self) -> emboss_runtime::EnumViewMut<${enum_name}, emboss_runtime::BitUInt<${bits}, ${bit_offset}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>, ST>> { let offset_res = (|| -> core::result::Result<(usize, usize), emboss_runtime::Error> { let offset = 0; let len = (${byte_length}) as usize; @@ -397,4 +454,3 @@ emboss_runtime::EnumViewMut::new(emboss_runtime::UInt::new(storage)) }; emboss_runtime::EnumViewMut::new(emboss_runtime::BitUInt::new(storage)) } - diff --git a/runtime/experimental/rust/BUILD b/runtime/experimental/rust/BUILD index 6dbb3c9..89691a2 100644 --- a/runtime/experimental/rust/BUILD +++ b/runtime/experimental/rust/BUILD @@ -24,5 +24,6 @@ rust_library( srcs = [ "src/lib.rs", "src/prelude.rs", + "src/typestate.rs", ], ) diff --git a/runtime/experimental/rust/src/lib.rs b/runtime/experimental/rust/src/lib.rs index d293365..37150dc 100644 --- a/runtime/experimental/rust/src/lib.rs +++ b/runtime/experimental/rust/src/lib.rs @@ -13,6 +13,11 @@ // limitations under the License. pub mod prelude; +pub mod typestate; +pub use crate::typestate::{ + CheckComplete, CompleteState, InfallibleRead, InfallibleWrite, IsComplete, State, + UncheckedState, +}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { @@ -42,10 +47,10 @@ pub trait MutStorage: Storage { } impl<'a, T: ?Sized + AsRef<[u8]>> Storage for &'a mut T { - type Sliced<'b> = Result<&'b [u8], Error> where Self: 'b; + type Sliced<'b> = &'b [u8] where Self: 'b; fn slice(&self, offset: usize, length: usize) -> Result, Error> { let bytes = self.as_ref(); - Ok(bytes.get(offset..offset + length).ok_or(Error::OutOfBounds)) + bytes.get(offset..offset + length).ok_or(Error::OutOfBounds) } fn try_read_byte(&self, offset: usize) -> Result { let bytes = self.as_ref(); @@ -54,10 +59,10 @@ impl<'a, T: ?Sized + AsRef<[u8]>> Storage for &'a mut T { } impl<'a, T: ?Sized + AsRef<[u8]>> Storage for &'a T { - type Sliced<'b> = Result<&'b [u8], Error> where Self: 'b; + type Sliced<'b> = &'b [u8] where Self: 'b; fn slice(&self, offset: usize, length: usize) -> Result, Error> { let bytes = (*self).as_ref(); - Ok(bytes.get(offset..offset + length).ok_or(Error::OutOfBounds)) + bytes.get(offset..offset + length).ok_or(Error::OutOfBounds) } fn try_read_byte(&self, offset: usize) -> Result { let bytes = self.as_ref(); @@ -66,10 +71,10 @@ impl<'a, T: ?Sized + AsRef<[u8]>> Storage for &'a T { } impl<'a, T: ?Sized + AsMut<[u8]> + AsRef<[u8]>> MutStorage for &'a mut T { - type SlicedMut<'b> = Result<&'b mut [u8], Error> where Self: 'b; + type SlicedMut<'b> = &'b mut [u8] where Self: 'b; fn slice_mut(&mut self, offset: usize, length: usize) -> Result, Error> { let bytes = self.as_mut(); - Ok(bytes.get_mut(offset..offset + length).ok_or(Error::OutOfBounds)) + bytes.get_mut(offset..offset + length).ok_or(Error::OutOfBounds) } fn try_write_byte(&mut self, offset: usize, val: u8) -> Result<(), Error> { let bytes = self.as_mut(); @@ -211,12 +216,12 @@ impl_smallest_uint!( u64, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64 ); -pub struct UInt { +pub struct UInt { storage: S, - _marker: core::marker::PhantomData, + _marker: core::marker::PhantomData<(E, ST)>, } -impl UInt { +impl UInt { pub fn new(storage: S) -> Self { Self { storage, @@ -225,7 +230,7 @@ impl UInt { } } -impl UInt +impl UInt where SizeSelector: SmallestUInt, as SmallestUInt>::T: DecodeFromStorage, @@ -239,7 +244,7 @@ where } } -impl UInt +impl UInt where SizeSelector: SmallestUInt, as SmallestUInt>::T: EncodeToStorage, @@ -250,6 +255,35 @@ where } } +impl InfallibleRead + for UInt +where + SizeSelector: SmallestUInt, + as SmallestUInt>::T: DecodeFromStorage, +{ + type ReadValue = as SmallestUInt>::T; + fn read(&self) -> as SmallestUInt>::T { + self.try_read().expect("infallible read in complete state") + } +} + +impl InfallibleWrite + for UInt +where + SizeSelector: SmallestUInt, + as SmallestUInt>::T: EncodeToStorage, +{ + type WriteValue = as SmallestUInt>::T; + type Output = UInt; + fn write(mut self, val: as SmallestUInt>::T) -> Self::Output { + self.try_write(val).expect("infallible write in complete state"); + UInt { + storage: self.storage, + _marker: core::marker::PhantomData, + } + } +} + pub trait SmallestInt { type T; type U; @@ -295,12 +329,12 @@ impl_smallest_int!( i64, u64, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64 ); -pub struct Int { +pub struct Int { storage: S, - _marker: core::marker::PhantomData, + _marker: core::marker::PhantomData<(E, ST)>, } -impl Int { +impl Int { pub fn new(storage: S) -> Self { Self { storage, @@ -309,7 +343,7 @@ impl Int { } } -impl Int +impl Int where SizeSelector: SmallestUInt + SmallestInt as SmallestUInt>::T>, as SmallestUInt>::T: DecodeFromStorage, @@ -322,7 +356,7 @@ where } } -impl Int +impl Int where SizeSelector: SmallestUInt + SmallestInt as SmallestUInt>::T>, as SmallestUInt>::T: EncodeToStorage, @@ -334,6 +368,35 @@ where } } +impl InfallibleRead + for Int +where + SizeSelector: SmallestUInt + SmallestInt as SmallestUInt>::T>, + as SmallestUInt>::T: DecodeFromStorage, +{ + type ReadValue = as SmallestInt>::T; + fn read(&self) -> as SmallestInt>::T { + self.try_read().expect("infallible read in complete state") + } +} + +impl InfallibleWrite + for Int +where + SizeSelector: SmallestUInt + SmallestInt as SmallestUInt>::T>, + as SmallestUInt>::T: EncodeToStorage, +{ + type WriteValue = as SmallestInt>::T; + type Output = Int; + fn write(mut self, val: as SmallestInt>::T) -> Self::Output { + self.try_write(val).expect("infallible write in complete state"); + Int { + storage: self.storage, + _marker: core::marker::PhantomData, + } + } +} + pub trait TryFromRaw { fn try_from_raw(val: T) -> Result> where @@ -378,7 +441,7 @@ pub trait TryWrite { fn try_write(&mut self, val: Self::WriteValue) -> Result<(), Error>; } -impl TryRead for UInt +impl TryRead for UInt where SizeSelector: SmallestUInt, as SmallestUInt>::T: DecodeFromStorage, @@ -389,7 +452,7 @@ where } } -impl TryWrite for UInt +impl TryWrite for UInt where SizeSelector: SmallestUInt, as SmallestUInt>::T: EncodeToStorage, @@ -400,7 +463,7 @@ where } } -impl TryRead for Int +impl TryRead for Int where SizeSelector: SmallestUInt + SmallestInt as SmallestUInt>::T>, as SmallestUInt>::T: DecodeFromStorage, @@ -411,7 +474,7 @@ where } } -impl TryWrite for Int +impl TryWrite for Int where SizeSelector: SmallestUInt + SmallestInt as SmallestUInt>::T>, as SmallestUInt>::T: EncodeToStorage, @@ -433,6 +496,17 @@ where } } +impl InfallibleRead for EnumView +where + Inner: InfallibleRead, + T: TryFromRaw, +{ + type ReadValue = Result>; + fn read(&self) -> Self::ReadValue { + T::try_from_raw(self.inner.read()) + } +} + impl EnumViewMut where Inner: TryWrite, @@ -443,6 +517,21 @@ where } } +impl InfallibleWrite for EnumViewMut +where + Inner: InfallibleWrite, + Inner::WriteValue: From, +{ + type WriteValue = T; + type Output = EnumViewMut; + fn write(self, val: T) -> Self::Output { + EnumViewMut { + inner: self.inner.write(Inner::WriteValue::from(val)), + _phantom: core::marker::PhantomData, + } + } +} + impl EnumViewMut where Inner: TryRead, @@ -454,12 +543,23 @@ where } } -pub struct BitUInt { +impl InfallibleRead for EnumViewMut +where + Inner: InfallibleRead, + T: TryFromRaw, +{ + type ReadValue = Result>; + fn read(&self) -> Self::ReadValue { + T::try_from_raw(self.inner.read()) + } +} + +pub struct BitUInt { storage: S, - _marker: core::marker::PhantomData, + _marker: core::marker::PhantomData<(E, ST)>, } -impl BitUInt { +impl BitUInt { pub fn new(storage: S) -> Self { Self { storage, @@ -468,7 +568,7 @@ impl BitUI } } -impl BitUInt +impl BitUInt where SizeSelector: SmallestUInt, { @@ -497,12 +597,22 @@ where } } -pub struct BitInt { +impl InfallibleRead for BitUInt +where + SizeSelector: SmallestUInt, +{ + type ReadValue = as SmallestUInt>::T; + fn read(&self) -> as SmallestUInt>::T { + self.try_read().expect("infallible read in complete state") + } +} + +pub struct BitInt { storage: S, - _marker: core::marker::PhantomData, + _marker: core::marker::PhantomData<(E, ST)>, } -impl BitInt { +impl BitInt { pub fn new(storage: S) -> Self { Self { storage, @@ -511,7 +621,7 @@ impl BitIn } } -impl BitInt +impl BitInt where SizeSelector: SmallestUInt + SmallestInt as SmallestUInt>::T>, { @@ -522,7 +632,17 @@ where } } -impl TryRead for BitUInt +impl InfallibleRead for BitInt +where + SizeSelector: SmallestUInt + SmallestInt as SmallestUInt>::T>, +{ + type ReadValue = as SmallestInt>::T; + fn read(&self) -> as SmallestInt>::T { + self.try_read().expect("infallible read in complete state") + } +} + +impl TryRead for BitUInt where SizeSelector: SmallestUInt, { @@ -532,7 +652,7 @@ where } } -impl TryRead for BitInt +impl TryRead for BitInt where SizeSelector: SmallestUInt + SmallestInt as SmallestUInt>::T>, { @@ -557,6 +677,10 @@ impl VirtualField { pub fn try_read(&self) -> core::result::Result { self.value } + + pub fn read(&self) -> T { + self.try_read().expect("infallible virtual field read in minimally complete state") + } } impl TryRead for VirtualField { @@ -566,3 +690,10 @@ impl TryRead for VirtualField { } } +impl InfallibleRead for VirtualField { + type ReadValue = T; + fn read(&self) -> T { + self.try_read().expect("infallible virtual field read in complete state") + } +} + diff --git a/runtime/experimental/rust/src/typestate.rs b/runtime/experimental/rust/src/typestate.rs new file mode 100644 index 0000000..36d6461 --- /dev/null +++ b/runtime/experimental/rust/src/typestate.rs @@ -0,0 +1,106 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Compile-time typestate markers and traits for Emboss views and writers. + +/// Unchecked view or field state. Requires runtime-checked `try_` accessors. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] +pub struct UncheckedState; + +/// Complete view state. All fields in the current layout are infallible. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] +pub struct CompleteState; + +/// Typestate transition trait for layout and validation mutations. +pub trait State { + type OnLayoutMutation: State; + type OnValidMutation: State; +} + +impl State for UncheckedState { + type OnLayoutMutation = UncheckedState; + type OnValidMutation = UncheckedState; +} + +impl State for CompleteState { + type OnLayoutMutation = UncheckedState; + type OnValidMutation = CompleteState; +} + +/// Marker trait for states that guarantee full layout completeness. +pub trait IsComplete: State {} +impl IsComplete for CompleteState {} + +/// Infallible read operation for views in a verified typestate (`IsComplete`). +pub trait InfallibleRead { + type ReadValue; + fn read(&self) -> Self::ReadValue; +} + +/// Infallible write operation for views in a verified typestate (`IsComplete`), consuming self and transitioning to the mutated state. +pub trait InfallibleWrite { + type WriteValue; + type Output; + fn write(self, val: Self::WriteValue) -> Self::Output; +} + +/// Trait for verifying dynamic layout completeness at runtime, upgrading the typestate to `CompleteState`. +pub trait CheckComplete { + type Completed; + fn check_complete(self) -> Result; +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{LittleEndian, Storage, UInt}; + + const TEST_VALUE: u32 = 0x12345678; + + fn assert_state() {} + fn assert_complete() {} + #[test] + fn test_state_marker_traits() { + assert_state::(); + assert_state::(); + + assert_complete::(); + } + + #[test] + fn test_on_layout_mutation_transitions() { + fn assert_is_unchecked(_view: &UInt<32, LittleEndian, S, UncheckedState>) {} + + let mut buf = [0u8; 4]; + let complete_view = + UInt::<32, LittleEndian, &mut [u8; 4], CompleteState>::new(&mut buf); + assert_eq!(complete_view.read(), 0); + + // Performing a write consumes the CompleteState view and degrades its state to UncheckedState. + let mutated_view = complete_view.write(TEST_VALUE); + assert_is_unchecked(&mutated_view); + + // The degraded view in UncheckedState must use checked reading. + assert_eq!(mutated_view.try_read().expect("valid read"), TEST_VALUE); + } + + #[test] + fn test_infallible_uint_read_write() { + let mut buf = [0u8; 4]; + let uint_view = UInt::<32, LittleEndian, &mut [u8; 4], CompleteState>::new(&mut buf); + let _ = uint_view.write(TEST_VALUE); + let read_view = UInt::<32, LittleEndian, &[u8; 4], CompleteState>::new(&buf); + assert_eq!(read_view.read(), TEST_VALUE); + } +}