Borrow compact insertion spans and repair omission test oracles
This commit is contained in:
@@ -1166,22 +1166,24 @@ fn inserted_value(text: &str, indent: &str, source: &str) -> String {
|
|||||||
// declaration field is itself omittable. For example, a selected String type
|
// declaration field is itself omittable. For example, a selected String type
|
||||||
// needs minimum_length; an Event Signal needs items. The complete tree remains
|
// needs minimum_length; an Event Signal needs items. The complete tree remains
|
||||||
// the comparison authority; this parallel tree is only an insertion template.
|
// the comparison authority; this parallel tree is only an insertion template.
|
||||||
struct InsertionSyntax {
|
struct InsertionSyntax<'a> {
|
||||||
values: BTreeMap<usize, String>,
|
source: &'a str,
|
||||||
|
values: BTreeMap<usize, Span>,
|
||||||
omitted_fields: BTreeSet<usize>,
|
omitted_fields: BTreeSet<usize>,
|
||||||
}
|
}
|
||||||
impl InsertionSyntax {
|
impl<'a> InsertionSyntax<'a> {
|
||||||
fn new(
|
fn new(
|
||||||
complete: &language::EditValue,
|
complete: &language::EditValue,
|
||||||
compact_values: &language::EditValue,
|
compact_values: &language::EditValue,
|
||||||
source: &str,
|
source: &'a str,
|
||||||
compact: &language::EditValue,
|
compact: &language::EditValue,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut result = Self {
|
let mut result = Self {
|
||||||
|
source,
|
||||||
values: BTreeMap::new(),
|
values: BTreeMap::new(),
|
||||||
omitted_fields: BTreeSet::new(),
|
omitted_fields: BTreeSet::new(),
|
||||||
};
|
};
|
||||||
result.collect(complete, compact_values, source);
|
result.collect(complete, compact_values);
|
||||||
if let (language::EditKind::Record(all), language::EditKind::Record(kept)) =
|
if let (language::EditKind::Record(all), language::EditKind::Record(kept)) =
|
||||||
(&complete.kind, &compact.kind)
|
(&complete.kind, &compact.kind)
|
||||||
{
|
{
|
||||||
@@ -1193,21 +1195,15 @@ impl InsertionSyntax {
|
|||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
fn collect(
|
fn collect(&mut self, complete: &language::EditValue, compact: &language::EditValue) {
|
||||||
&mut self,
|
// Borrow the existing compact text once. Copying each containing record
|
||||||
complete: &language::EditValue,
|
// here would duplicate a large nested payload at every depth.
|
||||||
compact: &language::EditValue,
|
self.values.insert(complete.span.start, compact.span);
|
||||||
source: &str,
|
|
||||||
) {
|
|
||||||
self.values.insert(
|
|
||||||
complete.span.start,
|
|
||||||
source[compact.span.start..compact.span.end].to_owned(),
|
|
||||||
);
|
|
||||||
match (&complete.kind, &compact.kind) {
|
match (&complete.kind, &compact.kind) {
|
||||||
(language::EditKind::Record(all), language::EditKind::Record(kept)) => {
|
(language::EditKind::Record(all), language::EditKind::Record(kept)) => {
|
||||||
for (key, field) in all {
|
for (key, field) in all {
|
||||||
if let Some(next) = kept.get(key) {
|
if let Some(next) = kept.get(key) {
|
||||||
self.collect(&field.value, &next.value, source);
|
self.collect(&field.value, &next.value);
|
||||||
} else {
|
} else {
|
||||||
self.omitted_fields.insert(field.value.span.start);
|
self.omitted_fields.insert(field.value.span.start);
|
||||||
}
|
}
|
||||||
@@ -1220,7 +1216,7 @@ impl InsertionSyntax {
|
|||||||
"compaction preserves array membership"
|
"compaction preserves array membership"
|
||||||
);
|
);
|
||||||
for (field, next) in all.iter().zip(kept) {
|
for (field, next) in all.iter().zip(kept) {
|
||||||
self.collect(field, next, source);
|
self.collect(field, next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -1232,7 +1228,7 @@ struct SyntaxUpdate<'a> {
|
|||||||
source: &'a str,
|
source: &'a str,
|
||||||
before: &'a str,
|
before: &'a str,
|
||||||
after: &'a str,
|
after: &'a str,
|
||||||
insertion: Option<&'a InsertionSyntax>,
|
insertion: Option<&'a InsertionSyntax<'a>>,
|
||||||
old_aliases: &'a BTreeMap<String, String>,
|
old_aliases: &'a BTreeMap<String, String>,
|
||||||
new_aliases: &'a BTreeMap<String, String>,
|
new_aliases: &'a BTreeMap<String, String>,
|
||||||
// Source3 migration only adds path qualification; the existing field identity
|
// Source3 migration only adds path qualification; the existing field identity
|
||||||
@@ -1243,8 +1239,12 @@ struct SyntaxUpdate<'a> {
|
|||||||
impl SyntaxUpdate<'_> {
|
impl SyntaxUpdate<'_> {
|
||||||
fn inserted(&self, value: &language::EditValue) -> &str {
|
fn inserted(&self, value: &language::EditValue) -> &str {
|
||||||
self.insertion
|
self.insertion
|
||||||
.and_then(|syntax| syntax.values.get(&value.span.start))
|
.and_then(|syntax| {
|
||||||
.map(String::as_str)
|
syntax
|
||||||
|
.values
|
||||||
|
.get(&value.span.start)
|
||||||
|
.map(|span| &syntax.source[span.start..span.end])
|
||||||
|
})
|
||||||
.unwrap_or(&self.after[value.span.start..value.span.end])
|
.unwrap_or(&self.after[value.span.start..value.span.end])
|
||||||
}
|
}
|
||||||
fn header(
|
fn header(
|
||||||
|
|||||||
@@ -3,6 +3,16 @@ use oa_core::{language, persistence, validation, *};
|
|||||||
fn read(source: &str) -> Project {
|
fn read(source: &str) -> Project {
|
||||||
language::parse(source).unwrap_or_else(|e| panic!("{e:?}\n{source}"))
|
language::parse(source).unwrap_or_else(|e| panic!("{e:?}\n{source}"))
|
||||||
}
|
}
|
||||||
|
fn replace_once(source: &str, before: &str, after: &str) -> String {
|
||||||
|
assert_eq!(
|
||||||
|
source.matches(before).count(),
|
||||||
|
1,
|
||||||
|
"unique mutation anchor {before:?}"
|
||||||
|
);
|
||||||
|
let changed = source.replacen(before, after, 1);
|
||||||
|
assert_ne!(changed, source, "test mutation must change the source");
|
||||||
|
changed
|
||||||
|
}
|
||||||
fn rejected(source: &str, fragment: &str) {
|
fn rejected(source: &str, fragment: &str) {
|
||||||
let errors = language::parse(source).expect_err(source);
|
let errors = language::parse(source).expect_err(source);
|
||||||
assert!(
|
assert!(
|
||||||
@@ -753,8 +763,8 @@ fn typed_events_vertices_and_transition_kinds_round_trip_in_both_layers() {
|
|||||||
source.push('}');
|
source.push('}');
|
||||||
let model = read(&source);
|
let model = read(&source);
|
||||||
let canonical = language::format(&model);
|
let canonical = language::format(&model);
|
||||||
|
assert!(!canonical.contains("transition_kind External;"));
|
||||||
for fragment in [
|
for fragment in [
|
||||||
"transition_kind External;",
|
|
||||||
"transition_kind Local;",
|
"transition_kind Local;",
|
||||||
"transition_kind Internal;",
|
"transition_kind Internal;",
|
||||||
"triggers [@deadline, @assess];",
|
"triggers [@deadline, @assess];",
|
||||||
@@ -776,6 +786,7 @@ fn typed_events_vertices_and_transition_kinds_round_trip_in_both_layers() {
|
|||||||
.find(|r| r.alias == "recover")
|
.find(|r| r.alias == "recover")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let RelationBody::StateTransition {
|
let RelationBody::StateTransition {
|
||||||
|
transition_kind,
|
||||||
trigger,
|
trigger,
|
||||||
guard,
|
guard,
|
||||||
effect,
|
effect,
|
||||||
@@ -784,32 +795,42 @@ fn typed_events_vertices_and_transition_kinds_round_trip_in_both_layers() {
|
|||||||
else {
|
else {
|
||||||
panic!("expected transition")
|
panic!("expected transition")
|
||||||
};
|
};
|
||||||
|
assert_eq!(*transition_kind, TransitionKind::External);
|
||||||
assert_eq!(trigger, "Operator confirms\nНе запускать код");
|
assert_eq!(trigger, "Operator confirms\nНе запускать код");
|
||||||
assert_eq!(guard, "available && status == \"Ready\"");
|
assert_eq!(guard, "available && status == \"Ready\"");
|
||||||
assert_eq!(effect, "Record the assessment");
|
assert_eq!(effect, "Record the assessment");
|
||||||
|
// External is omitted in compact output. Insert an explicit invalid
|
||||||
|
// discriminator into Recover using its unique, nondefault trigger list.
|
||||||
|
let invalid_kind = replace_once(
|
||||||
|
&canonical,
|
||||||
|
"triggers [@deadline, @assess];",
|
||||||
|
"transition_kind Unsupported;\n triggers [@deadline, @assess];",
|
||||||
|
);
|
||||||
|
rejected(&invalid_kind, "Unsupported");
|
||||||
for invalid in [
|
for invalid in [
|
||||||
canonical.replace("transition_kind External;", "transition_kind Unsupported;"),
|
replace_once(
|
||||||
canonical.replace(
|
&canonical,
|
||||||
"triggers [@deadline, @assess];",
|
"triggers [@deadline, @assess];",
|
||||||
"triggers [\"deadline\", @assess];",
|
"triggers [\"deadline\", @assess];",
|
||||||
),
|
),
|
||||||
canonical.replace(
|
replace_once(
|
||||||
|
&canonical,
|
||||||
"triggers [@deadline, @assess];",
|
"triggers [@deadline, @assess];",
|
||||||
"triggers [@deadline, @deadline];",
|
"triggers [@deadline, @deadline];",
|
||||||
),
|
),
|
||||||
canonical.replace("effects [@assess];", "effects [@payload];"),
|
replace_once(&canonical, "effects [@assess];", "effects [@payload];"),
|
||||||
canonical.replace("kind After;", "kind Relative;"),
|
replace_once(&canonical, "kind After;", "kind Relative;"),
|
||||||
canonical.replace("items [@payload];", "items [@assess];"),
|
replace_once(&canonical, "items [@payload];", "items [@assess];"),
|
||||||
] {
|
] {
|
||||||
assert!(
|
assert!(
|
||||||
language::parse(&invalid).is_err(),
|
language::parse(&invalid).is_err(),
|
||||||
"invalid typed state/event source accepted"
|
"invalid typed state/event source accepted"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let legacy = canonical
|
// Canonical source already omits External. Removing the supplied lists
|
||||||
.replace("transition_kind External;", "")
|
// independently exercises their empty omission defaults and retained prose.
|
||||||
.replace("triggers [@deadline, @assess];", "")
|
let legacy = replace_once(&canonical, "triggers [@deadline, @assess];", "");
|
||||||
.replace("effects [@assess];", "");
|
let legacy = replace_once(&legacy, "effects [@assess];", "");
|
||||||
let legacy = read(&legacy);
|
let legacy = read(&legacy);
|
||||||
let RelationBody::StateTransition {
|
let RelationBody::StateTransition {
|
||||||
transition_kind,
|
transition_kind,
|
||||||
@@ -829,7 +850,7 @@ fn typed_events_vertices_and_transition_kinds_round_trip_in_both_layers() {
|
|||||||
(retained_trigger, retained_guard, retained_effect),
|
(retained_trigger, retained_guard, retained_effect),
|
||||||
(trigger, guard, effect)
|
(trigger, guard, effect)
|
||||||
);
|
);
|
||||||
let draft = canonical.replace("expression \"30 s\";", "expression \"\";");
|
let draft = replace_once(&canonical, "expression \"30 s\";", "expression \"\";");
|
||||||
assert!(language::parse(&draft).is_ok());
|
assert!(language::parse(&draft).is_ok());
|
||||||
let deadline = model
|
let deadline = model
|
||||||
.elements
|
.elements
|
||||||
@@ -1038,7 +1059,7 @@ fn original_v2_text_and_json_preserve_identity_and_mixed_scenarios_after_extensi
|
|||||||
"fixture must cover actual old scenario content"
|
"fixture must cover actual old scenario content"
|
||||||
);
|
);
|
||||||
let canonical = language::format(&from_source);
|
let canonical = language::format(&from_source);
|
||||||
assert!(canonical.contains("viewpoint MixedLegacy;"));
|
assert!(!canonical.contains("viewpoint MixedLegacy;"));
|
||||||
for relation in from_source.relations.values() {
|
for relation in from_source.relations.values() {
|
||||||
if let RelationBody::StateTransition {
|
if let RelationBody::StateTransition {
|
||||||
transition_kind,
|
transition_kind,
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Ordinary default omission: reviewed follow-up
|
||||||
|
|
||||||
|
This branch-specific follow-up is based on production checkpoint `adffd19bbcf55c7cbfbf7a3937629db4c593e387`. It changes insertion storage and two language-test expectations. It is **not compiled, runtime-verified, integrated, or release-ready**. Previous runtime results in the [production report](text-default-omission-production-20260913.md) apply only to their captured inputs.
|
||||||
|
|
||||||
|
## Changes and independent review
|
||||||
|
|
||||||
|
`crates/oa-core/src/source_project.rs` now stores compact insertion spans and one borrowed source string instead of copying each value's substring. Keys still identify complete-after value starts; spans originate from parsing that exact compact string. Record/list matching, omitted-field decisions, fallback values, and final edit construction are unchanged. This removes repeated containing-payload copies by construction; no performance or memory benchmark is claimed.
|
||||||
|
|
||||||
|
The independent static critic checked parser-derived UTF-8 boundaries, source ownership, covariance and local borrow lifetimes. The compact string owner outlives each insertion/update operation, and copied spans do not borrow temporary syntax trees. No blocking static finding was reported. Rust type checking remains required.
|
||||||
|
|
||||||
|
`crates/oa-core/tests/language.rs` now expects canonical omission of External transition kind and MixedLegacy viewpoint. Existing whole-model, identity, legacy-default and round-trip checks remain. The invalid transition-kind test explicitly inserts Unsupported at a unique retained trigger-list anchor and checks the diagnostic. Every changed mutation checks that its anchor occurs exactly once and that the resulting source differs, preventing a silently ineffective replacement of an omitted field.
|
||||||
|
|
||||||
|
The independent critic reviewed both frozen files and found no blocking oracle defect. The parent inspected the actual diff and confirmed the reviewed hashes:
|
||||||
|
|
||||||
|
| File | SHA-256 |
|
||||||
|
| --- | --- |
|
||||||
|
| `crates/oa-core/src/source_project.rs` | `ee925b142c3072e2ad800922f43b6569a5c8537a037a6c0cd7533d9b9edefa6b` |
|
||||||
|
| `crates/oa-core/tests/language.rs` | `9ad8bd0d3804e75e1aefedb93895053e1ef27647c6169bc38a33a7d5950d7801` |
|
||||||
|
|
||||||
|
## Verification boundary
|
||||||
|
|
||||||
|
On Windows with the existing Rust 1.98.1 GNU LLVM toolchain, these checks passed for the two files above:
|
||||||
|
|
||||||
|
```text
|
||||||
|
rustfmt --edition 2024 --config skip_children=true --check crates/oa-core/src/source_project.rs crates/oa-core/tests/language.rs
|
||||||
|
git diff --check
|
||||||
|
```
|
||||||
|
|
||||||
|
No Cargo build or runtime gate was started for this follow-up. After the user reported cleanup, C: still reported approximately 4 MB free and the named retired build artifacts remained present. That observation does not establish which earlier cleanup commands ran. The user was asked for the latest manual script's output; the agent did not retry rejected deletion.
|
||||||
|
|
||||||
|
Required next execution is the changed language tests, source-update/preservation regressions, corrected port-anchor test including re-add behavior, and private policy/parser tests, followed by the full core suite and strict production Clippy. Retain the existing exact-input capture workflow and preserve failed results. GUI/public-consumer and combined source8 verification remain separate acceptance requirements. Linux remains user-paused.
|
||||||
|
|
||||||
|
Live ownership, blockers and handoffs remain in Gitea issue 11; the source8 partition remains in issue 7. This note records the changed artifact and evidence boundary rather than a replacement task ledger.
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
# Ordinary compact source: production checkpoint, verification incomplete
|
# Ordinary compact source: production checkpoint, verification incomplete
|
||||||
|
|
||||||
|
The [later reviewed follow-up](text-default-omission-followup-20260913.md) implements borrowed insertion spans and repairs language-test expectations. Its runtime verification is pending; the evidence below describes the original checkpoint.
|
||||||
|
|
||||||
Branch: `codex/text-defaults-9e8r6t`, based on `2a64344b929077c873cb81c4f0b36c6bb3ae1ffe`. This checkpoint implements ordinary source defaults and lossless editing, but is **not ready for integration or release acceptance**. The focused production run passed 75 tests. The full core build exhausted disk space before all targets linked. Subsequent execution of 36 completed integration binaries yielded 354 passed, 2 failed, 1 ignored; one failure was subsequently resolved and rerun, while the other has a reviewed test correction awaiting compilation.
|
Branch: `codex/text-defaults-9e8r6t`, based on `2a64344b929077c873cb81c4f0b36c6bb3ae1ffe`. This checkpoint implements ordinary source defaults and lossless editing, but is **not ready for integration or release acceptance**. The focused production run passed 75 tests. The full core build exhausted disk space before all targets linked. Subsequent execution of 36 completed integration binaries yielded 354 passed, 2 failed, 1 ignored; one failure was subsequently resolved and rerun, while the other has a reviewed test correction awaiting compilation.
|
||||||
|
|
||||||
## Behavior and architecture
|
## Behavior and architecture
|
||||||
|
|||||||
Reference in New Issue
Block a user