fix(source): count typed braces in shared syntax frames

This commit is contained in:
2026-09-12 22:06:04 -07:00
parent 021bdc5a92
commit b2c970b467
3 changed files with 90 additions and 2 deletions
@@ -264,6 +264,7 @@ pub(super) fn parse(
let name_span = p.span();
let name = p.quoted()?;
let identity = p.identity()?;
p.open()?;
let opening = p.take("{")?;
let header = Header {
kind,
@@ -363,6 +364,7 @@ pub(super) fn parse(
}
}
let closing = p.take("}")?;
p.close();
let body = fields.finish(kind, parameters, closing)?;
Ok(Declaration {
header,
@@ -527,6 +529,7 @@ impl Cursor<'_, '_> {
}
fn dimension(&mut self) -> Result<DimensionSyntax, Error> {
self.open()?;
self.take("{")?;
let mut exponents = [0; 7];
let mut seen = [false; 7];
@@ -577,6 +580,7 @@ impl Cursor<'_, '_> {
});
}
self.take("}")?;
self.close();
Ok(DimensionSyntax { exponents, entries })
}
@@ -595,6 +599,7 @@ impl Cursor<'_, '_> {
}
fn overrides(&mut self) -> Result<Vec<OverrideSyntax>, Error> {
self.open()?;
self.take("{")?;
let mut entries = Vec::new();
let mut names = BTreeSet::new();
@@ -621,6 +626,7 @@ impl Cursor<'_, '_> {
});
}
self.take("}")?;
self.close();
Ok(entries)
}
}
@@ -506,7 +506,7 @@ fn declaration_budgets_share_literals_types_references_and_failure_state() {
#[test]
fn typed_frame_budget_and_truncated_declaration_cleanup_have_nearby_successes() {
let source = "calculation c \"C\" { result List(Quantity(@q)); body unbound; }";
for (maximum, succeeds) in [(2, true), (1, false)] {
for (maximum, succeeds) in [(3, true), (2, false)] {
let mut profile = limits();
profile.frames = maximum;
let mut shared = Budget::new(profile).unwrap();
@@ -529,3 +529,85 @@ fn typed_frame_budget_and_truncated_declaration_cleanup_have_nearby_successes()
declaration("oa_check fresh \"Fresh\" { predicate true; }");
}
}
#[test]
fn typed_brace_frames_have_exact_zero_one_two_boundaries() {
let mut profile = limits();
profile.frames = 0;
let mut shared = Budget::new(profile).unwrap();
let tokens = lex::tokens("unbound", &mut shared).unwrap();
let mut cursor = Cursor::new("unbound", &tokens, &mut shared);
assert!(matches!(
syntax::binding(&mut cursor).unwrap(),
Binding::Unbound(_)
));
assert_eq!(cursor.frames, 0);
assert_eq!(shared.usage.deepest_frame, 0);
for (source, needed) in [
("calculation c \"C\" { result Number; body unbound; }", 1),
("quantity_kind q \"Q\" { dimension {}; algebra Linear; }", 2),
(
"oa_analysis a \"A\" { base ModelBaseline; overrides {}; }",
2,
),
] {
for maximum in 0..=2 {
let mut profile = limits();
profile.frames = maximum;
let mut shared = Budget::new(profile).unwrap();
let tokens = lex::tokens(source, &mut shared).unwrap();
let mut cursor = Cursor::new(source, &tokens, &mut shared);
let result = parse(&mut cursor, 8, None);
assert_eq!(
result.is_ok(),
maximum >= needed,
"{source}, limit {maximum}"
);
if let Err(error) = result {
assert!(error.message.contains("frames"));
assert_eq!(text(source, error.span), "{");
assert_eq!(
error.span.start,
if maximum == 0 {
source.find('{').unwrap()
} else {
source.rfind('{').unwrap()
}
);
} else {
assert_eq!(cursor.frames, 0);
cursor.end().unwrap();
assert_eq!(shared.usage.deepest_frame, needed);
}
}
}
}
#[test]
fn declaration_frames_restore_the_callers_inherited_context() {
let source = "calculation a \"A\" { result Number; body unbound; } calculation b \"B\" { result Number; body unbound; }";
for maximum in [1, 2] {
let mut profile = limits();
profile.frames = maximum;
let mut shared = Budget::new(profile).unwrap();
let tokens = lex::tokens(source, &mut shared).unwrap();
let mut cursor = Cursor::new(source, &tokens, &mut shared);
// Future project/include integration owns this outer context. The
// declaration consumer must add to it, then restore it after closing.
cursor.frames = 1;
let first = parse(&mut cursor, 8, None);
if maximum == 1 {
let error = first.unwrap_err();
assert!(error.message.contains("frames"));
assert_eq!(text(source, error.span), "{");
} else {
assert!(first.is_ok());
assert_eq!(cursor.frames, 1);
parse(&mut cursor, 8, None).unwrap();
assert_eq!(cursor.frames, 1);
cursor.end().unwrap();
assert_eq!(shared.usage.deepest_frame, 2);
}
}
}
+1 -1
View File
@@ -302,7 +302,7 @@ mod tests {
(String, "\"雪\""),
(Whitespace, " "),
(Punctuation, "@"),
(Identifier, "alias"),
(Keyword, "alias"),
],
),
("\"incomplete 雪", &[(Error, "\"incomplete 雪")]),