Skip to content

Commit e6d844d

Browse files
committed
Allow updating syntax tests with ranges
1 parent 6eb50aa commit e6d844d

File tree

2 files changed

+69
-36
lines changed

2 files changed

+69
-36
lines changed

internal/compiler/diagnostics.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,18 +274,36 @@ impl Diagnostic {
274274
}
275275
}
276276

277+
/// The exclusive end of this diagnostic.
278+
/// Returns a tuple with the line (starting at 1) and column number (starting at 1)
279+
///
280+
/// Can also return (0, 0) if the span is invalid
277281
pub fn end_line_column(&self) -> (usize, usize) {
278282
if !self.span.span.is_valid() {
279283
return (0, 0);
280284
}
281-
let offset = self.span.span.offset + self.span.span.length;
285+
// The end_line_column is exclusive.
286+
// Even if the span indicates a length of 0, the diagnostic should always
287+
// return an end_line_column that is at least one offset further, so that at least one
288+
// character is covered by the diagnostic.
289+
let offset = self.span.span.offset + self.span.span.length.max(1);
282290

283291
match &self.span.source_file {
284292
None => (0, 0),
285293
Some(sl) => sl.line_column(offset),
286294
}
287295
}
288296

297+
/// Return the length of this diagnostic in characters.
298+
pub fn length(&self) -> usize {
299+
// Like in end_line_column, the length should always be 1, even if the span indicates a
300+
// length of 0, as otherwise there is no character to display the diagnostic on.
301+
self.span.span.length.max(1)
302+
}
303+
304+
// NOTE: The return-type differs from the Spanned trait.
305+
// Because this is public API (Diagnostic is re-exported by the Interpreter), we cannot change
306+
// this.
289307
/// return the path of the source file where this error is attached
290308
pub fn source_file(&self) -> Option<&Path> {
291309
self.span.source_file().map(|sf| sf.path())
@@ -418,8 +436,10 @@ impl BuildDiagnostics {
418436
});
419437
let file_span = file.span;
420438
let s = codemap_diagnostic::SpanLabel {
421-
span: file_span
422-
.subspan(d.span.span.offset as u64, (d.span.span.offset + d.span.span.length) as u64),
439+
span: file_span.subspan(
440+
d.span.span.offset as u64,
441+
(d.span.span.offset + d.span.span.length) as u64,
442+
),
423443
style: codemap_diagnostic::SpanStyle::Primary,
424444
label: None,
425445
};

internal/compiler/tests/syntax_tests.rs

Lines changed: 46 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,6 @@ fn process_diagnostics(
212212
.filter_map(|(i, c)| if c == b'\n' { Some(i) } else { None })
213213
.collect::<Vec<usize>>();
214214

215-
let diag_copy = diags.clone();
216-
217215
let mut expected = extract_expected_diags(source);
218216
let captures: Vec<_> =
219217
expected.iter().map(|expected| &expected.comment_range).cloned().collect();
@@ -305,7 +303,7 @@ fn process_diagnostics(
305303

306304
if !success && update {
307305
let mut source = source.to_string();
308-
self::update(diag_copy, &mut source, lines, &captures);
306+
self::update(&diags, &mut source, lines, &captures);
309307
std::fs::write(path, source).unwrap();
310308
}
311309

@@ -314,7 +312,7 @@ fn process_diagnostics(
314312

315313
/// Rewrite the source to remove the old comments and add accurate error comments
316314
fn update(
317-
mut diags: Vec<&Diagnostic>,
315+
diags: &[&Diagnostic],
318316
source: &mut String,
319317
mut lines: Vec<usize>,
320318
to_remove: &[std::ops::Range<usize>],
@@ -328,42 +326,57 @@ fn update(
328326
}
329327
}
330328

331-
diags.sort_by_key(|d| {
332-
let (l, c) = d.line_column();
333-
(usize::MAX - l, c)
334-
});
335-
336-
let mut last_line = 0;
337-
let mut last_line_adjust = 0;
329+
let mut last_line_adjust = Vec::from_iter(std::iter::repeat_n(0, lines.len()));
338330

339331
for d in diags {
340-
let (l, c) = d.line_column();
341-
if c < 3 {
342-
panic!("Error message cannot be on the column < 3: {d:?}")
343-
}
332+
let mut insert_range_at = |range: &str, l, c: usize| {
333+
let column_adjust = if c < 3 { "<".repeat(3 - c) } else { "".to_string() };
334+
let byte_offset = lines[l - 1] + 1;
335+
let to_insert = format!(
336+
"//{indent}{range}{adjust}{column_adjust}{error_or_warning}{{{message}}}\n",
337+
indent = " ".repeat(c.max(3) - 3),
338+
adjust = "^".repeat(last_line_adjust[l - 1]),
339+
error_or_warning =
340+
if d.level() == DiagnosticLevel::Error { "error" } else { "warning" },
341+
message = d.message().replace('\n', "↵").replace(env!("CARGO_MANIFEST_DIR"), "📂")
342+
);
343+
if byte_offset > source.len() {
344+
source.push('\n');
345+
}
346+
source.insert_str(byte_offset, &to_insert);
347+
for line in (l - 1)..lines.len() {
348+
lines[line] += to_insert.len();
349+
}
350+
last_line_adjust[l - 1] += 1;
351+
};
344352

345-
if last_line == l {
346-
last_line_adjust += 1;
353+
let (line_start, column_start) = d.line_column();
354+
let (line_end, column_end) = d.end_line_column();
355+
356+
// The end column is exclusive, therefore use - 1 here
357+
let range = if d.length() <= 1 {
358+
// Single-character diagnostic, use "^" for the marker
359+
"^".to_owned()
347360
} else {
348-
last_line = l;
349-
last_line_adjust = 0;
350-
}
361+
let end = if line_start == line_end {
362+
// Same line, we can insert the closing "<"
363+
" ".repeat(column_end - column_start - 2) + "<"
364+
} else {
365+
// End is on a different line, we'll emit it later
366+
"".to_owned()
367+
};
368+
format!(">{end}")
369+
};
351370

352-
let byte_offset = lines[l - 1] + 1;
371+
insert_range_at(&range, line_start, column_start);
353372

354-
let to_insert = format!(
355-
"//{indent}^{adjust}{error_or_warning}{{{message}}}\n",
356-
indent = " ".repeat(c - 3),
357-
adjust = "^".repeat(last_line_adjust),
358-
error_or_warning =
359-
if d.level() == DiagnosticLevel::Error { "error" } else { "warning" },
360-
message = d.message().replace('\n', "↵").replace(env!("CARGO_MANIFEST_DIR"), "📂")
361-
);
362-
if byte_offset > source.len() {
363-
source.push('\n');
373+
// Insert the closing `<` at another line if necessary
374+
// Edge-case: If a single-character diagnostic is on a newline character (\n), its
375+
// end_line_column is technically on a new line, but the single ^ marker is enough, so no
376+
// closing character is needed.
377+
if line_start != line_end && d.length() > 1 {
378+
insert_range_at("<", line_end, column_end - 1);
364379
}
365-
source.insert_str(byte_offset, &to_insert);
366-
lines[l - 1] += to_insert.len();
367380
}
368381
}
369382

0 commit comments

Comments
 (0)