Skip to content
Merged
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
6 changes: 5 additions & 1 deletion packages/macros/src/attribute/type_hash/definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,11 @@ fn generate_code(
pub fn __{type_name}_encoded_type() {{
println!("{}");
}}"#,
type_hash_string.replace("\"", "\\\"")
type_hash_string
.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('{', "{{")
.replace('}', "}}")
)
} else {
String::new()
Expand Down
38 changes: 34 additions & 4 deletions packages/macros/src/attribute/type_hash/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,23 +102,24 @@ impl<'db, 'a> TypeHashParser<'db, 'a> {
.collect::<Vec<Result<(String, S12Type), Diagnostic>>>();

// 2. Build the string representation
let mut encoded_type = format!("\"{primary_type_name}\"(");
let mut encoded_type = format!("{}(", encode_json_string(primary_type_name));
let mut member_names = HashSet::new();
for result in members_types {
let (name, s12_type) = result?;
if !member_names.insert(name.clone()) {
return Err(Diagnostic::error(errors::DUPLICATE_SNIP12_NAME(&name)));
}
let type_name = s12_type.get_snip12_type_name()?;
let encoded_name = encode_json_string(&name);

// Format the member depending on the type variant
match self.plugin_type_info.type_variant {
TypeVariant::Struct => {
encoded_type.push_str(&format!("\"{name}\":\"{type_name}\","))
encoded_type.push_str(&format!("{encoded_name}:\"{type_name}\","))
}
TypeVariant::Enum => {
let tuple = maybe_tuple(&type_name)?;
encoded_type.push_str(&format!("\"{}\"({}),", name, tuple))
encoded_type.push_str(&format!("{encoded_name}({tuple}),"))
}
};

Expand Down Expand Up @@ -318,6 +319,27 @@ fn decode_escaped_string(s: &str) -> Option<String> {
Some(decoded)
}

/// Encodes a string as a JSON string literal, including the surrounding quotes.
fn encode_json_string(value: &str) -> String {
let mut encoded = String::with_capacity(value.len() + 2);
encoded.push('"');

for ch in value.chars() {
match ch {
'"' => encoded.push_str("\\\""),
'\\' => encoded.push_str("\\\\"),
'\n' => encoded.push_str("\\n"),
'\r' => encoded.push_str("\\r"),
'\t' => encoded.push_str("\\t"),
'\u{0}'..='\u{1f}' => encoded.push_str(&format!("\\u{:04x}", ch as u32)),
_ => encoded.push(ch),
}
}

encoded.push('"');
encoded
}

/// Returns the enum compliant string representation of a tuple for the encoded type.
///
/// If the input is not a tuple, it returns the input itself.
Expand All @@ -343,7 +365,7 @@ fn maybe_tuple(s: &str) -> Result<String, Diagnostic> {

#[cfg(test)]
mod tests {
use super::{parse_snip12_args, parse_string_arg};
use super::{encode_json_string, parse_snip12_args, parse_string_arg};

#[test]
fn rejects_duplicate_snip12_name_argument() {
Expand All @@ -363,6 +385,14 @@ mod tests {
assert_eq!(args.kind, "felt252");
}

#[test]
fn encodes_json_string() {
assert_eq!(
encode_json_string("quote\" slash\\ newline\n nul\0 unit\u{1f}"),
r#""quote\" slash\\ newline\n nul\u0000 unit\u001f""#
);
}

#[test]
fn parses_plain_string_arg() {
assert_eq!(parse_string_arg(r#""example""#).unwrap(), "example");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
source: src/tests/test_type_hash.rs
assertion_line: 549
expression: result
---
TokenStream:

pub enum MyEnum {
#[snip12(name: "variant\"\\")]
Value: felt252,
}
pub fn __MY_ENUM_encoded_type() {
println!("\"MyEnum\"(\"variant\\\"\\\\\"(\"felt\"))");
}
pub const MY_ENUM_TYPE_HASH: felt252 =
0x2ef1a57e9a7173f9bfa51b0ff1aacc069fdb14a65b86ac5d3bbf5e52ee1f227;



Diagnostics:

None

AuxData:

None
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
source: src/tests/test_type_hash.rs
assertion_line: 536
expression: result
---
TokenStream:

pub struct MyType {
#[snip12(name: "member\"\\}\n\r\t\0")]
pub value: felt252,
}
pub fn __MY_TYPE_encoded_type() {
println!("\"type\\\"\\\\{{\\n\\r\\t\\u0000\"(\"member\\\"\\\\}}\\n\\r\\t\\u0000\":\"felt\")");
}
pub const MY_TYPE_TYPE_HASH: felt252 =
0x2c33f80e28d377d8e4a8188f359b29a57599d5747e8284e37a1d6daefd76077;



Diagnostics:

None

AuxData:

None
26 changes: 26 additions & 0 deletions packages/macros/src/tests/test_type_hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,19 @@ fn test_duplicate_snip12_struct_name() {
assert_snapshot!(result);
}

#[test]
fn test_json_escaped_struct_names() {
let item = quote! {
pub struct MyType {
#[snip12(name: "member\"\\}\n\r\t\0")]
pub value: felt252,
}
};
let attr_stream = quote! { (name: "type\"\\{\n\r\t\0", debug: true) };
let result = get_string_result(attr_stream, item);
assert_snapshot!(result);
}

#[test]
fn test_duplicate_snip12_enum_name() {
let item = quote! {
Expand All @@ -654,6 +667,19 @@ fn test_duplicate_snip12_enum_name() {
assert_snapshot!(result);
}

#[test]
fn test_json_escaped_enum_variant_name() {
let item = quote! {
pub enum MyEnum {
#[snip12(name: "variant\"\\")]
Value: felt252,
}
};
let attr_stream = quote! { (debug: true) };
let result = get_string_result(attr_stream, item);
assert_snapshot!(result);
}

#[test]
fn test_debug_attribute() {
let item = quote! {
Expand Down