11use std:: collections:: { BTreeMap , BTreeSet } ;
22
33use crate :: analyzer:: analyze_source;
4- use crate :: diagnostic:: { Diagnostic , Span } ;
4+ use crate :: diagnostic:: { Diagnostic , Severity , Span , code } ;
55use crate :: syntax:: ast:: {
66 BinaryOp , Block , Callee , DataEffect , EffectDecl , Expr , FieldDecl , FileMode , FunctionDecl ,
77 GenericBound , GenericParam , Item , LetKind , Param , Program , Stmt , TypeDecl , TypeKind , TypeRef ,
@@ -22,13 +22,19 @@ pub struct LoweredRust {
2222 pub source_map : Vec < RustSourceMapEntry > ,
2323}
2424
25- #[ derive( Debug , Clone , PartialEq , Eq , serde:: Serialize ) ]
25+ #[ derive( Debug , Clone , PartialEq , Eq , serde:: Serialize , serde :: Deserialize ) ]
2626pub struct RustSourceMapEntry {
2727 pub kind : String ,
2828 pub source : Span ,
2929 pub generated : Span ,
3030}
3131
32+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
33+ pub struct RemappedRustcDiagnostic {
34+ pub diagnostic : Diagnostic ,
35+ pub mapped : bool ,
36+ }
37+
3238pub fn lower_source_to_rust ( file : & str , source : & str ) -> Result < String , Vec < Diagnostic > > {
3339 lower_source_to_rust_with_map ( file, source) . map ( |lowered| lowered. rust_source )
3440}
@@ -80,6 +86,103 @@ pub fn lower_program_to_rust_with_map(program: &Program) -> LoweredRust {
8086 RustLowerer :: new ( program) . lower ( )
8187}
8288
89+ pub fn parse_source_map_json ( source_map_json : & str ) -> Result < Vec < RustSourceMapEntry > , String > {
90+ serde_json:: from_str ( source_map_json)
91+ . map_err ( |error| format ! ( "failed to parse RSScript source map JSON: {error}" ) )
92+ }
93+
94+ pub fn remap_rustc_diagnostic_json (
95+ source_map : & [ RustSourceMapEntry ] ,
96+ rustc_json : & str ,
97+ ) -> Result < Option < RemappedRustcDiagnostic > , String > {
98+ let rustc: RustcJsonDiagnostic = serde_json:: from_str ( rustc_json)
99+ . map_err ( |error| format ! ( "failed to parse rustc JSON diagnostic: {error}" ) ) ?;
100+ if !matches ! ( rustc. level. as_str( ) , "error" | "warning" ) {
101+ return Ok ( None ) ;
102+ }
103+
104+ let rustc_span = rustc
105+ . spans
106+ . iter ( )
107+ . find ( |span| span. is_primary )
108+ . or_else ( || rustc. spans . first ( ) ) ;
109+ let backend_code = rustc
110+ . code
111+ . as_ref ( )
112+ . map ( |code| code. code . as_str ( ) )
113+ . unwrap_or ( "<none>" ) ;
114+
115+ if let Some ( rustc_span) = rustc_span
116+ && let Some ( entry) = best_source_map_entry (
117+ source_map,
118+ & rustc_span. file_name ,
119+ rustc_span. line_start ,
120+ rustc_span. column_start ,
121+ )
122+ {
123+ let severity = rustc_severity ( & rustc. level ) ;
124+ let summary = format ! ( "backend diagnostic mapped to RSScript: {}" , rustc. message) ;
125+ let diagnostic = Diagnostic {
126+ code : code:: RUSTC_DIAGNOSTIC_MAPPED . to_string ( ) ,
127+ severity,
128+ summary,
129+ span : entry. source . clone ( ) ,
130+ label : "backend diagnostic maps to this RSScript construct" . to_string ( ) ,
131+ causes : vec ! [
132+ format!( "rustc code: {backend_code}" ) ,
133+ format!(
134+ "generated Rust: {}:{}:{}" ,
135+ rustc_span. file_name, rustc_span. line_start, rustc_span. column_start
136+ ) ,
137+ rustc. message,
138+ ] ,
139+ fixes : Vec :: new ( ) ,
140+ } ;
141+ return Ok ( Some ( RemappedRustcDiagnostic {
142+ diagnostic,
143+ mapped : true ,
144+ } ) ) ;
145+ }
146+
147+ let generated = rustc_span
148+ . map ( generated_span_from_rustc)
149+ . unwrap_or_else ( || Span {
150+ file : "<rustc-json>" . to_string ( ) ,
151+ line : 1 ,
152+ column : 1 ,
153+ length : 1 ,
154+ } ) ;
155+ let diagnostic = Diagnostic {
156+ code : code:: RUSTC_DIAGNOSTIC_UNMAPPABLE . to_string ( ) ,
157+ severity : rustc_severity ( & rustc. level ) ,
158+ summary : format ! ( "unmappable backend diagnostic: {}" , rustc. message) ,
159+ span : generated,
160+ label : "generated Rust diagnostic could not be mapped to RSScript source" . to_string ( ) ,
161+ causes : vec ! [ format!( "rustc code: {backend_code}" ) , rustc. message] ,
162+ fixes : Vec :: new ( ) ,
163+ } ;
164+ Ok ( Some ( RemappedRustcDiagnostic {
165+ diagnostic,
166+ mapped : false ,
167+ } ) )
168+ }
169+
170+ pub fn remap_rustc_diagnostic_json_lines (
171+ source_map : & [ RustSourceMapEntry ] ,
172+ rustc_json_lines : & str ,
173+ ) -> Result < Vec < RemappedRustcDiagnostic > , String > {
174+ let mut diagnostics = Vec :: new ( ) ;
175+ for line in rustc_json_lines
176+ . lines ( )
177+ . filter ( |line| !line. trim ( ) . is_empty ( ) )
178+ {
179+ if let Some ( diagnostic) = remap_rustc_diagnostic_json ( source_map, line) ? {
180+ diagnostics. push ( diagnostic) ;
181+ }
182+ }
183+ Ok ( diagnostics)
184+ }
185+
83186struct RustLowerer < ' a > {
84187 program : & ' a Program ,
85188 type_kinds : BTreeMap < String , TypeKind > ,
@@ -589,6 +692,64 @@ fn stmt_span(statement: &Stmt) -> &Span {
589692 }
590693}
591694
695+ fn best_source_map_entry < ' a > (
696+ source_map : & ' a [ RustSourceMapEntry ] ,
697+ file : & str ,
698+ line : usize ,
699+ column : usize ,
700+ ) -> Option < & ' a RustSourceMapEntry > {
701+ source_map
702+ . iter ( )
703+ . filter ( |entry| entry. generated . file == file)
704+ . filter ( |entry| generated_span_starts_before_or_at ( & entry. generated , line, column) )
705+ . max_by_key ( |entry| ( entry. generated . line , entry. generated . column ) )
706+ }
707+
708+ fn generated_span_starts_before_or_at ( span : & Span , line : usize , column : usize ) -> bool {
709+ span. line < line || ( span. line == line && span. column <= column)
710+ }
711+
712+ fn rustc_severity ( level : & str ) -> Severity {
713+ if level == "warning" {
714+ Severity :: Warning
715+ } else {
716+ Severity :: Error
717+ }
718+ }
719+
720+ fn generated_span_from_rustc ( span : & RustcJsonSpan ) -> Span {
721+ Span {
722+ file : span. file_name . clone ( ) ,
723+ line : span. line_start ,
724+ column : span. column_start ,
725+ length : span. column_end . saturating_sub ( span. column_start ) . max ( 1 ) ,
726+ }
727+ }
728+
729+ #[ derive( serde:: Deserialize ) ]
730+ struct RustcJsonDiagnostic {
731+ message : String ,
732+ level : String ,
733+ code : Option < RustcJsonCode > ,
734+ #[ serde( default ) ]
735+ spans : Vec < RustcJsonSpan > ,
736+ }
737+
738+ #[ derive( serde:: Deserialize ) ]
739+ struct RustcJsonCode {
740+ code : String ,
741+ }
742+
743+ #[ derive( serde:: Deserialize ) ]
744+ struct RustcJsonSpan {
745+ file_name : String ,
746+ line_start : usize ,
747+ column_start : usize ,
748+ column_end : usize ,
749+ #[ serde( default ) ]
750+ is_primary : bool ,
751+ }
752+
592753fn push_source_marker (
593754 out : & mut String ,
594755 indent : usize ,
0 commit comments