Skip to content

Commit 739f856

Browse files
committed
test type checking
1 parent 3cc9f84 commit 739f856

12 files changed

+177
-92
lines changed

examples/error-examples/thread_idx_offset.desc renamed to examples/error-examples/thread_idx_offset.desc.off

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ fn npu_vec_add<r: prv, n: nat>(
2121
) -[grid: npu.grid<X<64>, X<1024>>]-> () {
2222
sched block in grid {
2323
sched thread in block {
24-
let a_ref = &uniq (*a_array).to_view.grp::<1024>[[block]][[thread + offset]];
25-
*a_ref = *a_ref + (*b_array).to_view.grp::<1024>[[block]][[thread + offset]]
24+
let a = thread + offset;
25+
let b = thread + offset;
26+
let a_ref = &uniq (*a_array).to_view.grp::<1024>[[block]][[a]];
27+
*a_ref = *a_ref + (*b_array).to_view.grp::<1024>[[block]][[b]]
2628
}
2729
}
2830
}

src/error.rs

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -123,35 +123,22 @@ pub fn with_related(report: miette::Report, _related_text: &str) -> miette::Repo
123123
}
124124

125125
/// Enum representing different types of compilation errors
126-
#[derive(Error, Diagnostic, Debug, Clone)]
126+
#[derive(Error, Debug, Clone)]
127127
pub enum CompileError {
128128
#[error(transparent)]
129-
#[diagnostic(
130-
code(descend::file_io_error),
131-
help("Check that the file exists and you have permission to read it")
132-
)]
133129
FileIO(#[from] FileIOErrorData),
134130

135-
#[error(transparent)]
136-
#[diagnostic(
137-
code(descend::parse_error),
138-
help("Check the syntax of your Descend code")
139-
)]
140-
Parse(#[from] ParseErrorData),
131+
#[error("{0}")]
132+
Parse(ParseErrorData),
141133

142134
#[error(transparent)]
143-
#[diagnostic(
144-
code(descend::type_check_error),
145-
help("Check your type annotations and variable usage")
146-
)]
147135
TypeCheck(#[from] TypeCheckErrorData),
148136

149137
#[error(transparent)]
150-
#[diagnostic(
151-
code(descend::codegen_error),
152-
help("This is an internal compiler error. Please report it as a bug")
153-
)]
154138
Codegen(#[from] CodegenErrorData),
139+
140+
#[error(transparent)]
141+
MissingMain(#[from] MissingMainErrorData),
155142
}
156143

157144
/// Data for FileIO errors with miette diagnostic support
@@ -181,7 +168,7 @@ pub struct ParseErrorData {
181168
pub message: String,
182169

183170
#[label("Parse error location")]
184-
pub span: Option<SourceSpan>,
171+
pub span: SourceSpan,
185172
}
186173

187174
/// Data for TypeCheck errors with miette diagnostic support
@@ -221,15 +208,63 @@ pub struct CodegenErrorData {
221208
pub span: Option<SourceSpan>,
222209
}
223210

211+
/// Data for MissingMain errors with miette diagnostic support
212+
#[derive(Error, Diagnostic, Debug, Clone)]
213+
#[error("Missing main function")]
214+
#[diagnostic(
215+
code(descend::missing_main_error),
216+
help("A main function is required as the entry point of your program")
217+
)]
218+
pub struct MissingMainErrorData {
219+
pub message: String,
220+
pub help_text: Option<String>,
221+
}
222+
224223
impl CompileError {
225224
/// Attach source code to the error for beautiful display
226225
pub fn with_source_code(self, source: miette::NamedSource<String>) -> miette::Report {
227226
miette::Report::new(self).with_source_code(source)
228227
}
229228
}
230229

