2020-03-02 06:05:15 +00:00
//! Eager expansion related utils
2020-03-02 16:10:20 +00:00
//!
//! Here is a dump of a discussion from Vadim Petrochenkov about Eager Expansion and
//! Its name resolution :
//!
//! > Eagerly expanded macros (and also macros eagerly expanded by eagerly expanded macros,
//! > which actually happens in practice too!) are resolved at the location of the "root" macro
//! > that performs the eager expansion on its arguments.
//! > If some name cannot be resolved at the eager expansion time it's considered unresolved,
//! > even if becomes available later (e.g. from a glob import or other macro).
//!
//! > Eagerly expanded macros don't add anything to the module structure of the crate and
//! > don't build any speculative module structures, i.e. they are expanded in a "flat"
//! > way even if tokens in them look like modules.
//!
//! > In other words, it kinda works for simple cases for which it was originally intended,
//! > and we need to live with it because it's available on stable and widely relied upon.
//!
//!
2021-06-14 04:57:10 +00:00
//! See the full discussion : <https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/Eager.20expansion.20of.20built-in.20macros>
2021-09-05 19:30:06 +00:00
use base_db ::CrateId ;
2023-07-17 13:49:15 +00:00
use rustc_hash ::FxHashMap ;
use syntax ::{ ted , Parse , SyntaxNode , TextRange , TextSize , WalkEvent } ;
2023-06-05 09:04:23 +00:00
use triomphe ::Arc ;
2020-03-02 06:05:15 +00:00
use crate ::{
ast ::{ self , AstNode } ,
2023-03-20 06:31:01 +00:00
db ::ExpandDatabase ,
2022-01-26 17:31:07 +00:00
hygiene ::Hygiene ,
mod_path ::ModPath ,
2022-02-21 18:14:06 +00:00
EagerCallInfo , ExpandError , ExpandResult , ExpandTo , InFile , MacroCallId , MacroCallKind ,
MacroCallLoc , MacroDefId , MacroDefKind , UnresolvedMacro ,
2020-03-02 06:05:15 +00:00
} ;
2023-06-19 06:14:04 +00:00
pub fn expand_eager_macro_input (
2023-03-20 06:31:01 +00:00
db : & dyn ExpandDatabase ,
2020-06-11 10:08:24 +00:00
krate : CrateId ,
2020-03-03 17:13:20 +00:00
macro_call : InFile < ast ::MacroCall > ,
def : MacroDefId ,
2022-01-26 17:31:07 +00:00
resolver : & dyn Fn ( ModPath ) -> Option < MacroDefId > ,
2023-06-05 09:04:23 +00:00
) -> Result < ExpandResult < Option < MacroCallId > > , UnresolvedMacro > {
2020-12-22 13:42:28 +00:00
let ast_map = db . ast_id_map ( macro_call . file_id ) ;
2023-07-17 13:49:15 +00:00
// the expansion which the ast id map is built upon has no whitespace, so the offsets are wrong as macro_call is from the token tree that has whitespace!
2020-12-22 13:42:28 +00:00
let call_id = InFile ::new ( macro_call . file_id , ast_map . ast_id ( & macro_call . value ) ) ;
2021-09-05 19:30:06 +00:00
let expand_to = ExpandTo ::from_call_site ( & macro_call . value ) ;
2020-12-22 13:42:28 +00:00
2020-03-03 18:41:33 +00:00
// Note:
2023-06-05 09:04:23 +00:00
// When `lazy_expand` is called, its *parent* file must already exist.
// Here we store an eager macro id for the argument expanded subtree
2020-03-03 18:41:33 +00:00
// for that purpose.
2021-11-14 15:25:40 +00:00
let arg_id = db . intern_macro_call ( MacroCallLoc {
2021-05-19 18:19:08 +00:00
def ,
krate ,
2023-07-17 13:49:15 +00:00
eager : None ,
2021-09-05 19:30:06 +00:00
kind : MacroCallKind ::FnLike { ast_id : call_id , expand_to : ExpandTo ::Expr } ,
2020-03-06 14:58:45 +00:00
} ) ;
2023-07-17 13:49:15 +00:00
let ExpandResult { value : ( arg_exp , arg_exp_map ) , err : parse_err } =
db . parse_macro_expansion ( arg_id . as_macro_file ( ) ) ;
// we need this map here as the expansion of the eager input fake file loses whitespace ...
let mut ws_mapping = FxHashMap ::default ( ) ;
if let Some ( ( _ , tm , _ ) ) = db . macro_arg ( arg_id ) . value . as_deref ( ) {
ws_mapping . extend ( tm . entries ( ) . filter_map ( | ( id , range ) | {
Some ( ( arg_exp_map . first_range_by_token ( id , syntax ::SyntaxKind ::TOMBSTONE ) ? , range ) )
} ) ) ;
}
let ExpandResult { value : expanded_eager_input , err } = {
eager_macro_recur (
db ,
& Hygiene ::new ( db , macro_call . file_id ) ,
InFile ::new ( arg_id . as_file ( ) , arg_exp . syntax_node ( ) ) ,
krate ,
resolver ,
) ?
2023-06-19 06:14:04 +00:00
} ;
2023-07-17 13:49:15 +00:00
let err = parse_err . or ( err ) ;
let Some ( ( expanded_eager_input , mapping ) ) = expanded_eager_input else {
return Ok ( ExpandResult { value : None , err } ) ;
2023-06-05 09:04:23 +00:00
} ;
2023-07-17 13:49:15 +00:00
let og_tmap = mbe ::syntax_node_to_token_map (
macro_call . value . token_tree ( ) . expect ( " macro_arg_text succeeded " ) . syntax ( ) ,
) ;
let ( mut subtree , expanded_eager_input_token_map ) =
mbe ::syntax_node_to_token_tree ( & expanded_eager_input ) ;
// The tokenmap and ids of subtree point into the expanded syntax node, but that is inaccessible from the outside
// so we need to remap them to the original input of the eager macro.
subtree . visit_ids ( & | id | {
// Note: we discard all token ids of braces and the like here, but that's not too bad and only a temporary fix
if let Some ( range ) =
expanded_eager_input_token_map . first_range_by_token ( id , syntax ::SyntaxKind ::TOMBSTONE )
{
// remap from expanded eager input to eager input expansion
if let Some ( og_range ) = mapping . get ( & range ) {
// remap from eager input expansion to original eager input
if let Some ( & og_range ) = ws_mapping . get ( og_range ) {
if let Some ( og_token ) = og_tmap . token_by_range ( og_range ) {
return og_token ;
}
}
}
}
tt ::TokenId ::UNSPECIFIED
} ) ;
2023-06-19 06:14:04 +00:00
subtree . delimiter = crate ::tt ::Delimiter ::unspecified ( ) ;
2020-03-03 17:13:20 +00:00
2023-06-05 09:04:23 +00:00
let loc = MacroCallLoc {
def ,
krate ,
eager : Some ( Box ::new ( EagerCallInfo {
2023-07-17 13:49:15 +00:00
arg : Arc ::new ( ( subtree , og_tmap ) ) ,
arg_id ,
2023-06-05 09:04:23 +00:00
error : err . clone ( ) ,
} ) ) ,
kind : MacroCallKind ::FnLike { ast_id : call_id , expand_to } ,
} ;
Ok ( ExpandResult { value : Some ( db . intern_macro_call ( loc ) ) , err } )
2020-03-02 06:05:15 +00:00
}
fn lazy_expand (
2023-03-20 06:31:01 +00:00
db : & dyn ExpandDatabase ,
2020-03-02 06:05:15 +00:00
def : & MacroDefId ,
macro_call : InFile < ast ::MacroCall > ,
2020-06-11 10:08:24 +00:00
krate : CrateId ,
2023-07-17 13:49:15 +00:00
) -> ExpandResult < ( InFile < Parse < SyntaxNode > > , Arc < mbe ::TokenMap > ) > {
2020-03-02 06:05:15 +00:00
let ast_id = db . ast_id_map ( macro_call . file_id ) . ast_id ( & macro_call . value ) ;
2021-09-05 19:30:06 +00:00
let expand_to = ExpandTo ::from_call_site ( & macro_call . value ) ;
2023-07-17 13:49:15 +00:00
let ast_id = macro_call . with_value ( ast_id ) ;
let id = def . as_lazy_macro ( db , krate , MacroCallKind ::FnLike { ast_id , expand_to } ) ;
2023-06-19 06:14:04 +00:00
let macro_file = id . as_macro_file ( ) ;
2023-07-17 13:49:15 +00:00
db . parse_macro_expansion ( macro_file )
. map ( | parse | ( InFile ::new ( macro_file . into ( ) , parse . 0 ) , parse . 1 ) )
2020-03-02 06:05:15 +00:00
}
fn eager_macro_recur (
2023-03-20 06:31:01 +00:00
db : & dyn ExpandDatabase ,
2022-01-27 15:57:53 +00:00
hygiene : & Hygiene ,
2020-03-02 06:05:15 +00:00
curr : InFile < SyntaxNode > ,
2020-06-11 10:08:24 +00:00
krate : CrateId ,
2022-01-26 17:31:07 +00:00
macro_resolver : & dyn Fn ( ModPath ) -> Option < MacroDefId > ,
2023-07-17 13:49:15 +00:00
) -> Result < ExpandResult < Option < ( SyntaxNode , FxHashMap < TextRange , TextRange > ) > > , UnresolvedMacro > {
2021-05-26 15:34:50 +00:00
let original = curr . value . clone_for_update ( ) ;
2023-07-17 13:49:15 +00:00
let mut mapping = FxHashMap ::default ( ) ;
2020-03-02 06:05:15 +00:00
2021-04-19 17:28:41 +00:00
let mut replacements = Vec ::new ( ) ;
2020-03-02 06:05:15 +00:00
2023-06-05 09:04:23 +00:00
// Note: We only report a single error inside of eager expansions
let mut error = None ;
2023-07-17 13:49:15 +00:00
let mut offset = 0 i32 ;
let apply_offset = | it : TextSize , offset : i32 | {
TextSize ::from ( u32 ::try_from ( offset + u32 ::from ( it ) as i32 ) . unwrap_or_default ( ) )
} ;
let mut children = original . preorder_with_tokens ( ) ;
2023-06-05 09:04:23 +00:00
2020-03-02 06:05:15 +00:00
// Collect replacement
2023-07-17 13:49:15 +00:00
while let Some ( child ) = children . next ( ) {
let WalkEvent ::Enter ( child ) = child else { continue } ;
let call = match child {
syntax ::NodeOrToken ::Node ( node ) = > match ast ::MacroCall ::cast ( node ) {
Some ( it ) = > {
children . skip_subtree ( ) ;
it
}
None = > continue ,
} ,
syntax ::NodeOrToken ::Token ( t ) = > {
mapping . insert (
TextRange ::new (
apply_offset ( t . text_range ( ) . start ( ) , offset ) ,
apply_offset ( t . text_range ( ) . end ( ) , offset ) ,
) ,
t . text_range ( ) ,
) ;
continue ;
}
} ;
let def = match call . path ( ) . and_then ( | path | ModPath ::from_src ( db , path , hygiene ) ) {
2023-01-09 18:36:22 +00:00
Some ( path ) = > macro_resolver ( path . clone ( ) ) . ok_or ( UnresolvedMacro { path } ) ? ,
2022-01-26 17:31:07 +00:00
None = > {
2023-06-19 06:14:04 +00:00
error = Some ( ExpandError ::other ( " malformed macro invocation " ) ) ;
2022-01-26 17:31:07 +00:00
continue ;
}
} ;
2023-06-05 09:04:23 +00:00
let ExpandResult { value , err } = match def . kind {
2021-03-18 14:37:14 +00:00
MacroDefKind ::BuiltInEager ( .. ) = > {
2023-06-19 06:14:04 +00:00
let ExpandResult { value , err } = match expand_eager_macro_input (
2020-06-11 10:08:24 +00:00
db ,
krate ,
2023-07-17 13:49:15 +00:00
curr . with_value ( call . clone ( ) ) ,
2020-06-11 10:08:24 +00:00
def ,
macro_resolver ,
2022-01-26 17:31:07 +00:00
) {
2023-06-05 09:04:23 +00:00
Ok ( it ) = > it ,
2022-01-26 17:31:07 +00:00
Err ( err ) = > return Err ( err ) ,
} ;
2023-06-19 06:14:04 +00:00
match value {
2023-07-17 13:49:15 +00:00
Some ( call_id ) = > {
2023-06-19 06:14:04 +00:00
let ExpandResult { value , err : err2 } =
2023-07-17 13:49:15 +00:00
db . parse_macro_expansion ( call_id . as_macro_file ( ) ) ;
let call_tt_start =
call . token_tree ( ) . unwrap ( ) . syntax ( ) . text_range ( ) . start ( ) ;
let call_start = apply_offset ( call . syntax ( ) . text_range ( ) . start ( ) , offset ) ;
if let Some ( ( _ , arg_map , _ ) ) = db . macro_arg ( call_id ) . value . as_deref ( ) {
mapping . extend ( arg_map . entries ( ) . filter_map ( | ( tid , range ) | {
value
. 1
. first_range_by_token ( tid , syntax ::SyntaxKind ::TOMBSTONE )
. map ( | r | ( r + call_start , range + call_tt_start ) )
} ) ) ;
} ;
2023-06-19 06:14:04 +00:00
ExpandResult {
value : Some ( value . 0. syntax_node ( ) . clone_for_update ( ) ) ,
err : err . or ( err2 ) ,
}
}
None = > ExpandResult { value : None , err } ,
}
2020-03-02 06:05:15 +00:00
}
2021-03-18 14:37:14 +00:00
MacroDefKind ::Declarative ( _ )
| MacroDefKind ::BuiltIn ( .. )
2021-06-09 16:02:31 +00:00
| MacroDefKind ::BuiltInAttr ( .. )
2021-03-18 14:37:14 +00:00
| MacroDefKind ::BuiltInDerive ( .. )
2021-03-18 15:11:18 +00:00
| MacroDefKind ::ProcMacro ( .. ) = > {
2023-07-17 13:49:15 +00:00
let ExpandResult { value : ( parse , tm ) , err } =
lazy_expand ( db , & def , curr . with_value ( call . clone ( ) ) , krate ) ;
let decl_mac = if let MacroDefKind ::Declarative ( ast_id ) = def . kind {
Some ( db . decl_macro_expander ( def . krate , ast_id ) )
} else {
None
} ;
2020-12-02 15:52:14 +00:00
2020-03-02 06:05:15 +00:00
// replace macro inside
2023-07-17 13:49:15 +00:00
let hygiene = Hygiene ::new ( db , parse . file_id ) ;
2023-06-05 09:04:23 +00:00
let ExpandResult { value , err : error } = eager_macro_recur (
db ,
& hygiene ,
// FIXME: We discard parse errors here
2023-07-17 13:49:15 +00:00
parse . as_ref ( ) . map ( | it | it . syntax_node ( ) ) ,
2023-06-05 09:04:23 +00:00
krate ,
macro_resolver ,
) ? ;
2023-06-19 06:14:04 +00:00
let err = err . or ( error ) ;
2023-07-17 13:49:15 +00:00
let call_tt_start = call . token_tree ( ) . unwrap ( ) . syntax ( ) . text_range ( ) . start ( ) ;
let call_start = apply_offset ( call . syntax ( ) . text_range ( ) . start ( ) , offset ) ;
if let Some ( ( _tt , arg_map , _ ) ) = parse
. file_id
. macro_file ( )
. and_then ( | id | db . macro_arg ( id . macro_call_id ) . value )
. as_deref ( )
{
mapping . extend ( arg_map . entries ( ) . filter_map ( | ( tid , range ) | {
tm . first_range_by_token (
decl_mac . as_ref ( ) . map ( | it | it . map_id_down ( tid ) ) . unwrap_or ( tid ) ,
syntax ::SyntaxKind ::TOMBSTONE ,
)
. map ( | r | ( r + call_start , range + call_tt_start ) )
} ) ) ;
} ;
// FIXME: Do we need to re-use _m here?
ExpandResult { value : value . map ( | ( n , _m ) | n ) , err }
2020-03-02 06:05:15 +00:00
}
} ;
2023-06-05 09:04:23 +00:00
if err . is_some ( ) {
error = err ;
}
2021-04-19 17:28:41 +00:00
// check if the whole original syntax is replaced
2023-07-17 13:49:15 +00:00
if call . syntax ( ) = = & original {
return Ok ( ExpandResult { value : value . zip ( Some ( mapping ) ) , err : error } ) ;
2021-01-03 09:56:59 +00:00
}
2023-06-05 09:04:23 +00:00
if let Some ( insert ) = value {
2023-07-17 13:49:15 +00:00
offset + = u32 ::from ( insert . text_range ( ) . len ( ) ) as i32
- u32 ::from ( call . syntax ( ) . text_range ( ) . len ( ) ) as i32 ;
replacements . push ( ( call , insert ) ) ;
2023-06-05 09:04:23 +00:00
}
2020-03-02 06:05:15 +00:00
}
2021-04-19 17:28:41 +00:00
replacements . into_iter ( ) . rev ( ) . for_each ( | ( old , new ) | ted ::replace ( old . syntax ( ) , new ) ) ;
2023-07-17 13:49:15 +00:00
Ok ( ExpandResult { value : Some ( ( original , mapping ) ) , err : error } )
2020-03-02 06:05:15 +00:00
}