feat(resolver): implement cross-file template resolution
Enable characters to inherit from templates defined in different files across the project structure. - Add file_index field to NameEntry to track declaration source files - Update NameTable::from_files() to set file indices when merging tables - Change conversion pipeline to pass &[ast::File] instead of flat arrays - Update merge functions to use two-level indexing: all_files[entry.file_index].declarations[entry.decl_index] - Update all affected tests to use new signatures
This commit is contained in:
10
src/lib.rs
10
src/lib.rs
@@ -173,12 +173,14 @@ impl Project {
|
||||
// Validate and convert all files
|
||||
let mut resolved_files = Vec::new();
|
||||
|
||||
for file in parsed_files {
|
||||
for file in &parsed_files {
|
||||
// First validate
|
||||
validate::validate_file(&file, &action_registry)?;
|
||||
validate::validate_file(file, &action_registry)?;
|
||||
|
||||
// Then convert AST to resolved types
|
||||
let declarations = resolve::convert::convert_file(&file)?;
|
||||
// Then convert AST to resolved types using the global name table and all parsed
|
||||
// files
|
||||
let declarations =
|
||||
resolve::convert::convert_file_with_all_files(file, &name_table, &parsed_files)?;
|
||||
|
||||
resolved_files.push(ResolvedFile { declarations });
|
||||
}
|
||||
|
||||
@@ -29,31 +29,45 @@ use crate::{
|
||||
/// Convert a parsed file into resolved declarations
|
||||
///
|
||||
/// This is the old version that doesn't handle template composition.
|
||||
/// Use `convert_file_with_templates` for full template support.
|
||||
/// Use `convert_file_with_name_table` for full template support.
|
||||
pub fn convert_file(file: &ast::File) -> Result<Vec<ResolvedDeclaration>> {
|
||||
// Use the template-aware version
|
||||
convert_file_with_templates(file)
|
||||
// Build name table for this file only (for backward compatibility)
|
||||
let name_table = NameTable::from_file(file)?;
|
||||
convert_file_with_name_table(file, &name_table)
|
||||
}
|
||||
|
||||
/// Convert a parsed file into resolved declarations with template composition
|
||||
/// support
|
||||
pub fn convert_file_with_templates(file: &ast::File) -> Result<Vec<ResolvedDeclaration>> {
|
||||
// Build name table for template lookups
|
||||
let name_table = NameTable::from_file(file)?;
|
||||
/// Convert a parsed file into resolved declarations with a provided name table
|
||||
///
|
||||
/// This allows using a global name table that contains declarations from
|
||||
/// multiple files.
|
||||
pub fn convert_file_with_name_table(
|
||||
file: &ast::File,
|
||||
name_table: &NameTable,
|
||||
) -> Result<Vec<ResolvedDeclaration>> {
|
||||
// For backward compatibility, use only this file (wrapped in a Vec for the API)
|
||||
convert_file_with_all_files(file, name_table, std::slice::from_ref(file))
|
||||
}
|
||||
|
||||
/// Convert a parsed file with access to all files
|
||||
///
|
||||
/// This is needed for cross-file template resolution.
|
||||
pub fn convert_file_with_all_files(
|
||||
file: &ast::File,
|
||||
name_table: &NameTable,
|
||||
all_files: &[ast::File],
|
||||
) -> Result<Vec<ResolvedDeclaration>> {
|
||||
let mut resolved = Vec::new();
|
||||
|
||||
for decl in &file.declarations {
|
||||
match decl {
|
||||
| ast::Declaration::Character(c) => {
|
||||
// Use template-aware conversion
|
||||
let resolved_char =
|
||||
convert_character_with_templates(c, &file.declarations, &name_table)?;
|
||||
let resolved_char = convert_character_with_templates(c, all_files, name_table)?;
|
||||
resolved.push(ResolvedDeclaration::Character(resolved_char));
|
||||
},
|
||||
| ast::Declaration::Template(t) => {
|
||||
// Use include-aware conversion
|
||||
let resolved_template =
|
||||
convert_template_with_includes(t, &file.declarations, &name_table)?;
|
||||
let resolved_template = convert_template_with_includes(t, all_files, name_table)?;
|
||||
resolved.push(ResolvedDeclaration::Template(resolved_template));
|
||||
},
|
||||
| ast::Declaration::LifeArc(la) => {
|
||||
@@ -96,6 +110,7 @@ pub fn convert_character(character: &ast::Character) -> Result<ResolvedCharacter
|
||||
|
||||
Ok(ResolvedCharacter {
|
||||
name: character.name.clone(),
|
||||
species: None,
|
||||
fields,
|
||||
prose_blocks,
|
||||
span: character.span.clone(),
|
||||
@@ -111,12 +126,12 @@ pub fn convert_character(character: &ast::Character) -> Result<ResolvedCharacter
|
||||
/// 4. Applying character's own fields on top
|
||||
pub fn convert_character_with_templates(
|
||||
character: &ast::Character,
|
||||
declarations: &[ast::Declaration],
|
||||
all_files: &[ast::File],
|
||||
name_table: &NameTable,
|
||||
) -> Result<ResolvedCharacter> {
|
||||
// Merge character templates if any
|
||||
let merged_fields = if character.template.is_some() {
|
||||
merge::merge_character_templates(character, declarations, name_table)?
|
||||
merge::merge_character_templates(character, all_files, name_table)?
|
||||
} else {
|
||||
character.fields.clone()
|
||||
};
|
||||
@@ -126,6 +141,7 @@ pub fn convert_character_with_templates(
|
||||
|
||||
Ok(ResolvedCharacter {
|
||||
name: character.name.clone(),
|
||||
species: None,
|
||||
fields,
|
||||
prose_blocks,
|
||||
span: character.span.clone(),
|
||||
@@ -151,13 +167,13 @@ pub fn convert_template(template: &ast::Template) -> Result<ResolvedTemplate> {
|
||||
/// 3. Adding template's own fields on top
|
||||
pub fn convert_template_with_includes(
|
||||
template: &ast::Template,
|
||||
declarations: &[ast::Declaration],
|
||||
all_files: &[ast::File],
|
||||
name_table: &NameTable,
|
||||
) -> Result<ResolvedTemplate> {
|
||||
// Resolve template includes if any
|
||||
let merged_fields = if !template.includes.is_empty() {
|
||||
let mut visited = std::collections::HashSet::new();
|
||||
merge::resolve_template_includes(template, declarations, name_table, &mut visited)?
|
||||
merge::resolve_template_includes(template, all_files, name_table, &mut visited)?
|
||||
} else {
|
||||
template.fields.clone()
|
||||
};
|
||||
@@ -328,6 +344,7 @@ mod tests {
|
||||
fn test_convert_simple_character() {
|
||||
let character = Character {
|
||||
name: "Martha".to_string(),
|
||||
species: None,
|
||||
fields: vec![
|
||||
Field {
|
||||
name: "age".to_string(),
|
||||
@@ -363,6 +380,7 @@ mod tests {
|
||||
|
||||
let character = Character {
|
||||
name: "Martha".to_string(),
|
||||
species: None,
|
||||
fields: vec![
|
||||
Field {
|
||||
name: "age".to_string(),
|
||||
@@ -392,6 +410,7 @@ mod tests {
|
||||
fn test_convert_character_duplicate_field_fails() {
|
||||
let character = Character {
|
||||
name: "Martha".to_string(),
|
||||
species: None,
|
||||
fields: vec![
|
||||
Field {
|
||||
name: "age".to_string(),
|
||||
@@ -434,6 +453,7 @@ mod tests {
|
||||
declarations: vec![
|
||||
ast::Declaration::Character(Character {
|
||||
name: "Martha".to_string(),
|
||||
species: None,
|
||||
fields: vec![Field {
|
||||
name: "age".to_string(),
|
||||
value: Value::Int(34),
|
||||
@@ -474,6 +494,7 @@ mod tests {
|
||||
}),
|
||||
ast::Declaration::Character(Character {
|
||||
name: "Martha".to_string(),
|
||||
species: None,
|
||||
fields: vec![],
|
||||
template: None,
|
||||
|
||||
@@ -555,6 +576,7 @@ mod tests {
|
||||
|
||||
let character = Character {
|
||||
name: "Martha".to_string(),
|
||||
species: None,
|
||||
fields: vec![Field {
|
||||
name: "age".to_string(),
|
||||
value: Value::Int(34),
|
||||
@@ -574,8 +596,8 @@ mod tests {
|
||||
};
|
||||
let name_table = NameTable::from_file(&file).unwrap();
|
||||
|
||||
let resolved =
|
||||
convert_character_with_templates(&character, &declarations, &name_table).unwrap();
|
||||
let files = vec![file.clone()];
|
||||
let resolved = convert_character_with_templates(&character, &files, &name_table).unwrap();
|
||||
|
||||
assert_eq!(resolved.name, "Martha");
|
||||
assert_eq!(resolved.fields.len(), 2);
|
||||
@@ -616,6 +638,7 @@ mod tests {
|
||||
|
||||
let character = Character {
|
||||
name: "Martha".to_string(),
|
||||
species: None,
|
||||
fields: vec![
|
||||
Field {
|
||||
name: "height".to_string(),
|
||||
@@ -643,8 +666,8 @@ mod tests {
|
||||
};
|
||||
let name_table = NameTable::from_file(&file).unwrap();
|
||||
|
||||
let resolved =
|
||||
convert_character_with_templates(&character, &declarations, &name_table).unwrap();
|
||||
let files = vec![file.clone()];
|
||||
let resolved = convert_character_with_templates(&character, &files, &name_table).unwrap();
|
||||
|
||||
assert_eq!(resolved.name, "Martha");
|
||||
assert_eq!(resolved.fields.len(), 2);
|
||||
@@ -690,8 +713,8 @@ mod tests {
|
||||
};
|
||||
let name_table = NameTable::from_file(&file).unwrap();
|
||||
|
||||
let resolved =
|
||||
convert_template_with_includes(&derived, &declarations, &name_table).unwrap();
|
||||
let files = vec![file.clone()];
|
||||
let resolved = convert_template_with_includes(&derived, &files, &name_table).unwrap();
|
||||
|
||||
assert_eq!(resolved.name, "Person");
|
||||
assert_eq!(resolved.fields.len(), 2);
|
||||
@@ -706,6 +729,7 @@ mod tests {
|
||||
fn test_convert_character_reserved_keyword_fails() {
|
||||
let character = Character {
|
||||
name: "Martha".to_string(),
|
||||
species: None,
|
||||
fields: vec![Field {
|
||||
name: "species".to_string(), // Reserved keyword!
|
||||
value: Value::String("human".to_string()),
|
||||
@@ -744,6 +768,7 @@ mod tests {
|
||||
|
||||
let character = Character {
|
||||
name: "Martha".to_string(),
|
||||
species: None,
|
||||
fields: vec![], // No fields - inherits range from template
|
||||
template: Some(vec!["Person".to_string()]),
|
||||
span: Span::new(0, 100),
|
||||
@@ -759,7 +784,8 @@ mod tests {
|
||||
};
|
||||
let name_table = NameTable::from_file(&file).unwrap();
|
||||
|
||||
let result = convert_character_with_templates(&character, &declarations, &name_table);
|
||||
let files = vec![file.clone()];
|
||||
let result = convert_character_with_templates(&character, &files, &name_table);
|
||||
assert!(result.is_err());
|
||||
if let Err(ResolveError::ValidationError { message, .. }) = result {
|
||||
assert!(message.contains("strict template"));
|
||||
|
||||
@@ -182,9 +182,9 @@ fn test_behavior_tree_end_to_end() {
|
||||
fn test_schedule_end_to_end() {
|
||||
let source = r#"
|
||||
schedule DailyRoutine {
|
||||
08:00 -> 12:00: work
|
||||
12:00 -> 13:00: lunch
|
||||
13:00 -> 17:00: work
|
||||
08:00 -> 12:00: work { }
|
||||
12:00 -> 13:00: lunch { }
|
||||
13:00 -> 17:00: work { }
|
||||
}
|
||||
"#;
|
||||
|
||||
@@ -335,8 +335,8 @@ Martha grew up in a small town.
|
||||
}
|
||||
|
||||
schedule DailyRoutine {
|
||||
08:00 -> 12:00: work
|
||||
12:00 -> 13:00: lunch
|
||||
08:00 -> 12:00: work { }
|
||||
12:00 -> 13:00: lunch { }
|
||||
}
|
||||
"#;
|
||||
|
||||
|
||||
@@ -89,9 +89,9 @@ fn valid_unique_fields() -> impl Strategy<Value = Vec<Field>> {
|
||||
fn valid_character() -> impl Strategy<Value = Character> {
|
||||
(valid_ident(), valid_unique_fields()).prop_map(|(name, fields)| Character {
|
||||
name,
|
||||
species: None,
|
||||
fields,
|
||||
template: None,
|
||||
|
||||
span: Span::new(0, 100),
|
||||
})
|
||||
}
|
||||
@@ -192,7 +192,8 @@ proptest! {
|
||||
val2 in valid_value()
|
||||
) {
|
||||
let character = Character {
|
||||
name,
|
||||
name: name.clone(),
|
||||
species: None,
|
||||
fields: vec![
|
||||
Field {
|
||||
name: field_name.clone(),
|
||||
@@ -239,6 +240,7 @@ proptest! {
|
||||
fn test_empty_character_converts(name in valid_ident()) {
|
||||
let character = Character {
|
||||
name: name.clone(),
|
||||
species: None,
|
||||
fields: vec![],
|
||||
template: None,
|
||||
|
||||
@@ -268,7 +270,7 @@ proptest! {
|
||||
|
||||
#[test]
|
||||
fn test_file_with_use_declarations_skips_them(
|
||||
characters in prop::collection::vec(valid_character(), 1..5),
|
||||
char_count in 1usize..5,
|
||||
use_count in 0usize..5
|
||||
) {
|
||||
let mut declarations = vec![];
|
||||
@@ -282,9 +284,16 @@ proptest! {
|
||||
}));
|
||||
}
|
||||
|
||||
// Add characters
|
||||
let char_count = characters.len();
|
||||
declarations.extend(characters.into_iter().map(Declaration::Character));
|
||||
// Add characters with unique names to avoid duplicate definition errors
|
||||
for i in 0..char_count {
|
||||
declarations.push(Declaration::Character(Character {
|
||||
name: format!("Char{}", i),
|
||||
species: None,
|
||||
fields: vec![],
|
||||
template: None,
|
||||
span: Span::new(0, 100),
|
||||
}));
|
||||
}
|
||||
|
||||
let file = File { declarations };
|
||||
let resolved = convert_file(&file).unwrap();
|
||||
@@ -308,6 +317,7 @@ mod edge_cases {
|
||||
) {
|
||||
let character = Character {
|
||||
name: "Test".to_string(),
|
||||
species: None,
|
||||
fields: vec![
|
||||
Field {
|
||||
name: "int_field".to_string(),
|
||||
@@ -351,6 +361,7 @@ mod edge_cases {
|
||||
) {
|
||||
let character = Character {
|
||||
name: name.clone(),
|
||||
species: None,
|
||||
fields: vec![Field {
|
||||
name: field_name.clone(),
|
||||
value: Value::String(string_val.clone()),
|
||||
|
||||
@@ -123,7 +123,7 @@ fn test_all_declaration_kinds() {
|
||||
state s {}
|
||||
}
|
||||
schedule S {
|
||||
10:00 -> 11:00: activity
|
||||
10:00 -> 11:00: activity { }
|
||||
}
|
||||
behavior B {
|
||||
action
|
||||
|
||||
@@ -458,9 +458,9 @@ proptest! {
|
||||
prop_oneof![
|
||||
valid_ident().prop_map(|name| Declaration::Character(Character {
|
||||
name,
|
||||
species: None,
|
||||
fields: vec![],
|
||||
template: None,
|
||||
|
||||
span: Span::new(0, 10),
|
||||
})),
|
||||
valid_ident().prop_map(|name| Declaration::Template(Template {
|
||||
|
||||
@@ -42,7 +42,7 @@ use crate::{
|
||||
/// Returns the fully merged fields for this template
|
||||
pub fn resolve_template_includes(
|
||||
template: &Template,
|
||||
declarations: &[Declaration],
|
||||
all_files: &[crate::syntax::ast::File],
|
||||
name_table: &NameTable,
|
||||
visited: &mut HashSet<String>,
|
||||
) -> Result<Vec<Field>> {
|
||||
@@ -69,8 +69,8 @@ pub fn resolve_template_includes(
|
||||
suggestion: name_table.find_suggestion(include_name),
|
||||
})?;
|
||||
|
||||
// Get the template declaration
|
||||
let included_template = match &declarations[entry.decl_index] {
|
||||
// Get the template declaration using two-level indexing
|
||||
let included_template = match &all_files[entry.file_index].declarations[entry.decl_index] {
|
||||
| Declaration::Template(t) => t,
|
||||
| _ => {
|
||||
return Err(ResolveError::ValidationError {
|
||||
@@ -88,7 +88,7 @@ pub fn resolve_template_includes(
|
||||
|
||||
// Recursively resolve the included template
|
||||
let included_fields =
|
||||
resolve_template_includes(included_template, declarations, name_table, visited)?;
|
||||
resolve_template_includes(included_template, all_files, name_table, visited)?;
|
||||
|
||||
// Merge included fields (replacing any existing fields with same name)
|
||||
merged_fields = merge_field_lists(merged_fields, included_fields);
|
||||
@@ -114,7 +114,7 @@ pub fn resolve_template_includes(
|
||||
/// Returns the fully merged fields for this character
|
||||
pub fn merge_character_templates(
|
||||
character: &Character,
|
||||
declarations: &[Declaration],
|
||||
all_files: &[crate::syntax::ast::File],
|
||||
name_table: &NameTable,
|
||||
) -> Result<Vec<Field>> {
|
||||
let mut merged_fields = Vec::new();
|
||||
@@ -131,8 +131,8 @@ pub fn merge_character_templates(
|
||||
suggestion: name_table.find_suggestion(template_name),
|
||||
})?;
|
||||
|
||||
// Get the template declaration
|
||||
let template = match &declarations[entry.decl_index] {
|
||||
// Get the template declaration using two-level indexing
|
||||
let template = match &all_files[entry.file_index].declarations[entry.decl_index] {
|
||||
| Declaration::Template(t) => t,
|
||||
| _ => {
|
||||
return Err(ResolveError::ValidationError {
|
||||
@@ -156,7 +156,7 @@ pub fn merge_character_templates(
|
||||
// Resolve template (which handles includes recursively)
|
||||
let mut visited = HashSet::new();
|
||||
let template_fields =
|
||||
resolve_template_includes(template, declarations, name_table, &mut visited)?;
|
||||
resolve_template_includes(template, all_files, name_table, &mut visited)?;
|
||||
|
||||
// Merge template fields into accumulated fields
|
||||
merged_fields = merge_field_lists(merged_fields, template_fields);
|
||||
@@ -506,6 +506,7 @@ mod tests {
|
||||
fn make_character(name: &str, fields: Vec<Field>, templates: Vec<&str>) -> Character {
|
||||
Character {
|
||||
name: name.to_string(),
|
||||
species: None,
|
||||
fields,
|
||||
template: if templates.is_empty() {
|
||||
None
|
||||
@@ -523,8 +524,9 @@ mod tests {
|
||||
let name_table = NameTable::from_file(&make_file(declarations.clone())).unwrap();
|
||||
let mut visited = HashSet::new();
|
||||
|
||||
let files = vec![make_file(declarations.clone())];
|
||||
let result =
|
||||
resolve_template_includes(&template, &declarations, &name_table, &mut visited).unwrap();
|
||||
resolve_template_includes(&template, &files, &name_table, &mut visited).unwrap();
|
||||
|
||||
assert_eq!(result.len(), 1);
|
||||
assert_eq!(result[0].name, "age");
|
||||
@@ -543,8 +545,9 @@ mod tests {
|
||||
let name_table = NameTable::from_file(&make_file(declarations.clone())).unwrap();
|
||||
let mut visited = HashSet::new();
|
||||
|
||||
let files = vec![make_file(declarations.clone())];
|
||||
let result =
|
||||
resolve_template_includes(&derived, &declarations, &name_table, &mut visited).unwrap();
|
||||
resolve_template_includes(&derived, &files, &name_table, &mut visited).unwrap();
|
||||
|
||||
assert_eq!(result.len(), 2);
|
||||
assert!(result.iter().any(|f| f.name == "age"));
|
||||
@@ -565,8 +568,8 @@ mod tests {
|
||||
let name_table = NameTable::from_file(&make_file(declarations.clone())).unwrap();
|
||||
let mut visited = HashSet::new();
|
||||
|
||||
let result =
|
||||
resolve_template_includes(&top, &declarations, &name_table, &mut visited).unwrap();
|
||||
let files = vec![make_file(declarations.clone())];
|
||||
let result = resolve_template_includes(&top, &files, &name_table, &mut visited).unwrap();
|
||||
|
||||
assert_eq!(result.len(), 3);
|
||||
assert!(result.iter().any(|f| f.name == "alive"));
|
||||
@@ -591,8 +594,9 @@ mod tests {
|
||||
let name_table = NameTable::from_file(&make_file(declarations.clone())).unwrap();
|
||||
let mut visited = HashSet::new();
|
||||
|
||||
let files = vec![make_file(declarations.clone())];
|
||||
let result =
|
||||
resolve_template_includes(&derived, &declarations, &name_table, &mut visited).unwrap();
|
||||
resolve_template_includes(&derived, &files, &name_table, &mut visited).unwrap();
|
||||
|
||||
assert_eq!(result.len(), 1);
|
||||
assert_eq!(result[0].name, "age");
|
||||
@@ -611,7 +615,8 @@ mod tests {
|
||||
];
|
||||
let name_table = NameTable::from_file(&make_file(declarations.clone())).unwrap();
|
||||
|
||||
let result = merge_character_templates(&character, &declarations, &name_table).unwrap();
|
||||
let files = vec![make_file(declarations.clone())];
|
||||
let result = merge_character_templates(&character, &files, &name_table).unwrap();
|
||||
|
||||
assert_eq!(result.len(), 1);
|
||||
assert_eq!(result[0].name, "age");
|
||||
@@ -636,7 +641,8 @@ mod tests {
|
||||
];
|
||||
let name_table = NameTable::from_file(&make_file(declarations.clone())).unwrap();
|
||||
|
||||
let result = merge_character_templates(&character, &declarations, &name_table).unwrap();
|
||||
let files = vec![make_file(declarations.clone())];
|
||||
let result = merge_character_templates(&character, &files, &name_table).unwrap();
|
||||
|
||||
assert_eq!(result.len(), 2);
|
||||
assert!(result
|
||||
@@ -664,7 +670,8 @@ mod tests {
|
||||
];
|
||||
let name_table = NameTable::from_file(&make_file(declarations.clone())).unwrap();
|
||||
|
||||
let result = merge_character_templates(&character, &declarations, &name_table).unwrap();
|
||||
let files = vec![make_file(declarations.clone())];
|
||||
let result = merge_character_templates(&character, &files, &name_table).unwrap();
|
||||
|
||||
assert_eq!(result.len(), 2);
|
||||
assert!(result
|
||||
@@ -686,7 +693,8 @@ mod tests {
|
||||
];
|
||||
let name_table = NameTable::from_file(&make_file(declarations.clone())).unwrap();
|
||||
|
||||
let result = merge_character_templates(&character, &declarations, &name_table);
|
||||
let files = vec![make_file(declarations.clone())];
|
||||
let result = merge_character_templates(&character, &files, &name_table);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
@@ -710,7 +718,8 @@ mod tests {
|
||||
];
|
||||
let name_table = NameTable::from_file(&make_file(declarations.clone())).unwrap();
|
||||
|
||||
let result = merge_character_templates(&character, &declarations, &name_table);
|
||||
let files = vec![make_file(declarations.clone())];
|
||||
let result = merge_character_templates(&character, &files, &name_table);
|
||||
assert!(result.is_err());
|
||||
if let Err(ResolveError::ValidationError { message, .. }) = result {
|
||||
assert!(message.contains("strict template"));
|
||||
@@ -727,7 +736,8 @@ mod tests {
|
||||
let name_table = NameTable::from_file(&make_file(declarations.clone())).unwrap();
|
||||
let mut visited = HashSet::new();
|
||||
|
||||
let result = resolve_template_includes(&a, &declarations, &name_table, &mut visited);
|
||||
let files = vec![make_file(declarations.clone())];
|
||||
let result = resolve_template_includes(&a, &files, &name_table, &mut visited);
|
||||
assert!(result.is_err());
|
||||
if let Err(ResolveError::CircularDependency { .. }) = result {
|
||||
// Expected
|
||||
|
||||
@@ -45,7 +45,10 @@ pub struct NameEntry {
|
||||
pub kind: DeclKind,
|
||||
pub qualified_path: QualifiedPath,
|
||||
pub span: Span,
|
||||
/// Index into the original declarations vector
|
||||
/// Index of the file this declaration came from (when building from
|
||||
/// multiple files)
|
||||
pub file_index: usize,
|
||||
/// Index into the file's declarations vector
|
||||
pub decl_index: usize,
|
||||
}
|
||||
|
||||
@@ -157,6 +160,7 @@ impl NameTable {
|
||||
kind,
|
||||
qualified_path,
|
||||
span,
|
||||
file_index: 0, // Single file, so file_index is always 0
|
||||
decl_index: index,
|
||||
},
|
||||
);
|
||||
@@ -282,8 +286,12 @@ impl NameTable {
|
||||
pub fn from_files(files: &[File]) -> Result<Self> {
|
||||
let mut combined = NameTable::new();
|
||||
|
||||
for file in files {
|
||||
let table = NameTable::from_file(file)?;
|
||||
for (file_index, file) in files.iter().enumerate() {
|
||||
let mut table = NameTable::from_file(file)?;
|
||||
// Update all entries to have the correct file_index
|
||||
for entry in table.entries.values_mut() {
|
||||
entry.file_index = file_index;
|
||||
}
|
||||
combined.merge(table)?;
|
||||
}
|
||||
|
||||
@@ -311,6 +319,7 @@ mod tests {
|
||||
declarations: vec![
|
||||
Declaration::Character(Character {
|
||||
name: "Martha".to_string(),
|
||||
species: None,
|
||||
fields: vec![],
|
||||
template: None,
|
||||
|
||||
@@ -339,6 +348,7 @@ mod tests {
|
||||
declarations: vec![
|
||||
Declaration::Character(Character {
|
||||
name: "Martha".to_string(),
|
||||
species: None,
|
||||
fields: vec![],
|
||||
template: None,
|
||||
|
||||
@@ -346,6 +356,7 @@ mod tests {
|
||||
}),
|
||||
Declaration::Character(Character {
|
||||
name: "Martha".to_string(),
|
||||
species: None,
|
||||
fields: vec![],
|
||||
template: None,
|
||||
|
||||
@@ -363,6 +374,7 @@ mod tests {
|
||||
let file = File {
|
||||
declarations: vec![Declaration::Character(Character {
|
||||
name: "Martha".to_string(),
|
||||
species: None,
|
||||
fields: vec![],
|
||||
template: None,
|
||||
|
||||
@@ -393,6 +405,7 @@ mod tests {
|
||||
}),
|
||||
Declaration::Character(Character {
|
||||
name: "characters".to_string(),
|
||||
species: None,
|
||||
fields: vec![],
|
||||
template: None,
|
||||
|
||||
@@ -467,6 +480,7 @@ mod tests {
|
||||
let file1 = File {
|
||||
declarations: vec![Declaration::Character(Character {
|
||||
name: "Martha".to_string(),
|
||||
species: None,
|
||||
fields: vec![],
|
||||
template: None,
|
||||
|
||||
@@ -477,6 +491,7 @@ mod tests {
|
||||
let file2 = File {
|
||||
declarations: vec![Declaration::Character(Character {
|
||||
name: "David".to_string(),
|
||||
species: None,
|
||||
fields: vec![],
|
||||
template: None,
|
||||
|
||||
@@ -499,6 +514,7 @@ mod tests {
|
||||
let file1 = File {
|
||||
declarations: vec![Declaration::Character(Character {
|
||||
name: "Martha".to_string(),
|
||||
species: None,
|
||||
fields: vec![],
|
||||
template: None,
|
||||
|
||||
@@ -509,6 +525,7 @@ mod tests {
|
||||
let file2 = File {
|
||||
declarations: vec![Declaration::Character(Character {
|
||||
name: "Martha".to_string(),
|
||||
species: None,
|
||||
fields: vec![],
|
||||
template: None,
|
||||
|
||||
@@ -529,6 +546,7 @@ mod tests {
|
||||
File {
|
||||
declarations: vec![Declaration::Character(Character {
|
||||
name: "Martha".to_string(),
|
||||
species: None,
|
||||
fields: vec![],
|
||||
template: None,
|
||||
|
||||
@@ -538,6 +556,7 @@ mod tests {
|
||||
File {
|
||||
declarations: vec![Declaration::Character(Character {
|
||||
name: "David".to_string(),
|
||||
species: None,
|
||||
fields: vec![],
|
||||
template: None,
|
||||
|
||||
@@ -558,6 +577,7 @@ mod tests {
|
||||
File {
|
||||
declarations: vec![Declaration::Character(Character {
|
||||
name: "Martha".to_string(),
|
||||
species: None,
|
||||
fields: vec![],
|
||||
template: None,
|
||||
|
||||
@@ -567,6 +587,7 @@ mod tests {
|
||||
File {
|
||||
declarations: vec![Declaration::Character(Character {
|
||||
name: "Martha".to_string(),
|
||||
species: None,
|
||||
fields: vec![],
|
||||
template: None,
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ fn valid_character_decl() -> impl Strategy<Value = (String, Declaration)> {
|
||||
valid_ident().prop_map(|name| {
|
||||
let decl = Declaration::Character(Character {
|
||||
name: name.clone(),
|
||||
species: None,
|
||||
fields: vec![],
|
||||
template: None,
|
||||
|
||||
@@ -157,6 +158,7 @@ proptest! {
|
||||
let declarations: Vec<_> = (0..count).map(|i| {
|
||||
Declaration::Character(Character {
|
||||
name: name.clone(),
|
||||
species: None,
|
||||
fields: vec![],
|
||||
template: None,
|
||||
|
||||
@@ -177,6 +179,7 @@ proptest! {
|
||||
declarations: vec![
|
||||
Declaration::Character(Character {
|
||||
name: name.clone(),
|
||||
species: None,
|
||||
fields: vec![],
|
||||
template: None,
|
||||
|
||||
@@ -254,6 +257,7 @@ proptest! {
|
||||
declarations: vec![
|
||||
Declaration::Character(Character {
|
||||
name: name.clone(),
|
||||
species: None,
|
||||
fields: vec![],
|
||||
template: None,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user