Skip to content

Commit

Permalink
Add optional annotations field to SierraProgram and `ContractClas…
Browse files Browse the repository at this point in the history
…s` (#4076)
  • Loading branch information
mkaput authored Oct 10, 2023
1 parent 1937daa commit 734a288
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 13 deletions.
4 changes: 2 additions & 2 deletions crates/cairo-lang-sierra/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ num-bigint.workspace = true
num-traits.workspace = true
salsa.workspace = true
serde.workspace = true
serde_json.workspace = true
sha3.workspace = true
smol_str.workspace = true
thiserror.workspace = true

[dev-dependencies]
cairo-lang-test-utils = { path = "../cairo-lang-test-utils", features = ["testing"] }
bimap.workspace = true
cairo-lang-test-utils = { path = "../cairo-lang-test-utils", features = ["testing"] }
env_logger.workspace = true
indoc.workspace = true
pretty_assertions.workspace = true
serde_json.workspace = true
test-case.workspace = true
test-log.workspace = true
31 changes: 31 additions & 0 deletions crates/cairo-lang-sierra/src/debug_info.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::collections::HashMap;
use std::hash::Hash;

use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use smol_str::SmolStr;
Expand Down Expand Up @@ -30,7 +31,36 @@ pub struct DebugInfo {
deserialize_with = "deserialize_map::<FunctionId, _>"
)]
pub user_func_names: HashMap<FunctionId, SmolStr>,
/// Non-crucial information about the program, for use by external libraries and tool.
///
/// See [`Annotations`] type documentation for more information about this field.
#[serde(default, skip_serializing_if = "Annotations::is_empty")]
pub annotations: Annotations,
}

/// Store for non-crucial information about the program, for use by external libraries and tool.
///
/// Keys represent tool namespaces, and values are tool-specific annotations themselves.
/// Annotation values are JSON values, so they can be arbitrarily complex.
///
/// ## Namespaces
///
/// In order to avoid collisions between tools, namespaces should be URL-like, contain tool name.
/// It is not required for namespace URLs to exist, but it is preferable nonetheless.
///
/// A single tool might want to use multiple namespaces, for example to group together annotations
/// coming from different subcomponents of the tool. In such case, namespaces should use path-like
/// notation (e.g. `example.com/sub-namespace`).
///
/// For future-proofing, it might be a good idea to version namespaces, e.g. `example.com/v1`.
///
/// ### Example well-formed namespaces
///
/// - `scarb.swmansion.com`
/// - `scarb.swmansion.com/v1`
/// - `scarb.swmansion.com/build-info/v1`
pub type Annotations = OrderedHashMap<String, serde_json::Value>;

impl DebugInfo {
/// Extracts the existing debug info from a program.
pub fn extract(program: &Program) -> Self {
Expand Down Expand Up @@ -59,6 +89,7 @@ impl DebugInfo {
func.id.debug_name.clone().map(|name| (FunctionId::new(func.id.id), name))
})
.collect(),
annotations: Default::default(),
}
}

Expand Down
2 changes: 2 additions & 0 deletions crates/cairo-lang-sierra/src/debug_info_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ fn test_extract_names() {
("Func1".into(), "Func1".into()),
("Func2".into(), "Func2".into())
]),
annotations: Default::default(),
}
);
}
Expand Down Expand Up @@ -68,6 +69,7 @@ fn test_populate_names() {
(1.into(), "rename_gb".into()),
]),
user_func_names: HashMap::from([(0.into(), "Func1".into()), (1.into(), "Func2".into())]),
annotations: Default::default(),
}
.populate(&mut program);

Expand Down
45 changes: 41 additions & 4 deletions crates/cairo-lang-sierra/src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use anyhow::Result;
use num_bigint::BigInt;
use serde::{Deserialize, Serialize};

use crate::debug_info::DebugInfo;
use crate::extensions::gas::{
BuiltinCostWithdrawGasLibfunc, RedepositGasLibfunc, WithdrawGasLibfunc,
};
Expand All @@ -20,17 +21,17 @@ use crate::ids::{
#[serde(tag = "version")]
pub enum VersionedProgram {
#[serde(rename = "1")]
V1(Program),
V1(ProgramArtifact),
}

impl From<Program> for VersionedProgram {
fn from(value: Program) -> Self {
impl From<ProgramArtifact> for VersionedProgram {
fn from(value: ProgramArtifact) -> Self {
VersionedProgram::V1(value)
}
}

impl VersionedProgram {
pub fn into_v1(self) -> Result<Program> {
pub fn into_v1(self) -> Result<ProgramArtifact> {
match self {
VersionedProgram::V1(program) => Ok(program),
}
Expand All @@ -45,6 +46,37 @@ impl fmt::Display for VersionedProgram {
}
}

/// Sierra program in a form for storage on the filesystem and sharing externally.
///
/// Do not serialize this struct directly, use [`VersionedProgram`] instead.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct ProgramArtifact {
/// Sierra program itself.
#[serde(flatten)]
pub program: Program,
/// Debug information for a Sierra program.
#[serde(skip_serializing_if = "Option::is_none")]
pub debug_info: Option<DebugInfo>,
}

impl ProgramArtifact {
/// Create a new [`ProgramArtifact`] without any extra information.
pub fn stripped(program: Program) -> Self {
Self { program, debug_info: None }
}

/// Add [`DebugInfo`] to the [`ProgramArtifact`], replacing existing one.
pub fn with_debug_info(self, debug_info: DebugInfo) -> Self {
Self { program: self.program, debug_info: Some(debug_info) }
}
}

impl fmt::Display for ProgramArtifact {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.program, f)
}
}

/// A full Sierra program.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Program {
Expand All @@ -61,6 +93,11 @@ impl Program {
pub fn get_statement(&self, id: &StatementIdx) -> Option<&Statement> {
self.statements.get(id.0)
}

/// Create a new [`ProgramArtifact`] out of this [`Program`].
pub fn into_artifact(self) -> VersionedProgram {
VersionedProgram::V1(ProgramArtifact::stripped(self))
}
}

/// Declaration of a concrete type.
Expand Down
3 changes: 1 addition & 2 deletions crates/cairo-lang-sierra/tests/format_test.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use cairo_lang_sierra::program::VersionedProgram;
use indoc::indoc;
use pretty_assertions::assert_eq;
use test_log::test;
Expand Down Expand Up @@ -107,7 +106,7 @@ fn versioned_program_display_test() {
"};
assert_eq!(
parser.parse(sierra_code).map(|p| p.to_string()),
Ok(VersionedProgram::from(parser.parse(sierra_code).unwrap()).to_string())
Ok(parser.parse(sierra_code).unwrap().into_artifact().to_string())
);
}

Expand Down
9 changes: 4 additions & 5 deletions crates/cairo-lang-sierra/tests/serde_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,10 @@ fn get_path(file_name: &str, ext: &str) -> PathBuf {
fn get_test_program_from_sierra(example_file_name: &str) -> VersionedProgram {
let path = get_path(example_file_name, "sierra");
let parser = cairo_lang_sierra::ProgramParser::new();
VersionedProgram::from(
parser
.parse(&std::fs::read_to_string(path).expect("Could not read example program."))
.expect("Could not parse example program."),
)
parser
.parse(&std::fs::read_to_string(path).expect("Could not read example program."))
.expect("Could not parse example program.")
.into_artifact()
}

// Parse code, serialize, and then deserialize it, ensuring the original parsed code is retained.
Expand Down

0 comments on commit 734a288

Please sign in to comment.