Migrate frozen data contracts and authored source to schema 4

This commit is contained in:
2026-09-06 03:39:02 +03:00
parent c5276b1943
commit 1230ab8a3e
21 changed files with 2006 additions and 126 deletions
@@ -0,0 +1,125 @@
//! Source3 data upgrade through token edits; authored modules, nesting and trivia survive.
use super::*;
pub(super) fn upgrade(
current: &CompiledSourceProject,
limits: ProjectLimits,
) -> Result<CompiledSourceProject, Vec<SourceDiagnostic>> {
let report = current
.decoded
.migration
.as_ref()
.expect("an explicit source migration report");
let project = &current.decoded.project;
let canonical: BTreeMap<_, _> = language::format_declarations(project).into_iter().collect();
let changed: BTreeSet<_> = report
.semantic_defaults
.iter()
.map(|default| default.object)
.collect();
let aliases: BTreeMap<_, _> = project
.identities()
.into_iter()
.map(|(id, alias)| (alias.to_owned(), id.to_string()))
.collect();
let mut edits: Edits = BTreeMap::new();
for (path, source) in &current.bundle.files {
let declarations =
language::edit_declarations(source, path == &current.bundle.entry, limits.model)
.map_err(|diagnostic| {
vec![SourceDiagnostic {
path: path.clone(),
diagnostic: *diagnostic,
related: vec![],
}]
})?;
for (id, actual) in declarations {
if !changed.contains(&id) {
continue;
}
let Some(after) = canonical.get(&id) else {
continue;
};
let mut next = language::edit_declaration(after).map_err(|diagnostic| {
vec![SourceDiagnostic {
path: path.clone(),
diagnostic: *diagnostic,
related: vec![],
}]
})?;
if actual.lexical_owner.is_some()
&& let language::EditKind::Record(fields) = &actual.fields.kind
&& !fields.contains_key("parent")
&& let language::EditKind::Record(fields) = &mut next.fields.kind
{
fields.remove("parent");
}
let update = SyntaxUpdate {
source,
before: source,
after,
old_aliases: &aliases,
new_aliases: &aliases,
legacy_value_paths: true,
};
update.value(
&actual.fields,
Some(&actual.fields),
&next.fields,
edits.entry(path.clone()).or_default(),
);
}
}
let source = &current.bundle.files[&current.bundle.entry];
let version = significant(source)
.get(1)
.map(|token| token.span)
.expect("a parsed version token");
edits
.entry(current.bundle.entry.clone())
.or_default()
.push((version, "4".into()));
// New allocation relations are emitted into the allocating interface's
// existing module, outside its lexical element body. Existing source never moves.
let mut added: BTreeMap<String, String> = BTreeMap::new();
for allocation in &report.generated_interface_allocations {
let path = current
.index
.declarations
.get(&allocation.source)
.map(|location| location.path.clone())
.unwrap_or_else(|| current.bundle.entry.clone());
let ending = newline(&current.bundle.files[&path]);
let text = added.entry(path).or_default();
text.push_str(ending);
text.push_str(&canonical[&allocation.id].replace('\n', ending));
}
for (path, text) in added {
let offset = if path == current.bundle.entry {
current.index.project_body.span.end
} else {
current.bundle.files[&path].len()
};
edits.entry(path).or_default().push((
Span {
start: offset,
end: offset,
},
text,
));
}
let mut bundle = current.bundle.clone();
apply_edits(&mut bundle, edits)?;
let (mut result, insertions) = compile_once(&bundle, limits)?;
if !insertions.is_empty() || result.decoded.project != *project {
return Err(vec![problem(
&bundle,
&bundle.entry,
Span::default(),
"migration_source_mismatch",
"Targeted source3 upgrade did not reproduce the migrated project.",
)]);
}
result.decoded.migration = Some(report.clone());
Ok(result)
}