Compare commits

..
6 changed files with 743 additions and 10 deletions
+64 -4
View File
@@ -1028,6 +1028,18 @@ fn edit_positions_from_tokens_with_text(
count: usize,
value_ends: Option<&BTreeMap<usize, usize>>,
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>> {
let mut ends: Vec<_> = (0..count).map(|at| token(at).span.end).collect();
let mut stack = Vec::new();
@@ -1044,10 +1056,27 @@ fn edit_positions_from_tokens_with_text(
ends: Vec<usize>,
value_ends: Option<&'a BTreeMap<usize, usize>>,
text: H,
source: &'a str,
quota: &'a mut Option<usize>,
}
impl<F: Fn(usize) -> Lexeme, H: FnMut(usize, Span) -> Result<(), Box<Diagnostic>>>
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 {
let mut low = 0;
let mut high = self.ends.len();
@@ -1068,6 +1097,7 @@ fn edit_positions_from_tokens_with_text(
fields
.iter()
.map(|(key, ast)| {
self.consume(1, ast.offset())?;
let value = self.value(ast)?;
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;
@@ -1083,6 +1113,7 @@ fn edit_positions_from_tokens_with_text(
.collect()
}
fn value(&mut self, ast: &Ast) -> Result<EditValue, Box<Diagnostic>> {
self.consume(1, ast.offset())?;
let index = self.token(ast.offset());
let span = Span {
start: ast.offset(),
@@ -1119,8 +1150,11 @@ fn edit_positions_from_tokens_with_text(
ends,
value_ends,
text,
source,
quota: &mut quota,
};
unit.declarations
let result = unit
.declarations
.iter()
.map(|declaration| {
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) {
&["kind", "alias", "id_keyword", "id"]
} 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> {
source: &'a str,
@@ -1337,6 +1383,10 @@ impl Parser<'_> {
.span
.end;
self.take(";")?;
#[cfg(test)]
if let Some(mixed) = &mut self.mixed {
mixed.projection(1, Span { start, end })?;
}
Ok((key, value, Span { start, end }))
}
@@ -1370,7 +1420,12 @@ impl Parser<'_> {
.span
.end;
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
@@ -1697,7 +1752,12 @@ impl Parser<'_> {
let lexical_owner = open.last().map(|&owner| declarations[owner].id);
#[cfg(test)]
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 {
kind,
@@ -39,6 +39,11 @@ pub struct Budget {
numeric: WorkMeter,
failed: Option<Error>,
text: Option<ProducerText>,
entries: Option<ProducerEntries>,
}
struct ProducerEntries {
carried: usize,
metadata: usize,
}
struct ProducerText {
carried: usize,
@@ -69,8 +74,64 @@ impl Budget {
),
failed: 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> {
self.index_bytes(carried, Span::default())?;
assert!(self.text.is_none());
@@ -200,6 +261,9 @@ impl Budget {
span,
"syntax entries",
)?;
if let Some(e) = &self.entries {
self.check_producer_entries(e.carried, e.metadata, 1, span)?;
}
self.usage.entries += 1;
Ok(())
}
@@ -68,8 +68,11 @@ pub(in crate::language) struct State<'a> {
budget: &'a mut Budget,
model: &'a mut ModelCounts,
typed: Vec<declarations::Declaration>,
pub(in crate::language) value_ends: BTreeMap<usize, usize>,
pub(in crate::language) lexical_owners: Vec<Option<usize>>,
value_ends: BTreeMap<usize, usize>,
lexical_owners: Vec<Option<usize>>,
pending_projection: usize,
reserved_values: usize,
reserved_owners: usize,
}
fn ordinary_error(error: super::Error) -> Error {
@@ -93,6 +96,42 @@ fn lexeme(token: lex::Token) -> Lexeme {
}
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> {
self.budget.own_text(bytes, span).map_err(ordinary_error)
}
@@ -263,7 +302,7 @@ fn parse_with_profile(
limits: ProjectLimits,
profile: Limits,
) -> Result<ParsedMixedSyntax<'_>, Vec<SourceDiagnostic>> {
parse_impl(bundle, limits, profile, None, false)
parse_impl(bundle, limits, profile, None, false, false)
}
fn parse_impl<'s>(
@@ -272,6 +311,7 @@ fn parse_impl<'s>(
profile: Limits,
carried: Option<carried::CarriedGuard<'s>>,
text_producers: bool,
entry_producers: bool,
) -> 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));
@@ -313,7 +353,14 @@ fn parse_impl<'s>(
.index_bytes(bytes, Span::default())
.map_err(at_entry)?;
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)?;
} else {
budget
@@ -337,6 +384,7 @@ fn parse_impl<'s>(
.expect("traversal checked source");
let failure = |error| located(path, source, ordinary_error(error));
let tokens = lex::tokens(source, &mut budget).map_err(failure)?;
let metadata_before = budget.producer_entries();
let mut parser = Parser {
source,
tokens: Vec::new(),
@@ -351,6 +399,9 @@ fn parse_impl<'s>(
typed: Vec::new(),
value_ends: BTreeMap::new(),
lexical_owners: Vec::new(),
pending_projection: 0,
reserved_values: 0,
reserved_owners: 0,
}),
};
let ordinary = parser
@@ -360,7 +411,19 @@ fn parse_impl<'s>(
drop(parser);
// Projected ordinary AST nodes/fields are bounded before constructing a
// 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
.checked_add(tokens.len())
.and_then(|n| n.checked_add(state.value_ends.len()))
@@ -377,12 +440,13 @@ fn parse_impl<'s>(
.lowering_entries(metadata, Span::default())
.map_err(failure)?;
}
let positions = language::edit_positions_from_tokens_with_text(
let positions = language::edit_positions_from_tokens_with_producers(
source,
&ordinary,
|at| lexeme(tokens[at]),
tokens.len(),
Some(&state.value_ends),
entry_producers.then_some(projection_slots),
|bytes, span| {
state
.budget
@@ -397,6 +461,23 @@ fn parse_impl<'s>(
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 {
traversal.include(path, raw, *span)?;
}
@@ -421,6 +502,12 @@ fn parse_impl<'s>(
}
traversal.finish()?;
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() {
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,
Some(guard),
false,
false,
)
}
@@ -106,6 +107,23 @@ fn parse_text_producers(
policy.limits,
Some(guard),
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)]
mod text_producer;
#[cfg(test)]
mod entry_producer;
@@ -0,0 +1,445 @@
//! 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 = match language::edit_positions_from_tokens_with_producers(
file.source,
&file.ordinary,
|at| lexeme(file.tokens[at]),
file.tokens.len(),
None,
Some(quota),
|_, _| Ok(()),
) {
Err(error) => error,
Ok(_) => panic!("injected projection quota must refuse"),
};
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,56 @@
# 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.
Initial a629ae01 focused compilation failed before any test ran: the quota
control's unwrap_err required Debug on its successful EditDeclaration vector.
The test now matches Err explicitly and panics on unexpected Ok, preserving
the same assertion without changing production types. Original compiler output
and source-bound receipt remain with parent gate evidence.