mod hash;
#[cfg(test)]
mod tests;
use std::cmp::Ordering;
use std::hash::Hash;
use std::sync::Arc;
use indexmap::IndexMap;
use petgraph::prelude::DiGraphMap;
use sha2::Digest;
use sha2::Sha256;
use url::Url;
use wdl_ast::TreeNode;
use wdl_ast::v1::Ast;
use wdl_ast::v1::DocumentItem;
use wdl_ast::v1::EnumDefinition;
use wdl_ast::v1::ImportStatement;
use wdl_ast::v1::StructDefinition;
use wdl_grammar::Diagnostic;
use wdl_grammar::Span;
use wdl_grammar::SyntaxKind;
use crate::AppliedEdit;
use crate::Diagnostics;
use crate::Exceptable;
use crate::document::Enum;
use crate::document::ImportedEnum;
use crate::document::ImportedStruct;
use crate::document::ImportedTask;
use crate::document::ImportedWorkflow;
use crate::document::Input;
use crate::document::Namespace;
use crate::document::Output;
use crate::document::Struct;
use crate::document::Task;
use crate::document::Workflow;
use crate::document::cache::hash::HashableCallable;
use crate::document::cache::hash::HashableItem;
use crate::types::Type;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum ItemKind {
Struct,
Enum,
Task,
Workflow,
Import,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub(crate) struct MergingImport {
pub(in crate::document) imported_tasks: IndexMap<String, ImportedTask>,
pub(in crate::document) imported_workflows: IndexMap<String, ImportedWorkflow>,
pub(in crate::document) imported_structs: IndexMap<String, ImportedStruct>,
pub(in crate::document) imported_enums: IndexMap<String, ImportedEnum>,
}
impl MergingImport {
pub(crate) fn items(&self) -> impl Iterator<Item = ImportedItem<'_>> {
self.imported_tasks
.values()
.map(ImportedItem::Task)
.chain(self.imported_workflows.values().map(ImportedItem::Workflow))
.chain(self.imported_structs.values().map(ImportedItem::Struct))
.chain(self.imported_enums.values().map(ImportedItem::Enum))
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum Import {
Namespace(Namespace),
Merging(MergingImport),
}
impl Import {
pub(crate) fn merging(&self) -> Option<&MergingImport> {
match self {
Import::Merging(i) => Some(i),
_ => None,
}
}
pub(crate) fn namespace(&self) -> Option<&Namespace> {
match self {
Import::Namespace(n) => Some(n),
_ => None,
}
}
fn namespace_mut(&mut self) -> Option<&mut Namespace> {
match self {
Import::Namespace(n) => Some(n),
_ => None,
}
}
fn structs(&self) -> impl Iterator<Item = &ImportedStruct> {
match self {
Import::Namespace(n) => n.imported_structs.values(),
Import::Merging(m) => m.imported_structs.values(),
}
}
pub(in crate::document) fn add_struct(&mut self, s: ImportedStruct) {
let _ = match self {
Import::Namespace(n) => n.imported_structs.insert(s.local_name.clone(), s),
Import::Merging(m) => m.imported_structs.insert(s.local_name.clone(), s),
};
}
fn enums(&self) -> impl Iterator<Item = &ImportedEnum> {
match self {
Import::Namespace(n) => n.imported_enums.values(),
Import::Merging(m) => m.imported_enums.values(),
}
}
pub(in crate::document) fn add_enum(&mut self, e: ImportedEnum) {
let _ = match self {
Import::Namespace(n) => n.imported_enums.insert(e.local_name.clone(), e),
Import::Merging(m) => m.imported_enums.insert(e.local_name.clone(), e),
};
}
}
pub(crate) enum ImportedItem<'a> {
Struct(&'a ImportedStruct),
Enum(&'a ImportedEnum),
Task(&'a ImportedTask),
Workflow(&'a ImportedWorkflow),
}
impl<'a> ImportedItem<'a> {
fn aliased_name(&self) -> &'a str {
match self {
ImportedItem::Struct(s) => &s.local_name,
ImportedItem::Enum(e) => &e.local_name,
ImportedItem::Task(t) => &t.local_name,
ImportedItem::Workflow(w) => &w.local_name,
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum MaybeImported<Local, Imported> {
Local(Local),
Imported(Imported),
}
impl<L, I> MaybeImported<L, I> {
pub fn is_imported(&self) -> bool {
matches!(self, Self::Imported(_))
}
pub fn expect_imported(self) -> I {
match self {
Self::Imported(i) => i,
Self::Local(_) => panic!("expected an imported item"),
}
}
pub fn expect_local(self) -> L {
match self {
Self::Local(l) => l,
Self::Imported(_) => panic!("expected a locally defined item"),
}
}
}
pub(in crate::document) type Item<'a> = MaybeImported<CachedItemRef<'a>, ImportedItem<'a>>;
pub type WorkflowRef<'a> = MaybeImported<&'a Workflow, &'a ImportedWorkflow>;
impl<'a> WorkflowRef<'a> {
pub fn name(&self) -> &'a str {
match self {
WorkflowRef::Local(w) => w.name(),
WorkflowRef::Imported(i) => &i.local_name,
}
}
pub fn name_span(&self) -> Span {
match self {
WorkflowRef::Local(w) => w.name_span(),
WorkflowRef::Imported(i) => i.span,
}
}
pub fn inputs(&self) -> Arc<IndexMap<String, Input>> {
match self {
WorkflowRef::Local(w) => Arc::clone(&w.inputs),
WorkflowRef::Imported(i) => Arc::clone(&i.inputs),
}
}
pub fn outputs(&self) -> Arc<IndexMap<String, Output>> {
match self {
WorkflowRef::Local(w) => Arc::clone(&w.outputs),
WorkflowRef::Imported(i) => Arc::clone(&i.outputs),
}
}
pub fn source(&self) -> Option<Arc<Url>> {
match self {
WorkflowRef::Local(_) => None,
WorkflowRef::Imported(i) => Some(i.source()),
}
}
}
pub type TaskRef<'a> = MaybeImported<&'a Task, &'a ImportedTask>;
impl<'a> TaskRef<'a> {
pub fn name(&self) -> &'a str {
match self {
TaskRef::Local(t) => t.name(),
TaskRef::Imported(i) => &i.local_name,
}
}
pub fn name_span(&self) -> Span {
match self {
TaskRef::Local(t) => t.name_span(),
TaskRef::Imported(i) => i.span,
}
}
pub fn inputs(&self) -> Arc<IndexMap<String, Input>> {
match self {
TaskRef::Local(t) => Arc::clone(&t.inputs),
TaskRef::Imported(i) => Arc::clone(&i.inputs),
}
}
pub fn outputs(&self) -> Arc<IndexMap<String, Output>> {
match self {
TaskRef::Local(t) => Arc::clone(&t.outputs),
TaskRef::Imported(i) => Arc::clone(&i.outputs),
}
}
pub fn source(&self) -> Option<Arc<Url>> {
match self {
TaskRef::Local(_) => None,
TaskRef::Imported(i) => Some(i.source()),
}
}
}
pub type StructRef<'a> = MaybeImported<&'a Struct, &'a ImportedStruct>;
impl<'a> StructRef<'a> {
pub fn name(&self) -> &'a str {
match self {
StructRef::Local(s) => s.name(),
StructRef::Imported(i) => &i.local_name,
}
}
pub fn name_span(&self) -> Span {
match self {
StructRef::Local(s) => s.name_span(),
StructRef::Imported(i) => i.span,
}
}
pub fn ty(&self) -> Option<&'a Type> {
match self {
StructRef::Local(s) => s.ty(),
StructRef::Imported(i) => i.ty(),
}
}
pub fn source(&self) -> Option<Arc<Url>> {
match self {
StructRef::Local(_) => None,
StructRef::Imported(i) => Some(i.source()),
}
}
pub fn definition(&self) -> StructDefinition {
match self {
StructRef::Local(s) => s.definition(),
StructRef::Imported(i) => i.definition(),
}
}
pub fn offset(&self) -> usize {
match self {
StructRef::Local(s) => s.offset(),
StructRef::Imported(i) => i.offset(),
}
}
pub fn node(&self) -> &'a rowan::GreenNode {
match self {
StructRef::Local(s) => s.node(),
StructRef::Imported(i) => i.node(),
}
}
}
pub type EnumRef<'a> = MaybeImported<&'a Enum, &'a ImportedEnum>;
impl<'a> EnumRef<'a> {
pub fn name(&self) -> &'a str {
match self {
EnumRef::Local(e) => e.name(),
EnumRef::Imported(i) => &i.local_name,
}
}
pub fn name_span(&self) -> Span {
match self {
EnumRef::Local(e) => e.name_span(),
EnumRef::Imported(i) => i.span,
}
}
pub fn ty(&self) -> Option<&'a Type> {
match self {
EnumRef::Local(e) => e.ty(),
EnumRef::Imported(i) => i.ty(),
}
}
pub fn source(&self) -> Option<Arc<Url>> {
match self {
EnumRef::Local(_) => None,
EnumRef::Imported(i) => Some(i.source()),
}
}
pub fn definition(&self) -> EnumDefinition {
match self {
EnumRef::Local(e) => e.definition(),
EnumRef::Imported(i) => i.definition(),
}
}
pub fn offset(&self) -> usize {
match self {
EnumRef::Local(e) => e.offset(),
EnumRef::Imported(i) => i.offset(),
}
}
pub fn node(&self) -> &'a rowan::GreenNode {
match self {
EnumRef::Local(e) => e.node(),
EnumRef::Imported(i) => i.node(),
}
}
}
impl<'a> Item<'a> {
fn name(&self) -> Option<&'a str> {
match self {
Item::Local(i) => i.name(),
Item::Imported(i) => Some(i.aliased_name()),
}
}
pub fn signature_hash(&self) -> Option<SignatureHash> {
match self {
Item::Local(i) => Some(i.signature_hash()),
Item::Imported(_) => None,
}
}
}
pub(in crate::document) type SignatureHash = [u8; 32];
pub(in crate::document) type BodyHash = [u8; 32];
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct WithBodyHash<T> {
pub body_hash: BodyHash,
pub item: T,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CachedItem<T> {
signature_hash: SignatureHash,
offset: usize,
item: T,
diagnostics: Diagnostics,
}
impl<T> CachedItem<T> {
pub(in crate::document) fn new(
signature_hash: SignatureHash,
offset: usize,
item: T,
diagnostics: Diagnostics,
) -> Self {
Self {
signature_hash,
offset,
item,
diagnostics,
}
}
pub fn item(&self) -> &T {
&self.item
}
pub fn item_mut(&mut self) -> &mut T {
&mut self.item
}
pub fn set_diagnostics(&mut self, diagnostics: Diagnostics) {
self.diagnostics = diagnostics;
self.shift_diagnostic_offsets();
}
pub(in crate::document) fn add_diagnostic(&mut self, mut diagnostic: Diagnostic) {
diagnostic.offset(-(self.offset as isize));
self.diagnostics.add(diagnostic);
}
pub(in crate::document) fn exceptable_add<N: TreeNode + Exceptable>(
&mut self,
mut diagnostic: Diagnostic,
element: &N,
exceptable_nodes: &Option<&'static [SyntaxKind]>,
) {
diagnostic.offset(-(self.offset as isize));
self.diagnostics
.exceptable_add(diagnostic, element, exceptable_nodes);
}
fn shift_diagnostic_offsets(&mut self) {
for diagnostic in &mut self.diagnostics.diagnostics {
diagnostic.offset(-(self.offset as isize))
}
}
}
impl CachedItem<Struct> {
fn target(&self) -> &Struct {
&self.item
}
}
impl CachedItem<Enum> {
fn target(&self) -> &Enum {
&self.item
}
}
impl<T> CachedItem<WithBodyHash<T>> {
fn target(&self) -> &T {
&self.item.item
}
}
#[derive(Debug)]
pub(in crate::document) enum CachedItemRefMut<'a> {
Struct(&'a mut CachedItem<Struct>),
Enum(&'a mut CachedItem<Enum>),
Task(&'a mut CachedItem<WithBodyHash<Task>>),
Workflow(&'a mut CachedItem<WithBodyHash<Workflow>>),
Import(&'a mut CachedItem<WithBodyHash<Import>>),
}
impl CachedItemRefMut<'_> {
fn offset(&self) -> usize {
match self {
Self::Struct(s) => s.offset,
Self::Enum(e) => e.offset,
Self::Task(t) => t.offset,
Self::Workflow(w) => w.offset,
Self::Import(i) => i.offset,
}
}
fn diagnostics_mut(&mut self) -> &mut Vec<Diagnostic> {
match self {
Self::Struct(s) => &mut s.diagnostics.diagnostics,
Self::Enum(e) => &mut e.diagnostics.diagnostics,
Self::Task(t) => &mut t.diagnostics.diagnostics,
Self::Workflow(w) => &mut w.diagnostics.diagnostics,
Self::Import(i) => &mut i.diagnostics.diagnostics,
}
}
fn shift_existing_diagnostics(&mut self, edits: &[AppliedEdit], new_item_offset: usize) {
fn shift_absolute_span(span: Span, edits: &[AppliedEdit]) -> Span {
let mut start = span.start();
let mut end = span.end();
for edit in edits {
let edit_start = edit.range.start;
let edit_end = edit.range.end;
let replacement_end = edit_start + edit.replacement_length;
let edit_diff = edit.replacement_length as isize - edit.range.len() as isize;
start = if start < edit_start {
start
} else if start <= edit_end {
replacement_end
} else {
start.saturating_add_signed(edit_diff)
};
end = if end < edit_start {
end
} else if end <= edit_end {
replacement_end
} else {
end.saturating_add_signed(edit_diff)
};
}
Span::new(start, end - start)
}
if edits.is_empty() {
return;
}
let original_item_offset = self.offset();
for diagnostic in self.diagnostics_mut() {
for label in diagnostic.labels_mut() {
let start_absolute = original_item_offset + label.span().start();
let end_absolute = original_item_offset + label.span().end();
let new_span = shift_absolute_span(
Span::new(start_absolute, end_absolute - start_absolute),
edits,
);
let new_relative_start = new_span.start().saturating_sub(new_item_offset);
label.set_span(Span::new(new_relative_start, new_span.len()));
}
}
match self {
Self::Struct(s) => {
let Struct {
name: _,
name_span,
offset: _,
node: _,
ty: _,
} = &mut s.item;
*name_span = shift_absolute_span(*name_span, edits)
}
Self::Enum(e) => {
let Enum {
name: _,
name_span,
offset: _,
node: _,
ty: _,
} = &mut e.item;
*name_span = shift_absolute_span(*name_span, edits)
}
Self::Task(t) => {
let Task {
name: _,
name_span,
span,
scopes,
inputs: _,
outputs,
} = &mut t.item.item;
*name_span = shift_absolute_span(*name_span, edits);
*span = shift_absolute_span(*span, edits);
for scope in scopes {
scope.span = shift_absolute_span(scope.span, edits);
for name in scope.names.values_mut() {
name.span = shift_absolute_span(name.span, edits);
}
}
for output in Arc::make_mut(outputs).values_mut() {
output.name_span = shift_absolute_span(output.name_span, edits);
}
}
Self::Workflow(wf) => {
let Workflow {
name: _,
name_span,
span,
scopes,
inputs: _,
outputs,
allows_nested_inputs: _,
calls: _,
} = &mut wf.item.item;
*name_span = shift_absolute_span(*name_span, edits);
*span = shift_absolute_span(*span, edits);
for scope in scopes {
scope.span = shift_absolute_span(scope.span, edits);
for name in scope.names.values_mut() {
name.span = shift_absolute_span(name.span, edits);
}
}
for output in Arc::make_mut(outputs).values_mut() {
output.name_span = shift_absolute_span(output.name_span, edits);
}
}
Self::Import(i) => match &mut i.item.item {
Import::Namespace(n) => {
let Namespace {
name: _,
span,
source: _,
document: _,
used: _,
imported_structs,
imported_enums,
} = n;
*span = shift_absolute_span(*span, edits);
for s in imported_structs.values_mut() {
s.span = shift_absolute_span(s.span, edits);
}
for e in imported_enums.values_mut() {
e.span = shift_absolute_span(e.span, edits);
}
}
Import::Merging(m) => {
let MergingImport {
imported_tasks,
imported_workflows,
imported_structs,
imported_enums,
} = m;
for t in imported_tasks.values_mut() {
t.span = shift_absolute_span(t.span, edits);
}
for w in imported_workflows.values_mut() {
w.span = shift_absolute_span(w.span, edits);
}
for s in imported_structs.values_mut() {
s.span = shift_absolute_span(s.span, edits);
}
for e in imported_enums.values_mut() {
e.span = shift_absolute_span(e.span, edits);
}
}
},
}
}
fn swap_offset(&mut self, offset: usize) {
match self {
Self::Struct(s) => s.offset = offset,
Self::Enum(e) => e.offset = offset,
Self::Task(t) => t.offset = offset,
Self::Workflow(w) => w.offset = offset,
Self::Import(i) => i.offset = offset,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub(in crate::document) enum CachedItemRef<'a> {
Struct(&'a CachedItem<Struct>),
Enum(&'a CachedItem<Enum>),
Task(&'a CachedItem<WithBodyHash<Task>>),
Workflow(&'a CachedItem<WithBodyHash<Workflow>>),
Import(&'a CachedItem<WithBodyHash<Import>>),
}
impl<'a> CachedItemRef<'a> {
pub fn name(&self) -> Option<&'a str> {
match self {
Self::Struct(s) => Some(s.item.name()),
Self::Enum(e) => Some(e.item.name()),
Self::Task(t) => Some(t.item.item.name()),
Self::Workflow(w) => Some(w.item.item.name()),
Self::Import(i) => match &i.item.item {
Import::Merging { .. } => None,
Import::Namespace(ns) => Some(ns.name()),
},
}
}
pub fn diagnostics(&self) -> impl Iterator<Item = Diagnostic> + use<'a> {
let (offset, diagnostics) = match self {
Self::Struct(s) => (s.offset, s.diagnostics.iter()),
Self::Enum(e) => (e.offset, e.diagnostics.iter()),
Self::Task(t) => (t.offset, t.diagnostics.iter()),
Self::Workflow(w) => (w.offset, w.diagnostics.iter()),
Self::Import(i) => (i.offset, i.diagnostics.iter()),
};
diagnostics.cloned().map(move |mut d| {
d.offset(offset as isize);
d
})
}
pub fn signature_hash(&self) -> SignatureHash {
match self {
Self::Struct(s) => s.signature_hash,
Self::Enum(e) => e.signature_hash,
Self::Task(t) => t.signature_hash,
Self::Workflow(w) => w.signature_hash,
Self::Import(i) => i.signature_hash,
}
}
pub fn body_hash(&self) -> Option<BodyHash> {
match self {
Self::Import(i) => Some(i.item.body_hash),
Self::Task(t) => Some(t.item.body_hash),
Self::Workflow(w) => Some(w.item.body_hash),
_ => None,
}
}
}
#[cfg(test)]
#[derive(Debug, Clone, Default)]
struct TestCache {
invalidated_signatures: Vec<SignatureHash>,
invalidated_bodies: Vec<SignatureHash>,
}
#[derive(Debug, Clone, Default)]
pub(crate) struct AnalysisCache {
pub structs: IndexMap<SignatureHash, CachedItem<Struct>>,
pub enums: IndexMap<SignatureHash, CachedItem<Enum>>,
pub tasks: IndexMap<SignatureHash, CachedItem<WithBodyHash<Task>>>,
pub workflow: Option<CachedItem<WithBodyHash<Workflow>>>,
pub imports: IndexMap<SignatureHash, CachedItem<WithBodyHash<Import>>>,
dependencies: DiGraphMap<SignatureHash, ()>,
#[cfg(test)]
tests: TestCache,
}
impl PartialEq for AnalysisCache {
fn eq(&self, other: &Self) -> bool {
self.structs == other.structs
&& self.enums == other.enums
&& self.tasks == other.tasks
&& self.workflow == other.workflow
&& self.imports == other.imports
&& self
.dependencies
.all_edges()
.all(|(a, b, _)| other.dependencies.contains_edge(a, b))
}
}
macro_rules! item_getters {
(
$(
(
$item_ty:ident, $cache_field:ident, $all_by_name:ident, $local_fn:ident, $local_fn_by_name:ident, $import_fn:ident, $import_fn_by_name:ident
) => ($ty:ty, $ref_ty:ident, $imported_ty:ty)
),+ $(,)+
) => {
$(
paste::paste! {
#[doc = "Gets the " $item_ty "s locally defined in the document."]
///
/// Returns `(index, hash, item)` tuples, where:
#[doc = "[`Self::" $item_ty "_by_index()`]."]
pub(crate) fn $local_fn(&self) -> impl Iterator<Item = (usize, SignatureHash, &$ty)> {
self.$cache_field
.iter()
.enumerate()
.map(|(idx, (hash, i))| (idx, *hash, i.target()))
}
#[doc = "Gets a locally defined " $item_ty " in the document by name."]
#[doc = "See: [`Self::" $local_fn "`]"]
pub(crate) fn $local_fn_by_name(&self, name: &str) -> Option<(usize, SignatureHash, &$ty)> {
self.$local_fn().find(|(_idx, _hash, i)| i.name() == name)
}
#[doc = "Gets the " $item_ty "s in the document."]
#[doc = "See: [`Self::" $local_fn "`], [`Self::" $import_fn "`]."]
pub(crate) fn $cache_field(&self) -> impl Iterator<Item = $ref_ty<'_>> {
self.$local_fn()
.map(|(_idx, _hash, t)| $ref_ty::Local(t))
.chain(self.$import_fn().map(|(_hash, t)| $ref_ty::Imported(t)))
}
#[doc = "Gets a " $item_ty " in the document by name."]
#[doc = "See: [`Self::" $local_fn_by_name "`], [`Self::" $import_fn_by_name "`]."]
pub(crate) fn $all_by_name(&self, name: &str) -> Option<(SignatureHash, $ref_ty<'_>)> {
self.$local_fn_by_name(name)
.map(|(_idx, hash, t)| (hash, $ref_ty::Local(t)))
.or_else(|| {
self.$import_fn_by_name(name)
.map(|(hash, t)| (hash, $ref_ty::Imported(t)))
})
}
#[doc = "Gets an imported " $item_ty " in the document by local name."]
pub(crate) fn $import_fn_by_name(
&self,
name: &str,
) -> Option<(SignatureHash, &$imported_ty)> {
self.$import_fn()
.find(|(_hash, t)| t.local_name == name)
}
}
)+
}
}
impl AnalysisCache {
item_getters!(
(task, tasks, task_by_name, local_tasks, local_task_by_name, imported_tasks, imported_task_by_name) => (Task, TaskRef, ImportedTask),
(struct, structs, struct_by_name, local_structs, local_struct_by_name, imported_structs, imported_struct_by_name) => (Struct, StructRef, ImportedStruct),
(enum, enums, enum_by_name, local_enums, local_enum_by_name, imported_enums, imported_enum_by_name) => (Enum, EnumRef, ImportedEnum),
);
pub fn len(&self) -> usize {
let Self {
structs,
enums,
tasks,
workflow,
imports,
dependencies: _,
#[cfg(test)]
tests: _,
} = self;
structs.len() + enums.len() + tasks.len() + workflow.is_some() as usize + imports.len()
}
#[cfg(test)]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub(crate) fn imported_structs(
&self,
) -> impl Iterator<Item = (SignatureHash, &ImportedStruct)> {
self.imports()
.flat_map(|(_idx, hash, i)| i.structs().map(move |s| (hash, s)))
}
pub(crate) fn imported_enums(&self) -> impl Iterator<Item = (SignatureHash, &ImportedEnum)> {
self.imports()
.flat_map(|(_idx, hash, i)| i.enums().map(move |e| (hash, e)))
}
pub(crate) fn imported_tasks(&self) -> impl Iterator<Item = (SignatureHash, &ImportedTask)> {
self.imports()
.filter_map(|(_idx, hash, i)| i.merging().map(|m| (hash, m)))
.flat_map(|(hash, i)| i.imported_tasks.values().map(move |t| (hash, t)))
}
pub(crate) fn imports(&self) -> impl Iterator<Item = (usize, SignatureHash, &Import)> {
self.imports
.iter()
.enumerate()
.map(|(idx, (hash, i))| (idx, *hash, &i.item.item))
}
pub(crate) fn namespaces(&self) -> impl Iterator<Item = (SignatureHash, &Namespace)> {
self.imports()
.filter_map(|(_idx, hash, i)| i.namespace().map(|ns| (hash, ns)))
}
pub fn namespace_by_name(&self, name: &str) -> Option<(SignatureHash, &Namespace)> {
self.namespaces().find(|(_, ns)| ns.name == name)
}
pub(crate) fn workflow(&self) -> Option<&Workflow> {
self.workflow.as_ref().map(|i| &i.item.item)
}
pub(crate) fn imported_workflow_by_name(
&self,
name: &str,
) -> Option<(SignatureHash, &ImportedWorkflow)> {
self.imports()
.filter_map(|(_idx, hash, i)| i.merging().map(|m| (hash, m)))
.find_map(|(hash, i)| i.imported_workflows.get(name).map(|w| (hash, w)))
}
pub(crate) fn imported_workflows(
&self,
) -> impl Iterator<Item = (SignatureHash, &ImportedWorkflow)> {
self.imports()
.filter_map(|(_idx, hash, i)| i.merging().map(|m| (hash, m)))
.flat_map(|(hash, i)| i.imported_workflows.values().map(move |w| (hash, w)))
}
pub(crate) fn workflow_by_name(&self, name: &str) -> Option<(SignatureHash, WorkflowRef<'_>)> {
self.workflow
.as_ref()
.filter(|wf| wf.item.item.name == name)
.map(|item| (item.signature_hash, WorkflowRef::Local(&item.item.item)))
.or_else(|| {
self.imported_workflow_by_name(name)
.map(|(hash, wf)| (hash, WorkflowRef::Imported(wf)))
})
}
}
impl AnalysisCache {
pub(in crate::document) fn dirty<'a>(
&self,
current_ast: &'a AstItems,
) -> impl Iterator<Item = (SignatureHash, Option<BodyHash>, &'a DocumentItem)> {
current_ast.items.iter().filter_map(move |ast_item| {
match self.get(&ast_item.signature_hash) {
Some(cache_item) => {
let Some(expected_body_hash) = cache_item.body_hash() else {
return None; };
let Some(new_body_hash) = ast_item.body_hash else {
return None;
};
if expected_body_hash == new_body_hash {
return None; }
Some((ast_item.signature_hash, ast_item.body_hash, &ast_item.item))
}
None => Some((ast_item.signature_hash, ast_item.body_hash, &ast_item.item)),
}
})
}
fn items(&self) -> impl Iterator<Item = CachedItemRef<'_>> {
self.structs
.values()
.map(CachedItemRef::Struct)
.chain(self.enums.values().map(CachedItemRef::Enum))
.chain(self.tasks.values().map(CachedItemRef::Task))
.chain(
self.workflow
.as_ref()
.into_iter()
.map(CachedItemRef::Workflow),
)
.chain(self.imports.values().map(CachedItemRef::Import))
}
pub(in crate::document) fn items_mut(&mut self) -> impl Iterator<Item = CachedItemRefMut<'_>> {
self.structs
.values_mut()
.map(CachedItemRefMut::Struct)
.chain(self.enums.values_mut().map(CachedItemRefMut::Enum))
.chain(self.tasks.values_mut().map(CachedItemRefMut::Task))
.chain(
self.workflow
.as_mut()
.into_iter()
.map(CachedItemRefMut::Workflow),
)
.chain(self.imports.values_mut().map(CachedItemRefMut::Import))
}
pub(in crate::document) fn diagnostics(&self) -> impl Iterator<Item = Diagnostic> + use<'_> {
self.items().flat_map(|i| i.diagnostics())
}
pub(in crate::document) fn get(&self, hash: &SignatureHash) -> Option<CachedItemRef<'_>> {
self.structs
.get(hash)
.map(CachedItemRef::Struct)
.or_else(|| self.enums.get(hash).map(CachedItemRef::Enum))
.or_else(|| self.tasks.get(hash).map(CachedItemRef::Task))
.or_else(|| {
if self.workflow.as_ref().map(|w| &w.signature_hash) == Some(hash) {
self.workflow.as_ref().map(CachedItemRef::Workflow)
} else {
None
}
})
.or_else(|| self.imports.get(hash).map(CachedItemRef::Import))
}
fn get_mut(&mut self, hash: &SignatureHash) -> Option<CachedItemRefMut<'_>> {
self.structs
.get_mut(hash)
.map(CachedItemRefMut::Struct)
.or_else(|| self.enums.get_mut(hash).map(CachedItemRefMut::Enum))
.or_else(|| self.tasks.get_mut(hash).map(CachedItemRefMut::Task))
.or_else(|| {
if self.workflow.as_ref().map(|w| &w.signature_hash) == Some(hash) {
self.workflow.as_mut().map(CachedItemRefMut::Workflow)
} else {
None
}
})
.or_else(|| self.imports.get_mut(hash).map(CachedItemRefMut::Import))
}
pub(in crate::document) fn item_by_name(&self, name: &str) -> Option<Item<'_>> {
self.items()
.map(Item::Local)
.chain(
self.imports()
.filter_map(|(_idx, _hash, i)| i.merging())
.flat_map(|i| i.items().map(Item::Imported)),
)
.find(|i| i.name() == Some(name))
}
pub(in crate::document) fn struct_by_index(&self, index: usize) -> Option<&Struct> {
Some(&self.structs.get_index(index)?.1.item)
}
pub(in crate::document) fn enum_by_index(&self, index: usize) -> Option<&Enum> {
Some(&self.enums.get_index(index)?.1.item)
}
fn keys(&self) -> impl Iterator<Item = &SignatureHash> {
self.structs
.keys()
.chain(self.enums.keys())
.chain(self.tasks.keys())
.chain(self.workflow.as_ref().map(|w| &w.signature_hash))
.chain(self.imports.keys())
}
pub(in crate::document) fn exports_hash(&self) -> BodyHash {
let mut hasher = Sha256::default();
let mut keys: Vec<_> = self.keys().collect();
keys.sort_unstable();
for signature in keys {
hasher.update(signature);
}
hasher.finalize().into()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(in crate::document) enum InvalidationStrategy {
Signature,
Body,
}
impl AnalysisCache {
pub(in crate::document) fn insert_import(
&mut self,
mut item: CachedItem<WithBodyHash<Import>>,
) {
let hash = item.signature_hash;
item.shift_diagnostic_offsets();
self.imports.insert(item.signature_hash, item);
self.dependencies.add_node(hash);
}
pub(in crate::document) fn insert_enum(&mut self, mut item: CachedItem<Enum>) {
let hash = item.signature_hash;
item.shift_diagnostic_offsets();
self.enums.insert(item.signature_hash, item);
self.dependencies.add_node(hash);
}
pub(in crate::document) fn insert_struct(&mut self, mut item: CachedItem<Struct>) {
let hash = item.signature_hash;
item.shift_diagnostic_offsets();
self.structs.insert(item.signature_hash, item);
self.dependencies.add_node(hash);
}
pub(in crate::document) fn insert_task(&mut self, mut item: CachedItem<WithBodyHash<Task>>) {
let hash = item.signature_hash;
item.shift_diagnostic_offsets();
self.tasks.insert(item.signature_hash, item);
self.dependencies.add_node(hash);
}
pub(in crate::document) fn set_workflow(&mut self, item: CachedItem<WithBodyHash<Workflow>>) {
let hash = item.signature_hash;
self.workflow = Some(item);
self.dependencies.add_node(hash);
}
pub(in crate::document) fn namespaces_mut(&mut self) -> impl Iterator<Item = &mut Namespace> {
self.imports
.values_mut()
.flat_map(|i| i.item.item.namespace_mut())
}
pub(in crate::document) fn workflow_item_mut(
&mut self,
) -> Option<&mut CachedItem<WithBodyHash<Workflow>>> {
self.workflow.as_mut()
}
pub(in crate::document) fn struct_item_mut(
&mut self,
index: usize,
) -> Option<&mut CachedItem<Struct>> {
Some(self.structs.get_index_mut(index)?.1)
}
fn remove_item(&mut self, hash: &SignatureHash) {
self.structs
.shift_remove(hash)
.map(|_| ())
.or_else(|| self.enums.shift_remove(hash).map(|_| ()))
.or_else(|| self.tasks.shift_remove(hash).map(|_| ()))
.or_else(|| self.imports.shift_remove(hash).map(|_| ()));
if self.workflow.as_ref().map(|w| &w.signature_hash) == Some(hash) {
self.workflow = None;
}
}
pub(in crate::document) fn invalidate(
&mut self,
ast_items: &AstItems,
edits: &[AppliedEdit],
hashes: impl IntoIterator<Item = (InvalidationStrategy, SignatureHash)>,
) {
let mut dirty_set = std::collections::HashSet::new();
for (strategy, hash) in hashes {
match strategy {
InvalidationStrategy::Signature => {
let mut stack = vec![hash];
while let Some(node) = stack.pop() {
if dirty_set.insert(node) {
#[cfg(test)]
self.tests.invalidated_signatures.push(node);
for dependent in self
.dependencies
.neighbors_directed(node, petgraph::Direction::Incoming)
{
stack.push(dependent);
}
}
}
}
InvalidationStrategy::Body => {
#[cfg(test)]
self.tests.invalidated_bodies.push(hash);
self.remove_item(&hash);
let outgoing: Vec<_> = self
.dependencies
.neighbors_directed(hash, petgraph::Direction::Outgoing)
.collect();
for dependency in outgoing {
self.dependencies.remove_edge(hash, dependency);
}
}
}
}
for hash in dirty_set {
self.remove_item(&hash);
self.dependencies.remove_node(hash);
}
for ast_item in &ast_items.items {
let hash = ast_item.signature_hash;
let Some(mut item) = self.get_mut(&hash) else {
continue;
};
item.shift_existing_diagnostics(edits, ast_item.offset);
item.swap_offset(ast_item.offset);
}
}
pub(in crate::document) fn intersect(
&self,
current_ast: &AstItems,
mut resolve_import_body_hash: impl FnMut(&ImportStatement) -> Option<BodyHash>,
) -> Vec<(InvalidationStrategy, SignatureHash)> {
let mut to_remove = Vec::new();
for cache_item in self.items() {
let signature_hash = cache_item.signature_hash();
if !current_ast.contains(&signature_hash) {
to_remove.push((InvalidationStrategy::Signature, signature_hash));
continue;
}
let new_body_hash = match cache_item {
CachedItemRef::Import(_) => {
let import_stmt = current_ast
.imports()
.find(|(h, _)| **h == signature_hash)
.map(|(_, i)| i)
.expect("should exist because current_ast contains hash");
resolve_import_body_hash(import_stmt)
}
_ => current_ast.get_body_hash(&signature_hash),
};
if cache_item.body_hash() != new_body_hash {
if matches!(cache_item, CachedItemRef::Import(_)) {
to_remove.push((InvalidationStrategy::Signature, signature_hash));
} else {
to_remove.push((InvalidationStrategy::Body, signature_hash));
}
}
}
to_remove
}
pub(in crate::document) fn enum_item_mut(
&mut self,
index: usize,
) -> Option<&mut CachedItem<Enum>> {
Some(self.enums.get_index_mut(index)?.1)
}
pub(in crate::document) fn add_dependency(
&mut self,
dependent: SignatureHash,
dependency: SignatureHash,
) {
self.dependencies.add_edge(dependent, dependency, ());
}
}
struct AstItem {
signature_hash: SignatureHash,
body_hash: Option<BodyHash>,
offset: usize,
item: DocumentItem,
}
pub(in crate::document) struct AstItems {
items: Vec<AstItem>,
}
impl AstItems {
pub fn contains(&self, signature_hash: &SignatureHash) -> bool {
self.items
.iter()
.any(|item| &item.signature_hash == signature_hash)
}
pub fn get_body_hash(&self, signature_hash: &SignatureHash) -> Option<BodyHash> {
self.items.iter().find_map(|item| {
if &item.signature_hash == signature_hash {
item.body_hash
} else {
None
}
})
}
pub fn len(&self) -> usize {
self.items.len()
}
fn imports(&self) -> impl Iterator<Item = (&SignatureHash, &ImportStatement)> {
self.items.iter().filter_map(|item| match &item.item {
DocumentItem::Import(i) => Some((&item.signature_hash, i)),
_ => None,
})
}
pub fn new(ast: &Ast) -> Self {
#[derive(PartialEq, Eq)]
struct DocumentItemOrd<'a>(&'a DocumentItem);
impl DocumentItemOrd<'_> {
fn ord(&self) -> u8 {
match self.0 {
DocumentItem::Import(_) => 0,
DocumentItem::Struct(_) | DocumentItem::Enum(_) => 1,
DocumentItem::Task(_) | DocumentItem::Workflow(_) => 2,
}
}
}
impl PartialOrd for DocumentItemOrd<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for DocumentItemOrd<'_> {
fn cmp(&self, other: &Self) -> Ordering {
self.ord().cmp(&other.ord())
}
}
let mut items = Vec::new();
for item in ast.items() {
let offset = usize::from(item.inner().text_range().start());
let (signature_hash, body_hash) = match &item {
DocumentItem::Import(i) => (HashableItem::hash(i), None),
DocumentItem::Struct(s) => (HashableItem::hash(s), None),
DocumentItem::Enum(e) => (HashableItem::hash(e), None),
DocumentItem::Task(t) => {
let (signature_hash, body_hash) = t.hash_callable();
(signature_hash, Some(body_hash))
}
DocumentItem::Workflow(w) => {
let (signature_hash, body_hash) = w.hash_callable();
(signature_hash, Some(body_hash))
}
};
items.push(AstItem {
signature_hash,
body_hash,
offset,
item,
});
}
items.sort_by(|a, b| DocumentItemOrd(&a.item).cmp(&DocumentItemOrd(&b.item)));
Self { items }
}
}