Skip to content

Commit b468b11

Browse files
mohammadfawazMohammad Fawaz
andauthored
Add Leo Library Support - Part 1 (#29196)
* Library packages with consts only. Part 1. * Address review comments --------- Co-authored-by: Mohammad Fawaz <mohammadfawaz@gmail.com>
1 parent c36aaa7 commit b468b11

300 files changed

Lines changed: 4188 additions & 930 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/abi/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,8 @@ fn is_record(comp_ty: &ast::CompositeType, ctx: &Ctx) -> bool {
248248
.flat_map(|scope| scope.composites.iter())
249249
.find(|(sym, _)| *sym == name)
250250
.map(|(_, c)| c.is_record),
251+
252+
ast::Stub::FromLibrary { .. } => None,
251253
};
252254
if let Some(is_record) = found {
253255
return is_record;
@@ -288,7 +290,7 @@ pub fn prune_non_interface_types(program: &mut abi::Program) {
288290
let mut used_types: HashSet<abi::Path> = HashSet::new();
289291

290292
// Extract program name without .aleo suffix for comparison
291-
let program_name = program.program.strip_suffix(".aleo").unwrap_or(&program.program);
293+
let program_name = &program.program;
292294

293295
// Phase 1: Collect from interface items
294296
for transition in &program.transitions {

crates/ast/src/common/location.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
// You should have received a copy of the GNU General Public License
1515
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16+
use crate::ProgramId;
1617

1718
use itertools::Itertools;
1819
use leo_span::Symbol;
@@ -22,8 +23,7 @@ use std::fmt::Display;
2223

2324
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
2425
pub struct Location {
25-
/// The program name. e.g. `credits`.
26-
/// Note. This does not include the `.aleo` network suffix.
26+
/// The program name. e.g. `credits.aleo` or `my_library`.
2727
pub program: Symbol,
2828
/// The absolute path to the item that this `Location` points to.
2929
pub path: Vec<Symbol>,
@@ -37,14 +37,14 @@ impl Location {
3737

3838
impl Display for Location {
3939
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40-
write!(f, "{}.aleo/{}", self.program, self.path.iter().format("::"))
40+
write!(f, "{}/{}", self.program, self.path.iter().format("::"))
4141
}
4242
}
4343

4444
impl<N: Network> From<Locator<N>> for Location {
4545
fn from(locator: Locator<N>) -> Self {
4646
Location {
47-
program: Symbol::intern(&locator.program_id().name().to_string()),
47+
program: ProgramId::from(locator.program_id()).as_symbol(),
4848
path: vec![Symbol::intern(&locator.resource().to_string())],
4949
}
5050
}

crates/ast/src/common/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ pub use path::*;
3535
mod positive_number;
3636
pub use positive_number::*;
3737

38+
mod program_id;
39+
pub use program_id::*;
40+
3841
mod network_name;
3942
pub use network_name::*;
4043

crates/ast/src/common/path.rs

Lines changed: 75 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@
1414
// You should have received a copy of the GNU General Public License
1515
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
1616

17-
use crate::{Expression, Identifier, Location, Node, NodeID, simple_node_impl};
17+
use crate::{Expression, Identifier, Location, Node, NodeID, ProgramId, simple_node_impl};
1818

1919
use leo_span::{Span, Symbol};
2020

21+
use indexmap::IndexSet;
2122
use itertools::Itertools;
2223
use serde::{Deserialize, Serialize};
2324
use std::{fmt, hash::Hash};
@@ -26,7 +27,7 @@ use std::{fmt, hash::Hash};
2627
#[derive(Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
2728
pub struct Path {
2829
/// The program this path belongs to, if set by the user
29-
user_program: Option<Identifier>,
30+
user_program: Option<ProgramId>,
3031

3132
/// The qualifying namespace segments written by the user, excluding the item itself.
3233
/// e.g., in `foo::bar::baz`, this would be `[foo, bar]`.
@@ -63,7 +64,7 @@ impl Path {
6364
/// - `span`: The source code span for this path.
6465
/// - `id`: The node ID.
6566
pub fn new(
66-
user_program: Option<Identifier>,
67+
user_program: Option<ProgramId>,
6768
qualifier: Vec<Identifier>,
6869
identifier: Identifier,
6970
span: Span,
@@ -93,12 +94,12 @@ impl Path {
9394
}
9495

9596
/// Returns the optional program identifier.
96-
pub fn user_program(&self) -> Option<&Identifier> {
97+
pub fn user_program(&self) -> Option<&ProgramId> {
9798
self.user_program.as_ref()
9899
}
99100

100101
/// Returns `self` after setting it `user_program` field to `user_program`.
101-
pub fn with_user_program(mut self, user_program: Identifier) -> Self {
102+
pub fn with_user_program(mut self, user_program: ProgramId) -> Self {
102103
self.user_program = Some(user_program);
103104
self
104105
}
@@ -131,7 +132,7 @@ impl Path {
131132
/// 3. None (unresolved or local)
132133
pub fn program(&self) -> Option<Symbol> {
133134
if let Some(id) = &self.user_program {
134-
return Some(id.name);
135+
return Some(id.as_symbol());
135136
}
136137

137138
match &self.target {
@@ -215,35 +216,79 @@ impl Path {
215216
Self { user_program, qualifier, identifier, target, span, id }
216217
}
217218

218-
/// Resolves this path as a global path using the current module context.
219+
/// Resolves this path as a global path within the current module context.
219220
///
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.
221+
/// This function converts a user-written path into a fully qualified
222+
/// [`PathTarget::Global`] by determining which program the path belongs to
223+
/// and constructing the corresponding module path.
224224
///
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
225+
/// Resolution follows two main cases:
226+
///
227+
/// 1. **External library access**
228+
/// If the path does not explicitly specify a program (`user_program` is `None`)
229+
/// and the first qualifier segment matches a known external library name,
230+
/// that segment is interpreted as the target program. The remaining qualifier
231+
/// segments and identifier form the path inside that program.
232+
///
233+
/// 2. **Local or explicitly-qualified program access**
234+
/// Otherwise, the path is resolved relative to the current module context.
235+
/// The final location is constructed by combining:
236+
/// - the current module path,
237+
/// - any user-written qualifier segments, and
238+
/// - the final identifier.
239+
///
240+
/// If the user explicitly wrote a program (via `user_program`), it overrides
241+
/// the default `program` parameter. Otherwise, the current program is used.
242+
///
243+
/// Importantly, this transformation **does not modify the user-written syntax**
244+
/// (`user_program`, `qualifier`, `identifier`). It only determines the internal
245+
/// `target` used during later compilation stages.
246+
pub fn resolve_as_global_in_module<I>(
247+
self,
248+
program: Symbol,
249+
external_libs: &IndexSet<Symbol>,
250+
current_module: I,
251+
) -> Self
228252
where
229253
I: IntoIterator<Item = Symbol>,
230254
{
231255
let Path { user_program, qualifier, identifier, span, id, .. } = self;
232256

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 }
257+
// Case 1: The path starts with a known external library name and the user
258+
// did not explicitly specify a program. In this situation we interpret
259+
// the first qualifier segment as the program name.
260+
if let Some(first) = qualifier.first()
261+
&& user_program.is_none()
262+
&& external_libs.contains(&first.name)
263+
{
264+
// Build the path within the external library by skipping the
265+
// first qualifier (the library name itself).
266+
let mut path: Vec<Symbol> = qualifier.iter().skip(1).map(|id| id.name).collect();
267+
path.push(identifier.name);
268+
269+
let target = PathTarget::Global(Location { program: first.name, path });
270+
271+
Self { user_program: None, qualifier, identifier, target, span, id }
272+
} else {
273+
// Case 2: Resolve relative to the current module.
274+
//
275+
// Construct the path by concatenating:
276+
// current_module + user qualifier + identifier.
277+
let mut path: Vec<Symbol> = Vec::new();
278+
path.extend(current_module);
279+
path.extend(qualifier.iter().map(|id| id.name));
280+
path.push(identifier.name);
281+
282+
// Determine which program this location belongs to:
283+
// - use the explicitly written program if provided
284+
// - otherwise fall back to the current program.
285+
let target = PathTarget::Global(Location {
286+
program: user_program.map(|id| id.as_symbol()).unwrap_or(program),
287+
path,
288+
});
289+
290+
Self { user_program, qualifier, identifier, target, span, id }
291+
}
247292
}
248293
}
249294

@@ -253,12 +298,12 @@ impl fmt::Display for Path {
253298
let program: Option<Symbol> = self
254299
.user_program
255300
.as_ref()
256-
.map(|id| id.name) // Convert Identifier -> Symbol
301+
.map(|id| id.as_symbol()) // Convert Identifier -> Symbol
257302
.or_else(|| self.try_global_location().map(|global| global.program));
258303

259304
// Program prefix
260305
if let Some(program) = program {
261-
write!(f, "{}.aleo/", program)?;
306+
write!(f, "{}/", program)?;
262307
}
263308

264309
// Qualifiers
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
use crate::{Identifier, NetworkName};
1818

1919
use core::fmt;
20-
use leo_span::Symbol;
20+
use leo_span::{Span, Symbol};
2121
use serde::{Deserialize, Serialize};
2222
use snarkvm::{
2323
console::program::ProgramID,
@@ -35,6 +35,14 @@ pub struct ProgramId {
3535
}
3636

3737
impl ProgramId {
38+
pub fn span(&self) -> Span {
39+
Span::new(self.name.span.lo, self.network.span.hi)
40+
}
41+
42+
pub fn as_symbol(&self) -> Symbol {
43+
Symbol::intern(&self.to_string())
44+
}
45+
3846
/// Initializes a new `ProgramId` from a string, using a network parameter to validate using snarkVM.
3947
pub fn from_str_with_network(string: &str, network: NetworkName) -> Result<Self> {
4048
match network {

crates/ast/src/composite/mod.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
pub mod member;
1818
pub use member::*;
1919

20-
use crate::{ConstParameter, Identifier, Indent, Mode, Node, NodeID, Type};
20+
use crate::{ConstParameter, Identifier, Indent, Mode, Node, NodeID, ProgramId, Type};
2121
use leo_span::{Span, Symbol};
2222

2323
use itertools::Itertools;
@@ -68,7 +68,7 @@ impl Composite {
6868
self.identifier.name
6969
}
7070

71-
pub fn from_external_record<N: Network>(input: &RecordType<N>, program: Symbol) -> Self {
71+
pub fn from_external_record<N: Network>(input: &RecordType<N>, program_id: ProgramId) -> Self {
7272
let mut members = Vec::with_capacity(input.entries().len() + 1);
7373
members.push(Member {
7474
mode: if input.owner().is_private() { Mode::Public } else { Mode::Private },
@@ -81,9 +81,9 @@ impl Composite {
8181
mode: if input.owner().is_public() { Mode::Public } else { Mode::Private },
8282
identifier: Identifier::from(id),
8383
type_: match entry {
84-
Public(t) => Type::from_snarkvm(t, program),
85-
Private(t) => Type::from_snarkvm(t, program),
86-
Constant(t) => Type::from_snarkvm(t, program),
84+
Public(t) => Type::from_snarkvm(t, program_id),
85+
Private(t) => Type::from_snarkvm(t, program_id),
86+
Constant(t) => Type::from_snarkvm(t, program_id),
8787
},
8888
span: Default::default(),
8989
id: Default::default(),
@@ -98,7 +98,7 @@ impl Composite {
9898
}
9999
}
100100

101-
pub fn from_snarkvm<N: Network>(input: &StructType<N>, program: Symbol) -> Self {
101+
pub fn from_snarkvm<N: Network>(input: &StructType<N>, program: ProgramId) -> Self {
102102
Self {
103103
identifier: Identifier::from(input.name()),
104104
const_parameters: Vec::new(),

0 commit comments

Comments
 (0)