about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLukas Wirth <lukastw97@gmail.com>2022-03-20 19:07:44 +0100
committerLukas Wirth <lukastw97@gmail.com>2022-03-20 19:07:44 +0100
commit68de7b30e04ed56c7acd61f5aecc5f447691be12 (patch)
tree17dc643d643117d1bbbb081d8204ff8688fd5c64
parent5a87f09a714b8299897a72ecfe206460903d703e (diff)
downloadrust-68de7b30e04ed56c7acd61f5aecc5f447691be12.tar.gz
rust-68de7b30e04ed56c7acd61f5aecc5f447691be12.zip
feat: Tag macro calls as unsafe if they expand to unsafe expressions
-rw-r--r--crates/hir/src/semantics.rs10
-rw-r--r--crates/hir/src/source_analyzer.rs78
-rw-r--r--crates/hir_def/src/body.rs10
-rw-r--r--crates/hir_def/src/body/lower.rs56
-rw-r--r--crates/hir_ty/src/diagnostics.rs2
-rw-r--r--crates/hir_ty/src/diagnostics/unsafe_check.rs49
-rw-r--r--crates/ide/src/syntax_highlighting/highlight.rs10
-rw-r--r--crates/ide/src/syntax_highlighting/html.rs1
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html1
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html1
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html1
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html1
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html1
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html1
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_general.html1
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_injection.html1
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html1
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_lifetimes.html1
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_macros.html1
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_operators.html1
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_rainbow.html1
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_strings.html1
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html21
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs18
-rw-r--r--crates/ide_db/src/syntax_helpers/node_ext.rs7
25 files changed, 208 insertions, 68 deletions
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index c2b7e9bb529..4afdde6494f 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -358,6 +358,10 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
         self.imp.resolve_macro_call(macro_call)
     }
 
+    pub fn is_unsafe_macro_call(&self, macro_call: &ast::MacroCall) -> bool {
+        self.imp.is_unsafe_macro_call(macro_call)
+    }
+
     pub fn resolve_attr_macro_call(&self, item: &ast::Item) -> Option<Macro> {
         self.imp.resolve_attr_macro_call(item)
     }
@@ -961,6 +965,12 @@ impl<'db> SemanticsImpl<'db> {
         sa.resolve_macro_call(self.db, macro_call)
     }
 
