feat(validate): check parent concept exists for sub_concepts
Added validation that sub_concept declarations reference an existing parent concept. Produces clear error message with guidance to add the missing concept declaration.
This commit is contained in:
@@ -410,6 +410,40 @@ pub fn validate_schedule_composition(schedule: &Schedule, collector: &mut ErrorC
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate that all sub_concepts reference existing parent concepts
|
||||
pub fn validate_sub_concept_parents(file: &File, collector: &mut ErrorCollector) {
|
||||
// Collect all concept names
|
||||
let concept_names: HashSet<String> = file
|
||||
.declarations
|
||||
.iter()
|
||||
.filter_map(|decl| {
|
||||
if let Declaration::Concept(c) = decl {
|
||||
Some(c.name.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Check each sub_concept references an existing parent
|
||||
for decl in &file.declarations {
|
||||
if let Declaration::SubConcept(sc) = decl {
|
||||
if !concept_names.contains(&sc.parent_concept) {
|
||||
collector.add(ResolveError::ValidationError {
|
||||
message: format!(
|
||||
"Parent concept '{}' not found for sub_concept '{}.{}'",
|
||||
sc.parent_concept, sc.parent_concept, sc.name
|
||||
),
|
||||
help: Some(format!(
|
||||
"Add 'concept {}' before defining sub_concepts for it.",
|
||||
sc.parent_concept
|
||||
)),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate an entire file
|
||||
///
|
||||
/// Collects all validation errors and returns them together instead of failing
|
||||
@@ -417,6 +451,9 @@ pub fn validate_schedule_composition(schedule: &Schedule, collector: &mut ErrorC
|
||||
pub fn validate_file(file: &File, action_registry: &HashSet<String>) -> Result<()> {
|
||||
let mut collector = ErrorCollector::new();
|
||||
|
||||
// Type system validation
|
||||
validate_sub_concept_parents(file, &mut collector);
|
||||
|
||||
for decl in &file.declarations {
|
||||
match decl {
|
||||
| Declaration::Character(c) => {
|
||||
@@ -652,4 +689,106 @@ mod tests {
|
||||
validate_behavior_tree_actions(&tree, ®istry, &mut collector);
|
||||
assert!(collector.has_errors());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sub_concept_with_valid_parent() {
|
||||
let file = File {
|
||||
declarations: vec![
|
||||
Declaration::Concept(ConceptDecl {
|
||||
name: "Cup".to_string(),
|
||||
span: Span::new(0, 10),
|
||||
}),
|
||||
Declaration::SubConcept(SubConceptDecl {
|
||||
name: "Type".to_string(),
|
||||
parent_concept: "Cup".to_string(),
|
||||
kind: SubConceptKind::Enum {
|
||||
variants: vec![
|
||||
"Small".to_string(),
|
||||
"Medium".to_string(),
|
||||
"Large".to_string(),
|
||||
],
|
||||
},
|
||||
span: Span::new(20, 60),
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
let mut collector = ErrorCollector::new();
|
||||
validate_sub_concept_parents(&file, &mut collector);
|
||||
assert!(!collector.has_errors());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sub_concept_with_missing_parent() {
|
||||
let file = File {
|
||||
declarations: vec![Declaration::SubConcept(SubConceptDecl {
|
||||
name: "Type".to_string(),
|
||||
parent_concept: "Cup".to_string(),
|
||||
kind: SubConceptKind::Enum {
|
||||
variants: vec!["Small".to_string()],
|
||||
},
|
||||
span: Span::new(0, 40),
|
||||
})],
|
||||
};
|
||||
|
||||
let mut collector = ErrorCollector::new();
|
||||
validate_sub_concept_parents(&file, &mut collector);
|
||||
assert!(collector.has_errors());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_sub_concepts_same_valid_parent() {
|
||||
let file = File {
|
||||
declarations: vec![
|
||||
Declaration::Concept(ConceptDecl {
|
||||
name: "Cup".to_string(),
|
||||
span: Span::new(0, 10),
|
||||
}),
|
||||
Declaration::SubConcept(SubConceptDecl {
|
||||
name: "Type".to_string(),
|
||||
parent_concept: "Cup".to_string(),
|
||||
kind: SubConceptKind::Enum {
|
||||
variants: vec!["Mug".to_string()],
|
||||
},
|
||||
span: Span::new(20, 40),
|
||||
}),
|
||||
Declaration::SubConcept(SubConceptDecl {
|
||||
name: "Material".to_string(),
|
||||
parent_concept: "Cup".to_string(),
|
||||
kind: SubConceptKind::Enum {
|
||||
variants: vec!["Glass".to_string()],
|
||||
},
|
||||
span: Span::new(50, 70),
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
let mut collector = ErrorCollector::new();
|
||||
validate_sub_concept_parents(&file, &mut collector);
|
||||
assert!(!collector.has_errors());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sub_concept_wrong_parent_name() {
|
||||
let file = File {
|
||||
declarations: vec![
|
||||
Declaration::Concept(ConceptDecl {
|
||||
name: "Plate".to_string(),
|
||||
span: Span::new(0, 10),
|
||||
}),
|
||||
Declaration::SubConcept(SubConceptDecl {
|
||||
name: "Type".to_string(),
|
||||
parent_concept: "Cup".to_string(),
|
||||
kind: SubConceptKind::Enum {
|
||||
variants: vec!["Small".to_string()],
|
||||
},
|
||||
span: Span::new(20, 40),
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
let mut collector = ErrorCollector::new();
|
||||
validate_sub_concept_parents(&file, &mut collector);
|
||||
assert!(collector.has_errors());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user