Skip to content

Commit ec474e1

Browse files
committed
fix: implement proper source location tracking in diagnostics
- Add source_files field to TypedProgram to carry source information - Add span tracking to zyntax_embed parsing path (walk_parse_tree/walk_pair_to_value) - Add set_current_span() method to AstHostFunctions trait with default impl - Implement set_current_span() in TypedAstBuilder to track current parsing position - Add parse_to_json_with_filename() method to LanguageGrammar - Update ZynML::run_file() to use load_module_file() for proper filename tracking - Populate SourceMap from program.source_files in lowering diagnostics - Update ConsoleDiagnosticDisplay to show real source code and accurate spans Diagnostics now show: - Real filenames instead of "unknown.zy" - Accurate line and column numbers from source - Actual source code snippets - Proper multi-character span underlines Fixes issue where all errors showed position 1:1 with placeholder text.
1 parent 1953b87 commit ec474e1

11 files changed

Lines changed: 278 additions & 48 deletions

File tree

crates/compiler/src/lowering.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -443,8 +443,11 @@ impl LoweringContext {
443443

444444
eprintln!("\n=== Type Checking Diagnostics ===");
445445

446-
// Create a source map (empty for now since we don't have source files in tests)
447-
let source_map = SourceMap::new();
446+
// Create a source map and populate it with source files from the program
447+
let mut source_map = SourceMap::new();
448+
for source_file in &program.source_files {
449+
source_map.add_file(source_file.name.clone(), source_file.content.clone());
450+
}
448451
let display = ConsoleDiagnosticDisplay::default();
449452

450453
// Use the built-in pretty formatter

crates/typed_ast/src/ast_convert.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ pub mod typescript_example {
230230
Ok(TypedProgram {
231231
declarations,
232232
span: Span::new(0, 0), // Would get from source
233+
source_files: vec![], // TODO: Add source file info
233234
})
234235
}
235236
}

