285 lines
9.7 KiB
Rust
285 lines
9.7 KiB
Rust
//! 校验任务提交、受限 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<Expression, Diagnostic> {
|
|
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<Expression, Diagnostic> {
|
|
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<Expression, Diagnostic> {
|
|
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<Expression, Diagnostic> {
|
|
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<Expression, Diagnostic> {
|
|
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<Expression, Diagnostic> {
|
|
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<Expression, Diagnostic> {
|
|
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))
|
|
})
|
|
}
|