Skip to content

Commit

Permalink
Implement code mappings (#1756)
Browse files Browse the repository at this point in the history
Closes #1647

---------

Co-authored-by: maciektr <[email protected]>
  • Loading branch information
DelevoXDG and maciektr committed Dec 4, 2024
1 parent 38b89fc commit c2fad94
Show file tree
Hide file tree
Showing 5 changed files with 264 additions and 21 deletions.
5 changes: 3 additions & 2 deletions scarb/src/compiler/plugin/proc_macro/host/attribute.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::compiler::plugin::proc_macro::host::aux_data::{EmittedAuxData, ProcMacroAuxData};
use crate::compiler::plugin::proc_macro::host::into_cairo_diagnostics;
use crate::compiler::plugin::proc_macro::host::{generate_code_mappings, into_cairo_diagnostics};
use crate::compiler::plugin::proc_macro::{
Expansion, ExpansionKind, ProcMacroHostPlugin, ProcMacroId, TokenStreamBuilder,
};
Expand Down Expand Up @@ -407,11 +407,12 @@ impl ProcMacroHostPlugin {
}

let file_name = format!("proc_{}", input.expansion.name);
let code_mappings = generate_code_mappings(&result.token_stream);
let content = result.token_stream.to_string();
PluginResult {
code: Some(PluginGeneratedFile {
name: file_name.into(),
code_mappings: Vec::new(),
code_mappings,
content,
diagnostics_note: Some(format!(
"this error originates in the attribute macro: `{}`",
Expand Down
44 changes: 29 additions & 15 deletions scarb/src/compiler/plugin/proc_macro/host/derive.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use crate::compiler::plugin::proc_macro::host::aux_data::{EmittedAuxData, ProcMacroAuxData};
use crate::compiler::plugin::proc_macro::host::{into_cairo_diagnostics, DERIVE_ATTR};
use crate::compiler::plugin::proc_macro::host::{
generate_code_mappings, into_cairo_diagnostics, DERIVE_ATTR,
};
use crate::compiler::plugin::proc_macro::{
Expansion, ExpansionKind, ProcMacroHostPlugin, ProcMacroId, TokenStreamBuilder,
};
use cairo_lang_defs::patcher::PatchBuilder;
use cairo_lang_defs::plugin::{DynGeneratedFileAuxData, PluginGeneratedFile, PluginResult};
use cairo_lang_macro::{
AllocationContext, Diagnostic, TokenStream, TokenStreamMetadata, TokenTree,
};
use cairo_lang_filesystem::ids::CodeMapping;
use cairo_lang_filesystem::span::TextWidth;
use cairo_lang_macro::{AllocationContext, Diagnostic, TokenStream, TokenStreamMetadata};
use cairo_lang_syntax::attribute::structured::{AttributeArgVariant, AttributeStructurize};
use cairo_lang_syntax::node::ast::{Expr, PathSegment};
use cairo_lang_syntax::node::db::SyntaxGroup;
Expand Down Expand Up @@ -73,7 +74,10 @@ impl ProcMacroHostPlugin {
let any_derives = !derives.is_empty();

let ctx = AllocationContext::default();
let mut derived_code = PatchBuilder::new(db, &item_ast);
let mut derived_code = String::new();
let mut code_mappings = Vec::new();
let mut current_width = TextWidth::default();

for derive in derives.iter() {
let token_stream = token_stream_builder.build(&ctx);
let result = self.instance(derive.package_id).generate_code(
Expand All @@ -99,17 +103,15 @@ impl ProcMacroHostPlugin {
continue;
}

for token in result.token_stream.tokens {
match token {
TokenTree::Ident(token) => {
derived_code.add_str(token.content.as_ref());
}
}
}
code_mappings.extend(generate_code_mappings_with_offset(
&result.token_stream,
current_width,
));
current_width = current_width + TextWidth::from_str(&result.token_stream.to_string());
derived_code.push_str(&result.token_stream.to_string());
}

if any_derives {
let derived_code = derived_code.build().0;
return Some(PluginResult {
code: if derived_code.is_empty() {
None
Expand All @@ -126,7 +128,7 @@ impl ProcMacroHostPlugin {
let note = format!("this error originates in {msg}: `{derive_names}`");
Some(PluginGeneratedFile {
name: "proc_macro_derive".into(),
code_mappings: Vec::new(),
code_mappings,
content: derived_code,
aux_data: if aux_data.is_empty() {
None
Expand All @@ -146,3 +148,15 @@ impl ProcMacroHostPlugin {
None
}
}

fn generate_code_mappings_with_offset(
token_stream: &TokenStream,
offset: TextWidth,
) -> Vec<CodeMapping> {
let mut mappings = generate_code_mappings(token_stream);
for mapping in &mut mappings {
mapping.span.start = mapping.span.start.add_width(offset);
mapping.span.end = mapping.span.end.add_width(offset);
}
mappings
}
5 changes: 3 additions & 2 deletions scarb/src/compiler/plugin/proc_macro/host/inline.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::compiler::plugin::proc_macro::host::aux_data::{EmittedAuxData, ProcMacroAuxData};
use crate::compiler::plugin::proc_macro::host::into_cairo_diagnostics;
use crate::compiler::plugin::proc_macro::host::{generate_code_mappings, into_cairo_diagnostics};
use crate::compiler::plugin::proc_macro::{
Expansion, ProcMacroId, ProcMacroInstance, TokenStreamBuilder,
};
Expand Down Expand Up @@ -77,10 +77,11 @@ impl InlineMacroExprPlugin for ProcMacroInlinePlugin {
DynGeneratedFileAuxData::new(emitted)
});
let content = token_stream.to_string();
let code_mappings = generate_code_mappings(&token_stream);
InlinePluginResult {
code: Some(PluginGeneratedFile {
name: "inline_proc_macro".into(),
code_mappings: Vec::new(),
code_mappings,
content,
aux_data,
diagnostics_note: Some(format!(
Expand Down
33 changes: 32 additions & 1 deletion scarb/src/compiler/plugin/proc_macro/host/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ use anyhow::{ensure, Result};
use cairo_lang_defs::plugin::PluginDiagnostic;
use cairo_lang_defs::plugin::{MacroPlugin, MacroPluginMetadata, PluginResult};
use cairo_lang_filesystem::db::Edition;
use cairo_lang_macro::{AllocationContext, Diagnostic, Severity, TokenStreamMetadata};
use cairo_lang_filesystem::ids::{CodeMapping, CodeOrigin};
use cairo_lang_filesystem::span::{TextOffset, TextSpan, TextWidth};
use cairo_lang_macro::{
AllocationContext, Diagnostic, Severity, TokenStream, TokenStreamMetadata, TokenTree,
};
use cairo_lang_semantic::plugin::PluginSuite;
use cairo_lang_syntax::node::db::SyntaxGroup;
use cairo_lang_syntax::node::ids::SyntaxStablePtrId;
Expand Down Expand Up @@ -259,3 +263,30 @@ impl ProcMacroHost {
&self.macros
}
}

fn generate_code_mappings(token_stream: &TokenStream) -> Vec<CodeMapping> {
token_stream
.tokens
.iter()
.scan(TextOffset::default(), |current_pos, token| {
let TokenTree::Ident(token) = token;
let token_width = TextWidth::from_str(token.content.as_ref());

let mapping = CodeMapping {
span: TextSpan {
start: *current_pos,
end: current_pos.add_width(token_width),
},
origin: CodeOrigin::Span(TextSpan {
start: TextOffset::default()
.add_width(TextWidth::new_for_testing(token.span.start as u32)),
end: TextOffset::default()
.add_width(TextWidth::new_for_testing(token.span.end as u32)),
}),
};

*current_pos = current_pos.add_width(token_width);
Some(mapping)
})
.collect()
}
198 changes: 197 additions & 1 deletion scarb/tests/build_cairo_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -984,7 +984,7 @@ fn can_implement_derive_macro() {
32
}}
}}
"#};
"#};
let token_stream = TokenStream::new(vec![TokenTree::Ident(Token::new(
code.clone(),
Expand Down Expand Up @@ -1573,3 +1573,199 @@ fn can_expand_impl_inner_func_attrr() {
"#});
}

#[test]
fn code_mappings_preserve_attribute_error_locations() {
let temp = TempDir::new().unwrap();
let t = temp.child("some");
CairoPluginProjectBuilder::default()
.lib_rs(indoc! {r#"
use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, TokenTree, Token, TextSpan};
#[attribute_macro]
pub fn some(_attr: TokenStream, mut token_stream: TokenStream) -> ProcMacroResult {
let token_stream_length = token_stream.to_string().len();
token_stream.tokens.push(TokenTree::Ident(Token::new(" ", TextSpan { start: token_stream_length + 1, end: token_stream_length + 5 })));
ProcMacroResult::new(token_stream)
}
"#})
.build(&t);
let project = temp.child("hello");
ProjectBuilder::start()
.name("hello")
.version("1.0.0")
.dep("some", &t)
.lib_cairo(indoc! {r#"
#[some]
fn f() -> felt252 {
let x = 1;
x = 2;
x
}
"#})
.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: Cannot assign to an immutable variable.
--> [..]lib.cairo[proc_some]:3:5
x = 2;
^***^
note: this error originates in the attribute macro: `some`
error: could not compile `hello` due to previous error
"#});
}

#[test]
fn code_mappings_preserve_inline_macro_error_locations() {
let temp = TempDir::new().unwrap();
let t = temp.child("some");
CairoPluginProjectBuilder::default()
.lib_rs(indoc! {r##"
use cairo_lang_macro::{inline_macro, ProcMacroResult, TokenStream, TokenTree, Token, TextSpan};
#[inline_macro]
pub fn some(_token_stream: TokenStream) -> ProcMacroResult {
let mut tokens = Vec::new();
tokens.push(TokenTree::Ident(Token::new(
"undefined".to_string(),
TextSpan::new(0, 9),
)));
ProcMacroResult::new(TokenStream::new(tokens))
}
"##})
.build(&t);

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

Scarb::quick_snapbox()
.arg("build")
.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: Identifier not found.
--> [..]lib.cairo:1:1
fn main() -> felt252 {
^*******^
error: could not compile `hello` due to previous error
"#});
}

#[test]
fn code_mappings_preserve_derive_error_locations() {
let temp = TempDir::new().unwrap();
let t = temp.child("some");
CairoPluginProjectBuilder::default()
.lib_rs(indoc! {r##"
use cairo_lang_macro::{derive_macro, ProcMacroResult, TokenStream, TokenTree, Token, TextSpan};
#[derive_macro]
pub fn custom_derive(token_stream: TokenStream) -> ProcMacroResult {
let name = token_stream
.clone()
.to_string()
.lines()
.find(|l| l.starts_with("struct"))
.unwrap()
.to_string()
.replace("struct", "")
.replace("}", "")
.replace("{", "")
.trim()
.to_string();
let code = indoc::formatdoc!{r#"
impl SomeImpl{name} of Hello<{name}> {{
fn world(self: @{name}) -> u8 {{
256
}}
}}
"#};
let token_stream = TokenStream::new(vec![TokenTree::Ident(Token::new(
code.clone(),
TextSpan {
start: 0,
end: code.len(),
},
))]);
ProcMacroResult::new(token_stream)
}
"##})
.add_dep(r#"indoc = "*""#)
.build(&t);

let project = temp.child("hello");
ProjectBuilder::start()
.name("hello")
.version("1.0.0")
.dep("some", &t)
.lib_cairo(indoc! {r#"
trait Hello<T> {
fn world(self: @T) -> u8;
}
#[derive(CustomDerive, Drop)]
struct SomeType {}
#[derive(CustomDerive, Drop)]
struct AnotherType {}
fn main() -> u8 {
let a = SomeType {};
a.world()
}
"#})
.build(&project);

Scarb::quick_snapbox()
.arg("build")
.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: The value does not fit within the range of type core::integer::u8.
--> [..]lib.cairo:1:1
trait Hello<T> {
^**************^
note: this error originates in the derive macro: `custom_derive`
error: The value does not fit within the range of type core::integer::u8.
--> [..]lib.cairo:1:1
trait Hello<T> {
^**************^
note: this error originates in the derive macro: `custom_derive`
error: could not compile `hello` due to previous error
"#});
}

0 comments on commit c2fad94

Please sign in to comment.