2323//! assert_eq!(pos.column, 0);
2424//! ```
2525
26- use std:: cell:: { Cell , OnceCell , RefCell } ;
26+ use std:: cell:: { OnceCell , RefCell } ;
2727use std:: collections:: HashMap ;
2828use std:: fmt;
2929use std:: io;
@@ -32,6 +32,9 @@ use serde::Deserialize;
3232use srcmap_codec:: { DecodeError , vlq_encode_unsigned} ;
3333use srcmap_scopes:: { Binding , CallSite , GeneratedRange , OriginalScope , Position , ScopeInfo } ;
3434
35+ #[ cfg( test) ]
36+ use std:: cell:: Cell ;
37+
3538pub mod js_identifiers;
3639pub mod offset_lookup;
3740pub mod source_view;
@@ -1987,10 +1990,11 @@ pub struct LazySourceMap {
19871990 /// If true, decode_line must decode sequentially from the start.
19881991 fast_scan : bool ,
19891992
1990- /// Highest line fully decoded so far (for progressive decode in fast-scan mode).
1991- /// VLQ state at the end of this line is stored in `decode_state`.
1992- decode_watermark : Cell < u32 > ,
1993- decode_state : Cell < VlqState > ,
1993+ /// Cumulative VLQ states indexed by the next undecoded line.
1994+ state_checkpoints : RefCell < Vec < VlqState > > ,
1995+
1996+ #[ cfg( test) ]
1997+ decode_work : Cell < u32 > ,
19941998}
19951999
19962000impl LazySourceMap {
@@ -2028,8 +2032,13 @@ impl LazySourceMap {
20282032 decoded_lines : RefCell :: new ( HashMap :: new ( ) ) ,
20292033 source_map,
20302034 fast_scan,
2031- decode_watermark : Cell :: new ( 0 ) ,
2032- decode_state : Cell :: new ( VlqState :: default ( ) ) ,
2035+ state_checkpoints : RefCell :: new ( if fast_scan {
2036+ vec ! [ VlqState :: default ( ) ]
2037+ } else {
2038+ Vec :: new ( )
2039+ } ) ,
2040+ #[ cfg( test) ]
2041+ decode_work : Cell :: new ( 0 ) ,
20332042 }
20342043 }
20352044
@@ -2311,70 +2320,71 @@ impl LazySourceMap {
23112320 Ok ( ( mappings, state) )
23122321 }
23132322
2314- /// Decode a single line's mappings on demand.
2315- ///
2316- /// Returns the cached result if the line has already been decoded.
2317- /// The line index is 0-based.
2318- pub fn decode_line ( & self , line : u32 ) -> Result < Vec < Mapping > , DecodeError > {
2319- // Check cache first
2320- if let Some ( cached) = self . decoded_lines . borrow ( ) . get ( & line) {
2321- return Ok ( cached. clone ( ) ) ;
2323+ fn ensure_line_decoded ( & self , line : u32 ) -> Result < ( ) , DecodeError > {
2324+ if self . decoded_lines . borrow ( ) . contains_key ( & line) {
2325+ return Ok ( ( ) ) ;
23222326 }
23232327
23242328 let line_idx = line as usize ;
23252329 if line_idx >= self . line_info . len ( ) {
2326- return Ok ( Vec :: new ( ) ) ;
2330+ return Ok ( ( ) ) ;
23272331 }
23282332
23292333 if self . fast_scan {
2330- // In fast-scan mode, VLQ state is not pre-computed.
2331- // Decode sequentially from the watermark (or line 0 for backward seeks).
2332- // For both forward and backward walks, use cached lines where available
2333- // and only walk VLQ bytes to compute state for already-decoded lines.
2334- let watermark = self . decode_watermark . get ( ) ;
2335- let start = if line >= watermark { watermark } else { 0 } ;
2336- let mut state = if line >= watermark {
2337- self . decode_state . get ( )
2338- } else {
2339- VlqState { source_index : 0 , original_line : 0 , original_column : 0 , name_index : 0 }
2340- } ;
2334+ let mut checkpoints = self . state_checkpoints . borrow_mut ( ) ;
2335+ let start = line_idx. min ( checkpoints. len ( ) - 1 ) ;
2336+ let mut state = checkpoints[ start] ;
2337+
2338+ for current_line_idx in start..=line_idx {
2339+ #[ cfg( test) ]
2340+ self . decode_work . set ( self . decode_work . get ( ) + 1 ) ;
23412341
2342- for l in start..=line {
2343- let info = & self . line_info [ l as usize ] ;
2344- if self . decoded_lines . borrow ( ) . contains_key ( & l ) {
2342+ let current_line = current_line_idx as u32 ;
2343+ let info = & self . line_info [ current_line_idx ] ;
2344+ if self . decoded_lines . borrow ( ) . contains_key ( & current_line ) {
23452345 // Already cached — just walk VLQ bytes to compute end-state
23462346 let bytes = self . raw_mappings . as_bytes ( ) ;
23472347 state = walk_vlq_state ( bytes, info. byte_offset , info. byte_end , state) ?;
23482348 } else {
2349- let ( mappings, new_state) = self . decode_line_with_state ( l , state) ?;
2349+ let ( mappings, new_state) = self . decode_line_with_state ( current_line , state) ?;
23502350 state = new_state;
2351- self . decoded_lines . borrow_mut ( ) . insert ( l , mappings) ;
2351+ self . decoded_lines . borrow_mut ( ) . insert ( current_line , mappings) ;
23522352 }
2353- }
23542353
2355- // Update watermark (only advance, never regress)
2356- if line + 1 > self . decode_watermark . get ( ) {
2357- self . decode_watermark . set ( line + 1 ) ;
2358- self . decode_state . set ( state) ;
2354+ let next_line_idx = current_line_idx + 1 ;
2355+ if next_line_idx == checkpoints. len ( ) {
2356+ checkpoints. push ( state) ;
2357+ } else {
2358+ checkpoints[ next_line_idx] = state;
2359+ }
23592360 }
23602361
2361- let cached = self . decoded_lines . borrow ( ) . get ( & line) . cloned ( ) ;
2362- return Ok ( cached. unwrap_or_default ( ) ) ;
2362+ return Ok ( ( ) ) ;
23632363 }
23642364
2365- // Normal mode: line_info has pre-computed VLQ state
23662365 let state = self . line_info [ line_idx] . state ;
23672366 let ( mappings, _) = self . decode_line_with_state ( line, state) ?;
2368- self . decoded_lines . borrow_mut ( ) . insert ( line, mappings. clone ( ) ) ;
2369- Ok ( mappings)
2367+ self . decoded_lines . borrow_mut ( ) . insert ( line, mappings) ;
2368+ Ok ( ( ) )
2369+ }
2370+
2371+ /// Decode a single line's mappings on demand.
2372+ ///
2373+ /// Returns the cached result if the line has already been decoded.
2374+ /// The line index is 0-based.
2375+ pub fn decode_line ( & self , line : u32 ) -> Result < Vec < Mapping > , DecodeError > {
2376+ self . ensure_line_decoded ( line) ?;
2377+ Ok ( self . decoded_lines . borrow ( ) . get ( & line) . cloned ( ) . unwrap_or_default ( ) )
23702378 }
23712379
23722380 /// Look up the original source position for a generated position.
23732381 ///
23742382 /// Both `line` and `column` are 0-based.
23752383 /// Returns `None` if no mapping exists or the mapping has no source.
23762384 pub fn original_position_for ( & self , line : u32 , column : u32 ) -> Option < OriginalLocation > {
2377- let line_mappings = self . decode_line ( line) . ok ( ) ?;
2385+ self . ensure_line_decoded ( line) . ok ( ) ?;
2386+ let decoded_lines = self . decoded_lines . borrow ( ) ;
2387+ let line_mappings = decoded_lines. get ( & line) ?;
23782388
23792389 if line_mappings. is_empty ( ) {
23802390 return None ;
@@ -7292,6 +7302,43 @@ mod tests {
72927302 assert_eq ! ( loc0. line, 0 ) ;
72937303 }
72947304
7305+ #[ test]
7306+ fn lazy_sourcemap_backward_cache_miss_resumes_from_checkpoint ( ) {
7307+ let mappings = std:: iter:: repeat_n ( "AACA" , 64 ) . collect :: < Vec < _ > > ( ) . join ( ";" ) ;
7308+ let json =
7309+ format ! ( r#"{{"version":3,"sources":["a.js"],"names":[],"mappings":"{mappings}"}}"# ) ;
7310+ let sm = LazySourceMap :: from_json_fast ( & json) . unwrap ( ) ;
7311+
7312+ sm. decode_line ( 63 ) . unwrap ( ) ;
7313+ sm. decoded_lines . borrow_mut ( ) . remove ( & 15 ) ;
7314+ sm. decode_work . set ( 0 ) ;
7315+
7316+ sm. decode_line ( 15 ) . unwrap ( ) ;
7317+
7318+ assert_eq ! ( sm. decode_work. get( ) , 1 , "must resume at the line-15 checkpoint" ) ;
7319+ }
7320+
7321+ #[ test]
7322+ fn lazy_sourcemap_fast_scan_matches_prescan_in_descending_and_randomized_order ( ) {
7323+ let mappings = std:: iter:: repeat_n ( "AACA,CAAC" , 64 ) . collect :: < Vec < _ > > ( ) . join ( ";" ) ;
7324+ let json =
7325+ format ! ( r#"{{"version":3,"sources":["a.js"],"names":[],"mappings":"{mappings}"}}"# ) ;
7326+ let fast = LazySourceMap :: from_json_fast ( & json) . unwrap ( ) ;
7327+ let prescan = LazySourceMap :: from_json_no_content ( & json) . unwrap ( ) ;
7328+ let position = |map : & LazySourceMap , line, column| {
7329+ map. original_position_for ( line, column)
7330+ . map ( |loc| ( loc. source , loc. line , loc. column , loc. name ) )
7331+ } ;
7332+
7333+ for line in ( 0 ..64 ) . rev ( ) {
7334+ assert_eq ! ( position( & fast, line, 1 ) , position( & prescan, line, 1 ) ) ;
7335+ }
7336+
7337+ for line in [ 17 , 2 , 61 , 9 , 42 , 0 , 31 , 5 , 63 , 23 , 17 , 42 ] {
7338+ assert_eq ! ( position( & fast, line, 2 ) , position( & prescan, line, 2 ) ) ;
7339+ }
7340+ }
7341+
72957342 #[ test]
72967343 fn lazy_sourcemap_fast_scan_vs_prescan_consistency ( ) {
72977344 // Verify fast_scan and prescan produce identical lookup results
0 commit comments