//! 校验任务提交、受限 worker 边界及等待的具体结果类型。 mod liveness; mod ownership; use super::Context; use crate::compiler::model::*; use crate::syntax::{ast, diagnostic::Diagnostic}; pub(super) use ownership::check; impl Context<'_> { pub(super) fn schedule( &mut self, operation: Schedule, arguments: &[ast::Expression], expected: Option<&Type>, source: &ast::Expression, ) -> Result { if operation == Schedule::Run && arguments.len() == 2 { return self.group_run(arguments, expected, source); } let [call] = arguments else { return Err(self.error(source, "task boundary requires one function call")); }; let value = self.expression(call, None)?; let Node::Call(function, values) = value.kind else { return Err(self.error(source, "task boundary requires an ordinary function call")); }; let signature = &self.signatures[function]; if operation != Schedule::Run && (signature.effects.suspend || signature.effects.handler || signature.effects.schedule || (operation == Schedule::Parallel && !signature.pure) || (operation == Schedule::Blocking && !signature.effects.blocking)) { return Err(Diagnostic::new( "C013", "worker boundary requires a pure or known blocking synchronous function", source.span, )); } if !transferable(signature, self.definitions) { return Err(Diagnostic::new( "C013", "task inputs, outputs and failure payloads must be transferable", source.span, )); } self.coerce( Expression { value_type: if operation == Schedule::Run { Type::Task(function) } else { signature.output() }, kind: Node::Schedule(operation, function, values), span: source.span, }, expected, ) } pub(super) fn join( &mut self, stop: bool, arguments: &[ast::Expression], expected: Option<&Type>, source: &ast::Expression, ) -> Result { let [argument] = arguments else { return Err(self.error(source, "wait/stop requires one Task")); }; let task = self.expression(argument, None)?; let output = match task.value_type { Type::Task(function) => self.signatures[function].output(), Type::Group(_) => Type::Unit, _ => return Err(self.error(source, "wait/stop requires a Task or Group")), }; if stop && output != Type::Unit { return Err(self.error(source, "stop requires a zero-output Task")); } self.coerce( Expression { value_type: output, kind: Node::Join { task: Box::new(task), stop, }, span: source.span, }, expected, ) } pub(super) fn group( &mut self, arguments: &[ast::Expression], expected: Option<&Type>, source: &ast::Expression, ) -> Result { let [limit] = arguments else { return Err(self.error(source, "group requires one limit")); }; let limit = self.expression(limit, Some(&Type::Int))?; self.coerce( Expression { value_type: Type::Group(self.function), kind: Node::Group(Box::new(limit)), span: source.span, }, expected, ) } fn group_run( &mut self, arguments: &[ast::Expression], expected: Option<&Type>, source: &ast::Expression, ) -> Result { let group = self.expression(&arguments[0], None)?; if !matches!(group.value_type, Type::Group(_)) || !matches!(group.kind, Node::Variable(_)) { return Err(self.error(source, "run(group, call) requires a local Group")); } let scheduled = self.schedule(Schedule::Run, &arguments[1..], None, source)?; let Node::Schedule(Schedule::Run, target, values) = scheduled.kind else { unreachable!("checked run"); }; if !self.signatures[target].outputs.is_empty() { return Err(self.error(source, "Group tasks must have zero outputs")); } self.coerce( Expression { value_type: Type::Unit, kind: Node::GroupRun { group: Box::new(group), target, arguments: values, }, span: source.span, }, expected, ) } pub(super) fn timeout( &mut self, arguments: &[ast::Expression], expected: Option<&Type>, source: &ast::Expression, ) -> Result { let [task, millis, fallback] = arguments else { return Err(self.error( source, "timeout requires a Task, duration and static fallback", )); }; let task = self.expression(task, None)?; let Type::Task(target) = task.value_type else { return Err(self.error(source, "timeout requires a Task")); }; let millis = self.expression(millis, Some(&Type::Int))?; let fallback = self .scope .targets .get(&fallback.span.start) .copied() .ok_or_else(|| { self.error( source, "timeout fallback must be a zero-input static function", ) })?; if !self.signatures[fallback].inputs.is_empty() || self.signatures[fallback].outputs != self.signatures[target].outputs { return Err(self.error( source, "timeout fallback must have the Task's named output signature", )); } self.coerce( Expression { value_type: self.signatures[target].output(), kind: Node::Timeout { task: Box::new(task), millis: Box::new(millis), fallback, }, span: source.span, }, expected, ) } pub(super) fn race( &mut self, arguments: &[ast::Expression], expected: Option<&Type>, source: &ast::Expression, ) -> Result { if arguments.len() < 2 { return Err(self.error(source, "race requires at least two Tasks")); } let mut tasks = Vec::new(); let mut signature = None; for argument in arguments { let task = self.expression(argument, None)?; let Type::Task(target) = task.value_type else { return Err(self.error(argument, "race arguments must be Tasks")); }; let outputs = &self.signatures[target].outputs; if signature.is_some_and(|previous| previous != outputs) { return Err(self.error(argument, "racing Tasks must have identical named outputs")); } signature = Some(outputs); tasks.push(task); } let Type::Task(first) = tasks[0].value_type else { unreachable!("checked Task"); }; self.coerce( Expression { value_type: self.signatures[first].output(), kind: Node::Race(tasks), span: source.span, }, expected, ) } pub(super) fn timer( &mut self, timer: Timer, arguments: &[ast::Expression], expected: Option<&Type>, source: &ast::Expression, ) -> Result { let package = if timer == Timer::Delay { "dever.task" } else { "dever.time" }; if self.definition_names.packages[self.source] != package { return Err(Diagnostic::new( "C006", "timer intrinsics belong to their official package", source.span, )); } let [millis] = arguments else { return Err(self.error(source, "sleep requires one duration")); }; let value = self.expression(millis, Some(&Type::Int))?; let value_type = self.io_result("dever.time.SleepResult", &[("Done", vec![])], source)?; self.coerce( Expression { value_type, kind: Node::Timer(timer, Box::new(value)), span: source.span, }, expected, ) } } pub(super) fn transferable(signature: &Signature, definitions: &[Definition]) -> bool { // 入队继承入口提供的只读执行身份;请求、Cookie 和可变认证上下文仍不能离开请求。 !signature.effects.request && signature .inputs .iter() .chain(&signature.outputs) .all(|(_, kind)| kind.transferable(definitions)) && signature.failures.iter().all(|failure| { let Shape::Choice(variants) = &definitions[failure.definition].shape else { unreachable!() }; variants[failure.variant] .fields .iter() .all(|(_, kind)| kind.transferable(definitions)) }) }