Skip to content
Open
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
12 changes: 12 additions & 0 deletions corelib/src/test/language_features/macro_test.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -389,3 +389,15 @@ fn test_defsite_inference() {
let z: defsite_inference::A = defsite_inference::use_local_impl_inference!(5);
assert_eq!(y.x, z.x);
}

macro define_and_use unhygienic {
() => {
let foo = 123;
};
}

#[test]
fn test_define_and_use_unhygienic() {
define_and_use!();
let _y = foo;
}
3 changes: 3 additions & 0 deletions crates/cairo-lang-parser/src/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ impl<'a> Lexer<'a> {
"return" => TokenKind::Return,
"match" => TokenKind::Match,
"macro" => TokenKind::Macro,
"unhygienic" => TokenKind::Unhygienic,
"if" => TokenKind::If,
"loop" => TokenKind::Loop,
"continue" => TokenKind::Continue,
Expand Down Expand Up @@ -372,6 +373,7 @@ enum TokenKind {
False,
True,
Extern,
Unhygienic,
Type,
Function,
Trait,
Expand Down Expand Up @@ -463,6 +465,7 @@ fn token_kind_to_terminal_syntax_kind(kind: TokenKind) -> SyntaxKind {
TokenKind::False => SyntaxKind::TerminalFalse,
TokenKind::True => SyntaxKind::TerminalTrue,
TokenKind::Extern => SyntaxKind::TerminalExtern,
TokenKind::Unhygienic => SyntaxKind::TerminalUnhygienic,
TokenKind::Type => SyntaxKind::TerminalType,
TokenKind::Function => SyntaxKind::TerminalFunction,
TokenKind::Trait => SyntaxKind::TerminalTrait,
Expand Down
5 changes: 5 additions & 0 deletions crates/cairo-lang-parser/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,10 @@ impl<'a> Parser<'a> {
) -> ItemMacroDeclarationGreen {
let macro_kw = self.take::<TerminalMacro>();
let name = self.parse_identifier();
let unhygienic: OptionTerminalUnhygienicGreen = match self.peek().kind {
SyntaxKind::TerminalUnhygienic => self.take::<TerminalUnhygienic>().into(),
_ => OptionTerminalUnhygienicEmpty::new_green(self.db).into(),
};
let lbrace = self.parse_token::<TerminalLBrace>();
let macro_rules = MacroRulesList::new_green(
self.db,
Expand All @@ -689,6 +693,7 @@ impl<'a> Parser<'a> {
visibility,
macro_kw,
name,
unhygienic,
lbrace,
macro_rules,
rbrace,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ macro some_macro {
│ ├── visibility (kind: VisibilityDefault) []
│ ├── macro_kw (kind: TokenMacro): 'macro'
│ ├── name (kind: TokenIdentifier): 'some_macro'
│ ├── unhygienic_kw (kind: OptionTerminalUnhygienicEmpty) []
│ ├── lbrace (kind: TokenLBrace): '{'
│ ├── rules (kind: MacroRulesList)
│ │ └── child #0 (kind: MacroRule)
Expand Down Expand Up @@ -167,6 +168,7 @@ fn use_macro() {
│ │ ├── visibility (kind: VisibilityDefault) []
│ │ ├── macro_kw (kind: TokenMacro): 'macro'
│ │ ├── name (kind: TokenIdentifier): 'some_macro'
│ │ ├── unhygienic_kw (kind: OptionTerminalUnhygienicEmpty) []
│ │ ├── lbrace (kind: TokenLBrace): '{'
│ │ ├── rules (kind: MacroRulesList)
│ │ │ ├── child #0 (kind: MacroRule)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ macro macro_name {
│ ├── visibility (kind: VisibilityDefault) []
│ ├── macro_kw (kind: TokenMacro): 'macro'
│ ├── name (kind: TokenIdentifier): 'macro_name'
│ ├── unhygienic_kw (kind: OptionTerminalUnhygienicEmpty) []
│ ├── lbrace (kind: TokenLBrace): '{'
│ ├── rules (kind: MacroRulesList)
│ │ └── child #0 (kind: MacroRule)
Expand Down Expand Up @@ -80,6 +81,7 @@ macro macro_name {
│ ├── visibility (kind: VisibilityDefault) []
│ ├── macro_kw (kind: TokenMacro): 'macro'
│ ├── name (kind: TokenIdentifier): 'macro_name'
│ ├── unhygienic_kw (kind: OptionTerminalUnhygienicEmpty) []
│ ├── lbrace (kind: TokenLBrace): '{'
│ ├── rules (kind: MacroRulesList)
│ │ ├── child #0 (kind: MacroRule)
Expand Down Expand Up @@ -225,6 +227,7 @@ error: Missing tokens. Expected a macro rule parameter kind.
│ ├── visibility (kind: VisibilityDefault) []
│ ├── macro_kw (kind: TokenMacro): 'macro'
│ ├── name (kind: TokenIdentifier): 'macro_name'
│ ├── unhygienic_kw (kind: OptionTerminalUnhygienicEmpty) []
│ ├── lbrace (kind: TokenLBrace): '{'
│ ├── rules (kind: MacroRulesList)
│ │ └── child #0 (kind: MacroRule)
Expand Down
37 changes: 32 additions & 5 deletions crates/cairo-lang-semantic/src/expr/compute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ struct InlineMacroExpansion {
pub name: String,
pub code_mappings: Vec<CodeMapping>,
pub is_plugin_macro: bool,
pub is_unhygienic: bool,
}

/// Context for computing the semantic model of expression trees.
Expand Down Expand Up @@ -529,11 +530,13 @@ fn expand_inline_macro(
let mut matcher_ctx =
MatcherContext { captures, placeholder_to_rep_id, ..Default::default() };
let expanded_code = expand_macro_rule(ctx.db, rule, &mut matcher_ctx)?;
let is_unhygienic = ctx.db.priv_macro_declaration_data(macro_declaration_id)?.is_unhygienic;
Ok(InlineMacroExpansion {
content: expanded_code.text,
name: macro_name.clone(),
code_mappings: expanded_code.code_mappings,
is_plugin_macro: false,
is_unhygienic,
})
} else if let Some(macro_plugin_id) =
ctx.db.crate_inline_macro_plugins(crate_id).get(&macro_name).cloned()
Expand Down Expand Up @@ -573,6 +576,7 @@ fn expand_inline_macro(
name: code.name.to_string(),
code_mappings: code.code_mappings,
is_plugin_macro: true,
is_unhygienic: false,
})
} else {
return Err(ctx.diagnostics.report(
Expand All @@ -591,8 +595,14 @@ fn compute_expr_inline_macro_semantic(
) -> Maybe<Expr> {
let db = ctx.db;
let prev_macro_call_data = ctx.resolver.macro_call_data.clone();
let InlineMacroExpansion { content, name, code_mappings: mappings, is_plugin_macro } =
expand_inline_macro(ctx, syntax)?;
let InlineMacroExpansion {
content,
name,
code_mappings: mappings,
is_plugin_macro,
is_unhygienic,
} = expand_inline_macro(ctx, syntax)?;

if !is_plugin_macro {
let user_defined_macro = ctx.resolver.resolve_generic_path(
&mut Default::default(),
Expand Down Expand Up @@ -650,6 +660,8 @@ fn compute_expr_inline_macro_semantic(
);
ctx.resolver.set_suppress_modifiers_diagnostics(prev_resolver_modifiers_suppression);
result
} else if is_unhygienic {
compute_expr_semantic(ctx, &expr_syntax)
} else {
ctx.run_in_macro_subscope(
|ctx| compute_expr_semantic(ctx, &expr_syntax),
Expand All @@ -659,7 +671,6 @@ fn compute_expr_inline_macro_semantic(
ctx.resolver.macro_call_data = prev_macro_call_data;
Ok(expr.expr)
}

/// Expands an inline macro used in statement position, computes its semantic model, and extends
/// `statements` with it.
fn expand_macro_for_statement(
Expand All @@ -669,8 +680,13 @@ fn expand_macro_for_statement(
statements: &mut Vec<StatementId>,
) -> Maybe<()> {
let prev_macro_call_data = ctx.resolver.macro_call_data.clone();
let InlineMacroExpansion { content, name, code_mappings: mappings, is_plugin_macro } =
expand_inline_macro(ctx, syntax)?;
let InlineMacroExpansion {
content,
name,
code_mappings: mappings,
is_plugin_macro,
is_unhygienic,
} = expand_inline_macro(ctx, syntax)?;
let new_file_long_id = FileLongId::Virtual(VirtualFile {
parent: Some(syntax.stable_ptr(ctx.db).untyped().file_id(ctx.db)),
name: name.clone().into(),
Expand Down Expand Up @@ -709,6 +725,17 @@ fn expand_macro_for_statement(
ctx.resolver.set_suppress_modifiers_diagnostics(prev_resolver_modifiers_suppression);
ctx.resolver.macro_call_data = prev_macro_call_data;
result
} else if is_unhygienic {
let mut ids = compute_statement_list_semantic(ctx, parsed_statements.clone());
if let Some(tail_expr) = tail {
let expr = compute_expr_semantic(ctx, &tail_expr);
ids.push(ctx.arenas.statements.alloc(semantic::Statement::Expr(
semantic::StatementExpr { expr: expr.id, stable_ptr: statement_stable_ptr },
)));
}
statements.extend(ids);
ctx.resolver.macro_call_data = prev_macro_call_data;
Ok(())
} else {
let result = ctx.run_in_macro_subscope(
|ctx| {
Expand Down
54 changes: 54 additions & 0 deletions crates/cairo-lang-semantic/src/expr/test_data/inline_macros
Original file line number Diff line number Diff line change
Expand Up @@ -1480,3 +1480,57 @@ error: Parser error in macro-expanded code: Skipped tokens. Expected: end of exp
_^
| let b = 2;
|__________________^

//! > ==========================================================================

//! > Test: Variable defined in unhygienic macro is visible outside.

//! > test_runner_name
test_function_diagnostics(expect_diagnostics: false)

//! > function
fn foo() {
define_var_unhygienic!();
let _x = a;
}

//! > function_name
foo

//! > module_code
macro define_var_unhygienic unhygienic {
() => {
let a = 42;
};
}

//! > expected_diagnostics

//! > ==========================================================================

//! > Test: Variable defined in hygienic macro is not visible outside.

//! > test_runner_name
test_function_diagnostics(expect_diagnostics: true)

//! > function
fn foo() {
define_var_hygienic!();
let _x = _a;
}

//! > function_name
foo

//! > module_code
macro define_var_hygienic {
() => {
let _a = 42;
};
}

//! > expected_diagnostics
error[E0006]: Identifier not found.
--> lib.cairo:8:14
let _x = _a;
^^
13 changes: 12 additions & 1 deletion crates/cairo-lang-semantic/src/items/macro_declaration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ pub struct MacroDeclarationData {
attributes: Vec<Attribute>,
diagnostics: Diagnostics<SemanticDiagnostic>,
resolver_data: Arc<ResolverData>,
pub is_unhygienic: bool,
}

/// The semantic data for a single macro rule in a macro declaration.
Expand Down Expand Up @@ -121,6 +122,10 @@ pub fn priv_macro_declaration_data(
);
}

let is_unhygienic = matches!(
macro_declaration_syntax.unhygienic_kw(syntax_db),
ast::OptionTerminalUnhygienic::TerminalUnhygienic(_)
);
let attributes = macro_declaration_syntax.attributes(syntax_db).structurize(syntax_db);
let inference_id = InferenceId::LookupItemDeclaration(LookupItemId::ModuleItem(
ModuleItemId::MacroDeclaration(macro_declaration_id),
Expand Down Expand Up @@ -174,7 +179,13 @@ pub fn priv_macro_declaration_data(
rules.push(MacroRuleData { pattern, expansion });
}
let resolver_data = Arc::new(resolver.data);
Ok(MacroDeclarationData { diagnostics: diagnostics.build(), attributes, resolver_data, rules })
Ok(MacroDeclarationData {
diagnostics: diagnostics.build(),
attributes,
resolver_data,
rules,
is_unhygienic,
})
}

/// Helper function to extract pattern elements from a WrappedMacro.
Expand Down
3 changes: 3 additions & 0 deletions crates/cairo-lang-syntax-codegen/src/cairo_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -900,10 +900,12 @@ pub fn get_spec() -> Vec<Node> {
.node("visibility", "Visibility")
.node("macro_kw", "TerminalMacro")
.key_node("name", "TerminalIdentifier")
.node("unhygienic_kw", "OptionTerminalUnhygienic")
.node("lbrace", "TerminalLBrace")
.node("rules", "MacroRulesList")
.node("rbrace", "TerminalRBrace")
)
.add_option("TerminalUnhygienic")
.add_list("MacroRulesList", "MacroRule")
.add_struct(StructBuilder::new("MacroRule")
.node("lhs", "WrappedMacro")
Expand Down Expand Up @@ -1022,6 +1024,7 @@ pub fn get_spec() -> Vec<Node> {
.add_keyword_token_and_terminal("Implicits")
.add_keyword_token_and_terminal("Let")
.add_keyword_token_and_terminal("Macro")
.add_keyword_token_and_terminal("Unhygienic")
.add_keyword_token_and_terminal("Match")
.add_keyword_token_and_terminal("Module")
.add_keyword_token_and_terminal("Mut")
Expand Down
Loading