crates/typed_ast/src/diagnostics.rs

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -331,35 +331,65 @@ impl DiagnosticDisplay for ConsoleDiagnosticDisplay {
331331

332332
// Group annotations by file and sort by line
333333
let mut file_annotations: HashMap<String, Vec<&Annotation>> = HashMap::new();
334+
335+
// Get the first source file name from the source map (for single-file programs)
336+
// TODO: For multi-file programs, track file_id in Span
337+
let default_filename = source_map.get_file_by_id(0)
338+
.map(|f| f.name.clone())
339+
.unwrap_or_else(|| "input.zy".to_string());
340+
334341
for annotation in &diagnostic.annotations {
335-
// For now, use a placeholder filename - in practice, get from source_map
336-
let filename = "input.zy".to_string(); // source_map.get_filename(annotation.span)
337-
file_annotations.entry(filename).or_default().push(annotation);
342+
file_annotations.entry(default_filename.clone()).or_default().push(annotation);
338343
}
339-
344+
340345
// Display each file's annotations
341346
for (filename, annotations) in file_annotations {
342-
writeln!(f, " --> {}:{}:{}", filename, 1, 1)?; // Placeholder line/column
343-
writeln!(f, " |")?;
344-
347+
// Get the source file
348+
let source_file = source_map.get_file(&filename);
349+
345350
// Sort annotations by span start
346351
let mut sorted_annotations = annotations;
347352
sorted_annotations.sort_by_key(|a| a.span.start);
348-
353+
349354
// Display source lines with annotations
350355
for annotation in sorted_annotations {
351-
// Placeholder source line display
352-
writeln!(f, "{:3} | {}", 1, " // source code line here")?;
353-
writeln!(f, " | {}{}",
354-
" ".repeat(4), // Column offset
355-
self.format_underline(annotation.style, 10) // Underline length
356-
)?;
357-
358-
if let Some(message) = &annotation.message {
359-
writeln!(f, " | {}{}", " ".repeat(4), message)?;
356+
// Get the actual location and source line
357+
if let Some(file) = source_file {
358+
let location = file.get_location(annotation.span.start);
359+
let source_line = file.get_line(location.line).unwrap_or("");
360+
361+
writeln!(f, " --> {}:{}:{}", filename, location.line, location.column)?;
362+
writeln!(f, " |")?;
363+
writeln!(f, "{:3} | {}", location.line, source_line)?;
364+
365+
// Calculate underline position and length
366+
let underline_start = location.column - 1; // Convert to 0-based
367+
let underline_len = annotation.span.len().max(1);
368+
369+
writeln!(f, " | {}{}",
370+
" ".repeat(underline_start),
371+
self.format_underline(annotation.style, underline_len)
372+
)?;
373+
374+
if let Some(message) = &annotation.message {
375+
writeln!(f, " | {}{}", " ".repeat(underline_start), message)?;
376+
}
377+
} else {
378+
// Fallback to placeholder if source file not found
379+
writeln!(f, " --> {}:{}:{}", filename, 1, 1)?;
380+
writeln!(f, " |")?;
381+
writeln!(f, "{:3} | {}", 1, " // source code line here")?;
382+
writeln!(f, " | {}{}",
383+
" ".repeat(4),
384+
self.format_underline(annotation.style, 10)
385+
)?;
386+
387+
if let Some(message) = &annotation.message {
388+
writeln!(f, " | {}{}", " ".repeat(4), message)?;
389+
}
360390
}
361391
}
362-
392+
363393
writeln!(f, " |")?;
364394
}
365395

crates/typed_ast/src/typed_ast.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
//! - Supports languages like Rust, Java, C#, TypeScript, and Haxe
1111
1212
use crate::arena::InternedString;
13-
use crate::source::Span;
13+
use crate::source::{Span, SourceFile};
1414
use crate::type_registry::{Type, Mutability, Visibility, CallingConvention};
1515
use serde::{Deserialize, Serialize};
1616

@@ -39,11 +39,24 @@ impl<T: Default> Default for TypedNode<T> {
3939
}
4040

4141
/// Typed program - the root of the AST
42-
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
42+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4343
pub struct TypedProgram {
4444
pub declarations: Vec<TypedNode<TypedDeclaration>>,
4545
#[serde(default)]
4646
pub span: Span,
47+
/// Source files used in this program (for diagnostics)
48+
#[serde(default)]
49+
pub source_files: Vec<SourceFile>,
50+
}
51+
52+
impl Default for TypedProgram {
53+
fn default() -> Self {
54+
Self {
55+
declarations: Vec::new(),
56+
span: Span::default(),
57+
source_files: Vec::new(),
58+
}
59+
}
4760
}
4861

4962
/// Top-level declarations

crates/typed_ast/src/typed_builder.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,36 @@ use crate::type_registry::TypeRegistry;
1818
pub struct TypedASTBuilder {
1919
arena: AstArena,
2020
pub registry: TypeRegistry,
21+
source_file: Option<String>,
22+
source_content: Option<String>,
2123
}
2224

2325
impl TypedASTBuilder {
2426
pub fn new() -> Self {
2527
Self {
2628
arena: AstArena::new(),
2729
registry: TypeRegistry::new(),
30+
source_file: None,
31+
source_content: None,
2832
}
2933
}
3034

35+
/// Set the source file information for span tracking
36+
pub fn set_source(&mut self, file_name: String, content: String) {
37+
self.source_file = Some(file_name);
38+
self.source_content = Some(content);
39+
}
40+
41+
/// Get the source file name
42+
pub fn source_file(&self) -> Option<&str> {
43+
self.source_file.as_deref()
44+
}
45+
46+
/// Get the source content
47+
pub fn source_content(&self) -> Option<&str> {
48+
self.source_content.as_deref()
49+
}
50+
3151
/// Get a reference to the arena for string interning
3252
pub fn arena(&mut self) -> &mut AstArena {
3353
&mut self.arena
@@ -1153,7 +1173,16 @@ impl TypedASTBuilder {
11531173
declarations: Vec<TypedNode<TypedDeclaration>>,
11541174
span: Span,
11551175
) -> TypedProgram {
1156-
TypedProgram { declarations, span }
1176+
use crate::source::SourceFile;
1177+
1178+
// Create SourceFile if we have source information
1179+
let source_files = if let (Some(name), Some(content)) = (self.source_file(), self.source_content()) {
1180+
vec![SourceFile::new(name.to_string(), content.to_string())]
1181+
} else {
1182+
vec![]
1183+
};
1184+
1185+
TypedProgram { declarations, span, source_files }
11571186
}
11581187
}
11591188

crates/whirlwind_adapter/src/adapter.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ impl WhirlwindAdapter {
182182
let mut program = TypedProgram {
183183
declarations: all_declarations,
184184
span,
185+
source_files: vec![],
185186
};
186187

187188
// Run our type inference engine to fill in Unknown types

crates/zyn_peg/src/runtime.rs

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,11 @@ pub trait AstHostFunctions {
243243
/// Finalize program and return serialized TypedAST JSON
244244
fn finalize_program(&mut self, program: NodeHandle) -> String;
245245

246+
/// Set the current source span for tracking locations
247+
fn set_current_span(&mut self, _start: usize, _end: usize) {
248+
// Default implementation does nothing - can be overridden
249+
}
250+
246251
// ========== Functions ==========
247252

248253
/// Create a function declaration
@@ -1102,6 +1107,8 @@ pub struct TypedAstBuilder {
11021107
enum_types: HashMap<String, Vec<String>>,
11031108
/// Program declaration handles (in order)
11041109
program_decls: Vec<NodeHandle>,
1110+
/// Current span being processed (start, end)
1111+
current_span: (usize, usize),
11051112
}
11061113

11071114
impl Default for TypedAstBuilder {
@@ -1131,9 +1138,25 @@ impl TypedAstBuilder {
11311138
variable_types: HashMap::new(),
11321139
enum_types: HashMap::new(),
11331140
program_decls: Vec::new(),
1141+
current_span: (0, 0),
11341142
}
11351143
}
11361144

1145+
/// Set the current span for the next node being created
1146+
pub fn set_current_span(&mut self, start: usize, end: usize) {
1147+
self.current_span = (start, end);
1148+
}
1149+
1150+
/// Get the current span
1151+
pub fn get_current_span(&self) -> (usize, usize) {
1152+
self.current_span
1153+
}
1154+
1155+
/// Set the source file information for span tracking and diagnostics
1156+
pub fn set_source(&mut self, file_name: String, content: String) {
1157+
self.inner.set_source(file_name, content);
1158+
}
1159+
11371160
/// Store an expression and return its handle
11381161
fn store_expr(&mut self, expr: TypedNode<TypedExpression>) -> NodeHandle {
11391162
let handle = self.alloc_handle();
@@ -1229,20 +1252,30 @@ impl TypedAstBuilder {
12291252
}
12301253
}
12311254

1232-
/// Get the default span for nodes
1255+
/// Get the default span for nodes (uses current span from parsing)
12331256
fn default_span(&self) -> Span {
1234-
self.inner.dummy_span()
1257+
Span::new(self.current_span.0, self.current_span.1)
12351258
}
12361259

12371260
/// Build and return the final TypedProgram
12381261
pub fn build_program(&self) -> TypedProgram {
1262+
use zyntax_typed_ast::source::SourceFile;
1263+
12391264
let decls: Vec<TypedNode<TypedDeclaration>> = self.program_decls.iter()
12401265
.filter_map(|h| self.declarations.get(h).cloned())
12411266
.collect();
12421267

1268+
// Create SourceFile if we have source information
1269+
let source_files = if let (Some(name), Some(content)) = (self.inner.source_file(), self.inner.source_content()) {
1270+
vec![SourceFile::new(name.to_string(), content.to_string())]
1271+
} else {
1272+
vec![]
1273+
};
1274+
12431275
TypedProgram {
12441276
declarations: decls,
12451277
span: self.default_span(),
1278+
source_files,
12461279
}
12471280
}
12481281

@@ -1306,6 +1339,10 @@ impl AstHostFunctions for TypedAstBuilder {
13061339
.unwrap_or_else(|e| format!(r#"{{"declarations": [], "error": "{}"}}"#, e))
13071340
}
13081341

1342+
fn set_current_span(&mut self, start: usize, end: usize) {
1343+
self.current_span = (start, end);
1344+
}
1345+
13091346
fn alloc_handle(&mut self) -> NodeHandle {
13101347
let handle = NodeHandle(self.next_id);
13111348
self.next_id += 1;
@@ -1487,6 +1524,7 @@ impl AstHostFunctions for TypedAstBuilder {
14871524
items: Vec<NodeHandle>,
14881525
) -> NodeHandle {
14891526
let span = self.default_span();
1527+
eprintln!("DEBUG: create_impl_block span=({}, {})", span.start, span.end);
14901528

14911529
// Convert trait type arguments from handles to Type
14921530
let trait_type_args: Vec<Type> = trait_args.iter()
@@ -2899,6 +2937,8 @@ pub struct CommandInterpreter<'a, H: AstHostFunctions> {
28992937
value_stack: Vec<RuntimeValue>,
29002938
/// Named variables
29012939
variables: HashMap<String, RuntimeValue>,
2940+
/// Current span being processed (start, end)
2941+
current_span: (usize, usize),
29022942
}
29032943

29042944
impl<'a, H: AstHostFunctions> CommandInterpreter<'a, H> {
@@ -2909,9 +2949,20 @@ impl<'a, H: AstHostFunctions> CommandInterpreter<'a, H> {
29092949
host,
29102950
value_stack: Vec::new(),
29112951
variables: HashMap::new(),
2952+
current_span: (0, 0),
29122953
}
29132954
}
29142955

2956+
/// Set the current span for the node being processed
2957+
pub fn set_current_span(&mut self, start: usize, end: usize) {
2958+
self.current_span = (start, end);
2959+
}
2960+
2961+
/// Get the current span
2962+
pub fn get_current_span(&self) -> (usize, usize) {
2963+
self.current_span
2964+
}
2965+
29152966
/// Unescape a string literal, processing escape sequences
29162967
fn unescape_string(s: &str) -> String {
29172968
let mut result = String::with_capacity(s.len());

crates/zynml/src/lib.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -256,10 +256,24 @@ impl ZynML {
256256

257257
/// Run a ZynML program from a file
258258
pub fn run_file(&mut self, path: &Path) -> Result<()> {
259-
let source = std::fs::read_to_string(path)
260-
.with_context(|| format!("Failed to read file: {}", path.display()))?;
259+
// Load the file with proper filename tracking for diagnostics
260+
let functions = self.runtime
261+
.load_module_file(path)
262+
.with_context(|| format!("Failed to load file: {}", path.display()))?;
263+
264+
if self.config.verbose {
265+
println!("Loaded {} functions", functions.len());
266+
}
261267

262-
self.run(&source)
268+
// Try to call main() if it exists
269+
if functions.iter().any(|f| f == "main") {
270+
if self.config.verbose {
271+
println!("Calling main()...");
272+
}
273+
self.call("main")
274+
} else {
275+
Ok(()) // No main function to run
276+
}
263277
}
264278

265279
/// Get reference to the underlying runtime

0 commit comments

Comments
 (0)