230+
impl Diagnostic for CompileError {
231+
fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
232+
match self {
233+
CompileError::Parse(_) => Some(Box::new("descend::parse_error")),
234+
CompileError::FileIO(_) => Some(Box::new("descend::file_io_error")),
235+
CompileError::TypeCheck(_) => Some(Box::new("descend::type_check_error")),
236+
CompileError::Codegen(_) => Some(Box::new("descend::codegen_error")),
237+
CompileError::MissingMain(_) => Some(Box::new("descend::missing_main_error")),
238+
}
239+
}
240+
241+
fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
242+
match self {
243+
CompileError::Parse(_) => Some(Box::new("Check the syntax of your Descend code")),
244+
CompileError::FileIO(_) => Some(Box::new("Check that the file exists and you have permission to read it")),
245+
CompileError::TypeCheck(_) => Some(Box::new("Check your type annotations and variable usage")),
246+
CompileError::Codegen(_) => Some(Box::new("This is an internal compiler error. Please report it as a bug")),
247+
CompileError::MissingMain(_) => Some(Box::new("A main function is required as the entry point of your program")),
248+
}
249+
}
250+
251+
fn labels<'a>(&'a self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + 'a>> {
252+
match self {
253+
CompileError::Parse(parse_err) => {
254+
let start: usize = parse_err.span.offset().into();
255+
let end = start + parse_err.span.len();
256+
Some(Box::new(std::iter::once(LabeledSpan::new_with_span(
257+
Some("Parse error location".to_string()),
258+
start..end,
259+
))))
260+
}
261+
_ => None,
262+
}
263+
}
264+
}
265+
231266
/// Convert ErrorKind to human-readable message
232-
fn error_kind_to_message(kind: std::io::ErrorKind) -> String {
267+
pub fn error_kind_to_message(kind: std::io::ErrorKind) -> String {
233268
match kind {
234269
std::io::ErrorKind::NotFound => "entity not found".to_string(),
235270
std::io::ErrorKind::PermissionDenied => "permission denied".to_string(),
@@ -239,17 +274,8 @@ fn error_kind_to_message(kind: std::io::ErrorKind) -> String {
239274
}
240275
}
241276

242-
/// Convert std::io::Error to CompileError for easy error handling
243-
impl From<std::io::Error> for CompileError {
244-
fn from(error: std::io::Error) -> Self {
245-
CompileError::FileIO(FileIOErrorData {
246-
file_path: "unknown file".to_string(),
247-
io_error_kind: error.kind(),
248-
error_message: error_kind_to_message(error.kind()),
249-
span: None,
250-
})
251-
}
252-
}
277+
// Note: std::io::Error should be converted through SourceCode::from_file
278+
// to preserve the file path information
253279

254280
/// Convert TyError to CompileError with span preservation
255281
impl From<crate::ty_check::error::TyError> for CompileError {
@@ -486,12 +512,9 @@ impl From<crate::ty_check::error::TyError> for CompileError {
486512
}
487513
}
488514
TyError::MissingMain => {
489-
CompileError::TypeCheck(TypeCheckErrorData {
515+
CompileError::MissingMain(MissingMainErrorData {
490516
message: "Missing main function".to_string(),
491-
span: None,
492-
secondary_spans: vec![],
493517
help_text: Some("A main function is required as the entry point of your program.".to_string()),
494-
related: None,
495518
})
496519
}
497520
TyError::NatEvalError(nat_err, span) => {

src/lib.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ pub mod ty_check;
1010

1111
pub fn compile(file_path: &str) -> Result<(String, String), CompileError> {
1212
let source = parser::SourceCode::from_file(file_path)?;
13-
let mut compil_unit = parser::parse(&source)?;
13+
compile_from_source(&source)
14+
}
15+
16+
pub fn compile_from_source(source: &parser::SourceCode) -> Result<(String, String), CompileError> {
17+
let mut compil_unit = parser::parse(source)?;
1418
ty_check::ty_check(&mut compil_unit)?;
1519

1620
let code_string = codegen::mlir::gen_checked(&compil_unit, false).map_err(|e| {
@@ -24,3 +28,26 @@ pub fn compile(file_path: &str) -> Result<(String, String), CompileError> {
2428

2529
Ok((code_string, ast_string))
2630
}
31+
32+
/// Compile a file and return a miette::Report for errors (with source code attached)
33+
pub fn compile_with_source(file_path: &str) -> Result<(String, String), miette::Report> {
34+
let source_code = std::fs::read_to_string(file_path).map_err(|e| {
35+
use crate::error::{CompileError, FileIOErrorData};
36+
CompileError::FileIO(FileIOErrorData {
37+
file_path: file_path.to_string(),
38+
io_error_kind: e.kind(),
39+
error_message: crate::error::error_kind_to_message(e.kind()),
40+
span: None,
41+
})
42+
.with_source_code(miette::NamedSource::new(file_path, String::new()))
43+
})?;
44+
45+
let named_source = miette::NamedSource::new(file_path, source_code.clone());
46+
let source = parser::SourceCode::from_file(file_path)
47+
.map_err(|e| e.with_source_code(named_source.clone()))?;
48+
49+
match compile_from_source(&source) {
50+
Ok(result) => Ok(result),
51+
Err(err) => Err(err.with_source_code(named_source)),
52+
}
53+
}

src/main.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use clap::Parser;
22
use descend;
3-
use descend::error::CompileError;
3+
use miette::IntoDiagnostic;
44
use std::fs;
55
use std::path::PathBuf;
66

@@ -20,7 +20,7 @@ struct Args {
2020
print_ast: bool,
2121
}
2222

23-
fn main() -> Result<(), CompileError> {
23+
fn main() -> miette::Result<()> {
2424
miette::set_panic_hook();
2525

2626
let args = Args::parse();
@@ -30,7 +30,7 @@ fn main() -> Result<(), CompileError> {
3030
let output_dir = &args.output_dir;
3131

3232
// Compile using Descend
33-
let (code_string, ast_string) = descend::compile(&input_path.to_string_lossy())?;
33+
let (code_string, ast_string) = descend::compile_with_source(&input_path.to_string_lossy())?;
3434

3535
// Generate output file path with appropriate extension
3636
let filename_stem = input_path.file_stem().unwrap_or_default().to_string_lossy();
@@ -40,11 +40,11 @@ fn main() -> Result<(), CompileError> {
4040
if print_ast {
4141
let ast_file = output_dir.join(format!("{}.ast", filename_stem));
4242
// Write result to file
43-
fs::write(&ast_file, ast_string)?;
43+
fs::write(&ast_file, ast_string).into_diagnostic()?;
4444
println!("AST file written successfully to: {}", ast_file.display());
4545
}
4646
// Write result to file
47-
fs::write(&code_file, code_string)?;
47+
fs::write(&code_file, code_string).into_diagnostic()?;
4848
println!("code file written successfully to: {}", code_file.display());
4949

5050
Ok(())

src/parser/mod.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,13 @@ pub use source::*;
1414
pub fn parse<'a>(source: &'a SourceCode<'a>) -> Result<CompilUnit<'a>, CompileError> {
1515
let parser = Parser::new(source);
1616
let mut items = parser.parse().map_err(|err| {
17+
let span = err.extract_span().unwrap_or_else(|| {
18+
// Fallback to a default span if extraction fails
19+
miette::SourceSpan::new(miette::SourceOffset::from(0), 1)
20+
});
1721
CompileError::Parse(ParseErrorData {
1822
message: format!("Parse error: {}", err),
19-
span: None, // TODO: Extract span from ParseError
23+
span,
2024
})
2125
})?;
2226
// TODO refactor to not require unnecessary copying out of items
@@ -311,6 +315,26 @@ pub mod error {
311315
ParseError { parser, err }
312316
}
313317

318+
/// Extract the source span from the parse error
319+
pub fn extract_span(&self) -> Option<miette::SourceSpan> {
320+
let line_num = u32::try_from(self.err.location.line).ok()?;
321+
let column_num = u32::try_from(self.err.location.column).ok()?;
322+
323+
// Convert from 1-based to 0-based line numbers
324+
let line_num = line_num - 1;
325+
// Convert from 1-based to 0-based column numbers
326+
let column_num = column_num - 1;
327+
328+
// Get the byte offset for the error location
329+
let start_offset = self.parser.source.get_offset(line_num, column_num);
330+
let end_offset = start_offset + 1; // Single character span
331+
332+
Some(miette::SourceSpan::new(
333+
miette::SourceOffset::from(start_offset),
334+
end_offset - start_offset,
335+
))
336+
}
337+
314338
pub fn emit(&self) -> ErrorReported {
315339
let label = format!("expected {}", self.err.expected);
316340
let line_num =

tests/error_examples.rs

Lines changed: 21 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,57 +2,33 @@
22
// instead of using the macro which is designed for successful compilation tests
33

44
use descend::error::CompileError;
5-
use std::io::ErrorKind;
65

76
#[test]
8-
fn test_fileio_error() {
7+
fn fileio_error() {
98
// Test compilation of a non-existent file which should fail with a FileIO error
10-
let err = descend::compile("examples/error-examples/nonexistent_file.desc")
11-
.err()
12-
.unwrap();
13-
14-
// Also test the full miette diagnostic output
15-
let miette_report = miette::Report::new(err.clone());
16-
17-
// Use the graphical handler to get fancy output
18-
let handler = miette::GraphicalReportHandler::new();
19-
let mut output = String::new();
20-
handler
21-
.render_report(&mut output, miette_report.as_ref())
22-
.unwrap();
23-
24-
insta::assert_snapshot!(output);
25-
26-
// Assert that compilation failed with a FileIO error
27-
match err {
28-
CompileError::FileIO(data) => {
29-
assert_eq!(
30-
data.file_path,
31-
"examples/error-examples/nonexistent_file.desc"
32-
);
33-
assert_eq!(data.io_error_kind, ErrorKind::NotFound);
34-
}
35-
_ => panic!("Expected FileIO error, got {:?}", err),
36-
}
9+
let err = test_error_compilation("examples/error-examples/nonexistent_file.desc");
10+
assert!(matches!(err, CompileError::FileIO(_)));
3711
}
38-
3912
#[test]
40-
fn test_missing_main_error() {
41-
// Test the missing main error directly by calling the type checker
42-
use descend::parser::SourceCode;
43-
use descend::ty_check::error::TyError;
13+
fn missing_main_error() {
14+
// Test compilation of a file without a main function which should fail with a MissingMain error
15+
let err = test_error_compilation("examples/error-examples/missing_main.desc");
16+
assert!(matches!(err, CompileError::MissingMain(_)));
17+
}
4418

45-
// Parse the file first
46-
let source = SourceCode::from_file("examples/error-examples/missing_main.desc").unwrap();
47-
let mut compil_unit = descend::parser::parse(&source).unwrap();
19+
#[test]
20+
fn parse_error() {
21+
let err = test_error_compilation("examples/error-examples/parse_error.desc");
22+
assert!(matches!(err, CompileError::Parse(_)));
23+
}
4824

49-
// Call type checker directly to get the actual MissingMain error
50-
let result = descend::ty_check::ty_check(&mut compil_unit);
51-
assert!(result.is_err()); // Should fail with MissingMain error
25+
fn test_error_compilation(file_path: &str) -> CompileError {
26+
// Test compilation of a file which should fail with the expected error
27+
let result = descend::compile(file_path);
28+
let err: CompileError = result.err().unwrap();
5229

53-
// Test the actual MissingMain error diagnostic
54-
let missing_main_error = TyError::MissingMain;
55-
let miette_report = miette::Report::new(missing_main_error);
30+
// Also test the full miette diagnostic output with source code
31+
let miette_report = descend::compile_with_source(file_path).err().unwrap();
5632

5733
// Use the graphical handler to get fancy output
5834
let handler = miette::GraphicalReportHandler::new();
@@ -61,5 +37,6 @@ fn test_missing_main_error() {
6137
.render_report(&mut output, miette_report.as_ref())
6238
.unwrap();
6339

64-
insta::assert_snapshot!(output);
40+
insta::assert_snapshot!(file_path, output);
41+
err
6542
}

tests/snapshots/error_examples__missing_main_error.snap renamed to tests/snapshots/error_examples__MissingMain.snap

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
source: tests/error_examples.rs
33
expression: output
44
---
5-
[31mdescend::ty_check::missing_main[0m
5+
[31mdescend::missing_main_error[0m
66

7-
[31m×[0m missing main function
8-
[36m help: [0mA main function is required as the entry point of your program.
7+
[31m×[0m Missing main function
8+
[36m help: [0mA main function is required as the entry point of your program
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
source: tests/error_examples.rs
3+
expression: output
4+
---
5+
descend::missing_main_error
6+
7+
× Missing main function
8+
 help: A main function is required as the entry point of your program

0 commit comments

Comments
 (0)