//! 为 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 { 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::>(); 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> { 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() }