1use std::fmt;
4
5pub const TRUNCATED: &str = "[diagnostic output truncated]";
6
7#[derive(Clone, Copy, Debug)]
8pub struct RenderLimits {
9 pub bytes: usize,
10 pub steps: usize,
11 pub type_depth: usize,
12}
13
14impl Default for RenderLimits {
15 fn default() -> Self {
16 Self {
17 bytes: 64 * 1024,
18 steps: 65_536,
19 type_depth: 512,
20 }
21 }
22}
23
24impl RenderLimits {
25 pub fn collection() -> Self {
26 Self {
27 bytes: 1024 * 1024,
28 ..Self::default()
29 }
30 }
31}
32
33#[derive(Debug, PartialEq, Eq)]
34pub struct RenderedText {
35 pub text: String,
36 pub truncated: bool,
37 steps: usize,
38}
39
40#[derive(Debug)]
41pub enum RenderError {
42 InvalidMetadata(&'static str),
43 Source(crate::source::SourceError),
44 Allocation,
45 Formatting,
46 Truncated,
48}
49
50impl fmt::Display for RenderError {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 match self {
53 Self::InvalidMetadata(context) => write!(f, "invalid diagnostic metadata: {context}"),
54 Self::Source(error) => write!(
55 f,
56 "invalid diagnostic metadata; source context unavailable: {error}"
57 ),
58 Self::Allocation => f.write_str("diagnostic allocation failed"),
59 Self::Formatting => f.write_str("diagnostic formatting failed"),
60 Self::Truncated => f.write_str(TRUNCATED),
61 }
62 }
63}
64impl std::error::Error for RenderError {}
65impl From<crate::source::SourceError> for RenderError {
66 fn from(error: crate::source::SourceError) -> Self {
67 Self::Source(error)
68 }
69}
70impl From<RenderError> for crate::compiler_error::CompilerFailure {
71 fn from(error: RenderError) -> Self {
72 Self::Internal {
73 stage: crate::compiler_error::CompilerStage::Infer,
74 span: None,
75 message: error.to_string(),
76 }
77 }
78}
79
80pub(crate) struct Writer {
81 text: String,
82 limits: RenderLimits,
83 steps: usize,
84 failure: Option<RenderError>,
85 abbreviated: bool,
86}
87
88impl RenderedText {
89 pub(crate) fn steps(&self) -> usize {
90 self.steps
91 }
92}
93
94impl Writer {
95 pub(crate) fn remaining_steps(&self) -> usize {
96 self.limits.steps.saturating_sub(self.steps)
97 }
98
99 pub(crate) fn render(
100 limits: RenderLimits,
101 render: impl FnOnce(&mut Self) -> Result<(), RenderError>,
102 ) -> Result<RenderedText, RenderError> {
103 if limits.bytes < TRUNCATED.len() {
104 return Err(RenderError::InvalidMetadata(
105 "render byte limit cannot hold truncation marker",
106 ));
107 }
108 let mut out = Self {
109 text: String::new(),
110 limits,
111 steps: 0,
112 failure: None,
113 abbreviated: false,
114 };
115 let result = render(&mut out);
116 match out.failure.take().map_or(result, Err) {
117 Ok(()) => Ok(RenderedText {
118 text: out.text,
119 truncated: out.abbreviated,
120 steps: out.steps,
121 }),
122 Err(RenderError::Truncated) => {
123 let budget = limits.bytes.saturating_sub(TRUNCATED.len());
124 let mut end = out.text.len().min(budget);
125 while !out.text.is_char_boundary(end) {
126 end = end.saturating_sub(1);
127 }
128 out.text.truncate(end);
129 out.text
131 .try_reserve(TRUNCATED.len())
132 .map_err(|_| RenderError::Allocation)?;
133 out.text.push_str(TRUNCATED);
134 Ok(RenderedText {
135 text: out.text,
136 truncated: true,
137 steps: out.steps,
138 })
139 }
140 Err(error) => Err(error),
141 }
142 }
143
144 pub(crate) fn append(&mut self, rendered: RenderedText) -> Result<(), RenderError> {
145 self.abbreviated |= rendered.truncated;
146 self.steps = self
147 .steps
148 .checked_add(rendered.steps)
149 .ok_or(RenderError::Truncated)?;
150 self.push(&rendered.text)
151 }
152
153 pub(crate) fn child_limits(&self) -> RenderLimits {
154 RenderLimits {
155 bytes: self
156 .limits
157 .bytes
158 .saturating_sub(self.text.len())
159 .max(TRUNCATED.len())
160 .min(RenderLimits::default().bytes),
161 steps: self
162 .limits
163 .steps
164 .saturating_sub(self.steps)
165 .saturating_sub(1),
166 type_depth: self.limits.type_depth,
167 }
168 }
169
170 pub(crate) fn step(&mut self) -> Result<(), RenderError> {
171 if self.steps >= self.limits.steps {
172 return Err(RenderError::Truncated);
173 }
174 self.steps += 1;
175 Ok(())
176 }
177
178 pub(crate) fn depth(&self, depth: usize) -> Result<(), RenderError> {
179 if depth > self.limits.type_depth {
180 Err(RenderError::Truncated)
181 } else {
182 Ok(())
183 }
184 }
185
186 pub(crate) fn push(&mut self, text: &str) -> Result<(), RenderError> {
187 self.step()?;
188 let remaining = self.limits.bytes.saturating_sub(self.text.len());
189 let mut end = remaining.min(text.len());
190 while !text.is_char_boundary(end) {
191 end = end.saturating_sub(1);
192 }
193 let prefix = text.get(..end).ok_or(RenderError::Formatting)?;
194 self.text
195 .try_reserve(prefix.len())
196 .map_err(|_| RenderError::Allocation)?;
197 self.text.push_str(prefix);
198 if end < text.len() {
199 return Err(RenderError::Truncated);
200 }
201 Ok(())
202 }
203
204 pub(crate) fn character(&mut self, ch: char) -> Result<(), RenderError> {
205 self.push(ch.encode_utf8(&mut [0; 4]))
206 }
207
208 pub(crate) fn format(&mut self, args: fmt::Arguments<'_>) -> Result<(), RenderError> {
209 match fmt::write(self, args) {
210 Ok(()) => Ok(()),
211 Err(_) => Err(self.failure.take().unwrap_or(RenderError::Formatting)),
212 }
213 }
214
215 pub(crate) fn repeat(&mut self, ch: char, count: usize) -> Result<(), RenderError> {
216 for _ in 0..count {
217 self.character(ch)?;
218 }
219 Ok(())
220 }
221}
222
223impl fmt::Write for Writer {
224 fn write_str(&mut self, text: &str) -> fmt::Result {
225 self.push(text).map_err(|error| {
226 self.failure = Some(error);
227 fmt::Error
228 })
229 }
230}
231
232pub fn failure_text(primary: &str, error: &RenderError) -> String {
234 match Writer::render(RenderLimits::default(), |out| {
235 out.format(format_args!("internal reporting failure: {error}\n"))?;
237 out.push(primary)
238 }) {
239 Ok(rendered) => rendered.text,
240 Err(_) => "internal reporting failure: diagnostic output unavailable".into(),
241 }
242}
243
244pub fn bounded_text(text: &str, bytes: usize) -> Result<RenderedText, RenderError> {
246 Writer::render(
247 RenderLimits {
248 bytes,
249 ..RenderLimits::default()
250 },
251 |out| out.push(text),
252 )
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258
259 #[test]
260 fn byte_boundaries_are_exact_and_unicode_truncation_is_valid() {
261 for extra in [0, 1] {
262 let input = "é".repeat(32 + extra);
263 let rendered = bounded_text(&input, 64).unwrap();
264 assert!(rendered.text.len() <= 64);
265 assert_eq!(rendered.truncated, extra != 0);
266 if extra == 0 {
267 assert_eq!(rendered.text, input);
268 } else {
269 assert!(rendered.text.ends_with(TRUNCATED));
270 }
271 }
272 }
273
274 #[test]
275 fn work_limit_and_allocation_failure_do_not_publish_partial_success() {
276 let limits = RenderLimits {
277 steps: 2,
278 ..Default::default()
279 };
280 let exact = Writer::render(limits, |out| {
281 out.push("first")?;
282 out.push("second")
283 })
284 .unwrap();
285 assert!(!exact.truncated);
286 let exceeded = Writer::render(limits, |out| {
287 out.push("first")?;
288 out.push("second")?;
289 out.push("third")
290 })
291 .unwrap();
292 assert!(exceeded.truncated);
293 let failed = Writer::render(limits, |out| {
294 out.push("partial")?;
295 Err(RenderError::Allocation)
296 });
297 assert!(matches!(failed, Err(RenderError::Allocation)));
298 assert_eq!(
299 Writer::render(limits, |out| out.push("healthy"))
300 .unwrap()
301 .text,
302 "healthy"
303 );
304 }
305
306 #[test]
307 fn a_broken_display_is_a_typed_failure() {
308 struct Broken;
309 impl fmt::Display for Broken {
310 fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
311 Err(fmt::Error)
312 }
313 }
314 let result = Writer::render(RenderLimits::default(), |out| {
315 out.format(format_args!("{Broken}"))
316 });
317 assert!(matches!(result, Err(RenderError::Formatting)));
318 }
319}