blob: 28800c6fabdbd73760776c7f86b49edbb029ba94 [file] [log] [blame]
//! Machinery for hygienic macros.
//!
//! Inspired by Matthew Flatt et al., “Macros That Work Together: Compile-Time Bindings, Partial
//! Expansion, and Definition Contexts,” *Journal of Functional Programming* 22, no. 2
//! (March 1, 2012): 181–216, <https://doi.org/10.1017/S0956796812000093>.
//!
//! Also see <https://rustc-dev-guide.rust-lang.org/macro-expansion.html#hygiene-and-hierarchies>
//!
//! # The Expansion Order Hierarchy
//!
//! `ExpnData` in rustc, rust-analyzer's version is [`MacroCallLoc`]. Traversing the hierarchy
//! upwards can be achieved by walking up [`MacroCallLoc::kind`]'s contained file id, as
//! [`MacroFile`]s are interned [`MacroCallLoc`]s.
//!
//! # The Macro Definition Hierarchy
//!
//! `SyntaxContextData` in rustc and rust-analyzer. Basically the same in both.
//!
//! # The Call-site Hierarchy
//!
//! `ExpnData::call_site` in rustc, [`MacroCallLoc::call_site`] in rust-analyzer.
// FIXME: Move this into the span crate? Not quite possible today as that depends on `MacroCallLoc`
// which contains a bunch of unrelated things
use std::convert::identity;
use span::{Edition, MacroCallId, Span, SyntaxContext};
use crate::db::ExpandDatabase;
pub use span::Transparency;
pub fn span_with_def_site_ctxt(
db: &dyn ExpandDatabase,
span: Span,
expn_id: MacroCallId,
edition: Edition,
) -> Span {
span_with_ctxt_from_mark(db, span, expn_id, Transparency::Opaque, edition)
}
pub fn span_with_call_site_ctxt(
db: &dyn ExpandDatabase,
span: Span,
expn_id: MacroCallId,
edition: Edition,
) -> Span {
span_with_ctxt_from_mark(db, span, expn_id, Transparency::Transparent, edition)
}
pub fn span_with_mixed_site_ctxt(
db: &dyn ExpandDatabase,
span: Span,
expn_id: MacroCallId,
edition: Edition,
) -> Span {
span_with_ctxt_from_mark(db, span, expn_id, Transparency::SemiTransparent, edition)
}
fn span_with_ctxt_from_mark(
db: &dyn ExpandDatabase,
span: Span,
expn_id: MacroCallId,
transparency: Transparency,
edition: Edition,
) -> Span {
Span {
ctx: apply_mark(db, SyntaxContext::root(edition), expn_id, transparency, edition),
..span
}
}
pub(super) fn apply_mark(
db: &dyn ExpandDatabase,
ctxt: span::SyntaxContext,
call_id: span::MacroCallId,
transparency: Transparency,
edition: Edition,
) -> SyntaxContext {
if transparency == Transparency::Opaque {
return apply_mark_internal(db, ctxt, call_id, transparency, edition);
}
let call_site_ctxt = db.lookup_intern_macro_call(call_id.into()).ctxt;
let mut call_site_ctxt = if transparency == Transparency::SemiTransparent {
call_site_ctxt.normalize_to_macros_2_0(db)
} else {
call_site_ctxt.normalize_to_macro_rules(db)
};
if call_site_ctxt.is_root() {
return apply_mark_internal(db, ctxt, call_id, transparency, edition);
}
// Otherwise, `expn_id` is a macros 1.0 definition and the call site is in a
// macros 2.0 expansion, i.e., a macros 1.0 invocation is in a macros 2.0 definition.
//
// In this case, the tokens from the macros 1.0 definition inherit the hygiene
// at their invocation. That is, we pretend that the macros 1.0 definition
// was defined at its invocation (i.e., inside the macros 2.0 definition)
// so that the macros 2.0 definition remains hygienic.
//
// See the example at `test/ui/hygiene/legacy_interaction.rs`.
for (call_id, transparency) in ctxt.marks(db) {
call_site_ctxt = apply_mark_internal(db, call_site_ctxt, call_id, transparency, edition);
}
apply_mark_internal(db, call_site_ctxt, call_id, transparency, edition)
}
fn apply_mark_internal(
db: &dyn ExpandDatabase,
ctxt: SyntaxContext,
call_id: MacroCallId,
transparency: Transparency,
edition: Edition,
) -> SyntaxContext {
let call_id = Some(call_id);
let mut opaque = ctxt.opaque(db);
let mut opaque_and_semitransparent = ctxt.opaque_and_semitransparent(db);
if transparency >= Transparency::Opaque {
let parent = opaque;
opaque = SyntaxContext::new(db, call_id, transparency, edition, parent, identity, identity);
}
if transparency >= Transparency::SemiTransparent {
let parent = opaque_and_semitransparent;
opaque_and_semitransparent =
SyntaxContext::new(db, call_id, transparency, edition, parent, |_| opaque, identity);
}
let parent = ctxt;
SyntaxContext::new(
db,
call_id,
transparency,
edition,
parent,
|_| opaque,
|_| opaque_and_semitransparent,
)
}