@@ -2,6 +2,7 @@ use std::{
22 borrow:: Cow ,
33 cell:: RefCell ,
44 hash:: { Hash , Hasher } ,
5+ sync:: { Mutex , OnceLock } ,
56} ;
67
78use rustc_hash:: FxHashMap as HashMap ;
@@ -10,7 +11,8 @@ use crate::{
1011 helpers:: { get_map, GeneratedInfo , OnChunk , OnName , OnSource , StreamChunks } ,
1112 linear_map:: LinearMap ,
1213 source:: { Mapping , OriginalLocation } ,
13- BoxSource , MapOptions , Rope , Source , SourceExt , SourceMap , SourceValue ,
14+ BoxSource , MapOptions , RawStringSource , Rope , Source , SourceExt , SourceMap ,
15+ SourceValue ,
1416} ;
1517
1618/// Concatenate multiple [Source]s to a single [Source].
@@ -55,9 +57,26 @@ use crate::{
5557/// .unwrap()
5658/// );
5759/// ```
58- #[ derive( Default , Clone ) ]
60+ #[ derive( Default ) ]
5961pub struct ConcatSource {
60- children : Vec < BoxSource > ,
62+ children : Mutex < Vec < BoxSource > > ,
63+ is_optimized : OnceLock < Vec < BoxSource > > ,
64+ }
65+
66+ impl Clone for ConcatSource {
67+ fn clone ( & self ) -> Self {
68+ Self {
69+ children : Mutex :: new ( self . children . lock ( ) . unwrap ( ) . clone ( ) ) ,
70+ is_optimized : match self . is_optimized . get ( ) {
71+ Some ( children) => {
72+ let once_lock = OnceLock :: new ( ) ;
73+ once_lock. get_or_init ( || children. clone ( ) ) ;
74+ once_lock
75+ }
76+ None => OnceLock :: default ( ) ,
77+ } ,
78+ }
79+ }
6180}
6281
6382impl std:: fmt:: Debug for ConcatSource {
@@ -66,7 +85,13 @@ impl std::fmt::Debug for ConcatSource {
6685 let indent_str = format ! ( "{:indent$}" , "" , indent = indent) ;
6786
6887 writeln ! ( f, "{indent_str}ConcatSource::new(vec![" ) ?;
69- for child in self . children . iter ( ) {
88+
89+ let original_children = self . children . lock ( ) . unwrap ( ) ;
90+ let children = match self . is_optimized . get ( ) {
91+ Some ( optimized_children) => optimized_children,
92+ None => original_children. as_ref ( ) ,
93+ } ;
94+ for child in children {
7095 writeln ! ( f, "{:indent$?}," , child, indent = indent + 2 ) ?;
7196 }
7297 write ! ( f, "{indent_str}]).boxed()" )
@@ -87,19 +112,33 @@ impl ConcatSource {
87112 concat_source
88113 }
89114
90- fn children ( & self ) -> & Vec < BoxSource > {
91- & self . children
115+ fn optimized_children ( & self ) -> & [ BoxSource ] {
116+ self . is_optimized . get_or_init ( || {
117+ let mut children = self . children . lock ( ) . unwrap ( ) ;
118+ optimize ( & mut children)
119+ } )
92120 }
93121
94122 /// Add a [Source] to concat.
95123 pub fn add < S : Source + ' static > ( & mut self , source : S ) {
124+ let children = & mut * self . children . lock ( ) . unwrap ( ) ;
125+
126+ if let Some ( optimized_children) = self . is_optimized . take ( ) {
127+ * children = optimized_children;
128+ }
129+
96130 // First check if it's already a BoxSource containing a ConcatSource
97131 if let Some ( box_source) = source. as_any ( ) . downcast_ref :: < BoxSource > ( ) {
98132 if let Some ( concat_source) =
99133 box_source. as_ref ( ) . as_any ( ) . downcast_ref :: < ConcatSource > ( )
100134 {
101135 // Extend with existing children (cheap clone due to Arc)
102- self . children . extend ( concat_source. children . iter ( ) . cloned ( ) ) ;
136+ let original_children = concat_source. children . lock ( ) . unwrap ( ) ;
137+ let other_children = match concat_source. is_optimized . get ( ) {
138+ Some ( optimized_children) => optimized_children,
139+ None => original_children. as_ref ( ) ,
140+ } ;
141+ children. extend ( other_children. iter ( ) . cloned ( ) ) ;
103142 return ;
104143 }
105144 }
@@ -108,30 +147,27 @@ impl ConcatSource {
108147 if let Some ( concat_source) = source. as_any ( ) . downcast_ref :: < ConcatSource > ( )
109148 {
110149 // Extend with existing children (cheap clone due to Arc)
111- self . children . extend ( concat_source. children . iter ( ) . cloned ( ) ) ;
150+ let original_children = concat_source. children . lock ( ) . unwrap ( ) ;
151+ let other_children = match concat_source. is_optimized . get ( ) {
152+ Some ( optimized_children) => optimized_children,
153+ None => original_children. as_ref ( ) ,
154+ } ;
155+ children. extend ( other_children. iter ( ) . cloned ( ) ) ;
112156 } else {
113157 // Regular source - box it and add to children
114- self . children . push ( SourceExt :: boxed ( source ) ) ;
158+ children. push ( source . boxed ( ) ) ;
115159 }
116160 }
117161}
118162
119163impl Source for ConcatSource {
120164 fn source ( & self ) -> SourceValue {
121- let children = self . children ( ) ;
122- if children. len ( ) == 1 {
123- children[ 0 ] . source ( )
124- } else {
125- let mut content = String :: new ( ) ;
126- for child in self . children ( ) {
127- content. push_str ( child. source ( ) . into_string_lossy ( ) . as_ref ( ) ) ;
128- }
129- SourceValue :: String ( Cow :: Owned ( content) )
130- }
165+ let rope = self . rope ( ) ;
166+ SourceValue :: String ( Cow :: Owned ( rope. to_string ( ) ) )
131167 }
132168
133169 fn rope ( & self ) -> Rope < ' _ > {
134- let children = self . children ( ) ;
170+ let children = self . optimized_children ( ) ;
135171 if children. len ( ) == 1 {
136172 children[ 0 ] . rope ( )
137173 } else {
@@ -145,7 +181,7 @@ impl Source for ConcatSource {
145181 }
146182
147183 fn buffer ( & self ) -> Cow < [ u8 ] > {
148- let children = self . children ( ) ;
184+ let children = self . optimized_children ( ) ;
149185 if children. len ( ) == 1 {
150186 children[ 0 ] . buffer ( )
151187 } else {
@@ -156,15 +192,19 @@ impl Source for ConcatSource {
156192 }
157193
158194 fn size ( & self ) -> usize {
159- self . children ( ) . iter ( ) . map ( |child| child. size ( ) ) . sum ( )
195+ self
196+ . optimized_children ( )
197+ . iter ( )
198+ . map ( |child| child. size ( ) )
199+ . sum ( )
160200 }
161201
162202 fn map ( & self , options : & MapOptions ) -> Option < SourceMap > {
163203 get_map ( self , options)
164204 }
165205
166206 fn to_writer ( & self , writer : & mut dyn std:: io:: Write ) -> std:: io:: Result < ( ) > {
167- for child in self . children ( ) {
207+ for child in self . optimized_children ( ) {
168208 child. to_writer ( writer) ?;
169209 }
170210 Ok ( ( ) )
@@ -174,15 +214,15 @@ impl Source for ConcatSource {
174214impl Hash for ConcatSource {
175215 fn hash < H : Hasher > ( & self , state : & mut H ) {
176216 "ConcatSource" . hash ( state) ;
177- for child in self . children ( ) . iter ( ) {
217+ for child in self . optimized_children ( ) . iter ( ) {
178218 child. hash ( state) ;
179219 }
180220 }
181221}
182222
183223impl PartialEq for ConcatSource {
184224 fn eq ( & self , other : & Self ) -> bool {
185- self . children ( ) == other. children ( )
225+ self . optimized_children ( ) == other. optimized_children ( )
186226 }
187227}
188228impl Eq for ConcatSource { }
@@ -195,9 +235,10 @@ impl StreamChunks for ConcatSource {
195235 on_source : OnSource < ' _ , ' a > ,
196236 on_name : OnName < ' _ , ' a > ,
197237 ) -> crate :: helpers:: GeneratedInfo {
198- if self . children ( ) . len ( ) == 1 {
199- return self . children [ 0 ]
200- . stream_chunks ( options, on_chunk, on_source, on_name) ;
238+ let children = self . optimized_children ( ) ;
239+
240+ if children. len ( ) == 1 {
241+ return children[ 0 ] . stream_chunks ( options, on_chunk, on_source, on_name) ;
201242 }
202243 let mut current_line_offset = 0 ;
203244 let mut current_column_offset = 0 ;
@@ -210,7 +251,7 @@ impl StreamChunks for ConcatSource {
210251 let name_index_mapping: RefCell < LinearMap < u32 > > =
211252 RefCell :: new ( LinearMap :: default ( ) ) ;
212253
213- for item in self . children ( ) {
254+ for item in children {
214255 source_index_mapping. borrow_mut ( ) . clear ( ) ;
215256 name_index_mapping. borrow_mut ( ) . clear ( ) ;
216257 let mut last_mapping_line = 0 ;
@@ -356,6 +397,56 @@ impl StreamChunks for ConcatSource {
356397 }
357398}
358399
400+ fn optimize ( children : & mut Vec < BoxSource > ) -> Vec < BoxSource > {
401+ let original_children = std:: mem:: take ( children) ;
402+
403+ if original_children. len ( ) <= 1 {
404+ return original_children; // Nothing to optimize
405+ }
406+
407+ let mut new_children = Vec :: new ( ) ;
408+ let mut current_raw_sources = Vec :: new ( ) ;
409+
410+ for child in original_children {
411+ if child. as_ref ( ) . as_any ( ) . is :: < RawStringSource > ( ) {
412+ current_raw_sources. push ( child) ;
413+ } else {
414+ // Flush any pending raw sources before adding the non-raw source
415+ merge_raw_sources ( & mut current_raw_sources, & mut new_children) ;
416+ new_children. push ( child) ;
417+ }
418+ }
419+
420+ // Flush any remaining pending raw sources
421+ merge_raw_sources ( & mut current_raw_sources, & mut new_children) ;
422+
423+ new_children
424+ }
425+
426+ /// Helper function to merge and flush pending raw sources.
427+ fn merge_raw_sources (
428+ raw_sources : & mut Vec < BoxSource > ,
429+ new_children : & mut Vec < BoxSource > ,
430+ ) {
431+ match raw_sources. len ( ) {
432+ 0 => { } // Nothing to flush
433+ 1 => {
434+ // Single source - move it directly
435+ new_children. push ( raw_sources. pop ( ) . unwrap ( ) ) ;
436+ }
437+ _ => {
438+ // Multiple sources - merge them
439+ let capacity = raw_sources. iter ( ) . map ( |s| s. size ( ) ) . sum ( ) ;
440+ let mut merged_content = String :: with_capacity ( capacity) ;
441+ for source in raw_sources. drain ( ..) {
442+ merged_content. push_str ( source. source ( ) . into_string_lossy ( ) . as_ref ( ) ) ;
443+ }
444+ let merged_source = RawStringSource :: from ( merged_content) ;
445+ new_children. push ( merged_source. boxed ( ) ) ;
446+ }
447+ }
448+ }
449+
359450#[ cfg( test) ]
360451mod tests {
361452 use crate :: { OriginalSource , RawBufferSource , RawStringSource } ;
@@ -640,6 +731,79 @@ mod tests {
640731 ) ;
641732 // The key test: verify that nested ConcatSources are flattened
642733 // Should have 6 direct children instead of nested structure
643- assert_eq ! ( outer_concat. children. len( ) , 6 ) ;
734+ assert_eq ! ( outer_concat. optimized_children( ) . len( ) , 1 ) ;
735+ }
736+
737+ #[ test]
738+ fn test_self_equality_no_deadlock ( ) {
739+ let concat_source = ConcatSource :: new ( [
740+ RawStringSource :: from ( "Hello " ) ,
741+ RawStringSource :: from ( "World" ) ,
742+ ] )
743+ . boxed ( ) ;
744+ assert_eq ! ( concat_source. as_ref( ) , concat_source. as_ref( ) ) ;
745+
746+ concat_source. source ( ) ;
747+
748+ assert_eq ! ( concat_source. as_ref( ) , concat_source. as_ref( ) ) ;
749+ }
750+
751+ #[ test]
752+ fn test_debug_output ( ) {
753+ let inner_concat = ConcatSource :: new ( [
754+ RawStringSource :: from ( "Hello " ) ,
755+ RawStringSource :: from ( "World" ) ,
756+ ] ) ;
757+
758+ let mut outer_concat = ConcatSource :: new ( [
759+ inner_concat. boxed ( ) ,
760+ RawStringSource :: from ( "!" ) . boxed ( ) ,
761+ ConcatSource :: new ( [
762+ RawStringSource :: from ( " How" ) ,
763+ RawStringSource :: from ( " are" ) ,
764+ ] )
765+ . boxed ( ) ,
766+ RawStringSource :: from ( " you?\n " ) . boxed ( ) ,
767+ ] ) ;
768+
769+ assert_eq ! (
770+ format!( "{:?}" , outer_concat) ,
771+ r#"ConcatSource::new(vec![
772+ RawStringSource::from_static("Hello ").boxed(),
773+ RawStringSource::from_static("World").boxed(),
774+ RawStringSource::from_static("!").boxed(),
775+ RawStringSource::from_static(" How").boxed(),
776+ RawStringSource::from_static(" are").boxed(),
777+ RawStringSource::from_static(" you?\n").boxed(),
778+ ]).boxed()"#
779+ ) ;
780+
781+ outer_concat. source ( ) ;
782+
783+ assert_eq ! (
784+ format!( "{:?}" , outer_concat) ,
785+ r#"ConcatSource::new(vec![
786+ RawStringSource::from_static("Hello World! How are you?\n").boxed(),
787+ ]).boxed()"#
788+ ) ;
789+
790+ outer_concat. add ( RawStringSource :: from ( "I'm fine." ) ) ;
791+
792+ assert_eq ! (
793+ format!( "{:?}" , outer_concat) ,
794+ r#"ConcatSource::new(vec![
795+ RawStringSource::from_static("Hello World! How are you?\n").boxed(),
796+ RawStringSource::from_static("I'm fine.").boxed(),
797+ ]).boxed()"#
798+ ) ;
799+
800+ outer_concat. source ( ) ;
801+
802+ assert_eq ! (
803+ format!( "{:?}" , outer_concat) ,
804+ r#"ConcatSource::new(vec![
805+ RawStringSource::from_static("Hello World! How are you?\nI'm fine.").boxed(),
806+ ]).boxed()"#
807+ ) ;
644808 }
645809}
0 commit comments