use std::borrow::Cow;
use std::collections::HashMap;
use std::collections::HashSet;
use std::collections::hash_map::Entry;
use std::path::Path;
use std::sync::Arc;
use arrayvec::ArrayString;
use indexmap::IndexMap;
use petgraph::graph::NodeIndex;
use rowan::GreenNode;
use rowan::TextRange;
use rowan::TextSize;
use url::Url;
use uuid::Uuid;
use wdl_ast::Ast;
use wdl_ast::AstNode;
use wdl_ast::Diagnostic;
use wdl_ast::Severity;
use wdl_ast::Span;
use wdl_ast::SupportedVersion;
use wdl_ast::SyntaxNode;
use crate::config::Config;
use crate::diagnostics::Context;
use crate::diagnostics::no_common_type;
use crate::diagnostics::unused_import;
use crate::graph::DocumentGraph;
use crate::graph::ParseState;
use crate::types::CallType;
use crate::types::Optional;
use crate::types::Type;
pub mod v1;
pub const TASK_VAR_NAME: &str = "task";
#[derive(Debug)]
pub struct Namespace {
span: Span,
source: Arc<Url>,
document: Document,
used: bool,
excepted: bool,
}
impl Namespace {
pub fn span(&self) -> Span {
self.span
}
pub fn source(&self) -> &Arc<Url> {
&self.source
}
pub fn document(&self) -> &Document {
&self.document
}
}
#[derive(Debug, Clone)]
pub struct Struct {
name: String,
name_span: Span,
offset: usize,
node: rowan::GreenNode,
namespace: Option<String>,
ty: Option<Type>,
}
impl Struct {
pub fn name(&self) -> &str {
&self.name
}
pub fn name_span(&self) -> Span {
self.name_span
}
pub fn offset(&self) -> usize {
self.offset
}
pub fn node(&self) -> &rowan::GreenNode {
&self.node
}
pub fn namespace(&self) -> Option<&str> {
self.namespace.as_deref()
}
pub fn ty(&self) -> Option<&Type> {
self.ty.as_ref()
}
}
#[derive(Debug, Clone)]
pub struct Enum {
name: String,
name_span: Span,
offset: usize,
node: rowan::GreenNode,
namespace: Option<String>,
ty: Option<Type>,
}
impl Enum {
pub fn name(&self) -> &str {
&self.name
}
pub fn name_span(&self) -> Span {
self.name_span
}
pub fn offset(&self) -> usize {
self.offset
}
pub fn node(&self) -> &rowan::GreenNode {
&self.node
}
pub fn definition(&self) -> wdl_ast::v1::EnumDefinition {
wdl_ast::v1::EnumDefinition::cast(wdl_ast::SyntaxNode::new_root(self.node.clone()))
.expect("stored node should be a valid enum definition")
}
pub fn namespace(&self) -> Option<&str> {
self.namespace.as_deref()
}
pub fn ty(&self) -> Option<&Type> {
self.ty.as_ref()
}
}
#[derive(Debug, Clone)]
pub struct Name {
span: Span,
ty: Type,
}
impl Name {
pub fn span(&self) -> Span {
self.span
}
pub fn ty(&self) -> &Type {
&self.ty
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ScopeIndex(usize);
#[derive(Debug)]
pub struct Scope {
parent: Option<ScopeIndex>,
span: Span,
names: IndexMap<String, Name>,
}
impl Scope {
fn new(parent: Option<ScopeIndex>, span: Span) -> Self {
Self {
parent,
span,
names: Default::default(),
}
}
pub fn insert(&mut self, name: impl Into<String>, span: Span, ty: Type) {
self.names.insert(name.into(), Name { span, ty });
}
}
#[derive(Debug, Clone, Copy)]
pub struct ScopeRef<'a> {
scopes: &'a [Scope],
index: ScopeIndex,
}
impl<'a> ScopeRef<'a> {
fn new(scopes: &'a [Scope], index: ScopeIndex) -> Self {
Self { scopes, index }
}
pub fn span(&self) -> Span {
self.scopes[self.index.0].span
}
pub fn parent(&self) -> Option<Self> {
self.scopes[self.index.0].parent.map(|p| Self {
scopes: self.scopes,
index: p,
})
}
pub fn names(&self) -> impl Iterator<Item = (&str, &Name)> + use<'_> {
self.scopes[self.index.0]
.names
.iter()
.map(|(name, n)| (name.as_str(), n))
}
pub fn local(&self, name: &str) -> Option<&Name> {
self.scopes[self.index.0].names.get(name)
}
pub fn lookup(&self, name: &str) -> Option<&Name> {
let mut current = Some(self.index);
while let Some(index) = current {
if let Some(name) = self.scopes[index.0].names.get(name) {
return Some(name);
}
current = self.scopes[index.0].parent;
}
None
}
}
#[derive(Debug)]
struct ScopeRefMut<'a> {
scopes: &'a mut [Scope],
index: ScopeIndex,
}
impl<'a> ScopeRefMut<'a> {
fn new(scopes: &'a mut [Scope], index: ScopeIndex) -> Self {
Self { scopes, index }
}
pub fn lookup(&self, name: &str) -> Option<&Name> {
let mut current = Some(self.index);
while let Some(index) = current {
if let Some(name) = self.scopes[index.0].names.get(name) {
return Some(name);
}
current = self.scopes[index.0].parent;
}
None
}
pub fn insert(&mut self, name: impl Into<String>, span: Span, ty: Type) {
self.scopes[self.index.0]
.names
.insert(name.into(), Name { span, ty });
}
pub fn as_scope_ref(&'a self) -> ScopeRef<'a> {
ScopeRef {
scopes: self.scopes,
index: self.index,
}
}
}
#[derive(Debug)]
pub struct ScopeUnion<'a> {
scope_refs: Vec<(ScopeRef<'a>, bool)>,
}
impl<'a> ScopeUnion<'a> {
pub fn new() -> Self {
Self {
scope_refs: Vec::new(),
}
}
pub fn insert(&mut self, scope_ref: ScopeRef<'a>, exhaustive: bool) {
self.scope_refs.push((scope_ref, exhaustive));
}
pub fn resolve(self) -> Result<HashMap<String, Name>, Vec<Diagnostic>> {
let mut errors = Vec::new();
let mut ignored: HashSet<String> = HashSet::new();
let mut names: HashMap<String, Name> = HashMap::new();
for (scope_ref, _) in &self.scope_refs {
for (name, info) in scope_ref.names() {
if ignored.contains(name) {
continue;
}
match names.entry(name.to_string()) {
Entry::Vacant(entry) => {
entry.insert(info.clone());
}
Entry::Occupied(mut entry) => {
let Some(ty) = entry.get().ty.common_type(&info.ty) else {
errors.push(no_common_type(
&entry.get().ty,
entry.get().span,
&info.ty,
info.span,
));
names.remove(name);
ignored.insert(name.to_string());
continue;
};
entry.get_mut().ty = ty;
}
}
}
}
for (scope_ref, _) in &self.scope_refs {
for (name, info) in &mut names {
if ignored.contains(name) {
continue;
}
if scope_ref.local(name).is_none() {
info.ty = info.ty.optional();
}
}
}
let has_exhaustive = self.scope_refs.iter().any(|(_, exhaustive)| *exhaustive);
if !has_exhaustive {
for info in names.values_mut() {
info.ty = info.ty.optional();
}
}
if !errors.is_empty() {
return Err(errors);
}
Ok(names)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Input {
ty: Type,
required: bool,
}
impl Input {
pub fn ty(&self) -> &Type {
&self.ty
}
pub fn required(&self) -> bool {
self.required
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Output {
ty: Type,
name_span: Span,
}
impl Output {
pub(crate) fn new(ty: Type, name_span: Span) -> Self {
Self { ty, name_span }
}
pub fn ty(&self) -> &Type {
&self.ty
}
pub fn name_span(&self) -> Span {
self.name_span
}
}
#[derive(Debug)]
pub struct Task {
name_span: Span,
name: String,
scopes: Vec<Scope>,
inputs: Arc<IndexMap<String, Input>>,
outputs: Arc<IndexMap<String, Output>>,
}
impl Task {
pub fn name(&self) -> &str {
&self.name
}
pub fn name_span(&self) -> Span {
self.name_span
}
pub fn scope(&self) -> ScopeRef<'_> {
ScopeRef::new(&self.scopes, ScopeIndex(0))
}
pub fn inputs(&self) -> &IndexMap<String, Input> {
&self.inputs
}
pub fn outputs(&self) -> &IndexMap<String, Output> {
&self.outputs
}
}
#[derive(Debug)]
pub struct Workflow {
name_span: Span,
name: String,
scopes: Vec<Scope>,
inputs: Arc<IndexMap<String, Input>>,
outputs: Arc<IndexMap<String, Output>>,
calls: HashMap<String, CallType>,
allows_nested_inputs: bool,
}
impl Workflow {
pub fn name(&self) -> &str {
&self.name
}
pub fn name_span(&self) -> Span {
self.name_span
}
pub fn scope(&self) -> ScopeRef<'_> {
ScopeRef::new(&self.scopes, ScopeIndex(0))
}
pub fn inputs(&self) -> &IndexMap<String, Input> {
&self.inputs
}
pub fn outputs(&self) -> &IndexMap<String, Output> {
&self.outputs
}
pub fn calls(&self) -> &HashMap<String, CallType> {
&self.calls
}
pub fn allows_nested_inputs(&self) -> bool {
self.allows_nested_inputs
}
}
#[derive(Debug)]
pub(crate) struct DocumentData {
config: Config,
root: Option<GreenNode>,
id: Arc<String>,
uri: Arc<Url>,
version: Option<SupportedVersion>,
namespaces: IndexMap<String, Namespace>,
tasks: IndexMap<String, Task>,
workflow: Option<Workflow>,
structs: IndexMap<String, Struct>,
enums: IndexMap<String, Enum>,
parse_diagnostics: Vec<Diagnostic>,
analysis_diagnostics: Vec<Diagnostic>,
}
impl DocumentData {
fn new(
config: Config,
uri: Arc<Url>,
root: Option<GreenNode>,
version: Option<SupportedVersion>,
diagnostics: Vec<Diagnostic>,
) -> Self {
Self {
config,
root,
id: Uuid::new_v4().to_string().into(),
uri,
version,
namespaces: Default::default(),
tasks: Default::default(),
workflow: Default::default(),
structs: Default::default(),
enums: Default::default(),
parse_diagnostics: diagnostics,
analysis_diagnostics: Default::default(),
}
}
pub fn context(&self, name: &str) -> Option<Context> {
if let Some(ns) = self.namespaces.get(name) {
Some(Context::Namespace(ns.span()))
} else if let Some(task) = self.tasks.get(name) {
Some(Context::Task(task.name_span()))
} else if let Some(wf) = &self.workflow
&& wf.name == name
{
Some(Context::Workflow(wf.name_span()))
} else if let Some(s) = self.structs.get(name) {
Some(Context::Struct(s.name_span()))
} else {
self.enums.get(name).map(|e| Context::Enum(e.name_span()))
}
}
}
#[derive(Debug, Clone)]
pub struct Document {
data: Arc<DocumentData>,
}
impl Document {
pub(crate) fn default_from_uri(uri: Arc<Url>) -> Self {
Self {
data: Arc::new(DocumentData::new(
Default::default(),
uri,
None,
None,
Default::default(),
)),
}
}
pub(crate) fn from_graph_node(
config: &Config,
graph: &DocumentGraph,
index: NodeIndex,
) -> Self {
let node = graph.get(index);
let (wdl_version, diagnostics) = match node.parse_state() {
ParseState::NotParsed => panic!("node should have been parsed"),
ParseState::Error(_) => return Self::default_from_uri(node.uri().clone()),
ParseState::Parsed {
wdl_version,
diagnostics,
..
} => (wdl_version, diagnostics),
};
let root = node.root().expect("node should have been parsed");
let (config, wdl_version) = match (root.version_statement(), wdl_version) {
(Some(stmt), Some(wdl_version)) => (
config.with_diagnostics_config(
config.diagnostics_config().excepted_for_node(stmt.inner()),
),
*wdl_version,
),
_ => {
return Self {
data: Arc::new(DocumentData::new(
config.clone(),
node.uri().clone(),
Some(root.inner().green().into()),
None,
diagnostics.to_vec(),
)),
};
}
};
let mut data = DocumentData::new(
config.clone(),
node.uri().clone(),
Some(root.inner().green().into()),
Some(wdl_version),
diagnostics.to_vec(),
);
match root.ast_with_version_fallback(config.fallback_version()) {
Ast::Unsupported => {}
Ast::V1(ast) => v1::populate_document(&mut data, &config, graph, index, &ast),
}
if let Some(severity) = config.diagnostics_config().unused_import {
let DocumentData {
namespaces,
analysis_diagnostics,
..
} = &mut data;
analysis_diagnostics.extend(
namespaces
.iter()
.filter(|(_, ns)| !ns.used && !ns.excepted)
.map(|(name, ns)| unused_import(name, ns.span()).with_severity(severity)),
);
}
Self {
data: Arc::new(data),
}
}
pub fn config(&self) -> &Config {
&self.data.config
}
pub fn root(&self) -> wdl_ast::Document {
wdl_ast::Document::cast(SyntaxNode::new_root(
self.data.root.clone().expect("should have a root"),
))
.expect("should cast")
}
pub fn id(&self) -> &Arc<String> {
&self.data.id
}
pub fn uri(&self) -> &Arc<Url> {
&self.data.uri
}
pub fn path(&self) -> Cow<'_, str> {
if let Ok(path) = self.data.uri.to_file_path() {
if let Some(path) = std::env::current_dir()
.ok()
.and_then(|cwd| path.strip_prefix(cwd).ok().and_then(Path::to_str))
{
return path.to_string().into();
}
if let Ok(path) = path.into_os_string().into_string() {
return path.into();
}
}
self.data.uri.as_str().into()
}
pub fn hash_span(&self, span: Span) -> Option<ArrayString<64>> {
let text = self.root().inner().text();
let text_len = usize::from(text.len());
if span.end() > text_len {
return None;
}
let range = TextRange::new(
TextSize::new(span.start() as u32),
TextSize::new(span.end() as u32),
);
let slice = text.slice(range);
let mut hasher = blake3::Hasher::new();
slice.for_each_chunk(|chunk| {
hasher.update(chunk.as_bytes());
});
Some(hasher.finalize().to_hex())
}
pub fn version(&self) -> Option<SupportedVersion> {
self.data.version
}
pub fn namespaces(&self) -> impl Iterator<Item = (&str, &Namespace)> {
self.data.namespaces.iter().map(|(n, ns)| (n.as_str(), ns))
}
pub fn namespace(&self, name: &str) -> Option<&Namespace> {
self.data.namespaces.get(name)
}
pub fn tasks(&self) -> impl Iterator<Item = &Task> {
self.data.tasks.iter().map(|(_, t)| t)
}
pub fn task_by_name(&self, name: &str) -> Option<&Task> {
self.data.tasks.get(name)
}
pub fn workflow(&self) -> Option<&Workflow> {
self.data.workflow.as_ref()
}
pub fn structs(&self) -> impl Iterator<Item = (&str, &Struct)> {
self.data.structs.iter().map(|(n, s)| (n.as_str(), s))
}
pub fn struct_by_name(&self, name: &str) -> Option<&Struct> {
self.data.structs.get(name)
}
pub fn enums(&self) -> impl Iterator<Item = (&str, &Enum)> {
self.data.enums.iter().map(|(n, e)| (n.as_str(), e))
}
pub fn enum_by_name(&self, name: &str) -> Option<&Enum> {
self.data.enums.get(name)
}
pub fn get_custom_type(&self, name: &str) -> Option<Type> {
if let Some(s) = self.struct_by_name(name) {
return s.ty().cloned();
}
if let Some(s) = self.enum_by_name(name) {
return s.ty().cloned();
}
None
}
pub fn get_variant_cache_key(
&self,
name: &str,
variant: &str,
) -> Option<crate::types::EnumVariantCacheKey> {
let (enum_index, _, r#enum) = self.data.enums.get_full(name)?;
let enum_ty = r#enum.ty()?.as_enum()?;
let variant_index = enum_ty.variants().iter().position(|v| v == variant)?;
Some(crate::types::EnumVariantCacheKey::new(
enum_index,
variant_index,
))
}
pub fn parse_diagnostics(&self) -> &[Diagnostic] {
&self.data.parse_diagnostics
}
pub fn analysis_diagnostics(&self) -> &[Diagnostic] {
&self.data.analysis_diagnostics
}
pub fn diagnostics(&self) -> impl Iterator<Item = &Diagnostic> {
self.data
.parse_diagnostics
.iter()
.chain(self.data.analysis_diagnostics.iter())
}
pub fn sort_diagnostics(&mut self) -> Self {
let data = &mut self.data;
let inner = Arc::get_mut(data).expect("should only have one reference");
inner.parse_diagnostics.sort();
inner.analysis_diagnostics.sort();
Self { data: data.clone() }
}
pub fn extend_diagnostics(&mut self, diagnostics: Vec<Diagnostic>) -> Self {
let data = &mut self.data;
let inner = Arc::get_mut(data).expect("should only have one reference");
inner.analysis_diagnostics.extend(diagnostics);
Self { data: data.clone() }
}
pub fn find_scope_by_position(&self, position: usize) -> Option<ScopeRef<'_>> {
fn find_scope(scopes: &[Scope], position: usize) -> Option<ScopeRef<'_>> {
let mut index = match scopes.binary_search_by_key(&position, |s| s.span.start()) {
Ok(index) => index,
Err(index) => {
if index == 0 {
return None;
}
index - 1
}
};
loop {
let scope = &scopes[index];
if scope.span.contains(position) {
return Some(ScopeRef::new(scopes, ScopeIndex(index)));
}
if index == 0 {
return None;
}
index -= 1;
}
}
if let Some(workflow) = &self.data.workflow
&& workflow.scope().span().contains(position)
{
return find_scope(&workflow.scopes, position);
}
let task = match self
.data
.tasks
.binary_search_by_key(&position, |_, t| t.scope().span().start())
{
Ok(index) => &self.data.tasks[index],
Err(index) => {
if index == 0 {
return None;
}
&self.data.tasks[index - 1]
}
};
if task.scope().span().contains(position) {
return find_scope(&task.scopes, position);
}
None
}
pub fn has_errors(&self) -> bool {
if self.diagnostics().any(|d| d.severity() == Severity::Error) {
return true;
}
for (_, ns) in self.namespaces() {
if ns.document.has_errors() {
return true;
}
}
false
}
pub fn visit<V: crate::Visitor>(&self, diagnostics: &mut crate::Diagnostics, visitor: &mut V) {
crate::visit(self, diagnostics, visitor)
}
}