feat: index complete authored names from retained mixed syntax
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user