wdl_analysis/
document.rs

1//! Representation of analyzed WDL documents.
2
3use std::borrow::Cow;
4use std::collections::HashMap;
5use std::path::Path;
6use std::sync::Arc;
7
8use indexmap::IndexMap;
9use petgraph::graph::NodeIndex;
10use rowan::GreenNode;
11use url::Url;
12use uuid::Uuid;
13use wdl_ast::Ast;
14use wdl_ast::AstNode;
15use wdl_ast::Diagnostic;
16use wdl_ast::Severity;
17use wdl_ast::Span;
18use wdl_ast::SupportedVersion;
19use wdl_ast::SyntaxNode;
20
21use crate::config::Config;
22use crate::diagnostics::unused_import;
23use crate::graph::DocumentGraph;
24use crate::graph::ParseState;
25use crate::types::CallType;
26use crate::types::Type;
27
28mod v1;
29
30/// The `task` variable name available in task command sections and outputs in
31/// WDL 1.2.
32pub const TASK_VAR_NAME: &str = "task";
33
34/// Represents a namespace introduced by an import.
35#[derive(Debug)]
36pub struct Namespace {
37    /// The span of the import that introduced the namespace.
38    span: Span,
39    /// The URI of the imported document that introduced the namespace.
40    source: Arc<Url>,
41    /// The namespace's document.
42    document: Document,
43    /// Whether or not the namespace is used (i.e. referenced) in the document.
44    used: bool,
45    /// Whether or not the namespace is excepted from the "unused import"
46    /// diagnostic.
47    excepted: bool,
48}
49
50impl Namespace {
51    /// Gets the span of the import that introduced the namespace.
52    pub fn span(&self) -> Span {
53        self.span
54    }
55
56    /// Gets the URI of the imported document that introduced the namespace.
57    pub fn source(&self) -> &Arc<Url> {
58        &self.source
59    }
60
61    /// Gets the imported document.
62    pub fn document(&self) -> &Document {
63        &self.document
64    }
65}
66
67/// Represents a struct in a document.
68#[derive(Debug, Clone)]
69pub struct Struct {
70    /// The name of the struct.
71    name: String,
72    /// The span that introduced the struct.
73    ///
74    /// This is either the name of a struct definition (local) or an import's
75    /// URI or alias (imported).
76    name_span: Span,
77    /// The offset of the CST node from the start of the document.
78    ///
79    /// This is used to adjust diagnostics resulting from traversing the struct
80    /// node as if it were the root of the CST.
81    offset: usize,
82    /// Stores the CST node of the struct.
83    ///
84    /// This is used to calculate type equivalence for imports.
85    node: rowan::GreenNode,
86    /// The namespace that defines the struct.
87    ///
88    /// This is `Some` only for imported structs.
89    namespace: Option<String>,
90    /// The type of the struct.
91    ///
92    /// Initially this is `None` until a type check occurs.
93    ty: Option<Type>,
94}
95
96impl Struct {
97    /// Gets the name of the struct.
98    pub fn name(&self) -> &str {
99        &self.name
100    }
101
102    /// Gets the span of the name.
103    pub fn name_span(&self) -> Span {
104        self.name_span
105    }
106
107    /// Gets the offset of the struct
108    pub fn offset(&self) -> usize {
109        self.offset
110    }
111
112    /// Gets the node of the struct.
113    pub fn node(&self) -> &rowan::GreenNode {
114        &self.node
115    }
116
117    /// Gets the namespace that defines this struct.
118    ///
119    /// Returns `None` for structs defined in the containing document or `Some`
120    /// for a struct introduced by an import.
121    pub fn namespace(&self) -> Option<&str> {
122        self.namespace.as_deref()
123    }
124
125    /// Gets the type of the struct.
126    ///
127    /// A value of `None` indicates that the type could not be determined for
128    /// the struct; this may happen if the struct definition is recursive.
129    pub fn ty(&self) -> Option<&Type> {
130        self.ty.as_ref()
131    }
132}
133
134/// Represents information about a name in a scope.
135#[derive(Debug, Clone)]
136pub struct Name {
137    /// The span of the name.
138    span: Span,
139    /// The type of the name.
140    ty: Type,
141}
142
143impl Name {
144    /// Gets the span of the name.
145    pub fn span(&self) -> Span {
146        self.span
147    }
148
149    /// Gets the type of the name.
150    pub fn ty(&self) -> &Type {
151        &self.ty
152    }
153}
154
155/// Represents an index of a scope in a collection of scopes.
156#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
157struct ScopeIndex(usize);
158
159/// Represents a scope in a WDL document.
160#[derive(Debug)]
161struct Scope {
162    /// The index of the parent scope.
163    ///
164    /// This is `None` for task and workflow scopes.
165    parent: Option<ScopeIndex>,
166    /// The span in the document where the names of the scope are visible.
167    span: Span,
168    /// The map of names in scope to their span and types.
169    names: IndexMap<String, Name>,
170}
171
172impl Scope {
173    /// Creates a new scope given the parent scope and span.
174    fn new(parent: Option<ScopeIndex>, span: Span) -> Self {
175        Self {
176            parent,
177            span,
178            names: Default::default(),
179        }
180    }
181
182    /// Inserts a name into the scope.
183    pub fn insert(&mut self, name: impl Into<String>, span: Span, ty: Type) {
184        self.names.insert(name.into(), Name { span, ty });
185    }
186}
187
188/// Represents a reference to a scope.
189#[derive(Debug, Clone, Copy)]
190pub struct ScopeRef<'a> {
191    /// The reference to the scopes collection.
192    scopes: &'a [Scope],
193    /// The index of the scope in the collection.
194    index: ScopeIndex,
195}
196
197impl<'a> ScopeRef<'a> {
198    /// Creates a new scope reference given the scope index.
199    fn new(scopes: &'a [Scope], index: ScopeIndex) -> Self {
200        Self { scopes, index }
201    }
202
203    /// Gets the span of the scope.
204    pub fn span(&self) -> Span {
205        self.scopes[self.index.0].span
206    }
207
208    /// Gets the parent scope.
209    ///
210    /// Returns `None` if there is no parent scope.
211    pub fn parent(&self) -> Option<Self> {
212        self.scopes[self.index.0].parent.map(|p| Self {
213            scopes: self.scopes,
214            index: p,
215        })
216    }
217
218    /// Gets all of the names available at this scope.
219    pub fn names(&self) -> impl Iterator<Item = (&str, &Name)> + use<'_> {
220        self.scopes[self.index.0]
221            .names
222            .iter()
223            .map(|(name, n)| (name.as_str(), n))
224    }
225
226    /// Gets a name local to this scope.
227    ///
228    /// Returns `None` if a name local to this scope was not found.
229    pub fn local(&self, name: &str) -> Option<&Name> {
230        self.scopes[self.index.0].names.get(name)
231    }
232
233    /// Lookups a name in the scope.
234    ///
235    /// Returns `None` if the name is not available in the scope.
236    pub fn lookup(&self, name: &str) -> Option<&Name> {
237        let mut current = Some(self.index);
238
239        while let Some(index) = current {
240            if let Some(name) = self.scopes[index.0].names.get(name) {
241                return Some(name);
242            }
243
244            current = self.scopes[index.0].parent;
245        }
246
247        None
248    }
249}
250
251/// Represents a mutable reference to a scope.
252#[derive(Debug)]
253struct ScopeRefMut<'a> {
254    /// The reference to all scopes.
255    scopes: &'a mut [Scope],
256    /// The index to the scope.
257    index: ScopeIndex,
258}
259
260impl<'a> ScopeRefMut<'a> {
261    /// Creates a new mutable scope reference given the scope index.
262    fn new(scopes: &'a mut [Scope], index: ScopeIndex) -> Self {
263        Self { scopes, index }
264    }
265
266    /// Lookups a name in the scope.
267    ///
268    /// Returns `None` if the name is not available in the scope.
269    pub fn lookup(&self, name: &str) -> Option<&Name> {
270        let mut current = Some(self.index);
271
272        while let Some(index) = current {
273            if let Some(name) = self.scopes[index.0].names.get(name) {
274                return Some(name);
275            }
276
277            current = self.scopes[index.0].parent;
278        }
279
280        None
281    }
282
283    /// Inserts a name into the scope.
284    pub fn insert(&mut self, name: impl Into<String>, span: Span, ty: Type) {
285        self.scopes[self.index.0]
286            .names
287            .insert(name.into(), Name { span, ty });
288    }
289
290    /// Converts the mutable scope reference to an immutable scope reference.
291    pub fn as_scope_ref(&'a self) -> ScopeRef<'a> {
292        ScopeRef {
293            scopes: self.scopes,
294            index: self.index,
295        }
296    }
297}
298
299/// Represents a task or workflow input.
300#[derive(Debug, Clone, PartialEq, Eq)]
301pub struct Input {
302    /// The type of the input.
303    ty: Type,
304    /// Whether or not the input is required.
305    ///
306    /// A required input is one that has a non-optional type and no default
307    /// expression.
308    required: bool,
309}
310
311impl Input {
312    /// Gets the type of the input.
313    pub fn ty(&self) -> &Type {
314        &self.ty
315    }
316
317    /// Whether or not the input is required.
318    pub fn required(&self) -> bool {
319        self.required
320    }
321}
322
323/// Represents a task or workflow output.
324#[derive(Debug, Clone, PartialEq, Eq)]
325pub struct Output {
326    /// The type of the output.
327    ty: Type,
328    /// The span of the output name.
329    name_span: Span,
330}
331
332impl Output {
333    /// Creates a new output with the given type.
334    pub(crate) fn new(ty: Type, name_span: Span) -> Self {
335        Self { ty, name_span }
336    }
337
338    /// Gets the type of the output.
339    pub fn ty(&self) -> &Type {
340        &self.ty
341    }
342
343    /// Gets the span of output's name.
344    pub fn name_span(&self) -> Span {
345        self.name_span
346    }
347}
348
349/// Represents a task in a document.
350#[derive(Debug)]
351pub struct Task {
352    /// The span of the task name.
353    name_span: Span,
354    /// The name of the task.
355    name: String,
356    /// The scopes contained in the task.
357    ///
358    /// The first scope will always be the task's scope.
359    ///
360    /// The scopes will be in sorted order by span start.
361    scopes: Vec<Scope>,
362    /// The inputs of the task.
363    inputs: Arc<IndexMap<String, Input>>,
364    /// The outputs of the task.
365    outputs: Arc<IndexMap<String, Output>>,
366}
367
368impl Task {
369    /// Gets the name of the task.
370    pub fn name(&self) -> &str {
371        &self.name
372    }
373
374    /// Gets the span of the name.
375    pub fn name_span(&self) -> Span {
376        self.name_span
377    }
378
379    /// Gets the scope of the task.
380    pub fn scope(&self) -> ScopeRef<'_> {
381        ScopeRef::new(&self.scopes, ScopeIndex(0))
382    }
383
384    /// Gets the inputs of the task.
385    pub fn inputs(&self) -> &IndexMap<String, Input> {
386        &self.inputs
387    }
388
389    /// Gets the outputs of the task.
390    pub fn outputs(&self) -> &IndexMap<String, Output> {
391        &self.outputs
392    }
393}
394
395/// Represents a workflow in a document.
396#[derive(Debug)]
397pub struct Workflow {
398    /// The span of the workflow name.
399    name_span: Span,
400    /// The name of the workflow.
401    name: String,
402    /// The scopes contained in the workflow.
403    ///
404    /// The first scope will always be the workflow's scope.
405    ///
406    /// The scopes will be in sorted order by span start.
407    scopes: Vec<Scope>,
408    /// The inputs of the workflow.
409    inputs: Arc<IndexMap<String, Input>>,
410    /// The outputs of the workflow.
411    outputs: Arc<IndexMap<String, Output>>,
412    /// The calls made by the workflow.
413    calls: HashMap<String, CallType>,
414    /// Whether or not nested inputs are allowed for the workflow.
415    allows_nested_inputs: bool,
416}
417
418impl Workflow {
419    /// Gets the name of the workflow.
420    pub fn name(&self) -> &str {
421        &self.name
422    }
423
424    /// Gets the span of the name.
425    pub fn name_span(&self) -> Span {
426        self.name_span
427    }
428
429    /// Gets the scope of the workflow.
430    pub fn scope(&self) -> ScopeRef<'_> {
431        ScopeRef::new(&self.scopes, ScopeIndex(0))
432    }
433
434    /// Gets the inputs of the workflow.
435    pub fn inputs(&self) -> &IndexMap<String, Input> {
436        &self.inputs
437    }
438
439    /// Gets the outputs of the workflow.
440    pub fn outputs(&self) -> &IndexMap<String, Output> {
441        &self.outputs
442    }
443
444    /// Gets the calls made by the workflow.
445    pub fn calls(&self) -> &HashMap<String, CallType> {
446        &self.calls
447    }
448
449    /// Determines if the workflow allows nested inputs.
450    pub fn allows_nested_inputs(&self) -> bool {
451        self.allows_nested_inputs
452    }
453}
454
455/// Represents analysis data about a WDL document.
456#[derive(Debug)]
457struct DocumentData {
458    /// The configuration under which this document was analyzed.
459    config: Config,
460    /// The root CST node of the document.
461    ///
462    /// This is `None` when the document could not be parsed.
463    root: Option<GreenNode>,
464    /// The document identifier.
465    ///
466    /// The identifier changes every time the document is analyzed.
467    id: Arc<String>,
468    /// The URI of the analyzed document.
469    uri: Arc<Url>,
470    /// The version of the document.
471    version: Option<SupportedVersion>,
472    /// The namespaces in the document.
473    namespaces: IndexMap<String, Namespace>,
474    /// The tasks in the document.
475    tasks: IndexMap<String, Task>,
476    /// The singular workflow in the document.
477    workflow: Option<Workflow>,
478    /// The structs in the document.
479    structs: IndexMap<String, Struct>,
480    /// The diagnostics from parsing.
481    parse_diagnostics: Vec<Diagnostic>,
482    /// The diagnostics from analysis.
483    analysis_diagnostics: Vec<Diagnostic>,
484}
485
486impl DocumentData {
487    /// Constructs a new analysis document data.
488    fn new(
489        config: Config,
490        uri: Arc<Url>,
491        root: Option<GreenNode>,
492        version: Option<SupportedVersion>,
493        diagnostics: Vec<Diagnostic>,
494    ) -> Self {
495        Self {
496            config,
497            root,
498            id: Uuid::new_v4().to_string().into(),
499            uri,
500            version,
501            namespaces: Default::default(),
502            tasks: Default::default(),
503            workflow: Default::default(),
504            structs: Default::default(),
505            parse_diagnostics: diagnostics,
506            analysis_diagnostics: Default::default(),
507        }
508    }
509}
510
511/// Represents an analyzed WDL document.
512///
513/// This type is cheaply cloned.
514#[derive(Debug, Clone)]
515pub struct Document {
516    /// The document data for the document.
517    data: Arc<DocumentData>,
518}
519
520impl Document {
521    /// Creates a new default document from a URI.
522    pub(crate) fn default_from_uri(uri: Arc<Url>) -> Self {
523        Self {
524            data: Arc::new(DocumentData::new(
525                Default::default(),
526                uri,
527                None,
528                None,
529                Default::default(),
530            )),
531        }
532    }
533
534    /// Creates a new analyzed document from a document graph node.
535    pub(crate) fn from_graph_node(
536        config: &Config,
537        graph: &DocumentGraph,
538        index: NodeIndex,
539    ) -> Self {
540        let node = graph.get(index);
541
542        let (wdl_version, diagnostics) = match node.parse_state() {
543            ParseState::NotParsed => panic!("node should have been parsed"),
544            ParseState::Error(_) => return Self::default_from_uri(node.uri().clone()),
545            ParseState::Parsed {
546                wdl_version,
547                diagnostics,
548                ..
549            } => (wdl_version, diagnostics),
550        };
551
552        let root = node.root().expect("node should have been parsed");
553        let (config, wdl_version) = match (root.version_statement(), wdl_version) {
554            (Some(stmt), Some(wdl_version)) => (
555                config.with_diagnostics_config(
556                    config.diagnostics_config().excepted_for_node(stmt.inner()),
557                ),
558                *wdl_version,
559            ),
560            _ => {
561                // Don't process a document with a missing version statement or an unsupported
562                // version unless a fallback version is configured
563                return Self {
564                    data: Arc::new(DocumentData::new(
565                        config.clone(),
566                        node.uri().clone(),
567                        Some(root.inner().green().into()),
568                        None,
569                        diagnostics.to_vec(),
570                    )),
571                };
572            }
573        };
574
575        let mut data = DocumentData::new(
576            config.clone(),
577            node.uri().clone(),
578            Some(root.inner().green().into()),
579            Some(wdl_version),
580            diagnostics.to_vec(),
581        );
582        match root.ast_with_version_fallback(config.fallback_version()) {
583            Ast::Unsupported => {}
584            Ast::V1(ast) => v1::populate_document(&mut data, &config, graph, index, &ast),
585        }
586
587        // Check for unused imports
588        if let Some(severity) = config.diagnostics_config().unused_import {
589            let DocumentData {
590                namespaces,
591                analysis_diagnostics,
592                ..
593            } = &mut data;
594
595            analysis_diagnostics.extend(
596                namespaces
597                    .iter()
598                    .filter(|(_, ns)| !ns.used && !ns.excepted)
599                    .map(|(name, ns)| unused_import(name, ns.span()).with_severity(severity)),
600            );
601        }
602
603        Self {
604            data: Arc::new(data),
605        }
606    }
607
608    /// Gets the analysis configuration.
609    pub fn config(&self) -> &Config {
610        &self.data.config
611    }
612
613    /// Gets the root AST document node.
614    ///
615    /// # Panics
616    ///
617    /// Panics if the document was not parsed.
618    pub fn root(&self) -> wdl_ast::Document {
619        wdl_ast::Document::cast(SyntaxNode::new_root(
620            self.data.root.clone().expect("should have a root"),
621        ))
622        .expect("should cast")
623    }
624
625    /// Gets the identifier of the document.
626    ///
627    /// This value changes when a document is reanalyzed.
628    pub fn id(&self) -> &Arc<String> {
629        &self.data.id
630    }
631
632    /// Gets the URI of the document.
633    pub fn uri(&self) -> &Arc<Url> {
634        &self.data.uri
635    }
636
637    /// Gets the path to the document.
638    ///
639    /// If the scheme of the document's URI is not `file`, this will return the
640    /// URI as a string. Otherwise, this will attempt to return the path
641    /// relative to the current working directory, or the absolute path
642    /// failing that.
643    pub fn path(&self) -> Cow<'_, str> {
644        if let Ok(path) = self.data.uri.to_file_path() {
645            if let Some(path) = std::env::current_dir()
646                .ok()
647                .and_then(|cwd| path.strip_prefix(cwd).ok().and_then(Path::to_str))
648            {
649                return path.to_string().into();
650            }
651
652            if let Ok(path) = path.into_os_string().into_string() {
653                return path.into();
654            }
655        }
656
657        self.data.uri.as_str().into()
658    }
659
660    /// Gets the supported version of the document.
661    ///
662    /// Returns `None` if the document could not be parsed or contains an
663    /// unsupported version.
664    pub fn version(&self) -> Option<SupportedVersion> {
665        self.data.version
666    }
667
668    /// Gets the namespaces in the document.
669    pub fn namespaces(&self) -> impl Iterator<Item = (&str, &Namespace)> {
670        self.data.namespaces.iter().map(|(n, ns)| (n.as_str(), ns))
671    }
672
673    /// Gets a namespace in the document by name.
674    pub fn namespace(&self, name: &str) -> Option<&Namespace> {
675        self.data.namespaces.get(name)
676    }
677
678    /// Gets the tasks in the document.
679    pub fn tasks(&self) -> impl Iterator<Item = &Task> {
680        self.data.tasks.iter().map(|(_, t)| t)
681    }
682
683    /// Gets a task in the document by name.
684    pub fn task_by_name(&self, name: &str) -> Option<&Task> {
685        self.data.tasks.get(name)
686    }
687
688    /// Gets a workflow in the document.
689    ///
690    /// Returns `None` if the document did not contain a workflow.
691    pub fn workflow(&self) -> Option<&Workflow> {
692        self.data.workflow.as_ref()
693    }
694
695    /// Gets the structs in the document.
696    pub fn structs(&self) -> impl Iterator<Item = (&str, &Struct)> {
697        self.data.structs.iter().map(|(n, s)| (n.as_str(), s))
698    }
699
700    /// Gets a struct in the document by name.
701    pub fn struct_by_name(&self, name: &str) -> Option<&Struct> {
702        self.data.structs.get(name)
703    }
704
705    /// Gets the parse diagnostics for the document.
706    pub fn parse_diagnostics(&self) -> &[Diagnostic] {
707        &self.data.parse_diagnostics
708    }
709
710    /// Gets the analysis diagnostics for the document.
711    pub fn analysis_diagnostics(&self) -> &[Diagnostic] {
712        &self.data.analysis_diagnostics
713    }
714
715    /// Gets all diagnostics for the document (both from parsing and analysis).
716    pub fn diagnostics(&self) -> impl Iterator<Item = &Diagnostic> {
717        self.data
718            .parse_diagnostics
719            .iter()
720            .chain(self.data.analysis_diagnostics.iter())
721    }
722
723    /// Sorts the diagnostics for the document.
724    ///
725    /// # Panics
726    ///
727    /// Panics if there is more than one reference to the document.
728    pub fn sort_diagnostics(&mut self) -> Self {
729        let data = &mut self.data;
730        let inner = Arc::get_mut(data).expect("should only have one reference");
731        inner.parse_diagnostics.sort();
732        inner.analysis_diagnostics.sort();
733        Self { data: data.clone() }
734    }
735
736    /// Extends the analysis diagnostics for the document.
737    ///
738    /// # Panics
739    ///
740    /// Panics if there is more than one reference to the document.
741    pub fn extend_diagnostics(&mut self, diagnostics: Vec<Diagnostic>) -> Self {
742        let data = &mut self.data;
743        let inner = Arc::get_mut(data).expect("should only have one reference");
744        inner.analysis_diagnostics.extend(diagnostics);
745        Self { data: data.clone() }
746    }
747
748    /// Finds a scope based on a position within the document.
749    pub fn find_scope_by_position(&self, position: usize) -> Option<ScopeRef<'_>> {
750        /// Finds a scope within a collection of sorted scopes by position.
751        fn find_scope(scopes: &[Scope], position: usize) -> Option<ScopeRef<'_>> {
752            let mut index = match scopes.binary_search_by_key(&position, |s| s.span.start()) {
753                Ok(index) => index,
754                Err(index) => {
755                    // This indicates that we couldn't find a match and the match would go _before_
756                    // the first scope, so there is no containing scope.
757                    if index == 0 {
758                        return None;
759                    }
760
761                    index - 1
762                }
763            };
764
765            // We now have the index to start looking up the list of scopes
766            // We walk up the list to try to find a span that contains the position
767            loop {
768                let scope = &scopes[index];
769                if scope.span.contains(position) {
770                    return Some(ScopeRef::new(scopes, ScopeIndex(index)));
771                }
772
773                if index == 0 {
774                    return None;
775                }
776
777                index -= 1;
778            }
779        }
780
781        // Check to see if the position is contained in the workflow
782        if let Some(workflow) = &self.data.workflow
783            && workflow.scope().span().contains(position)
784        {
785            return find_scope(&workflow.scopes, position);
786        }
787
788        // Search for a task that might contain the position
789        let task = match self
790            .data
791            .tasks
792            .binary_search_by_key(&position, |_, t| t.scope().span().start())
793        {
794            Ok(index) => &self.data.tasks[index],
795            Err(index) => {
796                // This indicates that we couldn't find a match and the match would go _before_
797                // the first task, so there is no containing task.
798                if index == 0 {
799                    return None;
800                }
801
802                &self.data.tasks[index - 1]
803            }
804        };
805
806        if task.scope().span().contains(position) {
807            return find_scope(&task.scopes, position);
808        }
809
810        None
811    }
812
813    /// Determines if the document, or any documents transitively imported by
814    /// this document, has errors.
815    ///
816    /// Returns `true` if the document, or one of its transitive imports, has at
817    /// least one error diagnostic.
818    ///
819    /// Returns `false` if the document, and all of its transitive imports, have
820    /// no error diagnostics.
821    pub fn has_errors(&self) -> bool {
822        // Check this document for errors
823        if self.diagnostics().any(|d| d.severity() == Severity::Error) {
824            return true;
825        }
826
827        // Check every imported document for errors
828        for (_, ns) in self.namespaces() {
829            if ns.document.has_errors() {
830                return true;
831            }
832        }
833
834        false
835    }
836
837    /// Visits the document with a pre-order traversal using the provided
838    /// visitor to visit each element in the document.
839    pub fn visit<V: crate::Visitor>(&self, diagnostics: &mut crate::Diagnostics, visitor: &mut V) {
840        crate::visit(self, diagnostics, visitor)
841    }
842}