tree_sitter_stack_graphs/
lib.rs

1// -*- coding: utf-8 -*-
2// ------------------------------------------------------------------------------------------------
3// Copyright © 2021, stack-graphs authors.
4// Licensed under either of Apache License, Version 2.0, or MIT license, at your option.
5// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details.
6// ------------------------------------------------------------------------------------------------
7
8//! This crate lets you construct [stack graphs][] using tree-sitter's [graph construction DSL][].
9//! The graph DSL lets you construct arbitrary graph structures from the parsed syntax tree of a
10//! source file.  If you construct a graph using the vocabulary of attributes described below, then
11//! the result of executing the graph DSL will be a valid stack graph, which we can then use for
12//! name binding lookups.
13//!
14//! ## Prerequisites
15//!
16//! [stack graphs]: https://docs.rs/stack-graphs/*/
17//! [graph construction DSL]: https://docs.rs/tree-sitter-graph/*/
18//!
19//! To process a particular source language, you will first need a tree-sitter grammar for that
20//! language.  There are already tree-sitter grammars [available][] for many languages.  If you do
21//! not have a tree-sitter grammar for your language, you will need to create that first.  (Check
22//! out the tree-sitter [discussion forum][] if you have questions or need pointers on how to do
23//! that.)
24//!
25//! [available]: https://tree-sitter.github.io/tree-sitter/#available-parsers
26//! [discussion forum]: https://github.com/tree-sitter/tree-sitter/discussions
27//!
28//! You will then need to create _stack graph construction rules_ for your language.  These rules
29//! are implemented using tree-sitter's [graph construction DSL][].  They define the particular
30//! stack graph nodes and edges that should be created for each part of the parsed syntax tree of a
31//! source file.
32//!
33//! ## Graph DSL vocabulary
34//!
35//! **Please note**: This documentation assumes you are already familiar with stack graphs, and how
36//! to use different stack graph node types, and the connectivity between nodes, to implement the
37//! name binding semantics of your language.  We assume that you know what kind of stack graph you
38//! want to produce; this documentation focuses only on the mechanics of _how_ to create that stack
39//! graph content.
40//!
41//! As mentioned above, your stack graph construction rules should create stack graph nodes and
42//! edges from the parsed content of a source file.  You will use TSG [stanzas][] to match on
43//! different parts of the parsed syntax tree, and create stack graph content for each match.
44//!
45//! ### Creating stack graph nodes
46//!
47//! To create a stack graph node for each identifier in a Python file, you could use the following
48//! TSG stanza:
49//!
50//! ``` skip
51//! (identifier) {
52//!   node new_node
53//! }
54//! ```
55//!
56//! (Here, `node` is a TSG statement that creates a new node, and `new_node` is the name of a local
57//! variable that the new node is assigned to, letting you refer to the new node in the rest of the
58//! stanza.)
59//!
60//! [stanzas]: https://docs.rs/tree-sitter-graph/*/tree_sitter_graph/reference/index.html#high-level-structure
61//!
62//! By default, this new node will be a _scope node_.  If you need to create a different kind of stack
63//! graph node, set the `type` attribute on the new node:
64//!
65//! ``` skip
66//! (identifier) {
67//!   node new_node
68//!   attr (new_node) type = "push_symbol"
69//! }
70//! ```
71//!
72//! The valid `type` values are:
73//!
74//! - `drop_scopes`: a _drop scopes_ node
75//! - `pop_symbol`: a _pop symbol_ node
76//! - `pop_scoped_symbol`: a _pop scoped symbol_ node
77//! - `push_symbol`: a _push symbol_ node
78//! - `push_scoped_symbol`: a _push scoped symbol_ node
79//! - `scope`: a _scope_ node
80//!
81//! A node without an explicit `type` attribute is assumed to be of type `scope`.
82//!
83//! Certain node types — `pop_symbol`, `pop_scoped_symbol`, `push_symbol` and `push_scoped_symbol` —
84//! also require you to provide a `symbol` attribute.  Its value must be a string, but will typically
85//! come from the content of a parsed syntax node using the [`source-text`][] function and a syntax
86//! capture:
87//!
88//! [`source-text`]: https://docs.rs/tree-sitter-graph/*/tree_sitter_graph/reference/functions/index.html#source-text
89//!
90//! ``` skip
91//! (identifier) @id {
92//!   node new_node
93//!   attr (new_node) type = "push_symbol", symbol = (source-text @id)
94//! }
95//! ```
96//!
97//! Node types `pop_symbol` and `pop_scoped_symbol` allow an optional `is_definition` attribute, which
98//! marks that node as a proper definition.  Node types `push_symbol` and `push_scoped_symbol` allow
99//! an optional `is_reference` attribute, which marks the node as a proper reference.  When `is_definition`
100//! or `is_reference` are set, the `source_node` attribute is required.
101//!
102//! ``` skip
103//! (identifier) @id {
104//!   node new_node
105//!   attr (new_node) type = "push_symbol", symbol = (source-text @id), is_reference, source_node = @id
106//! }
107//! ```
108//!
109//! A _push scoped symbol_ node requires a `scope` attribute.  Its value must be a reference to an `exported`
110//! node that you've already created. (This is the exported scope node that will be pushed onto the scope
111//! stack.)  For instance:
112//!
113//! ``` skip
114//! (identifier) @id {
115//!   node new_exported_scope_node
116//!   attr (new_exported_scope_node) is_exported
117//!   node new_push_scoped_symbol_node
118//!   attr (new_push_scoped_symbol_node)
119//!     type = "push_scoped_symbol",
120//!     symbol = (source-text @id),
121//!     scope = new_exported_scope_node
122//! }
123//! ```
124//!
125//! Nodes of type `scope` allow an optional `is_exported` attribute, that is required to use the scope
126//! in a `push_scoped_symbol` node.
127//!
128//!
129//! ### Annotating nodes with location information
130//!
131//! You can annotate any stack graph node that you create with location information, identifying
132//! the portion of the source file that the node "belongs to".  This is _required_ for definition
133//! and reference nodes, since the location information determines which parts of the source file
134//! the user can _click on_, and the _destination_ of any code navigation queries the user makes.
135//! To do this, add a `source_node` attribute, whose value is a syntax node capture:
136//!
137//! ``` skip
138//! (function_definition name: (identifier) @id) @func {
139//!   node def
140//!   attr (def) type = "pop_symbol", symbol = (source-text @id), source_node = @func, is_definition
141//! }
142//! ```
143//!
144//! Note how in this example, we use a different syntax node for the _target_ of the definition
145//! (the entirety of the function definition) and for the _name_ of the definition (the content of
146//! the function's `name`).
147//!
148//! Adding the `empty_source_span` attribute will use an empty source span located at the start of the
149//! span of the `source_node`. This can be useful when a proper reference or definition is desired,
150//! and thus `source_node` is required, but the span of the available source node is too large. For
151//! example, a module definition which is located at the start of the program, but does span the
152//! whole program:
153//!
154//! ``` skip
155//! (program)@prog {
156//!   ; ...
157//!   node mod_def
158//!   attr mod_def type = "pop_symbol", symbol = mod_name, is_definition, source_node = @prog, empty_source_span
159//!   ; ...
160//! }
161//! ```
162//!
163//! ### Annotating nodes with syntax type information
164//!
165//! You can annotate any stack graph node with information about its syntax type. To do this, add a `syntax_type`
166//! attribute, whose value is a string indicating the syntax type.
167//!
168//! ``` skip
169//! (function_definition name: (identifier) @id) @func {
170//!   node def
171//!   ; ...
172//!   attr (def) syntax_type = "function"
173//! }
174//! ```
175//!
176//! ### Annotating definitions with definiens information
177//!
178//! You cannot annotate definitions with a definiens, which is the thing the definition covers. For example, for
179//! a function definition, the definiens would be the function body. To do this, add a `definiens_node` attribute,
180//! whose value is a syntax node that spans the definiens.
181//!
182//! ``` skip
183//! (function_definition name: (identifier) @id body: (_) @body) @func {
184//!   node def
185//!   ; ...
186//!   attr (def) definiens_node = @body
187//! }
188//! ```
189//!
190//! Definiens are optional and setting them to `#null` explicitly is allowed.
191//!
192//! ### Connecting stack graph nodes with edges
193//!
194//! To connect two stack graph nodes, use the `edge` statement to add an edge between them:
195//!
196//! ``` skip
197//! (function_definition name: (identifier) @id) @func {
198//!   node def
199//!   attr (def) type = "pop_symbol", symbol = (source-text @id), source_node = @func, is_definition
200//!   node body
201//!   edge def -> body
202//! }
203//! ```
204//!
205//! To implement shadowing (which determines which definitions are selected when multiple are available),
206//! you can add a `precedence` attribute to each edge to indicate which paths are prioritized:
207//!
208//! ``` skip
209//! (function_definition name: (identifier) @id) @func {
210//!   node def
211//!   attr (def) type = "pop_symbol", symbol = (source-text @id), source_node = @func, is_definition
212//!   node body
213//!   edge def -> body
214//!   attr (def -> body) precedence = 1
215//! }
216//! ```
217//!
218//! (If you don't specify a `precedence`, the default is 0.)
219//!
220//! ### Referring to the singleton nodes
221//!
222//! The _root node_ and _jump to scope node_ are singleton nodes that always exist for all stack
223//! graphs.  You can refer to them using the `ROOT_NODE` and `JUMP_TO_SCOPE_NODE` global variables:
224//!
225//! ``` skip
226//! global ROOT_NODE
227//!
228//! (function_definition name: (identifier) @id) @func {
229//!   node def
230//!   attr (def) type = "pop_symbol", symbol = (source-text @id), source_node = @func, is_definition
231//!   edge ROOT_NODE -> def
232//! }
233//! ```
234//!
235//! ### Attaching debug information to nodes
236//!
237//! It is possible to attach extra information to nodes for debugging purposes.  This is done by adding
238//! `debug_*` attributes to nodes.  Each attribute defines a debug entry, with the key derived from the
239//! attribute name, and the value the string representation of the attribute value.  For example, mark
240//! a scope node with a kind as follows:
241//!
242//! ``` skip
243//! (function_definition name: (identifier) @id) @func {
244//!   ; ...
245//!   node param_scope
246//!   attr (param_scope) debug_kind = "param_scope"
247//!   ; ...
248//! }
249//! ```
250//!
251//! ### Working with paths
252//!
253//! Built-in path functions are available to compute symbols that depend on path information, such as
254//! module names or imports. The path of the file is provided in the global variable `FILE_PATH`.
255//!
256//! The following path functions are available:
257//! - `path-dir`: get the path consisting of all but the last component of the argument path, or `#null` if it ends in root
258//! - `path-fileext`: get the file extension, i.e. everything after the final `.` of the file name of the argument path, or `#null` if it has extension
259//! - `path-filename`: get the last component of the argument path, or `#null` if it has no final component
260//! - `path-filestem`: get the file stem of the argument path, i.e., everything before the extension, or `#null` if it has no file name
261//! - `path-join`: join all argument paths together
262//! - `path-normalize`: normalize the argument path by eliminating `.` and `..` components where possible
263//! - `path-split`: split the argument path into a list of its components
264//!
265//! The following example computes a module name from a file path:
266//!
267//! ``` skip
268//! global FILE_PATH
269//!
270//! (program)@prog {
271//!   ; ...
272//!   let dir = (path-dir FILE_PATH)
273//!   let stem = (path-filestem FILE_PATH)
274//!   let mod_name = (path-join dir stem)
275//!   node mod_def
276//!   attr mod_def type = "pop_symbol", symbol = mod_name, is_definition, source_node = @prog
277//!   ; ...
278//! }
279//! ```
280//!
281//! The following example resolves an import relative to the current file:
282//!
283//! ``` skip
284//! global FILE_PATH
285//!
286//! (import name:(_)@name)@import {
287//!   ; ...
288//!   let dir = (path-dir FILE_PATH)
289//!   let mod_name = (path-normalize (path-join dir (Source-text @name)))
290//!   node mod_def
291//!   attr mod_def type = "pop_symbol", symbol = mod_name, is_definition, source_node = @prog
292//!   ; ...
293//! }
294//! ```
295//!
296//! ## Using this crate from Rust
297//!
298//! If you need very fine-grained control over how to use the resulting stack graphs, you can
299//! construct and operate on [`StackGraph`][stack_graphs::graph::StackGraph] instances directly
300//! from Rust code.  You will need Rust bindings for the tree-sitter grammar for your source
301//! language — for instance, [`tree-sitter-python`][].  Grammar Rust bindings provide a global
302//! symbol [`language`][] that you will need.  For this example we assume the source of the stack
303//! graph rules is defined in a constant `STACK_GRAPH_RULES`.
304//!
305//! [`tree-sitter-python`]: https://docs.rs/tree-sitter-python/*/
306//! [`language`]: https://docs.rs/tree-sitter-python/*/tree_sitter_python/fn.language.html
307//!
308//! Once you have those, and the contents of the source file you want to analyze, you can construct
309//! a stack graph as follows:
310//!
311//! ```
312//! # use stack_graphs::graph::StackGraph;
313//! # use tree_sitter_graph::Variables;
314//! # use tree_sitter_stack_graphs::StackGraphLanguage;
315//! # use tree_sitter_stack_graphs::NoCancellation;
316//! #
317//! # // This documentation test is not meant to test Python's actual stack graph
318//! # // construction rules.  An empty TSG file is perfectly valid (it just won't produce any stack
319//! # // graph content).  This minimizes the amount of work that we do when running `cargo test`.
320//! # static STACK_GRAPH_RULES: &str = "";
321//! #
322//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
323//! let python_source = r#"
324//!   import sys
325//!   print(sys.path)
326//! "#;
327//! let grammar = tree_sitter_python::LANGUAGE.into();
328//! let tsg_source = STACK_GRAPH_RULES;
329//! let mut language = StackGraphLanguage::from_str(grammar, tsg_source)?;
330//! let mut stack_graph = StackGraph::new();
331//! let file_handle = stack_graph.get_or_create_file("test.py");
332//! let globals = Variables::new();
333//! language.build_stack_graph_into(&mut stack_graph, file_handle, python_source, &globals, &NoCancellation)?;
334//! # Ok(())
335//! # }
336//! ```
337
338use controlled_option::ControlledOption;
339use lsp_positions::SpanCalculator;
340use once_cell::sync::Lazy;
341use stack_graphs::arena::Handle;
342use stack_graphs::graph::File;
343use stack_graphs::graph::Node;
344use stack_graphs::graph::NodeID;
345use stack_graphs::graph::StackGraph;
346use std::borrow::Cow;
347use std::collections::HashMap;
348use std::collections::HashSet;
349use std::mem::transmute;
350use std::ops::BitOr;
351use std::path::Path;
352use std::path::PathBuf;
353use std::sync::atomic::AtomicBool;
354use std::sync::atomic::Ordering;
355use std::sync::Arc;
356use std::time::Duration;
357use std::time::Instant;
358use thiserror::Error;
359use tree_sitter::Parser;
360use tree_sitter_graph::functions::Functions;
361use tree_sitter_graph::graph::Edge;
362use tree_sitter_graph::graph::Graph;
363use tree_sitter_graph::graph::GraphNode;
364use tree_sitter_graph::graph::GraphNodeRef;
365use tree_sitter_graph::graph::Value;
366use tree_sitter_graph::parse_error::ParseError;
367use tree_sitter_graph::parse_error::TreeWithParseErrorVec;
368use tree_sitter_graph::ExecutionConfig;
369use util::DisplayParseErrorsPretty;
370use util::TreeSitterCancellationFlag;
371
372#[cfg(feature = "cli")]
373pub mod ci;
374#[cfg(feature = "cli")]
375pub mod cli;
376pub mod functions;
377pub mod loader;
378pub mod test;
379mod util;
380
381pub use tree_sitter_graph::VariableError;
382pub use tree_sitter_graph::Variables;
383
384pub(self) const MAX_PARSE_ERRORS: usize = 5;
385
386// Node type values
387static DROP_SCOPES_TYPE: &'static str = "drop_scopes";
388static POP_SCOPED_SYMBOL_TYPE: &'static str = "pop_scoped_symbol";
389static POP_SYMBOL_TYPE: &'static str = "pop_symbol";
390static PUSH_SCOPED_SYMBOL_TYPE: &'static str = "push_scoped_symbol";
391static PUSH_SYMBOL_TYPE: &'static str = "push_symbol";
392static SCOPE_TYPE: &'static str = "scope";
393
394// Node attribute names
395static DEBUG_ATTR_PREFIX: &'static str = "debug_";
396static DEFINIENS_NODE_ATTR: &'static str = "definiens_node";
397static EMPTY_SOURCE_SPAN_ATTR: &'static str = "empty_source_span";
398static IS_DEFINITION_ATTR: &'static str = "is_definition";
399static IS_ENDPOINT_ATTR: &'static str = "is_endpoint";
400static IS_EXPORTED_ATTR: &'static str = "is_exported";
401static IS_REFERENCE_ATTR: &'static str = "is_reference";
402static SCOPE_ATTR: &'static str = "scope";
403static SOURCE_NODE_ATTR: &'static str = "source_node";
404static SYMBOL_ATTR: &'static str = "symbol";
405static SYNTAX_TYPE_ATTR: &'static str = "syntax_type";
406static TYPE_ATTR: &'static str = "type";
407
408// Expected attributes per node type
409static POP_SCOPED_SYMBOL_ATTRS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
410    HashSet::from([
411        TYPE_ATTR,
412        SYMBOL_ATTR,
413        IS_DEFINITION_ATTR,
414        DEFINIENS_NODE_ATTR,
415        SYNTAX_TYPE_ATTR,
416    ])
417});
418static POP_SYMBOL_ATTRS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
419    HashSet::from([
420        TYPE_ATTR,
421        SYMBOL_ATTR,
422        IS_DEFINITION_ATTR,
423        DEFINIENS_NODE_ATTR,
424        SYNTAX_TYPE_ATTR,
425    ])
426});
427static PUSH_SCOPED_SYMBOL_ATTRS: Lazy<HashSet<&'static str>> =
428    Lazy::new(|| HashSet::from([TYPE_ATTR, SYMBOL_ATTR, SCOPE_ATTR, IS_REFERENCE_ATTR]));
429static PUSH_SYMBOL_ATTRS: Lazy<HashSet<&'static str>> =
430    Lazy::new(|| HashSet::from([TYPE_ATTR, SYMBOL_ATTR, IS_REFERENCE_ATTR]));
431static SCOPE_ATTRS: Lazy<HashSet<&'static str>> =
432    Lazy::new(|| HashSet::from([TYPE_ATTR, IS_EXPORTED_ATTR, IS_ENDPOINT_ATTR]));
433
434// Edge attribute names
435static PRECEDENCE_ATTR: &'static str = "precedence";
436
437// Global variables
438/// Name of the variable used to pass the root node.
439pub const ROOT_NODE_VAR: &'static str = "ROOT_NODE";
440/// Name of the variable used to pass the jump-to-scope node.
441pub const JUMP_TO_SCOPE_NODE_VAR: &'static str = "JUMP_TO_SCOPE_NODE";
442/// Name of the variable used to pass the file path.
443/// If a root path is given, it should be a descendant of the root path.
444pub const FILE_PATH_VAR: &'static str = "FILE_PATH";
445/// Name of the variable used to pass the root path.
446/// If given, should be an ancestor of the file path.
447pub const ROOT_PATH_VAR: &'static str = "ROOT_PATH";
448
449/// Holds information about how to construct stack graphs for a particular language.
450pub struct StackGraphLanguage {
451    language: tree_sitter::Language,
452    tsg: tree_sitter_graph::ast::File,
453    tsg_path: PathBuf,
454    tsg_source: std::borrow::Cow<'static, str>,
455    functions: Functions,
456}
457
458impl StackGraphLanguage {
459    /// Creates a new stack graph language for the given language and
460    /// TSG stack graph construction rules.
461    pub fn new(
462        language: tree_sitter::Language,
463        tsg: tree_sitter_graph::ast::File,
464    ) -> StackGraphLanguage {
465        debug_assert_eq!(language, tsg.language);
466        StackGraphLanguage {
467            language,
468            tsg,
469            tsg_path: PathBuf::from("<tsg>"),
470            tsg_source: Cow::from(String::new()),
471            functions: Self::default_functions(),
472        }
473    }
474
475    /// Creates a new stack graph language for the given language, loading the
476    /// TSG stack graph construction rules from a string. Keeps the source, which
477    /// can later be used for [`BuildError::display_pretty`][].
478    pub fn from_str(
479        language: tree_sitter::Language,
480        tsg_source: &str,
481    ) -> Result<StackGraphLanguage, LanguageError> {
482        let tsg = tree_sitter_graph::ast::File::from_str(language.clone(), tsg_source)?;
483        Ok(StackGraphLanguage {
484            language,
485            tsg,
486            tsg_path: PathBuf::from("<missing tsg path>"),
487            tsg_source: Cow::from(tsg_source.to_string()),
488            functions: Self::default_functions(),
489        })
490    }
491
492    /// Creates a new stack graph language for the given language, loading the TSG
493    /// stack graph construction rules from the given source. The path is purely for
494    /// informational purposes, and is not accessed. The source and path are kept,
495    /// e.g. to use for [`BuildError::display_pretty`][].
496    pub fn from_source(
497        language: tree_sitter::Language,
498        tsg_path: PathBuf,
499        tsg_source: &str,
500    ) -> Result<StackGraphLanguage, LanguageError> {
501        let mut sgl = Self::from_str(language, tsg_source)?;
502        sgl.tsg_path = tsg_path;
503        Ok(sgl)
504    }
505
506    pub fn set_tsg_info(&mut self, path: PathBuf, source: Cow<'static, str>) {
507        self.tsg_path = path;
508        self.tsg_source = source;
509    }
510
511    fn default_functions() -> tree_sitter_graph::functions::Functions {
512        let mut functions = tree_sitter_graph::functions::Functions::stdlib();
513        crate::functions::add_path_functions(&mut functions);
514        functions
515    }
516
517    pub fn functions_mut(&mut self) -> &mut tree_sitter_graph::functions::Functions {
518        &mut self.functions
519    }
520
521    pub fn language(&self) -> &tree_sitter::Language {
522        &self.language
523    }
524
525    /// Returns the original TSG path, if it was provided at construction or set with
526    /// [`set_tsg_info`][]. Can be used as input for [`BuildError::display_pretty`][].
527    pub fn tsg_path(&self) -> &Path {
528        &self.tsg_path
529    }
530
531    /// Returns the original TSG source, if it was provided at construction or set with
532    /// [`set_tsg_info`][]. Can be used as input for [`BuildError::display_pretty`][].
533    pub fn tsg_source(&self) -> &Cow<'static, str> {
534        &self.tsg_source
535    }
536}
537
538/// An error that can occur while loading in the TSG stack graph construction rules for a language
539#[derive(Debug, Error)]
540pub enum LanguageError {
541    #[error(transparent)]
542    ParseError(#[from] tree_sitter_graph::ParseError),
543}
544
545impl LanguageError {
546    pub fn display_pretty<'a>(
547        &'a self,
548        path: &'a Path,
549        source: &'a str,
550    ) -> impl std::fmt::Display + 'a {
551        match self {
552            Self::ParseError(err) => err.display_pretty(path, source),
553        }
554    }
555}
556
557impl StackGraphLanguage {
558    /// Executes the graph construction rules for this language against a source file, creating new
559    /// nodes and edges in `stack_graph`.  Any new nodes that we create will belong to `file`.
560    /// (The source file must be implemented in this language, otherwise you'll probably get a
561    /// parse error.)
562    pub fn build_stack_graph_into<'a>(
563        &'a self,
564        stack_graph: &'a mut StackGraph,
565        file: Handle<File>,
566        source: &'a str,
567        globals: &'a Variables<'a>,
568        cancellation_flag: &'a dyn CancellationFlag,
569    ) -> Result<(), BuildError> {
570        self.builder_into_stack_graph(stack_graph, file, source)
571            .build(globals, cancellation_flag)
572    }
573
574    /// Create a builder that will execute the graph construction rules for this language against
575    /// a source file, creating new nodes and edges in `stack_graph`.  Any new nodes created during
576    /// execution will belong to `file`.  (The source file must be implemented in this language,
577    /// otherwise you'll probably get a parse error.)
578    pub fn builder_into_stack_graph<'a>(
579        &'a self,
580        stack_graph: &'a mut StackGraph,
581        file: Handle<File>,
582        source: &'a str,
583    ) -> Builder<'a> {
584        Builder::new(self, stack_graph, file, source)
585    }
586}
587
588pub struct Builder<'a> {
589    sgl: &'a StackGraphLanguage,
590    stack_graph: &'a mut StackGraph,
591    file: Handle<File>,
592    source: &'a str,
593    graph: Graph<'a>,
594    remapped_nodes: HashMap<usize, NodeID>,
595    injected_node_count: usize,
596    span_calculator: SpanCalculator<'a>,
597}
598
599impl<'a> Builder<'a> {
600    fn new(
601        sgl: &'a StackGraphLanguage,
602        stack_graph: &'a mut StackGraph,
603        file: Handle<File>,
604        source: &'a str,
605    ) -> Self {
606        let span_calculator = SpanCalculator::new(source);
607        Builder {
608            sgl,
609            stack_graph,
610            file,
611            source,
612            graph: Graph::new(),
613            remapped_nodes: HashMap::new(),
614            injected_node_count: 0,
615            span_calculator,
616        }
617    }
618
619    /// Executes this builder.
620    pub fn build(
621        mut self,
622        globals: &'a Variables<'a>,
623        cancellation_flag: &dyn CancellationFlag,
624    ) -> Result<(), BuildError> {
625        let tree = {
626            let mut parser = Parser::new();
627            parser.set_language(&self.sgl.language)?;
628            let ts_cancellation_flag = TreeSitterCancellationFlag::from(cancellation_flag);
629            // The parser.set_cancellation_flag` is unsafe, because it does not tie the
630            // lifetime of the parser to the lifetime of the cancellation flag in any way.
631            // To make it more obvious that the parser does not outlive the cancellation flag,
632            // it is put into its own block here, instead of extending to the end of the method.
633            unsafe { parser.set_cancellation_flag(Some(ts_cancellation_flag.as_ref())) };
634            parser
635                .parse(self.source, None)
636                .ok_or(BuildError::ParseError)?
637        };
638        let parse_errors = ParseError::into_all(tree);
639        if parse_errors.errors().len() > 0 {
640            return Err(BuildError::ParseErrors(parse_errors));
641        }
642        let tree = parse_errors.into_tree();
643
644        let mut globals = Variables::nested(globals);
645
646        let root_node = self.inject_node(NodeID::root());
647        globals
648            .add(ROOT_NODE_VAR.into(), root_node.into())
649            .unwrap_or_default();
650
651        let jump_to_scope_node = self.inject_node(NodeID::jump_to());
652        globals
653            .add(JUMP_TO_SCOPE_NODE_VAR.into(), jump_to_scope_node.into())
654            .expect("Failed to set JUMP_TO_SCOPE_NODE");
655
656        if globals.get(&FILE_PATH_VAR.into()).is_none() {
657            let file_name = self.stack_graph[self.file].to_string();
658            globals
659                .add(FILE_PATH_VAR.into(), file_name.into())
660                .expect("Failed to set FILE_PATH");
661        }
662
663        let mut config = ExecutionConfig::new(&self.sgl.functions, &globals)
664            .lazy(true)
665            .debug_attributes(
666                [DEBUG_ATTR_PREFIX, "tsg_location"].concat().as_str().into(),
667                [DEBUG_ATTR_PREFIX, "tsg_variable"].concat().as_str().into(),
668                [DEBUG_ATTR_PREFIX, "tsg_match_node"]
669                    .concat()
670                    .as_str()
671                    .into(),
672            );
673
674        // The execute_into() method requires that the reference to the tree matches the lifetime
675        // parameter 'a of the Graph, because the Graph can hold references to the Tree. In this Builder,
676        // the Graph is created _before_ the Tree, so that it can be prepopulated with nodes. Because of
677        // that, the borrow checker complains that the Tree only lives as long as this method, not as long
678        // as the lifetime parameter 'a. Here we transmute the Tree reference to give it the required 'a
679        // lifetime, which is safe because:
680        // (1) this method takes ownership of the Builder; and
681        // (2) it returns no values connected to 'a.
682        // These together guarantee that no values connected to the lifetime 'a outlive the Tree.
683        let tree: &'a tree_sitter::Tree = unsafe { transmute(&tree) };
684        self.sgl.tsg.execute_into(
685            &mut self.graph,
686            tree,
687            self.source,
688            &mut config,
689            &(cancellation_flag as &dyn CancellationFlag),
690        )?;
691
692        self.load(cancellation_flag)
693    }
694
695    /// Create a graph node to represent the stack graph node. It is the callers responsibility to
696    /// ensure the stack graph node exists.
697    pub fn inject_node(&mut self, id: NodeID) -> GraphNodeRef {
698        let node = self.graph.add_graph_node();
699        self.remapped_nodes.insert(node.index(), id);
700        self.injected_node_count += 1;
701        node
702    }
703}
704
705/// Trait to signal that the execution is cancelled
706pub trait CancellationFlag: Sync {
707    fn check(&self, at: &'static str) -> Result<(), CancellationError>;
708}
709
710#[derive(Clone, Debug, Error)]
711#[error("Cancelled at \"{0}\"")]
712pub struct CancellationError(pub &'static str);
713
714impl stack_graphs::CancellationFlag for &dyn CancellationFlag {
715    fn check(&self, at: &'static str) -> Result<(), stack_graphs::CancellationError> {
716        CancellationFlag::check(*self, at).map_err(|err| stack_graphs::CancellationError(err.0))
717    }
718}
719
720impl tree_sitter_graph::CancellationFlag for &dyn CancellationFlag {
721    fn check(&self, at: &'static str) -> Result<(), tree_sitter_graph::CancellationError> {
722        CancellationFlag::check(*self, at)
723            .map_err(|err| tree_sitter_graph::CancellationError(err.0))
724    }
725}
726
727impl<'a> BitOr for &'a dyn CancellationFlag {
728    type Output = OrCancellationFlag<'a>;
729    fn bitor(self, rhs: Self) -> Self::Output {
730        OrCancellationFlag(self, rhs)
731    }
732}
733
734pub struct OrCancellationFlag<'a>(&'a dyn CancellationFlag, &'a dyn CancellationFlag);
735
736impl CancellationFlag for OrCancellationFlag<'_> {
737    fn check(&self, at: &'static str) -> Result<(), CancellationError> {
738        self.0.check(at)?;
739        self.1.check(at)?;
740        Ok(())
741    }
742}
743
744pub struct NoCancellation;
745
746impl CancellationFlag for NoCancellation {
747    fn check(&self, _at: &'static str) -> Result<(), CancellationError> {
748        Ok(())
749    }
750}
751
752pub struct CancelAfterDuration {
753    start: Instant,
754    limit: Duration,
755}
756
757impl CancelAfterDuration {
758    pub fn new(limit: Duration) -> Self {
759        Self {
760            start: Instant::now(),
761            limit,
762        }
763    }
764
765    pub fn from_option(limit: Option<Duration>) -> Box<dyn CancellationFlag> {
766        match limit {
767            Some(limit) => Box::new(Self::new(limit)),
768            None => Box::new(NoCancellation),
769        }
770    }
771}
772
773impl CancellationFlag for CancelAfterDuration {
774    fn check(&self, at: &'static str) -> Result<(), CancellationError> {
775        if self.start.elapsed().ge(&self.limit) {
776            return Err(CancellationError(at));
777        }
778        Ok(())
779    }
780}
781
782#[derive(Clone)]
783pub struct AtomicCancellationFlag {
784    flag: Arc<AtomicBool>,
785}
786
787impl AtomicCancellationFlag {
788    pub fn new() -> Self {
789        Self {
790            flag: Arc::new(AtomicBool::new(false)),
791        }
792    }
793
794    pub fn cancel(&self) {
795        self.flag.store(true, Ordering::Relaxed)
796    }
797}
798
799impl CancellationFlag for AtomicCancellationFlag {
800    fn check(&self, at: &'static str) -> Result<(), CancellationError> {
801        if self.flag.load(Ordering::Relaxed) {
802            return Err(CancellationError(at));
803        }
804        Ok(())
805    }
806}
807
808/// An error that can occur while loading a stack graph from a TSG file
809#[derive(Debug, Error)]
810pub enum BuildError {
811    #[error("{0}")]
812    Cancelled(&'static str),
813    #[error("Missing ‘type’ attribute on graph node")]
814    MissingNodeType(GraphNodeRef),
815    #[error("Missing ‘symbol’ attribute on graph node")]
816    MissingSymbol(GraphNodeRef),
817    #[error("Missing ‘scope’ attribute on graph node")]
818    MissingScope(GraphNodeRef),
819    #[error("Unknown ‘{0}’ flag type {1}")]
820    UnknownFlagType(String, String),
821    #[error("Unknown node type {0}")]
822    UnknownNodeType(String),
823    #[error("Unknown symbol type {0}")]
824    UnknownSymbolType(String),
825    #[error(transparent)]
826    ExecutionError(tree_sitter_graph::ExecutionError),
827    #[error("Error parsing source")]
828    ParseError,
829    #[error("Error parsing source")]
830    ParseErrors(TreeWithParseErrorVec),
831    #[error("Error converting shorthand ‘{0}’ on {1} with value {2}")]
832    ConversionError(String, String, String),
833    #[error(transparent)]
834    LanguageError(#[from] tree_sitter::LanguageError),
835    #[error("Expected exported symbol scope in {0}, got {1}")]
836    SymbolScopeError(String, String),
837}
838
839impl From<stack_graphs::CancellationError> for BuildError {
840    fn from(value: stack_graphs::CancellationError) -> Self {
841        Self::Cancelled(value.0)
842    }
843}
844
845impl From<tree_sitter_graph::ExecutionError> for BuildError {
846    fn from(value: tree_sitter_graph::ExecutionError) -> Self {
847        match value {
848            tree_sitter_graph::ExecutionError::Cancelled(err) => Self::Cancelled(err.0),
849            err => Self::ExecutionError(err),
850        }
851    }
852}
853
854impl BuildError {
855    pub fn display_pretty<'a>(
856        &'a self,
857        source_path: &'a Path,
858        source: &'a str,
859        tsg_path: &'a Path,
860        tsg: &'a str,
861    ) -> impl std::fmt::Display + 'a {
862        DisplayBuildErrorPretty {
863            error: self,
864            source_path,
865            source,
866            tsg_path,
867            tsg,
868        }
869    }
870}
871
872struct DisplayBuildErrorPretty<'a> {
873    error: &'a BuildError,
874    source_path: &'a Path,
875    source: &'a str,
876    tsg_path: &'a Path,
877    tsg: &'a str,
878}
879
880impl std::fmt::Display for DisplayBuildErrorPretty<'_> {
881    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
882        match self.error {
883            BuildError::ExecutionError(err) => write!(
884                f,
885                "{}",
886                err.display_pretty(self.source_path, self.source, self.tsg_path, self.tsg)
887            ),
888            BuildError::ParseErrors(parse_errors) => write!(
889                f,
890                "{}",
891                DisplayParseErrorsPretty {
892                    parse_errors,
893                    path: self.source_path,
894                    source: self.source,
895                    max_errors: crate::MAX_PARSE_ERRORS,
896                }
897            ),
898            err => err.fmt(f),
899        }
900    }
901}
902
903impl<'a> Builder<'a> {
904    fn load(mut self, cancellation_flag: &dyn CancellationFlag) -> Result<(), BuildError> {
905        let cancellation_flag: &dyn stack_graphs::CancellationFlag = &cancellation_flag;
906
907        // By default graph ids are used for stack graph local_ids. A remapping is computed
908        // for local_ids that already exist in the graph---all other graph ids are mapped to
909        // the same local_id. See [`self.node_id_for_index`] for more details.
910        let mut next_local_id = (self.graph.node_count() - self.injected_node_count) as u32;
911        for node in self.stack_graph.nodes_for_file(self.file) {
912            let local_id = self.stack_graph[node].id().local_id();
913            let index = (local_id as usize) + self.injected_node_count;
914            // find next available local_id for which no stack graph node exists yet
915            while self
916                .stack_graph
917                .node_for_id(NodeID::new_in_file(self.file, next_local_id))
918                .is_some()
919            {
920                next_local_id += 1;
921            }
922            // remap graph node index to the available stack graph node local_id
923            self.remapped_nodes
924                .insert(index, NodeID::new_in_file(self.file, next_local_id))
925                .map(|_| panic!("index already remapped"));
926        }
927
928        // First create a stack graph node for each TSG node.  (The skip(...) is because the first
929        // DSL nodes that we create are the proxies for the injected stack graph nodes.)
930        for node_ref in self.graph.iter_nodes().skip(self.injected_node_count) {
931            cancellation_flag.check("loading graph nodes")?;
932            let node_type = self.get_node_type(node_ref)?;
933            let handle = match node_type {
934                NodeType::DropScopes => self.load_drop_scopes(node_ref),
935                NodeType::PopScopedSymbol => self.load_pop_scoped_symbol(node_ref)?,
936                NodeType::PopSymbol => self.load_pop_symbol(node_ref)?,
937                NodeType::PushScopedSymbol => self.load_push_scoped_symbol(node_ref)?,
938                NodeType::PushSymbol => self.load_push_symbol(node_ref)?,
939                NodeType::Scope => self.load_scope(node_ref)?,
940            };
941            self.load_source_info(node_ref, handle)?;
942            self.load_node_debug_info(node_ref, handle)?;
943        }
944
945        for node in self.stack_graph.nodes_for_file(self.file) {
946            self.verify_node(node)?;
947        }
948
949        // Then add stack graph edges for each TSG edge.  Note that we _don't_ skip(...) here because
950        // there might be outgoing nodes from the “root” node that we need to process.
951        // (Technically the caller could add outgoing nodes from “jump to scope” as well, but those
952        // are invalid according to the stack graph semantics and will never be followed.
953        for source_ref in self.graph.iter_nodes() {
954            let source = &self.graph[source_ref];
955            let source_node_id = self.node_id_for_graph_node(source_ref);
956            let source_handle = self.stack_graph.node_for_id(source_node_id).unwrap();
957            for (sink_ref, edge) in source.iter_edges() {
958                cancellation_flag.check("loading graph edges")?;
959                let precedence = match edge.attributes.get(PRECEDENCE_ATTR) {
960                    Some(precedence) => precedence.as_integer()? as i32,
961                    None => 0,
962                };
963                let sink_node_id = self.node_id_for_graph_node(sink_ref);
964                let sink_handle = self.stack_graph.node_for_id(sink_node_id).unwrap();
965                self.stack_graph
966                    .add_edge(source_handle, sink_handle, precedence);
967                Self::load_edge_debug_info(
968                    &mut self.stack_graph,
969                    source_handle,
970                    sink_handle,
971                    edge,
972                )?;
973            }
974        }
975
976        Ok(())
977    }
978
979    fn get_node_type(&self, node_ref: GraphNodeRef) -> Result<NodeType, BuildError> {
980        let node = &self.graph[node_ref];
981        let node_type = match node.attributes.get(TYPE_ATTR) {
982            Some(node_type) => node_type.as_str()?,
983            None => return Ok(NodeType::Scope),
984        };
985        if node_type == DROP_SCOPES_TYPE {
986            return Ok(NodeType::DropScopes);
987        } else if node_type == POP_SCOPED_SYMBOL_TYPE {
988            return Ok(NodeType::PopScopedSymbol);
989        } else if node_type == POP_SYMBOL_TYPE {
990            return Ok(NodeType::PopSymbol);
991        } else if node_type == PUSH_SCOPED_SYMBOL_TYPE {
992            return Ok(NodeType::PushScopedSymbol);
993        } else if node_type == PUSH_SYMBOL_TYPE {
994            return Ok(NodeType::PushSymbol);
995        } else if node_type == SCOPE_TYPE {
996            return Ok(NodeType::Scope);
997        } else {
998            return Err(BuildError::UnknownNodeType(format!("{}", node_type)));
999        }
1000    }
1001
1002    fn verify_node(&self, node: Handle<Node>) -> Result<(), BuildError> {
1003        if let Node::PushScopedSymbol(node) = &self.stack_graph[node] {
1004            let scope = &self.stack_graph[self.stack_graph.node_for_id(node.scope).unwrap()];
1005            if !scope.is_exported_scope() {
1006                return Err(BuildError::SymbolScopeError(
1007                    format!("{}", node.display(self.stack_graph)),
1008                    format!("{}", scope.display(self.stack_graph)),
1009                ));
1010            }
1011        }
1012        Ok(())
1013    }
1014}
1015
1016enum NodeType {
1017    DropScopes,
1018    PopSymbol,
1019    PopScopedSymbol,
1020    PushSymbol,
1021    PushScopedSymbol,
1022    Scope,
1023}
1024
1025impl<'a> Builder<'a> {
1026    /// Get the NodeID corresponding to a Graph node.
1027    ///
1028    /// By default, graph nodes get their index shifted by [`self.injected_node_count`] as their
1029    /// local_id, unless they have a corresponding entry in the [`self.remapped_nodes`] map. This
1030    /// is the case if:
1031    /// 1. The node was injected, in which case it is mapped to the NodeID of the injected node.
1032    /// 2. The node's default local_id clashes with a preexisting node, in which case it is mapped to
1033    ///    an available local_id beyond the range of default local_ids.
1034    fn node_id_for_graph_node(&self, node_ref: GraphNodeRef) -> NodeID {
1035        let index = node_ref.index();
1036        self.remapped_nodes.get(&index).map_or_else(
1037            || NodeID::new_in_file(self.file, (index - self.injected_node_count) as u32),
1038            |id| *id,
1039        )
1040    }
1041
1042    fn load_drop_scopes(&mut self, node_ref: GraphNodeRef) -> Handle<Node> {
1043        let id = self.node_id_for_graph_node(node_ref);
1044        self.stack_graph.add_drop_scopes_node(id).unwrap()
1045    }
1046
1047    fn load_pop_scoped_symbol(
1048        &mut self,
1049        node_ref: GraphNodeRef,
1050    ) -> Result<Handle<Node>, BuildError> {
1051        let node = &self.graph[node_ref];
1052        let symbol = match node.attributes.get(SYMBOL_ATTR) {
1053            Some(symbol) => self.load_symbol(symbol)?,
1054            None => return Err(BuildError::MissingSymbol(node_ref)),
1055        };
1056        let symbol = self.stack_graph.add_symbol(&symbol);
1057        let id = self.node_id_for_graph_node(node_ref);
1058        let is_definition = self.load_flag(node, IS_DEFINITION_ATTR)?;
1059        self.verify_attributes(node, POP_SCOPED_SYMBOL_TYPE, &POP_SCOPED_SYMBOL_ATTRS);
1060        let node_handle = self
1061            .stack_graph
1062            .add_pop_scoped_symbol_node(id, symbol, is_definition)
1063            .unwrap();
1064        if is_definition {
1065            self.load_definiens_info(node_ref, node_handle)?;
1066        }
1067        Ok(node_handle)
1068    }
1069
1070    fn load_pop_symbol(&mut self, node_ref: GraphNodeRef) -> Result<Handle<Node>, BuildError> {
1071        let node = &self.graph[node_ref];
1072        let symbol = match node.attributes.get(SYMBOL_ATTR) {
1073            Some(symbol) => self.load_symbol(symbol)?,
1074            None => return Err(BuildError::MissingSymbol(node_ref)),
1075        };
1076        let symbol = self.stack_graph.add_symbol(&symbol);
1077        let id = self.node_id_for_graph_node(node_ref);
1078        let is_definition = self.load_flag(node, IS_DEFINITION_ATTR)?;
1079        self.verify_attributes(node, POP_SYMBOL_TYPE, &POP_SYMBOL_ATTRS);
1080        let node_handle = self
1081            .stack_graph
1082            .add_pop_symbol_node(id, symbol, is_definition)
1083            .unwrap();
1084        if is_definition {
1085            self.load_definiens_info(node_ref, node_handle)?;
1086        }
1087        Ok(node_handle)
1088    }
1089
1090    fn load_push_scoped_symbol(
1091        &mut self,
1092        node_ref: GraphNodeRef,
1093    ) -> Result<Handle<Node>, BuildError> {
1094        let node = &self.graph[node_ref];
1095        let symbol = match node.attributes.get(SYMBOL_ATTR) {
1096            Some(symbol) => self.load_symbol(symbol)?,
1097            None => return Err(BuildError::MissingSymbol(node_ref)),
1098        };
1099        let symbol = self.stack_graph.add_symbol(&symbol);
1100        let id = self.node_id_for_graph_node(node_ref);
1101        let scope = match node.attributes.get(SCOPE_ATTR) {
1102            Some(scope) => self.node_id_for_graph_node(scope.as_graph_node_ref()?),
1103            None => return Err(BuildError::MissingScope(node_ref)),
1104        };
1105        let is_reference = self.load_flag(node, IS_REFERENCE_ATTR)?;
1106        self.verify_attributes(node, PUSH_SCOPED_SYMBOL_TYPE, &PUSH_SCOPED_SYMBOL_ATTRS);
1107        Ok(self
1108            .stack_graph
1109            .add_push_scoped_symbol_node(id, symbol, scope, is_reference)
1110            .unwrap())
1111    }
1112
1113    fn load_push_symbol(&mut self, node_ref: GraphNodeRef) -> Result<Handle<Node>, BuildError> {
1114        let node = &self.graph[node_ref];
1115        let symbol = match node.attributes.get(SYMBOL_ATTR) {
1116            Some(symbol) => self.load_symbol(symbol)?,
1117            None => return Err(BuildError::MissingSymbol(node_ref)),
1118        };
1119        let symbol = self.stack_graph.add_symbol(&symbol);
1120        let id = self.node_id_for_graph_node(node_ref);
1121        let is_reference = self.load_flag(node, IS_REFERENCE_ATTR)?;
1122        self.verify_attributes(node, PUSH_SYMBOL_TYPE, &PUSH_SYMBOL_ATTRS);
1123        Ok(self
1124            .stack_graph
1125            .add_push_symbol_node(id, symbol, is_reference)
1126            .unwrap())
1127    }
1128
1129    fn load_scope(&mut self, node_ref: GraphNodeRef) -> Result<Handle<Node>, BuildError> {
1130        let node = &self.graph[node_ref];
1131        let id = self.node_id_for_graph_node(node_ref);
1132        let is_exported =
1133            self.load_flag(node, IS_EXPORTED_ATTR)? || self.load_flag(node, IS_ENDPOINT_ATTR)?;
1134        self.verify_attributes(node, SCOPE_TYPE, &SCOPE_ATTRS);
1135        Ok(self.stack_graph.add_scope_node(id, is_exported).unwrap())
1136    }
1137
1138    fn load_symbol(&self, value: &Value) -> Result<String, BuildError> {
1139        match value {
1140            Value::Integer(i) => Ok(i.to_string()),
1141            Value::String(s) => Ok(s.clone()),
1142            _ => Err(BuildError::UnknownSymbolType(format!("{}", value))),
1143        }
1144    }
1145
1146    fn load_flag(&self, node: &GraphNode, attribute: &str) -> Result<bool, BuildError> {
1147        match node.attributes.get(attribute) {
1148            Some(value) => value.as_boolean().map_err(|_| {
1149                BuildError::UnknownFlagType(format!("{}", attribute), format!("{}", value))
1150            }),
1151            None => Ok(false),
1152        }
1153    }
1154
1155    fn load_source_info(
1156        &mut self,
1157        node_ref: GraphNodeRef,
1158        node_handle: Handle<Node>,
1159    ) -> Result<(), BuildError> {
1160        let node = &self.graph[node_ref];
1161
1162        if let Some(source_node) = node.attributes.get(SOURCE_NODE_ATTR) {
1163            let source_node = &self.graph[source_node.as_syntax_node_ref()?];
1164            let mut source_span = self.span_calculator.for_node(source_node);
1165            if match node.attributes.get(EMPTY_SOURCE_SPAN_ATTR) {
1166                Some(empty_source_span) => empty_source_span.as_boolean()?,
1167                None => false,
1168            } {
1169                source_span.end = source_span.start.clone();
1170            }
1171            let containing_line = &self.source[source_span.start.containing_line.clone()];
1172            let containing_line = self.stack_graph.add_string(containing_line);
1173            let source_info = self.stack_graph.source_info_mut(node_handle);
1174            source_info.span = source_span;
1175            source_info.containing_line = ControlledOption::some(containing_line);
1176        }
1177
1178        if let Some(syntax_type) = node.attributes.get(SYNTAX_TYPE_ATTR) {
1179            let syntax_type = syntax_type.as_str()?;
1180            let syntax_type = self.stack_graph.add_string(syntax_type);
1181            let source_info = self.stack_graph.source_info_mut(node_handle);
1182            source_info.syntax_type = syntax_type.into();
1183        }
1184
1185        Ok(())
1186    }
1187
1188    fn load_definiens_info(
1189        &mut self,
1190        node_ref: GraphNodeRef,
1191        node_handle: Handle<Node>,
1192    ) -> Result<(), BuildError> {
1193        let node = &self.graph[node_ref];
1194        let definiens_node = match node.attributes.get(DEFINIENS_NODE_ATTR) {
1195            Some(Value::Null) => return Ok(()),
1196            Some(definiens_node) => &self.graph[definiens_node.as_syntax_node_ref()?],
1197            None => return Ok(()),
1198        };
1199        let definiens_span = self.span_calculator.for_node(definiens_node);
1200        let source_info = self.stack_graph.source_info_mut(node_handle);
1201        source_info.definiens_span = definiens_span;
1202        Ok(())
1203    }
1204
1205    fn load_node_debug_info(
1206        &mut self,
1207        node_ref: GraphNodeRef,
1208        node_handle: Handle<Node>,
1209    ) -> Result<(), BuildError> {
1210        let node = &self.graph[node_ref];
1211        for (name, value) in node.attributes.iter() {
1212            let name = name.to_string();
1213            if name.starts_with(DEBUG_ATTR_PREFIX) {
1214                let value = match value {
1215                    Value::String(value) => value.clone(),
1216                    value => value.to_string(),
1217                };
1218                let key = self
1219                    .stack_graph
1220                    .add_string(&name[DEBUG_ATTR_PREFIX.len()..]);
1221                let value = self.stack_graph.add_string(&value);
1222                self.stack_graph
1223                    .node_debug_info_mut(node_handle)
1224                    .add(key, value);
1225            }
1226        }
1227        Ok(())
1228    }
1229
1230    fn load_edge_debug_info(
1231        stack_graph: &mut StackGraph,
1232        source_handle: Handle<Node>,
1233        sink_handle: Handle<Node>,
1234        edge: &Edge,
1235    ) -> Result<(), BuildError> {
1236        for (name, value) in edge.attributes.iter() {
1237            let name = name.to_string();
1238            if name.starts_with(DEBUG_ATTR_PREFIX) {
1239                let value = match value {
1240                    Value::String(value) => value.clone(),
1241                    value => value.to_string(),
1242                };
1243                let key = stack_graph.add_string(&name[DEBUG_ATTR_PREFIX.len()..]);
1244                let value = stack_graph.add_string(&value);
1245                stack_graph
1246                    .edge_debug_info_mut(source_handle, sink_handle)
1247                    .add(key, value);
1248            }
1249        }
1250        Ok(())
1251    }
1252
1253    fn verify_attributes(
1254        &self,
1255        node: &GraphNode,
1256        node_type: &str,
1257        allowed_attributes: &HashSet<&'static str>,
1258    ) {
1259        for (id, _) in node.attributes.iter() {
1260            let id = id.as_str();
1261            if !allowed_attributes.contains(id)
1262                && id != SOURCE_NODE_ATTR
1263                && id != EMPTY_SOURCE_SPAN_ATTR
1264                && !id.starts_with(DEBUG_ATTR_PREFIX)
1265            {
1266                eprintln!("Unexpected attribute {} on node of type {}", id, node_type);
1267            }
1268        }
1269    }
1270}
1271
1272pub trait FileAnalyzer {
1273    /// Construct stack graph for the given file. Implementations must assume that nodes
1274    /// for the given file may already exist, and make sure to prevent node id conflicts,
1275    /// for example by using `StackGraph::new_node_id`.
1276    fn build_stack_graph_into<'a>(
1277        &self,
1278        stack_graph: &mut StackGraph,
1279        file: Handle<File>,
1280        path: &Path,
1281        source: &str,
1282        all_paths: &mut dyn Iterator<Item = &'a Path>,
1283        globals: &HashMap<String, String>,
1284        cancellation_flag: &dyn CancellationFlag,
1285    ) -> Result<(), BuildError>;
1286}