From 369ccbe84e2976cf558aa2145e88f3790a26b833 Mon Sep 17 00:00:00 2001 From: Fabian Stamm Date: Sat, 26 Jul 2025 13:12:34 +0200 Subject: [PATCH] Make sure that imports are always in the same order between runs. This makes the output more stable for putting it into a versioning system like git --- Cargo.lock | 2 +- Cargo.toml | 2 +- libjrpc/src/compile.rs | 4 +- libjrpc/src/ir.rs | 62 +++++++++++++++++++++++++------ libjrpc/src/targets/rust.rs | 6 +-- libjrpc/src/targets/typescript.rs | 16 ++++---- 6 files changed, 66 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8ea15e4..9c6def6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -561,7 +561,7 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jrpc-cli" -version = "0.1.0" +version = "0.1.1" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 463da14..16c8b63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "jrpc-cli" -version = "0.1.0" +version = "0.1.1" [workspace] resolver = "2" diff --git a/libjrpc/src/compile.rs b/libjrpc/src/compile.rs index 5a935b7..11261aa 100644 --- a/libjrpc/src/compile.rs +++ b/libjrpc/src/compile.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, path::PathBuf}; +use std::{collections::BTreeMap, path::PathBuf}; use anyhow::{Context, Result}; @@ -8,7 +8,7 @@ use crate::{ }; pub trait Compile { - fn new(options: &HashMap) -> Result + fn new(options: &BTreeMap) -> Result where Self: Sized; diff --git a/libjrpc/src/ir.rs b/libjrpc/src/ir.rs index dc5ebab..43ccd0a 100644 --- a/libjrpc/src/ir.rs +++ b/libjrpc/src/ir.rs @@ -1,5 +1,5 @@ use std::{ - collections::{HashMap, HashSet}, + collections::{BTreeMap, BTreeSet}, error::Error, fmt::{Debug, Display}, hash::{Hash, Hasher}, @@ -20,7 +20,7 @@ pub trait Definition: Debug { #[derive(Debug, Clone)] pub struct IR { - pub options: HashMap, + pub options: BTreeMap, pub steps: Vec, } @@ -58,7 +58,12 @@ impl ToString for BaseType { impl Hash for BaseType { fn hash(&self, state: &mut H) { - self.to_string().hash(state); + // Hash the enum variant itself + std::mem::discriminant(self).hash(state); + // If the variant has data, hash that too + if let BaseType::Custom(name) = self { + name.hash(state); + } } } @@ -178,7 +183,7 @@ impl Type { #[derive(Debug, Clone)] pub struct TypeDefinition { pub name: String, - pub depends: HashSet, + pub depends: BTreeSet, pub fields: Vec, pub position: ParserPosition, } @@ -223,7 +228,7 @@ pub struct EnumField { #[derive(Debug, Clone)] pub struct ServiceDefinition { pub name: String, - pub depends: HashSet, + pub depends: BTreeSet, pub methods: Vec, pub position: ParserPosition, } @@ -259,7 +264,7 @@ pub struct MethodOutput { #[derive(Debug, Clone)] pub struct MethodDecorators { pub description: Option, - pub parameter_descriptions: HashMap, + pub parameter_descriptions: BTreeMap, pub return_description: Option, } @@ -267,7 +272,7 @@ fn build_type(stmt: &TypeStatement) -> Result { let mut typedef = TypeDefinition { position: stmt.position.clone(), name: stmt.name.clone(), - depends: HashSet::new(), + depends: BTreeSet::new(), fields: Vec::new(), }; @@ -332,7 +337,7 @@ fn build_service(stmt: &ServiceStatement) -> Result { let mut servdef = ServiceDefinition { position: stmt.position.clone(), name: stmt.name.clone(), - depends: HashSet::new(), + depends: BTreeSet::new(), methods: Vec::new(), }; @@ -349,7 +354,7 @@ fn build_service(stmt: &ServiceStatement) -> Result { }), decorators: MethodDecorators { description: None, - parameter_descriptions: HashMap::new(), + parameter_descriptions: BTreeMap::new(), return_description: None, }, }; @@ -450,7 +455,7 @@ fn build_service(stmt: &ServiceStatement) -> Result { } pub fn build_ir(root: &Vec) -> Result { - let mut options = HashMap::::new(); + let mut options = BTreeMap::::new(); let mut steps = Vec::new(); for node in root { @@ -476,8 +481,8 @@ pub fn build_ir(root: &Vec) -> Result { } } - let mut all_types = HashSet::::new(); - let mut serv_types = HashSet::::new(); + let mut all_types = BTreeSet::::new(); + let mut serv_types = BTreeSet::::new(); for bi in &BUILT_INS { all_types.insert(bi.to_string()); @@ -577,3 +582,36 @@ impl Display for IRError { write!(f, "ParserError: {} at {:?}", self.message, self.position) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn btreeset_order_is_consistent() { + let mut set1 = BTreeSet::new(); + let mut set2 = BTreeSet::new(); + + let elements = vec![ + BaseType::Custom("CustomType".to_string()), + BaseType::Void, + BaseType::Bytes, + BaseType::Float, + ]; + + // Insert in normal order + for elem in &elements { + set1.insert(elem.clone()); + } + + // Insert in reverse order + for elem in elements.iter().rev() { + set2.insert(elem.clone()); + } + + let iter1: Vec<_> = set1.iter().cloned().collect(); + let iter2: Vec<_> = set2.iter().cloned().collect(); + + assert_eq!(iter1, iter2); // Order must be the same + } +} diff --git a/libjrpc/src/targets/rust.rs b/libjrpc/src/targets/rust.rs index 44715cb..b978c43 100644 --- a/libjrpc/src/targets/rust.rs +++ b/libjrpc/src/targets/rust.rs @@ -1,6 +1,6 @@ use anyhow::Result; use log::warn; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet}; use crate::compile::{Compile, CompileContext, FileGenerator}; use crate::ir::{BaseType, EnumDefinition, ServiceDefinition, Step, Type, TypeDefinition}; @@ -46,7 +46,7 @@ impl RustCompiler { fn add_dependencies( &mut self, file: &mut FileGenerator, - depends: &HashSet, + depends: &BTreeSet, ) -> Result<()> { for dep in depends { match dep { @@ -409,7 +409,7 @@ impl RustCompiler { } impl Compile for RustCompiler { - fn new(options: &HashMap) -> anyhow::Result { + fn new(options: &BTreeMap) -> anyhow::Result { let crate_name = if let Some(crate_name) = options.get("rust_crate") { crate_name.to_string() } else { diff --git a/libjrpc/src/targets/typescript.rs b/libjrpc/src/targets/typescript.rs index c33c801..751ea65 100644 --- a/libjrpc/src/targets/typescript.rs +++ b/libjrpc/src/targets/typescript.rs @@ -1,6 +1,7 @@ +use std::collections::{BTreeMap, BTreeSet}; + use anyhow::Result; use log::info; -use std::collections::{HashMap, HashSet}; use crate::compile::{Compile, CompileContext, FileGenerator}; use crate::ir::{BaseType, EnumDefinition, ServiceDefinition, Step, Type, TypeDefinition}; @@ -109,9 +110,6 @@ impl TypeScriptCompiler { fn type_to_typescript_ext(typ: &Type) -> String { let mut result = Self::type_to_typescript(&typ.0); let (optional, array, map) = typ.1.get_flags(); - if optional { - result = format!("({} | undefined)", result); - } if array { result = format!("({})[]", result); } @@ -122,13 +120,17 @@ impl TypeScriptCompiler { result ); } + if optional { + // Optional should be the last modifier + result = format!("({} | undefined)", result); + } result } fn add_dependencies( &mut self, file: &mut FileGenerator, - depends: &HashSet, + depends: &BTreeSet, ) -> Result<()> { let esm = F::ext(); file.a0(format!( @@ -433,7 +435,7 @@ import {{ VerificationError }} from \"./ts_base{esm}\"; } impl Compile for TypeScriptCompiler { - fn new(options: &HashMap) -> Result { + fn new(options: &BTreeMap) -> Result { let flavour = options .get("flavour") .cloned() @@ -538,7 +540,7 @@ impl Compile for TypeScriptCompiler { ) -> anyhow::Result<()> { let mut f = FileGenerator::new(); - self.add_dependencies(&mut f, &HashSet::new())?; + self.add_dependencies(&mut f, &BTreeSet::new())?; f.a0(format!("enum {} {{", definition.name)); for value in &definition.values {