feat(source): persist typed semantic reviews in schema five

This commit is contained in:
2026-09-06 04:46:41 +03:00
parent ab02b1b255
commit ab960cbebb
24 changed files with 1223 additions and 74 deletions
+50 -6
View File
@@ -1,6 +1,6 @@
//! Pure, bounded source projects. All includes resolve against supplied UTF-8
//! documents; this module never reads a filesystem, URL, or process directory.
use crate::{Id, Kind, Project, language, persistence::DecodeResult, validation};
use crate::{Id, Kind, Project, SCHEMA_VERSION, language, persistence::DecodeResult, validation};
use language::{Diagnostic, ParseLimits, Span};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
@@ -280,7 +280,7 @@ pub fn lint_bundle(bundle: &SourceBundle, limits: ProjectLimits) -> Vec<SourceDi
}
}
/// Compile reachable source3/4 files, explicitly upgrade data contracts, and stabilize UUIDs.
/// Compile reachable source3/4/5 files, explicitly upgrade contracts, and stabilize UUIDs.
/// Missing/unreachable files and every admission or engineering error leave the
/// input untouched. V1/V2 single-file migration remains in `language::parse`.
pub fn compile_bundle(
@@ -720,7 +720,7 @@ fn apply_edits(bundle: &mut SourceBundle, edits: Edits) -> Result<(), Vec<Source
Ok(())
}
/// Canonical, readable source4 export. Like `language::format`, the caller is
/// Canonical, readable current-version export. Like `language::format`, the caller is
/// responsible for supplying a valid model; compilation checks the result.
/// Existing source layout is preserved by `update_bundle`, not this formatter.
pub fn format_bundle(project: &Project) -> SourceBundle {
@@ -736,7 +736,7 @@ pub fn format_bundle(project: &Project) -> SourceBundle {
.push_str(&unindent(&declaration));
}
let mut entry = format!(
"arcadia 4;\n{}{{\n description {};\n",
"arcadia {SCHEMA_VERSION};\n{}{{\n description {};\n",
project_header(project),
quote(&project.description)
);
@@ -926,6 +926,7 @@ pub fn update_bundle_with_identity_map(
old_aliases: &old_aliases,
new_aliases: &new_aliases,
legacy_value_paths: false,
identity_origins: Some(&origins),
};
let local = edits.entry(path.clone()).or_default();
update.header(&actual.header, &previous.header, &next.header, local);
@@ -955,6 +956,7 @@ pub fn update_bundle_with_identity_map(
old_aliases: &old_aliases,
new_aliases: &new_aliases,
legacy_value_paths: false,
identity_origins: Some(&origins),
};
let mut local = Vec::new();
update.header(
@@ -1126,6 +1128,7 @@ struct SyntaxUpdate<'a> {
// Source3 migration only adds path qualification; the existing field identity
// must keep its authored argument trivia during that one-time transformation.
legacy_value_paths: bool,
identity_origins: Option<&'a BTreeMap<Id, Id>>,
}
impl SyntaxUpdate<'_> {
fn header(
@@ -1221,6 +1224,18 @@ impl SyntaxUpdate<'_> {
alias.clone(),
));
}
(EditKind::HistoricalUuid(_), EditKind::HistoricalUuid(id)) => {
let token = significant(&self.source[actual.span.start..actual.span.end])
.pop()
.expect("historical UUID string");
edits.push((
Span {
start: actual.span.start + token.span.start,
end: actual.span.start + token.span.end,
},
quote(&id.to_string()),
));
}
(EditKind::Atom, EditKind::Atom) => edits.push((
actual.span,
self.after[after.span.start..after.span.end].into(),
@@ -1345,11 +1360,27 @@ impl SyntaxUpdate<'_> {
let previous = before.unwrap_or(&[]);
let old_keys: Option<Vec<_>> = previous
.iter()
.map(|item| item_key(item, self.before, self.old_aliases, self.legacy_value_paths))
.map(|item| {
item_key(
item,
self.before,
self.old_aliases,
self.legacy_value_paths,
None,
)
})
.collect();
let new_keys: Option<Vec<_>> = after
.iter()
.map(|item| item_key(item, self.after, self.new_aliases, self.legacy_value_paths))
.map(|item| {
item_key(
item,
self.after,
self.new_aliases,
self.legacy_value_paths,
self.identity_origins,
)
})
.collect();
let keyed = old_keys.zip(new_keys).filter(|(old, new)| {
old.iter().collect::<BTreeSet<_>>().len() == old.len()
@@ -1457,9 +1488,22 @@ fn item_key(
source: &str,
aliases: &BTreeMap<String, String>,
legacy_value_paths: bool,
origins: Option<&BTreeMap<Id, Id>>,
) -> Option<String> {
use language::EditKind;
if matches!(value.kind, EditKind::Record(_))
&& let Some(key) = language::semantic_row_key(
&source[value.span.start..value.span.end],
origins.unwrap_or(&BTreeMap::new()),
)
{
return Some(key);
}
match &value.kind {
EditKind::HistoricalUuid(id) => Some(format!(
"uuid:{}",
origins.and_then(|map| map.get(id)).unwrap_or(id)
)),
EditKind::Reference(alias) => aliases.get(alias).map(|id| format!("ref:{id}")),
EditKind::Atom => Some(format!(
"literal:{}",