1 use std
::ops
::ControlFlow
;
3 use clippy_utils
::diagnostics
::span_lint_and_help
;
4 use clippy_utils
::is_lint_allowed
;
5 use clippy_utils
::source
::walk_span_to_context
;
6 use clippy_utils
::visitors
::{for_each_expr_with_closures, Descend}
;
8 use rustc_data_structures
::sync
::Lrc
;
10 use rustc_hir
::{Block, BlockCheckMode, ItemKind, Node, UnsafeSource}
;
11 use rustc_lexer
::{tokenize, TokenKind}
;
12 use rustc_lint
::{LateContext, LateLintPass, LintContext}
;
13 use rustc_middle
::lint
::in_external_macro
;
14 use rustc_session
::impl_lint_pass
;
15 use rustc_span
::{BytePos, Pos, RelativeBytePos, Span, SyntaxContext}
;
17 declare_clippy_lint
! {
19 /// Checks for `unsafe` blocks and impls without a `// SAFETY: ` comment
20 /// explaining why the unsafe operations performed inside
21 /// the block are safe.
23 /// Note the comment must appear on the line(s) preceding the unsafe block
24 /// with nothing appearing in between. The following is ok:
28 /// // This is a valid safety comment
32 /// But neither of these are:
35 /// // This is not a valid safety comment
37 /// /* SAFETY: Neither is this */ unsafe { *x },
41 /// ### Why is this bad?
42 /// Undocumented unsafe blocks and impls can make it difficult to
43 /// read and maintain code, as well as uncover unsoundness
48 /// use std::ptr::NonNull;
51 /// let ptr = unsafe { NonNull::new_unchecked(a) };
55 /// use std::ptr::NonNull;
58 /// // SAFETY: references are guaranteed to be non-null.
59 /// let ptr = unsafe { NonNull::new_unchecked(a) };
61 #[clippy::version = "1.58.0"]
62 pub UNDOCUMENTED_UNSAFE_BLOCKS
,
64 "creating an unsafe block without explaining why it is safe"
66 declare_clippy_lint
! {
68 /// Checks for `// SAFETY: ` comments on safe code.
70 /// ### Why is this bad?
71 /// Safe code has no safety requirements, so there is no need to
72 /// describe safety invariants.
76 /// use std::ptr::NonNull;
79 /// // SAFETY: references are guaranteed to be non-null.
80 /// let ptr = NonNull::new(a).unwrap();
84 /// use std::ptr::NonNull;
87 /// let ptr = NonNull::new(a).unwrap();
89 #[clippy::version = "1.67.0"]
90 pub UNNECESSARY_SAFETY_COMMENT
,
92 "annotating safe code with a safety comment"
95 #[derive(Copy, Clone)]
96 pub struct UndocumentedUnsafeBlocks
{
97 accept_comment_above_statement
: bool
,
98 accept_comment_above_attributes
: bool
,
101 impl UndocumentedUnsafeBlocks
{
102 pub fn new(accept_comment_above_statement
: bool
, accept_comment_above_attributes
: bool
) -> Self {
104 accept_comment_above_statement
,
105 accept_comment_above_attributes
,
110 impl_lint_pass
!(UndocumentedUnsafeBlocks
=> [UNDOCUMENTED_UNSAFE_BLOCKS
, UNNECESSARY_SAFETY_COMMENT
]);
112 impl<'tcx
> LateLintPass
<'tcx
> for UndocumentedUnsafeBlocks
{
113 fn check_block(&mut self, cx
: &LateContext
<'tcx
>, block
: &'tcx Block
<'tcx
>) {
114 if block
.rules
== BlockCheckMode
::UnsafeBlock(UnsafeSource
::UserProvided
)
115 && !in_external_macro(cx
.tcx
.sess
, block
.span
)
116 && !is_lint_allowed(cx
, UNDOCUMENTED_UNSAFE_BLOCKS
, block
.hir_id
)
117 && !is_unsafe_from_proc_macro(cx
, block
.span
)
118 && !block_has_safety_comment(cx
, block
.span
)
119 && !block_parents_have_safety_comment(
120 self.accept_comment_above_statement
,
121 self.accept_comment_above_attributes
,
126 let source_map
= cx
.tcx
.sess
.source_map();
127 let span
= if source_map
.is_multiline(block
.span
) {
128 source_map
.span_until_char(block
.span
, '
\n'
)
135 UNDOCUMENTED_UNSAFE_BLOCKS
,
137 "unsafe block missing a safety comment",
139 "consider adding a safety comment on the preceding line",
143 if let Some(tail
) = block
.expr
144 && !is_lint_allowed(cx
, UNNECESSARY_SAFETY_COMMENT
, tail
.hir_id
)
145 && !in_external_macro(cx
.tcx
.sess
, tail
.span
)
146 && let HasSafetyComment
::Yes(pos
) = stmt_has_safety_comment(cx
, tail
.span
, tail
.hir_id
)
147 && let Some(help_span
) = expr_has_unnecessary_safety_comment(cx
, tail
, pos
)
151 UNNECESSARY_SAFETY_COMMENT
,
153 "expression has unnecessary safety comment",
155 "consider removing the safety comment",
160 fn check_stmt(&mut self, cx
: &LateContext
<'tcx
>, stmt
: &hir
::Stmt
<'tcx
>) {
161 let (hir
::StmtKind
::Let(&hir
::LetStmt { init: Some(expr), .. }
)
162 | hir
::StmtKind
::Expr(expr
)
163 | hir
::StmtKind
::Semi(expr
)) = stmt
.kind
167 if !is_lint_allowed(cx
, UNNECESSARY_SAFETY_COMMENT
, stmt
.hir_id
)
168 && !in_external_macro(cx
.tcx
.sess
, stmt
.span
)
169 && let HasSafetyComment
::Yes(pos
) = stmt_has_safety_comment(cx
, stmt
.span
, stmt
.hir_id
)
170 && let Some(help_span
) = expr_has_unnecessary_safety_comment(cx
, expr
, pos
)
174 UNNECESSARY_SAFETY_COMMENT
,
176 "statement has unnecessary safety comment",
178 "consider removing the safety comment",
183 fn check_item(&mut self, cx
: &LateContext
<'_
>, item
: &hir
::Item
<'_
>) {
184 if in_external_macro(cx
.tcx
.sess
, item
.span
) {
188 let mk_spans
= |pos
: BytePos
| {
189 let source_map
= cx
.tcx
.sess
.source_map();
190 let span
= Span
::new(pos
, pos
, SyntaxContext
::root(), None
);
191 let help_span
= source_map
.span_extend_to_next_char(span
, '
\n'
, true);
192 let span
= if source_map
.is_multiline(item
.span
) {
193 source_map
.span_until_char(item
.span
, '
\n'
)
200 let item_has_safety_comment
= item_has_safety_comment(cx
, item
);
201 match (&item
.kind
, item_has_safety_comment
) {
202 // lint unsafe impl without safety comment
203 (ItemKind
::Impl(impl_
), HasSafetyComment
::No
) if impl_
.unsafety
== hir
::Unsafety
::Unsafe
=> {
204 if !is_lint_allowed(cx
, UNDOCUMENTED_UNSAFE_BLOCKS
, item
.hir_id())
205 && !is_unsafe_from_proc_macro(cx
, item
.span
)
207 let source_map
= cx
.tcx
.sess
.source_map();
208 let span
= if source_map
.is_multiline(item
.span
) {
209 source_map
.span_until_char(item
.span
, '
\n'
)
216 UNDOCUMENTED_UNSAFE_BLOCKS
,
218 "unsafe impl missing a safety comment",
220 "consider adding a safety comment on the preceding line",
224 // lint safe impl with unnecessary safety comment
225 (ItemKind
::Impl(impl_
), HasSafetyComment
::Yes(pos
)) if impl_
.unsafety
== hir
::Unsafety
::Normal
=> {
226 if !is_lint_allowed(cx
, UNNECESSARY_SAFETY_COMMENT
, item
.hir_id()) {
227 let (span
, help_span
) = mk_spans(pos
);
231 UNNECESSARY_SAFETY_COMMENT
,
233 "impl has unnecessary safety comment",
235 "consider removing the safety comment",
239 (ItemKind
::Impl(_
), _
) => {}
,
240 // const and static items only need a safety comment if their body is an unsafe block, lint otherwise
241 (&ItemKind
::Const(.., body
) | &ItemKind
::Static(.., body
), HasSafetyComment
::Yes(pos
)) => {
242 if !is_lint_allowed(cx
, UNNECESSARY_SAFETY_COMMENT
, body
.hir_id
) {
243 let body
= cx
.tcx
.hir().body(body
);
245 body
.value
.kind
, hir
::ExprKind
::Block(block
, _
)
246 if block
.rules
== BlockCheckMode
::UnsafeBlock(UnsafeSource
::UserProvided
)
248 let (span
, help_span
) = mk_spans(pos
);
252 UNNECESSARY_SAFETY_COMMENT
,
254 format
!("{} has unnecessary safety comment", item
.kind
.descr()),
256 "consider removing the safety comment",
261 // Aside from unsafe impls and consts/statics with an unsafe block, items in general
262 // do not have safety invariants that need to be documented, so lint those.
263 (_
, HasSafetyComment
::Yes(pos
)) => {
264 if !is_lint_allowed(cx
, UNNECESSARY_SAFETY_COMMENT
, item
.hir_id()) {
265 let (span
, help_span
) = mk_spans(pos
);
269 UNNECESSARY_SAFETY_COMMENT
,
271 format
!("{} has unnecessary safety comment", item
.kind
.descr()),
273 "consider removing the safety comment",
282 fn expr_has_unnecessary_safety_comment
<'tcx
>(
283 cx
: &LateContext
<'tcx
>,
284 expr
: &'tcx hir
::Expr
<'tcx
>,
285 comment_pos
: BytePos
,
287 if cx
.tcx
.hir().parent_iter(expr
.hir_id
).any(|(_
, ref node
)| {
291 rules
: BlockCheckMode
::UnsafeBlock(UnsafeSource
::UserProvided
),
299 // this should roughly be the reverse of `block_parents_have_safety_comment`
300 if for_each_expr_with_closures(cx
, expr
, |expr
| match expr
.kind
{
301 hir
::ExprKind
::Block(
303 rules
: BlockCheckMode
::UnsafeBlock(UnsafeSource
::UserProvided
),
307 ) => ControlFlow
::Break(()),
308 // statements will be handled by check_stmt itself again
309 hir
::ExprKind
::Block(..) => ControlFlow
::Continue(Descend
::No
),
310 _
=> ControlFlow
::Continue(Descend
::Yes
),
317 let source_map
= cx
.tcx
.sess
.source_map();
318 let span
= Span
::new(comment_pos
, comment_pos
, SyntaxContext
::root(), None
);
319 let help_span
= source_map
.span_extend_to_next_char(span
, '
\n'
, true);
324 fn is_unsafe_from_proc_macro(cx
: &LateContext
<'_
>, span
: Span
) -> bool
{
325 let source_map
= cx
.sess().source_map();
326 let file_pos
= source_map
.lookup_byte_offset(span
.lo());
331 .and_then(|src
| src
.get(file_pos
.pos
.to_usize()..))
332 .map_or(true, |src
| !src
.starts_with("unsafe"))
335 // Checks if any parent {expression, statement, block, local, const, static}
336 // has a safety comment
337 fn block_parents_have_safety_comment(
338 accept_comment_above_statement
: bool
,
339 accept_comment_above_attributes
: bool
,
340 cx
: &LateContext
<'_
>,
343 let (span
, hir_id
) = match cx
.tcx
.parent_hir_node(id
) {
344 Node
::Expr(expr
) => match cx
.tcx
.parent_hir_node(expr
.hir_id
) {
345 Node
::LetStmt(hir
::LetStmt { span, hir_id, .. }
) => (*span
, *hir_id
),
346 Node
::Item(hir
::Item
{
347 kind
: ItemKind
::Const(..) | ItemKind
::Static(..),
351 }) => (*span
, cx
.tcx
.local_def_id_to_hir_id(owner_id
.def_id
)),
353 if is_branchy(expr
) {
356 (expr
.span
, expr
.hir_id
)
359 Node
::Stmt(hir
::Stmt
{
361 hir
::StmtKind
::Let(hir
::LetStmt { span, hir_id, .. }
)
362 | hir
::StmtKind
::Expr(hir
::Expr { span, hir_id, .. }
)
363 | hir
::StmtKind
::Semi(hir
::Expr { span, hir_id, .. }
),
366 | Node
::LetStmt(hir
::LetStmt { span, hir_id, .. }
) => (*span
, *hir_id
),
367 Node
::Item(hir
::Item
{
368 kind
: ItemKind
::Const(..) | ItemKind
::Static(..),
372 }) => (*span
, cx
.tcx
.local_def_id_to_hir_id(owner_id
.def_id
)),
375 // if unsafe block is part of a let/const/static statement,
376 // and accept_comment_above_statement is set to true
377 // we accept the safety comment in the line the precedes this statement.
378 accept_comment_above_statement
379 && span_with_attrs_has_safety_comment(cx
, span
, hir_id
, accept_comment_above_attributes
)
382 /// Extends `span` to also include its attributes, then checks if that span has a safety comment.
383 fn span_with_attrs_has_safety_comment(
384 cx
: &LateContext
<'_
>,
387 accept_comment_above_attributes
: bool
,
389 let span
= if accept_comment_above_attributes
{
390 include_attrs_in_span(cx
, hir_id
, span
)
395 span_has_safety_comment(cx
, span
)
398 /// Checks if an expression is "branchy", e.g. loop, match/if/etc.
399 fn is_branchy(expr
: &hir
::Expr
<'_
>) -> bool
{
402 hir
::ExprKind
::If(..) | hir
::ExprKind
::Loop(..) | hir
::ExprKind
::Match(..)
406 /// Checks if the lines immediately preceding the block contain a safety comment.
407 fn block_has_safety_comment(cx
: &LateContext
<'_
>, span
: Span
) -> bool
{
408 // This intentionally ignores text before the start of a function so something like:
411 // fn foo() { unsafe { .. } }
413 // won't work. This is to avoid dealing with where such a comment should be place relative to
414 // attributes and doc comments.
417 span_from_macro_expansion_has_safety_comment(cx
, span
),
418 HasSafetyComment
::Yes(_
)
419 ) || span_has_safety_comment(cx
, span
)
422 fn include_attrs_in_span(cx
: &LateContext
<'_
>, hir_id
: HirId
, span
: Span
) -> Span
{
428 .fold(span
, |acc
, attr
| acc
.to(attr
.span
)))
431 enum HasSafetyComment
{
437 /// Checks if the lines immediately preceding the item contain a safety comment.
438 #[allow(clippy::collapsible_match)]
439 fn item_has_safety_comment(cx
: &LateContext
<'_
>, item
: &hir
::Item
<'_
>) -> HasSafetyComment
{
440 match span_from_macro_expansion_has_safety_comment(cx
, item
.span
) {
441 HasSafetyComment
::Maybe
=> (),
442 has_safety_comment
=> return has_safety_comment
,
445 if item
.span
.ctxt() != SyntaxContext
::root() {
446 return HasSafetyComment
::No
;
448 let comment_start
= match cx
.tcx
.parent_hir_node(item
.hir_id()) {
449 Node
::Crate(parent_mod
) => comment_start_before_item_in_mod(cx
, parent_mod
, parent_mod
.spans
.inner_span
, item
),
450 Node
::Item(parent_item
) => {
451 if let ItemKind
::Mod(parent_mod
) = &parent_item
.kind
{
452 comment_start_before_item_in_mod(cx
, parent_mod
, parent_item
.span
, item
)
454 // Doesn't support impls in this position. Pretend a comment was found.
455 return HasSafetyComment
::Maybe
;
458 Node
::Stmt(stmt
) => {
459 if let Node
::Block(block
) = cx
.tcx
.parent_hir_node(stmt
.hir_id
) {
460 walk_span_to_context(block
.span
, SyntaxContext
::root()).map(Span
::lo
)
462 // Problem getting the parent node. Pretend a comment was found.
463 return HasSafetyComment
::Maybe
;
467 // Doesn't support impls in this position. Pretend a comment was found.
468 return HasSafetyComment
::Maybe
;
472 let source_map
= cx
.sess().source_map();
473 if let Some(comment_start
) = comment_start
474 && let Ok(unsafe_line
) = source_map
.lookup_line(item
.span
.lo())
475 && let Ok(comment_start_line
) = source_map
.lookup_line(comment_start
)
476 && Lrc
::ptr_eq(&unsafe_line
.sf
, &comment_start_line
.sf
)
477 && let Some(src
) = unsafe_line
.sf
.src
.as_deref()
479 return if comment_start_line
.line
>= unsafe_line
.line
{
482 match text_has_safety_comment(
484 &unsafe_line
.sf
.lines()[comment_start_line
.line
+ 1..=unsafe_line
.line
],
485 unsafe_line
.sf
.start_pos
,
487 Some(b
) => HasSafetyComment
::Yes(b
),
488 None
=> HasSafetyComment
::No
,
492 HasSafetyComment
::Maybe
495 /// Checks if the lines immediately preceding the item contain a safety comment.
496 #[allow(clippy::collapsible_match)]
497 fn stmt_has_safety_comment(cx
: &LateContext
<'_
>, span
: Span
, hir_id
: HirId
) -> HasSafetyComment
{
498 match span_from_macro_expansion_has_safety_comment(cx
, span
) {
499 HasSafetyComment
::Maybe
=> (),
500 has_safety_comment
=> return has_safety_comment
,
503 if span
.ctxt() != SyntaxContext
::root() {
504 return HasSafetyComment
::No
;
507 let comment_start
= match cx
.tcx
.parent_hir_node(hir_id
) {
508 Node
::Block(block
) => walk_span_to_context(block
.span
, SyntaxContext
::root()).map(Span
::lo
),
509 _
=> return HasSafetyComment
::Maybe
,
512 let source_map
= cx
.sess().source_map();
513 if let Some(comment_start
) = comment_start
514 && let Ok(unsafe_line
) = source_map
.lookup_line(span
.lo())
515 && let Ok(comment_start_line
) = source_map
.lookup_line(comment_start
)
516 && Lrc
::ptr_eq(&unsafe_line
.sf
, &comment_start_line
.sf
)
517 && let Some(src
) = unsafe_line
.sf
.src
.as_deref()
519 return if comment_start_line
.line
>= unsafe_line
.line
{
522 match text_has_safety_comment(
524 &unsafe_line
.sf
.lines()[comment_start_line
.line
+ 1..=unsafe_line
.line
],
525 unsafe_line
.sf
.start_pos
,
527 Some(b
) => HasSafetyComment
::Yes(b
),
528 None
=> HasSafetyComment
::No
,
532 HasSafetyComment
::Maybe
535 fn comment_start_before_item_in_mod(
536 cx
: &LateContext
<'_
>,
537 parent_mod
: &hir
::Mod
<'_
>,
538 parent_mod_span
: Span
,
539 item
: &hir
::Item
<'_
>,
540 ) -> Option
<BytePos
> {
541 parent_mod
.item_ids
.iter().enumerate().find_map(|(idx
, item_id
)| {
542 if *item_id
== item
.item_id() {
544 // mod A { /* comment */ unsafe impl T {} ... }
545 // ^------------------------------------------^ returns the start of this span
546 // ^---------------------^ finally checks comments in this range
547 if let Some(sp
) = walk_span_to_context(parent_mod_span
, SyntaxContext
::root()) {
548 return Some(sp
.lo());
551 // some_item /* comment */ unsafe impl T {}
552 // ^-------^ returns the end of this span
553 // ^---------------^ finally checks comments in this range
554 let prev_item
= cx
.tcx
.hir().item(parent_mod
.item_ids
[idx
- 1]);
555 if let Some(sp
) = walk_span_to_context(prev_item
.span
, SyntaxContext
::root()) {
556 return Some(sp
.hi());
564 fn span_from_macro_expansion_has_safety_comment(cx
: &LateContext
<'_
>, span
: Span
) -> HasSafetyComment
{
565 let source_map
= cx
.sess().source_map();
566 let ctxt
= span
.ctxt();
567 if ctxt
== SyntaxContext
::root() {
568 HasSafetyComment
::Maybe
570 // From a macro expansion. Get the text from the start of the macro declaration to start of the
572 // macro_rules! foo { () => { stuff }; (x) => { unsafe { stuff } }; }
573 // ^--------------------------------------------^
574 if let Ok(unsafe_line
) = source_map
.lookup_line(span
.lo())
575 && let Ok(macro_line
) = source_map
.lookup_line(ctxt
.outer_expn_data().def_site
.lo())
576 && Lrc
::ptr_eq(&unsafe_line
.sf
, ¯o_line
.sf
)
577 && let Some(src
) = unsafe_line
.sf
.src
.as_deref()
579 if macro_line
.line
< unsafe_line
.line
{
580 match text_has_safety_comment(
582 &unsafe_line
.sf
.lines()[macro_line
.line
+ 1..=unsafe_line
.line
],
583 unsafe_line
.sf
.start_pos
,
585 Some(b
) => HasSafetyComment
::Yes(b
),
586 None
=> HasSafetyComment
::No
,
592 // Problem getting source text. Pretend a comment was found.
593 HasSafetyComment
::Maybe
598 fn get_body_search_span(cx
: &LateContext
<'_
>) -> Option
<Span
> {
599 let body
= cx
.enclosing_body?
;
600 let map
= cx
.tcx
.hir();
601 let mut span
= map
.body(body
).value
.span
;
602 let mut maybe_global_var
= false;
603 for (_
, node
) in map
.parent_iter(body
.hir_id
) {
605 Node
::Expr(e
) => span
= e
.span
,
606 Node
::Block(_
) | Node
::Arm(_
) | Node
::Stmt(_
) | Node
::LetStmt(_
) => (),
607 Node
::Item(hir
::Item
{
608 kind
: ItemKind
::Const(..) | ItemKind
::Static(..),
610 }) => maybe_global_var
= true,
611 Node
::Item(hir
::Item
{
612 kind
: ItemKind
::Mod(_
),
619 Node
::Crate(mod_
) if maybe_global_var
=> {
620 span
= mod_
.spans
.inner_span
;
628 fn span_has_safety_comment(cx
: &LateContext
<'_
>, span
: Span
) -> bool
{
629 let source_map
= cx
.sess().source_map();
630 let ctxt
= span
.ctxt();
632 && let Some(search_span
) = get_body_search_span(cx
)
634 if let Ok(unsafe_line
) = source_map
.lookup_line(span
.lo())
635 && let Some(body_span
) = walk_span_to_context(search_span
, SyntaxContext
::root())
636 && let Ok(body_line
) = source_map
.lookup_line(body_span
.lo())
637 && Lrc
::ptr_eq(&unsafe_line
.sf
, &body_line
.sf
)
638 && let Some(src
) = unsafe_line
.sf
.src
.as_deref()
640 // Get the text from the start of function body to the unsafe block.
641 // fn foo() { some_stuff; unsafe { stuff }; other_stuff; }
643 body_line
.line
< unsafe_line
.line
644 && text_has_safety_comment(
646 &unsafe_line
.sf
.lines()[body_line
.line
+ 1..=unsafe_line
.line
],
647 unsafe_line
.sf
.start_pos
,
651 // Problem getting source text. Pretend a comment was found.
659 /// Checks if the given text has a safety comment for the immediately proceeding line.
660 fn text_has_safety_comment(src
: &str, line_starts
: &[RelativeBytePos
], start_pos
: BytePos
) -> Option
<BytePos
> {
661 let mut lines
= line_starts
662 .array_windows
::<2>()
664 .map_while(|[start
, end
]| {
665 let start
= start
.to_usize();
666 let end
= end
.to_usize();
667 let text
= src
.get(start
..end
)?
;
668 let trimmed
= text
.trim_start();
669 Some((start
+ (text
.len() - trimmed
.len()), trimmed
))
671 .filter(|(_
, text
)| !text
.is_empty());
673 let (line_start
, line
) = lines
.next()?
;
674 let mut in_codeblock
= false;
675 // Check for a sequence of line comments.
676 if line
.starts_with("//") {
677 let (mut line
, mut line_start
) = (line
, line_start
);
679 // Don't lint if the safety comment is part of a codeblock in a doc comment.
680 // It may or may not be required, and we can't very easily check it (and we shouldn't, since
681 // the safety comment isn't referring to the node we're currently checking)
682 if line
.trim_start_matches("///").trim_start().starts_with("```") {
683 in_codeblock
= !in_codeblock
;
686 if line
.to_ascii_uppercase().contains("SAFETY:") && !in_codeblock
{
687 return Some(start_pos
+ BytePos(u32::try_from(line_start
).unwrap()));
690 Some((s
, x
)) if x
.starts_with("//") => (line
, line_start
) = (x
, s
),
695 // No line comments; look for the start of a block comment.
696 // This will only find them if they are at the start of a line.
697 let (mut line_start
, mut line
) = (line_start
, line
);
699 if line
.starts_with("/*") {
700 let src
= &src
[line_start
..line_starts
.last().unwrap().to_usize()];
701 let mut tokens
= tokenize(src
);
702 return (src
[..tokens
.next().unwrap().len
as usize]
703 .to_ascii_uppercase()
705 && tokens
.all(|t
| t
.kind
== TokenKind
::Whitespace
))
706 .then_some(start_pos
+ BytePos(u32::try_from(line_start
).unwrap()));
709 Some(x
) => (line_start
, line
) = x
,