Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit fedf724

Browse files
bors[bot]Veykril
andauthored
11774: feat: Tag macro calls as unsafe if they expand to unsafe expressions r=Veykril a=Veykril as long as they aren't inside an unsafe block inside the macro that is. Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
2 parents 966b692 + 3b7b223 commit fedf724

25 files changed

+215
-80
lines changed

crates/hir/src/semantics.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,10 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
358358
self.imp.resolve_macro_call(macro_call)
359359
}
360360

361+
pub fn is_unsafe_macro_call(&self, macro_call: &ast::MacroCall) -> bool {
362+
self.imp.is_unsafe_macro_call(macro_call)
363+
}
364+
361365
pub fn resolve_attr_macro_call(&self, item: &ast::Item) -> Option<Macro> {
362366
self.imp.resolve_attr_macro_call(item)
363367
}
@@ -961,6 +965,12 @@ impl<'db> SemanticsImpl<'db> {
961965
sa.resolve_macro_call(self.db, macro_call)
962966
}
963967

968+
fn is_unsafe_macro_call(&self, macro_call: &ast::MacroCall) -> bool {
969+
let sa = self.analyze(macro_call.syntax());
970+
let macro_call = self.find_file(macro_call.syntax()).with_value(macro_call);
971+
sa.is_unsafe_macro_call(self.db, macro_call)
972+
}
973+
964974
fn resolve_attr_macro_call(&self, item: &ast::Item) -> Option<Macro> {
965975
let item_in_file = self.wrap_node_infile(item.clone());
966976
let id = self.with_ctx(|ctx| {

crates/hir/src/source_analyzer.rs

Lines changed: 51 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ use hir_def::{
2525
};
2626
use hir_expand::{hygiene::Hygiene, name::AsName, HirFileId, InFile};
2727
use hir_ty::{
28-
diagnostics::{record_literal_missing_fields, record_pattern_missing_fields},
28+
diagnostics::{
29+
record_literal_missing_fields, record_pattern_missing_fields, unsafe_expressions,
30+
UnsafeExpr,
31+
},
2932
Adjust, Adjustment, AutoBorrow, InferenceResult, Interner, Substitution, TyExt,
3033
TyLoweringContext,
3134
};
@@ -46,8 +49,7 @@ use base_db::CrateId;
4649
pub(crate) struct SourceAnalyzer {
4750
pub(crate) file_id: HirFileId,
4851
pub(crate) resolver: Resolver,
49-
body: Option<Arc<Body>>,
50-
body_source_map: Option<Arc<BodySourceMap>>,
52+
def: Option<(DefWithBodyId, Arc<Body>, Arc<BodySourceMap>)>,
5153
infer: Option<Arc<InferenceResult>>,
5254
}
5355

@@ -67,8 +69,7 @@ impl SourceAnalyzer {
6769
let resolver = resolver_for_scope(db.upcast(), def, scope);
6870
SourceAnalyzer {
6971
resolver,
70-
body: Some(body),
71-
body_source_map: Some(source_map),
72+
def: Some((def, body, source_map)),
7273
infer: Some(db.infer(def)),
7374
file_id,
7475
}
@@ -87,26 +88,21 @@ impl SourceAnalyzer {
8788
Some(offset) => scope_for_offset(db, &scopes, &source_map, node.with_value(offset)),
8889
};
8990
let resolver = resolver_for_scope(db.upcast(), def, scope);
90-
SourceAnalyzer {
91-
resolver,
92-
body: Some(body),
93-
body_source_map: Some(source_map),
94-
infer: None,
95-
file_id,
96-
}
91+
SourceAnalyzer { resolver, def: Some((def, body, source_map)), infer: None, file_id }
9792
}
9893

9994
pub(crate) fn new_for_resolver(
10095
resolver: Resolver,
10196
node: InFile<&SyntaxNode>,
10297
) -> SourceAnalyzer {
103-
SourceAnalyzer {
104-
resolver,
105-
body: None,
106-
body_source_map: None,
107-
infer: None,
108-
file_id: node.file_id,
109-
}
98+
SourceAnalyzer { resolver, def: None, infer: None, file_id: node.file_id }
99+
}
100+
101+
fn body_source_map(&self) -> Option<&BodySourceMap> {
102+
self.def.as_ref().map(|(.., source_map)| &**source_map)
103+
}
104+
fn body(&self) -> Option<&Body> {
105+
self.def.as_ref().map(|(_, body, _)| &**body)
110106
}
111107

112108
fn expr_id(&self, db: &dyn HirDatabase, expr: &ast::Expr) -> Option<ExprId> {
@@ -116,22 +112,22 @@ impl SourceAnalyzer {
116112
}
117113
_ => InFile::new(self.file_id, expr.clone()),
118114
};
119-
let sm = self.body_source_map.as_ref()?;
115+
let sm = self.body_source_map()?;
120116
sm.node_expr(src.as_ref())
121117
}
122118

123119
fn pat_id(&self, pat: &ast::Pat) -> Option<PatId> {
124120
// FIXME: macros, see `expr_id`
125121
let src = InFile { file_id: self.file_id, value: pat };
126-
self.body_source_map.as_ref()?.node_pat(src)
122+
self.body_source_map()?.node_pat(src)
127123
}
128124

129125
fn expand_expr(
130126
&self,
131127
db: &dyn HirDatabase,
132128
expr: InFile<ast::MacroCall>,
133129
) -> Option<InFile<ast::Expr>> {
134-
let macro_file = self.body_source_map.as_ref()?.node_macro_file(expr.as_ref())?;
130+
let macro_file = self.body_source_map()?.node_macro_file(expr.as_ref())?;
135131
let expanded = db.parse_or_expand(macro_file)?;
136132

137133
let res = match ast::MacroCall::cast(expanded.clone()) {
@@ -196,7 +192,7 @@ impl SourceAnalyzer {
196192
param: &ast::SelfParam,
197193
) -> Option<Type> {
198194
let src = InFile { file_id: self.file_id, value: param };
199-
let pat_id = self.body_source_map.as_ref()?.node_self_param(src)?;
195+
let pat_id = self.body_source_map()?.node_self_param(src)?;
200196
let ty = self.infer.as_ref()?[pat_id].clone();
201197
Type::new_with_resolver(db, &self.resolver, ty)
202198
}
@@ -226,7 +222,7 @@ impl SourceAnalyzer {
226222
) -> Option<(Field, Option<Local>, Type)> {
227223
let record_expr = ast::RecordExpr::cast(field.syntax().parent().and_then(|p| p.parent())?)?;
228224
let expr = ast::Expr::from(record_expr);
229-
let expr_id = self.body_source_map.as_ref()?.node_expr(InFile::new(self.file_id, &expr))?;
225+
let expr_id = self.body_source_map()?.node_expr(InFile::new(self.file_id, &expr))?;
230226

231227
let local_name = field.field_name()?.as_name();
232228
let local = if field.name_ref().is_some() {
@@ -279,7 +275,7 @@ impl SourceAnalyzer {
279275
pat: &ast::IdentPat,
280276
) -> Option<ModuleDef> {
281277
let pat_id = self.pat_id(&pat.clone().into())?;
282-
let body = self.body.as_ref()?;
278+
let body = self.body()?;
283279
let path = match &body[pat_id] {
284280
Pat::Path(path) => path,
285281
_ => return None,
@@ -415,7 +411,7 @@ impl SourceAnalyzer {
415411
literal: &ast::RecordExpr,
416412
) -> Option<Vec<(Field, Type)>> {
417413
let krate = self.resolver.krate()?;
418-
let body = self.body.as_ref()?;
414+
let body = self.body()?;
419415
let infer = self.infer.as_ref()?;
420416

421417
let expr_id = self.expr_id(db, &literal.clone().into())?;
@@ -433,7 +429,7 @@ impl SourceAnalyzer {
433429
pattern: &ast::RecordPat,
434430
) -> Option<Vec<(Field, Type)>> {
435431
let krate = self.resolver.krate()?;
436-
let body = self.body.as_ref()?;
432+
let body = self.body()?;
437433
let infer = self.infer.as_ref()?;
438434

439435
let pat_id = self.pat_id(&pattern.clone().into())?;
@@ -488,6 +484,34 @@ impl SourceAnalyzer {
488484
let expr_id = self.expr_id(db, &record_lit.into())?;
489485
infer.variant_resolution_for_expr(expr_id)
490486
}
487+
488+
pub(crate) fn is_unsafe_macro_call(
489+
&self,
490+
db: &dyn HirDatabase,
491+
macro_call: InFile<&ast::MacroCall>,
492+
) -> bool {
493+
if let (Some((def, body, sm)), Some(infer)) = (&self.def, &self.infer) {
494+
if let Some(expr_ids) = sm.macro_expansion_expr(macro_call) {
495+
let mut is_unsafe = false;
496+
for &expr_id in expr_ids {
497+
unsafe_expressions(
498+
db,
499+
infer,
500+
*def,
501+
body,
502+
expr_id,
503+
&mut |UnsafeExpr { inside_unsafe_block, .. }| {
504+
is_unsafe |= !inside_unsafe_block
505+
},
506+
);
507+
if is_unsafe {
508+
return true;
509+
}
510+
}
511+
}
512+
}
513+
false
514+
}
491515
}
492516

493517
fn scope_for(

crates/hir_def/src/body.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use la_arena::{Arena, ArenaMap};
1919
use limit::Limit;
2020
use profile::Count;
2121
use rustc_hash::FxHashMap;
22+
use smallvec::SmallVec;
2223
use syntax::{ast, AstNode, AstPtr, SyntaxNodePtr};
2324

2425
use crate::{
@@ -293,6 +294,10 @@ pub struct BodySourceMap {
293294
field_map: FxHashMap<InFile<AstPtr<ast::RecordExprField>>, ExprId>,
294295
field_map_back: FxHashMap<ExprId, InFile<AstPtr<ast::RecordExprField>>>,
295296

297+
/// Maps a macro call to its lowered expressions, a single one if it expands to an expression,
298+
/// or multiple if it expands to MacroStmts.
299+
macro_call_to_exprs: FxHashMap<InFile<AstPtr<ast::MacroCall>>, SmallVec<[ExprId; 1]>>,
300+
296301
expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, HirFileId>,
297302

298303
/// Diagnostics accumulated during body lowering. These contain `AstPtr`s and so are stored in
@@ -461,6 +466,11 @@ impl BodySourceMap {
461466
self.field_map.get(&src).cloned()
462467
}
463468

469+
pub fn macro_expansion_expr(&self, node: InFile<&ast::MacroCall>) -> Option<&[ExprId]> {
470+
let src = node.map(AstPtr::new);
471+
self.macro_call_to_exprs.get(&src).map(|it| &**it)
472+
}
473+
464474
/// Get a reference to the body source map's diagnostics.
465475
pub fn diagnostics(&self) -> &[BodyDiagnostic] {
466476
&self.diagnostics

crates/hir_def/src/body/lower.rs

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use hir_expand::{
1313
use la_arena::Arena;
1414
use profile::Count;
1515
use rustc_hash::FxHashMap;
16+
use smallvec::smallvec;
1617
use syntax::{
1718
ast::{
1819
self, ArrayExprKind, AstChildren, HasArgList, HasLoopBody, HasName, LiteralKind,
@@ -507,14 +508,18 @@ impl ExprCollector<'_> {
507508
}
508509
ast::Expr::MacroCall(e) => {
509510
let macro_ptr = AstPtr::new(&e);
510-
let mut ids = None;
511-
self.collect_macro_call(e, macro_ptr, true, |this, expansion| {
512-
ids.get_or_insert(match expansion {
513-
Some(it) => this.collect_expr(it),
514-
None => this.alloc_expr(Expr::Missing, syntax_ptr.clone()),
515-
});
511+
let id = self.collect_macro_call(e, macro_ptr.clone(), true, |this, expansion| {
512+
expansion.map(|it| this.collect_expr(it))
516513
});
517-
ids.unwrap_or_else(|| self.alloc_expr(Expr::Missing, syntax_ptr.clone()))
514+
match id {
515+
Some(id) => {
516+
self.source_map
517+
.macro_call_to_exprs
518+
.insert(self.expander.to_source(macro_ptr), smallvec![id]);
519+
id
520+
}
521+
None => self.alloc_expr(Expr::Missing, syntax_ptr.clone()),
522+
}
518523
}
519524
ast::Expr::MacroStmts(e) => {
520525
e.statements().for_each(|s| self.collect_stmt(s));
@@ -529,13 +534,17 @@ impl ExprCollector<'_> {
529534
})
530535
}
531536

532-
fn collect_macro_call<F: FnMut(&mut Self, Option<T>), T: ast::AstNode>(
537+
fn collect_macro_call<F, T, U>(
533538
&mut self,
534539
mcall: ast::MacroCall,
535540
syntax_ptr: AstPtr<ast::MacroCall>,
536541
record_diagnostics: bool,
537-
mut collector: F,
538-
) {
542+
collector: F,
543+
) -> U
544+
where
545+
F: FnOnce(&mut Self, Option<T>) -> U,
546+
T: ast::AstNode,
547+
{
539548
// File containing the macro call. Expansion errors will be attached here.
540549
let outer_file = self.expander.current_file_id;
541550

@@ -551,8 +560,7 @@ impl ExprCollector<'_> {
551560
path,
552561
});
553562
}
554-
collector(self, None);
555-
return;
563+
return collector(self, None);
556564
}
557565
};
558566

@@ -625,11 +633,9 @@ impl ExprCollector<'_> {
625633
let macro_ptr = AstPtr::new(&m);
626634
let syntax_ptr = AstPtr::new(&stmt.expr().unwrap());
627635

628-
self.collect_macro_call(
629-
m,
630-
macro_ptr,
631-
false,
632-
|this, expansion| match expansion {
636+
let prev_stmt = self.statements_in_scope.len();
637+
self.collect_macro_call(m, macro_ptr.clone(), false, |this, expansion| {
638+
match expansion {
633639
Some(expansion) => {
634640
let statements: ast::MacroStmts = expansion;
635641

@@ -644,8 +650,24 @@ impl ExprCollector<'_> {
644650
let expr = this.alloc_expr(Expr::Missing, syntax_ptr.clone());
645651
this.statements_in_scope.push(Statement::Expr { expr, has_semi });
646652
}
647-
},
648-
);
653+
}
654+
});
655+
656+
let mut macro_exprs = smallvec![];
657+
for stmt in &self.statements_in_scope[prev_stmt..] {
658+
match *stmt {
659+
Statement::Let { initializer, else_branch, .. } => {
660+
macro_exprs.extend(initializer);
661+
macro_exprs.extend(else_branch);
662+
}
663+
Statement::Expr { expr, .. } => macro_exprs.push(expr),
664+
}
665+
}
666+
if !macro_exprs.is_empty() {
667+
self.source_map
668+
.macro_call_to_exprs
669+
.insert(self.expander.to_source(macro_ptr), macro_exprs);
670+
}
649671
} else {
650672
let expr = self.collect_expr_opt(stmt.expr());
651673
self.statements_in_scope.push(Statement::Expr { expr, has_semi });
@@ -870,15 +892,11 @@ impl ExprCollector<'_> {
870892
ast::Pat::MacroPat(mac) => match mac.macro_call() {
871893
Some(call) => {
872894
let macro_ptr = AstPtr::new(&call);
873-
let mut pat = None;
874-
self.collect_macro_call(call, macro_ptr, true, |this, expanded_pat| {
875-
pat = Some(this.collect_pat_opt_(expanded_pat));
876-
});
877-
878-
match pat {
879-
Some(pat) => return pat,
880-
None => Pat::Missing,
881-
}
895+
let pat =
896+
self.collect_macro_call(call, macro_ptr, true, |this, expanded_pat| {
897+
this.collect_pat_opt_(expanded_pat)
898+
});
899+
return pat;
882900
}
883901
None => Pat::Missing,
884902
},

crates/hir_ty/src/diagnostics.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ pub use crate::diagnostics::{
99
expr::{
1010
record_literal_missing_fields, record_pattern_missing_fields, BodyValidationDiagnostic,
1111
},
12-
unsafe_check::missing_unsafe,
12+
unsafe_check::{missing_unsafe, unsafe_expressions, UnsafeExpr},
1313
};

0 commit comments

Comments
 (0)