Skip to main content

miden_assembly_syntax/ast/item/
index.rs

1/// Represents the index of an item within its respective storage vector in some module
2#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3#[repr(transparent)]
4pub struct ItemIndex(u16);
5
6#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
7#[error("invalid item index: too many items")]
8pub struct ItemIndexError {
9    attempted: usize,
10}
11
12impl ItemIndex {
13    pub const MAX_ITEMS: usize = u16::MAX as usize + 1;
14
15    pub fn new(id: usize) -> Self {
16        Self::try_new(id).expect("invalid item index: too many items")
17    }
18
19    pub fn try_new(id: usize) -> Result<Self, ItemIndexError> {
20        let raw = id.try_into().map_err(|_| ItemIndexError { attempted: id })?;
21        Ok(Self(raw))
22    }
23
24    #[inline(always)]
25    pub const fn const_new(id: u16) -> Self {
26        Self(id)
27    }
28
29    /// Get the raw index value of this item
30    #[inline(always)]
31    pub const fn as_usize(&self) -> usize {
32        self.0 as usize
33    }
34}
35
36impl core::fmt::Display for ItemIndex {
37    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
38        write!(f, "{}", self.as_usize())
39    }
40}
41
42/// Uniquely identifies an item in a set of [crate::ast::Module]
43///
44/// A [GlobalItemIndex] is assigned to an item when it is added to the linker's module
45/// graph. The index uniquely identifies that item in the graph, and provides a unique,
46/// copyable, machine-word sized handle that can be trivially stored, passed around, and later used
47/// to perform constant-complexity operations against that item.
48///
49/// <div class="warning">
50/// As a result of this being just an index into a specific instance of a [super::ModuleGraph],
51/// it does not provide any guarantees about uniqueness or stability when the same module is stored
52/// in multiple graphs - each graph may assign it a unique index. You must ensure that you do not
53/// store these indices and attempt to use them with just any module graph - it is only valid with
54/// the one it was assigned from.
55/// </div>
56///
57/// In addition to the linker's module graph, these indices are also used with an instance of a
58/// `MastForestBuilder`. This is because the linker and `MastForestBuilder` instances
59/// are paired, i.e. the linker stores the syntax trees and call graph analysis for a program, while
60/// the `MastForestBuilder` caches the compiled procedures for the same program, as derived from
61/// the corresponding graph.
62///
63/// This is intended for use when we are doing global inter-procedural analysis on a (possibly
64/// growable) set of modules. It is expected that the index of a module in the set, as well as the
65/// index of an item in a module, are stable once allocated in the graph. The set of modules and
66/// items can grow, as long as growing the set only allocates unused identifiers.
67///
68/// NOTE: This struct is the same size as a u32
69#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
70pub struct GlobalItemIndex {
71    /// The index of the containing module in the global set of modules
72    pub module: ModuleIndex,
73    /// The local index of the procedure in the module
74    pub index: ItemIndex,
75}
76
77impl core::fmt::Display for GlobalItemIndex {
78    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
79        write!(f, "{}:{}", self.module, self.index)
80    }
81}
82
83/// A strongly-typed index into a set of [crate::ast::Module]
84#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
85#[repr(transparent)]
86pub struct ModuleIndex(u16);
87
88impl ModuleIndex {
89    pub const MAX_MODULES: usize = u16::MAX as usize + 1;
90
91    pub fn new(index: usize) -> Self {
92        Self::try_new(index).expect("invalid module index: too many modules")
93    }
94
95    pub fn try_new(index: usize) -> Result<Self, core::num::TryFromIntError> {
96        index.try_into().map(Self)
97    }
98
99    pub const fn const_new(index: u16) -> Self {
100        Self(index)
101    }
102
103    #[inline(always)]
104    pub const fn as_usize(&self) -> usize {
105        self.0 as usize
106    }
107}
108
109impl core::ops::Add<ItemIndex> for ModuleIndex {
110    type Output = GlobalItemIndex;
111
112    fn add(self, rhs: ItemIndex) -> Self::Output {
113        GlobalItemIndex { module: self, index: rhs }
114    }
115}
116
117impl core::fmt::Display for ModuleIndex {
118    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
119        write!(f, "{}", self.as_usize())
120    }
121}
122
123#[cfg(test)]
124mod regression_tests {
125    use std::string::String;
126
127    use super::ModuleIndex;
128    use crate::sema::{LimitKind, SemanticAnalysisError, SyntaxError};
129
130    #[test]
131    fn module_index_try_new_checks_capacity() {
132        let last = ModuleIndex::try_new(ModuleIndex::MAX_MODULES - 1)
133            .expect("the final representable module index must be accepted");
134
135        assert_eq!(last.as_usize(), ModuleIndex::MAX_MODULES - 1);
136        assert!(ModuleIndex::try_new(ModuleIndex::MAX_MODULES).is_err());
137    }
138
139    fn huge_library_masm() -> String {
140        let num_consts = usize::from(u16::MAX) + 2;
141        let mut masm = String::with_capacity(num_consts * 16);
142        masm.push_str("namespace ::m::huge\n\n");
143        for i in 0..num_consts {
144            masm.push_str("const A");
145            masm.push_str(&format!("{i}"));
146            masm.push_str(" = 0\n");
147        }
148        masm
149    }
150
151    #[test]
152    fn too_many_items_in_module_is_rejected_during_analysis() {
153        let test = crate::testing::SyntaxTestContext::new();
154        let err = test
155            .parse_module(&huge_library_masm())
156            .expect_err("expected oversized module to be rejected during analysis");
157
158        let syntax_error = err.downcast_ref::<SyntaxError>().expect("expected SyntaxError report");
159        assert!(
160            syntax_error.errors.iter().any(|error| {
161                matches!(error, SemanticAnalysisError::LimitExceeded { kind: LimitKind::Items, .. })
162            }),
163            "expected item-limit error, got {:?}",
164            syntax_error.errors
165        );
166    }
167}