ide_assists/
utils.rs

1//! Assorted functions shared by several assists.
2
3use std::slice;
4
5pub(crate) use gen_trait_fn_body::gen_trait_fn_body;
6use hir::{
7    DisplayTarget, HasAttrs as HirHasAttrs, HirDisplay, InFile, ModuleDef, PathResolution,
8    Semantics,
9    db::{ExpandDatabase, HirDatabase},
10};
11use ide_db::{
12    RootDatabase,
13    assists::ExprFillDefaultMode,
14    famous_defs::FamousDefs,
15    path_transform::PathTransform,
16    syntax_helpers::{node_ext::preorder_expr, prettify_macro_expansion},
17};
18use stdx::format_to;
19use syntax::{
20    AstNode, AstToken, Direction, NodeOrToken, SourceFile,
21    SyntaxKind::*,
22    SyntaxNode, SyntaxToken, T, TextRange, TextSize, WalkEvent,
23    ast::{
24        self, HasArgList, HasAttrs, HasGenericParams, HasName, HasTypeBounds, Whitespace,
25        edit::{AstNodeEdit, IndentLevel},
26        edit_in_place::AttrsOwnerEdit,
27        make,
28        syntax_factory::SyntaxFactory,
29    },
30    syntax_editor::{Element, Removable, SyntaxEditor},
31};
32
33use crate::{
34    AssistConfig,
35    assist_context::{AssistContext, SourceChangeBuilder},
36};
37
38mod gen_trait_fn_body;
39pub(crate) mod ref_field_expr;
40
41pub(crate) fn unwrap_trivial_block(block_expr: ast::BlockExpr) -> ast::Expr {
42    extract_trivial_expression(&block_expr)
43        .filter(|expr| !expr.syntax().text().contains_char('\n'))
44        .unwrap_or_else(|| block_expr.into())
45}
46
47pub fn extract_trivial_expression(block_expr: &ast::BlockExpr) -> Option<ast::Expr> {
48    if block_expr.modifier().is_some() {
49        return None;
50    }
51    let stmt_list = block_expr.stmt_list()?;
52    let has_anything_else = |thing: &SyntaxNode| -> bool {
53        let mut non_trivial_children =
54            stmt_list.syntax().children_with_tokens().filter(|it| match it.kind() {
55                WHITESPACE | T!['{'] | T!['}'] => false,
56                _ => it.as_node() != Some(thing),
57            });
58        non_trivial_children.next().is_some()
59    };
60    if stmt_list
61        .syntax()
62        .children_with_tokens()
63        .filter_map(NodeOrToken::into_token)
64        .any(|token| token.kind() == syntax::SyntaxKind::COMMENT)
65    {
66        return None;
67    }
68
69    if let Some(expr) = stmt_list.tail_expr() {
70        if has_anything_else(expr.syntax()) {
71            return None;
72        }
73        return Some(expr);
74    }
75    // Unwrap `{ continue; }`
76    let stmt = stmt_list.statements().next()?;
77    if let ast::Stmt::ExprStmt(expr_stmt) = stmt {
78        if has_anything_else(expr_stmt.syntax()) {
79            return None;
80        }
81        let expr = expr_stmt.expr()?;
82        if matches!(expr.syntax().kind(), CONTINUE_EXPR | BREAK_EXPR | RETURN_EXPR) {
83            return Some(expr);
84        }
85    }
86    None
87}
88
89/// This is a method with a heuristics to support test methods annotated with custom test annotations, such as
90/// `#[test_case(...)]`, `#[tokio::test]` and similar.
91/// Also a regular `#[test]` annotation is supported.
92///
93/// It may produce false positives, for example, `#[wasm_bindgen_test]` requires a different command to run the test,
94/// but it's better than not to have the runnables for the tests at all.
95pub fn test_related_attribute_syn(fn_def: &ast::Fn) -> Option<ast::Attr> {
96    fn_def.attrs().find_map(|attr| {
97        let path = attr.path()?;
98        let text = path.syntax().text().to_string();
99        if text.starts_with("test") || text.ends_with("test") { Some(attr) } else { None }
100    })
101}
102
103pub fn has_test_related_attribute(attrs: &hir::AttrsWithOwner) -> bool {
104    attrs.is_test()
105}
106
107#[derive(Clone, Copy, PartialEq)]
108pub enum IgnoreAssocItems {
109    DocHiddenAttrPresent,
110    No,
111}
112
113#[derive(Copy, Clone, PartialEq)]
114pub enum DefaultMethods {
115    Only,
116    No,
117}
118
119pub fn filter_assoc_items(
120    sema: &Semantics<'_, RootDatabase>,
121    items: &[hir::AssocItem],
122    default_methods: DefaultMethods,
123    ignore_items: IgnoreAssocItems,
124) -> Vec<InFile<ast::AssocItem>> {
125    return items
126        .iter()
127        .copied()
128        .filter(|assoc_item| {
129            if ignore_items == IgnoreAssocItems::DocHiddenAttrPresent
130                && assoc_item.attrs(sema.db).is_doc_hidden()
131            {
132                if let hir::AssocItem::Function(f) = assoc_item
133                    && !f.has_body(sema.db)
134                {
135                    return true;
136                }
137                return false;
138            }
139
140            true
141        })
142        // Note: This throws away items with no source.
143        .filter_map(|assoc_item| {
144            let item = match assoc_item {
145                hir::AssocItem::Function(it) => sema.source(it)?.map(ast::AssocItem::Fn),
146                hir::AssocItem::TypeAlias(it) => sema.source(it)?.map(ast::AssocItem::TypeAlias),
147                hir::AssocItem::Const(it) => sema.source(it)?.map(ast::AssocItem::Const),
148            };
149            Some(item)
150        })
151        .filter(has_def_name)
152        .filter(|it| match &it.value {
153            ast::AssocItem::Fn(def) => matches!(
154                (default_methods, def.body()),
155                (DefaultMethods::Only, Some(_)) | (DefaultMethods::No, None)
156            ),
157            ast::AssocItem::Const(def) => matches!(
158                (default_methods, def.body()),
159                (DefaultMethods::Only, Some(_)) | (DefaultMethods::No, None)
160            ),
161            _ => default_methods == DefaultMethods::No,
162        })
163        .collect();
164
165    fn has_def_name(item: &InFile<ast::AssocItem>) -> bool {
166        match &item.value {
167            ast::AssocItem::Fn(def) => def.name(),
168            ast::AssocItem::TypeAlias(def) => def.name(),
169            ast::AssocItem::Const(def) => def.name(),
170            ast::AssocItem::MacroCall(_) => None,
171        }
172        .is_some()
173    }
174}
175
176/// Given `original_items` retrieved from the trait definition (usually by
177/// [`filter_assoc_items()`]), clones each item for update and applies path transformation to it,
178/// then inserts into `impl_`. Returns the modified `impl_` and the first associated item that got
179/// inserted.
180#[must_use]
181pub fn add_trait_assoc_items_to_impl(
182    sema: &Semantics<'_, RootDatabase>,
183    config: &AssistConfig,
184    original_items: &[InFile<ast::AssocItem>],
185    trait_: hir::Trait,
186    impl_: &ast::Impl,
187    target_scope: &hir::SemanticsScope<'_>,
188) -> Vec<ast::AssocItem> {
189    let new_indent_level = IndentLevel::from_node(impl_.syntax()) + 1;
190    original_items
191        .iter()
192        .map(|InFile { file_id, value: original_item }| {
193            let mut cloned_item = {
194                if let Some(macro_file) = file_id.macro_file() {
195                    let span_map = sema.db.expansion_span_map(macro_file);
196                    let item_prettified = prettify_macro_expansion(
197                        sema.db,
198                        original_item.syntax().clone(),
199                        &span_map,
200                        target_scope.krate().into(),
201                    );
202                    if let Some(formatted) = ast::AssocItem::cast(item_prettified) {
203                        return formatted;
204                    } else {
205                        stdx::never!("formatted `AssocItem` could not be cast back to `AssocItem`");
206                    }
207                }
208                original_item
209            }
210            .reset_indent();
211
212            if let Some(source_scope) = sema.scope(original_item.syntax()) {
213                // FIXME: Paths in nested macros are not handled well. See
214                // `add_missing_impl_members::paths_in_nested_macro_should_get_transformed` test.
215                let transform =
216                    PathTransform::trait_impl(target_scope, &source_scope, trait_, impl_.clone());
217                cloned_item = ast::AssocItem::cast(transform.apply(cloned_item.syntax())).unwrap();
218            }
219            cloned_item.remove_attrs_and_docs();
220            cloned_item
221        })
222        .filter_map(|item| match item {
223            ast::AssocItem::Fn(fn_) if fn_.body().is_none() => {
224                let fn_ = fn_.clone_subtree();
225                let new_body = &make::block_expr(
226                    None,
227                    Some(match config.expr_fill_default {
228                        ExprFillDefaultMode::Todo => make::ext::expr_todo(),
229                        ExprFillDefaultMode::Underscore => make::ext::expr_underscore(),
230                        ExprFillDefaultMode::Default => make::ext::expr_todo(),
231                    }),
232                );
233                let new_body = AstNodeEdit::indent(new_body, IndentLevel::single());
234                let mut fn_editor = SyntaxEditor::new(fn_.syntax().clone());
235                fn_.replace_or_insert_body(&mut fn_editor, new_body);
236                let new_fn_ = fn_editor.finish().new_root().clone();
237                ast::AssocItem::cast(new_fn_)
238            }
239            ast::AssocItem::TypeAlias(type_alias) => {
240                let type_alias = type_alias.clone_subtree();
241                if let Some(type_bound_list) = type_alias.type_bound_list() {
242                    let mut type_alias_editor = SyntaxEditor::new(type_alias.syntax().clone());
243                    type_bound_list.remove(&mut type_alias_editor);
244                    let type_alias = type_alias_editor.finish().new_root().clone();
245                    ast::AssocItem::cast(type_alias)
246                } else {
247                    Some(ast::AssocItem::TypeAlias(type_alias))
248                }
249            }
250            item => Some(item),
251        })
252        .map(|item| AstNodeEdit::indent(&item, new_indent_level))
253        .collect()
254}
255
256pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize {
257    node.children_with_tokens()
258        .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR))
259        .map(|it| it.text_range().start())
260        .unwrap_or_else(|| node.text_range().start())
261}
262
263pub(crate) fn invert_boolean_expression(make: &SyntaxFactory, expr: ast::Expr) -> ast::Expr {
264    invert_special_case(make, &expr).unwrap_or_else(|| make.expr_prefix(T![!], expr).into())
265}
266
267// FIXME: Migrate usages of this function to the above function and remove this.
268pub(crate) fn invert_boolean_expression_legacy(expr: ast::Expr) -> ast::Expr {
269    invert_special_case_legacy(&expr).unwrap_or_else(|| make::expr_prefix(T![!], expr).into())
270}
271
272fn invert_special_case(make: &SyntaxFactory, expr: &ast::Expr) -> Option<ast::Expr> {
273    match expr {
274        ast::Expr::BinExpr(bin) => {
275            let op_kind = bin.op_kind()?;
276            let rev_kind = match op_kind {
277                ast::BinaryOp::CmpOp(ast::CmpOp::Eq { negated }) => {
278                    ast::BinaryOp::CmpOp(ast::CmpOp::Eq { negated: !negated })
279                }
280                ast::BinaryOp::CmpOp(ast::CmpOp::Ord { ordering: ast::Ordering::Less, strict }) => {
281                    ast::BinaryOp::CmpOp(ast::CmpOp::Ord {
282                        ordering: ast::Ordering::Greater,
283                        strict: !strict,
284                    })
285                }
286                ast::BinaryOp::CmpOp(ast::CmpOp::Ord {
287                    ordering: ast::Ordering::Greater,
288                    strict,
289                }) => ast::BinaryOp::CmpOp(ast::CmpOp::Ord {
290                    ordering: ast::Ordering::Less,
291                    strict: !strict,
292                }),
293                // Parenthesize other expressions before prefixing `!`
294                _ => {
295                    return Some(
296                        make.expr_prefix(T![!], make.expr_paren(expr.clone()).into()).into(),
297                    );
298                }
299            };
300
301            Some(make.expr_bin(bin.lhs()?, rev_kind, bin.rhs()?).into())
302        }
303        ast::Expr::MethodCallExpr(mce) => {
304            let receiver = mce.receiver()?;
305            let method = mce.name_ref()?;
306            let arg_list = mce.arg_list()?;
307
308            let method = match method.text().as_str() {
309                "is_some" => "is_none",
310                "is_none" => "is_some",
311                "is_ok" => "is_err",
312                "is_err" => "is_ok",
313                _ => return None,
314            };
315
316            Some(make.expr_method_call(receiver, make.name_ref(method), arg_list).into())
317        }
318        ast::Expr::PrefixExpr(pe) if pe.op_kind()? == ast::UnaryOp::Not => match pe.expr()? {
319            ast::Expr::ParenExpr(parexpr) => {
320                parexpr.expr().map(|e| e.clone_subtree().clone_for_update())
321            }
322            _ => pe.expr().map(|e| e.clone_subtree().clone_for_update()),
323        },
324        ast::Expr::Literal(lit) => match lit.kind() {
325            ast::LiteralKind::Bool(b) => match b {
326                true => Some(ast::Expr::Literal(make.expr_literal("false"))),
327                false => Some(ast::Expr::Literal(make.expr_literal("true"))),
328            },
329            _ => None,
330        },
331        _ => None,
332    }
333}
334
335fn invert_special_case_legacy(expr: &ast::Expr) -> Option<ast::Expr> {
336    match expr {
337        ast::Expr::BinExpr(bin) => {
338            let bin = bin.clone_subtree();
339            let op_token = bin.op_token()?;
340            let rev_token = match op_token.kind() {
341                T![==] => T![!=],
342                T![!=] => T![==],
343                T![<] => T![>=],
344                T![<=] => T![>],
345                T![>] => T![<=],
346                T![>=] => T![<],
347                // Parenthesize other expressions before prefixing `!`
348                _ => {
349                    return Some(
350                        make::expr_prefix(T![!], make::expr_paren(expr.clone()).into()).into(),
351                    );
352                }
353            };
354            let mut bin_editor = SyntaxEditor::new(bin.syntax().clone());
355            bin_editor.replace(op_token, make::token(rev_token));
356            ast::Expr::cast(bin_editor.finish().new_root().clone())
357        }
358        ast::Expr::MethodCallExpr(mce) => {
359            let receiver = mce.receiver()?;
360            let method = mce.name_ref()?;
361            let arg_list = mce.arg_list()?;
362
363            let method = match method.text().as_str() {
364                "is_some" => "is_none",
365                "is_none" => "is_some",
366                "is_ok" => "is_err",
367                "is_err" => "is_ok",
368                _ => return None,
369            };
370            Some(make::expr_method_call(receiver, make::name_ref(method), arg_list).into())
371        }
372        ast::Expr::PrefixExpr(pe) if pe.op_kind()? == ast::UnaryOp::Not => match pe.expr()? {
373            ast::Expr::ParenExpr(parexpr) => parexpr.expr(),
374            _ => pe.expr(),
375        },
376        ast::Expr::Literal(lit) => match lit.kind() {
377            ast::LiteralKind::Bool(b) => match b {
378                true => Some(ast::Expr::Literal(make::expr_literal("false"))),
379                false => Some(ast::Expr::Literal(make::expr_literal("true"))),
380            },
381            _ => None,
382        },
383        _ => None,
384    }
385}
386
387pub(crate) fn insert_attributes(
388    before: impl Element,
389    edit: &mut SyntaxEditor,
390    attrs: impl IntoIterator<Item = ast::Attr>,
391) {
392    let mut attrs = attrs.into_iter().peekable();
393    if attrs.peek().is_none() {
394        return;
395    }
396    let elem = before.syntax_element();
397    let indent = IndentLevel::from_element(&elem);
398    let whitespace = format!("\n{indent}");
399    edit.insert_all(
400        syntax::syntax_editor::Position::before(elem),
401        attrs
402            .flat_map(|attr| {
403                [attr.syntax().clone().into(), make::tokens::whitespace(&whitespace).into()]
404            })
405            .collect(),
406    );
407}
408
409pub(crate) fn next_prev() -> impl Iterator<Item = Direction> {
410    [Direction::Next, Direction::Prev].into_iter()
411}
412
413pub(crate) fn does_pat_match_variant(pat: &ast::Pat, var: &ast::Pat) -> bool {
414    let first_node_text = |pat: &ast::Pat| pat.syntax().first_child().map(|node| node.text());
415
416    let pat_head = match pat {
417        ast::Pat::IdentPat(bind_pat) => match bind_pat.pat() {
418            Some(p) => first_node_text(&p),
419            None => return pat.syntax().text() == var.syntax().text(),
420        },
421        pat => first_node_text(pat),
422    };
423
424    let var_head = first_node_text(var);
425
426    pat_head == var_head
427}
428
429pub(crate) fn does_pat_variant_nested_or_literal(ctx: &AssistContext<'_>, pat: &ast::Pat) -> bool {
430    check_pat_variant_nested_or_literal_with_depth(ctx, pat, 0)
431}
432
433fn check_pat_variant_from_enum(ctx: &AssistContext<'_>, pat: &ast::Pat) -> bool {
434    ctx.sema.type_of_pat(pat).is_none_or(|ty: hir::TypeInfo<'_>| {
435        ty.adjusted().as_adt().is_some_and(|adt| matches!(adt, hir::Adt::Enum(_)))
436    })
437}
438
439fn check_pat_variant_nested_or_literal_with_depth(
440    ctx: &AssistContext<'_>,
441    pat: &ast::Pat,
442    depth_after_refutable: usize,
443) -> bool {
444    if depth_after_refutable > 1 {
445        return true;
446    }
447
448    match pat {
449        ast::Pat::RestPat(_) | ast::Pat::WildcardPat(_) | ast::Pat::RefPat(_) => false,
450
451        ast::Pat::LiteralPat(_)
452        | ast::Pat::RangePat(_)
453        | ast::Pat::MacroPat(_)
454        | ast::Pat::PathPat(_)
455        | ast::Pat::BoxPat(_)
456        | ast::Pat::ConstBlockPat(_) => true,
457
458        ast::Pat::IdentPat(ident_pat) => ident_pat.pat().is_some_and(|pat| {
459            check_pat_variant_nested_or_literal_with_depth(ctx, &pat, depth_after_refutable)
460        }),
461        ast::Pat::ParenPat(paren_pat) => paren_pat.pat().is_none_or(|pat| {
462            check_pat_variant_nested_or_literal_with_depth(ctx, &pat, depth_after_refutable)
463        }),
464        ast::Pat::TuplePat(tuple_pat) => tuple_pat.fields().any(|pat| {
465            check_pat_variant_nested_or_literal_with_depth(ctx, &pat, depth_after_refutable)
466        }),
467        ast::Pat::RecordPat(record_pat) => {
468            let adjusted_next_depth =
469                depth_after_refutable + if check_pat_variant_from_enum(ctx, pat) { 1 } else { 0 };
470            record_pat.record_pat_field_list().is_none_or(|pat| {
471                pat.fields().any(|pat| {
472                    pat.pat().is_none_or(|pat| {
473                        check_pat_variant_nested_or_literal_with_depth(
474                            ctx,
475                            &pat,
476                            adjusted_next_depth,
477                        )
478                    })
479                })
480            })
481        }
482        ast::Pat::OrPat(or_pat) => or_pat.pats().any(|pat| {
483            check_pat_variant_nested_or_literal_with_depth(ctx, &pat, depth_after_refutable)
484        }),
485        ast::Pat::TupleStructPat(tuple_struct_pat) => {
486            let adjusted_next_depth =
487                depth_after_refutable + if check_pat_variant_from_enum(ctx, pat) { 1 } else { 0 };
488            tuple_struct_pat.fields().any(|pat| {
489                check_pat_variant_nested_or_literal_with_depth(ctx, &pat, adjusted_next_depth)
490            })
491        }
492        ast::Pat::SlicePat(slice_pat) => {
493            let mut pats = slice_pat.pats();
494            pats.next()
495                .is_none_or(|pat| !matches!(pat, ast::Pat::RestPat(_)) || pats.next().is_some())
496        }
497    }
498}
499
500// Uses a syntax-driven approach to find any impl blocks for the struct that
501// exist within the module/file
502//
503// Returns `None` if we've found an existing fn
504//
505// FIXME: change the new fn checking to a more semantic approach when that's more
506// viable (e.g. we process proc macros, etc)
507// FIXME: this partially overlaps with `find_impl_block_*`
508
509/// `find_struct_impl` looks for impl of a struct, but this also has additional feature
510/// where it takes a list of function names and check if they exist inside impl_, if
511/// even one match is found, it returns None.
512///
513/// That means this function can have 3 potential return values:
514///  - `None`: an impl exists, but one of the function names within the impl matches one of the provided names.
515///  - `Some(None)`: no impl exists.
516///  - `Some(Some(_))`: an impl exists, with no matching function names.
517pub(crate) fn find_struct_impl(
518    ctx: &AssistContext<'_>,
519    adt: &ast::Adt,
520    names: &[String],
521) -> Option<Option<ast::Impl>> {
522    let db = ctx.db();
523    let module = adt.syntax().parent()?;
524
525    let struct_def = ctx.sema.to_def(adt)?;
526
527    let block = module.descendants().filter_map(ast::Impl::cast).find_map(|impl_blk| {
528        let blk = ctx.sema.to_def(&impl_blk)?;
529
530        // FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}`
531        // (we currently use the wrong type parameter)
532        // also we wouldn't want to use e.g. `impl S<u32>`
533
534        let same_ty = match blk.self_ty(db).as_adt() {
535            Some(def) => def == struct_def,
536            None => false,
537        };
538        let not_trait_impl = blk.trait_(db).is_none();
539
540        if !(same_ty && not_trait_impl) { None } else { Some(impl_blk) }
541    });
542
543    if let Some(ref impl_blk) = block
544        && has_any_fn(impl_blk, names)
545    {
546        return None;
547    }
548
549    Some(block)
550}
551
552fn has_any_fn(imp: &ast::Impl, names: &[String]) -> bool {
553    if let Some(il) = imp.assoc_item_list() {
554        for item in il.assoc_items() {
555            if let ast::AssocItem::Fn(f) = item
556                && let Some(name) = f.name()
557                && names.iter().any(|n| n.eq_ignore_ascii_case(&name.text()))
558            {
559                return true;
560            }
561        }
562    }
563
564    false
565}
566
567/// Find the end of the `impl` block for the given `ast::Impl`.
568//
569// FIXME: this partially overlaps with `find_struct_impl`
570pub(crate) fn find_impl_block_end(impl_def: ast::Impl, buf: &mut String) -> Option<TextSize> {
571    buf.push('\n');
572    let end = impl_def
573        .assoc_item_list()
574        .and_then(|it| it.r_curly_token())?
575        .prev_sibling_or_token()?
576        .text_range()
577        .end();
578    Some(end)
579}
580
581/// Generates the surrounding `impl Type { <code> }` including type and lifetime
582/// parameters.
583// FIXME: migrate remaining uses to `generate_impl`
584pub(crate) fn generate_impl_text(adt: &ast::Adt, code: &str) -> String {
585    generate_impl_text_inner(adt, None, true, code)
586}
587
588/// Generates the surrounding `impl <trait> for Type { <code> }` including type
589/// and lifetime parameters, with `<trait>` appended to `impl`'s generic parameters' bounds.
590///
591/// This is useful for traits like `PartialEq`, since `impl<T> PartialEq for U<T>` often requires `T: PartialEq`.
592// FIXME: migrate remaining uses to `generate_trait_impl`
593#[allow(dead_code)]
594pub(crate) fn generate_trait_impl_text(adt: &ast::Adt, trait_text: &str, code: &str) -> String {
595    generate_impl_text_inner(adt, Some(trait_text), true, code)
596}
597
598/// Generates the surrounding `impl <trait> for Type { <code> }` including type
599/// and lifetime parameters, with `impl`'s generic parameters' bounds kept as-is.
600///
601/// This is useful for traits like `From<T>`, since `impl<T> From<T> for U<T>` doesn't require `T: From<T>`.
602// FIXME: migrate remaining uses to `generate_trait_impl_intransitive`
603pub(crate) fn generate_trait_impl_text_intransitive(
604    adt: &ast::Adt,
605    trait_text: &str,
606    code: &str,
607) -> String {
608    generate_impl_text_inner(adt, Some(trait_text), false, code)
609}
610
611fn generate_impl_text_inner(
612    adt: &ast::Adt,
613    trait_text: Option<&str>,
614    trait_is_transitive: bool,
615    code: &str,
616) -> String {
617    // Ensure lifetime params are before type & const params
618    let generic_params = adt.generic_param_list().map(|generic_params| {
619        let lifetime_params =
620            generic_params.lifetime_params().map(ast::GenericParam::LifetimeParam);
621        let ty_or_const_params = generic_params.type_or_const_params().filter_map(|param| {
622            let param = match param {
623                ast::TypeOrConstParam::Type(param) => {
624                    // remove defaults since they can't be specified in impls
625                    let mut bounds =
626                        param.type_bound_list().map_or_else(Vec::new, |it| it.bounds().collect());
627                    if let Some(trait_) = trait_text {
628                        // Add the current trait to `bounds` if the trait is transitive,
629                        // meaning `impl<T> Trait for U<T>` requires `T: Trait`.
630                        if trait_is_transitive {
631                            bounds.push(make::type_bound_text(trait_));
632                        }
633                    };
634                    // `{ty_param}: {bounds}`
635                    let param = make::type_param(param.name()?, make::type_bound_list(bounds));
636                    ast::GenericParam::TypeParam(param)
637                }
638                ast::TypeOrConstParam::Const(param) => {
639                    // remove defaults since they can't be specified in impls
640                    let param = make::const_param(param.name()?, param.ty()?);
641                    ast::GenericParam::ConstParam(param)
642                }
643            };
644            Some(param)
645        });
646
647        make::generic_param_list(itertools::chain(lifetime_params, ty_or_const_params))
648    });
649
650    // FIXME: use syntax::make & mutable AST apis instead
651    // `trait_text` and `code` can't be opaque blobs of text
652    let mut buf = String::with_capacity(code.len());
653
654    // Copy any cfg attrs from the original adt
655    buf.push_str("\n\n");
656    let cfg_attrs = adt
657        .attrs()
658        .filter(|attr| attr.as_simple_call().map(|(name, _arg)| name == "cfg").unwrap_or(false));
659    cfg_attrs.for_each(|attr| buf.push_str(&format!("{attr}\n")));
660
661    // `impl{generic_params} {trait_text} for {name}{generic_params.to_generic_args()}`
662    buf.push_str("impl");
663    if let Some(generic_params) = &generic_params {
664        format_to!(buf, "{generic_params}");
665    }
666    buf.push(' ');
667    if let Some(trait_text) = trait_text {
668        buf.push_str(trait_text);
669        buf.push_str(" for ");
670    }
671    buf.push_str(&adt.name().unwrap().text());
672    if let Some(generic_params) = generic_params {
673        format_to!(buf, "{}", generic_params.to_generic_args());
674    }
675
676    match adt.where_clause() {
677        Some(where_clause) => {
678            format_to!(buf, "\n{where_clause}\n{{\n{code}\n}}");
679        }
680        None => {
681            format_to!(buf, " {{\n{code}\n}}");
682        }
683    }
684
685    buf
686}
687
688/// Generates the corresponding `impl Type {}` including type and lifetime
689/// parameters.
690pub(crate) fn generate_impl_with_item(
691    adt: &ast::Adt,
692    body: Option<ast::AssocItemList>,
693) -> ast::Impl {
694    generate_impl_inner(false, adt, None, true, body)
695}
696
697pub(crate) fn generate_impl(adt: &ast::Adt) -> ast::Impl {
698    generate_impl_inner(false, adt, None, true, None)
699}
700
701/// Generates the corresponding `impl <trait> for Type {}` including type
702/// and lifetime parameters, with `<trait>` appended to `impl`'s generic parameters' bounds.
703///
704/// This is useful for traits like `PartialEq`, since `impl<T> PartialEq for U<T>` often requires `T: PartialEq`.
705pub(crate) fn generate_trait_impl(is_unsafe: bool, adt: &ast::Adt, trait_: ast::Type) -> ast::Impl {
706    generate_impl_inner(is_unsafe, adt, Some(trait_), true, None)
707}
708
709/// Generates the corresponding `impl <trait> for Type {}` including type
710/// and lifetime parameters, with `impl`'s generic parameters' bounds kept as-is.
711///
712/// This is useful for traits like `From<T>`, since `impl<T> From<T> for U<T>` doesn't require `T: From<T>`.
713pub(crate) fn generate_trait_impl_intransitive(adt: &ast::Adt, trait_: ast::Type) -> ast::Impl {
714    generate_impl_inner(false, adt, Some(trait_), false, None)
715}
716
717fn generate_impl_inner(
718    is_unsafe: bool,
719    adt: &ast::Adt,
720    trait_: Option<ast::Type>,
721    trait_is_transitive: bool,
722    body: Option<ast::AssocItemList>,
723) -> ast::Impl {
724    // Ensure lifetime params are before type & const params
725    let generic_params = adt.generic_param_list().map(|generic_params| {
726        let lifetime_params =
727            generic_params.lifetime_params().map(ast::GenericParam::LifetimeParam);
728        let ty_or_const_params = generic_params.type_or_const_params().filter_map(|param| {
729            let param = match param {
730                ast::TypeOrConstParam::Type(param) => {
731                    // remove defaults since they can't be specified in impls
732                    let mut bounds =
733                        param.type_bound_list().map_or_else(Vec::new, |it| it.bounds().collect());
734                    if let Some(trait_) = &trait_ {
735                        // Add the current trait to `bounds` if the trait is transitive,
736                        // meaning `impl<T> Trait for U<T>` requires `T: Trait`.
737                        if trait_is_transitive {
738                            bounds.push(make::type_bound(trait_.clone()));
739                        }
740                    };
741                    // `{ty_param}: {bounds}`
742                    let param = make::type_param(param.name()?, make::type_bound_list(bounds));
743                    ast::GenericParam::TypeParam(param)
744                }
745                ast::TypeOrConstParam::Const(param) => {
746                    // remove defaults since they can't be specified in impls
747                    let param = make::const_param(param.name()?, param.ty()?);
748                    ast::GenericParam::ConstParam(param)
749                }
750            };
751            Some(param)
752        });
753
754        make::generic_param_list(itertools::chain(lifetime_params, ty_or_const_params))
755    });
756    let generic_args =
757        generic_params.as_ref().map(|params| params.to_generic_args().clone_for_update());
758    let ty = make::ty_path(make::ext::ident_path(&adt.name().unwrap().text()));
759
760    let cfg_attrs =
761        adt.attrs().filter(|attr| attr.as_simple_call().is_some_and(|(name, _arg)| name == "cfg"));
762    match trait_ {
763        Some(trait_) => make::impl_trait(
764            cfg_attrs,
765            is_unsafe,
766            None,
767            None,
768            generic_params,
769            generic_args,
770            false,
771            trait_,
772            ty,
773            None,
774            adt.where_clause(),
775            body,
776        ),
777        None => make::impl_(cfg_attrs, generic_params, generic_args, ty, adt.where_clause(), body),
778    }
779    .clone_for_update()
780}
781
782pub(crate) fn add_method_to_adt(
783    builder: &mut SourceChangeBuilder,
784    adt: &ast::Adt,
785    impl_def: Option<ast::Impl>,
786    method: &str,
787) {
788    let mut buf = String::with_capacity(method.len() + 2);
789    if impl_def.is_some() {
790        buf.push('\n');
791    }
792    buf.push_str(method);
793
794    let start_offset = impl_def
795        .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf))
796        .unwrap_or_else(|| {
797            buf = generate_impl_text(adt, &buf);
798            adt.syntax().text_range().end()
799        });
800
801    builder.insert(start_offset, buf);
802}
803
804#[derive(Debug)]
805pub(crate) struct ReferenceConversion<'db> {
806    conversion: ReferenceConversionType,
807    ty: hir::Type<'db>,
808    impls_deref: bool,
809}
810
811#[derive(Debug)]
812enum ReferenceConversionType {
813    // reference can be stripped if the type is Copy
814    Copy,
815    // &String -> &str
816    AsRefStr,
817    // &Vec<T> -> &[T]
818    AsRefSlice,
819    // &Box<T> -> &T
820    Dereferenced,
821    // &Option<T> -> Option<&T>
822    Option,
823    // &Result<T, E> -> Result<&T, &E>
824    Result,
825}
826
827impl<'db> ReferenceConversion<'db> {
828    pub(crate) fn convert_type(
829        &self,
830        db: &'db dyn HirDatabase,
831        display_target: DisplayTarget,
832    ) -> ast::Type {
833        let ty = match self.conversion {
834            ReferenceConversionType::Copy => self.ty.display(db, display_target).to_string(),
835            ReferenceConversionType::AsRefStr => "&str".to_owned(),
836            ReferenceConversionType::AsRefSlice => {
837                let type_argument_name = self
838                    .ty
839                    .type_arguments()
840                    .next()
841                    .unwrap()
842                    .display(db, display_target)
843                    .to_string();
844                format!("&[{type_argument_name}]")
845            }
846            ReferenceConversionType::Dereferenced => {
847                let type_argument_name = self
848                    .ty
849                    .type_arguments()
850                    .next()
851                    .unwrap()
852                    .display(db, display_target)
853                    .to_string();
854                format!("&{type_argument_name}")
855            }
856            ReferenceConversionType::Option => {
857                let type_argument_name = self
858                    .ty
859                    .type_arguments()
860                    .next()
861                    .unwrap()
862                    .display(db, display_target)
863                    .to_string();
864                format!("Option<&{type_argument_name}>")
865            }
866            ReferenceConversionType::Result => {
867                let mut type_arguments = self.ty.type_arguments();
868                let first_type_argument_name =
869                    type_arguments.next().unwrap().display(db, display_target).to_string();
870                let second_type_argument_name =
871                    type_arguments.next().unwrap().display(db, display_target).to_string();
872                format!("Result<&{first_type_argument_name}, &{second_type_argument_name}>")
873            }
874        };
875
876        make::ty(&ty)
877    }
878
879    pub(crate) fn getter(&self, field_name: String) -> ast::Expr {
880        let expr = make::expr_field(make::ext::expr_self(), &field_name);
881
882        match self.conversion {
883            ReferenceConversionType::Copy => expr,
884            ReferenceConversionType::AsRefStr
885            | ReferenceConversionType::AsRefSlice
886            | ReferenceConversionType::Dereferenced
887            | ReferenceConversionType::Option
888            | ReferenceConversionType::Result => {
889                if self.impls_deref {
890                    make::expr_ref(expr, false)
891                } else {
892                    make::expr_method_call(expr, make::name_ref("as_ref"), make::arg_list([]))
893                        .into()
894                }
895            }
896        }
897    }
898}
899
900// FIXME: It should return a new hir::Type, but currently constructing new types is too cumbersome
901//        and all users of this function operate on string type names, so they can do the conversion
902//        itself themselves.
903pub(crate) fn convert_reference_type<'db>(
904    ty: hir::Type<'db>,
905    db: &'db RootDatabase,
906    famous_defs: &FamousDefs<'_, 'db>,
907) -> Option<ReferenceConversion<'db>> {
908    handle_copy(&ty, db)
909        .or_else(|| handle_as_ref_str(&ty, db, famous_defs))
910        .or_else(|| handle_as_ref_slice(&ty, db, famous_defs))
911        .or_else(|| handle_dereferenced(&ty, db, famous_defs))
912        .or_else(|| handle_option_as_ref(&ty, db, famous_defs))
913        .or_else(|| handle_result_as_ref(&ty, db, famous_defs))
914        .map(|(conversion, impls_deref)| ReferenceConversion { ty, conversion, impls_deref })
915}
916
917fn could_deref_to_target(ty: &hir::Type<'_>, target: &hir::Type<'_>, db: &dyn HirDatabase) -> bool {
918    let ty_ref = ty.add_reference(hir::Mutability::Shared);
919    let target_ref = target.add_reference(hir::Mutability::Shared);
920    ty_ref.could_coerce_to(db, &target_ref)
921}
922
923fn handle_copy(
924    ty: &hir::Type<'_>,
925    db: &dyn HirDatabase,
926) -> Option<(ReferenceConversionType, bool)> {
927    ty.is_copy(db).then_some((ReferenceConversionType::Copy, true))
928}
929
930fn handle_as_ref_str(
931    ty: &hir::Type<'_>,
932    db: &dyn HirDatabase,
933    famous_defs: &FamousDefs<'_, '_>,
934) -> Option<(ReferenceConversionType, bool)> {
935    let str_type = hir::BuiltinType::str().ty(db);
936
937    ty.impls_trait(db, famous_defs.core_convert_AsRef()?, slice::from_ref(&str_type))
938        .then_some((ReferenceConversionType::AsRefStr, could_deref_to_target(ty, &str_type, db)))
939}
940
941fn handle_as_ref_slice(
942    ty: &hir::Type<'_>,
943    db: &dyn HirDatabase,
944    famous_defs: &FamousDefs<'_, '_>,
945) -> Option<(ReferenceConversionType, bool)> {
946    let type_argument = ty.type_arguments().next()?;
947    let slice_type = hir::Type::new_slice(type_argument);
948
949    ty.impls_trait(db, famous_defs.core_convert_AsRef()?, slice::from_ref(&slice_type)).then_some((
950        ReferenceConversionType::AsRefSlice,
951        could_deref_to_target(ty, &slice_type, db),
952    ))
953}
954
955fn handle_dereferenced(
956    ty: &hir::Type<'_>,
957    db: &dyn HirDatabase,
958    famous_defs: &FamousDefs<'_, '_>,
959) -> Option<(ReferenceConversionType, bool)> {
960    let type_argument = ty.type_arguments().next()?;
961
962    ty.impls_trait(db, famous_defs.core_convert_AsRef()?, slice::from_ref(&type_argument))
963        .then_some((
964            ReferenceConversionType::Dereferenced,
965            could_deref_to_target(ty, &type_argument, db),
966        ))
967}
968
969fn handle_option_as_ref(
970    ty: &hir::Type<'_>,
971    db: &dyn HirDatabase,
972    famous_defs: &FamousDefs<'_, '_>,
973) -> Option<(ReferenceConversionType, bool)> {
974    if ty.as_adt() == famous_defs.core_option_Option()?.ty(db).as_adt() {
975        Some((ReferenceConversionType::Option, false))
976    } else {
977        None
978    }
979}
980
981fn handle_result_as_ref(
982    ty: &hir::Type<'_>,
983    db: &dyn HirDatabase,
984    famous_defs: &FamousDefs<'_, '_>,
985) -> Option<(ReferenceConversionType, bool)> {
986    if ty.as_adt() == famous_defs.core_result_Result()?.ty(db).as_adt() {
987        Some((ReferenceConversionType::Result, false))
988    } else {
989        None
990    }
991}
992
993pub(crate) fn get_methods(items: &ast::AssocItemList) -> Vec<ast::Fn> {
994    items
995        .assoc_items()
996        .flat_map(|i| match i {
997            ast::AssocItem::Fn(f) => Some(f),
998            _ => None,
999        })
1000        .filter(|f| f.name().is_some())
1001        .collect()
1002}
1003
1004/// Trim(remove leading and trailing whitespace) `initial_range` in `source_file`, return the trimmed range.
1005pub(crate) fn trimmed_text_range(source_file: &SourceFile, initial_range: TextRange) -> TextRange {
1006    let mut trimmed_range = initial_range;
1007    while source_file
1008        .syntax()
1009        .token_at_offset(trimmed_range.start())
1010        .find_map(Whitespace::cast)
1011        .is_some()
1012        && trimmed_range.start() < trimmed_range.end()
1013    {
1014        let start = trimmed_range.start() + TextSize::from(1);
1015        trimmed_range = TextRange::new(start, trimmed_range.end());
1016    }
1017    while source_file
1018        .syntax()
1019        .token_at_offset(trimmed_range.end())
1020        .find_map(Whitespace::cast)
1021        .is_some()
1022        && trimmed_range.start() < trimmed_range.end()
1023    {
1024        let end = trimmed_range.end() - TextSize::from(1);
1025        trimmed_range = TextRange::new(trimmed_range.start(), end);
1026    }
1027    trimmed_range
1028}
1029
1030/// Convert a list of function params to a list of arguments that can be passed
1031/// into a function call.
1032pub(crate) fn convert_param_list_to_arg_list(list: ast::ParamList) -> ast::ArgList {
1033    let mut args = vec![];
1034    for param in list.params() {
1035        if let Some(ast::Pat::IdentPat(pat)) = param.pat()
1036            && let Some(name) = pat.name()
1037        {
1038            let name = name.to_string();
1039            let expr = make::expr_path(make::ext::ident_path(&name));
1040            args.push(expr);
1041        }
1042    }
1043    make::arg_list(args)
1044}
1045
1046/// Calculate the number of hashes required for a raw string containing `s`
1047pub(crate) fn required_hashes(s: &str) -> usize {
1048    let mut res = 0usize;
1049    for idx in s.match_indices('"').map(|(i, _)| i) {
1050        let (_, sub) = s.split_at(idx + 1);
1051        let n_hashes = sub.chars().take_while(|c| *c == '#').count();
1052        res = res.max(n_hashes + 1)
1053    }
1054    res
1055}
1056#[test]
1057fn test_required_hashes() {
1058    assert_eq!(0, required_hashes("abc"));
1059    assert_eq!(0, required_hashes("###"));
1060    assert_eq!(1, required_hashes("\""));
1061    assert_eq!(2, required_hashes("\"#abc"));
1062    assert_eq!(0, required_hashes("#abc"));
1063    assert_eq!(3, required_hashes("#ab\"##c"));
1064    assert_eq!(5, required_hashes("#ab\"##\"####c"));
1065}
1066
1067/// Calculate the string literal suffix length
1068pub(crate) fn string_suffix(s: &str) -> Option<&str> {
1069    s.rfind(['"', '\'', '#']).map(|i| &s[i + 1..])
1070}
1071#[test]
1072fn test_string_suffix() {
1073    assert_eq!(Some(""), string_suffix(r#""abc""#));
1074    assert_eq!(Some(""), string_suffix(r#""""#));
1075    assert_eq!(Some("a"), string_suffix(r#"""a"#));
1076    assert_eq!(Some("i32"), string_suffix(r#"""i32"#));
1077    assert_eq!(Some("i32"), string_suffix(r#"r""i32"#));
1078    assert_eq!(Some("i32"), string_suffix(r##"r#""#i32"##));
1079}
1080
1081/// Calculate the string literal prefix length
1082pub(crate) fn string_prefix(s: &str) -> Option<&str> {
1083    s.split_once(['"', '\'', '#']).map(|(prefix, _)| prefix)
1084}
1085#[test]
1086fn test_string_prefix() {
1087    assert_eq!(Some(""), string_prefix(r#""abc""#));
1088    assert_eq!(Some(""), string_prefix(r#""""#));
1089    assert_eq!(Some(""), string_prefix(r#"""suffix"#));
1090    assert_eq!(Some("c"), string_prefix(r#"c"""#));
1091    assert_eq!(Some("r"), string_prefix(r#"r"""#));
1092    assert_eq!(Some("cr"), string_prefix(r#"cr"""#));
1093    assert_eq!(Some("r"), string_prefix(r##"r#""#"##));
1094}
1095
1096pub(crate) fn add_group_separators(s: &str, group_size: usize) -> String {
1097    let mut chars = Vec::new();
1098    for (i, ch) in s.chars().filter(|&ch| ch != '_').rev().enumerate() {
1099        if i > 0 && i % group_size == 0 && ch != '-' {
1100            chars.push('_');
1101        }
1102        chars.push(ch);
1103    }
1104
1105    chars.into_iter().rev().collect()
1106}
1107
1108/// Replaces the record expression, handling field shorthands including inside macros.
1109pub(crate) fn replace_record_field_expr(
1110    ctx: &AssistContext<'_>,
1111    edit: &mut SourceChangeBuilder,
1112    record_field: ast::RecordExprField,
1113    initializer: ast::Expr,
1114) {
1115    if let Some(ast::Expr::PathExpr(path_expr)) = record_field.expr() {
1116        // replace field shorthand
1117        let file_range = ctx.sema.original_range(path_expr.syntax());
1118        edit.insert(file_range.range.end(), format!(": {}", initializer.syntax().text()))
1119    } else if let Some(expr) = record_field.expr() {
1120        // just replace expr
1121        let file_range = ctx.sema.original_range(expr.syntax());
1122        edit.replace(file_range.range, initializer.syntax().text());
1123    }
1124}
1125
1126/// Creates a token tree list from a syntax node, creating the needed delimited sub token trees.
1127/// Assumes that the input syntax node is a valid syntax tree.
1128pub(crate) fn tt_from_syntax(node: SyntaxNode) -> Vec<NodeOrToken<ast::TokenTree, SyntaxToken>> {
1129    let mut tt_stack = vec![(None, vec![])];
1130
1131    for element in node.descendants_with_tokens() {
1132        let NodeOrToken::Token(token) = element else { continue };
1133
1134        match token.kind() {
1135            T!['('] | T!['{'] | T!['['] => {
1136                // Found an opening delimiter, start a new sub token tree
1137                tt_stack.push((Some(token.kind()), vec![]));
1138            }
1139            T![')'] | T!['}'] | T![']'] => {
1140                // Closing a subtree
1141                let (delimiter, tt) = tt_stack.pop().expect("unbalanced delimiters");
1142                let (_, parent_tt) = tt_stack
1143                    .last_mut()
1144                    .expect("parent token tree was closed before it was completed");
1145                let closing_delimiter = delimiter.map(|it| match it {
1146                    T!['('] => T![')'],
1147                    T!['{'] => T!['}'],
1148                    T!['['] => T![']'],
1149                    _ => unreachable!(),
1150                });
1151                stdx::always!(
1152                    closing_delimiter == Some(token.kind()),
1153                    "mismatched opening and closing delimiters"
1154                );
1155
1156                let sub_tt = make::token_tree(delimiter.expect("unbalanced delimiters"), tt);
1157                parent_tt.push(NodeOrToken::Node(sub_tt));
1158            }
1159            _ => {
1160                let (_, current_tt) = tt_stack.last_mut().expect("unmatched delimiters");
1161                current_tt.push(NodeOrToken::Token(token))
1162            }
1163        }
1164    }
1165
1166    tt_stack.pop().expect("parent token tree was closed before it was completed").1
1167}
1168
1169pub(crate) fn cover_let_chain(mut expr: ast::Expr, range: TextRange) -> Option<ast::Expr> {
1170    if !expr.syntax().text_range().contains_range(range) {
1171        return None;
1172    }
1173    loop {
1174        let (chain_expr, rest) = if let ast::Expr::BinExpr(bin_expr) = &expr
1175            && bin_expr.op_kind() == Some(ast::BinaryOp::LogicOp(ast::LogicOp::And))
1176        {
1177            (bin_expr.rhs(), bin_expr.lhs())
1178        } else {
1179            (Some(expr), None)
1180        };
1181
1182        if let Some(chain_expr) = chain_expr
1183            && chain_expr.syntax().text_range().contains_range(range)
1184        {
1185            break Some(chain_expr);
1186        }
1187        expr = rest?;
1188    }
1189}
1190
1191pub(crate) fn is_selected(
1192    it: &impl AstNode,
1193    selection: syntax::TextRange,
1194    allow_empty: bool,
1195) -> bool {
1196    selection.intersect(it.syntax().text_range()).is_some_and(|it| !it.is_empty())
1197        || allow_empty && it.syntax().text_range().contains_range(selection)
1198}
1199
1200pub fn is_body_const(sema: &Semantics<'_, RootDatabase>, expr: &ast::Expr) -> bool {
1201    let mut is_const = true;
1202    preorder_expr(expr, &mut |ev| {
1203        let expr = match ev {
1204            WalkEvent::Enter(_) if !is_const => return true,
1205            WalkEvent::Enter(expr) => expr,
1206            WalkEvent::Leave(_) => return false,
1207        };
1208        match expr {
1209            ast::Expr::CallExpr(call) => {
1210                if let Some(ast::Expr::PathExpr(path_expr)) = call.expr()
1211                    && let Some(PathResolution::Def(ModuleDef::Function(func))) =
1212                        path_expr.path().and_then(|path| sema.resolve_path(&path))
1213                {
1214                    is_const &= func.is_const(sema.db);
1215                }
1216            }
1217            ast::Expr::MethodCallExpr(call) => {
1218                is_const &=
1219                    sema.resolve_method_call(&call).map(|it| it.is_const(sema.db)).unwrap_or(true)
1220            }
1221            ast::Expr::ForExpr(_)
1222            | ast::Expr::ReturnExpr(_)
1223            | ast::Expr::TryExpr(_)
1224            | ast::Expr::YieldExpr(_)
1225            | ast::Expr::AwaitExpr(_) => is_const = false,
1226            _ => (),
1227        }
1228        !is_const
1229    });
1230    is_const
1231}
1232
1233// FIXME: #20460 When hir-ty can analyze the `never` statement at the end of block, remove it
1234pub(crate) fn is_never_block(
1235    sema: &Semantics<'_, RootDatabase>,
1236    block_expr: &ast::BlockExpr,
1237) -> bool {
1238    if let Some(tail_expr) = block_expr.tail_expr() {
1239        sema.type_of_expr(&tail_expr).is_some_and(|ty| ty.original.is_never())
1240    } else if let Some(ast::Stmt::ExprStmt(expr_stmt)) = block_expr.statements().last()
1241        && let Some(expr) = expr_stmt.expr()
1242    {
1243        sema.type_of_expr(&expr).is_some_and(|ty| ty.original.is_never())
1244    } else {
1245        false
1246    }
1247}