Skip to content

Commit 6bd92f9

Browse files
add support for unhygenic inline macro expansion
1 parent 396d1d8 commit 6bd92f9

File tree

12 files changed

+534
-14
lines changed

12 files changed

+534
-14
lines changed

corelib/src/test/language_features/macro_test.cairo

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,3 +389,15 @@ fn test_defsite_inference() {
389389
let z: defsite_inference::A = defsite_inference::use_local_impl_inference!(5);
390390
assert_eq!(y.x, z.x);
391391
}
392+
393+
macro define_and_use unhygenic {
394+
() => {
395+
let foo = 123;
396+
};
397+
}
398+
399+
#[test]
400+
fn test_define_and_use_unhygenic() {
401+
define_and_use!();
402+
let _y = foo;
403+
}

crates/cairo-lang-parser/src/lexer.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ impl<'a> Lexer<'a> {
214214
"return" => TokenKind::Return,
215215
"match" => TokenKind::Match,
216216
"macro" => TokenKind::Macro,
217+
"unhygenic" => TokenKind::Unhygenic,
217218
"if" => TokenKind::If,
218219
"loop" => TokenKind::Loop,
219220
"continue" => TokenKind::Continue,
@@ -372,6 +373,7 @@ enum TokenKind {
372373
False,
373374
True,
374375
Extern,
376+
Unhygenic,
375377
Type,
376378
Function,
377379
Trait,
@@ -463,6 +465,7 @@ fn token_kind_to_terminal_syntax_kind(kind: TokenKind) -> SyntaxKind {
463465
TokenKind::False => SyntaxKind::TerminalFalse,
464466
TokenKind::True => SyntaxKind::TerminalTrue,
465467
TokenKind::Extern => SyntaxKind::TerminalExtern,
468+
TokenKind::Unhygenic => SyntaxKind::TerminalUnhygenic,
466469
TokenKind::Type => SyntaxKind::TerminalType,
467470
TokenKind::Function => SyntaxKind::TerminalFunction,
468471
TokenKind::Trait => SyntaxKind::TerminalTrait,

crates/cairo-lang-parser/src/parser.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,10 @@ impl<'a> Parser<'a> {
677677
) -> ItemMacroDeclarationGreen {
678678
let macro_kw = self.take::<TerminalMacro>();
679679
let name = self.parse_identifier();
680+
let unhygenic: OptionTerminalUnhygenicGreen = match self.peek().kind {
681+
SyntaxKind::TerminalUnhygenic => self.take::<TerminalUnhygenic>().into(),
682+
_ => OptionTerminalUnhygenicEmpty::new_green(self.db).into(),
683+
};
680684
let lbrace = self.parse_token::<TerminalLBrace>();
681685
let macro_rules = MacroRulesList::new_green(
682686
self.db,
@@ -689,6 +693,7 @@ impl<'a> Parser<'a> {
689693
visibility,
690694
macro_kw,
691695
name,
696+
unhygenic,
692697
lbrace,
693698
macro_rules,
694699
rbrace,

crates/cairo-lang-parser/src/parser_test_data/partial_trees/inline_macro

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ macro some_macro {
5959
│ ├── visibility (kind: VisibilityDefault) []
6060
│ ├── macro_kw (kind: TokenMacro): 'macro'
6161
│ ├── name (kind: TokenIdentifier): 'some_macro'
62+
│ ├── unhygenic_kw (kind: OptionTerminalUnhygenicEmpty) []
6263
│ ├── lbrace (kind: TokenLBrace): '{'
6364
│ ├── rules (kind: MacroRulesList)
6465
│ │ └── child #0 (kind: MacroRule)
@@ -167,6 +168,7 @@ fn use_macro() {
167168
│ │ ├── visibility (kind: VisibilityDefault) []
168169
│ │ ├── macro_kw (kind: TokenMacro): 'macro'
169170
│ │ ├── name (kind: TokenIdentifier): 'some_macro'
171+
│ │ ├── unhygenic_kw (kind: OptionTerminalUnhygenicEmpty) []
170172
│ │ ├── lbrace (kind: TokenLBrace): '{'
171173
│ │ ├── rules (kind: MacroRulesList)
172174
│ │ │ ├── child #0 (kind: MacroRule)

crates/cairo-lang-parser/src/parser_test_data/partial_trees/macro_declaration

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ macro macro_name {
2424
│ ├── visibility (kind: VisibilityDefault) []
2525
│ ├── macro_kw (kind: TokenMacro): 'macro'
2626
│ ├── name (kind: TokenIdentifier): 'macro_name'
27+
│ ├── unhygenic_kw (kind: OptionTerminalUnhygenicEmpty) []
2728
│ ├── lbrace (kind: TokenLBrace): '{'
2829
│ ├── rules (kind: MacroRulesList)
2930
│ │ └── child #0 (kind: MacroRule)
@@ -80,6 +81,7 @@ macro macro_name {
8081
│ ├── visibility (kind: VisibilityDefault) []
8182
│ ├── macro_kw (kind: TokenMacro): 'macro'
8283
│ ├── name (kind: TokenIdentifier): 'macro_name'
84+
│ ├── unhygenic_kw (kind: OptionTerminalUnhygenicEmpty) []
8385
│ ├── lbrace (kind: TokenLBrace): '{'
8486
│ ├── rules (kind: MacroRulesList)
8587
│ │ ├── child #0 (kind: MacroRule)
@@ -225,6 +227,7 @@ error: Missing tokens. Expected a macro rule parameter kind.
225227
│ ├── visibility (kind: VisibilityDefault) []
226228
│ ├── macro_kw (kind: TokenMacro): 'macro'
227229
│ ├── name (kind: TokenIdentifier): 'macro_name'
230+
│ ├── unhygenic_kw (kind: OptionTerminalUnhygenicEmpty) []
228231
│ ├── lbrace (kind: TokenLBrace): '{'
229232
│ ├── rules (kind: MacroRulesList)
230233
│ │ └── child #0 (kind: MacroRule)

crates/cairo-lang-semantic/src/expr/compute.rs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ struct InlineMacroExpansion {
156156
pub name: String,
157157
pub code_mappings: Vec<CodeMapping>,
158158
pub is_plugin_macro: bool,
159+
pub is_unhygenic: bool,
159160
}
160161

161162
/// Context for computing the semantic model of expression trees.
@@ -529,11 +530,13 @@ fn expand_inline_macro(
529530
let mut matcher_ctx =
530531
MatcherContext { captures, placeholder_to_rep_id, ..Default::default() };
531532
let expanded_code = expand_macro_rule(ctx.db, rule, &mut matcher_ctx)?;
533+
let is_unhygenic = ctx.db.priv_macro_declaration_data(macro_declaration_id)?.is_unhygenic;
532534
Ok(InlineMacroExpansion {
533535
content: expanded_code.text,
534536
name: macro_name.clone(),
535537
code_mappings: expanded_code.code_mappings,
536538
is_plugin_macro: false,
539+
is_unhygenic,
537540
})
538541
} else if let Some(macro_plugin_id) =
539542
ctx.db.crate_inline_macro_plugins(crate_id).get(&macro_name).cloned()
@@ -573,6 +576,7 @@ fn expand_inline_macro(
573576
name: code.name.to_string(),
574577
code_mappings: code.code_mappings,
575578
is_plugin_macro: true,
579+
is_unhygenic: false,
576580
})
577581
} else {
578582
return Err(ctx.diagnostics.report(
@@ -591,8 +595,14 @@ fn compute_expr_inline_macro_semantic(
591595
) -> Maybe<Expr> {
592596
let db = ctx.db;
593597
let prev_macro_call_data = ctx.resolver.macro_call_data.clone();
594-
let InlineMacroExpansion { content, name, code_mappings: mappings, is_plugin_macro } =
595-
expand_inline_macro(ctx, syntax)?;
598+
let InlineMacroExpansion {
599+
content,
600+
name,
601+
code_mappings: mappings,
602+
is_plugin_macro,
603+
is_unhygenic,
604+
} = expand_inline_macro(ctx, syntax)?;
605+
596606
if !is_plugin_macro {
597607
let user_defined_macro = ctx.resolver.resolve_generic_path(
598608
&mut Default::default(),
@@ -650,6 +660,8 @@ fn compute_expr_inline_macro_semantic(
650660
);
651661
ctx.resolver.set_suppress_modifiers_diagnostics(prev_resolver_modifiers_suppression);
652662
result
663+
} else if is_unhygenic {
664+
compute_expr_semantic(ctx, &expr_syntax)
653665
} else {
654666
ctx.run_in_macro_subscope(
655667
|ctx| compute_expr_semantic(ctx, &expr_syntax),
@@ -659,7 +671,6 @@ fn compute_expr_inline_macro_semantic(
659671
ctx.resolver.macro_call_data = prev_macro_call_data;
660672
Ok(expr.expr)
661673
}
662-
663674
/// Expands an inline macro used in statement position, computes its semantic model, and extends
664675
/// `statements` with it.
665676
fn expand_macro_for_statement(
@@ -669,8 +680,13 @@ fn expand_macro_for_statement(
669680
statements: &mut Vec<StatementId>,
670681
) -> Maybe<()> {
671682
let prev_macro_call_data = ctx.resolver.macro_call_data.clone();
672-
let InlineMacroExpansion { content, name, code_mappings: mappings, is_plugin_macro } =
673-
expand_inline_macro(ctx, syntax)?;
683+
let InlineMacroExpansion {
684+
content,
685+
name,
686+
code_mappings: mappings,
687+
is_plugin_macro,
688+
is_unhygenic,
689+
} = expand_inline_macro(ctx, syntax)?;
674690
let new_file_long_id = FileLongId::Virtual(VirtualFile {
675691
parent: Some(syntax.stable_ptr(ctx.db).untyped().file_id(ctx.db)),
676692
name: name.clone().into(),
@@ -709,6 +725,17 @@ fn expand_macro_for_statement(
709725
ctx.resolver.set_suppress_modifiers_diagnostics(prev_resolver_modifiers_suppression);
710726
ctx.resolver.macro_call_data = prev_macro_call_data;
711727
result
728+
} else if is_unhygenic {
729+
let mut ids = compute_statement_list_semantic(ctx, parsed_statements.clone());
730+
if let Some(tail_expr) = tail {
731+
let expr = compute_expr_semantic(ctx, &tail_expr);
732+
ids.push(ctx.arenas.statements.alloc(semantic::Statement::Expr(
733+
semantic::StatementExpr { expr: expr.id, stable_ptr: statement_stable_ptr },
734+
)));
735+
}
736+
statements.extend(ids);
737+
ctx.resolver.macro_call_data = prev_macro_call_data;
738+
Ok(())
712739
} else {
713740
let result = ctx.run_in_macro_subscope(
714741
|ctx| {

crates/cairo-lang-semantic/src/expr/test_data/inline_macros

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1480,3 +1480,57 @@ error: Parser error in macro-expanded code: Skipped tokens. Expected: end of exp
14801480
_^
14811481
| let b = 2;
14821482
|__________________^
1483+
1484+
//! > ==========================================================================
1485+
1486+
//! > Test: Variable defined in unhygienic macro is visible outside.
1487+
1488+
//! > test_runner_name
1489+
test_function_diagnostics(expect_diagnostics: false)
1490+
1491+
//! > function
1492+
fn foo() {
1493+
define_var_unhygienic!();
1494+
let _x = a;
1495+
}
1496+
1497+
//! > function_name
1498+
foo
1499+
1500+
//! > module_code
1501+
macro define_var_unhygienic unhygenic {
1502+
() => {
1503+
let a = 42;
1504+
};
1505+
}
1506+
1507+
//! > expected_diagnostics
1508+
1509+
//! > ==========================================================================
1510+
1511+
//! > Test: Variable defined in hygenic macro is not visible outside.
1512+
1513+
//! > test_runner_name
1514+
test_function_diagnostics(expect_diagnostics: true)
1515+
1516+
//! > function
1517+
fn foo() {
1518+
define_var_hygenic!();
1519+
let _x = _a;
1520+
}
1521+
1522+
//! > function_name
1523+
foo
1524+
1525+
//! > module_code
1526+
macro define_var_hygenic {
1527+
() => {
1528+
let _a = 42;
1529+
};
1530+
}
1531+
1532+
//! > expected_diagnostics
1533+
error[E0006]: Identifier not found.
1534+
--> lib.cairo:8:14
1535+
let _x = _a;
1536+
^^

crates/cairo-lang-semantic/src/items/macro_declaration.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ pub struct MacroDeclarationData {
6868
attributes: Vec<Attribute>,
6969
diagnostics: Diagnostics<SemanticDiagnostic>,
7070
resolver_data: Arc<ResolverData>,
71+
pub is_unhygenic: bool,
7172
}
7273

7374
/// The semantic data for a single macro rule in a macro declaration.
@@ -121,6 +122,10 @@ pub fn priv_macro_declaration_data(
121122
);
122123
}
123124

125+
let is_unhygenic = matches!(
126+
macro_declaration_syntax.unhygenic_kw(syntax_db),
127+
ast::OptionTerminalUnhygenic::TerminalUnhygenic(_)
128+
);
124129
let attributes = macro_declaration_syntax.attributes(syntax_db).structurize(syntax_db);
125130
let inference_id = InferenceId::LookupItemDeclaration(LookupItemId::ModuleItem(
126131
ModuleItemId::MacroDeclaration(macro_declaration_id),
@@ -174,7 +179,13 @@ pub fn priv_macro_declaration_data(
174179
rules.push(MacroRuleData { pattern, expansion });
175180
}
176181
let resolver_data = Arc::new(resolver.data);
177-
Ok(MacroDeclarationData { diagnostics: diagnostics.build(), attributes, resolver_data, rules })
182+
Ok(MacroDeclarationData {
183+
diagnostics: diagnostics.build(),
184+
attributes,
185+
resolver_data,
186+
rules,
187+
is_unhygenic,
188+
})
178189
}
179190

180191
/// Helper function to extract pattern elements from a WrappedMacro.

crates/cairo-lang-syntax-codegen/src/cairo_spec.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,10 +900,12 @@ pub fn get_spec() -> Vec<Node> {
900900
.node("visibility", "Visibility")
901901
.node("macro_kw", "TerminalMacro")
902902
.key_node("name", "TerminalIdentifier")
903+
.node("unhygenic_kw", "OptionTerminalUnhygenic")
903904
.node("lbrace", "TerminalLBrace")
904905
.node("rules", "MacroRulesList")
905906
.node("rbrace", "TerminalRBrace")
906907
)
908+
.add_option("TerminalUnhygenic")
907909
.add_list("MacroRulesList", "MacroRule")
908910
.add_struct(StructBuilder::new("MacroRule")
909911
.node("lhs", "WrappedMacro")
@@ -1022,6 +1024,7 @@ pub fn get_spec() -> Vec<Node> {
10221024
.add_keyword_token_and_terminal("Implicits")
10231025
.add_keyword_token_and_terminal("Let")
10241026
.add_keyword_token_and_terminal("Macro")
1027+
.add_keyword_token_and_terminal("Unhygenic")
10251028
.add_keyword_token_and_terminal("Match")
10261029
.add_keyword_token_and_terminal("Module")
10271030
.add_keyword_token_and_terminal("Mut")

0 commit comments

Comments
 (0)