Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,284 @@
|
||||
//! 校验任务提交、受限 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))
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user