470 lines
16 KiB
Rust
470 lines
16 KiB
Rust
use crate::diagnostic::Diagnostic;
|
|
use crate::hir::{CallArgument, CollectionOp, Expression, ExpressionKind, SequenceKind};
|
|
use crate::source::Span;
|
|
use crate::syntax::{ContextualExpression, Expression as SourceExpression, ExpressionKind as Ast};
|
|
use crate::types::{Field, Type, output_type};
|
|
|
|
use super::expressions::ungroup;
|
|
use super::{Checked, body::Body};
|
|
|
|
impl Body<'_, '_> {
|
|
pub(super) fn contextual(
|
|
&mut self,
|
|
operation: &ContextualExpression,
|
|
span: Span,
|
|
) -> Checked<Expression> {
|
|
match operation {
|
|
ContextualExpression::Channel { element, capacity } => {
|
|
let element = self.context.resolve_type(element)?;
|
|
super::symbols::validate_type_constraints(&element, self.context.types, span)?;
|
|
if !element.transferable(self.context.types) {
|
|
return Err(Diagnostic::error(
|
|
"C005",
|
|
"a Channel item type must be transferable between threads",
|
|
span,
|
|
));
|
|
}
|
|
let capacity = self.capacity(
|
|
capacity,
|
|
dever_runtime::channel::MAX_CHANNEL_CAPACITY,
|
|
"channel capacity",
|
|
)?;
|
|
Ok(Expression {
|
|
kind: ExpressionKind::Channel {
|
|
element: element.clone(),
|
|
capacity: Box::new(capacity),
|
|
},
|
|
ty: Type::Channel(Box::new(element)),
|
|
span,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(super) fn named_concurrency(
|
|
&mut self,
|
|
name: &str,
|
|
arguments: &[SourceExpression],
|
|
span: Span,
|
|
) -> Option<Checked<Expression>> {
|
|
let result = match name {
|
|
"run" => self.run(arguments, span),
|
|
"wait" => self.wait(arguments, span),
|
|
"stop" => self.stop(arguments, span),
|
|
"group" => self.group(arguments, span),
|
|
"parallel" => self.parallel(arguments, span),
|
|
"blocking" => self.blocking(arguments, span),
|
|
"send" => self.send(arguments, span),
|
|
"receive" => self.receive(arguments, span),
|
|
"close" => self.close(arguments, span),
|
|
"timeout" => self.timeout(arguments, span),
|
|
"race" => self.race(arguments, span),
|
|
"stream" => self.stream(arguments, span),
|
|
_ => return None,
|
|
};
|
|
Some(result)
|
|
}
|
|
|
|
fn timeout(&mut self, arguments: &[SourceExpression], span: Span) -> Checked<Expression> {
|
|
exact_arity("timeout", arguments, 3, span)?;
|
|
let (task, Type::Task(outputs)) = self.affine_operand(&arguments[0], true)? else {
|
|
return Err(Diagnostic::error("C005", "timeout requires a Task", span));
|
|
};
|
|
let duration = self.expression(&arguments[1], Some(&Type::Int))?;
|
|
let handler = self.handler_reference(
|
|
&arguments[2],
|
|
&crate::types::HandlerSignature {
|
|
parameters: vec![],
|
|
bounds: vec![],
|
|
outputs: outputs.clone(),
|
|
},
|
|
)?;
|
|
Ok(Expression {
|
|
kind: ExpressionKind::Intrinsic {
|
|
operation: crate::intrinsic::Intrinsic::TaskTimeout,
|
|
handler: Some(handler),
|
|
arguments: vec![task, duration],
|
|
},
|
|
ty: output_type(&outputs),
|
|
span,
|
|
})
|
|
}
|
|
|
|
fn race(&mut self, sources: &[SourceExpression], span: Span) -> Checked<Expression> {
|
|
if sources.len() < 2 {
|
|
return Err(Diagnostic::error(
|
|
"C005",
|
|
"race requires at least two Tasks",
|
|
span,
|
|
));
|
|
}
|
|
let mut arguments = Vec::new();
|
|
let mut signature = None;
|
|
for source in sources {
|
|
let (task, Type::Task(outputs)) = self.affine_operand(source, true)? else {
|
|
return Err(Diagnostic::error(
|
|
"C005",
|
|
"race requires Tasks",
|
|
source.span,
|
|
));
|
|
};
|
|
if signature
|
|
.as_ref()
|
|
.is_some_and(|expected| expected != &outputs)
|
|
{
|
|
return Err(Diagnostic::error(
|
|
"C005",
|
|
"race Tasks must have identical output signatures",
|
|
source.span,
|
|
));
|
|
}
|
|
signature = Some(outputs);
|
|
arguments.push(task);
|
|
}
|
|
Ok(Expression {
|
|
kind: ExpressionKind::Intrinsic {
|
|
operation: crate::intrinsic::Intrinsic::TaskRace,
|
|
handler: None,
|
|
arguments,
|
|
},
|
|
ty: output_type(&signature.expect("at least two tasks")),
|
|
span,
|
|
})
|
|
}
|
|
|
|
fn stream(&mut self, sources: &[SourceExpression], span: Span) -> Checked<Expression> {
|
|
exact_arity("stream", sources, 1, span)?;
|
|
let value = self.expression(&sources[0], None)?;
|
|
let element = match &value.ty {
|
|
Type::Channel(element) | Type::List(element) => element.as_ref().clone(),
|
|
Type::Bytes => Type::Int,
|
|
_ => {
|
|
return Err(Diagnostic::error(
|
|
"C005",
|
|
"stream requires Channel, List or Bytes",
|
|
span,
|
|
));
|
|
}
|
|
};
|
|
if !element.transferable(self.context.types) {
|
|
return Err(Diagnostic::error(
|
|
"C005",
|
|
"async stream elements must be transferable",
|
|
span,
|
|
));
|
|
}
|
|
Ok(Expression {
|
|
kind: ExpressionKind::Intrinsic {
|
|
operation: crate::intrinsic::Intrinsic::StreamOf,
|
|
handler: None,
|
|
arguments: vec![value],
|
|
},
|
|
ty: Type::AsyncStream(Box::new(element)),
|
|
span,
|
|
})
|
|
}
|
|
|
|
fn wait(&mut self, arguments: &[SourceExpression], span: Span) -> Checked<Expression> {
|
|
exact_arity("wait", arguments, 1, span)?;
|
|
let (task, ty) = self.affine_operand(&arguments[0], true)?;
|
|
match ty {
|
|
Type::Task(outputs) => Ok(Expression {
|
|
kind: ExpressionKind::AwaitTask(Box::new(task)),
|
|
ty: output_type(&outputs),
|
|
span,
|
|
}),
|
|
Type::Group => Ok(Expression {
|
|
kind: ExpressionKind::AwaitGroup(Box::new(task)),
|
|
ty: Type::Unit,
|
|
span,
|
|
}),
|
|
_ => unreachable!("affine operands are Task or Group"),
|
|
}
|
|
}
|
|
|
|
fn run(&mut self, arguments: &[SourceExpression], span: Span) -> Checked<Expression> {
|
|
match arguments {
|
|
[call] => {
|
|
let call = self.checked_static_call(call)?;
|
|
self.require_transferable_call(&call.arguments, &call.outputs, span)?;
|
|
let outputs = call.outputs.clone();
|
|
Ok(Expression {
|
|
kind: ExpressionKind::RunCall {
|
|
group: None,
|
|
target: call.target,
|
|
arguments: call.arguments,
|
|
},
|
|
ty: Type::Task(outputs),
|
|
span,
|
|
})
|
|
}
|
|
[group, call] => {
|
|
let (group, ty) = self.affine_operand(group, false)?;
|
|
if ty != Type::Group {
|
|
return Err(Diagnostic::error(
|
|
"C005",
|
|
"the first argument to run(group, call) must be a Group local",
|
|
arguments[0].span,
|
|
));
|
|
}
|
|
let call = self.checked_static_call(call)?;
|
|
if !call.outputs.is_empty() {
|
|
return Err(Diagnostic::error(
|
|
"C005",
|
|
"a Group accepts only zero-output calls",
|
|
arguments[1].span,
|
|
));
|
|
}
|
|
self.require_transferable_call(&call.arguments, &call.outputs, span)?;
|
|
Ok(Expression {
|
|
kind: ExpressionKind::RunCall {
|
|
group: Some(Box::new(group)),
|
|
target: call.target,
|
|
arguments: call.arguments,
|
|
},
|
|
ty: Type::Unit,
|
|
span,
|
|
})
|
|
}
|
|
_ => Err(Diagnostic::error(
|
|
"C005",
|
|
"'run' expects a call, optionally preceded by a Group",
|
|
span,
|
|
)),
|
|
}
|
|
}
|
|
|
|
fn stop(&mut self, arguments: &[SourceExpression], span: Span) -> Checked<Expression> {
|
|
exact_arity("stop", arguments, 1, span)?;
|
|
let (value, ty) = self.affine_operand(&arguments[0], true)?;
|
|
let kind = match ty {
|
|
Type::Task(outputs) if outputs.is_empty() => ExpressionKind::StopTask(Box::new(value)),
|
|
Type::Task(_) => {
|
|
return Err(Diagnostic::error(
|
|
"C005",
|
|
"stop accepts only a zero-output Task; wait for result-bearing tasks",
|
|
arguments[0].span,
|
|
));
|
|
}
|
|
Type::Group => ExpressionKind::StopGroup(Box::new(value)),
|
|
_ => unreachable!("affine operands are Task or Group"),
|
|
};
|
|
Ok(Expression {
|
|
kind,
|
|
ty: Type::Unit,
|
|
span,
|
|
})
|
|
}
|
|
|
|
fn group(&mut self, arguments: &[SourceExpression], span: Span) -> Checked<Expression> {
|
|
exact_arity("group", arguments, 1, span)?;
|
|
let limit = self.capacity(
|
|
&arguments[0],
|
|
dever_runtime::task::MAX_GROUP_TASKS,
|
|
"group limit",
|
|
)?;
|
|
Ok(Expression {
|
|
kind: ExpressionKind::Group(Box::new(limit)),
|
|
ty: Type::Group,
|
|
span,
|
|
})
|
|
}
|
|
|
|
fn parallel(&mut self, arguments: &[SourceExpression], span: Span) -> Checked<Expression> {
|
|
exact_arity("parallel", arguments, 1, span)?;
|
|
let call = self.checked_static_call(&arguments[0])?;
|
|
if !call.pure {
|
|
return Err(Diagnostic::error(
|
|
"C005",
|
|
"parallel requires a pure function call",
|
|
arguments[0].span,
|
|
));
|
|
}
|
|
self.require_transferable_call(&call.arguments, &call.outputs, span)?;
|
|
Ok(Expression {
|
|
kind: ExpressionKind::ParallelCall {
|
|
target: call.target,
|
|
arguments: call.arguments,
|
|
},
|
|
ty: output_type(&call.outputs),
|
|
span,
|
|
})
|
|
}
|
|
|
|
fn blocking(&mut self, arguments: &[SourceExpression], span: Span) -> Checked<Expression> {
|
|
exact_arity("blocking", arguments, 1, span)?;
|
|
let call = self.checked_static_call(&arguments[0])?;
|
|
self.require_transferable_call(&call.arguments, &call.outputs, span)?;
|
|
Ok(Expression {
|
|
kind: ExpressionKind::BlockingCall {
|
|
target: call.target,
|
|
arguments: call.arguments,
|
|
},
|
|
ty: output_type(&call.outputs),
|
|
span,
|
|
})
|
|
}
|
|
|
|
fn send(&mut self, arguments: &[SourceExpression], span: Span) -> Checked<Expression> {
|
|
exact_arity("send", arguments, 2, span)?;
|
|
let channel = self.expression(&arguments[0], None)?;
|
|
let Type::Channel(element) = &channel.ty else {
|
|
return Err(Diagnostic::error(
|
|
"C005",
|
|
"send requires a Channel as its first argument",
|
|
arguments[0].span,
|
|
));
|
|
};
|
|
let value = self.expression(&arguments[1], Some(element))?;
|
|
Ok(Expression {
|
|
kind: ExpressionKind::ChannelSend {
|
|
channel: Box::new(channel),
|
|
value: Box::new(value),
|
|
},
|
|
ty: Type::Unit,
|
|
span,
|
|
})
|
|
}
|
|
|
|
fn receive(&mut self, arguments: &[SourceExpression], span: Span) -> Checked<Expression> {
|
|
exact_arity("receive", arguments, 1, span)?;
|
|
let channel = self.expression(&arguments[0], None)?;
|
|
let Type::Channel(element) = &channel.ty else {
|
|
return Err(Diagnostic::error(
|
|
"C005",
|
|
"receive requires a Channel value",
|
|
arguments[0].span,
|
|
));
|
|
};
|
|
let ty = element.as_ref().clone().nullable();
|
|
Ok(Expression {
|
|
kind: ExpressionKind::ChannelReceive(Box::new(channel)),
|
|
ty,
|
|
span,
|
|
})
|
|
}
|
|
|
|
fn close(&mut self, arguments: &[SourceExpression], span: Span) -> Checked<Expression> {
|
|
exact_arity("close", arguments, 1, span)?;
|
|
let value = self.expression(&arguments[0], None)?;
|
|
let kind = match &value.ty {
|
|
Type::Channel(_) => ExpressionKind::ChannelClose(Box::new(value)),
|
|
Type::Stream(_) | Type::AsyncStream(_) | Type::RowStream(_) => {
|
|
ExpressionKind::Collection {
|
|
operation: CollectionOp::Close,
|
|
handler: None,
|
|
sequence: Some(match value.ty {
|
|
Type::AsyncStream(_) => SequenceKind::AsyncStream,
|
|
Type::RowStream(_) => SequenceKind::RowStream,
|
|
_ => SequenceKind::Stream,
|
|
}),
|
|
arguments: vec![value],
|
|
}
|
|
}
|
|
_ => {
|
|
return Err(Diagnostic::error(
|
|
"C005",
|
|
"close requires a Stream, AsyncStream or Channel value",
|
|
arguments[0].span,
|
|
));
|
|
}
|
|
};
|
|
Ok(Expression {
|
|
kind,
|
|
ty: Type::Unit,
|
|
span,
|
|
})
|
|
}
|
|
|
|
fn affine_operand(
|
|
&mut self,
|
|
source: &SourceExpression,
|
|
consume: bool,
|
|
) -> Checked<(Expression, Type)> {
|
|
let Ast::Name(path) = &ungroup(source).kind else {
|
|
return Err(Diagnostic::error(
|
|
"C005",
|
|
"Task and Group operations require a direct local name",
|
|
source.span,
|
|
));
|
|
};
|
|
if path.len() != 1 {
|
|
return Err(Diagnostic::error(
|
|
"C005",
|
|
"Task and Group cannot be stored or projected",
|
|
source.span,
|
|
));
|
|
}
|
|
let (slot, ty) = self.affine_local(&path[0].text, source.span, consume)?;
|
|
Ok((
|
|
Expression {
|
|
kind: ExpressionKind::Local(slot),
|
|
ty: ty.clone(),
|
|
span: source.span,
|
|
},
|
|
ty,
|
|
))
|
|
}
|
|
|
|
fn capacity(
|
|
&mut self,
|
|
source: &SourceExpression,
|
|
maximum: usize,
|
|
label: &str,
|
|
) -> Checked<Expression> {
|
|
let value = self.expression(source, Some(&Type::Int))?;
|
|
if let ExpressionKind::Constant(crate::hir::Constant::Int(constant)) = &value.kind
|
|
&& (*constant <= 0
|
|
|| usize::try_from(*constant).map_or(true, |constant| constant > maximum))
|
|
{
|
|
return Err(Diagnostic::error(
|
|
"C005",
|
|
format!("{label} must be between 1 and {maximum}"),
|
|
source.span,
|
|
));
|
|
}
|
|
Ok(value)
|
|
}
|
|
|
|
fn require_transferable_call(
|
|
&self,
|
|
arguments: &[CallArgument],
|
|
outputs: &[Field],
|
|
span: Span,
|
|
) -> Checked<()> {
|
|
let arguments_transfer = arguments.iter().all(|argument| match argument {
|
|
CallArgument::Value(value) => value.ty.transferable(self.context.types),
|
|
CallArgument::Handler(_) => true,
|
|
});
|
|
let outputs_transfer = outputs
|
|
.iter()
|
|
.all(|field| field.ty.transferable(self.context.types));
|
|
if arguments_transfer && outputs_transfer {
|
|
Ok(())
|
|
} else {
|
|
Err(Diagnostic::error(
|
|
"C005",
|
|
"task inputs and outputs must be transferable between threads",
|
|
span,
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn exact_arity(
|
|
name: &str,
|
|
arguments: &[SourceExpression],
|
|
expected: usize,
|
|
span: Span,
|
|
) -> Checked<()> {
|
|
if arguments.len() == expected {
|
|
Ok(())
|
|
} else {
|
|
Err(Diagnostic::error(
|
|
"C005",
|
|
format!("'{name}' expects {expected} argument(s)"),
|
|
span,
|
|
))
|
|
}
|
|
}
|