resolve_lang/error.rs
1//! Errors that stop a resolution run, and the budgets that bound one.
2
3use core::fmt;
4
5use hir_lang::UnitId;
6
7/// Which budget a run exhausted.
8///
9/// # Examples
10///
11/// ```
12/// use resolve_lang::Limit;
13///
14/// assert_eq!(Limit::GlobBindings.name(), "glob bindings");
15/// ```
16#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
17#[non_exhaustive]
18pub enum Limit {
19 /// Names copied into scopes by glob imports (each glob edge times each
20 /// name it carries).
21 GlobBindings,
22 /// Steps spent walking inheritance chains for `self::`, `parent::`, and
23 /// `Class::member` lookups, and access checks over class hierarchies.
24 MemberSteps,
25}
26
27impl Limit {
28 /// A short name, for messages.
29 ///
30 /// # Examples
31 ///
32 /// ```
33 /// use resolve_lang::Limit;
34 ///
35 /// assert_eq!(Limit::MemberSteps.name(), "member lookup steps");
36 /// ```
37 #[must_use]
38 pub const fn name(self) -> &'static str {
39 match self {
40 Self::GlobBindings => "glob bindings",
41 Self::MemberSteps => "member lookup steps",
42 }
43 }
44}
45
46/// Why a resolution run stopped without a result.
47///
48/// Name errors in the program are not `ResolveError`s: they are
49/// [`Diagnostic`](crate::Diagnostic)s in a successful result. A run fails only
50/// when the input is unusable as a whole or exceeds a [`Budget`].
51///
52/// # Examples
53///
54/// ```
55/// use hir_lang::UnitId;
56/// use resolve_lang::ResolveError;
57///
58/// let e = ResolveError::DuplicateUnit { unit: UnitId::new(3) };
59/// assert_eq!(e.to_string(), "two units of the program have the id 3");
60/// ```
61#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
62#[non_exhaustive]
63pub enum ResolveError {
64 /// The program reached a budget limit. Raise the limit with
65 /// [`Budget`] if the input is trusted, or reject the input.
66 BudgetExceeded {
67 /// The limit reached.
68 limit: Limit,
69 },
70 /// Two units share a [`UnitId`]; `DefId`s could not tell them apart.
71 /// Give every unit of a program a distinct id.
72 DuplicateUnit {
73 /// The shared id.
74 unit: UnitId,
75 },
76}
77
78impl fmt::Display for ResolveError {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 match self {
81 Self::BudgetExceeded { limit } => {
82 write!(f, "resolution exceeded its budget of {}", limit.name())
83 }
84 Self::DuplicateUnit { unit } => {
85 write!(f, "two units of the program have the id {}", unit.as_u32())
86 }
87 }
88 }
89}
90
91impl core::error::Error for ResolveError {}
92
93/// Explicit limits on the work untrusted input can cause.
94///
95/// The resolver is linear in the size of the program except in three places,
96/// each bounded here: glob imports (each glob edge carries every name of its
97/// target), inheritance walks for member lookups, and did-you-mean
98/// comparisons. Exceeding the first two fails the run with
99/// [`ResolveError::BudgetExceeded`]; exhausting the suggestion budget only
100/// stops suggestions.
101///
102/// # Examples
103///
104/// ```
105/// use resolve_lang::Budget;
106///
107/// let b = Budget::default().with_glob_bindings(1_000);
108/// assert_eq!(b.glob_bindings(), 1_000);
109/// assert!(Budget::unlimited().suggestion_cells() > b.suggestion_cells());
110/// ```
111#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
112pub struct Budget {
113 glob_bindings: u64,
114 member_steps: u64,
115 suggestion_cells: u64,
116}
117
118impl Default for Budget {
119 /// 16 million glob bindings, 16 million member steps, 32 million
120 /// suggestion cells: far beyond real programs, small enough to bound a
121 /// hostile one to well under a second of work each.
122 fn default() -> Self {
123 Self {
124 glob_bindings: 16_000_000,
125 member_steps: 16_000_000,
126 suggestion_cells: 32_000_000,
127 }
128 }
129}
130
131impl Budget {
132 /// No limits (for trusted input).
133 ///
134 /// # Examples
135 ///
136 /// ```
137 /// use resolve_lang::Budget;
138 ///
139 /// assert_eq!(Budget::unlimited().member_steps(), u64::MAX);
140 /// ```
141 #[must_use]
142 pub const fn unlimited() -> Self {
143 Self {
144 glob_bindings: u64::MAX,
145 member_steps: u64::MAX,
146 suggestion_cells: u64::MAX,
147 }
148 }
149
150 /// Sets the glob-binding limit.
151 ///
152 /// # Examples
153 ///
154 /// ```
155 /// use resolve_lang::Budget;
156 ///
157 /// assert_eq!(Budget::default().with_glob_bindings(5).glob_bindings(), 5);
158 /// ```
159 #[must_use]
160 pub const fn with_glob_bindings(mut self, n: u64) -> Self {
161 self.glob_bindings = n;
162 self
163 }
164
165 /// Sets the member-lookup step limit.
166 ///
167 /// # Examples
168 ///
169 /// ```
170 /// use resolve_lang::Budget;
171 ///
172 /// assert_eq!(Budget::default().with_member_steps(5).member_steps(), 5);
173 /// ```
174 #[must_use]
175 pub const fn with_member_steps(mut self, n: u64) -> Self {
176 self.member_steps = n;
177 self
178 }
179
180 /// Sets the did-you-mean budget, in edit-distance table cells.
181 ///
182 /// # Examples
183 ///
184 /// ```
185 /// use resolve_lang::Budget;
186 ///
187 /// assert_eq!(Budget::default().with_suggestion_cells(0).suggestion_cells(), 0);
188 /// ```
189 #[must_use]
190 pub const fn with_suggestion_cells(mut self, n: u64) -> Self {
191 self.suggestion_cells = n;
192 self
193 }
194
195 /// The glob-binding limit.
196 ///
197 /// # Examples
198 ///
199 /// ```
200 /// use resolve_lang::Budget;
201 ///
202 /// assert_eq!(Budget::default().glob_bindings(), 16_000_000);
203 /// ```
204 #[must_use]
205 pub const fn glob_bindings(&self) -> u64 {
206 self.glob_bindings
207 }
208
209 /// The member-lookup step limit.
210 ///
211 /// # Examples
212 ///
213 /// ```
214 /// use resolve_lang::Budget;
215 ///
216 /// assert_eq!(Budget::default().member_steps(), 16_000_000);
217 /// ```
218 #[must_use]
219 pub const fn member_steps(&self) -> u64 {
220 self.member_steps
221 }
222
223 /// The did-you-mean budget.
224 ///
225 /// # Examples
226 ///
227 /// ```
228 /// use resolve_lang::Budget;
229 ///
230 /// assert_eq!(Budget::default().suggestion_cells(), 32_000_000);
231 /// ```
232 #[must_use]
233 pub const fn suggestion_cells(&self) -> u64 {
234 self.suggestion_cells
235 }
236}