257 lines
8.6 KiB
Rust
257 lines
8.6 KiB
Rust
//! 逐分句追踪业务失败的处理义务,并推导函数失败集合。
|
|
//! 只有返回、传播或显式恢复才能解除义务;分析结果不改变运行时错误身份。
|
|
|
|
mod capture;
|
|
mod collection;
|
|
mod expression;
|
|
mod flow;
|
|
mod storage;
|
|
mod stream;
|
|
mod task;
|
|
|
|
use crate::compiler::model::*;
|
|
use crate::syntax::{diagnostic::Diagnostic, source::Span};
|
|
use std::collections::{BTreeMap, BTreeSet};
|
|
|
|
pub(super) fn check(
|
|
function: &mut Function,
|
|
definitions: &[Definition],
|
|
signatures: &[Signature],
|
|
database_error: Option<usize>,
|
|
) -> Result<(), Diagnostic> {
|
|
let tracked_types = flow::tracked_types(definitions);
|
|
let mut next = 0;
|
|
let mut failures = BTreeSet::new();
|
|
let mut returns: Option<Flow> = None;
|
|
let mut inputs: Vec<Option<Flow>> = vec![None; function.signature.inputs.len()];
|
|
for clause in &function.clauses {
|
|
let mut state = State {
|
|
database_error,
|
|
definitions,
|
|
signatures,
|
|
tracked_types: &tracked_types,
|
|
values: vec![Flow::default(); clause.variables.len()],
|
|
pending: BTreeSet::new(),
|
|
closed_streams: BTreeSet::new(),
|
|
failures: BTreeSet::new(),
|
|
next,
|
|
work: 0,
|
|
span: clause.span,
|
|
};
|
|
for (position, (_, kind)) in function.signature.inputs.iter().enumerate() {
|
|
let pattern = &clause.patterns[position];
|
|
let value = match (&clause.variables[position].1, pattern) {
|
|
(_, Pattern::Null) => Flow::default(),
|
|
(Type::Named(index), _)
|
|
if matches!(definitions[*index].shape, Shape::Choice(_)) =>
|
|
{
|
|
let Shape::Choice(variants) = &definitions[*index].shape else {
|
|
unreachable!()
|
|
};
|
|
let mut possibilities = Vec::new();
|
|
for variant in 0..variants.len() {
|
|
if super::clauses::accepts_variant(
|
|
pattern,
|
|
variant,
|
|
function
|
|
.clauses
|
|
.iter()
|
|
.map(|clause| clause.patterns[position].clone()),
|
|
) {
|
|
possibilities
|
|
.push((variant, state.unknown_variant(*index, variant, 0)?));
|
|
}
|
|
}
|
|
Flow::variants(possibilities)
|
|
}
|
|
(kind, _) => state.unknown(kind, 0)?,
|
|
};
|
|
let input = if matches!(kind, Type::Optional(_))
|
|
&& matches!(pattern, Pattern::Present(_) | Pattern::Other)
|
|
{
|
|
Flow::variants([(1, value.clone())])
|
|
} else if matches!(pattern, Pattern::Null) {
|
|
Flow::variants([(0, Flow::default())])
|
|
} else {
|
|
value.clone()
|
|
};
|
|
if let Some(previous) = &mut inputs[position] {
|
|
previous.merge(&input);
|
|
} else {
|
|
inputs[position] = Some(input);
|
|
}
|
|
state.values[position] = value;
|
|
}
|
|
for binding in &clause.bindings {
|
|
let Pattern::Variant(variant) = clause.patterns[binding.input].inner() else {
|
|
unreachable!()
|
|
};
|
|
state.values[binding.variable] = state.values[binding.input]
|
|
.variants
|
|
.get(variant)
|
|
.and_then(|value| value.fields.get(&binding.field))
|
|
.cloned()
|
|
.unwrap_or_default();
|
|
}
|
|
for statement in &clause.body {
|
|
match statement {
|
|
Statement::Assign {
|
|
variable,
|
|
fields,
|
|
value,
|
|
} => {
|
|
let value = state.expression(value)?;
|
|
flow::assign(&mut state.values[*variable], fields, value);
|
|
}
|
|
Statement::Action(value) => {
|
|
state.expression(value)?;
|
|
}
|
|
Statement::Fail(value) => state.fail(value)?,
|
|
}
|
|
}
|
|
if !clause.terminates {
|
|
let mut output = match clause.outputs.as_slice() {
|
|
[] => Flow::default(),
|
|
[index] => state.values[*index].clone(),
|
|
outputs => Flow::fields(
|
|
outputs
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(field, index)| (field, state.values[*index].clone())),
|
|
),
|
|
};
|
|
state.refresh_streams(&mut output);
|
|
if function.signature.recovery.is_none()
|
|
&& !output.guaranteed
|
|
&& !state.pending.is_subset(&output.tokens)
|
|
{
|
|
return Err(Diagnostic::new(
|
|
"C012",
|
|
"failure is not handled: return, propagate, or declare an explicit recovery policy",
|
|
clause.span,
|
|
));
|
|
}
|
|
if let Some(previous) = &mut returns {
|
|
previous.merge(&output);
|
|
} else {
|
|
returns = Some(output);
|
|
}
|
|
}
|
|
next = state.next;
|
|
failures.extend(state.failures);
|
|
}
|
|
function.signature.failures = failures.into_iter().collect();
|
|
function.signature.input_flows = inputs.into_iter().map(Option::unwrap).collect();
|
|
function.signature.returns = returns;
|
|
Ok(())
|
|
}
|
|
|
|
struct State<'program> {
|
|
database_error: Option<usize>,
|
|
definitions: &'program [Definition],
|
|
signatures: &'program [Signature],
|
|
tracked_types: &'program [bool],
|
|
values: Vec<Flow>,
|
|
/// 尚未由当前分句处理或转移出去的错误义务。
|
|
pending: BTreeSet<usize>,
|
|
closed_streams: BTreeSet<usize>,
|
|
failures: BTreeSet<Failure>,
|
|
next: usize,
|
|
work: usize,
|
|
span: Span,
|
|
}
|
|
|
|
pub(super) fn prototype(
|
|
signature: &mut Signature,
|
|
definitions: &[Definition],
|
|
span: Span,
|
|
) -> Result<(), Diagnostic> {
|
|
let tracked_types = flow::tracked_types(definitions);
|
|
let mut state = State {
|
|
database_error: None,
|
|
definitions,
|
|
signatures: &[],
|
|
tracked_types: &tracked_types,
|
|
values: Vec::new(),
|
|
pending: BTreeSet::new(),
|
|
closed_streams: BTreeSet::new(),
|
|
failures: BTreeSet::new(),
|
|
next: 0,
|
|
work: 0,
|
|
span,
|
|
};
|
|
signature.input_flows = signature
|
|
.inputs
|
|
.iter()
|
|
.map(|(_, kind)| state.unknown(kind, 0))
|
|
.collect::<Result<_, _>>()?;
|
|
signature.returns = Some(match signature.outputs.as_slice() {
|
|
[] => Flow::default(),
|
|
[(_, kind)] => state.unknown(kind, 0)?,
|
|
fields => Flow::fields(
|
|
fields
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(index, (_, kind))| Ok((index, state.unknown(kind, 0)?)))
|
|
.collect::<Result<Vec<_>, Diagnostic>>()?,
|
|
),
|
|
});
|
|
Ok(())
|
|
}
|
|
|
|
impl State<'_> {
|
|
fn symbol(&mut self) -> Result<usize, Diagnostic> {
|
|
self.budget(0)?;
|
|
let symbol = self.next;
|
|
self.next += 1;
|
|
Ok(symbol)
|
|
}
|
|
|
|
fn token(&mut self) -> Result<usize, Diagnostic> {
|
|
let token = self.symbol()?;
|
|
self.pending.insert(token);
|
|
Ok(token)
|
|
}
|
|
|
|
fn budget(&mut self, depth: usize) -> Result<(), Diagnostic> {
|
|
// 达到分析边界就报告失败,不能把未完成的证明当作合同通过。
|
|
self.work += 1;
|
|
if self.work > 16_384 || depth > 128 {
|
|
return Err(Diagnostic::new(
|
|
"C015",
|
|
"failure analysis exceeds its structural limit",
|
|
self.span,
|
|
));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn arguments(
|
|
&mut self,
|
|
arguments: &[Expression],
|
|
signature: &Signature,
|
|
) -> Result<BTreeMap<usize, BTreeSet<usize>>, Diagnostic> {
|
|
self.call_inputs(arguments, signature, true)
|
|
}
|
|
|
|
fn call_inputs(
|
|
&mut self,
|
|
arguments: &[Expression],
|
|
signature: &Signature,
|
|
consume: bool,
|
|
) -> Result<BTreeMap<usize, BTreeSet<usize>>, Diagnostic> {
|
|
let mut mapping = BTreeMap::new();
|
|
let values = arguments
|
|
.iter()
|
|
.map(|argument| self.expression(argument))
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
for (value, template) in values.iter().zip(&signature.input_flows) {
|
|
flow::bind(template, value, &mut mapping);
|
|
if consume {
|
|
self.pending.retain(|token| !value.tokens.contains(token));
|
|
}
|
|
}
|
|
Ok(mapping)
|
|
}
|
|
}
|