Skip to content

Commit

Permalink
Add executable attributes API (#1296)
Browse files Browse the repository at this point in the history
commit-id:d3b61e99

---

**Stack**:
- #1298
- #1296⚠️ *Part of a stack created by [spr](https://github.com/ejoffe/spr). Do
not merge manually using the UI - doing so may have unexpected results.*
  • Loading branch information
maciektr committed May 7, 2024
1 parent 7507693 commit 289cd5f
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 6 deletions.
28 changes: 27 additions & 1 deletion plugins/cairo-lang-macro-attributes/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use scarb_stable_hash::short_hash;
use syn::spanned::Spanned;
use syn::{parse_macro_input, ItemFn};
use syn::{parse_macro_input, ItemFn, LitStr};

/// Constructs the attribute macro implementation.
///
Expand Down Expand Up @@ -125,3 +125,29 @@ fn hide_name(mut item: ItemFn) -> ItemFn {
item.sig.ident = syn::Ident::new(item_name.as_str(), item.sig.ident.span());
item
}

const EXEC_ATTR_PREFIX: &str = "__exec_attr_";

#[proc_macro]
pub fn executable_attribute(input: TokenStream) -> TokenStream {
let input: LitStr = parse_macro_input!(input as LitStr);
let callback_link = format!("EXEC_ATTR_DESERIALIZE{}", input.value().to_uppercase());
let callback_link = syn::Ident::new(callback_link.as_str(), input.span());
let item_name = format!("{EXEC_ATTR_PREFIX}{}", input.value());
let org_name = syn::Ident::new(item_name.as_str(), input.span());
let expanded = quote! {
fn #org_name() {
// No op to ensure no function with the same name is created.
}

#[::cairo_lang_macro::linkme::distributed_slice(::cairo_lang_macro::MACRO_DEFINITIONS_SLICE)]
#[linkme(crate = ::cairo_lang_macro::linkme)]
static #callback_link: ::cairo_lang_macro::ExpansionDefinition =
::cairo_lang_macro::ExpansionDefinition{
name: #item_name,
kind: ::cairo_lang_macro::ExpansionKind::Attr,
fun: ::cairo_lang_macro::ExpansionFunc::Attr(::cairo_lang_macro::no_op_attr),
};
};
TokenStream::from(expanded)
}
9 changes: 9 additions & 0 deletions plugins/cairo-lang-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,12 @@ pub unsafe extern "C" fn post_process_callback(
}
context
}

/// A no-op Cairo attribute macro implementation.
///
/// This macro implementation does not produce any changes.
/// Can be exposed as a placeholder macro for the internal purposes.
#[doc(hidden)]
pub fn no_op_attr(_attr: TokenStream, input: TokenStream) -> ProcMacroResult {
ProcMacroResult::new(input)
}
44 changes: 41 additions & 3 deletions scarb/src/compiler/plugin/proc_macro/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use crate::core::{Config, Package, PackageId};
use anyhow::{ensure, Context, Result};
use cairo_lang_defs::patcher::PatchBuilder;
use cairo_lang_macro::{
ExpansionKind, FullPathMarker, PostProcessContext, ProcMacroResult, TokenStream,
ExpansionKind as SharedExpansionKind, FullPathMarker, PostProcessContext, ProcMacroResult,
TokenStream,
};
use cairo_lang_macro_stable::{
StableExpansion, StableExpansionsList, StablePostProcessContext, StableProcMacroResult,
Expand Down Expand Up @@ -38,6 +39,8 @@ impl FromSyntaxNode for TokenStream {
}
}

const EXEC_ATTR_PREFIX: &str = "__exec_attr_";

