@@ -17,8 +17,10 @@ use rustc_hash::FxHashMap;
17
17
/// Implementations of this trait can be provided to the parser
18
18
/// to customize how include files are resolved.
19
19
pub trait SourceResolver {
20
+ fn ctx ( & mut self ) -> & mut SourceResolverContext ;
21
+
20
22
#[ cfg( feature = "fs" ) ]
21
- fn resolve < P > ( & self , path : P ) -> miette:: Result < ( PathBuf , String ) , Error >
23
+ fn resolve < P > ( & mut self , path : P ) -> miette:: Result < ( PathBuf , String ) , Error >
22
24
where
23
25
P : AsRef < Path > ,
24
26
{
@@ -27,8 +29,14 @@ pub trait SourceResolver {
27
29
"Could not resolve include file path: {e}"
28
30
) ) )
29
31
} ) ?;
32
+
33
+ self . ctx ( ) . check_include_errors ( & path) ?;
34
+
30
35
match std:: fs:: read_to_string ( & path) {
31
- Ok ( source) => Ok ( ( path, source) ) ,
36
+ Ok ( source) => {
37
+ self . ctx ( ) . add_path_to_include_graph ( path. clone ( ) ) ;
38
+ Ok ( ( path, source) )
39
+ }
32
40
Err ( _) => Err ( Error ( ErrorKind :: NotFound ( format ! (
33
41
"Could not resolve include file: {}" ,
34
42
path. display( )
@@ -41,6 +49,137 @@ pub trait SourceResolver {
41
49
P : AsRef < Path > ;
42
50
}
43
51
52
+ pub struct IncludeGraphNode {
53
+ parent : Option < PathBuf > ,
54
+ children : Vec < PathBuf > ,
55
+ }
56
+
57
+ #[ derive( Default ) ]
58
+ pub struct SourceResolverContext {
59
+ /// A graph representation of the include chain.
60
+ include_graph : FxHashMap < PathBuf , IncludeGraphNode > ,
61
+ /// Path being resolved.
62
+ current_file : Option < PathBuf > ,
63
+ }
64
+
65
+ impl SourceResolverContext {
66
+ pub fn check_include_errors ( & mut self , path : & PathBuf ) -> miette:: Result < ( ) , Error > {
67
+ // If the new path makes a cycle in the include graph, we return
68
+ // an error showing the cycle to the user.
69
+ if let Some ( cycle) = self . cycle_made_by_including_path ( path) {
70
+ return Err ( Error ( ErrorKind :: CyclicInclude ( cycle) ) ) ;
71
+ }
72
+
73
+ // If the new path doesn't make a cycle but it was already
74
+ // included before, we return a `MultipleInclude`
75
+ // error saying "<FILE> was already included in <FILE>".
76
+ if let Some ( parent_file) = self . path_was_already_included ( path) {
77
+ return Err ( Error ( ErrorKind :: MultipleInclude (
78
+ path. display ( ) . to_string ( ) ,
79
+ parent_file. display ( ) . to_string ( ) ,
80
+ ) ) ) ;
81
+ }
82
+
83
+ self . add_path_to_include_graph ( path. clone ( ) ) ;
84
+
85
+ Ok ( ( ) )
86
+ }
87
+
88
+ /// Changes `current_path` to its parent in the `include_graph`.
89
+ pub fn pop_current_file ( & mut self ) {
90
+ let parent = self
91
+ . current_file
92
+ . as_ref ( )
93
+ . and_then ( |file| self . include_graph . get ( file) . map ( |node| node. parent . clone ( ) ) )
94
+ . flatten ( ) ;
95
+ self . current_file = parent;
96
+ }
97
+
98
+ /// If including the path makes a cycle, returns a vector of the paths
99
+ /// that make the cycle. Else, returns None.
100
+ ///
101
+ /// To check if adding `path` to the include graph creates a cycle we just
102
+ /// need to verify if path is an ancestor of the current file.
103
+ fn cycle_made_by_including_path ( & self , path : & PathBuf ) -> Option < Cycle > {
104
+ let mut current_file = self . current_file . as_ref ( ) ;
105
+ let mut paths = Vec :: new ( ) ;
106
+
107
+ while let Some ( file) = current_file {
108
+ paths. push ( file. clone ( ) ) ;
109
+ current_file = self . get_parent ( file) ;
110
+ if file == path {
111
+ paths. reverse ( ) ;
112
+ paths. push ( path. clone ( ) ) ;
113
+ return Some ( Cycle { paths } ) ;
114
+ }
115
+ }
116
+
117
+ None
118
+ }
119
+
120
+ /// Returns the file that included `path`.
121
+ /// Returns `None` if `path` is the "main" file.
122
+ fn get_parent ( & self , path : & PathBuf ) -> Option < & PathBuf > {
123
+ self . include_graph
124
+ . get ( path)
125
+ . and_then ( |node| node. parent . as_ref ( ) )
126
+ }
127
+
128
+ /// If the path was already included, returns the path of the file that
129
+ /// included it. Else, returns None.
130
+ fn path_was_already_included ( & self , path : & PathBuf ) -> Option < PathBuf > {
131
+ // SAFETY: The call to expect should be unreachable, since the parent
132
+ // will only be None for the "main" file. But including the
133
+ // main file will trigger a cyclic include error before this
134
+ // function is called.
135
+ self . include_graph
136
+ . get ( path)
137
+ . map ( |node| node. parent . clone ( ) . expect ( "unreachable" ) )
138
+ }
139
+
140
+ /// Adds `path` as a child of `current_path`, and then changes
141
+ /// the `current_path` to `path`.
142
+ fn add_path_to_include_graph ( & mut self , path : PathBuf ) {
143
+ // 1. Add path to the current file children.
144
+ self . current_file . as_ref ( ) . and_then ( |file| {
145
+ self . include_graph
146
+ . get_mut ( file)
147
+ . map ( |node| node. children . push ( path. clone ( ) ) )
148
+ } ) ;
149
+
150
+ // 2. Add path to the include graph.
151
+ self . include_graph . insert (
152
+ path. clone ( ) ,
153
+ IncludeGraphNode {
154
+ parent : self . current_file . clone ( ) ,
155
+ children : Vec :: new ( ) ,
156
+ } ,
157
+ ) ;
158
+
159
+ // 3. Update the current file.
160
+ self . current_file = Some ( path) ;
161
+ }
162
+ }
163
+
164
+ /// We use this struct to print a nice error message when we find a cycle.
165
+ #[ derive( Debug , Clone , Eq , PartialEq ) ]
166
+ pub struct Cycle {
167
+ paths : Vec < PathBuf > ,
168
+ }
169
+
170
+ impl std:: fmt:: Display for Cycle {
171
+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
172
+ let parents = self . paths [ 0 ..( self . paths . len ( ) - 1 ) ] . iter ( ) ;
173
+ let children = self . paths [ 1 ..] . iter ( ) ;
174
+
175
+ for ( parent, child) in parents. zip ( children) {
176
+ write ! ( f, "\n {} includes {}" , parent. display( ) , child. display( ) ) ?;
177
+ }
178
+
179
+ Ok ( ( ) )
180
+ }
181
+ }
182
+
44
183
/// A source resolver that resolves include files from an in-memory map.
45
184
/// This is useful for testing or environments in which file system access
46
185
/// is not available.
@@ -49,6 +188,7 @@ pub trait SourceResolver {
49
188
/// contents prior to parsing.
50
189
pub struct InMemorySourceResolver {
51
190
sources : FxHashMap < PathBuf , String > ,
191
+ ctx : SourceResolverContext ,
52
192
}
53
193
54
194
impl FromIterator < ( Arc < str > , Arc < str > ) > for InMemorySourceResolver {
@@ -58,16 +198,24 @@ impl FromIterator<(Arc<str>, Arc<str>)> for InMemorySourceResolver {
58
198
map. insert ( PathBuf :: from ( path. to_string ( ) ) , source. to_string ( ) ) ;
59
199
}
60
200
61
- InMemorySourceResolver { sources : map }
201
+ InMemorySourceResolver {
202
+ sources : map,
203
+ ctx : Default :: default ( ) ,
204
+ }
62
205
}
63
206
}
64
207
65
208
impl SourceResolver for InMemorySourceResolver {
66
- fn resolve < P > ( & self , path : P ) -> miette:: Result < ( PathBuf , String ) , Error >
209
+ fn ctx ( & mut self ) -> & mut SourceResolverContext {
210
+ & mut self . ctx
211
+ }
212
+
213
+ fn resolve < P > ( & mut self , path : P ) -> miette:: Result < ( PathBuf , String ) , Error >
67
214
where
68
215
P : AsRef < Path > ,
69
216
{
70
217
let path = path. as_ref ( ) ;
218
+ self . ctx ( ) . check_include_errors ( & path. to_path_buf ( ) ) ?;
71
219
match self . sources . get ( path) {
72
220
Some ( source) => Ok ( ( path. to_owned ( ) , source. clone ( ) ) ) ,
73
221
None => Err ( Error ( ErrorKind :: NotFound ( format ! (
0 commit comments