Carry retained source ownership through private parser phase checks

This commit is contained in:
2026-09-24 17:20:46 -07:00
parent 18073a6f2e
commit d11b99bb4a
6 changed files with 850 additions and 8 deletions
@@ -64,6 +64,10 @@ impl Budget {
failed: None,
})
}
/// Immutable exact policy snapshot for private deliberate-snapshot continuation.
pub(super) fn limits(&self) -> Limits {
self.limits
}
pub fn active(&self) -> Result<(), Error> {
self.failed.clone().map_or(Ok(()), Err)
}
@@ -27,6 +27,8 @@ use crate::language::{
use crate::source_project::{ProjectLimits, SourceBundle, SourceDiagnostic, traversal::Traversal};
use std::collections::BTreeMap;
mod carried;
struct ModelCounts {
limits: ProjectLimits,
graph: usize,
@@ -44,6 +46,8 @@ pub(super) struct ParsedMixedSyntax<'s> {
// Includes the conservative dropped value-end subtotal. The semantic
// phase adds syntax entries to this once, not tokens/positions again.
metadata: usize,
// Kept through consumed resolution/indexing; fixes the live backing owner.
carried: Option<carried::CarriedInput<'s>>,
}
struct FileSyntax<'s> {
@@ -246,6 +250,15 @@ fn parse_with_profile(
limits: ProjectLimits,
profile: Limits,
) -> Result<ParsedMixedSyntax<'_>, Vec<SourceDiagnostic>> {
parse_impl(bundle, limits, profile, None)
}
fn parse_impl<'s>(
bundle: &'s SourceBundle,
limits: ProjectLimits,
profile: Limits,
carried: Option<carried::CarriedInput<'s>>,
) -> Result<ParsedMixedSyntax<'s>, Vec<SourceDiagnostic>> {
let entry_source = bundle.files.get(&bundle.entry).map_or("", String::as_str);
let at_entry = |error| located(&bundle.entry, entry_source, ordinary_error(error));
let maximum = Limits::source8(ParseLimits {
@@ -266,6 +279,9 @@ fn parse_with_profile(
)));
}
let mut budget = Budget::new(profile).map_err(at_entry)?;
if let Some(input) = &carried {
input.check(&budget, 0, 0).map_err(at_entry)?;
}
let bytes = bundle
.files
.values()
@@ -275,9 +291,13 @@ fn parse_with_profile(
.index_bytes(bytes, Span::default())
.map_err(at_entry)?;
let mut metadata = bundle.files.len();
budget
.lowering_entries(metadata, Span::default())
.map_err(at_entry)?;
if let Some(input) = &carried {
input.check(&budget, 0, metadata).map_err(at_entry)?;
} else {
budget
.lowering_entries(metadata, Span::default())
.map_err(at_entry)?;
}
// Tighter private byte/file-slot limits precede folded-path table and
// traversal allocations. Public Traversal's own validation order is intact.
let mut traversal = Traversal::new(bundle, limits)?;
@@ -325,10 +345,16 @@ fn parse_with_profile(
.and_then(|n| n.checked_add(projection_slots))
.and_then(|n| n.checked_add(state.lexical_owners.len()))
.ok_or_else(|| failure(super::error(Span::default(), "Metadata size overflow.")))?;
state
.budget
.lowering_entries(metadata, Span::default())
.map_err(failure)?;
if let Some(input) = &carried {
input
.check(state.budget, state.budget.usage.entries, metadata)
.map_err(failure)?;
} else {
state
.budget
.lowering_entries(metadata, Span::default())
.map_err(failure)?;
}
let positions = language::edit_positions_from_tokens(
source,
&ordinary,
@@ -367,12 +393,18 @@ fn parse_with_profile(
}
traversal.finish()?;
budget.active().map_err(at_entry)?;
if let Some(input) = &carried {
input
.check(&budget, budget.usage.entries, metadata)
.map_err(at_entry)?;
}
Ok(ParsedMixedSyntax {
bundle,
limits,
files: parsed.into_values().collect(),
budget,
metadata,
carried,
})
}
@@ -0,0 +1,121 @@
//! Fixture-constructible carried phase authority, not an emitted-output result.
//! Checks existing logical phase totals; does not meter every parser allocation.
use super::*;
use project::accounting::{Ledger, Slots};
struct SnapshotPolicy {
limits: Limits,
project_limits: ProjectLimits,
}
/// The allocation-owning bundle guard. No production constructor exists yet.
struct RetainedSnapshot {
bundle: SourceBundle,
policy: SnapshotPolicy,
retained: Slots,
}
enum SnapshotBacking<'s> {
Borrowed {
bundle: &'s SourceBundle,
policy: SnapshotPolicy,
},
Retained(&'s RetainedSnapshot),
}
/// Neither Clone nor Copy: fixes the bundle, policy and retained allocation owner.
pub(super) struct CarriedInput<'s> {
backing: SnapshotBacking<'s>,
}
impl<'s> CarriedInput<'s> {
fn policy(&self) -> &SnapshotPolicy {
match &self.backing {
SnapshotBacking::Borrowed { policy, .. } => policy,
SnapshotBacking::Retained(owner) => &owner.policy,
}
}
fn bundle(&self) -> &'s SourceBundle {
match &self.backing {
SnapshotBacking::Borrowed { bundle, .. } => bundle,
SnapshotBacking::Retained(owner) => &owner.bundle,
}
}
pub(super) fn slots(&self) -> Slots {
match &self.backing {
SnapshotBacking::Borrowed { .. } => Slots::default(),
SnapshotBacking::Retained(owner) => owner.retained,
}
}
pub(super) fn check(
&self,
budget: &Budget,
syntax: usize,
metadata: usize,
) -> Result<(), super::super::Error> {
let at = Span::default();
let total = self
.slots()
.add(Slots::entries(syntax), at)?
.add(Slots::entries(metadata), at)?;
// A local check, not another reservation retained in the syntax owner.
Ledger::default().reserve(budget, total, at)
}
}
/// Input already binds the actual bundle; callers cannot supply a replacement.
fn parse_carried(input: CarriedInput<'_>) -> Result<ParsedMixedSyntax<'_>, Vec<SourceDiagnostic>> {
let bundle = input.bundle();
let policy = input.policy();
parse_impl(bundle, policy.project_limits, policy.limits, Some(input))
}
#[cfg(test)]
impl SnapshotPolicy {
fn captured(syntax: &ParsedMixedSyntax<'_>) -> Self {
Self {
limits: syntax.budget.limits(),
project_limits: syntax.limits,
}
}
}
#[cfg(test)]
impl RetainedSnapshot {
fn fixture(bundle: SourceBundle, policy: SnapshotPolicy) -> Result<Self, super::super::Error> {
let at = Span::default();
let mut text = bundle.entry.len();
for (path, source) in &bundle.files {
text = Slots::text(text)
.add(Slots::text(path.len()), at)?
.add(Slots::text(source.len()), at)?
.text;
}
let retained = Slots {
entries: bundle.files.len(),
occurrences: 0,
text,
};
Ok(Self {
bundle,
policy,
retained,
})
}
fn input(&self) -> CarriedInput<'_> {
CarriedInput {
backing: SnapshotBacking::Retained(self),
}
}
}
#[cfg(test)]
impl<'s> CarriedInput<'s> {
fn borrowed(bundle: &'s SourceBundle, syntax: &ParsedMixedSyntax<'_>) -> Self {
Self {
backing: SnapshotBacking::Borrowed {
bundle,
policy: SnapshotPolicy::captured(syntax),
},
}
}
}
#[cfg(test)]
mod tests;
@@ -0,0 +1,655 @@
use super::*;
use crate::language::analysis_source::budget::Usage;
fn doc(body: &str) -> String {
format!(
"arcadia 8;\nproject p \"Project\" id \"00000000-0000-0000-0000-000000000001\" {{\n{body}\n}}\n"
)
}
fn empty() -> SourceBundle {
SourceBundle {
entry: "main.arcadia".into(),
files: BTreeMap::from([("main.arcadia".into(), doc(""))]),
}
}
fn chain() -> SourceBundle {
SourceBundle {
entry: "main.arcadia".into(),
files: BTreeMap::from([
("main.arcadia".into(), doc("include \"part.arcadia\";")),
(
"part.arcadia".into(),
"include \"last.arcadia\";\r\n".into(),
),
("last.arcadia".into(), "//é\r\n".into()),
]),
}
}
fn profile(entries: usize, bytes: usize) -> Limits {
Limits {
entries,
bytes,
..Limits::source8(ParseLimits::UNLIMITED)
}
}
// Fixture-only policies may deliberately be too tight to admit the target.
// These are validated profiles, not a production old-owner transition.
fn fixture_policy(limits: Limits, project_limits: ProjectLimits) -> SnapshotPolicy {
SnapshotPolicy {
limits: Budget::new(limits).unwrap().limits(),
project_limits,
}
}
fn owner(bundle: SourceBundle, limits: Limits) -> RetainedSnapshot {
RetainedSnapshot::fixture(bundle, fixture_policy(limits, ProjectLimits::default())).unwrap()
}
fn parse_failure(result: Result<ParsedMixedSyntax<'_>, Vec<SourceDiagnostic>>) -> SourceDiagnostic {
let errors = match result {
Ok(_) => panic!("unexpected syntax success"),
Err(e) => e,
};
assert_eq!(errors.len(), 1);
errors.into_iter().next().unwrap()
}
fn resolve_failure(syntax: ParsedMixedSyntax<'_>) -> SourceDiagnostic {
let failure = match project::resolve(syntax) {
Ok(_) => panic!("unexpected resolution success"),
Err(e) => e,
};
assert_eq!(failure.diagnostics.len(), 1);
failure.diagnostics.into_iter().next().unwrap()
}
fn diagnostic(d: &SourceDiagnostic, path: &str, start: usize, end: usize, message: &str) {
assert_eq!(d.path, path);
assert_eq!(d.diagnostic.code, "invalid_source");
assert_eq!(d.diagnostic.span, Span { start, end });
assert_eq!(d.diagnostic.message, message);
assert!(d.related.is_empty());
}
fn expected_project() -> crate::Project {
crate::Project {
schema_version: 8,
id: Id::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
alias: "p".into(),
name: "Project".into(),
description: String::new(),
definitions: BTreeMap::new(),
elements: BTreeMap::new(),
relations: BTreeMap::new(),
diagrams: BTreeMap::new(),
artifact_links: BTreeMap::new(),
traces: BTreeMap::new(),
}
}
#[test]
fn real_retained_bundles_keep_exact_guard_work_and_once_only_resolved_baseline() {
for (bundle, c, u, m, live, entries, bytes) in [
(
empty(),
Slots {
entries: 1,
occurrences: 0,
text: 102,
},
Usage {
bytes: 78,
tokens: 19,
entries: 1,
references: 0,
deepest_frame: 0,
},
11,
Slots {
entries: 13,
occurrences: 0,
text: 110,
},
17,
112,
),
(
chain(),
Slots {
entries: 3,
occurrences: 0,
text: 180,
},
Usage {
bytes: 132,
tokens: 31,
entries: 3,
references: 0,
deepest_frame: 0,
},
19,
Slots {
entries: 25,
occurrences: 0,
text: 212,
},
29,
214,
),
] {
let owner = owner(bundle, profile(entries, bytes));
assert_eq!(owner.retained, c);
let original = owner.bundle.clone();
let parsed = parse_carried(owner.input()).unwrap();
assert_eq!(parsed.metadata, m);
assert_eq!(parsed.budget.usage, u);
assert_eq!(parsed.budget.numeric_units(), 0);
assert!(std::ptr::eq(parsed.bundle, &owner.bundle));
assert!(
matches!(&parsed.carried.as_ref().unwrap().backing,SnapshotBacking::Retained(retained) if std::ptr::eq(*retained,&owner))
);
let resolved = project::resolve(parsed).unwrap();
assert_eq!(resolved.project(), &expected_project());
assert!(resolved.warnings().is_empty());
let (syntax, stored) = resolved.phase_state();
assert_eq!(stored, live);
assert_eq!(syntax.budget.usage, u);
assert_eq!(syntax.carried.as_ref().unwrap().slots(), c);
assert_eq!(
syntax.files.iter().map(|f| f.path).collect::<Vec<_>>(),
original
.files
.keys()
.map(String::as_str)
.collect::<Vec<_>>()
);
drop(resolved);
assert_eq!(owner.bundle, original);
}
}
#[test]
fn independent_combined_syntax_caps_and_canonical_failure_files() {
for (bundle, cap, path) in [(empty(), 13, "main.arcadia"), (chain(), 25, "part.arcadia")] {
let pass = owner(bundle.clone(), profile(cap, 1024));
drop(parse_carried(pass.input()).unwrap());
let fail = owner(bundle, profile(cap - 1, 1024));
let d = parse_failure(parse_carried(fail.input()));
diagnostic(&d, path, 0, 1, "Source8 lowering entry limit exceeded.");
assert_eq!((d.diagnostic.line, d.diagnostic.column), (1, 1));
}
let owner = owner(chain(), profile(20, 1024));
diagnostic(
&parse_failure(parse_carried(owner.input())),
"main.arcadia",
0,
1,
"Source8 lowering entry limit exceeded.",
);
}
#[test]
fn legacy_zero_timing_is_preserved_but_explicit_zero_carry_still_combines() {
let bundle = empty();
let original =
parse_with_profile(&bundle, ProjectLimits::default(), profile(11, 1024)).unwrap();
diagnostic(
&parse_failure(parse_carried(CarriedInput::borrowed(&bundle, &original))),
"main.arcadia",
0,
1,
"Source8 lowering entry limit exceeded.",
);
diagnostic(
&resolve_failure(original),
"main.arcadia",
0,
0,
"Source8 lowering entry limit exceeded.",
);
let original =
parse_with_profile(&bundle, ProjectLimits::default(), profile(12, 1024)).unwrap();
let carried = parse_carried(CarriedInput::borrowed(&bundle, &original)).unwrap();
assert_eq!(carried.budget.usage, original.budget.usage);
assert_eq!(carried.metadata, 11);
}
#[test]
fn resolver_entry_and_catalog_text_caps_include_c_once_after_parser_success() {
for (bundle, entries, bytes) in [(empty(), 17, 112), (chain(), 29, 214)] {
for limits in [profile(entries - 1, bytes), profile(entries, bytes - 1)] {
let held = owner(bundle.clone(), limits);
let parsed = parse_carried(held.input()).unwrap();
diagnostic(
&resolve_failure(parsed),
"main.arcadia",
11,
73,
if limits.entries == entries - 1 {
"Source8 lowering entry limit exceeded."
} else {
"Source8 index/output byte limit exceeded."
},
);
drop(
project::resolve(
parse_with_profile(&bundle, ProjectLimits::default(), limits).unwrap(),
)
.unwrap(),
);
}
}
}
#[test]
fn syntax_text_accounting_is_after_decoding_and_has_precise_original_location() {
for (bundle, bytes, path, start, end) in [
(empty(), 109, "main.arcadia", 11, 73),
(chain(), 211, "part.arcadia", 0, 23),
] {
let held = owner(bundle, profile(100, bytes));
let parsed = parse_carried(held.input()).unwrap();
assert_eq!(
parsed
.files
.iter()
.find_map(|f| f.ordinary.header.as_ref())
.unwrap()
.project
.name,
"Project"
);
diagnostic(
&resolve_failure(parsed),
path,
start,
end,
"Source8 index/output byte limit exceeded.",
);
}
}
#[test]
fn initial_c_and_file_slots_are_checked_before_traversal() {
for (bundle, cap) in [(empty(), 1), (chain(), 5)] {
let held = owner(bundle, profile(cap, 1024));
diagnostic(
&parse_failure(parse_carried(held.input())),
"main.arcadia",
0,
1,
"Source8 lowering entry limit exceeded.",
);
}
for (cap, code) in [(1, "invalid_source"), (2, "project_limit")] {
let held = RetainedSnapshot::fixture(
empty(),
fixture_policy(
profile(cap, 1024),
ProjectLimits {
max_files: 0,
..ProjectLimits::default()
},
),
)
.unwrap();
assert_eq!(
parse_failure(parse_carried(held.input())).diagnostic.code,
code
);
}
let held = owner(empty(), profile(100, 101));
diagnostic(
&parse_failure(parse_carried(held.input())),
"main.arcadia",
0,
1,
"Source8 index/output byte limit exceeded.",
);
let held = owner(empty(), profile(100, 102));
drop(parse_carried(held.input()).unwrap());
}
#[test]
fn explicit_fault_injections_cover_occurrence_caps_and_unreachable_overflows() {
let mut held = owner(empty(), profile(100, 1024));
let maximum = held.policy.limits.entries + held.policy.limits.references;
// Not real bundle ownership: only a fault-injected dimension check.
held.retained.occurrences = maximum;
drop(parse_carried(held.input()).unwrap());
held.retained.occurrences = maximum + 1;
diagnostic(
&parse_failure(parse_carried(held.input())),
"main.arcadia",
0,
1,
"Source8 lowering occurrence limit exceeded.",
);
held.retained = Slots {
entries: usize::MAX,
occurrences: 0,
text: 0,
};
let budget = Budget::new(profile(100, 1024)).unwrap();
// Valid hard limits reject C first: direct injection reaches arithmetic.
assert_eq!(
held.input().check(&budget, 0, 1).unwrap_err().message,
"Mixed auxiliary storage size overflow."
);
held.retained = Slots::default();
assert_eq!(
held.input()
.check(&budget, usize::MAX, 1)
.unwrap_err()
.message,
"Mixed auxiliary storage size overflow."
);
assert_eq!(
Slots::text(usize::MAX)
.add(Slots::text(1), Span::default())
.unwrap_err()
.message,
"Mixed auxiliary storage size overflow."
);
let mut ledger = Ledger {
live: Slots::entries(usize::MAX),
};
assert_eq!(
ledger
.reserve(&budget, Slots::entries(1), Span::default())
.unwrap_err()
.message,
"Mixed auxiliary storage size overflow."
);
}
fn same_policy(syntax: &ParsedMixedSyntax<'_>, policy: &SnapshotPolicy) {
let a = syntax.budget.limits();
let b = policy.limits;
assert_eq!(
[a.bytes, a.tokens, a.entries, a.references, a.frames],
[b.bytes, b.tokens, b.entries, b.references, b.frames]
);
assert_eq!(a.numeric_units, b.numeric_units);
let a = syntax.limits;
let b = policy.project_limits;
assert_eq!(
[
a.max_files,
a.max_include_depth,
a.max_include_edges,
a.model.max_source_bytes,
a.model.max_graph_objects,
a.model.max_records,
a.model.max_step_references
],
[
b.max_files,
b.max_include_depth,
b.max_include_edges,
b.model.max_source_bytes,
b.model.max_graph_objects,
b.model.max_records,
b.model.max_step_references
]
);
}
#[test]
fn exact_policy_capture_preserves_every_field_and_starts_fresh_snapshot_work() {
let base = empty();
let next = chain();
let limits = Limits {
bytes: 4096,
tokens: 2048,
entries: 1024,
references: 128,
frames: 64,
numeric_units: 4096,
};
let caller = ProjectLimits {
max_files: 7,
max_include_depth: 5,
max_include_edges: 9,
model: ParseLimits {
max_source_bytes: 8192,
max_graph_objects: 10,
max_records: 30,
max_step_references: 256,
},
};
let seed = parse_with_profile(&base, caller, limits).unwrap();
assert_eq!(seed.budget.usage.bytes, 78);
let input = CarriedInput::borrowed(&next, &seed);
let captured = SnapshotPolicy::captured(&seed);
drop(seed);
let parsed = parse_carried(input).unwrap();
same_policy(&parsed, &captured);
assert_eq!(
parsed.budget.usage,
Usage {
bytes: 132,
tokens: 31,
entries: 3,
references: 0,
deepest_frame: 0
}
);
assert_eq!(parsed.budget.numeric_units(), 0);
let resolved = project::resolve(parsed).unwrap();
same_policy(resolved.phase_state().0, &captured);
}
#[test]
fn inherited_byte_token_and_traversal_limits_reach_the_real_parser() {
let seed_bundle = empty();
let bundle = chain();
for (bytes, tokens, success) in [(132, 31, true), (131, 31, false), (132, 30, false)] {
let limits = Limits {
tokens,
..profile(100, bytes)
};
let seed = parse_with_profile(&seed_bundle, ProjectLimits::default(), limits).unwrap();
let result = parse_carried(CarriedInput::borrowed(&bundle, &seed));
if success {
drop(project::resolve(result.unwrap()).unwrap());
} else {
let d = parse_failure(result);
if bytes == 131 {
diagnostic(
&d,
"main.arcadia",
0,
1,
"Source8 index/output byte limit exceeded.",
);
} else {
diagnostic(&d, "last.arcadia", 5, 6, "Source8 tokens limit exceeded.");
assert_eq!((d.diagnostic.line, d.diagnostic.column), (1, 5));
}
}
}
for (files, depth, edges, success) in [
(3, 2, 2, true),
(2, 2, 2, false),
(3, 1, 2, false),
(3, 2, 1, false),
] {
let caller = ProjectLimits {
max_files: files,
max_include_depth: depth,
max_include_edges: edges,
..ProjectLimits::default()
};
let seed = parse_with_profile(&seed_bundle, caller, profile(100, 1024)).unwrap();
let result = parse_carried(CarriedInput::borrowed(&bundle, &seed));
if success {
drop(project::resolve(result.unwrap()).unwrap());
} else {
let d = parse_failure(result);
assert_eq!(d.diagnostic.code, "project_limit");
if files == 2 {
assert_eq!(d.path, "main.arcadia");
} else {
assert_eq!(d.path, "part.arcadia");
assert_eq!(
d.diagnostic.span,
Span {
start: 0,
end: if edges == 1 { 1 } else { 23 }
}
);
}
}
}
}
fn typed(expression: &str) -> SourceBundle {
SourceBundle {
entry: "main.arcadia".into(),
files: BTreeMap::from([(
"main.arcadia".into(),
doc(&format!(
"calculation c \"C\" id \"00000000-0000-0000-0000-000000000002\" {{ input x id \"00000000-0000-0000-0000-000000000003\" : Number; result Number; body {expression}; }}"
)),
)]),
}
}
#[test]
fn captured_numeric_reference_frame_and_model_policies_are_behavioral_not_just_copied() {
let base = empty();
for (target, limits, expected) in [
(
typed("1"),
Limits {
numeric_units: 72,
..profile(1000, 4096)
},
"NumericUnits",
),
(
typed("$x"),
Limits {
references: 0,
..profile(1000, 4096)
},
"Source8 references limit exceeded.",
),
(
typed("abs(1)"),
Limits {
frames: 0,
..profile(1000, 4096)
},
"Source8 syntax frames limit exceeded.",
),
] {
let seed = parse_with_profile(&base, ProjectLimits::default(), limits).unwrap();
let d = parse_failure(parse_carried(CarriedInput::borrowed(&target, &seed)));
assert!(d.diagnostic.message.contains(expected), "{:?}", d);
}
for expression in ["1", "$x", "abs(1)"] {
let target = typed(expression);
let seed =
parse_with_profile(&base, ProjectLimits::default(), profile(1000, 4096)).unwrap();
drop(
project::resolve(parse_carried(CarriedInput::borrowed(&target, &seed)).unwrap())
.unwrap(),
);
}
// Nonzero old numeric work must not leak into the second snapshot.
let one = typed("1");
let limits = Limits {
numeric_units: 73,
..profile(1000, 4096)
};
let seed = parse_with_profile(&one, ProjectLimits::default(), limits).unwrap();
assert_eq!(seed.budget.numeric_units(), 73);
let parsed = parse_carried(CarriedInput::borrowed(&one, &seed)).unwrap();
assert_eq!(parsed.budget.numeric_units(), 73);
assert_eq!(parsed.budget.usage, seed.budget.usage);
for (graph, records, steps) in [(0, 10, 10), (10, 1, 10), (10, 10, 0)] {
let caller = ProjectLimits {
model: ParseLimits {
max_graph_objects: graph,
max_records: records,
max_step_references: steps,
..ParseLimits::UNLIMITED
},
..ProjectLimits::default()
};
let seed = parse_with_profile(
&base,
caller,
Limits::source8(ParseLimits {
max_step_references: steps,
..ParseLimits::UNLIMITED
}),
)
.unwrap();
let target = if steps == 0 { typed("$x") } else { typed("1") };
let errors = parse_carried(CarriedInput::borrowed(&target, &seed));
if steps == 0 {
assert!(
parse_failure(errors)
.diagnostic
.message
.contains("references")
);
} else {
assert!(parse_failure(errors).diagnostic.message.contains("limit"));
}
}
}
#[test]
fn zero_carried_mixed_oa_sa_unicode_results_and_index_ownership_match_legacy() {
for (activity, property, layer) in [
("oa_activity", "oa_property", "Oa"),
("sa_function", "sa_property", "Sa"),
] {
let body = format!(
"node n id \"00000000-0000-0000-0000-000000000004\" {{ diagram @d; element @v; }}\n{property} v \"Value\" id \"00000000-0000-0000-0000-000000000003\" {{ subject @w; type Number; base 1; }}\n{activity} w \"Work\" id \"00000000-0000-0000-0000-000000000002\" {{}}\ndiagram d \"Diagram\" id \"00000000-0000-0000-0000-000000000005\" {{ layer {layer}; }}"
);
let bundle = SourceBundle {
entry: "main.arcadia".into(),
files: BTreeMap::from([(
"main.arcadia".into(),
format!("// 雨\r\n{}", doc(&body).replace('\n', "\r\n")),
)]),
};
let legacy = parse(&bundle, ProjectLimits::default()).unwrap();
let carried = parse_carried(CarriedInput::borrowed(&bundle, &legacy)).unwrap();
assert_eq!(legacy.budget.usage, carried.budget.usage);
assert_eq!(
legacy.budget.numeric_units(),
carried.budget.numeric_units()
);
assert_eq!(legacy.metadata, carried.metadata);
let legacy = project::resolve(legacy).unwrap();
let carried = project::resolve(carried).unwrap();
assert_eq!(legacy.project(), carried.project());
assert_eq!(legacy.warnings(), carried.warnings());
assert_eq!(legacy.phase_state().1, carried.phase_state().1);
assert!(carried.phase_state().0.carried.is_some());
let legacy = legacy.index().unwrap().index_ordinary().unwrap();
let carried = carried.index().unwrap().index_ordinary().unwrap();
assert_eq!(legacy.names(), carried.names());
assert_eq!(legacy.typed_occurrences(), carried.typed_occurrences());
assert_eq!(
legacy.ordinary_occurrences(),
carried.ordinary_occurrences()
);
assert!(std::ptr::eq(carried.bundle(), &bundle));
}
}
#[test]
fn ordinary_position_key_copy_is_counted_at_existing_postparse_text_phase() {
let source = doc(
"oa_activity a \"A\" id \"00000000-0000-0000-0000-000000000002\" { description \"é\"; }",
);
let start = source.find("description \"é\";").unwrap();
let end = start + "description \"é\";".len();
let bundle = SourceBundle {
entry: "main.arcadia".into(),
files: BTreeMap::from([("main.arcadia".into(), source)]),
};
let retained_text = bundle.files["main.arcadia"].len() + 24;
// Existing text metric: project8 + alias/name2 + field-span key11 +
// AST field key11 + decoded string2 + projected field key11 =45.
// The final11-byte key is copied before resolve accounts this phase.
let held = owner(bundle, profile(1000, retained_text + 44));
let parsed = parse_carried(held.input()).unwrap();
assert_eq!(parsed.files[0].positions.len(), 1);
diagnostic(
&resolve_failure(parsed),
"main.arcadia",
start,
end,
"Source8 index/output byte limit exceeded.",
);
}
@@ -28,6 +28,10 @@ impl ResolvedMixedProject<'_> {
pub fn project(&self) -> &Project {
&self.project
}
#[cfg(test)]
pub(super) fn phase_state(&self) -> (&ParsedMixedSyntax<'_>, Slots) {
(&self.syntax, self.storage.live)
}
pub fn warnings(&self) -> &[SourceDiagnostic] {
&self.warnings
}
@@ -438,7 +442,12 @@ fn active_references(
pub(in crate::language::analysis_source) fn resolve(
mut syntax: ParsedMixedSyntax<'_>,
) -> Result<ResolvedMixedProject<'_>, Failure> {
let mut storage = Ledger::default();
let mut storage = Ledger {
live: syntax
.carried
.as_ref()
.map_or_else(Slots::default, |input| input.slots()),
};
let lower_storage = accounting::syntax(&syntax, &mut storage)?;
let catalog = catalog(&syntax, &mut storage)?;
let entry = entry(&syntax);