//! 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, . //! //! Also see //! //! # 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::SemiOpaque, 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::SemiOpaque { 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_semiopaque = ctxt.opaque_and_semiopaque(db); if transparency >= Transparency::Opaque { let parent = opaque; opaque = SyntaxContext::new(db, call_id, transparency, edition, parent, identity, identity); } if transparency >= Transparency::SemiOpaque { let parent = opaque_and_semiopaque; opaque_and_semiopaque = SyntaxContext::new(db, call_id, transparency, edition, parent, |_| opaque, identity); } let parent = ctxt; SyntaxContext::new( db, call_id, transparency, edition, parent, |_| opaque, |_| opaque_and_semiopaque, ) }