Skip to main content

leo_ast/common/
path.rs

1// Copyright (C) 2019-2026 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Leo library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17use crate::{Expression, Identifier, Location, Node, NodeID, simple_node_impl};
18
19use leo_span::{Span, Symbol};
20
21use itertools::Itertools;
22use serde::{Deserialize, Serialize};
23use std::{fmt, hash::Hash};
24
25/// A Path in a program.
26#[derive(Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
27pub struct Path {
28    /// The program this path belongs to, if set by the user
29    user_program: Option<Identifier>,
30
31    /// The qualifying namespace segments written by the user, excluding the item itself.
32    /// e.g., in `foo::bar::baz`, this would be `[foo, bar]`.
33    qualifier: Vec<Identifier>,
34
35    /// The final item in the path, e.g., `baz` in `foo::bar::baz`.
36    identifier: Identifier,
37
38    /// The target type (i.e. local v.s. global) of this path.
39    target: PathTarget,
40
41    /// A span locating where the path occurred in the source.
42    pub span: Span,
43
44    /// The ID of the node.
45    pub id: NodeID,
46}
47
48#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
49pub enum PathTarget {
50    Unresolved,
51    Local(Symbol),
52    Global(Location),
53}
54
55simple_node_impl!(Path);
56
57impl Path {
58    /// Creates a new unresolved `Path` from the given components.
59    ///
60    /// - `user_program`: An optional program name (e.g. `credits` in `credits.aleo/Bar`)
61    /// - `qualifier`: The namespace segments (e.g., `foo::bar` in `foo::bar::baz`).
62    /// - `identifier`: The final item in the path (e.g., `baz`).
63    /// - `span`: The source code span for this path.
64    /// - `id`: The node ID.
65    pub fn new(
66        user_program: Option<Identifier>,
67        qualifier: Vec<Identifier>,
68        identifier: Identifier,
69        span: Span,
70        id: NodeID,
71    ) -> Self {
72        Self { user_program, qualifier, identifier, target: PathTarget::Unresolved, span, id }
73    }
74
75    /// Returns the final identifier of the path (e.g., `baz` in `foo::bar::baz`).
76    pub fn identifier(&self) -> &Identifier {
77        &self.identifier
78    }
79
80    /// Returns a slice of the qualifier segments (e.g., `[foo, bar]` in `foo::bar::baz`).
81    pub fn qualifier(&self) -> &[Identifier] {
82        &self.qualifier
83    }
84
85    /// Returns an iterator over all segments as `Symbol`s (qualifiers + identifier).
86    pub fn segments_iter(&self) -> impl Iterator<Item = Symbol> + '_ {
87        self.qualifier.iter().map(|id| id.name).chain(std::iter::once(self.identifier.name))
88    }
89
90    /// Returns a `Vec<Symbol>` of the segments.
91    pub fn segments(&self) -> Vec<Symbol> {
92        self.segments_iter().collect()
93    }
94
95    /// Returns the optional program identifier.
96    pub fn user_program(&self) -> Option<&Identifier> {
97        self.user_program.as_ref()
98    }
99
100    /// Returns `self` after setting it `user_program` field to `user_program`.
101    pub fn with_user_program(mut self, user_program: Identifier) -> Self {
102        self.user_program = Some(user_program);
103        self
104    }
105
106    pub fn span(&self) -> Span {
107        self.span
108    }
109
110    pub fn id(&self) -> NodeID {
111        self.id
112    }
113
114    pub fn is_resolved(&self) -> bool {
115        !matches!(self.target, PathTarget::Unresolved)
116    }
117
118    pub fn is_local(&self) -> bool {
119        matches!(self.target, PathTarget::Local(_))
120    }
121
122    pub fn is_global(&self) -> bool {
123        matches!(self.target, PathTarget::Global(_))
124    }
125
126    /// Returns the program symbol this path refers to, if known.
127    ///
128    /// Priority:
129    /// 1. User-written program qualifier (e.g. `foo.aleo/bar::baz`)
130    /// 2. Resolved global target program
131    /// 3. None (unresolved or local)
132    pub fn program(&self) -> Option<Symbol> {
133        if let Some(id) = &self.user_program {
134            return Some(id.name);
135        }
136
137        match &self.target {
138            PathTarget::Global(location) => Some(location.program),
139            _ => None,
140        }
141    }
142
143    /// Returns the `Symbol` if local, `None` if not.
144    pub fn try_local_symbol(&self) -> Option<Symbol> {
145        match self.target {
146            PathTarget::Local(sym) => Some(sym),
147            _ => None,
148        }
149    }
150
151    /// Returns the `Location` if global, `None` if not.
152    pub fn try_global_location(&self) -> Option<&Location> {
153        match &self.target {
154            PathTarget::Global(loc) => Some(loc),
155            _ => None,
156        }
157    }
158
159    /// Returns the `Symbol` if local, panics if not.
160    pub fn expect_local_symbol(&self) -> Symbol {
161        match self.target {
162            PathTarget::Local(sym) => sym,
163            _ => panic!("Expected a local path, found {:?}", self.target),
164        }
165    }
166
167    /// Returns the `Location` if global, panics if not.
168    pub fn expect_global_location(&self) -> &Location {
169        match &self.target {
170            PathTarget::Global(loc) => loc,
171            _ => panic!("Expected a global path, found {:?}", self.target),
172        }
173    }
174
175    /// Resolves this path to a local symbol.
176    pub fn to_local(self) -> Self {
177        Self { target: PathTarget::Local(self.identifier.name), ..self }
178    }
179
180    /// Resolves this path to a global location.
181    pub fn to_global(self, location: Location) -> Self {
182        Self { target: PathTarget::Global(location), ..self }
183    }
184
185    /// Returns a new `Path` with the final identifier replaced by `new_symbol`.
186    ///
187    /// This updates:
188    /// - `identifier.name`
189    /// - `target`:
190    ///   - `Local(_)` → `Local(new_symbol)`
191    ///   - `Global(Location)` → same location, but with the final path segment replaced
192    ///   - `Unresolved` → unchanged
193    pub fn with_updated_last_symbol(self, new_symbol: Symbol) -> Self {
194        let Path { mut identifier, target, user_program, qualifier, span, id } = self;
195
196        // Update user-visible identifier
197        identifier.name = new_symbol;
198
199        let target = match target {
200            PathTarget::Unresolved => PathTarget::Unresolved,
201
202            PathTarget::Local(_) => PathTarget::Local(new_symbol),
203
204            PathTarget::Global(location) => {
205                let Location { program, mut path } = location;
206
207                assert!(!path.is_empty(), "global location must have at least one path segment");
208
209                *path.last_mut().unwrap() = new_symbol;
210
211                PathTarget::Global(Location { program, path })
212            }
213        };
214
215        Self { user_program, qualifier, identifier, target, span, id }
216    }
217
218    /// Resolves this path as a global path using the current module context.
219    ///
220    /// This method constructs an absolute global `Location` by combining:
221    ///   1) the current module path,
222    ///   2) any user-written qualifier segments, and
223    ///   3) the final identifier.
224    ///
225    /// The resolution only affects the `target` field and preserves
226    /// the original user-written syntax of the path.
227    pub fn resolve_as_global_in_module<I>(self, program: Symbol, current_module: I) -> Self
228    where
229        I: IntoIterator<Item = Symbol>,
230    {
231        let Path { user_program, qualifier, identifier, span, id, .. } = self;
232
233        let mut path: Vec<Symbol> = Vec::new();
234
235        // 1. Current module
236        path.extend(current_module);
237
238        // 2. User-written qualifier
239        path.extend(qualifier.iter().map(|id| id.name));
240
241        // 3. Final identifier
242        path.push(identifier.name);
243
244        let target = PathTarget::Global(Location { program: user_program.map(|id| id.name).unwrap_or(program), path });
245
246        Self { user_program, qualifier, identifier, target, span, id }
247    }
248}
249
250impl fmt::Display for Path {
251    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252        // Determine the effective program symbol
253        let program: Option<Symbol> = self
254            .user_program
255            .as_ref()
256            .map(|id| id.name) // Convert Identifier -> Symbol
257            .or_else(|| self.try_global_location().map(|global| global.program));
258
259        // Program prefix
260        if let Some(program) = program {
261            write!(f, "{}.aleo/", program)?;
262        }
263
264        // Qualifiers
265        if !self.qualifier.is_empty() {
266            write!(f, "{}::", self.qualifier.iter().map(|id| &id.name).format("::"))?;
267        }
268
269        // Final identifier
270        write!(f, "{}", self.identifier.name)
271    }
272}
273
274impl fmt::Debug for Path {
275    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276        // First, display the user-written path
277        write!(f, "{}", self)?;
278
279        // Append resolved info if available
280        match &self.target {
281            PathTarget::Local(sym) => write!(f, " [local: {sym}]"),
282            PathTarget::Global(loc) => {
283                write!(f, " [global: {loc}]")
284            }
285            PathTarget::Unresolved => write!(f, " [unresolved]"),
286        }
287    }
288}
289
290impl From<Path> for Expression {
291    fn from(value: Path) -> Self {
292        Expression::Path(value)
293    }
294}