168 lines
6.1 KiB
Rust
168 lines
6.1 KiB
Rust
//! 为 result(call) 选择唯一且名义身份匹配的 choice。
|
|
//! 仅载荷形状相同不足以匹配业务错误;数值 Fault 不进入捕获集合。
|
|
|
|
use crate::compiler::{check::Context, model::*};
|
|
use crate::syntax::{ast, diagnostic::Diagnostic};
|
|
use std::collections::BTreeMap;
|
|
|
|
impl Context<'_> {
|
|
pub(crate) fn capture(
|
|
&mut self,
|
|
call: &ast::Expression,
|
|
expected: Option<&Type>,
|
|
source: &ast::Expression,
|
|
) -> Result<Expression, Diagnostic> {
|
|
let target = match expected {
|
|
Some(Type::Optional(inner)) => Some(inner.as_ref()),
|
|
expected => expected,
|
|
};
|
|
// 泛型解码直接使用结果 Choice 的成功字段作为类型上下文,不生成包装函数。
|
|
let decoded = if matches!(&call.kind, ast::ExpressionKind::Call(target, _)
|
|
if target.path().as_deref() == Some("dever.json.decode"))
|
|
{
|
|
let Some(Type::Named(index)) = target else {
|
|
return Err(self.error(
|
|
source,
|
|
"result(dever.json.decode(...)) requires an explicit result choice",
|
|
));
|
|
};
|
|
let Shape::Choice(variants) = &self.definitions[*index].shape else {
|
|
return Err(self.error(source, "JSON decode result requires a choice"));
|
|
};
|
|
let mut successes = variants.iter().filter(|variant| !variant.error);
|
|
let (Some(success), None) = (successes.next(), successes.next()) else {
|
|
return Err(self.error(
|
|
source,
|
|
"JSON decode result requires exactly one success variant",
|
|
));
|
|
};
|
|
let [(_, kind)] = success.fields.as_slice() else {
|
|
return Err(self.error(source, "JSON decode success variant requires one value"));
|
|
};
|
|
Some(kind.clone())
|
|
} else {
|
|
None
|
|
};
|
|
let value = self.expression(call, decoded.as_ref())?;
|
|
let decoded_failure;
|
|
let (origin, failures) = match &value.kind {
|
|
Node::Call(function, _) => {
|
|
let signature = &self.signatures[*function];
|
|
(signature.source, signature.failures.as_slice())
|
|
}
|
|
Node::Json(JsonOperation::Decode { error }, _) => {
|
|
decoded_failure = [Failure {
|
|
definition: *error,
|
|
variant: 1,
|
|
}];
|
|
(self.definitions[*error].source, decoded_failure.as_slice())
|
|
}
|
|
_ => {
|
|
return Err(self.error(
|
|
source,
|
|
"result requires a function call or typed JSON decode",
|
|
));
|
|
}
|
|
};
|
|
let mut candidates = Vec::new();
|
|
for (index, definition) in self.definitions.iter().enumerate() {
|
|
if definition.source != self.source && !self.definition_names.exported(index) {
|
|
continue;
|
|
}
|
|
if target.is_none()
|
|
&& definition.source != self.source
|
|
&& definition.source != origin
|
|
&& !failures.iter().any(|failure| failure.definition == index)
|
|
{
|
|
continue;
|
|
}
|
|
if target.is_some_and(|kind| *kind != Type::Named(index)) {
|
|
continue;
|
|
}
|
|
let Shape::Choice(variants) = &definition.shape else {
|
|
continue;
|
|
};
|
|
let successes = variants
|
|
.iter()
|
|
.enumerate()
|
|
.filter(|(_, variant)| !variant.error)
|
|
.collect::<Vec<_>>();
|
|
let [(success, variant)] = successes.as_slice() else {
|
|
continue;
|
|
};
|
|
let outputs_match = match &value.value_type {
|
|
Type::Unit => variant.fields.is_empty(),
|
|
Type::Outputs(outputs) => variant
|
|
.fields
|
|
.iter()
|
|
.map(|(_, kind)| kind)
|
|
.eq(outputs.iter().map(|(_, kind)| kind)),
|
|
kind => matches!(variant.fields.as_slice(), [(_, field)] if field == kind),
|
|
};
|
|
if !outputs_match {
|
|
continue;
|
|
}
|
|
if let Some(targets) = targets(index, variants, failures, self.definitions) {
|
|
candidates.push((index, *success, targets));
|
|
}
|
|
}
|
|
if candidates.len() != 1 {
|
|
return Err(Diagnostic::new(
|
|
"C005",
|
|
"result needs one unambiguous choice with matching success outputs and all declared errors",
|
|
source.span,
|
|
));
|
|
}
|
|
let (definition, success, targets) = candidates.pop().unwrap();
|
|
let capture = Capture {
|
|
call: value,
|
|
definition,
|
|
success,
|
|
targets,
|
|
};
|
|
self.coerce(
|
|
Expression {
|
|
kind: Node::Capture(Box::new(capture)),
|
|
value_type: Type::Named(definition),
|
|
span: source.span,
|
|
},
|
|
expected,
|
|
)
|
|
}
|
|
}
|
|
|
|
fn targets(
|
|
index: usize,
|
|
variants: &[Variant],
|
|
actual: &[Failure],
|
|
definitions: &[Definition],
|
|
) -> Option<Vec<(Failure, usize, bool)>> {
|
|
let mut mapping = BTreeMap::new();
|
|
for (position, variant) in variants.iter().enumerate().filter(|(_, value)| value.error) {
|
|
let direct = Failure {
|
|
definition: index,
|
|
variant: position,
|
|
};
|
|
if actual.contains(&direct) {
|
|
mapping.insert(direct, (position, false));
|
|
}
|
|
if let [(_, Type::Named(inner))] = variant.fields.as_slice()
|
|
&& let Shape::Choice(_) = &definitions[*inner].shape
|
|
{
|
|
for failure in actual.iter().filter(|failure| failure.definition == *inner) {
|
|
if mapping.insert(*failure, (position, true)).is_some() {
|
|
return None;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
actual
|
|
.iter()
|
|
.map(|failure| {
|
|
mapping
|
|
.get(failure)
|
|
.map(|(variant, wrapped)| (*failure, *variant, *wrapped))
|
|
})
|
|
.collect()
|
|
}
|