Skip to main content

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}