Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions rs/client-gen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod ctor_generators;
mod events_generator;
mod helpers;
mod mock_generator;
mod resolution;
mod root_generator;
mod service_generators;
mod type_generators;
Expand Down Expand Up @@ -102,8 +103,7 @@ impl<'ast> ClientGenerator<'ast, IdlPath<'ast>> {
let client_path = self.client_path.context("client path not set")?;
let idl_path = self.idl.0;

let idl = fs::read_to_string(idl_path)
.with_context(|| format!("Failed to open {} for reading", idl_path.display()))?;
let idl = resolution::resolve_idl_from_path(idl_path)?;

self.with_idl(&idl)
.generate_to(client_path)
Expand All @@ -114,8 +114,7 @@ impl<'ast> ClientGenerator<'ast, IdlPath<'ast>> {
pub fn generate_to(self, out_path: impl AsRef<Path>) -> Result<()> {
let idl_path = self.idl.0;

let idl = fs::read_to_string(idl_path)
.with_context(|| format!("Failed to open {} for reading", idl_path.display()))?;
let idl = resolution::resolve_idl_from_path(idl_path)?;

self.with_idl(&idl)
.generate_to(out_path)
Expand Down
62 changes: 62 additions & 0 deletions rs/client-gen/src/resolution.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use anyhow::Result;
use sails_idl_parser_v2::preprocess::{self, IdlLoader};
use std::fs;
use std::path::{Path, PathBuf};

struct FsLoader {
base_dir: PathBuf,
}

impl IdlLoader for FsLoader {
fn load(&self, path: &str) -> Result<(String, String), String> {
let full_path = self.base_dir.join(path);

let canonical_path = full_path
.canonicalize()
.map_err(|e| format!("Failed to resolve path '{}': {}", full_path.display(), e))?;

let content = fs::read_to_string(&canonical_path).map_err(|e| {
format!(
"Failed to read include '{}': {}",
canonical_path.display(),
e
)
})?;

let unique_id = canonical_path.to_string_lossy().into_owned();
Ok((content, unique_id))
}
}

pub fn resolve_idl_from_path(path: &Path) -> Result<String> {
// Resolve initial path to get correct base dir and filename
let canonical_path = path.canonicalize()?;

let parent_dir = canonical_path.parent().unwrap_or(Path::new("."));
let loader = FsLoader {
base_dir: parent_dir.to_path_buf(),
};

let filename = canonical_path
.file_name()
.and_then(|s| s.to_str())
.ok_or_else(|| anyhow::anyhow!("Invalid IDL path"))?;

preprocess::preprocess(filename, &loader).map_err(|e| anyhow::anyhow!(e))
}

#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;

#[test]
fn test_resolve_idl_from_path() {
let path = Path::new("tests/idls/recursive_main.idl");
let result = resolve_idl_from_path(path).unwrap();

assert!(result.contains("service Leaf"));
assert!(result.contains("service Middle"));
assert!(result.contains("service Main"));
}
}
7 changes: 7 additions & 0 deletions rs/client-gen/src/type_generators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ impl<'ast> Visitor<'ast> for TopLevelTypeGenerator<'ast> {
enum_def_generator.visit_enum_def(enum_def);
self.tokens.extend(enum_def_generator.finalize());
}
ast::TypeDef::Alias(alias_def) => {
let target_type_code = generate_type_decl_with_path(&alias_def.target, "");
quote_in! { self.tokens =>
$['\r']
pub type $(self.type_name) $(self.type_params_tokens.clone()) = $target_type_code;
};
}
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions rs/client-gen/tests/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ fn test_events_works() {
insta::assert_snapshot!(gen_client(idl));
}

#[test]
fn test_aliases_works() {
let idl = include_str!("idls/aliases_works.idl");

insta::assert_snapshot!(gen_client(idl));
}

