use crate::ast::node::Node;
use crate::ast::{Ast, LookupError};
use crate::compilation_state::CompilationState;
use crate::diagnostics::{Diagnostic, Diagnostics, Lint};
use crate::grammar::*;
use crate::utils::ptr_util::WeakPtr;
use std::collections::VecDeque;
macro_rules! patch_link {
($self:ident, $tag:expr) => {
if let Some(patch) = $self.link_patches.pop_front().unwrap() {
$tag.link = TypeRefDefinition::Patched(patch);
}
};
}
macro_rules! patch_element {
($element_ptr:expr, $patcher:expr) => {{
let element_ref = $element_ptr.borrow_mut();
$patcher.apply_patches(&element_ref.parser_scoped_identifier(), &mut element_ref.comment);
}};
}
pub unsafe fn patch_ast(compilation_state: &mut CompilationState) {
let mut patcher = CommentLinkPatcher {
link_patches: VecDeque::new(),
diagnostics: &mut compilation_state.diagnostics,
};
for node in compilation_state.ast.as_slice() {
match node {
Node::Struct(ptr) => patcher.compute_patches_for(ptr.borrow(), &compilation_state.ast),
Node::Class(ptr) => patcher.compute_patches_for(ptr.borrow(), &compilation_state.ast),
Node::Exception(ptr) => patcher.compute_patches_for(ptr.borrow(), &compilation_state.ast),
Node::Field(ptr) => patcher.compute_patches_for(ptr.borrow(), &compilation_state.ast),
Node::Interface(ptr) => patcher.compute_patches_for(ptr.borrow(), &compilation_state.ast),
Node::Operation(ptr) => patcher.compute_patches_for(ptr.borrow(), &compilation_state.ast),
Node::Enum(ptr) => patcher.compute_patches_for(ptr.borrow(), &compilation_state.ast),
Node::Enumerator(ptr) => patcher.compute_patches_for(ptr.borrow(), &compilation_state.ast),
Node::CustomType(ptr) => patcher.compute_patches_for(ptr.borrow(), &compilation_state.ast),
Node::TypeAlias(ptr) => patcher.compute_patches_for(ptr.borrow(), &compilation_state.ast),
_ => {} }
}
for node in compilation_state.ast.as_mut_slice() {
match node {
Node::Struct(ptr) => patch_element!(ptr, patcher),
Node::Class(ptr) => patch_element!(ptr, patcher),
Node::Exception(ptr) => patch_element!(ptr, patcher),
Node::Field(ptr) => patch_element!(ptr, patcher),
Node::Interface(ptr) => patch_element!(ptr, patcher),
Node::Operation(ptr) => patch_element!(ptr, patcher),
Node::Enum(ptr) => patch_element!(ptr, patcher),
Node::Enumerator(ptr) => patch_element!(ptr, patcher),
Node::CustomType(ptr) => patch_element!(ptr, patcher),
Node::TypeAlias(ptr) => patch_element!(ptr, patcher),
_ => {} }
}
debug_assert!(patcher.link_patches.is_empty());
}
struct CommentLinkPatcher<'a> {
link_patches: VecDeque<Option<WeakPtr<dyn Entity>>>,
diagnostics: &'a mut Diagnostics,
}
impl CommentLinkPatcher<'_> {
fn compute_patches_for(&mut self, commentable: &impl Commentable, ast: &Ast) {
if let Some(comment) = commentable.comment() {
if let Some(overview) = &comment.overview {
self.resolve_links_in(overview, commentable, ast);
}
for param_tag in &comment.params {
self.resolve_links_in(¶m_tag.message, commentable, ast);
}
for returns_tag in &comment.returns {
self.resolve_links_in(&returns_tag.message, commentable, ast);
}
for throws_tag in &comment.throws {
self.resolve_link(&throws_tag.thrown_type, commentable, ast);
self.resolve_links_in(&throws_tag.message, commentable, ast);
}
for see_tag in &comment.see {
self.resolve_link(&see_tag.link, commentable, ast);
}
}
}
fn resolve_links_in(&mut self, message: &Message, commentable: &impl Commentable, ast: &Ast) {
for component in &message.value {
if let MessageComponent::Link(link_tag) = component {
self.resolve_link(&link_tag.link, commentable, ast);
}
}
}
fn resolve_link<T>(&mut self, link: &TypeRefDefinition<T>, commentable: &impl Commentable, ast: &Ast)
where
T: Element + ?Sized,
{
let TypeRefDefinition::Unpatched(identifier) = link else {
panic!("encountered comment link that was already patched");
};
let result = ast
.find_node_with_scope(&identifier.value, &commentable.parser_scoped_identifier())
.and_then(<WeakPtr<dyn Entity>>::try_from);
self.link_patches.push_back(match result {
Ok(ptr) => Some(ptr),
Err(error) => {
let message = match error {
LookupError::DoesNotExist { identifier } => {
format!("no element named '{identifier}' exists in scope")
}
LookupError::TypeMismatch { actual, .. } => {
let type_string = match actual.as_str() {
"primitive" => "primitive types",
"module" => "modules",
_ => unreachable!(),
};
type_string.to_owned() + " cannot be linked to"
}
};
Diagnostic::new(Lint::BrokenDocLink { message })
.set_span(identifier.span())
.set_scope(commentable.parser_scoped_identifier())
.push_into(self.diagnostics);
None
}
});
}
fn apply_patches(&mut self, scope: &str, comment: &mut Option<DocComment>) {
if let Some(comment) = comment {
if let Some(overview) = &mut comment.overview {
self.patch_links_in(overview);
}
for param_tag in &mut comment.params {
self.patch_links_in(&mut param_tag.message);
}
for returns_tag in &mut comment.returns {
self.patch_links_in(&mut returns_tag.message);
}
for throws_tag in &mut comment.throws {
self.patch_thrown_type(scope, throws_tag);
self.patch_links_in(&mut throws_tag.message);
}
for see_tag in &mut comment.see {
patch_link!(self, see_tag);
}
}
}
fn patch_links_in(&mut self, message: &mut Message) {
for component in &mut message.value {
if let MessageComponent::Link(link_tag) = component {
patch_link!(self, link_tag);
}
}
}
fn patch_thrown_type(&mut self, scope: &str, tag: &mut ThrowsTag) {
if let Some(patch) = self.link_patches.pop_front().unwrap() {
match patch.downcast::<Exception>() {
Ok(converted_patch) => {
tag.thrown_type = TypeRefDefinition::Patched(converted_patch);
}
Err(original_patch) => {
let entity = original_patch.borrow();
let kind = entity.kind();
let note = format!(
"'{identifier}' is {a} {kind}",
identifier = entity.identifier(),
a = crate::utils::string_util::indefinite_article(kind),
);
Diagnostic::new(Lint::IncorrectDocComment {
message: format!(
"comment has a 'throws' tag for '{}', but it is not a throwable type",
entity.identifier(),
),
})
.add_note(
format!(
"{} '{}' was defined here: ",
entity.kind().to_owned(),
entity.identifier()
),
Some(entity.span()),
)
.add_note("operations can only throw exceptions", None)
.add_note(note, Some(entity.span()))
.set_span(tag.span())
.set_scope(scope)
.push_into(self.diagnostics);
}
}
}
}
}