feat: index complete authored names from retained mixed syntax

This commit is contained in:
2026-09-24 15:21:35 -07:00
parent 93eb6117f9
commit 50a5918fd6
6 changed files with 1166 additions and 19 deletions
@@ -11,6 +11,7 @@ use crate::{Id, Kind, Project, RelationKind, analysis, language, validation};
use std::collections::BTreeMap;
pub(in crate::language::analysis_source) mod accounting;
use accounting::{Ledger, Slots, add};
pub(in crate::language::analysis_source) mod ordinary_index;
/// Read-only authority: every component originates in the consumed syntax.
pub(in crate::language::analysis_source) struct ResolvedMixedProject<'s> {
@@ -49,20 +50,7 @@ impl<'s> ResolvedMixedProject<'s> {
};
let data =
super::super::index::mixed_data(input, &mut self.syntax.budget, &mut self.storage)
.map_err(|mut failure| {
// An invariant without a canonical file has no defensible source
// offset. Use the entry start explicitly, never a foreign span.
failure.file = failure.file.filter(|&f| f < self.syntax.files.len());
if failure.file.is_none_or(|file| {
self.syntax.files[file]
.source
.get(failure.error.span.start..failure.error.span.end)
.is_none()
}) {
failure.error.span = Span::default();
}
Failure::typed(&self.syntax, failure.file, failure.error)
})?;
.map_err(|failure| Failure::index(&self.syntax, failure))?;
Ok(IndexedMixedProject {
resolved: self,
data,
@@ -136,6 +124,20 @@ pub(in crate::language::analysis_source) struct Failure {
pub diagnostics: Vec<SourceDiagnostic>,
}
impl Failure {
/// Canonical attribution when justified; otherwise a safe explicit start fallback.
fn index(syntax: &ParsedMixedSyntax<'_>, mut failure: lower::Failure) -> Self {
failure.file = failure.file.filter(|&f| f < syntax.files.len());
if failure.file.is_none_or(|file| {
syntax.files[file]
.source
.get(failure.error.span.start..failure.error.span.end)
.is_none()
}) {
failure.error.span = Span::default();
}
Self::typed(syntax, failure.file, failure.error)
}
// Rejection transport is not retained in a successful witness. In particular
// a storage refusal needs a small diagnostic even when its allowance is zero.
fn typed(syntax: &ParsedMixedSyntax<'_>, file: Option<usize>, e: TypedError) -> Self {
@@ -0,0 +1,431 @@
//! Complete authored-name ownership from the sealed ordinary position projection.
//! Physical keys belong only to this immutable snapshot. This is not edit authority.
use super::super::super::{index, lex};
use super::*;
use crate::language::{EditField, EditKind, EditValue};
use std::collections::BTreeSet;
/// Canonical file key and the whole alias Word's half-open byte range.
pub(in crate::language::analysis_source) type OrdinaryKey = (usize, usize, usize);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(in crate::language::analysis_source) enum OrdinaryRole {
Header,
Reference,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(in crate::language::analysis_source) struct OrdinaryOccurrence {
pub owner: Id,
pub target: Id,
pub role: OrdinaryRole,
/// Alias token for Header; marker, intervening trivia and alias for Reference.
pub extent: Span,
}
/// Owns both occurrence families and their complete original admission witness.
pub(in crate::language::analysis_source) struct AuthoredMixedProject<'s> {
indexed: IndexedMixedProject<'s>,
ordinary: BTreeMap<OrdinaryKey, OrdinaryOccurrence>,
}
impl<'s> IndexedMixedProject<'s> {
pub fn index_ordinary(mut self) -> Result<AuthoredMixedProject<'s>, Failure> {
let ordinary = build(
&self.resolved.syntax,
&self.data,
&mut self.resolved.storage,
)
.map_err(|failure| Failure::index(&self.resolved.syntax, failure))?;
Ok(AuthoredMixedProject {
indexed: self,
ordinary,
})
}
}
impl AuthoredMixedProject<'_> {
pub fn project(&self) -> &Project {
self.indexed.project()
}
pub fn warnings(&self) -> &[SourceDiagnostic] {
self.indexed.warnings()
}
pub fn bundle(&self) -> &crate::source_project::SourceBundle {
self.indexed.bundle()
}
pub fn files(&self) -> impl Iterator<Item = (usize, &str, &str)> {
self.indexed.files()
}
pub fn names(&self) -> &BTreeMap<lower::Target, String> {
self.indexed.names()
}
pub fn typed_occurrences(&self) -> &BTreeMap<index::OccurrenceKey, index::Location> {
self.indexed.occurrences()
}
pub fn expressions(&self) -> &BTreeMap<index::ExpressionKey, index::Location> {
self.indexed.expressions()
}
pub fn ordinary_occurrences(&self) -> &BTreeMap<OrdinaryKey, OrdinaryOccurrence> {
&self.ordinary
}
}
struct Builder<'a, 'b> {
budget: &'a Budget,
storage: &'b mut Ledger,
globals: BTreeMap<&'a str, (Id, bool)>,
typed: BTreeSet<OrdinaryKey>,
ordinary: BTreeMap<OrdinaryKey, OrdinaryOccurrence>,
}
fn build(
syntax: &ParsedMixedSyntax<'_>,
data: &index::IndexData,
storage: &mut Ledger,
) -> Result<BTreeMap<OrdinaryKey, OrdinaryOccurrence>, lower::Failure> {
let mut file = None;
let mut b = Builder {
budget: &syntax.budget,
storage,
globals: BTreeMap::new(),
typed: BTreeSet::new(),
ordinary: BTreeMap::new(),
};
let result = (|| {
b.budget.active()?;
for (target, name) in &data.names {
if let lower::Target::Global(id) = target {
b.reserve(Span::default())?;
if b.globals.insert(name, (*id, false)).is_some() {
return Err(error(
Span::default(),
"Duplicate ordinary global spelling.",
));
}
}
}
for (key, at) in &data.occurrences {
file = Some(at.file);
let unit = syntax
.files
.get(at.file)
.ok_or_else(|| error(at.span, "Ordinary index has no canonical file."))?;
source(unit, at.span)?;
b.reserve(at.span)?;
if !b.typed.insert((at.file, at.span.start, at.span.end)) {
return Err(error(at.span, "Duplicate typed physical name token."));
}
if key.role == index::TokenRole::Declaration {
if key.target != lower::Target::Global(key.owner) {
return Err(error(at.span, "Typed global header has a different owner."));
}
b.header(key.owner, source(unit, at.span)?, at.span)?;
}
}
for (number, unit) in syntax.files.iter().enumerate() {
file = Some(number);
if let Some(header) = &unit.ordinary.header {
if unit.path != syntax.bundle.entry {
return Err(error(
header.span,
"Project header is outside the canonical entry.",
));
}
source(unit, header.span)?;
let start = token_at(unit, header.span.start, header.span)?;
let keyword = unit.tokens[start];
if keyword.class != lex::Class::Word || source(unit, keyword.span)? != "project" {
return Err(error(
header.span,
"Project header lacks its original project token.",
));
}
let alias = *unit
.tokens
.get(start + 1)
.ok_or_else(|| error(header.span, "Project header lacks an alias token."))?;
contains(header.span, alias.span)?;
word(unit, alias.span, &header.project.alias)?;
b.header(header.project.id, &header.project.alias, alias.span)?;
b.insert(
number,
alias.span,
OrdinaryOccurrence {
owner: header.project.id,
target: header.project.id,
role: OrdinaryRole::Header,
extent: alias.span,
},
)?;
}
if unit.ordinary.declarations.len() != unit.positions.len() {
return Err(error(
Span::default(),
"Ordinary declaration position count differs from syntax.",
));
}
for (declaration, (owner, position)) in
unit.ordinary.declarations.iter().zip(&unit.positions)
{
if *owner != declaration.id
|| position.span != declaration.span
|| position.body != declaration.body
|| position.lexical_owner != declaration.lexical_owner
{
return Err(error(
position.span,
"Ordinary position differs from its declaration owner.",
));
}
source(unit, declaration.span)?;
source(unit, declaration.body)?;
contains(declaration.span, declaration.body)?;
let alias = *position.header.get("alias").ok_or_else(|| {
error(
position.span,
"Ordinary declaration lacks an alias position.",
)
})?;
let start = token_at(unit, declaration.span.start, declaration.span)?;
if unit.tokens.get(start + 1).map(|t| t.span) != Some(alias)
|| alias.end > declaration.body.start
{
return Err(error(
alias,
"Ordinary alias is not its declaration header token.",
));
}
contains(declaration.span, alias)?;
word(unit, alias, &declaration.alias)?;
b.header(*owner, &declaration.alias, alias)?;
b.insert(
number,
alias,
OrdinaryOccurrence {
owner: *owner,
target: *owner,
role: OrdinaryRole::Header,
extent: alias,
},
)?;
// The synthetic root includes braces; actual fields lie in the interior.
let root_start = declaration.body.start.checked_sub(1).ok_or_else(|| {
error(position.fields.span, "Invalid ordinary body boundary.")
})?;
let root_end = declaration.body.end.checked_add(1).ok_or_else(|| {
error(position.fields.span, "Invalid ordinary body boundary.")
})?;
if position.fields.span
!= (Span {
start: root_start,
end: root_end,
})
{
return Err(error(
position.fields.span,
"Ordinary synthetic root differs from its body braces.",
));
}
source(unit, position.fields.span)?;
contains(declaration.span, position.fields.span)?;
let EditKind::Record(fields) = &position.fields.kind else {
return Err(error(
position.fields.span,
"Ordinary synthetic root must be a record.",
));
};
b.fields(
number,
unit,
*owner,
declaration.span,
declaration.body,
fields,
)?;
}
}
file = None;
if b.globals.values().any(|(_, seen)| !seen) {
return Err(error(
Span::default(),
"Global name is missing its authored declaration header.",
));
}
Ok(())
})();
result.map_err(|error| lower::Failure { file, error })?;
let globals = b.globals.len();
let typed = b.typed.len();
drop(b.globals);
drop(b.typed);
b.storage.release(Slots::entries(
add(globals, typed, Span::default())
.map_err(|error| lower::Failure { file: None, error })?,
));
Ok(b.ordinary)
}
impl Builder<'_, '_> {
fn reserve(&mut self, at: Span) -> Result<(), TypedError> {
self.storage.reserve(self.budget, Slots::entries(1), at)
}
fn header(&mut self, id: Id, alias: &str, at: Span) -> Result<(), TypedError> {
let Some((target, seen)) = self.globals.get_mut(alias) else {
return Err(error(at, "Ordinary header has no global target."));
};
if *target != id {
return Err(error(
at,
"Ordinary header differs from its global identity.",
));
}
if *seen {
return Err(error(
at,
"Global declaration header appears more than once.",
));
}
*seen = true;
Ok(())
}
fn insert(
&mut self,
file: usize,
alias: Span,
occurrence: OrdinaryOccurrence,
) -> Result<(), TypedError> {
let key = (file, alias.start, alias.end);
if self.typed.contains(&key) || self.ordinary.contains_key(&key) {
return Err(error(
alias,
"Duplicate ordinary or typed physical name token.",
));
}
self.reserve(alias)?;
self.ordinary.insert(key, occurrence);
Ok(())
}
fn fields(
&mut self,
file: usize,
unit: &FileSyntax<'_>,
owner: Id,
declaration: Span,
body: Span,
fields: &BTreeMap<String, EditField>,
) -> Result<(), TypedError> {
let mut work = Vec::new();
for field in fields.values() {
self.push_field(unit, &mut work, field, body)?;
}
while let Some((value, parent)) = work.pop() {
self.storage.release(Slots::entries(1));
source(unit, value.span)?;
contains(parent, value.span)?;
contains(declaration, value.span)?;
match &value.kind {
EditKind::Reference(alias) => {
let start = token_at(unit, value.span.start, value.span)?;
let marker = unit.tokens[start];
if marker.class != lex::Class::Punctuation || source(unit, marker.span)? != "@"
{
return Err(error(
value.span,
"Ordinary reference lacks its original marker token.",
));
}
let name = *unit.tokens.get(start + 1).ok_or_else(|| {
error(value.span, "Ordinary reference lacks its alias token.")
})?;
if name.span.end != value.span.end {
return Err(error(
value.span,
"Ordinary reference extent differs from its complete alias token.",
));
}
word(unit, name.span, alias)?;
let target = self
.globals
.get(alias.as_str())
.ok_or_else(|| {
error(name.span, "Ordinary reference has no global target.")
})?
.0;
self.insert(
file,
name.span,
OrdinaryOccurrence {
owner,
target,
role: OrdinaryRole::Reference,
extent: value.span,
},
)?;
}
EditKind::Record(fields) => {
for field in fields.values() {
self.push_field(unit, &mut work, field, value.span)?;
}
}
EditKind::List(items) => {
for item in items {
self.reserve(item.span)?;
work.push((item, value.span));
}
}
EditKind::Atom | EditKind::HistoricalUuid(_) => {}
}
}
drop(work);
Ok(())
}
fn push_field<'a>(
&mut self,
unit: &FileSyntax<'_>,
work: &mut Vec<(&'a EditValue, Span)>,
field: &'a EditField,
parent: Span,
) -> Result<(), TypedError> {
source(unit, field.span)?;
contains(parent, field.span)?;
contains(field.span, field.value.span)?;
self.reserve(field.value.span)?;
work.push((&field.value, field.span));
Ok(())
}
}
fn source<'a>(unit: &'a FileSyntax<'_>, span: Span) -> Result<&'a str, TypedError> {
unit.source
.get(span.start..span.end)
.ok_or_else(|| error(span, "Ordinary span is outside its original UTF-8 file."))
}
fn contains(parent: Span, child: Span) -> Result<(), TypedError> {
if child.start < parent.start || child.end > parent.end || child.start > child.end {
return Err(error(child, "Ordinary span crosses its containing syntax."));
}
Ok(())
}
fn token_at(unit: &FileSyntax<'_>, start: usize, at: Span) -> Result<usize, TypedError> {
unit.tokens
.binary_search_by_key(&start, |t| t.span.start)
.map_err(|_| {
error(
at,
"Ordinary occurrence does not start at an original token.",
)
})
}
fn word(unit: &FileSyntax<'_>, at: Span, expected: &str) -> Result<(), TypedError> {
let token = unit.tokens[token_at(unit, at.start, at)?];
if token.class != lex::Class::Word || token.span != at {
return Err(error(
at,
"Ordinary alias is not an original whole Word token.",
));
}
if source(unit, at)? != expected {
return Err(error(
at,
"Ordinary alias differs from its retained spelling.",
));
}
Ok(())
}
#[cfg(test)]
mod tests;
@@ -0,0 +1,674 @@
use super::super::tests::{accepted, id, rich_bundle, rich_model, single};
use super::*;
use crate::Layer;
use crate::source_project::{ProjectLimits, SourceBundle};
fn indexed(bundle: &SourceBundle) -> IndexedMixedProject<'_> {
accepted(bundle).index().unwrap()
}
fn span(start: usize, end: usize) -> Span {
Span { start, end }
}
fn record(
owner: usize,
target: usize,
role: OrdinaryRole,
start: usize,
end: usize,
) -> OrdinaryOccurrence {
OrdinaryOccurrence {
owner: id(owner),
target: id(target),
role,
extent: span(start, end),
}
}
fn fail(indexed: IndexedMixedProject<'_>, message: &str, path: &str) -> SourceDiagnostic {
let failure = match indexed.index_ordinary() {
Ok(_) => panic!("damaged ordinary index accepted"),
Err(e) => e,
};
assert_eq!(failure.diagnostics.len(), 1);
let diagnostic = failure.diagnostics.into_iter().next().unwrap();
assert_eq!(diagnostic.path, path);
assert_eq!(diagnostic.diagnostic.message, message);
diagnostic
}
#[test]
fn entire_rich_oa_sa_and_unicode_crlf_maps_preserve_complete_authority() {
use OrdinaryRole::{Header as H, Reference as R};
for layer in [Layer::Oa, Layer::Sa] {
for crlf in [false, true] {
let mut bundle = rich_bundle(layer);
if crlf {
for source in bundle.files.values_mut() {
*source = format!("// 雨\r\n{}", source.replace('\n', "\r\n"));
}
}
let indexed = indexed(&bundle);
let names = indexed.names().clone();
let typed = indexed.occurrences().clone();
let expressions = indexed.expressions().clone();
let warnings = indexed.warnings().to_vec();
let limits = indexed.resolved.static_limits;
let static_usage = indexed.resolved.static_usage;
let usage = indexed.resolved.syntax.budget.usage;
let numeric = indexed.resolved.syntax.budget.numeric_units();
let before = indexed.resolved.storage.live;
let complete = indexed.index_ordinary().unwrap();
let endpoints = if crlf {
[28, 133, 188, 200, 363, 426, 505]
} else {
[19, 122, 177, 189, 350, 412, 490]
};
let mut expected = BTreeMap::new();
for (at, owner, target, role) in [
(endpoints[0], 1, 1, H),
(endpoints[1], 4, 4, H),
(endpoints[2], 4, 5, R),
(endpoints[3], 4, 3, R),
(endpoints[4], 2, 2, H),
(endpoints[5], 5, 5, H),
] {
expected.insert(
(2, at, at + 1),
record(
owner,
target,
role,
if role == R { at - 1 } else { at },
at + 1,
),
);
}
if layer == Layer::Sa {
let at = endpoints[6];
expected.insert((2, at, at + 1), record(6, 6, H, at, at + 1));
}
assert_eq!(complete.ordinary_occurrences(), &expected);
assert_eq!(complete.project(), &rich_model(layer));
assert!(std::ptr::eq(complete.bundle(), &bundle));
assert_eq!(
complete.files().collect::<Vec<_>>(),
bundle
.files
.iter()
.enumerate()
.map(|(i, (p, s))| (i, p.as_str(), s.as_str()))
.collect::<Vec<_>>()
);
assert_eq!(complete.names(), &names);
assert_eq!(complete.typed_occurrences(), &typed);
assert_eq!(complete.expressions(), &expressions);
assert_eq!(complete.warnings(), &warnings);
assert_eq!(complete.indexed.resolved.static_limits, limits);
assert_eq!(complete.indexed.resolved.static_usage, static_usage);
assert_eq!(complete.indexed.resolved.syntax.budget.usage, usage);
assert_eq!(
complete.indexed.resolved.syntax.budget.numeric_units(),
numeric
);
assert_eq!(
complete.indexed.resolved.storage.live,
before
.add(Slots::entries(expected.len()), Span::default())
.unwrap()
);
let mut headers: BTreeSet<_> = complete
.ordinary
.values()
.filter(|o| o.role == H)
.map(|o| o.target)
.collect();
headers.extend(
complete
.typed_occurrences()
.keys()
.filter(|k| k.role == index::TokenRole::Declaration)
.map(|k| k.owner),
);
assert_eq!(
headers,
complete
.names()
.keys()
.filter_map(|t| if let lower::Target::Global(id) = t {
Some(*id)
} else {
None
})
.collect()
);
}
}
}
#[test]
fn canonical_file_keys_distinguish_equal_header_offsets_and_exclude_include_prose() {
let mut bundle = single("include \"b.arcadia\"; include \"a.arcadia\";");
for (path, alias, n) in [("a.arcadia", "aa", 2), ("b.arcadia", "bb", 3)] {
bundle.files.insert(
path.into(),
format!(
"oa_activity {alias} \"Same @aa @bb\" id \"{}\" {{ {} }}",
id(n),
if n == 3 { "parent @aa;" } else { "" }
),
);
}
let complete = indexed(&bundle).index_ordinary().unwrap();
assert_eq!(
complete.ordinary,
BTreeMap::from([
((0, 12, 14), record(2, 2, OrdinaryRole::Header, 12, 14)),
((1, 12, 14), record(3, 3, OrdinaryRole::Header, 12, 14)),
((1, 82, 84), record(3, 2, OrdinaryRole::Reference, 81, 84)),
((2, 19, 20), record(1, 1, OrdinaryRole::Header, 19, 20)),
])
);
}
fn rich_with_extra_headers() -> SourceBundle {
let mut bundle = rich_bundle(Layer::Oa);
let source = bundle.files.get_mut("main.arcadia").unwrap();
let at = source.rfind('}').unwrap();
let extra = (20..23)
.map(|n| format!("oa_activity extra{n} \"Extra\" id \"{}\" {{}}\n", id(n)))
.collect::<String>();
source.insert_str(at, &extra);
bundle
}
#[test]
fn exact_ordinary_peak_after_typed_peak_preserves_the_inherited_ledger() {
let bundle = rich_with_extra_headers();
let baseline = indexed(&bundle).resolved.storage.live;
// G10 + T21 + N9 =40, final ordinary header. Earlier typed peak is
// resolved baseline+101, one below ordinary resolved baseline+102.
for (extra, fits) in [(39, false), (40, true)] {
let profile = super::super::super::super::budget::Limits {
entries: baseline.entries + extra,
..super::super::super::super::budget::Limits::source8(language::ParseLimits::UNLIMITED)
};
let parsed =
super::super::super::parse_with_profile(&bundle, ProjectLimits::default(), profile)
.unwrap();
let indexed = resolve(parsed).unwrap().index().unwrap();
assert_eq!(indexed.resolved.storage.live, baseline);
if fits {
assert_eq!(
indexed
.index_ordinary()
.unwrap()
.indexed
.resolved
.storage
.live,
baseline.add(Slots::entries(9), Span::default()).unwrap()
);
} else {
let diagnostic = fail(
indexed,
"Source8 lowering entry limit exceeded.",
"main.arcadia",
);
let at = bundle.files["main.arcadia"].find("extra22").unwrap();
assert_eq!(diagnostic.diagnostic.span, span(at, at + 7));
}
}
}
fn node_field<'a>(
indexed: &'a mut IndexedMixedProject<'_>,
name: &str,
) -> &'a mut language::EditField {
let EditKind::Record(fields) = &mut indexed.resolved.syntax.files[2].positions[0].1.fields.kind
else {
panic!("node fields")
};
fields.get_mut(name).unwrap()
}
#[test]
fn projection_corruptions_reach_owner_header_marker_extent_and_spelling_checks() {
let bundle = rich_bundle(Layer::Oa);
for case in 0..12 {
let mut indexed = indexed(&bundle);
let message = match case {
0 => {
indexed.resolved.syntax.files[2].positions[0].0 = id(3);
"Ordinary position differs from its declaration owner."
}
1 => {
indexed.resolved.syntax.files[2].positions[0]
.1
.lexical_owner = Some(id(2));
"Ordinary position differs from its declaration owner."
}
2 => {
indexed.resolved.syntax.files[2].positions[0]
.1
.header
.insert("alias", span(177, 178));
"Ordinary alias is not its declaration header token."
}
3 => {
indexed.resolved.syntax.files[2]
.ordinary
.header
.as_mut()
.unwrap()
.project
.id = id(2);
"Ordinary header differs from its global identity."
}
4 => {
indexed.resolved.syntax.files[2].positions[0]
.1
.fields
.span
.start += 1;
"Ordinary synthetic root differs from its body braces."
}
5 => {
node_field(&mut indexed, "diagram").value.span.start = 177;
"Ordinary reference lacks its original marker token."
}
6 => {
node_field(&mut indexed, "diagram").value.span.end = 177;
"Ordinary reference extent differs from its complete alias token."
}
7 => {
node_field(&mut indexed, "diagram").value.span.end = 179;
"Ordinary reference extent differs from its complete alias token."
}
8 => {
node_field(&mut indexed, "diagram").value.kind =
EditKind::Reference("other".into());
"Ordinary alias differs from its retained spelling."
}
9 => {
let token = indexed.resolved.syntax.files[2]
.tokens
.iter_mut()
.find(|t| t.span.start == 177)
.unwrap();
token.class = lex::Class::String;
"Ordinary alias is not an original whole Word token."
}
10 => {
node_field(&mut indexed, "diagram").span.start = 0;
"Ordinary span crosses its containing syntax."
}
_ => {
indexed.data.names.remove(&lower::Target::Global(id(5)));
"Ordinary reference has no global target."
}
};
fail(indexed, message, "main.arcadia");
}
}
#[test]
fn missing_header_and_duplicate_physical_ownership_are_rejected() {
let bundle = rich_bundle(Layer::Oa);
let mut missing = indexed(&bundle);
missing.resolved.syntax.files[2]
.ordinary
.declarations
.remove(1);
missing.resolved.syntax.files[2].positions.remove(1);
let diagnostic = fail(
missing,
"Global name is missing its authored declaration header.",
"main.arcadia",
);
assert_eq!(diagnostic.diagnostic.span, Span::default());
let mut duplicate = indexed(&bundle);
// A second field position points at the same full reference. Parser emission
// cannot produce this; the control isolates ordinary physical ownership.
let field = node_field(&mut duplicate, "element");
field.span = span(168, 179);
field.value.span = span(176, 178);
field.value.kind = EditKind::Reference("d".into());
fail(
duplicate,
"Duplicate ordinary or typed physical name token.",
"main.arcadia",
);
let mut cross = indexed(&bundle);
let key = cross
.data
.occurrences
.keys()
.find(|k| k.role == index::TokenRole::Use(lower::Role::ParameterUse))
.unwrap()
.clone();
cross.data.occurrences.insert(
key,
index::Location {
file: 2,
span: span(122, 123),
},
);
fail(
cross,
"Duplicate ordinary or typed physical name token.",
"main.arcadia",
);
}
#[test]
fn invalid_file_and_utf8_damage_use_safe_explicit_diagnostic_locations() {
let mut bundle = rich_bundle(Layer::Oa);
*bundle.files.get_mut("main.arcadia").unwrap() =
bundle.files["main.arcadia"].replace("node n", "// 雨\nnode n");
let mut bad = indexed(&bundle);
let at = bad.data.occurrences.values_mut().next().unwrap();
at.file = usize::MAX;
assert_eq!(
fail(bad, "Ordinary index has no canonical file.", "main.arcadia")
.diagnostic
.span,
Span::default()
);
let mut bad = indexed(&bundle);
let byte = bundle.files["main.arcadia"].find('雨').unwrap() + 1;
bad.resolved.syntax.files[2].positions[0].1.fields.span = span(byte, byte + 1);
assert_eq!(
fail(
bad,
"Ordinary synthetic root differs from its body braces.",
"main.arcadia"
)
.diagnostic
.span,
Span::default()
);
}
#[test]
fn ordinary_resolution_ignores_same_spelled_scoped_parameter_names() {
let mut bundle = rich_bundle(Layer::Oa);
let main = bundle.files.get_mut("main.arcadia").unwrap();
*main = main
.replace("oa_activity w", "oa_activity x")
.replace("subject @w", "subject @x");
let end = main.rfind('}').unwrap();
main.insert_str(
end,
&format!(
"artifact attach \"Attachment\" id \"{}\" {{ owner @x; uri \"x\"; }}\n",
id(20)
),
);
let complete = indexed(&bundle).index_ordinary().unwrap();
let ordinary = complete
.ordinary
.values()
.find(|r| r.owner == id(20) && r.role == OrdinaryRole::Reference)
.unwrap();
assert_eq!(ordinary.target, id(2));
assert!(complete.names().contains_key(&lower::Target::Parameter {
calculation: id(10),
parameter: analysis::ParameterId(id(1))
}));
}
#[test]
fn small_stack_success_and_late_header_failure_leave_no_partial_owner() {
std::thread::Builder::new()
.stack_size(128 * 1024)
.spawn(|| {
let bundle = rich_bundle(Layer::Oa);
drop(indexed(&bundle).index_ordinary().unwrap());
let mut bad = indexed(&bundle);
bad.data
.names
.insert(lower::Target::Global(id(999)), "unowned".into());
fail(
bad,
"Global name is missing its authored declaration header.",
"main.arcadia",
);
let expression = format!("{}1{}", "abs(".repeat(127), ")".repeat(127));
let deep = single(&format!(
"calculation c \"C\" id \"{}\" {{ result Number; body {expression}; }}",
id(2)
));
drop(indexed(&deep).index_ordinary().unwrap());
})
.unwrap()
.join()
.unwrap();
}
#[test]
fn duplicate_global_header_and_cross_file_projection_damage_are_distinct() {
let bundle = rich_bundle(Layer::Oa);
let mut duplicate = indexed(&bundle);
let mut key = duplicate
.data
.occurrences
.keys()
.find(|k| k.role == index::TokenRole::Declaration && k.owner == id(10))
.unwrap()
.clone();
key.field = Some(lower::Field::Body);
duplicate.data.occurrences.insert(
key,
index::Location {
file: 2,
span: span(291, 292),
},
);
fail(
duplicate,
"Global declaration header appears more than once.",
"main.arcadia",
);
let mut bundle = single("include \"a.arcadia\"; include \"b.arcadia\";");
for (file, alias, n) in [("a.arcadia", "aa", 2), ("b.arcadia", "bb", 3)] {
bundle.files.insert(
file.into(),
format!("oa_activity {alias} \"Same\" id \"{}\" {{}}", id(n)),
);
}
let mut damaged = indexed(&bundle);
let other = damaged.resolved.syntax.files[1].positions.remove(0);
damaged.resolved.syntax.files[0].positions[0] = other;
fail(
damaged,
"Ordinary position differs from its declaration owner.",
"a.arcadia",
);
}
#[test]
fn nested_ordinary_declarations_use_no_typed_frame_policy() {
std::thread::Builder::new()
.stack_size(128 * 1024)
.spawn(|| {
let mut body = String::new();
for n in 2..34 {
body.push_str(&format!("oa_activity a{n} \"A\" id \"{}\" {{\n", id(n)));
}
body.push_str(&"}\n".repeat(32));
let bundle = single(&body);
let indexed = indexed(&bundle);
let usage = indexed.resolved.syntax.budget.usage;
let before = indexed.resolved.storage.live;
let complete = indexed.index_ordinary().unwrap();
assert_eq!(complete.ordinary.len(), 33);
assert!(
complete
.ordinary
.values()
.all(|o| o.role == OrdinaryRole::Header)
);
assert_eq!(complete.indexed.resolved.syntax.budget.usage, usage);
assert_eq!(
complete.indexed.resolved.storage.live,
before.add(Slots::entries(33), Span::default()).unwrap()
);
})
.unwrap()
.join()
.unwrap();
}
#[test]
fn malformed_reference_utf8_start_is_reported_without_slicing_panic() {
let mut bundle = rich_bundle(Layer::Oa);
*bundle.files.get_mut("main.arcadia").unwrap() =
bundle.files["main.arcadia"].replace("diagram @d", "diagram @ /* 雨 */d");
let mut damaged = indexed(&bundle);
let byte = bundle.files["main.arcadia"].find('雨').unwrap() + 1;
node_field(&mut damaged, "diagram").value.span.start = byte;
let diagnostic = fail(
damaged,
"Ordinary span is outside its original UTF-8 file.",
"main.arcadia",
);
assert_eq!(diagnostic.diagnostic.span, Span::default());
}
const CATEGORIES: &str = include_str!("tests/fixtures/categories.arcadia");
fn categories() -> SourceBundle {
SourceBundle {
entry: "main.arcadia".into(),
files: BTreeMap::from([("main.arcadia".into(), CATEGORIES.into())]),
}
}
#[test]
fn every_authored_category_reference_excludes_trivia_prose_and_historical_uuid() {
use OrdinaryRole::{Header as H, Reference as R};
let bundle = categories();
assert_eq!(CATEGORIES.len(), 2152);
let indexed = indexed(&bundle);
let model = indexed.project().clone();
let warnings = indexed.warnings().to_vec();
let before = indexed.resolved.storage.live;
let usage = indexed.resolved.syntax.budget.usage;
let numeric = indexed.resolved.syntax.budget.numeric_units();
let complete = indexed.index_ordinary().unwrap();
// Entire independently authored36-row map, checked by literal UTF-8 arithmetic.
let rows = [
(19, 20, 1, 1, H, 19),
(168, 169, 2, 2, H, 168),
(249, 254, 3, 3, H, 249),
(344, 345, 3, 2, R, 323),
(369, 377, 4, 4, H, 369),
(487, 488, 5, 5, H, 487),
(561, 565, 6, 6, H, 561),
(625, 630, 6, 3, R, 624),
(640, 648, 6, 4, R, 639),
(664, 665, 7, 7, H, 664),
(741, 745, 7, 6, R, 740),
(789, 793, 7, 6, R, 788),
(818, 822, 8, 8, H, 818),
(885, 889, 8, 6, R, 884),
(925, 926, 9, 9, H, 925),
(992, 997, 9, 3, R, 991),
(1003, 1004, 9, 5, R, 1002),
(1328, 1329, 10, 10, H, 1328),
(1391, 1394, 10, 12, R, 1390),
(1397, 1402, 10, 14, R, 1396),
(1422, 1425, 11, 11, H, 1422),
(1487, 1491, 11, 13, R, 1486),
(1514, 1517, 12, 12, H, 1514),
(1577, 1578, 12, 10, R, 1576),
(1617, 1620, 12, 11, R, 1616),
(1648, 1652, 13, 13, H, 1648),
(1712, 1715, 13, 11, R, 1711),
(1741, 1746, 13, 3, R, 1740),
(1789, 1794, 14, 14, H, 1789),
(1855, 1856, 14, 10, R, 1854),
(1882, 1890, 14, 4, R, 1881),
(1935, 1942, 15, 15, H, 1935),
(2004, 2005, 15, 10, R, 2003),
(2029, 2032, 15, 12, R, 2028),
(2047, 2051, 15, 13, R, 2046),
(2092, 2097, 15, 14, R, 2091),
];
let expected = rows
.into_iter()
.map(|(start, end, owner, target, role, extent)| {
((0, start, end), record(owner, target, role, extent, end))
})
.collect::<BTreeMap<_, _>>();
assert_eq!(complete.ordinary_occurrences(), &expected);
assert_eq!(complete.project(), &model);
assert_eq!(complete.warnings(), warnings);
assert!(std::ptr::eq(complete.bundle(), &bundle));
assert!(complete.typed_occurrences().is_empty());
assert!(complete.expressions().is_empty());
assert_eq!(
complete.indexed.resolved.storage.live,
before.add(Slots::entries(36), Span::default()).unwrap()
);
assert_eq!(complete.indexed.resolved.syntax.budget.usage, usage);
assert_eq!(
complete.indexed.resolved.syntax.budget.numeric_units(),
numeric
);
assert_eq!(complete.project().elements[&id(3)].parent, Some(id(2)));
assert_eq!(complete.project().elements[&id(4)].parent, Some(id(2)));
assert!(
!complete
.ordinary
.values()
.any(|o| o.owner == id(4) && o.role == R),
"implicit parent has no authored token"
);
assert_eq!(&CATEGORIES[323..345], "@ /* @noise child */ a");
let Some(semantic) = complete.project().traces[&id(9)]
.baseline
.as_ref()
.unwrap()
.semantic
.as_ref()
else {
panic!("historical semantic baseline")
};
assert!(
matches!(&semantic.facets[0],crate::semantic::FacetReview::Field(crate::semantic::FieldReview::Type(review)) if review.oa==crate::semantic::Observed::Value(crate::TypeRef::Named(id(3))))
);
}
#[test]
fn exact_scratch_peak_reserves_live_iterative_slots_before_growth() {
let bundle = categories();
let baseline = indexed(&bundle).resolved.storage.live;
// Context header leavesN32; G15; four root fields occupyW4. Popping
// target then pushing its two fields reachesW5: exactly52 added slots.
for (extra, fits) in [(51, false), (52, true)] {
let profile = super::super::super::super::budget::Limits {
entries: baseline.entries + extra,
..super::super::super::super::budget::Limits::source8(language::ParseLimits::UNLIMITED)
};
let parsed =
super::super::super::parse_with_profile(&bundle, ProjectLimits::default(), profile)
.unwrap();
let indexed = resolve(parsed).unwrap().index().unwrap();
assert_eq!(indexed.resolved.storage.live, baseline);
if fits {
assert_eq!(
indexed
.index_ordinary()
.unwrap()
.indexed
.resolved
.storage
.live,
baseline.add(Slots::entries(36), Span::default()).unwrap()
);
} else {
let diagnostic = fail(
indexed,
"Source8 lowering entry limit exceeded.",
"main.arcadia",
);
let start = CATEGORIES.find("references []; occurrence @right").unwrap() + 11;
assert_eq!(diagnostic.diagnostic.span, span(start, start + 2));
}
}
}
@@ -0,0 +1,29 @@
arcadia 8;
project p "Authored @p child" id "00000000-0000-0000-0000-000000000001" {
description "Do not index @child, @link or uuid in prose 雨";
oa_activity a "Parent child" id "00000000-0000-0000-0000-000000000002" {
oa_activity child "Explicit child" id "00000000-0000-0000-0000-000000000003" { parent @ /* @noise child */ a; }
oa_activity implicit "Implicit child" id "00000000-0000-0000-0000-000000000004" { description "child @a"; }
}
sa_function s "Selected" id "00000000-0000-0000-0000-000000000005" {}
interaction link "Link" id "00000000-0000-0000-0000-000000000006" { source @child; target @implicit; }
diagram d "View" id "00000000-0000-0000-0000-000000000007" {
routes [{ edge @link; }];
view_options { hidden_edges [@link]; };
}
artifact note "Evidence" id "00000000-0000-0000-0000-000000000008" { owner @link; uri "docs/@child.md"; }
trace t "Review" id "00000000-0000-0000-0000-000000000009" {
oa @child; sa @s; kind Manual;
baseline {
oa { name "child"; description "@child"; };
sa { name "s"; description "@s"; };
semantic { facets [{ Field { Type { oa { Value { Named uuid "00000000-0000-0000-0000-000000000003"; }; }; sa { Value Boolean; }; }; }; }]; };
};
}
oa_process q "Caller" id "00000000-0000-0000-0000-000000000010" { steps [@use, @right]; }
oa_process sub "Callee" id "00000000-0000-0000-0000-000000000011" { steps [@left]; }
oa_occurrence use "Call" id "00000000-0000-0000-0000-000000000012" { parent @q; body { SequenceReference { sequence @sub; }; }; }
oa_occurrence left "Left" id "00000000-0000-0000-0000-000000000013" { parent @sub; body { Action { action @child; lifeline none; }; }; }
oa_occurrence right "Right" id "00000000-0000-0000-0000-000000000014" { parent @q; body { Action { action @implicit; lifeline none; }; }; }
contextual_link context "Context" id "00000000-0000-0000-0000-000000000015" { owner @q; source { references [@use]; occurrence @left; }; target { references []; occurrence @right; }; purpose { Precedence { guard "@child"; }; }; }
}
@@ -6,7 +6,7 @@ use crate::analysis_numeric::Exact;
use crate::source_project::{ProjectLimits, SourceBundle};
use crate::*;
fn id(n: usize) -> Id {
pub(super) fn id(n: usize) -> Id {
Id::parse_str(&format!("00000000-0000-0000-0000-{n:012}")).unwrap()
}
fn doc(body: &str) -> String {
@@ -15,7 +15,7 @@ fn doc(body: &str) -> String {
id(1)
)
}
fn single(body: &str) -> SourceBundle {
pub(super) fn single(body: &str) -> SourceBundle {
SourceBundle {
entry: "main.arcadia".into(),
files: BTreeMap::from([("main.arcadia".into(), doc(body))]),
@@ -144,7 +144,7 @@ fn expected(layer: Layer) -> Project {
)]),
}
}
fn accepted(bundle: &SourceBundle) -> ResolvedMixedProject<'_> {
pub(super) fn accepted(bundle: &SourceBundle) -> ResolvedMixedProject<'_> {
resolve(parse(bundle, ProjectLimits::default()).unwrap()).unwrap_or_else(|e| panic!("{e:?}"))
}
fn rejected(bundle: &SourceBundle) -> Failure {
@@ -250,7 +250,7 @@ fn complete_oa_sa_forward_model_warning_and_original_authority() {
}
}
fn rich_bundle(layer: Layer) -> SourceBundle {
pub(super) fn rich_bundle(layer: Layer) -> SourceBundle {
let mut bundle = bundle(layer);
let main = bundle.files.get_mut("main.arcadia").unwrap();
*main = main
@@ -270,7 +270,7 @@ fn rich_bundle(layer: Layer) -> SourceBundle {
}
bundle
}
fn rich_model(layer: Layer) -> Project {
pub(super) fn rich_model(layer: Layer) -> Project {
let mut model = expected(layer);
for (alias, name, n, op) in [
("f", "F", 10, a::BinaryOp::Add),