#[test]
fn full_with_sails_path() {
const IDL: &str = include_str!("idls/full_coverage.idl");
Expand Down
12 changes: 12 additions & 0 deletions rs/client-gen/tests/idls/aliases_works.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
service Aliases {
types {
struct MyStruct { f: u32 }
alias MyU32 = u32;
alias MyStructAlias = MyStruct;
alias MyGeneric<T> = Result<T, String>;
}
functions {
DoSomething(p: MyU32) -> MyStructAlias;
Gen() -> MyGeneric<u32>;
}
}
5 changes: 5 additions & 0 deletions rs/client-gen/tests/idls/leaf.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
service Leaf {
functions {
DoLeaf();
}
}
7 changes: 7 additions & 0 deletions rs/client-gen/tests/idls/middle.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
!@include: leaf.idl

service Middle {
functions {
DoMiddle();
}
}
7 changes: 7 additions & 0 deletions rs/client-gen/tests/idls/recursive_main.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
!@include: middle.idl

service Main {
functions {
DoMain();
}
}
59 changes: 59 additions & 0 deletions rs/client-gen/tests/snapshots/generator__aliases_works.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
source: rs/client-gen/tests/generator.rs
expression: gen_client(idl)
---
// Code generated by sails-client-gen. DO NOT EDIT.
#[cfg(feature = "with_mocks")]
#[cfg(not(target_arch = "wasm32"))]
extern crate std;
#[allow(unused_imports)]
use sails_rs::{client::*, collections::*, prelude::*};

pub mod aliases {
use super::*;
#[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)]
#[codec(crate = sails_rs::scale_codec)]
#[scale_info(crate = sails_rs::scale_info)]
#[reflect_hash(crate = sails_rs)]
pub struct MyStruct {
pub f: u32,
}
pub type MyU32 = u32;
pub type MyStructAlias = MyStruct;
pub type MyGeneric<T> = Result<T, String>;
pub trait Aliases {
type Env: sails_rs::client::GearEnv;
fn do_something(
&mut self,
p: MyU32,
) -> sails_rs::client::PendingCall<io::DoSomething, Self::Env>;
fn gen(&mut self) -> sails_rs::client::PendingCall<io::Gen, Self::Env>;
}
pub struct AliasesImpl;
impl<E: sails_rs::client::GearEnv> Aliases for sails_rs::client::Service<AliasesImpl, E> {
type Env = E;
fn do_something(
&mut self,
p: MyU32,
) -> sails_rs::client::PendingCall<io::DoSomething, Self::Env> {
self.pending_call((p,))
}
fn gen(&mut self) -> sails_rs::client::PendingCall<io::Gen, Self::Env> {
self.pending_call(())
}
}

pub mod io {
use super::*;
sails_rs::io_struct_impl!(DoSomething (p: super::MyU32) -> super::MyStructAlias);
sails_rs::io_struct_impl!(Gen () -> super::MyGeneric<u32, >);
}

#[cfg(feature = "with_mocks")]
#[cfg(not(target_arch = "wasm32"))]
pub mod mockall {
use super::*;
use sails_rs::mockall::*;
mock! { pub Aliases {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl aliases::Aliases for Aliases { type Env = sails_rs::client::GstdEnv; fn do_something (&mut self, p: MyU32) -> sails_rs::client::PendingCall<aliases::io::DoSomething, sails_rs::client::GstdEnv>;fn gen (&mut self, ) -> sails_rs::client::PendingCall<aliases::io::Gen, sails_rs::client::GstdEnv>; } }
}
}
1 change: 1 addition & 0 deletions rs/idl-gen/src/type_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ impl UserDefinedEntry {
});
fields
}
sails_idl_meta::TypeDef::Alias(_) => Vec::new(),
}
}
}
Expand Down
11 changes: 9 additions & 2 deletions rs/idl-meta/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,8 @@ pub type ServiceEvent = EnumVariant;
/// - tuples (`Tuple`),
/// - named types (e.g. `Point<u32>`)
/// - container types like `Option<T>`, `Result<T, E>`
/// - user-defined types with generics (`UserDefined`),
/// - bare generic parameters (`T`).
/// - user-defined named type
/// - generic type parameter (e.g. `T`) used in type definitions.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum TypeDecl {
/// Slice type `[T]`.
Expand Down Expand Up @@ -493,6 +493,7 @@ impl Display for TypeParameter {
pub enum TypeDef {
Struct(StructDef),
Enum(EnumDef),
Alias(AliasDef),
}

/// Struct definition backing a named type or an enum variant payload.
Expand Down Expand Up @@ -574,3 +575,9 @@ pub struct EnumVariant {
pub docs: Vec<String>,
pub annotations: Vec<(String, Option<String>)>,
}

/// Alias definition backing a named alias type.
#[derive(Debug, Clone, PartialEq)]
pub struct AliasDef {
pub target: TypeDecl,
}
60 changes: 60 additions & 0 deletions rs/idl-meta/src/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ fn hash_type(
}
hash.finalize()
}
TypeDef::Alias(alias_def) => hash_type_decl(&alias_def.target, type_map, type_params)?,
};
Ok(bytes)
}
Expand Down Expand Up @@ -530,4 +531,63 @@ mod tests {
map
);
}