+    fn is_unsafe_macro_call(&self, macro_call: &ast::MacroCall) -> bool {
+        let sa = self.analyze(macro_call.syntax());
+        let macro_call = self.find_file(macro_call.syntax()).with_value(macro_call);
+        sa.is_unsafe_macro_call(self.db, macro_call)
+    }
+
     fn resolve_attr_macro_call(&self, item: &ast::Item) -> Option<Macro> {
         let item_in_file = self.wrap_node_infile(item.clone());
         let id = self.with_ctx(|ctx| {
diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs
index 576d063c432..6b2120b2532 100644
--- a/crates/hir/src/source_analyzer.rs
+++ b/crates/hir/src/source_analyzer.rs
@@ -25,7 +25,10 @@ use hir_def::{
 };
 use hir_expand::{hygiene::Hygiene, name::AsName, HirFileId, InFile};
 use hir_ty::{
-    diagnostics::{record_literal_missing_fields, record_pattern_missing_fields},
+    diagnostics::{
+        record_literal_missing_fields, record_pattern_missing_fields, unsafe_expressions,
+        UnsafeExpr,
+    },
     Adjust, Adjustment, AutoBorrow, InferenceResult, Interner, Substitution, TyExt,
     TyLoweringContext,
 };
@@ -46,8 +49,7 @@ use base_db::CrateId;
 pub(crate) struct SourceAnalyzer {
     pub(crate) file_id: HirFileId,
     pub(crate) resolver: Resolver,
-    body: Option<Arc<Body>>,
-    body_source_map: Option<Arc<BodySourceMap>>,
+    def: Option<(DefWithBodyId, Arc<Body>, Arc<BodySourceMap>)>,
     infer: Option<Arc<InferenceResult>>,
 }
 
@@ -67,8 +69,7 @@ impl SourceAnalyzer {
         let resolver = resolver_for_scope(db.upcast(), def, scope);
         SourceAnalyzer {
             resolver,
-            body: Some(body),
-            body_source_map: Some(source_map),
+            def: Some((def, body, source_map)),
             infer: Some(db.infer(def)),
             file_id,
         }
@@ -87,26 +88,21 @@ impl SourceAnalyzer {
             Some(offset) => scope_for_offset(db, &scopes, &source_map, node.with_value(offset)),
         };
         let resolver = resolver_for_scope(db.upcast(), def, scope);
-        SourceAnalyzer {
-            resolver,
-            body: Some(body),
-            body_source_map: Some(source_map),
-            infer: None,
-            file_id,
-        }
+        SourceAnalyzer { resolver, def: Some((def, body, source_map)), infer: None, file_id }
     }
 
     pub(crate) fn new_for_resolver(
         resolver: Resolver,
         node: InFile<&SyntaxNode>,
     ) -> SourceAnalyzer {
-        SourceAnalyzer {
-            resolver,
-            body: None,
-            body_source_map: None,
-            infer: None,
-            file_id: node.file_id,
-        }
+        SourceAnalyzer { resolver, def: None, infer: None, file_id: node.file_id }
+    }
+
+    fn body_source_map(&self) -> Option<&BodySourceMap> {
+        self.def.as_ref().map(|(.., source_map)| &**source_map)
+    }
+    fn body(&self) -> Option<&Body> {
+        self.def.as_ref().map(|(_, body, _)| &**body)
     }
 
     fn expr_id(&self, db: &dyn HirDatabase, expr: &ast::Expr) -> Option<ExprId> {
@@ -116,14 +112,14 @@ impl SourceAnalyzer {
             }
             _ => InFile::new(self.file_id, expr.clone()),
         };
-        let sm = self.body_source_map.as_ref()?;
+        let sm = self.body_source_map()?;
         sm.node_expr(src.as_ref())
     }
 
     fn pat_id(&self, pat: &ast::Pat) -> Option<PatId> {
         // FIXME: macros, see `expr_id`
         let src = InFile { file_id: self.file_id, value: pat };
-        self.body_source_map.as_ref()?.node_pat(src)
+        self.body_source_map()?.node_pat(src)
     }
 
     fn expand_expr(
@@ -131,7 +127,7 @@ impl SourceAnalyzer {
         db: &dyn HirDatabase,
         expr: InFile<ast::MacroCall>,
     ) -> Option<InFile<ast::Expr>> {
-        let macro_file = self.body_source_map.as_ref()?.node_macro_file(expr.as_ref())?;
+        let macro_file = self.body_source_map()?.node_macro_file(expr.as_ref())?;
         let expanded = db.parse_or_expand(macro_file)?;
 
         let res = match ast::MacroCall::cast(expanded.clone()) {
@@ -196,7 +192,7 @@ impl SourceAnalyzer {
         param: &ast::SelfParam,
     ) -> Option<Type> {
         let src = InFile { file_id: self.file_id, value: param };
-        let pat_id = self.body_source_map.as_ref()?.node_self_param(src)?;
+        let pat_id = self.body_source_map()?.node_self_param(src)?;
         let ty = self.infer.as_ref()?[pat_id].clone();
         Type::new_with_resolver(db, &self.resolver, ty)
     }
@@ -226,7 +222,7 @@ impl SourceAnalyzer {
     ) -> Option<(Field, Option<Local>, Type)> {
         let record_expr = ast::RecordExpr::cast(field.syntax().parent().and_then(|p| p.parent())?)?;
         let expr = ast::Expr::from(record_expr);
-        let expr_id = self.body_source_map.as_ref()?.node_expr(InFile::new(self.file_id, &expr))?;
+        let expr_id = self.body_source_map()?.node_expr(InFile::new(self.file_id, &expr))?;
 
         let local_name = field.field_name()?.as_name();
         let local = if field.name_ref().is_some() {
@@ -279,7 +275,7 @@ impl SourceAnalyzer {
         pat: &ast::IdentPat,
     ) -> Option<ModuleDef> {
         let pat_id = self.pat_id(&pat.clone().into())?;
-        let body = self.body.as_ref()?;
+        let body = self.body()?;
         let path = match &body[pat_id] {
             Pat::Path(path) => path,
             _ => return None,
@@ -415,7 +411,7 @@ impl SourceAnalyzer {
         literal: &ast::RecordExpr,
     ) -> Option<Vec<(Field, Type)>> {
         let krate = self.resolver.krate()?;
-        let body = self.body.as_ref()?;
+        let body = self.body()?;
         let infer = self.infer.as_ref()?;
 
         let expr_id = self.expr_id(db, &literal.clone().into())?;
@@ -433,7 +429,7 @@ impl SourceAnalyzer {
         pattern: &ast::RecordPat,
     ) -> Option<Vec<(Field, Type)>> {
         let krate = self.resolver.krate()?;
-        let body = self.body.as_ref()?;
+        let body = self.body()?;
         let infer = self.infer.as_ref()?;
 
         let pat_id = self.pat_id(&pattern.clone().into())?;
@@ -488,6 +484,34 @@ impl SourceAnalyzer {
         let expr_id = self.expr_id(db, &record_lit.into())?;
         infer.variant_resolution_for_expr(expr_id)
     }
+
+    pub(crate) fn is_unsafe_macro_call(
+        &self,
+        db: &dyn HirDatabase,
+        macro_call: InFile<&ast::MacroCall>,
+    ) -> bool {
+        if let (Some((def, body, sm)), Some(infer)) = (&self.def, &self.infer) {
+            if let Some(expr_ids) = sm.macro_expansion_expr(macro_call) {
+                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;
+                    }
+                }
+            }
+        }
+        false
+    }
 }
 
 fn scope_for(
diff --git a/crates/hir_def/src/body.rs b/crates/hir_def/src/body.rs
index ebb15e20cba..57dd3a3502f 100644
--- a/crates/hir_def/src/body.rs
+++ b/crates/hir_def/src/body.rs
@@ -19,6 +19,7 @@ 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::{
@@ -293,6 +294,10 @@ 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
@@ -461,6 +466,11 @@ 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)
+    }
+
     /// Get a reference to the body source map's diagnostics.
     pub fn diagnostics(&self) -> &[BodyDiagnostic] {
         &self.diagnostics
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index 512a9312a77..661486d6e01 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -13,6 +13,7 @@ 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,
@@ -507,14 +508,21 @@ impl ExprCollector<'_> {
             }
             ast::Expr::MacroCall(e) => {
                 let macro_ptr = AstPtr::new(&e);
-                let mut ids = None;
-                self.collect_macro_call(e, macro_ptr, true, |this, expansion| {
-                    ids.get_or_insert(match expansion {
-                        Some(it) => this.collect_expr(it),
-                        None => this.alloc_expr(Expr::Missing, syntax_ptr.clone()),
-                    });
+                let mut id = None;
+                self.collect_macro_call(e, macro_ptr.clone(), true, |this, expansion| {
+                    if let Some(it) = expansion {
+                        id.get_or_insert(this.collect_expr(it));
+                    }
                 });
-                ids.unwrap_or_else(|| self.alloc_expr(Expr::Missing, syntax_ptr.clone()))
+                match id {
+                    Some(id) => {
+                        self.source_map
+                            .macro_call_to_exprs
+                            .insert(self.expander.to_source(macro_ptr), smallvec![id]);
+                        id
+                    }
+                    None => self.alloc_expr(Expr::Missing, syntax_ptr.clone()),
+                }
             }
             ast::Expr::MacroStmts(e) => {
                 e.statements().for_each(|s| self.collect_stmt(s));
@@ -529,12 +537,12 @@ impl ExprCollector<'_> {
         })
     }
 
-    fn collect_macro_call<F: FnMut(&mut Self, Option<T>), T: ast::AstNode>(
+    fn collect_macro_call<F: FnOnce(&mut Self, Option<T>), T: ast::AstNode>(
         &mut self,
         mcall: ast::MacroCall,
         syntax_ptr: AstPtr<ast::MacroCall>,
         record_diagnostics: bool,
-        mut collector: F,
+        collector: F,
     ) {
         // File containing the macro call. Expansion errors will be attached here.
         let outer_file = self.expander.current_file_id;
@@ -625,17 +633,17 @@ impl ExprCollector<'_> {
                     let macro_ptr = AstPtr::new(&m);
                     let syntax_ptr = AstPtr::new(&stmt.expr().unwrap());
 
-                    self.collect_macro_call(
-                        m,
-                        macro_ptr,
-                        false,
-                        |this, expansion| match expansion {
+                    let prev_stmt = self.statements_in_scope.len();
+                    let mut tail = None;
+                    self.collect_macro_call(m, macro_ptr.clone(), false, |this, expansion| {
+                        match expansion {
                             Some(expansion) => {
                                 let statements: ast::MacroStmts = expansion;
 
                                 statements.statements().for_each(|stmt| this.collect_stmt(stmt));
                                 if let Some(expr) = statements.expr() {
                                     let expr = this.collect_expr(expr);
+                                    tail = Some(expr);
                                     this.statements_in_scope
                                         .push(Statement::Expr { expr, has_semi });
                                 }
@@ -644,8 +652,24 @@ impl ExprCollector<'_> {
                                 let expr = this.alloc_expr(Expr::Missing, syntax_ptr.clone());
                                 this.statements_in_scope.push(Statement::Expr { expr, has_semi });
                             }
-                        },
-                    );
+                        }
+                    });
+                    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);
+                            }
+                            Statement::Expr { expr, .. } => macro_exprs.push(expr),
+                        }
+                    }
+                    macro_exprs.extend(tail);
+                    if !macro_exprs.is_empty() {
+                        self.source_map
+                            .macro_call_to_exprs
+                            .insert(self.expander.to_source(macro_ptr), macro_exprs);
+                    }
                 } else {
                     let expr = self.collect_expr_opt(stmt.expr());
                     self.statements_in_scope.push(Statement::Expr { expr, has_semi });
diff --git a/crates/hir_ty/src/diagnostics.rs b/crates/hir_ty/src/diagnostics.rs
index a4702715e5b..37eb06be1d3 100644
--- a/crates/hir_ty/src/diagnostics.rs
+++ b/crates/hir_ty/src/diagnostics.rs
@@ -9,5 +9,5 @@ pub use crate::diagnostics::{
     expr::{
         record_literal_missing_fields, record_pattern_missing_fields, BodyValidationDiagnostic,
     },
-    unsafe_check::missing_unsafe,
+    unsafe_check::{missing_unsafe, unsafe_expressions, UnsafeExpr},
 };
diff --git a/crates/hir_ty/src/diagnostics/unsafe_check.rs b/crates/hir_ty/src/diagnostics/unsafe_check.rs
index 2a18df253e6..4e0dcf7b618 100644
--- a/crates/hir_ty/src/diagnostics/unsafe_check.rs
+++ b/crates/hir_ty/src/diagnostics/unsafe_check.rs
@@ -12,54 +12,57 @@ use crate::{db::HirDatabase, InferenceResult, Interner, TyExt, TyKind};
 
 pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> Vec<ExprId> {
     let infer = db.infer(def);
+    let mut res = Vec::new();
 
     let is_unsafe = match def {
         DefWithBodyId::FunctionId(it) => db.function_data(it).is_unsafe(),
         DefWithBodyId::StaticId(_) | DefWithBodyId::ConstId(_) => false,
     };
     if is_unsafe {
-        return Vec::new();
+        return res;
     }
 
-    unsafe_expressions(db, &infer, def)
-        .into_iter()
-        .filter(|it| !it.inside_unsafe_block)
-        .map(|it| it.expr)
-        .collect()
+    let body = db.body(def);
+    unsafe_expressions(db, &infer, def, &body, body.body_expr, &mut |expr| {
+        if !expr.inside_unsafe_block {
+            res.push(expr.expr);
+        }
+    });
+
+    res
 }
 
-struct UnsafeExpr {
-    pub(crate) expr: ExprId,
-    pub(crate) inside_unsafe_block: bool,
+pub struct UnsafeExpr {
+    pub expr: ExprId,
+    pub inside_unsafe_block: bool,
 }
 
-fn unsafe_expressions(
+pub fn unsafe_expressions(
     db: &dyn HirDatabase,
     infer: &InferenceResult,
     def: DefWithBodyId,
-) -> Vec<UnsafeExpr> {
-    let mut unsafe_exprs = vec![];
-    let body = db.body(def);
-    walk_unsafe(&mut unsafe_exprs, db, infer, def, &body, body.body_expr, false);
-
-    unsafe_exprs
+    body: &Body,
+    current: ExprId,
+    unsafe_expr_cb: &mut dyn FnMut(UnsafeExpr),
+) {
+    walk_unsafe(db, infer, def, body, current, false, unsafe_expr_cb)
 }
 
 fn walk_unsafe(
-    unsafe_exprs: &mut Vec<UnsafeExpr>,
     db: &dyn HirDatabase,
     infer: &InferenceResult,
     def: DefWithBodyId,
     body: &Body,
     current: ExprId,
     inside_unsafe_block: bool,
+    unsafe_expr_cb: &mut dyn FnMut(UnsafeExpr),
 ) {
     let expr = &body.exprs[current];
     match expr {
         &Expr::Call { callee, .. } => {
             if let Some(func) = infer[callee].as_fn_def(db) {
                 if db.function_data(func).is_unsafe() {
-                    unsafe_exprs.push(UnsafeExpr { expr: current, inside_unsafe_block });
+                    unsafe_expr_cb(UnsafeExpr { expr: current, inside_unsafe_block });
                 }
             }
         }
@@ -68,7 +71,7 @@ fn walk_unsafe(
             let value_or_partial = resolver.resolve_path_in_value_ns(db.upcast(), path.mod_path());
             if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id))) = value_or_partial {
                 if db.static_data(id).mutable {
-                    unsafe_exprs.push(UnsafeExpr { expr: current, inside_unsafe_block });
+                    unsafe_expr_cb(UnsafeExpr { expr: current, inside_unsafe_block });
                 }
             }
         }
@@ -78,21 +81,21 @@ fn walk_unsafe(
                 .map(|(func, _)| db.function_data(func).is_unsafe())
                 .unwrap_or(false)
             {
-                unsafe_exprs.push(UnsafeExpr { expr: current, inside_unsafe_block });
+                unsafe_expr_cb(UnsafeExpr { expr: current, inside_unsafe_block });
             }
         }
         Expr::UnaryOp { expr, op: UnaryOp::Deref } => {
             if let TyKind::Raw(..) = &infer[*expr].kind(Interner) {
-                unsafe_exprs.push(UnsafeExpr { expr: current, inside_unsafe_block });
+                unsafe_expr_cb(UnsafeExpr { expr: current, inside_unsafe_block });
             }
         }
         Expr::Unsafe { body: child } => {
-            return walk_unsafe(unsafe_exprs, db, infer, def, body, *child, true);
+            return walk_unsafe(db, infer, def, body, *child, true, unsafe_expr_cb);
         }
         _ => {}
     }
 
     expr.walk_child_exprs(|child| {
-        walk_unsafe(unsafe_exprs, db, infer, def, body, child, inside_unsafe_block);
+        walk_unsafe(db, infer, def, body, child, inside_unsafe_block, unsafe_expr_cb);
     });
 }
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs
index e7aa83dd9eb..7ac67756853 100644
--- a/crates/ide/src/syntax_highlighting/highlight.rs
+++ b/crates/ide/src/syntax_highlighting/highlight.rs
@@ -248,6 +248,16 @@ fn highlight_name_ref(
                         }
                     }
                 }
+                Definition::Macro(_) => {
+                    if let Some(macro_call) =
+                        ide_db::syntax_helpers::node_ext::full_path_of_name_ref(&name_ref)
+                            .and_then(|it| it.syntax().parent().and_then(ast::MacroCall::cast))
+                    {
+                        if sema.is_unsafe_macro_call(&macro_call) {
+                            h |= HlMod::Unsafe;
+                        }
+                    }
+                }
                 _ => (),
             }
 
diff --git a/crates/ide/src/syntax_highlighting/html.rs b/crates/ide/src/syntax_highlighting/html.rs
index 0491ce36350..9777c014c7a 100644
--- a/crates/ide/src/syntax_highlighting/html.rs
+++ b/crates/ide/src/syntax_highlighting/html.rs
@@ -71,6 +71,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .operator.unsafe    { color: #BC8383; }
 .mutable.unsafe     { color: #BC8383; text-decoration: underline; }
 .keyword.unsafe     { color: #BC8383; font-weight: bold; }
+.macro.unsafe       { color: #BC8383; }
 .parameter          { color: #94BFF3; }
 .text               { color: #DCDCCC; }
 .type               { color: #7CB8BB; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html b/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html
index b035e786d35..e07fd3925c7 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html
@@ -19,6 +19,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .operator.unsafe    { color: #BC8383; }
 .mutable.unsafe     { color: #BC8383; text-decoration: underline; }
 .keyword.unsafe     { color: #BC8383; font-weight: bold; }
+.macro.unsafe       { color: #BC8383; }
 .parameter          { color: #94BFF3; }
 .text               { color: #DCDCCC; }
 .type               { color: #7CB8BB; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html b/crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html
index 9fe2b50cde7..1a439881476 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html
@@ -19,6 +19,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .operator.unsafe    { color: #BC8383; }
 .mutable.unsafe     { color: #BC8383; text-decoration: underline; }
 .keyword.unsafe     { color: #BC8383; font-weight: bold; }
+.macro.unsafe       { color: #BC8383; }
 .parameter          { color: #94BFF3; }
 .text               { color: #DCDCCC; }
 .type               { color: #7CB8BB; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html b/crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html
index 37c8b566829..6704e5e8d53 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html
@@ -19,6 +19,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .operator.unsafe    { color: #BC8383; }
 .mutable.unsafe     { color: #BC8383; text-decoration: underline; }
 .keyword.unsafe     { color: #BC8383; font-weight: bold; }
+.macro.unsafe       { color: #BC8383; }
 .parameter          { color: #94BFF3; }
 .text               { color: #DCDCCC; }
 .type               { color: #7CB8BB; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html b/crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html
index 3e20b2f3512..5d66f832daf 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html
@@ -19,6 +19,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .operator.unsafe    { color: #BC8383; }
 .mutable.unsafe     { color: #BC8383; text-decoration: underline; }
 .keyword.unsafe     { color: #BC8383; font-weight: bold; }
+.macro.unsafe       { color: #BC8383; }
 .parameter          { color: #94BFF3; }
 .text               { color: #DCDCCC; }
 .type               { color: #7CB8BB; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
index 96044751b81..d9126421cda 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
@@ -19,6 +19,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .operator.unsafe    { color: #BC8383; }
 .mutable.unsafe     { color: #BC8383; text-decoration: underline; }
 .keyword.unsafe     { color: #BC8383; font-weight: bold; }
+.macro.unsafe       { color: #BC8383; }
 .parameter          { color: #94BFF3; }
 .text               { color: #DCDCCC; }
 .type               { color: #7CB8BB; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html b/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html
index 3208770d7f1..87b9da46e2c 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html
@@ -19,6 +19,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .operator.unsafe    { color: #BC8383; }
 .mutable.unsafe     { color: #BC8383; text-decoration: underline; }
 .keyword.unsafe     { color: #BC8383; font-weight: bold; }
+.macro.unsafe       { color: #BC8383; }
 .parameter          { color: #94BFF3; }
 .text               { color: #DCDCCC; }
 .type               { color: #7CB8BB; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_general.html b/crates/ide/src/syntax_highlighting/test_data/highlight_general.html
index 86ed8dbd777..d5c930e0494 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_general.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_general.html
@@ -19,6 +19,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .operator.unsafe    { color: #BC8383; }
 .mutable.unsafe     { color: #BC8383; text-decoration: underline; }
 .keyword.unsafe     { color: #BC8383; font-weight: bold; }
+.macro.unsafe       { color: #BC8383; }
 .parameter          { color: #94BFF3; }
 .text               { color: #DCDCCC; }
 .type               { color: #7CB8BB; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
index 023e791f8bb..ced7d22f03e 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
@@ -19,6 +19,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .operator.unsafe    { color: #BC8383; }
 .mutable.unsafe     { color: #BC8383; text-decoration: underline; }
 .keyword.unsafe     { color: #BC8383; font-weight: bold; }
+.macro.unsafe       { color: #BC8383; }
 .parameter          { color: #94BFF3; }
 .text               { color: #DCDCCC; }
 .type               { color: #7CB8BB; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html
index 145bba2a06c..278e0d84eea 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html
@@ -19,6 +19,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .operator.unsafe    { color: #BC8383; }
 .mutable.unsafe     { color: #BC8383; text-decoration: underline; }
 .keyword.unsafe     { color: #BC8383; font-weight: bold; }
+.macro.unsafe       { color: #BC8383; }
 .parameter          { color: #94BFF3; }
 .text               { color: #DCDCCC; }
 .type               { color: #7CB8BB; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_lifetimes.html b/crates/ide/src/syntax_highlighting/test_data/highlight_lifetimes.html
index 9e36b56d283..2d85fc8c925 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_lifetimes.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_lifetimes.html
@@ -19,6 +19,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .operator.unsafe    { color: #BC8383; }
 .mutable.unsafe     { color: #BC8383; text-decoration: underline; }
 .keyword.unsafe     { color: #BC8383; font-weight: bold; }
+.macro.unsafe       { color: #BC8383; }
 .parameter          { color: #94BFF3; }
 .text               { color: #DCDCCC; }
 .type               { color: #7CB8BB; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html b/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html
index c3f71d443f6..71add7e4955 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html
@@ -19,6 +19,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .operator.unsafe    { color: #BC8383; }
 .mutable.unsafe     { color: #BC8383; text-decoration: underline; }
 .keyword.unsafe     { color: #BC8383; font-weight: bold; }
+.macro.unsafe       { color: #BC8383; }
 .parameter          { color: #94BFF3; }
 .text               { color: #DCDCCC; }
 .type               { color: #7CB8BB; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_operators.html b/crates/ide/src/syntax_highlighting/test_data/highlight_operators.html
index 46220993e68..2369071ae2a 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_operators.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_operators.html
@@ -19,6 +19,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .operator.unsafe    { color: #BC8383; }
 .mutable.unsafe     { color: #BC8383; text-decoration: underline; }
 .keyword.unsafe     { color: #BC8383; font-weight: bold; }
+.macro.unsafe       { color: #BC8383; }
 .parameter          { color: #94BFF3; }
 .text               { color: #DCDCCC; }
 .type               { color: #7CB8BB; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_rainbow.html b/crates/ide/src/syntax_highlighting/test_data/highlight_rainbow.html
index e0287f11858..bff35c897e1 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_rainbow.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_rainbow.html
@@ -19,6 +19,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .operator.unsafe    { color: #BC8383; }
 .mutable.unsafe     { color: #BC8383; text-decoration: underline; }
 .keyword.unsafe     { color: #BC8383; font-weight: bold; }
+.macro.unsafe       { color: #BC8383; }
 .parameter          { color: #94BFF3; }
 .text               { color: #DCDCCC; }
 .type               { color: #7CB8BB; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
index 3715164bbf7..e26c017f952 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
@@ -19,6 +19,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .operator.unsafe    { color: #BC8383; }
 .mutable.unsafe     { color: #BC8383; text-decoration: underline; }
 .keyword.unsafe     { color: #BC8383; font-weight: bold; }
+.macro.unsafe       { color: #BC8383; }
 .parameter          { color: #94BFF3; }
 .text               { color: #DCDCCC; }
 .type               { color: #7CB8BB; }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
index 25bfcb6482f..113463aa7a5 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
@@ -19,6 +19,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .operator.unsafe    { color: #BC8383; }
 .mutable.unsafe     { color: #BC8383; text-decoration: underline; }
 .keyword.unsafe     { color: #BC8383; font-weight: bold; }
+.macro.unsafe       { color: #BC8383; }
 .parameter          { color: #94BFF3; }
 .text               { color: #DCDCCC; }
 .type               { color: #7CB8BB; }
@@ -41,7 +42,17 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 
 .unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
 </style>
-<pre><code><span class="keyword">static</span> <span class="keyword">mut</span> <span class="static declaration mutable unsafe">MUT_GLOBAL</span><span class="colon">:</span> <span class="struct">Struct</span> <span class="operator">=</span> <span class="struct">Struct</span> <span class="brace">{</span> <span class="field">field</span><span class="colon">:</span> <span class="numeric_literal">0</span> <span class="brace">}</span><span class="semicolon">;</span>
+<pre><code><span class="keyword">macro_rules</span><span class="punctuation">!</span> <span class="macro declaration">id</span> <span class="brace">{</span>
+    <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">&gt;</span> <span class="brace">{</span>
+        <span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span>
+    <span class="brace">}</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+<span class="keyword">macro_rules</span><span class="punctuation">!</span> <span class="macro declaration">unsafe_deref</span> <span class="brace">{</span>
+    <span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">&gt;</span> <span class="brace">{</span>
+        <span class="punctuation">*</span><span class="parenthesis">(</span><span class="operator">&</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="keyword">as</span> <span class="punctuation">*</span><span class="keyword">const</span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="parenthesis">)</span>
+    <span class="brace">}</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+<span class="keyword">static</span> <span class="keyword">mut</span> <span class="static declaration mutable unsafe">MUT_GLOBAL</span><span class="colon">:</span> <span class="struct">Struct</span> <span class="operator">=</span> <span class="struct">Struct</span> <span class="brace">{</span> <span class="field">field</span><span class="colon">:</span> <span class="numeric_literal">0</span> <span class="brace">}</span><span class="semicolon">;</span>
 <span class="keyword">static</span> <span class="static declaration">GLOBAL</span><span class="colon">:</span> <span class="struct">Struct</span> <span class="operator">=</span> <span class="struct">Struct</span> <span class="brace">{</span> <span class="field">field</span><span class="colon">:</span> <span class="numeric_literal">0</span> <span class="brace">}</span><span class="semicolon">;</span>
 <span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_fn</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
 
@@ -77,7 +88,15 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 <span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
     <span class="keyword">let</span> <span class="variable declaration">x</span> <span class="operator">=</span> <span class="operator">&</span><span class="numeric_literal">5</span> <span class="keyword">as</span> <span class="keyword">*</span><span class="keyword">const</span> <span class="punctuation">_</span> <span class="keyword">as</span> <span class="keyword">*</span><span class="keyword">const</span> <span class="builtin_type">usize</span><span class="semicolon">;</span>
     <span class="keyword">let</span> <span class="variable declaration">u</span> <span class="operator">=</span> <span class="union">Union</span> <span class="brace">{</span> <span class="field">b</span><span class="colon">:</span> <span class="numeric_literal">0</span> <span class="brace">}</span><span class="semicolon">;</span>
+
+    <span class="macro">id</span><span class="macro_bang">!</span> <span class="brace">{</span>
+        <span class="keyword unsafe">unsafe</span> <span class="brace">{</span> <span class="macro unsafe">unsafe_deref</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">}</span>
+    <span class="brace">}</span><span class="semicolon">;</span>
+
     <span class="keyword unsafe">unsafe</span> <span class="brace">{</span>
+        <span class="macro unsafe">unsafe_deref</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+        <span class="macro unsafe">id</span><span class="macro_bang">!</span> <span class="brace">{</span> <span class="macro unsafe">unsafe_deref</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">}</span><span class="semicolon">;</span>
+
         <span class="comment">// unsafe fn and method calls</span>
         <span class="function unsafe">unsafe_fn</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
         <span class="keyword">let</span> <span class="variable declaration">b</span> <span class="operator">=</span> <span class="variable">u</span><span class="operator">.</span><span class="field unsafe">b</span><span class="semicolon">;</span>
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index a8c69875e0c..c74bced637e 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -483,6 +483,16 @@ fn main() {
 fn test_unsafe_highlighting() {
     check_highlighting(
         r#"
+macro_rules! id {
+    ($($tt:tt)*) => {
+        $($tt)*
+    };
+}
+macro_rules! unsafe_deref {
+    () => {
+        *(&() as *const ())
+    };
+}
 static mut MUT_GLOBAL: Struct = Struct { field: 0 };
 static GLOBAL: Struct = Struct { field: 0 };
 unsafe fn unsafe_fn() {}
@@ -519,7 +529,15 @@ impl DoTheAutoref for u16 {
 fn main() {
     let x = &5 as *const _ as *const usize;
     let u = Union { b: 0 };
+
+    id! {
+        unsafe { unsafe_deref!() }
+    };
+
     unsafe {
+        unsafe_deref!();
+        id! { unsafe_deref!() };
+
         // unsafe fn and method calls
         unsafe_fn();
         let b = u.b;
diff --git a/crates/ide_db/src/syntax_helpers/node_ext.rs b/crates/ide_db/src/syntax_helpers/node_ext.rs
index c0f05299663..3d720458382 100644
--- a/crates/ide_db/src/syntax_helpers/node_ext.rs
+++ b/crates/ide_db/src/syntax_helpers/node_ext.rs
@@ -15,6 +15,13 @@ pub fn expr_as_name_ref(expr: &ast::Expr) -> Option<ast::NameRef> {
     }
 }
 
+pub fn full_path_of_name_ref(name_ref: &ast::NameRef) -> Option<ast::Path> {
+    let mut ancestors = name_ref.syntax().ancestors();
+    let _ = ancestors.next()?; // skip self
+    let _ = ancestors.next().filter(|it| ast::PathSegment::can_cast(it.kind()))?; // skip self
+    ancestors.take_while(|it| ast::Path::can_cast(it.kind())).last().and_then(ast::Path::cast)
+}
+
 pub fn block_as_lone_tail(block: &ast::BlockExpr) -> Option<ast::Expr> {
     block.statements().next().is_none().then(|| block.tail_expr()).flatten()
 }