use brink_syntax_native::ast::{self, AstNode as _};
use crate::hir::FileId;
use crate::hir::doc_block::DocPolicy;
use crate::provenance::NodeClass;
use crate::{Block, Diagnostic, DiagnosticCode, DocBlock, Knot, Name, Param, Stitch};
use super::doc_comment::lower_doc_comment;
use super::element::Elements;
use super::provenance::native_provenance;
fn report_header_tags(file_id: FileId, node: &ast::FlowDecl, diags: &mut Vec<Diagnostic>) {
for tag in node.tags() {
diags.push(diag(
file_id,
tag.syntax().text_range(),
DiagnosticCode::E129,
));
}
}
fn container_doc(
file_id: FileId,
outer: Option<ast::DocComment>,
body: Option<&ast::Body>,
policy: DocPolicy,
diags: &mut Vec<Diagnostic>,
) -> Option<DocBlock> {
if let Some(doc) = lower_doc_comment(file_id, outer, policy, diags) {
return Some(doc);
}
let inner = match body {
Some(ast::Body::Prose(b)) => b.doc(),
Some(ast::Body::Code(_)) | None => None,
};
lower_doc_comment(file_id, inner, policy, diags)
}
fn lower_body(
file_id: FileId,
body: &ast::Body,
elements: &mut Elements,
diags: &mut Vec<Diagnostic>,
) -> Block {
match body {
ast::Body::Prose(b) => super::body::lower_block(file_id, b, elements, diags),
ast::Body::Code(sb) => super::body::lower_stmt_block_as_body(file_id, sb, elements, diags),
}
}
fn diag(file: FileId, range: rowan::TextRange, code: DiagnosticCode) -> Diagnostic {
Diagnostic {
file,
range,
message: code.title().to_string(),
code,
}
}
pub(super) fn name_from(tok: Option<brink_syntax_native::SyntaxToken>) -> Option<Name> {
tok.map(|t| Name {
text: t.text().to_string(),
range: t.text_range(),
})
}
pub(super) fn lower_params(param_list: Option<ast::ParamList>) -> Vec<Param> {
param_list
.into_iter()
.flat_map(|pl| pl.params().collect::<Vec<_>>())
.filter_map(|p| {
name_from(p.name_token()).map(|name| Param {
name,
is_ref: p.is_ref(),
is_divert: false,
annotation: p
.type_annotation()
.as_ref()
.and_then(super::types::lower_type_annotation),
})
})
.collect()
}
pub(super) fn lower_top_level_container(
file_id: FileId,
node: &super::FlowOrFn,
elements: &mut Elements,
diags: &mut Vec<Diagnostic>,
) -> Option<Knot> {
let syntax = node.syntax();
let range = syntax.text_range();
let Some(name) = name_from(node.name_token()) else {
diags.push(diag(file_id, range, DiagnosticCode::E001));
return None;
};
let params = lower_params(node.param_list());
if let super::FlowOrFn::Flow(flow) = node {
report_header_tags(file_id, flow, diags);
}
let return_type = node
.return_type()
.as_ref()
.and_then(super::types::lower_type_annotation);
let mut stitches = Vec::new();
if let Some(ast::Body::Prose(body)) = node.body() {
for child in body.items() {
match child.kind() {
brink_syntax_native::SyntaxKind::FLOW_DECL => {
if let Some(nested) = ast::FlowDecl::cast(child.clone())
&& let Some(stitch) =
lower_stitch(file_id, &nested, &name.text, elements, diags)
{
stitches.push(stitch);
}
}
brink_syntax_native::SyntaxKind::FN_DECL => {
diags.push(diag(file_id, child.text_range(), DiagnosticCode::E129));
}
_ => {}
}
}
}
let doc = container_doc(
file_id,
node.doc(),
node.body().as_ref(),
DocPolicy::CALLABLE,
diags,
);
let mut body_block = node
.body()
.map_or_else(Block::default, |b| lower_body(file_id, &b, elements, diags));
super::body::fixup_return_kind(node.is_function(), &mut body_block);
if !node.is_function() && return_type.is_none() {
super::body::apply_implicit_done(&mut body_block);
}
let element_annotation = super::annotation::element_annotation(file_id, syntax, ¶ms, diags);
let convention_annotation = super::annotation::convention_annotation(
file_id,
syntax,
¶ms,
return_type.as_ref(),
diags,
);
let style_annotation = super::annotation::style_annotation(
file_id,
syntax,
element_annotation.as_ref(),
convention_annotation.as_ref(),
diags,
);
Some(Knot {
ptr: native_provenance(file_id, NodeClass::Knot, syntax),
name,
is_function: node.is_function(),
params,
body: body_block,
stitches,
is_local: false,
effects_assertion: super::annotation::effects_assertion(file_id, syntax, diags),
element_annotation,
convention_annotation,
style_annotation,
return_type,
doc,
visibility: node.is_pub().then_some(crate::VisibilityMark::Public),
was: None,
})
}
fn lower_stitch(
file_id: FileId,
node: &ast::FlowDecl,
enclosing_knot_name: &str,
elements: &mut Elements,
diags: &mut Vec<Diagnostic>,
) -> Option<Stitch> {
let syntax = node.syntax();
let range = syntax.text_range();
let Some(name) = name_from(node.name_token()) else {
diags.push(diag(file_id, range, DiagnosticCode::E002));
return None;
};
let params = lower_params(node.param_list());
report_header_tags(file_id, node, diags);
let return_type = node
.return_type()
.as_ref()
.and_then(super::types::lower_type_annotation);
if let Some(ast::Body::Prose(body)) = node.body() {
for child in body.items() {
match child.kind() {
brink_syntax_native::SyntaxKind::FLOW_DECL => {
diags.push(diag(file_id, child.text_range(), DiagnosticCode::E130));
}
brink_syntax_native::SyntaxKind::FN_DECL => {
diags.push(diag(file_id, child.text_range(), DiagnosticCode::E129));
}
_ => {}
}
}
}
let _ = enclosing_knot_name; let doc = container_doc(
file_id,
node.doc(),
node.body().as_ref(),
DocPolicy::CALLABLE,
diags,
);
let mut body_block = node
.body()
.map_or_else(Block::default, |b| lower_body(file_id, &b, elements, diags));
super::body::fixup_return_kind(false, &mut body_block);
if return_type.is_none() {
super::body::apply_implicit_done(&mut body_block);
}
let element_annotation = super::annotation::element_annotation(file_id, syntax, ¶ms, diags);
let convention_annotation = super::annotation::convention_annotation(
file_id,
syntax,
¶ms,
return_type.as_ref(),
diags,
);
let style_annotation = super::annotation::style_annotation(
file_id,
syntax,
element_annotation.as_ref(),
convention_annotation.as_ref(),
diags,
);
Some(Stitch {
ptr: native_provenance(file_id, NodeClass::Stitch, syntax),
name,
params,
body: body_block,
is_local: false,
effects_assertion: super::annotation::effects_assertion(file_id, syntax, diags),
element_annotation,
convention_annotation,
style_annotation,
return_type,
doc,
visibility: node.is_pub().then_some(crate::VisibilityMark::Public),
was: None,
})
}