/// Representation of a single procedural macro.
///
/// This struct is a wrapper around a shared library containing the procedural macro implementation.
Expand Down Expand Up @@ -105,7 +108,16 @@ impl ProcMacroInstance {
pub fn declared_attributes(&self) -> Vec<String> {
self.get_expansions()
.iter()
.filter(|e| e.kind == ExpansionKind::Attr)
.filter(|e| e.kind == ExpansionKind::Attr || e.kind == ExpansionKind::Executable)
.map(|e| e.name.clone())
.map(Into::into)
.collect()
}

pub fn executable_attributes(&self) -> Vec<String> {
self.get_expansions()
.iter()
.filter(|e| e.kind == ExpansionKind::Executable)
.map(|e| e.name.clone())
.map(Into::into)
.collect()
Expand Down Expand Up @@ -169,6 +181,24 @@ impl ProcMacroInstance {
}
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ExpansionKind {
Attr,
Derive,
Inline,
Executable,
}

impl From<SharedExpansionKind> for ExpansionKind {
fn from(kind: SharedExpansionKind) -> Self {
match kind {
SharedExpansionKind::Attr => Self::Attr,
SharedExpansionKind::Derive => Self::Derive,
SharedExpansionKind::Inline => Self::Inline,
}
}
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Expansion {
pub name: SmolStr,
Expand All @@ -191,9 +221,17 @@ impl Expansion {
let cstr = CStr::from_ptr(stable_expansion.name);
cstr.to_string_lossy().to_string()
};
// Handle special case for executable attributes.
if name.starts_with(EXEC_ATTR_PREFIX) {
let name = name.strip_prefix(EXEC_ATTR_PREFIX).unwrap();
return Self {
name: SmolStr::new(name),
kind: ExpansionKind::Executable,
};
}
Self {
name: SmolStr::new(name),
kind: ExpansionKind::from_stable(&stable_expansion.kind),
kind: SharedExpansionKind::from_stable(&stable_expansion.kind).into(),
}
}
}
Expand Down
17 changes: 15 additions & 2 deletions scarb/src/compiler/plugin/proc_macro/host.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::compiler::plugin::proc_macro::{Expansion, FromSyntaxNode, ProcMacroInstance};
use crate::compiler::plugin::proc_macro::{
Expansion, ExpansionKind, FromSyntaxNode, ProcMacroInstance,
};
use crate::core::{Config, Package, PackageId};
use anyhow::{ensure, Result};
use cairo_lang_defs::ids::{ModuleItemId, TopLevelLanguageElementId};
Expand All @@ -10,7 +12,7 @@ use cairo_lang_defs::plugin::{
use cairo_lang_defs::plugin::{InlineMacroExprPlugin, InlinePluginResult, PluginDiagnostic};
use cairo_lang_diagnostics::ToOption;
use cairo_lang_macro::{
AuxData, Diagnostic, ExpansionKind, FullPathMarker, Severity, TokenStream, TokenStreamMetadata,
AuxData, Diagnostic, FullPathMarker, Severity, TokenStream, TokenStreamMetadata,
};
use cairo_lang_semantic::db::SemanticGroup;
use cairo_lang_semantic::items::attribute::SemanticQueryAttrs;
Expand Down Expand Up @@ -225,6 +227,10 @@ impl ProcMacroHostPlugin {
attrs: Vec<ast::Attribute>,
item_ast: &ast::ModuleItem,
) -> Option<(ProcMacroId, TokenStream)> {
// Note this function does not affect the executable attributes,
// as it only pulls `ExpansionKind::Attr` from the plugin.
// This means that executable attributes will neither be removed from the item,
// nor will they cause the item to be rewritten.
let mut expansion = None;
for attr in attrs {
if expansion.is_none() {
Expand Down Expand Up @@ -607,6 +613,13 @@ impl MacroPlugin for ProcMacroHostPlugin {
.chain(vec![FULL_PATH_MARKER_KEY.to_string()])
.collect()
}

fn executable_attributes(&self) -> Vec<String> {
self.macros
.iter()
.flat_map(|m| m.executable_attributes())
.collect()
}
}

/// A Cairo compiler inline macro plugin controlling the inline procedural macro execution.
Expand Down
101 changes: 101 additions & 0 deletions scarb/tests/build_cairo_plugin.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use assert_fs::fixture::{FileWriteStr, PathChild};
use assert_fs::TempDir;
use cairo_lang_sierra::program::VersionedProgram;
use camino::Utf8PathBuf;
use indoc::{formatdoc, indoc};
use once_cell::sync::Lazy;
Expand All @@ -9,6 +10,7 @@ use std::path::PathBuf;

use scarb_test_support::command::Scarb;
use scarb_test_support::fsx;
use scarb_test_support::fsx::ChildPathEx;
use scarb_test_support::project_builder::ProjectBuilder;

static CAIRO_LANG_MACRO_PATH: Lazy<String> = Lazy::new(|| {
Expand Down Expand Up @@ -1183,3 +1185,102 @@ fn can_read_attribute_args() {
[..]Finished release target(s) in [..]
"#});
}

#[test]
fn can_create_executable_attribute() {
let temp = TempDir::new().unwrap();
let t = temp.child("some");
CairoPluginProjectBuilder::default()
.lib_rs(indoc! {r##"
use cairo_lang_macro::executable_attribute;
executable_attribute!("some");
"##})
.build(&t);

let project = temp.child("hello");
ProjectBuilder::start()
.name("hello")
.version("1.0.0")
.dep_starknet()
.dep("some", &t)
.lib_cairo(indoc! {r#"
#[some]
fn main() -> felt252 { 12 }
"#})
.build(&project);

Scarb::quick_snapbox()
.arg("build")
// Disable output from Cargo.
.env("CARGO_TERM_QUIET", "true")
.current_dir(&project)
.assert()
.success()
.stdout_matches(indoc! {r#"
[..]Compiling some v1.0.0 ([..]Scarb.toml)
[..]Compiling hello v1.0.0 ([..]Scarb.toml)
[..]Finished release target(s) in [..]
"#});
let sierra = project
.child("target")
.child("dev")
.child("hello.sierra.json")
.read_to_string();
let sierra = serde_json::from_str::<VersionedProgram>(&sierra).unwrap();
let sierra = sierra.into_v1().unwrap();
let executables = sierra.debug_info.unwrap().executables;
assert_eq!(executables.len(), 1);
let executables = executables.get("some").unwrap();
assert_eq!(executables.len(), 1);
let fid = executables.first().unwrap().clone();
assert_eq!(fid.clone().debug_name.unwrap(), "hello::main");
assert!(sierra
.program
.funcs
.iter()
.any(|f| f.id.clone() == fid.clone()));
}

#[test]
fn executable_name_cannot_clash_attr() {
let temp = TempDir::new().unwrap();
let t = temp.child("some");
CairoPluginProjectBuilder::default()
.lib_rs(indoc! {r##"
use cairo_lang_macro::{executable_attribute, attribute_macro, TokenStream, ProcMacroResult};
executable_attribute!("some");
#[attribute_macro]
fn some(_args: TokenStream, input: TokenStream) -> ProcMacroResult {
ProcMacroResult::new(input)
}
"##})
.build(&t);

let project = temp.child("hello");
ProjectBuilder::start()
.name("hello")
.version("1.0.0")
.dep_starknet()
.dep("some", &t)
.lib_cairo(indoc! {r#"
#[some]
fn main() -> felt252 { 12 }
"#})
.build(&project);

Scarb::quick_snapbox()
.arg("build")
// Disable output from Cargo.
.env("CARGO_TERM_QUIET", "true")
.current_dir(&project)
.assert()
.failure()
.stdout_matches(indoc! {r#"
[..]Compiling some v1.0.0 ([..]Scarb.toml)
[..]Compiling hello v1.0.0 ([..]Scarb.toml)
error: duplicate expansions defined for procedural macro some v1.0.0 ([..]Scarb.toml): some
"#});
}

0 comments on commit 289cd5f

Please sign in to comment.