Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee500908e3 | ||
|
|
147b247398 | ||
|
|
50a5918fd6 |
@@ -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,694 @@
|
||||
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..26)
|
||||
.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;
|
||||
// G13 + T21 + N12 =46, final ordinary header. Six extra leaf activities
|
||||
// add six retained warnings and eighteen lower-catalog slots. The prior
|
||||
// passing resolver cap97 therefore needs at most109; this stage needs111.
|
||||
for (extra, fits) in [(45, false), (46, 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(12), Span::default()).unwrap()
|
||||
);
|
||||
} else {
|
||||
let diagnostic = fail(
|
||||
indexed,
|
||||
"Source8 lowering entry limit exceeded.",
|
||||
"main.arcadia",
|
||||
);
|
||||
let at = bundle.files["main.arcadia"].find("extra25").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);
|
||||
// Reclassify an existing whole call token as a second header; remove its
|
||||
// former role so physical uniqueness cannot mask the header-coverage check.
|
||||
let call = duplicate
|
||||
.data
|
||||
.occurrences
|
||||
.iter()
|
||||
.find(|(_, at)| at.file == 2 && at.span == span(291, 292))
|
||||
.unwrap()
|
||||
.0
|
||||
.clone();
|
||||
duplicate.data.occurrences.remove(&call);
|
||||
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 @ // 雨\r\n 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\r\n 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 mut bundle = categories();
|
||||
let points = ["{ x 0; y 0; }"; 64].join(", ");
|
||||
let source = bundle.files.get_mut("main.arcadia").unwrap();
|
||||
*source = source.replace(
|
||||
"routes [{ edge @link; }];",
|
||||
&format!("routes [{{ edge @link; waypoints [{points}]; }}];"),
|
||||
);
|
||||
let baseline = indexed(&bundle).resolved.storage.live;
|
||||
// Unexpanded category peak52 is below the earlier resolver catalog/Vec
|
||||
// overlap. Sixty-four valid route points isolate this stage: N11 + G15,
|
||||
// pending edge1 + point values64, then pop one and push x/y =>W66.
|
||||
// Exactly92 slots are live; earlier catalog overlap is only74.
|
||||
for (extra, fits) in [(91, false), (92, 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 = bundle.files["main.arcadia"].rfind("y 0").unwrap() + 2;
|
||||
assert_eq!(diagnostic.diagnostic.span, span(start, start + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
# This exact-byte source fixture deliberately retains its authored CRLF.
|
||||
categories.arcadia -text whitespace=cr-at-eol
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
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),
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Private authored ordinary-name index implementation
|
||||
|
||||
Before compilation, source checkpoint `50a5918f` exposed Git normalization of
|
||||
the CRLF category fixture. A narrow fixture attribute now preserves its exact
|
||||
2,152 bytes and authored span oracle. Test-only resource variants were also
|
||||
strengthened to reach this new stage after prior phases fit; the initial source
|
||||
was not built or runtime-tested. Production bytes are unchanged by these repairs.
|
||||
|
||||
Local implementation on the `93eb6117f95353d25d594d59780018f197c51672` private lineage, following the root accepted ordinary-index contract (2026-09-24). Runtime acceptance is pending the parent-owned compiler/test gates; this note records design and authored evaluators, not passing results.
|
||||
|
||||
`IndexedMixedProject::index_ordinary(self)` consumes the admitted typed owner and returns `ordinary_index::AuthoredMixedProject`. It retains that owner and one map keyed by canonical file and whole alias-token byte range. Inline records carry owner/global target, Header/Reference role and full original extent. Read-only methods expose both occurrence families, expression keys, names, complete Project, warnings and original bundle/files. No adapter to legacy Index/Plan/Composition or independently replaceable source/profile is added.
|
||||
|
||||
Construction borrows original significant tokens and the sealed ordinary AST/position pairs. Headers are checked against declaration occurrences, not enclosing ranges; the synthetic root Record includes body braces, while real field/value extents are checked within their justified parents. Every Reference leaf is visited, including nested records/lists and arbitrary ordinary fields. A Reference must start at the significant `@` and end exactly at the immediately following whole Word. HistoricalUuid, Atom and prose/include/identity strings are excluded. No reparse, lower, model copy, name copy, numeric work, admission or frame call occurs. Completeness trusts the sealed projection plus exhaustive authored oracles; per-record checks do not establish arbitrary coordinated AST/projection omission rejection.
|
||||
|
||||
The inherited ledger remains live. New retention is exactly N=1+D+A entry slots, zero occurrence slots, zero text bytes. Scratch is G borrowed global-name entries with inline header-seen flags, T typed physical tokens and W currently queued borrowed values. Reserve before every map/set/work growth; a pop releases one logical slot; globals and typed lookup release after their containers drop. Work drains per declaration; the synthetic root is not queued. Actual rich OA/SA stage peaks are incoming baseline+34/+36 (retained+6/+7), but earlier typed peaks prevent those profiles from isolating this later stage. Rich plus six empty activities uses a later-stage46/45 boundary (retained12), clearing the preceding lower-catalog overlap including retained warnings; unexpanded category fixture has stage peak52 with W5, but that can be below preceding resolver overlap. The scratch-cap test adds64 finite route waypoints to a separate in-memory variant and uses92/91 with W66 (retained36). These hand-counts still require runtime verification, including earlier-stage admission.
|
||||
|
||||
Authored tests cover complete OA/SA maps with literal numeric spans, independent Unicode/CRLF spans, exact rich Project/model metadata retention, canonical coincident offsets and cross-file references, global versus calculation parameter spelling, nested explicit/implicit parents, comment-separated markers, relation/context/route/view/artifact/trace categories, historical UUID exclusion, precise corruption diagnostics and safe fallback, retained/scratch caps without Budget replacement, and bounded-stack success/late failure. The category literal initially contained an invalid duplicate route; the independent oracle critic caught it before compilation and the duplicate was removed. The corrected fixture has36 independently enumerated records (15 headers, 21 references). No inherited compatibility test or known failure disposition is removed.
|
||||
|
||||
Focused runtime at `147b247` reported 9 passing and 4 failing tests (parent-owned gate). Three failures were pre-index refusals from incorrectly authored C-style block comments: the shared raw scanner only treats `//` through newline as Comment. Category trivia now uses a byte-neutral line-comment replacement, preserving all36 numeric records and the full marker/alias extents; the UTF-8 corruption fixture likewise first admits a real line comment before damaging its retained start. The fourth control accidentally duplicated an already indexed call token before it could test header coverage; it now removes that token's former call role before assigning the forged second Header role. Intended later diagnostics remain unchanged. These are test/fixture corrections; production is unchanged, and the 147b247 failures remain evidence. Retest pending.
|
||||
Reference in New Issue
Block a user