#[test]
fn hash_alias_identical_to_target() {
let mut map = BTreeMap::new();

// 1. Define target struct
let struct_ty = Type {
name: "TargetStruct".to_string(),
type_params: vec![],
def: TypeDef::Struct(StructDef {
fields: vec![StructField {
name: Some("f1".to_string()),
type_decl: TypeDecl::Primitive(PrimitiveType::U32),
docs: vec![],
annotations: vec![],
}],
}),
docs: vec![],
annotations: vec![],
};
map.insert("TargetStruct", &struct_ty);

// 2. Define alias to that struct
let alias_ty = Type {
name: "MyAlias".to_string(),
type_params: vec![],
def: TypeDef::Alias(AliasDef {
target: TypeDecl::Named {
name: "TargetStruct".to_string(),
generics: vec![],
},
}),
docs: vec![],
annotations: vec![],
};
map.insert("MyAlias", &alias_ty);

let struct_hash = hash_type_decl(
&TypeDecl::Named {
name: "TargetStruct".to_string(),
generics: vec![],
},
&map,
None,
)
.unwrap();

let alias_hash = hash_type_decl(
&TypeDecl::Named {
name: "MyAlias".to_string(),
generics: vec![],
},
&map,
None,
)
.unwrap();

assert_eq!(struct_hash, alias_hash);
}
}
6 changes: 3 additions & 3 deletions rs/idl-meta/templates/type.askama
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
{%- for (k, v) in annotations %}
@{{ k }} {%- if v.is_some() -%}: {{ v.as_ref().unwrap() }}{% endif %}
{%- endfor %}
{% match def %}{% when TypeDef::Struct(_) %}struct{% when TypeDef::Enum(_) %}enum{% endmatch %} {{ name }}

{% match def %}{% when TypeDef::Struct(_) %}struct{% when TypeDef::Enum(_) %}enum{% when TypeDef::Alias(_) %}alias{% endmatch %} {{ name }}
{%- if type_params.len() > 0 -%}
<
{%- for type_param in type_params -%}
Expand All @@ -22,4 +21,5 @@
{{ variant | indent(4) }},
{%- endfor %}
}
{%- endmatch -%}
{%- when TypeDef::Alias(alias_def) -%} = {{ alias_def.target }};
{%- endmatch -%}
6 changes: 6 additions & 0 deletions rs/idl-meta/tests/snapshots/templates__type_alias.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
source: rs/idl-meta/tests/templates.rs
expression: idl
---
/// My alias docs
alias MyAlias= u32;
15 changes: 15 additions & 0 deletions rs/idl-meta/tests/templates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@ fn type_enum() {
insta::assert_snapshot!(idl);
}

#[test]
fn type_alias() {
let ty = Type {
name: "MyAlias".to_string(),
type_params: vec![],
def: TypeDef::Alias(AliasDef {
target: TypeDecl::Primitive(PrimitiveType::U32),
}),
docs: vec!["My alias docs".to_string()],
annotations: vec![],
};
let idl = ty.render().unwrap();
insta::assert_snapshot!(idl);
}

#[test]
fn idl_globals() {
let doc = IdlDoc {
Expand Down
14 changes: 14 additions & 0 deletions rs/idl-parser-v2/src/ffi/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -681,3 +681,17 @@ impl TypeDef {
}
}
}

/// FFI-safe representation of a [crate::ast::AliasDef].
#[repr(C)]
pub struct AliasDef {
pub raw_ptr: Ptr,
}

impl AliasDef {
pub fn from_ast(alias_def: &ast::AliasDef, _allocations: &mut Allocations) -> Self {
AliasDef {
raw_ptr: Ptr::from(alias_def),
}
}
}
Loading