fix: Simplify macro statement expansion handling

This commit is contained in:
Lukas Wirth 2022-07-01 14:43:57 +02:00
parent f8c416e1b9
commit 531e152390
10 changed files with 155 additions and 110 deletions

View File

@ -19,7 +19,6 @@ use la_arena::{Arena, ArenaMap};
use limit::Limit;
use profile::Count;
use rustc_hash::FxHashMap;
use smallvec::SmallVec;
use syntax::{ast, AstNode, AstPtr, SyntaxNodePtr};
use crate::{
@ -294,10 +293,6 @@ pub struct BodySourceMap {
field_map: FxHashMap<InFile<AstPtr<ast::RecordExprField>>, ExprId>,
field_map_back: FxHashMap<ExprId, InFile<AstPtr<ast::RecordExprField>>>,
/// Maps a macro call to its lowered expressions, a single one if it expands to an expression,
/// or multiple if it expands to MacroStmts.
macro_call_to_exprs: FxHashMap<InFile<AstPtr<ast::MacroCall>>, SmallVec<[ExprId; 1]>>,
expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, HirFileId>,
/// Diagnostics accumulated during body lowering. These contain `AstPtr`s and so are stored in
@ -466,9 +461,9 @@ impl BodySourceMap {
self.field_map.get(&src).cloned()
}
pub fn macro_expansion_expr(&self, node: InFile<&ast::MacroCall>) -> Option<&[ExprId]> {
let src = node.map(AstPtr::new);
self.macro_call_to_exprs.get(&src).map(|it| &**it)
pub fn macro_expansion_expr(&self, node: InFile<&ast::MacroExpr>) -> Option<ExprId> {
let src = node.map(AstPtr::new).map(AstPtr::upcast::<ast::MacroExpr>).map(AstPtr::upcast);
self.expr_map.get(&src).copied()
}
/// Get a reference to the body source map's diagnostics.

View File

@ -13,7 +13,6 @@ use hir_expand::{
use la_arena::Arena;
use profile::Count;
use rustc_hash::FxHashMap;
use smallvec::smallvec;
use syntax::{
ast::{
self, ArrayExprKind, AstChildren, HasArgList, HasLoopBody, HasName, LiteralKind,
@ -97,7 +96,6 @@ pub(super) fn lower(
or_pats: Default::default(),
},
expander,
statements_in_scope: Vec::new(),
name_to_pat_grouping: Default::default(),
is_lowering_inside_or_pat: false,
}
@ -109,7 +107,6 @@ struct ExprCollector<'a> {
expander: Expander,
body: Body,
source_map: BodySourceMap,
statements_in_scope: Vec<Statement>,
// a poor-mans union-find?
name_to_pat_grouping: FxHashMap<Name, Vec<PatId>>,
is_lowering_inside_or_pat: bool,
@ -514,27 +511,25 @@ impl ExprCollector<'_> {
ast::Expr::MacroExpr(e) => {
let e = e.macro_call()?;
let macro_ptr = AstPtr::new(&e);
let id = self.collect_macro_call(e, macro_ptr.clone(), true, |this, expansion| {
let id = self.collect_macro_call(e, macro_ptr, true, |this, expansion| {
expansion.map(|it| this.collect_expr(it))
});
match id {
Some(id) => {
self.source_map
.macro_call_to_exprs
.insert(self.expander.to_source(macro_ptr), smallvec![id]);
// Make the macro-call point to its expanded expression so we can query
// semantics on syntax pointers to the macro
let src = self.expander.to_source(syntax_ptr);
self.source_map.expr_map.insert(src, id);
id
}
None => self.alloc_expr(Expr::Missing, syntax_ptr.clone()),
None => self.alloc_expr(Expr::Missing, syntax_ptr),
}
}
ast::Expr::MacroStmts(e) => {
e.statements().for_each(|s| self.collect_stmt(s));
let tail = e
.expr()
.map(|e| self.collect_expr(e))
.unwrap_or_else(|| self.alloc_expr(Expr::Missing, syntax_ptr.clone()));
let statements = e.statements().filter_map(|s| self.collect_stmt(s)).collect();
let tail = e.expr().map(|e| self.collect_expr(e));
self.alloc_expr(Expr::MacroStmts { tail }, syntax_ptr)
self.alloc_expr(Expr::MacroStmts { tail, statements }, syntax_ptr)
}
ast::Expr::UnderscoreExpr(_) => self.alloc_expr(Expr::Underscore, syntax_ptr),
})
@ -607,11 +602,11 @@ impl ExprCollector<'_> {
}
}
fn collect_stmt(&mut self, s: ast::Stmt) {
fn collect_stmt(&mut self, s: ast::Stmt) -> Option<Statement> {
match s {
ast::Stmt::LetStmt(stmt) => {
if self.check_cfg(&stmt).is_none() {
return;
return None;
}
let pat = self.collect_pat_opt(stmt.pat());
let type_ref =
@ -621,70 +616,62 @@ impl ExprCollector<'_> {
.let_else()
.and_then(|let_else| let_else.block_expr())
.map(|block| self.collect_block(block));
self.statements_in_scope.push(Statement::Let {
pat,
type_ref,
initializer,
else_branch,
});
Some(Statement::Let { pat, type_ref, initializer, else_branch })
}
ast::Stmt::ExprStmt(stmt) => {
if let Some(expr) = stmt.expr() {
if self.check_cfg(&expr).is_none() {
return;
let expr = stmt.expr();
if let Some(expr) = &expr {
if self.check_cfg(expr).is_none() {
return None;
}
}
let has_semi = stmt.semicolon_token().is_some();
// Note that macro could be expended to multiple statements
if let Some(ast::Expr::MacroExpr(e)) = stmt.expr() {
let m = match e.macro_call() {
Some(it) => it,
None => return,
};
let macro_ptr = AstPtr::new(&m);
let syntax_ptr = AstPtr::new(&stmt.expr().unwrap());
let prev_stmt = self.statements_in_scope.len();
self.collect_macro_call(m, macro_ptr.clone(), false, |this, expansion| {
match expansion {
// Note that macro could be expanded to multiple statements
if let Some(expr @ ast::Expr::MacroExpr(mac)) = &expr {
let mac_call = mac.macro_call()?;
let syntax_ptr = AstPtr::new(expr);
let macro_ptr = AstPtr::new(&mac_call);
// let prev_stmt = self.statements_in_scope.len();
let stmt = self.collect_macro_call(
mac_call,
macro_ptr,
false,
|this, expansion: Option<ast::MacroStmts>| match expansion {
Some(expansion) => {
let statements: ast::MacroStmts = expansion;
let statements = expansion
.statements()
.filter_map(|stmt| this.collect_stmt(stmt))
.collect();
let tail = expansion.expr().map(|expr| this.collect_expr(expr));
statements.statements().for_each(|stmt| this.collect_stmt(stmt));
if let Some(expr) = statements.expr() {
let expr = this.collect_expr(expr);
this.statements_in_scope
.push(Statement::Expr { expr, has_semi });
}
}
None => {
let expr = this.alloc_expr(Expr::Missing, syntax_ptr.clone());
this.statements_in_scope.push(Statement::Expr { expr, has_semi });
}
}
});
let mac_stmts = this.alloc_expr(
Expr::MacroStmts { tail, statements },
AstPtr::new(&ast::Expr::MacroStmts(expansion)),
);
let mut macro_exprs = smallvec![];
for stmt in &self.statements_in_scope[prev_stmt..] {
match *stmt {
Statement::Let { initializer, else_branch, .. } => {
macro_exprs.extend(initializer);
macro_exprs.extend(else_branch);
Some(mac_stmts)
}
Statement::Expr { expr, .. } => macro_exprs.push(expr),
None => None,
},
);
let expr = match stmt {
Some(expr) => {
// Make the macro-call point to its expanded expression so we can query
// semantics on syntax pointers to the macro
let src = self.expander.to_source(syntax_ptr);
self.source_map.expr_map.insert(src, expr);
expr
}
}
if !macro_exprs.is_empty() {
self.source_map
.macro_call_to_exprs
.insert(self.expander.to_source(macro_ptr), macro_exprs);
}
None => self.alloc_expr(Expr::Missing, syntax_ptr),
};
Some(Statement::Expr { expr, has_semi })
} else {
let expr = self.collect_expr_opt(stmt.expr());
self.statements_in_scope.push(Statement::Expr { expr, has_semi });
let expr = self.collect_expr_opt(expr);
Some(Statement::Expr { expr, has_semi })
}
}
ast::Stmt::Item(_item) => {}
ast::Stmt::Item(_item) => None,
}
}
@ -703,22 +690,10 @@ impl ExprCollector<'_> {
};
let prev_def_map = mem::replace(&mut self.expander.def_map, def_map);
let prev_local_module = mem::replace(&mut self.expander.module, module);
let prev_statements = std::mem::take(&mut self.statements_in_scope);
block.statements().for_each(|s| self.collect_stmt(s));
block.tail_expr().and_then(|e| {
let expr = self.maybe_collect_expr(e)?;
self.statements_in_scope.push(Statement::Expr { expr, has_semi: false });
Some(())
});
let statements = block.statements().filter_map(|s| self.collect_stmt(s)).collect();
let tail = block.tail_expr().and_then(|e| self.maybe_collect_expr(e));
let mut tail = None;
if let Some(Statement::Expr { expr, has_semi: false }) = self.statements_in_scope.last() {
tail = Some(*expr);
self.statements_in_scope.pop();
}
let tail = tail;
let statements = std::mem::replace(&mut self.statements_in_scope, prev_statements).into();
let syntax_node_ptr = AstPtr::new(&block.into());
let expr_id = self.alloc_expr(
Expr::Block { id: block_id, statements, tail, label: None },
@ -903,10 +878,12 @@ impl ExprCollector<'_> {
ast::Pat::MacroPat(mac) => match mac.macro_call() {
Some(call) => {
let macro_ptr = AstPtr::new(&call);
let src = self.expander.to_source(Either::Left(AstPtr::new(&pat)));
let pat =
self.collect_macro_call(call, macro_ptr, true, |this, expanded_pat| {
this.collect_pat_opt_(expanded_pat)
});
self.source_map.pat_map.insert(src, pat);
return pat;
}
None => Pat::Missing,

View File

@ -199,7 +199,8 @@ pub enum Expr {
body: ExprId,
},
MacroStmts {
tail: ExprId,
statements: Box<[Statement]>,
tail: Option<ExprId>,
},
Array(Array),
Literal(Literal),
@ -254,7 +255,7 @@ impl Expr {
Expr::Let { expr, .. } => {
f(*expr);
}
Expr::Block { statements, tail, .. } => {
Expr::MacroStmts { tail, statements } | Expr::Block { statements, tail, .. } => {
for stmt in statements.iter() {
match stmt {
Statement::Let { initializer, .. } => {
@ -344,7 +345,6 @@ impl Expr {
f(*repeat)
}
},
Expr::MacroStmts { tail } => f(*tail),
Expr::Literal(_) => {}
Expr::Underscore => {}
}

View File

@ -774,7 +774,9 @@ impl<'a> InferenceContext<'a> {
None => self.table.new_float_var(),
},
},
Expr::MacroStmts { tail } => self.infer_expr_inner(*tail, expected),
Expr::MacroStmts { tail, statements } => {
self.infer_block(tgt_expr, statements, *tail, expected)
}
Expr::Underscore => {
// Underscore expressions may only appear in assignee expressions,
// which are handled by `infer_assignee_expr()`, so any underscore

View File

@ -2223,6 +2223,7 @@ impl Local {
let src = source_map.pat_syntax(self.pat_id).unwrap(); // Hmm...
let root = src.file_syntax(db.upcast());
src.map(|ast| match ast {
// Suspicious unwrap
Either::Left(it) => Either::Left(it.cast().unwrap().to_node(&root)),
Either::Right(it) => Either::Right(it.to_node(&root)),
})

View File

@ -539,24 +539,26 @@ impl SourceAnalyzer {
_ => (),
}
}
let macro_expr = match macro_call
.map(|it| it.syntax().parent().and_then(ast::MacroExpr::cast))
.transpose()
{
Some(it) => it,
None => return false,
};
if let (Some((def, body, sm)), Some(infer)) = (&self.def, &self.infer) {
if let Some(expr_ids) = sm.macro_expansion_expr(macro_call) {
if let Some(expanded_expr) = sm.macro_expansion_expr(macro_expr.as_ref()) {
let mut is_unsafe = false;
for &expr_id in expr_ids {
unsafe_expressions(
db,
infer,
*def,
body,
expr_id,
&mut |UnsafeExpr { inside_unsafe_block, .. }| {
is_unsafe |= !inside_unsafe_block
},
);
if is_unsafe {
return true;
}
}
unsafe_expressions(
db,
infer,
*def,
body,
expanded_expr,
&mut |UnsafeExpr { inside_unsafe_block, .. }| is_unsafe |= !inside_unsafe_block,
);
return is_unsafe;
}
}
false

View File

@ -810,3 +810,33 @@ fn main() {
"#]],
)
}
#[test]
fn regression_12644() {
check(
r#"
macro_rules! __rust_force_expr {
($e:expr) => {
$e
};
}
macro_rules! vec {
($elem:expr) => {
__rust_force_expr!($elem)
};
}
struct Struct;
impl Struct {
fn foo(self) {}
}
fn f() {
vec![Struct].$0;
}
"#,
expect![[r#"
me foo() fn(self)
"#]],
);
}

View File

@ -689,7 +689,7 @@ fn foo() ->$0 u32 {
never();
// ^^^^^^^
never!();
// FIXME sema doesn't give us types for macrocalls
// ^^^^^^^^
Never.never();
// ^^^^^^^^^^^^^

View File

@ -4920,3 +4920,34 @@ impl T for () {
"#]],
);
}
#[test]
fn hover_ranged_macro_call() {
check_hover_range(
r#"
macro_rules! __rust_force_expr {
($e:expr) => {
$e
};
}
macro_rules! vec {
($elem:expr) => {
__rust_force_expr!($elem)
};
}
struct Struct;
impl Struct {
fn foo(self) {}
}
fn f() {
$0vec![Struct]$0;
}
"#,
expect![[r#"
```rust
Struct
```"#]],
);
}

View File

@ -67,6 +67,13 @@ impl<N: AstNode> AstPtr<N> {
Some(AstPtr { raw: self.raw, _ty: PhantomData })
}
pub fn upcast<M: AstNode>(self) -> AstPtr<M>
where
N: Into<M>,
{
AstPtr { raw: self.raw, _ty: PhantomData }
}
/// Like `SyntaxNodePtr::cast` but the trait bounds work out.
pub fn try_from_raw(raw: SyntaxNodePtr) -> Option<AstPtr<N>> {
N::can_cast(raw.kind()).then(|| AstPtr { raw, _ty: PhantomData })