1use std::collections::{HashMap, HashSet, VecDeque};
27
28use crate::parser::{Config, Def};
29use crate::validation;
30use crate::{Error, Result};
31
32#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct ResolvedDef {
35 pub name: String,
37
38 pub traits: Vec<String>,
40
41 pub attrs: Vec<String>,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct ResolvedConfig {
48 pub defs: HashMap<String, ResolvedDef>,
50}
51
52pub fn resolve(config: &Config) -> Result<ResolvedConfig> {
93 let defs = &config.defs;
94
95 for (name, def) in defs {
97 if let Some(ref parent) = def.extends
98 && !defs.contains_key(parent)
99 {
100 return Err(Error::UndefinedParent {
101 def: name.clone(),
102 parent: parent.clone(),
103 });
104 }
105 }
106
107 let mut graph: HashMap<&str, Vec<&str>> = HashMap::new();
109 let mut in_degree: HashMap<&str, usize> = HashMap::new();
110
111 for name in defs.keys() {
112 in_degree.insert(name, 0);
113 }
114
115 for (name, def) in defs {
116 if let Some(ref parent) = def.extends {
117 graph.entry(parent).or_default().push(name);
118 *in_degree.entry(name).or_insert(0) += 1;
119 }
120 }
121
122 let mut queue: VecDeque<&str> = in_degree
124 .iter()
125 .filter(|(_, deg)| **deg == 0)
126 .map(|(name, _)| *name)
127 .collect();
128
129 let mut topo_order: Vec<&str> = Vec::with_capacity(defs.len());
130
131 while let Some(node) = queue.pop_front() {
132 topo_order.push(node);
133
134 if let Some(children) = graph.get(node) {
135 for &child in children {
136 let deg = in_degree.get_mut(child).unwrap();
137 *deg -= 1;
138 if *deg == 0 {
139 queue.push_back(child);
140 }
141 }
142 }
143 }
144
145 if topo_order.len() != defs.len() {
147 let remaining: Vec<_> = in_degree
148 .keys()
149 .filter(|&&name| !topo_order.contains(&name))
150 .copied()
151 .collect();
152 return Err(Error::CircularDependency(format_cycle(&remaining, defs)));
153 }
154
155 let mut resolved = HashMap::with_capacity(defs.len());
157
158 for name in topo_order {
159 let def = defs.get(name).unwrap();
160 let resolved_def = resolve_single(name, def, defs, &resolved)?;
161 resolved.insert(name.to_string(), resolved_def);
162 }
163
164 Ok(ResolvedConfig { defs: resolved })
165}
166
167fn resolve_single(
169 name: &str,
170 def: &Def,
171 _defs: &HashMap<String, Def>,
172 resolved: &HashMap<String, ResolvedDef>,
173) -> Result<ResolvedDef> {
174 let (traits, attrs) = if let Some(ref parent_name) = def.extends {
175 let parent = resolved
176 .get(parent_name)
177 .ok_or_else(|| Error::Validation(format!("parent '{parent_name}' not resolved")))?;
178
179 (
180 merge_unique(&parent.traits, &def.traits),
181 merge_unique(&parent.attrs, &def.attrs),
182 )
183 } else {
184 (def.traits.clone(), def.attrs.clone())
185 };
186
187 validation::validate_trait_names(&traits)?;
189
190 Ok(ResolvedDef {
191 name: name.to_string(),
192 traits,
193 attrs,
194 })
195}
196
197fn merge_unique(parent: &[String], child: &[String]) -> Vec<String> {
199 let mut seen = HashSet::with_capacity(parent.len() + child.len());
200 let mut result = Vec::with_capacity(parent.len() + child.len());
201
202 for item in parent.iter().chain(child.iter()) {
203 if seen.insert(item.clone()) {
204 result.push(item.clone());
205 }
206 }
207
208 result
209}
210
211fn format_cycle(nodes: &[&str], _defs: &HashMap<String, Def>) -> String {
213 if nodes.is_empty() {
214 return "unknown".to_string();
215 }
216
217 nodes.join(" → ")
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use crate::parser;
226
227 #[test]
228 fn test_resolve_basic() {
229 let toml = r#"
230[defs.base]
231traits = ["Debug", "Clone"]
232
233[defs.child]
234extends = "base"
235traits = ["PartialEq"]
236"#;
237
238 let config = parser::parse(toml).unwrap();
239 let resolved = resolve(&config).unwrap();
240
241 let child = resolved.defs.get("child").unwrap();
242 assert_eq!(child.traits, vec!["Debug", "Clone", "PartialEq"]);
243 }
244
245 #[test]
246 fn test_resolve_chain() {
247 let toml = r#"
248[defs.a]
249traits = ["Debug"]
250
251[defs.b]
252extends = "a"
253traits = ["Clone"]
254
255[defs.c]
256extends = "b"
257traits = ["PartialEq"]
258"#;
259
260 let config = parser::parse(toml).unwrap();
261 let resolved = resolve(&config).unwrap();
262
263 let c = resolved.defs.get("c").unwrap();
264 assert_eq!(c.traits, vec!["Debug", "Clone", "PartialEq"]);
265 }
266
267 #[test]
268 fn test_resolve_diamond() {
269 let toml = r#"
271[defs.base]
272traits = ["Debug"]
273
274[defs.left]
275extends = "base"
276traits = ["Clone"]
277
278[defs.right]
279extends = "base"
280traits = ["PartialEq"]
281
282[defs.bottom]
283extends = "left"
284traits = ["Hash"]
285"#;
286
287 let config = parser::parse(toml).unwrap();
288 let resolved = resolve(&config).unwrap();
289
290 let bottom = resolved.defs.get("bottom").unwrap();
291 assert_eq!(bottom.traits, vec!["Debug", "Clone", "Hash"]);
293 }
294
295 #[test]
296 fn test_resolve_circular() {
297 let toml = r#"
298[defs.a]
299extends = "b"
300traits = ["Debug"]
301
302[defs.b]
303extends = "c"
304traits = ["Clone"]
305
306[defs.c]
307extends = "a"
308traits = ["PartialEq"]
309"#;
310
311 let config = parser::parse(toml).unwrap();
312 let result = resolve(&config);
313
314 assert!(result.is_err());
315 let err = result.unwrap_err().to_string();
316 assert!(err.contains("circular"));
317 }
318
319 #[test]
320 fn test_resolve_self_reference() {
321 let toml = r#"
322[defs.a]
323extends = "a"
324traits = ["Debug"]
325"#;
326
327 let config = parser::parse(toml).unwrap();
328 let result = resolve(&config);
329
330 assert!(result.is_err());
331 let err = result.unwrap_err().to_string();
332 assert!(err.contains("circular"));
333 }
334
335 #[test]
336 fn test_resolve_undefined_parent() {
337 let toml = r#"
338[defs.child]
339extends = "nonexistent"
340traits = ["Debug"]
341"#;
342
343 let config = parser::parse(toml).unwrap();
344 let result = resolve(&config);
345
346 assert!(result.is_err());
347 let err = result.unwrap_err().to_string();
348 assert!(err.contains("nonexistent"));
349 }
350
351 #[test]
352 fn test_resolve_deduplication() {
353 let toml = r#"
354[defs.base]
355traits = ["Debug", "Clone"]
356
357[defs.child]
358extends = "base"
359traits = ["Clone", "PartialEq"]
360"#;
361
362 let config = parser::parse(toml).unwrap();
363 let resolved = resolve(&config).unwrap();
364
365 let child = resolved.defs.get("child").unwrap();
366 assert_eq!(child.traits, vec!["Debug", "Clone", "PartialEq"]);
368 }
369
370 #[test]
371 fn test_resolve_with_attrs() {
372 let toml = r#"
373[defs.base]
374traits = ["Debug"]
375attrs = ['#[serde(rename_all = "camelCase")]']
376
377[defs.child]
378extends = "base"
379traits = ["Clone"]
380attrs = ['#[serde(default)]']
381"#;
382
383 let config = parser::parse(toml).unwrap();
384 let resolved = resolve(&config).unwrap();
385
386 let child = resolved.defs.get("child").unwrap();
387 assert_eq!(child.traits, vec!["Debug", "Clone"]);
388 assert_eq!(child.attrs.len(), 2);
389 assert!(
390 child
391 .attrs
392 .contains(&"#[serde(rename_all = \"camelCase\")]".to_string())
393 );
394 assert!(child.attrs.contains(&"#[serde(default)]".to_string()));
395 }
396
397 #[test]
398 fn test_resolve_empty() {
399 let config = parser::parse("").unwrap();
400 let resolved = resolve(&config).unwrap();
401 assert!(resolved.defs.is_empty());
402 }
403
404 #[test]
405 fn test_resolve_multiple_roots() {
406 let toml = r#"
407[defs.a]
408traits = ["Debug"]
409
410[defs.b]
411traits = ["Clone"]
412
413[defs.c]
414extends = "a"
415traits = ["PartialEq"]
416"#;
417
418 let config = parser::parse(toml).unwrap();
419 let resolved = resolve(&config).unwrap();
420
421 assert_eq!(resolved.defs.len(), 3);
422 assert_eq!(resolved.defs["a"].traits, vec!["Debug"]);
423 assert_eq!(resolved.defs["b"].traits, vec!["Clone"]);
424 assert_eq!(resolved.defs["c"].traits, vec!["Debug", "PartialEq"]);
425 }
426}