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);
87impl ModuleIndex {
88    pub fn new(index: usize) -> Self {
89        Self(index.try_into().expect("invalid module index: too many modules"))
90    }
91
92    pub const fn const_new(index: u16) -> Self {
93        Self(index)
94    }
95
96    #[inline(always)]
97    pub const fn as_usize(&self) -> usize {
98        self.0 as usize
99    }
100}
101
102impl core::ops::Add<ItemIndex> for ModuleIndex {
103    type Output = GlobalItemIndex;
104
105    fn add(self, rhs: ItemIndex) -> Self::Output {
106        GlobalItemIndex { module: self, index: rhs }
107    }
108}
109
110impl core::fmt::Display for ModuleIndex {
111    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
112        write!(f, "{}", self.as_usize())
113    }
114}
115
116#[cfg(test)]
117mod regression_tests {
118    use std::string::String;
119
120    use crate::sema::{LimitKind, SemanticAnalysisError, SyntaxError};
121
122    fn huge_library_masm() -> String {
123        let num_consts = usize::from(u16::MAX) + 2;
124        let mut masm = String::with_capacity(num_consts * 16);
125        masm.push_str("namespace ::m::huge\n\n");
126        for i in 0..num_consts {
127            masm.push_str("const A");
128            masm.push_str(&format!("{i}"));
129            masm.push_str(" = 0\n");
130        }
131        masm
132    }
133
134    #[test]
135    fn too_many_items_in_module_is_rejected_during_analysis() {
136        let test = crate::testing::SyntaxTestContext::new();
137        let err = test
138            .parse_module(&huge_library_masm())
139            .expect_err("expected oversized module to be rejected during analysis");
140
141        let syntax_error = err.downcast_ref::<SyntaxError>().expect("expected SyntaxError report");
142        assert!(
143            syntax_error.errors.iter().any(|error| {
144                matches!(error, SemanticAnalysisError::LimitExceeded { kind: LimitKind::Items, .. })
145            }),
146            "expected item-limit error, got {:?}",
147            syntax_error.errors
148        );
149    }
150}