@@ -332,16 +332,51 @@ fn run_vm_loop(
332332
333333fn render_source_path_error ( source_path : & Path , err : & SourcePathError ) -> String {
334334 match err {
335- SourcePathError :: Source ( vm:: SourceError :: Parse ( parse) ) => {
336- let source = std:: fs:: read_to_string ( source_path) . unwrap_or_default ( ) ;
335+ SourcePathError :: SourceWithMap { .. } => {
336+ vm:: render_source_path_error ( source_path, err, true )
337+ }
338+ SourcePathError :: Source ( error) => render_source_error_at_path ( source_path, None , error) ,
339+ SourcePathError :: InvalidImportSyntax {
340+ path,
341+ line,
342+ message,
343+ } => {
344+ let source = std:: fs:: read_to_string ( path) . unwrap_or_default ( ) ;
337345 let mut source_map = SourceMap :: new ( ) ;
338- let source_id = source_map. add_source ( source_path. display ( ) . to_string ( ) , source) ;
346+ let source_id = source_map. add_source ( path. display ( ) . to_string ( ) , source) ;
347+ let parse = vm:: ParseError :: at_line ( * line, message. clone ( ) )
348+ . with_line_span_from_source ( & source_map, source_id) ;
349+ render_source_error ( & source_map, & parse, true )
350+ }
351+ _ => err. to_string ( ) ,
352+ }
353+ }
354+
355+ fn render_source_error_at_path (
356+ source_path : & Path ,
357+ source_override : Option < & str > ,
358+ error : & vm:: SourceError ,
359+ ) -> String {
360+ match error {
361+ vm:: SourceError :: Parse ( parse) => {
362+ let render_path = parse
363+ . message
364+ . split_once ( ": " )
365+ . map ( |( path, _) | Path :: new ( path) )
366+ . filter ( |path| path. exists ( ) )
367+ . unwrap_or ( source_path) ;
368+ let source = source_override
369+ . filter ( |_| render_path == source_path)
370+ . map ( str:: to_owned)
371+ . unwrap_or_else ( || std:: fs:: read_to_string ( render_path) . unwrap_or_default ( ) ) ;
372+ let mut source_map = SourceMap :: new ( ) ;
373+ let source_id = source_map. add_source ( render_path. display ( ) . to_string ( ) , source) ;
339374 let parse = parse
340375 . clone ( )
341376 . with_line_span_from_source ( & source_map, source_id) ;
342377 render_source_error ( & source_map, & parse, true )
343378 }
344- SourcePathError :: Source ( vm:: SourceError :: Compile ( compile) ) => {
379+ vm:: SourceError :: Compile ( compile) => {
345380 let render_path = compile
346381 . source_name ( )
347382 . map ( Path :: new)
@@ -352,19 +387,6 @@ fn render_source_path_error(source_path: &Path, err: &SourcePathError) -> String
352387 source_map. add_source ( render_path. display ( ) . to_string ( ) , source) ;
353388 vm:: render_compile_error ( & source_map, compile, true )
354389 }
355- SourcePathError :: InvalidImportSyntax {
356- path,
357- line,
358- message,
359- } => {
360- let source = std:: fs:: read_to_string ( path) . unwrap_or_default ( ) ;
361- let mut source_map = SourceMap :: new ( ) ;
362- let source_id = source_map. add_source ( path. display ( ) . to_string ( ) , source) ;
363- let parse = vm:: ParseError :: at_line ( * line, message. clone ( ) )
364- . with_line_span_from_source ( & source_map, source_id) ;
365- render_source_error ( & source_map, & parse, true )
366- }
367- _ => err. to_string ( ) ,
368390 }
369391}
370392
@@ -2372,4 +2394,117 @@ mod tests {
23722394 fn repl_input_incomplete_for_trailing_operator ( ) {
23732395 assert ! ( !super :: is_repl_input_complete( "let a = 1 +" ) ) ;
23742396 }
2397+
2398+ fn cli_diagnostic_root ( prefix : & str ) -> std:: path:: PathBuf {
2399+ let unique = format ! (
2400+ "{prefix}_{}_{}" ,
2401+ std:: process:: id( ) ,
2402+ SystemTime :: now( )
2403+ . duration_since( UNIX_EPOCH )
2404+ . expect( "clock should be valid" )
2405+ . as_nanos( )
2406+ ) ;
2407+ let root = std:: env:: temp_dir ( ) . join ( unique) ;
2408+ std:: fs:: create_dir_all ( & root) . expect ( "cli diagnostic root should be created" ) ;
2409+ root. canonicalize ( ) . unwrap_or ( root)
2410+ }
2411+
2412+ #[ test]
2413+ fn cli_nested_module_parse_error_renders_nested_source_frame ( ) {
2414+ let root = cli_diagnostic_root ( "pd-vm-cli-nested-parse-diag" ) ;
2415+ let main_path = root. join ( "main.rss" ) ;
2416+ let nested_path = root. join ( "nested.rss" ) ;
2417+ std:: fs:: write ( & main_path, "use self::nested as nested;\n nested::run();\n " )
2418+ . expect ( "main fixture should write" ) ;
2419+ std:: fs:: write ( & nested_path, "pub fn run( {\n " ) . expect ( "nested fixture should write" ) ;
2420+
2421+ let error = match vm:: compile_source_file_with_options (
2422+ & main_path,
2423+ vm:: CompileSourceFileOptions :: default ( ) ,
2424+ ) {
2425+ Ok ( _) => panic ! ( "nested parse error fixture should fail" ) ,
2426+ Err ( error) => error,
2427+ } ;
2428+ let rendered = super :: render_source_path_error ( & main_path, & error) ;
2429+
2430+ // The rendered frame must belong to the nested source: its path, its
2431+ // line text, and an underline, not the root file.
2432+ assert ! (
2433+ rendered. contains( & nested_path. display( ) . to_string( ) ) ,
2434+ "rendered diagnostic should name the nested path: {rendered}"
2435+ ) ;
2436+ assert ! (
2437+ rendered. contains( "pub fn run( {" ) ,
2438+ "rendered diagnostic should show the nested source line: {rendered}"
2439+ ) ;
2440+ assert ! (
2441+ rendered. contains( '^' ) ,
2442+ "rendered diagnostic should underline the nested source: {rendered}"
2443+ ) ;
2444+ assert ! (
2445+ !rendered. contains( "use self::nested as nested;" ) ,
2446+ "rendered diagnostic should not show the root source frame: {rendered}"
2447+ ) ;
2448+
2449+ let _ = std:: fs:: remove_dir_all ( & root) ;
2450+ }
2451+
2452+ #[ test]
2453+ fn cli_nested_strict_type_error_renders_nested_source_frame ( ) {
2454+ let root = cli_diagnostic_root ( "pd-vm-cli-nested-strict-diag" ) ;
2455+ let main_path = root. join ( "main.rss" ) ;
2456+ let nested_path = root. join ( "nested.rss" ) ;
2457+ std:: fs:: write ( & main_path, "use self::nested as nested;\n nested::run();\n " )
2458+ . expect ( "main fixture should write" ) ;
2459+ std:: fs:: write ( & nested_path, "pub fn run() -> unknown { 1 }\n " )
2460+ . expect ( "nested fixture should write" ) ;
2461+
2462+ let error = match vm:: compile_source_file_with_options (
2463+ & main_path,
2464+ vm:: CompileSourceFileOptions :: default ( ) ,
2465+ ) {
2466+ Ok ( _) => panic ! ( "strict nested fixture should fail" ) ,
2467+ Err ( error) => error,
2468+ } ;
2469+ let rendered = super :: render_source_path_error ( & main_path, & error) ;
2470+
2471+ assert ! (
2472+ rendered. contains( & nested_path. display( ) . to_string( ) ) ,
2473+ "rendered diagnostic should name the nested path: {rendered}"
2474+ ) ;
2475+ assert ! (
2476+ rendered. contains( "pub fn run() -> unknown { 1 }" ) ,
2477+ "rendered diagnostic should show the nested source line: {rendered}"
2478+ ) ;
2479+ assert ! (
2480+ rendered. contains( '^' ) ,
2481+ "rendered diagnostic should underline the nested source: {rendered}"
2482+ ) ;
2483+
2484+ let _ = std:: fs:: remove_dir_all ( & root) ;
2485+ }
2486+
2487+ #[ test]
2488+ fn render_source_error_uses_source_override_for_virtual_paths ( ) {
2489+ let virtual_path = std:: path:: Path :: new ( "__pd_vm_inmemory__/main.rss" ) ;
2490+ let error = vm:: SourceError :: Parse ( vm:: ParseError :: at_line ( 2 , "boom" ) ) ;
2491+ let rendered = super :: render_source_error_at_path (
2492+ virtual_path,
2493+ Some ( "line one\n line two target\n line three" ) ,
2494+ & error,
2495+ ) ;
2496+
2497+ assert ! (
2498+ rendered. contains( "__pd_vm_inmemory__/main.rss" ) ,
2499+ "rendered diagnostic should name the virtual path: {rendered}"
2500+ ) ;
2501+ assert ! (
2502+ rendered. contains( "line two target" ) ,
2503+ "rendered diagnostic should show the override source line: {rendered}"
2504+ ) ;
2505+ assert ! (
2506+ rendered. contains( '^' ) ,
2507+ "rendered diagnostic should underline the override source: {rendered}"
2508+ ) ;
2509+ }
23752510}
0 commit comments