11use clippy_config:: Conf ;
22use clippy_utils:: diagnostics:: span_lint;
3+ use clippy_utils:: macros:: macro_backtrace;
34use clippy_utils:: paths:: { PathNS , lookup_path_str} ;
4- use clippy_utils:: { get_unique_attr , sym} ;
5+ use clippy_utils:: { get_builtin_attr , is_from_proc_macro , sym} ;
56use rustc_data_structures:: fx:: FxHashSet ;
67use rustc_hir as hir;
78use rustc_hir:: def_id:: DefId ;
@@ -24,7 +25,7 @@ declare_clippy_lint! {
2425 /// #[clippy::may_panic]
2526 /// fn my_panicable_func(n: u32) {
2627 /// if n % 2 == 0 {
27- /// panic!("even number not allowed")
28+ /// panic!("even numbers are not allowed")
2829 /// }
2930 /// }
3031 ///
@@ -38,7 +39,7 @@ declare_clippy_lint! {
3839 /// #[clippy::may_panic]
3940 /// fn my_panicable_func(n: u32) {
4041 /// if n % 2 == 0 {
41- /// panic!("even number not allowed")
42+ /// panic!("even numbers are not allowed")
4243 /// }
4344 /// }
4445 ///
@@ -83,16 +84,13 @@ impl UndocumentedMayPanicCall {
8384 // A function is a may_panic_function if it has the may_panic attribute
8485 // or is in the may-panic-functions configuration
8586 fn is_may_panic_function ( & self , cx : & LateContext < ' _ > , def_id : DefId ) -> bool {
86- if get_unique_attr ( cx. sess ( ) , cx. tcx . get_all_attrs ( def_id) , sym:: may_panic) . is_some ( ) {
87- return true ;
88- }
89-
90- self . may_panic_def_ids . contains ( & def_id)
87+ get_builtin_attr ( cx. sess ( ) , cx. tcx . get_all_attrs ( def_id) , sym:: may_panic) . count ( ) > 0
88+ || self . may_panic_def_ids . contains ( & def_id)
9189 }
9290}
9391
94- impl LateLintPass < ' _ > for UndocumentedMayPanicCall {
95- fn check_expr ( & mut self , cx : & LateContext < ' _ > , expr : & ' _ rustc_hir :: Expr < ' _ > ) {
92+ impl < ' tcx > LateLintPass < ' tcx > for UndocumentedMayPanicCall {
93+ fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx hir :: Expr < ' tcx > ) {
9694 let def_id = match & expr. kind {
9795 hir:: ExprKind :: Call ( func, _args) => {
9896 if let hir:: ExprKind :: Path ( qpath) = & func. kind {
@@ -109,23 +107,59 @@ impl LateLintPass<'_> for UndocumentedMayPanicCall {
109107
110108 if let Some ( def_id) = def_id
111109 && self . is_may_panic_function ( cx, def_id)
112- && ! has_panic_comment_above ( cx, expr. span )
110+ && let Some ( lint_span ) = check_for_missing_panic_comment ( cx, expr)
113111 {
114112 span_lint (
115113 cx,
116114 UNDOCUMENTED_MAY_PANIC_CALL ,
117- expr . span ,
115+ lint_span ,
118116 "call to a function that may panic is not documented with a `// Panic:` comment" ,
119117 ) ;
120118 }
121119 }
122120}
123121
124- /// Checks if the lines immediately preceding the call contain a "Panic:" comment.
125- fn has_panic_comment_above ( cx : & LateContext < ' _ > , call_span : rustc_span:: Span ) -> bool {
122+ /// Checks if a panic comment is missing and returns the span to lint at
123+ /// Returns `None` if a panic comment exists
124+ /// Returns `Some(span)` if a panic comment is missing
125+ fn check_for_missing_panic_comment < ' tcx > (
126+ cx : & LateContext < ' tcx > ,
127+ expr : & ' tcx hir:: Expr < ' tcx > ,
128+ ) -> Option < rustc_span:: Span > {
129+ let call_span = expr. span ;
130+
131+ if call_span. from_expansion ( ) {
132+ // For external macros or proc macros, the user cannot modify the macro body,
133+ // so we only check callsites
134+ let is_external_or_proc_macro =
135+ call_span. in_external_macro ( cx. sess ( ) . source_map ( ) ) || is_from_proc_macro ( cx, expr) ;
136+
137+ // For locally defined macros, check the macro body first before checking the callsite
138+ if !is_external_or_proc_macro && has_panic_comment_above_span ( cx, call_span) {
139+ return None ;
140+ }
141+
142+ let mut lint_span = None ;
143+ for macro_call in macro_backtrace ( call_span) {
144+ if has_panic_comment_above_span ( cx, macro_call. span ) {
145+ return None ;
146+ }
147+ lint_span = Some ( macro_call. span ) ;
148+ }
149+
150+ lint_span
151+ } else if has_panic_comment_above_span ( cx, call_span) {
152+ None
153+ } else {
154+ Some ( call_span)
155+ }
156+ }
157+
158+ /// Checks if the lines immediately preceding a span contain a "Panic:" comment
159+ fn has_panic_comment_above_span ( cx : & LateContext < ' _ > , span : rustc_span:: Span ) -> bool {
126160 let source_map = cx. sess ( ) . source_map ( ) ;
127161
128- if let Ok ( call_line) = source_map. lookup_line ( call_span . lo ( ) )
162+ if let Ok ( call_line) = source_map. lookup_line ( span . lo ( ) )
129163 && call_line. line > 0
130164 && let Some ( src) = call_line. sf . src . as_deref ( )
131165 {
0 commit comments