Reserve retained parser entries at private producer boundaries

This commit is contained in:
2026-09-24 19:25:08 -07:00
parent 97a8703adc
commit a629ae01d4
6 changed files with 735 additions and 10 deletions
+64 -4
View File
@@ -1028,6 +1028,18 @@ fn edit_positions_from_tokens_with_text(
count: usize, count: usize,
value_ends: Option<&BTreeMap<usize, usize>>, value_ends: Option<&BTreeMap<usize, usize>>,
text: impl FnMut(usize, Span) -> Result<(), Box<Diagnostic>>, text: impl FnMut(usize, Span) -> Result<(), Box<Diagnostic>>,
) -> Result<Vec<(Id, EditDeclaration)>, Box<Diagnostic>> {
edit_positions_from_tokens_with_producers(source, unit, token, count, value_ends, None, text)
}
fn edit_positions_from_tokens_with_producers(
source: &str,
unit: &SourceUnit,
token: impl Fn(usize) -> Lexeme,
count: usize,
value_ends: Option<&BTreeMap<usize, usize>>,
mut quota: Option<usize>,
text: impl FnMut(usize, Span) -> Result<(), Box<Diagnostic>>,
) -> Result<Vec<(Id, EditDeclaration)>, Box<Diagnostic>> { ) -> Result<Vec<(Id, EditDeclaration)>, Box<Diagnostic>> {
let mut ends: Vec<_> = (0..count).map(|at| token(at).span.end).collect(); let mut ends: Vec<_> = (0..count).map(|at| token(at).span.end).collect();
let mut stack = Vec::new(); let mut stack = Vec::new();
@@ -1044,10 +1056,27 @@ fn edit_positions_from_tokens_with_text(
ends: Vec<usize>, ends: Vec<usize>,
value_ends: Option<&'a BTreeMap<usize, usize>>, value_ends: Option<&'a BTreeMap<usize, usize>>,
text: H, text: H,
source: &'a str,
quota: &'a mut Option<usize>,
} }
impl<F: Fn(usize) -> Lexeme, H: FnMut(usize, Span) -> Result<(), Box<Diagnostic>>> impl<F: Fn(usize) -> Lexeme, H: FnMut(usize, Span) -> Result<(), Box<Diagnostic>>>
Positions<'_, F, H> Positions<'_, F, H>
{ {
fn consume(&mut self, slots: usize, offset: usize) -> Result<(), Box<Diagnostic>> {
if let Some(remaining) = self.quota {
*remaining = remaining.checked_sub(slots).ok_or_else(|| {
Box::new(diagnostic(
self.source,
Error {
code: "invalid_source",
offset,
message: "Source8 projection quota underflow.".into(),
},
))
})?;
}
Ok(())
}
fn token(&self, start: usize) -> usize { fn token(&self, start: usize) -> usize {
let mut low = 0; let mut low = 0;
let mut high = self.ends.len(); let mut high = self.ends.len();
@@ -1068,6 +1097,7 @@ fn edit_positions_from_tokens_with_text(
fields fields
.iter() .iter()
.map(|(key, ast)| { .map(|(key, ast)| {
self.consume(1, ast.offset())?;
let value = self.value(ast)?; let value = self.value(ast)?;
let start = (self.token_at)(self.token(value.span.start) - 1).span.start; let start = (self.token_at)(self.token(value.span.start) - 1).span.start;
let end = (self.token_at)(self.token(value.span.end)).span.end; let end = (self.token_at)(self.token(value.span.end)).span.end;
@@ -1083,6 +1113,7 @@ fn edit_positions_from_tokens_with_text(
.collect() .collect()
} }
fn value(&mut self, ast: &Ast) -> Result<EditValue, Box<Diagnostic>> { fn value(&mut self, ast: &Ast) -> Result<EditValue, Box<Diagnostic>> {
self.consume(1, ast.offset())?;
let index = self.token(ast.offset()); let index = self.token(ast.offset());
let span = Span { let span = Span {
start: ast.offset(), start: ast.offset(),
@@ -1119,8 +1150,11 @@ fn edit_positions_from_tokens_with_text(
ends, ends,
value_ends, value_ends,
text, text,
source,
quota: &mut quota,
}; };
unit.declarations let result = unit
.declarations
.iter() .iter()
.map(|declaration| { .map(|declaration| {
if declaration.id_insertion.is_some() { if declaration.id_insertion.is_some() {
@@ -1133,6 +1167,7 @@ fn edit_positions_from_tokens_with_text(
}, },
))); )));
} }
positions.consume(7, declaration.span.start)?;
let roles: &[&str] = if matches!(declaration.kind, DeclKind::Node) { let roles: &[&str] = if matches!(declaration.kind, DeclKind::Node) {
&["kind", "alias", "id_keyword", "id"] &["kind", "alias", "id_keyword", "id"]
} else { } else {
@@ -1161,7 +1196,18 @@ fn edit_positions_from_tokens_with_text(
}, },
)) ))
}) })
.collect() .collect::<Result<Vec<_>, _>>()?;
if quota.is_some_and(|remaining| remaining != 0) {
return Err(Box::new(diagnostic(
source,
Error {
code: "invalid_source",
offset: 0,
message: "Source8 projection quota leftover.".into(),
},
)));
}
Ok(result)
} }
struct Parser<'a> { struct Parser<'a> {
source: &'a str, source: &'a str,
@@ -1337,6 +1383,10 @@ impl Parser<'_> {
.span .span
.end; .end;
self.take(";")?; self.take(";")?;
#[cfg(test)]
if let Some(mixed) = &mut self.mixed {
mixed.projection(1, Span { start, end })?;
}
Ok((key, value, Span { start, end })) Ok((key, value, Span { start, end }))
} }
@@ -1370,7 +1420,12 @@ impl Parser<'_> {
.span .span
.end; .end;
if let Some(mixed) = &mut self.mixed { if let Some(mixed) = &mut self.mixed {
mixed.value_ends.insert(ast.offset(), end); let span = Span {
start: ast.offset(),
end,
};
mixed.value_end(span)?;
mixed.projection(1, span)?;
} }
} }
result result
@@ -1697,7 +1752,12 @@ impl Parser<'_> {
let lexical_owner = open.last().map(|&owner| declarations[owner].id); let lexical_owner = open.last().map(|&owner| declarations[owner].id);
#[cfg(test)] #[cfg(test)]
if let Some(mixed) = &mut self.mixed { if let Some(mixed) = &mut self.mixed {
mixed.lexical_owners.push(open.last().copied()); let span = Span {
start: offset,
end: body_start,
};
mixed.lexical_owner(open.last().copied(), span)?;
mixed.projection(7, span)?;
} }
declarations.push(Declaration { declarations.push(Declaration {
kind, kind,
@@ -39,6 +39,11 @@ pub struct Budget {
numeric: WorkMeter, numeric: WorkMeter,
failed: Option<Error>, failed: Option<Error>,
text: Option<ProducerText>, text: Option<ProducerText>,
entries: Option<ProducerEntries>,
}
struct ProducerEntries {
carried: usize,
metadata: usize,
} }
struct ProducerText { struct ProducerText {
carried: usize, carried: usize,
@@ -69,8 +74,64 @@ impl Budget {
), ),
failed: None, failed: None,
text: None, text: None,
entries: None,
}) })
} }
pub(super) fn enable_entry_producers(
&mut self,
carried: usize,
files: usize,
) -> Result<(), Error> {
assert!(self.entries.is_none());
self.check_producer_entries(carried, files, 0, Span::default())?;
self.entries = Some(ProducerEntries {
carried,
metadata: files,
});
Ok(())
}
fn check_producer_entries(
&mut self,
carried: usize,
metadata: usize,
delta: usize,
span: Span,
) -> Result<(), Error> {
self.active()?;
let total = carried
.checked_add(self.usage.entries)
.and_then(|n| n.checked_add(metadata))
.and_then(|n| n.checked_add(delta));
let message = match total {
None => Some("Source8 producer entry size overflow."),
Some(n) if n > self.limits.entries => Some("Source8 lowering entry limit exceeded."),
_ => None,
};
if let Some(message) = message {
let failure = error(span, message);
self.failed = Some(failure.clone());
return Err(failure);
}
Ok(())
}
pub(super) fn producer_entries(&self) -> Option<usize> {
self.entries.as_ref().map(|e| e.metadata)
}
pub(super) fn reserve_metadata(&mut self, delta: usize, span: Span) -> Result<(), Error> {
if let Some(e) = &self.entries {
self.check_producer_entries(e.carried, e.metadata, delta, span)?;
self.entries.as_mut().expect("enabled entries").metadata += delta;
}
Ok(())
}
pub(super) fn producer_entry_overflow(&mut self, span: Span) -> Error {
if let Some(failure) = &self.failed {
return failure.clone();
}
let failure = error(span, "Source8 producer entry size overflow.");
self.failed = Some(failure.clone());
failure
}
pub(super) fn enable_text_producers(&mut self, carried: usize) -> Result<(), Error> { pub(super) fn enable_text_producers(&mut self, carried: usize) -> Result<(), Error> {
self.index_bytes(carried, Span::default())?; self.index_bytes(carried, Span::default())?;
assert!(self.text.is_none()); assert!(self.text.is_none());
@@ -200,6 +261,9 @@ impl Budget {
span, span,
"syntax entries", "syntax entries",
)?; )?;
if let Some(e) = &self.entries {
self.check_producer_entries(e.carried, e.metadata, 1, span)?;
}
self.usage.entries += 1; self.usage.entries += 1;
Ok(()) Ok(())
} }
@@ -68,8 +68,11 @@ pub(in crate::language) struct State<'a> {
budget: &'a mut Budget, budget: &'a mut Budget,
model: &'a mut ModelCounts, model: &'a mut ModelCounts,
typed: Vec<declarations::Declaration>, typed: Vec<declarations::Declaration>,
pub(in crate::language) value_ends: BTreeMap<usize, usize>, value_ends: BTreeMap<usize, usize>,
pub(in crate::language) lexical_owners: Vec<Option<usize>>, lexical_owners: Vec<Option<usize>>,
pending_projection: usize,
reserved_values: usize,
reserved_owners: usize,
} }
fn ordinary_error(error: super::Error) -> Error { fn ordinary_error(error: super::Error) -> Error {
@@ -93,6 +96,42 @@ fn lexeme(token: lex::Token) -> Lexeme {
} }
impl State<'_> { impl State<'_> {
pub(in crate::language) fn projection(
&mut self,
slots: usize,
span: Span,
) -> Result<(), Error> {
if self.budget.producer_entries().is_some() {
self.pending_projection = self
.pending_projection
.checked_add(slots)
.ok_or_else(|| ordinary_error(self.budget.producer_entry_overflow(span)))?;
}
Ok(())
}
pub(in crate::language) fn value_end(&mut self, span: Span) -> Result<(), Error> {
let new_key = !self.value_ends.contains_key(&span.start);
self.budget
.reserve_metadata(usize::from(new_key), span)
.map_err(ordinary_error)?;
if new_key {
self.reserved_values += 1;
}
self.value_ends.insert(span.start, span.end);
Ok(())
}
pub(in crate::language) fn lexical_owner(
&mut self,
owner: Option<usize>,
span: Span,
) -> Result<(), Error> {
self.budget
.reserve_metadata(1, span)
.map_err(ordinary_error)?;
self.lexical_owners.push(owner);
self.reserved_owners += 1;
Ok(())
}
pub(in crate::language) fn own_text(&mut self, bytes: usize, span: Span) -> Result<(), Error> { pub(in crate::language) fn own_text(&mut self, bytes: usize, span: Span) -> Result<(), Error> {
self.budget.own_text(bytes, span).map_err(ordinary_error) self.budget.own_text(bytes, span).map_err(ordinary_error)
} }
@@ -263,7 +302,7 @@ fn parse_with_profile(
limits: ProjectLimits, limits: ProjectLimits,
profile: Limits, profile: Limits,
) -> Result<ParsedMixedSyntax<'_>, Vec<SourceDiagnostic>> { ) -> Result<ParsedMixedSyntax<'_>, Vec<SourceDiagnostic>> {
parse_impl(bundle, limits, profile, None, false) parse_impl(bundle, limits, profile, None, false, false)
} }
fn parse_impl<'s>( fn parse_impl<'s>(
@@ -272,6 +311,7 @@ fn parse_impl<'s>(
profile: Limits, profile: Limits,
carried: Option<carried::CarriedGuard<'s>>, carried: Option<carried::CarriedGuard<'s>>,
text_producers: bool, text_producers: bool,
entry_producers: bool,
) -> Result<ParsedMixedSyntax<'s>, Vec<SourceDiagnostic>> { ) -> Result<ParsedMixedSyntax<'s>, Vec<SourceDiagnostic>> {
let entry_source = bundle.files.get(&bundle.entry).map_or("", String::as_str); 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 at_entry = |error| located(&bundle.entry, entry_source, ordinary_error(error));
@@ -313,7 +353,14 @@ fn parse_impl<'s>(
.index_bytes(bytes, Span::default()) .index_bytes(bytes, Span::default())
.map_err(at_entry)?; .map_err(at_entry)?;
let mut metadata = bundle.files.len(); let mut metadata = bundle.files.len();
if let Some(input) = &carried { if entry_producers {
let input = carried
.as_ref()
.expect("entry mode requires carried authority");
budget
.enable_entry_producers(input.slots().entries, metadata)
.map_err(at_entry)?;
} else if let Some(input) = &carried {
input.check(&budget, 0, metadata).map_err(at_entry)?; input.check(&budget, 0, metadata).map_err(at_entry)?;
} else { } else {
budget budget
@@ -337,6 +384,7 @@ fn parse_impl<'s>(
.expect("traversal checked source"); .expect("traversal checked source");
let failure = |error| located(path, source, ordinary_error(error)); let failure = |error| located(path, source, ordinary_error(error));
let tokens = lex::tokens(source, &mut budget).map_err(failure)?; let tokens = lex::tokens(source, &mut budget).map_err(failure)?;
let metadata_before = budget.producer_entries();
let mut parser = Parser { let mut parser = Parser {
source, source,
tokens: Vec::new(), tokens: Vec::new(),
@@ -351,6 +399,9 @@ fn parse_impl<'s>(
typed: Vec::new(), typed: Vec::new(),
value_ends: BTreeMap::new(), value_ends: BTreeMap::new(),
lexical_owners: Vec::new(), lexical_owners: Vec::new(),
pending_projection: 0,
reserved_values: 0,
reserved_owners: 0,
}), }),
}; };
let ordinary = parser let ordinary = parser
@@ -360,7 +411,19 @@ fn parse_impl<'s>(
drop(parser); drop(parser);
// Projected ordinary AST nodes/fields are bounded before constructing a // Projected ordinary AST nodes/fields are bounded before constructing a
// second position representation. Count references as slots, not syntax. // second position representation. Count references as slots, not syntax.
let projection_slots = ordinary_position_slots(&ordinary); let projection_slots = if entry_producers {
let slots = tokens
.len()
.checked_add(state.pending_projection)
.ok_or_else(|| failure(state.budget.producer_entry_overflow(Span::default())))?;
state
.budget
.reserve_metadata(slots, Span::default())
.map_err(failure)?;
state.pending_projection
} else {
ordinary_position_slots(&ordinary)
};
metadata = metadata metadata = metadata
.checked_add(tokens.len()) .checked_add(tokens.len())
.and_then(|n| n.checked_add(state.value_ends.len())) .and_then(|n| n.checked_add(state.value_ends.len()))
@@ -377,12 +440,13 @@ fn parse_impl<'s>(
.lowering_entries(metadata, Span::default()) .lowering_entries(metadata, Span::default())
.map_err(failure)?; .map_err(failure)?;
} }
let positions = language::edit_positions_from_tokens_with_text( let positions = language::edit_positions_from_tokens_with_producers(
source, source,
&ordinary, &ordinary,
|at| lexeme(tokens[at]), |at| lexeme(tokens[at]),
tokens.len(), tokens.len(),
Some(&state.value_ends), Some(&state.value_ends),
entry_producers.then_some(projection_slots),
|bytes, span| { |bytes, span| {
state state
.budget .budget
@@ -397,6 +461,23 @@ fn parse_impl<'s>(
related: Vec::new(), related: Vec::new(),
}] }]
})?; })?;
if let Some(before) = metadata_before {
let expected = tokens
.len()
.checked_add(state.value_ends.len())
.and_then(|n| n.checked_add(state.lexical_owners.len()))
.and_then(|n| n.checked_add(projection_slots))
.and_then(|n| n.checked_add(before));
if state.reserved_values != state.value_ends.len()
|| state.reserved_owners != state.lexical_owners.len()
|| expected != state.budget.producer_entries()
{
return Err(failure(super::error(
Span::default(),
"Source8 producer entry inventory mismatch.",
)));
}
}
for (raw, span) in &ordinary.includes { for (raw, span) in &ordinary.includes {
traversal.include(path, raw, *span)?; traversal.include(path, raw, *span)?;
} }
@@ -421,6 +502,12 @@ fn parse_impl<'s>(
} }
traversal.finish()?; traversal.finish()?;
budget.active().map_err(at_entry)?; budget.active().map_err(at_entry)?;
if budget.producer_entries().is_some_and(|m| m != metadata) {
return Err(at_entry(super::error(
Span::default(),
"Source8 producer entry inventory mismatch.",
)));
}
if let Some((_, temporary)) = budget.producer_text() { if let Some((_, temporary)) = budget.producer_text() {
assert_eq!(temporary, 0, "completed syntax has no temporary text"); assert_eq!(temporary, 0, "completed syntax has no temporary text");
} }
@@ -91,6 +91,7 @@ fn parse_carried(input: CarriedInput<'_>) -> Result<ParsedMixedSyntax<'_>, Vec<S
policy.limits, policy.limits,
Some(guard), Some(guard),
false, false,
false,
) )
} }
@@ -106,6 +107,23 @@ fn parse_text_producers(
policy.limits, policy.limits,
Some(guard), Some(guard),
true, true,
false,
)
}
/// Explicit retained text and entry producer fixture route.
#[cfg(test)]
fn parse_entry_producers(
input: CarriedInput<'_>,
) -> Result<ParsedMixedSyntax<'_>, Vec<SourceDiagnostic>> {
let (bundle, policy, guard) = input.into_parts();
parse_impl(
bundle,
policy.project_limits,
policy.limits,
Some(guard),
true,
true,
) )
} }
@@ -163,3 +181,6 @@ mod tests;
#[cfg(test)] #[cfg(test)]
mod text_producer; mod text_producer;
#[cfg(test)]
mod entry_producer;
@@ -0,0 +1,443 @@
//! Literal entry oracles, independently authored before the producer implementation.
use super::*;
const LIMIT: &str = "Source8 lowering entry limit exceeded.";
fn doc(body: &str) -> String {
format!(
"arcadia 8;\nproject p \"Project\" id \"00000000-0000-0000-0000-000000000001\" {{\n{body}\n}}\n"
)
}
fn bundle(body: &str) -> SourceBundle {
SourceBundle {
entry: "main.arcadia".into(),
files: BTreeMap::from([("main.arcadia".into(), doc(body))]),
}
}
fn chain() -> SourceBundle {
let mut b = bundle("include \"part.arcadia\";");
b.files.insert(
"part.arcadia".into(),
"include \"last.arcadia\";\r\n".into(),
);
b.files.insert("last.arcadia".into(), "//é\r\n".into());
b
}
fn held(bundle: SourceBundle, entries: usize) -> RetainedSnapshot {
RetainedSnapshot::fixture(
bundle,
SnapshotPolicy {
limits: Limits {
entries,
bytes: 4096,
..Limits::source8(ParseLimits::UNLIMITED)
},
project_limits: ProjectLimits::default(),
},
)
.unwrap()
}
fn failed(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 diagnostic(d: &SourceDiagnostic, path: &str, start: usize, end: usize) {
assert_eq!(d.path, path);
assert_eq!(d.diagnostic.code, "invalid_source");
assert_eq!(d.diagnostic.message, LIMIT);
assert_eq!(d.diagnostic.span, Span { start, end });
assert!(d.related.is_empty());
}
#[test]
fn real_owner_syntax_and_resolver_boundaries_are_separate() {
for (b, c, u, m, syntax_cap, resolve_cap, refused_path) in [
(bundle(""), 1, 1, 11, 13, 17, "main.arcadia"),
(chain(), 3, 3, 19, 25, 29, "part.arcadia"),
] {
let owner = held(b.clone(), syntax_cap);
let syntax = parse_entry_producers(owner.input()).unwrap();
assert_eq!(owner.retained.entries, c);
assert_eq!(syntax.budget.usage.entries, u);
assert_eq!(syntax.budget.producer_entries(), Some(m));
assert_eq!(syntax.metadata, m);
assert_eq!(
project::text_producer_inventory(&syntax).unwrap().entries,
c + u + m
);
drop(syntax);
let low = held(b.clone(), syntax_cap - 1);
diagnostic(
&failed(parse_entry_producers(low.input())),
refused_path,
0,
1,
);
for cap in [resolve_cap, resolve_cap - 1] {
let owner = held(b.clone(), cap);
let syntax = parse_entry_producers(owner.input()).unwrap();
let usage = syntax.budget.usage;
let numeric = syntax.budget.numeric_units();
match project::resolve(syntax) {
Ok(result) => {
assert_eq!(cap, resolve_cap);
assert_eq!(result.phase_state().1.entries, c + u + m);
assert_eq!(result.phase_state().0.budget.usage, usage);
assert_eq!(result.phase_state().0.budget.numeric_units(), numeric);
let text =
project::resolve(parse_text_producers(owner.input()).unwrap()).unwrap();
assert_eq!(result.project(), text.project());
assert_eq!(result.warnings(), text.warnings());
assert_eq!(result.phase_state().1, text.phase_state().1);
}
Err(failure) => {
assert_eq!(cap, resolve_cap - 1);
assert_eq!(failure.diagnostics.len(), 1);
diagnostic(&failure.diagnostics[0], "main.arcadia", 11, 73);
}
}
assert_eq!(owner.bundle, b);
}
}
}
#[test]
fn owner_and_value_refuse_at_the_original_producer() {
for (body, cap, offset, complete) in [
(
"oa_activity a \"A\" id \"00000000-0000-0000-0000-000000000002\" { }",
4,
75,
29,
),
(
"oa_activity a \"A\" id \"00000000-0000-0000-0000-000000000002\" { description \"\"; }",
7,
149,
37,
),
] {
let owner = held(bundle(body), cap);
diagnostic(
&failed(parse_entry_producers(owner.input())),
"main.arcadia",
offset,
offset + 1,
);
let owner = held(bundle(body), complete);
let syntax = parse_entry_producers(owner.input()).unwrap();
assert_eq!(
owner.retained.entries + syntax.budget.usage.entries + syntax.metadata,
complete
);
// The independent inventory walker has its own existing scratch peak.
let roomy = held(bundle(body), 4096);
let syntax = parse_entry_producers(roomy.input()).unwrap();
assert_eq!(
project::text_producer_inventory(&syntax).unwrap().entries,
complete
);
}
}
const NESTED: &str = "arcadia 8;\nproject p \"Project\" id \"00000000-0000-0000-0000-000000000001\" {\noa_activity a \"A\" id \"00000000-0000-0000-0000-000000000002\" { payload { items [@p, uuid \"00000000-0000-0000-0000-000000000001\", { flag true; }]; };\noa_activity b \"B\" id \"00000000-0000-0000-0000-000000000003\" { }\n}\nnode n id \"00000000-0000-0000-0000-000000000004\" { }\n}\n";
fn nested() -> SourceBundle {
SourceBundle {
entry: "main.arcadia".into(),
files: BTreeMap::from([("main.arcadia".into(), NESTED.into())]),
}
}
#[test]
fn nested_inventory_and_prepaid_projection_quota() {
assert_eq!(NESTED.len(), 344);
let owner = held(nested(), 103);
let syntax = parse_entry_producers(owner.input()).unwrap();
assert_eq!(syntax.budget.usage.entries, 13);
assert_eq!(syntax.budget.producer_entries(), Some(89));
assert_eq!(syntax.metadata, 89);
assert_eq!(
owner.retained.entries + syntax.budget.usage.entries + syntax.metadata,
103
);
let roomy = held(nested(), 4096);
let inventory_syntax = parse_entry_producers(roomy.input()).unwrap();
assert_eq!(
project::text_producer_inventory(&inventory_syntax)
.unwrap()
.entries,
103
);
let file = &syntax.files[0];
assert_eq!(file.tokens.len(), 49);
assert_eq!(file.lexical_owners, [None, Some(0), None]);
assert_eq!(file.positions.len(), 3);
assert_eq!(file.positions[0].1.header.len(), 5);
assert_eq!(file.positions[2].1.header.len(), 4);
let language::EditKind::Record(fields) = &file.positions[0].1.fields.kind else {
panic!("body")
};
let payload = &fields["payload"].value;
assert_eq!(
payload.span,
Span {
start: 145,
end: 221
}
);
let language::EditKind::Record(fields) = &payload.kind else {
panic!("payload")
};
let items = &fields["items"].value;
assert_eq!(
items.span,
Span {
start: 153,
end: 218
}
);
let language::EditKind::List(items) = &items.kind else {
panic!("items")
};
assert_eq!(
items.iter().map(|v| v.span).collect::<Vec<_>>(),
[
Span {
start: 154,
end: 156
},
Span {
start: 158,
end: 201
},
Span {
start: 203,
end: 217
}
]
);
let language::EditKind::Record(fields) = &items[2].kind else {
panic!("member")
};
assert_eq!(
fields["flag"].value.span,
Span {
start: 210,
end: 214
}
);
// Injected prepaid quota errors, not valid alternate source inventories.
for (quota, message) in [
(29, "Source8 projection quota underflow."),
(31, "Source8 projection quota leftover."),
] {
let error = language::edit_positions_from_tokens_with_producers(
file.source,
&file.ordinary,
|at| lexeme(file.tokens[at]),
file.tokens.len(),
None,
Some(quota),
|_, _| Ok(()),
)
.unwrap_err();
assert_eq!(error.message, message);
}
assert_eq!(syntax.budget.producer_entries(), Some(89));
let low = held(nested(), 102);
diagnostic(
&failed(parse_entry_producers(low.input())),
"main.arcadia",
0,
1,
);
}
fn make_state<'a>(budget: &'a mut Budget, model: &'a mut ModelCounts) -> State<'a> {
State {
source: "",
tokens: &[],
budget,
model,
typed: Vec::new(),
value_ends: BTreeMap::new(),
lexical_owners: Vec::new(),
pending_projection: 0,
reserved_values: 0,
reserved_owners: 0,
}
}
fn internal_budget(cap: usize) -> Budget {
Budget::new(Limits {
entries: cap,
..Limits::source8(ParseLimits::UNLIMITED)
})
.unwrap()
}
#[test]
fn internal_reserve_and_mutate_helpers_leave_collections_unchanged() {
// Scalar fault fixtures exercise atomic mutation; these do not invent real owners.
let mut model = ModelCounts {
limits: ProjectLimits::default(),
graph: 0,
records: 0,
includes: 0,
};
let mut budget = internal_budget(3);
budget.enable_entry_producers(1, 1).unwrap();
let mut state = make_state(&mut budget, &mut model);
state.value_end(Span { start: 10, end: 11 }).unwrap();
state.value_end(Span { start: 10, end: 12 }).unwrap();
assert_eq!(state.value_ends, BTreeMap::from([(10, 12)]));
assert_eq!(state.reserved_values, 1);
assert_eq!(state.budget.producer_entries(), Some(2));
let error = state.value_end(Span { start: 20, end: 21 }).unwrap_err();
assert_eq!(error.message, LIMIT);
assert_eq!(error.offset, 20);
let repeated = state.value_end(Span { start: 10, end: 99 }).unwrap_err();
assert_eq!(repeated.offset, 20);
assert_eq!(repeated.message, LIMIT);
assert_eq!(state.value_ends, BTreeMap::from([(10, 12)]));
assert_eq!(state.budget.producer_entries(), Some(2));
let error = state
.lexical_owner(Some(0), Span { start: 30, end: 31 })
.unwrap_err();
assert_eq!(error.offset, 20); // Original latched refusal survives a later producer.
assert!(state.lexical_owners.is_empty());
assert_eq!(state.reserved_owners, 0);
let mut budget = internal_budget(3);
budget.enable_entry_producers(1, 1).unwrap();
let mut state = make_state(&mut budget, &mut model);
state.lexical_owner(None, Span::default()).unwrap();
assert!(
state
.lexical_owner(None, Span { start: 40, end: 41 })
.is_err()
);
assert_eq!(state.lexical_owners, [None]);
assert_eq!(state.reserved_owners, 1);
assert_eq!(state.budget.producer_entries(), Some(2));
}
#[test]
fn internal_overflow_latch_and_syntax_work_precedence() {
let at = Span { start: 9, end: 10 };
let mut budget = internal_budget(2);
budget.enable_entry_producers(1, 1).unwrap();
let e = budget.entry(at).unwrap_err();
assert_eq!(e.message, LIMIT);
assert_eq!(budget.producer_entry_overflow(Span::default()).span, at);
assert_eq!(budget.active().unwrap_err().message, LIMIT);
assert_eq!(budget.usage.entries, 0);
assert_eq!(budget.producer_entries(), Some(1));
assert_eq!(
budget
.reserve_metadata(0, Span::default())
.unwrap_err()
.span,
at
);
let mut budget = internal_budget(0);
budget.enable_entry_producers(0, 0).unwrap();
assert_eq!(
budget.entry(at).unwrap_err().message,
"Source8 syntax entries limit exceeded."
);
let mut budget = internal_budget(100);
budget.enable_entry_producers(1, 1).unwrap();
assert_eq!(
budget.reserve_metadata(usize::MAX, at).unwrap_err().message,
"Source8 producer entry size overflow."
);
assert_eq!(budget.producer_entries(), Some(1));
assert_eq!(budget.active().unwrap_err().span, at);
let mut budget = internal_budget(100);
assert!(budget.enable_entry_producers(usize::MAX, 1).is_err());
assert_eq!(budget.producer_entries(), None);
assert!(budget.active().is_err());
let mut budget = internal_budget(100);
budget.enable_entry_producers(0, 0).unwrap();
let mut model = ModelCounts {
limits: ProjectLimits::default(),
graph: 0,
records: 0,
includes: 0,
};
let mut state = make_state(&mut budget, &mut model);
state.pending_projection = usize::MAX;
assert_eq!(
state.projection(1, at).unwrap_err().message,
"Source8 producer entry size overflow."
);
assert_eq!(state.pending_projection, usize::MAX);
assert!(state.budget.active().is_err());
}
#[test]
fn explicit_zero_and_actual_owner_preserve_complete_oa_sa_and_old_routes() {
for (work, property) in [
("oa_activity", "oa_property"),
("sa_function", "sa_property"),
] {
let body = format!(
"calculation c \"C\" id \"00000000-0000-0000-0000-000000000003\" {{ input x id \"00000000-0000-0000-0000-000000000004\" : Number; result Number; body $x; }}\n{property} v \"V\" id \"00000000-0000-0000-0000-000000000002\" {{ subject @w; type Number; base @c(x: 1); }}\n{work} w \"W\" id \"00000000-0000-0000-0000-000000000005\" {{}}"
);
let original = bundle(&body);
let owner = held(original.clone(), 4096);
for carried in [false, true] {
let seed = parse_text_producers(owner.input()).unwrap();
let input = if carried {
owner.input()
} else {
CarriedInput::borrowed(&owner.bundle, &seed)
};
let syntax = parse_entry_producers(input).unwrap();
let input = if carried {
owner.input()
} else {
CarriedInput::borrowed(&owner.bundle, &seed)
};
let prior = parse_text_producers(input).unwrap();
assert_eq!(syntax.budget.usage, prior.budget.usage);
assert_eq!(syntax.budget.numeric_units(), prior.budget.numeric_units());
assert_eq!(syntax.metadata, prior.metadata);
assert_eq!(syntax.budget.producer_text(), prior.budget.producer_text());
assert_eq!(syntax.budget.producer_entries(), Some(syntax.metadata));
let result = project::resolve(syntax).unwrap();
let prior = project::resolve(prior).unwrap();
assert_eq!(result.project(), prior.project());
assert_eq!(result.warnings(), prior.warnings());
assert_eq!(result.phase_state().1, prior.phase_state().1);
}
assert_eq!(owner.bundle, original);
}
// Text-only and phase entry acceptance retain their old checkpoints.
for cap in [12, 11] {
let owner = held(bundle(""), cap);
let seed = parse_with_profile(
&owner.bundle,
owner.policy.project_limits,
owner.policy.limits,
)
.unwrap();
assert!(seed.budget.producer_entries().is_none());
let result = parse_entry_producers(CarriedInput::borrowed(&owner.bundle, &seed));
if cap == 12 {
assert_eq!(result.unwrap().metadata, 11);
} else {
diagnostic(&failed(result), "main.arcadia", 0, 1);
}
}
let low = held(
bundle("oa_activity a \"A\" id \"00000000-0000-0000-0000-000000000002\" { }"),
4,
);
diagnostic(
&failed(parse_entry_producers(low.input())),
"main.arcadia",
75,
76,
);
diagnostic(
&failed(parse_text_producers(low.input())),
"main.arcadia",
0,
1,
);
diagnostic(&failed(parse_carried(low.input())), "main.arcadia", 0, 1);
}
@@ -0,0 +1,50 @@
# Private retained-entry producer prerequisite
Base `97a8703adc75467a7f8e75f5c6cb00ca542b707c`; implements the accepted
`harness/analysis-entry-producer-contract-20260925.md` in the parent source tree.
This is fixture-only phase/producer evidence, not emitted-output admission.
The explicit entry route also enables text producers and derives C and exact
policy from the existing carried guard. Budget adds only optional scalar C/M.
Existing legacy, carried-phase and text-only entry paths retain their checkpoints.
U keeps its original event sites and work-limit precedence; prospective aggregate
checks latch without changing totals. F is installed before traversal. Private
State helpers reserve unique value-end keys and lexical owner occurrences before
mutation. Successful grammar events count projection slots with checked addition.
The new route reserves token-end slots and projection together, without the old
borrowed-work counting Vec. A scalar quota checks each projected envelope, field
and actual value before growth. Synthetic bodies remain included in seven.
Per-file actual map/owner lengths and final independent metadata reconcile M;
resolver C then U+M transfer and text reconciliation are unchanged.
Six tests in `mixed::carried::entry_producer` cover independent literal E and H
syntax caps 13/12 and 25/24 separately from resolver 17/16 and 29/28, owner/value
prefix refusals 4/7, and the 344-byte nested syntax-only fixture (U13, M89, P30,
C1; 103/102). They check node/element envelopes, nested positions and owner order,
quota underflow/leftover, direct unchanged collections on refusal and repeated
map-key replacement. Labeled arithmetic controls exercise overflow, sticky-first
failure, prospective U and work-limit precedence. Whole OA/SA Project, warnings,
Usage, numeric work and retained totals are compared with text-only under actual
and explicit zero guards. Old-route diagnostics and original bytes are checked.
All byte policies are roomy 4096 so earlier text limits do not mask entry tests.
Source review identified a possible internal overflow call overwriting an already
latched error. The helper now returns the first failure; a direct fault control
checks that behavior. Repeated map keys now perform a zero-delta reservation
before replacement, preserving a latched refusal without modifying the map;
a direct control checks that branch too. These were corrected before compilation,
not a runtime bug
observation. Test review also caught that the independent inventory walker adds
its own existing scratch slots: exact-cap ordinary syntax controls remain at
29/37/103, while a separate roomy owner of the same literal runs that walk.
No live policy is widened and no exact-cap resolver success is implied.
No prior tests, stack sizes, policies, inherited failure dispositions
or unrelated formatting are changed. Only touched Rust files were directly
formatted with skip_children. Parent owns compiler, full disposition, Clippy,
format and prologue/stack acceptance; no runtime result is claimed here.
Allocator overhead, token storage under its existing token ceiling, delimiter and
other work stacks, numeric/decoder internals and model/validator heap remain
outside this retained metric. The extra shared projector wrapper and scalar
Budget/State fields require measured parent stack checks; no heap indirection or
larger stack is introduced. Final output acceptance remains a separate task.