@@ -83,6 +83,7 @@ impl From<Span> for CodeLoc {
83
83
/// - `title`: The title of the report (e.g., "unexpected negative number").
84
84
/// - `labels`: A collection of labels included in the report. Each label
85
85
/// contains a level, a span, and associated text.
86
+ /// - `footers`: A collection notes that appear after the end of the report.
86
87
#[ derive( Clone ) ]
87
88
pub ( crate ) struct Report {
88
89
code_cache : Arc < CodeCache > ,
@@ -110,12 +111,24 @@ impl Report {
110
111
code_loc. source_id . unwrap_or ( self . default_source_id ) ;
111
112
112
113
let code_cache = self . code_cache . read ( ) ;
113
- let code_origin =
114
- code_cache. get ( & source_id) . unwrap ( ) . origin . clone ( ) ;
114
+ let cache_entry = code_cache. get ( & source_id) . unwrap ( ) ;
115
+ let code_origin = cache_entry. origin . clone ( ) ;
116
+
117
+ // This could be faster if we maintain an ordered vector with the
118
+ // byte offset where each line begins. By doing a binary search
119
+ // on that vector, we can locate the line number in O(log(N))
120
+ // instead of O(N).
121
+ let ( line, column) = byte_offset_to_line_col (
122
+ & cache_entry. code ,
123
+ code_loc. span . start ( ) ,
124
+ )
125
+ . unwrap ( ) ;
115
126
116
127
Label {
117
128
level : level_as_text ( * level) ,
118
129
code_origin,
130
+ line,
131
+ column,
119
132
span : code_loc. span . clone ( ) ,
120
133
text,
121
134
}
@@ -232,6 +245,8 @@ impl Display for Report {
232
245
pub struct Label < ' a > {
233
246
level : & ' a str ,
234
247
code_origin : Option < String > ,
248
+ line : usize ,
249
+ column : usize ,
235
250
span : Span ,
236
251
text : & ' a str ,
237
252
}
@@ -436,3 +451,87 @@ fn level_as_text(level: Level) -> &'static str {
436
451
Level :: Help => "help" ,
437
452
}
438
453
}
454
+
455
+ /// Given a text slice and a position indicated as a byte offset, returns
456
+ /// the same position as a (line, column) pair.
457
+ fn byte_offset_to_line_col (
458
+ text : & str ,
459
+ byte_offset : usize ,
460
+ ) -> Option < ( usize , usize ) > {
461
+ // Check if the byte_offset is valid
462
+ if byte_offset > text. len ( ) {
463
+ return None ; // Out of bounds
464
+ }
465
+
466
+ let mut line = 1 ;
467
+ let mut col = 1 ;
468
+
469
+ // Iterate through the characters (not bytes) in the string
470
+ for ( i, c) in text. char_indices ( ) {
471
+ if i == byte_offset {
472
+ return Some ( ( line, col) ) ;
473
+ }
474
+ if c == '\n' {
475
+ line += 1 ;
476
+ col = 1 ; // Reset column to 1 after a newline
477
+ } else {
478
+ col += 1 ;
479
+ }
480
+ }
481
+
482
+ // If the byte_offset points to the last byte of the string, return the final position
483
+ if byte_offset == text. len ( ) {
484
+ return Some ( ( line, col) ) ;
485
+ }
486
+
487
+ None
488
+ }
489
+
490
+ #[ cfg( test) ]
491
+ mod tests {
492
+ use crate :: compiler:: report:: byte_offset_to_line_col;
493
+
494
+ #[ test]
495
+ fn byte_offset_to_line_col_single_line ( ) {
496
+ let text = "Hello, World!" ;
497
+ assert_eq ! ( byte_offset_to_line_col( text, 0 ) , Some ( ( 1 , 1 ) ) ) ; // Start of the string
498
+ assert_eq ! ( byte_offset_to_line_col( text, 7 ) , Some ( ( 1 , 8 ) ) ) ; // Byte offset of 'W'
499
+ assert_eq ! ( byte_offset_to_line_col( text, 12 ) , Some ( ( 1 , 13 ) ) ) ; // Byte offset of '!'
500
+ }
501
+
502
+ #[ test]
503
+ fn byte_offset_to_line_col_multiline ( ) {
504
+ let text = "Hello\n Rust\n World!" ;
505
+ assert_eq ! ( byte_offset_to_line_col( text, 0 ) , Some ( ( 1 , 1 ) ) ) ; // First character
506
+ assert_eq ! ( byte_offset_to_line_col( text, 5 ) , Some ( ( 1 , 6 ) ) ) ; // End of first line (newline)
507
+ assert_eq ! ( byte_offset_to_line_col( text, 6 ) , Some ( ( 2 , 1 ) ) ) ; // Start of second line ('R')
508
+ assert_eq ! ( byte_offset_to_line_col( text, 9 ) , Some ( ( 2 , 4 ) ) ) ; // Byte offset of 't' in "Rust"
509
+ assert_eq ! ( byte_offset_to_line_col( text, 11 ) , Some ( ( 3 , 1 ) ) ) ; // Start of third line ('W')
510
+ }
511
+
512
+ #[ test]
513
+ fn byte_offset_to_line_col_empty_string ( ) {
514
+ let text = "" ;
515
+ assert_eq ! ( byte_offset_to_line_col( text, 0 ) , Some ( ( 1 , 1 ) ) ) ;
516
+ }
517
+
518
+ #[ test]
519
+ fn byte_offset_to_line_col_out_of_bounds ( ) {
520
+ let text = "Hello, World!" ;
521
+ assert_eq ! ( byte_offset_to_line_col( text, text. len( ) + 1 ) , None ) ;
522
+ }
523
+
524
+ #[ test]
525
+ fn byte_offset_to_line_col_end_of_string ( ) {
526
+ let text = "Hello, World!" ;
527
+ assert_eq ! ( byte_offset_to_line_col( text, text. len( ) ) , Some ( ( 1 , 14 ) ) ) ; // Last position after '!'
528
+ }
529
+
530
+ #[ test]
531
+ fn byte_offset_to_line_col_multibyte_characters ( ) {
532
+ let text = "Hello, 你好!" ;
533
+ assert_eq ! ( byte_offset_to_line_col( text, 7 ) , Some ( ( 1 , 8 ) ) ) ; // Position of '你'
534
+ assert_eq ! ( byte_offset_to_line_col( text, 10 ) , Some ( ( 1 , 9 ) ) ) ; // Position of '好'
535
+ assert_eq ! ( byte_offset_to_line_col( text, 13 ) , Some ( ( 1 , 10 ) ) ) ; // Position of '!'
536
+ }
537
+ }
0 commit comments