summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/crates/ide
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/Cargo.toml31
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/assist_config.rs16
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/assist_context.rs347
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_explicit_type.rs325
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_label_to_loop.rs164
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_lifetime_to_type.rs229
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs1340
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs1709
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_return_type.rs447
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_turbo_fish.rs400
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs234
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/auto_import.rs1292
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/change_visibility.rs216
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs575
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_comment_block.rs395
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_integer_literal.rs268
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_into_to_from.rs351
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs556
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_let_else_to_match.rs497
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs574
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs840
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_while_to_loop.rs188
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs2147
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/expand_glob_import.rs900
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs5333
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_module.rs1770
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs1076
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs360
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs1279
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs606
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/flip_binexpr.rs139
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/flip_comma.rs92
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/flip_trait_bound.rs121
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_constant.rs255
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_enum_variant.rs179
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs657
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs334
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_deref.rs343
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_derive.rs132
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_documentation_template.rs1328
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_is_method.rs316
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_projection_method.rs342
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_variant.rs227
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_from_impl_for_enum.rs310
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs1787
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter.rs492
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs177
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_is_empty_from_len.rs295
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs495
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_setter.rs184
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs1194
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_local_variable.rs954
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs838
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_generic.rs144
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_lifetime.rs338
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/invert_if.rs144
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_imports.rs570
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_match_arms.rs822
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_bounds.rs122
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_from_mod_rs.rs130
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_guard.rs997
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_module_to_file.rs337
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_to_mod_rs.rs151
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/number_representation.rs183
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/promote_local_to_const.rs221
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/pull_assignment_up.rs507
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_method_call.rs548
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_path.rs1297
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/raw_string.rs509
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs241
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_mut.rs37
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_unused_param.rs409
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/reorder_fields.rs212
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/reorder_impl_items.rs284
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs1250
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs999
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_let_with_if_let.rs100
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs438
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_string_with_char.rs307
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_try_expr_with_match.rs150
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_turbofish_with_explicit_type.rs243
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/sort_items.rs588
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/split_import.rs82
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_ignore.rs98
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/unmerge_use.rs237
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/unnecessary_async.rs257
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs719
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_result_return_type.rs1020
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_return_type_in_result.rs980
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/lib.rs309
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/tests.rs558
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs2259
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/tests/sourcegen.rs195
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/utils.rs703
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs661
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/utils/suggest_name.rs775
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/Cargo.toml33
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions.rs691
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs380
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/cfg.rs93
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/derive.rs116
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/lint.rs61
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/repr.rs74
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs947
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/expr.rs280
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/extern_abi.rs108
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/field.rs43
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/flyimport.rs407
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/fn_param.rs196
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/format_string.rs130
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list.rs133
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs1160
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs237
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/lifetime.rs341
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/mod_.rs354
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs185
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs616
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs311
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/record.rs369
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/snippet.rs189
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/type.rs246
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/use_.rs120
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/vis.rs41
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/config.rs41
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/context.rs639
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs1293
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs413
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/item.rs637
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/lib.rs247
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/render.rs1910
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/render/const_.rs33
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs671
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/render/literal.rs191
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/render/macro_.rs270
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/render/pattern.rs193
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/render/type_alias.rs57
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/render/union_literal.rs77
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/render/variant.rs96
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/snippet.rs214
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests.rs305
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs1016
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs672
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs1232
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/fn_param.rs274
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/item.rs154
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs247
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs716
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/predicate.rs131
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/proc_macros.rs133
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/record.rs229
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs895
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/type_pos.rs671
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/use_tree.rs384
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/visibility.rs90
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/Cargo.toml39
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/active_parameter.rs78
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/apply_change.rs163
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/assists.rs137
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/defs.rs545
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/famous_defs.rs185
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs7682
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/helpers.rs105
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs674
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs446
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use/tests.rs1084
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs295
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/items_locator.rs151
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/label.rs48
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/lib.rs246
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/line_index.rs300
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs287
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/rename.rs540
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/rust_doc.rs34
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/search.rs785
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/source_change.rs99
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs429
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/format_string.rs308
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/insert_whitespace_into_node.rs136
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/node_ext.rs460
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt533
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/tests/sourcegen_lints.rs284
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/traits.rs273
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/ty_filter.rs86
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/use_trivial_contructor.rs34
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/Cargo.toml34
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs30
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/field_shorthand.rs203
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/inactive_code.rs144
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs486
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/invalid_derive_target.rs38
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/macro_error.rs218
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/malformed_derive.rs37
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs334
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs837
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_match_arms.rs1012
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs101
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs283
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs131
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs573
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unimplemented_builtin_macro.rs16
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs336
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_extern_crate.rs49
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_import.rs90
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs76
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_module.rs156
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs62
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/useless_braces.rs148
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs260
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/tests.rs145
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/tests/sourcegen.rs73
-rw-r--r--src/tools/rust-analyzer/crates/ide-ssr/Cargo.toml26
-rw-r--r--src/tools/rust-analyzer/crates/ide-ssr/src/errors.rs29
-rw-r--r--src/tools/rust-analyzer/crates/ide-ssr/src/fragments.rs58
-rw-r--r--src/tools/rust-analyzer/crates/ide-ssr/src/from_comment.rs35
-rw-r--r--src/tools/rust-analyzer/crates/ide-ssr/src/lib.rs358
-rw-r--r--src/tools/rust-analyzer/crates/ide-ssr/src/matching.rs803
-rw-r--r--src/tools/rust-analyzer/crates/ide-ssr/src/nester.rs99
-rw-r--r--src/tools/rust-analyzer/crates/ide-ssr/src/parsing.rs406
-rw-r--r--src/tools/rust-analyzer/crates/ide-ssr/src/replacing.rs242
-rw-r--r--src/tools/rust-analyzer/crates/ide-ssr/src/resolving.rs308
-rw-r--r--src/tools/rust-analyzer/crates/ide-ssr/src/search.rs289
-rw-r--r--src/tools/rust-analyzer/crates/ide-ssr/src/tests.rs1397
-rw-r--r--src/tools/rust-analyzer/crates/ide/Cargo.toml47
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/annotations.rs789
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs460
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/doc_links.rs549
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/doc_links/intra_doc_links.rs77
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/doc_links/tests.rs491
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/expand_macro.rs521
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/extend_selection.rs662
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/file_structure.rs579
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/fixture.rs87
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/fn_references.rs94
-rwxr-xr-xsrc/tools/rust-analyzer/crates/ide/src/folding_ranges.rs626
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs112
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/goto_definition.rs1634
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs344
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs296
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/highlight_related.rs1377
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/hover.rs390
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/hover/render.rs563
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/hover/tests.rs5053
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs2818
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/join_lines.rs1087
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/lib.rs702
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/markdown_remove.rs22
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/markup.rs38
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/matching_brace.rs78
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/moniker.rs342
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/move_item.rs890
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/navigation_target.rs623
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/parent_module.rs167
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/prime_caches.rs158
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/prime_caches/topologic_sort.rs98
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/references.rs1636
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/rename.rs2252
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/runnables.rs2163
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/shuffle_crate_graph.rs71
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/signature_help.rs1334
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/ssr.rs255
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/static_index.rs321
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/status.rs164
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs449
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/escape.rs25
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/format.rs50
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs690
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlights.rs92
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/html.rs97
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs279
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/injector.rs81
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/macro_.rs128
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tags.rs340
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html62
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html58
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html66
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html50
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html190
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html47
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_general.html233
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html62
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html58
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_lifetimes.html55
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html96
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_inline.html51
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_outline.html50
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_operators.html58
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_rainbow.html56
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html164
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html126
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs1096
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_tree.rs339
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/typing.rs1210
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/typing/on_enter.rs616
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/view_crate_graph.rs93
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/view_hir.rs26
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/view_item_tree.rs16
296 files changed, 142246 insertions, 0 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-assists/Cargo.toml b/src/tools/rust-analyzer/crates/ide-assists/Cargo.toml
new file mode 100644
index 000000000..fca09d384
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+name = "ide-assists"
+version = "0.0.0"
+description = "TBD"
+license = "MIT OR Apache-2.0"
+edition = "2021"
+rust-version = "1.57"
+
+[lib]
+doctest = false
+
+[dependencies]
+cov-mark = "2.0.0-pre.1"
+
+itertools = "0.10.3"
+either = "1.7.0"
+
+stdx = { path = "../stdx", version = "0.0.0" }
+syntax = { path = "../syntax", version = "0.0.0" }
+text-edit = { path = "../text-edit", version = "0.0.0" }
+profile = { path = "../profile", version = "0.0.0" }
+ide-db = { path = "../ide-db", version = "0.0.0" }
+hir = { path = "../hir", version = "0.0.0" }
+
+[dev-dependencies]
+test-utils = { path = "../test-utils" }
+sourcegen = { path = "../sourcegen" }
+expect-test = "1.4.0"
+
+[features]
+in-rust-tree = []
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/assist_config.rs b/src/tools/rust-analyzer/crates/ide-assists/src/assist_config.rs
new file mode 100644
index 000000000..d4d148c77
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/assist_config.rs
@@ -0,0 +1,16 @@
+//! Settings for tweaking assists.
+//!
+//! The fun thing here is `SnippetCap` -- this type can only be created in this
+//! module, and we use to statically check that we only produce snippet
+//! assists if we are allowed to.
+
+use ide_db::{imports::insert_use::InsertUseConfig, SnippetCap};
+
+use crate::AssistKind;
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct AssistConfig {
+ pub snippet_cap: Option<SnippetCap>,
+ pub allowed: Option<Vec<AssistKind>>,
+ pub insert_use: InsertUseConfig,
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/assist_context.rs b/src/tools/rust-analyzer/crates/ide-assists/src/assist_context.rs
new file mode 100644
index 000000000..f9b426614
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/assist_context.rs
@@ -0,0 +1,347 @@
+//! See [`AssistContext`].
+
+use std::mem;
+
+use hir::Semantics;
+use ide_db::{
+ base_db::{AnchoredPathBuf, FileId, FileRange},
+ SnippetCap,
+};
+use ide_db::{
+ label::Label,
+ source_change::{FileSystemEdit, SourceChange},
+ RootDatabase,
+};
+use syntax::{
+ algo::{self, find_node_at_offset, find_node_at_range},
+ AstNode, AstToken, Direction, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxNodePtr,
+ SyntaxToken, TextRange, TextSize, TokenAtOffset,
+};
+use text_edit::{TextEdit, TextEditBuilder};
+
+use crate::{
+ assist_config::AssistConfig, Assist, AssistId, AssistKind, AssistResolveStrategy, GroupLabel,
+};
+
+/// `AssistContext` allows to apply an assist or check if it could be applied.
+///
+/// Assists use a somewhat over-engineered approach, given the current needs.
+/// The assists workflow consists of two phases. In the first phase, a user asks
+/// for the list of available assists. In the second phase, the user picks a
+/// particular assist and it gets applied.
+///
+/// There are two peculiarities here:
+///
+/// * first, we ideally avoid computing more things then necessary to answer "is
+/// assist applicable" in the first phase.
+/// * second, when we are applying assist, we don't have a guarantee that there
+/// weren't any changes between the point when user asked for assists and when
+/// they applied a particular assist. So, when applying assist, we need to do
+/// all the checks from scratch.
+///
+/// To avoid repeating the same code twice for both "check" and "apply"
+/// functions, we use an approach reminiscent of that of Django's function based
+/// views dealing with forms. Each assist receives a runtime parameter,
+/// `resolve`. It first check if an edit is applicable (potentially computing
+/// info required to compute the actual edit). If it is applicable, and
+/// `resolve` is `true`, it then computes the actual edit.
+///
+/// So, to implement the original assists workflow, we can first apply each edit
+/// with `resolve = false`, and then applying the selected edit again, with
+/// `resolve = true` this time.
+///
+/// Note, however, that we don't actually use such two-phase logic at the
+/// moment, because the LSP API is pretty awkward in this place, and it's much
+/// easier to just compute the edit eagerly :-)
+pub(crate) struct AssistContext<'a> {
+ pub(crate) config: &'a AssistConfig,
+ pub(crate) sema: Semantics<'a, RootDatabase>,
+ frange: FileRange,
+ trimmed_range: TextRange,
+ source_file: SourceFile,
+}
+
+impl<'a> AssistContext<'a> {
+ pub(crate) fn new(
+ sema: Semantics<'a, RootDatabase>,
+ config: &'a AssistConfig,
+ frange: FileRange,
+ ) -> AssistContext<'a> {
+ let source_file = sema.parse(frange.file_id);
+
+ let start = frange.range.start();
+ let end = frange.range.end();
+ let left = source_file.syntax().token_at_offset(start);
+ let right = source_file.syntax().token_at_offset(end);
+ let left =
+ left.right_biased().and_then(|t| algo::skip_whitespace_token(t, Direction::Next));
+ let right =
+ right.left_biased().and_then(|t| algo::skip_whitespace_token(t, Direction::Prev));
+ let left = left.map(|t| t.text_range().start().clamp(start, end));
+ let right = right.map(|t| t.text_range().end().clamp(start, end));
+
+ let trimmed_range = match (left, right) {
+ (Some(left), Some(right)) if left <= right => TextRange::new(left, right),
+ // Selection solely consists of whitespace so just fall back to the original
+ _ => frange.range,
+ };
+
+ AssistContext { config, sema, frange, source_file, trimmed_range }
+ }
+
+ pub(crate) fn db(&self) -> &RootDatabase {
+ self.sema.db
+ }
+
+ // NB, this ignores active selection.
+ pub(crate) fn offset(&self) -> TextSize {
+ self.frange.range.start()
+ }
+
+ pub(crate) fn file_id(&self) -> FileId {
+ self.frange.file_id
+ }
+
+ pub(crate) fn has_empty_selection(&self) -> bool {
+ self.trimmed_range.is_empty()
+ }
+
+ /// Returns the selected range trimmed for whitespace tokens, that is the range will be snapped
+ /// to the nearest enclosed token.
+ pub(crate) fn selection_trimmed(&self) -> TextRange {
+ self.trimmed_range
+ }
+
+ pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> {
+ self.source_file.syntax().token_at_offset(self.offset())
+ }
+ pub(crate) fn find_token_syntax_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> {
+ self.token_at_offset().find(|it| it.kind() == kind)
+ }
+ pub(crate) fn find_token_at_offset<T: AstToken>(&self) -> Option<T> {
+ self.token_at_offset().find_map(T::cast)
+ }
+ pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> {
+ find_node_at_offset(self.source_file.syntax(), self.offset())
+ }
+ pub(crate) fn find_node_at_range<N: AstNode>(&self) -> Option<N> {
+ find_node_at_range(self.source_file.syntax(), self.trimmed_range)
+ }
+ pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> {
+ self.sema.find_node_at_offset_with_descend(self.source_file.syntax(), self.offset())
+ }
+ /// Returns the element covered by the selection range, this excludes trailing whitespace in the selection.
+ pub(crate) fn covering_element(&self) -> SyntaxElement {
+ self.source_file.syntax().covering_element(self.selection_trimmed())
+ }
+}
+
+pub(crate) struct Assists {
+ file: FileId,
+ resolve: AssistResolveStrategy,
+ buf: Vec<Assist>,
+ allowed: Option<Vec<AssistKind>>,
+}
+
+impl Assists {
+ pub(crate) fn new(ctx: &AssistContext<'_>, resolve: AssistResolveStrategy) -> Assists {
+ Assists {
+ resolve,
+ file: ctx.frange.file_id,
+ buf: Vec::new(),
+ allowed: ctx.config.allowed.clone(),
+ }
+ }
+
+ pub(crate) fn finish(mut self) -> Vec<Assist> {
+ self.buf.sort_by_key(|assist| assist.target.len());
+ self.buf
+ }
+
+ pub(crate) fn add(
+ &mut self,
+ id: AssistId,
+ label: impl Into<String>,
+ target: TextRange,
+ f: impl FnOnce(&mut AssistBuilder),
+ ) -> Option<()> {
+ let mut f = Some(f);
+ self.add_impl(None, id, label.into(), target, &mut |it| f.take().unwrap()(it))
+ }
+
+ pub(crate) fn add_group(
+ &mut self,
+ group: &GroupLabel,
+ id: AssistId,
+ label: impl Into<String>,
+ target: TextRange,
+ f: impl FnOnce(&mut AssistBuilder),
+ ) -> Option<()> {
+ let mut f = Some(f);
+ self.add_impl(Some(group), id, label.into(), target, &mut |it| f.take().unwrap()(it))
+ }
+
+ fn add_impl(
+ &mut self,
+ group: Option<&GroupLabel>,
+ id: AssistId,
+ label: String,
+ target: TextRange,
+ f: &mut dyn FnMut(&mut AssistBuilder),
+ ) -> Option<()> {
+ if !self.is_allowed(&id) {
+ return None;
+ }
+
+ let mut trigger_signature_help = false;
+ let source_change = if self.resolve.should_resolve(&id) {
+ let mut builder = AssistBuilder::new(self.file);
+ f(&mut builder);
+ trigger_signature_help = builder.trigger_signature_help;
+ Some(builder.finish())
+ } else {
+ None
+ };
+
+ let label = Label::new(label);
+ let group = group.cloned();
+ self.buf.push(Assist { id, label, group, target, source_change, trigger_signature_help });
+ Some(())
+ }
+
+ fn is_allowed(&self, id: &AssistId) -> bool {
+ match &self.allowed {
+ Some(allowed) => allowed.iter().any(|kind| kind.contains(id.1)),
+ None => true,
+ }
+ }
+}
+
+pub(crate) struct AssistBuilder {
+ edit: TextEditBuilder,
+ file_id: FileId,
+ source_change: SourceChange,
+ trigger_signature_help: bool,
+
+ /// Maps the original, immutable `SyntaxNode` to a `clone_for_update` twin.
+ mutated_tree: Option<TreeMutator>,
+}
+
+pub(crate) struct TreeMutator {
+ immutable: SyntaxNode,
+ mutable_clone: SyntaxNode,
+}
+
+impl TreeMutator {
+ pub(crate) fn new(immutable: &SyntaxNode) -> TreeMutator {
+ let immutable = immutable.ancestors().last().unwrap();
+ let mutable_clone = immutable.clone_for_update();
+ TreeMutator { immutable, mutable_clone }
+ }
+
+ pub(crate) fn make_mut<N: AstNode>(&self, node: &N) -> N {
+ N::cast(self.make_syntax_mut(node.syntax())).unwrap()
+ }
+
+ pub(crate) fn make_syntax_mut(&self, node: &SyntaxNode) -> SyntaxNode {
+ let ptr = SyntaxNodePtr::new(node);
+ ptr.to_node(&self.mutable_clone)
+ }
+}
+
+impl AssistBuilder {
+ pub(crate) fn new(file_id: FileId) -> AssistBuilder {
+ AssistBuilder {
+ edit: TextEdit::builder(),
+ file_id,
+ source_change: SourceChange::default(),
+ trigger_signature_help: false,
+ mutated_tree: None,
+ }
+ }
+
+ pub(crate) fn edit_file(&mut self, file_id: FileId) {
+ self.commit();
+ self.file_id = file_id;
+ }
+
+ fn commit(&mut self) {
+ if let Some(tm) = self.mutated_tree.take() {
+ algo::diff(&tm.immutable, &tm.mutable_clone).into_text_edit(&mut self.edit)
+ }
+
+ let edit = mem::take(&mut self.edit).finish();
+ if !edit.is_empty() {
+ self.source_change.insert_source_edit(self.file_id, edit);
+ }
+ }
+
+ pub(crate) fn make_mut<N: AstNode>(&mut self, node: N) -> N {
+ self.mutated_tree.get_or_insert_with(|| TreeMutator::new(node.syntax())).make_mut(&node)
+ }
+ /// Returns a copy of the `node`, suitable for mutation.
+ ///
+ /// Syntax trees in rust-analyzer are typically immutable, and mutating
+ /// operations panic at runtime. However, it is possible to make a copy of
+ /// the tree and mutate the copy freely. Mutation is based on interior
+ /// mutability, and different nodes in the same tree see the same mutations.
+ ///
+ /// The typical pattern for an assist is to find specific nodes in the read
+ /// phase, and then get their mutable couterparts using `make_mut` in the
+ /// mutable state.
+ pub(crate) fn make_syntax_mut(&mut self, node: SyntaxNode) -> SyntaxNode {
+ self.mutated_tree.get_or_insert_with(|| TreeMutator::new(&node)).make_syntax_mut(&node)
+ }
+
+ /// Remove specified `range` of text.
+ pub(crate) fn delete(&mut self, range: TextRange) {
+ self.edit.delete(range)
+ }
+ /// Append specified `text` at the given `offset`
+ pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) {
+ self.edit.insert(offset, text.into())
+ }
+ /// Append specified `snippet` at the given `offset`
+ pub(crate) fn insert_snippet(
+ &mut self,
+ _cap: SnippetCap,
+ offset: TextSize,
+ snippet: impl Into<String>,
+ ) {
+ self.source_change.is_snippet = true;
+ self.insert(offset, snippet);
+ }
+ /// Replaces specified `range` of text with a given string.
+ pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
+ self.edit.replace(range, replace_with.into())
+ }
+ /// Replaces specified `range` of text with a given `snippet`.
+ pub(crate) fn replace_snippet(
+ &mut self,
+ _cap: SnippetCap,
+ range: TextRange,
+ snippet: impl Into<String>,
+ ) {
+ self.source_change.is_snippet = true;
+ self.replace(range, snippet);
+ }
+ pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
+ algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
+ }
+ pub(crate) fn create_file(&mut self, dst: AnchoredPathBuf, content: impl Into<String>) {
+ let file_system_edit = FileSystemEdit::CreateFile { dst, initial_contents: content.into() };
+ self.source_change.push_file_system_edit(file_system_edit);
+ }
+ pub(crate) fn move_file(&mut self, src: FileId, dst: AnchoredPathBuf) {
+ let file_system_edit = FileSystemEdit::MoveFile { src, dst };
+ self.source_change.push_file_system_edit(file_system_edit);
+ }
+ pub(crate) fn trigger_signature_help(&mut self) {
+ self.trigger_signature_help = true;
+ }
+
+ fn finish(mut self) -> SourceChange {
+ self.commit();
+ mem::take(&mut self.source_change)
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_explicit_type.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_explicit_type.rs
new file mode 100644
index 000000000..bfa9759ec
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_explicit_type.rs
@@ -0,0 +1,325 @@
+use hir::HirDisplay;
+use ide_db::syntax_helpers::node_ext::walk_ty;
+use syntax::ast::{self, AstNode, LetStmt, Param};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: add_explicit_type
+//
+// Specify type for a let binding.
+//
+// ```
+// fn main() {
+// let x$0 = 92;
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// let x: i32 = 92;
+// }
+// ```
+pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let (ascribed_ty, expr, pat) = if let Some(let_stmt) = ctx.find_node_at_offset::<LetStmt>() {
+ let cursor_in_range = {
+ let eq_range = let_stmt.eq_token()?.text_range();
+ ctx.offset() < eq_range.start()
+ };
+ if !cursor_in_range {
+ cov_mark::hit!(add_explicit_type_not_applicable_if_cursor_after_equals);
+ return None;
+ }
+
+ (let_stmt.ty(), let_stmt.initializer(), let_stmt.pat()?)
+ } else if let Some(param) = ctx.find_node_at_offset::<Param>() {
+ if param.syntax().ancestors().nth(2).and_then(ast::ClosureExpr::cast).is_none() {
+ cov_mark::hit!(add_explicit_type_not_applicable_in_fn_param);
+ return None;
+ }
+ (param.ty(), None, param.pat()?)
+ } else {
+ return None;
+ };
+
+ let module = ctx.sema.scope(pat.syntax())?.module();
+ let pat_range = pat.syntax().text_range();
+
+ // Don't enable the assist if there is a type ascription without any placeholders
+ if let Some(ty) = &ascribed_ty {
+ let mut contains_infer_ty = false;
+ walk_ty(ty, &mut |ty| contains_infer_ty |= matches!(ty, ast::Type::InferType(_)));
+ if !contains_infer_ty {
+ cov_mark::hit!(add_explicit_type_not_applicable_if_ty_already_specified);
+ return None;
+ }
+ }
+
+ let ty = match (pat, expr) {
+ (ast::Pat::IdentPat(_), Some(expr)) => ctx.sema.type_of_expr(&expr)?,
+ (pat, _) => ctx.sema.type_of_pat(&pat)?,
+ }
+ .adjusted();
+
+ // Fully unresolved or unnameable types can't be annotated
+ if (ty.contains_unknown() && ty.type_arguments().count() == 0) || ty.is_closure() {
+ cov_mark::hit!(add_explicit_type_not_applicable_if_ty_not_inferred);
+ return None;
+ }
+
+ let inferred_type = ty.display_source_code(ctx.db(), module.into()).ok()?;
+ acc.add(
+ AssistId("add_explicit_type", AssistKind::RefactorRewrite),
+ format!("Insert explicit type `{}`", inferred_type),
+ pat_range,
+ |builder| match ascribed_ty {
+ Some(ascribed_ty) => {
+ builder.replace(ascribed_ty.syntax().text_range(), inferred_type);
+ }
+ None => {
+ builder.insert(pat_range.end(), format!(": {}", inferred_type));
+ }
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
+
+ #[test]
+ fn add_explicit_type_target() {
+ check_assist_target(add_explicit_type, r#"fn f() { let a$0 = 1; }"#, "a");
+ }
+
+ #[test]
+ fn add_explicit_type_simple() {
+ check_assist(
+ add_explicit_type,
+ r#"fn f() { let a$0 = 1; }"#,
+ r#"fn f() { let a: i32 = 1; }"#,
+ );
+ }
+
+ #[test]
+ fn add_explicit_type_simple_on_infer_ty() {
+ check_assist(
+ add_explicit_type,
+ r#"fn f() { let a$0: _ = 1; }"#,
+ r#"fn f() { let a: i32 = 1; }"#,
+ );
+ }
+
+ #[test]
+ fn add_explicit_type_simple_nested_infer_ty() {
+ check_assist(
+ add_explicit_type,
+ r#"
+//- minicore: option
+fn f() {
+ let a$0: Option<_> = Option::Some(1);
+}
+"#,
+ r#"
+fn f() {
+ let a: Option<i32> = Option::Some(1);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_explicit_type_macro_call_expr() {
+ check_assist(
+ add_explicit_type,
+ r"macro_rules! v { () => {0u64} } fn f() { let a$0 = v!(); }",
+ r"macro_rules! v { () => {0u64} } fn f() { let a: u64 = v!(); }",
+ );
+ }
+
+ #[test]
+ fn add_explicit_type_not_applicable_for_fully_unresolved() {
+ cov_mark::check!(add_explicit_type_not_applicable_if_ty_not_inferred);
+ check_assist_not_applicable(add_explicit_type, r#"fn f() { let a$0 = None; }"#);
+ }
+
+ #[test]
+ fn add_explicit_type_applicable_for_partially_unresolved() {
+ check_assist(
+ add_explicit_type,
+ r#"
+ struct Vec<T, V> { t: T, v: V }
+ impl<T> Vec<T, Vec<ZZZ, i32>> {
+ fn new() -> Self {
+ panic!()
+ }
+ }
+ fn f() { let a$0 = Vec::new(); }"#,
+ r#"
+ struct Vec<T, V> { t: T, v: V }
+ impl<T> Vec<T, Vec<ZZZ, i32>> {
+ fn new() -> Self {
+ panic!()
+ }
+ }
+ fn f() { let a: Vec<_, Vec<_, i32>> = Vec::new(); }"#,
+ );
+ }
+
+ #[test]
+ fn add_explicit_type_not_applicable_closure_expr() {
+ check_assist_not_applicable(add_explicit_type, r#"fn f() { let a$0 = || {}; }"#);
+ }
+
+ #[test]
+ fn add_explicit_type_not_applicable_ty_already_specified() {
+ cov_mark::check!(add_explicit_type_not_applicable_if_ty_already_specified);
+ check_assist_not_applicable(add_explicit_type, r#"fn f() { let a$0: i32 = 1; }"#);
+ }
+
+ #[test]
+ fn add_explicit_type_not_applicable_cursor_after_equals_of_let() {
+ cov_mark::check!(add_explicit_type_not_applicable_if_cursor_after_equals);
+ check_assist_not_applicable(
+ add_explicit_type,
+ r#"fn f() {let a =$0 match 1 {2 => 3, 3 => 5};}"#,
+ )
+ }
+
+ /// https://github.com/rust-lang/rust-analyzer/issues/2922
+ #[test]
+ fn regression_issue_2922() {
+ check_assist(
+ add_explicit_type,
+ r#"
+fn main() {
+ let $0v = [0.0; 2];
+}
+"#,
+ r#"
+fn main() {
+ let v: [f64; 2] = [0.0; 2];
+}
+"#,
+ );
+ // note: this may break later if we add more consteval. it just needs to be something that our
+ // consteval engine doesn't understand
+ check_assist_not_applicable(
+ add_explicit_type,
+ r#"
+//- minicore: option
+
+fn main() {
+ let $0l = [0.0; Some(2).unwrap()];
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn default_generics_should_not_be_added() {
+ check_assist(
+ add_explicit_type,
+ r#"
+struct Test<K, T = u8> { k: K, t: T }
+
+fn main() {
+ let test$0 = Test { t: 23u8, k: 33 };
+}
+"#,
+ r#"
+struct Test<K, T = u8> { k: K, t: T }
+
+fn main() {
+ let test: Test<i32> = Test { t: 23u8, k: 33 };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn type_should_be_added_after_pattern() {
+ // LetStmt = Attr* 'let' Pat (':' Type)? '=' initializer:Expr ';'
+ check_assist(
+ add_explicit_type,
+ r#"
+fn main() {
+ let $0test @ () = ();
+}
+"#,
+ r#"
+fn main() {
+ let test @ (): () = ();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_explicit_type_inserts_coercions() {
+ check_assist(
+ add_explicit_type,
+ r#"
+//- minicore: coerce_unsized
+fn f() {
+ let $0x: *const [_] = &[3];
+}
+"#,
+ r#"
+fn f() {
+ let x: *const [i32] = &[3];
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_explicit_type_not_applicable_fn_param() {
+ cov_mark::check!(add_explicit_type_not_applicable_in_fn_param);
+ check_assist_not_applicable(add_explicit_type, r#"fn f(x$0: ()) {}"#);
+ }
+
+ #[test]
+ fn add_explicit_type_ascribes_closure_param() {
+ check_assist(
+ add_explicit_type,
+ r#"
+fn f() {
+ |y$0| {
+ let x: i32 = y;
+ };
+}
+"#,
+ r#"
+fn f() {
+ |y: i32| {
+ let x: i32 = y;
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_explicit_type_ascribes_closure_param_already_ascribed() {
+ check_assist(
+ add_explicit_type,
+ r#"
+//- minicore: option
+fn f() {
+ |mut y$0: Option<_>| {
+ y = Some(3);
+ };
+}
+"#,
+ r#"
+fn f() {
+ |mut y: Option<i32>| {
+ y = Some(3);
+ };
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_label_to_loop.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_label_to_loop.rs
new file mode 100644
index 000000000..001f1e8bb
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_label_to_loop.rs
@@ -0,0 +1,164 @@
+use ide_db::syntax_helpers::node_ext::for_each_break_and_continue_expr;
+use syntax::{
+ ast::{self, AstNode, HasLoopBody},
+ T,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: add_label_to_loop
+//
+// Adds a label to a loop.
+//
+// ```
+// fn main() {
+// loop$0 {
+// break;
+// continue;
+// }
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// 'l: loop {
+// break 'l;
+// continue 'l;
+// }
+// }
+// ```
+pub(crate) fn add_label_to_loop(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let loop_kw = ctx.find_token_syntax_at_offset(T![loop])?;
+ let loop_expr = loop_kw.parent().and_then(ast::LoopExpr::cast)?;
+ if loop_expr.label().is_some() {
+ return None;
+ }
+
+ acc.add(
+ AssistId("add_label_to_loop", AssistKind::Generate),
+ "Add Label",
+ loop_expr.syntax().text_range(),
+ |builder| {
+ builder.insert(loop_kw.text_range().start(), "'l: ");
+
+ let loop_body = loop_expr.loop_body().and_then(|it| it.stmt_list());
+ for_each_break_and_continue_expr(
+ loop_expr.label(),
+ loop_body,
+ &mut |expr| match expr {
+ ast::Expr::BreakExpr(break_expr) => {
+ if let Some(break_token) = break_expr.break_token() {
+ builder.insert(break_token.text_range().end(), " 'l")
+ }
+ }
+ ast::Expr::ContinueExpr(continue_expr) => {
+ if let Some(continue_token) = continue_expr.continue_token() {
+ builder.insert(continue_token.text_range().end(), " 'l")
+ }
+ }
+ _ => {}
+ },
+ );
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn add_label() {
+ check_assist(
+ add_label_to_loop,
+ r#"
+fn main() {
+ loop$0 {
+ break;
+ continue;
+ }
+}"#,
+ r#"
+fn main() {
+ 'l: loop {
+ break 'l;
+ continue 'l;
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn add_label_to_outer_loop() {
+ check_assist(
+ add_label_to_loop,
+ r#"
+fn main() {
+ loop$0 {
+ break;
+ continue;
+ loop {
+ break;
+ continue;
+ }
+ }
+}"#,
+ r#"
+fn main() {
+ 'l: loop {
+ break 'l;
+ continue 'l;
+ loop {
+ break;
+ continue;
+ }
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn add_label_to_inner_loop() {
+ check_assist(
+ add_label_to_loop,
+ r#"
+fn main() {
+ loop {
+ break;
+ continue;
+ loop$0 {
+ break;
+ continue;
+ }
+ }
+}"#,
+ r#"
+fn main() {
+ loop {
+ break;
+ continue;
+ 'l: loop {
+ break 'l;
+ continue 'l;
+ }
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn do_not_add_label_if_exists() {
+ check_assist_not_applicable(
+ add_label_to_loop,
+ r#"
+fn main() {
+ 'l: loop$0 {
+ break 'l;
+ continue 'l;
+ }
+}"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_lifetime_to_type.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_lifetime_to_type.rs
new file mode 100644
index 000000000..12213c845
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_lifetime_to_type.rs
@@ -0,0 +1,229 @@
+use syntax::ast::{self, AstNode, HasGenericParams, HasName};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: add_lifetime_to_type
+//
+// Adds a new lifetime to a struct, enum or union.
+//
+// ```
+// struct Point {
+// x: &$0u32,
+// y: u32,
+// }
+// ```
+// ->
+// ```
+// struct Point<'a> {
+// x: &'a u32,
+// y: u32,
+// }
+// ```
+pub(crate) fn add_lifetime_to_type(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let ref_type_focused = ctx.find_node_at_offset::<ast::RefType>()?;
+ if ref_type_focused.lifetime().is_some() {
+ return None;
+ }
+
+ let node = ctx.find_node_at_offset::<ast::Adt>()?;
+ let has_lifetime = node
+ .generic_param_list()
+ .map_or(false, |gen_list| gen_list.lifetime_params().next().is_some());
+
+ if has_lifetime {
+ return None;
+ }
+
+ let ref_types = fetch_borrowed_types(&node)?;
+ let target = node.syntax().text_range();
+
+ acc.add(
+ AssistId("add_lifetime_to_type", AssistKind::Generate),
+ "Add lifetime",
+ target,
+ |builder| {
+ match node.generic_param_list() {
+ Some(gen_param) => {
+ if let Some(left_angle) = gen_param.l_angle_token() {
+ builder.insert(left_angle.text_range().end(), "'a, ");
+ }
+ }
+ None => {
+ if let Some(name) = node.name() {
+ builder.insert(name.syntax().text_range().end(), "<'a>");
+ }
+ }
+ }
+
+ for ref_type in ref_types {
+ if let Some(amp_token) = ref_type.amp_token() {
+ builder.insert(amp_token.text_range().end(), "'a ");
+ }
+ }
+ },
+ )
+}
+
+fn fetch_borrowed_types(node: &ast::Adt) -> Option<Vec<ast::RefType>> {
+ let ref_types: Vec<ast::RefType> = match node {
+ ast::Adt::Enum(enum_) => {
+ let variant_list = enum_.variant_list()?;
+ variant_list
+ .variants()
+ .filter_map(|variant| {
+ let field_list = variant.field_list()?;
+
+ find_ref_types_from_field_list(&field_list)
+ })
+ .flatten()
+ .collect()
+ }
+ ast::Adt::Struct(strukt) => {
+ let field_list = strukt.field_list()?;
+ find_ref_types_from_field_list(&field_list)?
+ }
+ ast::Adt::Union(un) => {
+ let record_field_list = un.record_field_list()?;
+ record_field_list
+ .fields()
+ .filter_map(|r_field| {
+ if let ast::Type::RefType(ref_type) = r_field.ty()? {
+ if ref_type.lifetime().is_none() {
+ return Some(ref_type);
+ }
+ }
+
+ None
+ })
+ .collect()
+ }
+ };
+
+ if ref_types.is_empty() {
+ None
+ } else {
+ Some(ref_types)
+ }
+}
+
+fn find_ref_types_from_field_list(field_list: &ast::FieldList) -> Option<Vec<ast::RefType>> {
+ let ref_types: Vec<ast::RefType> = match field_list {
+ ast::FieldList::RecordFieldList(record_list) => record_list
+ .fields()
+ .filter_map(|f| {
+ if let ast::Type::RefType(ref_type) = f.ty()? {
+ if ref_type.lifetime().is_none() {
+ return Some(ref_type);
+ }
+ }
+
+ None
+ })
+ .collect(),
+ ast::FieldList::TupleFieldList(tuple_field_list) => tuple_field_list
+ .fields()
+ .filter_map(|f| {
+ if let ast::Type::RefType(ref_type) = f.ty()? {
+ if ref_type.lifetime().is_none() {
+ return Some(ref_type);
+ }
+ }
+
+ None
+ })
+ .collect(),
+ };
+
+ if ref_types.is_empty() {
+ None
+ } else {
+ Some(ref_types)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn add_lifetime_to_struct() {
+ check_assist(
+ add_lifetime_to_type,
+ r#"struct Foo { a: &$0i32 }"#,
+ r#"struct Foo<'a> { a: &'a i32 }"#,
+ );
+
+ check_assist(
+ add_lifetime_to_type,
+ r#"struct Foo { a: &$0i32, b: &usize }"#,
+ r#"struct Foo<'a> { a: &'a i32, b: &'a usize }"#,
+ );
+
+ check_assist(
+ add_lifetime_to_type,
+ r#"struct Foo { a: &$0i32, b: usize }"#,
+ r#"struct Foo<'a> { a: &'a i32, b: usize }"#,
+ );
+
+ check_assist(
+ add_lifetime_to_type,
+ r#"struct Foo<T> { a: &$0T, b: usize }"#,
+ r#"struct Foo<'a, T> { a: &'a T, b: usize }"#,
+ );
+
+ check_assist_not_applicable(add_lifetime_to_type, r#"struct Foo<'a> { a: &$0'a i32 }"#);
+ check_assist_not_applicable(add_lifetime_to_type, r#"struct Foo { a: &'a$0 i32 }"#);
+ }
+
+ #[test]
+ fn add_lifetime_to_enum() {
+ check_assist(
+ add_lifetime_to_type,
+ r#"enum Foo { Bar { a: i32 }, Other, Tuple(u32, &$0u32)}"#,
+ r#"enum Foo<'a> { Bar { a: i32 }, Other, Tuple(u32, &'a u32)}"#,
+ );
+
+ check_assist(
+ add_lifetime_to_type,
+ r#"enum Foo { Bar { a: &$0i32 }}"#,
+ r#"enum Foo<'a> { Bar { a: &'a i32 }}"#,
+ );
+
+ check_assist(
+ add_lifetime_to_type,
+ r#"enum Foo<T> { Bar { a: &$0i32, b: &T }}"#,
+ r#"enum Foo<'a, T> { Bar { a: &'a i32, b: &'a T }}"#,
+ );
+
+ check_assist_not_applicable(
+ add_lifetime_to_type,
+ r#"enum Foo<'a> { Bar { a: &$0'a i32 }}"#,
+ );
+ check_assist_not_applicable(add_lifetime_to_type, r#"enum Foo { Bar, $0Misc }"#);
+ }
+
+ #[test]
+ fn add_lifetime_to_union() {
+ check_assist(
+ add_lifetime_to_type,
+ r#"union Foo { a: &$0i32 }"#,
+ r#"union Foo<'a> { a: &'a i32 }"#,
+ );
+
+ check_assist(
+ add_lifetime_to_type,
+ r#"union Foo { a: &$0i32, b: &usize }"#,
+ r#"union Foo<'a> { a: &'a i32, b: &'a usize }"#,
+ );
+
+ check_assist(
+ add_lifetime_to_type,
+ r#"union Foo<T> { a: &$0T, b: usize }"#,
+ r#"union Foo<'a, T> { a: &'a T, b: usize }"#,
+ );
+
+ check_assist_not_applicable(add_lifetime_to_type, r#"struct Foo<'a> { a: &'a $0i32 }"#);
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs
new file mode 100644
index 000000000..c808c010c
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs
@@ -0,0 +1,1340 @@
+use hir::HasSource;
+use ide_db::{
+ syntax_helpers::insert_whitespace_into_node::insert_ws_into, traits::resolve_target_trait,
+};
+use syntax::ast::{self, make, AstNode};
+
+use crate::{
+ assist_context::{AssistContext, Assists},
+ utils::{
+ add_trait_assoc_items_to_impl, filter_assoc_items, gen_trait_fn_body, render_snippet,
+ Cursor, DefaultMethods,
+ },
+ AssistId, AssistKind,
+};
+
+// Assist: add_impl_missing_members
+//
+// Adds scaffold for required impl members.
+//
+// ```
+// trait Trait<T> {
+// type X;
+// fn foo(&self) -> T;
+// fn bar(&self) {}
+// }
+//
+// impl Trait<u32> for () {$0
+//
+// }
+// ```
+// ->
+// ```
+// trait Trait<T> {
+// type X;
+// fn foo(&self) -> T;
+// fn bar(&self) {}
+// }
+//
+// impl Trait<u32> for () {
+// $0type X;
+//
+// fn foo(&self) -> u32 {
+// todo!()
+// }
+// }
+// ```
+pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ add_missing_impl_members_inner(
+ acc,
+ ctx,
+ DefaultMethods::No,
+ "add_impl_missing_members",
+ "Implement missing members",
+ )
+}
+
+// Assist: add_impl_default_members
+//
+// Adds scaffold for overriding default impl members.
+//
+// ```
+// trait Trait {
+// type X;
+// fn foo(&self);
+// fn bar(&self) {}
+// }
+//
+// impl Trait for () {
+// type X = ();
+// fn foo(&self) {}$0
+// }
+// ```
+// ->
+// ```
+// trait Trait {
+// type X;
+// fn foo(&self);
+// fn bar(&self) {}
+// }
+//
+// impl Trait for () {
+// type X = ();
+// fn foo(&self) {}
+//
+// $0fn bar(&self) {}
+// }
+// ```
+pub(crate) fn add_missing_default_members(
+ acc: &mut Assists,
+ ctx: &AssistContext<'_>,
+) -> Option<()> {
+ add_missing_impl_members_inner(
+ acc,
+ ctx,
+ DefaultMethods::Only,
+ "add_impl_default_members",
+ "Implement default members",
+ )
+}
+
+fn add_missing_impl_members_inner(
+ acc: &mut Assists,
+ ctx: &AssistContext<'_>,
+ mode: DefaultMethods,
+ assist_id: &'static str,
+ label: &'static str,
+) -> Option<()> {
+ let _p = profile::span("add_missing_impl_members_inner");
+ let impl_def = ctx.find_node_at_offset::<ast::Impl>()?;
+ let target_scope = ctx.sema.scope(impl_def.syntax())?;
+ let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?;
+
+ let missing_items = filter_assoc_items(
+ &ctx.sema,
+ &ide_db::traits::get_missing_assoc_items(&ctx.sema, &impl_def),
+ mode,
+ );
+
+ if missing_items.is_empty() {
+ return None;
+ }
+
+ let target = impl_def.syntax().text_range();
+ acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| {
+ let missing_items = missing_items
+ .into_iter()
+ .map(|it| {
+ if ctx.sema.hir_file_for(it.syntax()).is_macro() {
+ if let Some(it) = ast::AssocItem::cast(insert_ws_into(it.syntax().clone())) {
+ return it;
+ }
+ }
+ it.clone_for_update()
+ })
+ .collect();
+ let (new_impl_def, first_new_item) = add_trait_assoc_items_to_impl(
+ &ctx.sema,
+ missing_items,
+ trait_,
+ impl_def.clone(),
+ target_scope,
+ );
+ match ctx.config.snippet_cap {
+ None => builder.replace(target, new_impl_def.to_string()),
+ Some(cap) => {
+ let mut cursor = Cursor::Before(first_new_item.syntax());
+ let placeholder;
+ if let DefaultMethods::No = mode {
+ if let ast::AssocItem::Fn(func) = &first_new_item {
+ if try_gen_trait_body(ctx, func, &trait_, &impl_def).is_none() {
+ if let Some(m) =
+ func.syntax().descendants().find_map(ast::MacroCall::cast)
+ {
+ if m.syntax().text() == "todo!()" {
+ placeholder = m;
+ cursor = Cursor::Replace(placeholder.syntax());
+ }
+ }
+ }
+ }
+ }
+ builder.replace_snippet(
+ cap,
+ target,
+ render_snippet(cap, new_impl_def.syntax(), cursor),
+ )
+ }
+ };
+ })
+}
+
+fn try_gen_trait_body(
+ ctx: &AssistContext<'_>,
+ func: &ast::Fn,
+ trait_: &hir::Trait,
+ impl_def: &ast::Impl,
+) -> Option<()> {
+ let trait_path = make::ext::ident_path(&trait_.name(ctx.db()).to_string());
+ let hir_ty = ctx.sema.resolve_type(&impl_def.self_ty()?)?;
+ let adt = hir_ty.as_adt()?.source(ctx.db())?;
+ gen_trait_fn_body(func, &trait_path, &adt.value)
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn test_add_missing_impl_members() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+trait Foo {
+ type Output;
+
+ const CONST: usize = 42;
+
+ fn foo(&self);
+ fn bar(&self);
+ fn baz(&self);
+}
+
+struct S;
+
+impl Foo for S {
+ fn bar(&self) {}
+$0
+}"#,
+ r#"
+trait Foo {
+ type Output;
+
+ const CONST: usize = 42;
+
+ fn foo(&self);
+ fn bar(&self);
+ fn baz(&self);
+}
+
+struct S;
+
+impl Foo for S {
+ fn bar(&self) {}
+
+ $0type Output;
+
+ const CONST: usize = 42;
+
+ fn foo(&self) {
+ todo!()
+ }
+
+ fn baz(&self) {
+ todo!()
+ }
+
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_copied_overriden_members() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+trait Foo {
+ fn foo(&self);
+ fn bar(&self) -> bool { true }
+ fn baz(&self) -> u32 { 42 }
+}
+
+struct S;
+
+impl Foo for S {
+ fn bar(&self) {}
+$0
+}"#,
+ r#"
+trait Foo {
+ fn foo(&self);
+ fn bar(&self) -> bool { true }
+ fn baz(&self) -> u32 { 42 }
+}
+
+struct S;
+
+impl Foo for S {
+ fn bar(&self) {}
+
+ fn foo(&self) {
+ ${0:todo!()}
+ }
+
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_empty_impl_def() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+trait Foo { fn foo(&self); }
+struct S;
+impl Foo for S { $0 }"#,
+ r#"
+trait Foo { fn foo(&self); }
+struct S;
+impl Foo for S {
+ fn foo(&self) {
+ ${0:todo!()}
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_impl_def_without_braces() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+trait Foo { fn foo(&self); }
+struct S;
+impl Foo for S$0"#,
+ r#"
+trait Foo { fn foo(&self); }
+struct S;
+impl Foo for S {
+ fn foo(&self) {
+ ${0:todo!()}
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn fill_in_type_params_1() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+trait Foo<T> { fn foo(&self, t: T) -> &T; }
+struct S;
+impl Foo<u32> for S { $0 }"#,
+ r#"
+trait Foo<T> { fn foo(&self, t: T) -> &T; }
+struct S;
+impl Foo<u32> for S {
+ fn foo(&self, t: u32) -> &u32 {
+ ${0:todo!()}
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn fill_in_type_params_2() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+trait Foo<T> { fn foo(&self, t: T) -> &T; }
+struct S;
+impl<U> Foo<U> for S { $0 }"#,
+ r#"
+trait Foo<T> { fn foo(&self, t: T) -> &T; }
+struct S;
+impl<U> Foo<U> for S {
+ fn foo(&self, t: U) -> &U {
+ ${0:todo!()}
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_cursor_after_empty_impl_def() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+trait Foo { fn foo(&self); }
+struct S;
+impl Foo for S {}$0"#,
+ r#"
+trait Foo { fn foo(&self); }
+struct S;
+impl Foo for S {
+ fn foo(&self) {
+ ${0:todo!()}
+ }
+}"#,
+ )
+ }
+
+ #[test]
+ fn test_qualify_path_1() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+mod foo {
+ pub struct Bar;
+ trait Foo { fn foo(&self, bar: Bar); }
+}
+struct S;
+impl foo::Foo for S { $0 }"#,
+ r#"
+mod foo {
+ pub struct Bar;
+ trait Foo { fn foo(&self, bar: Bar); }
+}
+struct S;
+impl foo::Foo for S {
+ fn foo(&self, bar: foo::Bar) {
+ ${0:todo!()}
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_qualify_path_2() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+mod foo {
+ pub mod bar {
+ pub struct Bar;
+ pub trait Foo { fn foo(&self, bar: Bar); }
+ }
+}
+
+use foo::bar;
+
+struct S;
+impl bar::Foo for S { $0 }"#,
+ r#"
+mod foo {
+ pub mod bar {
+ pub struct Bar;
+ pub trait Foo { fn foo(&self, bar: Bar); }
+ }
+}
+
+use foo::bar;
+
+struct S;
+impl bar::Foo for S {
+ fn foo(&self, bar: bar::Bar) {
+ ${0:todo!()}
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_qualify_path_generic() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+mod foo {
+ pub struct Bar<T>;
+ trait Foo { fn foo(&self, bar: Bar<u32>); }
+}
+struct S;
+impl foo::Foo for S { $0 }"#,
+ r#"
+mod foo {
+ pub struct Bar<T>;
+ trait Foo { fn foo(&self, bar: Bar<u32>); }
+}
+struct S;
+impl foo::Foo for S {
+ fn foo(&self, bar: foo::Bar<u32>) {
+ ${0:todo!()}
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_qualify_path_and_substitute_param() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+mod foo {
+ pub struct Bar<T>;
+ trait Foo<T> { fn foo(&self, bar: Bar<T>); }
+}
+struct S;
+impl foo::Foo<u32> for S { $0 }"#,
+ r#"
+mod foo {
+ pub struct Bar<T>;
+ trait Foo<T> { fn foo(&self, bar: Bar<T>); }
+}
+struct S;
+impl foo::Foo<u32> for S {
+ fn foo(&self, bar: foo::Bar<u32>) {
+ ${0:todo!()}
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_substitute_param_no_qualify() {
+ // when substituting params, the substituted param should not be qualified!
+ check_assist(
+ add_missing_impl_members,
+ r#"
+mod foo {
+ trait Foo<T> { fn foo(&self, bar: T); }
+ pub struct Param;
+}
+struct Param;
+struct S;
+impl foo::Foo<Param> for S { $0 }"#,
+ r#"
+mod foo {
+ trait Foo<T> { fn foo(&self, bar: T); }
+ pub struct Param;
+}
+struct Param;
+struct S;
+impl foo::Foo<Param> for S {
+ fn foo(&self, bar: Param) {
+ ${0:todo!()}
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_qualify_path_associated_item() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+mod foo {
+ pub struct Bar<T>;
+ impl Bar<T> { type Assoc = u32; }
+ trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); }
+}
+struct S;
+impl foo::Foo for S { $0 }"#,
+ r#"
+mod foo {
+ pub struct Bar<T>;
+ impl Bar<T> { type Assoc = u32; }
+ trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); }
+}
+struct S;
+impl foo::Foo for S {
+ fn foo(&self, bar: foo::Bar<u32>::Assoc) {
+ ${0:todo!()}
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_qualify_path_nested() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+mod foo {
+ pub struct Bar<T>;
+ pub struct Baz;
+ trait Foo { fn foo(&self, bar: Bar<Baz>); }
+}
+struct S;
+impl foo::Foo for S { $0 }"#,
+ r#"
+mod foo {
+ pub struct Bar<T>;
+ pub struct Baz;
+ trait Foo { fn foo(&self, bar: Bar<Baz>); }
+}
+struct S;
+impl foo::Foo for S {
+ fn foo(&self, bar: foo::Bar<foo::Baz>) {
+ ${0:todo!()}
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_qualify_path_fn_trait_notation() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+mod foo {
+ pub trait Fn<Args> { type Output; }
+ trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); }
+}
+struct S;
+impl foo::Foo for S { $0 }"#,
+ r#"
+mod foo {
+ pub trait Fn<Args> { type Output; }
+ trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); }
+}
+struct S;
+impl foo::Foo for S {
+ fn foo(&self, bar: dyn Fn(u32) -> i32) {
+ ${0:todo!()}
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_empty_trait() {
+ check_assist_not_applicable(
+ add_missing_impl_members,
+ r#"
+trait Foo;
+struct S;
+impl Foo for S { $0 }"#,
+ )
+ }
+
+ #[test]
+ fn test_ignore_unnamed_trait_members_and_default_methods() {
+ check_assist_not_applicable(
+ add_missing_impl_members,
+ r#"
+trait Foo {
+ fn (arg: u32);
+ fn valid(some: u32) -> bool { false }
+}
+struct S;
+impl Foo for S { $0 }"#,
+ )
+ }
+
+ #[test]
+ fn test_with_docstring_and_attrs() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+#[doc(alias = "test alias")]
+trait Foo {
+ /// doc string
+ type Output;
+
+ #[must_use]
+ fn foo(&self);
+}
+struct S;
+impl Foo for S {}$0"#,
+ r#"
+#[doc(alias = "test alias")]
+trait Foo {
+ /// doc string
+ type Output;
+
+ #[must_use]
+ fn foo(&self);
+}
+struct S;
+impl Foo for S {
+ $0type Output;
+
+ fn foo(&self) {
+ todo!()
+ }
+}"#,
+ )
+ }
+
+ #[test]
+ fn test_default_methods() {
+ check_assist(
+ add_missing_default_members,
+ r#"
+trait Foo {
+ type Output;
+
+ const CONST: usize = 42;
+
+ fn valid(some: u32) -> bool { false }
+ fn foo(some: u32) -> bool;
+}
+struct S;
+impl Foo for S { $0 }"#,
+ r#"
+trait Foo {
+ type Output;
+
+ const CONST: usize = 42;
+
+ fn valid(some: u32) -> bool { false }
+ fn foo(some: u32) -> bool;
+}
+struct S;
+impl Foo for S {
+ $0fn valid(some: u32) -> bool { false }
+}"#,
+ )
+ }
+
+ #[test]
+ fn test_generic_single_default_parameter() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+trait Foo<T = Self> {
+ fn bar(&self, other: &T);
+}
+
+struct S;
+impl Foo for S { $0 }"#,
+ r#"
+trait Foo<T = Self> {
+ fn bar(&self, other: &T);
+}
+
+struct S;
+impl Foo for S {
+ fn bar(&self, other: &Self) {
+ ${0:todo!()}
+ }
+}"#,
+ )
+ }
+
+ #[test]
+ fn test_generic_default_parameter_is_second() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+trait Foo<T1, T2 = Self> {
+ fn bar(&self, this: &T1, that: &T2);
+}
+
+struct S<T>;
+impl Foo<T> for S<T> { $0 }"#,
+ r#"
+trait Foo<T1, T2 = Self> {
+ fn bar(&self, this: &T1, that: &T2);
+}
+
+struct S<T>;
+impl Foo<T> for S<T> {
+ fn bar(&self, this: &T, that: &Self) {
+ ${0:todo!()}
+ }
+}"#,
+ )
+ }
+
+ #[test]
+ fn test_assoc_type_bounds_are_removed() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+trait Tr {
+ type Ty: Copy + 'static;
+}
+
+impl Tr for ()$0 {
+}"#,
+ r#"
+trait Tr {
+ type Ty: Copy + 'static;
+}
+
+impl Tr for () {
+ $0type Ty;
+}"#,
+ )
+ }
+
+ #[test]
+ fn test_whitespace_fixup_preserves_bad_tokens() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+trait Tr {
+ fn foo();
+}
+
+impl Tr for ()$0 {
+ +++
+}"#,
+ r#"
+trait Tr {
+ fn foo();
+}
+
+impl Tr for () {
+ fn foo() {
+ ${0:todo!()}
+ }
+ +++
+}"#,
+ )
+ }
+
+ #[test]
+ fn test_whitespace_fixup_preserves_comments() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+trait Tr {
+ fn foo();
+}
+
+impl Tr for ()$0 {
+ // very important
+}"#,
+ r#"
+trait Tr {
+ fn foo();
+}
+
+impl Tr for () {
+ fn foo() {
+ ${0:todo!()}
+ }
+ // very important
+}"#,
+ )
+ }
+
+ #[test]
+ fn weird_path() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+trait Test {
+ fn foo(&self, x: crate)
+}
+impl Test for () {
+ $0
+}
+"#,
+ r#"
+trait Test {
+ fn foo(&self, x: crate)
+}
+impl Test for () {
+ fn foo(&self, x: crate) {
+ ${0:todo!()}
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn missing_generic_type() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+trait Foo<BAR> {
+ fn foo(&self, bar: BAR);
+}
+impl Foo for () {
+ $0
+}
+"#,
+ r#"
+trait Foo<BAR> {
+ fn foo(&self, bar: BAR);
+}
+impl Foo for () {
+ fn foo(&self, bar: BAR) {
+ ${0:todo!()}
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn does_not_requalify_self_as_crate() {
+ check_assist(
+ add_missing_default_members,
+ r"
+struct Wrapper<T>(T);
+
+trait T {
+ fn f(self) -> Wrapper<Self> {
+ Wrapper(self)
+ }
+}
+
+impl T for () {
+ $0
+}
+",
+ r"
+struct Wrapper<T>(T);
+
+trait T {
+ fn f(self) -> Wrapper<Self> {
+ Wrapper(self)
+ }
+}
+
+impl T for () {
+ $0fn f(self) -> Wrapper<Self> {
+ Wrapper(self)
+ }
+}
+",
+ );
+ }
+
+ #[test]
+ fn test_default_body_generation() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+//- minicore: default
+struct Foo(usize);
+
+impl Default for Foo {
+ $0
+}
+"#,
+ r#"
+struct Foo(usize);
+
+impl Default for Foo {
+ $0fn default() -> Self {
+ Self(Default::default())
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_from_macro() {
+ check_assist(
+ add_missing_default_members,
+ r#"
+macro_rules! foo {
+ () => {
+ trait FooB {
+ fn foo<'lt>(&'lt self) {}
+ }
+ }
+}
+foo!();
+struct Foo(usize);
+
+impl FooB for Foo {
+ $0
+}
+"#,
+ r#"
+macro_rules! foo {
+ () => {
+ trait FooB {
+ fn foo<'lt>(&'lt self) {}
+ }
+ }
+}
+foo!();
+struct Foo(usize);
+
+impl FooB for Foo {
+ $0fn foo< 'lt>(& 'lt self){}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_assoc_type_when_trait_with_same_name_in_scope() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+pub trait Foo {}
+
+pub trait Types {
+ type Foo;
+}
+
+pub trait Behavior<T: Types> {
+ fn reproduce(&self, foo: T::Foo);
+}
+
+pub struct Impl;
+
+impl<T: Types> Behavior<T> for Impl { $0 }"#,
+ r#"
+pub trait Foo {}
+
+pub trait Types {
+ type Foo;
+}
+
+pub trait Behavior<T: Types> {
+ fn reproduce(&self, foo: T::Foo);
+}
+
+pub struct Impl;
+
+impl<T: Types> Behavior<T> for Impl {
+ fn reproduce(&self, foo: <T as Types>::Foo) {
+ ${0:todo!()}
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_assoc_type_on_concrete_type() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+pub trait Types {
+ type Foo;
+}
+
+impl Types for u32 {
+ type Foo = bool;
+}
+
+pub trait Behavior<T: Types> {
+ fn reproduce(&self, foo: T::Foo);
+}
+
+pub struct Impl;
+
+impl Behavior<u32> for Impl { $0 }"#,
+ r#"
+pub trait Types {
+ type Foo;
+}
+
+impl Types for u32 {
+ type Foo = bool;
+}
+
+pub trait Behavior<T: Types> {
+ fn reproduce(&self, foo: T::Foo);
+}
+
+pub struct Impl;
+
+impl Behavior<u32> for Impl {
+ fn reproduce(&self, foo: <u32 as Types>::Foo) {
+ ${0:todo!()}
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_assoc_type_on_concrete_type_qualified() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+pub trait Types {
+ type Foo;
+}
+
+impl Types for std::string::String {
+ type Foo = bool;
+}
+
+pub trait Behavior<T: Types> {
+ fn reproduce(&self, foo: T::Foo);
+}
+
+pub struct Impl;
+
+impl Behavior<std::string::String> for Impl { $0 }"#,
+ r#"
+pub trait Types {
+ type Foo;
+}
+
+impl Types for std::string::String {
+ type Foo = bool;
+}
+
+pub trait Behavior<T: Types> {
+ fn reproduce(&self, foo: T::Foo);
+}
+
+pub struct Impl;
+
+impl Behavior<std::string::String> for Impl {
+ fn reproduce(&self, foo: <std::string::String as Types>::Foo) {
+ ${0:todo!()}
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_assoc_type_on_concrete_type_multi_option_ambiguous() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+pub trait Types {
+ type Foo;
+}
+
+pub trait Types2 {
+ type Foo;
+}
+
+impl Types for u32 {
+ type Foo = bool;
+}
+
+impl Types2 for u32 {
+ type Foo = String;
+}
+
+pub trait Behavior<T: Types + Types2> {
+ fn reproduce(&self, foo: <T as Types2>::Foo);
+}
+
+pub struct Impl;
+
+impl Behavior<u32> for Impl { $0 }"#,
+ r#"
+pub trait Types {
+ type Foo;
+}
+
+pub trait Types2 {
+ type Foo;
+}
+
+impl Types for u32 {
+ type Foo = bool;
+}
+
+impl Types2 for u32 {
+ type Foo = String;
+}
+
+pub trait Behavior<T: Types + Types2> {
+ fn reproduce(&self, foo: <T as Types2>::Foo);
+}
+
+pub struct Impl;
+
+impl Behavior<u32> for Impl {
+ fn reproduce(&self, foo: <u32 as Types2>::Foo) {
+ ${0:todo!()}
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_assoc_type_on_concrete_type_multi_option() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+pub trait Types {
+ type Foo;
+}
+
+pub trait Types2 {
+ type Bar;
+}
+
+impl Types for u32 {
+ type Foo = bool;
+}
+
+impl Types2 for u32 {
+ type Bar = String;
+}
+
+pub trait Behavior<T: Types + Types2> {
+ fn reproduce(&self, foo: T::Bar);
+}
+
+pub struct Impl;
+
+impl Behavior<u32> for Impl { $0 }"#,
+ r#"
+pub trait Types {
+ type Foo;
+}
+
+pub trait Types2 {
+ type Bar;
+}
+
+impl Types for u32 {
+ type Foo = bool;
+}
+
+impl Types2 for u32 {
+ type Bar = String;
+}
+
+pub trait Behavior<T: Types + Types2> {
+ fn reproduce(&self, foo: T::Bar);
+}
+
+pub struct Impl;
+
+impl Behavior<u32> for Impl {
+ fn reproduce(&self, foo: <u32 as Types2>::Bar) {
+ ${0:todo!()}
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_assoc_type_on_concrete_type_multi_option_foreign() {
+ check_assist(
+ add_missing_impl_members,
+ r#"
+mod bar {
+ pub trait Types2 {
+ type Bar;
+ }
+}
+
+pub trait Types {
+ type Foo;
+}
+
+impl Types for u32 {
+ type Foo = bool;
+}
+
+impl bar::Types2 for u32 {
+ type Bar = String;
+}
+
+pub trait Behavior<T: Types + bar::Types2> {
+ fn reproduce(&self, foo: T::Bar);
+}
+
+pub struct Impl;
+
+impl Behavior<u32> for Impl { $0 }"#,
+ r#"
+mod bar {
+ pub trait Types2 {
+ type Bar;
+ }
+}
+
+pub trait Types {
+ type Foo;
+}
+
+impl Types for u32 {
+ type Foo = bool;
+}
+
+impl bar::Types2 for u32 {
+ type Bar = String;
+}
+
+pub trait Behavior<T: Types + bar::Types2> {
+ fn reproduce(&self, foo: T::Bar);
+}
+
+pub struct Impl;
+
+impl Behavior<u32> for Impl {
+ fn reproduce(&self, foo: <u32 as bar::Types2>::Bar) {
+ ${0:todo!()}
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_transform_path_in_path_expr() {
+ check_assist(
+ add_missing_default_members,
+ r#"
+pub trait Const {
+ const FOO: u32;
+}
+
+pub trait Trait<T: Const> {
+ fn foo() -> bool {
+ match T::FOO {
+ 0 => true,
+ _ => false,
+ }
+ }
+}
+
+impl Const for u32 {
+ const FOO: u32 = 1;
+}
+
+struct Impl;
+
+impl Trait<u32> for Impl { $0 }"#,
+ r#"
+pub trait Const {
+ const FOO: u32;
+}
+
+pub trait Trait<T: Const> {
+ fn foo() -> bool {
+ match T::FOO {
+ 0 => true,
+ _ => false,
+ }
+ }
+}
+
+impl Const for u32 {
+ const FOO: u32 = 1;
+}
+
+struct Impl;
+
+impl Trait<u32> for Impl {
+ $0fn foo() -> bool {
+ match <u32 as Const>::FOO {
+ 0 => true,
+ _ => false,
+ }
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_default_partial_eq() {
+ check_assist(
+ add_missing_default_members,
+ r#"
+//- minicore: eq
+struct SomeStruct {
+ data: usize,
+ field: (usize, usize),
+}
+impl PartialEq for SomeStruct {$0}
+"#,
+ r#"
+struct SomeStruct {
+ data: usize,
+ field: (usize, usize),
+}
+impl PartialEq for SomeStruct {
+ $0fn ne(&self, other: &Self) -> bool {
+ !self.eq(other)
+ }
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs
new file mode 100644
index 000000000..b16f6fe03
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs
@@ -0,0 +1,1709 @@
+use std::iter::{self, Peekable};
+
+use either::Either;
+use hir::{Adt, Crate, HasAttrs, HasSource, ModuleDef, Semantics};
+use ide_db::RootDatabase;
+use ide_db::{famous_defs::FamousDefs, helpers::mod_path_to_ast};
+use itertools::Itertools;
+use syntax::ast::{self, make, AstNode, HasName, MatchArmList, MatchExpr, Pat};
+
+use crate::{
+ utils::{self, render_snippet, Cursor},
+ AssistContext, AssistId, AssistKind, Assists,
+};
+
+// Assist: add_missing_match_arms
+//
+// Adds missing clauses to a `match` expression.
+//
+// ```
+// enum Action { Move { distance: u32 }, Stop }
+//
+// fn handle(action: Action) {
+// match action {
+// $0
+// }
+// }
+// ```
+// ->
+// ```
+// enum Action { Move { distance: u32 }, Stop }
+//
+// fn handle(action: Action) {
+// match action {
+// $0Action::Move { distance } => todo!(),
+// Action::Stop => todo!(),
+// }
+// }
+// ```
+pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let match_expr = ctx.find_node_at_offset_with_descend::<ast::MatchExpr>()?;
+ let match_arm_list = match_expr.match_arm_list()?;
+ let target_range = ctx.sema.original_range(match_expr.syntax()).range;
+
+ if let None = cursor_at_trivial_match_arm_list(ctx, &match_expr, &match_arm_list) {
+ let arm_list_range = ctx.sema.original_range(match_arm_list.syntax()).range;
+ let cursor_in_range = arm_list_range.contains_range(ctx.selection_trimmed());
+ if cursor_in_range {
+ cov_mark::hit!(not_applicable_outside_of_range_right);
+ return None;
+ }
+ }
+
+ let expr = match_expr.expr()?;
+
+ let mut has_catch_all_arm = false;
+
+ let top_lvl_pats: Vec<_> = match_arm_list
+ .arms()
+ .filter_map(|arm| Some((arm.pat()?, arm.guard().is_some())))
+ .flat_map(|(pat, has_guard)| {
+ match pat {
+ // Special case OrPat as separate top-level pats
+ Pat::OrPat(or_pat) => Either::Left(or_pat.pats()),
+ _ => Either::Right(iter::once(pat)),
+ }
+ .map(move |pat| (pat, has_guard))
+ })
+ .map(|(pat, has_guard)| {
+ has_catch_all_arm |= !has_guard && matches!(pat, Pat::WildcardPat(_));
+ pat
+ })
+ // Exclude top level wildcards so that they are expanded by this assist, retains status quo in #8129.
+ .filter(|pat| !matches!(pat, Pat::WildcardPat(_)))
+ .collect();
+
+ let module = ctx.sema.scope(expr.syntax())?.module();
+ let (mut missing_pats, is_non_exhaustive): (
+ Peekable<Box<dyn Iterator<Item = (ast::Pat, bool)>>>,
+ bool,
+ ) = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) {
+ let is_non_exhaustive = enum_def.is_non_exhaustive(ctx.db(), module.krate());
+
+ let variants = enum_def.variants(ctx.db());
+
+ let missing_pats = variants
+ .into_iter()
+ .filter_map(|variant| {
+ Some((
+ build_pat(ctx.db(), module, variant)?,
+ variant.should_be_hidden(ctx.db(), module.krate()),
+ ))
+ })
+ .filter(|(variant_pat, _)| is_variant_missing(&top_lvl_pats, variant_pat));
+
+ let option_enum = FamousDefs(&ctx.sema, module.krate()).core_option_Option().map(lift_enum);
+ let missing_pats: Box<dyn Iterator<Item = _>> = if Some(enum_def) == option_enum {
+ // Match `Some` variant first.
+ cov_mark::hit!(option_order);
+ Box::new(missing_pats.rev())
+ } else {
+ Box::new(missing_pats)
+ };
+ (missing_pats.peekable(), is_non_exhaustive)
+ } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) {
+ let is_non_exhaustive =
+ enum_defs.iter().any(|enum_def| enum_def.is_non_exhaustive(ctx.db(), module.krate()));
+
+ let mut n_arms = 1;
+ let variants_of_enums: Vec<Vec<ExtendedVariant>> = enum_defs
+ .into_iter()
+ .map(|enum_def| enum_def.variants(ctx.db()))
+ .inspect(|variants| n_arms *= variants.len())
+ .collect();
+
+ // When calculating the match arms for a tuple of enums, we want
+ // to create a match arm for each possible combination of enum
+ // values. The `multi_cartesian_product` method transforms
+ // Vec<Vec<EnumVariant>> into Vec<(EnumVariant, .., EnumVariant)>
+ // where each tuple represents a proposed match arm.
+
+ // A number of arms grows very fast on even a small tuple of large enums.
+ // We skip the assist beyond an arbitrary threshold.
+ if n_arms > 256 {
+ return None;
+ }
+ let missing_pats = variants_of_enums
+ .into_iter()
+ .multi_cartesian_product()
+ .inspect(|_| cov_mark::hit!(add_missing_match_arms_lazy_computation))
+ .map(|variants| {
+ let is_hidden = variants
+ .iter()
+ .any(|variant| variant.should_be_hidden(ctx.db(), module.krate()));
+ let patterns =
+ variants.into_iter().filter_map(|variant| build_pat(ctx.db(), module, variant));
+
+ (ast::Pat::from(make::tuple_pat(patterns)), is_hidden)
+ })
+ .filter(|(variant_pat, _)| is_variant_missing(&top_lvl_pats, variant_pat));
+ ((Box::new(missing_pats) as Box<dyn Iterator<Item = _>>).peekable(), is_non_exhaustive)
+ } else {
+ return None;
+ };
+
+ let mut needs_catch_all_arm = is_non_exhaustive && !has_catch_all_arm;
+
+ if !needs_catch_all_arm && missing_pats.peek().is_none() {
+ return None;
+ }
+
+ acc.add(
+ AssistId("add_missing_match_arms", AssistKind::QuickFix),
+ "Fill match arms",
+ target_range,
+ |builder| {
+ let new_match_arm_list = match_arm_list.clone_for_update();
+ let missing_arms = missing_pats
+ .map(|(pat, hidden)| {
+ (make::match_arm(iter::once(pat), None, make::ext::expr_todo()), hidden)
+ })
+ .map(|(it, hidden)| (it.clone_for_update(), hidden));
+
+ let catch_all_arm = new_match_arm_list
+ .arms()
+ .find(|arm| matches!(arm.pat(), Some(ast::Pat::WildcardPat(_))));
+ if let Some(arm) = catch_all_arm {
+ let is_empty_expr = arm.expr().map_or(true, |e| match e {
+ ast::Expr::BlockExpr(b) => {
+ b.statements().next().is_none() && b.tail_expr().is_none()
+ }
+ ast::Expr::TupleExpr(t) => t.fields().next().is_none(),
+ _ => false,
+ });
+ if is_empty_expr {
+ arm.remove();
+ } else {
+ cov_mark::hit!(add_missing_match_arms_empty_expr);
+ }
+ }
+ let mut first_new_arm = None;
+ for (arm, hidden) in missing_arms {
+ if hidden {
+ needs_catch_all_arm = !has_catch_all_arm;
+ } else {
+ first_new_arm.get_or_insert_with(|| arm.clone());
+ new_match_arm_list.add_arm(arm);
+ }
+ }
+ if needs_catch_all_arm && !has_catch_all_arm {
+ cov_mark::hit!(added_wildcard_pattern);
+ let arm = make::match_arm(
+ iter::once(make::wildcard_pat().into()),
+ None,
+ make::ext::expr_todo(),
+ )
+ .clone_for_update();
+ first_new_arm.get_or_insert_with(|| arm.clone());
+ new_match_arm_list.add_arm(arm);
+ }
+
+ let old_range = ctx.sema.original_range(match_arm_list.syntax()).range;
+ match (first_new_arm, ctx.config.snippet_cap) {
+ (Some(first_new_arm), Some(cap)) => {
+ let extend_lifetime;
+ let cursor =
+ match first_new_arm.syntax().descendants().find_map(ast::WildcardPat::cast)
+ {
+ Some(it) => {
+ extend_lifetime = it.syntax().clone();
+ Cursor::Replace(&extend_lifetime)
+ }
+ None => Cursor::Before(first_new_arm.syntax()),
+ };
+ let snippet = render_snippet(cap, new_match_arm_list.syntax(), cursor);
+ builder.replace_snippet(cap, old_range, snippet);
+ }
+ _ => builder.replace(old_range, new_match_arm_list.to_string()),
+ }
+ },
+ )
+}
+
+fn cursor_at_trivial_match_arm_list(
+ ctx: &AssistContext<'_>,
+ match_expr: &MatchExpr,
+ match_arm_list: &MatchArmList,
+) -> Option<()> {
+ // match x { $0 }
+ if match_arm_list.arms().next() == None {
+ cov_mark::hit!(add_missing_match_arms_empty_body);
+ return Some(());
+ }
+
+ // match x {
+ // bar => baz,
+ // $0
+ // }
+ if let Some(last_arm) = match_arm_list.arms().last() {
+ let last_arm_range = last_arm.syntax().text_range();
+ let match_expr_range = match_expr.syntax().text_range();
+ if last_arm_range.end() <= ctx.offset() && ctx.offset() < match_expr_range.end() {
+ cov_mark::hit!(add_missing_match_arms_end_of_last_arm);
+ return Some(());
+ }
+ }
+
+ // match { _$0 => {...} }
+ let wild_pat = ctx.find_node_at_offset_with_descend::<ast::WildcardPat>()?;
+ let arm = wild_pat.syntax().parent().and_then(ast::MatchArm::cast)?;
+ let arm_match_expr = arm.syntax().ancestors().nth(2).and_then(ast::MatchExpr::cast)?;
+ if arm_match_expr == *match_expr {
+ cov_mark::hit!(add_missing_match_arms_trivial_arm);
+ return Some(());
+ }
+
+ None
+}
+
+fn is_variant_missing(existing_pats: &[Pat], var: &Pat) -> bool {
+ !existing_pats.iter().any(|pat| does_pat_match_variant(pat, var))
+}
+
+// Fixme: this is still somewhat limited, use hir_ty::diagnostics::match_check?
+fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool {
+ match (pat, var) {
+ (Pat::WildcardPat(_), _) => true,
+ (Pat::TuplePat(tpat), Pat::TuplePat(tvar)) => {
+ tpat.fields().zip(tvar.fields()).all(|(p, v)| does_pat_match_variant(&p, &v))
+ }
+ _ => utils::does_pat_match_variant(pat, var),
+ }
+}
+
+#[derive(Eq, PartialEq, Clone, Copy)]
+enum ExtendedEnum {
+ Bool,
+ Enum(hir::Enum),
+}
+
+#[derive(Eq, PartialEq, Clone, Copy)]
+enum ExtendedVariant {
+ True,
+ False,
+ Variant(hir::Variant),
+}
+
+impl ExtendedVariant {
+ fn should_be_hidden(self, db: &RootDatabase, krate: Crate) -> bool {
+ match self {
+ ExtendedVariant::Variant(var) => {
+ var.attrs(db).has_doc_hidden() && var.module(db).krate() != krate
+ }
+ _ => false,
+ }
+ }
+}
+
+fn lift_enum(e: hir::Enum) -> ExtendedEnum {
+ ExtendedEnum::Enum(e)
+}
+
+impl ExtendedEnum {
+ fn is_non_exhaustive(self, db: &RootDatabase, krate: Crate) -> bool {
+ match self {
+ ExtendedEnum::Enum(e) => {
+ e.attrs(db).by_key("non_exhaustive").exists() && e.module(db).krate() != krate
+ }
+ _ => false,
+ }
+ }
+
+ fn variants(self, db: &RootDatabase) -> Vec<ExtendedVariant> {
+ match self {
+ ExtendedEnum::Enum(e) => {
+ e.variants(db).into_iter().map(ExtendedVariant::Variant).collect::<Vec<_>>()
+ }
+ ExtendedEnum::Bool => {
+ Vec::<ExtendedVariant>::from([ExtendedVariant::True, ExtendedVariant::False])
+ }
+ }
+ }
+}
+
+fn resolve_enum_def(sema: &Semantics<'_, RootDatabase>, expr: &ast::Expr) -> Option<ExtendedEnum> {
+ sema.type_of_expr(expr)?.adjusted().autoderef(sema.db).find_map(|ty| match ty.as_adt() {
+ Some(Adt::Enum(e)) => Some(ExtendedEnum::Enum(e)),
+ _ => ty.is_bool().then(|| ExtendedEnum::Bool),
+ })
+}
+
+fn resolve_tuple_of_enum_def(
+ sema: &Semantics<'_, RootDatabase>,
+ expr: &ast::Expr,
+) -> Option<Vec<ExtendedEnum>> {
+ sema.type_of_expr(expr)?
+ .adjusted()
+ .tuple_fields(sema.db)
+ .iter()
+ .map(|ty| {
+ ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
+ Some(Adt::Enum(e)) => Some(lift_enum(e)),
+ // For now we only handle expansion for a tuple of enums. Here
+ // we map non-enum items to None and rely on `collect` to
+ // convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>.
+ _ => ty.is_bool().then(|| ExtendedEnum::Bool),
+ })
+ })
+ .collect()
+}
+
+fn build_pat(db: &RootDatabase, module: hir::Module, var: ExtendedVariant) -> Option<ast::Pat> {
+ match var {
+ ExtendedVariant::Variant(var) => {
+ let path = mod_path_to_ast(&module.find_use_path(db, ModuleDef::from(var))?);
+
+ // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though
+ let pat: ast::Pat = match var.source(db)?.value.kind() {
+ ast::StructKind::Tuple(field_list) => {
+ let pats =
+ iter::repeat(make::wildcard_pat().into()).take(field_list.fields().count());
+ make::tuple_struct_pat(path, pats).into()
+ }
+ ast::StructKind::Record(field_list) => {
+ let pats = field_list
+ .fields()
+ .map(|f| make::ext::simple_ident_pat(f.name().unwrap()).into());
+ make::record_pat(path, pats).into()
+ }
+ ast::StructKind::Unit => make::path_pat(path),
+ };
+
+ Some(pat)
+ }
+ ExtendedVariant::True => Some(ast::Pat::from(make::literal_pat("true"))),
+ ExtendedVariant::False => Some(ast::Pat::from(make::literal_pat("false"))),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{
+ check_assist, check_assist_not_applicable, check_assist_target, check_assist_unresolved,
+ };
+
+ use super::add_missing_match_arms;
+
+ #[test]
+ fn all_match_arms_provided() {
+ check_assist_not_applicable(
+ add_missing_match_arms,
+ r#"
+enum A {
+ As,
+ Bs{x:i32, y:Option<i32>},
+ Cs(i32, Option<i32>),
+}
+fn main() {
+ match A::As$0 {
+ A::As,
+ A::Bs{x,y:Some(_)} => {}
+ A::Cs(_, Some(_)) => {}
+ }
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_outside_of_range_left() {
+ check_assist_not_applicable(
+ add_missing_match_arms,
+ r#"
+enum A { X, Y }
+
+fn foo(a: A) {
+ $0 match a {
+ A::X => { }
+ }
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_outside_of_range_right() {
+ cov_mark::check!(not_applicable_outside_of_range_right);
+ check_assist_not_applicable(
+ add_missing_match_arms,
+ r#"
+enum A { X, Y }
+
+fn foo(a: A) {
+ match a {$0
+ A::X => { }
+ }
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn all_boolean_match_arms_provided() {
+ check_assist_not_applicable(
+ add_missing_match_arms,
+ r#"
+fn foo(a: bool) {
+ match a$0 {
+ true => {}
+ false => {}
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn tuple_of_non_enum() {
+ // for now this case is not handled, although it potentially could be
+ // in the future
+ check_assist_not_applicable(
+ add_missing_match_arms,
+ r#"
+fn main() {
+ match (0, false)$0 {
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_boolean() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+fn foo(a: bool) {
+ match a$0 {
+ }
+}
+"#,
+ r#"
+fn foo(a: bool) {
+ match a {
+ $0true => todo!(),
+ false => todo!(),
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn partial_fill_boolean() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+fn foo(a: bool) {
+ match a$0 {
+ true => {}
+ }
+}
+"#,
+ r#"
+fn foo(a: bool) {
+ match a {
+ true => {}
+ $0false => todo!(),
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn all_boolean_tuple_arms_provided() {
+ check_assist_not_applicable(
+ add_missing_match_arms,
+ r#"
+fn foo(a: bool) {
+ match (a, a)$0 {
+ (true, true) => {}
+ (true, false) => {}
+ (false, true) => {}
+ (false, false) => {}
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn fill_boolean_tuple() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+fn foo(a: bool) {
+ match (a, a)$0 {
+ }
+}
+"#,
+ r#"
+fn foo(a: bool) {
+ match (a, a) {
+ $0(true, true) => todo!(),
+ (true, false) => todo!(),
+ (false, true) => todo!(),
+ (false, false) => todo!(),
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn partial_fill_boolean_tuple() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+fn foo(a: bool) {
+ match (a, a)$0 {
+ (false, true) => {}
+ }
+}
+"#,
+ r#"
+fn foo(a: bool) {
+ match (a, a) {
+ (false, true) => {}
+ $0(true, true) => todo!(),
+ (true, false) => todo!(),
+ (false, false) => todo!(),
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn partial_fill_record_tuple() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A {
+ As,
+ Bs { x: i32, y: Option<i32> },
+ Cs(i32, Option<i32>),
+}
+fn main() {
+ match A::As$0 {
+ A::Bs { x, y: Some(_) } => {}
+ A::Cs(_, Some(_)) => {}
+ }
+}
+"#,
+ r#"
+enum A {
+ As,
+ Bs { x: i32, y: Option<i32> },
+ Cs(i32, Option<i32>),
+}
+fn main() {
+ match A::As {
+ A::Bs { x, y: Some(_) } => {}
+ A::Cs(_, Some(_)) => {}
+ $0A::As => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn partial_fill_option() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+//- minicore: option
+fn main() {
+ match None$0 {
+ None => {}
+ }
+}
+"#,
+ r#"
+fn main() {
+ match None {
+ None => {}
+ Some(${0:_}) => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn partial_fill_or_pat() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A { As, Bs, Cs(Option<i32>) }
+fn main() {
+ match A::As$0 {
+ A::Cs(_) | A::Bs => {}
+ }
+}
+"#,
+ r#"
+enum A { As, Bs, Cs(Option<i32>) }
+fn main() {
+ match A::As {
+ A::Cs(_) | A::Bs => {}
+ $0A::As => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn partial_fill() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A { As, Bs, Cs, Ds(String), Es(B) }
+enum B { Xs, Ys }
+fn main() {
+ match A::As$0 {
+ A::Bs if 0 < 1 => {}
+ A::Ds(_value) => { let x = 1; }
+ A::Es(B::Xs) => (),
+ }
+}
+"#,
+ r#"
+enum A { As, Bs, Cs, Ds(String), Es(B) }
+enum B { Xs, Ys }
+fn main() {
+ match A::As {
+ A::Bs if 0 < 1 => {}
+ A::Ds(_value) => { let x = 1; }
+ A::Es(B::Xs) => (),
+ $0A::As => todo!(),
+ A::Cs => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn partial_fill_bind_pat() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A { As, Bs, Cs(Option<i32>) }
+fn main() {
+ match A::As$0 {
+ A::As(_) => {}
+ a @ A::Bs(_) => {}
+ }
+}
+"#,
+ r#"
+enum A { As, Bs, Cs(Option<i32>) }
+fn main() {
+ match A::As {
+ A::As(_) => {}
+ a @ A::Bs(_) => {}
+ A::Cs(${0:_}) => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_empty_body() {
+ cov_mark::check!(add_missing_match_arms_empty_body);
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } }
+
+fn main() {
+ let a = A::As;
+ match a {$0}
+}
+"#,
+ r#"
+enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } }
+
+fn main() {
+ let a = A::As;
+ match a {
+ $0A::As => todo!(),
+ A::Bs => todo!(),
+ A::Cs(_) => todo!(),
+ A::Ds(_, _) => todo!(),
+ A::Es { x, y } => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_end_of_last_arm() {
+ cov_mark::check!(add_missing_match_arms_end_of_last_arm);
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A { One, Two }
+enum B { One, Two }
+
+fn main() {
+ let a = A::One;
+ let b = B::One;
+ match (a, b) {
+ (A::Two, B::One) => {},$0
+ }
+}
+"#,
+ r#"
+enum A { One, Two }
+enum B { One, Two }
+
+fn main() {
+ let a = A::One;
+ let b = B::One;
+ match (a, b) {
+ (A::Two, B::One) => {},
+ $0(A::One, B::One) => todo!(),
+ (A::One, B::Two) => todo!(),
+ (A::Two, B::Two) => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_tuple_of_enum() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A { One, Two }
+enum B { One, Two }
+
+fn main() {
+ let a = A::One;
+ let b = B::One;
+ match (a$0, b) {}
+}
+"#,
+ r#"
+enum A { One, Two }
+enum B { One, Two }
+
+fn main() {
+ let a = A::One;
+ let b = B::One;
+ match (a, b) {
+ $0(A::One, B::One) => todo!(),
+ (A::One, B::Two) => todo!(),
+ (A::Two, B::One) => todo!(),
+ (A::Two, B::Two) => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_tuple_of_enum_ref() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A { One, Two }
+enum B { One, Two }
+
+fn main() {
+ let a = A::One;
+ let b = B::One;
+ match (&a$0, &b) {}
+}
+"#,
+ r#"
+enum A { One, Two }
+enum B { One, Two }
+
+fn main() {
+ let a = A::One;
+ let b = B::One;
+ match (&a, &b) {
+ $0(A::One, B::One) => todo!(),
+ (A::One, B::Two) => todo!(),
+ (A::Two, B::One) => todo!(),
+ (A::Two, B::Two) => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_tuple_of_enum_partial() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A { One, Two }
+enum B { One, Two }
+
+fn main() {
+ let a = A::One;
+ let b = B::One;
+ match (a$0, b) {
+ (A::Two, B::One) => {}
+ }
+}
+"#,
+ r#"
+enum A { One, Two }
+enum B { One, Two }
+
+fn main() {
+ let a = A::One;
+ let b = B::One;
+ match (a, b) {
+ (A::Two, B::One) => {}
+ $0(A::One, B::One) => todo!(),
+ (A::One, B::Two) => todo!(),
+ (A::Two, B::Two) => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_tuple_of_enum_partial_with_wildcards() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+//- minicore: option
+fn main() {
+ let a = Some(1);
+ let b = Some(());
+ match (a$0, b) {
+ (Some(_), _) => {}
+ (None, Some(_)) => {}
+ }
+}
+"#,
+ r#"
+fn main() {
+ let a = Some(1);
+ let b = Some(());
+ match (a, b) {
+ (Some(_), _) => {}
+ (None, Some(_)) => {}
+ $0(None, None) => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_partial_with_deep_pattern() {
+ // Fixme: cannot handle deep patterns
+ check_assist_not_applicable(
+ add_missing_match_arms,
+ r#"
+//- minicore: option
+fn main() {
+ match $0Some(true) {
+ Some(true) => {}
+ None => {}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_tuple_of_enum_not_applicable() {
+ check_assist_not_applicable(
+ add_missing_match_arms,
+ r#"
+enum A { One, Two }
+enum B { One, Two }
+
+fn main() {
+ let a = A::One;
+ let b = B::One;
+ match (a$0, b) {
+ (A::Two, B::One) => {}
+ (A::One, B::One) => {}
+ (A::One, B::Two) => {}
+ (A::Two, B::Two) => {}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_single_element_tuple_of_enum() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A { One, Two }
+
+fn main() {
+ let a = A::One;
+ match (a$0, ) {
+ }
+}
+"#,
+ r#"
+enum A { One, Two }
+
+fn main() {
+ let a = A::One;
+ match (a, ) {
+ $0(A::One,) => todo!(),
+ (A::Two,) => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_fill_match_arm_refs() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A { As }
+
+fn foo(a: &A) {
+ match a$0 {
+ }
+}
+"#,
+ r#"
+enum A { As }
+
+fn foo(a: &A) {
+ match a {
+ $0A::As => todo!(),
+ }
+}
+"#,
+ );
+
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A {
+ Es { x: usize, y: usize }
+}
+
+fn foo(a: &mut A) {
+ match a$0 {
+ }
+}
+"#,
+ r#"
+enum A {
+ Es { x: usize, y: usize }
+}
+
+fn foo(a: &mut A) {
+ match a {
+ $0A::Es { x, y } => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_target_simple() {
+ check_assist_target(
+ add_missing_match_arms,
+ r#"
+enum E { X, Y }
+
+fn main() {
+ match E::X$0 {}
+}
+"#,
+ "match E::X {}",
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_target_complex() {
+ check_assist_target(
+ add_missing_match_arms,
+ r#"
+enum E { X, Y }
+
+fn main() {
+ match E::X$0 {
+ E::X => {}
+ }
+}
+"#,
+ "match E::X {
+ E::X => {}
+ }",
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_trivial_arm() {
+ cov_mark::check!(add_missing_match_arms_trivial_arm);
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum E { X, Y }
+
+fn main() {
+ match E::X {
+ $0_ => {}
+ }
+}
+"#,
+ r#"
+enum E { X, Y }
+
+fn main() {
+ match E::X {
+ $0E::X => todo!(),
+ E::Y => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wildcard_inside_expression_not_applicable() {
+ check_assist_not_applicable(
+ add_missing_match_arms,
+ r#"
+enum E { X, Y }
+
+fn foo(e : E) {
+ match e {
+ _ => {
+ println!("1");$0
+ println!("2");
+ }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_qualifies_path() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+mod foo { pub enum E { X, Y } }
+use foo::E::X;
+
+fn main() {
+ match X {
+ $0
+ }
+}
+"#,
+ r#"
+mod foo { pub enum E { X, Y } }
+use foo::E::X;
+
+fn main() {
+ match X {
+ $0X => todo!(),
+ foo::E::Y => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_preserves_comments() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A { One, Two }
+fn foo(a: A) {
+ match a $0 {
+ // foo bar baz
+ A::One => {}
+ // This is where the rest should be
+ }
+}
+"#,
+ r#"
+enum A { One, Two }
+fn foo(a: A) {
+ match a {
+ // foo bar baz
+ A::One => {}
+ $0A::Two => todo!(),
+ // This is where the rest should be
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_preserves_comments_empty() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A { One, Two }
+fn foo(a: A) {
+ match a {
+ // foo bar baz$0
+ }
+}
+"#,
+ r#"
+enum A { One, Two }
+fn foo(a: A) {
+ match a {
+ $0A::One => todo!(),
+ A::Two => todo!(),
+ // foo bar baz
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_placeholder() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A { One, Two, }
+fn foo(a: A) {
+ match a$0 {
+ _ => (),
+ }
+}
+"#,
+ r#"
+enum A { One, Two, }
+fn foo(a: A) {
+ match a {
+ $0A::One => todo!(),
+ A::Two => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn option_order() {
+ cov_mark::check!(option_order);
+ check_assist(
+ add_missing_match_arms,
+ r#"
+//- minicore: option
+fn foo(opt: Option<i32>) {
+ match opt$0 {
+ }
+}
+"#,
+ r#"
+fn foo(opt: Option<i32>) {
+ match opt {
+ Some(${0:_}) => todo!(),
+ None => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn works_inside_macro_call() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+macro_rules! m { ($expr:expr) => {$expr}}
+enum Test {
+ A,
+ B,
+ C,
+}
+
+fn foo(t: Test) {
+ m!(match t$0 {});
+}"#,
+ r#"
+macro_rules! m { ($expr:expr) => {$expr}}
+enum Test {
+ A,
+ B,
+ C,
+}
+
+fn foo(t: Test) {
+ m!(match t {
+ $0Test::A => todo!(),
+ Test::B => todo!(),
+ Test::C => todo!(),
+});
+}"#,
+ );
+ }
+
+ #[test]
+ fn lazy_computation() {
+ // Computing a single missing arm is enough to determine applicability of the assist.
+ cov_mark::check_count!(add_missing_match_arms_lazy_computation, 1);
+ check_assist_unresolved(
+ add_missing_match_arms,
+ r#"
+enum A { One, Two, }
+fn foo(tuple: (A, A)) {
+ match $0tuple {};
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn adds_comma_before_new_arms() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+fn foo(t: bool) {
+ match $0t {
+ true => 1 + 2
+ }
+}"#,
+ r#"
+fn foo(t: bool) {
+ match t {
+ true => 1 + 2,
+ $0false => todo!(),
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn does_not_add_extra_comma() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+fn foo(t: bool) {
+ match $0t {
+ true => 1 + 2,
+ }
+}"#,
+ r#"
+fn foo(t: bool) {
+ match t {
+ true => 1 + 2,
+ $0false => todo!(),
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn does_not_remove_catch_all_with_non_empty_expr() {
+ cov_mark::check!(add_missing_match_arms_empty_expr);
+ check_assist(
+ add_missing_match_arms,
+ r#"
+fn foo(t: bool) {
+ match $0t {
+ _ => 1 + 2,
+ }
+}"#,
+ r#"
+fn foo(t: bool) {
+ match t {
+ _ => 1 + 2,
+ $0true => todo!(),
+ false => todo!(),
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn does_not_fill_hidden_variants() {
+ cov_mark::check!(added_wildcard_pattern);
+ check_assist(
+ add_missing_match_arms,
+ r#"
+//- /main.rs crate:main deps:e
+fn foo(t: ::e::E) {
+ match $0t {
+ }
+}
+//- /e.rs crate:e
+pub enum E { A, #[doc(hidden)] B, }
+"#,
+ r#"
+fn foo(t: ::e::E) {
+ match t {
+ $0e::E::A => todo!(),
+ _ => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn does_not_fill_hidden_variants_tuple() {
+ cov_mark::check!(added_wildcard_pattern);
+ check_assist(
+ add_missing_match_arms,
+ r#"
+//- /main.rs crate:main deps:e
+fn foo(t: (bool, ::e::E)) {
+ match $0t {
+ }
+}
+//- /e.rs crate:e
+pub enum E { A, #[doc(hidden)] B, }
+"#,
+ r#"
+fn foo(t: (bool, ::e::E)) {
+ match t {
+ $0(true, e::E::A) => todo!(),
+ (false, e::E::A) => todo!(),
+ _ => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn fills_wildcard_with_only_hidden_variants() {
+ cov_mark::check!(added_wildcard_pattern);
+ check_assist(
+ add_missing_match_arms,
+ r#"
+//- /main.rs crate:main deps:e
+fn foo(t: ::e::E) {
+ match $0t {
+ }
+}
+//- /e.rs crate:e
+pub enum E { #[doc(hidden)] A, }
+"#,
+ r#"
+fn foo(t: ::e::E) {
+ match t {
+ ${0:_} => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn does_not_fill_wildcard_when_hidden_variants_are_explicit() {
+ check_assist_not_applicable(
+ add_missing_match_arms,
+ r#"
+//- /main.rs crate:main deps:e
+fn foo(t: ::e::E) {
+ match $0t {
+ e::E::A => todo!(),
+ }
+}
+//- /e.rs crate:e
+pub enum E { #[doc(hidden)] A, }
+"#,
+ );
+ }
+
+ // FIXME: I don't think the assist should be applicable in this case
+ #[test]
+ fn does_not_fill_wildcard_with_wildcard() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+//- /main.rs crate:main deps:e
+fn foo(t: ::e::E) {
+ match $0t {
+ _ => todo!(),
+ }
+}
+//- /e.rs crate:e
+pub enum E { #[doc(hidden)] A, }
+"#,
+ r#"
+fn foo(t: ::e::E) {
+ match t {
+ _ => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn fills_wildcard_on_non_exhaustive_with_explicit_matches() {
+ cov_mark::check!(added_wildcard_pattern);
+ check_assist(
+ add_missing_match_arms,
+ r#"
+//- /main.rs crate:main deps:e
+fn foo(t: ::e::E) {
+ match $0t {
+ e::E::A => todo!(),
+ }
+}
+//- /e.rs crate:e
+#[non_exhaustive]
+pub enum E { A, }
+"#,
+ r#"
+fn foo(t: ::e::E) {
+ match t {
+ e::E::A => todo!(),
+ ${0:_} => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn fills_wildcard_on_non_exhaustive_without_matches() {
+ cov_mark::check!(added_wildcard_pattern);
+ check_assist(
+ add_missing_match_arms,
+ r#"
+//- /main.rs crate:main deps:e
+fn foo(t: ::e::E) {
+ match $0t {
+ }
+}
+//- /e.rs crate:e
+#[non_exhaustive]
+pub enum E { A, }
+"#,
+ r#"
+fn foo(t: ::e::E) {
+ match t {
+ $0e::E::A => todo!(),
+ _ => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn fills_wildcard_on_non_exhaustive_with_doc_hidden() {
+ cov_mark::check!(added_wildcard_pattern);
+ check_assist(
+ add_missing_match_arms,
+ r#"
+//- /main.rs crate:main deps:e
+fn foo(t: ::e::E) {
+ match $0t {
+ }
+}
+//- /e.rs crate:e
+#[non_exhaustive]
+pub enum E { A, #[doc(hidden)] B }"#,
+ r#"
+fn foo(t: ::e::E) {
+ match t {
+ $0e::E::A => todo!(),
+ _ => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn fills_wildcard_on_non_exhaustive_with_doc_hidden_with_explicit_arms() {
+ cov_mark::check!(added_wildcard_pattern);
+ check_assist(
+ add_missing_match_arms,
+ r#"
+//- /main.rs crate:main deps:e
+fn foo(t: ::e::E) {
+ match $0t {
+ e::E::A => todo!(),
+ }
+}
+//- /e.rs crate:e
+#[non_exhaustive]
+pub enum E { A, #[doc(hidden)] B }"#,
+ r#"
+fn foo(t: ::e::E) {
+ match t {
+ e::E::A => todo!(),
+ ${0:_} => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn fill_wildcard_with_partial_wildcard() {
+ cov_mark::check!(added_wildcard_pattern);
+ check_assist(
+ add_missing_match_arms,
+ r#"
+//- /main.rs crate:main deps:e
+fn foo(t: ::e::E, b: bool) {
+ match $0t {
+ _ if b => todo!(),
+ }
+}
+//- /e.rs crate:e
+pub enum E { #[doc(hidden)] A, }"#,
+ r#"
+fn foo(t: ::e::E, b: bool) {
+ match t {
+ _ if b => todo!(),
+ ${0:_} => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn does_not_fill_wildcard_with_partial_wildcard_and_wildcard() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+//- /main.rs crate:main deps:e
+fn foo(t: ::e::E, b: bool) {
+ match $0t {
+ _ if b => todo!(),
+ _ => todo!(),
+ }
+}
+//- /e.rs crate:e
+pub enum E { #[doc(hidden)] A, }"#,
+ r#"
+fn foo(t: ::e::E, b: bool) {
+ match t {
+ _ if b => todo!(),
+ _ => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn non_exhaustive_doc_hidden_tuple_fills_wildcard() {
+ cov_mark::check!(added_wildcard_pattern);
+ check_assist(
+ add_missing_match_arms,
+ r#"
+//- /main.rs crate:main deps:e
+fn foo(t: ::e::E) {
+ match $0t {
+ }
+}
+//- /e.rs crate:e
+#[non_exhaustive]
+pub enum E { A, #[doc(hidden)] B, }"#,
+ r#"
+fn foo(t: ::e::E) {
+ match t {
+ $0e::E::A => todo!(),
+ _ => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn ignores_doc_hidden_for_crate_local_enums() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum E { A, #[doc(hidden)] B, }
+
+fn foo(t: E) {
+ match $0t {
+ }
+}"#,
+ r#"
+enum E { A, #[doc(hidden)] B, }
+
+fn foo(t: E) {
+ match t {
+ $0E::A => todo!(),
+ E::B => todo!(),
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn ignores_non_exhaustive_for_crate_local_enums() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+#[non_exhaustive]
+enum E { A, B, }
+
+fn foo(t: E) {
+ match $0t {
+ }
+}"#,
+ r#"
+#[non_exhaustive]
+enum E { A, B, }
+
+fn foo(t: E) {
+ match t {
+ $0E::A => todo!(),
+ E::B => todo!(),
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn ignores_doc_hidden_and_non_exhaustive_for_crate_local_enums() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+#[non_exhaustive]
+enum E { A, #[doc(hidden)] B, }
+
+fn foo(t: E) {
+ match $0t {
+ }
+}"#,
+ r#"
+#[non_exhaustive]
+enum E { A, #[doc(hidden)] B, }
+
+fn foo(t: E) {
+ match t {
+ $0E::A => todo!(),
+ E::B => todo!(),
+ }
+}"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_return_type.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_return_type.rs
new file mode 100644
index 000000000..f858d7a15
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_return_type.rs
@@ -0,0 +1,447 @@
+use hir::HirDisplay;
+use syntax::{ast, match_ast, AstNode, SyntaxKind, SyntaxToken, TextRange, TextSize};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: add_return_type
+//
+// Adds the return type to a function or closure inferred from its tail expression if it doesn't have a return
+// type specified. This assists is useable in a functions or closures tail expression or return type position.
+//
+// ```
+// fn foo() { 4$02i32 }
+// ```
+// ->
+// ```
+// fn foo() -> i32 { 42i32 }
+// ```
+pub(crate) fn add_return_type(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let (fn_type, tail_expr, builder_edit_pos) = extract_tail(ctx)?;
+ let module = ctx.sema.scope(tail_expr.syntax())?.module();
+ let ty = ctx.sema.type_of_expr(&peel_blocks(tail_expr.clone()))?.original();
+ if ty.is_unit() {
+ return None;
+ }
+ let ty = ty.display_source_code(ctx.db(), module.into()).ok()?;
+
+ acc.add(
+ AssistId("add_return_type", AssistKind::RefactorRewrite),
+ match fn_type {
+ FnType::Function => "Add this function's return type",
+ FnType::Closure { .. } => "Add this closure's return type",
+ },
+ tail_expr.syntax().text_range(),
+ |builder| {
+ match builder_edit_pos {
+ InsertOrReplace::Insert(insert_pos, needs_whitespace) => {
+ let preceeding_whitespace = if needs_whitespace { " " } else { "" };
+ builder.insert(insert_pos, &format!("{}-> {} ", preceeding_whitespace, ty))
+ }
+ InsertOrReplace::Replace(text_range) => {
+ builder.replace(text_range, &format!("-> {}", ty))
+ }
+ }
+ if let FnType::Closure { wrap_expr: true } = fn_type {
+ cov_mark::hit!(wrap_closure_non_block_expr);
+ // `|x| x` becomes `|x| -> T x` which is invalid, so wrap it in a block
+ builder.replace(tail_expr.syntax().text_range(), &format!("{{{}}}", tail_expr));
+ }
+ },
+ )
+}
+
+enum InsertOrReplace {
+ Insert(TextSize, bool),
+ Replace(TextRange),
+}
+
+/// Check the potentially already specified return type and reject it or turn it into a builder command
+/// if allowed.
+fn ret_ty_to_action(
+ ret_ty: Option<ast::RetType>,
+ insert_after: SyntaxToken,
+) -> Option<InsertOrReplace> {
+ match ret_ty {
+ Some(ret_ty) => match ret_ty.ty() {
+ Some(ast::Type::InferType(_)) | None => {
+ cov_mark::hit!(existing_infer_ret_type);
+ cov_mark::hit!(existing_infer_ret_type_closure);
+ Some(InsertOrReplace::Replace(ret_ty.syntax().text_range()))
+ }
+ _ => {
+ cov_mark::hit!(existing_ret_type);
+ cov_mark::hit!(existing_ret_type_closure);
+ None
+ }
+ },
+ None => {
+ let insert_after_pos = insert_after.text_range().end();
+ let (insert_pos, needs_whitespace) = match insert_after.next_token() {
+ Some(it) if it.kind() == SyntaxKind::WHITESPACE => {
+ (insert_after_pos + TextSize::from(1), false)
+ }
+ _ => (insert_after_pos, true),
+ };
+
+ Some(InsertOrReplace::Insert(insert_pos, needs_whitespace))
+ }
+ }
+}
+
+enum FnType {
+ Function,
+ Closure { wrap_expr: bool },
+}
+
+/// If we're looking at a block that is supposed to return `()`, type inference
+/// will just tell us it has type `()`. We have to look at the tail expression
+/// to see the mismatched actual type. This 'unpeels' the various blocks to
+/// hopefully let us see the type the user intends. (This still doesn't handle
+/// all situations fully correctly; the 'ideal' way to handle this would be to
+/// run type inference on the function again, but with a variable as the return
+/// type.)
+fn peel_blocks(mut expr: ast::Expr) -> ast::Expr {
+ loop {
+ match_ast! {
+ match (expr.syntax()) {
+ ast::BlockExpr(it) => {
+ if let Some(tail) = it.tail_expr() {
+ expr = tail.clone();
+ } else {
+ break;
+ }
+ },
+ ast::IfExpr(it) => {
+ if let Some(then_branch) = it.then_branch() {
+ expr = ast::Expr::BlockExpr(then_branch.clone());
+ } else {
+ break;
+ }
+ },
+ ast::MatchExpr(it) => {
+ if let Some(arm_expr) = it.match_arm_list().and_then(|l| l.arms().next()).and_then(|a| a.expr()) {
+ expr = arm_expr;
+ } else {
+ break;
+ }
+ },
+ _ => break,
+ }
+ }
+ }
+ expr
+}
+
+fn extract_tail(ctx: &AssistContext<'_>) -> Option<(FnType, ast::Expr, InsertOrReplace)> {
+ let (fn_type, tail_expr, return_type_range, action) =
+ if let Some(closure) = ctx.find_node_at_offset::<ast::ClosureExpr>() {
+ let rpipe = closure.param_list()?.syntax().last_token()?;
+ let rpipe_pos = rpipe.text_range().end();
+
+ let action = ret_ty_to_action(closure.ret_type(), rpipe)?;
+
+ let body = closure.body()?;
+ let body_start = body.syntax().first_token()?.text_range().start();
+ let (tail_expr, wrap_expr) = match body {
+ ast::Expr::BlockExpr(block) => (block.tail_expr()?, false),
+ body => (body, true),
+ };
+
+ let ret_range = TextRange::new(rpipe_pos, body_start);
+ (FnType::Closure { wrap_expr }, tail_expr, ret_range, action)
+ } else {
+ let func = ctx.find_node_at_offset::<ast::Fn>()?;
+
+ let rparen = func.param_list()?.r_paren_token()?;
+ let rparen_pos = rparen.text_range().end();
+ let action = ret_ty_to_action(func.ret_type(), rparen)?;
+
+ let body = func.body()?;
+ let stmt_list = body.stmt_list()?;
+ let tail_expr = stmt_list.tail_expr()?;
+
+ let ret_range_end = stmt_list.l_curly_token()?.text_range().start();
+ let ret_range = TextRange::new(rparen_pos, ret_range_end);
+ (FnType::Function, tail_expr, ret_range, action)
+ };
+ let range = ctx.selection_trimmed();
+ if return_type_range.contains_range(range) {
+ cov_mark::hit!(cursor_in_ret_position);
+ cov_mark::hit!(cursor_in_ret_position_closure);
+ } else if tail_expr.syntax().text_range().contains_range(range) {
+ cov_mark::hit!(cursor_on_tail);
+ cov_mark::hit!(cursor_on_tail_closure);
+ } else {
+ return None;
+ }
+ Some((fn_type, tail_expr, action))
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn infer_return_type_specified_inferred() {
+ cov_mark::check!(existing_infer_ret_type);
+ check_assist(
+ add_return_type,
+ r#"fn foo() -> $0_ {
+ 45
+}"#,
+ r#"fn foo() -> i32 {
+ 45
+}"#,
+ );
+ }
+
+ #[test]
+ fn infer_return_type_specified_inferred_closure() {
+ cov_mark::check!(existing_infer_ret_type_closure);
+ check_assist(
+ add_return_type,
+ r#"fn foo() {
+ || -> _ {$045};
+}"#,
+ r#"fn foo() {
+ || -> i32 {45};
+}"#,
+ );
+ }
+
+ #[test]
+ fn infer_return_type_cursor_at_return_type_pos() {
+ cov_mark::check!(cursor_in_ret_position);
+ check_assist(
+ add_return_type,
+ r#"fn foo() $0{
+ 45
+}"#,
+ r#"fn foo() -> i32 {
+ 45
+}"#,
+ );
+ }
+
+ #[test]
+ fn infer_return_type_cursor_at_return_type_pos_closure() {
+ cov_mark::check!(cursor_in_ret_position_closure);
+ check_assist(
+ add_return_type,
+ r#"fn foo() {
+ || $045
+}"#,
+ r#"fn foo() {
+ || -> i32 {45}
+}"#,
+ );
+ }
+
+ #[test]
+ fn infer_return_type() {
+ cov_mark::check!(cursor_on_tail);
+ check_assist(
+ add_return_type,
+ r#"fn foo() {
+ 45$0
+}"#,
+ r#"fn foo() -> i32 {
+ 45
+}"#,
+ );
+ }
+
+ #[test]
+ fn infer_return_type_no_whitespace() {
+ check_assist(
+ add_return_type,
+ r#"fn foo(){
+ 45$0
+}"#,
+ r#"fn foo() -> i32 {
+ 45
+}"#,
+ );
+ }
+
+ #[test]
+ fn infer_return_type_nested() {
+ check_assist(
+ add_return_type,
+ r#"fn foo() {
+ if true {
+ 3$0
+ } else {
+ 5
+ }
+}"#,
+ r#"fn foo() -> i32 {
+ if true {
+ 3
+ } else {
+ 5
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn infer_return_type_nested_match() {
+ check_assist(
+ add_return_type,
+ r#"fn foo() {
+ match true {
+ true => { 3$0 },
+ false => { 5 },
+ }
+}"#,
+ r#"fn foo() -> i32 {
+ match true {
+ true => { 3 },
+ false => { 5 },
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_ret_type_specified() {
+ cov_mark::check!(existing_ret_type);
+ check_assist_not_applicable(
+ add_return_type,
+ r#"fn foo() -> i32 {
+ ( 45$0 + 32 ) * 123
+}"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_non_tail_expr() {
+ check_assist_not_applicable(
+ add_return_type,
+ r#"fn foo() {
+ let x = $03;
+ ( 45 + 32 ) * 123
+}"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_unit_return_type() {
+ check_assist_not_applicable(
+ add_return_type,
+ r#"fn foo() {
+ ($0)
+}"#,
+ );
+ }
+
+ #[test]
+ fn infer_return_type_closure_block() {
+ cov_mark::check!(cursor_on_tail_closure);
+ check_assist(
+ add_return_type,
+ r#"fn foo() {
+ |x: i32| {
+ x$0
+ };
+}"#,
+ r#"fn foo() {
+ |x: i32| -> i32 {
+ x
+ };
+}"#,
+ );
+ }
+
+ #[test]
+ fn infer_return_type_closure() {
+ check_assist(
+ add_return_type,
+ r#"fn foo() {
+ |x: i32| { x$0 };
+}"#,
+ r#"fn foo() {
+ |x: i32| -> i32 { x };
+}"#,
+ );
+ }
+
+ #[test]
+ fn infer_return_type_closure_no_whitespace() {
+ check_assist(
+ add_return_type,
+ r#"fn foo() {
+ |x: i32|{ x$0 };
+}"#,
+ r#"fn foo() {
+ |x: i32| -> i32 { x };
+}"#,
+ );
+ }
+
+ #[test]
+ fn infer_return_type_closure_wrap() {
+ cov_mark::check!(wrap_closure_non_block_expr);
+ check_assist(
+ add_return_type,
+ r#"fn foo() {
+ |x: i32| x$0;
+}"#,
+ r#"fn foo() {
+ |x: i32| -> i32 {x};
+}"#,
+ );
+ }
+
+ #[test]
+ fn infer_return_type_nested_closure() {
+ check_assist(
+ add_return_type,
+ r#"fn foo() {
+ || {
+ if true {
+ 3$0
+ } else {
+ 5
+ }
+ }
+}"#,
+ r#"fn foo() {
+ || -> i32 {
+ if true {
+ 3
+ } else {
+ 5
+ }
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_ret_type_specified_closure() {
+ cov_mark::check!(existing_ret_type_closure);
+ check_assist_not_applicable(
+ add_return_type,
+ r#"fn foo() {
+ || -> i32 { 3$0 }
+}"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_non_tail_expr_closure() {
+ check_assist_not_applicable(
+ add_return_type,
+ r#"fn foo() {
+ || -> i32 {
+ let x = 3$0;
+ 6
+ }
+}"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_turbo_fish.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_turbo_fish.rs
new file mode 100644
index 000000000..c0bf238db
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_turbo_fish.rs
@@ -0,0 +1,400 @@
+use ide_db::defs::{Definition, NameRefClass};
+use itertools::Itertools;
+use syntax::{ast, AstNode, SyntaxKind, T};
+
+use crate::{
+ assist_context::{AssistContext, Assists},
+ AssistId, AssistKind,
+};
+
+// Assist: add_turbo_fish
+//
+// Adds `::<_>` to a call of a generic method or function.
+//
+// ```
+// fn make<T>() -> T { todo!() }
+// fn main() {
+// let x = make$0();
+// }
+// ```
+// ->
+// ```
+// fn make<T>() -> T { todo!() }
+// fn main() {
+// let x = make::<${0:_}>();
+// }
+// ```
+pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let ident = ctx.find_token_syntax_at_offset(SyntaxKind::IDENT).or_else(|| {
+ let arg_list = ctx.find_node_at_offset::<ast::ArgList>()?;
+ if arg_list.args().next().is_some() {
+ return None;
+ }
+ cov_mark::hit!(add_turbo_fish_after_call);
+ cov_mark::hit!(add_type_ascription_after_call);
+ arg_list.l_paren_token()?.prev_token().filter(|it| it.kind() == SyntaxKind::IDENT)
+ })?;
+ let next_token = ident.next_token()?;
+ if next_token.kind() == T![::] {
+ cov_mark::hit!(add_turbo_fish_one_fish_is_enough);
+ return None;
+ }
+ let name_ref = ast::NameRef::cast(ident.parent()?)?;
+ let def = match NameRefClass::classify(&ctx.sema, &name_ref)? {
+ NameRefClass::Definition(def) => def,
+ NameRefClass::FieldShorthand { .. } => return None,
+ };
+ let fun = match def {
+ Definition::Function(it) => it,
+ _ => return None,
+ };
+ let generics = hir::GenericDef::Function(fun).params(ctx.sema.db);
+ if generics.is_empty() {
+ cov_mark::hit!(add_turbo_fish_non_generic);
+ return None;
+ }
+
+ if let Some(let_stmt) = ctx.find_node_at_offset::<ast::LetStmt>() {
+ if let_stmt.colon_token().is_none() {
+ let type_pos = let_stmt.pat()?.syntax().last_token()?.text_range().end();
+ let semi_pos = let_stmt.syntax().last_token()?.text_range().end();
+
+ acc.add(
+ AssistId("add_type_ascription", AssistKind::RefactorRewrite),
+ "Add `: _` before assignment operator",
+ ident.text_range(),
+ |builder| {
+ if let_stmt.semicolon_token().is_none() {
+ builder.insert(semi_pos, ";");
+ }
+ match ctx.config.snippet_cap {
+ Some(cap) => builder.insert_snippet(cap, type_pos, ": ${0:_}"),
+ None => builder.insert(type_pos, ": _"),
+ }
+ },
+ )?
+ } else {
+ cov_mark::hit!(add_type_ascription_already_typed);
+ }
+ }
+
+ let number_of_arguments = generics
+ .iter()
+ .filter(|param| {
+ matches!(param, hir::GenericParam::TypeParam(_) | hir::GenericParam::ConstParam(_))
+ })
+ .count();
+
+ acc.add(
+ AssistId("add_turbo_fish", AssistKind::RefactorRewrite),
+ "Add `::<>`",
+ ident.text_range(),
+ |builder| {
+ builder.trigger_signature_help();
+ match ctx.config.snippet_cap {
+ Some(cap) => {
+ let snip = format!("::<{}>", get_snippet_fish_head(number_of_arguments));
+ builder.insert_snippet(cap, ident.text_range().end(), snip)
+ }
+ None => {
+ let fish_head = std::iter::repeat("_").take(number_of_arguments).format(", ");
+ let snip = format!("::<{}>", fish_head);
+ builder.insert(ident.text_range().end(), snip);
+ }
+ }
+ },
+ )
+}
+
+/// This will create a snippet string with tabstops marked
+fn get_snippet_fish_head(number_of_arguments: usize) -> String {
+ let mut fish_head = (1..number_of_arguments)
+ .format_with("", |i, f| f(&format_args!("${{{}:_}}, ", i)))
+ .to_string();
+
+ // tabstop 0 is a special case and always the last one
+ fish_head.push_str("${0:_}");
+ fish_head
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_by_label, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn add_turbo_fish_function() {
+ check_assist(
+ add_turbo_fish,
+ r#"
+fn make<T>() -> T {}
+fn main() {
+ make$0();
+}
+"#,
+ r#"
+fn make<T>() -> T {}
+fn main() {
+ make::<${0:_}>();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_turbo_fish_function_multiple_generic_types() {
+ check_assist(
+ add_turbo_fish,
+ r#"
+fn make<T, A>() -> T {}
+fn main() {
+ make$0();
+}
+"#,
+ r#"
+fn make<T, A>() -> T {}
+fn main() {
+ make::<${1:_}, ${0:_}>();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_turbo_fish_function_many_generic_types() {
+ check_assist(
+ add_turbo_fish,
+ r#"
+fn make<T, A, B, C, D, E, F>() -> T {}
+fn main() {
+ make$0();
+}
+"#,
+ r#"
+fn make<T, A, B, C, D, E, F>() -> T {}
+fn main() {
+ make::<${1:_}, ${2:_}, ${3:_}, ${4:_}, ${5:_}, ${6:_}, ${0:_}>();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_turbo_fish_after_call() {
+ cov_mark::check!(add_turbo_fish_after_call);
+ check_assist(
+ add_turbo_fish,
+ r#"
+fn make<T>() -> T {}
+fn main() {
+ make()$0;
+}
+"#,
+ r#"
+fn make<T>() -> T {}
+fn main() {
+ make::<${0:_}>();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_turbo_fish_method() {
+ check_assist(
+ add_turbo_fish,
+ r#"
+struct S;
+impl S {
+ fn make<T>(&self) -> T {}
+}
+fn main() {
+ S.make$0();
+}
+"#,
+ r#"
+struct S;
+impl S {
+ fn make<T>(&self) -> T {}
+}
+fn main() {
+ S.make::<${0:_}>();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_turbo_fish_one_fish_is_enough() {
+ cov_mark::check!(add_turbo_fish_one_fish_is_enough);
+ check_assist_not_applicable(
+ add_turbo_fish,
+ r#"
+fn make<T>() -> T {}
+fn main() {
+ make$0::<()>();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_turbo_fish_non_generic() {
+ cov_mark::check!(add_turbo_fish_non_generic);
+ check_assist_not_applicable(
+ add_turbo_fish,
+ r#"
+fn make() -> () {}
+fn main() {
+ make$0();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_type_ascription_function() {
+ check_assist_by_label(
+ add_turbo_fish,
+ r#"
+fn make<T>() -> T {}
+fn main() {
+ let x = make$0();
+}
+"#,
+ r#"
+fn make<T>() -> T {}
+fn main() {
+ let x: ${0:_} = make();
+}
+"#,
+ "Add `: _` before assignment operator",
+ );
+ }
+
+ #[test]
+ fn add_type_ascription_after_call() {
+ cov_mark::check!(add_type_ascription_after_call);
+ check_assist_by_label(
+ add_turbo_fish,
+ r#"
+fn make<T>() -> T {}
+fn main() {
+ let x = make()$0;
+}
+"#,
+ r#"
+fn make<T>() -> T {}
+fn main() {
+ let x: ${0:_} = make();
+}
+"#,
+ "Add `: _` before assignment operator",
+ );
+ }
+
+ #[test]
+ fn add_type_ascription_method() {
+ check_assist_by_label(
+ add_turbo_fish,
+ r#"
+struct S;
+impl S {
+ fn make<T>(&self) -> T {}
+}
+fn main() {
+ let x = S.make$0();
+}
+"#,
+ r#"
+struct S;
+impl S {
+ fn make<T>(&self) -> T {}
+}
+fn main() {
+ let x: ${0:_} = S.make();
+}
+"#,
+ "Add `: _` before assignment operator",
+ );
+ }
+
+ #[test]
+ fn add_type_ascription_already_typed() {
+ cov_mark::check!(add_type_ascription_already_typed);
+ check_assist(
+ add_turbo_fish,
+ r#"
+fn make<T>() -> T {}
+fn main() {
+ let x: () = make$0();
+}
+"#,
+ r#"
+fn make<T>() -> T {}
+fn main() {
+ let x: () = make::<${0:_}>();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_type_ascription_append_semicolon() {
+ check_assist_by_label(
+ add_turbo_fish,
+ r#"
+fn make<T>() -> T {}
+fn main() {
+ let x = make$0()
+}
+"#,
+ r#"
+fn make<T>() -> T {}
+fn main() {
+ let x: ${0:_} = make();
+}
+"#,
+ "Add `: _` before assignment operator",
+ );
+ }
+
+ #[test]
+ fn add_turbo_fish_function_lifetime_parameter() {
+ check_assist(
+ add_turbo_fish,
+ r#"
+fn make<'a, T, A>(t: T, a: A) {}
+fn main() {
+ make$0(5, 2);
+}
+"#,
+ r#"
+fn make<'a, T, A>(t: T, a: A) {}
+fn main() {
+ make::<${1:_}, ${0:_}>(5, 2);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_turbo_fish_function_const_parameter() {
+ check_assist(
+ add_turbo_fish,
+ r#"
+fn make<T, const N: usize>(t: T) {}
+fn main() {
+ make$0(3);
+}
+"#,
+ r#"
+fn make<T, const N: usize>(t: T) {}
+fn main() {
+ make::<${1:_}, ${0:_}>(3);
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs
new file mode 100644
index 000000000..2853d1d1b
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs
@@ -0,0 +1,234 @@
+use std::collections::VecDeque;
+
+use syntax::ast::{self, AstNode};
+
+use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: apply_demorgan
+//
+// Apply https://en.wikipedia.org/wiki/De_Morgan%27s_laws[De Morgan's law].
+// This transforms expressions of the form `!l || !r` into `!(l && r)`.
+// This also works with `&&`. This assist can only be applied with the cursor
+// on either `||` or `&&`.
+//
+// ```
+// fn main() {
+// if x != 4 ||$0 y < 3.14 {}
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// if !(x == 4 && y >= 3.14) {}
+// }
+// ```
+pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
+ let op = expr.op_kind()?;
+ let op_range = expr.op_token()?.text_range();
+
+ let opposite_op = match op {
+ ast::BinaryOp::LogicOp(ast::LogicOp::And) => "||",
+ ast::BinaryOp::LogicOp(ast::LogicOp::Or) => "&&",
+ _ => return None,
+ };
+
+ let cursor_in_range = op_range.contains_range(ctx.selection_trimmed());
+ if !cursor_in_range {
+ return None;
+ }
+
+ let mut expr = expr;
+
+ // Walk up the tree while we have the same binary operator
+ while let Some(parent_expr) = expr.syntax().parent().and_then(ast::BinExpr::cast) {
+ match expr.op_kind() {
+ Some(parent_op) if parent_op == op => {
+ expr = parent_expr;
+ }
+ _ => break,
+ }
+ }
+
+ let mut expr_stack = vec![expr.clone()];
+ let mut terms = Vec::new();
+ let mut op_ranges = Vec::new();
+
+ // Find all the children with the same binary operator
+ while let Some(expr) = expr_stack.pop() {
+ let mut traverse_bin_expr_arm = |expr| {
+ if let ast::Expr::BinExpr(bin_expr) = expr {
+ if let Some(expr_op) = bin_expr.op_kind() {
+ if expr_op == op {
+ expr_stack.push(bin_expr);
+ } else {
+ terms.push(ast::Expr::BinExpr(bin_expr));
+ }
+ } else {
+ terms.push(ast::Expr::BinExpr(bin_expr));
+ }
+ } else {
+ terms.push(expr);
+ }
+ };
+
+ op_ranges.extend(expr.op_token().map(|t| t.text_range()));
+ traverse_bin_expr_arm(expr.lhs()?);
+ traverse_bin_expr_arm(expr.rhs()?);
+ }
+
+ acc.add(
+ AssistId("apply_demorgan", AssistKind::RefactorRewrite),
+ "Apply De Morgan's law",
+ op_range,
+ |edit| {
+ terms.sort_by_key(|t| t.syntax().text_range().start());
+ let mut terms = VecDeque::from(terms);
+
+ let paren_expr = expr.syntax().parent().and_then(ast::ParenExpr::cast);
+
+ let neg_expr = paren_expr
+ .clone()
+ .and_then(|paren_expr| paren_expr.syntax().parent())
+ .and_then(ast::PrefixExpr::cast)
+ .and_then(|prefix_expr| {
+ if prefix_expr.op_kind().unwrap() == ast::UnaryOp::Not {
+ Some(prefix_expr)
+ } else {
+ None
+ }
+ });
+
+ for op_range in op_ranges {
+ edit.replace(op_range, opposite_op);
+ }
+
+ if let Some(paren_expr) = paren_expr {
+ for term in terms {
+ let range = term.syntax().text_range();
+ let not_term = invert_boolean_expression(term);
+
+ edit.replace(range, not_term.syntax().text());
+ }
+
+ if let Some(neg_expr) = neg_expr {
+ cov_mark::hit!(demorgan_double_negation);
+ edit.replace(neg_expr.op_token().unwrap().text_range(), "");
+ } else {
+ cov_mark::hit!(demorgan_double_parens);
+ edit.replace(paren_expr.l_paren_token().unwrap().text_range(), "!(");
+ }
+ } else {
+ if let Some(lhs) = terms.pop_front() {
+ let lhs_range = lhs.syntax().text_range();
+ let not_lhs = invert_boolean_expression(lhs);
+
+ edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text()));
+ }
+
+ if let Some(rhs) = terms.pop_back() {
+ let rhs_range = rhs.syntax().text_range();
+ let not_rhs = invert_boolean_expression(rhs);
+
+ edit.replace(rhs_range, format!("{})", not_rhs.syntax().text()));
+ }
+
+ for term in terms {
+ let term_range = term.syntax().text_range();
+ let not_term = invert_boolean_expression(term);
+ edit.replace(term_range, not_term.syntax().text());
+ }
+ }
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn demorgan_handles_leq() {
+ check_assist(
+ apply_demorgan,
+ r#"
+struct S;
+fn f() { S < S &&$0 S <= S }
+"#,
+ r#"
+struct S;
+fn f() { !(S >= S || S > S) }
+"#,
+ );
+ }
+
+ #[test]
+ fn demorgan_handles_geq() {
+ check_assist(
+ apply_demorgan,
+ r#"
+struct S;
+fn f() { S > S &&$0 S >= S }
+"#,
+ r#"
+struct S;
+fn f() { !(S <= S || S < S) }
+"#,
+ );
+ }
+
+ #[test]
+ fn demorgan_turns_and_into_or() {
+ check_assist(apply_demorgan, "fn f() { !x &&$0 !x }", "fn f() { !(x || x) }")
+ }
+
+ #[test]
+ fn demorgan_turns_or_into_and() {
+ check_assist(apply_demorgan, "fn f() { !x ||$0 !x }", "fn f() { !(x && x) }")
+ }
+
+ #[test]
+ fn demorgan_removes_inequality() {
+ check_assist(apply_demorgan, "fn f() { x != x ||$0 !x }", "fn f() { !(x == x && x) }")
+ }
+
+ #[test]
+ fn demorgan_general_case() {
+ check_assist(apply_demorgan, "fn f() { x ||$0 x }", "fn f() { !(!x && !x) }")
+ }
+
+ #[test]
+ fn demorgan_multiple_terms() {
+ check_assist(apply_demorgan, "fn f() { x ||$0 y || z }", "fn f() { !(!x && !y && !z) }");
+ check_assist(apply_demorgan, "fn f() { x || y ||$0 z }", "fn f() { !(!x && !y && !z) }");
+ }
+
+ #[test]
+ fn demorgan_doesnt_apply_with_cursor_not_on_op() {
+ check_assist_not_applicable(apply_demorgan, "fn f() { $0 !x || !x }")
+ }
+
+ #[test]
+ fn demorgan_doesnt_double_negation() {
+ cov_mark::check!(demorgan_double_negation);
+ check_assist(apply_demorgan, "fn f() { !(x ||$0 x) }", "fn f() { (!x && !x) }")
+ }
+
+ #[test]
+ fn demorgan_doesnt_double_parens() {
+ cov_mark::check!(demorgan_double_parens);
+ check_assist(apply_demorgan, "fn f() { (x ||$0 x) }", "fn f() { !(!x && !x) }")
+ }
+
+ // https://github.com/rust-lang/rust-analyzer/issues/10963
+ #[test]
+ fn demorgan_doesnt_hang() {
+ check_assist(
+ apply_demorgan,
+ "fn f() { 1 || 3 &&$0 4 || 5 }",
+ "fn f() { !(!1 || !3 || !4) || 5 }",
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/auto_import.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/auto_import.rs
new file mode 100644
index 000000000..949cf3167
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/auto_import.rs
@@ -0,0 +1,1292 @@
+use std::cmp::Reverse;
+
+use hir::{db::HirDatabase, Module};
+use ide_db::{
+ helpers::mod_path_to_ast,
+ imports::{
+ import_assets::{ImportAssets, ImportCandidate, LocatedImport},
+ insert_use::{insert_use, ImportScope},
+ },
+};
+use syntax::{ast, AstNode, NodeOrToken, SyntaxElement};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
+
+// Feature: Auto Import
+//
+// Using the `auto-import` assist it is possible to insert missing imports for unresolved items.
+// When inserting an import it will do so in a structured manner by keeping imports grouped,
+// separated by a newline in the following order:
+//
+// - `std` and `core`
+// - External Crates
+// - Current Crate, paths prefixed by `crate`
+// - Current Module, paths prefixed by `self`
+// - Super Module, paths prefixed by `super`
+//
+// Example:
+// ```rust
+// use std::fs::File;
+//
+// use itertools::Itertools;
+// use syntax::ast;
+//
+// use crate::utils::insert_use;
+//
+// use self::auto_import;
+//
+// use super::AssistContext;
+// ```
+//
+// .Import Granularity
+//
+// It is possible to configure how use-trees are merged with the `imports.granularity.group` setting.
+// It has the following configurations:
+//
+// - `crate`: Merge imports from the same crate into a single use statement. This kind of
+// nesting is only supported in Rust versions later than 1.24.
+// - `module`: Merge imports from the same module into a single use statement.
+// - `item`: Don't merge imports at all, creating one import per item.
+// - `preserve`: Do not change the granularity of any imports. For auto-import this has the same
+// effect as `item`.
+//
+// In `VS Code` the configuration for this is `rust-analyzer.imports.granularity.group`.
+//
+// .Import Prefix
+//
+// The style of imports in the same crate is configurable through the `imports.prefix` setting.
+// It has the following configurations:
+//
+// - `crate`: This setting will force paths to be always absolute, starting with the `crate`
+// prefix, unless the item is defined outside of the current crate.
+// - `self`: This setting will force paths that are relative to the current module to always
+// start with `self`. This will result in paths that always start with either `crate`, `self`,
+// `super` or an extern crate identifier.
+// - `plain`: This setting does not impose any restrictions in imports.
+//
+// In `VS Code` the configuration for this is `rust-analyzer.imports.prefix`.
+//
+// image::https://user-images.githubusercontent.com/48062697/113020673-b85be580-917a-11eb-9022-59585f35d4f8.gif[]
+
+// Assist: auto_import
+//
+// If the name is unresolved, provides all possible imports for it.
+//
+// ```
+// fn main() {
+// let map = HashMap$0::new();
+// }
+// # pub mod std { pub mod collections { pub struct HashMap { } } }
+// ```
+// ->
+// ```
+// use std::collections::HashMap;
+//
+// fn main() {
+// let map = HashMap::new();
+// }
+// # pub mod std { pub mod collections { pub struct HashMap { } } }
+// ```
+pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let (import_assets, syntax_under_caret) = find_importable_node(ctx)?;
+ let mut proposed_imports =
+ import_assets.search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind);
+ if proposed_imports.is_empty() {
+ return None;
+ }
+
+ let range = match &syntax_under_caret {
+ NodeOrToken::Node(node) => ctx.sema.original_range(node).range,
+ NodeOrToken::Token(token) => token.text_range(),
+ };
+ let group_label = group_label(import_assets.import_candidate());
+ let scope = ImportScope::find_insert_use_container(
+ &match syntax_under_caret {
+ NodeOrToken::Node(it) => it,
+ NodeOrToken::Token(it) => it.parent()?,
+ },
+ &ctx.sema,
+ )?;
+
+ // we aren't interested in different namespaces
+ proposed_imports.dedup_by(|a, b| a.import_path == b.import_path);
+
+ let current_node = match ctx.covering_element() {
+ NodeOrToken::Node(node) => Some(node),
+ NodeOrToken::Token(token) => token.parent(),
+ };
+
+ let current_module =
+ current_node.as_ref().and_then(|node| ctx.sema.scope(node)).map(|scope| scope.module());
+
+ // prioritize more relevant imports
+ proposed_imports
+ .sort_by_key(|import| Reverse(relevance_score(ctx, import, current_module.as_ref())));
+
+ for import in proposed_imports {
+ acc.add_group(
+ &group_label,
+ AssistId("auto_import", AssistKind::QuickFix),
+ format!("Import `{}`", import.import_path),
+ range,
+ |builder| {
+ let scope = match scope.clone() {
+ ImportScope::File(it) => ImportScope::File(builder.make_mut(it)),
+ ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
+ ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
+ };
+ insert_use(&scope, mod_path_to_ast(&import.import_path), &ctx.config.insert_use);
+ },
+ );
+ }
+ Some(())
+}
+
+pub(super) fn find_importable_node(
+ ctx: &AssistContext<'_>,
+) -> Option<(ImportAssets, SyntaxElement)> {
+ if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
+ ImportAssets::for_exact_path(&path_under_caret, &ctx.sema)
+ .zip(Some(path_under_caret.syntax().clone().into()))
+ } else if let Some(method_under_caret) =
+ ctx.find_node_at_offset_with_descend::<ast::MethodCallExpr>()
+ {
+ ImportAssets::for_method_call(&method_under_caret, &ctx.sema)
+ .zip(Some(method_under_caret.syntax().clone().into()))
+ } else if let Some(pat) = ctx
+ .find_node_at_offset_with_descend::<ast::IdentPat>()
+ .filter(ast::IdentPat::is_simple_ident)
+ {
+ ImportAssets::for_ident_pat(&ctx.sema, &pat).zip(Some(pat.syntax().clone().into()))
+ } else {
+ None
+ }
+}
+
+fn group_label(import_candidate: &ImportCandidate) -> GroupLabel {
+ let name = match import_candidate {
+ ImportCandidate::Path(candidate) => format!("Import {}", candidate.name.text()),
+ ImportCandidate::TraitAssocItem(candidate) => {
+ format!("Import a trait for item {}", candidate.assoc_item_name.text())
+ }
+ ImportCandidate::TraitMethod(candidate) => {
+ format!("Import a trait for method {}", candidate.assoc_item_name.text())
+ }
+ };
+ GroupLabel(name)
+}
+
+/// Determine how relevant a given import is in the current context. Higher scores are more
+/// relevant.
+fn relevance_score(
+ ctx: &AssistContext<'_>,
+ import: &LocatedImport,
+ current_module: Option<&Module>,
+) -> i32 {
+ let mut score = 0;
+
+ let db = ctx.db();
+
+ let item_module = match import.item_to_import {
+ hir::ItemInNs::Types(item) | hir::ItemInNs::Values(item) => item.module(db),
+ hir::ItemInNs::Macros(makro) => Some(makro.module(db)),
+ };
+
+ match item_module.zip(current_module) {
+ // get the distance between the imported path and the current module
+ // (prefer items that are more local)
+ Some((item_module, current_module)) => {
+ score -= module_distance_hueristic(db, &current_module, &item_module) as i32;
+ }
+
+ // could not find relevant modules, so just use the length of the path as an estimate
+ None => return -(2 * import.import_path.len() as i32),
+ }
+
+ score
+}
+
+/// A heuristic that gives a higher score to modules that are more separated.
+fn module_distance_hueristic(db: &dyn HirDatabase, current: &Module, item: &Module) -> usize {
+ // get the path starting from the item to the respective crate roots
+ let mut current_path = current.path_to_root(db);
+ let mut item_path = item.path_to_root(db);
+
+ // we want paths going from the root to the item
+ current_path.reverse();
+ item_path.reverse();
+
+ // length of the common prefix of the two paths
+ let prefix_length = current_path.iter().zip(&item_path).take_while(|(a, b)| a == b).count();
+
+ // how many modules differ between the two paths (all modules, removing any duplicates)
+ let distinct_length = current_path.len() + item_path.len() - 2 * prefix_length;
+
+ // cost of importing from another crate
+ let crate_boundary_cost = if current.krate() == item.krate() {
+ 0
+ } else if item.krate().is_builtin(db) {
+ 2
+ } else {
+ 4
+ };
+
+ distinct_length + crate_boundary_cost
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use hir::Semantics;
+ use ide_db::{
+ assists::AssistResolveStrategy,
+ base_db::{fixture::WithFixture, FileRange},
+ RootDatabase,
+ };
+
+ use crate::tests::{
+ check_assist, check_assist_not_applicable, check_assist_target, TEST_CONFIG,
+ };
+
+ fn check_auto_import_order(before: &str, order: &[&str]) {
+ let (db, file_id, range_or_offset) = RootDatabase::with_range_or_offset(before);
+ let frange = FileRange { file_id, range: range_or_offset.into() };
+
+ let sema = Semantics::new(&db);
+ let config = TEST_CONFIG;
+ let ctx = AssistContext::new(sema, &config, frange);
+ let mut acc = Assists::new(&ctx, AssistResolveStrategy::All);
+ auto_import(&mut acc, &ctx);
+ let assists = acc.finish();
+
+ let labels = assists.iter().map(|assist| assist.label.to_string()).collect::<Vec<_>>();
+
+ assert_eq!(labels, order);
+ }
+
+ #[test]
+ fn prefer_shorter_paths() {
+ let before = r"
+//- /main.rs crate:main deps:foo,bar
+HashMap$0::new();
+
+//- /lib.rs crate:foo
+pub mod collections { pub struct HashMap; }
+
+//- /lib.rs crate:bar
+pub mod collections { pub mod hash_map { pub struct HashMap; } }
+ ";
+
+ check_auto_import_order(
+ before,
+ &["Import `foo::collections::HashMap`", "Import `bar::collections::hash_map::HashMap`"],
+ )
+ }
+
+ #[test]
+ fn prefer_same_crate() {
+ let before = r"
+//- /main.rs crate:main deps:foo
+HashMap$0::new();
+
+mod collections {
+ pub mod hash_map {
+ pub struct HashMap;
+ }
+}
+
+//- /lib.rs crate:foo
+pub struct HashMap;
+ ";
+
+ check_auto_import_order(
+ before,
+ &["Import `collections::hash_map::HashMap`", "Import `foo::HashMap`"],
+ )
+ }
+
+ #[test]
+ fn not_applicable_if_scope_inside_macro() {
+ check_assist_not_applicable(
+ auto_import,
+ r"
+mod bar {
+ pub struct Baz;
+}
+macro_rules! foo {
+ ($it:ident) => {
+ mod __ {
+ fn __(x: $it) {}
+ }
+ };
+}
+foo! {
+ Baz$0
+}
+",
+ );
+ }
+
+ #[test]
+ fn applicable_in_attributes() {
+ check_assist(
+ auto_import,
+ r"
+//- proc_macros: identity
+#[proc_macros::identity]
+mod foo {
+ mod bar {
+ const _: Baz$0 = ();
+ }
+}
+mod baz {
+ pub struct Baz;
+}
+",
+ r"
+#[proc_macros::identity]
+mod foo {
+ mod bar {
+ use crate::baz::Baz;
+
+ const _: Baz = ();
+ }
+}
+mod baz {
+ pub struct Baz;
+}
+",
+ );
+ }
+
+ #[test]
+ fn applicable_when_found_an_import_partial() {
+ check_assist(
+ auto_import,
+ r"
+ mod std {
+ pub mod fmt {
+ pub struct Formatter;
+ }
+ }
+
+ use std::fmt;
+
+ $0Formatter
+ ",
+ r"
+ mod std {
+ pub mod fmt {
+ pub struct Formatter;
+ }
+ }
+
+ use std::fmt::{self, Formatter};
+
+ Formatter
+ ",
+ );
+ }
+
+ #[test]
+ fn applicable_when_found_an_import() {
+ check_assist(
+ auto_import,
+ r"
+ $0PubStruct
+
+ pub mod PubMod {
+ pub struct PubStruct;
+ }
+ ",
+ r"
+ use PubMod::PubStruct;
+
+ PubStruct
+
+ pub mod PubMod {
+ pub struct PubStruct;
+ }
+ ",
+ );
+ }
+
+ #[test]
+ fn applicable_when_found_an_import_in_macros() {
+ check_assist(
+ auto_import,
+ r"
+ macro_rules! foo {
+ ($i:ident) => { fn foo(a: $i) {} }
+ }
+ foo!(Pub$0Struct);
+
+ pub mod PubMod {
+ pub struct PubStruct;
+ }
+ ",
+ r"
+ use PubMod::PubStruct;
+
+ macro_rules! foo {
+ ($i:ident) => { fn foo(a: $i) {} }
+ }
+ foo!(PubStruct);
+
+ pub mod PubMod {
+ pub struct PubStruct;
+ }
+ ",
+ );
+ }
+
+ #[test]
+ fn applicable_when_found_multiple_imports() {
+ check_assist(
+ auto_import,
+ r"
+ PubSt$0ruct
+
+ pub mod PubMod1 {
+ pub struct PubStruct;
+ }
+ pub mod PubMod2 {
+ pub struct PubStruct;
+ }
+ pub mod PubMod3 {
+ pub struct PubStruct;
+ }
+ ",
+ r"
+ use PubMod3::PubStruct;
+
+ PubStruct
+
+ pub mod PubMod1 {
+ pub struct PubStruct;
+ }
+ pub mod PubMod2 {
+ pub struct PubStruct;
+ }
+ pub mod PubMod3 {
+ pub struct PubStruct;
+ }
+ ",
+ );
+ }
+
+ #[test]
+ fn not_applicable_for_already_imported_types() {
+ check_assist_not_applicable(
+ auto_import,
+ r"
+ use PubMod::PubStruct;
+
+ PubStruct$0
+
+ pub mod PubMod {
+ pub struct PubStruct;
+ }
+ ",
+ );
+ }
+
+ #[test]
+ fn not_applicable_for_types_with_private_paths() {
+ check_assist_not_applicable(
+ auto_import,
+ r"
+ PrivateStruct$0
+
+ pub mod PubMod {
+ struct PrivateStruct;
+ }
+ ",
+ );
+ }
+
+ #[test]
+ fn not_applicable_when_no_imports_found() {
+ check_assist_not_applicable(
+ auto_import,
+ "
+ PubStruct$0",
+ );
+ }
+
+ #[test]
+ fn function_import() {
+ check_assist(
+ auto_import,
+ r"
+ test_function$0
+
+ pub mod PubMod {
+ pub fn test_function() {};
+ }
+ ",
+ r"
+ use PubMod::test_function;
+
+ test_function
+
+ pub mod PubMod {
+ pub fn test_function() {};
+ }
+ ",
+ );
+ }
+
+ #[test]
+ fn macro_import() {
+ check_assist(
+ auto_import,
+ r"
+//- /lib.rs crate:crate_with_macro
+#[macro_export]
+macro_rules! foo {
+ () => ()
+}
+
+//- /main.rs crate:main deps:crate_with_macro
+fn main() {
+ foo$0
+}
+",
+ r"use crate_with_macro::foo;
+
+fn main() {
+ foo
+}
+",
+ );
+ }
+
+ #[test]
+ fn auto_import_target() {
+ check_assist_target(
+ auto_import,
+ r"
+ struct AssistInfo {
+ group_label: Option<$0GroupLabel>,
+ }
+
+ mod m { pub struct GroupLabel; }
+ ",
+ "GroupLabel",
+ )
+ }
+
+ #[test]
+ fn not_applicable_when_path_start_is_imported() {
+ check_assist_not_applicable(
+ auto_import,
+ r"
+ pub mod mod1 {
+ pub mod mod2 {
+ pub mod mod3 {
+ pub struct TestStruct;
+ }
+ }
+ }
+
+ use mod1::mod2;
+ fn main() {
+ mod2::mod3::TestStruct$0
+ }
+ ",
+ );
+ }
+
+ #[test]
+ fn not_applicable_for_imported_function() {
+ check_assist_not_applicable(
+ auto_import,
+ r"
+ pub mod test_mod {
+ pub fn test_function() {}
+ }
+
+ use test_mod::test_function;
+ fn main() {
+ test_function$0
+ }
+ ",
+ );
+ }
+
+ #[test]
+ fn associated_struct_function() {
+ check_assist(
+ auto_import,
+ r"
+ mod test_mod {
+ pub struct TestStruct {}
+ impl TestStruct {
+ pub fn test_function() {}
+ }
+ }
+
+ fn main() {
+ TestStruct::test_function$0
+ }
+ ",
+ r"
+ use test_mod::TestStruct;
+
+ mod test_mod {
+ pub struct TestStruct {}
+ impl TestStruct {
+ pub fn test_function() {}
+ }
+ }
+
+ fn main() {
+ TestStruct::test_function
+ }
+ ",
+ );
+ }
+
+ #[test]
+ fn associated_struct_const() {
+ check_assist(
+ auto_import,
+ r"
+ mod test_mod {
+ pub struct TestStruct {}
+ impl TestStruct {
+ const TEST_CONST: u8 = 42;
+ }
+ }
+
+ fn main() {
+ TestStruct::TEST_CONST$0
+ }
+ ",
+ r"
+ use test_mod::TestStruct;
+
+ mod test_mod {
+ pub struct TestStruct {}
+ impl TestStruct {
+ const TEST_CONST: u8 = 42;
+ }
+ }
+
+ fn main() {
+ TestStruct::TEST_CONST
+ }
+ ",
+ );
+ }
+
+ #[test]
+ fn associated_trait_function() {
+ check_assist(
+ auto_import,
+ r"
+ mod test_mod {
+ pub trait TestTrait {
+ fn test_function();
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_function() {}
+ }
+ }
+
+ fn main() {
+ test_mod::TestStruct::test_function$0
+ }
+ ",
+ r"
+ use test_mod::TestTrait;
+
+ mod test_mod {
+ pub trait TestTrait {
+ fn test_function();
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_function() {}
+ }
+ }
+
+ fn main() {
+ test_mod::TestStruct::test_function
+ }
+ ",
+ );
+ }
+
+ #[test]
+ fn not_applicable_for_imported_trait_for_function() {
+ check_assist_not_applicable(
+ auto_import,
+ r"
+ mod test_mod {
+ pub trait TestTrait {
+ fn test_function();
+ }
+ pub trait TestTrait2 {
+ fn test_function();
+ }
+ pub enum TestEnum {
+ One,
+ Two,
+ }
+ impl TestTrait2 for TestEnum {
+ fn test_function() {}
+ }
+ impl TestTrait for TestEnum {
+ fn test_function() {}
+ }
+ }
+
+ use test_mod::TestTrait2;
+ fn main() {
+ test_mod::TestEnum::test_function$0;
+ }
+ ",
+ )
+ }
+
+ #[test]
+ fn associated_trait_const() {
+ check_assist(
+ auto_import,
+ r"
+ mod test_mod {
+ pub trait TestTrait {
+ const TEST_CONST: u8;
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const TEST_CONST: u8 = 42;
+ }
+ }
+
+ fn main() {
+ test_mod::TestStruct::TEST_CONST$0
+ }
+ ",
+ r"
+ use test_mod::TestTrait;
+
+ mod test_mod {
+ pub trait TestTrait {
+ const TEST_CONST: u8;
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const TEST_CONST: u8 = 42;
+ }
+ }
+
+ fn main() {
+ test_mod::TestStruct::TEST_CONST
+ }
+ ",
+ );
+ }
+
+ #[test]
+ fn not_applicable_for_imported_trait_for_const() {
+ check_assist_not_applicable(
+ auto_import,
+ r"
+ mod test_mod {
+ pub trait TestTrait {
+ const TEST_CONST: u8;
+ }
+ pub trait TestTrait2 {
+ const TEST_CONST: f64;
+ }
+ pub enum TestEnum {
+ One,
+ Two,
+ }
+ impl TestTrait2 for TestEnum {
+ const TEST_CONST: f64 = 42.0;
+ }
+ impl TestTrait for TestEnum {
+ const TEST_CONST: u8 = 42;
+ }
+ }
+
+ use test_mod::TestTrait2;
+ fn main() {
+ test_mod::TestEnum::TEST_CONST$0;
+ }
+ ",
+ )
+ }
+
+ #[test]
+ fn trait_method() {
+ check_assist(
+ auto_import,
+ r"
+ mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self) {}
+ }
+ }
+
+ fn main() {
+ let test_struct = test_mod::TestStruct {};
+ test_struct.test_meth$0od()
+ }
+ ",
+ r"
+ use test_mod::TestTrait;
+
+ mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self) {}
+ }
+ }
+
+ fn main() {
+ let test_struct = test_mod::TestStruct {};
+ test_struct.test_method()
+ }
+ ",
+ );
+ }
+
+ #[test]
+ fn trait_method_cross_crate() {
+ check_assist(
+ auto_import,
+ r"
+ //- /main.rs crate:main deps:dep
+ fn main() {
+ let test_struct = dep::test_mod::TestStruct {};
+ test_struct.test_meth$0od()
+ }
+ //- /dep.rs crate:dep
+ pub mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self) {}
+ }
+ }
+ ",
+ r"
+ use dep::test_mod::TestTrait;
+
+ fn main() {
+ let test_struct = dep::test_mod::TestStruct {};
+ test_struct.test_method()
+ }
+ ",
+ );
+ }
+
+ #[test]
+ fn assoc_fn_cross_crate() {
+ check_assist(
+ auto_import,
+ r"
+ //- /main.rs crate:main deps:dep
+ fn main() {
+ dep::test_mod::TestStruct::test_func$0tion
+ }
+ //- /dep.rs crate:dep
+ pub mod test_mod {
+ pub trait TestTrait {
+ fn test_function();
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_function() {}
+ }
+ }
+ ",
+ r"
+ use dep::test_mod::TestTrait;
+
+ fn main() {
+ dep::test_mod::TestStruct::test_function
+ }
+ ",
+ );
+ }
+
+ #[test]
+ fn assoc_const_cross_crate() {
+ check_assist(
+ auto_import,
+ r"
+ //- /main.rs crate:main deps:dep
+ fn main() {
+ dep::test_mod::TestStruct::CONST$0
+ }
+ //- /dep.rs crate:dep
+ pub mod test_mod {
+ pub trait TestTrait {
+ const CONST: bool;
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const CONST: bool = true;
+ }
+ }
+ ",
+ r"
+ use dep::test_mod::TestTrait;
+
+ fn main() {
+ dep::test_mod::TestStruct::CONST
+ }
+ ",
+ );
+ }
+
+ #[test]
+ fn assoc_fn_as_method_cross_crate() {
+ check_assist_not_applicable(
+ auto_import,
+ r"
+ //- /main.rs crate:main deps:dep
+ fn main() {
+ let test_struct = dep::test_mod::TestStruct {};
+ test_struct.test_func$0tion()
+ }
+ //- /dep.rs crate:dep
+ pub mod test_mod {
+ pub trait TestTrait {
+ fn test_function();
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_function() {}
+ }
+ }
+ ",
+ );
+ }
+
+ #[test]
+ fn private_trait_cross_crate() {
+ check_assist_not_applicable(
+ auto_import,
+ r"
+ //- /main.rs crate:main deps:dep
+ fn main() {
+ let test_struct = dep::test_mod::TestStruct {};
+ test_struct.test_meth$0od()
+ }
+ //- /dep.rs crate:dep
+ pub mod test_mod {
+ trait TestTrait {
+ fn test_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self) {}
+ }
+ }
+ ",
+ );
+ }
+
+ #[test]
+ fn not_applicable_for_imported_trait_for_method() {
+ check_assist_not_applicable(
+ auto_import,
+ r"
+ mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self);
+ }
+ pub trait TestTrait2 {
+ fn test_method(&self);
+ }
+ pub enum TestEnum {
+ One,
+ Two,
+ }
+ impl TestTrait2 for TestEnum {
+ fn test_method(&self) {}
+ }
+ impl TestTrait for TestEnum {
+ fn test_method(&self) {}
+ }
+ }
+
+ use test_mod::TestTrait2;
+ fn main() {
+ let one = test_mod::TestEnum::One;
+ one.test$0_method();
+ }
+ ",
+ )
+ }
+
+ #[test]
+ fn dep_import() {
+ check_assist(
+ auto_import,
+ r"
+//- /lib.rs crate:dep
+pub struct Struct;
+
+//- /main.rs crate:main deps:dep
+fn main() {
+ Struct$0
+}
+",
+ r"use dep::Struct;
+
+fn main() {
+ Struct
+}
+",
+ );
+ }
+
+ #[test]
+ fn whole_segment() {
+ // Tests that only imports whose last segment matches the identifier get suggested.
+ check_assist(
+ auto_import,
+ r"
+//- /lib.rs crate:dep
+pub mod fmt {
+ pub trait Display {}
+}
+
+pub fn panic_fmt() {}
+
+//- /main.rs crate:main deps:dep
+struct S;
+
+impl f$0mt::Display for S {}
+",
+ r"use dep::fmt;
+
+struct S;
+
+impl fmt::Display for S {}
+",
+ );
+ }
+
+ #[test]
+ fn macro_generated() {
+ // Tests that macro-generated items are suggested from external crates.
+ check_assist(
+ auto_import,
+ r"
+//- /lib.rs crate:dep
+macro_rules! mac {
+ () => {
+ pub struct Cheese;
+ };
+}
+
+mac!();
+
+//- /main.rs crate:main deps:dep
+fn main() {
+ Cheese$0;
+}
+",
+ r"use dep::Cheese;
+
+fn main() {
+ Cheese;
+}
+",
+ );
+ }
+
+ #[test]
+ fn casing() {
+ // Tests that differently cased names don't interfere and we only suggest the matching one.
+ check_assist(
+ auto_import,
+ r"
+//- /lib.rs crate:dep
+pub struct FMT;
+pub struct fmt;
+
+//- /main.rs crate:main deps:dep
+fn main() {
+ FMT$0;
+}
+",
+ r"use dep::FMT;
+
+fn main() {
+ FMT;
+}
+",
+ );
+ }
+
+ #[test]
+ fn inner_items() {
+ check_assist(
+ auto_import,
+ r#"
+mod baz {
+ pub struct Foo {}
+}
+
+mod bar {
+ fn bar() {
+ Foo$0;
+ println!("Hallo");
+ }
+}
+"#,
+ r#"
+mod baz {
+ pub struct Foo {}
+}
+
+mod bar {
+ use crate::baz::Foo;
+
+ fn bar() {
+ Foo;
+ println!("Hallo");
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn uses_abs_path_with_extern_crate_clash() {
+ cov_mark::check!(ambiguous_crate_start);
+ check_assist(
+ auto_import,
+ r#"
+//- /main.rs crate:main deps:foo
+mod foo {}
+
+const _: () = {
+ Foo$0
+};
+//- /foo.rs crate:foo
+pub struct Foo
+"#,
+ r#"
+use ::foo::Foo;
+
+mod foo {}
+
+const _: () = {
+ Foo
+};
+"#,
+ );
+ }
+
+ #[test]
+ fn works_on_ident_patterns() {
+ check_assist(
+ auto_import,
+ r#"
+mod foo {
+ pub struct Foo {}
+}
+fn foo() {
+ let Foo$0;
+}
+"#,
+ r#"
+use foo::Foo;
+
+mod foo {
+ pub struct Foo {}
+}
+fn foo() {
+ let Foo;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn works_in_derives() {
+ check_assist(
+ auto_import,
+ r#"
+//- minicore:derive
+mod foo {
+ #[rustc_builtin_macro]
+ pub macro Copy {}
+}
+#[derive(Copy$0)]
+struct Foo;
+"#,
+ r#"
+use foo::Copy;
+
+mod foo {
+ #[rustc_builtin_macro]
+ pub macro Copy {}
+}
+#[derive(Copy)]
+struct Foo;
+"#,
+ );
+ }
+
+ #[test]
+ fn works_in_use_start() {
+ check_assist(
+ auto_import,
+ r#"
+mod bar {
+ pub mod foo {
+ pub struct Foo;
+ }
+}
+use foo$0::Foo;
+"#,
+ r#"
+mod bar {
+ pub mod foo {
+ pub struct Foo;
+ }
+}
+use bar::foo;
+use foo::Foo;
+"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_in_non_start_use() {
+ check_assist_not_applicable(
+ auto_import,
+ r"
+mod bar {
+ pub mod foo {
+ pub struct Foo;
+ }
+}
+use foo::Foo$0;
+",
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/change_visibility.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/change_visibility.rs
new file mode 100644
index 000000000..2b1d8f6f0
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/change_visibility.rs
@@ -0,0 +1,216 @@
+use syntax::{
+ ast::{self, HasName, HasVisibility},
+ AstNode,
+ SyntaxKind::{
+ CONST, ENUM, FN, MACRO_DEF, MODULE, STATIC, STRUCT, TRAIT, TYPE_ALIAS, USE, VISIBILITY,
+ },
+ T,
+};
+
+use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: change_visibility
+//
+// Adds or changes existing visibility specifier.
+//
+// ```
+// $0fn frobnicate() {}
+// ```
+// ->
+// ```
+// pub(crate) fn frobnicate() {}
+// ```
+pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ if let Some(vis) = ctx.find_node_at_offset::<ast::Visibility>() {
+ return change_vis(acc, vis);
+ }
+ add_vis(acc, ctx)
+}
+
+fn add_vis(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let item_keyword = ctx.token_at_offset().find(|leaf| {
+ matches!(
+ leaf.kind(),
+ T![const]
+ | T![static]
+ | T![fn]
+ | T![mod]
+ | T![struct]
+ | T![enum]
+ | T![trait]
+ | T![type]
+ | T![use]
+ | T![macro]
+ )
+ });
+
+ let (offset, target) = if let Some(keyword) = item_keyword {
+ let parent = keyword.parent()?;
+ let def_kws =
+ vec![CONST, STATIC, TYPE_ALIAS, FN, MODULE, STRUCT, ENUM, TRAIT, USE, MACRO_DEF];
+ // Parent is not a definition, can't add visibility
+ if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) {
+ return None;
+ }
+ // Already have visibility, do nothing
+ if parent.children().any(|child| child.kind() == VISIBILITY) {
+ return None;
+ }
+ (vis_offset(&parent), keyword.text_range())
+ } else if let Some(field_name) = ctx.find_node_at_offset::<ast::Name>() {
+ let field = field_name.syntax().ancestors().find_map(ast::RecordField::cast)?;
+ if field.name()? != field_name {
+ cov_mark::hit!(change_visibility_field_false_positive);
+ return None;
+ }
+ if field.visibility().is_some() {
+ return None;
+ }
+ (vis_offset(field.syntax()), field_name.syntax().text_range())
+ } else if let Some(field) = ctx.find_node_at_offset::<ast::TupleField>() {
+ if field.visibility().is_some() {
+ return None;
+ }
+ (vis_offset(field.syntax()), field.syntax().text_range())
+ } else {
+ return None;
+ };
+
+ acc.add(
+ AssistId("change_visibility", AssistKind::RefactorRewrite),
+ "Change visibility to pub(crate)",
+ target,
+ |edit| {
+ edit.insert(offset, "pub(crate) ");
+ },
+ )
+}
+
+fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
+ if vis.syntax().text() == "pub" {
+ let target = vis.syntax().text_range();
+ return acc.add(
+ AssistId("change_visibility", AssistKind::RefactorRewrite),
+ "Change Visibility to pub(crate)",
+ target,
+ |edit| {
+ edit.replace(vis.syntax().text_range(), "pub(crate)");
+ },
+ );
+ }
+ if vis.syntax().text() == "pub(crate)" {
+ let target = vis.syntax().text_range();
+ return acc.add(
+ AssistId("change_visibility", AssistKind::RefactorRewrite),
+ "Change visibility to pub",
+ target,
+ |edit| {
+ edit.replace(vis.syntax().text_range(), "pub");
+ },
+ );
+ }
+ None
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
+
+ use super::*;
+
+ #[test]
+ fn change_visibility_adds_pub_crate_to_items() {
+ check_assist(change_visibility, "$0fn foo() {}", "pub(crate) fn foo() {}");
+ check_assist(change_visibility, "f$0n foo() {}", "pub(crate) fn foo() {}");
+ check_assist(change_visibility, "$0struct Foo {}", "pub(crate) struct Foo {}");
+ check_assist(change_visibility, "$0mod foo {}", "pub(crate) mod foo {}");
+ check_assist(change_visibility, "$0trait Foo {}", "pub(crate) trait Foo {}");
+ check_assist(change_visibility, "m$0od {}", "pub(crate) mod {}");
+ check_assist(change_visibility, "unsafe f$0n foo() {}", "pub(crate) unsafe fn foo() {}");
+ check_assist(change_visibility, "$0macro foo() {}", "pub(crate) macro foo() {}");
+ check_assist(change_visibility, "$0use foo;", "pub(crate) use foo;");
+ }
+
+ #[test]
+ fn change_visibility_works_with_struct_fields() {
+ check_assist(
+ change_visibility,
+ r"struct S { $0field: u32 }",
+ r"struct S { pub(crate) field: u32 }",
+ );
+ check_assist(change_visibility, r"struct S ( $0u32 )", r"struct S ( pub(crate) u32 )");
+ }
+
+ #[test]
+ fn change_visibility_field_false_positive() {
+ cov_mark::check!(change_visibility_field_false_positive);
+ check_assist_not_applicable(
+ change_visibility,
+ r"struct S { field: [(); { let $0x = ();}] }",
+ )
+ }
+
+ #[test]
+ fn change_visibility_pub_to_pub_crate() {
+ check_assist(change_visibility, "$0pub fn foo() {}", "pub(crate) fn foo() {}")
+ }
+
+ #[test]
+ fn change_visibility_pub_crate_to_pub() {
+ check_assist(change_visibility, "$0pub(crate) fn foo() {}", "pub fn foo() {}")
+ }
+
+ #[test]
+ fn change_visibility_const() {
+ check_assist(change_visibility, "$0const FOO = 3u8;", "pub(crate) const FOO = 3u8;");
+ }
+
+ #[test]
+ fn change_visibility_static() {
+ check_assist(change_visibility, "$0static FOO = 3u8;", "pub(crate) static FOO = 3u8;");
+ }
+
+ #[test]
+ fn change_visibility_type_alias() {
+ check_assist(change_visibility, "$0type T = ();", "pub(crate) type T = ();");
+ }
+
+ #[test]
+ fn change_visibility_handles_comment_attrs() {
+ check_assist(
+ change_visibility,
+ r"
+ /// docs
+
+ // comments
+
+ #[derive(Debug)]
+ $0struct Foo;
+ ",
+ r"
+ /// docs
+
+ // comments
+
+ #[derive(Debug)]
+ pub(crate) struct Foo;
+ ",
+ )
+ }
+
+ #[test]
+ fn not_applicable_for_enum_variants() {
+ check_assist_not_applicable(
+ change_visibility,
+ r"mod foo { pub enum Foo {Foo1} }
+ fn main() { foo::Foo::Foo1$0 } ",
+ );
+ }
+
+ #[test]
+ fn change_visibility_target() {
+ check_assist_target(change_visibility, "$0fn foo() {}", "fn");
+ check_assist_target(change_visibility, "pub(crate)$0 fn foo() {}", "pub(crate)");
+ check_assist_target(change_visibility, "struct S { $0field: u32 }", "field");
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs
new file mode 100644
index 000000000..db96ad330
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs
@@ -0,0 +1,575 @@
+use hir::{known, AsAssocItem, Semantics};
+use ide_db::{
+ famous_defs::FamousDefs,
+ syntax_helpers::node_ext::{
+ block_as_lone_tail, for_each_tail_expr, is_pattern_cond, preorder_expr,
+ },
+ RootDatabase,
+};
+use itertools::Itertools;
+use syntax::{
+ ast::{self, edit::AstNodeEdit, make, HasArgList},
+ ted, AstNode, SyntaxNode,
+};
+
+use crate::{
+ utils::{invert_boolean_expression, unwrap_trivial_block},
+ AssistContext, AssistId, AssistKind, Assists,
+};
+
+// Assist: convert_if_to_bool_then
+//
+// Converts an if expression into a corresponding `bool::then` call.
+//
+// ```
+// # //- minicore: option
+// fn main() {
+// if$0 cond {
+// Some(val)
+// } else {
+// None
+// }
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// cond.then(|| val)
+// }
+// ```
+pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ // FIXME applies to match as well
+ let expr = ctx.find_node_at_offset::<ast::IfExpr>()?;
+ if !expr.if_token()?.text_range().contains_inclusive(ctx.offset()) {
+ return None;
+ }
+
+ let cond = expr.condition().filter(|cond| !is_pattern_cond(cond.clone()))?;
+ let then = expr.then_branch()?;
+ let else_ = match expr.else_branch()? {
+ ast::ElseBranch::Block(b) => b,
+ ast::ElseBranch::IfExpr(_) => {
+ cov_mark::hit!(convert_if_to_bool_then_chain);
+ return None;
+ }
+ };
+
+ let (none_variant, some_variant) = option_variants(&ctx.sema, expr.syntax())?;
+
+ let (invert_cond, closure_body) = match (
+ block_is_none_variant(&ctx.sema, &then, none_variant),
+ block_is_none_variant(&ctx.sema, &else_, none_variant),
+ ) {
+ (invert @ true, false) => (invert, ast::Expr::BlockExpr(else_)),
+ (invert @ false, true) => (invert, ast::Expr::BlockExpr(then)),
+ _ => return None,
+ };
+
+ if is_invalid_body(&ctx.sema, some_variant, &closure_body) {
+ cov_mark::hit!(convert_if_to_bool_then_pattern_invalid_body);
+ return None;
+ }
+
+ let target = expr.syntax().text_range();
+ acc.add(
+ AssistId("convert_if_to_bool_then", AssistKind::RefactorRewrite),
+ "Convert `if` expression to `bool::then` call",
+ target,
+ |builder| {
+ let closure_body = closure_body.clone_for_update();
+ // Rewrite all `Some(e)` in tail position to `e`
+ let mut replacements = Vec::new();
+ for_each_tail_expr(&closure_body, &mut |e| {
+ let e = match e {
+ ast::Expr::BreakExpr(e) => e.expr(),
+ e @ ast::Expr::CallExpr(_) => Some(e.clone()),
+ _ => None,
+ };
+ if let Some(ast::Expr::CallExpr(call)) = e {
+ if let Some(arg_list) = call.arg_list() {
+ if let Some(arg) = arg_list.args().next() {
+ replacements.push((call.syntax().clone(), arg.syntax().clone()));
+ }
+ }
+ }
+ });
+ replacements.into_iter().for_each(|(old, new)| ted::replace(old, new));
+ let closure_body = match closure_body {
+ ast::Expr::BlockExpr(block) => unwrap_trivial_block(block),
+ e => e,
+ };
+
+ let parenthesize = matches!(
+ cond,
+ ast::Expr::BinExpr(_)
+ | ast::Expr::BlockExpr(_)
+ | ast::Expr::BoxExpr(_)
+ | ast::Expr::BreakExpr(_)
+ | ast::Expr::CastExpr(_)
+ | ast::Expr::ClosureExpr(_)
+ | ast::Expr::ContinueExpr(_)
+ | ast::Expr::ForExpr(_)
+ | ast::Expr::IfExpr(_)
+ | ast::Expr::LoopExpr(_)
+ | ast::Expr::MacroExpr(_)
+ | ast::Expr::MatchExpr(_)
+ | ast::Expr::PrefixExpr(_)
+ | ast::Expr::RangeExpr(_)
+ | ast::Expr::RefExpr(_)
+ | ast::Expr::ReturnExpr(_)
+ | ast::Expr::WhileExpr(_)
+ | ast::Expr::YieldExpr(_)
+ );
+ let cond = if invert_cond { invert_boolean_expression(cond) } else { cond };
+ let cond = if parenthesize { make::expr_paren(cond) } else { cond };
+ let arg_list = make::arg_list(Some(make::expr_closure(None, closure_body)));
+ let mcall = make::expr_method_call(cond, make::name_ref("then"), arg_list);
+ builder.replace(target, mcall.to_string());
+ },
+ )
+}
+
+// Assist: convert_bool_then_to_if
+//
+// Converts a `bool::then` method call to an equivalent if expression.
+//
+// ```
+// # //- minicore: bool_impl
+// fn main() {
+// (0 == 0).then$0(|| val)
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// if 0 == 0 {
+// Some(val)
+// } else {
+// None
+// }
+// }
+// ```
+pub(crate) fn convert_bool_then_to_if(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let name_ref = ctx.find_node_at_offset::<ast::NameRef>()?;
+ let mcall = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast)?;
+ let receiver = mcall.receiver()?;
+ let closure_body = mcall.arg_list()?.args().exactly_one().ok()?;
+ let closure_body = match closure_body {
+ ast::Expr::ClosureExpr(expr) => expr.body()?,
+ _ => return None,
+ };
+ // Verify this is `bool::then` that is being called.
+ let func = ctx.sema.resolve_method_call(&mcall)?;
+ if func.name(ctx.sema.db).to_string() != "then" {
+ return None;
+ }
+ let assoc = func.as_assoc_item(ctx.sema.db)?;
+ match assoc.container(ctx.sema.db) {
+ hir::AssocItemContainer::Impl(impl_) if impl_.self_ty(ctx.sema.db).is_bool() => {}
+ _ => return None,
+ }
+
+ let target = mcall.syntax().text_range();
+ acc.add(
+ AssistId("convert_bool_then_to_if", AssistKind::RefactorRewrite),
+ "Convert `bool::then` call to `if`",
+ target,
+ |builder| {
+ let closure_body = match closure_body {
+ ast::Expr::BlockExpr(block) => block,
+ e => make::block_expr(None, Some(e)),
+ };
+
+ let closure_body = closure_body.clone_for_update();
+ // Wrap all tails in `Some(...)`
+ let none_path = make::expr_path(make::ext::ident_path("None"));
+ let some_path = make::expr_path(make::ext::ident_path("Some"));
+ let mut replacements = Vec::new();
+ for_each_tail_expr(&ast::Expr::BlockExpr(closure_body.clone()), &mut |e| {
+ let e = match e {
+ ast::Expr::BreakExpr(e) => e.expr(),
+ ast::Expr::ReturnExpr(e) => e.expr(),
+ _ => Some(e.clone()),
+ };
+ if let Some(expr) = e {
+ replacements.push((
+ expr.syntax().clone(),
+ make::expr_call(some_path.clone(), make::arg_list(Some(expr)))
+ .syntax()
+ .clone_for_update(),
+ ));
+ }
+ });
+ replacements.into_iter().for_each(|(old, new)| ted::replace(old, new));
+
+ let cond = match &receiver {
+ ast::Expr::ParenExpr(expr) => expr.expr().unwrap_or(receiver),
+ _ => receiver,
+ };
+ let if_expr = make::expr_if(
+ cond,
+ closure_body.reset_indent(),
+ Some(ast::ElseBranch::Block(make::block_expr(None, Some(none_path)))),
+ )
+ .indent(mcall.indent_level());
+
+ builder.replace(target, if_expr.to_string());
+ },
+ )
+}
+
+fn option_variants(
+ sema: &Semantics<'_, RootDatabase>,
+ expr: &SyntaxNode,
+) -> Option<(hir::Variant, hir::Variant)> {
+ let fam = FamousDefs(sema, sema.scope(expr)?.krate());
+ let option_variants = fam.core_option_Option()?.variants(sema.db);
+ match &*option_variants {
+ &[variant0, variant1] => Some(if variant0.name(sema.db) == known::None {
+ (variant0, variant1)
+ } else {
+ (variant1, variant0)
+ }),
+ _ => None,
+ }
+}
+
+/// Traverses the expression checking if it contains `return` or `?` expressions or if any tail is not a `Some(expr)` expression.
+/// If any of these conditions are met it is impossible to rewrite this as a `bool::then` call.
+fn is_invalid_body(
+ sema: &Semantics<'_, RootDatabase>,
+ some_variant: hir::Variant,
+ expr: &ast::Expr,
+) -> bool {
+ let mut invalid = false;
+ preorder_expr(expr, &mut |e| {
+ invalid |=
+ matches!(e, syntax::WalkEvent::Enter(ast::Expr::TryExpr(_) | ast::Expr::ReturnExpr(_)));
+ invalid
+ });
+ if !invalid {
+ for_each_tail_expr(expr, &mut |e| {
+ if invalid {
+ return;
+ }
+ let e = match e {
+ ast::Expr::BreakExpr(e) => e.expr(),
+ e @ ast::Expr::CallExpr(_) => Some(e.clone()),
+ _ => None,
+ };
+ if let Some(ast::Expr::CallExpr(call)) = e {
+ if let Some(ast::Expr::PathExpr(p)) = call.expr() {
+ let res = p.path().and_then(|p| sema.resolve_path(&p));
+ if let Some(hir::PathResolution::Def(hir::ModuleDef::Variant(v))) = res {
+ return invalid |= v != some_variant;
+ }
+ }
+ }
+ invalid = true
+ });
+ }
+ invalid
+}
+
+fn block_is_none_variant(
+ sema: &Semantics<'_, RootDatabase>,
+ block: &ast::BlockExpr,
+ none_variant: hir::Variant,
+) -> bool {
+ block_as_lone_tail(block).and_then(|e| match e {
+ ast::Expr::PathExpr(pat) => match sema.resolve_path(&pat.path()?)? {
+ hir::PathResolution::Def(hir::ModuleDef::Variant(v)) => Some(v),
+ _ => None,
+ },
+ _ => None,
+ }) == Some(none_variant)
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn convert_if_to_bool_then_simple() {
+ check_assist(
+ convert_if_to_bool_then,
+ r"
+//- minicore:option
+fn main() {
+ if$0 true {
+ Some(15)
+ } else {
+ None
+ }
+}
+",
+ r"
+fn main() {
+ true.then(|| 15)
+}
+",
+ );
+ }
+
+ #[test]
+ fn convert_if_to_bool_then_invert() {
+ check_assist(
+ convert_if_to_bool_then,
+ r"
+//- minicore:option
+fn main() {
+ if$0 true {
+ None
+ } else {
+ Some(15)
+ }
+}
+",
+ r"
+fn main() {
+ false.then(|| 15)
+}
+",
+ );
+ }
+
+ #[test]
+ fn convert_if_to_bool_then_none_none() {
+ check_assist_not_applicable(
+ convert_if_to_bool_then,
+ r"
+//- minicore:option
+fn main() {
+ if$0 true {
+ None
+ } else {
+ None
+ }
+}
+",
+ );
+ }
+
+ #[test]
+ fn convert_if_to_bool_then_some_some() {
+ check_assist_not_applicable(
+ convert_if_to_bool_then,
+ r"
+//- minicore:option
+fn main() {
+ if$0 true {
+ Some(15)
+ } else {
+ Some(15)
+ }
+}
+",
+ );
+ }
+
+ #[test]
+ fn convert_if_to_bool_then_mixed() {
+ check_assist_not_applicable(
+ convert_if_to_bool_then,
+ r"
+//- minicore:option
+fn main() {
+ if$0 true {
+ if true {
+ Some(15)
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+}
+",
+ );
+ }
+
+ #[test]
+ fn convert_if_to_bool_then_chain() {
+ cov_mark::check!(convert_if_to_bool_then_chain);
+ check_assist_not_applicable(
+ convert_if_to_bool_then,
+ r"
+//- minicore:option
+fn main() {
+ if$0 true {
+ Some(15)
+ } else if true {
+ None
+ } else {
+ None
+ }
+}
+",
+ );
+ }
+
+ #[test]
+ fn convert_if_to_bool_then_pattern_cond() {
+ check_assist_not_applicable(
+ convert_if_to_bool_then,
+ r"
+//- minicore:option
+fn main() {
+ if$0 let true = true {
+ Some(15)
+ } else {
+ None
+ }
+}
+",
+ );
+ }
+
+ #[test]
+ fn convert_if_to_bool_then_pattern_invalid_body() {
+ cov_mark::check_count!(convert_if_to_bool_then_pattern_invalid_body, 2);
+ check_assist_not_applicable(
+ convert_if_to_bool_then,
+ r"
+//- minicore:option
+fn make_me_an_option() -> Option<i32> { None }
+fn main() {
+ if$0 true {
+ if true {
+ make_me_an_option()
+ } else {
+ Some(15)
+ }
+ } else {
+ None
+ }
+}
+",
+ );
+ check_assist_not_applicable(
+ convert_if_to_bool_then,
+ r"
+//- minicore:option
+fn main() {
+ if$0 true {
+ if true {
+ return;
+ }
+ Some(15)
+ } else {
+ None
+ }
+}
+",
+ );
+ }
+
+ #[test]
+ fn convert_bool_then_to_if_inapplicable() {
+ check_assist_not_applicable(
+ convert_bool_then_to_if,
+ r"
+//- minicore:bool_impl
+fn main() {
+ 0.t$0hen(|| 15);
+}
+",
+ );
+ check_assist_not_applicable(
+ convert_bool_then_to_if,
+ r"
+//- minicore:bool_impl
+fn main() {
+ true.t$0hen(15);
+}
+",
+ );
+ check_assist_not_applicable(
+ convert_bool_then_to_if,
+ r"
+//- minicore:bool_impl
+fn main() {
+ true.t$0hen(|| 15, 15);
+}
+",
+ );
+ }
+
+ #[test]
+ fn convert_bool_then_to_if_simple() {
+ check_assist(
+ convert_bool_then_to_if,
+ r"
+//- minicore:bool_impl
+fn main() {
+ true.t$0hen(|| 15)
+}
+",
+ r"
+fn main() {
+ if true {
+ Some(15)
+ } else {
+ None
+ }
+}
+",
+ );
+ check_assist(
+ convert_bool_then_to_if,
+ r"
+//- minicore:bool_impl
+fn main() {
+ true.t$0hen(|| {
+ 15
+ })
+}
+",
+ r"
+fn main() {
+ if true {
+ Some(15)
+ } else {
+ None
+ }
+}
+",
+ );
+ }
+
+ #[test]
+ fn convert_bool_then_to_if_tails() {
+ check_assist(
+ convert_bool_then_to_if,
+ r"
+//- minicore:bool_impl
+fn main() {
+ true.t$0hen(|| {
+ loop {
+ if false {
+ break 0;
+ }
+ break 15;
+ }
+ })
+}
+",
+ r"
+fn main() {
+ if true {
+ loop {
+ if false {
+ break Some(0);
+ }
+ break Some(15);
+ }
+ } else {
+ None
+ }
+}
+",
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_comment_block.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_comment_block.rs
new file mode 100644
index 000000000..f171dd81a
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_comment_block.rs
@@ -0,0 +1,395 @@
+use itertools::Itertools;
+use syntax::{
+ ast::{self, edit::IndentLevel, Comment, CommentKind, CommentShape, Whitespace},
+ AstToken, Direction, SyntaxElement, TextRange,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: line_to_block
+//
+// Converts comments between block and single-line form.
+//
+// ```
+// // Multi-line$0
+// // comment
+// ```
+// ->
+// ```
+// /*
+// Multi-line
+// comment
+// */
+// ```
+pub(crate) fn convert_comment_block(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let comment = ctx.find_token_at_offset::<ast::Comment>()?;
+ // Only allow comments which are alone on their line
+ if let Some(prev) = comment.syntax().prev_token() {
+ if Whitespace::cast(prev).filter(|w| w.text().contains('\n')).is_none() {
+ return None;
+ }
+ }
+
+ match comment.kind().shape {
+ ast::CommentShape::Block => block_to_line(acc, comment),
+ ast::CommentShape::Line => line_to_block(acc, comment),
+ }
+}
+
+fn block_to_line(acc: &mut Assists, comment: ast::Comment) -> Option<()> {
+ let target = comment.syntax().text_range();
+
+ acc.add(
+ AssistId("block_to_line", AssistKind::RefactorRewrite),
+ "Replace block comment with line comments",
+ target,
+ |edit| {
+ let indentation = IndentLevel::from_token(comment.syntax());
+ let line_prefix = CommentKind { shape: CommentShape::Line, ..comment.kind() }.prefix();
+
+ let text = comment.text();
+ let text = &text[comment.prefix().len()..(text.len() - "*/".len())].trim();
+
+ let lines = text.lines().peekable();
+
+ let indent_spaces = indentation.to_string();
+ let output = lines
+ .map(|l| l.trim_start_matches(&indent_spaces))
+ .map(|l| {
+ // Don't introduce trailing whitespace
+ if l.is_empty() {
+ line_prefix.to_string()
+ } else {
+ format!("{} {}", line_prefix, l.trim_start_matches(&indent_spaces))
+ }
+ })
+ .join(&format!("\n{}", indent_spaces));
+
+ edit.replace(target, output)
+ },
+ )
+}
+
+fn line_to_block(acc: &mut Assists, comment: ast::Comment) -> Option<()> {
+ // Find all the comments we'll be collapsing into a block
+ let comments = relevant_line_comments(&comment);
+
+ // Establish the target of our edit based on the comments we found
+ let target = TextRange::new(
+ comments[0].syntax().text_range().start(),
+ comments.last().unwrap().syntax().text_range().end(),
+ );
+
+ acc.add(
+ AssistId("line_to_block", AssistKind::RefactorRewrite),
+ "Replace line comments with a single block comment",
+ target,
+ |edit| {
+ // We pick a single indentation level for the whole block comment based on the
+ // comment where the assist was invoked. This will be prepended to the
+ // contents of each line comment when they're put into the block comment.
+ let indentation = IndentLevel::from_token(comment.syntax());
+
+ let block_comment_body =
+ comments.into_iter().map(|c| line_comment_text(indentation, c)).join("\n");
+
+ let block_prefix =
+ CommentKind { shape: CommentShape::Block, ..comment.kind() }.prefix();
+
+ let output = format!("{}\n{}\n{}*/", block_prefix, block_comment_body, indentation);
+
+ edit.replace(target, output)
+ },
+ )
+}
+
+/// The line -> block assist can be invoked from anywhere within a sequence of line comments.
+/// relevant_line_comments crawls backwards and forwards finding the complete sequence of comments that will
+/// be joined.
+fn relevant_line_comments(comment: &ast::Comment) -> Vec<Comment> {
+ // The prefix identifies the kind of comment we're dealing with
+ let prefix = comment.prefix();
+ let same_prefix = |c: &ast::Comment| c.prefix() == prefix;
+
+ // These tokens are allowed to exist between comments
+ let skippable = |not: &SyntaxElement| {
+ not.clone()
+ .into_token()
+ .and_then(Whitespace::cast)
+ .map(|w| !w.spans_multiple_lines())
+ .unwrap_or(false)
+ };
+
+ // Find all preceding comments (in reverse order) that have the same prefix
+ let prev_comments = comment
+ .syntax()
+ .siblings_with_tokens(Direction::Prev)
+ .filter(|s| !skippable(s))
+ .map(|not| not.into_token().and_then(Comment::cast).filter(same_prefix))
+ .take_while(|opt_com| opt_com.is_some())
+ .flatten()
+ .skip(1); // skip the first element so we don't duplicate it in next_comments
+
+ let next_comments = comment
+ .syntax()
+ .siblings_with_tokens(Direction::Next)
+ .filter(|s| !skippable(s))
+ .map(|not| not.into_token().and_then(Comment::cast).filter(same_prefix))
+ .take_while(|opt_com| opt_com.is_some())
+ .flatten();
+
+ let mut comments: Vec<_> = prev_comments.collect();
+ comments.reverse();
+ comments.extend(next_comments);
+ comments
+}
+
+// Line comments usually begin with a single space character following the prefix as seen here:
+//^
+// But comments can also include indented text:
+// > Hello there
+//
+// We handle this by stripping *AT MOST* one space character from the start of the line
+// This has its own problems because it can cause alignment issues:
+//
+// /*
+// a ----> a
+//b ----> b
+// */
+//
+// But since such comments aren't idiomatic we're okay with this.
+fn line_comment_text(indentation: IndentLevel, comm: ast::Comment) -> String {
+ let contents_without_prefix = comm.text().strip_prefix(comm.prefix()).unwrap();
+ let contents = contents_without_prefix.strip_prefix(' ').unwrap_or(contents_without_prefix);
+
+ // Don't add the indentation if the line is empty
+ if contents.is_empty() {
+ contents.to_owned()
+ } else {
+ indentation.to_string() + contents
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn single_line_to_block() {
+ check_assist(
+ convert_comment_block,
+ r#"
+// line$0 comment
+fn main() {
+ foo();
+}
+"#,
+ r#"
+/*
+line comment
+*/
+fn main() {
+ foo();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn single_line_to_block_indented() {
+ check_assist(
+ convert_comment_block,
+ r#"
+fn main() {
+ // line$0 comment
+ foo();
+}
+"#,
+ r#"
+fn main() {
+ /*
+ line comment
+ */
+ foo();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn multiline_to_block() {
+ check_assist(
+ convert_comment_block,
+ r#"
+fn main() {
+ // above
+ // line$0 comment
+ //
+ // below
+ foo();
+}
+"#,
+ r#"
+fn main() {
+ /*
+ above
+ line comment
+
+ below
+ */
+ foo();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn end_of_line_to_block() {
+ check_assist_not_applicable(
+ convert_comment_block,
+ r#"
+fn main() {
+ foo(); // end-of-line$0 comment
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn single_line_different_kinds() {
+ check_assist(
+ convert_comment_block,
+ r#"
+fn main() {
+ /// different prefix
+ // line$0 comment
+ // below
+ foo();
+}
+"#,
+ r#"
+fn main() {
+ /// different prefix
+ /*
+ line comment
+ below
+ */
+ foo();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn single_line_separate_chunks() {
+ check_assist(
+ convert_comment_block,
+ r#"
+fn main() {
+ // different chunk
+
+ // line$0 comment
+ // below
+ foo();
+}
+"#,
+ r#"
+fn main() {
+ // different chunk
+
+ /*
+ line comment
+ below
+ */
+ foo();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn doc_block_comment_to_lines() {
+ check_assist(
+ convert_comment_block,
+ r#"
+/**
+ hi$0 there
+*/
+"#,
+ r#"
+/// hi there
+"#,
+ );
+ }
+
+ #[test]
+ fn block_comment_to_lines() {
+ check_assist(
+ convert_comment_block,
+ r#"
+/*
+ hi$0 there
+*/
+"#,
+ r#"
+// hi there
+"#,
+ );
+ }
+
+ #[test]
+ fn inner_doc_block_to_lines() {
+ check_assist(
+ convert_comment_block,
+ r#"
+/*!
+ hi$0 there
+*/
+"#,
+ r#"
+//! hi there
+"#,
+ );
+ }
+
+ #[test]
+ fn block_to_lines_indent() {
+ check_assist(
+ convert_comment_block,
+ r#"
+fn main() {
+ /*!
+ hi$0 there
+
+ ```
+ code_sample
+ ```
+ */
+}
+"#,
+ r#"
+fn main() {
+ //! hi there
+ //!
+ //! ```
+ //! code_sample
+ //! ```
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn end_of_line_block_to_line() {
+ check_assist_not_applicable(
+ convert_comment_block,
+ r#"
+fn main() {
+ foo(); /* end-of-line$0 comment */
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_integer_literal.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_integer_literal.rs
new file mode 100644
index 000000000..9060696cd
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_integer_literal.rs
@@ -0,0 +1,268 @@
+use syntax::{ast, ast::Radix, AstToken};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
+
+// Assist: convert_integer_literal
+//
+// Converts the base of integer literals to other bases.
+//
+// ```
+// const _: i32 = 10$0;
+// ```
+// ->
+// ```
+// const _: i32 = 0b1010;
+// ```
+pub(crate) fn convert_integer_literal(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let literal = ctx.find_node_at_offset::<ast::Literal>()?;
+ let literal = match literal.kind() {
+ ast::LiteralKind::IntNumber(it) => it,
+ _ => return None,
+ };
+ let radix = literal.radix();
+ let value = literal.value()?;
+ let suffix = literal.suffix();
+
+ let range = literal.syntax().text_range();
+ let group_id = GroupLabel("Convert integer base".into());
+
+ for &target_radix in Radix::ALL {
+ if target_radix == radix {
+ continue;
+ }
+
+ let mut converted = match target_radix {
+ Radix::Binary => format!("0b{:b}", value),
+ Radix::Octal => format!("0o{:o}", value),
+ Radix::Decimal => value.to_string(),
+ Radix::Hexadecimal => format!("0x{:X}", value),
+ };
+
+ let label = format!("Convert {} to {}{}", literal, converted, suffix.unwrap_or_default());
+
+ // Appends the type suffix back into the new literal if it exists.
+ if let Some(suffix) = suffix {
+ converted.push_str(suffix);
+ }
+
+ acc.add_group(
+ &group_id,
+ AssistId("convert_integer_literal", AssistKind::RefactorInline),
+ label,
+ range,
+ |builder| builder.replace(range, converted),
+ );
+ }
+
+ Some(())
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist_by_label, check_assist_not_applicable, check_assist_target};
+
+ use super::*;
+
+ #[test]
+ fn binary_target() {
+ check_assist_target(convert_integer_literal, "const _: i32 = 0b1010$0;", "0b1010");
+ }
+
+ #[test]
+ fn octal_target() {
+ check_assist_target(convert_integer_literal, "const _: i32 = 0o12$0;", "0o12");
+ }
+
+ #[test]
+ fn decimal_target() {
+ check_assist_target(convert_integer_literal, "const _: i32 = 10$0;", "10");
+ }
+
+ #[test]
+ fn hexadecimal_target() {
+ check_assist_target(convert_integer_literal, "const _: i32 = 0xA$0;", "0xA");
+ }
+
+ #[test]
+ fn binary_target_with_underscores() {
+ check_assist_target(convert_integer_literal, "const _: i32 = 0b10_10$0;", "0b10_10");
+ }
+
+ #[test]
+ fn octal_target_with_underscores() {
+ check_assist_target(convert_integer_literal, "const _: i32 = 0o1_2$0;", "0o1_2");
+ }
+
+ #[test]
+ fn decimal_target_with_underscores() {
+ check_assist_target(convert_integer_literal, "const _: i32 = 1_0$0;", "1_0");
+ }
+
+ #[test]
+ fn hexadecimal_target_with_underscores() {
+ check_assist_target(convert_integer_literal, "const _: i32 = 0x_A$0;", "0x_A");
+ }
+
+ #[test]
+ fn convert_decimal_integer() {
+ let before = "const _: i32 = 1000$0;";
+
+ check_assist_by_label(
+ convert_integer_literal,
+ before,
+ "const _: i32 = 0b1111101000;",
+ "Convert 1000 to 0b1111101000",
+ );
+
+ check_assist_by_label(
+ convert_integer_literal,
+ before,
+ "const _: i32 = 0o1750;",
+ "Convert 1000 to 0o1750",
+ );
+
+ check_assist_by_label(
+ convert_integer_literal,
+ before,
+ "const _: i32 = 0x3E8;",
+ "Convert 1000 to 0x3E8",
+ );
+ }
+
+ #[test]
+ fn convert_hexadecimal_integer() {
+ let before = "const _: i32 = 0xFF$0;";
+
+ check_assist_by_label(
+ convert_integer_literal,
+ before,
+ "const _: i32 = 0b11111111;",
+ "Convert 0xFF to 0b11111111",
+ );
+
+ check_assist_by_label(
+ convert_integer_literal,
+ before,
+ "const _: i32 = 0o377;",
+ "Convert 0xFF to 0o377",
+ );
+
+ check_assist_by_label(
+ convert_integer_literal,
+ before,
+ "const _: i32 = 255;",
+ "Convert 0xFF to 255",
+ );
+ }
+
+ #[test]
+ fn convert_binary_integer() {
+ let before = "const _: i32 = 0b11111111$0;";
+
+ check_assist_by_label(
+ convert_integer_literal,
+ before,
+ "const _: i32 = 0o377;",
+ "Convert 0b11111111 to 0o377",
+ );
+
+ check_assist_by_label(
+ convert_integer_literal,
+ before,
+ "const _: i32 = 255;",
+ "Convert 0b11111111 to 255",
+ );
+
+ check_assist_by_label(
+ convert_integer_literal,
+ before,
+ "const _: i32 = 0xFF;",
+ "Convert 0b11111111 to 0xFF",
+ );
+ }
+
+ #[test]
+ fn convert_octal_integer() {
+ let before = "const _: i32 = 0o377$0;";
+
+ check_assist_by_label(
+ convert_integer_literal,
+ before,
+ "const _: i32 = 0b11111111;",
+ "Convert 0o377 to 0b11111111",
+ );
+
+ check_assist_by_label(
+ convert_integer_literal,
+ before,
+ "const _: i32 = 255;",
+ "Convert 0o377 to 255",
+ );
+
+ check_assist_by_label(
+ convert_integer_literal,
+ before,
+ "const _: i32 = 0xFF;",
+ "Convert 0o377 to 0xFF",
+ );
+ }
+
+ #[test]
+ fn convert_integer_with_underscores() {
+ let before = "const _: i32 = 1_00_0$0;";
+
+ check_assist_by_label(
+ convert_integer_literal,
+ before,
+ "const _: i32 = 0b1111101000;",
+ "Convert 1_00_0 to 0b1111101000",
+ );
+
+ check_assist_by_label(
+ convert_integer_literal,
+ before,
+ "const _: i32 = 0o1750;",
+ "Convert 1_00_0 to 0o1750",
+ );
+
+ check_assist_by_label(
+ convert_integer_literal,
+ before,
+ "const _: i32 = 0x3E8;",
+ "Convert 1_00_0 to 0x3E8",
+ );
+ }
+
+ #[test]
+ fn convert_integer_with_suffix() {
+ let before = "const _: i32 = 1000i32$0;";
+
+ check_assist_by_label(
+ convert_integer_literal,
+ before,
+ "const _: i32 = 0b1111101000i32;",
+ "Convert 1000i32 to 0b1111101000i32",
+ );
+
+ check_assist_by_label(
+ convert_integer_literal,
+ before,
+ "const _: i32 = 0o1750i32;",
+ "Convert 1000i32 to 0o1750i32",
+ );
+
+ check_assist_by_label(
+ convert_integer_literal,
+ before,
+ "const _: i32 = 0x3E8i32;",
+ "Convert 1000i32 to 0x3E8i32",
+ );
+ }
+
+ #[test]
+ fn convert_overflowing_literal() {
+ let before = "const _: i32 =
+ 111111111111111111111111111111111111111111111111111111111111111111111111$0;";
+ check_assist_not_applicable(convert_integer_literal, before);
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_into_to_from.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_into_to_from.rs
new file mode 100644
index 000000000..30f6dd41a
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_into_to_from.rs
@@ -0,0 +1,351 @@
+use ide_db::{famous_defs::FamousDefs, helpers::mod_path_to_ast, traits::resolve_target_trait};
+use syntax::ast::{self, AstNode, HasName};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// FIXME: this should be a diagnostic
+
+// Assist: convert_into_to_from
+//
+// Converts an Into impl to an equivalent From impl.
+//
+// ```
+// # //- minicore: from
+// impl $0Into<Thing> for usize {
+// fn into(self) -> Thing {
+// Thing {
+// b: self.to_string(),
+// a: self
+// }
+// }
+// }
+// ```
+// ->
+// ```
+// impl From<usize> for Thing {
+// fn from(val: usize) -> Self {
+// Thing {
+// b: val.to_string(),
+// a: val
+// }
+// }
+// }
+// ```
+pub(crate) fn convert_into_to_from(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let impl_ = ctx.find_node_at_offset::<ast::Impl>()?;
+ let src_type = impl_.self_ty()?;
+ let ast_trait = impl_.trait_()?;
+
+ let module = ctx.sema.scope(impl_.syntax())?.module();
+
+ let trait_ = resolve_target_trait(&ctx.sema, &impl_)?;
+ if trait_ != FamousDefs(&ctx.sema, module.krate()).core_convert_Into()? {
+ return None;
+ }
+
+ let src_type_path = {
+ let src_type_path = src_type.syntax().descendants().find_map(ast::Path::cast)?;
+ let src_type_def = match ctx.sema.resolve_path(&src_type_path) {
+ Some(hir::PathResolution::Def(module_def)) => module_def,
+ _ => return None,
+ };
+
+ mod_path_to_ast(&module.find_use_path(ctx.db(), src_type_def)?)
+ };
+
+ let dest_type = match &ast_trait {
+ ast::Type::PathType(path) => {
+ path.path()?.segment()?.generic_arg_list()?.generic_args().next()?
+ }
+ _ => return None,
+ };
+
+ let into_fn = impl_.assoc_item_list()?.assoc_items().find_map(|item| {
+ if let ast::AssocItem::Fn(f) = item {
+ if f.name()?.text() == "into" {
+ return Some(f);
+ }
+ };
+ None
+ })?;
+
+ let into_fn_name = into_fn.name()?;
+ let into_fn_params = into_fn.param_list()?;
+ let into_fn_return = into_fn.ret_type()?;
+
+ let selfs = into_fn
+ .body()?
+ .syntax()
+ .descendants()
+ .filter_map(ast::NameRef::cast)
+ .filter(|name| name.text() == "self" || name.text() == "Self");
+
+ acc.add(
+ AssistId("convert_into_to_from", AssistKind::RefactorRewrite),
+ "Convert Into to From",
+ impl_.syntax().text_range(),
+ |builder| {
+ builder.replace(src_type.syntax().text_range(), dest_type.to_string());
+ builder.replace(ast_trait.syntax().text_range(), format!("From<{}>", src_type));
+ builder.replace(into_fn_return.syntax().text_range(), "-> Self");
+ builder.replace(into_fn_params.syntax().text_range(), format!("(val: {})", src_type));
+ builder.replace(into_fn_name.syntax().text_range(), "from");
+
+ for s in selfs {
+ match s.text().as_ref() {
+ "self" => builder.replace(s.syntax().text_range(), "val"),
+ "Self" => builder.replace(s.syntax().text_range(), src_type_path.to_string()),
+ _ => {}
+ }
+ }
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ #[test]
+ fn convert_into_to_from_converts_a_struct() {
+ check_assist(
+ convert_into_to_from,
+ r#"
+//- minicore: from
+struct Thing {
+ a: String,
+ b: usize
+}
+
+impl $0core::convert::Into<Thing> for usize {
+ fn into(self) -> Thing {
+ Thing {
+ b: self.to_string(),
+ a: self
+ }
+ }
+}
+"#,
+ r#"
+struct Thing {
+ a: String,
+ b: usize
+}
+
+impl From<usize> for Thing {
+ fn from(val: usize) -> Self {
+ Thing {
+ b: val.to_string(),
+ a: val
+ }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn convert_into_to_from_converts_enums() {
+ check_assist(
+ convert_into_to_from,
+ r#"
+//- minicore: from
+enum Thing {
+ Foo(String),
+ Bar(String)
+}
+
+impl $0core::convert::Into<String> for Thing {
+ fn into(self) -> String {
+ match self {
+ Self::Foo(s) => s,
+ Self::Bar(s) => s
+ }
+ }
+}
+"#,
+ r#"
+enum Thing {
+ Foo(String),
+ Bar(String)
+}
+
+impl From<Thing> for String {
+ fn from(val: Thing) -> Self {
+ match val {
+ Thing::Foo(s) => s,
+ Thing::Bar(s) => s
+ }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn convert_into_to_from_on_enum_with_lifetimes() {
+ check_assist(
+ convert_into_to_from,
+ r#"
+//- minicore: from
+enum Thing<'a> {
+ Foo(&'a str),
+ Bar(&'a str)
+}
+
+impl<'a> $0core::convert::Into<&'a str> for Thing<'a> {
+ fn into(self) -> &'a str {
+ match self {
+ Self::Foo(s) => s,
+ Self::Bar(s) => s
+ }
+ }
+}
+"#,
+ r#"
+enum Thing<'a> {
+ Foo(&'a str),
+ Bar(&'a str)
+}
+
+impl<'a> From<Thing<'a>> for &'a str {
+ fn from(val: Thing<'a>) -> Self {
+ match val {
+ Thing::Foo(s) => s,
+ Thing::Bar(s) => s
+ }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn convert_into_to_from_works_on_references() {
+ check_assist(
+ convert_into_to_from,
+ r#"
+//- minicore: from
+struct Thing(String);
+
+impl $0core::convert::Into<String> for &Thing {
+ fn into(self) -> Thing {
+ self.0.clone()
+ }
+}
+"#,
+ r#"
+struct Thing(String);
+
+impl From<&Thing> for String {
+ fn from(val: &Thing) -> Self {
+ val.0.clone()
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn convert_into_to_from_works_on_qualified_structs() {
+ check_assist(
+ convert_into_to_from,
+ r#"
+//- minicore: from
+mod things {
+ pub struct Thing(String);
+ pub struct BetterThing(String);
+}
+
+impl $0core::convert::Into<things::BetterThing> for &things::Thing {
+ fn into(self) -> Thing {
+ things::BetterThing(self.0.clone())
+ }
+}
+"#,
+ r#"
+mod things {
+ pub struct Thing(String);
+ pub struct BetterThing(String);
+}
+
+impl From<&things::Thing> for things::BetterThing {
+ fn from(val: &things::Thing) -> Self {
+ things::BetterThing(val.0.clone())
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn convert_into_to_from_works_on_qualified_enums() {
+ check_assist(
+ convert_into_to_from,
+ r#"
+//- minicore: from
+mod things {
+ pub enum Thing {
+ A(String)
+ }
+ pub struct BetterThing {
+ B(String)
+ }
+}
+
+impl $0core::convert::Into<things::BetterThing> for &things::Thing {
+ fn into(self) -> Thing {
+ match self {
+ Self::A(s) => things::BetterThing::B(s)
+ }
+ }
+}
+"#,
+ r#"
+mod things {
+ pub enum Thing {
+ A(String)
+ }
+ pub struct BetterThing {
+ B(String)
+ }
+}
+
+impl From<&things::Thing> for things::BetterThing {
+ fn from(val: &things::Thing) -> Self {
+ match val {
+ things::Thing::A(s) => things::BetterThing::B(s)
+ }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn convert_into_to_from_not_applicable_on_any_trait_named_into() {
+ check_assist_not_applicable(
+ convert_into_to_from,
+ r#"
+//- minicore: from
+pub trait Into<T> {
+ pub fn into(self) -> T;
+}
+
+struct Thing {
+ a: String,
+}
+
+impl $0Into<Thing> for String {
+ fn into(self) -> Thing {
+ Thing {
+ a: self
+ }
+ }
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs
new file mode 100644
index 000000000..2cf370c09
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs
@@ -0,0 +1,556 @@
+use hir::known;
+use ide_db::famous_defs::FamousDefs;
+use stdx::format_to;
+use syntax::{
+ ast::{self, edit_in_place::Indent, make, HasArgList, HasLoopBody},
+ AstNode,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: convert_iter_for_each_to_for
+//
+// Converts an Iterator::for_each function into a for loop.
+//
+// ```
+// # //- minicore: iterators
+// # use core::iter;
+// fn main() {
+// let iter = iter::repeat((9, 2));
+// iter.for_each$0(|(x, y)| {
+// println!("x: {}, y: {}", x, y);
+// });
+// }
+// ```
+// ->
+// ```
+// # use core::iter;
+// fn main() {
+// let iter = iter::repeat((9, 2));
+// for (x, y) in iter {
+// println!("x: {}, y: {}", x, y);
+// }
+// }
+// ```
+pub(crate) fn convert_iter_for_each_to_for(
+ acc: &mut Assists,
+ ctx: &AssistContext<'_>,
+) -> Option<()> {
+ let method = ctx.find_node_at_offset::<ast::MethodCallExpr>()?;
+
+ let closure = match method.arg_list()?.args().next()? {
+ ast::Expr::ClosureExpr(expr) => expr,
+ _ => return None,
+ };
+
+ let (method, receiver) = validate_method_call_expr(ctx, method)?;
+
+ let param_list = closure.param_list()?;
+ let param = param_list.params().next()?.pat()?;
+ let body = closure.body()?;
+
+ let stmt = method.syntax().parent().and_then(ast::ExprStmt::cast);
+ let range = stmt.as_ref().map_or(method.syntax(), AstNode::syntax).text_range();
+
+ acc.add(
+ AssistId("convert_iter_for_each_to_for", AssistKind::RefactorRewrite),
+ "Replace this `Iterator::for_each` with a for loop",
+ range,
+ |builder| {
+ let indent =
+ stmt.as_ref().map_or_else(|| method.indent_level(), ast::ExprStmt::indent_level);
+
+ let block = match body {
+ ast::Expr::BlockExpr(block) => block,
+ _ => make::block_expr(Vec::new(), Some(body)),
+ }
+ .clone_for_update();
+ block.reindent_to(indent);
+
+ let expr_for_loop = make::expr_for_loop(param, receiver, block);
+ builder.replace(range, expr_for_loop.to_string())
+ },
+ )
+}
+
+// Assist: convert_for_loop_with_for_each
+//
+// Converts a for loop into a for_each loop on the Iterator.
+//
+// ```
+// fn main() {
+// let x = vec![1, 2, 3];
+// for$0 v in x {
+// let y = v * 2;
+// }
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// let x = vec![1, 2, 3];
+// x.into_iter().for_each(|v| {
+// let y = v * 2;
+// });
+// }
+// ```
+pub(crate) fn convert_for_loop_with_for_each(
+ acc: &mut Assists,
+ ctx: &AssistContext<'_>,
+) -> Option<()> {
+ let for_loop = ctx.find_node_at_offset::<ast::ForExpr>()?;
+ let iterable = for_loop.iterable()?;
+ let pat = for_loop.pat()?;
+ let body = for_loop.loop_body()?;
+ if body.syntax().text_range().start() < ctx.offset() {
+ cov_mark::hit!(not_available_in_body);
+ return None;
+ }
+
+ acc.add(
+ AssistId("convert_for_loop_with_for_each", AssistKind::RefactorRewrite),
+ "Replace this for loop with `Iterator::for_each`",
+ for_loop.syntax().text_range(),
+ |builder| {
+ let mut buf = String::new();
+
+ if let Some((expr_behind_ref, method)) =
+ is_ref_and_impls_iter_method(&ctx.sema, &iterable)
+ {
+ // We have either "for x in &col" and col implements a method called iter
+ // or "for x in &mut col" and col implements a method called iter_mut
+ format_to!(buf, "{}.{}()", expr_behind_ref, method);
+ } else if let ast::Expr::RangeExpr(..) = iterable {
+ // range expressions need to be parenthesized for the syntax to be correct
+ format_to!(buf, "({})", iterable);
+ } else if impls_core_iter(&ctx.sema, &iterable) {
+ format_to!(buf, "{}", iterable);
+ } else if let ast::Expr::RefExpr(_) = iterable {
+ format_to!(buf, "({}).into_iter()", iterable);
+ } else {
+ format_to!(buf, "{}.into_iter()", iterable);
+ }
+
+ format_to!(buf, ".for_each(|{}| {});", pat, body);
+
+ builder.replace(for_loop.syntax().text_range(), buf)
+ },
+ )
+}
+
+/// If iterable is a reference where the expression behind the reference implements a method
+/// returning an Iterator called iter or iter_mut (depending on the type of reference) then return
+/// the expression behind the reference and the method name
+fn is_ref_and_impls_iter_method(
+ sema: &hir::Semantics<'_, ide_db::RootDatabase>,
+ iterable: &ast::Expr,
+) -> Option<(ast::Expr, hir::Name)> {
+ let ref_expr = match iterable {
+ ast::Expr::RefExpr(r) => r,
+ _ => return None,
+ };
+ let wanted_method = if ref_expr.mut_token().is_some() { known::iter_mut } else { known::iter };
+ let expr_behind_ref = ref_expr.expr()?;
+ let ty = sema.type_of_expr(&expr_behind_ref)?.adjusted();
+ let scope = sema.scope(iterable.syntax())?;
+ let krate = scope.krate();
+ let iter_trait = FamousDefs(sema, krate).core_iter_Iterator()?;
+
+ let has_wanted_method = ty
+ .iterate_method_candidates(
+ sema.db,
+ &scope,
+ &scope.visible_traits().0,
+ None,
+ Some(&wanted_method),
+ |func| {
+ if func.ret_type(sema.db).impls_trait(sema.db, iter_trait, &[]) {
+ return Some(());
+ }
+ None
+ },
+ )
+ .is_some();
+ if !has_wanted_method {
+ return None;
+ }
+
+ Some((expr_behind_ref, wanted_method))
+}
+
+/// Whether iterable implements core::Iterator
+fn impls_core_iter(sema: &hir::Semantics<'_, ide_db::RootDatabase>, iterable: &ast::Expr) -> bool {
+ (|| {
+ let it_typ = sema.type_of_expr(iterable)?.adjusted();
+
+ let module = sema.scope(iterable.syntax())?.module();
+
+ let krate = module.krate();
+ let iter_trait = FamousDefs(sema, krate).core_iter_Iterator()?;
+ cov_mark::hit!(test_already_impls_iterator);
+ Some(it_typ.impls_trait(sema.db, iter_trait, &[]))
+ })()
+ .unwrap_or(false)
+}
+
+fn validate_method_call_expr(
+ ctx: &AssistContext<'_>,
+ expr: ast::MethodCallExpr,
+) -> Option<(ast::Expr, ast::Expr)> {
+ let name_ref = expr.name_ref()?;
+ if !name_ref.syntax().text_range().contains_range(ctx.selection_trimmed()) {
+ cov_mark::hit!(test_for_each_not_applicable_invalid_cursor_pos);
+ return None;
+ }
+ if name_ref.text() != "for_each" {
+ return None;
+ }
+
+ let sema = &ctx.sema;
+
+ let receiver = expr.receiver()?;
+ let expr = ast::Expr::MethodCallExpr(expr);
+
+ let it_type = sema.type_of_expr(&receiver)?.adjusted();
+ let module = sema.scope(receiver.syntax())?.module();
+ let krate = module.krate();
+
+ let iter_trait = FamousDefs(sema, krate).core_iter_Iterator()?;
+ it_type.impls_trait(sema.db, iter_trait, &[]).then(|| (expr, receiver))
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn test_for_each_in_method_stmt() {
+ check_assist(
+ convert_iter_for_each_to_for,
+ r#"
+//- minicore: iterators
+fn main() {
+ let it = core::iter::repeat(92);
+ it.$0for_each(|(x, y)| {
+ println!("x: {}, y: {}", x, y);
+ });
+}
+"#,
+ r#"
+fn main() {
+ let it = core::iter::repeat(92);
+ for (x, y) in it {
+ println!("x: {}, y: {}", x, y);
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_for_each_in_method() {
+ check_assist(
+ convert_iter_for_each_to_for,
+ r#"
+//- minicore: iterators
+fn main() {
+ let it = core::iter::repeat(92);
+ it.$0for_each(|(x, y)| {
+ println!("x: {}, y: {}", x, y);
+ })
+}
+"#,
+ r#"
+fn main() {
+ let it = core::iter::repeat(92);
+ for (x, y) in it {
+ println!("x: {}, y: {}", x, y);
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_for_each_without_braces_stmt() {
+ check_assist(
+ convert_iter_for_each_to_for,
+ r#"
+//- minicore: iterators
+fn main() {
+ let it = core::iter::repeat(92);
+ it.$0for_each(|(x, y)| println!("x: {}, y: {}", x, y));
+}
+"#,
+ r#"
+fn main() {
+ let it = core::iter::repeat(92);
+ for (x, y) in it {
+ println!("x: {}, y: {}", x, y)
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_for_each_not_applicable() {
+ check_assist_not_applicable(
+ convert_iter_for_each_to_for,
+ r#"
+//- minicore: iterators
+fn main() {
+ ().$0for_each(|x| println!("{}", x));
+}"#,
+ )
+ }
+
+ #[test]
+ fn test_for_each_not_applicable_invalid_cursor_pos() {
+ cov_mark::check!(test_for_each_not_applicable_invalid_cursor_pos);
+ check_assist_not_applicable(
+ convert_iter_for_each_to_for,
+ r#"
+//- minicore: iterators
+fn main() {
+ core::iter::repeat(92).for_each(|(x, y)| $0println!("x: {}, y: {}", x, y));
+}"#,
+ )
+ }
+
+ #[test]
+ fn each_to_for_not_for() {
+ check_assist_not_applicable(
+ convert_for_loop_with_for_each,
+ r"
+let mut x = vec![1, 2, 3];
+x.iter_mut().$0for_each(|v| *v *= 2);
+ ",
+ )
+ }
+
+ #[test]
+ fn each_to_for_simple_for() {
+ check_assist(
+ convert_for_loop_with_for_each,
+ r"
+fn main() {
+ let x = vec![1, 2, 3];
+ for $0v in x {
+ v *= 2;
+ }
+}",
+ r"
+fn main() {
+ let x = vec![1, 2, 3];
+ x.into_iter().for_each(|v| {
+ v *= 2;
+ });
+}",
+ )
+ }
+
+ #[test]
+ fn each_to_for_for_in_range() {
+ check_assist(
+ convert_for_loop_with_for_each,
+ r#"
+//- minicore: range, iterators
+impl<T> core::iter::Iterator for core::ops::Range<T> {
+ type Item = T;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ None
+ }
+}
+
+fn main() {
+ for $0x in 0..92 {
+ print!("{}", x);
+ }
+}"#,
+ r#"
+impl<T> core::iter::Iterator for core::ops::Range<T> {
+ type Item = T;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ None
+ }
+}
+
+fn main() {
+ (0..92).for_each(|x| {
+ print!("{}", x);
+ });
+}"#,
+ )
+ }
+
+ #[test]
+ fn each_to_for_not_available_in_body() {
+ cov_mark::check!(not_available_in_body);
+ check_assist_not_applicable(
+ convert_for_loop_with_for_each,
+ r"
+fn main() {
+ let x = vec![1, 2, 3];
+ for v in x {
+ $0v *= 2;
+ }
+}",
+ )
+ }
+
+ #[test]
+ fn each_to_for_for_borrowed() {
+ check_assist(
+ convert_for_loop_with_for_each,
+ r#"
+//- minicore: iterators
+use core::iter::{Repeat, repeat};
+
+struct S;
+impl S {
+ fn iter(&self) -> Repeat<i32> { repeat(92) }
+ fn iter_mut(&mut self) -> Repeat<i32> { repeat(92) }
+}
+
+fn main() {
+ let x = S;
+ for $0v in &x {
+ let a = v * 2;
+ }
+}
+"#,
+ r#"
+use core::iter::{Repeat, repeat};
+
+struct S;
+impl S {
+ fn iter(&self) -> Repeat<i32> { repeat(92) }
+ fn iter_mut(&mut self) -> Repeat<i32> { repeat(92) }
+}
+
+fn main() {
+ let x = S;
+ x.iter().for_each(|v| {
+ let a = v * 2;
+ });
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn each_to_for_for_borrowed_no_iter_method() {
+ check_assist(
+ convert_for_loop_with_for_each,
+ r"
+struct NoIterMethod;
+fn main() {
+ let x = NoIterMethod;
+ for $0v in &x {
+ let a = v * 2;
+ }
+}
+",
+ r"
+struct NoIterMethod;
+fn main() {
+ let x = NoIterMethod;
+ (&x).into_iter().for_each(|v| {
+ let a = v * 2;
+ });
+}
+",
+ )
+ }
+
+ #[test]
+ fn each_to_for_for_borrowed_mut() {
+ check_assist(
+ convert_for_loop_with_for_each,
+ r#"
+//- minicore: iterators
+use core::iter::{Repeat, repeat};
+
+struct S;
+impl S {
+ fn iter(&self) -> Repeat<i32> { repeat(92) }
+ fn iter_mut(&mut self) -> Repeat<i32> { repeat(92) }
+}
+
+fn main() {
+ let x = S;
+ for $0v in &mut x {
+ let a = v * 2;
+ }
+}
+"#,
+ r#"
+use core::iter::{Repeat, repeat};
+
+struct S;
+impl S {
+ fn iter(&self) -> Repeat<i32> { repeat(92) }
+ fn iter_mut(&mut self) -> Repeat<i32> { repeat(92) }
+}
+
+fn main() {
+ let x = S;
+ x.iter_mut().for_each(|v| {
+ let a = v * 2;
+ });
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn each_to_for_for_borrowed_mut_behind_var() {
+ check_assist(
+ convert_for_loop_with_for_each,
+ r"
+fn main() {
+ let x = vec![1, 2, 3];
+ let y = &mut x;
+ for $0v in y {
+ *v *= 2;
+ }
+}",
+ r"
+fn main() {
+ let x = vec![1, 2, 3];
+ let y = &mut x;
+ y.into_iter().for_each(|v| {
+ *v *= 2;
+ });
+}",
+ )
+ }
+
+ #[test]
+ fn each_to_for_already_impls_iterator() {
+ cov_mark::check!(test_already_impls_iterator);
+ check_assist(
+ convert_for_loop_with_for_each,
+ r#"
+//- minicore: iterators
+fn main() {
+ for$0 a in core::iter::repeat(92).take(1) {
+ println!("{}", a);
+ }
+}
+"#,
+ r#"
+fn main() {
+ core::iter::repeat(92).take(1).for_each(|a| {
+ println!("{}", a);
+ });
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_let_else_to_match.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_let_else_to_match.rs
new file mode 100644
index 000000000..00095de25
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_let_else_to_match.rs
@@ -0,0 +1,497 @@
+use hir::Semantics;
+use ide_db::RootDatabase;
+use syntax::ast::{edit::AstNodeEdit, AstNode, HasName, LetStmt, Name, Pat};
+use syntax::T;
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+/// Gets a list of binders in a pattern, and whether they are mut.
+fn binders_in_pat(
+ acc: &mut Vec<(Name, bool)>,
+ pat: &Pat,
+ sem: &Semantics<'_, RootDatabase>,
+) -> Option<()> {
+ use Pat::*;
+ match pat {
+ IdentPat(p) => {
+ let ident = p.name()?;
+ let ismut = p.ref_token().is_none() && p.mut_token().is_some();
+ // check for const reference
+ if sem.resolve_bind_pat_to_const(p).is_none() {
+ acc.push((ident, ismut));
+ }
+ if let Some(inner) = p.pat() {
+ binders_in_pat(acc, &inner, sem)?;
+ }
+ Some(())
+ }
+ BoxPat(p) => p.pat().and_then(|p| binders_in_pat(acc, &p, sem)),
+ RestPat(_) | LiteralPat(_) | PathPat(_) | WildcardPat(_) | ConstBlockPat(_) => Some(()),
+ OrPat(p) => {
+ for p in p.pats() {
+ binders_in_pat(acc, &p, sem)?;
+ }
+ Some(())
+ }
+ ParenPat(p) => p.pat().and_then(|p| binders_in_pat(acc, &p, sem)),
+ RangePat(p) => {
+ if let Some(st) = p.start() {
+ binders_in_pat(acc, &st, sem)?
+ }
+ if let Some(ed) = p.end() {
+ binders_in_pat(acc, &ed, sem)?
+ }
+ Some(())
+ }
+ RecordPat(p) => {
+ for f in p.record_pat_field_list()?.fields() {
+ let pat = f.pat()?;
+ binders_in_pat(acc, &pat, sem)?;
+ }
+ Some(())
+ }
+ RefPat(p) => p.pat().and_then(|p| binders_in_pat(acc, &p, sem)),
+ SlicePat(p) => {
+ for p in p.pats() {
+ binders_in_pat(acc, &p, sem)?;
+ }
+ Some(())
+ }
+ TuplePat(p) => {
+ for p in p.fields() {
+ binders_in_pat(acc, &p, sem)?;
+ }
+ Some(())
+ }
+ TupleStructPat(p) => {
+ for p in p.fields() {
+ binders_in_pat(acc, &p, sem)?;
+ }
+ Some(())
+ }
+ // don't support macro pat yet
+ MacroPat(_) => None,
+ }
+}
+
+fn binders_to_str(binders: &[(Name, bool)], addmut: bool) -> String {
+ let vars = binders
+ .iter()
+ .map(
+ |(ident, ismut)| {
+ if *ismut && addmut {
+ format!("mut {}", ident)
+ } else {
+ ident.to_string()
+ }
+ },
+ )
+ .collect::<Vec<_>>()
+ .join(", ");
+ if binders.is_empty() {
+ String::from("{}")
+ } else if binders.len() == 1 {
+ vars
+ } else {
+ format!("({})", vars)
+ }
+}
+
+// Assist: convert_let_else_to_match
+//
+// Converts let-else statement to let statement and match expression.
+//
+// ```
+// fn main() {
+// let Ok(mut x) = f() else$0 { return };
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// let mut x = match f() {
+// Ok(x) => x,
+// _ => return,
+// };
+// }
+// ```
+pub(crate) fn convert_let_else_to_match(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ // should focus on else token to trigger
+ let else_token = ctx.find_token_syntax_at_offset(T![else])?;
+ let let_stmt = LetStmt::cast(else_token.parent()?.parent()?)?;
+ let let_else_block = let_stmt.let_else()?.block_expr()?;
+ let let_init = let_stmt.initializer()?;
+ if let_stmt.ty().is_some() {
+ // don't support let with type annotation
+ return None;
+ }
+ let pat = let_stmt.pat()?;
+ let mut binders = Vec::new();
+ binders_in_pat(&mut binders, &pat, &ctx.sema)?;
+
+ let target = let_stmt.syntax().text_range();
+ acc.add(
+ AssistId("convert_let_else_to_match", AssistKind::RefactorRewrite),
+ "Convert let-else to let and match",
+ target,
+ |edit| {
+ let indent_level = let_stmt.indent_level().0 as usize;
+ let indent = " ".repeat(indent_level);
+ let indent1 = " ".repeat(indent_level + 1);
+
+ let binders_str = binders_to_str(&binders, false);
+ let binders_str_mut = binders_to_str(&binders, true);
+
+ let init_expr = let_init.syntax().text();
+ let mut pat_no_mut = pat.syntax().text().to_string();
+ // remove the mut from the pattern
+ for (b, ismut) in binders.iter() {
+ if *ismut {
+ pat_no_mut = pat_no_mut.replace(&format!("mut {b}"), &b.to_string());
+ }
+ }
+
+ let only_expr = let_else_block.statements().next().is_none();
+ let branch2 = match &let_else_block.tail_expr() {
+ Some(tail) if only_expr => format!("{},", tail.syntax().text()),
+ _ => let_else_block.syntax().text().to_string(),
+ };
+ let replace = if binders.is_empty() {
+ format!(
+ "match {init_expr} {{
+{indent1}{pat_no_mut} => {binders_str}
+{indent1}_ => {branch2}
+{indent}}}"
+ )
+ } else {
+ format!(
+ "let {binders_str_mut} = match {init_expr} {{
+{indent1}{pat_no_mut} => {binders_str},
+{indent1}_ => {branch2}
+{indent}}};"
+ )
+ };
+ edit.replace(target, replace);
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
+
+ #[test]
+ fn convert_let_else_to_match_no_type_let() {
+ check_assist_not_applicable(
+ convert_let_else_to_match,
+ r#"
+fn main() {
+ let 1: u32 = v.iter().sum() else$0 { return };
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_let_else_to_match_on_else() {
+ check_assist_not_applicable(
+ convert_let_else_to_match,
+ r#"
+fn main() {
+ let Ok(x) = f() else {$0 return };
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn convert_let_else_to_match_no_macropat() {
+ check_assist_not_applicable(
+ convert_let_else_to_match,
+ r#"
+fn main() {
+ let m!() = g() else$0 { return };
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn convert_let_else_to_match_target() {
+ check_assist_target(
+ convert_let_else_to_match,
+ r"
+fn main() {
+ let Ok(x) = f() else$0 { continue };
+}",
+ "let Ok(x) = f() else { continue };",
+ );
+ }
+
+ #[test]
+ fn convert_let_else_to_match_basic() {
+ check_assist(
+ convert_let_else_to_match,
+ r"
+fn main() {
+ let Ok(x) = f() else$0 { continue };
+}",
+ r"
+fn main() {
+ let x = match f() {
+ Ok(x) => x,
+ _ => continue,
+ };
+}",
+ );
+ }
+
+ #[test]
+ fn convert_let_else_to_match_const_ref() {
+ check_assist(
+ convert_let_else_to_match,
+ r"
+enum Option<T> {
+ Some(T),
+ None,
+}
+use Option::*;
+fn main() {
+ let None = f() el$0se { continue };
+}",
+ r"
+enum Option<T> {
+ Some(T),
+ None,
+}
+use Option::*;
+fn main() {
+ match f() {
+ None => {}
+ _ => continue,
+ }
+}",
+ );
+ }
+
+ #[test]
+ fn convert_let_else_to_match_const_ref_const() {
+ check_assist(
+ convert_let_else_to_match,
+ r"
+const NEG1: i32 = -1;
+fn main() {
+ let NEG1 = f() el$0se { continue };
+}",
+ r"
+const NEG1: i32 = -1;
+fn main() {
+ match f() {
+ NEG1 => {}
+ _ => continue,
+ }
+}",
+ );
+ }
+
+ #[test]
+ fn convert_let_else_to_match_mut() {
+ check_assist(
+ convert_let_else_to_match,
+ r"
+fn main() {
+ let Ok(mut x) = f() el$0se { continue };
+}",
+ r"
+fn main() {
+ let mut x = match f() {
+ Ok(x) => x,
+ _ => continue,
+ };
+}",
+ );
+ }
+
+ #[test]
+ fn convert_let_else_to_match_multi_binders() {
+ check_assist(
+ convert_let_else_to_match,
+ r#"
+fn main() {
+ let ControlFlow::Break((x, "tag", y, ..)) = f() else$0 { g(); return };
+}"#,
+ r#"
+fn main() {
+ let (x, y) = match f() {
+ ControlFlow::Break((x, "tag", y, ..)) => (x, y),
+ _ => { g(); return }
+ };
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_let_else_to_match_slice() {
+ check_assist(
+ convert_let_else_to_match,
+ r#"
+fn main() {
+ let [one, 1001, other] = f() else$0 { break };
+}"#,
+ r#"
+fn main() {
+ let (one, other) = match f() {
+ [one, 1001, other] => (one, other),
+ _ => break,
+ };
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_let_else_to_match_struct() {
+ check_assist(
+ convert_let_else_to_match,
+ r#"
+fn main() {
+ let [Struct { inner: Some(it) }, 1001, other] = f() else$0 { break };
+}"#,
+ r#"
+fn main() {
+ let (it, other) = match f() {
+ [Struct { inner: Some(it) }, 1001, other] => (it, other),
+ _ => break,
+ };
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_let_else_to_match_struct_ident_pat() {
+ check_assist(
+ convert_let_else_to_match,
+ r#"
+fn main() {
+ let [Struct { inner }, 1001, other] = f() else$0 { break };
+}"#,
+ r#"
+fn main() {
+ let (inner, other) = match f() {
+ [Struct { inner }, 1001, other] => (inner, other),
+ _ => break,
+ };
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_let_else_to_match_no_binder() {
+ check_assist(
+ convert_let_else_to_match,
+ r#"
+fn main() {
+ let (8 | 9) = f() else$0 { panic!() };
+}"#,
+ r#"
+fn main() {
+ match f() {
+ (8 | 9) => {}
+ _ => panic!(),
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_let_else_to_match_range() {
+ check_assist(
+ convert_let_else_to_match,
+ r#"
+fn main() {
+ let 1.. = f() e$0lse { return };
+}"#,
+ r#"
+fn main() {
+ match f() {
+ 1.. => {}
+ _ => return,
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_let_else_to_match_refpat() {
+ check_assist(
+ convert_let_else_to_match,
+ r#"
+fn main() {
+ let Ok(&mut x) = f(&mut 0) else$0 { return };
+}"#,
+ r#"
+fn main() {
+ let x = match f(&mut 0) {
+ Ok(&mut x) => x,
+ _ => return,
+ };
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_let_else_to_match_refmut() {
+ check_assist(
+ convert_let_else_to_match,
+ r#"
+fn main() {
+ let Ok(ref mut x) = f() else$0 { return };
+}"#,
+ r#"
+fn main() {
+ let x = match f() {
+ Ok(ref mut x) => x,
+ _ => return,
+ };
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_let_else_to_match_atpat() {
+ check_assist(
+ convert_let_else_to_match,
+ r#"
+fn main() {
+ let out @ Ok(ins) = f() else$0 { return };
+}"#,
+ r#"
+fn main() {
+ let (out, ins) = match f() {
+ out @ Ok(ins) => (out, ins),
+ _ => return,
+ };
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_let_else_to_match_complex_init() {
+ check_assist(
+ convert_let_else_to_match,
+ r#"
+fn main() {
+ let v = vec![1, 2, 3];
+ let &[mut x, y, ..] = &v.iter().collect::<Vec<_>>()[..] else$0 { return };
+}"#,
+ r#"
+fn main() {
+ let v = vec![1, 2, 3];
+ let (mut x, y) = match &v.iter().collect::<Vec<_>>()[..] {
+ &[x, y, ..] => (x, y),
+ _ => return,
+ };
+}"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs
new file mode 100644
index 000000000..cb75619ce
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs
@@ -0,0 +1,574 @@
+use std::iter::once;
+
+use ide_db::syntax_helpers::node_ext::{is_pattern_cond, single_let};
+use syntax::{
+ ast::{
+ self,
+ edit::{AstNodeEdit, IndentLevel},
+ make,
+ },
+ ted, AstNode,
+ SyntaxKind::{FN, LOOP_EXPR, WHILE_EXPR, WHITESPACE},
+ T,
+};
+
+use crate::{
+ assist_context::{AssistContext, Assists},
+ utils::invert_boolean_expression,
+ AssistId, AssistKind,
+};
+
+// Assist: convert_to_guarded_return
+//
+// Replace a large conditional with a guarded return.
+//
+// ```
+// fn main() {
+// $0if cond {
+// foo();
+// bar();
+// }
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// if !cond {
+// return;
+// }
+// foo();
+// bar();
+// }
+// ```
+pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
+ if if_expr.else_branch().is_some() {
+ return None;
+ }
+
+ let cond = if_expr.condition()?;
+
+ // Check if there is an IfLet that we can handle.
+ let (if_let_pat, cond_expr) = if is_pattern_cond(cond.clone()) {
+ let let_ = single_let(cond)?;
+ match let_.pat() {
+ Some(ast::Pat::TupleStructPat(pat)) if pat.fields().count() == 1 => {
+ let path = pat.path()?;
+ if path.qualifier().is_some() {
+ return None;
+ }
+
+ let bound_ident = pat.fields().next().unwrap();
+ if !ast::IdentPat::can_cast(bound_ident.syntax().kind()) {
+ return None;
+ }
+
+ (Some((path, bound_ident)), let_.expr()?)
+ }
+ _ => return None, // Unsupported IfLet.
+ }
+ } else {
+ (None, cond)
+ };
+
+ let then_block = if_expr.then_branch()?;
+ let then_block = then_block.stmt_list()?;
+
+ let parent_block = if_expr.syntax().parent()?.ancestors().find_map(ast::BlockExpr::cast)?;
+
+ if parent_block.tail_expr()? != if_expr.clone().into() {
+ return None;
+ }
+
+ // FIXME: This relies on untyped syntax tree and casts to much. It should be
+ // rewritten to use strongly-typed APIs.
+
+ // check for early return and continue
+ let first_in_then_block = then_block.syntax().first_child()?;
+ if ast::ReturnExpr::can_cast(first_in_then_block.kind())
+ || ast::ContinueExpr::can_cast(first_in_then_block.kind())
+ || first_in_then_block
+ .children()
+ .any(|x| ast::ReturnExpr::can_cast(x.kind()) || ast::ContinueExpr::can_cast(x.kind()))
+ {
+ return None;
+ }
+
+ let parent_container = parent_block.syntax().parent()?;
+
+ let early_expression: ast::Expr = match parent_container.kind() {
+ WHILE_EXPR | LOOP_EXPR => make::expr_continue(None),
+ FN => make::expr_return(None),
+ _ => return None,
+ };
+
+ if then_block.syntax().first_child_or_token().map(|t| t.kind() == T!['{']).is_none() {
+ return None;
+ }
+
+ then_block.syntax().last_child_or_token().filter(|t| t.kind() == T!['}'])?;
+
+ let target = if_expr.syntax().text_range();
+ acc.add(
+ AssistId("convert_to_guarded_return", AssistKind::RefactorRewrite),
+ "Convert to guarded return",
+ target,
+ |edit| {
+ let if_expr = edit.make_mut(if_expr);
+ let if_indent_level = IndentLevel::from_node(if_expr.syntax());
+ let replacement = match if_let_pat {
+ None => {
+ // If.
+ let new_expr = {
+ let then_branch =
+ make::block_expr(once(make::expr_stmt(early_expression).into()), None);
+ let cond = invert_boolean_expression(cond_expr);
+ make::expr_if(cond, then_branch, None).indent(if_indent_level)
+ };
+ new_expr.syntax().clone_for_update()
+ }
+ Some((path, bound_ident)) => {
+ // If-let.
+ let match_expr = {
+ let happy_arm = {
+ let pat = make::tuple_struct_pat(
+ path,
+ once(make::ext::simple_ident_pat(make::name("it")).into()),
+ );
+ let expr = {
+ let path = make::ext::ident_path("it");
+ make::expr_path(path)
+ };
+ make::match_arm(once(pat.into()), None, expr)
+ };
+
+ let sad_arm = make::match_arm(
+ // FIXME: would be cool to use `None` or `Err(_)` if appropriate
+ once(make::wildcard_pat().into()),
+ None,
+ early_expression,
+ );
+
+ make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm]))
+ };
+
+ let let_stmt = make::let_stmt(bound_ident, None, Some(match_expr));
+ let let_stmt = let_stmt.indent(if_indent_level);
+ let_stmt.syntax().clone_for_update()
+ }
+ };
+
+ let then_block_items = then_block.dedent(IndentLevel(1)).clone_for_update();
+
+ let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
+ let end_of_then =
+ if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
+ end_of_then.prev_sibling_or_token().unwrap()
+ } else {
+ end_of_then
+ };
+
+ let then_statements = replacement
+ .children_with_tokens()
+ .chain(
+ then_block_items
+ .syntax()
+ .children_with_tokens()
+ .skip(1)
+ .take_while(|i| *i != end_of_then),
+ )
+ .collect();
+
+ ted::replace_with_many(if_expr.syntax(), then_statements)
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn convert_inside_fn() {
+ check_assist(
+ convert_to_guarded_return,
+ r#"
+fn main() {
+ bar();
+ if$0 true {
+ foo();
+
+ // comment
+ bar();
+ }
+}
+"#,
+ r#"
+fn main() {
+ bar();
+ if false {
+ return;
+ }
+ foo();
+
+ // comment
+ bar();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn convert_let_inside_fn() {
+ check_assist(
+ convert_to_guarded_return,
+ r#"
+fn main(n: Option<String>) {
+ bar();
+ if$0 let Some(n) = n {
+ foo(n);
+
+ // comment
+ bar();
+ }
+}
+"#,
+ r#"
+fn main(n: Option<String>) {
+ bar();
+ let n = match n {
+ Some(it) => it,
+ _ => return,
+ };
+ foo(n);
+
+ // comment
+ bar();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn convert_if_let_result() {
+ check_assist(
+ convert_to_guarded_return,
+ r#"
+fn main() {
+ if$0 let Ok(x) = Err(92) {
+ foo(x);
+ }
+}
+"#,
+ r#"
+fn main() {
+ let x = match Err(92) {
+ Ok(it) => it,
+ _ => return,
+ };
+ foo(x);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn convert_let_ok_inside_fn() {
+ check_assist(
+ convert_to_guarded_return,
+ r#"
+fn main(n: Option<String>) {
+ bar();
+ if$0 let Some(n) = n {
+ foo(n);
+
+ // comment
+ bar();
+ }
+}
+"#,
+ r#"
+fn main(n: Option<String>) {
+ bar();
+ let n = match n {
+ Some(it) => it,
+ _ => return,
+ };
+ foo(n);
+
+ // comment
+ bar();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn convert_let_mut_ok_inside_fn() {
+ check_assist(
+ convert_to_guarded_return,
+ r#"
+fn main(n: Option<String>) {
+ bar();
+ if$0 let Some(mut n) = n {
+ foo(n);
+
+ // comment
+ bar();
+ }
+}
+"#,
+ r#"
+fn main(n: Option<String>) {
+ bar();
+ let mut n = match n {
+ Some(it) => it,
+ _ => return,
+ };
+ foo(n);
+
+ // comment
+ bar();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn convert_let_ref_ok_inside_fn() {
+ check_assist(
+ convert_to_guarded_return,
+ r#"
+fn main(n: Option<&str>) {
+ bar();
+ if$0 let Some(ref n) = n {
+ foo(n);
+
+ // comment
+ bar();
+ }
+}
+"#,
+ r#"
+fn main(n: Option<&str>) {
+ bar();
+ let ref n = match n {
+ Some(it) => it,
+ _ => return,
+ };
+ foo(n);
+
+ // comment
+ bar();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn convert_inside_while() {
+ check_assist(
+ convert_to_guarded_return,
+ r#"
+fn main() {
+ while true {
+ if$0 true {
+ foo();
+ bar();
+ }
+ }
+}
+"#,
+ r#"
+fn main() {
+ while true {
+ if false {
+ continue;
+ }
+ foo();
+ bar();
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn convert_let_inside_while() {
+ check_assist(
+ convert_to_guarded_return,
+ r#"
+fn main() {
+ while true {
+ if$0 let Some(n) = n {
+ foo(n);
+ bar();
+ }
+ }
+}
+"#,
+ r#"
+fn main() {
+ while true {
+ let n = match n {
+ Some(it) => it,
+ _ => continue,
+ };
+ foo(n);
+ bar();
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn convert_inside_loop() {
+ check_assist(
+ convert_to_guarded_return,
+ r#"
+fn main() {
+ loop {
+ if$0 true {
+ foo();
+ bar();
+ }
+ }
+}
+"#,
+ r#"
+fn main() {
+ loop {
+ if false {
+ continue;
+ }
+ foo();
+ bar();
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn convert_let_inside_loop() {
+ check_assist(
+ convert_to_guarded_return,
+ r#"
+fn main() {
+ loop {
+ if$0 let Some(n) = n {
+ foo(n);
+ bar();
+ }
+ }
+}
+"#,
+ r#"
+fn main() {
+ loop {
+ let n = match n {
+ Some(it) => it,
+ _ => continue,
+ };
+ foo(n);
+ bar();
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn ignore_already_converted_if() {
+ check_assist_not_applicable(
+ convert_to_guarded_return,
+ r#"
+fn main() {
+ if$0 true {
+ return;
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn ignore_already_converted_loop() {
+ check_assist_not_applicable(
+ convert_to_guarded_return,
+ r#"
+fn main() {
+ loop {
+ if$0 true {
+ continue;
+ }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn ignore_return() {
+ check_assist_not_applicable(
+ convert_to_guarded_return,
+ r#"
+fn main() {
+ if$0 true {
+ return
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn ignore_else_branch() {
+ check_assist_not_applicable(
+ convert_to_guarded_return,
+ r#"
+fn main() {
+ if$0 true {
+ foo();
+ } else {
+ bar()
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn ignore_statements_aftert_if() {
+ check_assist_not_applicable(
+ convert_to_guarded_return,
+ r#"
+fn main() {
+ if$0 true {
+ foo();
+ }
+ bar();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn ignore_statements_inside_if() {
+ check_assist_not_applicable(
+ convert_to_guarded_return,
+ r#"
+fn main() {
+ if false {
+ if$0 true {
+ foo();
+ }
+ }
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs
new file mode 100644
index 000000000..4ab8e93a2
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs
@@ -0,0 +1,840 @@
+use either::Either;
+use ide_db::defs::{Definition, NameRefClass};
+use syntax::{
+ ast::{self, AstNode, HasGenericParams, HasVisibility},
+ match_ast, SyntaxNode,
+};
+
+use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: convert_tuple_struct_to_named_struct
+//
+// Converts tuple struct to struct with named fields, and analogously for tuple enum variants.
+//
+// ```
+// struct Point$0(f32, f32);
+//
+// impl Point {
+// pub fn new(x: f32, y: f32) -> Self {
+// Point(x, y)
+// }
+//
+// pub fn x(&self) -> f32 {
+// self.0
+// }
+//
+// pub fn y(&self) -> f32 {
+// self.1
+// }
+// }
+// ```
+// ->
+// ```
+// struct Point { field1: f32, field2: f32 }
+//
+// impl Point {
+// pub fn new(x: f32, y: f32) -> Self {
+// Point { field1: x, field2: y }
+// }
+//
+// pub fn x(&self) -> f32 {
+// self.field1
+// }
+//
+// pub fn y(&self) -> f32 {
+// self.field2
+// }
+// }
+// ```
+pub(crate) fn convert_tuple_struct_to_named_struct(
+ acc: &mut Assists,
+ ctx: &AssistContext<'_>,
+) -> Option<()> {
+ let strukt = ctx
+ .find_node_at_offset::<ast::Struct>()
+ .map(Either::Left)
+ .or_else(|| ctx.find_node_at_offset::<ast::Variant>().map(Either::Right))?;
+ let field_list = strukt.as_ref().either(|s| s.field_list(), |v| v.field_list())?;
+ let tuple_fields = match field_list {
+ ast::FieldList::TupleFieldList(it) => it,
+ ast::FieldList::RecordFieldList(_) => return None,
+ };
+ let strukt_def = match &strukt {
+ Either::Left(s) => Either::Left(ctx.sema.to_def(s)?),
+ Either::Right(v) => Either::Right(ctx.sema.to_def(v)?),
+ };
+ let target = strukt.as_ref().either(|s| s.syntax(), |v| v.syntax()).text_range();
+
+ acc.add(
+ AssistId("convert_tuple_struct_to_named_struct", AssistKind::RefactorRewrite),
+ "Convert to named struct",
+ target,
+ |edit| {
+ let names = generate_names(tuple_fields.fields());
+ edit_field_references(ctx, edit, tuple_fields.fields(), &names);
+ edit_struct_references(ctx, edit, strukt_def, &names);
+ edit_struct_def(ctx, edit, &strukt, tuple_fields, names);
+ },
+ )
+}
+
+fn edit_struct_def(
+ ctx: &AssistContext<'_>,
+ edit: &mut AssistBuilder,
+ strukt: &Either<ast::Struct, ast::Variant>,
+ tuple_fields: ast::TupleFieldList,
+ names: Vec<ast::Name>,
+) {
+ let record_fields = tuple_fields
+ .fields()
+ .zip(names)
+ .filter_map(|(f, name)| Some(ast::make::record_field(f.visibility(), name, f.ty()?)));
+ let record_fields = ast::make::record_field_list(record_fields);
+ let tuple_fields_text_range = tuple_fields.syntax().text_range();
+
+ edit.edit_file(ctx.file_id());
+
+ if let Either::Left(strukt) = strukt {
+ if let Some(w) = strukt.where_clause() {
+ edit.delete(w.syntax().text_range());
+ edit.insert(
+ tuple_fields_text_range.start(),
+ ast::make::tokens::single_newline().text(),
+ );
+ edit.insert(tuple_fields_text_range.start(), w.syntax().text());
+ edit.insert(tuple_fields_text_range.start(), ",");
+ edit.insert(
+ tuple_fields_text_range.start(),
+ ast::make::tokens::single_newline().text(),
+ );
+ } else {
+ edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text());
+ }
+ if let Some(t) = strukt.semicolon_token() {
+ edit.delete(t.text_range());
+ }
+ } else {
+ edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text());
+ }
+
+ edit.replace(tuple_fields_text_range, record_fields.to_string());
+}
+
+fn edit_struct_references(
+ ctx: &AssistContext<'_>,
+ edit: &mut AssistBuilder,
+ strukt: Either<hir::Struct, hir::Variant>,
+ names: &[ast::Name],
+) {
+ let strukt_def = match strukt {
+ Either::Left(s) => Definition::Adt(hir::Adt::Struct(s)),
+ Either::Right(v) => Definition::Variant(v),
+ };
+ let usages = strukt_def.usages(&ctx.sema).include_self_refs().all();
+
+ let edit_node = |edit: &mut AssistBuilder, node: SyntaxNode| -> Option<()> {
+ match_ast! {
+ match node {
+ ast::TupleStructPat(tuple_struct_pat) => {
+ edit.replace(
+ tuple_struct_pat.syntax().text_range(),
+ ast::make::record_pat_with_fields(
+ tuple_struct_pat.path()?,
+ ast::make::record_pat_field_list(tuple_struct_pat.fields().zip(names).map(
+ |(pat, name)| {
+ ast::make::record_pat_field(
+ ast::make::name_ref(&name.to_string()),
+ pat,
+ )
+ },
+ )),
+ )
+ .to_string(),
+ );
+ },
+ // for tuple struct creations like Foo(42)
+ ast::CallExpr(call_expr) => {
+ let path = call_expr.syntax().descendants().find_map(ast::PathExpr::cast).and_then(|expr| expr.path())?;
+
+ // this also includes method calls like Foo::new(42), we should skip them
+ if let Some(name_ref) = path.segment().and_then(|s| s.name_ref()) {
+ match NameRefClass::classify(&ctx.sema, &name_ref) {
+ Some(NameRefClass::Definition(Definition::SelfType(_))) => {},
+ Some(NameRefClass::Definition(def)) if def == strukt_def => {},
+ _ => return None,
+ };
+ }
+
+ let arg_list = call_expr.syntax().descendants().find_map(ast::ArgList::cast)?;
+
+ edit.replace(
+ call_expr.syntax().text_range(),
+ ast::make::record_expr(
+ path,
+ ast::make::record_expr_field_list(arg_list.args().zip(names).map(
+ |(expr, name)| {
+ ast::make::record_expr_field(
+ ast::make::name_ref(&name.to_string()),
+ Some(expr),
+ )
+ },
+ )),
+ )
+ .to_string(),
+ );
+ },
+ _ => return None,
+ }
+ }
+ Some(())
+ };
+
+ for (file_id, refs) in usages {
+ edit.edit_file(file_id);
+ for r in refs {
+ for node in r.name.syntax().ancestors() {
+ if edit_node(edit, node).is_some() {
+ break;
+ }
+ }
+ }
+ }
+}
+
+fn edit_field_references(
+ ctx: &AssistContext<'_>,
+ edit: &mut AssistBuilder,
+ fields: impl Iterator<Item = ast::TupleField>,
+ names: &[ast::Name],
+) {
+ for (field, name) in fields.zip(names) {
+ let field = match ctx.sema.to_def(&field) {
+ Some(it) => it,
+ None => continue,
+ };
+ let def = Definition::Field(field);
+ let usages = def.usages(&ctx.sema).all();
+ for (file_id, refs) in usages {
+ edit.edit_file(file_id);
+ for r in refs {
+ if let Some(name_ref) = r.name.as_name_ref() {
+ edit.replace(name_ref.syntax().text_range(), name.text());
+ }
+ }
+ }
+ }
+}
+
+fn generate_names(fields: impl Iterator<Item = ast::TupleField>) -> Vec<ast::Name> {
+ fields.enumerate().map(|(i, _)| ast::make::name(&format!("field{}", i + 1))).collect()
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn not_applicable_other_than_tuple_struct() {
+ check_assist_not_applicable(
+ convert_tuple_struct_to_named_struct,
+ r#"struct Foo$0 { bar: u32 };"#,
+ );
+ check_assist_not_applicable(convert_tuple_struct_to_named_struct, r#"struct Foo$0;"#);
+ }
+
+ #[test]
+ fn convert_simple_struct() {
+ check_assist(
+ convert_tuple_struct_to_named_struct,
+ r#"
+struct Inner;
+struct A$0(Inner);
+
+impl A {
+ fn new(inner: Inner) -> A {
+ A(inner)
+ }
+
+ fn new_with_default() -> A {
+ A::new(Inner)
+ }
+
+ fn into_inner(self) -> Inner {
+ self.0
+ }
+}"#,
+ r#"
+struct Inner;
+struct A { field1: Inner }
+
+impl A {
+ fn new(inner: Inner) -> A {
+ A { field1: inner }
+ }
+
+ fn new_with_default() -> A {
+ A::new(Inner)
+ }
+
+ fn into_inner(self) -> Inner {
+ self.field1
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_struct_referenced_via_self_kw() {
+ check_assist(
+ convert_tuple_struct_to_named_struct,
+ r#"
+struct Inner;
+struct A$0(Inner);
+
+impl A {
+ fn new(inner: Inner) -> Self {
+ Self(inner)
+ }
+
+ fn new_with_default() -> Self {
+ Self::new(Inner)
+ }
+
+ fn into_inner(self) -> Inner {
+ self.0
+ }
+}"#,
+ r#"
+struct Inner;
+struct A { field1: Inner }
+
+impl A {
+ fn new(inner: Inner) -> Self {
+ Self { field1: inner }
+ }
+
+ fn new_with_default() -> Self {
+ Self::new(Inner)
+ }
+
+ fn into_inner(self) -> Inner {
+ self.field1
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_destructured_struct() {
+ check_assist(
+ convert_tuple_struct_to_named_struct,
+ r#"
+struct Inner;
+struct A$0(Inner);
+
+impl A {
+ fn into_inner(self) -> Inner {
+ let A(first) = self;
+ first
+ }
+
+ fn into_inner_via_self(self) -> Inner {
+ let Self(first) = self;
+ first
+ }
+}"#,
+ r#"
+struct Inner;
+struct A { field1: Inner }
+
+impl A {
+ fn into_inner(self) -> Inner {
+ let A { field1: first } = self;
+ first
+ }
+
+ fn into_inner_via_self(self) -> Inner {
+ let Self { field1: first } = self;
+ first
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_struct_with_visibility() {
+ check_assist(
+ convert_tuple_struct_to_named_struct,
+ r#"
+struct A$0(pub u32, pub(crate) u64);
+
+impl A {
+ fn new() -> A {
+ A(42, 42)
+ }
+
+ fn into_first(self) -> u32 {
+ self.0
+ }
+
+ fn into_second(self) -> u64 {
+ self.1
+ }
+}"#,
+ r#"
+struct A { pub field1: u32, pub(crate) field2: u64 }
+
+impl A {
+ fn new() -> A {
+ A { field1: 42, field2: 42 }
+ }
+
+ fn into_first(self) -> u32 {
+ self.field1
+ }
+
+ fn into_second(self) -> u64 {
+ self.field2
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_struct_with_wrapped_references() {
+ check_assist(
+ convert_tuple_struct_to_named_struct,
+ r#"
+struct Inner$0(u32);
+struct Outer(Inner);
+
+impl Outer {
+ fn new() -> Self {
+ Self(Inner(42))
+ }
+
+ fn into_inner(self) -> u32 {
+ (self.0).0
+ }
+
+ fn into_inner_destructed(self) -> u32 {
+ let Outer(Inner(x)) = self;
+ x
+ }
+}"#,
+ r#"
+struct Inner { field1: u32 }
+struct Outer(Inner);
+
+impl Outer {
+ fn new() -> Self {
+ Self(Inner { field1: 42 })
+ }
+
+ fn into_inner(self) -> u32 {
+ (self.0).field1
+ }
+
+ fn into_inner_destructed(self) -> u32 {
+ let Outer(Inner { field1: x }) = self;
+ x
+ }
+}"#,
+ );
+
+ check_assist(
+ convert_tuple_struct_to_named_struct,
+ r#"
+struct Inner(u32);
+struct Outer$0(Inner);
+
+impl Outer {
+ fn new() -> Self {
+ Self(Inner(42))
+ }
+
+ fn into_inner(self) -> u32 {
+ (self.0).0
+ }
+
+ fn into_inner_destructed(self) -> u32 {
+ let Outer(Inner(x)) = self;
+ x
+ }
+}"#,
+ r#"
+struct Inner(u32);
+struct Outer { field1: Inner }
+
+impl Outer {
+ fn new() -> Self {
+ Self { field1: Inner(42) }
+ }
+
+ fn into_inner(self) -> u32 {
+ (self.field1).0
+ }
+
+ fn into_inner_destructed(self) -> u32 {
+ let Outer { field1: Inner(x) } = self;
+ x
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_struct_with_multi_file_references() {
+ check_assist(
+ convert_tuple_struct_to_named_struct,
+ r#"
+//- /main.rs
+struct Inner;
+struct A$0(Inner);
+
+mod foo;
+
+//- /foo.rs
+use crate::{A, Inner};
+fn f() {
+ let a = A(Inner);
+}
+"#,
+ r#"
+//- /main.rs
+struct Inner;
+struct A { field1: Inner }
+
+mod foo;
+
+//- /foo.rs
+use crate::{A, Inner};
+fn f() {
+ let a = A { field1: Inner };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn convert_struct_with_where_clause() {
+ check_assist(
+ convert_tuple_struct_to_named_struct,
+ r#"
+struct Wrap$0<T>(T)
+where
+ T: Display;
+"#,
+ r#"
+struct Wrap<T>
+where
+ T: Display,
+{ field1: T }
+
+"#,
+ );
+ }
+ #[test]
+ fn not_applicable_other_than_tuple_variant() {
+ check_assist_not_applicable(
+ convert_tuple_struct_to_named_struct,
+ r#"enum Enum { Variant$0 { value: usize } };"#,
+ );
+ check_assist_not_applicable(
+ convert_tuple_struct_to_named_struct,
+ r#"enum Enum { Variant$0 }"#,
+ );
+ }
+
+ #[test]
+ fn convert_simple_variant() {
+ check_assist(
+ convert_tuple_struct_to_named_struct,
+ r#"
+enum A {
+ $0Variant(usize),
+}
+
+impl A {
+ fn new(value: usize) -> A {
+ A::Variant(value)
+ }
+
+ fn new_with_default() -> A {
+ A::new(Default::default())
+ }
+
+ fn value(self) -> usize {
+ match self {
+ A::Variant(value) => value,
+ }
+ }
+}"#,
+ r#"
+enum A {
+ Variant { field1: usize },
+}
+
+impl A {
+ fn new(value: usize) -> A {
+ A::Variant { field1: value }
+ }
+
+ fn new_with_default() -> A {
+ A::new(Default::default())
+ }
+
+ fn value(self) -> usize {
+ match self {
+ A::Variant { field1: value } => value,
+ }
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_variant_referenced_via_self_kw() {
+ check_assist(
+ convert_tuple_struct_to_named_struct,
+ r#"
+enum A {
+ $0Variant(usize),
+}
+
+impl A {
+ fn new(value: usize) -> A {
+ Self::Variant(value)
+ }
+
+ fn new_with_default() -> A {
+ Self::new(Default::default())
+ }
+
+ fn value(self) -> usize {
+ match self {
+ Self::Variant(value) => value,
+ }
+ }
+}"#,
+ r#"
+enum A {
+ Variant { field1: usize },
+}
+
+impl A {
+ fn new(value: usize) -> A {
+ Self::Variant { field1: value }
+ }
+
+ fn new_with_default() -> A {
+ Self::new(Default::default())
+ }
+
+ fn value(self) -> usize {
+ match self {
+ Self::Variant { field1: value } => value,
+ }
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_destructured_variant() {
+ check_assist(
+ convert_tuple_struct_to_named_struct,
+ r#"
+enum A {
+ $0Variant(usize),
+}
+
+impl A {
+ fn into_inner(self) -> usize {
+ let A::Variant(first) = self;
+ first
+ }
+
+ fn into_inner_via_self(self) -> usize {
+ let Self::Variant(first) = self;
+ first
+ }
+}"#,
+ r#"
+enum A {
+ Variant { field1: usize },
+}
+
+impl A {
+ fn into_inner(self) -> usize {
+ let A::Variant { field1: first } = self;
+ first
+ }
+
+ fn into_inner_via_self(self) -> usize {
+ let Self::Variant { field1: first } = self;
+ first
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_variant_with_wrapped_references() {
+ check_assist(
+ convert_tuple_struct_to_named_struct,
+ r#"
+enum Inner {
+ $0Variant(usize),
+}
+enum Outer {
+ Variant(Inner),
+}
+
+impl Outer {
+ fn new() -> Self {
+ Self::Variant(Inner::Variant(42))
+ }
+
+ fn into_inner_destructed(self) -> u32 {
+ let Outer::Variant(Inner::Variant(x)) = self;
+ x
+ }
+}"#,
+ r#"
+enum Inner {
+ Variant { field1: usize },
+}
+enum Outer {
+ Variant(Inner),
+}
+
+impl Outer {
+ fn new() -> Self {
+ Self::Variant(Inner::Variant { field1: 42 })
+ }
+
+ fn into_inner_destructed(self) -> u32 {
+ let Outer::Variant(Inner::Variant { field1: x }) = self;
+ x
+ }
+}"#,
+ );
+
+ check_assist(
+ convert_tuple_struct_to_named_struct,
+ r#"
+enum Inner {
+ Variant(usize),
+}
+enum Outer {
+ $0Variant(Inner),
+}
+
+impl Outer {
+ fn new() -> Self {
+ Self::Variant(Inner::Variant(42))
+ }
+
+ fn into_inner_destructed(self) -> u32 {
+ let Outer::Variant(Inner::Variant(x)) = self;
+ x
+ }
+}"#,
+ r#"
+enum Inner {
+ Variant(usize),
+}
+enum Outer {
+ Variant { field1: Inner },
+}
+
+impl Outer {
+ fn new() -> Self {
+ Self::Variant { field1: Inner::Variant(42) }
+ }
+
+ fn into_inner_destructed(self) -> u32 {
+ let Outer::Variant { field1: Inner::Variant(x) } = self;
+ x
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_variant_with_multi_file_references() {
+ check_assist(
+ convert_tuple_struct_to_named_struct,
+ r#"
+//- /main.rs
+struct Inner;
+enum A {
+ $0Variant(Inner),
+}
+
+mod foo;
+
+//- /foo.rs
+use crate::{A, Inner};
+fn f() {
+ let a = A::Variant(Inner);
+}
+"#,
+ r#"
+//- /main.rs
+struct Inner;
+enum A {
+ Variant { field1: Inner },
+}
+
+mod foo;
+
+//- /foo.rs
+use crate::{A, Inner};
+fn f() {
+ let a = A::Variant { field1: Inner };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn convert_directly_used_variant() {
+ check_assist(
+ convert_tuple_struct_to_named_struct,
+ r#"
+//- /main.rs
+struct Inner;
+enum A {
+ $0Variant(Inner),
+}
+
+mod foo;
+
+//- /foo.rs
+use crate::{A::Variant, Inner};
+fn f() {
+ let a = Variant(Inner);
+}
+"#,
+ r#"
+//- /main.rs
+struct Inner;
+enum A {
+ Variant { field1: Inner },
+}
+
+mod foo;
+
+//- /foo.rs
+use crate::{A::Variant, Inner};
+fn f() {
+ let a = Variant { field1: Inner };
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_while_to_loop.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_while_to_loop.rs
new file mode 100644
index 000000000..c34b68411
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_while_to_loop.rs
@@ -0,0 +1,188 @@
+use std::iter::once;
+
+use ide_db::syntax_helpers::node_ext::is_pattern_cond;
+use syntax::{
+ ast::{
+ self,
+ edit::{AstNodeEdit, IndentLevel},
+ make, HasLoopBody,
+ },
+ AstNode, T,
+};
+
+use crate::{
+ assist_context::{AssistContext, Assists},
+ utils::invert_boolean_expression,
+ AssistId, AssistKind,
+};
+
+// Assist: convert_while_to_loop
+//
+// Replace a while with a loop.
+//
+// ```
+// fn main() {
+// $0while cond {
+// foo();
+// }
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// loop {
+// if !cond {
+// break;
+// }
+// foo();
+// }
+// }
+// ```
+pub(crate) fn convert_while_to_loop(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let while_kw = ctx.find_token_syntax_at_offset(T![while])?;
+ let while_expr = while_kw.parent().and_then(ast::WhileExpr::cast)?;
+ let while_body = while_expr.loop_body()?;
+ let while_cond = while_expr.condition()?;
+
+ let target = while_expr.syntax().text_range();
+ acc.add(
+ AssistId("convert_while_to_loop", AssistKind::RefactorRewrite),
+ "Convert while to loop",
+ target,
+ |edit| {
+ let while_indent_level = IndentLevel::from_node(while_expr.syntax());
+
+ let break_block =
+ make::block_expr(once(make::expr_stmt(make::expr_break(None, None)).into()), None)
+ .indent(while_indent_level);
+ let block_expr = if is_pattern_cond(while_cond.clone()) {
+ let if_expr = make::expr_if(while_cond, while_body, Some(break_block.into()));
+ let stmts = once(make::expr_stmt(if_expr).into());
+ make::block_expr(stmts, None)
+ } else {
+ let if_cond = invert_boolean_expression(while_cond);
+ let if_expr = make::expr_if(if_cond, break_block, None);
+ let stmts = once(make::expr_stmt(if_expr).into()).chain(while_body.statements());
+ make::block_expr(stmts, while_body.tail_expr())
+ };
+
+ let replacement = make::expr_loop(block_expr.indent(while_indent_level));
+ edit.replace(target, replacement.syntax().text())
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn convert_inside_fn() {
+ check_assist(
+ convert_while_to_loop,
+ r#"
+fn main() {
+ while$0 cond {
+ foo();
+ }
+}
+"#,
+ r#"
+fn main() {
+ loop {
+ if !cond {
+ break;
+ }
+ foo();
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn convert_busy_wait() {
+ check_assist(
+ convert_while_to_loop,
+ r#"
+fn main() {
+ while$0 cond() {}
+}
+"#,
+ r#"
+fn main() {
+ loop {
+ if !cond() {
+ break;
+ }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn convert_trailing_expr() {
+ check_assist(
+ convert_while_to_loop,
+ r#"
+fn main() {
+ while$0 cond() {
+ bar()
+ }
+}
+"#,
+ r#"
+fn main() {
+ loop {
+ if !cond() {
+ break;
+ }
+ bar()
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn convert_while_let() {
+ check_assist(
+ convert_while_to_loop,
+ r#"
+fn main() {
+ while$0 let Some(_) = foo() {
+ bar();
+ }
+}
+"#,
+ r#"
+fn main() {
+ loop {
+ if let Some(_) = foo() {
+ bar();
+ } else {
+ break;
+ }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn ignore_cursor_in_body() {
+ check_assist_not_applicable(
+ convert_while_to_loop,
+ r#"
+fn main() {
+ while cond {$0
+ bar();
+ }
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
new file mode 100644
index 000000000..c1f57532b
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
@@ -0,0 +1,2147 @@
+use ide_db::{
+ assists::{AssistId, AssistKind},
+ defs::Definition,
+ search::{FileReference, SearchScope, UsageSearchResult},
+};
+use syntax::{
+ ast::{self, AstNode, FieldExpr, HasName, IdentPat, MethodCallExpr},
+ TextRange,
+};
+
+use crate::assist_context::{AssistBuilder, AssistContext, Assists};
+
+// Assist: destructure_tuple_binding
+//
+// Destructures a tuple binding in place.
+//
+// ```
+// fn main() {
+// let $0t = (1,2);
+// let v = t.0;
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// let ($0_0, _1) = (1,2);
+// let v = _0;
+// }
+// ```
+pub(crate) fn destructure_tuple_binding(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ destructure_tuple_binding_impl(acc, ctx, false)
+}
+
+// And when `with_sub_pattern` enabled (currently disabled):
+// Assist: destructure_tuple_binding_in_sub_pattern
+//
+// Destructures tuple items in sub-pattern (after `@`).
+//
+// ```
+// fn main() {
+// let $0t = (1,2);
+// let v = t.0;
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// let t @ ($0_0, _1) = (1,2);
+// let v = _0;
+// }
+// ```
+pub(crate) fn destructure_tuple_binding_impl(
+ acc: &mut Assists,
+ ctx: &AssistContext<'_>,
+ with_sub_pattern: bool,
+) -> Option<()> {
+ let ident_pat = ctx.find_node_at_offset::<ast::IdentPat>()?;
+ let data = collect_data(ident_pat, ctx)?;
+
+ if with_sub_pattern {
+ acc.add(
+ AssistId("destructure_tuple_binding_in_sub_pattern", AssistKind::RefactorRewrite),
+ "Destructure tuple in sub-pattern",
+ data.range,
+ |builder| {
+ edit_tuple_assignment(ctx, builder, &data, true);
+ edit_tuple_usages(&data, builder, ctx, true);
+ },
+ );
+ }
+
+ acc.add(
+ AssistId("destructure_tuple_binding", AssistKind::RefactorRewrite),
+ if with_sub_pattern { "Destructure tuple in place" } else { "Destructure tuple" },
+ data.range,
+ |builder| {
+ edit_tuple_assignment(ctx, builder, &data, false);
+ edit_tuple_usages(&data, builder, ctx, false);
+ },
+ );
+
+ Some(())
+}
+
+fn collect_data(ident_pat: IdentPat, ctx: &AssistContext<'_>) -> Option<TupleData> {
+ if ident_pat.at_token().is_some() {
+ // Cannot destructure pattern with sub-pattern:
+ // Only IdentPat can have sub-pattern,
+ // but not TuplePat (`(a,b)`).
+ cov_mark::hit!(destructure_tuple_subpattern);
+ return None;
+ }
+
+ let ty = ctx.sema.type_of_pat(&ident_pat.clone().into())?.adjusted();
+ let ref_type = if ty.is_mutable_reference() {
+ Some(RefType::Mutable)
+ } else if ty.is_reference() {
+ Some(RefType::ReadOnly)
+ } else {
+ None
+ };
+ // might be reference
+ let ty = ty.strip_references();
+ // must be tuple
+ let field_types = ty.tuple_fields(ctx.db());
+ if field_types.is_empty() {
+ cov_mark::hit!(destructure_tuple_no_tuple);
+ return None;
+ }
+
+ let name = ident_pat.name()?.to_string();
+ let range = ident_pat.syntax().text_range();
+
+ let usages = ctx.sema.to_def(&ident_pat).map(|def| {
+ Definition::Local(def)
+ .usages(&ctx.sema)
+ .in_scope(SearchScope::single_file(ctx.file_id()))
+ .all()
+ });
+
+ let field_names = (0..field_types.len())
+ .map(|i| generate_name(ctx, i, &name, &ident_pat, &usages))
+ .collect::<Vec<_>>();
+
+ Some(TupleData { ident_pat, range, ref_type, field_names, usages })
+}
+
+fn generate_name(
+ _ctx: &AssistContext<'_>,
+ index: usize,
+ _tuple_name: &str,
+ _ident_pat: &IdentPat,
+ _usages: &Option<UsageSearchResult>,
+) -> String {
+ // FIXME: detect if name already used
+ format!("_{}", index)
+}
+
+enum RefType {
+ ReadOnly,
+ Mutable,
+}
+struct TupleData {
+ ident_pat: IdentPat,
+ // name: String,
+ range: TextRange,
+ ref_type: Option<RefType>,
+ field_names: Vec<String>,
+ // field_types: Vec<Type>,
+ usages: Option<UsageSearchResult>,
+}
+fn edit_tuple_assignment(
+ ctx: &AssistContext<'_>,
+ builder: &mut AssistBuilder,
+ data: &TupleData,
+ in_sub_pattern: bool,
+) {
+ let tuple_pat = {
+ let original = &data.ident_pat;
+ let is_ref = original.ref_token().is_some();
+ let is_mut = original.mut_token().is_some();
+ let fields = data.field_names.iter().map(|name| {
+ ast::Pat::from(ast::make::ident_pat(is_ref, is_mut, ast::make::name(name)))
+ });
+ ast::make::tuple_pat(fields)
+ };
+
+ let add_cursor = |text: &str| {
+ // place cursor on first tuple item
+ let first_tuple = &data.field_names[0];
+ text.replacen(first_tuple, &format!("$0{}", first_tuple), 1)
+ };
+
+ // with sub_pattern: keep original tuple and add subpattern: `tup @ (_0, _1)`
+ if in_sub_pattern {
+ let text = format!(" @ {}", tuple_pat);
+ match ctx.config.snippet_cap {
+ Some(cap) => {
+ let snip = add_cursor(&text);
+ builder.insert_snippet(cap, data.range.end(), snip);
+ }
+ None => builder.insert(data.range.end(), text),
+ };
+ } else {
+ let text = tuple_pat.to_string();
+ match ctx.config.snippet_cap {
+ Some(cap) => {
+ let snip = add_cursor(&text);
+ builder.replace_snippet(cap, data.range, snip);
+ }
+ None => builder.replace(data.range, text),
+ };
+ }
+}
+
+fn edit_tuple_usages(
+ data: &TupleData,
+ builder: &mut AssistBuilder,
+ ctx: &AssistContext<'_>,
+ in_sub_pattern: bool,
+) {
+ if let Some(usages) = data.usages.as_ref() {
+ for (file_id, refs) in usages.iter() {
+ builder.edit_file(*file_id);
+
+ for r in refs {
+ edit_tuple_usage(ctx, builder, r, data, in_sub_pattern);
+ }
+ }
+ }
+}
+fn edit_tuple_usage(
+ ctx: &AssistContext<'_>,
+ builder: &mut AssistBuilder,
+ usage: &FileReference,
+ data: &TupleData,
+ in_sub_pattern: bool,
+) {
+ match detect_tuple_index(usage, data) {
+ Some(index) => edit_tuple_field_usage(ctx, builder, data, index),
+ None => {
+ if in_sub_pattern {
+ cov_mark::hit!(destructure_tuple_call_with_subpattern);
+ return;
+ }
+
+ // no index access -> make invalid -> requires handling by user
+ // -> put usage in block comment
+ //
+ // Note: For macro invocations this might result in still valid code:
+ // When a macro accepts the tuple as argument, as well as no arguments at all,
+ // uncommenting the tuple still leaves the macro call working (see `tests::in_macro_call::empty_macro`).
+ // But this is an unlikely case. Usually the resulting macro call will become erroneous.
+ builder.insert(usage.range.start(), "/*");
+ builder.insert(usage.range.end(), "*/");
+ }
+ }
+}
+
+fn edit_tuple_field_usage(
+ ctx: &AssistContext<'_>,
+ builder: &mut AssistBuilder,
+ data: &TupleData,
+ index: TupleIndex,
+) {
+ let field_name = &data.field_names[index.index];
+
+ if data.ref_type.is_some() {
+ let ref_data = handle_ref_field_usage(ctx, &index.field_expr);
+ builder.replace(ref_data.range, ref_data.format(field_name));
+ } else {
+ builder.replace(index.range, field_name);
+ }
+}
+struct TupleIndex {
+ index: usize,
+ range: TextRange,
+ field_expr: FieldExpr,
+}
+fn detect_tuple_index(usage: &FileReference, data: &TupleData) -> Option<TupleIndex> {
+ // usage is IDENT
+ // IDENT
+ // NAME_REF
+ // PATH_SEGMENT
+ // PATH
+ // PATH_EXPR
+ // PAREN_EXRP*
+ // FIELD_EXPR
+
+ let node = usage
+ .name
+ .syntax()
+ .ancestors()
+ .skip_while(|s| !ast::PathExpr::can_cast(s.kind()))
+ .skip(1) // PATH_EXPR
+ .find(|s| !ast::ParenExpr::can_cast(s.kind()))?; // skip parentheses
+
+ if let Some(field_expr) = ast::FieldExpr::cast(node) {
+ let idx = field_expr.name_ref()?.as_tuple_field()?;
+ if idx < data.field_names.len() {
+ // special case: in macro call -> range of `field_expr` in applied macro, NOT range in actual file!
+ if field_expr.syntax().ancestors().any(|a| ast::MacroStmts::can_cast(a.kind())) {
+ cov_mark::hit!(destructure_tuple_macro_call);
+
+ // issue: cannot differentiate between tuple index passed into macro or tuple index as result of macro:
+ // ```rust
+ // macro_rules! m {
+ // ($t1:expr, $t2:expr) => { $t1; $t2.0 }
+ // }
+ // let t = (1,2);
+ // m!(t.0, t)
+ // ```
+ // -> 2 tuple index usages detected!
+ //
+ // -> only handle `t`
+ return None;
+ }
+
+ Some(TupleIndex { index: idx, range: field_expr.syntax().text_range(), field_expr })
+ } else {
+ // tuple index out of range
+ None
+ }
+ } else {
+ None
+ }
+}
+
+struct RefData {
+ range: TextRange,
+ needs_deref: bool,
+ needs_parentheses: bool,
+}
+impl RefData {
+ fn format(&self, field_name: &str) -> String {
+ match (self.needs_deref, self.needs_parentheses) {
+ (true, true) => format!("(*{})", field_name),
+ (true, false) => format!("*{}", field_name),
+ (false, true) => format!("({})", field_name),
+ (false, false) => field_name.to_string(),
+ }
+ }
+}
+fn handle_ref_field_usage(ctx: &AssistContext<'_>, field_expr: &FieldExpr) -> RefData {
+ let s = field_expr.syntax();
+ let mut ref_data =
+ RefData { range: s.text_range(), needs_deref: true, needs_parentheses: true };
+
+ let parent = match s.parent().map(ast::Expr::cast) {
+ Some(Some(parent)) => parent,
+ Some(None) => {
+ ref_data.needs_parentheses = false;
+ return ref_data;
+ }
+ None => return ref_data,
+ };
+
+ match parent {
+ ast::Expr::ParenExpr(it) => {
+ // already parens in place -> don't replace
+ ref_data.needs_parentheses = false;
+ // there might be a ref outside: `&(t.0)` -> can be removed
+ if let Some(it) = it.syntax().parent().and_then(ast::RefExpr::cast) {
+ ref_data.needs_deref = false;
+ ref_data.range = it.syntax().text_range();
+ }
+ }
+ ast::Expr::RefExpr(it) => {
+ // `&*` -> cancel each other out
+ ref_data.needs_deref = false;
+ ref_data.needs_parentheses = false;
+ // might be surrounded by parens -> can be removed too
+ match it.syntax().parent().and_then(ast::ParenExpr::cast) {
+ Some(parent) => ref_data.range = parent.syntax().text_range(),
+ None => ref_data.range = it.syntax().text_range(),
+ };
+ }
+ // higher precedence than deref `*`
+ // https://doc.rust-lang.org/reference/expressions.html#expression-precedence
+ // -> requires parentheses
+ ast::Expr::PathExpr(_it) => {}
+ ast::Expr::MethodCallExpr(it) => {
+ // `field_expr` is `self_param` (otherwise it would be in `ArgList`)
+
+ // test if there's already auto-ref in place (`value` -> `&value`)
+ // -> no method accepting `self`, but `&self` -> no need for deref
+ //
+ // other combinations (`&value` -> `value`, `&&value` -> `&value`, `&value` -> `&&value`) might or might not be able to auto-ref/deref,
+ // but there might be trait implementations an added `&` might resolve to
+ // -> ONLY handle auto-ref from `value` to `&value`
+ fn is_auto_ref(ctx: &AssistContext<'_>, call_expr: &MethodCallExpr) -> bool {
+ fn impl_(ctx: &AssistContext<'_>, call_expr: &MethodCallExpr) -> Option<bool> {
+ let rec = call_expr.receiver()?;
+ let rec_ty = ctx.sema.type_of_expr(&rec)?.original();
+ // input must be actual value
+ if rec_ty.is_reference() {
+ return Some(false);
+ }
+
+ // doesn't resolve trait impl
+ let f = ctx.sema.resolve_method_call(call_expr)?;
+ let self_param = f.self_param(ctx.db())?;
+ // self must be ref
+ match self_param.access(ctx.db()) {
+ hir::Access::Shared | hir::Access::Exclusive => Some(true),
+ hir::Access::Owned => Some(false),
+ }
+ }
+ impl_(ctx, call_expr).unwrap_or(false)
+ }
+
+ if is_auto_ref(ctx, &it) {
+ ref_data.needs_deref = false;
+ ref_data.needs_parentheses = false;
+ }
+ }
+ ast::Expr::FieldExpr(_it) => {
+ // `t.0.my_field`
+ ref_data.needs_deref = false;
+ ref_data.needs_parentheses = false;
+ }
+ ast::Expr::IndexExpr(_it) => {
+ // `t.0[1]`
+ ref_data.needs_deref = false;
+ ref_data.needs_parentheses = false;
+ }
+ ast::Expr::TryExpr(_it) => {
+ // `t.0?`
+ // requires deref and parens: `(*_0)`
+ }
+ // lower precedence than deref `*` -> no parens
+ _ => {
+ ref_data.needs_parentheses = false;
+ }
+ };
+
+ ref_data
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ // Tests for direct tuple destructure:
+ // `let $0t = (1,2);` -> `let (_0, _1) = (1,2);`
+
+ fn assist(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ destructure_tuple_binding_impl(acc, ctx, false)
+ }
+
+ #[test]
+ fn dont_trigger_on_unit() {
+ cov_mark::check!(destructure_tuple_no_tuple);
+ check_assist_not_applicable(
+ assist,
+ r#"
+fn main() {
+let $0v = ();
+}
+ "#,
+ )
+ }
+ #[test]
+ fn dont_trigger_on_number() {
+ cov_mark::check!(destructure_tuple_no_tuple);
+ check_assist_not_applicable(
+ assist,
+ r#"
+fn main() {
+let $0v = 32;
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn destructure_3_tuple() {
+ check_assist(
+ assist,
+ r#"
+fn main() {
+ let $0tup = (1,2,3);
+}
+ "#,
+ r#"
+fn main() {
+ let ($0_0, _1, _2) = (1,2,3);
+}
+ "#,
+ )
+ }
+ #[test]
+ fn destructure_2_tuple() {
+ check_assist(
+ assist,
+ r#"
+fn main() {
+ let $0tup = (1,2);
+}
+ "#,
+ r#"
+fn main() {
+ let ($0_0, _1) = (1,2);
+}
+ "#,
+ )
+ }
+ #[test]
+ fn replace_indices() {
+ check_assist(
+ assist,
+ r#"
+fn main() {
+ let $0tup = (1,2,3);
+ let v1 = tup.0;
+ let v2 = tup.1;
+ let v3 = tup.2;
+}
+ "#,
+ r#"
+fn main() {
+ let ($0_0, _1, _2) = (1,2,3);
+ let v1 = _0;
+ let v2 = _1;
+ let v3 = _2;
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn replace_usage_in_parentheses() {
+ check_assist(
+ assist,
+ r#"
+fn main() {
+ let $0tup = (1,2,3);
+ let a = (tup).1;
+ let b = ((tup)).1;
+}
+ "#,
+ r#"
+fn main() {
+ let ($0_0, _1, _2) = (1,2,3);
+ let a = _1;
+ let b = _1;
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn handle_function_call() {
+ check_assist(
+ assist,
+ r#"
+fn main() {
+ let $0tup = (1,2);
+ let v = tup.into();
+}
+ "#,
+ r#"
+fn main() {
+ let ($0_0, _1) = (1,2);
+ let v = /*tup*/.into();
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn handle_invalid_index() {
+ check_assist(
+ assist,
+ r#"
+fn main() {
+ let $0tup = (1,2);
+ let v = tup.3;
+}
+ "#,
+ r#"
+fn main() {
+ let ($0_0, _1) = (1,2);
+ let v = /*tup*/.3;
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn dont_replace_variable_with_same_name_as_tuple() {
+ check_assist(
+ assist,
+ r#"
+fn main() {
+ let tup = (1,2);
+ let v = tup.1;
+ let $0tup = (1,2,3);
+ let v = tup.1;
+ let tup = (1,2,3);
+ let v = tup.1;
+}
+ "#,
+ r#"
+fn main() {
+ let tup = (1,2);
+ let v = tup.1;
+ let ($0_0, _1, _2) = (1,2,3);
+ let v = _1;
+ let tup = (1,2,3);
+ let v = tup.1;
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn keep_function_call_in_tuple_item() {
+ check_assist(
+ assist,
+ r#"
+fn main() {
+ let $0t = ("3.14", 0);
+ let pi: f32 = t.0.parse().unwrap_or(0.0);
+}
+ "#,
+ r#"
+fn main() {
+ let ($0_0, _1) = ("3.14", 0);
+ let pi: f32 = _0.parse().unwrap_or(0.0);
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn keep_type() {
+ check_assist(
+ assist,
+ r#"
+fn main() {
+ let $0t: (usize, i32) = (1,2);
+}
+ "#,
+ r#"
+fn main() {
+ let ($0_0, _1): (usize, i32) = (1,2);
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn destructure_reference() {
+ check_assist(
+ assist,
+ r#"
+fn main() {
+ let t = (1,2);
+ let $0t = &t;
+ let v = t.0;
+}
+ "#,
+ r#"
+fn main() {
+ let t = (1,2);
+ let ($0_0, _1) = &t;
+ let v = *_0;
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn destructure_multiple_reference() {
+ check_assist(
+ assist,
+ r#"
+fn main() {
+ let t = (1,2);
+ let $0t = &&t;
+ let v = t.0;
+}
+ "#,
+ r#"
+fn main() {
+ let t = (1,2);
+ let ($0_0, _1) = &&t;
+ let v = *_0;
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn keep_reference() {
+ check_assist(
+ assist,
+ r#"
+fn foo(t: &(usize, usize)) -> usize {
+ match t {
+ &$0t => t.0
+ }
+}
+ "#,
+ r#"
+fn foo(t: &(usize, usize)) -> usize {
+ match t {
+ &($0_0, _1) => _0
+ }
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn with_ref() {
+ check_assist(
+ assist,
+ r#"
+fn main() {
+ let ref $0t = (1,2);
+ let v = t.0;
+}
+ "#,
+ r#"
+fn main() {
+ let (ref $0_0, ref _1) = (1,2);
+ let v = *_0;
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn with_mut() {
+ check_assist(
+ assist,
+ r#"
+fn main() {
+ let mut $0t = (1,2);
+ t.0 = 42;
+ let v = t.0;
+}
+ "#,
+ r#"
+fn main() {
+ let (mut $0_0, mut _1) = (1,2);
+ _0 = 42;
+ let v = _0;
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn with_ref_mut() {
+ check_assist(
+ assist,
+ r#"
+fn main() {
+ let ref mut $0t = (1,2);
+ t.0 = 42;
+ let v = t.0;
+}
+ "#,
+ r#"
+fn main() {
+ let (ref mut $0_0, ref mut _1) = (1,2);
+ *_0 = 42;
+ let v = *_0;
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn dont_trigger_for_non_tuple_reference() {
+ check_assist_not_applicable(
+ assist,
+ r#"
+fn main() {
+ let v = 42;
+ let $0v = &42;
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn dont_trigger_on_static_tuple() {
+ check_assist_not_applicable(
+ assist,
+ r#"
+static $0TUP: (usize, usize) = (1,2);
+ "#,
+ )
+ }
+
+ #[test]
+ fn dont_trigger_on_wildcard() {
+ check_assist_not_applicable(
+ assist,
+ r#"
+fn main() {
+ let $0_ = (1,2);
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn dont_trigger_in_struct() {
+ check_assist_not_applicable(
+ assist,
+ r#"
+struct S {
+ $0tup: (usize, usize),
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn dont_trigger_in_struct_creation() {
+ check_assist_not_applicable(
+ assist,
+ r#"
+struct S {
+ tup: (usize, usize),
+}
+fn main() {
+ let s = S {
+ $0tup: (1,2),
+ };
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn dont_trigger_on_tuple_struct() {
+ check_assist_not_applicable(
+ assist,
+ r#"
+struct S(usize, usize);
+fn main() {
+ let $0s = S(1,2);
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn dont_trigger_when_subpattern_exists() {
+ // sub-pattern is only allowed with IdentPat (name), not other patterns (like TuplePat)
+ cov_mark::check!(destructure_tuple_subpattern);
+ check_assist_not_applicable(
+ assist,
+ r#"
+fn sum(t: (usize, usize)) -> usize {
+ match t {
+ $0t @ (1..=3,1..=3) => t.0 + t.1,
+ _ => 0,
+ }
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn in_subpattern() {
+ check_assist(
+ assist,
+ r#"
+fn main() {
+ let t1 @ (_, $0t2) = (1, (2,3));
+ let v = t1.0 + t2.0 + t2.1;
+}
+ "#,
+ r#"
+fn main() {
+ let t1 @ (_, ($0_0, _1)) = (1, (2,3));
+ let v = t1.0 + _0 + _1;
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn in_nested_tuple() {
+ check_assist(
+ assist,
+ r#"
+fn main() {
+ let ($0tup, v) = ((1,2),3);
+}
+ "#,
+ r#"
+fn main() {
+ let (($0_0, _1), v) = ((1,2),3);
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn in_closure() {
+ check_assist(
+ assist,
+ r#"
+fn main() {
+ let $0tup = (1,2,3);
+ let f = |v| v + tup.1;
+}
+ "#,
+ r#"
+fn main() {
+ let ($0_0, _1, _2) = (1,2,3);
+ let f = |v| v + _1;
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn in_closure_args() {
+ check_assist(
+ assist,
+ r#"
+fn main() {
+ let f = |$0t| t.0 + t.1;
+ let v = f((1,2));
+}
+ "#,
+ r#"
+fn main() {
+ let f = |($0_0, _1)| _0 + _1;
+ let v = f((1,2));
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn in_function_args() {
+ check_assist(
+ assist,
+ r#"
+fn f($0t: (usize, usize)) {
+ let v = t.0;
+}
+ "#,
+ r#"
+fn f(($0_0, _1): (usize, usize)) {
+ let v = _0;
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn in_if_let() {
+ check_assist(
+ assist,
+ r#"
+fn f(t: (usize, usize)) {
+ if let $0t = t {
+ let v = t.0;
+ }
+}
+ "#,
+ r#"
+fn f(t: (usize, usize)) {
+ if let ($0_0, _1) = t {
+ let v = _0;
+ }
+}
+ "#,
+ )
+ }
+ #[test]
+ fn in_if_let_option() {
+ check_assist(
+ assist,
+ r#"
+//- minicore: option
+fn f(o: Option<(usize, usize)>) {
+ if let Some($0t) = o {
+ let v = t.0;
+ }
+}
+ "#,
+ r#"
+fn f(o: Option<(usize, usize)>) {
+ if let Some(($0_0, _1)) = o {
+ let v = _0;
+ }
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn in_match() {
+ check_assist(
+ assist,
+ r#"
+fn main() {
+ match (1,2) {
+ $0t => t.1,
+ };
+}
+ "#,
+ r#"
+fn main() {
+ match (1,2) {
+ ($0_0, _1) => _1,
+ };
+}
+ "#,
+ )
+ }
+ #[test]
+ fn in_match_option() {
+ check_assist(
+ assist,
+ r#"
+//- minicore: option
+fn main() {
+ match Some((1,2)) {
+ Some($0t) => t.1,
+ _ => 0,
+ };
+}
+ "#,
+ r#"
+fn main() {
+ match Some((1,2)) {
+ Some(($0_0, _1)) => _1,
+ _ => 0,
+ };
+}
+ "#,
+ )
+ }
+ #[test]
+ fn in_match_reference_option() {
+ check_assist(
+ assist,
+ r#"
+//- minicore: option
+fn main() {
+ let t = (1,2);
+ match Some(&t) {
+ Some($0t) => t.1,
+ _ => 0,
+ };
+}
+ "#,
+ r#"
+fn main() {
+ let t = (1,2);
+ match Some(&t) {
+ Some(($0_0, _1)) => *_1,
+ _ => 0,
+ };
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn in_for() {
+ check_assist(
+ assist,
+ r#"
+//- minicore: iterators
+fn main() {
+ for $0t in core::iter::repeat((1,2)) {
+ let v = t.1;
+ }
+}
+ "#,
+ r#"
+fn main() {
+ for ($0_0, _1) in core::iter::repeat((1,2)) {
+ let v = _1;
+ }
+}
+ "#,
+ )
+ }
+ #[test]
+ fn in_for_nested() {
+ check_assist(
+ assist,
+ r#"
+//- minicore: iterators
+fn main() {
+ for (a, $0b) in core::iter::repeat((1,(2,3))) {
+ let v = b.1;
+ }
+}
+ "#,
+ r#"
+fn main() {
+ for (a, ($0_0, _1)) in core::iter::repeat((1,(2,3))) {
+ let v = _1;
+ }
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn not_applicable_on_tuple_usage() {
+ //Improvement: might be reasonable to allow & implement
+ check_assist_not_applicable(
+ assist,
+ r#"
+fn main() {
+ let t = (1,2);
+ let v = $0t.0;
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn replace_all() {
+ check_assist(
+ assist,
+ r#"
+fn main() {
+ let $0t = (1,2);
+ let v = t.1;
+ let s = (t.0 + t.1) / 2;
+ let f = |v| v + t.0;
+ let r = f(t.1);
+ let e = t == (9,0);
+ let m =
+ match t {
+ (_,2) if t.0 > 2 => 1,
+ _ => 0,
+ };
+}
+ "#,
+ r#"
+fn main() {
+ let ($0_0, _1) = (1,2);
+ let v = _1;
+ let s = (_0 + _1) / 2;
+ let f = |v| v + _0;
+ let r = f(_1);
+ let e = /*t*/ == (9,0);
+ let m =
+ match /*t*/ {
+ (_,2) if _0 > 2 => 1,
+ _ => 0,
+ };
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn non_trivial_tuple_assignment() {
+ check_assist(
+ assist,
+ r#"
+fn main {
+ let $0t =
+ if 1 > 2 {
+ (1,2)
+ } else {
+ (5,6)
+ };
+ let v1 = t.0;
+ let v2 =
+ if t.0 > t.1 {
+ t.0 - t.1
+ } else {
+ t.1 - t.0
+ };
+}
+ "#,
+ r#"
+fn main {
+ let ($0_0, _1) =
+ if 1 > 2 {
+ (1,2)
+ } else {
+ (5,6)
+ };
+ let v1 = _0;
+ let v2 =
+ if _0 > _1 {
+ _0 - _1
+ } else {
+ _1 - _0
+ };
+}
+ "#,
+ )
+ }
+
+ mod assist {
+ use super::*;
+ use crate::tests::check_assist_by_label;
+
+ fn assist(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ destructure_tuple_binding_impl(acc, ctx, true)
+ }
+ fn in_place_assist(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ destructure_tuple_binding_impl(acc, ctx, false)
+ }
+
+ pub(crate) fn check_in_place_assist(ra_fixture_before: &str, ra_fixture_after: &str) {
+ check_assist_by_label(
+ in_place_assist,
+ ra_fixture_before,
+ ra_fixture_after,
+ // "Destructure tuple in place",
+ "Destructure tuple",
+ );
+ }
+
+ pub(crate) fn check_sub_pattern_assist(ra_fixture_before: &str, ra_fixture_after: &str) {
+ check_assist_by_label(
+ assist,
+ ra_fixture_before,
+ ra_fixture_after,
+ "Destructure tuple in sub-pattern",
+ );
+ }
+
+ pub(crate) fn check_both_assists(
+ ra_fixture_before: &str,
+ ra_fixture_after_in_place: &str,
+ ra_fixture_after_in_sub_pattern: &str,
+ ) {
+ check_in_place_assist(ra_fixture_before, ra_fixture_after_in_place);
+ check_sub_pattern_assist(ra_fixture_before, ra_fixture_after_in_sub_pattern);
+ }
+ }
+
+ /// Tests for destructure of tuple in sub-pattern:
+ /// `let $0t = (1,2);` -> `let t @ (_0, _1) = (1,2);`
+ mod sub_pattern {
+ use super::assist::*;
+ use super::*;
+ use crate::tests::check_assist_by_label;
+
+ #[test]
+ fn destructure_in_sub_pattern() {
+ check_sub_pattern_assist(
+ r#"
+#![feature(bindings_after_at)]
+
+fn main() {
+ let $0t = (1,2);
+}
+ "#,
+ r#"
+#![feature(bindings_after_at)]
+
+fn main() {
+ let t @ ($0_0, _1) = (1,2);
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn trigger_both_destructure_tuple_assists() {
+ fn assist(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ destructure_tuple_binding_impl(acc, ctx, true)
+ }
+ let text = r#"
+fn main() {
+ let $0t = (1,2);
+}
+ "#;
+ check_assist_by_label(
+ assist,
+ text,
+ r#"
+fn main() {
+ let ($0_0, _1) = (1,2);
+}
+ "#,
+ "Destructure tuple in place",
+ );
+ check_assist_by_label(
+ assist,
+ text,
+ r#"
+fn main() {
+ let t @ ($0_0, _1) = (1,2);
+}
+ "#,
+ "Destructure tuple in sub-pattern",
+ );
+ }
+
+ #[test]
+ fn replace_indices() {
+ check_sub_pattern_assist(
+ r#"
+fn main() {
+ let $0t = (1,2);
+ let v1 = t.0;
+ let v2 = t.1;
+}
+ "#,
+ r#"
+fn main() {
+ let t @ ($0_0, _1) = (1,2);
+ let v1 = _0;
+ let v2 = _1;
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn keep_function_call() {
+ cov_mark::check!(destructure_tuple_call_with_subpattern);
+ check_sub_pattern_assist(
+ r#"
+fn main() {
+ let $0t = (1,2);
+ let v = t.into();
+}
+ "#,
+ r#"
+fn main() {
+ let t @ ($0_0, _1) = (1,2);
+ let v = t.into();
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn keep_type() {
+ check_sub_pattern_assist(
+ r#"
+fn main() {
+ let $0t: (usize, i32) = (1,2);
+ let v = t.1;
+ let f = t.into();
+}
+ "#,
+ r#"
+fn main() {
+ let t @ ($0_0, _1): (usize, i32) = (1,2);
+ let v = _1;
+ let f = t.into();
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn in_function_args() {
+ check_sub_pattern_assist(
+ r#"
+fn f($0t: (usize, usize)) {
+ let v = t.0;
+ let f = t.into();
+}
+ "#,
+ r#"
+fn f(t @ ($0_0, _1): (usize, usize)) {
+ let v = _0;
+ let f = t.into();
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn with_ref() {
+ check_sub_pattern_assist(
+ r#"
+fn main() {
+ let ref $0t = (1,2);
+ let v = t.1;
+ let f = t.into();
+}
+ "#,
+ r#"
+fn main() {
+ let ref t @ (ref $0_0, ref _1) = (1,2);
+ let v = *_1;
+ let f = t.into();
+}
+ "#,
+ )
+ }
+ #[test]
+ fn with_mut() {
+ check_sub_pattern_assist(
+ r#"
+fn main() {
+ let mut $0t = (1,2);
+ let v = t.1;
+ let f = t.into();
+}
+ "#,
+ r#"
+fn main() {
+ let mut t @ (mut $0_0, mut _1) = (1,2);
+ let v = _1;
+ let f = t.into();
+}
+ "#,
+ )
+ }
+ #[test]
+ fn with_ref_mut() {
+ check_sub_pattern_assist(
+ r#"
+fn main() {
+ let ref mut $0t = (1,2);
+ let v = t.1;
+ let f = t.into();
+}
+ "#,
+ r#"
+fn main() {
+ let ref mut t @ (ref mut $0_0, ref mut _1) = (1,2);
+ let v = *_1;
+ let f = t.into();
+}
+ "#,
+ )
+ }
+ }
+
+ /// Tests for tuple usage in macro call:
+ /// `println!("{}", t.0)`
+ mod in_macro_call {
+ use super::assist::*;
+
+ #[test]
+ fn detect_macro_call() {
+ cov_mark::check!(destructure_tuple_macro_call);
+ check_in_place_assist(
+ r#"
+macro_rules! m {
+ ($e:expr) => { "foo"; $e };
+}
+
+fn main() {
+ let $0t = (1,2);
+ m!(t.0);
+}
+ "#,
+ r#"
+macro_rules! m {
+ ($e:expr) => { "foo"; $e };
+}
+
+fn main() {
+ let ($0_0, _1) = (1,2);
+ m!(/*t*/.0);
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn tuple_usage() {
+ check_both_assists(
+ // leading `"foo"` to ensure `$e` doesn't start at position `0`
+ r#"
+macro_rules! m {
+ ($e:expr) => { "foo"; $e };
+}
+
+fn main() {
+ let $0t = (1,2);
+ m!(t);
+}
+ "#,
+ r#"
+macro_rules! m {
+ ($e:expr) => { "foo"; $e };
+}
+
+fn main() {
+ let ($0_0, _1) = (1,2);
+ m!(/*t*/);
+}
+ "#,
+ r#"
+macro_rules! m {
+ ($e:expr) => { "foo"; $e };
+}
+
+fn main() {
+ let t @ ($0_0, _1) = (1,2);
+ m!(t);
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn tuple_function_usage() {
+ check_both_assists(
+ r#"
+macro_rules! m {
+ ($e:expr) => { "foo"; $e };
+}
+
+fn main() {
+ let $0t = (1,2);
+ m!(t.into());
+}
+ "#,
+ r#"
+macro_rules! m {
+ ($e:expr) => { "foo"; $e };
+}
+
+fn main() {
+ let ($0_0, _1) = (1,2);
+ m!(/*t*/.into());
+}
+ "#,
+ r#"
+macro_rules! m {
+ ($e:expr) => { "foo"; $e };
+}
+
+fn main() {
+ let t @ ($0_0, _1) = (1,2);
+ m!(t.into());
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn tuple_index_usage() {
+ check_both_assists(
+ r#"
+macro_rules! m {
+ ($e:expr) => { "foo"; $e };
+}
+
+fn main() {
+ let $0t = (1,2);
+ m!(t.0);
+}
+ "#,
+ // FIXME: replace `t.0` with `_0` (cannot detect range of tuple index in macro call)
+ r#"
+macro_rules! m {
+ ($e:expr) => { "foo"; $e };
+}
+
+fn main() {
+ let ($0_0, _1) = (1,2);
+ m!(/*t*/.0);
+}
+ "#,
+ // FIXME: replace `t.0` with `_0`
+ r#"
+macro_rules! m {
+ ($e:expr) => { "foo"; $e };
+}
+
+fn main() {
+ let t @ ($0_0, _1) = (1,2);
+ m!(t.0);
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn tuple_in_parentheses_index_usage() {
+ check_both_assists(
+ r#"
+macro_rules! m {
+ ($e:expr) => { "foo"; $e };
+}
+
+fn main() {
+ let $0t = (1,2);
+ m!((t).0);
+}
+ "#,
+ // FIXME: replace `(t).0` with `_0`
+ r#"
+macro_rules! m {
+ ($e:expr) => { "foo"; $e };
+}
+
+fn main() {
+ let ($0_0, _1) = (1,2);
+ m!((/*t*/).0);
+}
+ "#,
+ // FIXME: replace `(t).0` with `_0`
+ r#"
+macro_rules! m {
+ ($e:expr) => { "foo"; $e };
+}
+
+fn main() {
+ let t @ ($0_0, _1) = (1,2);
+ m!((t).0);
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn empty_macro() {
+ check_in_place_assist(
+ r#"
+macro_rules! m {
+ () => { "foo" };
+ ($e:expr) => { $e; "foo" };
+}
+
+fn main() {
+ let $0t = (1,2);
+ m!(t);
+}
+ "#,
+ // FIXME: macro allows no arg -> is valid. But assist should result in invalid code
+ r#"
+macro_rules! m {
+ () => { "foo" };
+ ($e:expr) => { $e; "foo" };
+}
+
+fn main() {
+ let ($0_0, _1) = (1,2);
+ m!(/*t*/);
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn tuple_index_in_macro() {
+ check_both_assists(
+ r#"
+macro_rules! m {
+ ($t:expr, $i:expr) => { $t.0 + $i };
+}
+
+fn main() {
+ let $0t = (1,2);
+ m!(t, t.0);
+}
+ "#,
+ // FIXME: replace `t.0` in macro call (not IN macro) with `_0`
+ r#"
+macro_rules! m {
+ ($t:expr, $i:expr) => { $t.0 + $i };
+}
+
+fn main() {
+ let ($0_0, _1) = (1,2);
+ m!(/*t*/, /*t*/.0);
+}
+ "#,
+ // FIXME: replace `t.0` in macro call with `_0`
+ r#"
+macro_rules! m {
+ ($t:expr, $i:expr) => { $t.0 + $i };
+}
+
+fn main() {
+ let t @ ($0_0, _1) = (1,2);
+ m!(t, t.0);
+}
+ "#,
+ )
+ }
+ }
+
+ mod refs {
+ use super::assist::*;
+
+ #[test]
+ fn no_ref() {
+ check_in_place_assist(
+ r#"
+fn main() {
+ let $0t = &(1,2);
+ let v: i32 = t.0;
+}
+ "#,
+ r#"
+fn main() {
+ let ($0_0, _1) = &(1,2);
+ let v: i32 = *_0;
+}
+ "#,
+ )
+ }
+ #[test]
+ fn no_ref_with_parens() {
+ check_in_place_assist(
+ r#"
+fn main() {
+ let $0t = &(1,2);
+ let v: i32 = (t.0);
+}
+ "#,
+ r#"
+fn main() {
+ let ($0_0, _1) = &(1,2);
+ let v: i32 = (*_0);
+}
+ "#,
+ )
+ }
+ #[test]
+ fn with_ref() {
+ check_in_place_assist(
+ r#"
+fn main() {
+ let $0t = &(1,2);
+ let v: &i32 = &t.0;
+}
+ "#,
+ r#"
+fn main() {
+ let ($0_0, _1) = &(1,2);
+ let v: &i32 = _0;
+}
+ "#,
+ )
+ }
+ #[test]
+ fn with_ref_in_parens_ref() {
+ check_in_place_assist(
+ r#"
+fn main() {
+ let $0t = &(1,2);
+ let v: &i32 = &(t.0);
+}
+ "#,
+ r#"
+fn main() {
+ let ($0_0, _1) = &(1,2);
+ let v: &i32 = _0;
+}
+ "#,
+ )
+ }
+ #[test]
+ fn with_ref_in_ref_parens() {
+ check_in_place_assist(
+ r#"
+fn main() {
+ let $0t = &(1,2);
+ let v: &i32 = (&t.0);
+}
+ "#,
+ r#"
+fn main() {
+ let ($0_0, _1) = &(1,2);
+ let v: &i32 = _0;
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn deref_and_parentheses() {
+ // Operator/Expressions with higher precedence than deref (`*`):
+ // https://doc.rust-lang.org/reference/expressions.html#expression-precedence
+ // * Path
+ // * Method call
+ // * Field expression
+ // * Function calls, array indexing
+ // * `?`
+ check_in_place_assist(
+ r#"
+//- minicore: option
+fn f1(v: i32) {}
+fn f2(v: &i32) {}
+trait T {
+ fn do_stuff(self) {}
+}
+impl T for i32 {
+ fn do_stuff(self) {}
+}
+impl T for &i32 {
+ fn do_stuff(self) {}
+}
+struct S4 {
+ value: i32,
+}
+
+fn foo() -> Option<()> {
+ let $0t = &(0, (1,"1"), Some(2), [3;3], S4 { value: 4 }, &5);
+ let v: i32 = t.0; // deref, no parens
+ let v: &i32 = &t.0; // no deref, no parens, remove `&`
+ f1(t.0); // deref, no parens
+ f2(&t.0); // `&*` -> cancel out -> no deref, no parens
+ // https://github.com/rust-lang/rust-analyzer/issues/1109#issuecomment-658868639
+ // let v: i32 = t.1.0; // no deref, no parens
+ let v: i32 = t.4.value; // no deref, no parens
+ t.0.do_stuff(); // deref, parens
+ let v: i32 = t.2?; // deref, parens
+ let v: i32 = t.3[0]; // no deref, no parens
+ (t.0).do_stuff(); // deref, no additional parens
+ let v: i32 = *t.5; // deref (-> 2), no parens
+
+ None
+}
+ "#,
+ r#"
+fn f1(v: i32) {}
+fn f2(v: &i32) {}
+trait T {
+ fn do_stuff(self) {}
+}
+impl T for i32 {
+ fn do_stuff(self) {}
+}
+impl T for &i32 {
+ fn do_stuff(self) {}
+}
+struct S4 {
+ value: i32,
+}
+
+fn foo() -> Option<()> {
+ let ($0_0, _1, _2, _3, _4, _5) = &(0, (1,"1"), Some(2), [3;3], S4 { value: 4 }, &5);
+ let v: i32 = *_0; // deref, no parens
+ let v: &i32 = _0; // no deref, no parens, remove `&`
+ f1(*_0); // deref, no parens
+ f2(_0); // `&*` -> cancel out -> no deref, no parens
+ // https://github.com/rust-lang/rust-analyzer/issues/1109#issuecomment-658868639
+ // let v: i32 = t.1.0; // no deref, no parens
+ let v: i32 = _4.value; // no deref, no parens
+ (*_0).do_stuff(); // deref, parens
+ let v: i32 = (*_2)?; // deref, parens
+ let v: i32 = _3[0]; // no deref, no parens
+ (*_0).do_stuff(); // deref, no additional parens
+ let v: i32 = **_5; // deref (-> 2), no parens
+
+ None
+}
+ "#,
+ )
+ }
+
+ // ---------
+ // auto-ref/deref
+
+ #[test]
+ fn self_auto_ref_doesnt_need_deref() {
+ check_in_place_assist(
+ r#"
+#[derive(Clone, Copy)]
+struct S;
+impl S {
+ fn f(&self) {}
+}
+
+fn main() {
+ let $0t = &(S,2);
+ let s = t.0.f();
+}
+ "#,
+ r#"
+#[derive(Clone, Copy)]
+struct S;
+impl S {
+ fn f(&self) {}
+}
+
+fn main() {
+ let ($0_0, _1) = &(S,2);
+ let s = _0.f();
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn self_owned_requires_deref() {
+ check_in_place_assist(
+ r#"
+#[derive(Clone, Copy)]
+struct S;
+impl S {
+ fn f(self) {}
+}
+
+fn main() {
+ let $0t = &(S,2);
+ let s = t.0.f();
+}
+ "#,
+ r#"
+#[derive(Clone, Copy)]
+struct S;
+impl S {
+ fn f(self) {}
+}
+
+fn main() {
+ let ($0_0, _1) = &(S,2);
+ let s = (*_0).f();
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn self_auto_ref_in_trait_call_doesnt_require_deref() {
+ check_in_place_assist(
+ r#"
+trait T {
+ fn f(self);
+}
+#[derive(Clone, Copy)]
+struct S;
+impl T for &S {
+ fn f(self) {}
+}
+
+fn main() {
+ let $0t = &(S,2);
+ let s = t.0.f();
+}
+ "#,
+ // FIXME: doesn't need deref * parens. But `ctx.sema.resolve_method_call` doesn't resolve trait implementations
+ r#"
+trait T {
+ fn f(self);
+}
+#[derive(Clone, Copy)]
+struct S;
+impl T for &S {
+ fn f(self) {}
+}
+
+fn main() {
+ let ($0_0, _1) = &(S,2);
+ let s = (*_0).f();
+}
+ "#,
+ )
+ }
+ #[test]
+ fn no_auto_deref_because_of_owned_and_ref_trait_impl() {
+ check_in_place_assist(
+ r#"
+trait T {
+ fn f(self);
+}
+#[derive(Clone, Copy)]
+struct S;
+impl T for S {
+ fn f(self) {}
+}
+impl T for &S {
+ fn f(self) {}
+}
+
+fn main() {
+ let $0t = &(S,2);
+ let s = t.0.f();
+}
+ "#,
+ r#"
+trait T {
+ fn f(self);
+}
+#[derive(Clone, Copy)]
+struct S;
+impl T for S {
+ fn f(self) {}
+}
+impl T for &S {
+ fn f(self) {}
+}
+
+fn main() {
+ let ($0_0, _1) = &(S,2);
+ let s = (*_0).f();
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn no_outer_parens_when_ref_deref() {
+ check_in_place_assist(
+ r#"
+#[derive(Clone, Copy)]
+struct S;
+impl S {
+ fn do_stuff(&self) -> i32 { 42 }
+}
+fn main() {
+ let $0t = &(S,&S);
+ let v = (&t.0).do_stuff();
+}
+ "#,
+ r#"
+#[derive(Clone, Copy)]
+struct S;
+impl S {
+ fn do_stuff(&self) -> i32 { 42 }
+}
+fn main() {
+ let ($0_0, _1) = &(S,&S);
+ let v = _0.do_stuff();
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn auto_ref_deref() {
+ check_in_place_assist(
+ r#"
+#[derive(Clone, Copy)]
+struct S;
+impl S {
+ fn do_stuff(&self) -> i32 { 42 }
+}
+fn main() {
+ let $0t = &(S,&S);
+ let v = (&t.0).do_stuff(); // no deref, remove parens
+ // `t.0` gets auto-refed -> no deref needed -> no parens
+ let v = t.0.do_stuff(); // no deref, no parens
+ let v = &t.0.do_stuff(); // `&` is for result -> no deref, no parens
+ // deref: `_1` is `&&S`, but method called is on `&S` -> there might be a method accepting `&&S`
+ let v = t.1.do_stuff(); // deref, parens
+}
+ "#,
+ r#"
+#[derive(Clone, Copy)]
+struct S;
+impl S {
+ fn do_stuff(&self) -> i32 { 42 }
+}
+fn main() {
+ let ($0_0, _1) = &(S,&S);
+ let v = _0.do_stuff(); // no deref, remove parens
+ // `t.0` gets auto-refed -> no deref needed -> no parens
+ let v = _0.do_stuff(); // no deref, no parens
+ let v = &_0.do_stuff(); // `&` is for result -> no deref, no parens
+ // deref: `_1` is `&&S`, but method called is on `&S` -> there might be a method accepting `&&S`
+ let v = (*_1).do_stuff(); // deref, parens
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn mutable() {
+ check_in_place_assist(
+ r#"
+fn f_owned(v: i32) {}
+fn f(v: &i32) {}
+fn f_mut(v: &mut i32) { *v = 42; }
+
+fn main() {
+ let $0t = &mut (1,2);
+ let v = t.0;
+ t.0 = 42;
+ f_owned(t.0);
+ f(&t.0);
+ f_mut(&mut t.0);
+}
+ "#,
+ r#"
+fn f_owned(v: i32) {}
+fn f(v: &i32) {}
+fn f_mut(v: &mut i32) { *v = 42; }
+
+fn main() {
+ let ($0_0, _1) = &mut (1,2);
+ let v = *_0;
+ *_0 = 42;
+ f_owned(*_0);
+ f(_0);
+ f_mut(_0);
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn with_ref_keyword() {
+ check_in_place_assist(
+ r#"
+fn f_owned(v: i32) {}
+fn f(v: &i32) {}
+
+fn main() {
+ let ref $0t = (1,2);
+ let v = t.0;
+ f_owned(t.0);
+ f(&t.0);
+}
+ "#,
+ r#"
+fn f_owned(v: i32) {}
+fn f(v: &i32) {}
+
+fn main() {
+ let (ref $0_0, ref _1) = (1,2);
+ let v = *_0;
+ f_owned(*_0);
+ f(_0);
+}
+ "#,
+ )
+ }
+ #[test]
+ fn with_ref_mut_keywords() {
+ check_in_place_assist(
+ r#"
+fn f_owned(v: i32) {}
+fn f(v: &i32) {}
+fn f_mut(v: &mut i32) { *v = 42; }
+
+fn main() {
+ let ref mut $0t = (1,2);
+ let v = t.0;
+ t.0 = 42;
+ f_owned(t.0);
+ f(&t.0);
+ f_mut(&mut t.0);
+}
+ "#,
+ r#"
+fn f_owned(v: i32) {}
+fn f(v: &i32) {}
+fn f_mut(v: &mut i32) { *v = 42; }
+
+fn main() {
+ let (ref mut $0_0, ref mut _1) = (1,2);
+ let v = *_0;
+ *_0 = 42;
+ f_owned(*_0);
+ f(_0);
+ f_mut(_0);
+}
+ "#,
+ )
+ }
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/expand_glob_import.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/expand_glob_import.rs
new file mode 100644
index 000000000..87f5018fb
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/expand_glob_import.rs
@@ -0,0 +1,900 @@
+use either::Either;
+use hir::{AssocItem, HasVisibility, Module, ModuleDef, Name, PathResolution, ScopeDef};
+use ide_db::{
+ defs::{Definition, NameRefClass},
+ search::SearchScope,
+};
+use stdx::never;
+use syntax::{
+ ast::{self, make},
+ ted, AstNode, Direction, SyntaxNode, SyntaxToken, T,
+};
+
+use crate::{
+ assist_context::{AssistContext, Assists},
+ AssistId, AssistKind,
+};
+
+// Assist: expand_glob_import
+//
+// Expands glob imports.
+//
+// ```
+// mod foo {
+// pub struct Bar;
+// pub struct Baz;
+// }
+//
+// use foo::*$0;
+//
+// fn qux(bar: Bar, baz: Baz) {}
+// ```
+// ->
+// ```
+// mod foo {
+// pub struct Bar;
+// pub struct Baz;
+// }
+//
+// use foo::{Bar, Baz};
+//
+// fn qux(bar: Bar, baz: Baz) {}
+// ```
+pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let star = ctx.find_token_syntax_at_offset(T![*])?;
+ let use_tree = star.parent().and_then(ast::UseTree::cast)?;
+ let (parent, mod_path) = find_parent_and_path(&star)?;
+ let target_module = match ctx.sema.resolve_path(&mod_path)? {
+ PathResolution::Def(ModuleDef::Module(it)) => it,
+ _ => return None,
+ };
+
+ let current_scope = ctx.sema.scope(&star.parent()?)?;
+ let current_module = current_scope.module();
+
+ let refs_in_target = find_refs_in_mod(ctx, target_module, current_module)?;
+ let imported_defs = find_imported_defs(ctx, star)?;
+
+ let target = parent.either(|n| n.syntax().clone(), |n| n.syntax().clone());
+ acc.add(
+ AssistId("expand_glob_import", AssistKind::RefactorRewrite),
+ "Expand glob import",
+ target.text_range(),
+ |builder| {
+ let use_tree = builder.make_mut(use_tree);
+
+ let names_to_import = find_names_to_import(ctx, refs_in_target, imported_defs);
+ let expanded = make::use_tree_list(names_to_import.iter().map(|n| {
+ let path = make::ext::ident_path(&n.to_string());
+ make::use_tree(path, None, None, false)
+ }))
+ .clone_for_update();
+
+ match use_tree.star_token() {
+ Some(star) => {
+ let needs_braces = use_tree.path().is_some() && names_to_import.len() != 1;
+ if needs_braces {
+ ted::replace(star, expanded.syntax())
+ } else {
+ let without_braces = expanded
+ .syntax()
+ .children_with_tokens()
+ .filter(|child| !matches!(child.kind(), T!['{'] | T!['}']))
+ .collect();
+ ted::replace_with_many(star, without_braces)
+ }
+ }
+ None => never!(),
+ }
+ },
+ )
+}
+
+fn find_parent_and_path(
+ star: &SyntaxToken,
+) -> Option<(Either<ast::UseTree, ast::UseTreeList>, ast::Path)> {
+ return star.parent_ancestors().find_map(|n| {
+ find_use_tree_list(n.clone())
+ .map(|(u, p)| (Either::Right(u), p))
+ .or_else(|| find_use_tree(n).map(|(u, p)| (Either::Left(u), p)))
+ });
+
+ fn find_use_tree_list(n: SyntaxNode) -> Option<(ast::UseTreeList, ast::Path)> {
+ let use_tree_list = ast::UseTreeList::cast(n)?;
+ let path = use_tree_list.parent_use_tree().path()?;
+ Some((use_tree_list, path))
+ }
+
+ fn find_use_tree(n: SyntaxNode) -> Option<(ast::UseTree, ast::Path)> {
+ let use_tree = ast::UseTree::cast(n)?;
+ let path = use_tree.path()?;
+ Some((use_tree, path))
+ }
+}
+
+fn def_is_referenced_in(def: Definition, ctx: &AssistContext<'_>) -> bool {
+ let search_scope = SearchScope::single_file(ctx.file_id());
+ def.usages(&ctx.sema).in_scope(search_scope).at_least_one()
+}
+
+#[derive(Debug, Clone)]
+struct Ref {
+ // could be alias
+ visible_name: Name,
+ def: Definition,
+}
+
+impl Ref {
+ fn from_scope_def(name: Name, scope_def: ScopeDef) -> Option<Self> {
+ match scope_def {
+ ScopeDef::ModuleDef(def) => {
+ Some(Ref { visible_name: name, def: Definition::from(def) })
+ }
+ _ => None,
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+struct Refs(Vec<Ref>);
+
+impl Refs {
+ fn used_refs(&self, ctx: &AssistContext<'_>) -> Refs {
+ Refs(
+ self.0
+ .clone()
+ .into_iter()
+ .filter(|r| {
+ if let Definition::Trait(tr) = r.def {
+ if tr.items(ctx.db()).into_iter().any(|ai| {
+ if let AssocItem::Function(f) = ai {
+ def_is_referenced_in(Definition::Function(f), ctx)
+ } else {
+ false
+ }
+ }) {
+ return true;
+ }
+ }
+
+ def_is_referenced_in(r.def, ctx)
+ })
+ .collect(),
+ )
+ }
+
+ fn filter_out_by_defs(&self, defs: Vec<Definition>) -> Refs {
+ Refs(self.0.clone().into_iter().filter(|r| !defs.contains(&r.def)).collect())
+ }
+}
+
+fn find_refs_in_mod(ctx: &AssistContext<'_>, module: Module, visible_from: Module) -> Option<Refs> {
+ if !is_mod_visible_from(ctx, module, visible_from) {
+ return None;
+ }
+
+ let module_scope = module.scope(ctx.db(), Some(visible_from));
+ let refs = module_scope.into_iter().filter_map(|(n, d)| Ref::from_scope_def(n, d)).collect();
+ Some(Refs(refs))
+}
+
+fn is_mod_visible_from(ctx: &AssistContext<'_>, module: Module, from: Module) -> bool {
+ match module.parent(ctx.db()) {
+ Some(parent) => {
+ module.visibility(ctx.db()).is_visible_from(ctx.db(), from.into())
+ && is_mod_visible_from(ctx, parent, from)
+ }
+ None => true,
+ }
+}
+
+// looks for name refs in parent use block's siblings
+//
+// mod bar {
+// mod qux {
+// struct Qux;
+// }
+//
+// pub use qux::Qux;
+// }
+//
+// ↓ ---------------
+// use foo::*$0;
+// use baz::Baz;
+// ↑ ---------------
+fn find_imported_defs(ctx: &AssistContext<'_>, star: SyntaxToken) -> Option<Vec<Definition>> {
+ let parent_use_item_syntax = star.parent_ancestors().find_map(|n| {
+ if ast::Use::can_cast(n.kind()) {
+ Some(n)
+ } else {
+ None
+ }
+ })?;
+
+ Some(
+ [Direction::Prev, Direction::Next]
+ .into_iter()
+ .flat_map(|dir| {
+ parent_use_item_syntax
+ .siblings(dir.to_owned())
+ .filter(|n| ast::Use::can_cast(n.kind()))
+ })
+ .flat_map(|n| n.descendants().filter_map(ast::NameRef::cast))
+ .filter_map(|r| match NameRefClass::classify(&ctx.sema, &r)? {
+ NameRefClass::Definition(
+ def @ (Definition::Macro(_)
+ | Definition::Module(_)
+ | Definition::Function(_)
+ | Definition::Adt(_)
+ | Definition::Variant(_)
+ | Definition::Const(_)
+ | Definition::Static(_)
+ | Definition::Trait(_)
+ | Definition::TypeAlias(_)),
+ ) => Some(def),
+ _ => None,
+ })
+ .collect(),
+ )
+}
+
+fn find_names_to_import(
+ ctx: &AssistContext<'_>,
+ refs_in_target: Refs,
+ imported_defs: Vec<Definition>,
+) -> Vec<Name> {
+ let used_refs = refs_in_target.used_refs(ctx).filter_out_by_defs(imported_defs);
+ used_refs.0.iter().map(|r| r.visible_name.clone()).collect()
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn expanding_glob_import() {
+ check_assist(
+ expand_glob_import,
+ r"
+mod foo {
+ pub struct Bar;
+ pub struct Baz;
+ pub struct Qux;
+
+ pub fn f() {}
+}
+
+use foo::*$0;
+
+fn qux(bar: Bar, baz: Baz) {
+ f();
+}
+",
+ r"
+mod foo {
+ pub struct Bar;
+ pub struct Baz;
+ pub struct Qux;
+
+ pub fn f() {}
+}
+
+use foo::{Bar, Baz, f};
+
+fn qux(bar: Bar, baz: Baz) {
+ f();
+}
+",
+ )
+ }
+
+ #[test]
+ fn expanding_glob_import_unused() {
+ check_assist(
+ expand_glob_import,
+ r"
+mod foo {
+ pub struct Bar;
+ pub struct Baz;
+ pub struct Qux;
+
+ pub fn f() {}
+}
+
+use foo::*$0;
+
+fn qux() {}
+",
+ r"
+mod foo {
+ pub struct Bar;
+ pub struct Baz;
+ pub struct Qux;
+
+ pub fn f() {}
+}
+
+use foo::{};
+
+fn qux() {}
+",
+ )
+ }
+
+ #[test]
+ fn expanding_glob_import_with_existing_explicit_names() {
+ check_assist(
+ expand_glob_import,
+ r"
+mod foo {
+ pub struct Bar;
+ pub struct Baz;
+ pub struct Qux;
+
+ pub fn f() {}
+}
+
+use foo::{*$0, f};
+
+fn qux(bar: Bar, baz: Baz) {
+ f();
+}
+",
+ r"
+mod foo {
+ pub struct Bar;
+ pub struct Baz;
+ pub struct Qux;
+
+ pub fn f() {}
+}
+
+use foo::{Bar, Baz, f};
+
+fn qux(bar: Bar, baz: Baz) {
+ f();
+}
+",
+ )
+ }
+
+ #[test]
+ fn expanding_glob_import_with_existing_uses_in_same_module() {
+ check_assist(
+ expand_glob_import,
+ r"
+mod foo {
+ pub struct Bar;
+ pub struct Baz;
+ pub struct Qux;
+
+ pub fn f() {}
+}
+
+use foo::Bar;
+use foo::{*$0, f};
+
+fn qux(bar: Bar, baz: Baz) {
+ f();
+}
+",
+ r"
+mod foo {
+ pub struct Bar;
+ pub struct Baz;
+ pub struct Qux;
+
+ pub fn f() {}
+}
+
+use foo::Bar;
+use foo::{Baz, f};
+
+fn qux(bar: Bar, baz: Baz) {
+ f();
+}
+",
+ )
+ }
+
+ #[test]
+ fn expanding_nested_glob_import() {
+ check_assist(
+ expand_glob_import,
+ r"
+mod foo {
+ pub mod bar {
+ pub struct Bar;
+ pub struct Baz;
+ pub struct Qux;
+
+ pub fn f() {}
+ }
+
+ pub mod baz {
+ pub fn g() {}
+ }
+}
+
+use foo::{bar::{*$0, f}, baz::*};
+
+fn qux(bar: Bar, baz: Baz) {
+ f();
+ g();
+}
+",
+ r"
+mod foo {
+ pub mod bar {
+ pub struct Bar;
+ pub struct Baz;
+ pub struct Qux;
+
+ pub fn f() {}
+ }
+
+ pub mod baz {
+ pub fn g() {}
+ }
+}
+
+use foo::{bar::{Bar, Baz, f}, baz::*};
+
+fn qux(bar: Bar, baz: Baz) {
+ f();
+ g();
+}
+",
+ );
+
+ check_assist(
+ expand_glob_import,
+ r"
+mod foo {
+ pub mod bar {
+ pub struct Bar;
+ pub struct Baz;
+ pub struct Qux;
+
+ pub fn f() {}
+ }
+
+ pub mod baz {
+ pub fn g() {}
+ }
+}
+
+use foo::{bar::{Bar, Baz, f}, baz::*$0};
+
+fn qux(bar: Bar, baz: Baz) {
+ f();
+ g();
+}
+",
+ r"
+mod foo {
+ pub mod bar {
+ pub struct Bar;
+ pub struct Baz;
+ pub struct Qux;
+
+ pub fn f() {}
+ }
+
+ pub mod baz {
+ pub fn g() {}
+ }
+}
+
+use foo::{bar::{Bar, Baz, f}, baz::g};
+
+fn qux(bar: Bar, baz: Baz) {
+ f();
+ g();
+}
+",
+ );
+
+ check_assist(
+ expand_glob_import,
+ r"
+mod foo {
+ pub mod bar {
+ pub struct Bar;
+ pub struct Baz;
+ pub struct Qux;
+
+ pub fn f() {}
+ }
+
+ pub mod baz {
+ pub fn g() {}
+
+ pub mod qux {
+ pub fn h() {}
+ pub fn m() {}
+
+ pub mod q {
+ pub fn j() {}
+ }
+ }
+ }
+}
+
+use foo::{
+ bar::{*, f},
+ baz::{g, qux::*$0}
+};
+
+fn qux(bar: Bar, baz: Baz) {
+ f();
+ g();
+ h();
+ q::j();
+}
+",
+ r"
+mod foo {
+ pub mod bar {
+ pub struct Bar;
+ pub struct Baz;
+ pub struct Qux;
+
+ pub fn f() {}
+ }
+
+ pub mod baz {
+ pub fn g() {}
+
+ pub mod qux {
+ pub fn h() {}
+ pub fn m() {}
+
+ pub mod q {
+ pub fn j() {}
+ }
+ }
+ }
+}
+
+use foo::{
+ bar::{*, f},
+ baz::{g, qux::{h, q}}
+};
+
+fn qux(bar: Bar, baz: Baz) {
+ f();
+ g();
+ h();
+ q::j();
+}
+",
+ );
+
+ check_assist(
+ expand_glob_import,
+ r"
+mod foo {
+ pub mod bar {
+ pub struct Bar;
+ pub struct Baz;
+ pub struct Qux;
+
+ pub fn f() {}
+ }
+
+ pub mod baz {
+ pub fn g() {}
+
+ pub mod qux {
+ pub fn h() {}
+ pub fn m() {}
+
+ pub mod q {
+ pub fn j() {}
+ }
+ }
+ }
+}
+
+use foo::{
+ bar::{*, f},
+ baz::{g, qux::{h, q::*$0}}
+};
+
+fn qux(bar: Bar, baz: Baz) {
+ f();
+ g();
+ h();
+ j();
+}
+",
+ r"
+mod foo {
+ pub mod bar {
+ pub struct Bar;
+ pub struct Baz;
+ pub struct Qux;
+
+ pub fn f() {}
+ }
+
+ pub mod baz {
+ pub fn g() {}
+
+ pub mod qux {
+ pub fn h() {}
+ pub fn m() {}
+
+ pub mod q {
+ pub fn j() {}
+ }
+ }
+ }
+}
+
+use foo::{
+ bar::{*, f},
+ baz::{g, qux::{h, q::j}}
+};
+
+fn qux(bar: Bar, baz: Baz) {
+ f();
+ g();
+ h();
+ j();
+}
+",
+ );
+
+ check_assist(
+ expand_glob_import,
+ r"
+mod foo {
+ pub mod bar {
+ pub struct Bar;
+ pub struct Baz;
+ pub struct Qux;
+
+ pub fn f() {}
+ }
+
+ pub mod baz {
+ pub fn g() {}
+
+ pub mod qux {
+ pub fn h() {}
+ pub fn m() {}
+
+ pub mod q {
+ pub fn j() {}
+ }
+ }
+ }
+}
+
+use foo::{
+ bar::{*, f},
+ baz::{g, qux::{q::j, *$0}}
+};
+
+fn qux(bar: Bar, baz: Baz) {
+ f();
+ g();
+ h();
+ j();
+}
+",
+ r"
+mod foo {
+ pub mod bar {
+ pub struct Bar;
+ pub struct Baz;
+ pub struct Qux;
+
+ pub fn f() {}
+ }
+
+ pub mod baz {
+ pub fn g() {}
+
+ pub mod qux {
+ pub fn h() {}
+ pub fn m() {}
+
+ pub mod q {
+ pub fn j() {}
+ }
+ }
+ }
+}
+
+use foo::{
+ bar::{*, f},
+ baz::{g, qux::{q::j, h}}
+};
+
+fn qux(bar: Bar, baz: Baz) {
+ f();
+ g();
+ h();
+ j();
+}
+",
+ );
+ }
+
+ #[test]
+ fn expanding_glob_import_with_macro_defs() {
+ check_assist(
+ expand_glob_import,
+ r#"
+//- /lib.rs crate:foo
+#[macro_export]
+macro_rules! bar {
+ () => ()
+}
+
+pub fn baz() {}
+
+//- /main.rs crate:main deps:foo
+use foo::*$0;
+
+fn main() {
+ bar!();
+ baz();
+}
+"#,
+ r#"
+use foo::{bar, baz};
+
+fn main() {
+ bar!();
+ baz();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn expanding_glob_import_with_trait_method_uses() {
+ check_assist(
+ expand_glob_import,
+ r"
+//- /lib.rs crate:foo
+pub trait Tr {
+ fn method(&self) {}
+}
+impl Tr for () {}
+
+//- /main.rs crate:main deps:foo
+use foo::*$0;
+
+fn main() {
+ ().method();
+}
+",
+ r"
+use foo::Tr;
+
+fn main() {
+ ().method();
+}
+",
+ );
+
+ check_assist(
+ expand_glob_import,
+ r"
+//- /lib.rs crate:foo
+pub trait Tr {
+ fn method(&self) {}
+}
+impl Tr for () {}
+
+pub trait Tr2 {
+ fn method2(&self) {}
+}
+impl Tr2 for () {}
+
+//- /main.rs crate:main deps:foo
+use foo::*$0;
+
+fn main() {
+ ().method();
+}
+",
+ r"
+use foo::Tr;
+
+fn main() {
+ ().method();
+}
+",
+ );
+ }
+
+ #[test]
+ fn expanding_is_not_applicable_if_target_module_is_not_accessible_from_current_scope() {
+ check_assist_not_applicable(
+ expand_glob_import,
+ r"
+mod foo {
+ mod bar {
+ pub struct Bar;
+ }
+}
+
+use foo::bar::*$0;
+
+fn baz(bar: Bar) {}
+",
+ );
+
+ check_assist_not_applicable(
+ expand_glob_import,
+ r"
+mod foo {
+ mod bar {
+ pub mod baz {
+ pub struct Baz;
+ }
+ }
+}
+
+use foo::bar::baz::*$0;
+
+fn qux(baz: Baz) {}
+",
+ );
+ }
+
+ #[test]
+ fn expanding_is_not_applicable_if_cursor_is_not_in_star_token() {
+ check_assist_not_applicable(
+ expand_glob_import,
+ r"
+ mod foo {
+ pub struct Bar;
+ pub struct Baz;
+ pub struct Qux;
+ }
+
+ use foo::Bar$0;
+
+ fn qux(bar: Bar, baz: Baz) {}
+ ",
+ )
+ }
+
+ #[test]
+ fn expanding_glob_import_single_nested_glob_only() {
+ check_assist(
+ expand_glob_import,
+ r"
+mod foo {
+ pub struct Bar;
+}
+
+use foo::{*$0};
+
+struct Baz {
+ bar: Bar
+}
+",
+ r"
+mod foo {
+ pub struct Bar;
+}
+
+use foo::{Bar};
+
+struct Baz {
+ bar: Bar
+}
+",
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs
new file mode 100644
index 000000000..52a55ead3
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs
@@ -0,0 +1,5333 @@
+use std::iter;
+
+use ast::make;
+use either::Either;
+use hir::{
+ HasSource, HirDisplay, InFile, Local, ModuleDef, PathResolution, Semantics, TypeInfo, TypeParam,
+};
+use ide_db::{
+ defs::{Definition, NameRefClass},
+ famous_defs::FamousDefs,
+ helpers::mod_path_to_ast,
+ imports::insert_use::{insert_use, ImportScope},
+ search::{FileReference, ReferenceCategory, SearchScope},
+ syntax_helpers::node_ext::{preorder_expr, walk_expr, walk_pat, walk_patterns_in_expr},
+ FxIndexSet, RootDatabase,
+};
+use itertools::Itertools;
+use stdx::format_to;
+use syntax::{
+ ast::{
+ self,
+ edit::{AstNodeEdit, IndentLevel},
+ AstNode, HasGenericParams,
+ },
+ match_ast, ted, SyntaxElement,
+ SyntaxKind::{self, COMMENT},
+ SyntaxNode, SyntaxToken, TextRange, TextSize, TokenAtOffset, WalkEvent, T,
+};
+
+use crate::{
+ assist_context::{AssistContext, Assists, TreeMutator},
+ utils::generate_impl_text,
+ AssistId,
+};
+
+// Assist: extract_function
+//
+// Extracts selected statements and comments into new function.
+//
+// ```
+// fn main() {
+// let n = 1;
+// $0let m = n + 2;
+// // calculate
+// let k = m + n;$0
+// let g = 3;
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// let n = 1;
+// fun_name(n);
+// let g = 3;
+// }
+//
+// fn $0fun_name(n: i32) {
+// let m = n + 2;
+// // calculate
+// let k = m + n;
+// }
+// ```
+pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let range = ctx.selection_trimmed();
+ if range.is_empty() {
+ return None;
+ }
+
+ let node = ctx.covering_element();
+ if node.kind() == COMMENT {
+ cov_mark::hit!(extract_function_in_comment_is_not_applicable);
+ return None;
+ }
+
+ let node = match node {
+ syntax::NodeOrToken::Node(n) => n,
+ syntax::NodeOrToken::Token(t) => t.parent()?,
+ };
+
+ let body = extraction_target(&node, range)?;
+ let container_info = body.analyze_container(&ctx.sema)?;
+
+ let (locals_used, self_param) = body.analyze(&ctx.sema);
+
+ let anchor = if self_param.is_some() { Anchor::Method } else { Anchor::Freestanding };
+ let insert_after = node_to_insert_after(&body, anchor)?;
+ let semantics_scope = ctx.sema.scope(&insert_after)?;
+ let module = semantics_scope.module();
+
+ let ret_ty = body.return_ty(ctx)?;
+ let control_flow = body.external_control_flow(ctx, &container_info)?;
+ let ret_values = body.ret_values(ctx, node.parent().as_ref().unwrap_or(&node));
+
+ let target_range = body.text_range();
+
+ let scope = ImportScope::find_insert_use_container(&node, &ctx.sema)?;
+
+ acc.add(
+ AssistId("extract_function", crate::AssistKind::RefactorExtract),
+ "Extract into function",
+ target_range,
+ move |builder| {
+ let outliving_locals: Vec<_> = ret_values.collect();
+ if stdx::never!(!outliving_locals.is_empty() && !ret_ty.is_unit()) {
+ // We should not have variables that outlive body if we have expression block
+ return;
+ }
+
+ let params =
+ body.extracted_function_params(ctx, &container_info, locals_used.iter().copied());
+
+ let extracted_from_trait_impl = body.extracted_from_trait_impl();
+
+ let name = make_function_name(&semantics_scope);
+
+ let fun = Function {
+ name,
+ self_param,
+ params,
+ control_flow,
+ ret_ty,
+ body,
+ outliving_locals,
+ mods: container_info,
+ };
+
+ let new_indent = IndentLevel::from_node(&insert_after);
+ let old_indent = fun.body.indent_level();
+
+ builder.replace(target_range, make_call(ctx, &fun, old_indent));
+
+ let fn_def = match fun.self_param_adt(ctx) {
+ Some(adt) if extracted_from_trait_impl => {
+ let fn_def = format_function(ctx, module, &fun, old_indent, new_indent + 1);
+ generate_impl_text(&adt, &fn_def).replace("{\n\n", "{")
+ }
+ _ => format_function(ctx, module, &fun, old_indent, new_indent),
+ };
+
+ if fn_def.contains("ControlFlow") {
+ let scope = match scope {
+ ImportScope::File(it) => ImportScope::File(builder.make_mut(it)),
+ ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
+ ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
+ };
+
+ let control_flow_enum =
+ FamousDefs(&ctx.sema, module.krate()).core_ops_ControlFlow();
+
+ if let Some(control_flow_enum) = control_flow_enum {
+ let mod_path = module.find_use_path_prefixed(
+ ctx.sema.db,
+ ModuleDef::from(control_flow_enum),
+ ctx.config.insert_use.prefix_kind,
+ );
+
+ if let Some(mod_path) = mod_path {
+ insert_use(&scope, mod_path_to_ast(&mod_path), &ctx.config.insert_use);
+ }
+ }
+ }
+
+ let insert_offset = insert_after.text_range().end();
+
+ match ctx.config.snippet_cap {
+ Some(cap) => builder.insert_snippet(cap, insert_offset, fn_def),
+ None => builder.insert(insert_offset, fn_def),
+ };
+ },
+ )
+}
+
+fn make_function_name(semantics_scope: &hir::SemanticsScope<'_>) -> ast::NameRef {
+ let mut names_in_scope = vec![];
+ semantics_scope.process_all_names(&mut |name, _| names_in_scope.push(name.to_string()));
+
+ let default_name = "fun_name";
+
+ let mut name = default_name.to_string();
+ let mut counter = 0;
+ while names_in_scope.contains(&name) {
+ counter += 1;
+ name = format!("{}{}", &default_name, counter)
+ }
+ make::name_ref(&name)
+}
+
+/// Try to guess what user wants to extract
+///
+/// We have basically have two cases:
+/// * We want whole node, like `loop {}`, `2 + 2`, `{ let n = 1; }` exprs.
+/// Then we can use `ast::Expr`
+/// * We want a few statements for a block. E.g.
+/// ```rust,no_run
+/// fn foo() -> i32 {
+/// let m = 1;
+/// $0
+/// let n = 2;
+/// let k = 3;
+/// k + n
+/// $0
+/// }
+/// ```
+///
+fn extraction_target(node: &SyntaxNode, selection_range: TextRange) -> Option<FunctionBody> {
+ if let Some(stmt) = ast::Stmt::cast(node.clone()) {
+ return match stmt {
+ ast::Stmt::Item(_) => None,
+ ast::Stmt::ExprStmt(_) | ast::Stmt::LetStmt(_) => Some(FunctionBody::from_range(
+ node.parent().and_then(ast::StmtList::cast)?,
+ node.text_range(),
+ )),
+ };
+ }
+
+ // Covering element returned the parent block of one or multiple statements that have been selected
+ if let Some(stmt_list) = ast::StmtList::cast(node.clone()) {
+ if let Some(block_expr) = stmt_list.syntax().parent().and_then(ast::BlockExpr::cast) {
+ if block_expr.syntax().text_range() == selection_range {
+ return FunctionBody::from_expr(block_expr.into());
+ }
+ }
+
+ // Extract the full statements.
+ return Some(FunctionBody::from_range(stmt_list, selection_range));
+ }
+
+ let expr = ast::Expr::cast(node.clone())?;
+ // A node got selected fully
+ if node.text_range() == selection_range {
+ return FunctionBody::from_expr(expr);
+ }
+
+ node.ancestors().find_map(ast::Expr::cast).and_then(FunctionBody::from_expr)
+}
+
+#[derive(Debug)]
+struct Function {
+ name: ast::NameRef,
+ self_param: Option<ast::SelfParam>,
+ params: Vec<Param>,
+ control_flow: ControlFlow,
+ ret_ty: RetType,
+ body: FunctionBody,
+ outliving_locals: Vec<OutlivedLocal>,
+ mods: ContainerInfo,
+}
+
+#[derive(Debug)]
+struct Param {
+ var: Local,
+ ty: hir::Type,
+ move_local: bool,
+ requires_mut: bool,
+ is_copy: bool,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum ParamKind {
+ Value,
+ MutValue,
+ SharedRef,
+ MutRef,
+}
+
+#[derive(Debug, Eq, PartialEq)]
+enum FunType {
+ Unit,
+ Single(hir::Type),
+ Tuple(Vec<hir::Type>),
+}
+
+/// Where to put extracted function definition
+#[derive(Debug)]
+enum Anchor {
+ /// Extract free function and put right after current top-level function
+ Freestanding,
+ /// Extract method and put right after current function in the impl-block
+ Method,
+}
+
+// FIXME: ControlFlow and ContainerInfo both track some function modifiers, feels like these two should
+// probably be merged somehow.
+#[derive(Debug)]
+struct ControlFlow {
+ kind: Option<FlowKind>,
+ is_async: bool,
+ is_unsafe: bool,
+}
+
+/// The thing whose expression we are extracting from. Can be a function, const, static, const arg, ...
+#[derive(Clone, Debug)]
+struct ContainerInfo {
+ is_const: bool,
+ is_in_tail: bool,
+ parent_loop: Option<SyntaxNode>,
+ /// The function's return type, const's type etc.
+ ret_type: Option<hir::Type>,
+ generic_param_lists: Vec<ast::GenericParamList>,
+ where_clauses: Vec<ast::WhereClause>,
+}
+
+/// Control flow that is exported from extracted function
+///
+/// E.g.:
+/// ```rust,no_run
+/// loop {
+/// $0
+/// if 42 == 42 {
+/// break;
+/// }
+/// $0
+/// }
+/// ```
+#[derive(Debug, Clone)]
+enum FlowKind {
+ /// Return with value (`return $expr;`)
+ Return(Option<ast::Expr>),
+ Try {
+ kind: TryKind,
+ },
+ /// Break with label and value (`break 'label $expr;`)
+ Break(Option<ast::Lifetime>, Option<ast::Expr>),
+ /// Continue with label (`continue 'label;`)
+ Continue(Option<ast::Lifetime>),
+}
+
+#[derive(Debug, Clone)]
+enum TryKind {
+ Option,
+ Result { ty: hir::Type },
+}
+
+#[derive(Debug)]
+enum RetType {
+ Expr(hir::Type),
+ Stmt,
+}
+
+impl RetType {
+ fn is_unit(&self) -> bool {
+ match self {
+ RetType::Expr(ty) => ty.is_unit(),
+ RetType::Stmt => true,
+ }
+ }
+}
+
+/// Semantically same as `ast::Expr`, but preserves identity when using only part of the Block
+/// This is the future function body, the part that is being extracted.
+#[derive(Debug)]
+enum FunctionBody {
+ Expr(ast::Expr),
+ Span { parent: ast::StmtList, text_range: TextRange },
+}
+
+#[derive(Debug)]
+struct OutlivedLocal {
+ local: Local,
+ mut_usage_outside_body: bool,
+}
+
+/// Container of local variable usages
+///
+/// Semanticall same as `UsageSearchResult`, but provides more convenient interface
+struct LocalUsages(ide_db::search::UsageSearchResult);
+
+impl LocalUsages {
+ fn find_local_usages(ctx: &AssistContext<'_>, var: Local) -> Self {
+ Self(
+ Definition::Local(var)
+ .usages(&ctx.sema)
+ .in_scope(SearchScope::single_file(ctx.file_id()))
+ .all(),
+ )
+ }
+
+ fn iter(&self) -> impl Iterator<Item = &FileReference> + '_ {
+ self.0.iter().flat_map(|(_, rs)| rs)
+ }
+}
+
+impl Function {
+ fn return_type(&self, ctx: &AssistContext<'_>) -> FunType {
+ match &self.ret_ty {
+ RetType::Expr(ty) if ty.is_unit() => FunType::Unit,
+ RetType::Expr(ty) => FunType::Single(ty.clone()),
+ RetType::Stmt => match self.outliving_locals.as_slice() {
+ [] => FunType::Unit,
+ [var] => FunType::Single(var.local.ty(ctx.db())),
+ vars => {
+ let types = vars.iter().map(|v| v.local.ty(ctx.db())).collect();
+ FunType::Tuple(types)
+ }
+ },
+ }
+ }
+
+ fn self_param_adt(&self, ctx: &AssistContext<'_>) -> Option<ast::Adt> {
+ let self_param = self.self_param.as_ref()?;
+ let def = ctx.sema.to_def(self_param)?;
+ let adt = def.ty(ctx.db()).strip_references().as_adt()?;
+ let InFile { file_id: _, value } = adt.source(ctx.db())?;
+ Some(value)
+ }
+}
+
+impl ParamKind {
+ fn is_ref(&self) -> bool {
+ matches!(self, ParamKind::SharedRef | ParamKind::MutRef)
+ }
+}
+
+impl Param {
+ fn kind(&self) -> ParamKind {
+ match (self.move_local, self.requires_mut, self.is_copy) {
+ (false, true, _) => ParamKind::MutRef,
+ (false, false, false) => ParamKind::SharedRef,
+ (true, true, _) => ParamKind::MutValue,
+ (_, false, _) => ParamKind::Value,
+ }
+ }
+
+ fn to_arg(&self, ctx: &AssistContext<'_>) -> ast::Expr {
+ let var = path_expr_from_local(ctx, self.var);
+ match self.kind() {
+ ParamKind::Value | ParamKind::MutValue => var,
+ ParamKind::SharedRef => make::expr_ref(var, false),
+ ParamKind::MutRef => make::expr_ref(var, true),
+ }
+ }
+
+ fn to_param(&self, ctx: &AssistContext<'_>, module: hir::Module) -> ast::Param {
+ let var = self.var.name(ctx.db()).to_string();
+ let var_name = make::name(&var);
+ let pat = match self.kind() {
+ ParamKind::MutValue => make::ident_pat(false, true, var_name),
+ ParamKind::Value | ParamKind::SharedRef | ParamKind::MutRef => {
+ make::ext::simple_ident_pat(var_name)
+ }
+ };
+
+ let ty = make_ty(&self.ty, ctx, module);
+ let ty = match self.kind() {
+ ParamKind::Value | ParamKind::MutValue => ty,
+ ParamKind::SharedRef => make::ty_ref(ty, false),
+ ParamKind::MutRef => make::ty_ref(ty, true),
+ };
+
+ make::param(pat.into(), ty)
+ }
+}
+
+impl TryKind {
+ fn of_ty(ty: hir::Type, ctx: &AssistContext<'_>) -> Option<TryKind> {
+ if ty.is_unknown() {
+ // We favour Result for `expr?`
+ return Some(TryKind::Result { ty });
+ }
+ let adt = ty.as_adt()?;
+ let name = adt.name(ctx.db());
+ // FIXME: use lang items to determine if it is std type or user defined
+ // E.g. if user happens to define type named `Option`, we would have false positive
+ match name.to_string().as_str() {
+ "Option" => Some(TryKind::Option),
+ "Result" => Some(TryKind::Result { ty }),
+ _ => None,
+ }
+ }
+}
+
+impl FlowKind {
+ fn make_result_handler(&self, expr: Option<ast::Expr>) -> ast::Expr {
+ match self {
+ FlowKind::Return(_) => make::expr_return(expr),
+ FlowKind::Break(label, _) => make::expr_break(label.clone(), expr),
+ FlowKind::Try { .. } => {
+ stdx::never!("cannot have result handler with try");
+ expr.unwrap_or_else(|| make::expr_return(None))
+ }
+ FlowKind::Continue(label) => {
+ stdx::always!(expr.is_none(), "continue with value is not possible");
+ make::expr_continue(label.clone())
+ }
+ }
+ }
+
+ fn expr_ty(&self, ctx: &AssistContext<'_>) -> Option<hir::Type> {
+ match self {
+ FlowKind::Return(Some(expr)) | FlowKind::Break(_, Some(expr)) => {
+ ctx.sema.type_of_expr(expr).map(TypeInfo::adjusted)
+ }
+ FlowKind::Try { .. } => {
+ stdx::never!("try does not have defined expr_ty");
+ None
+ }
+ _ => None,
+ }
+ }
+}
+
+impl FunctionBody {
+ fn parent(&self) -> Option<SyntaxNode> {
+ match self {
+ FunctionBody::Expr(expr) => expr.syntax().parent(),
+ FunctionBody::Span { parent, .. } => Some(parent.syntax().clone()),
+ }
+ }
+
+ fn node(&self) -> &SyntaxNode {
+ match self {
+ FunctionBody::Expr(e) => e.syntax(),
+ FunctionBody::Span { parent, .. } => parent.syntax(),
+ }
+ }
+
+ fn extracted_from_trait_impl(&self) -> bool {
+ match self.node().ancestors().find_map(ast::Impl::cast) {
+ Some(c) => return c.trait_().is_some(),
+ None => false,
+ }
+ }
+
+ fn descendants(&self) -> impl Iterator<Item = SyntaxNode> {
+ match self {
+ FunctionBody::Expr(expr) => expr.syntax().descendants(),
+ FunctionBody::Span { parent, .. } => parent.syntax().descendants(),
+ }
+ }
+
+ fn descendant_paths(&self) -> impl Iterator<Item = ast::Path> {
+ self.descendants().filter_map(|node| {
+ match_ast! {
+ match node {
+ ast::Path(it) => Some(it),
+ _ => None
+ }
+ }
+ })
+ }
+
+ fn from_expr(expr: ast::Expr) -> Option<Self> {
+ match expr {
+ ast::Expr::BreakExpr(it) => it.expr().map(Self::Expr),
+ ast::Expr::ReturnExpr(it) => it.expr().map(Self::Expr),
+ ast::Expr::BlockExpr(it) if !it.is_standalone() => None,
+ expr => Some(Self::Expr(expr)),
+ }
+ }
+
+ fn from_range(parent: ast::StmtList, selected: TextRange) -> FunctionBody {
+ let full_body = parent.syntax().children_with_tokens();
+
+ let mut text_range = full_body
+ .filter(|it| ast::Stmt::can_cast(it.kind()) || it.kind() == COMMENT)
+ .map(|element| element.text_range())
+ .filter(|&range| selected.intersect(range).filter(|it| !it.is_empty()).is_some())
+ .reduce(|acc, stmt| acc.cover(stmt));
+
+ if let Some(tail_range) = parent
+ .tail_expr()
+ .map(|it| it.syntax().text_range())
+ .filter(|&it| selected.intersect(it).is_some())
+ {
+ text_range = Some(match text_range {
+ Some(text_range) => text_range.cover(tail_range),
+ None => tail_range,
+ });
+ }
+ Self::Span { parent, text_range: text_range.unwrap_or(selected) }
+ }
+
+ fn indent_level(&self) -> IndentLevel {
+ match &self {
+ FunctionBody::Expr(expr) => IndentLevel::from_node(expr.syntax()),
+ FunctionBody::Span { parent, .. } => IndentLevel::from_node(parent.syntax()) + 1,
+ }
+ }
+
+ fn tail_expr(&self) -> Option<ast::Expr> {
+ match &self {
+ FunctionBody::Expr(expr) => Some(expr.clone()),
+ FunctionBody::Span { parent, text_range } => {
+ let tail_expr = parent.tail_expr()?;
+ text_range.contains_range(tail_expr.syntax().text_range()).then(|| tail_expr)
+ }
+ }
+ }
+
+ fn walk_expr(&self, cb: &mut dyn FnMut(ast::Expr)) {
+ match self {
+ FunctionBody::Expr(expr) => walk_expr(expr, cb),
+ FunctionBody::Span { parent, text_range } => {
+ parent
+ .statements()
+ .filter(|stmt| text_range.contains_range(stmt.syntax().text_range()))
+ .filter_map(|stmt| match stmt {
+ ast::Stmt::ExprStmt(expr_stmt) => expr_stmt.expr(),
+ ast::Stmt::Item(_) => None,
+ ast::Stmt::LetStmt(stmt) => stmt.initializer(),
+ })
+ .for_each(|expr| walk_expr(&expr, cb));
+ if let Some(expr) = parent
+ .tail_expr()
+ .filter(|it| text_range.contains_range(it.syntax().text_range()))
+ {
+ walk_expr(&expr, cb);
+ }
+ }
+ }
+ }
+
+ fn preorder_expr(&self, cb: &mut dyn FnMut(WalkEvent<ast::Expr>) -> bool) {
+ match self {
+ FunctionBody::Expr(expr) => preorder_expr(expr, cb),
+ FunctionBody::Span { parent, text_range } => {
+ parent
+ .statements()
+ .filter(|stmt| text_range.contains_range(stmt.syntax().text_range()))
+ .filter_map(|stmt| match stmt {
+ ast::Stmt::ExprStmt(expr_stmt) => expr_stmt.expr(),
+ ast::Stmt::Item(_) => None,
+ ast::Stmt::LetStmt(stmt) => stmt.initializer(),
+ })
+ .for_each(|expr| preorder_expr(&expr, cb));
+ if let Some(expr) = parent
+ .tail_expr()
+ .filter(|it| text_range.contains_range(it.syntax().text_range()))
+ {
+ preorder_expr(&expr, cb);
+ }
+ }
+ }
+ }
+
+ fn walk_pat(&self, cb: &mut dyn FnMut(ast::Pat)) {
+ match self {
+ FunctionBody::Expr(expr) => walk_patterns_in_expr(expr, cb),
+ FunctionBody::Span { parent, text_range } => {
+ parent
+ .statements()
+ .filter(|stmt| text_range.contains_range(stmt.syntax().text_range()))
+ .for_each(|stmt| match stmt {
+ ast::Stmt::ExprStmt(expr_stmt) => {
+ if let Some(expr) = expr_stmt.expr() {
+ walk_patterns_in_expr(&expr, cb)
+ }
+ }
+ ast::Stmt::Item(_) => (),
+ ast::Stmt::LetStmt(stmt) => {
+ if let Some(pat) = stmt.pat() {
+ walk_pat(&pat, cb);
+ }
+ if let Some(expr) = stmt.initializer() {
+ walk_patterns_in_expr(&expr, cb);
+ }
+ }
+ });
+ if let Some(expr) = parent
+ .tail_expr()
+ .filter(|it| text_range.contains_range(it.syntax().text_range()))
+ {
+ walk_patterns_in_expr(&expr, cb);
+ }
+ }
+ }
+ }
+
+ fn text_range(&self) -> TextRange {
+ match self {
+ FunctionBody::Expr(expr) => expr.syntax().text_range(),
+ &FunctionBody::Span { text_range, .. } => text_range,
+ }
+ }
+
+ fn contains_range(&self, range: TextRange) -> bool {
+ self.text_range().contains_range(range)
+ }
+
+ fn precedes_range(&self, range: TextRange) -> bool {
+ self.text_range().end() <= range.start()
+ }
+
+ fn contains_node(&self, node: &SyntaxNode) -> bool {
+ self.contains_range(node.text_range())
+ }
+}
+
+impl FunctionBody {
+ /// Analyzes a function body, returning the used local variables that are referenced in it as well as
+ /// whether it contains an await expression.
+ fn analyze(
+ &self,
+ sema: &Semantics<'_, RootDatabase>,
+ ) -> (FxIndexSet<Local>, Option<ast::SelfParam>) {
+ let mut self_param = None;
+ let mut res = FxIndexSet::default();
+ let mut cb = |name_ref: Option<_>| {
+ let local_ref =
+ match name_ref.and_then(|name_ref| NameRefClass::classify(sema, &name_ref)) {
+ Some(
+ NameRefClass::Definition(Definition::Local(local_ref))
+ | NameRefClass::FieldShorthand { local_ref, field_ref: _ },
+ ) => local_ref,
+ _ => return,
+ };
+ let InFile { file_id, value } = local_ref.source(sema.db);
+ // locals defined inside macros are not relevant to us
+ if !file_id.is_macro() {
+ match value {
+ Either::Right(it) => {
+ self_param.replace(it);
+ }
+ Either::Left(_) => {
+ res.insert(local_ref);
+ }
+ }
+ }
+ };
+ self.walk_expr(&mut |expr| match expr {
+ ast::Expr::PathExpr(path_expr) => {
+ cb(path_expr.path().and_then(|it| it.as_single_name_ref()))
+ }
+ ast::Expr::ClosureExpr(closure_expr) => {
+ if let Some(body) = closure_expr.body() {
+ body.syntax().descendants().map(ast::NameRef::cast).for_each(|it| cb(it));
+ }
+ }
+ ast::Expr::MacroExpr(expr) => {
+ if let Some(tt) = expr.macro_call().and_then(|call| call.token_tree()) {
+ tt.syntax()
+ .children_with_tokens()
+ .flat_map(SyntaxElement::into_token)
+ .filter(|it| it.kind() == SyntaxKind::IDENT)
+ .flat_map(|t| sema.descend_into_macros(t))
+ .for_each(|t| cb(t.parent().and_then(ast::NameRef::cast)));
+ }
+ }
+ _ => (),
+ });
+ (res, self_param)
+ }
+
+ fn analyze_container(&self, sema: &Semantics<'_, RootDatabase>) -> Option<ContainerInfo> {
+ let mut ancestors = self.parent()?.ancestors();
+ let infer_expr_opt = |expr| sema.type_of_expr(&expr?).map(TypeInfo::adjusted);
+ let mut parent_loop = None;
+ let mut set_parent_loop = |loop_: &dyn ast::HasLoopBody| {
+ if loop_
+ .loop_body()
+ .map_or(false, |it| it.syntax().text_range().contains_range(self.text_range()))
+ {
+ parent_loop.get_or_insert(loop_.syntax().clone());
+ }
+ };
+
+ let (is_const, expr, ty) = loop {
+ let anc = ancestors.next()?;
+ break match_ast! {
+ match anc {
+ ast::ClosureExpr(closure) => (false, closure.body(), infer_expr_opt(closure.body())),
+ ast::BlockExpr(block_expr) => {
+ let (constness, block) = match block_expr.modifier() {
+ Some(ast::BlockModifier::Const(_)) => (true, block_expr),
+ Some(ast::BlockModifier::Try(_)) => (false, block_expr),
+ Some(ast::BlockModifier::Label(label)) if label.lifetime().is_some() => (false, block_expr),
+ _ => continue,
+ };
+ let expr = Some(ast::Expr::BlockExpr(block));
+ (constness, expr.clone(), infer_expr_opt(expr))
+ },
+ ast::Fn(fn_) => {
+ let func = sema.to_def(&fn_)?;
+ let mut ret_ty = func.ret_type(sema.db);
+ if func.is_async(sema.db) {
+ if let Some(async_ret) = func.async_ret_type(sema.db) {
+ ret_ty = async_ret;
+ }
+ }
+ (fn_.const_token().is_some(), fn_.body().map(ast::Expr::BlockExpr), Some(ret_ty))
+ },
+ ast::Static(statik) => {
+ (true, statik.body(), Some(sema.to_def(&statik)?.ty(sema.db)))
+ },
+ ast::ConstArg(ca) => {
+ (true, ca.expr(), infer_expr_opt(ca.expr()))
+ },
+ ast::Const(konst) => {
+ (true, konst.body(), Some(sema.to_def(&konst)?.ty(sema.db)))
+ },
+ ast::ConstParam(cp) => {
+ (true, cp.default_val(), Some(sema.to_def(&cp)?.ty(sema.db)))
+ },
+ ast::ConstBlockPat(cbp) => {
+ let expr = cbp.block_expr().map(ast::Expr::BlockExpr);
+ (true, expr.clone(), infer_expr_opt(expr))
+ },
+ ast::Variant(__) => return None,
+ ast::Meta(__) => return None,
+ ast::LoopExpr(it) => {
+ set_parent_loop(&it);
+ continue;
+ },
+ ast::ForExpr(it) => {
+ set_parent_loop(&it);
+ continue;
+ },
+ ast::WhileExpr(it) => {
+ set_parent_loop(&it);
+ continue;
+ },
+ _ => continue,
+ }
+ };
+ };
+ let container_tail = match expr? {
+ ast::Expr::BlockExpr(block) => block.tail_expr(),
+ expr => Some(expr),
+ };
+ let is_in_tail =
+ container_tail.zip(self.tail_expr()).map_or(false, |(container_tail, body_tail)| {
+ container_tail.syntax().text_range().contains_range(body_tail.syntax().text_range())
+ });
+
+ let parent = self.parent()?;
+ let parents = generic_parents(&parent);
+ let generic_param_lists = parents.iter().filter_map(|it| it.generic_param_list()).collect();
+ let where_clauses = parents.iter().filter_map(|it| it.where_clause()).collect();
+
+ Some(ContainerInfo {
+ is_in_tail,
+ is_const,
+ parent_loop,
+ ret_type: ty,
+ generic_param_lists,
+ where_clauses,
+ })
+ }
+
+ fn return_ty(&self, ctx: &AssistContext<'_>) -> Option<RetType> {
+ match self.tail_expr() {
+ Some(expr) => ctx.sema.type_of_expr(&expr).map(TypeInfo::original).map(RetType::Expr),
+ None => Some(RetType::Stmt),
+ }
+ }
+
+ /// Local variables defined inside `body` that are accessed outside of it
+ fn ret_values<'a>(
+ &self,
+ ctx: &'a AssistContext<'_>,
+ parent: &SyntaxNode,
+ ) -> impl Iterator<Item = OutlivedLocal> + 'a {
+ let parent = parent.clone();
+ let range = self.text_range();
+ locals_defined_in_body(&ctx.sema, self)
+ .into_iter()
+ .filter_map(move |local| local_outlives_body(ctx, range, local, &parent))
+ }
+
+ /// Analyses the function body for external control flow.
+ fn external_control_flow(
+ &self,
+ ctx: &AssistContext<'_>,
+ container_info: &ContainerInfo,
+ ) -> Option<ControlFlow> {
+ let mut ret_expr = None;
+ let mut try_expr = None;
+ let mut break_expr = None;
+ let mut continue_expr = None;
+ let mut is_async = false;
+ let mut _is_unsafe = false;
+
+ let mut unsafe_depth = 0;
+ let mut loop_depth = 0;
+
+ self.preorder_expr(&mut |expr| {
+ let expr = match expr {
+ WalkEvent::Enter(e) => e,
+ WalkEvent::Leave(expr) => {
+ match expr {
+ ast::Expr::LoopExpr(_)
+ | ast::Expr::ForExpr(_)
+ | ast::Expr::WhileExpr(_) => loop_depth -= 1,
+ ast::Expr::BlockExpr(block_expr) if block_expr.unsafe_token().is_some() => {
+ unsafe_depth -= 1
+ }
+ _ => (),
+ }
+ return false;
+ }
+ };
+ match expr {
+ ast::Expr::LoopExpr(_) | ast::Expr::ForExpr(_) | ast::Expr::WhileExpr(_) => {
+ loop_depth += 1;
+ }
+ ast::Expr::BlockExpr(block_expr) if block_expr.unsafe_token().is_some() => {
+ unsafe_depth += 1
+ }
+ ast::Expr::ReturnExpr(it) => {
+ ret_expr = Some(it);
+ }
+ ast::Expr::TryExpr(it) => {
+ try_expr = Some(it);
+ }
+ ast::Expr::BreakExpr(it) if loop_depth == 0 => {
+ break_expr = Some(it);
+ }
+ ast::Expr::ContinueExpr(it) if loop_depth == 0 => {
+ continue_expr = Some(it);
+ }
+ ast::Expr::AwaitExpr(_) => is_async = true,
+ // FIXME: Do unsafe analysis on expression, sem highlighting knows this so we should be able
+ // to just lift that out of there
+ // expr if unsafe_depth ==0 && expr.is_unsafe => is_unsafe = true,
+ _ => {}
+ }
+ false
+ });
+
+ let kind = match (try_expr, ret_expr, break_expr, continue_expr) {
+ (Some(_), _, None, None) => {
+ let ret_ty = container_info.ret_type.clone()?;
+ let kind = TryKind::of_ty(ret_ty, ctx)?;
+
+ Some(FlowKind::Try { kind })
+ }
+ (Some(_), _, _, _) => {
+ cov_mark::hit!(external_control_flow_try_and_bc);
+ return None;
+ }
+ (None, Some(r), None, None) => Some(FlowKind::Return(r.expr())),
+ (None, Some(_), _, _) => {
+ cov_mark::hit!(external_control_flow_return_and_bc);
+ return None;
+ }
+ (None, None, Some(_), Some(_)) => {
+ cov_mark::hit!(external_control_flow_break_and_continue);
+ return None;
+ }
+ (None, None, Some(b), None) => Some(FlowKind::Break(b.lifetime(), b.expr())),
+ (None, None, None, Some(c)) => Some(FlowKind::Continue(c.lifetime())),
+ (None, None, None, None) => None,
+ };
+
+ Some(ControlFlow { kind, is_async, is_unsafe: _is_unsafe })
+ }
+
+ /// find variables that should be extracted as params
+ ///
+ /// Computes additional info that affects param type and mutability
+ fn extracted_function_params(
+ &self,
+ ctx: &AssistContext<'_>,
+ container_info: &ContainerInfo,
+ locals: impl Iterator<Item = Local>,
+ ) -> Vec<Param> {
+ locals
+ .map(|local| (local, local.source(ctx.db())))
+ .filter(|(_, src)| is_defined_outside_of_body(ctx, self, src))
+ .filter_map(|(local, src)| match src.value {
+ Either::Left(src) => Some((local, src)),
+ Either::Right(_) => {
+ stdx::never!(false, "Local::is_self returned false, but source is SelfParam");
+ None
+ }
+ })
+ .map(|(var, src)| {
+ let usages = LocalUsages::find_local_usages(ctx, var);
+ let ty = var.ty(ctx.db());
+
+ let defined_outside_parent_loop = container_info
+ .parent_loop
+ .as_ref()
+ .map_or(true, |it| it.text_range().contains_range(src.syntax().text_range()));
+
+ let is_copy = ty.is_copy(ctx.db());
+ let has_usages = self.has_usages_after_body(&usages);
+ let requires_mut =
+ !ty.is_mutable_reference() && has_exclusive_usages(ctx, &usages, self);
+ // We can move the value into the function call if it's not used after the call,
+ // if the var is not used but defined outside a loop we are extracting from we can't move it either
+ // as the function will reuse it in the next iteration.
+ let move_local = (!has_usages && defined_outside_parent_loop) || ty.is_reference();
+ Param { var, ty, move_local, requires_mut, is_copy }
+ })
+ .collect()
+ }
+
+ fn has_usages_after_body(&self, usages: &LocalUsages) -> bool {
+ usages.iter().any(|reference| self.precedes_range(reference.range))
+ }
+}
+
+enum GenericParent {
+ Fn(ast::Fn),
+ Impl(ast::Impl),
+ Trait(ast::Trait),
+}
+
+impl GenericParent {
+ fn generic_param_list(&self) -> Option<ast::GenericParamList> {
+ match self {
+ GenericParent::Fn(fn_) => fn_.generic_param_list(),
+ GenericParent::Impl(impl_) => impl_.generic_param_list(),
+ GenericParent::Trait(trait_) => trait_.generic_param_list(),
+ }
+ }
+
+ fn where_clause(&self) -> Option<ast::WhereClause> {
+ match self {
+ GenericParent::Fn(fn_) => fn_.where_clause(),
+ GenericParent::Impl(impl_) => impl_.where_clause(),
+ GenericParent::Trait(trait_) => trait_.where_clause(),
+ }
+ }
+}
+
+/// Search `parent`'s ancestors for items with potentially applicable generic parameters
+fn generic_parents(parent: &SyntaxNode) -> Vec<GenericParent> {
+ let mut list = Vec::new();
+ if let Some(parent_item) = parent.ancestors().find_map(ast::Item::cast) {
+ match parent_item {
+ ast::Item::Fn(ref fn_) => {
+ if let Some(parent_parent) = parent_item
+ .syntax()
+ .parent()
+ .and_then(|it| it.parent())
+ .and_then(ast::Item::cast)
+ {
+ match parent_parent {
+ ast::Item::Impl(impl_) => list.push(GenericParent::Impl(impl_)),
+ ast::Item::Trait(trait_) => list.push(GenericParent::Trait(trait_)),
+ _ => (),
+ }
+ }
+ list.push(GenericParent::Fn(fn_.clone()));
+ }
+ _ => (),
+ }
+ }
+ list
+}
+
+/// checks if relevant var is used with `&mut` access inside body
+fn has_exclusive_usages(
+ ctx: &AssistContext<'_>,
+ usages: &LocalUsages,
+ body: &FunctionBody,
+) -> bool {
+ usages
+ .iter()
+ .filter(|reference| body.contains_range(reference.range))
+ .any(|reference| reference_is_exclusive(reference, body, ctx))
+}
+
+/// checks if this reference requires `&mut` access inside node
+fn reference_is_exclusive(
+ reference: &FileReference,
+ node: &dyn HasTokenAtOffset,
+ ctx: &AssistContext<'_>,
+) -> bool {
+ // we directly modify variable with set: `n = 0`, `n += 1`
+ if reference.category == Some(ReferenceCategory::Write) {
+ return true;
+ }
+
+ // we take `&mut` reference to variable: `&mut v`
+ let path = match path_element_of_reference(node, reference) {
+ Some(path) => path,
+ None => return false,
+ };
+
+ expr_require_exclusive_access(ctx, &path).unwrap_or(false)
+}
+
+/// checks if this expr requires `&mut` access, recurses on field access
+fn expr_require_exclusive_access(ctx: &AssistContext<'_>, expr: &ast::Expr) -> Option<bool> {
+ if let ast::Expr::MacroExpr(_) = expr {
+ // FIXME: expand macro and check output for mutable usages of the variable?
+ return None;
+ }
+
+ let parent = expr.syntax().parent()?;
+
+ if let Some(bin_expr) = ast::BinExpr::cast(parent.clone()) {
+ if matches!(bin_expr.op_kind()?, ast::BinaryOp::Assignment { .. }) {
+ return Some(bin_expr.lhs()?.syntax() == expr.syntax());
+ }
+ return Some(false);
+ }
+
+ if let Some(ref_expr) = ast::RefExpr::cast(parent.clone()) {
+ return Some(ref_expr.mut_token().is_some());
+ }
+
+ if let Some(method_call) = ast::MethodCallExpr::cast(parent.clone()) {
+ let func = ctx.sema.resolve_method_call(&method_call)?;
+ let self_param = func.self_param(ctx.db())?;
+ let access = self_param.access(ctx.db());
+
+ return Some(matches!(access, hir::Access::Exclusive));
+ }
+
+ if let Some(field) = ast::FieldExpr::cast(parent) {
+ return expr_require_exclusive_access(ctx, &field.into());
+ }
+
+ Some(false)
+}
+
+trait HasTokenAtOffset {
+ fn token_at_offset(&self, offset: TextSize) -> TokenAtOffset<SyntaxToken>;
+}
+
+impl HasTokenAtOffset for SyntaxNode {
+ fn token_at_offset(&self, offset: TextSize) -> TokenAtOffset<SyntaxToken> {
+ SyntaxNode::token_at_offset(self, offset)
+ }
+}
+
+impl HasTokenAtOffset for FunctionBody {
+ fn token_at_offset(&self, offset: TextSize) -> TokenAtOffset<SyntaxToken> {
+ match self {
+ FunctionBody::Expr(expr) => expr.syntax().token_at_offset(offset),
+ FunctionBody::Span { parent, text_range } => {
+ match parent.syntax().token_at_offset(offset) {
+ TokenAtOffset::None => TokenAtOffset::None,
+ TokenAtOffset::Single(t) => {
+ if text_range.contains_range(t.text_range()) {
+ TokenAtOffset::Single(t)
+ } else {
+ TokenAtOffset::None
+ }
+ }
+ TokenAtOffset::Between(a, b) => {
+ match (
+ text_range.contains_range(a.text_range()),
+ text_range.contains_range(b.text_range()),
+ ) {
+ (true, true) => TokenAtOffset::Between(a, b),
+ (true, false) => TokenAtOffset::Single(a),
+ (false, true) => TokenAtOffset::Single(b),
+ (false, false) => TokenAtOffset::None,
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+/// find relevant `ast::Expr` for reference
+///
+/// # Preconditions
+///
+/// `node` must cover `reference`, that is `node.text_range().contains_range(reference.range)`
+fn path_element_of_reference(
+ node: &dyn HasTokenAtOffset,
+ reference: &FileReference,
+) -> Option<ast::Expr> {
+ let token = node.token_at_offset(reference.range.start()).right_biased().or_else(|| {
+ stdx::never!(false, "cannot find token at variable usage: {:?}", reference);
+ None
+ })?;
+ let path = token.parent_ancestors().find_map(ast::Expr::cast).or_else(|| {
+ stdx::never!(false, "cannot find path parent of variable usage: {:?}", token);
+ None
+ })?;
+ stdx::always!(
+ matches!(path, ast::Expr::PathExpr(_) | ast::Expr::MacroExpr(_)),
+ "unexpected expression type for variable usage: {:?}",
+ path
+ );
+ Some(path)
+}
+
+/// list local variables defined inside `body`
+fn locals_defined_in_body(
+ sema: &Semantics<'_, RootDatabase>,
+ body: &FunctionBody,
+) -> FxIndexSet<Local> {
+ // FIXME: this doesn't work well with macros
+ // see https://github.com/rust-lang/rust-analyzer/pull/7535#discussion_r570048550
+ let mut res = FxIndexSet::default();
+ body.walk_pat(&mut |pat| {
+ if let ast::Pat::IdentPat(pat) = pat {
+ if let Some(local) = sema.to_def(&pat) {
+ res.insert(local);
+ }
+ }
+ });
+ res
+}
+
+/// Returns usage details if local variable is used after(outside of) body
+fn local_outlives_body(
+ ctx: &AssistContext<'_>,
+ body_range: TextRange,
+ local: Local,
+ parent: &SyntaxNode,
+) -> Option<OutlivedLocal> {
+ let usages = LocalUsages::find_local_usages(ctx, local);
+ let mut has_mut_usages = false;
+ let mut any_outlives = false;
+ for usage in usages.iter() {
+ if body_range.end() <= usage.range.start() {
+ has_mut_usages |= reference_is_exclusive(usage, parent, ctx);
+ any_outlives |= true;
+ if has_mut_usages {
+ break; // no need to check more elements we have all the info we wanted
+ }
+ }
+ }
+ if !any_outlives {
+ return None;
+ }
+ Some(OutlivedLocal { local, mut_usage_outside_body: has_mut_usages })
+}
+
+/// checks if the relevant local was defined before(outside of) body
+fn is_defined_outside_of_body(
+ ctx: &AssistContext<'_>,
+ body: &FunctionBody,
+ src: &hir::InFile<Either<ast::IdentPat, ast::SelfParam>>,
+) -> bool {
+ src.file_id.original_file(ctx.db()) == ctx.file_id()
+ && !body.contains_node(either_syntax(&src.value))
+}
+
+fn either_syntax(value: &Either<ast::IdentPat, ast::SelfParam>) -> &SyntaxNode {
+ match value {
+ Either::Left(pat) => pat.syntax(),
+ Either::Right(it) => it.syntax(),
+ }
+}
+
+/// find where to put extracted function definition
+///
+/// Function should be put right after returned node
+fn node_to_insert_after(body: &FunctionBody, anchor: Anchor) -> Option<SyntaxNode> {
+ let node = body.node();
+ let mut ancestors = node.ancestors().peekable();
+ let mut last_ancestor = None;
+ while let Some(next_ancestor) = ancestors.next() {
+ match next_ancestor.kind() {
+ SyntaxKind::SOURCE_FILE => break,
+ SyntaxKind::ITEM_LIST if !matches!(anchor, Anchor::Freestanding) => continue,
+ SyntaxKind::ITEM_LIST => {
+ if ancestors.peek().map(SyntaxNode::kind) == Some(SyntaxKind::MODULE) {
+ break;
+ }
+ }
+ SyntaxKind::ASSOC_ITEM_LIST if !matches!(anchor, Anchor::Method) => continue,
+ SyntaxKind::ASSOC_ITEM_LIST if body.extracted_from_trait_impl() => continue,
+ SyntaxKind::ASSOC_ITEM_LIST => {
+ if ancestors.peek().map(SyntaxNode::kind) == Some(SyntaxKind::IMPL) {
+ break;
+ }
+ }
+ _ => (),
+ }
+ last_ancestor = Some(next_ancestor);
+ }
+ last_ancestor
+}
+
+fn make_call(ctx: &AssistContext<'_>, fun: &Function, indent: IndentLevel) -> String {
+ let ret_ty = fun.return_type(ctx);
+
+ let args = make::arg_list(fun.params.iter().map(|param| param.to_arg(ctx)));
+ let name = fun.name.clone();
+ let mut call_expr = if fun.self_param.is_some() {
+ let self_arg = make::expr_path(make::ext::ident_path("self"));
+ make::expr_method_call(self_arg, name, args)
+ } else {
+ let func = make::expr_path(make::path_unqualified(make::path_segment(name)));
+ make::expr_call(func, args)
+ };
+
+ let handler = FlowHandler::from_ret_ty(fun, &ret_ty);
+
+ if fun.control_flow.is_async {
+ call_expr = make::expr_await(call_expr);
+ }
+ let expr = handler.make_call_expr(call_expr).indent(indent);
+
+ let mut_modifier = |var: &OutlivedLocal| if var.mut_usage_outside_body { "mut " } else { "" };
+
+ let mut buf = String::new();
+ match fun.outliving_locals.as_slice() {
+ [] => {}
+ [var] => {
+ format_to!(buf, "let {}{} = ", mut_modifier(var), var.local.name(ctx.db()))
+ }
+ vars => {
+ buf.push_str("let (");
+ let bindings = vars.iter().format_with(", ", |local, f| {
+ f(&format_args!("{}{}", mut_modifier(local), local.local.name(ctx.db())))
+ });
+ format_to!(buf, "{}", bindings);
+ buf.push_str(") = ");
+ }
+ }
+
+ format_to!(buf, "{}", expr);
+ let insert_comma = fun
+ .body
+ .parent()
+ .and_then(ast::MatchArm::cast)
+ .map_or(false, |it| it.comma_token().is_none());
+ if insert_comma {
+ buf.push(',');
+ } else if fun.ret_ty.is_unit() && (!fun.outliving_locals.is_empty() || !expr.is_block_like()) {
+ buf.push(';');
+ }
+ buf
+}
+
+enum FlowHandler {
+ None,
+ Try { kind: TryKind },
+ If { action: FlowKind },
+ IfOption { action: FlowKind },
+ MatchOption { none: FlowKind },
+ MatchResult { err: FlowKind },
+}
+
+impl FlowHandler {
+ fn from_ret_ty(fun: &Function, ret_ty: &FunType) -> FlowHandler {
+ match &fun.control_flow.kind {
+ None => FlowHandler::None,
+ Some(flow_kind) => {
+ let action = flow_kind.clone();
+ if *ret_ty == FunType::Unit {
+ match flow_kind {
+ FlowKind::Return(None)
+ | FlowKind::Break(_, None)
+ | FlowKind::Continue(_) => FlowHandler::If { action },
+ FlowKind::Return(_) | FlowKind::Break(_, _) => {
+ FlowHandler::IfOption { action }
+ }
+ FlowKind::Try { kind } => FlowHandler::Try { kind: kind.clone() },
+ }
+ } else {
+ match flow_kind {
+ FlowKind::Return(None)
+ | FlowKind::Break(_, None)
+ | FlowKind::Continue(_) => FlowHandler::MatchOption { none: action },
+ FlowKind::Return(_) | FlowKind::Break(_, _) => {
+ FlowHandler::MatchResult { err: action }
+ }
+ FlowKind::Try { kind } => FlowHandler::Try { kind: kind.clone() },
+ }
+ }
+ }
+ }
+ }
+
+ fn make_call_expr(&self, call_expr: ast::Expr) -> ast::Expr {
+ match self {
+ FlowHandler::None => call_expr,
+ FlowHandler::Try { kind: _ } => make::expr_try(call_expr),
+ FlowHandler::If { action } => {
+ let action = action.make_result_handler(None);
+ let stmt = make::expr_stmt(action);
+ let block = make::block_expr(iter::once(stmt.into()), None);
+ let controlflow_break_path = make::path_from_text("ControlFlow::Break");
+ let condition = make::expr_let(
+ make::tuple_struct_pat(
+ controlflow_break_path,
+ iter::once(make::wildcard_pat().into()),
+ )
+ .into(),
+ call_expr,
+ );
+ make::expr_if(condition.into(), block, None)
+ }
+ FlowHandler::IfOption { action } => {
+ let path = make::ext::ident_path("Some");
+ let value_pat = make::ext::simple_ident_pat(make::name("value"));
+ let pattern = make::tuple_struct_pat(path, iter::once(value_pat.into()));
+ let cond = make::expr_let(pattern.into(), call_expr);
+ let value = make::expr_path(make::ext::ident_path("value"));
+ let action_expr = action.make_result_handler(Some(value));
+ let action_stmt = make::expr_stmt(action_expr);
+ let then = make::block_expr(iter::once(action_stmt.into()), None);
+ make::expr_if(cond.into(), then, None)
+ }
+ FlowHandler::MatchOption { none } => {
+ let some_name = "value";
+
+ let some_arm = {
+ let path = make::ext::ident_path("Some");
+ let value_pat = make::ext::simple_ident_pat(make::name(some_name));
+ let pat = make::tuple_struct_pat(path, iter::once(value_pat.into()));
+ let value = make::expr_path(make::ext::ident_path(some_name));
+ make::match_arm(iter::once(pat.into()), None, value)
+ };
+ let none_arm = {
+ let path = make::ext::ident_path("None");
+ let pat = make::path_pat(path);
+ make::match_arm(iter::once(pat), None, none.make_result_handler(None))
+ };
+ let arms = make::match_arm_list(vec![some_arm, none_arm]);
+ make::expr_match(call_expr, arms)
+ }
+ FlowHandler::MatchResult { err } => {
+ let ok_name = "value";
+ let err_name = "value";
+
+ let ok_arm = {
+ let path = make::ext::ident_path("Ok");
+ let value_pat = make::ext::simple_ident_pat(make::name(ok_name));
+ let pat = make::tuple_struct_pat(path, iter::once(value_pat.into()));
+ let value = make::expr_path(make::ext::ident_path(ok_name));
+ make::match_arm(iter::once(pat.into()), None, value)
+ };
+ let err_arm = {
+ let path = make::ext::ident_path("Err");
+ let value_pat = make::ext::simple_ident_pat(make::name(err_name));
+ let pat = make::tuple_struct_pat(path, iter::once(value_pat.into()));
+ let value = make::expr_path(make::ext::ident_path(err_name));
+ make::match_arm(
+ iter::once(pat.into()),
+ None,
+ err.make_result_handler(Some(value)),
+ )
+ };
+ let arms = make::match_arm_list(vec![ok_arm, err_arm]);
+ make::expr_match(call_expr, arms)
+ }
+ }
+ }
+}
+
+fn path_expr_from_local(ctx: &AssistContext<'_>, var: Local) -> ast::Expr {
+ let name = var.name(ctx.db()).to_string();
+ make::expr_path(make::ext::ident_path(&name))
+}
+
+fn format_function(
+ ctx: &AssistContext<'_>,
+ module: hir::Module,
+ fun: &Function,
+ old_indent: IndentLevel,
+ new_indent: IndentLevel,
+) -> String {
+ let mut fn_def = String::new();
+ let params = fun.make_param_list(ctx, module);
+ let ret_ty = fun.make_ret_ty(ctx, module);
+ let body = make_body(ctx, old_indent, new_indent, fun);
+ let const_kw = if fun.mods.is_const { "const " } else { "" };
+ let async_kw = if fun.control_flow.is_async { "async " } else { "" };
+ let unsafe_kw = if fun.control_flow.is_unsafe { "unsafe " } else { "" };
+ let (generic_params, where_clause) = make_generic_params_and_where_clause(ctx, fun);
+ match ctx.config.snippet_cap {
+ Some(_) => format_to!(
+ fn_def,
+ "\n\n{}{}{}{}fn $0{}",
+ new_indent,
+ const_kw,
+ async_kw,
+ unsafe_kw,
+ fun.name,
+ ),
+ None => format_to!(
+ fn_def,
+ "\n\n{}{}{}{}fn {}",
+ new_indent,
+ const_kw,
+ async_kw,
+ unsafe_kw,
+ fun.name,
+ ),
+ }
+
+ if let Some(generic_params) = generic_params {
+ format_to!(fn_def, "{}", generic_params);
+ }
+
+ format_to!(fn_def, "{}", params);
+
+ if let Some(ret_ty) = ret_ty {
+ format_to!(fn_def, " {}", ret_ty);
+ }
+
+ if let Some(where_clause) = where_clause {
+ format_to!(fn_def, " {}", where_clause);
+ }
+
+ format_to!(fn_def, " {}", body);
+
+ fn_def
+}
+
+fn make_generic_params_and_where_clause(
+ ctx: &AssistContext<'_>,
+ fun: &Function,
+) -> (Option<ast::GenericParamList>, Option<ast::WhereClause>) {
+ let used_type_params = fun.type_params(ctx);
+
+ let generic_param_list = make_generic_param_list(ctx, fun, &used_type_params);
+ let where_clause = make_where_clause(ctx, fun, &used_type_params);
+
+ (generic_param_list, where_clause)
+}
+
+fn make_generic_param_list(
+ ctx: &AssistContext<'_>,
+ fun: &Function,
+ used_type_params: &[TypeParam],
+) -> Option<ast::GenericParamList> {
+ let mut generic_params = fun
+ .mods
+ .generic_param_lists
+ .iter()
+ .flat_map(|parent_params| {
+ parent_params
+ .generic_params()
+ .filter(|param| param_is_required(ctx, param, used_type_params))
+ })
+ .peekable();
+
+ if generic_params.peek().is_some() {
+ Some(make::generic_param_list(generic_params))
+ } else {
+ None
+ }
+}
+
+fn param_is_required(
+ ctx: &AssistContext<'_>,
+ param: &ast::GenericParam,
+ used_type_params: &[TypeParam],
+) -> bool {
+ match param {
+ ast::GenericParam::ConstParam(_) | ast::GenericParam::LifetimeParam(_) => false,
+ ast::GenericParam::TypeParam(type_param) => match &ctx.sema.to_def(type_param) {
+ Some(def) => used_type_params.contains(def),
+ _ => false,
+ },
+ }
+}
+
+fn make_where_clause(
+ ctx: &AssistContext<'_>,
+ fun: &Function,
+ used_type_params: &[TypeParam],
+) -> Option<ast::WhereClause> {
+ let mut predicates = fun
+ .mods
+ .where_clauses
+ .iter()
+ .flat_map(|parent_where_clause| {
+ parent_where_clause
+ .predicates()
+ .filter(|pred| pred_is_required(ctx, pred, used_type_params))
+ })
+ .peekable();
+
+ if predicates.peek().is_some() {
+ Some(make::where_clause(predicates))
+ } else {
+ None
+ }
+}
+
+fn pred_is_required(
+ ctx: &AssistContext<'_>,
+ pred: &ast::WherePred,
+ used_type_params: &[TypeParam],
+) -> bool {
+ match resolved_type_param(ctx, pred) {
+ Some(it) => used_type_params.contains(&it),
+ None => false,
+ }
+}
+
+fn resolved_type_param(ctx: &AssistContext<'_>, pred: &ast::WherePred) -> Option<TypeParam> {
+ let path = match pred.ty()? {
+ ast::Type::PathType(path_type) => path_type.path(),
+ _ => None,
+ }?;
+
+ match ctx.sema.resolve_path(&path)? {
+ PathResolution::TypeParam(type_param) => Some(type_param),
+ _ => None,
+ }
+}
+
+impl Function {
+ /// Collect all the `TypeParam`s used in the `body` and `params`.
+ fn type_params(&self, ctx: &AssistContext<'_>) -> Vec<TypeParam> {
+ let type_params_in_descendant_paths =
+ self.body.descendant_paths().filter_map(|it| match ctx.sema.resolve_path(&it) {
+ Some(PathResolution::TypeParam(type_param)) => Some(type_param),
+ _ => None,
+ });
+ let type_params_in_params = self.params.iter().filter_map(|p| p.ty.as_type_param(ctx.db()));
+ type_params_in_descendant_paths.chain(type_params_in_params).collect()
+ }
+
+ fn make_param_list(&self, ctx: &AssistContext<'_>, module: hir::Module) -> ast::ParamList {
+ let self_param = self.self_param.clone();
+ let params = self.params.iter().map(|param| param.to_param(ctx, module));
+ make::param_list(self_param, params)
+ }
+
+ fn make_ret_ty(&self, ctx: &AssistContext<'_>, module: hir::Module) -> Option<ast::RetType> {
+ let fun_ty = self.return_type(ctx);
+ let handler = if self.mods.is_in_tail {
+ FlowHandler::None
+ } else {
+ FlowHandler::from_ret_ty(self, &fun_ty)
+ };
+ let ret_ty = match &handler {
+ FlowHandler::None => {
+ if matches!(fun_ty, FunType::Unit) {
+ return None;
+ }
+ fun_ty.make_ty(ctx, module)
+ }
+ FlowHandler::Try { kind: TryKind::Option } => {
+ make::ext::ty_option(fun_ty.make_ty(ctx, module))
+ }
+ FlowHandler::Try { kind: TryKind::Result { ty: parent_ret_ty } } => {
+ let handler_ty = parent_ret_ty
+ .type_arguments()
+ .nth(1)
+ .map(|ty| make_ty(&ty, ctx, module))
+ .unwrap_or_else(make::ty_placeholder);
+ make::ext::ty_result(fun_ty.make_ty(ctx, module), handler_ty)
+ }
+ FlowHandler::If { .. } => make::ty("ControlFlow<()>"),
+ FlowHandler::IfOption { action } => {
+ let handler_ty = action
+ .expr_ty(ctx)
+ .map(|ty| make_ty(&ty, ctx, module))
+ .unwrap_or_else(make::ty_placeholder);
+ make::ext::ty_option(handler_ty)
+ }
+ FlowHandler::MatchOption { .. } => make::ext::ty_option(fun_ty.make_ty(ctx, module)),
+ FlowHandler::MatchResult { err } => {
+ let handler_ty = err
+ .expr_ty(ctx)
+ .map(|ty| make_ty(&ty, ctx, module))
+ .unwrap_or_else(make::ty_placeholder);
+ make::ext::ty_result(fun_ty.make_ty(ctx, module), handler_ty)
+ }
+ };
+ Some(make::ret_type(ret_ty))
+ }
+}
+
+impl FunType {
+ fn make_ty(&self, ctx: &AssistContext<'_>, module: hir::Module) -> ast::Type {
+ match self {
+ FunType::Unit => make::ty_unit(),
+ FunType::Single(ty) => make_ty(ty, ctx, module),
+ FunType::Tuple(types) => match types.as_slice() {
+ [] => {
+ stdx::never!("tuple type with 0 elements");
+ make::ty_unit()
+ }
+ [ty] => {
+ stdx::never!("tuple type with 1 element");
+ make_ty(ty, ctx, module)
+ }
+ types => {
+ let types = types.iter().map(|ty| make_ty(ty, ctx, module));
+ make::ty_tuple(types)
+ }
+ },
+ }
+ }
+}
+
+fn make_body(
+ ctx: &AssistContext<'_>,
+ old_indent: IndentLevel,
+ new_indent: IndentLevel,
+ fun: &Function,
+) -> ast::BlockExpr {
+ let ret_ty = fun.return_type(ctx);
+ let handler = if fun.mods.is_in_tail {
+ FlowHandler::None
+ } else {
+ FlowHandler::from_ret_ty(fun, &ret_ty)
+ };
+
+ let block = match &fun.body {
+ FunctionBody::Expr(expr) => {
+ let expr = rewrite_body_segment(ctx, &fun.params, &handler, expr.syntax());
+ let expr = ast::Expr::cast(expr).unwrap();
+ match expr {
+ ast::Expr::BlockExpr(block) => {
+ // If the extracted expression is itself a block, there is no need to wrap it inside another block.
+ let block = block.dedent(old_indent);
+ // Recreate the block for formatting consistency with other extracted functions.
+ make::block_expr(block.statements(), block.tail_expr())
+ }
+ _ => {
+ let expr = expr.dedent(old_indent).indent(IndentLevel(1));
+
+ make::block_expr(Vec::new(), Some(expr))
+ }
+ }
+ }
+ FunctionBody::Span { parent, text_range } => {
+ let mut elements: Vec<_> = parent
+ .syntax()
+ .children_with_tokens()
+ .filter(|it| text_range.contains_range(it.text_range()))
+ .map(|it| match &it {
+ syntax::NodeOrToken::Node(n) => syntax::NodeOrToken::Node(
+ rewrite_body_segment(ctx, &fun.params, &handler, n),
+ ),
+ _ => it,
+ })
+ .collect();
+
+ let mut tail_expr = match &elements.last() {
+ Some(syntax::NodeOrToken::Node(node)) if ast::Expr::can_cast(node.kind()) => {
+ ast::Expr::cast(node.clone())
+ }
+ _ => None,
+ };
+
+ match tail_expr {
+ Some(_) => {
+ elements.pop();
+ }
+ None => match fun.outliving_locals.as_slice() {
+ [] => {}
+ [var] => {
+ tail_expr = Some(path_expr_from_local(ctx, var.local));
+ }
+ vars => {
+ let exprs = vars.iter().map(|var| path_expr_from_local(ctx, var.local));
+ let expr = make::expr_tuple(exprs);
+ tail_expr = Some(expr);
+ }
+ },
+ };
+
+ let body_indent = IndentLevel(1);
+ let elements = elements
+ .into_iter()
+ .map(|node_or_token| match &node_or_token {
+ syntax::NodeOrToken::Node(node) => match ast::Stmt::cast(node.clone()) {
+ Some(stmt) => {
+ let indented = stmt.dedent(old_indent).indent(body_indent);
+ let ast_node = indented.syntax().clone_subtree();
+ syntax::NodeOrToken::Node(ast_node)
+ }
+ _ => node_or_token,
+ },
+ _ => node_or_token,
+ })
+ .collect::<Vec<SyntaxElement>>();
+ let tail_expr = tail_expr.map(|expr| expr.dedent(old_indent).indent(body_indent));
+
+ make::hacky_block_expr_with_comments(elements, tail_expr)
+ }
+ };
+
+ let block = match &handler {
+ FlowHandler::None => block,
+ FlowHandler::Try { kind } => {
+ let block = with_default_tail_expr(block, make::expr_unit());
+ map_tail_expr(block, |tail_expr| {
+ let constructor = match kind {
+ TryKind::Option => "Some",
+ TryKind::Result { .. } => "Ok",
+ };
+ let func = make::expr_path(make::ext::ident_path(constructor));
+ let args = make::arg_list(iter::once(tail_expr));
+ make::expr_call(func, args)
+ })
+ }
+ FlowHandler::If { .. } => {
+ let controlflow_continue = make::expr_call(
+ make::expr_path(make::path_from_text("ControlFlow::Continue")),
+ make::arg_list(iter::once(make::expr_unit())),
+ );
+ with_tail_expr(block, controlflow_continue)
+ }
+ FlowHandler::IfOption { .. } => {
+ let none = make::expr_path(make::ext::ident_path("None"));
+ with_tail_expr(block, none)
+ }
+ FlowHandler::MatchOption { .. } => map_tail_expr(block, |tail_expr| {
+ let some = make::expr_path(make::ext::ident_path("Some"));
+ let args = make::arg_list(iter::once(tail_expr));
+ make::expr_call(some, args)
+ }),
+ FlowHandler::MatchResult { .. } => map_tail_expr(block, |tail_expr| {
+ let ok = make::expr_path(make::ext::ident_path("Ok"));
+ let args = make::arg_list(iter::once(tail_expr));
+ make::expr_call(ok, args)
+ }),
+ };
+
+ block.indent(new_indent)
+}
+
+fn map_tail_expr(block: ast::BlockExpr, f: impl FnOnce(ast::Expr) -> ast::Expr) -> ast::BlockExpr {
+ let tail_expr = match block.tail_expr() {
+ Some(tail_expr) => tail_expr,
+ None => return block,
+ };
+ make::block_expr(block.statements(), Some(f(tail_expr)))
+}
+
+fn with_default_tail_expr(block: ast::BlockExpr, tail_expr: ast::Expr) -> ast::BlockExpr {
+ match block.tail_expr() {
+ Some(_) => block,
+ None => make::block_expr(block.statements(), Some(tail_expr)),
+ }
+}
+
+fn with_tail_expr(block: ast::BlockExpr, tail_expr: ast::Expr) -> ast::BlockExpr {
+ let stmt_tail = block.tail_expr().map(|expr| make::expr_stmt(expr).into());
+ let stmts = block.statements().chain(stmt_tail);
+ make::block_expr(stmts, Some(tail_expr))
+}
+
+fn format_type(ty: &hir::Type, ctx: &AssistContext<'_>, module: hir::Module) -> String {
+ ty.display_source_code(ctx.db(), module.into()).ok().unwrap_or_else(|| "_".to_string())
+}
+
+fn make_ty(ty: &hir::Type, ctx: &AssistContext<'_>, module: hir::Module) -> ast::Type {
+ let ty_str = format_type(ty, ctx, module);
+ make::ty(&ty_str)
+}
+
+fn rewrite_body_segment(
+ ctx: &AssistContext<'_>,
+ params: &[Param],
+ handler: &FlowHandler,
+ syntax: &SyntaxNode,
+) -> SyntaxNode {
+ let syntax = fix_param_usages(ctx, params, syntax);
+ update_external_control_flow(handler, &syntax);
+ syntax
+}
+
+/// change all usages to account for added `&`/`&mut` for some params
+fn fix_param_usages(ctx: &AssistContext<'_>, params: &[Param], syntax: &SyntaxNode) -> SyntaxNode {
+ let mut usages_for_param: Vec<(&Param, Vec<ast::Expr>)> = Vec::new();
+
+ let tm = TreeMutator::new(syntax);
+
+ for param in params {
+ if !param.kind().is_ref() {
+ continue;
+ }
+
+ let usages = LocalUsages::find_local_usages(ctx, param.var);
+ let usages = usages
+ .iter()
+ .filter(|reference| syntax.text_range().contains_range(reference.range))
+ .filter_map(|reference| path_element_of_reference(syntax, reference))
+ .map(|expr| tm.make_mut(&expr));
+
+ usages_for_param.push((param, usages.collect()));
+ }
+
+ let res = tm.make_syntax_mut(syntax);
+
+ for (param, usages) in usages_for_param {
+ for usage in usages {
+ match usage.syntax().ancestors().skip(1).find_map(ast::Expr::cast) {
+ Some(ast::Expr::MethodCallExpr(_) | ast::Expr::FieldExpr(_)) => {
+ // do nothing
+ }
+ Some(ast::Expr::RefExpr(node))
+ if param.kind() == ParamKind::MutRef && node.mut_token().is_some() =>
+ {
+ ted::replace(node.syntax(), node.expr().unwrap().syntax());
+ }
+ Some(ast::Expr::RefExpr(node))
+ if param.kind() == ParamKind::SharedRef && node.mut_token().is_none() =>
+ {
+ ted::replace(node.syntax(), node.expr().unwrap().syntax());
+ }
+ Some(_) | None => {
+ let p = &make::expr_prefix(T![*], usage.clone()).clone_for_update();
+ ted::replace(usage.syntax(), p.syntax())
+ }
+ }
+ }
+ }
+
+ res
+}
+
+fn update_external_control_flow(handler: &FlowHandler, syntax: &SyntaxNode) {
+ let mut nested_loop = None;
+ let mut nested_scope = None;
+ for event in syntax.preorder() {
+ match event {
+ WalkEvent::Enter(e) => match e.kind() {
+ SyntaxKind::LOOP_EXPR | SyntaxKind::WHILE_EXPR | SyntaxKind::FOR_EXPR => {
+ if nested_loop.is_none() {
+ nested_loop = Some(e.clone());
+ }
+ }
+ SyntaxKind::FN
+ | SyntaxKind::CONST
+ | SyntaxKind::STATIC
+ | SyntaxKind::IMPL
+ | SyntaxKind::MODULE => {
+ if nested_scope.is_none() {
+ nested_scope = Some(e.clone());
+ }
+ }
+ _ => {}
+ },
+ WalkEvent::Leave(e) => {
+ if nested_scope.is_none() {
+ if let Some(expr) = ast::Expr::cast(e.clone()) {
+ match expr {
+ ast::Expr::ReturnExpr(return_expr) if nested_scope.is_none() => {
+ let expr = return_expr.expr();
+ if let Some(replacement) = make_rewritten_flow(handler, expr) {
+ ted::replace(return_expr.syntax(), replacement.syntax())
+ }
+ }
+ ast::Expr::BreakExpr(break_expr) if nested_loop.is_none() => {
+ let expr = break_expr.expr();
+ if let Some(replacement) = make_rewritten_flow(handler, expr) {
+ ted::replace(break_expr.syntax(), replacement.syntax())
+ }
+ }
+ ast::Expr::ContinueExpr(continue_expr) if nested_loop.is_none() => {
+ if let Some(replacement) = make_rewritten_flow(handler, None) {
+ ted::replace(continue_expr.syntax(), replacement.syntax())
+ }
+ }
+ _ => {
+ // do nothing
+ }
+ }
+ }
+ }
+
+ if nested_loop.as_ref() == Some(&e) {
+ nested_loop = None;
+ }
+ if nested_scope.as_ref() == Some(&e) {
+ nested_scope = None;
+ }
+ }
+ };
+ }
+}
+
+fn make_rewritten_flow(handler: &FlowHandler, arg_expr: Option<ast::Expr>) -> Option<ast::Expr> {
+ let value = match handler {
+ FlowHandler::None | FlowHandler::Try { .. } => return None,
+ FlowHandler::If { .. } => make::expr_call(
+ make::expr_path(make::path_from_text("ControlFlow::Break")),
+ make::arg_list(iter::once(make::expr_unit())),
+ ),
+ FlowHandler::IfOption { .. } => {
+ let expr = arg_expr.unwrap_or_else(|| make::expr_tuple(Vec::new()));
+ let args = make::arg_list(iter::once(expr));
+ make::expr_call(make::expr_path(make::ext::ident_path("Some")), args)
+ }
+ FlowHandler::MatchOption { .. } => make::expr_path(make::ext::ident_path("None")),
+ FlowHandler::MatchResult { .. } => {
+ let expr = arg_expr.unwrap_or_else(|| make::expr_tuple(Vec::new()));
+ let args = make::arg_list(iter::once(expr));
+ make::expr_call(make::expr_path(make::ext::ident_path("Err")), args)
+ }
+ };
+ Some(make::expr_return(Some(value)).clone_for_update())
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn no_args_from_binary_expr() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ foo($01 + 1$0);
+}
+"#,
+ r#"
+fn foo() {
+ foo(fun_name());
+}
+
+fn $0fun_name() -> i32 {
+ 1 + 1
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_args_from_binary_expr_in_module() {
+ check_assist(
+ extract_function,
+ r#"
+mod bar {
+ fn foo() {
+ foo($01 + 1$0);
+ }
+}
+"#,
+ r#"
+mod bar {
+ fn foo() {
+ foo(fun_name());
+ }
+
+ fn $0fun_name() -> i32 {
+ 1 + 1
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_args_from_binary_expr_indented() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ $0{ 1 + 1 }$0;
+}
+"#,
+ r#"
+fn foo() {
+ fun_name();
+}
+
+fn $0fun_name() -> i32 {
+ 1 + 1
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_args_from_stmt_with_last_expr() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() -> i32 {
+ let k = 1;
+ $0let m = 1;
+ m + 1$0
+}
+"#,
+ r#"
+fn foo() -> i32 {
+ let k = 1;
+ fun_name()
+}
+
+fn $0fun_name() -> i32 {
+ let m = 1;
+ m + 1
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_args_from_stmt_unit() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ let k = 3;
+ $0let m = 1;
+ let n = m + 1;$0
+ let g = 5;
+}
+"#,
+ r#"
+fn foo() {
+ let k = 3;
+ fun_name();
+ let g = 5;
+}
+
+fn $0fun_name() {
+ let m = 1;
+ let n = m + 1;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_args_if() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ $0if true { }$0
+}
+"#,
+ r#"
+fn foo() {
+ fun_name();
+}
+
+fn $0fun_name() {
+ if true { }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_args_if_else() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() -> i32 {
+ $0if true { 1 } else { 2 }$0
+}
+"#,
+ r#"
+fn foo() -> i32 {
+ fun_name()
+}
+
+fn $0fun_name() -> i32 {
+ if true { 1 } else { 2 }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_args_if_let_else() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() -> i32 {
+ $0if let true = false { 1 } else { 2 }$0
+}
+"#,
+ r#"
+fn foo() -> i32 {
+ fun_name()
+}
+
+fn $0fun_name() -> i32 {
+ if let true = false { 1 } else { 2 }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_args_match() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() -> i32 {
+ $0match true {
+ true => 1,
+ false => 2,
+ }$0
+}
+"#,
+ r#"
+fn foo() -> i32 {
+ fun_name()
+}
+
+fn $0fun_name() -> i32 {
+ match true {
+ true => 1,
+ false => 2,
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_args_while() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ $0while true { }$0
+}
+"#,
+ r#"
+fn foo() {
+ fun_name();
+}
+
+fn $0fun_name() {
+ while true { }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_args_for() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ $0for v in &[0, 1] { }$0
+}
+"#,
+ r#"
+fn foo() {
+ fun_name();
+}
+
+fn $0fun_name() {
+ for v in &[0, 1] { }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_args_from_loop_unit() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ $0loop {
+ let m = 1;
+ }$0
+}
+"#,
+ r#"
+fn foo() {
+ fun_name()
+}
+
+fn $0fun_name() -> ! {
+ loop {
+ let m = 1;
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_args_from_loop_with_return() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ let v = $0loop {
+ let m = 1;
+ break m;
+ }$0;
+}
+"#,
+ r#"
+fn foo() {
+ let v = fun_name();
+}
+
+fn $0fun_name() -> i32 {
+ loop {
+ let m = 1;
+ break m;
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_args_from_match() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ let v: i32 = $0match Some(1) {
+ Some(x) => x,
+ None => 0,
+ }$0;
+}
+"#,
+ r#"
+fn foo() {
+ let v: i32 = fun_name();
+}
+
+fn $0fun_name() -> i32 {
+ match Some(1) {
+ Some(x) => x,
+ None => 0,
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_partial_block_single_line() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ let n = 1;
+ let mut v = $0n * n;$0
+ v += 1;
+}
+"#,
+ r#"
+fn foo() {
+ let n = 1;
+ let mut v = fun_name(n);
+ v += 1;
+}
+
+fn $0fun_name(n: i32) -> i32 {
+ let mut v = n * n;
+ v
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_partial_block() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ let m = 2;
+ let n = 1;
+ let mut v = m $0* n;
+ let mut w = 3;$0
+ v += 1;
+ w += 1;
+}
+"#,
+ r#"
+fn foo() {
+ let m = 2;
+ let n = 1;
+ let (mut v, mut w) = fun_name(m, n);
+ v += 1;
+ w += 1;
+}
+
+fn $0fun_name(m: i32, n: i32) -> (i32, i32) {
+ let mut v = m * n;
+ let mut w = 3;
+ (v, w)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn argument_form_expr() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() -> u32 {
+ let n = 2;
+ $0n+2$0
+}
+"#,
+ r#"
+fn foo() -> u32 {
+ let n = 2;
+ fun_name(n)
+}
+
+fn $0fun_name(n: u32) -> u32 {
+ n+2
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn argument_used_twice_form_expr() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() -> u32 {
+ let n = 2;
+ $0n+n$0
+}
+"#,
+ r#"
+fn foo() -> u32 {
+ let n = 2;
+ fun_name(n)
+}
+
+fn $0fun_name(n: u32) -> u32 {
+ n+n
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn two_arguments_form_expr() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() -> u32 {
+ let n = 2;
+ let m = 3;
+ $0n+n*m$0
+}
+"#,
+ r#"
+fn foo() -> u32 {
+ let n = 2;
+ let m = 3;
+ fun_name(n, m)
+}
+
+fn $0fun_name(n: u32, m: u32) -> u32 {
+ n+n*m
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn argument_and_locals() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() -> u32 {
+ let n = 2;
+ $0let m = 1;
+ n + m$0
+}
+"#,
+ r#"
+fn foo() -> u32 {
+ let n = 2;
+ fun_name(n)
+}
+
+fn $0fun_name(n: u32) -> u32 {
+ let m = 1;
+ n + m
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn in_comment_is_not_applicable() {
+ cov_mark::check!(extract_function_in_comment_is_not_applicable);
+ check_assist_not_applicable(extract_function, r"fn main() { 1 + /* $0comment$0 */ 1; }");
+ }
+
+ #[test]
+ fn part_of_expr_stmt() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ $01$0 + 1;
+}
+"#,
+ r#"
+fn foo() {
+ fun_name() + 1;
+}
+
+fn $0fun_name() -> i32 {
+ 1
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn function_expr() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ $0bar(1 + 1)$0
+}
+"#,
+ r#"
+fn foo() {
+ fun_name();
+}
+
+fn $0fun_name() {
+ bar(1 + 1)
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn extract_from_nested() {
+ check_assist(
+ extract_function,
+ r#"
+fn main() {
+ let x = true;
+ let tuple = match x {
+ true => ($02 + 2$0, true)
+ _ => (0, false)
+ };
+}
+"#,
+ r#"
+fn main() {
+ let x = true;
+ let tuple = match x {
+ true => (fun_name(), true)
+ _ => (0, false)
+ };
+}
+
+fn $0fun_name() -> i32 {
+ 2 + 2
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn param_from_closure() {
+ check_assist(
+ extract_function,
+ r#"
+fn main() {
+ let lambda = |x: u32| $0x * 2$0;
+}
+"#,
+ r#"
+fn main() {
+ let lambda = |x: u32| fun_name(x);
+}
+
+fn $0fun_name(x: u32) -> u32 {
+ x * 2
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_return_stmt() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() -> u32 {
+ $0return 2 + 2$0;
+}
+"#,
+ r#"
+fn foo() -> u32 {
+ return fun_name();
+}
+
+fn $0fun_name() -> u32 {
+ 2 + 2
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn does_not_add_extra_whitespace() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() -> u32 {
+
+
+ $0return 2 + 2$0;
+}
+"#,
+ r#"
+fn foo() -> u32 {
+
+
+ return fun_name();
+}
+
+fn $0fun_name() -> u32 {
+ 2 + 2
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn break_stmt() {
+ check_assist(
+ extract_function,
+ r#"
+fn main() {
+ let result = loop {
+ $0break 2 + 2$0;
+ };
+}
+"#,
+ r#"
+fn main() {
+ let result = loop {
+ break fun_name();
+ };
+}
+
+fn $0fun_name() -> i32 {
+ 2 + 2
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_cast() {
+ check_assist(
+ extract_function,
+ r#"
+fn main() {
+ let v = $00f32 as u32$0;
+}
+"#,
+ r#"
+fn main() {
+ let v = fun_name();
+}
+
+fn $0fun_name() -> u32 {
+ 0f32 as u32
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn return_not_applicable() {
+ check_assist_not_applicable(extract_function, r"fn foo() { $0return$0; } ");
+ }
+
+ #[test]
+ fn method_to_freestanding() {
+ check_assist(
+ extract_function,
+ r#"
+struct S;
+
+impl S {
+ fn foo(&self) -> i32 {
+ $01+1$0
+ }
+}
+"#,
+ r#"
+struct S;
+
+impl S {
+ fn foo(&self) -> i32 {
+ fun_name()
+ }
+}
+
+fn $0fun_name() -> i32 {
+ 1+1
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn method_with_reference() {
+ check_assist(
+ extract_function,
+ r#"
+struct S { f: i32 };
+
+impl S {
+ fn foo(&self) -> i32 {
+ $0self.f+self.f$0
+ }
+}
+"#,
+ r#"
+struct S { f: i32 };
+
+impl S {
+ fn foo(&self) -> i32 {
+ self.fun_name()
+ }
+
+ fn $0fun_name(&self) -> i32 {
+ self.f+self.f
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn method_with_mut() {
+ check_assist(
+ extract_function,
+ r#"
+struct S { f: i32 };
+
+impl S {
+ fn foo(&mut self) {
+ $0self.f += 1;$0
+ }
+}
+"#,
+ r#"
+struct S { f: i32 };
+
+impl S {
+ fn foo(&mut self) {
+ self.fun_name();
+ }
+
+ fn $0fun_name(&mut self) {
+ self.f += 1;
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn variable_defined_inside_and_used_after_no_ret() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ let n = 1;
+ $0let k = n * n;$0
+ let m = k + 1;
+}
+"#,
+ r#"
+fn foo() {
+ let n = 1;
+ let k = fun_name(n);
+ let m = k + 1;
+}
+
+fn $0fun_name(n: i32) -> i32 {
+ let k = n * n;
+ k
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn variable_defined_inside_and_used_after_mutably_no_ret() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ let n = 1;
+ $0let mut k = n * n;$0
+ k += 1;
+}
+"#,
+ r#"
+fn foo() {
+ let n = 1;
+ let mut k = fun_name(n);
+ k += 1;
+}
+
+fn $0fun_name(n: i32) -> i32 {
+ let mut k = n * n;
+ k
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn two_variables_defined_inside_and_used_after_no_ret() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ let n = 1;
+ $0let k = n * n;
+ let m = k + 2;$0
+ let h = k + m;
+}
+"#,
+ r#"
+fn foo() {
+ let n = 1;
+ let (k, m) = fun_name(n);
+ let h = k + m;
+}
+
+fn $0fun_name(n: i32) -> (i32, i32) {
+ let k = n * n;
+ let m = k + 2;
+ (k, m)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn multi_variables_defined_inside_and_used_after_mutably_no_ret() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ let n = 1;
+ $0let mut k = n * n;
+ let mut m = k + 2;
+ let mut o = m + 3;
+ o += 1;$0
+ k += o;
+ m = 1;
+}
+"#,
+ r#"
+fn foo() {
+ let n = 1;
+ let (mut k, mut m, o) = fun_name(n);
+ k += o;
+ m = 1;
+}
+
+fn $0fun_name(n: i32) -> (i32, i32, i32) {
+ let mut k = n * n;
+ let mut m = k + 2;
+ let mut o = m + 3;
+ o += 1;
+ (k, m, o)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn nontrivial_patterns_define_variables() {
+ check_assist(
+ extract_function,
+ r#"
+struct Counter(i32);
+fn foo() {
+ $0let Counter(n) = Counter(0);$0
+ let m = n;
+}
+"#,
+ r#"
+struct Counter(i32);
+fn foo() {
+ let n = fun_name();
+ let m = n;
+}
+
+fn $0fun_name() -> i32 {
+ let Counter(n) = Counter(0);
+ n
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn struct_with_two_fields_pattern_define_variables() {
+ check_assist(
+ extract_function,
+ r#"
+struct Counter { n: i32, m: i32 };
+fn foo() {
+ $0let Counter { n, m: k } = Counter { n: 1, m: 2 };$0
+ let h = n + k;
+}
+"#,
+ r#"
+struct Counter { n: i32, m: i32 };
+fn foo() {
+ let (n, k) = fun_name();
+ let h = n + k;
+}
+
+fn $0fun_name() -> (i32, i32) {
+ let Counter { n, m: k } = Counter { n: 1, m: 2 };
+ (n, k)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn mut_var_from_outer_scope() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ let mut n = 1;
+ $0n += 1;$0
+ let m = n + 1;
+}
+"#,
+ r#"
+fn foo() {
+ let mut n = 1;
+ fun_name(&mut n);
+ let m = n + 1;
+}
+
+fn $0fun_name(n: &mut i32) {
+ *n += 1;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn mut_field_from_outer_scope() {
+ check_assist(
+ extract_function,
+ r#"
+struct C { n: i32 }
+fn foo() {
+ let mut c = C { n: 0 };
+ $0c.n += 1;$0
+ let m = c.n + 1;
+}
+"#,
+ r#"
+struct C { n: i32 }
+fn foo() {
+ let mut c = C { n: 0 };
+ fun_name(&mut c);
+ let m = c.n + 1;
+}
+
+fn $0fun_name(c: &mut C) {
+ c.n += 1;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn mut_nested_field_from_outer_scope() {
+ check_assist(
+ extract_function,
+ r#"
+struct P { n: i32}
+struct C { p: P }
+fn foo() {
+ let mut c = C { p: P { n: 0 } };
+ let mut v = C { p: P { n: 0 } };
+ let u = C { p: P { n: 0 } };
+ $0c.p.n += u.p.n;
+ let r = &mut v.p.n;$0
+ let m = c.p.n + v.p.n + u.p.n;
+}
+"#,
+ r#"
+struct P { n: i32}
+struct C { p: P }
+fn foo() {
+ let mut c = C { p: P { n: 0 } };
+ let mut v = C { p: P { n: 0 } };
+ let u = C { p: P { n: 0 } };
+ fun_name(&mut c, &u, &mut v);
+ let m = c.p.n + v.p.n + u.p.n;
+}
+
+fn $0fun_name(c: &mut C, u: &C, v: &mut C) {
+ c.p.n += u.p.n;
+ let r = &mut v.p.n;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn mut_param_many_usages_stmt() {
+ check_assist(
+ extract_function,
+ r#"
+fn bar(k: i32) {}
+trait I: Copy {
+ fn succ(&self) -> Self;
+ fn inc(&mut self) -> Self { let v = self.succ(); *self = v; v }
+}
+impl I for i32 {
+ fn succ(&self) -> Self { *self + 1 }
+}
+fn foo() {
+ let mut n = 1;
+ $0n += n;
+ bar(n);
+ bar(n+1);
+ bar(n*n);
+ bar(&n);
+ n.inc();
+ let v = &mut n;
+ *v = v.succ();
+ n.succ();$0
+ let m = n + 1;
+}
+"#,
+ r#"
+fn bar(k: i32) {}
+trait I: Copy {
+ fn succ(&self) -> Self;
+ fn inc(&mut self) -> Self { let v = self.succ(); *self = v; v }
+}
+impl I for i32 {
+ fn succ(&self) -> Self { *self + 1 }
+}
+fn foo() {
+ let mut n = 1;
+ fun_name(&mut n);
+ let m = n + 1;
+}
+
+fn $0fun_name(n: &mut i32) {
+ *n += *n;
+ bar(*n);
+ bar(*n+1);
+ bar(*n**n);
+ bar(&*n);
+ n.inc();
+ let v = n;
+ *v = v.succ();
+ n.succ();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn mut_param_many_usages_expr() {
+ check_assist(
+ extract_function,
+ r#"
+fn bar(k: i32) {}
+trait I: Copy {
+ fn succ(&self) -> Self;
+ fn inc(&mut self) -> Self { let v = self.succ(); *self = v; v }
+}
+impl I for i32 {
+ fn succ(&self) -> Self { *self + 1 }
+}
+fn foo() {
+ let mut n = 1;
+ $0{
+ n += n;
+ bar(n);
+ bar(n+1);
+ bar(n*n);
+ bar(&n);
+ n.inc();
+ let v = &mut n;
+ *v = v.succ();
+ n.succ();
+ }$0
+ let m = n + 1;
+}
+"#,
+ r#"
+fn bar(k: i32) {}
+trait I: Copy {
+ fn succ(&self) -> Self;
+ fn inc(&mut self) -> Self { let v = self.succ(); *self = v; v }
+}
+impl I for i32 {
+ fn succ(&self) -> Self { *self + 1 }
+}
+fn foo() {
+ let mut n = 1;
+ fun_name(&mut n);
+ let m = n + 1;
+}
+
+fn $0fun_name(n: &mut i32) {
+ *n += *n;
+ bar(*n);
+ bar(*n+1);
+ bar(*n**n);
+ bar(&*n);
+ n.inc();
+ let v = n;
+ *v = v.succ();
+ n.succ();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn mut_param_by_value() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ let mut n = 1;
+ $0n += 1;$0
+}
+"#,
+ r"
+fn foo() {
+ let mut n = 1;
+ fun_name(n);
+}
+
+fn $0fun_name(mut n: i32) {
+ n += 1;
+}
+",
+ );
+ }
+
+ #[test]
+ fn mut_param_because_of_mut_ref() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ let mut n = 1;
+ $0let v = &mut n;
+ *v += 1;$0
+ let k = n;
+}
+"#,
+ r#"
+fn foo() {
+ let mut n = 1;
+ fun_name(&mut n);
+ let k = n;
+}
+
+fn $0fun_name(n: &mut i32) {
+ let v = n;
+ *v += 1;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn mut_param_by_value_because_of_mut_ref() {
+ check_assist(
+ extract_function,
+ r"
+fn foo() {
+ let mut n = 1;
+ $0let v = &mut n;
+ *v += 1;$0
+}
+",
+ r#"
+fn foo() {
+ let mut n = 1;
+ fun_name(n);
+}
+
+fn $0fun_name(mut n: i32) {
+ let v = &mut n;
+ *v += 1;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn mut_method_call() {
+ check_assist(
+ extract_function,
+ r#"
+trait I {
+ fn inc(&mut self);
+}
+impl I for i32 {
+ fn inc(&mut self) { *self += 1 }
+}
+fn foo() {
+ let mut n = 1;
+ $0n.inc();$0
+}
+"#,
+ r#"
+trait I {
+ fn inc(&mut self);
+}
+impl I for i32 {
+ fn inc(&mut self) { *self += 1 }
+}
+fn foo() {
+ let mut n = 1;
+ fun_name(n);
+}
+
+fn $0fun_name(mut n: i32) {
+ n.inc();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn shared_method_call() {
+ check_assist(
+ extract_function,
+ r#"
+trait I {
+ fn succ(&self);
+}
+impl I for i32 {
+ fn succ(&self) { *self + 1 }
+}
+fn foo() {
+ let mut n = 1;
+ $0n.succ();$0
+}
+"#,
+ r"
+trait I {
+ fn succ(&self);
+}
+impl I for i32 {
+ fn succ(&self) { *self + 1 }
+}
+fn foo() {
+ let mut n = 1;
+ fun_name(n);
+}
+
+fn $0fun_name(n: i32) {
+ n.succ();
+}
+",
+ );
+ }
+
+ #[test]
+ fn mut_method_call_with_other_receiver() {
+ check_assist(
+ extract_function,
+ r#"
+trait I {
+ fn inc(&mut self, n: i32);
+}
+impl I for i32 {
+ fn inc(&mut self, n: i32) { *self += n }
+}
+fn foo() {
+ let mut n = 1;
+ $0let mut m = 2;
+ m.inc(n);$0
+}
+"#,
+ r"
+trait I {
+ fn inc(&mut self, n: i32);
+}
+impl I for i32 {
+ fn inc(&mut self, n: i32) { *self += n }
+}
+fn foo() {
+ let mut n = 1;
+ fun_name(n);
+}
+
+fn $0fun_name(n: i32) {
+ let mut m = 2;
+ m.inc(n);
+}
+",
+ );
+ }
+
+ #[test]
+ fn non_copy_without_usages_after() {
+ check_assist(
+ extract_function,
+ r#"
+struct Counter(i32);
+fn foo() {
+ let c = Counter(0);
+ $0let n = c.0;$0
+}
+"#,
+ r"
+struct Counter(i32);
+fn foo() {
+ let c = Counter(0);
+ fun_name(c);
+}
+
+fn $0fun_name(c: Counter) {
+ let n = c.0;
+}
+",
+ );
+ }
+
+ #[test]
+ fn non_copy_used_after() {
+ check_assist(
+ extract_function,
+ r"
+struct Counter(i32);
+fn foo() {
+ let c = Counter(0);
+ $0let n = c.0;$0
+ let m = c.0;
+}
+",
+ r#"
+struct Counter(i32);
+fn foo() {
+ let c = Counter(0);
+ fun_name(&c);
+ let m = c.0;
+}
+
+fn $0fun_name(c: &Counter) {
+ let n = c.0;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn copy_used_after() {
+ check_assist(
+ extract_function,
+ r#"
+//- minicore: copy
+fn foo() {
+ let n = 0;
+ $0let m = n;$0
+ let k = n;
+}
+"#,
+ r#"
+fn foo() {
+ let n = 0;
+ fun_name(n);
+ let k = n;
+}
+
+fn $0fun_name(n: i32) {
+ let m = n;
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn copy_custom_used_after() {
+ check_assist(
+ extract_function,
+ r#"
+//- minicore: copy, derive
+#[derive(Clone, Copy)]
+struct Counter(i32);
+fn foo() {
+ let c = Counter(0);
+ $0let n = c.0;$0
+ let m = c.0;
+}
+"#,
+ r#"
+#[derive(Clone, Copy)]
+struct Counter(i32);
+fn foo() {
+ let c = Counter(0);
+ fun_name(c);
+ let m = c.0;
+}
+
+fn $0fun_name(c: Counter) {
+ let n = c.0;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn indented_stmts() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ if true {
+ loop {
+ $0let n = 1;
+ let m = 2;$0
+ }
+ }
+}
+"#,
+ r#"
+fn foo() {
+ if true {
+ loop {
+ fun_name();
+ }
+ }
+}
+
+fn $0fun_name() {
+ let n = 1;
+ let m = 2;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn indented_stmts_inside_mod() {
+ check_assist(
+ extract_function,
+ r#"
+mod bar {
+ fn foo() {
+ if true {
+ loop {
+ $0let n = 1;
+ let m = 2;$0
+ }
+ }
+ }
+}
+"#,
+ r#"
+mod bar {
+ fn foo() {
+ if true {
+ loop {
+ fun_name();
+ }
+ }
+ }
+
+ fn $0fun_name() {
+ let n = 1;
+ let m = 2;
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn break_loop() {
+ check_assist(
+ extract_function,
+ r#"
+//- minicore: option
+fn foo() {
+ loop {
+ let n = 1;
+ $0let m = n + 1;
+ break;
+ let k = 2;$0
+ let h = 1 + k;
+ }
+}
+"#,
+ r#"
+fn foo() {
+ loop {
+ let n = 1;
+ let k = match fun_name(n) {
+ Some(value) => value,
+ None => break,
+ };
+ let h = 1 + k;
+ }
+}
+
+fn $0fun_name(n: i32) -> Option<i32> {
+ let m = n + 1;
+ return None;
+ let k = 2;
+ Some(k)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn return_to_parent() {
+ check_assist(
+ extract_function,
+ r#"
+//- minicore: copy, result
+fn foo() -> i64 {
+ let n = 1;
+ $0let m = n + 1;
+ return 1;
+ let k = 2;$0
+ (n + k) as i64
+}
+"#,
+ r#"
+fn foo() -> i64 {
+ let n = 1;
+ let k = match fun_name(n) {
+ Ok(value) => value,
+ Err(value) => return value,
+ };
+ (n + k) as i64
+}
+
+fn $0fun_name(n: i32) -> Result<i32, i64> {
+ let m = n + 1;
+ return Err(1);
+ let k = 2;
+ Ok(k)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn break_and_continue() {
+ cov_mark::check!(external_control_flow_break_and_continue);
+ check_assist_not_applicable(
+ extract_function,
+ r#"
+fn foo() {
+ loop {
+ let n = 1;
+ $0let m = n + 1;
+ break;
+ let k = 2;
+ continue;
+ let k = k + 1;$0
+ let r = n + k;
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn return_and_break() {
+ cov_mark::check!(external_control_flow_return_and_bc);
+ check_assist_not_applicable(
+ extract_function,
+ r#"
+fn foo() {
+ loop {
+ let n = 1;
+ $0let m = n + 1;
+ break;
+ let k = 2;
+ return;
+ let k = k + 1;$0
+ let r = n + k;
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn break_loop_with_if() {
+ check_assist(
+ extract_function,
+ r#"
+//- minicore: try
+fn foo() {
+ loop {
+ let mut n = 1;
+ $0let m = n + 1;
+ break;
+ n += m;$0
+ let h = 1 + n;
+ }
+}
+"#,
+ r#"
+use core::ops::ControlFlow;
+
+fn foo() {
+ loop {
+ let mut n = 1;
+ if let ControlFlow::Break(_) = fun_name(&mut n) {
+ break;
+ }
+ let h = 1 + n;
+ }
+}
+
+fn $0fun_name(n: &mut i32) -> ControlFlow<()> {
+ let m = *n + 1;
+ return ControlFlow::Break(());
+ *n += m;
+ ControlFlow::Continue(())
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn break_loop_nested() {
+ check_assist(
+ extract_function,
+ r#"
+//- minicore: try
+fn foo() {
+ loop {
+ let mut n = 1;
+ $0let m = n + 1;
+ if m == 42 {
+ break;
+ }$0
+ let h = 1;
+ }
+}
+"#,
+ r#"
+use core::ops::ControlFlow;
+
+fn foo() {
+ loop {
+ let mut n = 1;
+ if let ControlFlow::Break(_) = fun_name(n) {
+ break;
+ }
+ let h = 1;
+ }
+}
+
+fn $0fun_name(n: i32) -> ControlFlow<()> {
+ let m = n + 1;
+ if m == 42 {
+ return ControlFlow::Break(());
+ }
+ ControlFlow::Continue(())
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn break_loop_nested_labeled() {
+ check_assist(
+ extract_function,
+ r#"
+//- minicore: try
+fn foo() {
+ 'bar: loop {
+ loop {
+ $0break 'bar;$0
+ }
+ }
+}
+"#,
+ r#"
+use core::ops::ControlFlow;
+
+fn foo() {
+ 'bar: loop {
+ loop {
+ if let ControlFlow::Break(_) = fun_name() {
+ break 'bar;
+ }
+ }
+ }
+}
+
+fn $0fun_name() -> ControlFlow<()> {
+ return ControlFlow::Break(());
+ ControlFlow::Continue(())
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn continue_loop_nested_labeled() {
+ check_assist(
+ extract_function,
+ r#"
+//- minicore: try
+fn foo() {
+ 'bar: loop {
+ loop {
+ $0continue 'bar;$0
+ }
+ }
+}
+"#,
+ r#"
+use core::ops::ControlFlow;
+
+fn foo() {
+ 'bar: loop {
+ loop {
+ if let ControlFlow::Break(_) = fun_name() {
+ continue 'bar;
+ }
+ }
+ }
+}
+
+fn $0fun_name() -> ControlFlow<()> {
+ return ControlFlow::Break(());
+ ControlFlow::Continue(())
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn return_from_nested_loop() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ loop {
+ let n = 1;$0
+ let k = 1;
+ loop {
+ return;
+ }
+ let m = k + 1;$0
+ let h = 1 + m;
+ }
+}
+"#,
+ r#"
+fn foo() {
+ loop {
+ let n = 1;
+ let m = match fun_name() {
+ Some(value) => value,
+ None => return,
+ };
+ let h = 1 + m;
+ }
+}
+
+fn $0fun_name() -> Option<i32> {
+ let k = 1;
+ loop {
+ return None;
+ }
+ let m = k + 1;
+ Some(m)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn break_from_nested_loop() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ loop {
+ let n = 1;
+ $0let k = 1;
+ loop {
+ break;
+ }
+ let m = k + 1;$0
+ let h = 1 + m;
+ }
+}
+"#,
+ r#"
+fn foo() {
+ loop {
+ let n = 1;
+ let m = fun_name();
+ let h = 1 + m;
+ }
+}
+
+fn $0fun_name() -> i32 {
+ let k = 1;
+ loop {
+ break;
+ }
+ let m = k + 1;
+ m
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn break_from_nested_and_outer_loops() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ loop {
+ let n = 1;
+ $0let k = 1;
+ loop {
+ break;
+ }
+ if k == 42 {
+ break;
+ }
+ let m = k + 1;$0
+ let h = 1 + m;
+ }
+}
+"#,
+ r#"
+fn foo() {
+ loop {
+ let n = 1;
+ let m = match fun_name() {
+ Some(value) => value,
+ None => break,
+ };
+ let h = 1 + m;
+ }
+}
+
+fn $0fun_name() -> Option<i32> {
+ let k = 1;
+ loop {
+ break;
+ }
+ if k == 42 {
+ return None;
+ }
+ let m = k + 1;
+ Some(m)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn return_from_nested_fn() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ loop {
+ let n = 1;
+ $0let k = 1;
+ fn test() {
+ return;
+ }
+ let m = k + 1;$0
+ let h = 1 + m;
+ }
+}
+"#,
+ r#"
+fn foo() {
+ loop {
+ let n = 1;
+ let m = fun_name();
+ let h = 1 + m;
+ }
+}
+
+fn $0fun_name() -> i32 {
+ let k = 1;
+ fn test() {
+ return;
+ }
+ let m = k + 1;
+ m
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn break_with_value() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() -> i32 {
+ loop {
+ let n = 1;
+ $0let k = 1;
+ if k == 42 {
+ break 3;
+ }
+ let m = k + 1;$0
+ let h = 1;
+ }
+}
+"#,
+ r#"
+fn foo() -> i32 {
+ loop {
+ let n = 1;
+ if let Some(value) = fun_name() {
+ break value;
+ }
+ let h = 1;
+ }
+}
+
+fn $0fun_name() -> Option<i32> {
+ let k = 1;
+ if k == 42 {
+ return Some(3);
+ }
+ let m = k + 1;
+ None
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn break_with_value_and_label() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() -> i32 {
+ 'bar: loop {
+ let n = 1;
+ $0let k = 1;
+ if k == 42 {
+ break 'bar 4;
+ }
+ let m = k + 1;$0
+ let h = 1;
+ }
+}
+"#,
+ r#"
+fn foo() -> i32 {
+ 'bar: loop {
+ let n = 1;
+ if let Some(value) = fun_name() {
+ break 'bar value;
+ }
+ let h = 1;
+ }
+}
+
+fn $0fun_name() -> Option<i32> {
+ let k = 1;
+ if k == 42 {
+ return Some(4);
+ }
+ let m = k + 1;
+ None
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn break_with_value_and_return() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() -> i64 {
+ loop {
+ let n = 1;$0
+ let k = 1;
+ if k == 42 {
+ break 3;
+ }
+ let m = k + 1;$0
+ let h = 1 + m;
+ }
+}
+"#,
+ r#"
+fn foo() -> i64 {
+ loop {
+ let n = 1;
+ let m = match fun_name() {
+ Ok(value) => value,
+ Err(value) => break value,
+ };
+ let h = 1 + m;
+ }
+}
+
+fn $0fun_name() -> Result<i32, i64> {
+ let k = 1;
+ if k == 42 {
+ return Err(3);
+ }
+ let m = k + 1;
+ Ok(m)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn try_option() {
+ check_assist(
+ extract_function,
+ r#"
+//- minicore: option
+fn bar() -> Option<i32> { None }
+fn foo() -> Option<()> {
+ let n = bar()?;
+ $0let k = foo()?;
+ let m = k + 1;$0
+ let h = 1 + m;
+ Some(())
+}
+"#,
+ r#"
+fn bar() -> Option<i32> { None }
+fn foo() -> Option<()> {
+ let n = bar()?;
+ let m = fun_name()?;
+ let h = 1 + m;
+ Some(())
+}
+
+fn $0fun_name() -> Option<i32> {
+ let k = foo()?;
+ let m = k + 1;
+ Some(m)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn try_option_unit() {
+ check_assist(
+ extract_function,
+ r#"
+//- minicore: option
+fn foo() -> Option<()> {
+ let n = 1;
+ $0let k = foo()?;
+ let m = k + 1;$0
+ let h = 1 + n;
+ Some(())
+}
+"#,
+ r#"
+fn foo() -> Option<()> {
+ let n = 1;
+ fun_name()?;
+ let h = 1 + n;
+ Some(())
+}
+
+fn $0fun_name() -> Option<()> {
+ let k = foo()?;
+ let m = k + 1;
+ Some(())
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn try_result() {
+ check_assist(
+ extract_function,
+ r#"
+//- minicore: result
+fn foo() -> Result<(), i64> {
+ let n = 1;
+ $0let k = foo()?;
+ let m = k + 1;$0
+ let h = 1 + m;
+ Ok(())
+}
+"#,
+ r#"
+fn foo() -> Result<(), i64> {
+ let n = 1;
+ let m = fun_name()?;
+ let h = 1 + m;
+ Ok(())
+}
+
+fn $0fun_name() -> Result<i32, i64> {
+ let k = foo()?;
+ let m = k + 1;
+ Ok(m)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn try_option_with_return() {
+ check_assist(
+ extract_function,
+ r#"
+//- minicore: option
+fn foo() -> Option<()> {
+ let n = 1;
+ $0let k = foo()?;
+ if k == 42 {
+ return None;
+ }
+ let m = k + 1;$0
+ let h = 1 + m;
+ Some(())
+}
+"#,
+ r#"
+fn foo() -> Option<()> {
+ let n = 1;
+ let m = fun_name()?;
+ let h = 1 + m;
+ Some(())
+}
+
+fn $0fun_name() -> Option<i32> {
+ let k = foo()?;
+ if k == 42 {
+ return None;
+ }
+ let m = k + 1;
+ Some(m)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn try_result_with_return() {
+ check_assist(
+ extract_function,
+ r#"
+//- minicore: result
+fn foo() -> Result<(), i64> {
+ let n = 1;
+ $0let k = foo()?;
+ if k == 42 {
+ return Err(1);
+ }
+ let m = k + 1;$0
+ let h = 1 + m;
+ Ok(())
+}
+"#,
+ r#"
+fn foo() -> Result<(), i64> {
+ let n = 1;
+ let m = fun_name()?;
+ let h = 1 + m;
+ Ok(())
+}
+
+fn $0fun_name() -> Result<i32, i64> {
+ let k = foo()?;
+ if k == 42 {
+ return Err(1);
+ }
+ let m = k + 1;
+ Ok(m)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn try_and_break() {
+ cov_mark::check!(external_control_flow_try_and_bc);
+ check_assist_not_applicable(
+ extract_function,
+ r#"
+//- minicore: option
+fn foo() -> Option<()> {
+ loop {
+ let n = Some(1);
+ $0let m = n? + 1;
+ break;
+ let k = 2;
+ let k = k + 1;$0
+ let r = n + k;
+ }
+ Some(())
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn try_and_return_ok() {
+ check_assist(
+ extract_function,
+ r#"
+//- minicore: result
+fn foo() -> Result<(), i64> {
+ let n = 1;
+ $0let k = foo()?;
+ if k == 42 {
+ return Ok(1);
+ }
+ let m = k + 1;$0
+ let h = 1 + m;
+ Ok(())
+}
+"#,
+ r#"
+fn foo() -> Result<(), i64> {
+ let n = 1;
+ let m = fun_name()?;
+ let h = 1 + m;
+ Ok(())
+}
+
+fn $0fun_name() -> Result<i32, i64> {
+ let k = foo()?;
+ if k == 42 {
+ return Ok(1);
+ }
+ let m = k + 1;
+ Ok(m)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn param_usage_in_macro() {
+ check_assist(
+ extract_function,
+ r#"
+macro_rules! m {
+ ($val:expr) => { $val };
+}
+
+fn foo() {
+ let n = 1;
+ $0let k = n * m!(n);$0
+ let m = k + 1;
+}
+"#,
+ r#"
+macro_rules! m {
+ ($val:expr) => { $val };
+}
+
+fn foo() {
+ let n = 1;
+ let k = fun_name(n);
+ let m = k + 1;
+}
+
+fn $0fun_name(n: i32) -> i32 {
+ let k = n * m!(n);
+ k
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_with_await() {
+ check_assist(
+ extract_function,
+ r#"
+//- minicore: future
+fn main() {
+ $0some_function().await;$0
+}
+
+async fn some_function() {
+
+}
+"#,
+ r#"
+fn main() {
+ fun_name().await;
+}
+
+async fn $0fun_name() {
+ some_function().await;
+}
+
+async fn some_function() {
+
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_with_await_and_result_not_producing_match_expr() {
+ check_assist(
+ extract_function,
+ r#"
+//- minicore: future, result
+async fn foo() -> Result<(), ()> {
+ $0async {}.await;
+ Err(())?$0
+}
+"#,
+ r#"
+async fn foo() -> Result<(), ()> {
+ fun_name().await?
+}
+
+async fn $0fun_name() -> Result<(), ()> {
+ async {}.await;
+ Err(())?
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_with_await_and_result_producing_match_expr() {
+ check_assist(
+ extract_function,
+ r#"
+//- minicore: future
+async fn foo() -> i32 {
+ loop {
+ let n = 1;$0
+ let k = async { 1 }.await;
+ if k == 42 {
+ break 3;
+ }
+ let m = k + 1;$0
+ let h = 1 + m;
+ }
+}
+"#,
+ r#"
+async fn foo() -> i32 {
+ loop {
+ let n = 1;
+ let m = match fun_name().await {
+ Ok(value) => value,
+ Err(value) => break value,
+ };
+ let h = 1 + m;
+ }
+}
+
+async fn $0fun_name() -> Result<i32, i32> {
+ let k = async { 1 }.await;
+ if k == 42 {
+ return Err(3);
+ }
+ let m = k + 1;
+ Ok(m)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_with_await_in_args() {
+ check_assist(
+ extract_function,
+ r#"
+//- minicore: future
+fn main() {
+ $0function_call("a", some_function().await);$0
+}
+
+async fn some_function() {
+
+}
+"#,
+ r#"
+fn main() {
+ fun_name().await;
+}
+
+async fn $0fun_name() {
+ function_call("a", some_function().await);
+}
+
+async fn some_function() {
+
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_does_not_extract_standalone_blocks() {
+ check_assist_not_applicable(
+ extract_function,
+ r#"
+fn main() $0{}$0
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_adds_comma_for_match_arm() {
+ check_assist(
+ extract_function,
+ r#"
+fn main() {
+ match 6 {
+ 100 => $0{ 100 }$0
+ _ => 0,
+ };
+}
+"#,
+ r#"
+fn main() {
+ match 6 {
+ 100 => fun_name(),
+ _ => 0,
+ };
+}
+
+fn $0fun_name() -> i32 {
+ 100
+}
+"#,
+ );
+ check_assist(
+ extract_function,
+ r#"
+fn main() {
+ match 6 {
+ 100 => $0{ 100 }$0,
+ _ => 0,
+ };
+}
+"#,
+ r#"
+fn main() {
+ match 6 {
+ 100 => fun_name(),
+ _ => 0,
+ };
+}
+
+fn $0fun_name() -> i32 {
+ 100
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_does_not_tear_comments_apart() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ /*$0*/
+ foo();
+ foo();
+ /*$0*/
+}
+"#,
+ r#"
+fn foo() {
+ fun_name();
+}
+
+fn $0fun_name() {
+ /**/
+ foo();
+ foo();
+ /**/
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_does_not_tear_body_apart() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ $0foo();
+}$0
+"#,
+ r#"
+fn foo() {
+ fun_name();
+}
+
+fn $0fun_name() {
+ foo();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_does_not_wrap_res_in_res() {
+ check_assist(
+ extract_function,
+ r#"
+//- minicore: result
+fn foo() -> Result<(), i64> {
+ $0Result::<i32, i64>::Ok(0)?;
+ Ok(())$0
+}
+"#,
+ r#"
+fn foo() -> Result<(), i64> {
+ fun_name()?
+}
+
+fn $0fun_name() -> Result<(), i64> {
+ Result::<i32, i64>::Ok(0)?;
+ Ok(())
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_knows_const() {
+ check_assist(
+ extract_function,
+ r#"
+const fn foo() {
+ $0()$0
+}
+"#,
+ r#"
+const fn foo() {
+ fun_name();
+}
+
+const fn $0fun_name() {
+ ()
+}
+"#,
+ );
+ check_assist(
+ extract_function,
+ r#"
+const FOO: () = {
+ $0()$0
+};
+"#,
+ r#"
+const FOO: () = {
+ fun_name();
+};
+
+const fn $0fun_name() {
+ ()
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_does_not_move_outer_loop_vars() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ let mut x = 5;
+ for _ in 0..10 {
+ $0x += 1;$0
+ }
+}
+"#,
+ r#"
+fn foo() {
+ let mut x = 5;
+ for _ in 0..10 {
+ fun_name(&mut x);
+ }
+}
+
+fn $0fun_name(x: &mut i32) {
+ *x += 1;
+}
+"#,
+ );
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ for _ in 0..10 {
+ let mut x = 5;
+ $0x += 1;$0
+ }
+}
+"#,
+ r#"
+fn foo() {
+ for _ in 0..10 {
+ let mut x = 5;
+ fun_name(x);
+ }
+}
+
+fn $0fun_name(mut x: i32) {
+ x += 1;
+}
+"#,
+ );
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ loop {
+ let mut x = 5;
+ for _ in 0..10 {
+ $0x += 1;$0
+ }
+ }
+}
+"#,
+ r#"
+fn foo() {
+ loop {
+ let mut x = 5;
+ for _ in 0..10 {
+ fun_name(&mut x);
+ }
+ }
+}
+
+fn $0fun_name(x: &mut i32) {
+ *x += 1;
+}
+"#,
+ );
+ }
+
+ // regression test for #9822
+ #[test]
+ fn extract_mut_ref_param_has_no_mut_binding_in_loop() {
+ check_assist(
+ extract_function,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(&mut self) {}
+}
+fn foo() {
+ let mut x = Foo;
+ while false {
+ let y = &mut x;
+ $0y.foo();$0
+ }
+ let z = x;
+}
+"#,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(&mut self) {}
+}
+fn foo() {
+ let mut x = Foo;
+ while false {
+ let y = &mut x;
+ fun_name(y);
+ }
+ let z = x;
+}
+
+fn $0fun_name(y: &mut Foo) {
+ y.foo();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_with_macro_arg() {
+ check_assist(
+ extract_function,
+ r#"
+macro_rules! m {
+ ($val:expr) => { $val };
+}
+fn main() {
+ let bar = "bar";
+ $0m!(bar);$0
+}
+"#,
+ r#"
+macro_rules! m {
+ ($val:expr) => { $val };
+}
+fn main() {
+ let bar = "bar";
+ fun_name(bar);
+}
+
+fn $0fun_name(bar: &str) {
+ m!(bar);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unresolveable_types_default_to_placeholder() {
+ check_assist(
+ extract_function,
+ r#"
+fn foo() {
+ let a = __unresolved;
+ let _ = $0{a}$0;
+}
+"#,
+ r#"
+fn foo() {
+ let a = __unresolved;
+ let _ = fun_name(a);
+}
+
+fn $0fun_name(a: _) -> _ {
+ a
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn reference_mutable_param_with_further_usages() {
+ check_assist(
+ extract_function,
+ r#"
+pub struct Foo {
+ field: u32,
+}
+
+pub fn testfn(arg: &mut Foo) {
+ $0arg.field = 8;$0
+ // Simulating access after the extracted portion
+ arg.field = 16;
+}
+"#,
+ r#"
+pub struct Foo {
+ field: u32,
+}
+
+pub fn testfn(arg: &mut Foo) {
+ fun_name(arg);
+ // Simulating access after the extracted portion
+ arg.field = 16;
+}
+
+fn $0fun_name(arg: &mut Foo) {
+ arg.field = 8;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn reference_mutable_param_without_further_usages() {
+ check_assist(
+ extract_function,
+ r#"
+pub struct Foo {
+ field: u32,
+}
+
+pub fn testfn(arg: &mut Foo) {
+ $0arg.field = 8;$0
+}
+"#,
+ r#"
+pub struct Foo {
+ field: u32,
+}
+
+pub fn testfn(arg: &mut Foo) {
+ fun_name(arg);
+}
+
+fn $0fun_name(arg: &mut Foo) {
+ arg.field = 8;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_function_copies_comment_at_start() {
+ check_assist(
+ extract_function,
+ r#"
+fn func() {
+ let i = 0;
+ $0// comment here!
+ let x = 0;$0
+}
+"#,
+ r#"
+fn func() {
+ let i = 0;
+ fun_name();
+}
+
+fn $0fun_name() {
+ // comment here!
+ let x = 0;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_function_copies_comment_in_between() {
+ check_assist(
+ extract_function,
+ r#"
+fn func() {
+ let i = 0;$0
+ let a = 0;
+ // comment here!
+ let x = 0;$0
+}
+"#,
+ r#"
+fn func() {
+ let i = 0;
+ fun_name();
+}
+
+fn $0fun_name() {
+ let a = 0;
+ // comment here!
+ let x = 0;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_function_copies_comment_at_end() {
+ check_assist(
+ extract_function,
+ r#"
+fn func() {
+ let i = 0;
+ $0let x = 0;
+ // comment here!$0
+}
+"#,
+ r#"
+fn func() {
+ let i = 0;
+ fun_name();
+}
+
+fn $0fun_name() {
+ let x = 0;
+ // comment here!
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_function_copies_comment_indented() {
+ check_assist(
+ extract_function,
+ r#"
+fn func() {
+ let i = 0;
+ $0let x = 0;
+ while(true) {
+ // comment here!
+ }$0
+}
+"#,
+ r#"
+fn func() {
+ let i = 0;
+ fun_name();
+}
+
+fn $0fun_name() {
+ let x = 0;
+ while(true) {
+ // comment here!
+ }
+}
+"#,
+ );
+ }
+
+ // FIXME: we do want to preserve whitespace
+ #[test]
+ fn extract_function_does_not_preserve_whitespace() {
+ check_assist(
+ extract_function,
+ r#"
+fn func() {
+ let i = 0;
+ $0let a = 0;
+
+ let x = 0;$0
+}
+"#,
+ r#"
+fn func() {
+ let i = 0;
+ fun_name();
+}
+
+fn $0fun_name() {
+ let a = 0;
+ let x = 0;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_function_long_form_comment() {
+ check_assist(
+ extract_function,
+ r#"
+fn func() {
+ let i = 0;
+ $0/* a comment */
+ let x = 0;$0
+}
+"#,
+ r#"
+fn func() {
+ let i = 0;
+ fun_name();
+}
+
+fn $0fun_name() {
+ /* a comment */
+ let x = 0;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn it_should_not_generate_duplicate_function_names() {
+ check_assist(
+ extract_function,
+ r#"
+fn fun_name() {
+ $0let x = 0;$0
+}
+"#,
+ r#"
+fn fun_name() {
+ fun_name1();
+}
+
+fn $0fun_name1() {
+ let x = 0;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn should_increment_suffix_until_it_finds_space() {
+ check_assist(
+ extract_function,
+ r#"
+fn fun_name1() {
+ let y = 0;
+}
+
+fn fun_name() {
+ $0let x = 0;$0
+}
+"#,
+ r#"
+fn fun_name1() {
+ let y = 0;
+}
+
+fn fun_name() {
+ fun_name2();
+}
+
+fn $0fun_name2() {
+ let x = 0;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_method_from_trait_impl() {
+ check_assist(
+ extract_function,
+ r#"
+struct Struct(i32);
+trait Trait {
+ fn bar(&self) -> i32;
+}
+
+impl Trait for Struct {
+ fn bar(&self) -> i32 {
+ $0self.0 + 2$0
+ }
+}
+"#,
+ r#"
+struct Struct(i32);
+trait Trait {
+ fn bar(&self) -> i32;
+}
+
+impl Trait for Struct {
+ fn bar(&self) -> i32 {
+ self.fun_name()
+ }
+}
+
+impl Struct {
+ fn $0fun_name(&self) -> i32 {
+ self.0 + 2
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn closure_arguments() {
+ check_assist(
+ extract_function,
+ r#"
+fn parent(factor: i32) {
+ let v = &[1, 2, 3];
+
+ $0v.iter().map(|it| it * factor);$0
+}
+"#,
+ r#"
+fn parent(factor: i32) {
+ let v = &[1, 2, 3];
+
+ fun_name(v, factor);
+}
+
+fn $0fun_name(v: &[i32; 3], factor: i32) {
+ v.iter().map(|it| it * factor);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn preserve_generics() {
+ check_assist(
+ extract_function,
+ r#"
+fn func<T: Debug>(i: T) {
+ $0foo(i);$0
+}
+"#,
+ r#"
+fn func<T: Debug>(i: T) {
+ fun_name(i);
+}
+
+fn $0fun_name<T: Debug>(i: T) {
+ foo(i);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn preserve_generics_from_body() {
+ check_assist(
+ extract_function,
+ r#"
+fn func<T: Default>() -> T {
+ $0T::default()$0
+}
+"#,
+ r#"
+fn func<T: Default>() -> T {
+ fun_name()
+}
+
+fn $0fun_name<T: Default>() -> T {
+ T::default()
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn filter_unused_generics() {
+ check_assist(
+ extract_function,
+ r#"
+fn func<T: Debug, U: Copy>(i: T, u: U) {
+ bar(u);
+ $0foo(i);$0
+}
+"#,
+ r#"
+fn func<T: Debug, U: Copy>(i: T, u: U) {
+ bar(u);
+ fun_name(i);
+}
+
+fn $0fun_name<T: Debug>(i: T) {
+ foo(i);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn empty_generic_param_list() {
+ check_assist(
+ extract_function,
+ r#"
+fn func<T: Debug>(t: T, i: u32) {
+ bar(t);
+ $0foo(i);$0
+}
+"#,
+ r#"
+fn func<T: Debug>(t: T, i: u32) {
+ bar(t);
+ fun_name(i);
+}
+
+fn $0fun_name(i: u32) {
+ foo(i);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn preserve_where_clause() {
+ check_assist(
+ extract_function,
+ r#"
+fn func<T>(i: T) where T: Debug {
+ $0foo(i);$0
+}
+"#,
+ r#"
+fn func<T>(i: T) where T: Debug {
+ fun_name(i);
+}
+
+fn $0fun_name<T>(i: T) where T: Debug {
+ foo(i);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn filter_unused_where_clause() {
+ check_assist(
+ extract_function,
+ r#"
+fn func<T, U>(i: T, u: U) where T: Debug, U: Copy {
+ bar(u);
+ $0foo(i);$0
+}
+"#,
+ r#"
+fn func<T, U>(i: T, u: U) where T: Debug, U: Copy {
+ bar(u);
+ fun_name(i);
+}
+
+fn $0fun_name<T>(i: T) where T: Debug {
+ foo(i);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn nested_generics() {
+ check_assist(
+ extract_function,
+ r#"
+struct Struct<T: Into<i32>>(T);
+impl <T: Into<i32> + Copy> Struct<T> {
+ fn func<V: Into<i32>>(&self, v: V) -> i32 {
+ let t = self.0;
+ $0t.into() + v.into()$0
+ }
+}
+"#,
+ r#"
+struct Struct<T: Into<i32>>(T);
+impl <T: Into<i32> + Copy> Struct<T> {
+ fn func<V: Into<i32>>(&self, v: V) -> i32 {
+ let t = self.0;
+ fun_name(t, v)
+ }
+}
+
+fn $0fun_name<T: Into<i32> + Copy, V: Into<i32>>(t: T, v: V) -> i32 {
+ t.into() + v.into()
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn filters_unused_nested_generics() {
+ check_assist(
+ extract_function,
+ r#"
+struct Struct<T: Into<i32>, U: Debug>(T, U);
+impl <T: Into<i32> + Copy, U: Debug> Struct<T, U> {
+ fn func<V: Into<i32>>(&self, v: V) -> i32 {
+ let t = self.0;
+ $0t.into() + v.into()$0
+ }
+}
+"#,
+ r#"
+struct Struct<T: Into<i32>, U: Debug>(T, U);
+impl <T: Into<i32> + Copy, U: Debug> Struct<T, U> {
+ fn func<V: Into<i32>>(&self, v: V) -> i32 {
+ let t = self.0;
+ fun_name(t, v)
+ }
+}
+
+fn $0fun_name<T: Into<i32> + Copy, V: Into<i32>>(t: T, v: V) -> i32 {
+ t.into() + v.into()
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn nested_where_clauses() {
+ check_assist(
+ extract_function,
+ r#"
+struct Struct<T>(T) where T: Into<i32>;
+impl <T> Struct<T> where T: Into<i32> + Copy {
+ fn func<V>(&self, v: V) -> i32 where V: Into<i32> {
+ let t = self.0;
+ $0t.into() + v.into()$0
+ }
+}
+"#,
+ r#"
+struct Struct<T>(T) where T: Into<i32>;
+impl <T> Struct<T> where T: Into<i32> + Copy {
+ fn func<V>(&self, v: V) -> i32 where V: Into<i32> {
+ let t = self.0;
+ fun_name(t, v)
+ }
+}
+
+fn $0fun_name<T, V>(t: T, v: V) -> i32 where T: Into<i32> + Copy, V: Into<i32> {
+ t.into() + v.into()
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn filters_unused_nested_where_clauses() {
+ check_assist(
+ extract_function,
+ r#"
+struct Struct<T, U>(T, U) where T: Into<i32>, U: Debug;
+impl <T, U> Struct<T, U> where T: Into<i32> + Copy, U: Debug {
+ fn func<V>(&self, v: V) -> i32 where V: Into<i32> {
+ let t = self.0;
+ $0t.into() + v.into()$0
+ }
+}
+"#,
+ r#"
+struct Struct<T, U>(T, U) where T: Into<i32>, U: Debug;
+impl <T, U> Struct<T, U> where T: Into<i32> + Copy, U: Debug {
+ fn func<V>(&self, v: V) -> i32 where V: Into<i32> {
+ let t = self.0;
+ fun_name(t, v)
+ }
+}
+
+fn $0fun_name<T, V>(t: T, v: V) -> i32 where T: Into<i32> + Copy, V: Into<i32> {
+ t.into() + v.into()
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_module.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_module.rs
new file mode 100644
index 000000000..b3c4d306a
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_module.rs
@@ -0,0 +1,1770 @@
+use std::{
+ collections::{HashMap, HashSet},
+ iter,
+};
+
+use hir::{HasSource, ModuleSource};
+use ide_db::{
+ assists::{AssistId, AssistKind},
+ base_db::FileId,
+ defs::{Definition, NameClass, NameRefClass},
+ search::{FileReference, SearchScope},
+};
+use stdx::format_to;
+use syntax::{
+ algo::find_node_at_range,
+ ast::{
+ self,
+ edit::{AstNodeEdit, IndentLevel},
+ make, HasName, HasVisibility,
+ },
+ match_ast, ted, AstNode, SourceFile,
+ SyntaxKind::{self, WHITESPACE},
+ SyntaxNode, TextRange,
+};
+
+use crate::{AssistContext, Assists};
+
+use super::remove_unused_param::range_to_remove;
+
+// Assist: extract_module
+//
+// Extracts a selected region as seperate module. All the references, visibility and imports are
+// resolved.
+//
+// ```
+// $0fn foo(name: i32) -> i32 {
+// name + 1
+// }$0
+//
+// fn bar(name: i32) -> i32 {
+// name + 2
+// }
+// ```
+// ->
+// ```
+// mod modname {
+// pub(crate) fn foo(name: i32) -> i32 {
+// name + 1
+// }
+// }
+//
+// fn bar(name: i32) -> i32 {
+// name + 2
+// }
+// ```
+pub(crate) fn extract_module(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ if ctx.has_empty_selection() {
+ return None;
+ }
+
+ let node = ctx.covering_element();
+ let node = match node {
+ syntax::NodeOrToken::Node(n) => n,
+ syntax::NodeOrToken::Token(t) => t.parent()?,
+ };
+
+ //If the selection is inside impl block, we need to place new module outside impl block,
+ //as impl blocks cannot contain modules
+
+ let mut impl_parent: Option<ast::Impl> = None;
+ let mut impl_child_count: usize = 0;
+ if let Some(parent_assoc_list) = node.parent() {
+ if let Some(parent_impl) = parent_assoc_list.parent() {
+ if let Some(impl_) = ast::Impl::cast(parent_impl) {
+ impl_child_count = parent_assoc_list.children().count();
+ impl_parent = Some(impl_);
+ }
+ }
+ }
+
+ let mut curr_parent_module: Option<ast::Module> = None;
+ if let Some(mod_syn_opt) = node.ancestors().find(|it| ast::Module::can_cast(it.kind())) {
+ curr_parent_module = ast::Module::cast(mod_syn_opt);
+ }
+
+ let mut module = extract_target(&node, ctx.selection_trimmed())?;
+ if module.body_items.is_empty() {
+ return None;
+ }
+
+ let old_item_indent = module.body_items[0].indent_level();
+
+ acc.add(
+ AssistId("extract_module", AssistKind::RefactorExtract),
+ "Extract Module",
+ module.text_range,
+ |builder| {
+ //This takes place in three steps:
+ //
+ //- Firstly, we will update the references(usages) e.g. converting a
+ // function call bar() to modname::bar(), and similarly for other items
+ //
+ //- Secondly, changing the visibility of each item inside the newly selected module
+ // i.e. making a fn a() {} to pub(crate) fn a() {}
+ //
+ //- Thirdly, resolving all the imports this includes removing paths from imports
+ // outside the module, shifting/cloning them inside new module, or shifting the imports, or making
+ // new import statemnts
+
+ //We are getting item usages and record_fields together, record_fields
+ //for change_visibility and usages for first point mentioned above in the process
+ let (usages_to_be_processed, record_fields) = module.get_usages_and_record_fields(ctx);
+
+ let import_paths_to_be_removed = module.resolve_imports(curr_parent_module, ctx);
+ module.change_visibility(record_fields);
+
+ let mut body_items: Vec<String> = Vec::new();
+ let mut items_to_be_processed: Vec<ast::Item> = module.body_items.clone();
+ let mut new_item_indent = old_item_indent + 1;
+
+ if impl_parent.is_some() {
+ new_item_indent = old_item_indent + 2;
+ } else {
+ items_to_be_processed = [module.use_items.clone(), items_to_be_processed].concat();
+ }
+
+ for item in items_to_be_processed {
+ let item = item.indent(IndentLevel(1));
+ let mut indented_item = String::new();
+ format_to!(indented_item, "{}{}", new_item_indent, item.to_string());
+ body_items.push(indented_item);
+ }
+
+ let mut body = body_items.join("\n\n");
+
+ if let Some(impl_) = &impl_parent {
+ let mut impl_body_def = String::new();
+
+ if let Some(self_ty) = impl_.self_ty() {
+ format_to!(
+ impl_body_def,
+ "{}impl {} {{\n{}\n{}}}",
+ old_item_indent + 1,
+ self_ty.to_string(),
+ body,
+ old_item_indent + 1
+ );
+
+ body = impl_body_def;
+
+ // Add the import for enum/struct corresponding to given impl block
+ module.make_use_stmt_of_node_with_super(self_ty.syntax());
+ for item in module.use_items {
+ let mut indented_item = String::new();
+ format_to!(indented_item, "{}{}", old_item_indent + 1, item.to_string());
+ body = format!("{}\n\n{}", indented_item, body);
+ }
+ }
+ }
+
+ let mut module_def = String::new();
+
+ format_to!(module_def, "mod {} {{\n{}\n{}}}", module.name, body, old_item_indent);
+
+ let mut usages_to_be_updated_for_curr_file = vec![];
+ for usages_to_be_updated_for_file in usages_to_be_processed {
+ if usages_to_be_updated_for_file.0 == ctx.file_id() {
+ usages_to_be_updated_for_curr_file = usages_to_be_updated_for_file.1;
+ continue;
+ }
+ builder.edit_file(usages_to_be_updated_for_file.0);
+ for usage_to_be_processed in usages_to_be_updated_for_file.1 {
+ builder.replace(usage_to_be_processed.0, usage_to_be_processed.1)
+ }
+ }
+
+ builder.edit_file(ctx.file_id());
+ for usage_to_be_processed in usages_to_be_updated_for_curr_file {
+ builder.replace(usage_to_be_processed.0, usage_to_be_processed.1)
+ }
+
+ for import_path_text_range in import_paths_to_be_removed {
+ builder.delete(import_path_text_range);
+ }
+
+ if let Some(impl_) = impl_parent {
+ // Remove complete impl block if it has only one child (as such it will be empty
+ // after deleting that child)
+ let node_to_be_removed = if impl_child_count == 1 {
+ impl_.syntax()
+ } else {
+ //Remove selected node
+ &node
+ };
+
+ builder.delete(node_to_be_removed.text_range());
+ // Remove preceding indentation from node
+ if let Some(range) = indent_range_before_given_node(node_to_be_removed) {
+ builder.delete(range);
+ }
+
+ builder.insert(impl_.syntax().text_range().end(), format!("\n\n{}", module_def));
+ } else {
+ builder.replace(module.text_range, module_def)
+ }
+ },
+ )
+}
+
+#[derive(Debug)]
+struct Module {
+ text_range: TextRange,
+ name: &'static str,
+ /// All items except use items.
+ body_items: Vec<ast::Item>,
+ /// Use items are kept separately as they help when the selection is inside an impl block,
+ /// we can directly take these items and keep them outside generated impl block inside
+ /// generated module.
+ use_items: Vec<ast::Item>,
+}
+
+fn extract_target(node: &SyntaxNode, selection_range: TextRange) -> Option<Module> {
+ let selected_nodes = node
+ .children()
+ .filter(|node| selection_range.contains_range(node.text_range()))
+ .chain(iter::once(node.clone()));
+ let (use_items, body_items) = selected_nodes
+ .filter_map(ast::Item::cast)
+ .partition(|item| matches!(item, ast::Item::Use(..)));
+
+ Some(Module { text_range: selection_range, name: "modname", body_items, use_items })
+}
+
+impl Module {
+ fn get_usages_and_record_fields(
+ &self,
+ ctx: &AssistContext<'_>,
+ ) -> (HashMap<FileId, Vec<(TextRange, String)>>, Vec<SyntaxNode>) {
+ let mut adt_fields = Vec::new();
+ let mut refs: HashMap<FileId, Vec<(TextRange, String)>> = HashMap::new();
+
+ //Here impl is not included as each item inside impl will be tied to the parent of
+ //implementing block(a struct, enum, etc), if the parent is in selected module, it will
+ //get updated by ADT section given below or if it is not, then we dont need to do any operation
+ for item in &self.body_items {
+ match_ast! {
+ match (item.syntax()) {
+ ast::Adt(it) => {
+ if let Some( nod ) = ctx.sema.to_def(&it) {
+ let node_def = Definition::Adt(nod);
+ self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs);
+
+ //Enum Fields are not allowed to explicitly specify pub, it is implied
+ match it {
+ ast::Adt::Struct(x) => {
+ if let Some(field_list) = x.field_list() {
+ match field_list {
+ ast::FieldList::RecordFieldList(record_field_list) => {
+ record_field_list.fields().for_each(|record_field| {
+ adt_fields.push(record_field.syntax().clone());
+ });
+ },
+ ast::FieldList::TupleFieldList(tuple_field_list) => {
+ tuple_field_list.fields().for_each(|tuple_field| {
+ adt_fields.push(tuple_field.syntax().clone());
+ });
+ },
+ }
+ }
+ },
+ ast::Adt::Union(x) => {
+ if let Some(record_field_list) = x.record_field_list() {
+ record_field_list.fields().for_each(|record_field| {
+ adt_fields.push(record_field.syntax().clone());
+ });
+ }
+ },
+ ast::Adt::Enum(_) => {},
+ }
+ }
+ },
+ ast::TypeAlias(it) => {
+ if let Some( nod ) = ctx.sema.to_def(&it) {
+ let node_def = Definition::TypeAlias(nod);
+ self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs);
+ }
+ },
+ ast::Const(it) => {
+ if let Some( nod ) = ctx.sema.to_def(&it) {
+ let node_def = Definition::Const(nod);
+ self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs);
+ }
+ },
+ ast::Static(it) => {
+ if let Some( nod ) = ctx.sema.to_def(&it) {
+ let node_def = Definition::Static(nod);
+ self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs);
+ }
+ },
+ ast::Fn(it) => {
+ if let Some( nod ) = ctx.sema.to_def(&it) {
+ let node_def = Definition::Function(nod);
+ self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs);
+ }
+ },
+ ast::Macro(it) => {
+ if let Some(nod) = ctx.sema.to_def(&it) {
+ self.expand_and_group_usages_file_wise(ctx, Definition::Macro(nod), &mut refs);
+ }
+ },
+ _ => (),
+ }
+ }
+ }
+
+ (refs, adt_fields)
+ }
+
+ fn expand_and_group_usages_file_wise(
+ &self,
+ ctx: &AssistContext<'_>,
+ node_def: Definition,
+ refs_in_files: &mut HashMap<FileId, Vec<(TextRange, String)>>,
+ ) {
+ for (file_id, references) in node_def.usages(&ctx.sema).all() {
+ let source_file = ctx.sema.parse(file_id);
+ let usages_in_file = references
+ .into_iter()
+ .filter_map(|usage| self.get_usage_to_be_processed(&source_file, usage));
+ refs_in_files.entry(file_id).or_default().extend(usages_in_file);
+ }
+ }
+
+ fn get_usage_to_be_processed(
+ &self,
+ source_file: &SourceFile,
+ FileReference { range, name, .. }: FileReference,
+ ) -> Option<(TextRange, String)> {
+ let path: ast::Path = find_node_at_range(source_file.syntax(), range)?;
+
+ for desc in path.syntax().descendants() {
+ if desc.to_string() == name.syntax().to_string()
+ && !self.text_range.contains_range(desc.text_range())
+ {
+ if let Some(name_ref) = ast::NameRef::cast(desc) {
+ return Some((
+ name_ref.syntax().text_range(),
+ format!("{}::{}", self.name, name_ref),
+ ));
+ }
+ }
+ }
+
+ None
+ }
+
+ fn change_visibility(&mut self, record_fields: Vec<SyntaxNode>) {
+ let (mut replacements, record_field_parents, impls) =
+ get_replacements_for_visibilty_change(&mut self.body_items, false);
+
+ let mut impl_items: Vec<ast::Item> = impls
+ .into_iter()
+ .flat_map(|impl_| impl_.syntax().descendants())
+ .filter_map(ast::Item::cast)
+ .collect();
+
+ let (mut impl_item_replacements, _, _) =
+ get_replacements_for_visibilty_change(&mut impl_items, true);
+
+ replacements.append(&mut impl_item_replacements);
+
+ for (_, field_owner) in record_field_parents {
+ for desc in field_owner.descendants().filter_map(ast::RecordField::cast) {
+ let is_record_field_present =
+ record_fields.clone().into_iter().any(|x| x.to_string() == desc.to_string());
+ if is_record_field_present {
+ replacements.push((desc.visibility(), desc.syntax().clone()));
+ }
+ }
+ }
+
+ for (vis, syntax) in replacements {
+ let item = syntax.children_with_tokens().find(|node_or_token| {
+ match node_or_token.kind() {
+ // We're skipping comments, doc comments, and attribute macros that may precede the keyword
+ // that the visibility should be placed before.
+ SyntaxKind::COMMENT | SyntaxKind::ATTR | SyntaxKind::WHITESPACE => false,
+ _ => true,
+ }
+ });
+
+ add_change_vis(vis, item);
+ }
+ }
+
+ fn resolve_imports(
+ &mut self,
+ curr_parent_module: Option<ast::Module>,
+ ctx: &AssistContext<'_>,
+ ) -> Vec<TextRange> {
+ let mut import_paths_to_be_removed: Vec<TextRange> = vec![];
+ let mut node_set: HashSet<String> = HashSet::new();
+
+ for item in self.body_items.clone() {
+ for x in item.syntax().descendants() {
+ if let Some(name) = ast::Name::cast(x.clone()) {
+ if let Some(name_classify) = NameClass::classify(&ctx.sema, &name) {
+ //Necessary to avoid two same names going through
+ if !node_set.contains(&name.syntax().to_string()) {
+ node_set.insert(name.syntax().to_string());
+ let def_opt: Option<Definition> = match name_classify {
+ NameClass::Definition(def) => Some(def),
+ _ => None,
+ };
+
+ if let Some(def) = def_opt {
+ if let Some(import_path) = self
+ .process_names_and_namerefs_for_import_resolve(
+ def,
+ name.syntax(),
+ &curr_parent_module,
+ ctx,
+ )
+ {
+ check_intersection_and_push(
+ &mut import_paths_to_be_removed,
+ import_path,
+ );
+ }
+ }
+ }
+ }
+ }
+
+ if let Some(name_ref) = ast::NameRef::cast(x) {
+ if let Some(name_classify) = NameRefClass::classify(&ctx.sema, &name_ref) {
+ //Necessary to avoid two same names going through
+ if !node_set.contains(&name_ref.syntax().to_string()) {
+ node_set.insert(name_ref.syntax().to_string());
+ let def_opt: Option<Definition> = match name_classify {
+ NameRefClass::Definition(def) => Some(def),
+ _ => None,
+ };
+
+ if let Some(def) = def_opt {
+ if let Some(import_path) = self
+ .process_names_and_namerefs_for_import_resolve(
+ def,
+ name_ref.syntax(),
+ &curr_parent_module,
+ ctx,
+ )
+ {
+ check_intersection_and_push(
+ &mut import_paths_to_be_removed,
+ import_path,
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ import_paths_to_be_removed
+ }
+
+ fn process_names_and_namerefs_for_import_resolve(
+ &mut self,
+ def: Definition,
+ node_syntax: &SyntaxNode,
+ curr_parent_module: &Option<ast::Module>,
+ ctx: &AssistContext<'_>,
+ ) -> Option<TextRange> {
+ //We only need to find in the current file
+ let selection_range = ctx.selection_trimmed();
+ let curr_file_id = ctx.file_id();
+ let search_scope = SearchScope::single_file(curr_file_id);
+ let usage_res = def.usages(&ctx.sema).in_scope(search_scope).all();
+ let file = ctx.sema.parse(curr_file_id);
+
+ let mut exists_inside_sel = false;
+ let mut exists_outside_sel = false;
+ for (_, refs) in usage_res.iter() {
+ let mut non_use_nodes_itr = refs.iter().filter_map(|x| {
+ if find_node_at_range::<ast::Use>(file.syntax(), x.range).is_none() {
+ let path_opt = find_node_at_range::<ast::Path>(file.syntax(), x.range);
+ return path_opt;
+ }
+
+ None
+ });
+
+ if non_use_nodes_itr
+ .clone()
+ .any(|x| !selection_range.contains_range(x.syntax().text_range()))
+ {
+ exists_outside_sel = true;
+ }
+ if non_use_nodes_itr.any(|x| selection_range.contains_range(x.syntax().text_range())) {
+ exists_inside_sel = true;
+ }
+ }
+
+ let source_exists_outside_sel_in_same_mod = does_source_exists_outside_sel_in_same_mod(
+ def,
+ ctx,
+ curr_parent_module,
+ selection_range,
+ curr_file_id,
+ );
+
+ let use_stmt_opt: Option<ast::Use> = usage_res.into_iter().find_map(|(file_id, refs)| {
+ if file_id == curr_file_id {
+ refs.into_iter()
+ .rev()
+ .find_map(|fref| find_node_at_range(file.syntax(), fref.range))
+ } else {
+ None
+ }
+ });
+
+ let mut use_tree_str_opt: Option<Vec<ast::Path>> = None;
+ //Exists inside and outside selection
+ // - Use stmt for item is present -> get the use_tree_str and reconstruct the path in new
+ // module
+ // - Use stmt for item is not present ->
+ //If it is not found, the definition is either ported inside new module or it stays
+ //outside:
+ //- Def is inside: Nothing to import
+ //- Def is outside: Import it inside with super
+
+ //Exists inside selection but not outside -> Check for the import of it in original module,
+ //get the use_tree_str, reconstruct the use stmt in new module
+
+ let mut import_path_to_be_removed: Option<TextRange> = None;
+ if exists_inside_sel && exists_outside_sel {
+ //Changes to be made only inside new module
+
+ //If use_stmt exists, find the use_tree_str, reconstruct it inside new module
+ //If not, insert a use stmt with super and the given nameref
+ if let Some((use_tree_str, _)) =
+ self.process_use_stmt_for_import_resolve(use_stmt_opt, node_syntax)
+ {
+ use_tree_str_opt = Some(use_tree_str);
+ } else if source_exists_outside_sel_in_same_mod {
+ //Considered only after use_stmt is not present
+ //source_exists_outside_sel_in_same_mod | exists_outside_sel(exists_inside_sel =
+ //true for all cases)
+ // false | false -> Do nothing
+ // false | true -> If source is in selection -> nothing to do, If source is outside
+ // mod -> ust_stmt transversal
+ // true | false -> super import insertion
+ // true | true -> super import insertion
+ self.make_use_stmt_of_node_with_super(node_syntax);
+ }
+ } else if exists_inside_sel && !exists_outside_sel {
+ //Changes to be made inside new module, and remove import from outside
+
+ if let Some((mut use_tree_str, text_range_opt)) =
+ self.process_use_stmt_for_import_resolve(use_stmt_opt, node_syntax)
+ {
+ if let Some(text_range) = text_range_opt {
+ import_path_to_be_removed = Some(text_range);
+ }
+
+ if source_exists_outside_sel_in_same_mod {
+ if let Some(first_path_in_use_tree) = use_tree_str.last() {
+ let first_path_in_use_tree_str = first_path_in_use_tree.to_string();
+ if !first_path_in_use_tree_str.contains("super")
+ && !first_path_in_use_tree_str.contains("crate")
+ {
+ let super_path = make::ext::ident_path("super");
+ use_tree_str.push(super_path);
+ }
+ }
+ }
+
+ use_tree_str_opt = Some(use_tree_str);
+ } else if source_exists_outside_sel_in_same_mod {
+ self.make_use_stmt_of_node_with_super(node_syntax);
+ }
+ }
+
+ if let Some(use_tree_str) = use_tree_str_opt {
+ let mut use_tree_str = use_tree_str;
+ use_tree_str.reverse();
+
+ if !(!exists_outside_sel && exists_inside_sel && source_exists_outside_sel_in_same_mod)
+ {
+ if let Some(first_path_in_use_tree) = use_tree_str.first() {
+ let first_path_in_use_tree_str = first_path_in_use_tree.to_string();
+ if first_path_in_use_tree_str.contains("super") {
+ let super_path = make::ext::ident_path("super");
+ use_tree_str.insert(0, super_path)
+ }
+ }
+ }
+
+ let use_ =
+ make::use_(None, make::use_tree(make::join_paths(use_tree_str), None, None, false));
+ let item = ast::Item::from(use_);
+ self.use_items.insert(0, item);
+ }
+
+ import_path_to_be_removed
+ }
+
+ fn make_use_stmt_of_node_with_super(&mut self, node_syntax: &SyntaxNode) -> ast::Item {
+ let super_path = make::ext::ident_path("super");
+ let node_path = make::ext::ident_path(&node_syntax.to_string());
+ let use_ = make::use_(
+ None,
+ make::use_tree(make::join_paths(vec![super_path, node_path]), None, None, false),
+ );
+
+ let item = ast::Item::from(use_);
+ self.use_items.insert(0, item.clone());
+ item
+ }
+
+ fn process_use_stmt_for_import_resolve(
+ &self,
+ use_stmt_opt: Option<ast::Use>,
+ node_syntax: &SyntaxNode,
+ ) -> Option<(Vec<ast::Path>, Option<TextRange>)> {
+ if let Some(use_stmt) = use_stmt_opt {
+ for desc in use_stmt.syntax().descendants() {
+ if let Some(path_seg) = ast::PathSegment::cast(desc) {
+ if path_seg.syntax().to_string() == node_syntax.to_string() {
+ let mut use_tree_str = vec![path_seg.parent_path()];
+ get_use_tree_paths_from_path(path_seg.parent_path(), &mut use_tree_str);
+ for ancs in path_seg.syntax().ancestors() {
+ //Here we are looking for use_tree with same string value as node
+ //passed above as the range_to_remove function looks for a comma and
+ //then includes it in the text range to remove it. But the comma only
+ //appears at the use_tree level
+ if let Some(use_tree) = ast::UseTree::cast(ancs) {
+ if use_tree.syntax().to_string() == node_syntax.to_string() {
+ return Some((
+ use_tree_str,
+ Some(range_to_remove(use_tree.syntax())),
+ ));
+ }
+ }
+ }
+
+ return Some((use_tree_str, None));
+ }
+ }
+ }
+ }
+
+ None
+ }
+}
+
+fn check_intersection_and_push(
+ import_paths_to_be_removed: &mut Vec<TextRange>,
+ import_path: TextRange,
+) {
+ if import_paths_to_be_removed.len() > 0 {
+ // Text ranges recieved here for imports are extended to the
+ // next/previous comma which can cause intersections among them
+ // and later deletion of these can cause panics similar
+ // to reported in #11766. So to mitigate it, we
+ // check for intersection between all current members
+ // and if it exists we combine both text ranges into
+ // one
+ let r = import_paths_to_be_removed
+ .into_iter()
+ .position(|it| it.intersect(import_path).is_some());
+ match r {
+ Some(it) => {
+ import_paths_to_be_removed[it] = import_paths_to_be_removed[it].cover(import_path)
+ }
+ None => import_paths_to_be_removed.push(import_path),
+ }
+ } else {
+ import_paths_to_be_removed.push(import_path);
+ }
+}
+
+fn does_source_exists_outside_sel_in_same_mod(
+ def: Definition,
+ ctx: &AssistContext<'_>,
+ curr_parent_module: &Option<ast::Module>,
+ selection_range: TextRange,
+ curr_file_id: FileId,
+) -> bool {
+ let mut source_exists_outside_sel_in_same_mod = false;
+ match def {
+ Definition::Module(x) => {
+ let source = x.definition_source(ctx.db());
+ let have_same_parent;
+ if let Some(ast_module) = &curr_parent_module {
+ if let Some(hir_module) = x.parent(ctx.db()) {
+ have_same_parent =
+ compare_hir_and_ast_module(ast_module, hir_module, ctx).is_some();
+ } else {
+ let source_file_id = source.file_id.original_file(ctx.db());
+ have_same_parent = source_file_id == curr_file_id;
+ }
+ } else {
+ let source_file_id = source.file_id.original_file(ctx.db());
+ have_same_parent = source_file_id == curr_file_id;
+ }
+
+ if have_same_parent {
+ match source.value {
+ ModuleSource::Module(module_) => {
+ source_exists_outside_sel_in_same_mod =
+ !selection_range.contains_range(module_.syntax().text_range());
+ }
+ _ => {}
+ }
+ }
+ }
+ Definition::Function(x) => {
+ if let Some(source) = x.source(ctx.db()) {
+ let have_same_parent = if let Some(ast_module) = &curr_parent_module {
+ compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some()
+ } else {
+ let source_file_id = source.file_id.original_file(ctx.db());
+ source_file_id == curr_file_id
+ };
+
+ if have_same_parent {
+ source_exists_outside_sel_in_same_mod =
+ !selection_range.contains_range(source.value.syntax().text_range());
+ }
+ }
+ }
+ Definition::Adt(x) => {
+ if let Some(source) = x.source(ctx.db()) {
+ let have_same_parent = if let Some(ast_module) = &curr_parent_module {
+ compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some()
+ } else {
+ let source_file_id = source.file_id.original_file(ctx.db());
+ source_file_id == curr_file_id
+ };
+
+ if have_same_parent {
+ source_exists_outside_sel_in_same_mod =
+ !selection_range.contains_range(source.value.syntax().text_range());
+ }
+ }
+ }
+ Definition::Variant(x) => {
+ if let Some(source) = x.source(ctx.db()) {
+ let have_same_parent = if let Some(ast_module) = &curr_parent_module {
+ compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some()
+ } else {
+ let source_file_id = source.file_id.original_file(ctx.db());
+ source_file_id == curr_file_id
+ };
+
+ if have_same_parent {
+ source_exists_outside_sel_in_same_mod =
+ !selection_range.contains_range(source.value.syntax().text_range());
+ }
+ }
+ }
+ Definition::Const(x) => {
+ if let Some(source) = x.source(ctx.db()) {
+ let have_same_parent = if let Some(ast_module) = &curr_parent_module {
+ compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some()
+ } else {
+ let source_file_id = source.file_id.original_file(ctx.db());
+ source_file_id == curr_file_id
+ };
+
+ if have_same_parent {
+ source_exists_outside_sel_in_same_mod =
+ !selection_range.contains_range(source.value.syntax().text_range());
+ }
+ }
+ }
+ Definition::Static(x) => {
+ if let Some(source) = x.source(ctx.db()) {
+ let have_same_parent = if let Some(ast_module) = &curr_parent_module {
+ compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some()
+ } else {
+ let source_file_id = source.file_id.original_file(ctx.db());
+ source_file_id == curr_file_id
+ };
+
+ if have_same_parent {
+ source_exists_outside_sel_in_same_mod =
+ !selection_range.contains_range(source.value.syntax().text_range());
+ }
+ }
+ }
+ Definition::Trait(x) => {
+ if let Some(source) = x.source(ctx.db()) {
+ let have_same_parent = if let Some(ast_module) = &curr_parent_module {
+ compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some()
+ } else {
+ let source_file_id = source.file_id.original_file(ctx.db());
+ source_file_id == curr_file_id
+ };
+
+ if have_same_parent {
+ source_exists_outside_sel_in_same_mod =
+ !selection_range.contains_range(source.value.syntax().text_range());
+ }
+ }
+ }
+ Definition::TypeAlias(x) => {
+ if let Some(source) = x.source(ctx.db()) {
+ let have_same_parent = if let Some(ast_module) = &curr_parent_module {
+ compare_hir_and_ast_module(ast_module, x.module(ctx.db()), ctx).is_some()
+ } else {
+ let source_file_id = source.file_id.original_file(ctx.db());
+ source_file_id == curr_file_id
+ };
+
+ if have_same_parent {
+ source_exists_outside_sel_in_same_mod =
+ !selection_range.contains_range(source.value.syntax().text_range());
+ }
+ }
+ }
+ _ => {}
+ }
+
+ source_exists_outside_sel_in_same_mod
+}
+
+fn get_replacements_for_visibilty_change(
+ items: &mut [ast::Item],
+ is_clone_for_updated: bool,
+) -> (
+ Vec<(Option<ast::Visibility>, SyntaxNode)>,
+ Vec<(Option<ast::Visibility>, SyntaxNode)>,
+ Vec<ast::Impl>,
+) {
+ let mut replacements = Vec::new();
+ let mut record_field_parents = Vec::new();
+ let mut impls = Vec::new();
+
+ for item in items {
+ if !is_clone_for_updated {
+ *item = item.clone_for_update();
+ }
+ //Use stmts are ignored
+ match item {
+ ast::Item::Const(it) => replacements.push((it.visibility(), it.syntax().clone())),
+ ast::Item::Enum(it) => replacements.push((it.visibility(), it.syntax().clone())),
+ ast::Item::ExternCrate(it) => replacements.push((it.visibility(), it.syntax().clone())),
+ ast::Item::Fn(it) => replacements.push((it.visibility(), it.syntax().clone())),
+ //Associated item's visibility should not be changed
+ ast::Item::Impl(it) if it.for_token().is_none() => impls.push(it.clone()),
+ ast::Item::MacroDef(it) => replacements.push((it.visibility(), it.syntax().clone())),
+ ast::Item::Module(it) => replacements.push((it.visibility(), it.syntax().clone())),
+ ast::Item::Static(it) => replacements.push((it.visibility(), it.syntax().clone())),
+ ast::Item::Struct(it) => {
+ replacements.push((it.visibility(), it.syntax().clone()));
+ record_field_parents.push((it.visibility(), it.syntax().clone()));
+ }
+ ast::Item::Trait(it) => replacements.push((it.visibility(), it.syntax().clone())),
+ ast::Item::TypeAlias(it) => replacements.push((it.visibility(), it.syntax().clone())),
+ ast::Item::Union(it) => {
+ replacements.push((it.visibility(), it.syntax().clone()));
+ record_field_parents.push((it.visibility(), it.syntax().clone()));
+ }
+ _ => (),
+ }
+ }
+
+ (replacements, record_field_parents, impls)
+}
+
+fn get_use_tree_paths_from_path(
+ path: ast::Path,
+ use_tree_str: &mut Vec<ast::Path>,
+) -> Option<&mut Vec<ast::Path>> {
+ path.syntax().ancestors().filter(|x| x.to_string() != path.to_string()).find_map(|x| {
+ if let Some(use_tree) = ast::UseTree::cast(x) {
+ if let Some(upper_tree_path) = use_tree.path() {
+ if upper_tree_path.to_string() != path.to_string() {
+ use_tree_str.push(upper_tree_path.clone());
+ get_use_tree_paths_from_path(upper_tree_path, use_tree_str);
+ return Some(use_tree);
+ }
+ }
+ }
+ None
+ })?;
+
+ Some(use_tree_str)
+}
+
+fn add_change_vis(vis: Option<ast::Visibility>, node_or_token_opt: Option<syntax::SyntaxElement>) {
+ if vis.is_none() {
+ if let Some(node_or_token) = node_or_token_opt {
+ let pub_crate_vis = make::visibility_pub_crate().clone_for_update();
+ ted::insert(ted::Position::before(node_or_token), pub_crate_vis.syntax());
+ }
+ }
+}
+
+fn compare_hir_and_ast_module(
+ ast_module: &ast::Module,
+ hir_module: hir::Module,
+ ctx: &AssistContext<'_>,
+) -> Option<()> {
+ let hir_mod_name = hir_module.name(ctx.db())?;
+ let ast_mod_name = ast_module.name()?;
+ if hir_mod_name.to_string() != ast_mod_name.to_string() {
+ return None;
+ }
+
+ Some(())
+}
+
+fn indent_range_before_given_node(node: &SyntaxNode) -> Option<TextRange> {
+ node.siblings_with_tokens(syntax::Direction::Prev)
+ .find(|x| x.kind() == WHITESPACE)
+ .map(|x| x.text_range())
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn test_not_applicable_without_selection() {
+ check_assist_not_applicable(
+ extract_module,
+ r"
+$0pub struct PublicStruct {
+ field: i32,
+}
+ ",
+ )
+ }
+
+ #[test]
+ fn test_extract_module() {
+ check_assist(
+ extract_module,
+ r"
+ mod thirdpartycrate {
+ pub mod nest {
+ pub struct SomeType;
+ pub struct SomeType2;
+ }
+ pub struct SomeType1;
+ }
+
+ mod bar {
+ use crate::thirdpartycrate::{nest::{SomeType, SomeType2}, SomeType1};
+
+ pub struct PublicStruct {
+ field: PrivateStruct,
+ field1: SomeType1,
+ }
+
+ impl PublicStruct {
+ pub fn new() -> Self {
+ Self { field: PrivateStruct::new(), field1: SomeType1 }
+ }
+ }
+
+ fn foo() {
+ let _s = PrivateStruct::new();
+ let _a = bar();
+ }
+
+$0struct PrivateStruct {
+ inner: SomeType,
+}
+
+pub struct PrivateStruct1 {
+ pub inner: i32,
+}
+
+impl PrivateStruct {
+ fn new() -> Self {
+ PrivateStruct { inner: SomeType }
+ }
+}
+
+fn bar() -> i32 {
+ 2
+}$0
+ }
+ ",
+ r"
+ mod thirdpartycrate {
+ pub mod nest {
+ pub struct SomeType;
+ pub struct SomeType2;
+ }
+ pub struct SomeType1;
+ }
+
+ mod bar {
+ use crate::thirdpartycrate::{nest::{SomeType2}, SomeType1};
+
+ pub struct PublicStruct {
+ field: modname::PrivateStruct,
+ field1: SomeType1,
+ }
+
+ impl PublicStruct {
+ pub fn new() -> Self {
+ Self { field: modname::PrivateStruct::new(), field1: SomeType1 }
+ }
+ }
+
+ fn foo() {
+ let _s = modname::PrivateStruct::new();
+ let _a = modname::bar();
+ }
+
+mod modname {
+ use crate::thirdpartycrate::nest::SomeType;
+
+ pub(crate) struct PrivateStruct {
+ pub(crate) inner: SomeType,
+ }
+
+ pub struct PrivateStruct1 {
+ pub inner: i32,
+ }
+
+ impl PrivateStruct {
+ pub(crate) fn new() -> Self {
+ PrivateStruct { inner: SomeType }
+ }
+ }
+
+ pub(crate) fn bar() -> i32 {
+ 2
+ }
+}
+ }
+ ",
+ );
+ }
+
+ #[test]
+ fn test_extract_module_for_function_only() {
+ check_assist(
+ extract_module,
+ r"
+$0fn foo(name: i32) -> i32 {
+ name + 1
+}$0
+
+ fn bar(name: i32) -> i32 {
+ name + 2
+ }
+ ",
+ r"
+mod modname {
+ pub(crate) fn foo(name: i32) -> i32 {
+ name + 1
+ }
+}
+
+ fn bar(name: i32) -> i32 {
+ name + 2
+ }
+ ",
+ )
+ }
+
+ #[test]
+ fn test_extract_module_for_impl_having_corresponding_adt_in_selection() {
+ check_assist(
+ extract_module,
+ r"
+ mod impl_play {
+$0struct A {}
+
+impl A {
+ pub fn new_a() -> i32 {
+ 2
+ }
+}$0
+
+ fn a() {
+ let _a = A::new_a();
+ }
+ }
+ ",
+ r"
+ mod impl_play {
+mod modname {
+ pub(crate) struct A {}
+
+ impl A {
+ pub fn new_a() -> i32 {
+ 2
+ }
+ }
+}
+
+ fn a() {
+ let _a = modname::A::new_a();
+ }
+ }
+ ",
+ )
+ }
+
+ #[test]
+ fn test_import_resolve_when_its_only_inside_selection() {
+ check_assist(
+ extract_module,
+ r"
+ mod foo {
+ pub struct PrivateStruct;
+ pub struct PrivateStruct1;
+ }
+
+ mod bar {
+ use super::foo::{PrivateStruct, PrivateStruct1};
+
+$0struct Strukt {
+ field: PrivateStruct,
+}$0
+
+ struct Strukt1 {
+ field: PrivateStruct1,
+ }
+ }
+ ",
+ r"
+ mod foo {
+ pub struct PrivateStruct;
+ pub struct PrivateStruct1;
+ }
+
+ mod bar {
+ use super::foo::{PrivateStruct1};
+
+mod modname {
+ use super::super::foo::PrivateStruct;
+
+ pub(crate) struct Strukt {
+ pub(crate) field: PrivateStruct,
+ }
+}
+
+ struct Strukt1 {
+ field: PrivateStruct1,
+ }
+ }
+ ",
+ )
+ }
+
+ #[test]
+ fn test_import_resolve_when_its_inside_and_outside_selection_and_source_not_in_same_mod() {
+ check_assist(
+ extract_module,
+ r"
+ mod foo {
+ pub struct PrivateStruct;
+ }
+
+ mod bar {
+ use super::foo::PrivateStruct;
+
+$0struct Strukt {
+ field: PrivateStruct,
+}$0
+
+ struct Strukt1 {
+ field: PrivateStruct,
+ }
+ }
+ ",
+ r"
+ mod foo {
+ pub struct PrivateStruct;
+ }
+
+ mod bar {
+ use super::foo::PrivateStruct;
+
+mod modname {
+ use super::super::foo::PrivateStruct;
+
+ pub(crate) struct Strukt {
+ pub(crate) field: PrivateStruct,
+ }
+}
+
+ struct Strukt1 {
+ field: PrivateStruct,
+ }
+ }
+ ",
+ )
+ }
+
+ #[test]
+ fn test_import_resolve_when_its_inside_and_outside_selection_and_source_is_in_same_mod() {
+ check_assist(
+ extract_module,
+ r"
+ mod bar {
+ pub struct PrivateStruct;
+
+$0struct Strukt {
+ field: PrivateStruct,
+}$0
+
+ struct Strukt1 {
+ field: PrivateStruct,
+ }
+ }
+ ",
+ r"
+ mod bar {
+ pub struct PrivateStruct;
+
+mod modname {
+ use super::PrivateStruct;
+
+ pub(crate) struct Strukt {
+ pub(crate) field: PrivateStruct,
+ }
+}
+
+ struct Strukt1 {
+ field: PrivateStruct,
+ }
+ }
+ ",
+ )
+ }
+
+ #[test]
+ fn test_extract_module_for_correspoding_adt_of_impl_present_in_same_mod_but_not_in_selection() {
+ check_assist(
+ extract_module,
+ r"
+ mod impl_play {
+ struct A {}
+
+$0impl A {
+ pub fn new_a() -> i32 {
+ 2
+ }
+}$0
+
+ fn a() {
+ let _a = A::new_a();
+ }
+ }
+ ",
+ r"
+ mod impl_play {
+ struct A {}
+
+mod modname {
+ use super::A;
+
+ impl A {
+ pub fn new_a() -> i32 {
+ 2
+ }
+ }
+}
+
+ fn a() {
+ let _a = A::new_a();
+ }
+ }
+ ",
+ )
+ }
+
+ #[test]
+ fn test_extract_module_for_impl_not_having_corresponding_adt_in_selection_and_not_in_same_mod_but_with_super(
+ ) {
+ check_assist(
+ extract_module,
+ r"
+ mod foo {
+ pub struct A {}
+ }
+ mod impl_play {
+ use super::foo::A;
+
+$0impl A {
+ pub fn new_a() -> i32 {
+ 2
+ }
+}$0
+
+ fn a() {
+ let _a = A::new_a();
+ }
+ }
+ ",
+ r"
+ mod foo {
+ pub struct A {}
+ }
+ mod impl_play {
+ use super::foo::A;
+
+mod modname {
+ use super::super::foo::A;
+
+ impl A {
+ pub fn new_a() -> i32 {
+ 2
+ }
+ }
+}
+
+ fn a() {
+ let _a = A::new_a();
+ }
+ }
+ ",
+ )
+ }
+
+ #[test]
+ fn test_import_resolve_for_trait_bounds_on_function() {
+ check_assist(
+ extract_module,
+ r"
+ mod impl_play2 {
+ trait JustATrait {}
+
+$0struct A {}
+
+fn foo<T: JustATrait>(arg: T) -> T {
+ arg
+}
+
+impl JustATrait for A {}
+
+fn bar() {
+ let a = A {};
+ foo(a);
+}$0
+ }
+ ",
+ r"
+ mod impl_play2 {
+ trait JustATrait {}
+
+mod modname {
+ use super::JustATrait;
+
+ pub(crate) struct A {}
+
+ pub(crate) fn foo<T: JustATrait>(arg: T) -> T {
+ arg
+ }
+
+ impl JustATrait for A {}
+
+ pub(crate) fn bar() {
+ let a = A {};
+ foo(a);
+ }
+}
+ }
+ ",
+ )
+ }
+
+ #[test]
+ fn test_extract_module_for_module() {
+ check_assist(
+ extract_module,
+ r"
+ mod impl_play2 {
+$0mod impl_play {
+ pub struct A {}
+}$0
+ }
+ ",
+ r"
+ mod impl_play2 {
+mod modname {
+ pub(crate) mod impl_play {
+ pub struct A {}
+ }
+}
+ }
+ ",
+ )
+ }
+
+ #[test]
+ fn test_extract_module_with_multiple_files() {
+ check_assist(
+ extract_module,
+ r"
+ //- /main.rs
+ mod foo;
+
+ use foo::PrivateStruct;
+
+ pub struct Strukt {
+ field: PrivateStruct,
+ }
+
+ fn main() {
+ $0struct Strukt1 {
+ field: Strukt,
+ }$0
+ }
+ //- /foo.rs
+ pub struct PrivateStruct;
+ ",
+ r"
+ mod foo;
+
+ use foo::PrivateStruct;
+
+ pub struct Strukt {
+ field: PrivateStruct,
+ }
+
+ fn main() {
+ mod modname {
+ use super::Strukt;
+
+ pub(crate) struct Strukt1 {
+ pub(crate) field: Strukt,
+ }
+ }
+ }
+ ",
+ )
+ }
+
+ #[test]
+ fn test_extract_module_macro_rules() {
+ check_assist(
+ extract_module,
+ r"
+$0macro_rules! m {
+ () => {};
+}$0
+m! {}
+ ",
+ r"
+mod modname {
+ macro_rules! m {
+ () => {};
+ }
+}
+modname::m! {}
+ ",
+ );
+ }
+
+ #[test]
+ fn test_do_not_apply_visibility_modifier_to_trait_impl_items() {
+ check_assist(
+ extract_module,
+ r"
+ trait ATrait {
+ fn function();
+ }
+
+ struct A {}
+
+$0impl ATrait for A {
+ fn function() {}
+}$0
+ ",
+ r"
+ trait ATrait {
+ fn function();
+ }
+
+ struct A {}
+
+mod modname {
+ use super::A;
+
+ use super::ATrait;
+
+ impl ATrait for A {
+ fn function() {}
+ }
+}
+ ",
+ )
+ }
+
+ #[test]
+ fn test_if_inside_impl_block_generate_module_outside() {
+ check_assist(
+ extract_module,
+ r"
+ struct A {}
+
+ impl A {
+$0fn foo() {}$0
+ fn bar() {}
+ }
+ ",
+ r"
+ struct A {}
+
+ impl A {
+ fn bar() {}
+ }
+
+mod modname {
+ use super::A;
+
+ impl A {
+ pub(crate) fn foo() {}
+ }
+}
+ ",
+ )
+ }
+
+ #[test]
+ fn test_if_inside_impl_block_generate_module_outside_but_impl_block_having_one_child() {
+ check_assist(
+ extract_module,
+ r"
+ struct A {}
+ struct B {}
+
+ impl A {
+$0fn foo(x: B) {}$0
+ }
+ ",
+ r"
+ struct A {}
+ struct B {}
+
+mod modname {
+ use super::B;
+
+ use super::A;
+
+ impl A {
+ pub(crate) fn foo(x: B) {}
+ }
+}
+ ",
+ )
+ }
+
+ #[test]
+ fn test_issue_11766() {
+ //https://github.com/rust-lang/rust-analyzer/issues/11766
+ check_assist(
+ extract_module,
+ r"
+ mod x {
+ pub struct Foo;
+ pub struct Bar;
+ }
+
+ use x::{Bar, Foo};
+
+ $0type A = (Foo, Bar);$0
+ ",
+ r"
+ mod x {
+ pub struct Foo;
+ pub struct Bar;
+ }
+
+ use x::{};
+
+ mod modname {
+ use super::x::Bar;
+
+ use super::x::Foo;
+
+ pub(crate) type A = (Foo, Bar);
+ }
+ ",
+ )
+ }
+
+ #[test]
+ fn test_issue_12790() {
+ check_assist(
+ extract_module,
+ r"
+ $0/// A documented function
+ fn documented_fn() {}
+
+ // A commented function with a #[] attribute macro
+ #[cfg(test)]
+ fn attribute_fn() {}
+
+ // A normally commented function
+ fn normal_fn() {}
+
+ /// A documented Struct
+ struct DocumentedStruct {
+ // Normal field
+ x: i32,
+
+ /// Documented field
+ y: i32,
+
+ // Macroed field
+ #[cfg(test)]
+ z: i32,
+ }
+
+ // A macroed Struct
+ #[cfg(test)]
+ struct MacroedStruct {
+ // Normal field
+ x: i32,
+
+ /// Documented field
+ y: i32,
+
+ // Macroed field
+ #[cfg(test)]
+ z: i32,
+ }
+
+ // A normal Struct
+ struct NormalStruct {
+ // Normal field
+ x: i32,
+
+ /// Documented field
+ y: i32,
+
+ // Macroed field
+ #[cfg(test)]
+ z: i32,
+ }
+
+ /// A documented type
+ type DocumentedType = i32;
+
+ // A macroed type
+ #[cfg(test)]
+ type MacroedType = i32;
+
+ /// A module to move
+ mod module {}
+
+ /// An impl to move
+ impl NormalStruct {
+ /// A method
+ fn new() {}
+ }
+
+ /// A documented trait
+ trait DocTrait {
+ /// Inner function
+ fn doc() {}
+ }
+
+ /// An enum
+ enum DocumentedEnum {
+ /// A variant
+ A,
+ /// Another variant
+ B { x: i32, y: i32 }
+ }
+
+ /// Documented const
+ const MY_CONST: i32 = 0;$0
+ ",
+ r"
+ mod modname {
+ /// A documented function
+ pub(crate) fn documented_fn() {}
+
+ // A commented function with a #[] attribute macro
+ #[cfg(test)]
+ pub(crate) fn attribute_fn() {}
+
+ // A normally commented function
+ pub(crate) fn normal_fn() {}
+
+ /// A documented Struct
+ pub(crate) struct DocumentedStruct {
+ // Normal field
+ pub(crate) x: i32,
+
+ /// Documented field
+ pub(crate) y: i32,
+
+ // Macroed field
+ #[cfg(test)]
+ pub(crate) z: i32,
+ }
+
+ // A macroed Struct
+ #[cfg(test)]
+ pub(crate) struct MacroedStruct {
+ // Normal field
+ pub(crate) x: i32,
+
+ /// Documented field
+ pub(crate) y: i32,
+
+ // Macroed field
+ #[cfg(test)]
+ pub(crate) z: i32,
+ }
+
+ // A normal Struct
+ pub(crate) struct NormalStruct {
+ // Normal field
+ pub(crate) x: i32,
+
+ /// Documented field
+ pub(crate) y: i32,
+
+ // Macroed field
+ #[cfg(test)]
+ pub(crate) z: i32,
+ }
+
+ /// A documented type
+ pub(crate) type DocumentedType = i32;
+
+ // A macroed type
+ #[cfg(test)]
+ pub(crate) type MacroedType = i32;
+
+ /// A module to move
+ pub(crate) mod module {}
+
+ /// An impl to move
+ impl NormalStruct {
+ /// A method
+ pub(crate) fn new() {}
+ }
+
+ /// A documented trait
+ pub(crate) trait DocTrait {
+ /// Inner function
+ fn doc() {}
+ }
+
+ /// An enum
+ pub(crate) enum DocumentedEnum {
+ /// A variant
+ A,
+ /// Another variant
+ B { x: i32, y: i32 }
+ }
+
+ /// Documented const
+ pub(crate) const MY_CONST: i32 = 0;
+ }
+ ",
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs
new file mode 100644
index 000000000..a93648f2d
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -0,0 +1,1076 @@
+use std::iter;
+
+use either::Either;
+use hir::{Module, ModuleDef, Name, Variant};
+use ide_db::{
+ defs::Definition,
+ helpers::mod_path_to_ast,
+ imports::insert_use::{insert_use, ImportScope, InsertUseConfig},
+ search::FileReference,
+ FxHashSet, RootDatabase,
+};
+use itertools::{Itertools, Position};
+use syntax::{
+ ast::{
+ self, edit::IndentLevel, edit_in_place::Indent, make, AstNode, HasAttrs, HasGenericParams,
+ HasName, HasVisibility,
+ },
+ match_ast, ted, SyntaxElement,
+ SyntaxKind::*,
+ SyntaxNode, T,
+};
+
+use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: extract_struct_from_enum_variant
+//
+// Extracts a struct from enum variant.
+//
+// ```
+// enum A { $0One(u32, u32) }
+// ```
+// ->
+// ```
+// struct One(u32, u32);
+//
+// enum A { One(One) }
+// ```
+pub(crate) fn extract_struct_from_enum_variant(
+ acc: &mut Assists,
+ ctx: &AssistContext<'_>,
+) -> Option<()> {
+ let variant = ctx.find_node_at_offset::<ast::Variant>()?;
+ let field_list = extract_field_list_if_applicable(&variant)?;
+
+ let variant_name = variant.name()?;
+ let variant_hir = ctx.sema.to_def(&variant)?;
+ if existing_definition(ctx.db(), &variant_name, &variant_hir) {
+ cov_mark::hit!(test_extract_enum_not_applicable_if_struct_exists);
+ return None;
+ }
+
+ let enum_ast = variant.parent_enum();
+ let enum_hir = ctx.sema.to_def(&enum_ast)?;
+ let target = variant.syntax().text_range();
+ acc.add(
+ AssistId("extract_struct_from_enum_variant", AssistKind::RefactorRewrite),
+ "Extract struct from enum variant",
+ target,
+ |builder| {
+ let variant_hir_name = variant_hir.name(ctx.db());
+ let enum_module_def = ModuleDef::from(enum_hir);
+ let usages = Definition::Variant(variant_hir).usages(&ctx.sema).all();
+
+ let mut visited_modules_set = FxHashSet::default();
+ let current_module = enum_hir.module(ctx.db());
+ visited_modules_set.insert(current_module);
+ // record file references of the file the def resides in, we only want to swap to the edited file in the builder once
+ let mut def_file_references = None;
+ for (file_id, references) in usages {
+ if file_id == ctx.file_id() {
+ def_file_references = Some(references);
+ continue;
+ }
+ builder.edit_file(file_id);
+ let processed = process_references(
+ ctx,
+ builder,
+ &mut visited_modules_set,
+ &enum_module_def,
+ &variant_hir_name,
+ references,
+ );
+ processed.into_iter().for_each(|(path, node, import)| {
+ apply_references(ctx.config.insert_use, path, node, import)
+ });
+ }
+ builder.edit_file(ctx.file_id());
+
+ let variant = builder.make_mut(variant.clone());
+ if let Some(references) = def_file_references {
+ let processed = process_references(
+ ctx,
+ builder,
+ &mut visited_modules_set,
+ &enum_module_def,
+ &variant_hir_name,
+ references,
+ );
+ processed.into_iter().for_each(|(path, node, import)| {
+ apply_references(ctx.config.insert_use, path, node, import)
+ });
+ }
+
+ let indent = enum_ast.indent_level();
+ let generic_params = enum_ast
+ .generic_param_list()
+ .and_then(|known_generics| extract_generic_params(&known_generics, &field_list));
+ let generics = generic_params.as_ref().map(|generics| generics.clone_for_update());
+ let def =
+ create_struct_def(variant_name.clone(), &variant, &field_list, generics, &enum_ast);
+ def.reindent_to(indent);
+
+ let start_offset = &variant.parent_enum().syntax().clone();
+ ted::insert_all_raw(
+ ted::Position::before(start_offset),
+ vec![
+ def.syntax().clone().into(),
+ make::tokens::whitespace(&format!("\n\n{}", indent)).into(),
+ ],
+ );
+
+ update_variant(&variant, generic_params.map(|g| g.clone_for_update()));
+ },
+ )
+}
+
+fn extract_field_list_if_applicable(
+ variant: &ast::Variant,
+) -> Option<Either<ast::RecordFieldList, ast::TupleFieldList>> {
+ match variant.kind() {
+ ast::StructKind::Record(field_list) if field_list.fields().next().is_some() => {
+ Some(Either::Left(field_list))
+ }
+ ast::StructKind::Tuple(field_list) if field_list.fields().count() > 1 => {
+ Some(Either::Right(field_list))
+ }
+ _ => None,
+ }
+}
+
+fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &Variant) -> bool {
+ variant
+ .parent_enum(db)
+ .module(db)
+ .scope(db, None)
+ .into_iter()
+ .filter(|(_, def)| match def {
+ // only check type-namespace
+ hir::ScopeDef::ModuleDef(def) => matches!(
+ def,
+ ModuleDef::Module(_)
+ | ModuleDef::Adt(_)
+ | ModuleDef::Variant(_)
+ | ModuleDef::Trait(_)
+ | ModuleDef::TypeAlias(_)
+ | ModuleDef::BuiltinType(_)
+ ),
+ _ => false,
+ })
+ .any(|(name, _)| name.to_string() == variant_name.to_string())
+}
+
+fn extract_generic_params(
+ known_generics: &ast::GenericParamList,
+ field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
+) -> Option<ast::GenericParamList> {
+ let mut generics = known_generics.generic_params().map(|param| (param, false)).collect_vec();
+
+ let tagged_one = match field_list {
+ Either::Left(field_list) => field_list
+ .fields()
+ .filter_map(|f| f.ty())
+ .fold(false, |tagged, ty| tag_generics_in_variant(&ty, &mut generics) || tagged),
+ Either::Right(field_list) => field_list
+ .fields()
+ .filter_map(|f| f.ty())
+ .fold(false, |tagged, ty| tag_generics_in_variant(&ty, &mut generics) || tagged),
+ };
+
+ let generics = generics.into_iter().filter_map(|(param, tag)| tag.then(|| param));
+ tagged_one.then(|| make::generic_param_list(generics))
+}
+
+fn tag_generics_in_variant(ty: &ast::Type, generics: &mut [(ast::GenericParam, bool)]) -> bool {
+ let mut tagged_one = false;
+
+ for token in ty.syntax().descendants_with_tokens().filter_map(SyntaxElement::into_token) {
+ for (param, tag) in generics.iter_mut().filter(|(_, tag)| !tag) {
+ match param {
+ ast::GenericParam::LifetimeParam(lt)
+ if matches!(token.kind(), T![lifetime_ident]) =>
+ {
+ if let Some(lt) = lt.lifetime() {
+ if lt.text().as_str() == token.text() {
+ *tag = true;
+ tagged_one = true;
+ break;
+ }
+ }
+ }
+ param if matches!(token.kind(), T![ident]) => {
+ if match param {
+ ast::GenericParam::ConstParam(konst) => konst
+ .name()
+ .map(|name| name.text().as_str() == token.text())
+ .unwrap_or_default(),
+ ast::GenericParam::TypeParam(ty) => ty
+ .name()
+ .map(|name| name.text().as_str() == token.text())
+ .unwrap_or_default(),
+ ast::GenericParam::LifetimeParam(lt) => lt
+ .lifetime()
+ .map(|lt| lt.text().as_str() == token.text())
+ .unwrap_or_default(),
+ } {
+ *tag = true;
+ tagged_one = true;
+ break;
+ }
+ }
+ _ => (),
+ }
+ }
+ }
+
+ tagged_one
+}
+
+fn create_struct_def(
+ variant_name: ast::Name,
+ variant: &ast::Variant,
+ field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
+ generics: Option<ast::GenericParamList>,
+ enum_: &ast::Enum,
+) -> ast::Struct {
+ let enum_vis = enum_.visibility();
+
+ let insert_vis = |node: &'_ SyntaxNode, vis: &'_ SyntaxNode| {
+ let vis = vis.clone_for_update();
+ ted::insert(ted::Position::before(node), vis);
+ };
+
+ // for fields without any existing visibility, use visibility of enum
+ let field_list: ast::FieldList = match field_list {
+ Either::Left(field_list) => {
+ let field_list = field_list.clone_for_update();
+
+ if let Some(vis) = &enum_vis {
+ field_list
+ .fields()
+ .filter(|field| field.visibility().is_none())
+ .filter_map(|field| field.name())
+ .for_each(|it| insert_vis(it.syntax(), vis.syntax()));
+ }
+
+ field_list.into()
+ }
+ Either::Right(field_list) => {
+ let field_list = field_list.clone_for_update();
+
+ if let Some(vis) = &enum_vis {
+ field_list
+ .fields()
+ .filter(|field| field.visibility().is_none())
+ .filter_map(|field| field.ty())
+ .for_each(|it| insert_vis(it.syntax(), vis.syntax()));
+ }
+
+ field_list.into()
+ }
+ };
+
+ field_list.reindent_to(IndentLevel::single());
+
+ let strukt = make::struct_(enum_vis, variant_name, generics, field_list).clone_for_update();
+
+ // FIXME: Consider making this an actual function somewhere (like in `AttrsOwnerEdit`) after some deliberation
+ let attrs_and_docs = |node: &SyntaxNode| {
+ let mut select_next_ws = false;
+ node.children_with_tokens().filter(move |child| {
+ let accept = match child.kind() {
+ ATTR | COMMENT => {
+ select_next_ws = true;
+ return true;
+ }
+ WHITESPACE if select_next_ws => true,
+ _ => false,
+ };
+ select_next_ws = false;
+
+ accept
+ })
+ };
+
+ // copy attributes & comments from variant
+ let variant_attrs = attrs_and_docs(variant.syntax())
+ .map(|tok| match tok.kind() {
+ WHITESPACE => make::tokens::single_newline().into(),
+ _ => tok,
+ })
+ .collect();
+ ted::insert_all(ted::Position::first_child_of(strukt.syntax()), variant_attrs);
+
+ // copy attributes from enum
+ ted::insert_all(
+ ted::Position::first_child_of(strukt.syntax()),
+ enum_.attrs().map(|it| it.syntax().clone_for_update().into()).collect(),
+ );
+ strukt
+}
+
+fn update_variant(variant: &ast::Variant, generics: Option<ast::GenericParamList>) -> Option<()> {
+ let name = variant.name()?;
+ let ty = generics
+ .filter(|generics| generics.generic_params().count() > 0)
+ .map(|generics| {
+ let mut generic_str = String::with_capacity(8);
+
+ for (p, more) in generics.generic_params().with_position().map(|p| match p {
+ Position::First(p) | Position::Middle(p) => (p, true),
+ Position::Last(p) | Position::Only(p) => (p, false),
+ }) {
+ match p {
+ ast::GenericParam::ConstParam(konst) => {
+ if let Some(name) = konst.name() {
+ generic_str.push_str(name.text().as_str());
+ }
+ }
+ ast::GenericParam::LifetimeParam(lt) => {
+ if let Some(lt) = lt.lifetime() {
+ generic_str.push_str(lt.text().as_str());
+ }
+ }
+ ast::GenericParam::TypeParam(ty) => {
+ if let Some(name) = ty.name() {
+ generic_str.push_str(name.text().as_str());
+ }
+ }
+ }
+ if more {
+ generic_str.push_str(", ");
+ }
+ }
+
+ make::ty(&format!("{}<{}>", &name.text(), &generic_str))
+ })
+ .unwrap_or_else(|| make::ty(&name.text()));
+
+ let tuple_field = make::tuple_field(None, ty);
+ let replacement = make::variant(
+ name,
+ Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))),
+ )
+ .clone_for_update();
+ ted::replace(variant.syntax(), replacement.syntax());
+ Some(())
+}
+
+fn apply_references(
+ insert_use_cfg: InsertUseConfig,
+ segment: ast::PathSegment,
+ node: SyntaxNode,
+ import: Option<(ImportScope, hir::ModPath)>,
+) {
+ if let Some((scope, path)) = import {
+ insert_use(&scope, mod_path_to_ast(&path), &insert_use_cfg);
+ }
+ // deep clone to prevent cycle
+ let path = make::path_from_segments(iter::once(segment.clone_subtree()), false);
+ ted::insert_raw(ted::Position::before(segment.syntax()), path.clone_for_update().syntax());
+ ted::insert_raw(ted::Position::before(segment.syntax()), make::token(T!['(']));
+ ted::insert_raw(ted::Position::after(&node), make::token(T![')']));
+}
+
+fn process_references(
+ ctx: &AssistContext<'_>,
+ builder: &mut AssistBuilder,
+ visited_modules: &mut FxHashSet<Module>,
+ enum_module_def: &ModuleDef,
+ variant_hir_name: &Name,
+ refs: Vec<FileReference>,
+) -> Vec<(ast::PathSegment, SyntaxNode, Option<(ImportScope, hir::ModPath)>)> {
+ // we have to recollect here eagerly as we are about to edit the tree we need to calculate the changes
+ // and corresponding nodes up front
+ refs.into_iter()
+ .flat_map(|reference| {
+ let (segment, scope_node, module) = reference_to_node(&ctx.sema, reference)?;
+ let segment = builder.make_mut(segment);
+ let scope_node = builder.make_syntax_mut(scope_node);
+ if !visited_modules.contains(&module) {
+ let mod_path = module.find_use_path_prefixed(
+ ctx.sema.db,
+ *enum_module_def,
+ ctx.config.insert_use.prefix_kind,
+ );
+ if let Some(mut mod_path) = mod_path {
+ mod_path.pop_segment();
+ mod_path.push_segment(variant_hir_name.clone());
+ let scope = ImportScope::find_insert_use_container(&scope_node, &ctx.sema)?;
+ visited_modules.insert(module);
+ return Some((segment, scope_node, Some((scope, mod_path))));
+ }
+ }
+ Some((segment, scope_node, None))
+ })
+ .collect()
+}
+
+fn reference_to_node(
+ sema: &hir::Semantics<'_, RootDatabase>,
+ reference: FileReference,
+) -> Option<(ast::PathSegment, SyntaxNode, hir::Module)> {
+ let segment =
+ reference.name.as_name_ref()?.syntax().parent().and_then(ast::PathSegment::cast)?;
+ let parent = segment.parent_path().syntax().parent()?;
+ let expr_or_pat = match_ast! {
+ match parent {
+ ast::PathExpr(_it) => parent.parent()?,
+ ast::RecordExpr(_it) => parent,
+ ast::TupleStructPat(_it) => parent,
+ ast::RecordPat(_it) => parent,
+ _ => return None,
+ }
+ };
+ let module = sema.scope(&expr_or_pat)?.module();
+ Some((segment, expr_or_pat, module))
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn test_extract_struct_several_fields_tuple() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ "enum A { $0One(u32, u32) }",
+ r#"struct One(u32, u32);
+
+enum A { One(One) }"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_struct_several_fields_named() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ "enum A { $0One { foo: u32, bar: u32 } }",
+ r#"struct One{ foo: u32, bar: u32 }
+
+enum A { One(One) }"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_struct_one_field_named() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ "enum A { $0One { foo: u32 } }",
+ r#"struct One{ foo: u32 }
+
+enum A { One(One) }"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_struct_carries_over_generics() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ r"enum En<T> { Var { a: T$0 } }",
+ r#"struct Var<T>{ a: T }
+
+enum En<T> { Var(Var<T>) }"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_struct_carries_over_attributes() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ r#"#[derive(Debug)]
+#[derive(Clone)]
+enum Enum { Variant{ field: u32$0 } }"#,
+ r#"#[derive(Debug)]#[derive(Clone)] struct Variant{ field: u32 }
+
+#[derive(Debug)]
+#[derive(Clone)]
+enum Enum { Variant(Variant) }"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_struct_indent_to_parent_enum() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ r#"
+enum Enum {
+ Variant {
+ field: u32$0
+ }
+}"#,
+ r#"
+struct Variant{
+ field: u32
+}
+
+enum Enum {
+ Variant(Variant)
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_struct_indent_to_parent_enum_in_mod() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ r#"
+mod indenting {
+ enum Enum {
+ Variant {
+ field: u32$0
+ }
+ }
+}"#,
+ r#"
+mod indenting {
+ struct Variant{
+ field: u32
+ }
+
+ enum Enum {
+ Variant(Variant)
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_struct_keep_comments_and_attrs_one_field_named() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ r#"
+enum A {
+ $0One {
+ // leading comment
+ /// doc comment
+ #[an_attr]
+ foo: u32
+ // trailing comment
+ }
+}"#,
+ r#"
+struct One{
+ // leading comment
+ /// doc comment
+ #[an_attr]
+ foo: u32
+ // trailing comment
+}
+
+enum A {
+ One(One)
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_struct_keep_comments_and_attrs_several_fields_named() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ r#"
+enum A {
+ $0One {
+ // comment
+ /// doc
+ #[attr]
+ foo: u32,
+ // comment
+ #[attr]
+ /// doc
+ bar: u32
+ }
+}"#,
+ r#"
+struct One{
+ // comment
+ /// doc
+ #[attr]
+ foo: u32,
+ // comment
+ #[attr]
+ /// doc
+ bar: u32
+}
+
+enum A {
+ One(One)
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_struct_keep_comments_and_attrs_several_fields_tuple() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ "enum A { $0One(/* comment */ #[attr] u32, /* another */ u32 /* tail */) }",
+ r#"
+struct One(/* comment */ #[attr] u32, /* another */ u32 /* tail */);
+
+enum A { One(One) }"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_struct_keep_comments_and_attrs_on_variant_struct() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ r#"
+enum A {
+ /* comment */
+ // other
+ /// comment
+ #[attr]
+ $0One {
+ a: u32
+ }
+}"#,
+ r#"
+/* comment */
+// other
+/// comment
+#[attr]
+struct One{
+ a: u32
+}
+
+enum A {
+ One(One)
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_struct_keep_comments_and_attrs_on_variant_tuple() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ r#"
+enum A {
+ /* comment */
+ // other
+ /// comment
+ #[attr]
+ $0One(u32, u32)
+}"#,
+ r#"
+/* comment */
+// other
+/// comment
+#[attr]
+struct One(u32, u32);
+
+enum A {
+ One(One)
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_struct_keep_existing_visibility_named() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ "enum A { $0One{ a: u32, pub(crate) b: u32, pub(super) c: u32, d: u32 } }",
+ r#"
+struct One{ a: u32, pub(crate) b: u32, pub(super) c: u32, d: u32 }
+
+enum A { One(One) }"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_struct_keep_existing_visibility_tuple() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ "enum A { $0One(u32, pub(crate) u32, pub(super) u32, u32) }",
+ r#"
+struct One(u32, pub(crate) u32, pub(super) u32, u32);
+
+enum A { One(One) }"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_enum_variant_name_value_namespace() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ r#"const One: () = ();
+enum A { $0One(u32, u32) }"#,
+ r#"const One: () = ();
+struct One(u32, u32);
+
+enum A { One(One) }"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_struct_no_visibility() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ "enum A { $0One(u32, u32) }",
+ r#"
+struct One(u32, u32);
+
+enum A { One(One) }"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_struct_pub_visibility() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ "pub enum A { $0One(u32, u32) }",
+ r#"
+pub struct One(pub u32, pub u32);
+
+pub enum A { One(One) }"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_struct_pub_in_mod_visibility() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ "pub(in something) enum A { $0One{ a: u32, b: u32 } }",
+ r#"
+pub(in something) struct One{ pub(in something) a: u32, pub(in something) b: u32 }
+
+pub(in something) enum A { One(One) }"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_struct_pub_crate_visibility() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ "pub(crate) enum A { $0One{ a: u32, b: u32, c: u32 } }",
+ r#"
+pub(crate) struct One{ pub(crate) a: u32, pub(crate) b: u32, pub(crate) c: u32 }
+
+pub(crate) enum A { One(One) }"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_struct_with_complex_imports() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ r#"mod my_mod {
+ fn another_fn() {
+ let m = my_other_mod::MyEnum::MyField(1, 1);
+ }
+
+ pub mod my_other_mod {
+ fn another_fn() {
+ let m = MyEnum::MyField(1, 1);
+ }
+
+ pub enum MyEnum {
+ $0MyField(u8, u8),
+ }
+ }
+}
+
+fn another_fn() {
+ let m = my_mod::my_other_mod::MyEnum::MyField(1, 1);
+}"#,
+ r#"use my_mod::my_other_mod::MyField;
+
+mod my_mod {
+ use self::my_other_mod::MyField;
+
+ fn another_fn() {
+ let m = my_other_mod::MyEnum::MyField(MyField(1, 1));
+ }
+
+ pub mod my_other_mod {
+ fn another_fn() {
+ let m = MyEnum::MyField(MyField(1, 1));
+ }
+
+ pub struct MyField(pub u8, pub u8);
+
+ pub enum MyEnum {
+ MyField(MyField),
+ }
+ }
+}
+
+fn another_fn() {
+ let m = my_mod::my_other_mod::MyEnum::MyField(MyField(1, 1));
+}"#,
+ );
+ }
+
+ #[test]
+ fn extract_record_fix_references() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ r#"
+enum E {
+ $0V { i: i32, j: i32 }
+}
+
+fn f() {
+ let E::V { i, j } = E::V { i: 9, j: 2 };
+}
+"#,
+ r#"
+struct V{ i: i32, j: i32 }
+
+enum E {
+ V(V)
+}
+
+fn f() {
+ let E::V(V { i, j }) = E::V(V { i: 9, j: 2 });
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn extract_record_fix_references2() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ r#"
+enum E {
+ $0V(i32, i32)
+}
+
+fn f() {
+ let E::V(i, j) = E::V(9, 2);
+}
+"#,
+ r#"
+struct V(i32, i32);
+
+enum E {
+ V(V)
+}
+
+fn f() {
+ let E::V(V(i, j)) = E::V(V(9, 2));
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_several_files() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ r#"
+//- /main.rs
+enum E {
+ $0V(i32, i32)
+}
+mod foo;
+
+//- /foo.rs
+use crate::E;
+fn f() {
+ let e = E::V(9, 2);
+}
+"#,
+ r#"
+//- /main.rs
+struct V(i32, i32);
+
+enum E {
+ V(V)
+}
+mod foo;
+
+//- /foo.rs
+use crate::{E, V};
+fn f() {
+ let e = E::V(V(9, 2));
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_several_files_record() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ r#"
+//- /main.rs
+enum E {
+ $0V { i: i32, j: i32 }
+}
+mod foo;
+
+//- /foo.rs
+use crate::E;
+fn f() {
+ let e = E::V { i: 9, j: 2 };
+}
+"#,
+ r#"
+//- /main.rs
+struct V{ i: i32, j: i32 }
+
+enum E {
+ V(V)
+}
+mod foo;
+
+//- /foo.rs
+use crate::{E, V};
+fn f() {
+ let e = E::V(V { i: 9, j: 2 });
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_extract_struct_record_nested_call_exp() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ r#"
+enum A { $0One { a: u32, b: u32 } }
+
+struct B(A);
+
+fn foo() {
+ let _ = B(A::One { a: 1, b: 2 });
+}
+"#,
+ r#"
+struct One{ a: u32, b: u32 }
+
+enum A { One(One) }
+
+struct B(A);
+
+fn foo() {
+ let _ = B(A::One(One { a: 1, b: 2 }));
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_enum_not_applicable_for_element_with_no_fields() {
+ check_assist_not_applicable(extract_struct_from_enum_variant, r#"enum A { $0One }"#);
+ }
+
+ #[test]
+ fn test_extract_enum_not_applicable_if_struct_exists() {
+ cov_mark::check!(test_extract_enum_not_applicable_if_struct_exists);
+ check_assist_not_applicable(
+ extract_struct_from_enum_variant,
+ r#"
+struct One;
+enum A { $0One(u8, u32) }
+"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_not_applicable_one_field() {
+ check_assist_not_applicable(extract_struct_from_enum_variant, r"enum A { $0One(u32) }");
+ }
+
+ #[test]
+ fn test_extract_not_applicable_no_field_tuple() {
+ check_assist_not_applicable(extract_struct_from_enum_variant, r"enum A { $0None() }");
+ }
+
+ #[test]
+ fn test_extract_not_applicable_no_field_named() {
+ check_assist_not_applicable(extract_struct_from_enum_variant, r"enum A { $0None {} }");
+ }
+
+ #[test]
+ fn test_extract_struct_only_copies_needed_generics() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ r#"
+enum X<'a, 'b, 'x> {
+ $0A { a: &'a &'x mut () },
+ B { b: &'b () },
+ C { c: () },
+}
+"#,
+ r#"
+struct A<'a, 'x>{ a: &'a &'x mut () }
+
+enum X<'a, 'b, 'x> {
+ A(A<'a, 'x>),
+ B { b: &'b () },
+ C { c: () },
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_struct_with_liftime_type_const() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ r#"
+enum X<'b, T, V, const C: usize> {
+ $0A { a: T, b: X<'b>, c: [u8; C] },
+ D { d: V },
+}
+"#,
+ r#"
+struct A<'b, T, const C: usize>{ a: T, b: X<'b>, c: [u8; C] }
+
+enum X<'b, T, V, const C: usize> {
+ A(A<'b, T, C>),
+ D { d: V },
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_struct_without_generics() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ r#"
+enum X<'a, 'b> {
+ A { a: &'a () },
+ B { b: &'b () },
+ $0C { c: () },
+}
+"#,
+ r#"
+struct C{ c: () }
+
+enum X<'a, 'b> {
+ A { a: &'a () },
+ B { b: &'b () },
+ C(C),
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_struct_keeps_trait_bounds() {
+ check_assist(
+ extract_struct_from_enum_variant,
+ r#"
+enum En<T: TraitT, V: TraitV> {
+ $0A { a: T },
+ B { b: V },
+}
+"#,
+ r#"
+struct A<T: TraitT>{ a: T }
+
+enum En<T: TraitT, V: TraitV> {
+ A(A<T>),
+ B { b: V },
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs
new file mode 100644
index 000000000..af584cdb4
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs
@@ -0,0 +1,360 @@
+use either::Either;
+use ide_db::syntax_helpers::node_ext::walk_ty;
+use itertools::Itertools;
+use syntax::{
+ ast::{self, edit::IndentLevel, AstNode, HasGenericParams, HasName},
+ match_ast,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: extract_type_alias
+//
+// Extracts the selected type as a type alias.
+//
+// ```
+// struct S {
+// field: $0(u8, u8, u8)$0,
+// }
+// ```
+// ->
+// ```
+// type $0Type = (u8, u8, u8);
+//
+// struct S {
+// field: Type,
+// }
+// ```
+pub(crate) fn extract_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ if ctx.has_empty_selection() {
+ return None;
+ }
+
+ let ty = ctx.find_node_at_range::<ast::Type>()?;
+ let item = ty.syntax().ancestors().find_map(ast::Item::cast)?;
+ let assoc_owner = item.syntax().ancestors().nth(2).and_then(|it| {
+ match_ast! {
+ match it {
+ ast::Trait(tr) => Some(Either::Left(tr)),
+ ast::Impl(impl_) => Some(Either::Right(impl_)),
+ _ => None,
+ }
+ }
+ });
+ let node = assoc_owner.as_ref().map_or_else(
+ || item.syntax(),
+ |impl_| impl_.as_ref().either(AstNode::syntax, AstNode::syntax),
+ );
+ let insert_pos = node.text_range().start();
+ let target = ty.syntax().text_range();
+
+ acc.add(
+ AssistId("extract_type_alias", AssistKind::RefactorExtract),
+ "Extract type as type alias",
+ target,
+ |builder| {
+ let mut known_generics = match item.generic_param_list() {
+ Some(it) => it.generic_params().collect(),
+ None => Vec::new(),
+ };
+ if let Some(it) = assoc_owner.as_ref().and_then(|it| match it {
+ Either::Left(it) => it.generic_param_list(),
+ Either::Right(it) => it.generic_param_list(),
+ }) {
+ known_generics.extend(it.generic_params());
+ }
+ let generics = collect_used_generics(&ty, &known_generics);
+
+ let replacement = if !generics.is_empty() {
+ format!(
+ "Type<{}>",
+ generics.iter().format_with(", ", |generic, f| {
+ match generic {
+ ast::GenericParam::ConstParam(cp) => f(&cp.name().unwrap()),
+ ast::GenericParam::LifetimeParam(lp) => f(&lp.lifetime().unwrap()),
+ ast::GenericParam::TypeParam(tp) => f(&tp.name().unwrap()),
+ }
+ })
+ )
+ } else {
+ String::from("Type")
+ };
+ builder.replace(target, replacement);
+
+ let indent = IndentLevel::from_node(node);
+ let generics = if !generics.is_empty() {
+ format!("<{}>", generics.iter().format(", "))
+ } else {
+ String::new()
+ };
+ match ctx.config.snippet_cap {
+ Some(cap) => {
+ builder.insert_snippet(
+ cap,
+ insert_pos,
+ format!("type $0Type{} = {};\n\n{}", generics, ty, indent),
+ );
+ }
+ None => {
+ builder.insert(
+ insert_pos,
+ format!("type Type{} = {};\n\n{}", generics, ty, indent),
+ );
+ }
+ }
+ },
+ )
+}
+
+fn collect_used_generics<'gp>(
+ ty: &ast::Type,
+ known_generics: &'gp [ast::GenericParam],
+) -> Vec<&'gp ast::GenericParam> {
+ // can't use a closure -> closure here cause lifetime inference fails for that
+ fn find_lifetime(text: &str) -> impl Fn(&&ast::GenericParam) -> bool + '_ {
+ move |gp: &&ast::GenericParam| match gp {
+ ast::GenericParam::LifetimeParam(lp) => {
+ lp.lifetime().map_or(false, |lt| lt.text() == text)
+ }
+ _ => false,
+ }
+ }
+
+ let mut generics = Vec::new();
+ walk_ty(ty, &mut |ty| match ty {
+ ast::Type::PathType(ty) => {
+ if let Some(path) = ty.path() {
+ if let Some(name_ref) = path.as_single_name_ref() {
+ if let Some(param) = known_generics.iter().find(|gp| {
+ match gp {
+ ast::GenericParam::ConstParam(cp) => cp.name(),
+ ast::GenericParam::TypeParam(tp) => tp.name(),
+ _ => None,
+ }
+ .map_or(false, |n| n.text() == name_ref.text())
+ }) {
+ generics.push(param);
+ }
+ }
+ generics.extend(
+ path.segments()
+ .filter_map(|seg| seg.generic_arg_list())
+ .flat_map(|it| it.generic_args())
+ .filter_map(|it| match it {
+ ast::GenericArg::LifetimeArg(lt) => {
+ let lt = lt.lifetime()?;
+ known_generics.iter().find(find_lifetime(&lt.text()))
+ }
+ _ => None,
+ }),
+ );
+ }
+ }
+ ast::Type::ImplTraitType(impl_ty) => {
+ if let Some(it) = impl_ty.type_bound_list() {
+ generics.extend(
+ it.bounds()
+ .filter_map(|it| it.lifetime())
+ .filter_map(|lt| known_generics.iter().find(find_lifetime(&lt.text()))),
+ );
+ }
+ }
+ ast::Type::DynTraitType(dyn_ty) => {
+ if let Some(it) = dyn_ty.type_bound_list() {
+ generics.extend(
+ it.bounds()
+ .filter_map(|it| it.lifetime())
+ .filter_map(|lt| known_generics.iter().find(find_lifetime(&lt.text()))),
+ );
+ }
+ }
+ ast::Type::RefType(ref_) => generics.extend(
+ ref_.lifetime().and_then(|lt| known_generics.iter().find(find_lifetime(&lt.text()))),
+ ),
+ _ => (),
+ });
+ // stable resort to lifetime, type, const
+ generics.sort_by_key(|gp| match gp {
+ ast::GenericParam::ConstParam(_) => 2,
+ ast::GenericParam::LifetimeParam(_) => 0,
+ ast::GenericParam::TypeParam(_) => 1,
+ });
+ generics
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn test_not_applicable_without_selection() {
+ check_assist_not_applicable(
+ extract_type_alias,
+ r"
+struct S {
+ field: $0(u8, u8, u8),
+}
+ ",
+ );
+ }
+
+ #[test]
+ fn test_simple_types() {
+ check_assist(
+ extract_type_alias,
+ r"
+struct S {
+ field: $0u8$0,
+}
+ ",
+ r#"
+type $0Type = u8;
+
+struct S {
+ field: Type,
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn test_generic_type_arg() {
+ check_assist(
+ extract_type_alias,
+ r"
+fn generic<T>() {}
+
+fn f() {
+ generic::<$0()$0>();
+}
+ ",
+ r#"
+fn generic<T>() {}
+
+type $0Type = ();
+
+fn f() {
+ generic::<Type>();
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn test_inner_type_arg() {
+ check_assist(
+ extract_type_alias,
+ r"
+struct Vec<T> {}
+struct S {
+ v: Vec<Vec<$0Vec<u8>$0>>,
+}
+ ",
+ r#"
+struct Vec<T> {}
+type $0Type = Vec<u8>;
+
+struct S {
+ v: Vec<Vec<Type>>,
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn test_extract_inner_type() {
+ check_assist(
+ extract_type_alias,
+ r"
+struct S {
+ field: ($0u8$0,),
+}
+ ",
+ r#"
+type $0Type = u8;
+
+struct S {
+ field: (Type,),
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn extract_from_impl_or_trait() {
+ // When invoked in an impl/trait, extracted type alias should be placed next to the
+ // impl/trait, not inside.
+ check_assist(
+ extract_type_alias,
+ r#"
+impl S {
+ fn f() -> $0(u8, u8)$0 {}
+}
+ "#,
+ r#"
+type $0Type = (u8, u8);
+
+impl S {
+ fn f() -> Type {}
+}
+ "#,
+ );
+ check_assist(
+ extract_type_alias,
+ r#"
+trait Tr {
+ fn f() -> $0(u8, u8)$0 {}
+}
+ "#,
+ r#"
+type $0Type = (u8, u8);
+
+trait Tr {
+ fn f() -> Type {}
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn indentation() {
+ check_assist(
+ extract_type_alias,
+ r#"
+mod m {
+ fn f() -> $0u8$0 {}
+}
+ "#,
+ r#"
+mod m {
+ type $0Type = u8;
+
+ fn f() -> Type {}
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn generics() {
+ check_assist(
+ extract_type_alias,
+ r#"
+struct Struct<const C: usize>;
+impl<'outer, Outer, const OUTER: usize> () {
+ fn func<'inner, Inner, const INNER: usize>(_: $0&(Struct<INNER>, Struct<OUTER>, Outer, &'inner (), Inner, &'outer ())$0) {}
+}
+"#,
+ r#"
+struct Struct<const C: usize>;
+type $0Type<'inner, 'outer, Outer, Inner, const INNER: usize, const OUTER: usize> = &(Struct<INNER>, Struct<OUTER>, Outer, &'inner (), Inner, &'outer ());
+
+impl<'outer, Outer, const OUTER: usize> () {
+ fn func<'inner, Inner, const INNER: usize>(_: Type<'inner, 'outer, Outer, Inner, INNER, OUTER>) {}
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs
new file mode 100644
index 000000000..3596b6f82
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs
@@ -0,0 +1,1279 @@
+use stdx::format_to;
+use syntax::{
+ ast::{self, AstNode},
+ NodeOrToken,
+ SyntaxKind::{
+ BLOCK_EXPR, BREAK_EXPR, CLOSURE_EXPR, COMMENT, LOOP_EXPR, MATCH_ARM, MATCH_GUARD,
+ PATH_EXPR, RETURN_EXPR,
+ },
+ SyntaxNode,
+};
+
+use crate::{utils::suggest_name, AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: extract_variable
+//
+// Extracts subexpression into a variable.
+//
+// ```
+// fn main() {
+// $0(1 + 2)$0 * 4;
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// let $0var_name = (1 + 2);
+// var_name * 4;
+// }
+// ```
+pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ if ctx.has_empty_selection() {
+ return None;
+ }
+
+ let node = match ctx.covering_element() {
+ NodeOrToken::Node(it) => it,
+ NodeOrToken::Token(it) if it.kind() == COMMENT => {
+ cov_mark::hit!(extract_var_in_comment_is_not_applicable);
+ return None;
+ }
+ NodeOrToken::Token(it) => it.parent()?,
+ };
+ let node = node.ancestors().take_while(|anc| anc.text_range() == node.text_range()).last()?;
+ let to_extract = node
+ .descendants()
+ .take_while(|it| ctx.selection_trimmed().contains_range(it.text_range()))
+ .find_map(valid_target_expr)?;
+
+ if let Some(ty_info) = ctx.sema.type_of_expr(&to_extract) {
+ if ty_info.adjusted().is_unit() {
+ return None;
+ }
+ }
+
+ let reference_modifier = match get_receiver_type(ctx, &to_extract) {
+ Some(receiver_type) if receiver_type.is_mutable_reference() => "&mut ",
+ Some(receiver_type) if receiver_type.is_reference() => "&",
+ _ => "",
+ };
+
+ let parent_ref_expr = to_extract.syntax().parent().and_then(ast::RefExpr::cast);
+ let var_modifier = match parent_ref_expr {
+ Some(expr) if expr.mut_token().is_some() => "mut ",
+ _ => "",
+ };
+
+ let anchor = Anchor::from(&to_extract)?;
+ let indent = anchor.syntax().prev_sibling_or_token()?.as_token()?.clone();
+ let target = to_extract.syntax().text_range();
+ acc.add(
+ AssistId("extract_variable", AssistKind::RefactorExtract),
+ "Extract into variable",
+ target,
+ move |edit| {
+ let field_shorthand =
+ match to_extract.syntax().parent().and_then(ast::RecordExprField::cast) {
+ Some(field) => field.name_ref(),
+ None => None,
+ };
+
+ let mut buf = String::new();
+
+ let var_name = match &field_shorthand {
+ Some(it) => it.to_string(),
+ None => suggest_name::for_variable(&to_extract, &ctx.sema),
+ };
+ let expr_range = match &field_shorthand {
+ Some(it) => it.syntax().text_range().cover(to_extract.syntax().text_range()),
+ None => to_extract.syntax().text_range(),
+ };
+
+ match anchor {
+ Anchor::Before(_) | Anchor::Replace(_) => {
+ format_to!(buf, "let {}{} = {}", var_modifier, var_name, reference_modifier)
+ }
+ Anchor::WrapInBlock(_) => {
+ format_to!(buf, "{{ let {} = {}", var_name, reference_modifier)
+ }
+ };
+ format_to!(buf, "{}", to_extract.syntax());
+
+ if let Anchor::Replace(stmt) = anchor {
+ cov_mark::hit!(test_extract_var_expr_stmt);
+ if stmt.semicolon_token().is_none() {
+ buf.push(';');
+ }
+ match ctx.config.snippet_cap {
+ Some(cap) => {
+ let snip = buf.replace(
+ &format!("let {}{}", var_modifier, var_name),
+ &format!("let {}$0{}", var_modifier, var_name),
+ );
+ edit.replace_snippet(cap, expr_range, snip)
+ }
+ None => edit.replace(expr_range, buf),
+ }
+ return;
+ }
+
+ buf.push(';');
+
+ // We want to maintain the indent level,
+ // but we do not want to duplicate possible
+ // extra newlines in the indent block
+ let text = indent.text();
+ if text.starts_with('\n') {
+ buf.push('\n');
+ buf.push_str(text.trim_start_matches('\n'));
+ } else {
+ buf.push_str(text);
+ }
+
+ edit.replace(expr_range, var_name.clone());
+ let offset = anchor.syntax().text_range().start();
+ match ctx.config.snippet_cap {
+ Some(cap) => {
+ let snip = buf.replace(
+ &format!("let {}{}", var_modifier, var_name),
+ &format!("let {}$0{}", var_modifier, var_name),
+ );
+ edit.insert_snippet(cap, offset, snip)
+ }
+ None => edit.insert(offset, buf),
+ }
+
+ if let Anchor::WrapInBlock(_) = anchor {
+ edit.insert(anchor.syntax().text_range().end(), " }");
+ }
+ },
+ )
+}
+
+/// Check whether the node is a valid expression which can be extracted to a variable.
+/// In general that's true for any expression, but in some cases that would produce invalid code.
+fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> {
+ match node.kind() {
+ PATH_EXPR | LOOP_EXPR => None,
+ BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()),
+ RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()),
+ BLOCK_EXPR => {
+ ast::BlockExpr::cast(node).filter(|it| it.is_standalone()).map(ast::Expr::from)
+ }
+ _ => ast::Expr::cast(node),
+ }
+}
+
+fn get_receiver_type(ctx: &AssistContext<'_>, expression: &ast::Expr) -> Option<hir::Type> {
+ let receiver = get_receiver(expression.clone())?;
+ Some(ctx.sema.type_of_expr(&receiver)?.original())
+}
+
+/// In the expression `a.b.c.x()`, find `a`
+fn get_receiver(expression: ast::Expr) -> Option<ast::Expr> {
+ match expression {
+ ast::Expr::FieldExpr(field) if field.expr().is_some() => {
+ let nested_expression = &field.expr()?;
+ get_receiver(nested_expression.to_owned())
+ }
+ _ => Some(expression),
+ }
+}
+
+#[derive(Debug)]
+enum Anchor {
+ Before(SyntaxNode),
+ Replace(ast::ExprStmt),
+ WrapInBlock(SyntaxNode),
+}
+
+impl Anchor {
+ fn from(to_extract: &ast::Expr) -> Option<Anchor> {
+ to_extract
+ .syntax()
+ .ancestors()
+ .take_while(|it| !ast::Item::can_cast(it.kind()) || ast::MacroCall::can_cast(it.kind()))
+ .find_map(|node| {
+ if ast::MacroCall::can_cast(node.kind()) {
+ return None;
+ }
+ if let Some(expr) =
+ node.parent().and_then(ast::StmtList::cast).and_then(|it| it.tail_expr())
+ {
+ if expr.syntax() == &node {
+ cov_mark::hit!(test_extract_var_last_expr);
+ return Some(Anchor::Before(node));
+ }
+ }
+
+ if let Some(parent) = node.parent() {
+ if parent.kind() == CLOSURE_EXPR {
+ cov_mark::hit!(test_extract_var_in_closure_no_block);
+ return Some(Anchor::WrapInBlock(node));
+ }
+ if parent.kind() == MATCH_ARM {
+ if node.kind() == MATCH_GUARD {
+ cov_mark::hit!(test_extract_var_in_match_guard);
+ } else {
+ cov_mark::hit!(test_extract_var_in_match_arm_no_block);
+ return Some(Anchor::WrapInBlock(node));
+ }
+ }
+ }
+
+ if let Some(stmt) = ast::Stmt::cast(node.clone()) {
+ if let ast::Stmt::ExprStmt(stmt) = stmt {
+ if stmt.expr().as_ref() == Some(to_extract) {
+ return Some(Anchor::Replace(stmt));
+ }
+ }
+ return Some(Anchor::Before(node));
+ }
+ None
+ })
+ }
+
+ fn syntax(&self) -> &SyntaxNode {
+ match self {
+ Anchor::Before(it) | Anchor::WrapInBlock(it) => it,
+ Anchor::Replace(stmt) => stmt.syntax(),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
+
+ use super::*;
+
+ #[test]
+ fn test_extract_var_simple() {
+ check_assist(
+ extract_variable,
+ r#"
+fn foo() {
+ foo($01 + 1$0);
+}"#,
+ r#"
+fn foo() {
+ let $0var_name = 1 + 1;
+ foo(var_name);
+}"#,
+ );
+ }
+
+ #[test]
+ fn extract_var_in_comment_is_not_applicable() {
+ cov_mark::check!(extract_var_in_comment_is_not_applicable);
+ check_assist_not_applicable(extract_variable, "fn main() { 1 + /* $0comment$0 */ 1; }");
+ }
+
+ #[test]
+ fn test_extract_var_expr_stmt() {
+ cov_mark::check!(test_extract_var_expr_stmt);
+ check_assist(
+ extract_variable,
+ r#"
+fn foo() {
+ $0 1 + 1$0;
+}"#,
+ r#"
+fn foo() {
+ let $0var_name = 1 + 1;
+}"#,
+ );
+ check_assist(
+ extract_variable,
+ r"
+fn foo() {
+ $0{ let x = 0; x }$0
+ something_else();
+}",
+ r"
+fn foo() {
+ let $0var_name = { let x = 0; x };
+ something_else();
+}",
+ );
+ }
+
+ #[test]
+ fn test_extract_var_part_of_expr_stmt() {
+ check_assist(
+ extract_variable,
+ r"
+fn foo() {
+ $01$0 + 1;
+}",
+ r"
+fn foo() {
+ let $0var_name = 1;
+ var_name + 1;
+}",
+ );
+ }
+
+ #[test]
+ fn test_extract_var_last_expr() {
+ cov_mark::check!(test_extract_var_last_expr);
+ check_assist(
+ extract_variable,
+ r#"
+fn foo() {
+ bar($01 + 1$0)
+}
+"#,
+ r#"
+fn foo() {
+ let $0var_name = 1 + 1;
+ bar(var_name)
+}
+"#,
+ );
+ check_assist(
+ extract_variable,
+ r#"
+fn foo() -> i32 {
+ $0bar(1 + 1)$0
+}
+
+fn bar(i: i32) -> i32 {
+ i
+}
+"#,
+ r#"
+fn foo() -> i32 {
+ let $0bar = bar(1 + 1);
+ bar
+}
+
+fn bar(i: i32) -> i32 {
+ i
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_extract_var_in_match_arm_no_block() {
+ cov_mark::check!(test_extract_var_in_match_arm_no_block);
+ check_assist(
+ extract_variable,
+ r#"
+fn main() {
+ let x = true;
+ let tuple = match x {
+ true => ($02 + 2$0, true)
+ _ => (0, false)
+ };
+}
+"#,
+ r#"
+fn main() {
+ let x = true;
+ let tuple = match x {
+ true => { let $0var_name = 2 + 2; (var_name, true) }
+ _ => (0, false)
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_var_in_match_arm_with_block() {
+ check_assist(
+ extract_variable,
+ r#"
+fn main() {
+ let x = true;
+ let tuple = match x {
+ true => {
+ let y = 1;
+ ($02 + y$0, true)
+ }
+ _ => (0, false)
+ };
+}
+"#,
+ r#"
+fn main() {
+ let x = true;
+ let tuple = match x {
+ true => {
+ let y = 1;
+ let $0var_name = 2 + y;
+ (var_name, true)
+ }
+ _ => (0, false)
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_var_in_match_guard() {
+ cov_mark::check!(test_extract_var_in_match_guard);
+ check_assist(
+ extract_variable,
+ r#"
+fn main() {
+ match () {
+ () if $010 > 0$0 => 1
+ _ => 2
+ };
+}
+"#,
+ r#"
+fn main() {
+ let $0var_name = 10 > 0;
+ match () {
+ () if var_name => 1
+ _ => 2
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_var_in_closure_no_block() {
+ cov_mark::check!(test_extract_var_in_closure_no_block);
+ check_assist(
+ extract_variable,
+ r#"
+fn main() {
+ let lambda = |x: u32| $0x * 2$0;
+}
+"#,
+ r#"
+fn main() {
+ let lambda = |x: u32| { let $0var_name = x * 2; var_name };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_var_in_closure_with_block() {
+ check_assist(
+ extract_variable,
+ r#"
+fn main() {
+ let lambda = |x: u32| { $0x * 2$0 };
+}
+"#,
+ r#"
+fn main() {
+ let lambda = |x: u32| { let $0var_name = x * 2; var_name };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_var_path_simple() {
+ check_assist(
+ extract_variable,
+ "
+fn main() {
+ let o = $0Some(true)$0;
+}
+",
+ "
+fn main() {
+ let $0var_name = Some(true);
+ let o = var_name;
+}
+",
+ );
+ }
+
+ #[test]
+ fn test_extract_var_path_method() {
+ check_assist(
+ extract_variable,
+ "
+fn main() {
+ let v = $0bar.foo()$0;
+}
+",
+ "
+fn main() {
+ let $0foo = bar.foo();
+ let v = foo;
+}
+",
+ );
+ }
+
+ #[test]
+ fn test_extract_var_return() {
+ check_assist(
+ extract_variable,
+ "
+fn foo() -> u32 {
+ $0return 2 + 2$0;
+}
+",
+ "
+fn foo() -> u32 {
+ let $0var_name = 2 + 2;
+ return var_name;
+}
+",
+ );
+ }
+
+ #[test]
+ fn test_extract_var_does_not_add_extra_whitespace() {
+ check_assist(
+ extract_variable,
+ "
+fn foo() -> u32 {
+
+
+ $0return 2 + 2$0;
+}
+",
+ "
+fn foo() -> u32 {
+
+
+ let $0var_name = 2 + 2;
+ return var_name;
+}
+",
+ );
+
+ check_assist(
+ extract_variable,
+ "
+fn foo() -> u32 {
+
+ $0return 2 + 2$0;
+}
+",
+ "
+fn foo() -> u32 {
+
+ let $0var_name = 2 + 2;
+ return var_name;
+}
+",
+ );
+
+ check_assist(
+ extract_variable,
+ "
+fn foo() -> u32 {
+ let foo = 1;
+
+ // bar
+
+
+ $0return 2 + 2$0;
+}
+",
+ "
+fn foo() -> u32 {
+ let foo = 1;
+
+ // bar
+
+
+ let $0var_name = 2 + 2;
+ return var_name;
+}
+",
+ );
+ }
+
+ #[test]
+ fn test_extract_var_break() {
+ check_assist(
+ extract_variable,
+ "
+fn main() {
+ let result = loop {
+ $0break 2 + 2$0;
+ };
+}
+",
+ "
+fn main() {
+ let result = loop {
+ let $0var_name = 2 + 2;
+ break var_name;
+ };
+}
+",
+ );
+ }
+
+ #[test]
+ fn test_extract_var_for_cast() {
+ check_assist(
+ extract_variable,
+ "
+fn main() {
+ let v = $00f32 as u32$0;
+}
+",
+ "
+fn main() {
+ let $0var_name = 0f32 as u32;
+ let v = var_name;
+}
+",
+ );
+ }
+
+ #[test]
+ fn extract_var_field_shorthand() {
+ check_assist(
+ extract_variable,
+ r#"
+struct S {
+ foo: i32
+}
+
+fn main() {
+ S { foo: $01 + 1$0 }
+}
+"#,
+ r#"
+struct S {
+ foo: i32
+}
+
+fn main() {
+ let $0foo = 1 + 1;
+ S { foo }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn extract_var_name_from_type() {
+ check_assist(
+ extract_variable,
+ r#"
+struct Test(i32);
+
+fn foo() -> Test {
+ $0{ Test(10) }$0
+}
+"#,
+ r#"
+struct Test(i32);
+
+fn foo() -> Test {
+ let $0test = { Test(10) };
+ test
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn extract_var_name_from_parameter() {
+ check_assist(
+ extract_variable,
+ r#"
+fn bar(test: u32, size: u32)
+
+fn foo() {
+ bar(1, $01+1$0);
+}
+"#,
+ r#"
+fn bar(test: u32, size: u32)
+
+fn foo() {
+ let $0size = 1+1;
+ bar(1, size);
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn extract_var_parameter_name_has_precedence_over_type() {
+ check_assist(
+ extract_variable,
+ r#"
+struct TextSize(u32);
+fn bar(test: u32, size: TextSize)
+
+fn foo() {
+ bar(1, $0{ TextSize(1+1) }$0);
+}
+"#,
+ r#"
+struct TextSize(u32);
+fn bar(test: u32, size: TextSize)
+
+fn foo() {
+ let $0size = { TextSize(1+1) };
+ bar(1, size);
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn extract_var_name_from_function() {
+ check_assist(
+ extract_variable,
+ r#"
+fn is_required(test: u32, size: u32) -> bool
+
+fn foo() -> bool {
+ $0is_required(1, 2)$0
+}
+"#,
+ r#"
+fn is_required(test: u32, size: u32) -> bool
+
+fn foo() -> bool {
+ let $0is_required = is_required(1, 2);
+ is_required
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn extract_var_name_from_method() {
+ check_assist(
+ extract_variable,
+ r#"
+struct S;
+impl S {
+ fn bar(&self, n: u32) -> u32 { n }
+}
+
+fn foo() -> u32 {
+ $0S.bar(1)$0
+}
+"#,
+ r#"
+struct S;
+impl S {
+ fn bar(&self, n: u32) -> u32 { n }
+}
+
+fn foo() -> u32 {
+ let $0bar = S.bar(1);
+ bar
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn extract_var_name_from_method_param() {
+ check_assist(
+ extract_variable,
+ r#"
+struct S;
+impl S {
+ fn bar(&self, n: u32, size: u32) { n }
+}
+
+fn foo() {
+ S.bar($01 + 1$0, 2)
+}
+"#,
+ r#"
+struct S;
+impl S {
+ fn bar(&self, n: u32, size: u32) { n }
+}
+
+fn foo() {
+ let $0n = 1 + 1;
+ S.bar(n, 2)
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn extract_var_name_from_ufcs_method_param() {
+ check_assist(
+ extract_variable,
+ r#"
+struct S;
+impl S {
+ fn bar(&self, n: u32, size: u32) { n }
+}
+
+fn foo() {
+ S::bar(&S, $01 + 1$0, 2)
+}
+"#,
+ r#"
+struct S;
+impl S {
+ fn bar(&self, n: u32, size: u32) { n }
+}
+
+fn foo() {
+ let $0n = 1 + 1;
+ S::bar(&S, n, 2)
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn extract_var_parameter_name_has_precedence_over_function() {
+ check_assist(
+ extract_variable,
+ r#"
+fn bar(test: u32, size: u32)
+
+fn foo() {
+ bar(1, $0symbol_size(1, 2)$0);
+}
+"#,
+ r#"
+fn bar(test: u32, size: u32)
+
+fn foo() {
+ let $0size = symbol_size(1, 2);
+ bar(1, size);
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn extract_macro_call() {
+ check_assist(
+ extract_variable,
+ r"
+struct Vec;
+macro_rules! vec {
+ () => {Vec}
+}
+fn main() {
+ let _ = $0vec![]$0;
+}
+",
+ r"
+struct Vec;
+macro_rules! vec {
+ () => {Vec}
+}
+fn main() {
+ let $0vec = vec![];
+ let _ = vec;
+}
+",
+ );
+ }
+
+ #[test]
+ fn test_extract_var_for_return_not_applicable() {
+ check_assist_not_applicable(extract_variable, "fn foo() { $0return$0; } ");
+ }
+
+ #[test]
+ fn test_extract_var_for_break_not_applicable() {
+ check_assist_not_applicable(extract_variable, "fn main() { loop { $0break$0; }; }");
+ }
+
+ #[test]
+ fn test_extract_var_unit_expr_not_applicable() {
+ check_assist_not_applicable(
+ extract_variable,
+ r#"
+fn foo() {
+ let mut i = 3;
+ $0if i >= 0 {
+ i += 1;
+ } else {
+ i -= 1;
+ }$0
+}"#,
+ );
+ }
+
+ // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic
+ #[test]
+ fn extract_var_target() {
+ check_assist_target(extract_variable, "fn foo() -> u32 { $0return 2 + 2$0; }", "2 + 2");
+
+ check_assist_target(
+ extract_variable,
+ "
+fn main() {
+ let x = true;
+ let tuple = match x {
+ true => ($02 + 2$0, true)
+ _ => (0, false)
+ };
+}
+",
+ "2 + 2",
+ );
+ }
+
+ #[test]
+ fn extract_var_no_block_body() {
+ check_assist_not_applicable(
+ extract_variable,
+ r"
+const X: usize = $0100$0;
+",
+ );
+ }
+
+ #[test]
+ fn test_extract_var_mutable_reference_parameter() {
+ check_assist(
+ extract_variable,
+ r#"
+struct S {
+ vec: Vec<u8>
+}
+
+fn foo(s: &mut S) {
+ $0s.vec$0.push(0);
+}"#,
+ r#"
+struct S {
+ vec: Vec<u8>
+}
+
+fn foo(s: &mut S) {
+ let $0vec = &mut s.vec;
+ vec.push(0);
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_var_mutable_reference_parameter_deep_nesting() {
+ check_assist(
+ extract_variable,
+ r#"
+struct Y {
+ field: X
+}
+struct X {
+ field: S
+}
+struct S {
+ vec: Vec<u8>
+}
+
+fn foo(f: &mut Y) {
+ $0f.field.field.vec$0.push(0);
+}"#,
+ r#"
+struct Y {
+ field: X
+}
+struct X {
+ field: S
+}
+struct S {
+ vec: Vec<u8>
+}
+
+fn foo(f: &mut Y) {
+ let $0vec = &mut f.field.field.vec;
+ vec.push(0);
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_var_reference_parameter() {
+ check_assist(
+ extract_variable,
+ r#"
+struct X;
+
+impl X {
+ fn do_thing(&self) {
+
+ }
+}
+
+struct S {
+ sub: X
+}
+
+fn foo(s: &S) {
+ $0s.sub$0.do_thing();
+}"#,
+ r#"
+struct X;
+
+impl X {
+ fn do_thing(&self) {
+
+ }
+}
+
+struct S {
+ sub: X
+}
+
+fn foo(s: &S) {
+ let $0x = &s.sub;
+ x.do_thing();
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_var_reference_parameter_deep_nesting() {
+ check_assist(
+ extract_variable,
+ r#"
+struct Z;
+impl Z {
+ fn do_thing(&self) {
+
+ }
+}
+
+struct Y {
+ field: Z
+}
+
+struct X {
+ field: Y
+}
+
+struct S {
+ sub: X
+}
+
+fn foo(s: &S) {
+ $0s.sub.field.field$0.do_thing();
+}"#,
+ r#"
+struct Z;
+impl Z {
+ fn do_thing(&self) {
+
+ }
+}
+
+struct Y {
+ field: Z
+}
+
+struct X {
+ field: Y
+}
+
+struct S {
+ sub: X
+}
+
+fn foo(s: &S) {
+ let $0z = &s.sub.field.field;
+ z.do_thing();
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_var_regular_parameter() {
+ check_assist(
+ extract_variable,
+ r#"
+struct X;
+
+impl X {
+ fn do_thing(&self) {
+
+ }
+}
+
+struct S {
+ sub: X
+}
+
+fn foo(s: S) {
+ $0s.sub$0.do_thing();
+}"#,
+ r#"
+struct X;
+
+impl X {
+ fn do_thing(&self) {
+
+ }
+}
+
+struct S {
+ sub: X
+}
+
+fn foo(s: S) {
+ let $0x = s.sub;
+ x.do_thing();
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_var_mutable_reference_local() {
+ check_assist(
+ extract_variable,
+ r#"
+struct X;
+
+struct S {
+ sub: X
+}
+
+impl S {
+ fn new() -> S {
+ S {
+ sub: X::new()
+ }
+ }
+}
+
+impl X {
+ fn new() -> X {
+ X { }
+ }
+ fn do_thing(&self) {
+
+ }
+}
+
+
+fn foo() {
+ let local = &mut S::new();
+ $0local.sub$0.do_thing();
+}"#,
+ r#"
+struct X;
+
+struct S {
+ sub: X
+}
+
+impl S {
+ fn new() -> S {
+ S {
+ sub: X::new()
+ }
+ }
+}
+
+impl X {
+ fn new() -> X {
+ X { }
+ }
+ fn do_thing(&self) {
+
+ }
+}
+
+
+fn foo() {
+ let local = &mut S::new();
+ let $0x = &mut local.sub;
+ x.do_thing();
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_var_reference_local() {
+ check_assist(
+ extract_variable,
+ r#"
+struct X;
+
+struct S {
+ sub: X
+}
+
+impl S {
+ fn new() -> S {
+ S {
+ sub: X::new()
+ }
+ }
+}
+
+impl X {
+ fn new() -> X {
+ X { }
+ }
+ fn do_thing(&self) {
+
+ }
+}
+
+
+fn foo() {
+ let local = &S::new();
+ $0local.sub$0.do_thing();
+}"#,
+ r#"
+struct X;
+
+struct S {
+ sub: X
+}
+
+impl S {
+ fn new() -> S {
+ S {
+ sub: X::new()
+ }
+ }
+}
+
+impl X {
+ fn new() -> X {
+ X { }
+ }
+ fn do_thing(&self) {
+
+ }
+}
+
+
+fn foo() {
+ let local = &S::new();
+ let $0x = &local.sub;
+ x.do_thing();
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_extract_var_for_mutable_borrow() {
+ check_assist(
+ extract_variable,
+ r#"
+fn foo() {
+ let v = &mut $00$0;
+}"#,
+ r#"
+fn foo() {
+ let mut $0var_name = 0;
+ let v = &mut var_name;
+}"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs
new file mode 100644
index 000000000..b33846f54
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs
@@ -0,0 +1,606 @@
+use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
+use ide_db::base_db::FileId;
+use syntax::{
+ ast::{self, HasVisibility as _},
+ AstNode, TextRange, TextSize,
+};
+
+use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
+
+// FIXME: this really should be a fix for diagnostic, rather than an assist.
+
+// Assist: fix_visibility
+//
+// Makes inaccessible item public.
+//
+// ```
+// mod m {
+// fn frobnicate() {}
+// }
+// fn main() {
+// m::frobnicate$0() {}
+// }
+// ```
+// ->
+// ```
+// mod m {
+// $0pub(crate) fn frobnicate() {}
+// }
+// fn main() {
+// m::frobnicate() {}
+// }
+// ```
+pub(crate) fn fix_visibility(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ add_vis_to_referenced_module_def(acc, ctx)
+ .or_else(|| add_vis_to_referenced_record_field(acc, ctx))
+}
+
+fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let path: ast::Path = ctx.find_node_at_offset()?;
+ let path_res = ctx.sema.resolve_path(&path)?;
+ let def = match path_res {
+ PathResolution::Def(def) => def,
+ _ => return None,
+ };
+
+ let current_module = ctx.sema.scope(path.syntax())?.module();
+ let target_module = def.module(ctx.db())?;
+
+ if def.visibility(ctx.db()).is_visible_from(ctx.db(), current_module.into()) {
+ return None;
+ };
+
+ let (offset, current_visibility, target, target_file, target_name) =
+ target_data_for_def(ctx.db(), def)?;
+
+ let missing_visibility =
+ if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
+
+ let assist_label = match target_name {
+ None => format!("Change visibility to {}", missing_visibility),
+ Some(name) => format!("Change visibility of {} to {}", name, missing_visibility),
+ };
+
+ acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
+ builder.edit_file(target_file);
+ match ctx.config.snippet_cap {
+ Some(cap) => match current_visibility {
+ Some(current_visibility) => builder.replace_snippet(
+ cap,
+ current_visibility.syntax().text_range(),
+ format!("$0{}", missing_visibility),
+ ),
+ None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
+ },
+ None => match current_visibility {
+ Some(current_visibility) => {
+ builder.replace(current_visibility.syntax().text_range(), missing_visibility)
+ }
+ None => builder.insert(offset, format!("{} ", missing_visibility)),
+ },
+ }
+ })
+}
+
+fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let record_field: ast::RecordExprField = ctx.find_node_at_offset()?;
+ let (record_field_def, _, _) = ctx.sema.resolve_record_field(&record_field)?;
+
+ let current_module = ctx.sema.scope(record_field.syntax())?.module();
+ let visibility = record_field_def.visibility(ctx.db());
+ if visibility.is_visible_from(ctx.db(), current_module.into()) {
+ return None;
+ }
+
+ let parent = record_field_def.parent_def(ctx.db());
+ let parent_name = parent.name(ctx.db());
+ let target_module = parent.module(ctx.db());
+
+ let in_file_source = record_field_def.source(ctx.db())?;
+ let (offset, current_visibility, target) = match in_file_source.value {
+ hir::FieldSource::Named(it) => {
+ let s = it.syntax();
+ (vis_offset(s), it.visibility(), s.text_range())
+ }
+ hir::FieldSource::Pos(it) => {
+ let s = it.syntax();
+ (vis_offset(s), it.visibility(), s.text_range())
+ }
+ };
+
+ let missing_visibility =
+ if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
+ let target_file = in_file_source.file_id.original_file(ctx.db());
+
+ let target_name = record_field_def.name(ctx.db());
+ let assist_label =
+ format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility);
+
+ acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
+ builder.edit_file(target_file);
+ match ctx.config.snippet_cap {
+ Some(cap) => match current_visibility {
+ Some(current_visibility) => builder.replace_snippet(
+ cap,
+ current_visibility.syntax().text_range(),
+ format!("$0{}", missing_visibility),
+ ),
+ None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
+ },
+ None => match current_visibility {
+ Some(current_visibility) => {
+ builder.replace(current_visibility.syntax().text_range(), missing_visibility)
+ }
+ None => builder.insert(offset, format!("{} ", missing_visibility)),
+ },
+ }
+ })
+}
+
+fn target_data_for_def(
+ db: &dyn HirDatabase,
+ def: hir::ModuleDef,
+) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId, Option<hir::Name>)> {
+ fn offset_target_and_file_id<S, Ast>(
+ db: &dyn HirDatabase,
+ x: S,
+ ) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId)>
+ where
+ S: HasSource<Ast = Ast>,
+ Ast: AstNode + ast::HasVisibility,
+ {
+ let source = x.source(db)?;
+ let in_file_syntax = source.syntax();
+ let file_id = in_file_syntax.file_id;
+ let syntax = in_file_syntax.value;
+ let current_visibility = source.value.visibility();
+ Some((
+ vis_offset(syntax),
+ current_visibility,
+ syntax.text_range(),
+ file_id.original_file(db.upcast()),
+ ))
+ }
+
+ let target_name;
+ let (offset, current_visibility, target, target_file) = match def {
+ hir::ModuleDef::Function(f) => {
+ target_name = Some(f.name(db));
+ offset_target_and_file_id(db, f)?
+ }
+ hir::ModuleDef::Adt(adt) => {
+ target_name = Some(adt.name(db));
+ match adt {
+ hir::Adt::Struct(s) => offset_target_and_file_id(db, s)?,
+ hir::Adt::Union(u) => offset_target_and_file_id(db, u)?,
+ hir::Adt::Enum(e) => offset_target_and_file_id(db, e)?,
+ }
+ }
+ hir::ModuleDef::Const(c) => {
+ target_name = c.name(db);
+ offset_target_and_file_id(db, c)?
+ }
+ hir::ModuleDef::Static(s) => {
+ target_name = Some(s.name(db));
+ offset_target_and_file_id(db, s)?
+ }
+ hir::ModuleDef::Trait(t) => {
+ target_name = Some(t.name(db));
+ offset_target_and_file_id(db, t)?
+ }
+ hir::ModuleDef::TypeAlias(t) => {
+ target_name = Some(t.name(db));
+ offset_target_and_file_id(db, t)?
+ }
+ hir::ModuleDef::Module(m) => {
+ target_name = m.name(db);
+ let in_file_source = m.declaration_source(db)?;
+ let file_id = in_file_source.file_id.original_file(db.upcast());
+ let syntax = in_file_source.value.syntax();
+ (vis_offset(syntax), in_file_source.value.visibility(), syntax.text_range(), file_id)
+ }
+ // FIXME
+ hir::ModuleDef::Macro(_) => return None,
+ // Enum variants can't be private, we can't modify builtin types
+ hir::ModuleDef::Variant(_) | hir::ModuleDef::BuiltinType(_) => return None,
+ };
+
+ Some((offset, current_visibility, target, target_file, target_name))
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn fix_visibility_of_fn() {
+ check_assist(
+ fix_visibility,
+ r"mod foo { fn foo() {} }
+ fn main() { foo::foo$0() } ",
+ r"mod foo { $0pub(crate) fn foo() {} }
+ fn main() { foo::foo() } ",
+ );
+ check_assist_not_applicable(
+ fix_visibility,
+ r"mod foo { pub fn foo() {} }
+ fn main() { foo::foo$0() } ",
+ )
+ }
+
+ #[test]
+ fn fix_visibility_of_adt_in_submodule() {
+ check_assist(
+ fix_visibility,
+ r"mod foo { struct Foo; }
+ fn main() { foo::Foo$0 } ",
+ r"mod foo { $0pub(crate) struct Foo; }
+ fn main() { foo::Foo } ",
+ );
+ check_assist_not_applicable(
+ fix_visibility,
+ r"mod foo { pub struct Foo; }
+ fn main() { foo::Foo$0 } ",
+ );
+ check_assist(
+ fix_visibility,
+ r"mod foo { enum Foo; }
+ fn main() { foo::Foo$0 } ",
+ r"mod foo { $0pub(crate) enum Foo; }
+ fn main() { foo::Foo } ",
+ );
+ check_assist_not_applicable(
+ fix_visibility,
+ r"mod foo { pub enum Foo; }
+ fn main() { foo::Foo$0 } ",
+ );
+ check_assist(
+ fix_visibility,
+ r"mod foo { union Foo; }
+ fn main() { foo::Foo$0 } ",
+ r"mod foo { $0pub(crate) union Foo; }
+ fn main() { foo::Foo } ",
+ );
+ check_assist_not_applicable(
+ fix_visibility,
+ r"mod foo { pub union Foo; }
+ fn main() { foo::Foo$0 } ",
+ );
+ }
+
+ #[test]
+ fn fix_visibility_of_adt_in_other_file() {
+ check_assist(
+ fix_visibility,
+ r"
+//- /main.rs
+mod foo;
+fn main() { foo::Foo$0 }
+
+//- /foo.rs
+struct Foo;
+",
+ r"$0pub(crate) struct Foo;
+",
+ );
+ }
+
+ #[test]
+ fn fix_visibility_of_struct_field() {
+ check_assist(
+ fix_visibility,
+ r"mod foo { pub struct Foo { bar: (), } }
+ fn main() { foo::Foo { $0bar: () }; } ",
+ r"mod foo { pub struct Foo { $0pub(crate) bar: (), } }
+ fn main() { foo::Foo { bar: () }; } ",
+ );
+ check_assist(
+ fix_visibility,
+ r"
+//- /lib.rs
+mod foo;
+fn main() { foo::Foo { $0bar: () }; }
+//- /foo.rs
+pub struct Foo { bar: () }
+",
+ r"pub struct Foo { $0pub(crate) bar: () }
+",
+ );
+ check_assist_not_applicable(
+ fix_visibility,
+ r"mod foo { pub struct Foo { pub bar: (), } }
+ fn main() { foo::Foo { $0bar: () }; } ",
+ );
+ check_assist_not_applicable(
+ fix_visibility,
+ r"
+//- /lib.rs
+mod foo;
+fn main() { foo::Foo { $0bar: () }; }
+//- /foo.rs
+pub struct Foo { pub bar: () }
+",
+ );
+ }
+
+ #[test]
+ fn fix_visibility_of_enum_variant_field() {
+ // Enum variants, as well as their fields, always get the enum's visibility. In fact, rustc
+ // rejects any visibility specifiers on them, so this assist should never fire on them.
+ check_assist_not_applicable(
+ fix_visibility,
+ r"mod foo { pub enum Foo { Bar { bar: () } } }
+ fn main() { foo::Foo::Bar { $0bar: () }; } ",
+ );
+ check_assist_not_applicable(
+ fix_visibility,
+ r"
+//- /lib.rs
+mod foo;
+fn main() { foo::Foo::Bar { $0bar: () }; }
+//- /foo.rs
+pub enum Foo { Bar { bar: () } }
+",
+ );
+ check_assist_not_applicable(
+ fix_visibility,
+ r"mod foo { pub struct Foo { pub bar: (), } }
+ fn main() { foo::Foo { $0bar: () }; } ",
+ );
+ check_assist_not_applicable(
+ fix_visibility,
+ r"
+//- /lib.rs
+mod foo;
+fn main() { foo::Foo { $0bar: () }; }
+//- /foo.rs
+pub struct Foo { pub bar: () }
+",
+ );
+ }
+
+ #[test]
+ fn fix_visibility_of_union_field() {
+ check_assist(
+ fix_visibility,
+ r"mod foo { pub union Foo { bar: (), } }
+ fn main() { foo::Foo { $0bar: () }; } ",
+ r"mod foo { pub union Foo { $0pub(crate) bar: (), } }
+ fn main() { foo::Foo { bar: () }; } ",
+ );
+ check_assist(
+ fix_visibility,
+ r"
+//- /lib.rs
+mod foo;
+fn main() { foo::Foo { $0bar: () }; }
+//- /foo.rs
+pub union Foo { bar: () }
+",
+ r"pub union Foo { $0pub(crate) bar: () }
+",
+ );
+ check_assist_not_applicable(
+ fix_visibility,
+ r"mod foo { pub union Foo { pub bar: (), } }
+ fn main() { foo::Foo { $0bar: () }; } ",
+ );
+ check_assist_not_applicable(
+ fix_visibility,
+ r"
+//- /lib.rs
+mod foo;
+fn main() { foo::Foo { $0bar: () }; }
+//- /foo.rs
+pub union Foo { pub bar: () }
+",
+ );
+ }
+
+ #[test]
+ fn fix_visibility_of_const() {
+ check_assist(
+ fix_visibility,
+ r"mod foo { const FOO: () = (); }
+ fn main() { foo::FOO$0 } ",
+ r"mod foo { $0pub(crate) const FOO: () = (); }
+ fn main() { foo::FOO } ",
+ );
+ check_assist_not_applicable(
+ fix_visibility,
+ r"mod foo { pub const FOO: () = (); }
+ fn main() { foo::FOO$0 } ",
+ );
+ }
+
+ #[test]
+ fn fix_visibility_of_static() {
+ check_assist(
+ fix_visibility,
+ r"mod foo { static FOO: () = (); }
+ fn main() { foo::FOO$0 } ",
+ r"mod foo { $0pub(crate) static FOO: () = (); }
+ fn main() { foo::FOO } ",
+ );
+ check_assist_not_applicable(
+ fix_visibility,
+ r"mod foo { pub static FOO: () = (); }
+ fn main() { foo::FOO$0 } ",
+ );
+ }
+
+ #[test]
+ fn fix_visibility_of_trait() {
+ check_assist(
+ fix_visibility,
+ r"mod foo { trait Foo { fn foo(&self) {} } }
+ fn main() { let x: &dyn foo::$0Foo; } ",
+ r"mod foo { $0pub(crate) trait Foo { fn foo(&self) {} } }
+ fn main() { let x: &dyn foo::Foo; } ",
+ );
+ check_assist_not_applicable(
+ fix_visibility,
+ r"mod foo { pub trait Foo { fn foo(&self) {} } }
+ fn main() { let x: &dyn foo::Foo$0; } ",
+ );
+ }
+
+ #[test]
+ fn fix_visibility_of_type_alias() {
+ check_assist(
+ fix_visibility,
+ r"mod foo { type Foo = (); }
+ fn main() { let x: foo::Foo$0; } ",
+ r"mod foo { $0pub(crate) type Foo = (); }
+ fn main() { let x: foo::Foo; } ",
+ );
+ check_assist_not_applicable(
+ fix_visibility,
+ r"mod foo { pub type Foo = (); }
+ fn main() { let x: foo::Foo$0; } ",
+ );
+ }
+
+ #[test]
+ fn fix_visibility_of_module() {
+ check_assist(
+ fix_visibility,
+ r"mod foo { mod bar { fn bar() {} } }
+ fn main() { foo::bar$0::bar(); } ",
+ r"mod foo { $0pub(crate) mod bar { fn bar() {} } }
+ fn main() { foo::bar::bar(); } ",
+ );
+
+ check_assist(
+ fix_visibility,
+ r"
+//- /main.rs
+mod foo;
+fn main() { foo::bar$0::baz(); }
+
+//- /foo.rs
+mod bar {
+ pub fn baz() {}
+}
+",
+ r"$0pub(crate) mod bar {
+ pub fn baz() {}
+}
+",
+ );
+
+ check_assist_not_applicable(
+ fix_visibility,
+ r"mod foo { pub mod bar { pub fn bar() {} } }
+ fn main() { foo::bar$0::bar(); } ",
+ );
+ }
+
+ #[test]
+ fn fix_visibility_of_inline_module_in_other_file() {
+ check_assist(
+ fix_visibility,
+ r"
+//- /main.rs
+mod foo;
+fn main() { foo::bar$0::baz(); }
+
+//- /foo.rs
+mod bar;
+//- /foo/bar.rs
+pub fn baz() {}
+",
+ r"$0pub(crate) mod bar;
+",
+ );
+ }
+
+ #[test]
+ fn fix_visibility_of_module_declaration_in_other_file() {
+ check_assist(
+ fix_visibility,
+ r"
+//- /main.rs
+mod foo;
+fn main() { foo::bar$0>::baz(); }
+
+//- /foo.rs
+mod bar {
+ pub fn baz() {}
+}
+",
+ r"$0pub(crate) mod bar {
+ pub fn baz() {}
+}
+",
+ );
+ }
+
+ #[test]
+ fn adds_pub_when_target_is_in_another_crate() {
+ check_assist(
+ fix_visibility,
+ r"
+//- /main.rs crate:a deps:foo
+foo::Bar$0
+//- /lib.rs crate:foo
+struct Bar;
+",
+ r"$0pub struct Bar;
+",
+ )
+ }
+
+ #[test]
+ fn replaces_pub_crate_with_pub() {
+ check_assist(
+ fix_visibility,
+ r"
+//- /main.rs crate:a deps:foo
+foo::Bar$0
+//- /lib.rs crate:foo
+pub(crate) struct Bar;
+",
+ r"$0pub struct Bar;
+",
+ );
+ check_assist(
+ fix_visibility,
+ r"
+//- /main.rs crate:a deps:foo
+fn main() {
+ foo::Foo { $0bar: () };
+}
+//- /lib.rs crate:foo
+pub struct Foo { pub(crate) bar: () }
+",
+ r"pub struct Foo { $0pub bar: () }
+",
+ );
+ }
+
+ #[test]
+ fn fix_visibility_of_reexport() {
+ // FIXME: broken test, this should fix visibility of the re-export
+ // rather than the struct.
+ check_assist(
+ fix_visibility,
+ r#"
+mod foo {
+ use bar::Baz;
+ mod bar { pub(super) struct Baz; }
+}
+foo::Baz$0
+"#,
+ r#"
+mod foo {
+ use bar::Baz;
+ mod bar { $0pub(crate) struct Baz; }
+}
+foo::Baz
+"#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/flip_binexpr.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/flip_binexpr.rs
new file mode 100644
index 000000000..2ea6f58fa
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/flip_binexpr.rs
@@ -0,0 +1,139 @@
+use syntax::ast::{self, AstNode, BinExpr};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: flip_binexpr
+//
+// Flips operands of a binary expression.
+//
+// ```
+// fn main() {
+// let _ = 90 +$0 2;
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// let _ = 2 + 90;
+// }
+// ```
+pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let expr = ctx.find_node_at_offset::<BinExpr>()?;
+ let lhs = expr.lhs()?.syntax().clone();
+ let rhs = expr.rhs()?.syntax().clone();
+ let op_range = expr.op_token()?.text_range();
+ // The assist should be applied only if the cursor is on the operator
+ let cursor_in_range = op_range.contains_range(ctx.selection_trimmed());
+ if !cursor_in_range {
+ return None;
+ }
+ let action: FlipAction = expr.op_kind()?.into();
+ // The assist should not be applied for certain operators
+ if let FlipAction::DontFlip = action {
+ return None;
+ }
+
+ acc.add(
+ AssistId("flip_binexpr", AssistKind::RefactorRewrite),
+ "Flip binary expression",
+ op_range,
+ |edit| {
+ if let FlipAction::FlipAndReplaceOp(new_op) = action {
+ edit.replace(op_range, new_op);
+ }
+ edit.replace(lhs.text_range(), rhs.text());
+ edit.replace(rhs.text_range(), lhs.text());
+ },
+ )
+}
+
+enum FlipAction {
+ // Flip the expression
+ Flip,
+ // Flip the expression and replace the operator with this string
+ FlipAndReplaceOp(&'static str),
+ // Do not flip the expression
+ DontFlip,
+}
+
+impl From<ast::BinaryOp> for FlipAction {
+ fn from(op_kind: ast::BinaryOp) -> Self {
+ match op_kind {
+ ast::BinaryOp::Assignment { .. } => FlipAction::DontFlip,
+ ast::BinaryOp::CmpOp(ast::CmpOp::Ord { ordering, strict }) => {
+ let rev_op = match (ordering, strict) {
+ (ast::Ordering::Less, true) => ">",
+ (ast::Ordering::Less, false) => ">=",
+ (ast::Ordering::Greater, true) => "<",
+ (ast::Ordering::Greater, false) => "<=",
+ };
+ FlipAction::FlipAndReplaceOp(rev_op)
+ }
+ _ => FlipAction::Flip,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
+
+ #[test]
+ fn flip_binexpr_target_is_the_op() {
+ check_assist_target(flip_binexpr, "fn f() { let res = 1 ==$0 2; }", "==")
+ }
+
+ #[test]
+ fn flip_binexpr_not_applicable_for_assignment() {
+ check_assist_not_applicable(flip_binexpr, "fn f() { let mut _x = 1; _x +=$0 2 }")
+ }
+
+ #[test]
+ fn flip_binexpr_works_for_eq() {
+ check_assist(flip_binexpr, "fn f() { let res = 1 ==$0 2; }", "fn f() { let res = 2 == 1; }")
+ }
+
+ #[test]
+ fn flip_binexpr_works_for_gt() {
+ check_assist(flip_binexpr, "fn f() { let res = 1 >$0 2; }", "fn f() { let res = 2 < 1; }")
+ }
+
+ #[test]
+ fn flip_binexpr_works_for_lteq() {
+ check_assist(flip_binexpr, "fn f() { let res = 1 <=$0 2; }", "fn f() { let res = 2 >= 1; }")
+ }
+
+ #[test]
+ fn flip_binexpr_works_for_complex_expr() {
+ check_assist(
+ flip_binexpr,
+ "fn f() { let res = (1 + 1) ==$0 (2 + 2); }",
+ "fn f() { let res = (2 + 2) == (1 + 1); }",
+ )
+ }
+
+ #[test]
+ fn flip_binexpr_works_inside_match() {
+ check_assist(
+ flip_binexpr,
+ r#"
+ fn dyn_eq(&self, other: &dyn Diagnostic) -> bool {
+ match other.downcast_ref::<Self>() {
+ None => false,
+ Some(it) => it ==$0 self,
+ }
+ }
+ "#,
+ r#"
+ fn dyn_eq(&self, other: &dyn Diagnostic) -> bool {
+ match other.downcast_ref::<Self>() {
+ None => false,
+ Some(it) => self == it,
+ }
+ }
+ "#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/flip_comma.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/flip_comma.rs
new file mode 100644
index 000000000..f40f2713a
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/flip_comma.rs
@@ -0,0 +1,92 @@
+use syntax::{algo::non_trivia_sibling, Direction, SyntaxKind, T};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: flip_comma
+//
+// Flips two comma-separated items.
+//
+// ```
+// fn main() {
+// ((1, 2),$0 (3, 4));
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// ((3, 4), (1, 2));
+// }
+// ```
+pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let comma = ctx.find_token_syntax_at_offset(T![,])?;
+ let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?;
+ let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?;
+
+ // Don't apply a "flip" in case of a last comma
+ // that typically comes before punctuation
+ if next.kind().is_punct() {
+ return None;
+ }
+
+ // Don't apply a "flip" inside the macro call
+ // since macro input are just mere tokens
+ if comma.parent_ancestors().any(|it| it.kind() == SyntaxKind::MACRO_CALL) {
+ return None;
+ }
+
+ acc.add(
+ AssistId("flip_comma", AssistKind::RefactorRewrite),
+ "Flip comma",
+ comma.text_range(),
+ |edit| {
+ edit.replace(prev.text_range(), next.to_string());
+ edit.replace(next.text_range(), prev.to_string());
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
+
+ #[test]
+ fn flip_comma_works_for_function_parameters() {
+ check_assist(
+ flip_comma,
+ r#"fn foo(x: i32,$0 y: Result<(), ()>) {}"#,
+ r#"fn foo(y: Result<(), ()>, x: i32) {}"#,
+ )
+ }
+
+ #[test]
+ fn flip_comma_target() {
+ check_assist_target(flip_comma, r#"fn foo(x: i32,$0 y: Result<(), ()>) {}"#, ",")
+ }
+
+ #[test]
+ fn flip_comma_before_punct() {
+ // See https://github.com/rust-lang/rust-analyzer/issues/1619
+ // "Flip comma" assist shouldn't be applicable to the last comma in enum or struct
+ // declaration body.
+ check_assist_not_applicable(flip_comma, "pub enum Test { A,$0 }");
+ check_assist_not_applicable(flip_comma, "pub struct Test { foo: usize,$0 }");
+ }
+
+ #[test]
+ fn flip_comma_works() {
+ check_assist(
+ flip_comma,
+ r#"fn main() {((1, 2),$0 (3, 4));}"#,
+ r#"fn main() {((3, 4), (1, 2));}"#,
+ )
+ }
+
+ #[test]
+ fn flip_comma_not_applicable_for_macro_input() {
+ // "Flip comma" assist shouldn't be applicable inside the macro call
+ // See https://github.com/rust-lang/rust-analyzer/issues/7693
+ check_assist_not_applicable(flip_comma, r#"bar!(a,$0 b)"#);
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/flip_trait_bound.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/flip_trait_bound.rs
new file mode 100644
index 000000000..e3ae4970b
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/flip_trait_bound.rs
@@ -0,0 +1,121 @@
+use syntax::{
+ algo::non_trivia_sibling,
+ ast::{self, AstNode},
+ Direction, T,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: flip_trait_bound
+//
+// Flips two trait bounds.
+//
+// ```
+// fn foo<T: Clone +$0 Copy>() { }
+// ```
+// ->
+// ```
+// fn foo<T: Copy + Clone>() { }
+// ```
+pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ // We want to replicate the behavior of `flip_binexpr` by only suggesting
+ // the assist when the cursor is on a `+`
+ let plus = ctx.find_token_syntax_at_offset(T![+])?;
+
+ // Make sure we're in a `TypeBoundList`
+ if ast::TypeBoundList::cast(plus.parent()?).is_none() {
+ return None;
+ }
+
+ let (before, after) = (
+ non_trivia_sibling(plus.clone().into(), Direction::Prev)?,
+ non_trivia_sibling(plus.clone().into(), Direction::Next)?,
+ );
+
+ let target = plus.text_range();
+ acc.add(
+ AssistId("flip_trait_bound", AssistKind::RefactorRewrite),
+ "Flip trait bounds",
+ target,
+ |edit| {
+ edit.replace(before.text_range(), after.to_string());
+ edit.replace(after.text_range(), before.to_string());
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
+
+ #[test]
+ fn flip_trait_bound_assist_available() {
+ check_assist_target(flip_trait_bound, "struct S<T> where T: A $0+ B + C { }", "+")
+ }
+
+ #[test]
+ fn flip_trait_bound_not_applicable_for_single_trait_bound() {
+ check_assist_not_applicable(flip_trait_bound, "struct S<T> where T: $0A { }")
+ }
+
+ #[test]
+ fn flip_trait_bound_works_for_struct() {
+ check_assist(
+ flip_trait_bound,
+ "struct S<T> where T: A $0+ B { }",
+ "struct S<T> where T: B + A { }",
+ )
+ }
+
+ #[test]
+ fn flip_trait_bound_works_for_trait_impl() {
+ check_assist(
+ flip_trait_bound,
+ "impl X for S<T> where T: A +$0 B { }",
+ "impl X for S<T> where T: B + A { }",
+ )
+ }
+
+ #[test]
+ fn flip_trait_bound_works_for_fn() {
+ check_assist(flip_trait_bound, "fn f<T: A $0+ B>(t: T) { }", "fn f<T: B + A>(t: T) { }")
+ }
+
+ #[test]
+ fn flip_trait_bound_works_for_fn_where_clause() {
+ check_assist(
+ flip_trait_bound,
+ "fn f<T>(t: T) where T: A +$0 B { }",
+ "fn f<T>(t: T) where T: B + A { }",
+ )
+ }
+
+ #[test]
+ fn flip_trait_bound_works_for_lifetime() {
+ check_assist(
+ flip_trait_bound,
+ "fn f<T>(t: T) where T: A $0+ 'static { }",
+ "fn f<T>(t: T) where T: 'static + A { }",
+ )
+ }
+
+ #[test]
+ fn flip_trait_bound_works_for_complex_bounds() {
+ check_assist(
+ flip_trait_bound,
+ "struct S<T> where T: A<T> $0+ b_mod::B<T> + C<T> { }",
+ "struct S<T> where T: b_mod::B<T> + A<T> + C<T> { }",
+ )
+ }
+
+ #[test]
+ fn flip_trait_bound_works_for_long_bounds() {
+ check_assist(
+ flip_trait_bound,
+ "struct S<T> where T: A + B + C + D + E + F +$0 G + H + I + J { }",
+ "struct S<T> where T: A + B + C + D + E + G + F + H + I + J { }",
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_constant.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_constant.rs
new file mode 100644
index 000000000..eaa6de73e
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_constant.rs
@@ -0,0 +1,255 @@
+use crate::assist_context::{AssistContext, Assists};
+use hir::{HasVisibility, HirDisplay, Module};
+use ide_db::{
+ assists::{AssistId, AssistKind},
+ base_db::{FileId, Upcast},
+ defs::{Definition, NameRefClass},
+};
+use syntax::{
+ ast::{self, edit::IndentLevel, NameRef},
+ AstNode, Direction, SyntaxKind, TextSize,
+};
+
+// Assist: generate_constant
+//
+// Generate a named constant.
+//
+// ```
+// struct S { i: usize }
+// impl S { pub fn new(n: usize) {} }
+// fn main() {
+// let v = S::new(CAPA$0CITY);
+// }
+// ```
+// ->
+// ```
+// struct S { i: usize }
+// impl S { pub fn new(n: usize) {} }
+// fn main() {
+// const CAPACITY: usize = $0;
+// let v = S::new(CAPACITY);
+// }
+// ```
+
+pub(crate) fn generate_constant(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let constant_token = ctx.find_node_at_offset::<ast::NameRef>()?;
+ if constant_token.to_string().chars().any(|it| !(it.is_uppercase() || it == '_')) {
+ cov_mark::hit!(not_constant_name);
+ return None;
+ }
+ if NameRefClass::classify(&ctx.sema, &constant_token).is_some() {
+ cov_mark::hit!(already_defined);
+ return None;
+ }
+ let expr = constant_token.syntax().ancestors().find_map(ast::Expr::cast)?;
+ let statement = expr.syntax().ancestors().find_map(ast::Stmt::cast)?;
+ let ty = ctx.sema.type_of_expr(&expr)?;
+ let scope = ctx.sema.scope(statement.syntax())?;
+ let constant_module = scope.module();
+ let type_name = ty.original().display_source_code(ctx.db(), constant_module.into()).ok()?;
+ let target = statement.syntax().parent()?.text_range();
+ let path = constant_token.syntax().ancestors().find_map(ast::Path::cast)?;
+
+ let name_refs = path.segments().map(|s| s.name_ref());
+ let mut outer_exists = false;
+ let mut not_exist_name_ref = Vec::new();
+ let mut current_module = constant_module;
+ for name_ref in name_refs {
+ let name_ref_value = name_ref?;
+ let name_ref_class = NameRefClass::classify(&ctx.sema, &name_ref_value);
+ match name_ref_class {
+ Some(NameRefClass::Definition(Definition::Module(m))) => {
+ if !m.visibility(ctx.sema.db).is_visible_from(ctx.sema.db, constant_module.into()) {
+ return None;
+ }
+ outer_exists = true;
+ current_module = m;
+ }
+ Some(_) => {
+ return None;
+ }
+ None => {
+ not_exist_name_ref.push(name_ref_value);
+ }
+ }
+ }
+ let (offset, indent, file_id, post_string) =
+ target_data_for_generate_constant(ctx, current_module, constant_module).unwrap_or_else(
+ || {
+ let indent = IndentLevel::from_node(statement.syntax());
+ (statement.syntax().text_range().start(), indent, None, format!("\n{}", indent))
+ },
+ );
+
+ let text = get_text_for_generate_constant(not_exist_name_ref, indent, outer_exists, type_name)?;
+ acc.add(
+ AssistId("generate_constant", AssistKind::QuickFix),
+ "Generate constant",
+ target,
+ |builder| {
+ if let Some(file_id) = file_id {
+ builder.edit_file(file_id);
+ }
+ builder.insert(offset, format!("{}{}", text, post_string));
+ },
+ )
+}
+
+fn get_text_for_generate_constant(
+ mut not_exist_name_ref: Vec<NameRef>,
+ indent: IndentLevel,
+ outer_exists: bool,
+ type_name: String,
+) -> Option<String> {
+ let constant_token = not_exist_name_ref.pop()?;
+ let vis = if not_exist_name_ref.len() == 0 && !outer_exists { "" } else { "\npub " };
+ let mut text = format!("{}const {}: {} = $0;", vis, constant_token, type_name);
+ while let Some(name_ref) = not_exist_name_ref.pop() {
+ let vis = if not_exist_name_ref.len() == 0 && !outer_exists { "" } else { "\npub " };
+ text = text.replace("\n", "\n ");
+ text = format!("{}mod {} {{{}\n}}", vis, name_ref.to_string(), text);
+ }
+ Some(text.replace("\n", &format!("\n{}", indent)))
+}
+
+fn target_data_for_generate_constant(
+ ctx: &AssistContext<'_>,
+ current_module: Module,
+ constant_module: Module,
+) -> Option<(TextSize, IndentLevel, Option<FileId>, String)> {
+ if current_module == constant_module {
+ // insert in current file
+ return None;
+ }
+ let in_file_source = current_module.definition_source(ctx.sema.db);
+ let file_id = in_file_source.file_id.original_file(ctx.sema.db.upcast());
+ match in_file_source.value {
+ hir::ModuleSource::Module(module_node) => {
+ let indent = IndentLevel::from_node(module_node.syntax());
+ let l_curly_token = module_node.item_list()?.l_curly_token()?;
+ let offset = l_curly_token.text_range().end();
+
+ let siblings_has_newline = l_curly_token
+ .siblings_with_tokens(Direction::Next)
+ .find(|it| it.kind() == SyntaxKind::WHITESPACE && it.to_string().contains("\n"))
+ .is_some();
+ let post_string =
+ if siblings_has_newline { format!("{}", indent) } else { format!("\n{}", indent) };
+ Some((offset, indent + 1, Some(file_id), post_string))
+ }
+ _ => Some((TextSize::from(0), 0.into(), Some(file_id), "\n".into())),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ #[test]
+ fn test_trivial() {
+ check_assist(
+ generate_constant,
+ r#"struct S { i: usize }
+impl S {
+ pub fn new(n: usize) {}
+}
+fn main() {
+ let v = S::new(CAPA$0CITY);
+}"#,
+ r#"struct S { i: usize }
+impl S {
+ pub fn new(n: usize) {}
+}
+fn main() {
+ const CAPACITY: usize = $0;
+ let v = S::new(CAPACITY);
+}"#,
+ );
+ }
+ #[test]
+ fn test_wont_apply_when_defined() {
+ cov_mark::check!(already_defined);
+ check_assist_not_applicable(
+ generate_constant,
+ r#"struct S { i: usize }
+impl S {
+ pub fn new(n: usize) {}
+}
+fn main() {
+ const CAPACITY: usize = 10;
+ let v = S::new(CAPAC$0ITY);
+}"#,
+ );
+ }
+ #[test]
+ fn test_wont_apply_when_maybe_not_constant() {
+ cov_mark::check!(not_constant_name);
+ check_assist_not_applicable(
+ generate_constant,
+ r#"struct S { i: usize }
+impl S {
+ pub fn new(n: usize) {}
+}
+fn main() {
+ let v = S::new(capa$0city);
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_constant_with_path() {
+ check_assist(
+ generate_constant,
+ r#"mod foo {}
+fn bar() -> i32 {
+ foo::A_CON$0STANT
+}"#,
+ r#"mod foo {
+ pub const A_CONSTANT: i32 = $0;
+}
+fn bar() -> i32 {
+ foo::A_CONSTANT
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_constant_with_longer_path() {
+ check_assist(
+ generate_constant,
+ r#"mod foo {
+ pub mod goo {}
+}
+fn bar() -> i32 {
+ foo::goo::A_CON$0STANT
+}"#,
+ r#"mod foo {
+ pub mod goo {
+ pub const A_CONSTANT: i32 = $0;
+ }
+}
+fn bar() -> i32 {
+ foo::goo::A_CONSTANT
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_constant_with_not_exist_longer_path() {
+ check_assist(
+ generate_constant,
+ r#"fn bar() -> i32 {
+ foo::goo::A_CON$0STANT
+}"#,
+ r#"mod foo {
+ pub mod goo {
+ pub const A_CONSTANT: i32 = $0;
+ }
+}
+fn bar() -> i32 {
+ foo::goo::A_CONSTANT
+}"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_enum_variant.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_enum_variant.rs
new file mode 100644
index 000000000..5e9995a98
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_enum_variant.rs
@@ -0,0 +1,179 @@
+use ide_db::{famous_defs::FamousDefs, RootDatabase};
+use syntax::ast::{self, AstNode, HasName};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: generate_default_from_enum_variant
+//
+// Adds a Default impl for an enum using a variant.
+//
+// ```
+// enum Version {
+// Undefined,
+// Minor$0,
+// Major,
+// }
+// ```
+// ->
+// ```
+// enum Version {
+// Undefined,
+// Minor,
+// Major,
+// }
+//
+// impl Default for Version {
+// fn default() -> Self {
+// Self::Minor
+// }
+// }
+// ```
+pub(crate) fn generate_default_from_enum_variant(
+ acc: &mut Assists,
+ ctx: &AssistContext<'_>,
+) -> Option<()> {
+ let variant = ctx.find_node_at_offset::<ast::Variant>()?;
+ let variant_name = variant.name()?;
+ let enum_name = variant.parent_enum().name()?;
+ if !matches!(variant.kind(), ast::StructKind::Unit) {
+ cov_mark::hit!(test_gen_default_on_non_unit_variant_not_implemented);
+ return None;
+ }
+
+ if existing_default_impl(&ctx.sema, &variant).is_some() {
+ cov_mark::hit!(test_gen_default_impl_already_exists);
+ return None;
+ }
+
+ let target = variant.syntax().text_range();
+ acc.add(
+ AssistId("generate_default_from_enum_variant", AssistKind::Generate),
+ "Generate `Default` impl from this enum variant",
+ target,
+ |edit| {
+ let start_offset = variant.parent_enum().syntax().text_range().end();
+ let buf = format!(
+ r#"
+
+impl Default for {0} {{
+ fn default() -> Self {{
+ Self::{1}
+ }}
+}}"#,
+ enum_name, variant_name
+ );
+ edit.insert(start_offset, buf);
+ },
+ )
+}
+
+fn existing_default_impl(
+ sema: &'_ hir::Semantics<'_, RootDatabase>,
+ variant: &ast::Variant,
+) -> Option<()> {
+ let variant = sema.to_def(variant)?;
+ let enum_ = variant.parent_enum(sema.db);
+ let krate = enum_.module(sema.db).krate();
+
+ let default_trait = FamousDefs(sema, krate).core_default_Default()?;
+ let enum_type = enum_.ty(sema.db);
+
+ if enum_type.impls_trait(sema.db, default_trait, &[]) {
+ Some(())
+ } else {
+ None
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn test_generate_default_from_variant() {
+ check_assist(
+ generate_default_from_enum_variant,
+ r#"
+//- minicore: default
+enum Variant {
+ Undefined,
+ Minor$0,
+ Major,
+}
+"#,
+ r#"
+enum Variant {
+ Undefined,
+ Minor,
+ Major,
+}
+
+impl Default for Variant {
+ fn default() -> Self {
+ Self::Minor
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_default_already_implemented() {
+ cov_mark::check!(test_gen_default_impl_already_exists);
+ check_assist_not_applicable(
+ generate_default_from_enum_variant,
+ r#"
+//- minicore: default
+enum Variant {
+ Undefined,
+ Minor$0,
+ Major,
+}
+
+impl Default for Variant {
+ fn default() -> Self {
+ Self::Minor
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_add_from_impl_no_element() {
+ cov_mark::check!(test_gen_default_on_non_unit_variant_not_implemented);
+ check_assist_not_applicable(
+ generate_default_from_enum_variant,
+ r#"
+//- minicore: default
+enum Variant {
+ Undefined,
+ Minor(u32)$0,
+ Major,
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_default_from_variant_with_one_variant() {
+ check_assist(
+ generate_default_from_enum_variant,
+ r#"
+//- minicore: default
+enum Variant { Undefi$0ned }
+"#,
+ r#"
+enum Variant { Undefined }
+
+impl Default for Variant {
+ fn default() -> Self {
+ Self::Undefined
+ }
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs
new file mode 100644
index 000000000..cbd33de19
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs
@@ -0,0 +1,657 @@
+use ide_db::famous_defs::FamousDefs;
+use itertools::Itertools;
+use stdx::format_to;
+use syntax::{
+ ast::{self, HasGenericParams, HasName, HasTypeBounds, Impl},
+ AstNode,
+};
+
+use crate::{
+ assist_context::{AssistContext, Assists},
+ AssistId,
+};
+
+// Assist: generate_default_from_new
+//
+// Generates default implementation from new method.
+//
+// ```
+// struct Example { _inner: () }
+//
+// impl Example {
+// pub fn n$0ew() -> Self {
+// Self { _inner: () }
+// }
+// }
+// ```
+// ->
+// ```
+// struct Example { _inner: () }
+//
+// impl Example {
+// pub fn new() -> Self {
+// Self { _inner: () }
+// }
+// }
+//
+// impl Default for Example {
+// fn default() -> Self {
+// Self::new()
+// }
+// }
+// ```
+pub(crate) fn generate_default_from_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let fn_node = ctx.find_node_at_offset::<ast::Fn>()?;
+ let fn_name = fn_node.name()?;
+
+ if fn_name.text() != "new" {
+ cov_mark::hit!(other_function_than_new);
+ return None;
+ }
+
+ if fn_node.param_list()?.params().next().is_some() {
+ cov_mark::hit!(new_function_with_parameters);
+ return None;
+ }
+
+ let impl_ = fn_node.syntax().ancestors().into_iter().find_map(ast::Impl::cast)?;
+ if is_default_implemented(ctx, &impl_) {
+ cov_mark::hit!(default_block_is_already_present);
+ cov_mark::hit!(struct_in_module_with_default);
+ return None;
+ }
+
+ let insert_location = impl_.syntax().text_range();
+
+ acc.add(
+ AssistId("generate_default_from_new", crate::AssistKind::Generate),
+ "Generate a Default impl from a new fn",
+ insert_location,
+ move |builder| {
+ let default_code = " fn default() -> Self {
+ Self::new()
+ }";
+ let code = generate_trait_impl_text_from_impl(&impl_, "Default", default_code);
+ builder.insert(insert_location.end(), code);
+ },
+ )
+}
+
+fn generate_trait_impl_text_from_impl(impl_: &ast::Impl, trait_text: &str, code: &str) -> String {
+ let generic_params = impl_.generic_param_list();
+ let mut buf = String::with_capacity(code.len());
+ buf.push_str("\n\n");
+ buf.push_str("impl");
+
+ if let Some(generic_params) = &generic_params {
+ let lifetimes = generic_params.lifetime_params().map(|lt| format!("{}", lt.syntax()));
+ let toc_params = generic_params.type_or_const_params().map(|toc_param| match toc_param {
+ ast::TypeOrConstParam::Type(type_param) => {
+ let mut buf = String::new();
+ if let Some(it) = type_param.name() {
+ format_to!(buf, "{}", it.syntax());
+ }
+ if let Some(it) = type_param.colon_token() {
+ format_to!(buf, "{} ", it);
+ }
+ if let Some(it) = type_param.type_bound_list() {
+ format_to!(buf, "{}", it.syntax());
+ }
+ buf
+ }
+ ast::TypeOrConstParam::Const(const_param) => const_param.syntax().to_string(),
+ });
+ let generics = lifetimes.chain(toc_params).format(", ");
+ format_to!(buf, "<{}>", generics);
+ }
+
+ buf.push(' ');
+ buf.push_str(trait_text);
+ buf.push_str(" for ");
+ buf.push_str(&impl_.self_ty().unwrap().syntax().text().to_string());
+
+ match impl_.where_clause() {
+ Some(where_clause) => {
+ format_to!(buf, "\n{}\n{{\n{}\n}}", where_clause, code);
+ }
+ None => {
+ format_to!(buf, " {{\n{}\n}}", code);
+ }
+ }
+
+ buf
+}
+
+fn is_default_implemented(ctx: &AssistContext<'_>, impl_: &Impl) -> bool {
+ let db = ctx.sema.db;
+ let impl_ = ctx.sema.to_def(impl_);
+ let impl_def = match impl_ {
+ Some(value) => value,
+ None => return false,
+ };
+
+ let ty = impl_def.self_ty(db);
+ let krate = impl_def.module(db).krate();
+ let default = FamousDefs(&ctx.sema, krate).core_default_Default();
+ let default_trait = match default {
+ Some(value) => value,
+ None => return false,
+ };
+
+ ty.impls_trait(db, default_trait, &[])
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn generate_default() {
+ check_assist(
+ generate_default_from_new,
+ r#"
+//- minicore: default
+struct Example { _inner: () }
+
+impl Example {
+ pub fn ne$0w() -> Self {
+ Self { _inner: () }
+ }
+}
+
+fn main() {}
+"#,
+ r#"
+struct Example { _inner: () }
+
+impl Example {
+ pub fn new() -> Self {
+ Self { _inner: () }
+ }
+}
+
+impl Default for Example {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+fn main() {}
+"#,
+ );
+ }
+
+ #[test]
+ fn generate_default2() {
+ check_assist(
+ generate_default_from_new,
+ r#"
+//- minicore: default
+struct Test { value: u32 }
+
+impl Test {
+ pub fn ne$0w() -> Self {
+ Self { value: 0 }
+ }
+}
+"#,
+ r#"
+struct Test { value: u32 }
+
+impl Test {
+ pub fn new() -> Self {
+ Self { value: 0 }
+ }
+}
+
+impl Default for Test {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn new_function_with_generic() {
+ check_assist(
+ generate_default_from_new,
+ r#"
+//- minicore: default
+pub struct Foo<T> {
+ _bar: *mut T,
+}
+
+impl<T> Foo<T> {
+ pub fn ne$0w() -> Self {
+ unimplemented!()
+ }
+}
+"#,
+ r#"
+pub struct Foo<T> {
+ _bar: *mut T,
+}
+
+impl<T> Foo<T> {
+ pub fn new() -> Self {
+ unimplemented!()
+ }
+}
+
+impl<T> Default for Foo<T> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn new_function_with_generics() {
+ check_assist(
+ generate_default_from_new,
+ r#"
+//- minicore: default
+pub struct Foo<T, B> {
+ _tars: *mut T,
+ _bar: *mut B,
+}
+
+impl<T, B> Foo<T, B> {
+ pub fn ne$0w() -> Self {
+ unimplemented!()
+ }
+}
+"#,
+ r#"
+pub struct Foo<T, B> {
+ _tars: *mut T,
+ _bar: *mut B,
+}
+
+impl<T, B> Foo<T, B> {
+ pub fn new() -> Self {
+ unimplemented!()
+ }
+}
+
+impl<T, B> Default for Foo<T, B> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn new_function_with_generic_and_bound() {
+ check_assist(
+ generate_default_from_new,
+ r#"
+//- minicore: default
+pub struct Foo<T> {
+ t: T,
+}
+
+impl<T: From<i32>> Foo<T> {
+ pub fn ne$0w() -> Self {
+ Foo { t: 0.into() }
+ }
+}
+"#,
+ r#"
+pub struct Foo<T> {
+ t: T,
+}
+
+impl<T: From<i32>> Foo<T> {
+ pub fn new() -> Self {
+ Foo { t: 0.into() }
+ }
+}
+
+impl<T: From<i32>> Default for Foo<T> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn new_function_with_generics_and_bounds() {
+ check_assist(
+ generate_default_from_new,
+ r#"
+//- minicore: default
+pub struct Foo<T, B> {
+ _tars: T,
+ _bar: B,
+}
+
+impl<T: From<i32>, B: From<i64>> Foo<T, B> {
+ pub fn ne$0w() -> Self {
+ unimplemented!()
+ }
+}
+"#,
+ r#"
+pub struct Foo<T, B> {
+ _tars: T,
+ _bar: B,
+}
+
+impl<T: From<i32>, B: From<i64>> Foo<T, B> {
+ pub fn new() -> Self {
+ unimplemented!()
+ }
+}
+
+impl<T: From<i32>, B: From<i64>> Default for Foo<T, B> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn new_function_with_generic_and_where() {
+ check_assist(
+ generate_default_from_new,
+ r#"
+//- minicore: default
+pub struct Foo<T> {
+ t: T,
+}
+
+impl<T: From<i32>> Foo<T>
+where
+ Option<T>: Debug
+{
+ pub fn ne$0w() -> Self {
+ Foo { t: 0.into() }
+ }
+}
+"#,
+ r#"
+pub struct Foo<T> {
+ t: T,
+}
+
+impl<T: From<i32>> Foo<T>
+where
+ Option<T>: Debug
+{
+ pub fn new() -> Self {
+ Foo { t: 0.into() }
+ }
+}
+
+impl<T: From<i32>> Default for Foo<T>
+where
+ Option<T>: Debug
+{
+ fn default() -> Self {
+ Self::new()
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn new_function_with_generics_and_wheres() {
+ check_assist(
+ generate_default_from_new,
+ r#"
+//- minicore: default
+pub struct Foo<T, B> {
+ _tars: T,
+ _bar: B,
+}
+
+impl<T: From<i32>, B: From<i64>> Foo<T, B>
+where
+ Option<T>: Debug, Option<B>: Debug,
+{
+ pub fn ne$0w() -> Self {
+ unimplemented!()
+ }
+}
+"#,
+ r#"
+pub struct Foo<T, B> {
+ _tars: T,
+ _bar: B,
+}
+
+impl<T: From<i32>, B: From<i64>> Foo<T, B>
+where
+ Option<T>: Debug, Option<B>: Debug,
+{
+ pub fn new() -> Self {
+ unimplemented!()
+ }
+}
+
+impl<T: From<i32>, B: From<i64>> Default for Foo<T, B>
+where
+ Option<T>: Debug, Option<B>: Debug,
+{
+ fn default() -> Self {
+ Self::new()
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn new_function_with_parameters() {
+ cov_mark::check!(new_function_with_parameters);
+ check_assist_not_applicable(
+ generate_default_from_new,
+ r#"
+//- minicore: default
+struct Example { _inner: () }
+
+impl Example {
+ pub fn $0new(value: ()) -> Self {
+ Self { _inner: value }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn other_function_than_new() {
+ cov_mark::check!(other_function_than_new);
+ check_assist_not_applicable(
+ generate_default_from_new,
+ r#"
+struct Example { _inner: () }
+
+impl Example {
+ pub fn a$0dd() -> Self {
+ Self { _inner: () }
+ }
+}
+
+"#,
+ );
+ }
+
+ #[test]
+ fn default_block_is_already_present() {
+ cov_mark::check!(default_block_is_already_present);
+ check_assist_not_applicable(
+ generate_default_from_new,
+ r#"
+//- minicore: default
+struct Example { _inner: () }
+
+impl Example {
+ pub fn n$0ew() -> Self {
+ Self { _inner: () }
+ }
+}
+
+impl Default for Example {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn standalone_new_function() {
+ check_assist_not_applicable(
+ generate_default_from_new,
+ r#"
+fn n$0ew() -> u32 {
+ 0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn multiple_struct_blocks() {
+ check_assist(
+ generate_default_from_new,
+ r#"
+//- minicore: default
+struct Example { _inner: () }
+struct Test { value: u32 }
+
+impl Example {
+ pub fn new$0() -> Self {
+ Self { _inner: () }
+ }
+}
+"#,
+ r#"
+struct Example { _inner: () }
+struct Test { value: u32 }
+
+impl Example {
+ pub fn new() -> Self {
+ Self { _inner: () }
+ }
+}
+
+impl Default for Example {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn when_struct_is_after_impl() {
+ check_assist(
+ generate_default_from_new,
+ r#"
+//- minicore: default
+impl Example {
+ pub fn $0new() -> Self {
+ Self { _inner: () }
+ }
+}
+
+struct Example { _inner: () }
+"#,
+ r#"
+impl Example {
+ pub fn new() -> Self {
+ Self { _inner: () }
+ }
+}
+
+impl Default for Example {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+struct Example { _inner: () }
+"#,
+ );
+ }
+
+ #[test]
+ fn struct_in_module() {
+ check_assist(
+ generate_default_from_new,
+ r#"
+//- minicore: default
+mod test {
+ struct Example { _inner: () }
+
+ impl Example {
+ pub fn n$0ew() -> Self {
+ Self { _inner: () }
+ }
+ }
+}
+"#,
+ r#"
+mod test {
+ struct Example { _inner: () }
+
+ impl Example {
+ pub fn new() -> Self {
+ Self { _inner: () }
+ }
+ }
+
+impl Default for Example {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn struct_in_module_with_default() {
+ cov_mark::check!(struct_in_module_with_default);
+ check_assist_not_applicable(
+ generate_default_from_new,
+ r#"
+//- minicore: default
+mod test {
+ struct Example { _inner: () }
+
+ impl Example {
+ pub fn n$0ew() -> Self {
+ Self { _inner: () }
+ }
+ }
+
+ impl Default for Example {
+ fn default() -> Self {
+ Self::new()
+ }
+ }
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs
new file mode 100644
index 000000000..85b193663
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs
@@ -0,0 +1,334 @@
+use hir::{self, HasCrate, HasSource, HasVisibility};
+use syntax::ast::{self, make, AstNode, HasGenericParams, HasName, HasVisibility as _};
+
+use crate::{
+ utils::{convert_param_list_to_arg_list, find_struct_impl, render_snippet, Cursor},
+ AssistContext, AssistId, AssistKind, Assists, GroupLabel,
+};
+use syntax::ast::edit::AstNodeEdit;
+
+// Assist: generate_delegate_methods
+//
+// Generate delegate methods.
+//
+// ```
+// struct Age(u8);
+// impl Age {
+// fn age(&self) -> u8 {
+// self.0
+// }
+// }
+//
+// struct Person {
+// ag$0e: Age,
+// }
+// ```
+// ->
+// ```
+// struct Age(u8);
+// impl Age {
+// fn age(&self) -> u8 {
+// self.0
+// }
+// }
+//
+// struct Person {
+// age: Age,
+// }
+//
+// impl Person {
+// $0fn age(&self) -> u8 {
+// self.age.age()
+// }
+// }
+// ```
+pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
+ let strukt_name = strukt.name()?;
+ let current_module = ctx.sema.scope(strukt.syntax())?.module();
+
+ let (field_name, field_ty, target) = match ctx.find_node_at_offset::<ast::RecordField>() {
+ Some(field) => {
+ let field_name = field.name()?;
+ let field_ty = field.ty()?;
+ (format!("{}", field_name), field_ty, field.syntax().text_range())
+ }
+ None => {
+ let field = ctx.find_node_at_offset::<ast::TupleField>()?;
+ let field_list = ctx.find_node_at_offset::<ast::TupleFieldList>()?;
+ let field_list_index = field_list.fields().position(|it| it == field)?;
+ let field_ty = field.ty()?;
+ (format!("{}", field_list_index), field_ty, field.syntax().text_range())
+ }
+ };
+
+ let sema_field_ty = ctx.sema.resolve_type(&field_ty)?;
+ let krate = sema_field_ty.krate(ctx.db());
+ let mut methods = vec![];
+ sema_field_ty.iterate_assoc_items(ctx.db(), krate, |item| {
+ if let hir::AssocItem::Function(f) = item {
+ if f.self_param(ctx.db()).is_some() && f.is_visible_from(ctx.db(), current_module) {
+ methods.push(f)
+ }
+ }
+ Option::<()>::None
+ });
+
+ for method in methods {
+ let adt = ast::Adt::Struct(strukt.clone());
+ let name = method.name(ctx.db()).to_string();
+ let impl_def = find_struct_impl(ctx, &adt, &name).flatten();
+ acc.add_group(
+ &GroupLabel("Generate delegate methods…".to_owned()),
+ AssistId("generate_delegate_methods", AssistKind::Generate),
+ format!("Generate delegate for `{}.{}()`", field_name, method.name(ctx.db())),
+ target,
+ |builder| {
+ // Create the function
+ let method_source = match method.source(ctx.db()) {
+ Some(source) => source.value,
+ None => return,
+ };
+ let method_name = method.name(ctx.db());
+ let vis = method_source.visibility();
+ let name = make::name(&method.name(ctx.db()).to_string());
+ let params =
+ method_source.param_list().unwrap_or_else(|| make::param_list(None, []));
+ let type_params = method_source.generic_param_list();
+ let arg_list = match method_source.param_list() {
+ Some(list) => convert_param_list_to_arg_list(list),
+ None => make::arg_list([]),
+ };
+ let tail_expr = make::expr_method_call(
+ make::ext::field_from_idents(["self", &field_name]).unwrap(), // This unwrap is ok because we have at least 1 arg in the list
+ make::name_ref(&method_name.to_string()),
+ arg_list,
+ );
+ let body = make::block_expr([], Some(tail_expr));
+ let ret_type = method_source.ret_type();
+ let is_async = method_source.async_token().is_some();
+ let f = make::fn_(vis, name, type_params, params, body, ret_type, is_async)
+ .indent(ast::edit::IndentLevel(1))
+ .clone_for_update();
+
+ let cursor = Cursor::Before(f.syntax());
+
+ // Create or update an impl block, attach the function to it,
+ // then insert into our code.
+ match impl_def {
+ Some(impl_def) => {
+ // Remember where in our source our `impl` block lives.
+ let impl_def = impl_def.clone_for_update();
+ let old_range = impl_def.syntax().text_range();
+
+ // Attach the function to the impl block
+ let assoc_items = impl_def.get_or_create_assoc_item_list();
+ assoc_items.add_item(f.clone().into());
+
+ // Update the impl block.
+ match ctx.config.snippet_cap {
+ Some(cap) => {
+ let snippet = render_snippet(cap, impl_def.syntax(), cursor);
+ builder.replace_snippet(cap, old_range, snippet);
+ }
+ None => {
+ builder.replace(old_range, impl_def.syntax().to_string());
+ }
+ }
+ }
+ None => {
+ // Attach the function to the impl block
+ let name = &strukt_name.to_string();
+ let params = strukt.generic_param_list();
+ let ty_params = params.clone();
+ let impl_def = make::impl_(make::ext::ident_path(name), params, ty_params)
+ .clone_for_update();
+ let assoc_items = impl_def.get_or_create_assoc_item_list();
+ assoc_items.add_item(f.clone().into());
+
+ // Insert the impl block.
+ match ctx.config.snippet_cap {
+ Some(cap) => {
+ let offset = strukt.syntax().text_range().end();
+ let snippet = render_snippet(cap, impl_def.syntax(), cursor);
+ let snippet = format!("\n\n{}", snippet);
+ builder.insert_snippet(cap, offset, snippet);
+ }
+ None => {
+ let offset = strukt.syntax().text_range().end();
+ let snippet = format!("\n\n{}", impl_def.syntax());
+ builder.insert(offset, snippet);
+ }
+ }
+ }
+ }
+ },
+ )?;
+ }
+ Some(())
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn test_generate_delegate_create_impl_block() {
+ check_assist(
+ generate_delegate_methods,
+ r#"
+struct Age(u8);
+impl Age {
+ fn age(&self) -> u8 {
+ self.0
+ }
+}
+
+struct Person {
+ ag$0e: Age,
+}"#,
+ r#"
+struct Age(u8);
+impl Age {
+ fn age(&self) -> u8 {
+ self.0
+ }
+}
+
+struct Person {
+ age: Age,
+}
+
+impl Person {
+ $0fn age(&self) -> u8 {
+ self.age.age()
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_delegate_update_impl_block() {
+ check_assist(
+ generate_delegate_methods,
+ r#"
+struct Age(u8);
+impl Age {
+ fn age(&self) -> u8 {
+ self.0
+ }
+}
+
+struct Person {
+ ag$0e: Age,
+}
+
+impl Person {}"#,
+ r#"
+struct Age(u8);
+impl Age {
+ fn age(&self) -> u8 {
+ self.0
+ }
+}
+
+struct Person {
+ age: Age,
+}
+
+impl Person {
+ $0fn age(&self) -> u8 {
+ self.age.age()
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_delegate_tuple_struct() {
+ check_assist(
+ generate_delegate_methods,
+ r#"
+struct Age(u8);
+impl Age {
+ fn age(&self) -> u8 {
+ self.0
+ }
+}
+
+struct Person(A$0ge);"#,
+ r#"
+struct Age(u8);
+impl Age {
+ fn age(&self) -> u8 {
+ self.0
+ }
+}
+
+struct Person(Age);
+
+impl Person {
+ $0fn age(&self) -> u8 {
+ self.0.age()
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_delegate_enable_all_attributes() {
+ check_assist(
+ generate_delegate_methods,
+ r#"
+struct Age<T>(T);
+impl<T> Age<T> {
+ pub(crate) async fn age<J, 'a>(&'a mut self, ty: T, arg: J) -> T {
+ self.0
+ }
+}
+
+struct Person<T> {
+ ag$0e: Age<T>,
+}"#,
+ r#"
+struct Age<T>(T);
+impl<T> Age<T> {
+ pub(crate) async fn age<J, 'a>(&'a mut self, ty: T, arg: J) -> T {
+ self.0
+ }
+}
+
+struct Person<T> {
+ age: Age<T>,
+}
+
+impl<T> Person<T> {
+ $0pub(crate) async fn age<J, 'a>(&'a mut self, ty: T, arg: J) -> T {
+ self.age.age(ty, arg)
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_delegate_visibility() {
+ check_assist_not_applicable(
+ generate_delegate_methods,
+ r#"
+mod m {
+ pub struct Age(u8);
+ impl Age {
+ fn age(&self) -> u8 {
+ self.0
+ }
+ }
+}
+
+struct Person {
+ ag$0e: m::Age,
+}"#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_deref.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_deref.rs
new file mode 100644
index 000000000..b9637ee8d
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_deref.rs
@@ -0,0 +1,343 @@
+use std::fmt::Display;
+
+use hir::{ModPath, ModuleDef};
+use ide_db::{famous_defs::FamousDefs, RootDatabase};
+use syntax::{
+ ast::{self, HasName},
+ AstNode, SyntaxNode,
+};
+
+use crate::{
+ assist_context::{AssistBuilder, AssistContext, Assists},
+ utils::generate_trait_impl_text,
+ AssistId, AssistKind,
+};
+
+// Assist: generate_deref
+//
+// Generate `Deref` impl using the given struct field.
+//
+// ```
+// # //- minicore: deref, deref_mut
+// struct A;
+// struct B {
+// $0a: A
+// }
+// ```
+// ->
+// ```
+// struct A;
+// struct B {
+// a: A
+// }
+//
+// impl core::ops::Deref for B {
+// type Target = A;
+//
+// fn deref(&self) -> &Self::Target {
+// &self.a
+// }
+// }
+// ```
+pub(crate) fn generate_deref(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ generate_record_deref(acc, ctx).or_else(|| generate_tuple_deref(acc, ctx))
+}
+
+fn generate_record_deref(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
+ let field = ctx.find_node_at_offset::<ast::RecordField>()?;
+
+ let deref_type_to_generate = match existing_deref_impl(&ctx.sema, &strukt) {
+ None => DerefType::Deref,
+ Some(DerefType::Deref) => DerefType::DerefMut,
+ Some(DerefType::DerefMut) => {
+ cov_mark::hit!(test_add_record_deref_impl_already_exists);
+ return None;
+ }
+ };
+
+ let module = ctx.sema.to_def(&strukt)?.module(ctx.db());
+ let trait_ = deref_type_to_generate.to_trait(&ctx.sema, module.krate())?;
+ let trait_path = module.find_use_path(ctx.db(), ModuleDef::Trait(trait_))?;
+
+ let field_type = field.ty()?;
+ let field_name = field.name()?;
+ let target = field.syntax().text_range();
+ acc.add(
+ AssistId("generate_deref", AssistKind::Generate),
+ format!("Generate `{:?}` impl using `{}`", deref_type_to_generate, field_name),
+ target,
+ |edit| {
+ generate_edit(
+ edit,
+ strukt,
+ field_type.syntax(),
+ field_name.syntax(),
+ deref_type_to_generate,
+ trait_path,
+ )
+ },
+ )
+}
+
+fn generate_tuple_deref(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
+ let field = ctx.find_node_at_offset::<ast::TupleField>()?;
+ let field_list = ctx.find_node_at_offset::<ast::TupleFieldList>()?;
+ let field_list_index =
+ field_list.syntax().children().into_iter().position(|s| &s == field.syntax())?;
+
+ let deref_type_to_generate = match existing_deref_impl(&ctx.sema, &strukt) {
+ None => DerefType::Deref,
+ Some(DerefType::Deref) => DerefType::DerefMut,
+ Some(DerefType::DerefMut) => {
+ cov_mark::hit!(test_add_field_deref_impl_already_exists);
+ return None;
+ }
+ };
+
+ let module = ctx.sema.to_def(&strukt)?.module(ctx.db());
+ let trait_ = deref_type_to_generate.to_trait(&ctx.sema, module.krate())?;
+ let trait_path = module.find_use_path(ctx.db(), ModuleDef::Trait(trait_))?;
+
+ let field_type = field.ty()?;
+ let target = field.syntax().text_range();
+ acc.add(
+ AssistId("generate_deref", AssistKind::Generate),
+ format!("Generate `{:?}` impl using `{}`", deref_type_to_generate, field.syntax()),
+ target,
+ |edit| {
+ generate_edit(
+ edit,
+ strukt,
+ field_type.syntax(),
+ field_list_index,
+ deref_type_to_generate,
+ trait_path,
+ )
+ },
+ )
+}
+
+fn generate_edit(
+ edit: &mut AssistBuilder,
+ strukt: ast::Struct,
+ field_type_syntax: &SyntaxNode,
+ field_name: impl Display,
+ deref_type: DerefType,
+ trait_path: ModPath,
+) {
+ let start_offset = strukt.syntax().text_range().end();
+ let impl_code = match deref_type {
+ DerefType::Deref => format!(
+ r#" type Target = {0};
+
+ fn deref(&self) -> &Self::Target {{
+ &self.{1}
+ }}"#,
+ field_type_syntax, field_name
+ ),
+ DerefType::DerefMut => format!(
+ r#" fn deref_mut(&mut self) -> &mut Self::Target {{
+ &mut self.{}
+ }}"#,
+ field_name
+ ),
+ };
+ let strukt_adt = ast::Adt::Struct(strukt);
+ let deref_impl = generate_trait_impl_text(&strukt_adt, &trait_path.to_string(), &impl_code);
+ edit.insert(start_offset, deref_impl);
+}
+
+fn existing_deref_impl(
+ sema: &hir::Semantics<'_, RootDatabase>,
+ strukt: &ast::Struct,
+) -> Option<DerefType> {
+ let strukt = sema.to_def(strukt)?;
+ let krate = strukt.module(sema.db).krate();
+
+ let deref_trait = FamousDefs(sema, krate).core_ops_Deref()?;
+ let deref_mut_trait = FamousDefs(sema, krate).core_ops_DerefMut()?;
+ let strukt_type = strukt.ty(sema.db);
+
+ if strukt_type.impls_trait(sema.db, deref_trait, &[]) {
+ if strukt_type.impls_trait(sema.db, deref_mut_trait, &[]) {
+ Some(DerefType::DerefMut)
+ } else {
+ Some(DerefType::Deref)
+ }
+ } else {
+ None
+ }
+}
+
+#[derive(Debug)]
+enum DerefType {
+ Deref,
+ DerefMut,
+}
+
+impl DerefType {
+ fn to_trait(
+ &self,
+ sema: &hir::Semantics<'_, RootDatabase>,
+ krate: hir::Crate,
+ ) -> Option<hir::Trait> {
+ match self {
+ DerefType::Deref => FamousDefs(sema, krate).core_ops_Deref(),
+ DerefType::DerefMut => FamousDefs(sema, krate).core_ops_DerefMut(),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn test_generate_record_deref() {
+ check_assist(
+ generate_deref,
+ r#"
+//- minicore: deref
+struct A { }
+struct B { $0a: A }"#,
+ r#"
+struct A { }
+struct B { a: A }
+
+impl core::ops::Deref for B {
+ type Target = A;
+
+ fn deref(&self) -> &Self::Target {
+ &self.a
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_record_deref_short_path() {
+ check_assist(
+ generate_deref,
+ r#"
+//- minicore: deref
+use core::ops::Deref;
+struct A { }
+struct B { $0a: A }"#,
+ r#"
+use core::ops::Deref;
+struct A { }
+struct B { a: A }
+
+impl Deref for B {
+ type Target = A;
+
+ fn deref(&self) -> &Self::Target {
+ &self.a
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_field_deref_idx_0() {
+ check_assist(
+ generate_deref,
+ r#"
+//- minicore: deref
+struct A { }
+struct B($0A);"#,
+ r#"
+struct A { }
+struct B(A);
+
+impl core::ops::Deref for B {
+ type Target = A;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}"#,
+ );
+ }
+ #[test]
+ fn test_generate_field_deref_idx_1() {
+ check_assist(
+ generate_deref,
+ r#"
+//- minicore: deref
+struct A { }
+struct B(u8, $0A);"#,
+ r#"
+struct A { }
+struct B(u8, A);
+
+impl core::ops::Deref for B {
+ type Target = A;
+
+ fn deref(&self) -> &Self::Target {
+ &self.1
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_generates_derefmut_when_deref_present() {
+ check_assist(
+ generate_deref,
+ r#"
+//- minicore: deref, deref_mut
+struct B { $0a: u8 }
+
+impl core::ops::Deref for B {}
+"#,
+ r#"
+struct B { a: u8 }
+
+impl core::ops::DerefMut for B {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.a
+ }
+}
+
+impl core::ops::Deref for B {}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_record_deref_not_applicable_if_already_impl() {
+ cov_mark::check!(test_add_record_deref_impl_already_exists);
+ check_assist_not_applicable(
+ generate_deref,
+ r#"
+//- minicore: deref, deref_mut
+struct A { }
+struct B { $0a: A }
+
+impl core::ops::Deref for B {}
+impl core::ops::DerefMut for B {}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_generate_field_deref_not_applicable_if_already_impl() {
+ cov_mark::check!(test_add_field_deref_impl_already_exists);
+ check_assist_not_applicable(
+ generate_deref,
+ r#"
+//- minicore: deref, deref_mut
+struct A { }
+struct B($0A)
+
+impl core::ops::Deref for B {}
+impl core::ops::DerefMut for B {}
+"#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_derive.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_derive.rs
new file mode 100644
index 000000000..339245b94
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_derive.rs
@@ -0,0 +1,132 @@
+use syntax::{
+ ast::{self, AstNode, HasAttrs},
+ SyntaxKind::{COMMENT, WHITESPACE},
+ TextSize,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: generate_derive
+//
+// Adds a new `#[derive()]` clause to a struct or enum.
+//
+// ```
+// struct Point {
+// x: u32,
+// y: u32,$0
+// }
+// ```
+// ->
+// ```
+// #[derive($0)]
+// struct Point {
+// x: u32,
+// y: u32,
+// }
+// ```
+pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let cap = ctx.config.snippet_cap?;
+ let nominal = ctx.find_node_at_offset::<ast::Adt>()?;
+ let node_start = derive_insertion_offset(&nominal)?;
+ let target = nominal.syntax().text_range();
+ acc.add(
+ AssistId("generate_derive", AssistKind::Generate),
+ "Add `#[derive]`",
+ target,
+ |builder| {
+ let derive_attr = nominal
+ .attrs()
+ .filter_map(|x| x.as_simple_call())
+ .filter(|(name, _arg)| name == "derive")
+ .map(|(_name, arg)| arg)
+ .next();
+ match derive_attr {
+ None => {
+ builder.insert_snippet(cap, node_start, "#[derive($0)]\n");
+ }
+ Some(tt) => {
+ // Just move the cursor.
+ builder.insert_snippet(
+ cap,
+ tt.syntax().text_range().end() - TextSize::of(')'),
+ "$0",
+ )
+ }
+ };
+ },
+ )
+}
+
+// Insert `derive` after doc comments.
+fn derive_insertion_offset(nominal: &ast::Adt) -> Option<TextSize> {
+ let non_ws_child = nominal
+ .syntax()
+ .children_with_tokens()
+ .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?;
+ Some(non_ws_child.text_range().start())
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_target};
+
+ use super::*;
+
+ #[test]
+ fn add_derive_new() {
+ check_assist(
+ generate_derive,
+ "struct Foo { a: i32, $0}",
+ "#[derive($0)]\nstruct Foo { a: i32, }",
+ );
+ check_assist(
+ generate_derive,
+ "struct Foo { $0 a: i32, }",
+ "#[derive($0)]\nstruct Foo { a: i32, }",
+ );
+ }
+
+ #[test]
+ fn add_derive_existing() {
+ check_assist(
+ generate_derive,
+ "#[derive(Clone)]\nstruct Foo { a: i32$0, }",
+ "#[derive(Clone$0)]\nstruct Foo { a: i32, }",
+ );
+ }
+
+ #[test]
+ fn add_derive_new_with_doc_comment() {
+ check_assist(
+ generate_derive,
+ "
+/// `Foo` is a pretty important struct.
+/// It does stuff.
+struct Foo { a: i32$0, }
+ ",
+ "
+/// `Foo` is a pretty important struct.
+/// It does stuff.
+#[derive($0)]
+struct Foo { a: i32, }
+ ",
+ );
+ }
+
+ #[test]
+ fn add_derive_target() {
+ check_assist_target(
+ generate_derive,
+ "
+struct SomeThingIrrelevant;
+/// `Foo` is a pretty important struct.
+/// It does stuff.
+struct Foo { a: i32$0, }
+struct EvenMoreIrrelevant;
+ ",
+ "/// `Foo` is a pretty important struct.
+/// It does stuff.
+struct Foo { a: i32, }",
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_documentation_template.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_documentation_template.rs
new file mode 100644
index 000000000..c91141f8e
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_documentation_template.rs
@@ -0,0 +1,1328 @@
+use hir::{AsAssocItem, HasVisibility, ModuleDef, Visibility};
+use ide_db::assists::{AssistId, AssistKind};
+use itertools::Itertools;
+use stdx::{format_to, to_lower_snake_case};
+use syntax::{
+ algo::skip_whitespace_token,
+ ast::{self, edit::IndentLevel, HasDocComments, HasName},
+ match_ast, AstNode, AstToken,
+};
+
+use crate::assist_context::{AssistContext, Assists};
+
+// Assist: generate_documentation_template
+//
+// Adds a documentation template above a function definition / declaration.
+//
+// ```
+// pub struct S;
+// impl S {
+// pub unsafe fn set_len$0(&mut self, len: usize) -> Result<(), std::io::Error> {
+// /* ... */
+// }
+// }
+// ```
+// ->
+// ```
+// pub struct S;
+// impl S {
+// /// Sets the length of this [`S`].
+// ///
+// /// # Errors
+// ///
+// /// This function will return an error if .
+// ///
+// /// # Safety
+// ///
+// /// .
+// pub unsafe fn set_len(&mut self, len: usize) -> Result<(), std::io::Error> {
+// /* ... */
+// }
+// }
+// ```
+pub(crate) fn generate_documentation_template(
+ acc: &mut Assists,
+ ctx: &AssistContext<'_>,
+) -> Option<()> {
+ let name = ctx.find_node_at_offset::<ast::Name>()?;
+ let ast_func = name.syntax().parent().and_then(ast::Fn::cast)?;
+ if is_in_trait_impl(&ast_func, ctx) || ast_func.doc_comments().next().is_some() {
+ return None;
+ }
+
+ let parent_syntax = ast_func.syntax();
+ let text_range = parent_syntax.text_range();
+ let indent_level = IndentLevel::from_node(parent_syntax);
+
+ acc.add(
+ AssistId("generate_documentation_template", AssistKind::Generate),
+ "Generate a documentation template",
+ text_range,
+ |builder| {
+ // Introduction / short function description before the sections
+ let mut doc_lines = vec![introduction_builder(&ast_func, ctx).unwrap_or(".".into())];
+ // Then come the sections
+ for section_builder in [panics_builder, errors_builder, safety_builder] {
+ if let Some(mut lines) = section_builder(&ast_func) {
+ doc_lines.push("".into());
+ doc_lines.append(&mut lines);
+ }
+ }
+ builder.insert(text_range.start(), documentation_from_lines(doc_lines, indent_level));
+ },
+ )
+}
+
+// Assist: generate_doc_example
+//
+// Generates a rustdoc example when editing an item's documentation.
+//
+// ```
+// /// Adds two numbers.$0
+// pub fn add(a: i32, b: i32) -> i32 { a + b }
+// ```
+// ->
+// ```
+// /// Adds two numbers.
+// ///
+// /// # Examples
+// ///
+// /// ```
+// /// use test::add;
+// ///
+// /// assert_eq!(add(a, b), );
+// /// ```
+// pub fn add(a: i32, b: i32) -> i32 { a + b }
+// ```
+pub(crate) fn generate_doc_example(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let tok: ast::Comment = ctx.find_token_at_offset()?;
+ let node = tok.syntax().parent()?;
+ let last_doc_token =
+ ast::AnyHasDocComments::cast(node.clone())?.doc_comments().last()?.syntax().clone();
+ let next_token = skip_whitespace_token(last_doc_token.next_token()?, syntax::Direction::Next)?;
+
+ let example = match_ast! {
+ match node {
+ ast::Fn(it) => make_example_for_fn(&it, ctx)?,
+ _ => return None,
+ }
+ };
+
+ let mut lines = string_vec_from(&["", "# Examples", "", "```"]);
+ lines.extend(example.lines().map(String::from));
+ lines.push("```".into());
+ let indent_level = IndentLevel::from_node(&node);
+
+ acc.add(
+ AssistId("generate_doc_example", AssistKind::Generate),
+ "Generate a documentation example",
+ node.text_range(),
+ |builder| {
+ builder.insert(
+ next_token.text_range().start(),
+ documentation_from_lines(lines, indent_level),
+ );
+ },
+ )
+}
+
+fn make_example_for_fn(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> Option<String> {
+ if !is_public(ast_func, ctx)? {
+ // Doctests for private items can't actually name the item, so they're pretty useless.
+ return None;
+ }
+
+ if is_in_trait_def(ast_func, ctx) {
+ // This is not yet implemented.
+ return None;
+ }
+
+ let mut example = String::new();
+
+ let is_unsafe = ast_func.unsafe_token().is_some();
+ let param_list = ast_func.param_list()?;
+ let ref_mut_params = ref_mut_params(&param_list);
+ let self_name = self_name(ast_func);
+
+ format_to!(example, "use {};\n\n", build_path(ast_func, ctx)?);
+ if let Some(self_name) = &self_name {
+ if let Some(mtbl) = is_ref_mut_self(ast_func) {
+ let mtbl = if mtbl == true { " mut" } else { "" };
+ format_to!(example, "let{} {} = ;\n", mtbl, self_name);
+ }
+ }
+ for param_name in &ref_mut_params {
+ format_to!(example, "let mut {} = ;\n", param_name);
+ }
+ // Call the function, check result
+ let function_call = function_call(ast_func, &param_list, self_name.as_deref(), is_unsafe)?;
+ if returns_a_value(ast_func, ctx) {
+ if count_parameters(&param_list) < 3 {
+ format_to!(example, "assert_eq!({}, );\n", function_call);
+ } else {
+ format_to!(example, "let result = {};\n", function_call);
+ example.push_str("assert_eq!(result, );\n");
+ }
+ } else {
+ format_to!(example, "{};\n", function_call);
+ }
+ // Check the mutated values
+ if is_ref_mut_self(ast_func) == Some(true) {
+ format_to!(example, "assert_eq!({}, );", self_name?);
+ }
+ for param_name in &ref_mut_params {
+ format_to!(example, "assert_eq!({}, );", param_name);
+ }
+ Some(example)
+}
+
+fn introduction_builder(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> Option<String> {
+ let hir_func = ctx.sema.to_def(ast_func)?;
+ let container = hir_func.as_assoc_item(ctx.db())?.container(ctx.db());
+ if let hir::AssocItemContainer::Impl(imp) = container {
+ let ret_ty = hir_func.ret_type(ctx.db());
+ let self_ty = imp.self_ty(ctx.db());
+ let name = ast_func.name()?.to_string();
+ let linkable_self_ty = self_type_without_lifetimes(ast_func);
+ let linkable_self_ty = linkable_self_ty.as_deref();
+
+ let intro_for_new = || {
+ let is_new = name == "new";
+ if is_new && ret_ty == self_ty {
+ Some(format!("Creates a new [`{}`].", linkable_self_ty?))
+ } else {
+ None
+ }
+ };
+
+ let intro_for_getter = || match (
+ hir_func.self_param(ctx.sema.db),
+ &*hir_func.params_without_self(ctx.sema.db),
+ ) {
+ (Some(self_param), []) if self_param.access(ctx.sema.db) != hir::Access::Owned => {
+ if name.starts_with("as_") || name.starts_with("to_") || name == "get" {
+ return None;
+ }
+ let mut what = name.trim_end_matches("_mut").replace('_', " ");
+ if what == "len" {
+ what = "length".into()
+ }
+ let reference = if ret_ty.is_mutable_reference() {
+ " a mutable reference to"
+ } else if ret_ty.is_reference() {
+ " a reference to"
+ } else {
+ ""
+ };
+ Some(format!("Returns{reference} the {what} of this [`{}`].", linkable_self_ty?))
+ }
+ _ => None,
+ };
+
+ let intro_for_setter = || {
+ if !name.starts_with("set_") {
+ return None;
+ }
+
+ let mut what = name.trim_start_matches("set_").replace('_', " ");
+ if what == "len" {
+ what = "length".into()
+ };
+ Some(format!("Sets the {what} of this [`{}`].", linkable_self_ty?))
+ };
+
+ if let Some(intro) = intro_for_new() {
+ return Some(intro);
+ }
+ if let Some(intro) = intro_for_getter() {
+ return Some(intro);
+ }
+ if let Some(intro) = intro_for_setter() {
+ return Some(intro);
+ }
+ }
+ None
+}
+
+/// Builds an optional `# Panics` section
+fn panics_builder(ast_func: &ast::Fn) -> Option<Vec<String>> {
+ match can_panic(ast_func) {
+ Some(true) => Some(string_vec_from(&["# Panics", "", "Panics if ."])),
+ _ => None,
+ }
+}
+
+/// Builds an optional `# Errors` section
+fn errors_builder(ast_func: &ast::Fn) -> Option<Vec<String>> {
+ match return_type(ast_func)?.to_string().contains("Result") {
+ true => Some(string_vec_from(&["# Errors", "", "This function will return an error if ."])),
+ false => None,
+ }
+}
+
+/// Builds an optional `# Safety` section
+fn safety_builder(ast_func: &ast::Fn) -> Option<Vec<String>> {
+ let is_unsafe = ast_func.unsafe_token().is_some();
+ match is_unsafe {
+ true => Some(string_vec_from(&["# Safety", "", "."])),
+ false => None,
+ }
+}
+
+/// Checks if the function is public / exported
+fn is_public(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> Option<bool> {
+ let hir_func = ctx.sema.to_def(ast_func)?;
+ Some(
+ hir_func.visibility(ctx.db()) == Visibility::Public
+ && all_parent_mods_public(&hir_func, ctx),
+ )
+}
+
+/// Checks that all parent modules of the function are public / exported
+fn all_parent_mods_public(hir_func: &hir::Function, ctx: &AssistContext<'_>) -> bool {
+ let mut module = hir_func.module(ctx.db());
+ loop {
+ if let Some(parent) = module.parent(ctx.db()) {
+ match ModuleDef::from(module).visibility(ctx.db()) {
+ Visibility::Public => module = parent,
+ _ => break false,
+ }
+ } else {
+ break true;
+ }
+ }
+}
+
+/// Returns the name of the current crate
+fn crate_name(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> Option<String> {
+ let krate = ctx.sema.scope(ast_func.syntax())?.krate();
+ Some(krate.display_name(ctx.db())?.to_string())
+}
+
+/// `None` if function without a body; some bool to guess if function can panic
+fn can_panic(ast_func: &ast::Fn) -> Option<bool> {
+ let body = ast_func.body()?.to_string();
+ let can_panic = body.contains("panic!(")
+ // FIXME it would be better to not match `debug_assert*!` macro invocations
+ || body.contains("assert!(")
+ || body.contains(".unwrap()")
+ || body.contains(".expect(");
+ Some(can_panic)
+}
+
+/// Helper function to get the name that should be given to `self` arguments
+fn self_name(ast_func: &ast::Fn) -> Option<String> {
+ self_partial_type(ast_func).map(|name| to_lower_snake_case(&name))
+}
+
+/// Heper function to get the name of the type of `self`
+fn self_type(ast_func: &ast::Fn) -> Option<ast::Type> {
+ ast_func.syntax().ancestors().find_map(ast::Impl::cast).and_then(|i| i.self_ty())
+}
+
+/// Output the real name of `Self` like `MyType<T>`, without the lifetimes.
+fn self_type_without_lifetimes(ast_func: &ast::Fn) -> Option<String> {
+ let path_segment = match self_type(ast_func)? {
+ ast::Type::PathType(path_type) => path_type.path()?.segment()?,
+ _ => return None,
+ };
+ let mut name = path_segment.name_ref()?.to_string();
+ let generics = path_segment.generic_arg_list().into_iter().flat_map(|list| {
+ list.generic_args()
+ .filter(|generic| matches!(generic, ast::GenericArg::TypeArg(_)))
+ .map(|generic| generic.to_string())
+ });
+ let generics: String = generics.format(", ").to_string();
+ if !generics.is_empty() {
+ name.push('<');
+ name.push_str(&generics);
+ name.push('>');
+ }
+ Some(name)
+}
+
+/// Heper function to get the name of the type of `self` without generic arguments
+fn self_partial_type(ast_func: &ast::Fn) -> Option<String> {
+ let mut self_type = self_type(ast_func)?.to_string();
+ if let Some(idx) = self_type.find(|c| ['<', ' '].contains(&c)) {
+ self_type.truncate(idx);
+ }
+ Some(self_type)
+}
+
+/// Helper function to determine if the function is in a trait implementation
+fn is_in_trait_impl(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> bool {
+ ctx.sema
+ .to_def(ast_func)
+ .and_then(|hir_func| hir_func.as_assoc_item(ctx.db()))
+ .and_then(|assoc_item| assoc_item.containing_trait_impl(ctx.db()))
+ .is_some()
+}
+
+/// Helper function to determine if the function definition is in a trait definition
+fn is_in_trait_def(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> bool {
+ ctx.sema
+ .to_def(ast_func)
+ .and_then(|hir_func| hir_func.as_assoc_item(ctx.db()))
+ .and_then(|assoc_item| assoc_item.containing_trait(ctx.db()))
+ .is_some()
+}
+
+/// Returns `None` if no `self` at all, `Some(true)` if there is `&mut self` else `Some(false)`
+fn is_ref_mut_self(ast_func: &ast::Fn) -> Option<bool> {
+ let self_param = ast_func.param_list()?.self_param()?;
+ Some(self_param.mut_token().is_some() && self_param.amp_token().is_some())
+}
+
+/// Helper function to determine if a parameter is `&mut`
+fn is_a_ref_mut_param(param: &ast::Param) -> bool {
+ match param.ty() {
+ Some(ast::Type::RefType(param_ref)) => param_ref.mut_token().is_some(),
+ _ => false,
+ }
+}
+
+/// Helper function to build the list of `&mut` parameters
+fn ref_mut_params(param_list: &ast::ParamList) -> Vec<String> {
+ param_list
+ .params()
+ .filter_map(|param| match is_a_ref_mut_param(&param) {
+ // Maybe better filter the param name (to do this maybe extract a function from
+ // `arguments_from_params`?) in case of a `mut a: &mut T`. Anyway managing most (not
+ // all) cases might be enough, the goal is just to produce a template.
+ true => Some(param.pat()?.to_string()),
+ false => None,
+ })
+ .collect()
+}
+
+/// Helper function to build the comma-separated list of arguments of the function
+fn arguments_from_params(param_list: &ast::ParamList) -> String {
+ let args_iter = param_list.params().map(|param| match param.pat() {
+ // To avoid `mut` in the function call (which would be a nonsense), `Pat` should not be
+ // written as is so its variants must be managed independently. Other variants (for
+ // instance `TuplePat`) could be managed later.
+ Some(ast::Pat::IdentPat(ident_pat)) => match ident_pat.name() {
+ Some(name) => match is_a_ref_mut_param(&param) {
+ true => format!("&mut {}", name),
+ false => name.to_string(),
+ },
+ None => "_".to_string(),
+ },
+ _ => "_".to_string(),
+ });
+ args_iter.format(", ").to_string()
+}
+
+/// Helper function to build a function call. `None` if expected `self_name` was not provided
+fn function_call(
+ ast_func: &ast::Fn,
+ param_list: &ast::ParamList,
+ self_name: Option<&str>,
+ is_unsafe: bool,
+) -> Option<String> {
+ let name = ast_func.name()?;
+ let arguments = arguments_from_params(param_list);
+ let function_call = if param_list.self_param().is_some() {
+ format!("{}.{}({})", self_name?, name, arguments)
+ } else if let Some(implementation) = self_partial_type(ast_func) {
+ format!("{}::{}({})", implementation, name, arguments)
+ } else {
+ format!("{}({})", name, arguments)
+ };
+ match is_unsafe {
+ true => Some(format!("unsafe {{ {} }}", function_call)),
+ false => Some(function_call),
+ }
+}
+
+/// Helper function to count the parameters including `self`
+fn count_parameters(param_list: &ast::ParamList) -> usize {
+ param_list.params().count() + if param_list.self_param().is_some() { 1 } else { 0 }
+}
+
+/// Helper function to transform lines of documentation into a Rust code documentation
+fn documentation_from_lines(doc_lines: Vec<String>, indent_level: IndentLevel) -> String {
+ let mut result = String::new();
+ for doc_line in doc_lines {
+ result.push_str("///");
+ if !doc_line.is_empty() {
+ result.push(' ');
+ result.push_str(&doc_line);
+ }
+ result.push('\n');
+ result.push_str(&indent_level.to_string());
+ }
+ result
+}
+
+/// Helper function to transform an array of borrowed strings to an owned `Vec<String>`
+fn string_vec_from(string_array: &[&str]) -> Vec<String> {
+ string_array.iter().map(|&s| s.to_owned()).collect()
+}
+
+/// Helper function to build the path of the module in the which is the node
+fn build_path(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> Option<String> {
+ let crate_name = crate_name(ast_func, ctx)?;
+ let leaf = self_partial_type(ast_func)
+ .or_else(|| ast_func.name().map(|n| n.to_string()))
+ .unwrap_or_else(|| "*".into());
+ let module_def: ModuleDef = ctx.sema.to_def(ast_func)?.module(ctx.db()).into();
+ match module_def.canonical_path(ctx.db()) {
+ Some(path) => Some(format!("{}::{}::{}", crate_name, path, leaf)),
+ None => Some(format!("{}::{}", crate_name, leaf)),
+ }
+}
+
+/// Helper function to get the return type of a function
+fn return_type(ast_func: &ast::Fn) -> Option<ast::Type> {
+ ast_func.ret_type()?.ty()
+}
+
+/// Helper function to determine if the function returns some data
+fn returns_a_value(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> bool {
+ ctx.sema
+ .to_def(ast_func)
+ .map(|hir_func| hir_func.ret_type(ctx.db()))
+ .map(|ret_ty| !ret_ty.is_unit() && !ret_ty.is_never())
+ .unwrap_or(false)
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn not_applicable_on_function_calls() {
+ check_assist_not_applicable(
+ generate_documentation_template,
+ r#"
+fn hello_world() {}
+fn calls_hello_world() {
+ hello_world$0();
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn not_applicable_in_trait_impl() {
+ check_assist_not_applicable(
+ generate_documentation_template,
+ r#"
+trait MyTrait {}
+struct MyStruct;
+impl MyTrait for MyStruct {
+ fn hello_world$0();
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn not_applicable_if_function_already_documented() {
+ check_assist_not_applicable(
+ generate_documentation_template,
+ r#"
+/// Some documentation here
+pub fn $0documented_function() {}
+"#,
+ );
+ }
+
+ #[test]
+ fn supports_noop_function() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+pub fn no$0op() {}
+"#,
+ r#"
+/// .
+pub fn noop() {}
+"#,
+ );
+ }
+
+ #[test]
+ fn is_applicable_if_function_is_private() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+fn priv$0ate() {}
+"#,
+ r#"
+/// .
+fn private() {}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_doc_example_for_private_fn() {
+ check_assist_not_applicable(
+ generate_doc_example,
+ r#"
+///$0
+fn private() {}
+"#,
+ );
+ }
+
+ #[test]
+ fn supports_a_parameter() {
+ check_assist(
+ generate_doc_example,
+ r#"
+/// $0.
+pub fn noop_with_param(_a: i32) {}
+"#,
+ r#"
+/// .
+///
+/// # Examples
+///
+/// ```
+/// use test::noop_with_param;
+///
+/// noop_with_param(_a);
+/// ```
+pub fn noop_with_param(_a: i32) {}
+"#,
+ );
+ }
+
+ #[test]
+ fn detects_unsafe_function() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+pub unsafe fn no$0op_unsafe() {}
+"#,
+ r#"
+/// .
+///
+/// # Safety
+///
+/// .
+pub unsafe fn noop_unsafe() {}
+"#,
+ );
+ check_assist(
+ generate_doc_example,
+ r#"
+/// .
+///
+/// # Safety$0
+///
+/// .
+pub unsafe fn noop_unsafe() {}
+"#,
+ r#"
+/// .
+///
+/// # Safety
+///
+/// .
+///
+/// # Examples
+///
+/// ```
+/// use test::noop_unsafe;
+///
+/// unsafe { noop_unsafe() };
+/// ```
+pub unsafe fn noop_unsafe() {}
+"#,
+ );
+ }
+
+ #[test]
+ fn guesses_panic_macro_can_panic() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+pub fn panic$0s_if(a: bool) {
+ if a {
+ panic!();
+ }
+}
+"#,
+ r#"
+/// .
+///
+/// # Panics
+///
+/// Panics if .
+pub fn panics_if(a: bool) {
+ if a {
+ panic!();
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn guesses_assert_macro_can_panic() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+pub fn $0panics_if_not(a: bool) {
+ assert!(a == true);
+}
+"#,
+ r#"
+/// .
+///
+/// # Panics
+///
+/// Panics if .
+pub fn panics_if_not(a: bool) {
+ assert!(a == true);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn guesses_unwrap_can_panic() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+pub fn $0panics_if_none(a: Option<()>) {
+ a.unwrap();
+}
+"#,
+ r#"
+/// .
+///
+/// # Panics
+///
+/// Panics if .
+pub fn panics_if_none(a: Option<()>) {
+ a.unwrap();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn guesses_expect_can_panic() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+pub fn $0panics_if_none2(a: Option<()>) {
+ a.expect("Bouh!");
+}
+"#,
+ r#"
+/// .
+///
+/// # Panics
+///
+/// Panics if .
+pub fn panics_if_none2(a: Option<()>) {
+ a.expect("Bouh!");
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn checks_output_in_example() {
+ check_assist(
+ generate_doc_example,
+ r#"
+///$0
+pub fn returns_a_value$0() -> i32 {
+ 0
+}
+"#,
+ r#"
+///
+///
+/// # Examples
+///
+/// ```
+/// use test::returns_a_value;
+///
+/// assert_eq!(returns_a_value(), );
+/// ```
+pub fn returns_a_value() -> i32 {
+ 0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn detects_result_output() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+pub fn returns_a_result$0() -> Result<i32, std::io::Error> {
+ Ok(0)
+}
+"#,
+ r#"
+/// .
+///
+/// # Errors
+///
+/// This function will return an error if .
+pub fn returns_a_result() -> Result<i32, std::io::Error> {
+ Ok(0)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn checks_ref_mut_in_example() {
+ check_assist(
+ generate_doc_example,
+ r#"
+///$0
+pub fn modifies_a_value$0(a: &mut i32) {
+ *a = 0;
+}
+"#,
+ r#"
+///
+///
+/// # Examples
+///
+/// ```
+/// use test::modifies_a_value;
+///
+/// let mut a = ;
+/// modifies_a_value(&mut a);
+/// assert_eq!(a, );
+/// ```
+pub fn modifies_a_value(a: &mut i32) {
+ *a = 0;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn stores_result_if_at_least_3_params() {
+ check_assist(
+ generate_doc_example,
+ r#"
+///$0
+pub fn sum3$0(a: i32, b: i32, c: i32) -> i32 {
+ a + b + c
+}
+"#,
+ r#"
+///
+///
+/// # Examples
+///
+/// ```
+/// use test::sum3;
+///
+/// let result = sum3(a, b, c);
+/// assert_eq!(result, );
+/// ```
+pub fn sum3(a: i32, b: i32, c: i32) -> i32 {
+ a + b + c
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn supports_fn_in_mods() {
+ check_assist(
+ generate_doc_example,
+ r#"
+pub mod a {
+ pub mod b {
+ ///$0
+ pub fn noop() {}
+ }
+}
+"#,
+ r#"
+pub mod a {
+ pub mod b {
+ ///
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use test::a::b::noop;
+ ///
+ /// noop();
+ /// ```
+ pub fn noop() {}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn supports_fn_in_impl() {
+ check_assist(
+ generate_doc_example,
+ r#"
+pub struct MyStruct;
+impl MyStruct {
+ ///$0
+ pub fn noop() {}
+}
+"#,
+ r#"
+pub struct MyStruct;
+impl MyStruct {
+ ///
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use test::MyStruct;
+ ///
+ /// MyStruct::noop();
+ /// ```
+ pub fn noop() {}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn supports_unsafe_fn_in_trait() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+pub trait MyTrait {
+ unsafe fn unsafe_funct$0ion_trait();
+}
+"#,
+ r#"
+pub trait MyTrait {
+ /// .
+ ///
+ /// # Safety
+ ///
+ /// .
+ unsafe fn unsafe_function_trait();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn supports_fn_in_trait_with_default_panicking() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+pub trait MyTrait {
+ fn function_trait_with_$0default_panicking() {
+ panic!()
+ }
+}
+"#,
+ r#"
+pub trait MyTrait {
+ /// .
+ ///
+ /// # Panics
+ ///
+ /// Panics if .
+ fn function_trait_with_default_panicking() {
+ panic!()
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn supports_fn_in_trait_returning_result() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+pub trait MyTrait {
+ fn function_tr$0ait_returning_result() -> Result<(), std::io::Error>;
+}
+"#,
+ r#"
+pub trait MyTrait {
+ /// .
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if .
+ fn function_trait_returning_result() -> Result<(), std::io::Error>;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn detects_new() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+pub struct String(u8);
+impl String {
+ pub fn new$0(x: u8) -> String {
+ String(x)
+ }
+}
+"#,
+ r#"
+pub struct String(u8);
+impl String {
+ /// Creates a new [`String`].
+ pub fn new(x: u8) -> String {
+ String(x)
+ }
+}
+"#,
+ );
+ check_assist(
+ generate_documentation_template,
+ r#"
+#[derive(Debug, PartialEq)]
+pub struct MyGenericStruct<T> {
+ pub x: T,
+}
+impl<T> MyGenericStruct<T> {
+ pub fn new$0(x: T) -> MyGenericStruct<T> {
+ MyGenericStruct { x }
+ }
+}
+"#,
+ r#"
+#[derive(Debug, PartialEq)]
+pub struct MyGenericStruct<T> {
+ pub x: T,
+}
+impl<T> MyGenericStruct<T> {
+ /// Creates a new [`MyGenericStruct<T>`].
+ pub fn new(x: T) -> MyGenericStruct<T> {
+ MyGenericStruct { x }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn removes_one_lifetime_from_description() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+#[derive(Debug, PartialEq)]
+pub struct MyGenericStruct<'a, T> {
+ pub x: &'a T,
+}
+impl<'a, T> MyGenericStruct<'a, T> {
+ pub fn new$0(x: &'a T) -> Self {
+ MyGenericStruct { x }
+ }
+}
+"#,
+ r#"
+#[derive(Debug, PartialEq)]
+pub struct MyGenericStruct<'a, T> {
+ pub x: &'a T,
+}
+impl<'a, T> MyGenericStruct<'a, T> {
+ /// Creates a new [`MyGenericStruct<T>`].
+ pub fn new(x: &'a T) -> Self {
+ MyGenericStruct { x }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn removes_all_lifetimes_from_description() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+#[derive(Debug, PartialEq)]
+pub struct MyGenericStruct<'a, 'b, T> {
+ pub x: &'a T,
+ pub y: &'b T,
+}
+impl<'a, 'b, T> MyGenericStruct<'a, 'b, T> {
+ pub fn new$0(x: &'a T, y: &'b T) -> Self {
+ MyGenericStruct { x, y }
+ }
+}
+"#,
+ r#"
+#[derive(Debug, PartialEq)]
+pub struct MyGenericStruct<'a, 'b, T> {
+ pub x: &'a T,
+ pub y: &'b T,
+}
+impl<'a, 'b, T> MyGenericStruct<'a, 'b, T> {
+ /// Creates a new [`MyGenericStruct<T>`].
+ pub fn new(x: &'a T, y: &'b T) -> Self {
+ MyGenericStruct { x, y }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn removes_all_lifetimes_and_brackets_from_description() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+#[derive(Debug, PartialEq)]
+pub struct MyGenericStruct<'a, 'b> {
+ pub x: &'a usize,
+ pub y: &'b usize,
+}
+impl<'a, 'b> MyGenericStruct<'a, 'b> {
+ pub fn new$0(x: &'a usize, y: &'b usize) -> Self {
+ MyGenericStruct { x, y }
+ }
+}
+"#,
+ r#"
+#[derive(Debug, PartialEq)]
+pub struct MyGenericStruct<'a, 'b> {
+ pub x: &'a usize,
+ pub y: &'b usize,
+}
+impl<'a, 'b> MyGenericStruct<'a, 'b> {
+ /// Creates a new [`MyGenericStruct`].
+ pub fn new(x: &'a usize, y: &'b usize) -> Self {
+ MyGenericStruct { x, y }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn detects_new_with_self() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+#[derive(Debug, PartialEq)]
+pub struct MyGenericStruct2<T> {
+ pub x: T,
+}
+impl<T> MyGenericStruct2<T> {
+ pub fn new$0(x: T) -> Self {
+ MyGenericStruct2 { x }
+ }
+}
+"#,
+ r#"
+#[derive(Debug, PartialEq)]
+pub struct MyGenericStruct2<T> {
+ pub x: T,
+}
+impl<T> MyGenericStruct2<T> {
+ /// Creates a new [`MyGenericStruct2<T>`].
+ pub fn new(x: T) -> Self {
+ MyGenericStruct2 { x }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn supports_method_call() {
+ check_assist(
+ generate_doc_example,
+ r#"
+impl<T> MyGenericStruct<T> {
+ ///$0
+ pub fn consume(self) {}
+}
+"#,
+ r#"
+impl<T> MyGenericStruct<T> {
+ ///
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use test::MyGenericStruct;
+ ///
+ /// let my_generic_struct = ;
+ /// my_generic_struct.consume();
+ /// ```
+ pub fn consume(self) {}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn checks_modified_self_param() {
+ check_assist(
+ generate_doc_example,
+ r#"
+impl<T> MyGenericStruct<T> {
+ ///$0
+ pub fn modify(&mut self, new_value: T) {
+ self.x = new_value;
+ }
+}
+"#,
+ r#"
+impl<T> MyGenericStruct<T> {
+ ///
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use test::MyGenericStruct;
+ ///
+ /// let mut my_generic_struct = ;
+ /// my_generic_struct.modify(new_value);
+ /// assert_eq!(my_generic_struct, );
+ /// ```
+ pub fn modify(&mut self, new_value: T) {
+ self.x = new_value;
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn generates_intro_for_getters() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+pub struct S;
+impl S {
+ pub fn speed$0(&self) -> f32 { 0.0 }
+}
+"#,
+ r#"
+pub struct S;
+impl S {
+ /// Returns the speed of this [`S`].
+ pub fn speed(&self) -> f32 { 0.0 }
+}
+"#,
+ );
+ check_assist(
+ generate_documentation_template,
+ r#"
+pub struct S;
+impl S {
+ pub fn data$0(&self) -> &[u8] { &[] }
+}
+"#,
+ r#"
+pub struct S;
+impl S {
+ /// Returns a reference to the data of this [`S`].
+ pub fn data(&self) -> &[u8] { &[] }
+}
+"#,
+ );
+ check_assist(
+ generate_documentation_template,
+ r#"
+pub struct S;
+impl S {
+ pub fn data$0(&mut self) -> &mut [u8] { &mut [] }
+}
+"#,
+ r#"
+pub struct S;
+impl S {
+ /// Returns a mutable reference to the data of this [`S`].
+ pub fn data(&mut self) -> &mut [u8] { &mut [] }
+}
+"#,
+ );
+ check_assist(
+ generate_documentation_template,
+ r#"
+pub struct S;
+impl S {
+ pub fn data_mut$0(&mut self) -> &mut [u8] { &mut [] }
+}
+"#,
+ r#"
+pub struct S;
+impl S {
+ /// Returns a mutable reference to the data of this [`S`].
+ pub fn data_mut(&mut self) -> &mut [u8] { &mut [] }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_getter_intro_for_prefixed_methods() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+pub struct S;
+impl S {
+ pub fn as_bytes$0(&self) -> &[u8] { &[] }
+}
+"#,
+ r#"
+pub struct S;
+impl S {
+ /// .
+ pub fn as_bytes(&self) -> &[u8] { &[] }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn generates_intro_for_setters() {
+ check_assist(
+ generate_documentation_template,
+ r#"
+pub struct S;
+impl S {
+ pub fn set_data$0(&mut self, data: Vec<u8>) {}
+}
+"#,
+ r#"
+pub struct S;
+impl S {
+ /// Sets the data of this [`S`].
+ pub fn set_data(&mut self, data: Vec<u8>) {}
+}
+"#,
+ );
+ check_assist(
+ generate_documentation_template,
+ r#"
+pub struct S;
+impl S {
+ pub fn set_domain_name$0(&mut self, name: String) {}
+}
+"#,
+ r#"
+pub struct S;
+impl S {
+ /// Sets the domain name of this [`S`].
+ pub fn set_domain_name(&mut self, name: String) {}
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_is_method.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_is_method.rs
new file mode 100644
index 000000000..52d27d8a7
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_is_method.rs
@@ -0,0 +1,316 @@
+use ide_db::assists::GroupLabel;
+use stdx::to_lower_snake_case;
+use syntax::ast::HasVisibility;
+use syntax::ast::{self, AstNode, HasName};
+
+use crate::{
+ utils::{add_method_to_adt, find_struct_impl},
+ AssistContext, AssistId, AssistKind, Assists,
+};
+
+// Assist: generate_enum_is_method
+//
+// Generate an `is_` method for this enum variant.
+//
+// ```
+// enum Version {
+// Undefined,
+// Minor$0,
+// Major,
+// }
+// ```
+// ->
+// ```
+// enum Version {
+// Undefined,
+// Minor,
+// Major,
+// }
+//
+// impl Version {
+// /// Returns `true` if the version is [`Minor`].
+// ///
+// /// [`Minor`]: Version::Minor
+// #[must_use]
+// fn is_minor(&self) -> bool {
+// matches!(self, Self::Minor)
+// }
+// }
+// ```
+pub(crate) fn generate_enum_is_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let variant = ctx.find_node_at_offset::<ast::Variant>()?;
+ let variant_name = variant.name()?;
+ let parent_enum = ast::Adt::Enum(variant.parent_enum());
+ let pattern_suffix = match variant.kind() {
+ ast::StructKind::Record(_) => " { .. }",
+ ast::StructKind::Tuple(_) => "(..)",
+ ast::StructKind::Unit => "",
+ };
+
+ let enum_name = parent_enum.name()?;
+ let enum_lowercase_name = to_lower_snake_case(&enum_name.to_string()).replace('_', " ");
+ let fn_name = format!("is_{}", &to_lower_snake_case(&variant_name.text()));
+
+ // Return early if we've found an existing new fn
+ let impl_def = find_struct_impl(ctx, &parent_enum, &fn_name)?;
+
+ let target = variant.syntax().text_range();
+ acc.add_group(
+ &GroupLabel("Generate an `is_`,`as_`, or `try_into_` for this enum variant".to_owned()),
+ AssistId("generate_enum_is_method", AssistKind::Generate),
+ "Generate an `is_` method for this enum variant",
+ target,
+ |builder| {
+ let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{} ", v));
+ let method = format!(
+ " /// Returns `true` if the {} is [`{variant}`].
+ ///
+ /// [`{variant}`]: {}::{variant}
+ #[must_use]
+ {}fn {}(&self) -> bool {{
+ matches!(self, Self::{variant}{})
+ }}",
+ enum_lowercase_name,
+ enum_name,
+ vis,
+ fn_name,
+ pattern_suffix,
+ variant = variant_name
+ );
+
+ add_method_to_adt(builder, &parent_enum, impl_def, &method);
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn test_generate_enum_is_from_variant() {
+ check_assist(
+ generate_enum_is_method,
+ r#"
+enum Variant {
+ Undefined,
+ Minor$0,
+ Major,
+}"#,
+ r#"enum Variant {
+ Undefined,
+ Minor,
+ Major,
+}
+
+impl Variant {
+ /// Returns `true` if the variant is [`Minor`].
+ ///
+ /// [`Minor`]: Variant::Minor
+ #[must_use]
+ fn is_minor(&self) -> bool {
+ matches!(self, Self::Minor)
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_enum_is_already_implemented() {
+ check_assist_not_applicable(
+ generate_enum_is_method,
+ r#"
+enum Variant {
+ Undefined,
+ Minor$0,
+ Major,
+}
+
+impl Variant {
+ fn is_minor(&self) -> bool {
+ matches!(self, Self::Minor)
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_enum_is_from_tuple_variant() {
+ check_assist(
+ generate_enum_is_method,
+ r#"
+enum Variant {
+ Undefined,
+ Minor(u32)$0,
+ Major,
+}"#,
+ r#"enum Variant {
+ Undefined,
+ Minor(u32),
+ Major,
+}
+
+impl Variant {
+ /// Returns `true` if the variant is [`Minor`].
+ ///
+ /// [`Minor`]: Variant::Minor
+ #[must_use]
+ fn is_minor(&self) -> bool {
+ matches!(self, Self::Minor(..))
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_enum_is_from_record_variant() {
+ check_assist(
+ generate_enum_is_method,
+ r#"
+enum Variant {
+ Undefined,
+ Minor { foo: i32 }$0,
+ Major,
+}"#,
+ r#"enum Variant {
+ Undefined,
+ Minor { foo: i32 },
+ Major,
+}
+
+impl Variant {
+ /// Returns `true` if the variant is [`Minor`].
+ ///
+ /// [`Minor`]: Variant::Minor
+ #[must_use]
+ fn is_minor(&self) -> bool {
+ matches!(self, Self::Minor { .. })
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_enum_is_from_variant_with_one_variant() {
+ check_assist(
+ generate_enum_is_method,
+ r#"enum Variant { Undefi$0ned }"#,
+ r#"
+enum Variant { Undefined }
+
+impl Variant {
+ /// Returns `true` if the variant is [`Undefined`].
+ ///
+ /// [`Undefined`]: Variant::Undefined
+ #[must_use]
+ fn is_undefined(&self) -> bool {
+ matches!(self, Self::Undefined)
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_enum_is_from_variant_with_visibility_marker() {
+ check_assist(
+ generate_enum_is_method,
+ r#"
+pub(crate) enum Variant {
+ Undefined,
+ Minor$0,
+ Major,
+}"#,
+ r#"pub(crate) enum Variant {
+ Undefined,
+ Minor,
+ Major,
+}
+
+impl Variant {
+ /// Returns `true` if the variant is [`Minor`].
+ ///
+ /// [`Minor`]: Variant::Minor
+ #[must_use]
+ pub(crate) fn is_minor(&self) -> bool {
+ matches!(self, Self::Minor)
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_multiple_generate_enum_is_from_variant() {
+ check_assist(
+ generate_enum_is_method,
+ r#"
+enum Variant {
+ Undefined,
+ Minor,
+ Major$0,
+}
+
+impl Variant {
+ /// Returns `true` if the variant is [`Minor`].
+ ///
+ /// [`Minor`]: Variant::Minor
+ #[must_use]
+ fn is_minor(&self) -> bool {
+ matches!(self, Self::Minor)
+ }
+}"#,
+ r#"enum Variant {
+ Undefined,
+ Minor,
+ Major,
+}
+
+impl Variant {
+ /// Returns `true` if the variant is [`Minor`].
+ ///
+ /// [`Minor`]: Variant::Minor
+ #[must_use]
+ fn is_minor(&self) -> bool {
+ matches!(self, Self::Minor)
+ }
+
+ /// Returns `true` if the variant is [`Major`].
+ ///
+ /// [`Major`]: Variant::Major
+ #[must_use]
+ fn is_major(&self) -> bool {
+ matches!(self, Self::Major)
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_enum_is_variant_names() {
+ check_assist(
+ generate_enum_is_method,
+ r#"
+enum GeneratorState {
+ Yielded,
+ Complete$0,
+ Major,
+}"#,
+ r#"enum GeneratorState {
+ Yielded,
+ Complete,
+ Major,
+}
+
+impl GeneratorState {
+ /// Returns `true` if the generator state is [`Complete`].
+ ///
+ /// [`Complete`]: GeneratorState::Complete
+ #[must_use]
+ fn is_complete(&self) -> bool {
+ matches!(self, Self::Complete)
+ }
+}"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_projection_method.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_projection_method.rs
new file mode 100644
index 000000000..b19aa0f65
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_projection_method.rs
@@ -0,0 +1,342 @@
+use ide_db::assists::GroupLabel;
+use itertools::Itertools;
+use stdx::to_lower_snake_case;
+use syntax::ast::HasVisibility;
+use syntax::ast::{self, AstNode, HasName};
+
+use crate::{
+ utils::{add_method_to_adt, find_struct_impl},
+ AssistContext, AssistId, AssistKind, Assists,
+};
+
+// Assist: generate_enum_try_into_method
+//
+// Generate a `try_into_` method for this enum variant.
+//
+// ```
+// enum Value {
+// Number(i32),
+// Text(String)$0,
+// }
+// ```
+// ->
+// ```
+// enum Value {
+// Number(i32),
+// Text(String),
+// }
+//
+// impl Value {
+// fn try_into_text(self) -> Result<String, Self> {
+// if let Self::Text(v) = self {
+// Ok(v)
+// } else {
+// Err(self)
+// }
+// }
+// }
+// ```
+pub(crate) fn generate_enum_try_into_method(
+ acc: &mut Assists,
+ ctx: &AssistContext<'_>,
+) -> Option<()> {
+ generate_enum_projection_method(
+ acc,
+ ctx,
+ "generate_enum_try_into_method",
+ "Generate a `try_into_` method for this enum variant",
+ ProjectionProps {
+ fn_name_prefix: "try_into",
+ self_param: "self",
+ return_prefix: "Result<",
+ return_suffix: ", Self>",
+ happy_case: "Ok",
+ sad_case: "Err(self)",
+ },
+ )
+}
+
+// Assist: generate_enum_as_method
+//
+// Generate an `as_` method for this enum variant.
+//
+// ```
+// enum Value {
+// Number(i32),
+// Text(String)$0,
+// }
+// ```
+// ->
+// ```
+// enum Value {
+// Number(i32),
+// Text(String),
+// }
+//
+// impl Value {
+// fn as_text(&self) -> Option<&String> {
+// if let Self::Text(v) = self {
+// Some(v)
+// } else {
+// None
+// }
+// }
+// }
+// ```
+pub(crate) fn generate_enum_as_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ generate_enum_projection_method(
+ acc,
+ ctx,
+ "generate_enum_as_method",
+ "Generate an `as_` method for this enum variant",
+ ProjectionProps {
+ fn_name_prefix: "as",
+ self_param: "&self",
+ return_prefix: "Option<&",
+ return_suffix: ">",
+ happy_case: "Some",
+ sad_case: "None",
+ },
+ )
+}
+
+struct ProjectionProps {
+ fn_name_prefix: &'static str,
+ self_param: &'static str,
+ return_prefix: &'static str,
+ return_suffix: &'static str,
+ happy_case: &'static str,
+ sad_case: &'static str,
+}
+
+fn generate_enum_projection_method(
+ acc: &mut Assists,
+ ctx: &AssistContext<'_>,
+ assist_id: &'static str,
+ assist_description: &str,
+ props: ProjectionProps,
+) -> Option<()> {
+ let variant = ctx.find_node_at_offset::<ast::Variant>()?;
+ let variant_name = variant.name()?;
+ let parent_enum = ast::Adt::Enum(variant.parent_enum());
+
+ let (pattern_suffix, field_type, bound_name) = match variant.kind() {
+ ast::StructKind::Record(record) => {
+ let (field,) = record.fields().collect_tuple()?;
+ let name = field.name()?.to_string();
+ let ty = field.ty()?;
+ let pattern_suffix = format!(" {{ {} }}", name);
+ (pattern_suffix, ty, name)
+ }
+ ast::StructKind::Tuple(tuple) => {
+ let (field,) = tuple.fields().collect_tuple()?;
+ let ty = field.ty()?;
+ ("(v)".to_owned(), ty, "v".to_owned())
+ }
+ ast::StructKind::Unit => return None,
+ };
+
+ let fn_name =
+ format!("{}_{}", props.fn_name_prefix, &to_lower_snake_case(&variant_name.text()));
+
+ // Return early if we've found an existing new fn
+ let impl_def = find_struct_impl(ctx, &parent_enum, &fn_name)?;
+
+ let target = variant.syntax().text_range();
+ acc.add_group(
+ &GroupLabel("Generate an `is_`,`as_`, or `try_into_` for this enum variant".to_owned()),
+ AssistId(assist_id, AssistKind::Generate),
+ assist_description,
+ target,
+ |builder| {
+ let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{} ", v));
+ let method = format!(
+ " {0}fn {1}({2}) -> {3}{4}{5} {{
+ if let Self::{6}{7} = self {{
+ {8}({9})
+ }} else {{
+ {10}
+ }}
+ }}",
+ vis,
+ fn_name,
+ props.self_param,
+ props.return_prefix,
+ field_type.syntax(),
+ props.return_suffix,
+ variant_name,
+ pattern_suffix,
+ props.happy_case,
+ bound_name,
+ props.sad_case,
+ );
+
+ add_method_to_adt(builder, &parent_enum, impl_def, &method);
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn test_generate_enum_try_into_tuple_variant() {
+ check_assist(
+ generate_enum_try_into_method,
+ r#"
+enum Value {
+ Number(i32),
+ Text(String)$0,
+}"#,
+ r#"enum Value {
+ Number(i32),
+ Text(String),
+}
+
+impl Value {
+ fn try_into_text(self) -> Result<String, Self> {
+ if let Self::Text(v) = self {
+ Ok(v)
+ } else {
+ Err(self)
+ }
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_enum_try_into_already_implemented() {
+ check_assist_not_applicable(
+ generate_enum_try_into_method,
+ r#"enum Value {
+ Number(i32),
+ Text(String)$0,
+}
+
+impl Value {
+ fn try_into_text(self) -> Result<String, Self> {
+ if let Self::Text(v) = self {
+ Ok(v)
+ } else {
+ Err(self)
+ }
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_enum_try_into_unit_variant() {
+ check_assist_not_applicable(
+ generate_enum_try_into_method,
+ r#"enum Value {
+ Number(i32),
+ Text(String),
+ Unit$0,
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_enum_try_into_record_with_multiple_fields() {
+ check_assist_not_applicable(
+ generate_enum_try_into_method,
+ r#"enum Value {
+ Number(i32),
+ Text(String),
+ Both { first: i32, second: String }$0,
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_enum_try_into_tuple_with_multiple_fields() {
+ check_assist_not_applicable(
+ generate_enum_try_into_method,
+ r#"enum Value {
+ Number(i32),
+ Text(String, String)$0,
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_enum_try_into_record_variant() {
+ check_assist(
+ generate_enum_try_into_method,
+ r#"enum Value {
+ Number(i32),
+ Text { text: String }$0,
+}"#,
+ r#"enum Value {
+ Number(i32),
+ Text { text: String },
+}
+
+impl Value {
+ fn try_into_text(self) -> Result<String, Self> {
+ if let Self::Text { text } = self {
+ Ok(text)
+ } else {
+ Err(self)
+ }
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_enum_as_tuple_variant() {
+ check_assist(
+ generate_enum_as_method,
+ r#"
+enum Value {
+ Number(i32),
+ Text(String)$0,
+}"#,
+ r#"enum Value {
+ Number(i32),
+ Text(String),
+}
+
+impl Value {
+ fn as_text(&self) -> Option<&String> {
+ if let Self::Text(v) = self {
+ Some(v)
+ } else {
+ None
+ }
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_enum_as_record_variant() {
+ check_assist(
+ generate_enum_as_method,
+ r#"enum Value {
+ Number(i32),
+ Text { text: String }$0,
+}"#,
+ r#"enum Value {
+ Number(i32),
+ Text { text: String },
+}
+
+impl Value {
+ fn as_text(&self) -> Option<&String> {
+ if let Self::Text { text } = self {
+ Some(text)
+ } else {
+ None
+ }
+ }
+}"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_variant.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_variant.rs
new file mode 100644
index 000000000..4461fbd5a
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_variant.rs
@@ -0,0 +1,227 @@
+use hir::{HasSource, InFile};
+use ide_db::assists::{AssistId, AssistKind};
+use syntax::{
+ ast::{self, edit::IndentLevel},
+ AstNode, TextSize,
+};
+
+use crate::assist_context::{AssistContext, Assists};
+
+// Assist: generate_enum_variant
+//
+// Adds a variant to an enum.
+//
+// ```
+// enum Countries {
+// Ghana,
+// }
+//
+// fn main() {
+// let country = Countries::Lesotho$0;
+// }
+// ```
+// ->
+// ```
+// enum Countries {
+// Ghana,
+// Lesotho,
+// }
+//
+// fn main() {
+// let country = Countries::Lesotho;
+// }
+// ```
+pub(crate) fn generate_enum_variant(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let path_expr: ast::PathExpr = ctx.find_node_at_offset()?;
+ let path = path_expr.path()?;
+
+ if ctx.sema.resolve_path(&path).is_some() {
+ // No need to generate anything if the path resolves
+ return None;
+ }
+
+ let name_ref = path.segment()?.name_ref()?;
+ if name_ref.text().starts_with(char::is_lowercase) {
+ // Don't suggest generating variant if the name starts with a lowercase letter
+ return None;
+ }
+
+ if let Some(hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Enum(e)))) =
+ ctx.sema.resolve_path(&path.qualifier()?)
+ {
+ let target = path.syntax().text_range();
+ return add_variant_to_accumulator(acc, ctx, target, e, &name_ref);
+ }
+
+ None
+}
+
+fn add_variant_to_accumulator(
+ acc: &mut Assists,
+ ctx: &AssistContext<'_>,
+ target: syntax::TextRange,
+ adt: hir::Enum,
+ name_ref: &ast::NameRef,
+) -> Option<()> {
+ let db = ctx.db();
+ let InFile { file_id, value: enum_node } = adt.source(db)?.original_ast_node(db)?;
+ let enum_indent = IndentLevel::from_node(&enum_node.syntax());
+
+ let variant_list = enum_node.variant_list()?;
+ let offset = variant_list.syntax().text_range().end() - TextSize::of('}');
+ let empty_enum = variant_list.variants().next().is_none();
+
+ acc.add(
+ AssistId("generate_enum_variant", AssistKind::Generate),
+ "Generate variant",
+ target,
+ |builder| {
+ builder.edit_file(file_id.original_file(db));
+ let text = format!(
+ "{maybe_newline}{indent_1}{name},\n{enum_indent}",
+ maybe_newline = if empty_enum { "\n" } else { "" },
+ indent_1 = IndentLevel(1),
+ name = name_ref,
+ enum_indent = enum_indent
+ );
+ builder.insert(offset, text)
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn generate_basic_enum_variant_in_empty_enum() {
+ check_assist(
+ generate_enum_variant,
+ r"
+enum Foo {}
+fn main() {
+ Foo::Bar$0
+}
+",
+ r"
+enum Foo {
+ Bar,
+}
+fn main() {
+ Foo::Bar
+}
+",
+ )
+ }
+
+ #[test]
+ fn generate_basic_enum_variant_in_non_empty_enum() {
+ check_assist(
+ generate_enum_variant,
+ r"
+enum Foo {
+ Bar,
+}
+fn main() {
+ Foo::Baz$0
+}
+",
+ r"
+enum Foo {
+ Bar,
+ Baz,
+}
+fn main() {
+ Foo::Baz
+}
+",
+ )
+ }
+
+ #[test]
+ fn generate_basic_enum_variant_in_different_file() {
+ check_assist(
+ generate_enum_variant,
+ r"
+//- /main.rs
+mod foo;
+use foo::Foo;
+
+fn main() {
+ Foo::Baz$0
+}
+
+//- /foo.rs
+enum Foo {
+ Bar,
+}
+",
+ r"
+enum Foo {
+ Bar,
+ Baz,
+}
+",
+ )
+ }
+
+ #[test]
+ fn not_applicable_for_existing_variant() {
+ check_assist_not_applicable(
+ generate_enum_variant,
+ r"
+enum Foo {
+ Bar,
+}
+fn main() {
+ Foo::Bar$0
+}
+",
+ )
+ }
+
+ #[test]
+ fn not_applicable_for_lowercase() {
+ check_assist_not_applicable(
+ generate_enum_variant,
+ r"
+enum Foo {
+ Bar,
+}
+fn main() {
+ Foo::new$0
+}
+",
+ )
+ }
+
+ #[test]
+ fn indentation_level_is_correct() {
+ check_assist(
+ generate_enum_variant,
+ r"
+mod m {
+ enum Foo {
+ Bar,
+ }
+}
+fn main() {
+ m::Foo::Baz$0
+}
+",
+ r"
+mod m {
+ enum Foo {
+ Bar,
+ Baz,
+ }
+}
+fn main() {
+ m::Foo::Baz
+}
+",
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_from_impl_for_enum.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_from_impl_for_enum.rs
new file mode 100644
index 000000000..507ea012b
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_from_impl_for_enum.rs
@@ -0,0 +1,310 @@
+use ide_db::{famous_defs::FamousDefs, RootDatabase};
+use syntax::ast::{self, AstNode, HasName};
+
+use crate::{utils::generate_trait_impl_text, AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: generate_from_impl_for_enum
+//
+// Adds a From impl for this enum variant with one tuple field.
+//
+// ```
+// enum A { $0One(u32) }
+// ```
+// ->
+// ```
+// enum A { One(u32) }
+//
+// impl From<u32> for A {
+// fn from(v: u32) -> Self {
+// Self::One(v)
+// }
+// }
+// ```
+pub(crate) fn generate_from_impl_for_enum(
+ acc: &mut Assists,
+ ctx: &AssistContext<'_>,
+) -> Option<()> {
+ let variant = ctx.find_node_at_offset::<ast::Variant>()?;
+ let variant_name = variant.name()?;
+ let enum_ = ast::Adt::Enum(variant.parent_enum());
+ let (field_name, field_type) = match variant.kind() {
+ ast::StructKind::Tuple(field_list) => {
+ if field_list.fields().count() != 1 {
+ return None;
+ }
+ (None, field_list.fields().next()?.ty()?)
+ }
+ ast::StructKind::Record(field_list) => {
+ if field_list.fields().count() != 1 {
+ return None;
+ }
+ let field = field_list.fields().next()?;
+ (Some(field.name()?), field.ty()?)
+ }
+ ast::StructKind::Unit => return None,
+ };
+
+ if existing_from_impl(&ctx.sema, &variant).is_some() {
+ cov_mark::hit!(test_add_from_impl_already_exists);
+ return None;
+ }
+
+ let target = variant.syntax().text_range();
+ acc.add(
+ AssistId("generate_from_impl_for_enum", AssistKind::Generate),
+ "Generate `From` impl for this enum variant",
+ target,
+ |edit| {
+ let start_offset = variant.parent_enum().syntax().text_range().end();
+ let from_trait = format!("From<{}>", field_type.syntax());
+ let impl_code = if let Some(name) = field_name {
+ format!(
+ r#" fn from({0}: {1}) -> Self {{
+ Self::{2} {{ {0} }}
+ }}"#,
+ name.text(),
+ field_type.syntax(),
+ variant_name,
+ )
+ } else {
+ format!(
+ r#" fn from(v: {}) -> Self {{
+ Self::{}(v)
+ }}"#,
+ field_type.syntax(),
+ variant_name,
+ )
+ };
+ let from_impl = generate_trait_impl_text(&enum_, &from_trait, &impl_code);
+ edit.insert(start_offset, from_impl);
+ },
+ )
+}
+
+fn existing_from_impl(
+ sema: &'_ hir::Semantics<'_, RootDatabase>,
+ variant: &ast::Variant,
+) -> Option<()> {
+ let variant = sema.to_def(variant)?;
+ let enum_ = variant.parent_enum(sema.db);
+ let krate = enum_.module(sema.db).krate();
+
+ let from_trait = FamousDefs(sema, krate).core_convert_From()?;
+
+ let enum_type = enum_.ty(sema.db);
+
+ let wrapped_type = variant.fields(sema.db).get(0)?.ty(sema.db);
+
+ if enum_type.impls_trait(sema.db, from_trait, &[wrapped_type]) {
+ Some(())
+ } else {
+ None
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn test_generate_from_impl_for_enum() {
+ check_assist(
+ generate_from_impl_for_enum,
+ r#"
+//- minicore: from
+enum A { $0One(u32) }
+"#,
+ r#"
+enum A { One(u32) }
+
+impl From<u32> for A {
+ fn from(v: u32) -> Self {
+ Self::One(v)
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_from_impl_for_enum_complicated_path() {
+ check_assist(
+ generate_from_impl_for_enum,
+ r#"
+//- minicore: from
+enum A { $0One(foo::bar::baz::Boo) }
+"#,
+ r#"
+enum A { One(foo::bar::baz::Boo) }
+
+impl From<foo::bar::baz::Boo> for A {
+ fn from(v: foo::bar::baz::Boo) -> Self {
+ Self::One(v)
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_add_from_impl_no_element() {
+ check_assist_not_applicable(
+ generate_from_impl_for_enum,
+ r#"
+//- minicore: from
+enum A { $0One }
+"#,
+ );
+ }
+
+ #[test]
+ fn test_add_from_impl_more_than_one_element_in_tuple() {
+ check_assist_not_applicable(
+ generate_from_impl_for_enum,
+ r#"
+//- minicore: from
+enum A { $0One(u32, String) }
+"#,
+ );
+ }
+
+ #[test]
+ fn test_add_from_impl_struct_variant() {
+ check_assist(
+ generate_from_impl_for_enum,
+ r#"
+//- minicore: from
+enum A { $0One { x: u32 } }
+"#,
+ r#"
+enum A { One { x: u32 } }
+
+impl From<u32> for A {
+ fn from(x: u32) -> Self {
+ Self::One { x }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_add_from_impl_already_exists() {
+ cov_mark::check!(test_add_from_impl_already_exists);
+ check_assist_not_applicable(
+ generate_from_impl_for_enum,
+ r#"
+//- minicore: from
+enum A { $0One(u32), }
+
+impl From<u32> for A {
+ fn from(v: u32) -> Self {
+ Self::One(v)
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_add_from_impl_different_variant_impl_exists() {
+ check_assist(
+ generate_from_impl_for_enum,
+ r#"
+//- minicore: from
+enum A { $0One(u32), Two(String), }
+
+impl From<String> for A {
+ fn from(v: String) -> Self {
+ A::Two(v)
+ }
+}
+
+pub trait From<T> {
+ fn from(T) -> Self;
+}
+"#,
+ r#"
+enum A { One(u32), Two(String), }
+
+impl From<u32> for A {
+ fn from(v: u32) -> Self {
+ Self::One(v)
+ }
+}
+
+impl From<String> for A {
+ fn from(v: String) -> Self {
+ A::Two(v)
+ }
+}
+
+pub trait From<T> {
+ fn from(T) -> Self;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_add_from_impl_static_str() {
+ check_assist(
+ generate_from_impl_for_enum,
+ r#"
+//- minicore: from
+enum A { $0One(&'static str) }
+"#,
+ r#"
+enum A { One(&'static str) }
+
+impl From<&'static str> for A {
+ fn from(v: &'static str) -> Self {
+ Self::One(v)
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_add_from_impl_generic_enum() {
+ check_assist(
+ generate_from_impl_for_enum,
+ r#"
+//- minicore: from
+enum Generic<T, U: Clone> { $0One(T), Two(U) }
+"#,
+ r#"
+enum Generic<T, U: Clone> { One(T), Two(U) }
+
+impl<T, U: Clone> From<T> for Generic<T, U> {
+ fn from(v: T) -> Self {
+ Self::One(v)
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_add_from_impl_with_lifetime() {
+ check_assist(
+ generate_from_impl_for_enum,
+ r#"
+//- minicore: from
+enum Generic<'a> { $0One(&'a i32) }
+"#,
+ r#"
+enum Generic<'a> { One(&'a i32) }
+
+impl<'a> From<&'a i32> for Generic<'a> {
+ fn from(v: &'a i32) -> Self {
+ Self::One(v)
+ }
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs
new file mode 100644
index 000000000..d564a0540
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs
@@ -0,0 +1,1787 @@
+use hir::{HasSource, HirDisplay, Module, Semantics, TypeInfo};
+use ide_db::{
+ base_db::FileId,
+ defs::{Definition, NameRefClass},
+ famous_defs::FamousDefs,
+ FxHashMap, FxHashSet, RootDatabase, SnippetCap,
+};
+use stdx::to_lower_snake_case;
+use syntax::{
+ ast::{
+ self,
+ edit::{AstNodeEdit, IndentLevel},
+ make, AstNode, CallExpr, HasArgList, HasModuleItem,
+ },
+ SyntaxKind, SyntaxNode, TextRange, TextSize,
+};
+
+use crate::{
+ utils::convert_reference_type,
+ utils::{find_struct_impl, render_snippet, Cursor},
+ AssistContext, AssistId, AssistKind, Assists,
+};
+
+// Assist: generate_function
+//
+// Adds a stub function with a signature matching the function under the cursor.
+//
+// ```
+// struct Baz;
+// fn baz() -> Baz { Baz }
+// fn foo() {
+// bar$0("", baz());
+// }
+//
+// ```
+// ->
+// ```
+// struct Baz;
+// fn baz() -> Baz { Baz }
+// fn foo() {
+// bar("", baz());
+// }
+//
+// fn bar(arg: &str, baz: Baz) ${0:-> _} {
+// todo!()
+// }
+//
+// ```
+pub(crate) fn generate_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ gen_fn(acc, ctx).or_else(|| gen_method(acc, ctx))
+}
+
+fn gen_fn(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let path_expr: ast::PathExpr = ctx.find_node_at_offset()?;
+ let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
+ let path = path_expr.path()?;
+ let name_ref = path.segment()?.name_ref()?;
+ if ctx.sema.resolve_path(&path).is_some() {
+ // The function call already resolves, no need to add a function
+ return None;
+ }
+
+ let fn_name = &*name_ref.text();
+ let target_module;
+ let mut adt_name = None;
+
+ let (target, file, insert_offset) = match path.qualifier() {
+ Some(qualifier) => match ctx.sema.resolve_path(&qualifier) {
+ Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) => {
+ target_module = Some(module);
+ get_fn_target(ctx, &target_module, call.clone())?
+ }
+ Some(hir::PathResolution::Def(hir::ModuleDef::Adt(adt))) => {
+ if let hir::Adt::Enum(_) = adt {
+ // Don't suggest generating function if the name starts with an uppercase letter
+ if name_ref.text().starts_with(char::is_uppercase) {
+ return None;
+ }
+ }
+
+ let current_module = ctx.sema.scope(call.syntax())?.module();
+ let module = adt.module(ctx.sema.db);
+ target_module = if current_module == module { None } else { Some(module) };
+ if current_module.krate() != module.krate() {
+ return None;
+ }
+ let (impl_, file) = get_adt_source(ctx, &adt, fn_name)?;
+ let (target, insert_offset) = get_method_target(ctx, &module, &impl_)?;
+ adt_name = if impl_.is_none() { Some(adt.name(ctx.sema.db)) } else { None };
+ (target, file, insert_offset)
+ }
+ _ => {
+ return None;
+ }
+ },
+ _ => {
+ target_module = None;
+ get_fn_target(ctx, &target_module, call.clone())?
+ }
+ };
+ let function_builder = FunctionBuilder::from_call(ctx, &call, fn_name, target_module, target)?;
+ let text_range = call.syntax().text_range();
+ let label = format!("Generate {} function", function_builder.fn_name);
+ add_func_to_accumulator(
+ acc,
+ ctx,
+ text_range,
+ function_builder,
+ insert_offset,
+ file,
+ adt_name,
+ label,
+ )
+}
+
+fn gen_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let call: ast::MethodCallExpr = ctx.find_node_at_offset()?;
+ if ctx.sema.resolve_method_call(&call).is_some() {
+ return None;
+ }
+
+ let fn_name = call.name_ref()?;
+ let adt = ctx.sema.type_of_expr(&call.receiver()?)?.original().strip_references().as_adt()?;
+
+ let current_module = ctx.sema.scope(call.syntax())?.module();
+ let target_module = adt.module(ctx.sema.db);
+
+ if current_module.krate() != target_module.krate() {
+ return None;
+ }
+ let (impl_, file) = get_adt_source(ctx, &adt, fn_name.text().as_str())?;
+ let (target, insert_offset) = get_method_target(ctx, &target_module, &impl_)?;
+ let function_builder =
+ FunctionBuilder::from_method_call(ctx, &call, &fn_name, target_module, target)?;
+ let text_range = call.syntax().text_range();
+ let adt_name = if impl_.is_none() { Some(adt.name(ctx.sema.db)) } else { None };
+ let label = format!("Generate {} method", function_builder.fn_name);
+ add_func_to_accumulator(
+ acc,
+ ctx,
+ text_range,
+ function_builder,
+ insert_offset,
+ file,
+ adt_name,
+ label,
+ )
+}
+
+fn add_func_to_accumulator(
+ acc: &mut Assists,
+ ctx: &AssistContext<'_>,
+ text_range: TextRange,
+ function_builder: FunctionBuilder,
+ insert_offset: TextSize,
+ file: FileId,
+ adt_name: Option<hir::Name>,
+ label: String,
+) -> Option<()> {
+ acc.add(AssistId("generate_function", AssistKind::Generate), label, text_range, |builder| {
+ let function_template = function_builder.render();
+ let mut func = function_template.to_string(ctx.config.snippet_cap);
+ if let Some(name) = adt_name {
+ func = format!("\nimpl {} {{\n{}\n}}", name, func);
+ }
+ builder.edit_file(file);
+ match ctx.config.snippet_cap {
+ Some(cap) => builder.insert_snippet(cap, insert_offset, func),
+ None => builder.insert(insert_offset, func),
+ }
+ })
+}
+
+fn get_adt_source(
+ ctx: &AssistContext<'_>,
+ adt: &hir::Adt,
+ fn_name: &str,
+) -> Option<(Option<ast::Impl>, FileId)> {
+ let range = adt.source(ctx.sema.db)?.syntax().original_file_range(ctx.sema.db);
+ let file = ctx.sema.parse(range.file_id);
+ let adt_source =
+ ctx.sema.find_node_at_offset_with_macros(file.syntax(), range.range.start())?;
+ find_struct_impl(ctx, &adt_source, fn_name).map(|impl_| (impl_, range.file_id))
+}
+
+struct FunctionTemplate {
+ leading_ws: String,
+ fn_def: ast::Fn,
+ ret_type: Option<ast::RetType>,
+ should_focus_return_type: bool,
+ trailing_ws: String,
+ tail_expr: ast::Expr,
+}
+
+impl FunctionTemplate {
+ fn to_string(&self, cap: Option<SnippetCap>) -> String {
+ let f = match cap {
+ Some(cap) => {
+ let cursor = if self.should_focus_return_type {
+ // Focus the return type if there is one
+ match self.ret_type {
+ Some(ref ret_type) => ret_type.syntax(),
+ None => self.tail_expr.syntax(),
+ }
+ } else {
+ self.tail_expr.syntax()
+ };
+ render_snippet(cap, self.fn_def.syntax(), Cursor::Replace(cursor))
+ }
+ None => self.fn_def.to_string(),
+ };
+
+ format!("{}{}{}", self.leading_ws, f, self.trailing_ws)
+ }
+}
+
+struct FunctionBuilder {
+ target: GeneratedFunctionTarget,
+ fn_name: ast::Name,
+ type_params: Option<ast::GenericParamList>,
+ params: ast::ParamList,
+ ret_type: Option<ast::RetType>,
+ should_focus_return_type: bool,
+ needs_pub: bool,
+ is_async: bool,
+}
+
+impl FunctionBuilder {
+ /// Prepares a generated function that matches `call`.
+ /// The function is generated in `target_module` or next to `call`
+ fn from_call(
+ ctx: &AssistContext<'_>,
+ call: &ast::CallExpr,
+ fn_name: &str,
+ target_module: Option<hir::Module>,
+ target: GeneratedFunctionTarget,
+ ) -> Option<Self> {
+ let needs_pub = target_module.is_some();
+ let target_module =
+ target_module.or_else(|| ctx.sema.scope(target.syntax()).map(|it| it.module()))?;
+ let fn_name = make::name(fn_name);
+ let (type_params, params) =
+ fn_args(ctx, target_module, ast::CallableExpr::Call(call.clone()))?;
+
+ let await_expr = call.syntax().parent().and_then(ast::AwaitExpr::cast);
+ let is_async = await_expr.is_some();
+
+ let (ret_type, should_focus_return_type) =
+ make_return_type(ctx, &ast::Expr::CallExpr(call.clone()), target_module);
+
+ Some(Self {
+ target,
+ fn_name,
+ type_params,
+ params,
+ ret_type,
+ should_focus_return_type,
+ needs_pub,
+ is_async,
+ })
+ }
+
+ fn from_method_call(
+ ctx: &AssistContext<'_>,
+ call: &ast::MethodCallExpr,
+ name: &ast::NameRef,
+ target_module: Module,
+ target: GeneratedFunctionTarget,
+ ) -> Option<Self> {
+ let needs_pub =
+ !module_is_descendant(&ctx.sema.scope(call.syntax())?.module(), &target_module, ctx);
+ let fn_name = make::name(&name.text());
+ let (type_params, params) =
+ fn_args(ctx, target_module, ast::CallableExpr::MethodCall(call.clone()))?;
+
+ let await_expr = call.syntax().parent().and_then(ast::AwaitExpr::cast);
+ let is_async = await_expr.is_some();
+
+ let (ret_type, should_focus_return_type) =
+ make_return_type(ctx, &ast::Expr::MethodCallExpr(call.clone()), target_module);
+
+ Some(Self {
+ target,
+ fn_name,
+ type_params,
+ params,
+ ret_type,
+ should_focus_return_type,
+ needs_pub,
+ is_async,
+ })
+ }
+
+ fn render(self) -> FunctionTemplate {
+ let placeholder_expr = make::ext::expr_todo();
+ let fn_body = make::block_expr(vec![], Some(placeholder_expr));
+ let visibility = if self.needs_pub { Some(make::visibility_pub_crate()) } else { None };
+ let mut fn_def = make::fn_(
+ visibility,
+ self.fn_name,
+ self.type_params,
+ self.params,
+ fn_body,
+ self.ret_type,
+ self.is_async,
+ );
+ let leading_ws;
+ let trailing_ws;
+
+ match self.target {
+ GeneratedFunctionTarget::BehindItem(it) => {
+ let indent = IndentLevel::from_node(&it);
+ leading_ws = format!("\n\n{}", indent);
+ fn_def = fn_def.indent(indent);
+ trailing_ws = String::new();
+ }
+ GeneratedFunctionTarget::InEmptyItemList(it) => {
+ let indent = IndentLevel::from_node(&it);
+ leading_ws = format!("\n{}", indent + 1);
+ fn_def = fn_def.indent(indent + 1);
+ trailing_ws = format!("\n{}", indent);
+ }
+ };
+
+ FunctionTemplate {
+ leading_ws,
+ ret_type: fn_def.ret_type(),
+ // PANIC: we guarantee we always create a function body with a tail expr
+ tail_expr: fn_def.body().unwrap().tail_expr().unwrap(),
+ should_focus_return_type: self.should_focus_return_type,
+ fn_def,
+ trailing_ws,
+ }
+ }
+}
+
+/// Makes an optional return type along with whether the return type should be focused by the cursor.
+/// If we cannot infer what the return type should be, we create a placeholder type.
+///
+/// The rule for whether we focus a return type or not (and thus focus the function body),
+/// is rather simple:
+/// * If we could *not* infer what the return type should be, focus it (so the user can fill-in
+/// the correct return type).
+/// * If we could infer the return type, don't focus it (and thus focus the function body) so the
+/// user can change the `todo!` function body.
+fn make_return_type(
+ ctx: &AssistContext<'_>,
+ call: &ast::Expr,
+ target_module: Module,
+) -> (Option<ast::RetType>, bool) {
+ let (ret_ty, should_focus_return_type) = {
+ match ctx.sema.type_of_expr(call).map(TypeInfo::original) {
+ Some(ty) if ty.is_unknown() => (Some(make::ty_placeholder()), true),
+ None => (Some(make::ty_placeholder()), true),
+ Some(ty) if ty.is_unit() => (None, false),
+ Some(ty) => {
+ let rendered = ty.display_source_code(ctx.db(), target_module.into());
+ match rendered {
+ Ok(rendered) => (Some(make::ty(&rendered)), false),
+ Err(_) => (Some(make::ty_placeholder()), true),
+ }
+ }
+ }
+ };
+ let ret_type = ret_ty.map(make::ret_type);
+ (ret_type, should_focus_return_type)
+}
+
+fn get_fn_target(
+ ctx: &AssistContext<'_>,
+ target_module: &Option<Module>,
+ call: CallExpr,
+) -> Option<(GeneratedFunctionTarget, FileId, TextSize)> {
+ let mut file = ctx.file_id();
+ let target = match target_module {
+ Some(target_module) => {
+ let module_source = target_module.definition_source(ctx.db());
+ let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, &module_source)?;
+ file = in_file;
+ target
+ }
+ None => next_space_for_fn_after_call_site(ast::CallableExpr::Call(call))?,
+ };
+ Some((target.clone(), file, get_insert_offset(&target)))
+}
+
+fn get_method_target(
+ ctx: &AssistContext<'_>,
+ target_module: &Module,
+ impl_: &Option<ast::Impl>,
+) -> Option<(GeneratedFunctionTarget, TextSize)> {
+ let target = match impl_ {
+ Some(impl_) => next_space_for_fn_in_impl(impl_)?,
+ None => {
+ next_space_for_fn_in_module(ctx.sema.db, &target_module.definition_source(ctx.sema.db))?
+ .1
+ }
+ };
+ Some((target.clone(), get_insert_offset(&target)))
+}
+
+fn get_insert_offset(target: &GeneratedFunctionTarget) -> TextSize {
+ match &target {
+ GeneratedFunctionTarget::BehindItem(it) => it.text_range().end(),
+ GeneratedFunctionTarget::InEmptyItemList(it) => it.text_range().start() + TextSize::of('{'),
+ }
+}
+
+#[derive(Clone)]
+enum GeneratedFunctionTarget {
+ BehindItem(SyntaxNode),
+ InEmptyItemList(SyntaxNode),
+}
+
+impl GeneratedFunctionTarget {
+ fn syntax(&self) -> &SyntaxNode {
+ match self {
+ GeneratedFunctionTarget::BehindItem(it) => it,
+ GeneratedFunctionTarget::InEmptyItemList(it) => it,
+ }
+ }
+}
+
+/// Computes the type variables and arguments required for the generated function
+fn fn_args(
+ ctx: &AssistContext<'_>,
+ target_module: hir::Module,
+ call: ast::CallableExpr,
+) -> Option<(Option<ast::GenericParamList>, ast::ParamList)> {
+ let mut arg_names = Vec::new();
+ let mut arg_types = Vec::new();
+ for arg in call.arg_list()?.args() {
+ arg_names.push(fn_arg_name(&ctx.sema, &arg));
+ arg_types.push(fn_arg_type(ctx, target_module, &arg));
+ }
+ deduplicate_arg_names(&mut arg_names);
+ let params = arg_names.into_iter().zip(arg_types).map(|(name, ty)| {
+ make::param(make::ext::simple_ident_pat(make::name(&name)).into(), make::ty(&ty))
+ });
+
+ Some((
+ None,
+ make::param_list(
+ match call {
+ ast::CallableExpr::Call(_) => None,
+ ast::CallableExpr::MethodCall(_) => Some(make::self_param()),
+ },
+ params,
+ ),
+ ))
+}
+
+/// Makes duplicate argument names unique by appending incrementing numbers.
+///
+/// ```
+/// let mut names: Vec<String> =
+/// vec!["foo".into(), "foo".into(), "bar".into(), "baz".into(), "bar".into()];
+/// deduplicate_arg_names(&mut names);
+/// let expected: Vec<String> =
+/// vec!["foo_1".into(), "foo_2".into(), "bar_1".into(), "baz".into(), "bar_2".into()];
+/// assert_eq!(names, expected);
+/// ```
+fn deduplicate_arg_names(arg_names: &mut Vec<String>) {
+ let mut arg_name_counts = FxHashMap::default();
+ for name in arg_names.iter() {
+ *arg_name_counts.entry(name).or_insert(0) += 1;
+ }
+ let duplicate_arg_names: FxHashSet<String> = arg_name_counts
+ .into_iter()
+ .filter(|(_, count)| *count >= 2)
+ .map(|(name, _)| name.clone())
+ .collect();
+
+ let mut counter_per_name = FxHashMap::default();
+ for arg_name in arg_names.iter_mut() {
+ if duplicate_arg_names.contains(arg_name) {
+ let counter = counter_per_name.entry(arg_name.clone()).or_insert(1);
+ arg_name.push('_');
+ arg_name.push_str(&counter.to_string());
+ *counter += 1;
+ }
+ }
+}
+
+fn fn_arg_name(sema: &Semantics<'_, RootDatabase>, arg_expr: &ast::Expr) -> String {
+ let name = (|| match arg_expr {
+ ast::Expr::CastExpr(cast_expr) => Some(fn_arg_name(sema, &cast_expr.expr()?)),
+ expr => {
+ let name_ref = expr
+ .syntax()
+ .descendants()
+ .filter_map(ast::NameRef::cast)
+ .filter(|name| name.ident_token().is_some())
+ .last()?;
+ if let Some(NameRefClass::Definition(Definition::Const(_) | Definition::Static(_))) =
+ NameRefClass::classify(sema, &name_ref)
+ {
+ return Some(name_ref.to_string().to_lowercase());
+ };
+ Some(to_lower_snake_case(&name_ref.to_string()))
+ }
+ })();
+ match name {
+ Some(mut name) if name.starts_with(|c: char| c.is_ascii_digit()) => {
+ name.insert_str(0, "arg");
+ name
+ }
+ Some(name) => name,
+ None => "arg".to_string(),
+ }
+}
+
+fn fn_arg_type(ctx: &AssistContext<'_>, target_module: hir::Module, fn_arg: &ast::Expr) -> String {
+ fn maybe_displayed_type(
+ ctx: &AssistContext<'_>,
+ target_module: hir::Module,
+ fn_arg: &ast::Expr,
+ ) -> Option<String> {
+ let ty = ctx.sema.type_of_expr(fn_arg)?.adjusted();
+ if ty.is_unknown() {
+ return None;
+ }
+
+ if ty.is_reference() || ty.is_mutable_reference() {
+ let famous_defs = &FamousDefs(&ctx.sema, ctx.sema.scope(fn_arg.syntax())?.krate());
+ convert_reference_type(ty.strip_references(), ctx.db(), famous_defs)
+ .map(|conversion| conversion.convert_type(ctx.db()))
+ .or_else(|| ty.display_source_code(ctx.db(), target_module.into()).ok())
+ } else {
+ ty.display_source_code(ctx.db(), target_module.into()).ok()
+ }
+ }
+
+ maybe_displayed_type(ctx, target_module, fn_arg).unwrap_or_else(|| String::from("_"))
+}
+
+/// Returns the position inside the current mod or file
+/// directly after the current block
+/// We want to write the generated function directly after
+/// fns, impls or macro calls, but inside mods
+fn next_space_for_fn_after_call_site(expr: ast::CallableExpr) -> Option<GeneratedFunctionTarget> {
+ let mut ancestors = expr.syntax().ancestors().peekable();
+ let mut last_ancestor: Option<SyntaxNode> = None;
+ while let Some(next_ancestor) = ancestors.next() {
+ match next_ancestor.kind() {
+ SyntaxKind::SOURCE_FILE => {
+ break;
+ }
+ SyntaxKind::ITEM_LIST => {
+ if ancestors.peek().map(|a| a.kind()) == Some(SyntaxKind::MODULE) {
+ break;
+ }
+ }
+ _ => {}
+ }
+ last_ancestor = Some(next_ancestor);
+ }
+ last_ancestor.map(GeneratedFunctionTarget::BehindItem)
+}
+
+fn next_space_for_fn_in_module(
+ db: &dyn hir::db::AstDatabase,
+ module_source: &hir::InFile<hir::ModuleSource>,
+) -> Option<(FileId, GeneratedFunctionTarget)> {
+ let file = module_source.file_id.original_file(db);
+ let assist_item = match &module_source.value {
+ hir::ModuleSource::SourceFile(it) => match it.items().last() {
+ Some(last_item) => GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()),
+ None => GeneratedFunctionTarget::BehindItem(it.syntax().clone()),
+ },
+ hir::ModuleSource::Module(it) => match it.item_list().and_then(|it| it.items().last()) {
+ Some(last_item) => GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()),
+ None => GeneratedFunctionTarget::InEmptyItemList(it.item_list()?.syntax().clone()),
+ },
+ hir::ModuleSource::BlockExpr(it) => {
+ if let Some(last_item) =
+ it.statements().take_while(|stmt| matches!(stmt, ast::Stmt::Item(_))).last()
+ {
+ GeneratedFunctionTarget::BehindItem(last_item.syntax().clone())
+ } else {
+ GeneratedFunctionTarget::InEmptyItemList(it.syntax().clone())
+ }
+ }
+ };
+ Some((file, assist_item))
+}
+
+fn next_space_for_fn_in_impl(impl_: &ast::Impl) -> Option<GeneratedFunctionTarget> {
+ if let Some(last_item) = impl_.assoc_item_list().and_then(|it| it.assoc_items().last()) {
+ Some(GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()))
+ } else {
+ Some(GeneratedFunctionTarget::InEmptyItemList(impl_.assoc_item_list()?.syntax().clone()))
+ }
+}
+
+fn module_is_descendant(module: &hir::Module, ans: &hir::Module, ctx: &AssistContext<'_>) -> bool {
+ if module == ans {
+ return true;
+ }
+ for c in ans.children(ctx.sema.db) {
+ if module_is_descendant(module, &c, ctx) {
+ return true;
+ }
+ }
+ false
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn add_function_with_no_args() {
+ check_assist(
+ generate_function,
+ r"
+fn foo() {
+ bar$0();
+}
+",
+ r"
+fn foo() {
+ bar();
+}
+
+fn bar() ${0:-> _} {
+ todo!()
+}
+",
+ )
+ }
+
+ #[test]
+ fn add_function_from_method() {
+ // This ensures that the function is correctly generated
+ // in the next outer mod or file
+ check_assist(
+ generate_function,
+ r"
+impl Foo {
+ fn foo() {
+ bar$0();
+ }
+}
+",
+ r"
+impl Foo {
+ fn foo() {
+ bar();
+ }
+}
+
+fn bar() ${0:-> _} {
+ todo!()
+}
+",
+ )
+ }
+
+ #[test]
+ fn add_function_directly_after_current_block() {
+ // The new fn should not be created at the end of the file or module
+ check_assist(
+ generate_function,
+ r"
+fn foo1() {
+ bar$0();
+}
+
+fn foo2() {}
+",
+ r"
+fn foo1() {
+ bar();
+}
+
+fn bar() ${0:-> _} {
+ todo!()
+}
+
+fn foo2() {}
+",
+ )
+ }
+
+ #[test]
+ fn add_function_with_no_args_in_same_module() {
+ check_assist(
+ generate_function,
+ r"
+mod baz {
+ fn foo() {
+ bar$0();
+ }
+}
+",
+ r"
+mod baz {
+ fn foo() {
+ bar();
+ }
+
+ fn bar() ${0:-> _} {
+ todo!()
+ }
+}
+",
+ )
+ }
+
+ #[test]
+ fn add_function_with_upper_camel_case_arg() {
+ check_assist(
+ generate_function,
+ r"
+struct BazBaz;
+fn foo() {
+ bar$0(BazBaz);
+}
+",
+ r"
+struct BazBaz;
+fn foo() {
+ bar(BazBaz);
+}
+
+fn bar(baz_baz: BazBaz) ${0:-> _} {
+ todo!()
+}
+",
+ );
+ }
+
+ #[test]
+ fn add_function_with_upper_camel_case_arg_as_cast() {
+ check_assist(
+ generate_function,
+ r"
+struct BazBaz;
+fn foo() {
+ bar$0(&BazBaz as *const BazBaz);
+}
+",
+ r"
+struct BazBaz;
+fn foo() {
+ bar(&BazBaz as *const BazBaz);
+}
+
+fn bar(baz_baz: *const BazBaz) ${0:-> _} {
+ todo!()
+}
+",
+ );
+ }
+
+ #[test]
+ fn add_function_with_function_call_arg() {
+ check_assist(
+ generate_function,
+ r"
+struct Baz;
+fn baz() -> Baz { todo!() }
+fn foo() {
+ bar$0(baz());
+}
+",
+ r"
+struct Baz;
+fn baz() -> Baz { todo!() }
+fn foo() {
+ bar(baz());
+}
+
+fn bar(baz: Baz) ${0:-> _} {
+ todo!()
+}
+",
+ );
+ }
+
+ #[test]
+ fn add_function_with_method_call_arg() {
+ check_assist(
+ generate_function,
+ r"
+struct Baz;
+impl Baz {
+ fn foo(&self) -> Baz {
+ ba$0r(self.baz())
+ }
+ fn baz(&self) -> Baz {
+ Baz
+ }
+}
+",
+ r"
+struct Baz;
+impl Baz {
+ fn foo(&self) -> Baz {
+ bar(self.baz())
+ }
+ fn baz(&self) -> Baz {
+ Baz
+ }
+}
+
+fn bar(baz: Baz) -> Baz {
+ ${0:todo!()}
+}
+",
+ )
+ }
+
+ #[test]
+ fn add_function_with_string_literal_arg() {
+ check_assist(
+ generate_function,
+ r#"
+fn foo() {
+ $0bar("bar")
+}
+"#,
+ r#"
+fn foo() {
+ bar("bar")
+}
+
+fn bar(arg: &str) {
+ ${0:todo!()}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn add_function_with_char_literal_arg() {
+ check_assist(
+ generate_function,
+ r#"
+fn foo() {
+ $0bar('x')
+}
+"#,
+ r#"
+fn foo() {
+ bar('x')
+}
+
+fn bar(arg: char) {
+ ${0:todo!()}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn add_function_with_int_literal_arg() {
+ check_assist(
+ generate_function,
+ r"
+fn foo() {
+ $0bar(42)
+}
+",
+ r"
+fn foo() {
+ bar(42)
+}
+
+fn bar(arg: i32) {
+ ${0:todo!()}
+}
+",
+ )
+ }
+
+ #[test]
+ fn add_function_with_cast_int_literal_arg() {
+ check_assist(
+ generate_function,
+ r"
+fn foo() {
+ $0bar(42 as u8)
+}
+",
+ r"
+fn foo() {
+ bar(42 as u8)
+}
+
+fn bar(arg: u8) {
+ ${0:todo!()}
+}
+",
+ )
+ }
+
+ #[test]
+ fn name_of_cast_variable_is_used() {
+ // Ensures that the name of the cast type isn't used
+ // in the generated function signature.
+ check_assist(
+ generate_function,
+ r"
+fn foo() {
+ let x = 42;
+ bar$0(x as u8)
+}
+",
+ r"
+fn foo() {
+ let x = 42;
+ bar(x as u8)
+}
+
+fn bar(x: u8) {
+ ${0:todo!()}
+}
+",
+ )
+ }
+
+ #[test]
+ fn add_function_with_variable_arg() {
+ check_assist(
+ generate_function,
+ r"
+fn foo() {
+ let worble = ();
+ $0bar(worble)
+}
+",
+ r"
+fn foo() {
+ let worble = ();
+ bar(worble)
+}
+
+fn bar(worble: ()) {
+ ${0:todo!()}
+}
+",
+ )
+ }
+
+ #[test]
+ fn add_function_with_impl_trait_arg() {
+ check_assist(
+ generate_function,
+ r#"
+//- minicore: sized
+trait Foo {}
+fn foo() -> impl Foo {
+ todo!()
+}
+fn baz() {
+ $0bar(foo())
+}
+"#,
+ r#"
+trait Foo {}
+fn foo() -> impl Foo {
+ todo!()
+}
+fn baz() {
+ bar(foo())
+}
+
+fn bar(foo: impl Foo) {
+ ${0:todo!()}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn borrowed_arg() {
+ check_assist(
+ generate_function,
+ r"
+struct Baz;
+fn baz() -> Baz { todo!() }
+
+fn foo() {
+ bar$0(&baz())
+}
+",
+ r"
+struct Baz;
+fn baz() -> Baz { todo!() }
+
+fn foo() {
+ bar(&baz())
+}
+
+fn bar(baz: &Baz) {
+ ${0:todo!()}
+}
+",
+ )
+ }
+
+ #[test]
+ fn add_function_with_qualified_path_arg() {
+ check_assist(
+ generate_function,
+ r"
+mod Baz {
+ pub struct Bof;
+ pub fn baz() -> Bof { Bof }
+}
+fn foo() {
+ $0bar(Baz::baz())
+}
+",
+ r"
+mod Baz {
+ pub struct Bof;
+ pub fn baz() -> Bof { Bof }
+}
+fn foo() {
+ bar(Baz::baz())
+}
+
+fn bar(baz: Baz::Bof) {
+ ${0:todo!()}
+}
+",
+ )
+ }
+
+ #[test]
+ fn add_function_with_generic_arg() {
+ // FIXME: This is wrong, generated `bar` should include generic parameter.
+ check_assist(
+ generate_function,
+ r"
+fn foo<T>(t: T) {
+ $0bar(t)
+}
+",
+ r"
+fn foo<T>(t: T) {
+ bar(t)
+}
+
+fn bar(t: T) {
+ ${0:todo!()}
+}
+",
+ )
+ }
+
+ #[test]
+ fn add_function_with_fn_arg() {
+ // FIXME: The argument in `bar` is wrong.
+ check_assist(
+ generate_function,
+ r"
+struct Baz;
+impl Baz {
+ fn new() -> Self { Baz }
+}
+fn foo() {
+ $0bar(Baz::new);
+}
+",
+ r"
+struct Baz;
+impl Baz {
+ fn new() -> Self { Baz }
+}
+fn foo() {
+ bar(Baz::new);
+}
+
+fn bar(new: fn) ${0:-> _} {
+ todo!()
+}
+",
+ )
+ }
+
+ #[test]
+ fn add_function_with_closure_arg() {
+ // FIXME: The argument in `bar` is wrong.
+ check_assist(
+ generate_function,
+ r"
+fn foo() {
+ let closure = |x: i64| x - 1;
+ $0bar(closure)
+}
+",
+ r"
+fn foo() {
+ let closure = |x: i64| x - 1;
+ bar(closure)
+}
+
+fn bar(closure: _) {
+ ${0:todo!()}
+}
+",
+ )
+ }
+
+ #[test]
+ fn unresolveable_types_default_to_placeholder() {
+ check_assist(
+ generate_function,
+ r"
+fn foo() {
+ $0bar(baz)
+}
+",
+ r"
+fn foo() {
+ bar(baz)
+}
+
+fn bar(baz: _) {
+ ${0:todo!()}
+}
+",
+ )
+ }
+
+ #[test]
+ fn arg_names_dont_overlap() {
+ check_assist(
+ generate_function,
+ r"
+struct Baz;
+fn baz() -> Baz { Baz }
+fn foo() {
+ $0bar(baz(), baz())
+}
+",
+ r"
+struct Baz;
+fn baz() -> Baz { Baz }
+fn foo() {
+ bar(baz(), baz())
+}
+
+fn bar(baz_1: Baz, baz_2: Baz) {
+ ${0:todo!()}
+}
+",
+ )
+ }
+
+ #[test]
+ fn arg_name_counters_start_at_1_per_name() {
+ check_assist(
+ generate_function,
+ r#"
+struct Baz;
+fn baz() -> Baz { Baz }
+fn foo() {
+ $0bar(baz(), baz(), "foo", "bar")
+}
+"#,
+ r#"
+struct Baz;
+fn baz() -> Baz { Baz }
+fn foo() {
+ bar(baz(), baz(), "foo", "bar")
+}
+
+fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) {
+ ${0:todo!()}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn add_function_in_module() {
+ check_assist(
+ generate_function,
+ r"
+mod bar {}
+
+fn foo() {
+ bar::my_fn$0()
+}
+",
+ r"
+mod bar {
+ pub(crate) fn my_fn() {
+ ${0:todo!()}
+ }
+}
+
+fn foo() {
+ bar::my_fn()
+}
+",
+ )
+ }
+
+ #[test]
+ fn qualified_path_uses_correct_scope() {
+ check_assist(
+ generate_function,
+ r#"
+mod foo {
+ pub struct Foo;
+}
+fn bar() {
+ use foo::Foo;
+ let foo = Foo;
+ baz$0(foo)
+}
+"#,
+ r#"
+mod foo {
+ pub struct Foo;
+}
+fn bar() {
+ use foo::Foo;
+ let foo = Foo;
+ baz(foo)
+}
+
+fn baz(foo: foo::Foo) {
+ ${0:todo!()}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn add_function_in_module_containing_other_items() {
+ check_assist(
+ generate_function,
+ r"
+mod bar {
+ fn something_else() {}
+}
+
+fn foo() {
+ bar::my_fn$0()
+}
+",
+ r"
+mod bar {
+ fn something_else() {}
+
+ pub(crate) fn my_fn() {
+ ${0:todo!()}
+ }
+}
+
+fn foo() {
+ bar::my_fn()
+}
+",
+ )
+ }
+
+ #[test]
+ fn add_function_in_nested_module() {
+ check_assist(
+ generate_function,
+ r"
+mod bar {
+ mod baz {}
+}
+
+fn foo() {
+ bar::baz::my_fn$0()
+}
+",
+ r"
+mod bar {
+ mod baz {
+ pub(crate) fn my_fn() {
+ ${0:todo!()}
+ }
+ }
+}
+
+fn foo() {
+ bar::baz::my_fn()
+}
+",
+ )
+ }
+
+ #[test]
+ fn add_function_in_another_file() {
+ check_assist(
+ generate_function,
+ r"
+//- /main.rs
+mod foo;
+
+fn main() {
+ foo::bar$0()
+}
+//- /foo.rs
+",
+ r"
+
+
+pub(crate) fn bar() {
+ ${0:todo!()}
+}",
+ )
+ }
+
+ #[test]
+ fn add_function_with_return_type() {
+ check_assist(
+ generate_function,
+ r"
+fn main() {
+ let x: u32 = foo$0();
+}
+",
+ r"
+fn main() {
+ let x: u32 = foo();
+}
+
+fn foo() -> u32 {
+ ${0:todo!()}
+}
+",
+ )
+ }
+
+ #[test]
+ fn add_function_not_applicable_if_function_already_exists() {
+ check_assist_not_applicable(
+ generate_function,
+ r"
+fn foo() {
+ bar$0();
+}
+
+fn bar() {}
+",
+ )
+ }
+
+ #[test]
+ fn add_function_not_applicable_if_unresolved_variable_in_call_is_selected() {
+ check_assist_not_applicable(
+ // bar is resolved, but baz isn't.
+ // The assist is only active if the cursor is on an unresolved path,
+ // but the assist should only be offered if the path is a function call.
+ generate_function,
+ r#"
+fn foo() {
+ bar(b$0az);
+}
+
+fn bar(baz: ()) {}
+"#,
+ )
+ }
+
+ #[test]
+ fn create_method_with_no_args() {
+ check_assist(
+ generate_function,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(&self) {
+ self.bar()$0;
+ }
+}
+"#,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(&self) {
+ self.bar();
+ }
+
+ fn bar(&self) ${0:-> _} {
+ todo!()
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn create_function_with_async() {
+ check_assist(
+ generate_function,
+ r"
+fn foo() {
+ $0bar(42).await();
+}
+",
+ r"
+fn foo() {
+ bar(42).await();
+}
+
+async fn bar(arg: i32) ${0:-> _} {
+ todo!()
+}
+",
+ )
+ }
+
+ #[test]
+ fn create_method() {
+ check_assist(
+ generate_function,
+ r"
+struct S;
+fn foo() {S.bar$0();}
+",
+ r"
+struct S;
+fn foo() {S.bar();}
+impl S {
+
+
+fn bar(&self) ${0:-> _} {
+ todo!()
+}
+}
+",
+ )
+ }
+
+ #[test]
+ fn create_method_within_an_impl() {
+ check_assist(
+ generate_function,
+ r"
+struct S;
+fn foo() {S.bar$0();}
+impl S {}
+
+",
+ r"
+struct S;
+fn foo() {S.bar();}
+impl S {
+ fn bar(&self) ${0:-> _} {
+ todo!()
+ }
+}
+
+",
+ )
+ }
+
+ #[test]
+ fn create_method_from_different_module() {
+ check_assist(
+ generate_function,
+ r"
+mod s {
+ pub struct S;
+}
+fn foo() {s::S.bar$0();}
+",
+ r"
+mod s {
+ pub struct S;
+impl S {
+
+
+ pub(crate) fn bar(&self) ${0:-> _} {
+ todo!()
+ }
+}
+}
+fn foo() {s::S.bar();}
+",
+ )
+ }
+
+ #[test]
+ fn create_method_from_descendant_module() {
+ check_assist(
+ generate_function,
+ r"
+struct S;
+mod s {
+ fn foo() {
+ super::S.bar$0();
+ }
+}
+
+",
+ r"
+struct S;
+mod s {
+ fn foo() {
+ super::S.bar();
+ }
+}
+impl S {
+
+
+fn bar(&self) ${0:-> _} {
+ todo!()
+}
+}
+
+",
+ )
+ }
+
+ #[test]
+ fn create_method_with_cursor_anywhere_on_call_expresion() {
+ check_assist(
+ generate_function,
+ r"
+struct S;
+fn foo() {$0S.bar();}
+",
+ r"
+struct S;
+fn foo() {S.bar();}
+impl S {
+
+
+fn bar(&self) ${0:-> _} {
+ todo!()
+}
+}
+",
+ )
+ }
+
+ #[test]
+ fn create_static_method() {
+ check_assist(
+ generate_function,
+ r"
+struct S;
+fn foo() {S::bar$0();}
+",
+ r"
+struct S;
+fn foo() {S::bar();}
+impl S {
+
+
+fn bar() ${0:-> _} {
+ todo!()
+}
+}
+",
+ )
+ }
+
+ #[test]
+ fn create_static_method_within_an_impl() {
+ check_assist(
+ generate_function,
+ r"
+struct S;
+fn foo() {S::bar$0();}
+impl S {}
+
+",
+ r"
+struct S;
+fn foo() {S::bar();}
+impl S {
+ fn bar() ${0:-> _} {
+ todo!()
+ }
+}
+
+",
+ )
+ }
+
+ #[test]
+ fn create_static_method_from_different_module() {
+ check_assist(
+ generate_function,
+ r"
+mod s {
+ pub struct S;
+}
+fn foo() {s::S::bar$0();}
+",
+ r"
+mod s {
+ pub struct S;
+impl S {
+
+
+ pub(crate) fn bar() ${0:-> _} {
+ todo!()
+ }
+}
+}
+fn foo() {s::S::bar();}
+",
+ )
+ }
+
+ #[test]
+ fn create_static_method_with_cursor_anywhere_on_call_expresion() {
+ check_assist(
+ generate_function,
+ r"
+struct S;
+fn foo() {$0S::bar();}
+",
+ r"
+struct S;
+fn foo() {S::bar();}
+impl S {
+
+
+fn bar() ${0:-> _} {
+ todo!()
+}
+}
+",
+ )
+ }
+
+ #[test]
+ fn no_panic_on_invalid_global_path() {
+ check_assist(
+ generate_function,
+ r"
+fn main() {
+ ::foo$0();
+}
+",
+ r"
+fn main() {
+ ::foo();
+}
+
+fn foo() ${0:-> _} {
+ todo!()
+}
+",
+ )
+ }
+
+ #[test]
+ fn handle_tuple_indexing() {
+ check_assist(
+ generate_function,
+ r"
+fn main() {
+ let a = ((),);
+ foo$0(a.0);
+}
+",
+ r"
+fn main() {
+ let a = ((),);
+ foo(a.0);
+}
+
+fn foo(a: ()) ${0:-> _} {
+ todo!()
+}
+",
+ )
+ }
+
+ #[test]
+ fn add_function_with_const_arg() {
+ check_assist(
+ generate_function,
+ r"
+const VALUE: usize = 0;
+fn main() {
+ foo$0(VALUE);
+}
+",
+ r"
+const VALUE: usize = 0;
+fn main() {
+ foo(VALUE);
+}
+
+fn foo(value: usize) ${0:-> _} {
+ todo!()
+}
+",
+ )
+ }
+
+ #[test]
+ fn add_function_with_static_arg() {
+ check_assist(
+ generate_function,
+ r"
+static VALUE: usize = 0;
+fn main() {
+ foo$0(VALUE);
+}
+",
+ r"
+static VALUE: usize = 0;
+fn main() {
+ foo(VALUE);
+}
+
+fn foo(value: usize) ${0:-> _} {
+ todo!()
+}
+",
+ )
+ }
+
+ #[test]
+ fn add_function_with_static_mut_arg() {
+ check_assist(
+ generate_function,
+ r"
+static mut VALUE: usize = 0;
+fn main() {
+ foo$0(VALUE);
+}
+",
+ r"
+static mut VALUE: usize = 0;
+fn main() {
+ foo(VALUE);
+}
+
+fn foo(value: usize) ${0:-> _} {
+ todo!()
+}
+",
+ )
+ }
+
+ #[test]
+ fn not_applicable_for_enum_variant() {
+ check_assist_not_applicable(
+ generate_function,
+ r"
+enum Foo {}
+fn main() {
+ Foo::Bar$0(true)
+}
+",
+ );
+ }
+
+ #[test]
+ fn applicable_for_enum_method() {
+ check_assist(
+ generate_function,
+ r"
+enum Foo {}
+fn main() {
+ Foo::new$0();
+}
+",
+ r"
+enum Foo {}
+fn main() {
+ Foo::new();
+}
+impl Foo {
+
+
+fn new() ${0:-> _} {
+ todo!()
+}
+}
+",
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter.rs
new file mode 100644
index 000000000..76fcef0ca
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter.rs
@@ -0,0 +1,492 @@
+use ide_db::famous_defs::FamousDefs;
+use stdx::{format_to, to_lower_snake_case};
+use syntax::ast::{self, AstNode, HasName, HasVisibility};
+
+use crate::{
+ utils::{convert_reference_type, find_impl_block_end, find_struct_impl, generate_impl_text},
+ AssistContext, AssistId, AssistKind, Assists, GroupLabel,
+};
+
+// Assist: generate_getter
+//
+// Generate a getter method.
+//
+// ```
+// # //- minicore: as_ref
+// # pub struct String;
+// # impl AsRef<str> for String {
+// # fn as_ref(&self) -> &str {
+// # ""
+// # }
+// # }
+// #
+// struct Person {
+// nam$0e: String,
+// }
+// ```
+// ->
+// ```
+// # pub struct String;
+// # impl AsRef<str> for String {
+// # fn as_ref(&self) -> &str {
+// # ""
+// # }
+// # }
+// #
+// struct Person {
+// name: String,
+// }
+//
+// impl Person {
+// fn $0name(&self) -> &str {
+// self.name.as_ref()
+// }
+// }
+// ```
+pub(crate) fn generate_getter(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ generate_getter_impl(acc, ctx, false)
+}
+
+// Assist: generate_getter_mut
+//
+// Generate a mut getter method.
+//
+// ```
+// struct Person {
+// nam$0e: String,
+// }
+// ```
+// ->
+// ```
+// struct Person {
+// name: String,
+// }
+//
+// impl Person {
+// fn $0name_mut(&mut self) -> &mut String {
+// &mut self.name
+// }
+// }
+// ```
+pub(crate) fn generate_getter_mut(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ generate_getter_impl(acc, ctx, true)
+}
+
+pub(crate) fn generate_getter_impl(
+ acc: &mut Assists,
+ ctx: &AssistContext<'_>,
+ mutable: bool,
+) -> Option<()> {
+ let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
+ let field = ctx.find_node_at_offset::<ast::RecordField>()?;
+
+ let field_name = field.name()?;
+ let field_ty = field.ty()?;
+
+ // Return early if we've found an existing fn
+ let mut fn_name = to_lower_snake_case(&field_name.to_string());
+ if mutable {
+ format_to!(fn_name, "_mut");
+ }
+ let impl_def = find_struct_impl(ctx, &ast::Adt::Struct(strukt.clone()), fn_name.as_str())?;
+
+ let (id, label) = if mutable {
+ ("generate_getter_mut", "Generate a mut getter method")
+ } else {
+ ("generate_getter", "Generate a getter method")
+ };
+ let target = field.syntax().text_range();
+ acc.add_group(
+ &GroupLabel("Generate getter/setter".to_owned()),
+ AssistId(id, AssistKind::Generate),
+ label,
+ target,
+ |builder| {
+ let mut buf = String::with_capacity(512);
+
+ if impl_def.is_some() {
+ buf.push('\n');
+ }
+
+ let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
+ let (ty, body) = if mutable {
+ (format!("&mut {}", field_ty), format!("&mut self.{}", field_name))
+ } else {
+ (|| {
+ let krate = ctx.sema.scope(field_ty.syntax())?.krate();
+ let famous_defs = &FamousDefs(&ctx.sema, krate);
+ ctx.sema
+ .resolve_type(&field_ty)
+ .and_then(|ty| convert_reference_type(ty, ctx.db(), famous_defs))
+ .map(|conversion| {
+ cov_mark::hit!(convert_reference_type);
+ (
+ conversion.convert_type(ctx.db()),
+ conversion.getter(field_name.to_string()),
+ )
+ })
+ })()
+ .unwrap_or_else(|| (format!("&{}", field_ty), format!("&self.{}", field_name)))
+ };
+
+ format_to!(
+ buf,
+ " {}fn {}(&{}self) -> {} {{
+ {}
+ }}",
+ vis,
+ fn_name,
+ mutable.then(|| "mut ").unwrap_or_default(),
+ ty,
+ body,
+ );
+
+ let start_offset = impl_def
+ .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf))
+ .unwrap_or_else(|| {
+ buf = generate_impl_text(&ast::Adt::Struct(strukt.clone()), &buf);
+ strukt.syntax().text_range().end()
+ });
+
+ match ctx.config.snippet_cap {
+ Some(cap) => {
+ builder.insert_snippet(cap, start_offset, buf.replacen("fn ", "fn $0", 1))
+ }
+ None => builder.insert(start_offset, buf),
+ }
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn test_generate_getter_from_field() {
+ check_assist(
+ generate_getter,
+ r#"
+struct Context {
+ dat$0a: Data,
+}
+"#,
+ r#"
+struct Context {
+ data: Data,
+}
+
+impl Context {
+ fn $0data(&self) -> &Data {
+ &self.data
+ }
+}
+"#,
+ );
+
+ check_assist(
+ generate_getter_mut,
+ r#"
+struct Context {
+ dat$0a: Data,
+}
+"#,
+ r#"
+struct Context {
+ data: Data,
+}
+
+impl Context {
+ fn $0data_mut(&mut self) -> &mut Data {
+ &mut self.data
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_getter_already_implemented() {
+ check_assist_not_applicable(
+ generate_getter,
+ r#"
+struct Context {
+ dat$0a: Data,
+}
+
+impl Context {
+ fn data(&self) -> &Data {
+ &self.data
+ }
+}
+"#,
+ );
+
+ check_assist_not_applicable(
+ generate_getter_mut,
+ r#"
+struct Context {
+ dat$0a: Data,
+}
+
+impl Context {
+ fn data_mut(&mut self) -> &mut Data {
+ &mut self.data
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_getter_from_field_with_visibility_marker() {
+ check_assist(
+ generate_getter,
+ r#"
+pub(crate) struct Context {
+ dat$0a: Data,
+}
+"#,
+ r#"
+pub(crate) struct Context {
+ data: Data,
+}
+
+impl Context {
+ pub(crate) fn $0data(&self) -> &Data {
+ &self.data
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_multiple_generate_getter() {
+ check_assist(
+ generate_getter,
+ r#"
+struct Context {
+ data: Data,
+ cou$0nt: usize,
+}
+
+impl Context {
+ fn data(&self) -> &Data {
+ &self.data
+ }
+}
+"#,
+ r#"
+struct Context {
+ data: Data,
+ count: usize,
+}
+
+impl Context {
+ fn data(&self) -> &Data {
+ &self.data
+ }
+
+ fn $0count(&self) -> &usize {
+ &self.count
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_not_a_special_case() {
+ cov_mark::check_count!(convert_reference_type, 0);
+ // Fake string which doesn't implement AsRef<str>
+ check_assist(
+ generate_getter,
+ r#"
+pub struct String;
+
+struct S { foo: $0String }
+"#,
+ r#"
+pub struct String;
+
+struct S { foo: String }
+
+impl S {
+ fn $0foo(&self) -> &String {
+ &self.foo
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_convert_reference_type() {
+ cov_mark::check_count!(convert_reference_type, 6);
+
+ // Copy
+ check_assist(
+ generate_getter,
+ r#"
+//- minicore: copy
+struct S { foo: $0bool }
+"#,
+ r#"
+struct S { foo: bool }
+
+impl S {
+ fn $0foo(&self) -> bool {
+ self.foo
+ }
+}
+"#,
+ );
+
+ // AsRef<str>
+ check_assist(
+ generate_getter,
+ r#"
+//- minicore: as_ref
+pub struct String;
+impl AsRef<str> for String {
+ fn as_ref(&self) -> &str {
+ ""
+ }
+}
+
+struct S { foo: $0String }
+"#,
+ r#"
+pub struct String;
+impl AsRef<str> for String {
+ fn as_ref(&self) -> &str {
+ ""
+ }
+}
+
+struct S { foo: String }
+
+impl S {
+ fn $0foo(&self) -> &str {
+ self.foo.as_ref()
+ }
+}
+"#,
+ );
+
+ // AsRef<T>
+ check_assist(
+ generate_getter,
+ r#"
+//- minicore: as_ref
+struct Sweets;
+
+pub struct Box<T>(T);
+impl<T> AsRef<T> for Box<T> {
+ fn as_ref(&self) -> &T {
+ &self.0
+ }
+}
+
+struct S { foo: $0Box<Sweets> }
+"#,
+ r#"
+struct Sweets;
+
+pub struct Box<T>(T);
+impl<T> AsRef<T> for Box<T> {
+ fn as_ref(&self) -> &T {
+ &self.0
+ }
+}
+
+struct S { foo: Box<Sweets> }
+
+impl S {
+ fn $0foo(&self) -> &Sweets {
+ self.foo.as_ref()
+ }
+}
+"#,
+ );
+
+ // AsRef<[T]>
+ check_assist(
+ generate_getter,
+ r#"
+//- minicore: as_ref
+pub struct Vec<T>;
+impl<T> AsRef<[T]> for Vec<T> {
+ fn as_ref(&self) -> &[T] {
+ &[]
+ }
+}
+
+struct S { foo: $0Vec<()> }
+"#,
+ r#"
+pub struct Vec<T>;
+impl<T> AsRef<[T]> for Vec<T> {
+ fn as_ref(&self) -> &[T] {
+ &[]
+ }
+}
+
+struct S { foo: Vec<()> }
+
+impl S {
+ fn $0foo(&self) -> &[()] {
+ self.foo.as_ref()
+ }
+}
+"#,
+ );
+
+ // Option
+ check_assist(
+ generate_getter,
+ r#"
+//- minicore: option
+struct Failure;
+
+struct S { foo: $0Option<Failure> }
+"#,
+ r#"
+struct Failure;
+
+struct S { foo: Option<Failure> }
+
+impl S {
+ fn $0foo(&self) -> Option<&Failure> {
+ self.foo.as_ref()
+ }
+}
+"#,
+ );
+
+ // Result
+ check_assist(
+ generate_getter,
+ r#"
+//- minicore: result
+struct Context {
+ dat$0a: Result<bool, i32>,
+}
+"#,
+ r#"
+struct Context {
+ data: Result<bool, i32>,
+}
+
+impl Context {
+ fn $0data(&self) -> Result<&bool, &i32> {
+ self.data.as_ref()
+ }
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs
new file mode 100644
index 000000000..68287a20b
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs
@@ -0,0 +1,177 @@
+use syntax::ast::{self, AstNode, HasName};
+
+use crate::{utils::generate_impl_text, AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: generate_impl
+//
+// Adds a new inherent impl for a type.
+//
+// ```
+// struct Ctx<T: Clone> {
+// data: T,$0
+// }
+// ```
+// ->
+// ```
+// struct Ctx<T: Clone> {
+// data: T,
+// }
+//
+// impl<T: Clone> Ctx<T> {
+// $0
+// }
+// ```
+pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let nominal = ctx.find_node_at_offset::<ast::Adt>()?;
+ let name = nominal.name()?;
+ let target = nominal.syntax().text_range();
+
+ acc.add(
+ AssistId("generate_impl", AssistKind::Generate),
+ format!("Generate impl for `{}`", name),
+ target,
+ |edit| {
+ let start_offset = nominal.syntax().text_range().end();
+ match ctx.config.snippet_cap {
+ Some(cap) => {
+ let snippet = generate_impl_text(&nominal, " $0");
+ edit.insert_snippet(cap, start_offset, snippet);
+ }
+ None => {
+ let snippet = generate_impl_text(&nominal, "");
+ edit.insert(start_offset, snippet);
+ }
+ }
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_target};
+
+ use super::*;
+
+ #[test]
+ fn test_add_impl() {
+ check_assist(
+ generate_impl,
+ "struct Foo {$0}\n",
+ "struct Foo {}\n\nimpl Foo {\n $0\n}\n",
+ );
+ check_assist(
+ generate_impl,
+ "struct Foo<T: Clone> {$0}",
+ "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n $0\n}",
+ );
+ check_assist(
+ generate_impl,
+ "struct Foo<'a, T: Foo<'a>> {$0}",
+ "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}",
+ );
+ check_assist(
+ generate_impl,
+ r#"
+ struct MyOwnArray<T, const S: usize> {}$0"#,
+ r#"
+ struct MyOwnArray<T, const S: usize> {}
+
+ impl<T, const S: usize> MyOwnArray<T, S> {
+ $0
+ }"#,
+ );
+ check_assist(
+ generate_impl,
+ r#"
+ #[cfg(feature = "foo")]
+ struct Foo<'a, T: Foo<'a>> {$0}"#,
+ r#"
+ #[cfg(feature = "foo")]
+ struct Foo<'a, T: Foo<'a>> {}
+
+ #[cfg(feature = "foo")]
+ impl<'a, T: Foo<'a>> Foo<'a, T> {
+ $0
+ }"#,
+ );
+
+ check_assist(
+ generate_impl,
+ r#"
+ #[cfg(not(feature = "foo"))]
+ struct Foo<'a, T: Foo<'a>> {$0}"#,
+ r#"
+ #[cfg(not(feature = "foo"))]
+ struct Foo<'a, T: Foo<'a>> {}
+
+ #[cfg(not(feature = "foo"))]
+ impl<'a, T: Foo<'a>> Foo<'a, T> {
+ $0
+ }"#,
+ );
+
+ check_assist(
+ generate_impl,
+ r#"
+ struct Defaulted<T = i32> {}$0"#,
+ r#"
+ struct Defaulted<T = i32> {}
+
+ impl<T> Defaulted<T> {
+ $0
+ }"#,
+ );
+
+ check_assist(
+ generate_impl,
+ r#"
+ struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {}$0"#,
+ r#"
+ struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {}
+
+ impl<'a, 'b: 'a, T: Debug + Clone + 'a + 'b, const S: usize> Defaulted<'a, 'b, T, S> {
+ $0
+ }"#,
+ );
+
+ check_assist(
+ generate_impl,
+ r#"pub trait Trait {}
+struct Struct<T>$0
+where
+ T: Trait,
+{
+ inner: T,
+}"#,
+ r#"pub trait Trait {}
+struct Struct<T>
+where
+ T: Trait,
+{
+ inner: T,
+}
+
+impl<T> Struct<T>
+where
+ T: Trait,
+{
+ $0
+}"#,
+ );
+ }
+
+ #[test]
+ fn add_impl_target() {
+ check_assist_target(
+ generate_impl,
+ "
+struct SomeThingIrrelevant;
+/// Has a lifetime parameter
+struct Foo<'a, T: Foo<'a>> {$0}
+struct EvenMoreIrrelevant;
+",
+ "/// Has a lifetime parameter
+struct Foo<'a, T: Foo<'a>> {}",
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_is_empty_from_len.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_is_empty_from_len.rs
new file mode 100644
index 000000000..9ce525ca3
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_is_empty_from_len.rs
@@ -0,0 +1,295 @@
+use hir::{known, HasSource, Name};
+use syntax::{
+ ast::{self, HasName},
+ AstNode,
+};
+
+use crate::{
+ assist_context::{AssistContext, Assists},
+ AssistId, AssistKind,
+};
+
+// Assist: generate_is_empty_from_len
+//
+// Generates is_empty implementation from the len method.
+//
+// ```
+// struct MyStruct { data: Vec<String> }
+//
+// impl MyStruct {
+// #[must_use]
+// p$0ub fn len(&self) -> usize {
+// self.data.len()
+// }
+// }
+// ```
+// ->
+// ```
+// struct MyStruct { data: Vec<String> }
+//
+// impl MyStruct {
+// #[must_use]
+// pub fn len(&self) -> usize {
+// self.data.len()
+// }
+//
+// #[must_use]
+// pub fn is_empty(&self) -> bool {
+// self.len() == 0
+// }
+// }
+// ```
+pub(crate) fn generate_is_empty_from_len(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let fn_node = ctx.find_node_at_offset::<ast::Fn>()?;
+ let fn_name = fn_node.name()?;
+
+ if fn_name.text() != "len" {
+ cov_mark::hit!(len_function_not_present);
+ return None;
+ }
+
+ if fn_node.param_list()?.params().next().is_some() {
+ cov_mark::hit!(len_function_with_parameters);
+ return None;
+ }
+
+ let impl_ = fn_node.syntax().ancestors().find_map(ast::Impl::cast)?;
+ let len_fn = get_impl_method(ctx, &impl_, &known::len)?;
+ if !len_fn.ret_type(ctx.sema.db).is_usize() {
+ cov_mark::hit!(len_fn_different_return_type);
+ return None;
+ }
+
+ if get_impl_method(ctx, &impl_, &known::is_empty).is_some() {
+ cov_mark::hit!(is_empty_already_implemented);
+ return None;
+ }
+
+ let node = len_fn.source(ctx.sema.db)?;
+ let range = node.syntax().value.text_range();
+
+ acc.add(
+ AssistId("generate_is_empty_from_len", AssistKind::Generate),
+ "Generate a is_empty impl from a len function",
+ range,
+ |builder| {
+ let code = r#"
+
+ #[must_use]
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }"#
+ .to_string();
+ builder.insert(range.end(), code)
+ },
+ )
+}
+
+fn get_impl_method(
+ ctx: &AssistContext<'_>,
+ impl_: &ast::Impl,
+ fn_name: &Name,
+) -> Option<hir::Function> {
+ let db = ctx.sema.db;
+ let impl_def: hir::Impl = ctx.sema.to_def(impl_)?;
+
+ let scope = ctx.sema.scope(impl_.syntax())?;
+ let ty = impl_def.self_ty(db);
+ ty.iterate_method_candidates(
+ db,
+ &scope,
+ &scope.visible_traits().0,
+ None,
+ Some(fn_name),
+ |func| Some(func),
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn len_function_not_present() {
+ cov_mark::check!(len_function_not_present);
+ check_assist_not_applicable(
+ generate_is_empty_from_len,
+ r#"
+struct MyStruct { data: Vec<String> }
+
+impl MyStruct {
+ p$0ub fn test(&self) -> usize {
+ self.data.len()
+ }
+ }
+"#,
+ );
+ }
+
+ #[test]
+ fn len_function_with_parameters() {
+ cov_mark::check!(len_function_with_parameters);
+ check_assist_not_applicable(
+ generate_is_empty_from_len,
+ r#"
+struct MyStruct { data: Vec<String> }
+
+impl MyStruct {
+ #[must_use]
+ p$0ub fn len(&self, _i: bool) -> usize {
+ self.data.len()
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn is_empty_already_implemented() {
+ cov_mark::check!(is_empty_already_implemented);
+ check_assist_not_applicable(
+ generate_is_empty_from_len,
+ r#"
+struct MyStruct { data: Vec<String> }
+
+impl MyStruct {
+ #[must_use]
+ p$0ub fn len(&self) -> usize {
+ self.data.len()
+ }
+
+ #[must_use]
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn len_fn_different_return_type() {
+ cov_mark::check!(len_fn_different_return_type);
+ check_assist_not_applicable(
+ generate_is_empty_from_len,
+ r#"
+struct MyStruct { data: Vec<String> }
+
+impl MyStruct {
+ #[must_use]
+ p$0ub fn len(&self) -> u32 {
+ self.data.len()
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn generate_is_empty() {
+ check_assist(
+ generate_is_empty_from_len,
+ r#"
+struct MyStruct { data: Vec<String> }
+
+impl MyStruct {
+ #[must_use]
+ p$0ub fn len(&self) -> usize {
+ self.data.len()
+ }
+}
+"#,
+ r#"
+struct MyStruct { data: Vec<String> }
+
+impl MyStruct {
+ #[must_use]
+ pub fn len(&self) -> usize {
+ self.data.len()
+ }
+
+ #[must_use]
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn multiple_functions_in_impl() {
+ check_assist(
+ generate_is_empty_from_len,
+ r#"
+struct MyStruct { data: Vec<String> }
+
+impl MyStruct {
+ #[must_use]
+ pub fn new() -> Self {
+ Self { data: 0 }
+ }
+
+ #[must_use]
+ p$0ub fn len(&self) -> usize {
+ self.data.len()
+ }
+
+ pub fn work(&self) -> Option<usize> {
+
+ }
+}
+"#,
+ r#"
+struct MyStruct { data: Vec<String> }
+
+impl MyStruct {
+ #[must_use]
+ pub fn new() -> Self {
+ Self { data: 0 }
+ }
+
+ #[must_use]
+ pub fn len(&self) -> usize {
+ self.data.len()
+ }
+
+ #[must_use]
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+
+ pub fn work(&self) -> Option<usize> {
+
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn multiple_impls() {
+ check_assist_not_applicable(
+ generate_is_empty_from_len,
+ r#"
+struct MyStruct { data: Vec<String> }
+
+impl MyStruct {
+ #[must_use]
+ p$0ub fn len(&self) -> usize {
+ self.data.len()
+ }
+}
+
+impl MyStruct {
+ #[must_use]
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs
new file mode 100644
index 000000000..6c93875e9
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs
@@ -0,0 +1,495 @@
+use ide_db::{
+ imports::import_assets::item_for_path_search, use_trivial_contructor::use_trivial_constructor,
+};
+use itertools::Itertools;
+use stdx::format_to;
+use syntax::ast::{self, AstNode, HasName, HasVisibility, StructKind};
+
+use crate::{
+ utils::{find_impl_block_start, find_struct_impl, generate_impl_text},
+ AssistContext, AssistId, AssistKind, Assists,
+};
+
+// Assist: generate_new
+//
+// Adds a `fn new` for a type.
+//
+// ```
+// struct Ctx<T: Clone> {
+// data: T,$0
+// }
+// ```
+// ->
+// ```
+// struct Ctx<T: Clone> {
+// data: T,
+// }
+//
+// impl<T: Clone> Ctx<T> {
+// fn $0new(data: T) -> Self { Self { data } }
+// }
+// ```
+pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
+
+ // We want to only apply this to non-union structs with named fields
+ let field_list = match strukt.kind() {
+ StructKind::Record(named) => named,
+ _ => return None,
+ };
+
+ // Return early if we've found an existing new fn
+ let impl_def = find_struct_impl(ctx, &ast::Adt::Struct(strukt.clone()), "new")?;
+
+ let current_module = ctx.sema.scope(strukt.syntax())?.module();
+
+ let target = strukt.syntax().text_range();
+ acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| {
+ let mut buf = String::with_capacity(512);
+
+ if impl_def.is_some() {
+ buf.push('\n');
+ }
+
+ let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
+
+ let trivial_constructors = field_list
+ .fields()
+ .map(|f| {
+ let ty = ctx.sema.resolve_type(&f.ty()?)?;
+
+ let item_in_ns = hir::ItemInNs::from(hir::ModuleDef::from(ty.as_adt()?));
+
+ let type_path = current_module
+ .find_use_path(ctx.sema.db, item_for_path_search(ctx.sema.db, item_in_ns)?)?;
+
+ let expr = use_trivial_constructor(
+ &ctx.sema.db,
+ ide_db::helpers::mod_path_to_ast(&type_path),
+ &ty,
+ )?;
+
+ Some(format!("{}: {}", f.name()?.syntax(), expr))
+ })
+ .collect::<Vec<_>>();
+
+ let params = field_list
+ .fields()
+ .enumerate()
+ .filter_map(|(i, f)| {
+ if trivial_constructors[i].is_none() {
+ Some(format!("{}: {}", f.name()?.syntax(), f.ty()?.syntax()))
+ } else {
+ None
+ }
+ })
+ .format(", ");
+
+ let fields = field_list
+ .fields()
+ .enumerate()
+ .filter_map(|(i, f)| {
+ let contructor = trivial_constructors[i].clone();
+ if contructor.is_some() {
+ contructor
+ } else {
+ Some(f.name()?.to_string())
+ }
+ })
+ .format(", ");
+
+ format_to!(buf, " {}fn new({}) -> Self {{ Self {{ {} }} }}", vis, params, fields);
+
+ let start_offset = impl_def
+ .and_then(|impl_def| find_impl_block_start(impl_def, &mut buf))
+ .unwrap_or_else(|| {
+ buf = generate_impl_text(&ast::Adt::Struct(strukt.clone()), &buf);
+ strukt.syntax().text_range().end()
+ });
+
+ match ctx.config.snippet_cap {
+ None => builder.insert(start_offset, buf),
+ Some(cap) => {
+ buf = buf.replace("fn new", "fn $0new");
+ builder.insert_snippet(cap, start_offset, buf);
+ }
+ }
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
+
+ use super::*;
+
+ #[test]
+ fn test_generate_new_with_zst_fields() {
+ check_assist(
+ generate_new,
+ r#"
+struct Empty;
+
+struct Foo { empty: Empty $0}
+"#,
+ r#"
+struct Empty;
+
+struct Foo { empty: Empty }
+
+impl Foo {
+ fn $0new() -> Self { Self { empty: Empty } }
+}
+"#,
+ );
+ check_assist(
+ generate_new,
+ r#"
+struct Empty;
+
+struct Foo { baz: String, empty: Empty $0}
+"#,
+ r#"
+struct Empty;
+
+struct Foo { baz: String, empty: Empty }
+
+impl Foo {
+ fn $0new(baz: String) -> Self { Self { baz, empty: Empty } }
+}
+"#,
+ );
+ check_assist(
+ generate_new,
+ r#"
+enum Empty { Bar }
+
+struct Foo { empty: Empty $0}
+"#,
+ r#"
+enum Empty { Bar }
+
+struct Foo { empty: Empty }
+
+impl Foo {
+ fn $0new() -> Self { Self { empty: Empty::Bar } }
+}
+"#,
+ );
+
+ // make sure the assist only works on unit variants
+ check_assist(
+ generate_new,
+ r#"
+struct Empty {}
+
+struct Foo { empty: Empty $0}
+"#,
+ r#"
+struct Empty {}
+
+struct Foo { empty: Empty }
+
+impl Foo {
+ fn $0new(empty: Empty) -> Self { Self { empty } }
+}
+"#,
+ );
+ check_assist(
+ generate_new,
+ r#"
+enum Empty { Bar {} }
+
+struct Foo { empty: Empty $0}
+"#,
+ r#"
+enum Empty { Bar {} }
+
+struct Foo { empty: Empty }
+
+impl Foo {
+ fn $0new(empty: Empty) -> Self { Self { empty } }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_new() {
+ check_assist(
+ generate_new,
+ r#"
+struct Foo {$0}
+"#,
+ r#"
+struct Foo {}
+
+impl Foo {
+ fn $0new() -> Self { Self { } }
+}
+"#,
+ );
+ check_assist(
+ generate_new,
+ r#"
+struct Foo<T: Clone> {$0}
+"#,
+ r#"
+struct Foo<T: Clone> {}
+
+impl<T: Clone> Foo<T> {
+ fn $0new() -> Self { Self { } }
+}
+"#,
+ );
+ check_assist(
+ generate_new,
+ r#"
+struct Foo<'a, T: Foo<'a>> {$0}
+"#,
+ r#"
+struct Foo<'a, T: Foo<'a>> {}
+
+impl<'a, T: Foo<'a>> Foo<'a, T> {
+ fn $0new() -> Self { Self { } }
+}
+"#,
+ );
+ check_assist(
+ generate_new,
+ r#"
+struct Foo { baz: String $0}
+"#,
+ r#"
+struct Foo { baz: String }
+
+impl Foo {
+ fn $0new(baz: String) -> Self { Self { baz } }
+}
+"#,
+ );
+ check_assist(
+ generate_new,
+ r#"
+struct Foo { baz: String, qux: Vec<i32> $0}
+"#,
+ r#"
+struct Foo { baz: String, qux: Vec<i32> }
+
+impl Foo {
+ fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn check_that_visibility_modifiers_dont_get_brought_in() {
+ check_assist(
+ generate_new,
+ r#"
+struct Foo { pub baz: String, pub qux: Vec<i32> $0}
+"#,
+ r#"
+struct Foo { pub baz: String, pub qux: Vec<i32> }
+
+impl Foo {
+ fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn check_it_reuses_existing_impls() {
+ check_assist(
+ generate_new,
+ r#"
+struct Foo {$0}
+
+impl Foo {}
+"#,
+ r#"
+struct Foo {}
+
+impl Foo {
+ fn $0new() -> Self { Self { } }
+}
+"#,
+ );
+ check_assist(
+ generate_new,
+ r#"
+struct Foo {$0}
+
+impl Foo {
+ fn qux(&self) {}
+}
+"#,
+ r#"
+struct Foo {}
+
+impl Foo {
+ fn $0new() -> Self { Self { } }
+
+ fn qux(&self) {}
+}
+"#,
+ );
+
+ check_assist(
+ generate_new,
+ r#"
+struct Foo {$0}
+
+impl Foo {
+ fn qux(&self) {}
+ fn baz() -> i32 {
+ 5
+ }
+}
+"#,
+ r#"
+struct Foo {}
+
+impl Foo {
+ fn $0new() -> Self { Self { } }
+
+ fn qux(&self) {}
+ fn baz() -> i32 {
+ 5
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn check_visibility_of_new_fn_based_on_struct() {
+ check_assist(
+ generate_new,
+ r#"
+pub struct Foo {$0}
+"#,
+ r#"
+pub struct Foo {}
+
+impl Foo {
+ pub fn $0new() -> Self { Self { } }
+}
+"#,
+ );
+ check_assist(
+ generate_new,
+ r#"
+pub(crate) struct Foo {$0}
+"#,
+ r#"
+pub(crate) struct Foo {}
+
+impl Foo {
+ pub(crate) fn $0new() -> Self { Self { } }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn generate_new_not_applicable_if_fn_exists() {
+ check_assist_not_applicable(
+ generate_new,
+ r#"
+struct Foo {$0}
+
+impl Foo {
+ fn new() -> Self {
+ Self
+ }
+}
+"#,
+ );
+
+ check_assist_not_applicable(
+ generate_new,
+ r#"
+struct Foo {$0}
+
+impl Foo {
+ fn New() -> Self {
+ Self
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn generate_new_target() {
+ check_assist_target(
+ generate_new,
+ r#"
+struct SomeThingIrrelevant;
+/// Has a lifetime parameter
+struct Foo<'a, T: Foo<'a>> {$0}
+struct EvenMoreIrrelevant;
+"#,
+ "/// Has a lifetime parameter
+struct Foo<'a, T: Foo<'a>> {}",
+ );
+ }
+
+ #[test]
+ fn test_unrelated_new() {
+ check_assist(
+ generate_new,
+ r#"
+pub struct AstId<N: AstNode> {
+ file_id: HirFileId,
+ file_ast_id: FileAstId<N>,
+}
+
+impl<N: AstNode> AstId<N> {
+ pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> {
+ AstId { file_id, file_ast_id }
+ }
+}
+
+pub struct Source<T> {
+ pub file_id: HirFileId,$0
+ pub ast: T,
+}
+
+impl<T> Source<T> {
+ pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
+ Source { file_id: self.file_id, ast: f(self.ast) }
+ }
+}
+"#,
+ r#"
+pub struct AstId<N: AstNode> {
+ file_id: HirFileId,
+ file_ast_id: FileAstId<N>,
+}
+
+impl<N: AstNode> AstId<N> {
+ pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> {
+ AstId { file_id, file_ast_id }
+ }
+}
+
+pub struct Source<T> {
+ pub file_id: HirFileId,
+ pub ast: T,
+}
+
+impl<T> Source<T> {
+ pub fn $0new(file_id: HirFileId, ast: T) -> Self { Self { file_id, ast } }
+
+ pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
+ Source { file_id: self.file_id, ast: f(self.ast) }
+ }
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_setter.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_setter.rs
new file mode 100644
index 000000000..2a7ad6ce3
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_setter.rs
@@ -0,0 +1,184 @@
+use stdx::{format_to, to_lower_snake_case};
+use syntax::ast::{self, AstNode, HasName, HasVisibility};
+
+use crate::{
+ utils::{find_impl_block_end, find_struct_impl, generate_impl_text},
+ AssistContext, AssistId, AssistKind, Assists, GroupLabel,
+};
+
+// Assist: generate_setter
+//
+// Generate a setter method.
+//
+// ```
+// struct Person {
+// nam$0e: String,
+// }
+// ```
+// ->
+// ```
+// struct Person {
+// name: String,
+// }
+//
+// impl Person {
+// fn set_name(&mut self, name: String) {
+// self.name = name;
+// }
+// }
+// ```
+pub(crate) fn generate_setter(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
+ let field = ctx.find_node_at_offset::<ast::RecordField>()?;
+
+ let field_name = field.name()?;
+ let field_ty = field.ty()?;
+
+ // Return early if we've found an existing fn
+ let fn_name = to_lower_snake_case(&field_name.to_string());
+ let impl_def = find_struct_impl(
+ ctx,
+ &ast::Adt::Struct(strukt.clone()),
+ format!("set_{}", fn_name).as_str(),
+ )?;
+
+ let target = field.syntax().text_range();
+ acc.add_group(
+ &GroupLabel("Generate getter/setter".to_owned()),
+ AssistId("generate_setter", AssistKind::Generate),
+ "Generate a setter method",
+ target,
+ |builder| {
+ let mut buf = String::with_capacity(512);
+
+ if impl_def.is_some() {
+ buf.push('\n');
+ }
+
+ let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
+ format_to!(
+ buf,
+ " {}fn set_{}(&mut self, {}: {}) {{
+ self.{} = {};
+ }}",
+ vis,
+ fn_name,
+ fn_name,
+ field_ty,
+ fn_name,
+ fn_name,
+ );
+
+ let start_offset = impl_def
+ .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf))
+ .unwrap_or_else(|| {
+ buf = generate_impl_text(&ast::Adt::Struct(strukt.clone()), &buf);
+ strukt.syntax().text_range().end()
+ });
+
+ builder.insert(start_offset, buf);
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ fn check_not_applicable(ra_fixture: &str) {
+ check_assist_not_applicable(generate_setter, ra_fixture)
+ }
+
+ #[test]
+ fn test_generate_setter_from_field() {
+ check_assist(
+ generate_setter,
+ r#"
+struct Person<T: Clone> {
+ dat$0a: T,
+}"#,
+ r#"
+struct Person<T: Clone> {
+ data: T,
+}
+
+impl<T: Clone> Person<T> {
+ fn set_data(&mut self, data: T) {
+ self.data = data;
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_setter_already_implemented() {
+ check_not_applicable(
+ r#"
+struct Person<T: Clone> {
+ dat$0a: T,
+}
+
+impl<T: Clone> Person<T> {
+ fn set_data(&mut self, data: T) {
+ self.data = data;
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_generate_setter_from_field_with_visibility_marker() {
+ check_assist(
+ generate_setter,
+ r#"
+pub(crate) struct Person<T: Clone> {
+ dat$0a: T,
+}"#,
+ r#"
+pub(crate) struct Person<T: Clone> {
+ data: T,
+}
+
+impl<T: Clone> Person<T> {
+ pub(crate) fn set_data(&mut self, data: T) {
+ self.data = data;
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_multiple_generate_setter() {
+ check_assist(
+ generate_setter,
+ r#"
+struct Context<T: Clone> {
+ data: T,
+ cou$0nt: usize,
+}
+
+impl<T: Clone> Context<T> {
+ fn set_data(&mut self, data: T) {
+ self.data = data;
+ }
+}"#,
+ r#"
+struct Context<T: Clone> {
+ data: T,
+ count: usize,
+}
+
+impl<T: Clone> Context<T> {
+ fn set_data(&mut self, data: T) {
+ self.data = data;
+ }
+
+ fn set_count(&mut self, count: usize) {
+ self.count = count;
+ }
+}"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs
new file mode 100644
index 000000000..80d3b9255
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs
@@ -0,0 +1,1194 @@
+use ast::make;
+use either::Either;
+use hir::{db::HirDatabase, PathResolution, Semantics, TypeInfo};
+use ide_db::{
+ base_db::{FileId, FileRange},
+ defs::Definition,
+ imports::insert_use::remove_path_if_in_use_stmt,
+ path_transform::PathTransform,
+ search::{FileReference, SearchScope},
+ syntax_helpers::{insert_whitespace_into_node::insert_ws_into, node_ext::expr_as_name_ref},
+ RootDatabase,
+};
+use itertools::{izip, Itertools};
+use syntax::{
+ ast::{self, edit_in_place::Indent, HasArgList, PathExpr},
+ ted, AstNode,
+};
+
+use crate::{
+ assist_context::{AssistContext, Assists},
+ AssistId, AssistKind,
+};
+
+// Assist: inline_into_callers
+//
+// Inline a function or method body into all of its callers where possible, creating a `let` statement per parameter
+// unless the parameter can be inlined. The parameter will be inlined either if it the supplied argument is a simple local
+// or if the parameter is only accessed inside the function body once.
+// If all calls can be inlined the function will be removed.
+//
+// ```
+// fn print(_: &str) {}
+// fn foo$0(word: &str) {
+// if !word.is_empty() {
+// print(word);
+// }
+// }
+// fn bar() {
+// foo("안녕하세요");
+// foo("여러분");
+// }
+// ```
+// ->
+// ```
+// fn print(_: &str) {}
+//
+// fn bar() {
+// {
+// let word = "안녕하세요";
+// if !word.is_empty() {
+// print(word);
+// }
+// };
+// {
+// let word = "여러분";
+// if !word.is_empty() {
+// print(word);
+// }
+// };
+// }
+// ```
+pub(crate) fn inline_into_callers(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let def_file = ctx.file_id();
+ let name = ctx.find_node_at_offset::<ast::Name>()?;
+ let ast_func = name.syntax().parent().and_then(ast::Fn::cast)?;
+ let func_body = ast_func.body()?;
+ let param_list = ast_func.param_list()?;
+
+ let function = ctx.sema.to_def(&ast_func)?;
+
+ let params = get_fn_params(ctx.sema.db, function, &param_list)?;
+
+ let usages = Definition::Function(function).usages(&ctx.sema);
+ if !usages.at_least_one() {
+ return None;
+ }
+
+ let is_recursive_fn = usages
+ .clone()
+ .in_scope(SearchScope::file_range(FileRange {
+ file_id: def_file,
+ range: func_body.syntax().text_range(),
+ }))
+ .at_least_one();
+ if is_recursive_fn {
+ cov_mark::hit!(inline_into_callers_recursive);
+ return None;
+ }
+
+ acc.add(
+ AssistId("inline_into_callers", AssistKind::RefactorInline),
+ "Inline into all callers",
+ name.syntax().text_range(),
+ |builder| {
+ let mut usages = usages.all();
+ let current_file_usage = usages.references.remove(&def_file);
+
+ let mut remove_def = true;
+ let mut inline_refs_for_file = |file_id, refs: Vec<FileReference>| {
+ builder.edit_file(file_id);
+ let count = refs.len();
+ // The collects are required as we are otherwise iterating while mutating 🙅‍♀️🙅‍♂️
+ let (name_refs, name_refs_use): (Vec<_>, Vec<_>) = refs
+ .into_iter()
+ .filter_map(|file_ref| match file_ref.name {
+ ast::NameLike::NameRef(name_ref) => Some(name_ref),
+ _ => None,
+ })
+ .partition_map(|name_ref| {
+ match name_ref.syntax().ancestors().find_map(ast::UseTree::cast) {
+ Some(use_tree) => Either::Right(builder.make_mut(use_tree)),
+ None => Either::Left(name_ref),
+ }
+ });
+ let call_infos: Vec<_> = name_refs
+ .into_iter()
+ .filter_map(CallInfo::from_name_ref)
+ .map(|call_info| {
+ let mut_node = builder.make_syntax_mut(call_info.node.syntax().clone());
+ (call_info, mut_node)
+ })
+ .collect();
+ let replaced = call_infos
+ .into_iter()
+ .map(|(call_info, mut_node)| {
+ let replacement =
+ inline(&ctx.sema, def_file, function, &func_body, &params, &call_info);
+ ted::replace(mut_node, replacement.syntax());
+ })
+ .count();
+ if replaced + name_refs_use.len() == count {
+ // we replaced all usages in this file, so we can remove the imports
+ name_refs_use.into_iter().for_each(|use_tree| {
+ if let Some(path) = use_tree.path() {
+ remove_path_if_in_use_stmt(&path);
+ }
+ })
+ } else {
+ remove_def = false;
+ }
+ };
+ for (file_id, refs) in usages.into_iter() {
+ inline_refs_for_file(file_id, refs);
+ }
+ match current_file_usage {
+ Some(refs) => inline_refs_for_file(def_file, refs),
+ None => builder.edit_file(def_file),
+ }
+ if remove_def {
+ builder.delete(ast_func.syntax().text_range());
+ }
+ },
+ )
+}
+
+// Assist: inline_call
+//
+// Inlines a function or method body creating a `let` statement per parameter unless the parameter
+// can be inlined. The parameter will be inlined either if it the supplied argument is a simple local
+// or if the parameter is only accessed inside the function body once.
+//
+// ```
+// # //- minicore: option
+// fn foo(name: Option<&str>) {
+// let name = name.unwrap$0();
+// }
+// ```
+// ->
+// ```
+// fn foo(name: Option<&str>) {
+// let name = match name {
+// Some(val) => val,
+// None => panic!("called `Option::unwrap()` on a `None` value"),
+// };
+// }
+// ```
+pub(crate) fn inline_call(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let name_ref: ast::NameRef = ctx.find_node_at_offset()?;
+ let call_info = CallInfo::from_name_ref(name_ref.clone())?;
+ let (function, label) = match &call_info.node {
+ ast::CallableExpr::Call(call) => {
+ let path = match call.expr()? {
+ ast::Expr::PathExpr(path) => path.path(),
+ _ => None,
+ }?;
+ let function = match ctx.sema.resolve_path(&path)? {
+ PathResolution::Def(hir::ModuleDef::Function(f)) => f,
+ _ => return None,
+ };
+ (function, format!("Inline `{}`", path))
+ }
+ ast::CallableExpr::MethodCall(call) => {
+ (ctx.sema.resolve_method_call(call)?, format!("Inline `{}`", name_ref))
+ }
+ };
+
+ let fn_source = ctx.sema.source(function)?;
+ let fn_body = fn_source.value.body()?;
+ let param_list = fn_source.value.param_list()?;
+
+ let FileRange { file_id, range } = fn_source.syntax().original_file_range(ctx.sema.db);
+ if file_id == ctx.file_id() && range.contains(ctx.offset()) {
+ cov_mark::hit!(inline_call_recursive);
+ return None;
+ }
+ let params = get_fn_params(ctx.sema.db, function, &param_list)?;
+
+ if call_info.arguments.len() != params.len() {
+ // Can't inline the function because they've passed the wrong number of
+ // arguments to this function
+ cov_mark::hit!(inline_call_incorrect_number_of_arguments);
+ return None;
+ }
+
+ let syntax = call_info.node.syntax().clone();
+ acc.add(
+ AssistId("inline_call", AssistKind::RefactorInline),
+ label,
+ syntax.text_range(),
+ |builder| {
+ let replacement = inline(&ctx.sema, file_id, function, &fn_body, &params, &call_info);
+
+ builder.replace_ast(
+ match call_info.node {
+ ast::CallableExpr::Call(it) => ast::Expr::CallExpr(it),
+ ast::CallableExpr::MethodCall(it) => ast::Expr::MethodCallExpr(it),
+ },
+ replacement,
+ );
+ },
+ )
+}
+
+struct CallInfo {
+ node: ast::CallableExpr,
+ arguments: Vec<ast::Expr>,
+ generic_arg_list: Option<ast::GenericArgList>,
+}
+
+impl CallInfo {
+ fn from_name_ref(name_ref: ast::NameRef) -> Option<CallInfo> {
+ let parent = name_ref.syntax().parent()?;
+ if let Some(call) = ast::MethodCallExpr::cast(parent.clone()) {
+ let receiver = call.receiver()?;
+ let mut arguments = vec![receiver];
+ arguments.extend(call.arg_list()?.args());
+ Some(CallInfo {
+ generic_arg_list: call.generic_arg_list(),
+ node: ast::CallableExpr::MethodCall(call),
+ arguments,
+ })
+ } else if let Some(segment) = ast::PathSegment::cast(parent) {
+ let path = segment.syntax().parent().and_then(ast::Path::cast)?;
+ let path = path.syntax().parent().and_then(ast::PathExpr::cast)?;
+ let call = path.syntax().parent().and_then(ast::CallExpr::cast)?;
+
+ Some(CallInfo {
+ arguments: call.arg_list()?.args().collect(),
+ node: ast::CallableExpr::Call(call),
+ generic_arg_list: segment.generic_arg_list(),
+ })
+ } else {
+ None
+ }
+ }
+}
+
+fn get_fn_params(
+ db: &dyn HirDatabase,
+ function: hir::Function,
+ param_list: &ast::ParamList,
+) -> Option<Vec<(ast::Pat, Option<ast::Type>, hir::Param)>> {
+ let mut assoc_fn_params = function.assoc_fn_params(db).into_iter();
+
+ let mut params = Vec::new();
+ if let Some(self_param) = param_list.self_param() {
+ // FIXME this should depend on the receiver as well as the self_param
+ params.push((
+ make::ident_pat(
+ self_param.amp_token().is_some(),
+ self_param.mut_token().is_some(),
+ make::name("this"),
+ )
+ .into(),
+ None,
+ assoc_fn_params.next()?,
+ ));
+ }
+ for param in param_list.params() {
+ params.push((param.pat()?, param.ty(), assoc_fn_params.next()?));
+ }
+
+ Some(params)
+}
+
+fn inline(
+ sema: &Semantics<'_, RootDatabase>,
+ function_def_file_id: FileId,
+ function: hir::Function,
+ fn_body: &ast::BlockExpr,
+ params: &[(ast::Pat, Option<ast::Type>, hir::Param)],
+ CallInfo { node, arguments, generic_arg_list }: &CallInfo,
+) -> ast::Expr {
+ let body = if sema.hir_file_for(fn_body.syntax()).is_macro() {
+ cov_mark::hit!(inline_call_defined_in_macro);
+ if let Some(body) = ast::BlockExpr::cast(insert_ws_into(fn_body.syntax().clone())) {
+ body
+ } else {
+ fn_body.clone_for_update()
+ }
+ } else {
+ fn_body.clone_for_update()
+ };
+ let usages_for_locals = |local| {
+ Definition::Local(local)
+ .usages(sema)
+ .all()
+ .references
+ .remove(&function_def_file_id)
+ .unwrap_or_default()
+ .into_iter()
+ };
+ let param_use_nodes: Vec<Vec<_>> = params
+ .iter()
+ .map(|(pat, _, param)| {
+ if !matches!(pat, ast::Pat::IdentPat(pat) if pat.is_simple_ident()) {
+ return Vec::new();
+ }
+ // FIXME: we need to fetch all locals declared in the parameter here
+ // not only the local if it is a simple binding
+ match param.as_local(sema.db) {
+ Some(l) => usages_for_locals(l)
+ .map(|FileReference { name, range, .. }| match name {
+ ast::NameLike::NameRef(_) => body
+ .syntax()
+ .covering_element(range)
+ .ancestors()
+ .nth(3)
+ .and_then(ast::PathExpr::cast),
+ _ => None,
+ })
+ .collect::<Option<Vec<_>>>()
+ .unwrap_or_default(),
+ None => Vec::new(),
+ }
+ })
+ .collect();
+ if function.self_param(sema.db).is_some() {
+ let this = || make::name_ref("this").syntax().clone_for_update();
+ if let Some(self_local) = params[0].2.as_local(sema.db) {
+ usages_for_locals(self_local)
+ .flat_map(|FileReference { name, range, .. }| match name {
+ ast::NameLike::NameRef(_) => Some(body.syntax().covering_element(range)),
+ _ => None,
+ })
+ .for_each(|it| {
+ ted::replace(it, &this());
+ })
+ }
+ }
+ // Inline parameter expressions or generate `let` statements depending on whether inlining works or not.
+ for ((pat, param_ty, _), usages, expr) in izip!(params, param_use_nodes, arguments).rev() {
+ let inline_direct = |usage, replacement: &ast::Expr| {
+ if let Some(field) = path_expr_as_record_field(usage) {
+ cov_mark::hit!(inline_call_inline_direct_field);
+ field.replace_expr(replacement.clone_for_update());
+ } else {
+ ted::replace(usage.syntax(), &replacement.syntax().clone_for_update());
+ }
+ };
+ // izip confuses RA due to our lack of hygiene info currently losing us type info causing incorrect errors
+ let usages: &[ast::PathExpr] = &*usages;
+ let expr: &ast::Expr = expr;
+ match usages {
+ // inline single use closure arguments
+ [usage]
+ if matches!(expr, ast::Expr::ClosureExpr(_))
+ && usage.syntax().parent().and_then(ast::Expr::cast).is_some() =>
+ {
+ cov_mark::hit!(inline_call_inline_closure);
+ let expr = make::expr_paren(expr.clone());
+ inline_direct(usage, &expr);
+ }
+ // inline single use literals
+ [usage] if matches!(expr, ast::Expr::Literal(_)) => {
+ cov_mark::hit!(inline_call_inline_literal);
+ inline_direct(usage, expr);
+ }
+ // inline direct local arguments
+ [_, ..] if expr_as_name_ref(expr).is_some() => {
+ cov_mark::hit!(inline_call_inline_locals);
+ usages.iter().for_each(|usage| inline_direct(usage, expr));
+ }
+ // can't inline, emit a let statement
+ _ => {
+ let ty =
+ sema.type_of_expr(expr).filter(TypeInfo::has_adjustment).and(param_ty.clone());
+ if let Some(stmt_list) = body.stmt_list() {
+ stmt_list.push_front(
+ make::let_stmt(pat.clone(), ty, Some(expr.clone()))
+ .clone_for_update()
+ .into(),
+ )
+ }
+ }
+ }
+ }
+ if let Some(generic_arg_list) = generic_arg_list.clone() {
+ if let Some((target, source)) = &sema.scope(node.syntax()).zip(sema.scope(fn_body.syntax()))
+ {
+ PathTransform::function_call(target, source, function, generic_arg_list)
+ .apply(body.syntax());
+ }
+ }
+
+ let original_indentation = match node {
+ ast::CallableExpr::Call(it) => it.indent_level(),
+ ast::CallableExpr::MethodCall(it) => it.indent_level(),
+ };
+ body.reindent_to(original_indentation);
+
+ match body.tail_expr() {
+ Some(expr) if body.statements().next().is_none() => expr,
+ _ => match node
+ .syntax()
+ .parent()
+ .and_then(ast::BinExpr::cast)
+ .and_then(|bin_expr| bin_expr.lhs())
+ {
+ Some(lhs) if lhs.syntax() == node.syntax() => {
+ make::expr_paren(ast::Expr::BlockExpr(body)).clone_for_update()
+ }
+ _ => ast::Expr::BlockExpr(body),
+ },
+ }
+}
+
+fn path_expr_as_record_field(usage: &PathExpr) -> Option<ast::RecordExprField> {
+ let path = usage.path()?;
+ let name_ref = path.as_single_name_ref()?;
+ ast::RecordExprField::for_name_ref(&name_ref)
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn no_args_or_return_value_gets_inlined_without_block() {
+ check_assist(
+ inline_call,
+ r#"
+fn foo() { println!("Hello, World!"); }
+fn main() {
+ fo$0o();
+}
+"#,
+ r#"
+fn foo() { println!("Hello, World!"); }
+fn main() {
+ { println!("Hello, World!"); };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_when_incorrect_number_of_parameters_are_provided() {
+ cov_mark::check!(inline_call_incorrect_number_of_arguments);
+ check_assist_not_applicable(
+ inline_call,
+ r#"
+fn add(a: u32, b: u32) -> u32 { a + b }
+fn main() { let x = add$0(42); }
+"#,
+ );
+ }
+
+ #[test]
+ fn args_with_side_effects() {
+ check_assist(
+ inline_call,
+ r#"
+fn foo(name: String) {
+ println!("Hello, {}!", name);
+}
+fn main() {
+ foo$0(String::from("Michael"));
+}
+"#,
+ r#"
+fn foo(name: String) {
+ println!("Hello, {}!", name);
+}
+fn main() {
+ {
+ let name = String::from("Michael");
+ println!("Hello, {}!", name);
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn function_with_multiple_statements() {
+ check_assist(
+ inline_call,
+ r#"
+fn foo(a: u32, b: u32) -> u32 {
+ let x = a + b;
+ let y = x - b;
+ x * y
+}
+
+fn main() {
+ let x = foo$0(1, 2);
+}
+"#,
+ r#"
+fn foo(a: u32, b: u32) -> u32 {
+ let x = a + b;
+ let y = x - b;
+ x * y
+}
+
+fn main() {
+ let x = {
+ let b = 2;
+ let x = 1 + b;
+ let y = x - b;
+ x * y
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn function_with_self_param() {
+ check_assist(
+ inline_call,
+ r#"
+struct Foo(u32);
+
+impl Foo {
+ fn add(self, a: u32) -> Self {
+ Foo(self.0 + a)
+ }
+}
+
+fn main() {
+ let x = Foo::add$0(Foo(3), 2);
+}
+"#,
+ r#"
+struct Foo(u32);
+
+impl Foo {
+ fn add(self, a: u32) -> Self {
+ Foo(self.0 + a)
+ }
+}
+
+fn main() {
+ let x = {
+ let this = Foo(3);
+ Foo(this.0 + 2)
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn method_by_val() {
+ check_assist(
+ inline_call,
+ r#"
+struct Foo(u32);
+
+impl Foo {
+ fn add(self, a: u32) -> Self {
+ Foo(self.0 + a)
+ }
+}
+
+fn main() {
+ let x = Foo(3).add$0(2);
+}
+"#,
+ r#"
+struct Foo(u32);
+
+impl Foo {
+ fn add(self, a: u32) -> Self {
+ Foo(self.0 + a)
+ }
+}
+
+fn main() {
+ let x = {
+ let this = Foo(3);
+ Foo(this.0 + 2)
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn method_by_ref() {
+ check_assist(
+ inline_call,
+ r#"
+struct Foo(u32);
+
+impl Foo {
+ fn add(&self, a: u32) -> Self {
+ Foo(self.0 + a)
+ }
+}
+
+fn main() {
+ let x = Foo(3).add$0(2);
+}
+"#,
+ r#"
+struct Foo(u32);
+
+impl Foo {
+ fn add(&self, a: u32) -> Self {
+ Foo(self.0 + a)
+ }
+}
+
+fn main() {
+ let x = {
+ let ref this = Foo(3);
+ Foo(this.0 + 2)
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn method_by_ref_mut() {
+ check_assist(
+ inline_call,
+ r#"
+struct Foo(u32);
+
+impl Foo {
+ fn clear(&mut self) {
+ self.0 = 0;
+ }
+}
+
+fn main() {
+ let mut foo = Foo(3);
+ foo.clear$0();
+}
+"#,
+ r#"
+struct Foo(u32);
+
+impl Foo {
+ fn clear(&mut self) {
+ self.0 = 0;
+ }
+}
+
+fn main() {
+ let mut foo = Foo(3);
+ {
+ let ref mut this = foo;
+ this.0 = 0;
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn function_multi_use_expr_in_param() {
+ check_assist(
+ inline_call,
+ r#"
+fn square(x: u32) -> u32 {
+ x * x
+}
+fn main() {
+ let x = 51;
+ let y = square$0(10 + x);
+}
+"#,
+ r#"
+fn square(x: u32) -> u32 {
+ x * x
+}
+fn main() {
+ let x = 51;
+ let y = {
+ let x = 10 + x;
+ x * x
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn function_use_local_in_param() {
+ cov_mark::check!(inline_call_inline_locals);
+ check_assist(
+ inline_call,
+ r#"
+fn square(x: u32) -> u32 {
+ x * x
+}
+fn main() {
+ let local = 51;
+ let y = square$0(local);
+}
+"#,
+ r#"
+fn square(x: u32) -> u32 {
+ x * x
+}
+fn main() {
+ let local = 51;
+ let y = local * local;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn method_in_impl() {
+ check_assist(
+ inline_call,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(&self) {
+ self;
+ self;
+ }
+ fn bar(&self) {
+ self.foo$0();
+ }
+}
+"#,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(&self) {
+ self;
+ self;
+ }
+ fn bar(&self) {
+ {
+ let ref this = self;
+ this;
+ this;
+ };
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wraps_closure_in_paren() {
+ cov_mark::check!(inline_call_inline_closure);
+ check_assist(
+ inline_call,
+ r#"
+fn foo(x: fn()) {
+ x();
+}
+
+fn main() {
+ foo$0(|| {})
+}
+"#,
+ r#"
+fn foo(x: fn()) {
+ x();
+}
+
+fn main() {
+ {
+ (|| {})();
+ }
+}
+"#,
+ );
+ check_assist(
+ inline_call,
+ r#"
+fn foo(x: fn()) {
+ x();
+}
+
+fn main() {
+ foo$0(main)
+}
+"#,
+ r#"
+fn foo(x: fn()) {
+ x();
+}
+
+fn main() {
+ {
+ main();
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn inline_single_literal_expr() {
+ cov_mark::check!(inline_call_inline_literal);
+ check_assist(
+ inline_call,
+ r#"
+fn foo(x: u32) -> u32{
+ x
+}
+
+fn main() {
+ foo$0(222);
+}
+"#,
+ r#"
+fn foo(x: u32) -> u32{
+ x
+}
+
+fn main() {
+ 222;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn inline_emits_type_for_coercion() {
+ check_assist(
+ inline_call,
+ r#"
+fn foo(x: *const u32) -> u32 {
+ x as u32
+}
+
+fn main() {
+ foo$0(&222);
+}
+"#,
+ r#"
+fn foo(x: *const u32) -> u32 {
+ x as u32
+}
+
+fn main() {
+ {
+ let x: *const u32 = &222;
+ x as u32
+ };
+}
+"#,
+ );
+ }
+
+ // FIXME: const generics aren't being substituted, this is blocked on better support for them
+ #[test]
+ fn inline_substitutes_generics() {
+ check_assist(
+ inline_call,
+ r#"
+fn foo<T, const N: usize>() {
+ bar::<T, N>()
+}
+
+fn bar<U, const M: usize>() {}
+
+fn main() {
+ foo$0::<usize, {0}>();
+}
+"#,
+ r#"
+fn foo<T, const N: usize>() {
+ bar::<T, N>()
+}
+
+fn bar<U, const M: usize>() {}
+
+fn main() {
+ bar::<usize, N>();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn inline_callers() {
+ check_assist(
+ inline_into_callers,
+ r#"
+fn do_the_math$0(b: u32) -> u32 {
+ let foo = 10;
+ foo * b + foo
+}
+fn foo() {
+ do_the_math(0);
+ let bar = 10;
+ do_the_math(bar);
+}
+"#,
+ r#"
+
+fn foo() {
+ {
+ let foo = 10;
+ foo * 0 + foo
+ };
+ let bar = 10;
+ {
+ let foo = 10;
+ foo * bar + foo
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn inline_callers_across_files() {
+ check_assist(
+ inline_into_callers,
+ r#"
+//- /lib.rs
+mod foo;
+fn do_the_math$0(b: u32) -> u32 {
+ let foo = 10;
+ foo * b + foo
+}
+//- /foo.rs
+use super::do_the_math;
+fn foo() {
+ do_the_math(0);
+ let bar = 10;
+ do_the_math(bar);
+}
+"#,
+ r#"
+//- /lib.rs
+mod foo;
+
+//- /foo.rs
+fn foo() {
+ {
+ let foo = 10;
+ foo * 0 + foo
+ };
+ let bar = 10;
+ {
+ let foo = 10;
+ foo * bar + foo
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn inline_callers_across_files_with_def_file() {
+ check_assist(
+ inline_into_callers,
+ r#"
+//- /lib.rs
+mod foo;
+fn do_the_math$0(b: u32) -> u32 {
+ let foo = 10;
+ foo * b + foo
+}
+fn bar(a: u32, b: u32) -> u32 {
+ do_the_math(0);
+}
+//- /foo.rs
+use super::do_the_math;
+fn foo() {
+ do_the_math(0);
+}
+"#,
+ r#"
+//- /lib.rs
+mod foo;
+
+fn bar(a: u32, b: u32) -> u32 {
+ {
+ let foo = 10;
+ foo * 0 + foo
+ };
+}
+//- /foo.rs
+fn foo() {
+ {
+ let foo = 10;
+ foo * 0 + foo
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn inline_callers_recursive() {
+ cov_mark::check!(inline_into_callers_recursive);
+ check_assist_not_applicable(
+ inline_into_callers,
+ r#"
+fn foo$0() {
+ foo();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn inline_call_recursive() {
+ cov_mark::check!(inline_call_recursive);
+ check_assist_not_applicable(
+ inline_call,
+ r#"
+fn foo() {
+ foo$0();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn inline_call_field_shorthand() {
+ cov_mark::check!(inline_call_inline_direct_field);
+ check_assist(
+ inline_call,
+ r#"
+struct Foo {
+ field: u32,
+ field1: u32,
+ field2: u32,
+ field3: u32,
+}
+fn foo(field: u32, field1: u32, val2: u32, val3: u32) -> Foo {
+ Foo {
+ field,
+ field1,
+ field2: val2,
+ field3: val3,
+ }
+}
+fn main() {
+ let bar = 0;
+ let baz = 0;
+ foo$0(bar, 0, baz, 0);
+}
+"#,
+ r#"
+struct Foo {
+ field: u32,
+ field1: u32,
+ field2: u32,
+ field3: u32,
+}
+fn foo(field: u32, field1: u32, val2: u32, val3: u32) -> Foo {
+ Foo {
+ field,
+ field1,
+ field2: val2,
+ field3: val3,
+ }
+}
+fn main() {
+ let bar = 0;
+ let baz = 0;
+ Foo {
+ field: bar,
+ field1: 0,
+ field2: baz,
+ field3: 0,
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn inline_callers_wrapped_in_parentheses() {
+ check_assist(
+ inline_into_callers,
+ r#"
+fn foo$0() -> u32 {
+ let x = 0;
+ x
+}
+fn bar() -> u32 {
+ foo() + foo()
+}
+"#,
+ r#"
+
+fn bar() -> u32 {
+ ({
+ let x = 0;
+ x
+ }) + {
+ let x = 0;
+ x
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn inline_call_wrapped_in_parentheses() {
+ check_assist(
+ inline_call,
+ r#"
+fn foo() -> u32 {
+ let x = 0;
+ x
+}
+fn bar() -> u32 {
+ foo$0() + foo()
+}
+"#,
+ r#"
+fn foo() -> u32 {
+ let x = 0;
+ x
+}
+fn bar() -> u32 {
+ ({
+ let x = 0;
+ x
+ }) + foo()
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn inline_call_defined_in_macro() {
+ cov_mark::check!(inline_call_defined_in_macro);
+ check_assist(
+ inline_call,
+ r#"
+macro_rules! define_foo {
+ () => { fn foo() -> u32 {
+ let x = 0;
+ x
+ } };
+}
+define_foo!();
+fn bar() -> u32 {
+ foo$0()
+}
+"#,
+ r#"
+macro_rules! define_foo {
+ () => { fn foo() -> u32 {
+ let x = 0;
+ x
+ } };
+}
+define_foo!();
+fn bar() -> u32 {
+ {
+ let x = 0;
+ x
+ }
+}
+"#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_local_variable.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_local_variable.rs
new file mode 100644
index 000000000..7259d6781
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_local_variable.rs
@@ -0,0 +1,954 @@
+use either::Either;
+use hir::{PathResolution, Semantics};
+use ide_db::{
+ base_db::FileId,
+ defs::Definition,
+ search::{FileReference, UsageSearchResult},
+ RootDatabase,
+};
+use syntax::{
+ ast::{self, AstNode, AstToken, HasName},
+ SyntaxElement, TextRange,
+};
+
+use crate::{
+ assist_context::{AssistContext, Assists},
+ AssistId, AssistKind,
+};
+
+// Assist: inline_local_variable
+//
+// Inlines a local variable.
+//
+// ```
+// fn main() {
+// let x$0 = 1 + 2;
+// x * 4;
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// (1 + 2) * 4;
+// }
+// ```
+pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let file_id = ctx.file_id();
+ let range = ctx.selection_trimmed();
+ let InlineData { let_stmt, delete_let, references, target } =
+ if let Some(path_expr) = ctx.find_node_at_offset::<ast::PathExpr>() {
+ inline_usage(&ctx.sema, path_expr, range, file_id)
+ } else if let Some(let_stmt) = ctx.find_node_at_offset::<ast::LetStmt>() {
+ inline_let(&ctx.sema, let_stmt, range, file_id)
+ } else {
+ None
+ }?;
+ let initializer_expr = let_stmt.initializer()?;
+
+ let delete_range = delete_let.then(|| {
+ if let Some(whitespace) = let_stmt
+ .syntax()
+ .next_sibling_or_token()
+ .and_then(SyntaxElement::into_token)
+ .and_then(ast::Whitespace::cast)
+ {
+ TextRange::new(
+ let_stmt.syntax().text_range().start(),
+ whitespace.syntax().text_range().end(),
+ )
+ } else {
+ let_stmt.syntax().text_range()
+ }
+ });
+
+ let wrap_in_parens = references
+ .into_iter()
+ .filter_map(|FileReference { range, name, .. }| match name {
+ ast::NameLike::NameRef(name) => Some((range, name)),
+ _ => None,
+ })
+ .map(|(range, name_ref)| {
+ if range != name_ref.syntax().text_range() {
+ // Do not rename inside macros
+ // FIXME: This feels like a bad heuristic for macros
+ return None;
+ }
+ let usage_node =
+ name_ref.syntax().ancestors().find(|it| ast::PathExpr::can_cast(it.kind()));
+ let usage_parent_option =
+ usage_node.and_then(|it| it.parent()).and_then(ast::Expr::cast);
+ let usage_parent = match usage_parent_option {
+ Some(u) => u,
+ None => return Some((range, name_ref, false)),
+ };
+ let initializer = matches!(
+ initializer_expr,
+ ast::Expr::CallExpr(_)
+ | ast::Expr::IndexExpr(_)
+ | ast::Expr::MethodCallExpr(_)
+ | ast::Expr::FieldExpr(_)
+ | ast::Expr::TryExpr(_)
+ | ast::Expr::Literal(_)
+ | ast::Expr::TupleExpr(_)
+ | ast::Expr::ArrayExpr(_)
+ | ast::Expr::ParenExpr(_)
+ | ast::Expr::PathExpr(_)
+ | ast::Expr::BlockExpr(_),
+ );
+ let parent = matches!(
+ usage_parent,
+ ast::Expr::CallExpr(_)
+ | ast::Expr::TupleExpr(_)
+ | ast::Expr::ArrayExpr(_)
+ | ast::Expr::ParenExpr(_)
+ | ast::Expr::ForExpr(_)
+ | ast::Expr::WhileExpr(_)
+ | ast::Expr::BreakExpr(_)
+ | ast::Expr::ReturnExpr(_)
+ | ast::Expr::MatchExpr(_)
+ | ast::Expr::BlockExpr(_)
+ );
+ Some((range, name_ref, !(initializer || parent)))
+ })
+ .collect::<Option<Vec<_>>>()?;
+
+ let init_str = initializer_expr.syntax().text().to_string();
+ let init_in_paren = format!("({})", &init_str);
+
+ let target = match target {
+ ast::NameOrNameRef::Name(it) => it.syntax().text_range(),
+ ast::NameOrNameRef::NameRef(it) => it.syntax().text_range(),
+ };
+
+ acc.add(
+ AssistId("inline_local_variable", AssistKind::RefactorInline),
+ "Inline variable",
+ target,
+ move |builder| {
+ if let Some(range) = delete_range {
+ builder.delete(range);
+ }
+ for (range, name, should_wrap) in wrap_in_parens {
+ let replacement = if should_wrap { &init_in_paren } else { &init_str };
+ if ast::RecordExprField::for_field_name(&name).is_some() {
+ cov_mark::hit!(inline_field_shorthand);
+ builder.insert(range.end(), format!(": {}", replacement));
+ } else {
+ builder.replace(range, replacement.clone())
+ }
+ }
+ },
+ )
+}
+
+struct InlineData {
+ let_stmt: ast::LetStmt,
+ delete_let: bool,
+ target: ast::NameOrNameRef,
+ references: Vec<FileReference>,
+}
+
+fn inline_let(
+ sema: &Semantics<'_, RootDatabase>,
+ let_stmt: ast::LetStmt,
+ range: TextRange,
+ file_id: FileId,
+) -> Option<InlineData> {
+ let bind_pat = match let_stmt.pat()? {
+ ast::Pat::IdentPat(pat) => pat,
+ _ => return None,
+ };
+ if bind_pat.mut_token().is_some() {
+ cov_mark::hit!(test_not_inline_mut_variable);
+ return None;
+ }
+ if !bind_pat.syntax().text_range().contains_range(range) {
+ cov_mark::hit!(not_applicable_outside_of_bind_pat);
+ return None;
+ }
+
+ let local = sema.to_def(&bind_pat)?;
+ let UsageSearchResult { mut references } = Definition::Local(local).usages(sema).all();
+ match references.remove(&file_id) {
+ Some(references) => Some(InlineData {
+ let_stmt,
+ delete_let: true,
+ target: ast::NameOrNameRef::Name(bind_pat.name()?),
+ references,
+ }),
+ None => {
+ cov_mark::hit!(test_not_applicable_if_variable_unused);
+ None
+ }
+ }
+}
+
+fn inline_usage(
+ sema: &Semantics<'_, RootDatabase>,
+ path_expr: ast::PathExpr,
+ range: TextRange,
+ file_id: FileId,
+) -> Option<InlineData> {
+ let path = path_expr.path()?;
+ let name = path.as_single_name_ref()?;
+ if !name.syntax().text_range().contains_range(range) {
+ cov_mark::hit!(test_not_inline_selection_too_broad);
+ return None;
+ }
+
+ let local = match sema.resolve_path(&path)? {
+ PathResolution::Local(local) => local,
+ _ => return None,
+ };
+ if local.is_mut(sema.db) {
+ cov_mark::hit!(test_not_inline_mut_variable_use);
+ return None;
+ }
+
+ // FIXME: Handle multiple local definitions
+ let bind_pat = match local.source(sema.db).value {
+ Either::Left(ident) => ident,
+ _ => return None,
+ };
+
+ let let_stmt = ast::LetStmt::cast(bind_pat.syntax().parent()?)?;
+
+ let UsageSearchResult { mut references } = Definition::Local(local).usages(sema).all();
+ let mut references = references.remove(&file_id)?;
+ let delete_let = references.len() == 1;
+ references.retain(|fref| fref.name.as_name_ref() == Some(&name));
+
+ Some(InlineData { let_stmt, delete_let, target: ast::NameOrNameRef::NameRef(name), references })
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn test_inline_let_bind_literal_expr() {
+ check_assist(
+ inline_local_variable,
+ r"
+fn bar(a: usize) {}
+fn foo() {
+ let a$0 = 1;
+ a + 1;
+ if a > 10 {
+ }
+
+ while a > 10 {
+
+ }
+ let b = a * 10;
+ bar(a);
+}",
+ r"
+fn bar(a: usize) {}
+fn foo() {
+ 1 + 1;
+ if 1 > 10 {
+ }
+
+ while 1 > 10 {
+
+ }
+ let b = 1 * 10;
+ bar(1);
+}",
+ );
+ }
+
+ #[test]
+ fn test_inline_let_bind_bin_expr() {
+ check_assist(
+ inline_local_variable,
+ r"
+fn bar(a: usize) {}
+fn foo() {
+ let a$0 = 1 + 1;
+ a + 1;
+ if a > 10 {
+ }
+
+ while a > 10 {
+
+ }
+ let b = a * 10;
+ bar(a);
+}",
+ r"
+fn bar(a: usize) {}
+fn foo() {
+ (1 + 1) + 1;
+ if (1 + 1) > 10 {
+ }
+
+ while (1 + 1) > 10 {
+
+ }
+ let b = (1 + 1) * 10;
+ bar(1 + 1);
+}",
+ );
+ }
+
+ #[test]
+ fn test_inline_let_bind_function_call_expr() {
+ check_assist(
+ inline_local_variable,
+ r"
+fn bar(a: usize) {}
+fn foo() {
+ let a$0 = bar(1);
+ a + 1;
+ if a > 10 {
+ }
+
+ while a > 10 {
+
+ }
+ let b = a * 10;
+ bar(a);
+}",
+ r"
+fn bar(a: usize) {}
+fn foo() {
+ bar(1) + 1;
+ if bar(1) > 10 {
+ }
+
+ while bar(1) > 10 {
+
+ }
+ let b = bar(1) * 10;
+ bar(bar(1));
+}",
+ );
+ }
+
+ #[test]
+ fn test_inline_let_bind_cast_expr() {
+ check_assist(
+ inline_local_variable,
+ r"
+fn bar(a: usize): usize { a }
+fn foo() {
+ let a$0 = bar(1) as u64;
+ a + 1;
+ if a > 10 {
+ }
+
+ while a > 10 {
+
+ }
+ let b = a * 10;
+ bar(a);
+}",
+ r"
+fn bar(a: usize): usize { a }
+fn foo() {
+ (bar(1) as u64) + 1;
+ if (bar(1) as u64) > 10 {
+ }
+
+ while (bar(1) as u64) > 10 {
+
+ }
+ let b = (bar(1) as u64) * 10;
+ bar(bar(1) as u64);
+}",
+ );
+ }
+
+ #[test]
+ fn test_inline_let_bind_block_expr() {
+ check_assist(
+ inline_local_variable,
+ r"
+fn foo() {
+ let a$0 = { 10 + 1 };
+ a + 1;
+ if a > 10 {
+ }
+
+ while a > 10 {
+
+ }
+ let b = a * 10;
+ bar(a);
+}",
+ r"
+fn foo() {
+ { 10 + 1 } + 1;
+ if { 10 + 1 } > 10 {
+ }
+
+ while { 10 + 1 } > 10 {
+
+ }
+ let b = { 10 + 1 } * 10;
+ bar({ 10 + 1 });
+}",
+ );
+ }
+
+ #[test]
+ fn test_inline_let_bind_paren_expr() {
+ check_assist(
+ inline_local_variable,
+ r"
+fn foo() {
+ let a$0 = ( 10 + 1 );
+ a + 1;
+ if a > 10 {
+ }
+
+ while a > 10 {
+
+ }
+ let b = a * 10;
+ bar(a);
+}",
+ r"
+fn foo() {
+ ( 10 + 1 ) + 1;
+ if ( 10 + 1 ) > 10 {
+ }
+
+ while ( 10 + 1 ) > 10 {
+
+ }
+ let b = ( 10 + 1 ) * 10;
+ bar(( 10 + 1 ));
+}",
+ );
+ }
+
+ #[test]
+ fn test_not_inline_mut_variable() {
+ cov_mark::check!(test_not_inline_mut_variable);
+ check_assist_not_applicable(
+ inline_local_variable,
+ r"
+fn foo() {
+ let mut a$0 = 1 + 1;
+ a + 1;
+}",
+ );
+ }
+
+ #[test]
+ fn test_not_inline_mut_variable_use() {
+ cov_mark::check!(test_not_inline_mut_variable_use);
+ check_assist_not_applicable(
+ inline_local_variable,
+ r"
+fn foo() {
+ let mut a = 1 + 1;
+ a$0 + 1;
+}",
+ );
+ }
+
+ #[test]
+ fn test_call_expr() {
+ check_assist(
+ inline_local_variable,
+ r"
+fn foo() {
+ let a$0 = bar(10 + 1);
+ let b = a * 10;
+ let c = a as usize;
+}",
+ r"
+fn foo() {
+ let b = bar(10 + 1) * 10;
+ let c = bar(10 + 1) as usize;
+}",
+ );
+ }
+
+ #[test]
+ fn test_index_expr() {
+ check_assist(
+ inline_local_variable,
+ r"
+fn foo() {
+ let x = vec![1, 2, 3];
+ let a$0 = x[0];
+ let b = a * 10;
+ let c = a as usize;
+}",
+ r"
+fn foo() {
+ let x = vec![1, 2, 3];
+ let b = x[0] * 10;
+ let c = x[0] as usize;
+}",
+ );
+ }
+
+ #[test]
+ fn test_method_call_expr() {
+ check_assist(
+ inline_local_variable,
+ r"
+fn foo() {
+ let bar = vec![1];
+ let a$0 = bar.len();
+ let b = a * 10;
+ let c = a as usize;
+}",
+ r"
+fn foo() {
+ let bar = vec![1];
+ let b = bar.len() * 10;
+ let c = bar.len() as usize;
+}",
+ );
+ }
+
+ #[test]
+ fn test_field_expr() {
+ check_assist(
+ inline_local_variable,
+ r"
+struct Bar {
+ foo: usize
+}
+
+fn foo() {
+ let bar = Bar { foo: 1 };
+ let a$0 = bar.foo;
+ let b = a * 10;
+ let c = a as usize;
+}",
+ r"
+struct Bar {
+ foo: usize
+}
+
+fn foo() {
+ let bar = Bar { foo: 1 };
+ let b = bar.foo * 10;
+ let c = bar.foo as usize;
+}",
+ );
+ }
+
+ #[test]
+ fn test_try_expr() {
+ check_assist(
+ inline_local_variable,
+ r"
+fn foo() -> Option<usize> {
+ let bar = Some(1);
+ let a$0 = bar?;
+ let b = a * 10;
+ let c = a as usize;
+ None
+}",
+ r"
+fn foo() -> Option<usize> {
+ let bar = Some(1);
+ let b = bar? * 10;
+ let c = bar? as usize;
+ None
+}",
+ );
+ }
+
+ #[test]
+ fn test_ref_expr() {
+ check_assist(
+ inline_local_variable,
+ r"
+fn foo() {
+ let bar = 10;
+ let a$0 = &bar;
+ let b = a * 10;
+}",
+ r"
+fn foo() {
+ let bar = 10;
+ let b = (&bar) * 10;
+}",
+ );
+ }
+
+ #[test]
+ fn test_tuple_expr() {
+ check_assist(
+ inline_local_variable,
+ r"
+fn foo() {
+ let a$0 = (10, 20);
+ let b = a[0];
+}",
+ r"
+fn foo() {
+ let b = (10, 20)[0];
+}",
+ );
+ }
+
+ #[test]
+ fn test_array_expr() {
+ check_assist(
+ inline_local_variable,
+ r"
+fn foo() {
+ let a$0 = [1, 2, 3];
+ let b = a.len();
+}",
+ r"
+fn foo() {
+ let b = [1, 2, 3].len();
+}",
+ );
+ }
+
+ #[test]
+ fn test_paren() {
+ check_assist(
+ inline_local_variable,
+ r"
+fn foo() {
+ let a$0 = (10 + 20);
+ let b = a * 10;
+ let c = a as usize;
+}",
+ r"
+fn foo() {
+ let b = (10 + 20) * 10;
+ let c = (10 + 20) as usize;
+}",
+ );
+ }
+
+ #[test]
+ fn test_path_expr() {
+ check_assist(
+ inline_local_variable,
+ r"
+fn foo() {
+ let d = 10;
+ let a$0 = d;
+ let b = a * 10;
+ let c = a as usize;
+}",
+ r"
+fn foo() {
+ let d = 10;
+ let b = d * 10;
+ let c = d as usize;
+}",
+ );
+ }
+
+ #[test]
+ fn test_block_expr() {
+ check_assist(
+ inline_local_variable,
+ r"
+fn foo() {
+ let a$0 = { 10 };
+ let b = a * 10;
+ let c = a as usize;
+}",
+ r"
+fn foo() {
+ let b = { 10 } * 10;
+ let c = { 10 } as usize;
+}",
+ );
+ }
+
+ #[test]
+ fn test_used_in_different_expr1() {
+ check_assist(
+ inline_local_variable,
+ r"
+fn foo() {
+ let a$0 = 10 + 20;
+ let b = a * 10;
+ let c = (a, 20);
+ let d = [a, 10];
+ let e = (a);
+}",
+ r"
+fn foo() {
+ let b = (10 + 20) * 10;
+ let c = (10 + 20, 20);
+ let d = [10 + 20, 10];
+ let e = (10 + 20);
+}",
+ );
+ }
+
+ #[test]
+ fn test_used_in_for_expr() {
+ check_assist(
+ inline_local_variable,
+ r"
+fn foo() {
+ let a$0 = vec![10, 20];
+ for i in a {}
+}",
+ r"
+fn foo() {
+ for i in vec![10, 20] {}
+}",
+ );
+ }
+
+ #[test]
+ fn test_used_in_while_expr() {
+ check_assist(
+ inline_local_variable,
+ r"
+fn foo() {
+ let a$0 = 1 > 0;
+ while a {}
+}",
+ r"
+fn foo() {
+ while 1 > 0 {}
+}",
+ );
+ }
+
+ #[test]
+ fn test_used_in_break_expr() {
+ check_assist(
+ inline_local_variable,
+ r"
+fn foo() {
+ let a$0 = 1 + 1;
+ loop {
+ break a;
+ }
+}",
+ r"
+fn foo() {
+ loop {
+ break 1 + 1;
+ }
+}",
+ );
+ }
+
+ #[test]
+ fn test_used_in_return_expr() {
+ check_assist(
+ inline_local_variable,
+ r"
+fn foo() {
+ let a$0 = 1 > 0;
+ return a;
+}",
+ r"
+fn foo() {
+ return 1 > 0;
+}",
+ );
+ }
+
+ #[test]
+ fn test_used_in_match_expr() {
+ check_assist(
+ inline_local_variable,
+ r"
+fn foo() {
+ let a$0 = 1 > 0;
+ match a {}
+}",
+ r"
+fn foo() {
+ match 1 > 0 {}
+}",
+ );
+ }
+
+ #[test]
+ fn inline_field_shorthand() {
+ cov_mark::check!(inline_field_shorthand);
+ check_assist(
+ inline_local_variable,
+ r"
+struct S { foo: i32}
+fn main() {
+ let $0foo = 92;
+ S { foo }
+}
+",
+ r"
+struct S { foo: i32}
+fn main() {
+ S { foo: 92 }
+}
+",
+ );
+ }
+
+ #[test]
+ fn test_not_applicable_if_variable_unused() {
+ cov_mark::check!(test_not_applicable_if_variable_unused);
+ check_assist_not_applicable(
+ inline_local_variable,
+ r"
+fn foo() {
+ let $0a = 0;
+}
+ ",
+ )
+ }
+
+ #[test]
+ fn not_applicable_outside_of_bind_pat() {
+ cov_mark::check!(not_applicable_outside_of_bind_pat);
+ check_assist_not_applicable(
+ inline_local_variable,
+ r"
+fn main() {
+ let x = $01 + 2;
+ x * 4;
+}
+",
+ )
+ }
+
+ #[test]
+ fn works_on_local_usage() {
+ check_assist(
+ inline_local_variable,
+ r#"
+fn f() {
+ let xyz = 0;
+ xyz$0;
+}
+"#,
+ r#"
+fn f() {
+ 0;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn does_not_remove_let_when_multiple_usages() {
+ check_assist(
+ inline_local_variable,
+ r#"
+fn f() {
+ let xyz = 0;
+ xyz$0;
+ xyz;
+}
+"#,
+ r#"
+fn f() {
+ let xyz = 0;
+ 0;
+ xyz;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_with_non_ident_pattern() {
+ check_assist_not_applicable(
+ inline_local_variable,
+ r#"
+fn main() {
+ let (x, y) = (0, 1);
+ x$0;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_on_local_usage_in_macro() {
+ check_assist_not_applicable(
+ inline_local_variable,
+ r#"
+macro_rules! m {
+ ($i:ident) => { $i }
+}
+fn f() {
+ let xyz = 0;
+ m!(xyz$0); // replacing it would break the macro
+}
+"#,
+ );
+ check_assist_not_applicable(
+ inline_local_variable,
+ r#"
+macro_rules! m {
+ ($i:ident) => { $i }
+}
+fn f() {
+ let xyz$0 = 0;
+ m!(xyz); // replacing it would break the macro
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_not_inline_selection_too_broad() {
+ cov_mark::check!(test_not_inline_selection_too_broad);
+ check_assist_not_applicable(
+ inline_local_variable,
+ r#"
+fn f() {
+ let foo = 0;
+ let bar = 0;
+ $0foo + bar$0;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_inline_ref_in_let() {
+ check_assist(
+ inline_local_variable,
+ r#"
+fn f() {
+ let x = {
+ let y = 0;
+ y$0
+ };
+}
+"#,
+ r#"
+fn f() {
+ let x = {
+ 0
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_inline_let_unit_struct() {
+ check_assist_not_applicable(
+ inline_local_variable,
+ r#"
+struct S;
+fn f() {
+ let S$0 = S;
+ S;
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs
new file mode 100644
index 000000000..054663a06
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs
@@ -0,0 +1,838 @@
+// Some ideas for future improvements:
+// - Support replacing aliases which are used in expressions, e.g. `A::new()`.
+// - "inline_alias_to_users" assist #10881.
+// - Remove unused aliases if there are no longer any users, see inline_call.rs.
+
+use hir::{HasSource, PathResolution};
+use itertools::Itertools;
+use std::collections::HashMap;
+use syntax::{
+ ast::{self, make, HasGenericParams, HasName},
+ ted, AstNode, NodeOrToken, SyntaxNode,
+};
+
+use crate::{
+ assist_context::{AssistContext, Assists},
+ AssistId, AssistKind,
+};
+
+// Assist: inline_type_alias
+//
+// Replace a type alias with its concrete type.
+//
+// ```
+// type A<T = u32> = Vec<T>;
+//
+// fn main() {
+// let a: $0A;
+// }
+// ```
+// ->
+// ```
+// type A<T = u32> = Vec<T>;
+//
+// fn main() {
+// let a: Vec<u32>;
+// }
+// ```
+pub(crate) fn inline_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ enum Replacement {
+ Generic { lifetime_map: LifetimeMap, const_and_type_map: ConstAndTypeMap },
+ Plain,
+ }
+
+ let alias_instance = ctx.find_node_at_offset::<ast::PathType>()?;
+ let concrete_type;
+ let replacement;
+ match alias_instance.path()?.as_single_name_ref() {
+ Some(nameref) if nameref.Self_token().is_some() => {
+ match ctx.sema.resolve_path(&alias_instance.path()?)? {
+ PathResolution::SelfType(imp) => {
+ concrete_type = imp.source(ctx.db())?.value.self_ty()?;
+ }
+ // FIXME: should also work in ADT definitions
+ _ => return None,
+ }
+
+ replacement = Replacement::Plain;
+ }
+ _ => {
+ let alias = get_type_alias(&ctx, &alias_instance)?;
+ concrete_type = alias.ty()?;
+
+ replacement = if let Some(alias_generics) = alias.generic_param_list() {
+ if alias_generics.generic_params().next().is_none() {
+ cov_mark::hit!(no_generics_params);
+ return None;
+ }
+
+ let instance_args =
+ alias_instance.syntax().descendants().find_map(ast::GenericArgList::cast);
+
+ Replacement::Generic {
+ lifetime_map: LifetimeMap::new(&instance_args, &alias_generics)?,
+ const_and_type_map: ConstAndTypeMap::new(&instance_args, &alias_generics)?,
+ }
+ } else {
+ Replacement::Plain
+ };
+ }
+ }
+
+ let target = alias_instance.syntax().text_range();
+
+ acc.add(
+ AssistId("inline_type_alias", AssistKind::RefactorInline),
+ "Inline type alias",
+ target,
+ |builder| {
+ let replacement_text = match replacement {
+ Replacement::Generic { lifetime_map, const_and_type_map } => {
+ create_replacement(&lifetime_map, &const_and_type_map, &concrete_type)
+ }
+ Replacement::Plain => concrete_type.to_string(),
+ };
+
+ builder.replace(target, replacement_text);
+ },
+ )
+}
+
+struct LifetimeMap(HashMap<String, ast::Lifetime>);
+
+impl LifetimeMap {
+ fn new(
+ instance_args: &Option<ast::GenericArgList>,
+ alias_generics: &ast::GenericParamList,
+ ) -> Option<Self> {
+ let mut inner = HashMap::new();
+
+ let wildcard_lifetime = make::lifetime("'_");
+ let lifetimes = alias_generics
+ .lifetime_params()
+ .filter_map(|lp| lp.lifetime())
+ .map(|l| l.to_string())
+ .collect_vec();
+
+ for lifetime in &lifetimes {
+ inner.insert(lifetime.to_string(), wildcard_lifetime.clone());
+ }
+
+ if let Some(instance_generic_args_list) = &instance_args {
+ for (index, lifetime) in instance_generic_args_list
+ .lifetime_args()
+ .filter_map(|arg| arg.lifetime())
+ .enumerate()
+ {
+ let key = match lifetimes.get(index) {
+ Some(key) => key,
+ None => {
+ cov_mark::hit!(too_many_lifetimes);
+ return None;
+ }
+ };
+
+ inner.insert(key.clone(), lifetime);
+ }
+ }
+
+ Some(Self(inner))
+ }
+}
+
+struct ConstAndTypeMap(HashMap<String, SyntaxNode>);
+
+impl ConstAndTypeMap {
+ fn new(
+ instance_args: &Option<ast::GenericArgList>,
+ alias_generics: &ast::GenericParamList,
+ ) -> Option<Self> {
+ let mut inner = HashMap::new();
+ let instance_generics = generic_args_to_const_and_type_generics(instance_args);
+ let alias_generics = generic_param_list_to_const_and_type_generics(&alias_generics);
+
+ if instance_generics.len() > alias_generics.len() {
+ cov_mark::hit!(too_many_generic_args);
+ return None;
+ }
+
+ // Any declaration generics that don't have a default value must have one
+ // provided by the instance.
+ for (i, declaration_generic) in alias_generics.iter().enumerate() {
+ let key = declaration_generic.replacement_key()?;
+
+ if let Some(instance_generic) = instance_generics.get(i) {
+ inner.insert(key, instance_generic.replacement_value()?);
+ } else if let Some(value) = declaration_generic.replacement_value() {
+ inner.insert(key, value);
+ } else {
+ cov_mark::hit!(missing_replacement_param);
+ return None;
+ }
+ }
+
+ Some(Self(inner))
+ }
+}
+
+/// This doesn't attempt to ensure specified generics are compatible with those
+/// required by the type alias, other than lifetimes which must either all be
+/// specified or all omitted. It will replace TypeArgs with ConstArgs and vice
+/// versa if they're in the wrong position. It supports partially specified
+/// generics.
+///
+/// 1. Map the provided instance's generic args to the type alias's generic
+/// params:
+///
+/// ```
+/// type A<'a, const N: usize, T = u64> = &'a [T; N];
+/// ^ alias generic params
+/// let a: A<100>;
+/// ^ instance generic args
+/// ```
+///
+/// generic['a] = '_ due to omission
+/// generic[N] = 100 due to the instance arg
+/// generic[T] = u64 due to the default param
+///
+/// 2. Copy the concrete type and substitute in each found mapping:
+///
+/// &'_ [u64; 100]
+///
+/// 3. Remove wildcard lifetimes entirely:
+///
+/// &[u64; 100]
+fn create_replacement(
+ lifetime_map: &LifetimeMap,
+ const_and_type_map: &ConstAndTypeMap,
+ concrete_type: &ast::Type,
+) -> String {
+ let updated_concrete_type = concrete_type.clone_for_update();
+ let mut replacements = Vec::new();
+ let mut removals = Vec::new();
+
+ for syntax in updated_concrete_type.syntax().descendants() {
+ let syntax_string = syntax.to_string();
+ let syntax_str = syntax_string.as_str();
+
+ if let Some(old_lifetime) = ast::Lifetime::cast(syntax.clone()) {
+ if let Some(new_lifetime) = lifetime_map.0.get(&old_lifetime.to_string()) {
+ if new_lifetime.text() == "'_" {
+ removals.push(NodeOrToken::Node(syntax.clone()));
+
+ if let Some(ws) = syntax.next_sibling_or_token() {
+ removals.push(ws.clone());
+ }
+
+ continue;
+ }
+
+ replacements.push((syntax.clone(), new_lifetime.syntax().clone_for_update()));
+ }
+ } else if let Some(replacement_syntax) = const_and_type_map.0.get(syntax_str) {
+ let new_string = replacement_syntax.to_string();
+ let new = if new_string == "_" {
+ make::wildcard_pat().syntax().clone_for_update()
+ } else {
+ replacement_syntax.clone_for_update()
+ };
+
+ replacements.push((syntax.clone(), new));
+ }
+ }
+
+ for (old, new) in replacements {
+ ted::replace(old, new);
+ }
+
+ for syntax in removals {
+ ted::remove(syntax);
+ }
+
+ updated_concrete_type.to_string()
+}
+
+fn get_type_alias(ctx: &AssistContext<'_>, path: &ast::PathType) -> Option<ast::TypeAlias> {
+ let resolved_path = ctx.sema.resolve_path(&path.path()?)?;
+
+ // We need the generics in the correct order to be able to map any provided
+ // instance generics to declaration generics. The `hir::TypeAlias` doesn't
+ // keep the order, so we must get the `ast::TypeAlias` from the hir
+ // definition.
+ if let PathResolution::Def(hir::ModuleDef::TypeAlias(ta)) = resolved_path {
+ Some(ctx.sema.source(ta)?.value)
+ } else {
+ None
+ }
+}
+
+enum ConstOrTypeGeneric {
+ ConstArg(ast::ConstArg),
+ TypeArg(ast::TypeArg),
+ ConstParam(ast::ConstParam),
+ TypeParam(ast::TypeParam),
+}
+
+impl ConstOrTypeGeneric {
+ fn replacement_key(&self) -> Option<String> {
+ // Only params are used as replacement keys.
+ match self {
+ ConstOrTypeGeneric::ConstParam(cp) => Some(cp.name()?.to_string()),
+ ConstOrTypeGeneric::TypeParam(tp) => Some(tp.name()?.to_string()),
+ _ => None,
+ }
+ }
+
+ fn replacement_value(&self) -> Option<SyntaxNode> {
+ Some(match self {
+ ConstOrTypeGeneric::ConstArg(ca) => ca.expr()?.syntax().clone(),
+ ConstOrTypeGeneric::TypeArg(ta) => ta.syntax().clone(),
+ ConstOrTypeGeneric::ConstParam(cp) => cp.default_val()?.syntax().clone(),
+ ConstOrTypeGeneric::TypeParam(tp) => tp.default_type()?.syntax().clone(),
+ })
+ }
+}
+
+fn generic_param_list_to_const_and_type_generics(
+ generics: &ast::GenericParamList,
+) -> Vec<ConstOrTypeGeneric> {
+ let mut others = Vec::new();
+
+ for param in generics.generic_params() {
+ match param {
+ ast::GenericParam::LifetimeParam(_) => {}
+ ast::GenericParam::ConstParam(cp) => {
+ others.push(ConstOrTypeGeneric::ConstParam(cp));
+ }
+ ast::GenericParam::TypeParam(tp) => others.push(ConstOrTypeGeneric::TypeParam(tp)),
+ }
+ }
+
+ others
+}
+
+fn generic_args_to_const_and_type_generics(
+ generics: &Option<ast::GenericArgList>,
+) -> Vec<ConstOrTypeGeneric> {
+ let mut others = Vec::new();
+
+ // It's fine for there to be no instance generics because the declaration
+ // might have default values or they might be inferred.
+ if let Some(generics) = generics {
+ for arg in generics.generic_args() {
+ match arg {
+ ast::GenericArg::TypeArg(ta) => {
+ others.push(ConstOrTypeGeneric::TypeArg(ta));
+ }
+ ast::GenericArg::ConstArg(ca) => {
+ others.push(ConstOrTypeGeneric::ConstArg(ca));
+ }
+ _ => {}
+ }
+ }
+ }
+
+ others
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ #[test]
+ fn empty_generic_params() {
+ cov_mark::check!(no_generics_params);
+ check_assist_not_applicable(
+ inline_type_alias,
+ r#"
+type A<> = T;
+fn main() {
+ let a: $0A<u32>;
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn too_many_generic_args() {
+ cov_mark::check!(too_many_generic_args);
+ check_assist_not_applicable(
+ inline_type_alias,
+ r#"
+type A<T> = T;
+fn main() {
+ let a: $0A<u32, u64>;
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn too_many_lifetimes() {
+ cov_mark::check!(too_many_lifetimes);
+ check_assist_not_applicable(
+ inline_type_alias,
+ r#"
+type A<'a> = &'a &'b u32;
+fn f<'a>() {
+ let a: $0A<'a, 'b> = 0;
+}
+"#,
+ );
+ }
+
+ // This must be supported in order to support "inline_alias_to_users" or
+ // whatever it will be called.
+ #[test]
+ fn alias_as_expression_ignored() {
+ check_assist_not_applicable(
+ inline_type_alias,
+ r#"
+type A = Vec<u32>;
+fn main() {
+ let a: A = $0A::new();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn primitive_arg() {
+ check_assist(
+ inline_type_alias,
+ r#"
+type A<T> = T;
+fn main() {
+ let a: $0A<u32> = 0;
+}
+"#,
+ r#"
+type A<T> = T;
+fn main() {
+ let a: u32 = 0;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_generic_replacements() {
+ check_assist(
+ inline_type_alias,
+ r#"
+type A = Vec<u32>;
+fn main() {
+ let a: $0A;
+}
+"#,
+ r#"
+type A = Vec<u32>;
+fn main() {
+ let a: Vec<u32>;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn param_expression() {
+ check_assist(
+ inline_type_alias,
+ r#"
+type A<const N: usize = { 1 }> = [u32; N];
+fn main() {
+ let a: $0A;
+}
+"#,
+ r#"
+type A<const N: usize = { 1 }> = [u32; N];
+fn main() {
+ let a: [u32; { 1 }];
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn param_default_value() {
+ check_assist(
+ inline_type_alias,
+ r#"
+type A<const N: usize = 1> = [u32; N];
+fn main() {
+ let a: $0A;
+}
+"#,
+ r#"
+type A<const N: usize = 1> = [u32; N];
+fn main() {
+ let a: [u32; 1];
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn all_param_types() {
+ check_assist(
+ inline_type_alias,
+ r#"
+struct Struct<const C: usize>;
+type A<'inner1, 'outer1, Outer1, const INNER1: usize, Inner1: Clone, const OUTER1: usize> = (Struct<INNER1>, Struct<OUTER1>, Outer1, &'inner1 (), Inner1, &'outer1 ());
+fn foo<'inner2, 'outer2, Outer2, const INNER2: usize, Inner2, const OUTER2: usize>() {
+ let a: $0A<'inner2, 'outer2, Outer2, INNER2, Inner2, OUTER2>;
+}
+"#,
+ r#"
+struct Struct<const C: usize>;
+type A<'inner1, 'outer1, Outer1, const INNER1: usize, Inner1: Clone, const OUTER1: usize> = (Struct<INNER1>, Struct<OUTER1>, Outer1, &'inner1 (), Inner1, &'outer1 ());
+fn foo<'inner2, 'outer2, Outer2, const INNER2: usize, Inner2, const OUTER2: usize>() {
+ let a: (Struct<INNER2>, Struct<OUTER2>, Outer2, &'inner2 (), Inner2, &'outer2 ());
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn omitted_lifetimes() {
+ check_assist(
+ inline_type_alias,
+ r#"
+type A<'l, 'r> = &'l &'r u32;
+fn main() {
+ let a: $0A;
+}
+"#,
+ r#"
+type A<'l, 'r> = &'l &'r u32;
+fn main() {
+ let a: &&u32;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn omitted_type() {
+ check_assist(
+ inline_type_alias,
+ r#"
+type A<'r, 'l, T = u32> = &'l std::collections::HashMap<&'r str, T>;
+fn main() {
+ let a: $0A<'_, '_>;
+}
+"#,
+ r#"
+type A<'r, 'l, T = u32> = &'l std::collections::HashMap<&'r str, T>;
+fn main() {
+ let a: &std::collections::HashMap<&str, u32>;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn omitted_everything() {
+ check_assist(
+ inline_type_alias,
+ r#"
+type A<'r, 'l, T = u32> = &'l std::collections::HashMap<&'r str, T>;
+fn main() {
+ let v = std::collections::HashMap<&str, u32>;
+ let a: $0A = &v;
+}
+"#,
+ r#"
+type A<'r, 'l, T = u32> = &'l std::collections::HashMap<&'r str, T>;
+fn main() {
+ let v = std::collections::HashMap<&str, u32>;
+ let a: &std::collections::HashMap<&str, u32> = &v;
+}
+"#,
+ );
+ }
+
+ // This doesn't actually cause the GenericArgsList to contain a AssocTypeArg.
+ #[test]
+ fn arg_associated_type() {
+ check_assist(
+ inline_type_alias,
+ r#"
+trait Tra { type Assoc; fn a(); }
+struct Str {}
+impl Tra for Str {
+ type Assoc = u32;
+ fn a() {
+ type A<T> = Vec<T>;
+ let a: $0A<Self::Assoc>;
+ }
+}
+"#,
+ r#"
+trait Tra { type Assoc; fn a(); }
+struct Str {}
+impl Tra for Str {
+ type Assoc = u32;
+ fn a() {
+ type A<T> = Vec<T>;
+ let a: Vec<Self::Assoc>;
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn param_default_associated_type() {
+ check_assist(
+ inline_type_alias,
+ r#"
+trait Tra { type Assoc; fn a() }
+struct Str {}
+impl Tra for Str {
+ type Assoc = u32;
+ fn a() {
+ type A<T = Self::Assoc> = Vec<T>;
+ let a: $0A;
+ }
+}
+"#,
+ r#"
+trait Tra { type Assoc; fn a() }
+struct Str {}
+impl Tra for Str {
+ type Assoc = u32;
+ fn a() {
+ type A<T = Self::Assoc> = Vec<T>;
+ let a: Vec<Self::Assoc>;
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn function_pointer() {
+ check_assist(
+ inline_type_alias,
+ r#"
+type A = fn(u32);
+fn foo(a: u32) {}
+fn main() {
+ let a: $0A = foo;
+}
+"#,
+ r#"
+type A = fn(u32);
+fn foo(a: u32) {}
+fn main() {
+ let a: fn(u32) = foo;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn closure() {
+ check_assist(
+ inline_type_alias,
+ r#"
+type A = Box<dyn FnOnce(u32) -> u32>;
+fn main() {
+ let a: $0A = Box::new(|_| 0);
+}
+"#,
+ r#"
+type A = Box<dyn FnOnce(u32) -> u32>;
+fn main() {
+ let a: Box<dyn FnOnce(u32) -> u32> = Box::new(|_| 0);
+}
+"#,
+ );
+ }
+
+ // Type aliases can't be used in traits, but someone might use the assist to
+ // fix the error.
+ #[test]
+ fn bounds() {
+ check_assist(
+ inline_type_alias,
+ r#"type A = std::io::Write; fn f<T>() where T: $0A {}"#,
+ r#"type A = std::io::Write; fn f<T>() where T: std::io::Write {}"#,
+ );
+ }
+
+ #[test]
+ fn function_parameter() {
+ check_assist(
+ inline_type_alias,
+ r#"
+type A = std::io::Write;
+fn f(a: impl $0A) {}
+"#,
+ r#"
+type A = std::io::Write;
+fn f(a: impl std::io::Write) {}
+"#,
+ );
+ }
+
+ #[test]
+ fn arg_expression() {
+ check_assist(
+ inline_type_alias,
+ r#"
+type A<const N: usize> = [u32; N];
+fn main() {
+ let a: $0A<{ 1 + 1 }>;
+}
+"#,
+ r#"
+type A<const N: usize> = [u32; N];
+fn main() {
+ let a: [u32; { 1 + 1 }];
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn alias_instance_generic_path() {
+ check_assist(
+ inline_type_alias,
+ r#"
+type A<const N: usize> = [u32; N];
+fn main() {
+ let a: $0A<u32::MAX>;
+}
+"#,
+ r#"
+type A<const N: usize> = [u32; N];
+fn main() {
+ let a: [u32; u32::MAX];
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn generic_type() {
+ check_assist(
+ inline_type_alias,
+ r#"
+type A = String;
+fn f(a: Vec<$0A>) {}
+"#,
+ r#"
+type A = String;
+fn f(a: Vec<String>) {}
+"#,
+ );
+ }
+
+ #[test]
+ fn missing_replacement_param() {
+ cov_mark::check!(missing_replacement_param);
+ check_assist_not_applicable(
+ inline_type_alias,
+ r#"
+type A<U> = Vec<T>;
+fn main() {
+ let a: $0A;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn full_path_type_is_replaced() {
+ check_assist(
+ inline_type_alias,
+ r#"
+mod foo {
+ pub type A = String;
+}
+fn main() {
+ let a: foo::$0A;
+}
+"#,
+ r#"
+mod foo {
+ pub type A = String;
+}
+fn main() {
+ let a: String;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn inline_self_type() {
+ check_assist(
+ inline_type_alias,
+ r#"
+struct Strukt;
+
+impl Strukt {
+ fn new() -> Self$0 {}
+}
+"#,
+ r#"
+struct Strukt;
+
+impl Strukt {
+ fn new() -> Strukt {}
+}
+"#,
+ );
+ check_assist(
+ inline_type_alias,
+ r#"
+struct Strukt<'a, T, const C: usize>(&'a [T; C]);
+
+impl<T, const C: usize> Strukt<'_, T, C> {
+ fn new() -> Self$0 {}
+}
+"#,
+ r#"
+struct Strukt<'a, T, const C: usize>(&'a [T; C]);
+
+impl<T, const C: usize> Strukt<'_, T, C> {
+ fn new() -> Strukt<'_, T, C> {}
+}
+"#,
+ );
+ check_assist(
+ inline_type_alias,
+ r#"
+struct Strukt<'a, T, const C: usize>(&'a [T; C]);
+
+trait Tr<'b, T> {}
+
+impl<T, const C: usize> Tr<'static, u8> for Strukt<'_, T, C> {
+ fn new() -> Self$0 {}
+}
+"#,
+ r#"
+struct Strukt<'a, T, const C: usize>(&'a [T; C]);
+
+trait Tr<'b, T> {}
+
+impl<T, const C: usize> Tr<'static, u8> for Strukt<'_, T, C> {
+ fn new() -> Strukt<'_, T, C> {}
+}
+"#,
+ );
+
+ check_assist_not_applicable(
+ inline_type_alias,
+ r#"
+trait Tr {
+ fn new() -> Self$0;
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_generic.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_generic.rs
new file mode 100644
index 000000000..062c816ae
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_generic.rs
@@ -0,0 +1,144 @@
+use syntax::{
+ ast::{self, edit_in_place::GenericParamsOwnerEdit, make, AstNode},
+ ted,
+};
+
+use crate::{utils::suggest_name, AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: introduce_named_generic
+//
+// Replaces `impl Trait` function argument with the named generic.
+//
+// ```
+// fn foo(bar: $0impl Bar) {}
+// ```
+// ->
+// ```
+// fn foo<B: Bar>(bar: B) {}
+// ```
+pub(crate) fn introduce_named_generic(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let impl_trait_type = ctx.find_node_at_offset::<ast::ImplTraitType>()?;
+ let param = impl_trait_type.syntax().parent().and_then(ast::Param::cast)?;
+ let fn_ = param.syntax().ancestors().find_map(ast::Fn::cast)?;
+
+ let type_bound_list = impl_trait_type.type_bound_list()?;
+
+ let target = fn_.syntax().text_range();
+ acc.add(
+ AssistId("introduce_named_generic", AssistKind::RefactorRewrite),
+ "Replace impl trait with generic",
+ target,
+ |edit| {
+ let impl_trait_type = edit.make_mut(impl_trait_type);
+ let fn_ = edit.make_mut(fn_);
+
+ let type_param_name = suggest_name::for_generic_parameter(&impl_trait_type);
+
+ let type_param = make::type_param(make::name(&type_param_name), Some(type_bound_list))
+ .clone_for_update();
+ let new_ty = make::ty(&type_param_name).clone_for_update();
+
+ ted::replace(impl_trait_type.syntax(), new_ty.syntax());
+ fn_.get_or_create_generic_param_list().add_generic_param(type_param.into())
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::tests::check_assist;
+
+ #[test]
+ fn introduce_named_generic_params() {
+ check_assist(
+ introduce_named_generic,
+ r#"fn foo<G>(bar: $0impl Bar) {}"#,
+ r#"fn foo<G, B: Bar>(bar: B) {}"#,
+ );
+ }
+
+ #[test]
+ fn replace_impl_trait_without_generic_params() {
+ check_assist(
+ introduce_named_generic,
+ r#"fn foo(bar: $0impl Bar) {}"#,
+ r#"fn foo<B: Bar>(bar: B) {}"#,
+ );
+ }
+
+ #[test]
+ fn replace_two_impl_trait_with_generic_params() {
+ check_assist(
+ introduce_named_generic,
+ r#"fn foo<G>(foo: impl Foo, bar: $0impl Bar) {}"#,
+ r#"fn foo<G, B: Bar>(foo: impl Foo, bar: B) {}"#,
+ );
+ }
+
+ #[test]
+ fn replace_impl_trait_with_empty_generic_params() {
+ check_assist(
+ introduce_named_generic,
+ r#"fn foo<>(bar: $0impl Bar) {}"#,
+ r#"fn foo<B: Bar>(bar: B) {}"#,
+ );
+ }
+
+ #[test]
+ fn replace_impl_trait_with_empty_multiline_generic_params() {
+ check_assist(
+ introduce_named_generic,
+ r#"
+fn foo<
+>(bar: $0impl Bar) {}
+"#,
+ r#"
+fn foo<B: Bar
+>(bar: B) {}
+"#,
+ );
+ }
+
+ #[test]
+ fn replace_impl_trait_with_exist_generic_letter() {
+ // FIXME: This is wrong, we should pick a different name if the one we
+ // want is already bound.
+ check_assist(
+ introduce_named_generic,
+ r#"fn foo<B>(bar: $0impl Bar) {}"#,
+ r#"fn foo<B, B: Bar>(bar: B) {}"#,
+ );
+ }
+
+ #[test]
+ fn replace_impl_trait_with_multiline_generic_params() {
+ check_assist(
+ introduce_named_generic,
+ r#"
+fn foo<
+ G: Foo,
+ F,
+ H,
+>(bar: $0impl Bar) {}
+"#,
+ r#"
+fn foo<
+ G: Foo,
+ F,
+ H, B: Bar,
+>(bar: B) {}
+"#,
+ );
+ }
+
+ #[test]
+ fn replace_impl_trait_multiple() {
+ check_assist(
+ introduce_named_generic,
+ r#"fn foo(bar: $0impl Foo + Bar) {}"#,
+ r#"fn foo<F: Foo + Bar>(bar: F) {}"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_lifetime.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_lifetime.rs
new file mode 100644
index 000000000..ce91dd237
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_lifetime.rs
@@ -0,0 +1,338 @@
+use ide_db::FxHashSet;
+use syntax::{
+ ast::{self, edit_in_place::GenericParamsOwnerEdit, make, HasGenericParams},
+ ted::{self, Position},
+ AstNode, TextRange,
+};
+
+use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
+
+static ASSIST_NAME: &str = "introduce_named_lifetime";
+static ASSIST_LABEL: &str = "Introduce named lifetime";
+
+// Assist: introduce_named_lifetime
+//
+// Change an anonymous lifetime to a named lifetime.
+//
+// ```
+// impl Cursor<'_$0> {
+// fn node(self) -> &SyntaxNode {
+// match self {
+// Cursor::Replace(node) | Cursor::Before(node) => node,
+// }
+// }
+// }
+// ```
+// ->
+// ```
+// impl<'a> Cursor<'a> {
+// fn node(self) -> &SyntaxNode {
+// match self {
+// Cursor::Replace(node) | Cursor::Before(node) => node,
+// }
+// }
+// }
+// ```
+pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ // FIXME: How can we handle renaming any one of multiple anonymous lifetimes?
+ // FIXME: should also add support for the case fun(f: &Foo) -> &$0Foo
+ let lifetime =
+ ctx.find_node_at_offset::<ast::Lifetime>().filter(|lifetime| lifetime.text() == "'_")?;
+ let lifetime_loc = lifetime.lifetime_ident_token()?.text_range();
+
+ if let Some(fn_def) = lifetime.syntax().ancestors().find_map(ast::Fn::cast) {
+ generate_fn_def_assist(acc, fn_def, lifetime_loc, lifetime)
+ } else if let Some(impl_def) = lifetime.syntax().ancestors().find_map(ast::Impl::cast) {
+ generate_impl_def_assist(acc, impl_def, lifetime_loc, lifetime)
+ } else {
+ None
+ }
+}
+
+/// Generate the assist for the fn def case
+fn generate_fn_def_assist(
+ acc: &mut Assists,
+ fn_def: ast::Fn,
+ lifetime_loc: TextRange,
+ lifetime: ast::Lifetime,
+) -> Option<()> {
+ let param_list: ast::ParamList = fn_def.param_list()?;
+ let new_lifetime_param = generate_unique_lifetime_param_name(fn_def.generic_param_list())?;
+ let self_param =
+ // use the self if it's a reference and has no explicit lifetime
+ param_list.self_param().filter(|p| p.lifetime().is_none() && p.amp_token().is_some());
+ // compute the location which implicitly has the same lifetime as the anonymous lifetime
+ let loc_needing_lifetime = if let Some(self_param) = self_param {
+ // if we have a self reference, use that
+ Some(NeedsLifetime::SelfParam(self_param))
+ } else {
+ // otherwise, if there's a single reference parameter without a named liftime, use that
+ let fn_params_without_lifetime: Vec<_> = param_list
+ .params()
+ .filter_map(|param| match param.ty() {
+ Some(ast::Type::RefType(ascribed_type)) if ascribed_type.lifetime().is_none() => {
+ Some(NeedsLifetime::RefType(ascribed_type))
+ }
+ _ => None,
+ })
+ .collect();
+ match fn_params_without_lifetime.len() {
+ 1 => Some(fn_params_without_lifetime.into_iter().next()?),
+ 0 => None,
+ // multiple unnnamed is invalid. assist is not applicable
+ _ => return None,
+ }
+ };
+ acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| {
+ let fn_def = builder.make_mut(fn_def);
+ let lifetime = builder.make_mut(lifetime);
+ let loc_needing_lifetime =
+ loc_needing_lifetime.and_then(|it| it.make_mut(builder).to_position());
+
+ fn_def.get_or_create_generic_param_list().add_generic_param(
+ make::lifetime_param(new_lifetime_param.clone()).clone_for_update().into(),
+ );
+ ted::replace(lifetime.syntax(), new_lifetime_param.clone_for_update().syntax());
+ if let Some(position) = loc_needing_lifetime {
+ ted::insert(position, new_lifetime_param.clone_for_update().syntax());
+ }
+ })
+}
+
+/// Generate the assist for the impl def case
+fn generate_impl_def_assist(
+ acc: &mut Assists,
+ impl_def: ast::Impl,
+ lifetime_loc: TextRange,
+ lifetime: ast::Lifetime,
+) -> Option<()> {
+ let new_lifetime_param = generate_unique_lifetime_param_name(impl_def.generic_param_list())?;
+ acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| {
+ let impl_def = builder.make_mut(impl_def);
+ let lifetime = builder.make_mut(lifetime);
+
+ impl_def.get_or_create_generic_param_list().add_generic_param(
+ make::lifetime_param(new_lifetime_param.clone()).clone_for_update().into(),
+ );
+ ted::replace(lifetime.syntax(), new_lifetime_param.clone_for_update().syntax());
+ })
+}
+
+/// Given a type parameter list, generate a unique lifetime parameter name
+/// which is not in the list
+fn generate_unique_lifetime_param_name(
+ existing_type_param_list: Option<ast::GenericParamList>,
+) -> Option<ast::Lifetime> {
+ match existing_type_param_list {
+ Some(type_params) => {
+ let used_lifetime_params: FxHashSet<_> =
+ type_params.lifetime_params().map(|p| p.syntax().text().to_string()).collect();
+ ('a'..='z').map(|it| format!("'{}", it)).find(|it| !used_lifetime_params.contains(it))
+ }
+ None => Some("'a".to_string()),
+ }
+ .map(|it| make::lifetime(&it))
+}
+
+enum NeedsLifetime {
+ SelfParam(ast::SelfParam),
+ RefType(ast::RefType),
+}
+
+impl NeedsLifetime {
+ fn make_mut(self, builder: &mut AssistBuilder) -> Self {
+ match self {
+ Self::SelfParam(it) => Self::SelfParam(builder.make_mut(it)),
+ Self::RefType(it) => Self::RefType(builder.make_mut(it)),
+ }
+ }
+
+ fn to_position(self) -> Option<Position> {
+ match self {
+ Self::SelfParam(it) => Some(Position::after(it.amp_token()?)),
+ Self::RefType(it) => Some(Position::after(it.amp_token()?)),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ #[test]
+ fn test_example_case() {
+ check_assist(
+ introduce_named_lifetime,
+ r#"impl Cursor<'_$0> {
+ fn node(self) -> &SyntaxNode {
+ match self {
+ Cursor::Replace(node) | Cursor::Before(node) => node,
+ }
+ }
+ }"#,
+ r#"impl<'a> Cursor<'a> {
+ fn node(self) -> &SyntaxNode {
+ match self {
+ Cursor::Replace(node) | Cursor::Before(node) => node,
+ }
+ }
+ }"#,
+ );
+ }
+
+ #[test]
+ fn test_example_case_simplified() {
+ check_assist(
+ introduce_named_lifetime,
+ r#"impl Cursor<'_$0> {"#,
+ r#"impl<'a> Cursor<'a> {"#,
+ );
+ }
+
+ #[test]
+ fn test_example_case_cursor_after_tick() {
+ check_assist(
+ introduce_named_lifetime,
+ r#"impl Cursor<'$0_> {"#,
+ r#"impl<'a> Cursor<'a> {"#,
+ );
+ }
+
+ #[test]
+ fn test_impl_with_other_type_param() {
+ check_assist(
+ introduce_named_lifetime,
+ "impl<I> fmt::Display for SepByBuilder<'_$0, I>
+ where
+ I: Iterator,
+ I::Item: fmt::Display,
+ {",
+ "impl<I, 'a> fmt::Display for SepByBuilder<'a, I>
+ where
+ I: Iterator,
+ I::Item: fmt::Display,
+ {",
+ )
+ }
+
+ #[test]
+ fn test_example_case_cursor_before_tick() {
+ check_assist(
+ introduce_named_lifetime,
+ r#"impl Cursor<$0'_> {"#,
+ r#"impl<'a> Cursor<'a> {"#,
+ );
+ }
+
+ #[test]
+ fn test_not_applicable_cursor_position() {
+ check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'_>$0 {"#);
+ check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor$0<'_> {"#);
+ }
+
+ #[test]
+ fn test_not_applicable_lifetime_already_name() {
+ check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'a$0> {"#);
+ check_assist_not_applicable(introduce_named_lifetime, r#"fn my_fun<'a>() -> X<'a$0>"#);
+ }
+
+ #[test]
+ fn test_with_type_parameter() {
+ check_assist(
+ introduce_named_lifetime,
+ r#"impl<T> Cursor<T, '_$0>"#,
+ r#"impl<T, 'a> Cursor<T, 'a>"#,
+ );
+ }
+
+ #[test]
+ fn test_with_existing_lifetime_name_conflict() {
+ check_assist(
+ introduce_named_lifetime,
+ r#"impl<'a, 'b> Cursor<'a, 'b, '_$0>"#,
+ r#"impl<'a, 'b, 'c> Cursor<'a, 'b, 'c>"#,
+ );
+ }
+
+ #[test]
+ fn test_function_return_value_anon_lifetime_param() {
+ check_assist(
+ introduce_named_lifetime,
+ r#"fn my_fun() -> X<'_$0>"#,
+ r#"fn my_fun<'a>() -> X<'a>"#,
+ );
+ }
+
+ #[test]
+ fn test_function_return_value_anon_reference_lifetime() {
+ check_assist(
+ introduce_named_lifetime,
+ r#"fn my_fun() -> &'_$0 X"#,
+ r#"fn my_fun<'a>() -> &'a X"#,
+ );
+ }
+
+ #[test]
+ fn test_function_param_anon_lifetime() {
+ check_assist(
+ introduce_named_lifetime,
+ r#"fn my_fun(x: X<'_$0>)"#,
+ r#"fn my_fun<'a>(x: X<'a>)"#,
+ );
+ }
+
+ #[test]
+ fn test_function_add_lifetime_to_params() {
+ check_assist(
+ introduce_named_lifetime,
+ r#"fn my_fun(f: &Foo) -> X<'_$0>"#,
+ r#"fn my_fun<'a>(f: &'a Foo) -> X<'a>"#,
+ );
+ }
+
+ #[test]
+ fn test_function_add_lifetime_to_params_in_presence_of_other_lifetime() {
+ check_assist(
+ introduce_named_lifetime,
+ r#"fn my_fun<'other>(f: &Foo, b: &'other Bar) -> X<'_$0>"#,
+ r#"fn my_fun<'other, 'a>(f: &'a Foo, b: &'other Bar) -> X<'a>"#,
+ );
+ }
+
+ #[test]
+ fn test_function_not_applicable_without_self_and_multiple_unnamed_param_lifetimes() {
+ // this is not permitted under lifetime elision rules
+ check_assist_not_applicable(
+ introduce_named_lifetime,
+ r#"fn my_fun(f: &Foo, b: &Bar) -> X<'_$0>"#,
+ );
+ }
+
+ #[test]
+ fn test_function_add_lifetime_to_self_ref_param() {
+ check_assist(
+ introduce_named_lifetime,
+ r#"fn my_fun<'other>(&self, f: &Foo, b: &'other Bar) -> X<'_$0>"#,
+ r#"fn my_fun<'other, 'a>(&'a self, f: &Foo, b: &'other Bar) -> X<'a>"#,
+ );
+ }
+
+ #[test]
+ fn test_function_add_lifetime_to_param_with_non_ref_self() {
+ check_assist(
+ introduce_named_lifetime,
+ r#"fn my_fun<'other>(self, f: &Foo, b: &'other Bar) -> X<'_$0>"#,
+ r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#,
+ );
+ }
+
+ #[test]
+ fn test_function_add_lifetime_to_self_ref_mut() {
+ check_assist(
+ introduce_named_lifetime,
+ r#"fn foo(&mut self) -> &'_$0 ()"#,
+ r#"fn foo<'a>(&'a mut self) -> &'a ()"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/invert_if.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/invert_if.rs
new file mode 100644
index 000000000..547158e29
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/invert_if.rs
@@ -0,0 +1,144 @@
+use ide_db::syntax_helpers::node_ext::is_pattern_cond;
+use syntax::{
+ ast::{self, AstNode},
+ T,
+};
+
+use crate::{
+ assist_context::{AssistContext, Assists},
+ utils::invert_boolean_expression,
+ AssistId, AssistKind,
+};
+
+// Assist: invert_if
+//
+// This transforms if expressions of the form `if !x {A} else {B}` into `if x {B} else {A}`
+// This also works with `!=`. This assist can only be applied with the cursor on `if`.
+//
+// ```
+// fn main() {
+// if$0 !y { A } else { B }
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// if y { B } else { A }
+// }
+// ```
+pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let if_keyword = ctx.find_token_syntax_at_offset(T![if])?;
+ let expr = ast::IfExpr::cast(if_keyword.parent()?)?;
+ let if_range = if_keyword.text_range();
+ let cursor_in_range = if_range.contains_range(ctx.selection_trimmed());
+ if !cursor_in_range {
+ return None;
+ }
+
+ let cond = expr.condition()?;
+ // This assist should not apply for if-let.
+ if is_pattern_cond(cond.clone()) {
+ return None;
+ }
+
+ let then_node = expr.then_branch()?.syntax().clone();
+ let else_block = match expr.else_branch()? {
+ ast::ElseBranch::Block(it) => it,
+ ast::ElseBranch::IfExpr(_) => return None,
+ };
+
+ acc.add(AssistId("invert_if", AssistKind::RefactorRewrite), "Invert if", if_range, |edit| {
+ let flip_cond = invert_boolean_expression(cond.clone());
+ edit.replace_ast(cond, flip_cond);
+
+ let else_node = else_block.syntax();
+ let else_range = else_node.text_range();
+ let then_range = then_node.text_range();
+
+ edit.replace(else_range, then_node.text());
+ edit.replace(then_range, else_node.text());
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ #[test]
+ fn invert_if_composite_condition() {
+ check_assist(
+ invert_if,
+ "fn f() { i$0f x == 3 || x == 4 || x == 5 { 1 } else { 3 * 2 } }",
+ "fn f() { if !(x == 3 || x == 4 || x == 5) { 3 * 2 } else { 1 } }",
+ )
+ }
+
+ #[test]
+ fn invert_if_remove_not_parentheses() {
+ check_assist(
+ invert_if,
+ "fn f() { i$0f !(x == 3 || x == 4 || x == 5) { 3 * 2 } else { 1 } }",
+ "fn f() { if x == 3 || x == 4 || x == 5 { 1 } else { 3 * 2 } }",
+ )
+ }
+
+ #[test]
+ fn invert_if_remove_inequality() {
+ check_assist(
+ invert_if,
+ "fn f() { i$0f x != 3 { 1 } else { 3 + 2 } }",
+ "fn f() { if x == 3 { 3 + 2 } else { 1 } }",
+ )
+ }
+
+ #[test]
+ fn invert_if_remove_not() {
+ check_assist(
+ invert_if,
+ "fn f() { $0if !cond { 3 * 2 } else { 1 } }",
+ "fn f() { if cond { 1 } else { 3 * 2 } }",
+ )
+ }
+
+ #[test]
+ fn invert_if_general_case() {
+ check_assist(
+ invert_if,
+ "fn f() { i$0f cond { 3 * 2 } else { 1 } }",
+ "fn f() { if !cond { 1 } else { 3 * 2 } }",
+ )
+ }
+
+ #[test]
+ fn invert_if_doesnt_apply_with_cursor_not_on_if() {
+ check_assist_not_applicable(invert_if, "fn f() { if !$0cond { 3 * 2 } else { 1 } }")
+ }
+
+ #[test]
+ fn invert_if_doesnt_apply_with_if_let() {
+ check_assist_not_applicable(
+ invert_if,
+ "fn f() { i$0f let Some(_) = Some(1) { 1 } else { 0 } }",
+ )
+ }
+
+ #[test]
+ fn invert_if_option_case() {
+ check_assist(
+ invert_if,
+ "fn f() { if$0 doc_style.is_some() { Class::DocComment } else { Class::Comment } }",
+ "fn f() { if doc_style.is_none() { Class::Comment } else { Class::DocComment } }",
+ )
+ }
+
+ #[test]
+ fn invert_if_result_case() {
+ check_assist(
+ invert_if,
+ "fn f() { i$0f doc_style.is_err() { Class::Err } else { Class::Ok } }",
+ "fn f() { if doc_style.is_ok() { Class::Ok } else { Class::Err } }",
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_imports.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_imports.rs
new file mode 100644
index 000000000..7e102ceba
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_imports.rs
@@ -0,0 +1,570 @@
+use either::Either;
+use ide_db::imports::merge_imports::{try_merge_imports, try_merge_trees, MergeBehavior};
+use syntax::{algo::neighbor, ast, match_ast, ted, AstNode, SyntaxElement, SyntaxNode};
+
+use crate::{
+ assist_context::{AssistContext, Assists},
+ utils::next_prev,
+ AssistId, AssistKind,
+};
+
+use Edit::*;
+
+// Assist: merge_imports
+//
+// Merges two imports with a common prefix.
+//
+// ```
+// use std::$0fmt::Formatter;
+// use std::io;
+// ```
+// ->
+// ```
+// use std::{fmt::Formatter, io};
+// ```
+pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let (target, edits) = if ctx.has_empty_selection() {
+ // Merge a neighbor
+ let tree: ast::UseTree = ctx.find_node_at_offset()?;
+ let target = tree.syntax().text_range();
+
+ let edits = if let Some(use_item) = tree.syntax().parent().and_then(ast::Use::cast) {
+ let mut neighbor = next_prev().find_map(|dir| neighbor(&use_item, dir)).into_iter();
+ use_item.try_merge_from(&mut neighbor)
+ } else {
+ let mut neighbor = next_prev().find_map(|dir| neighbor(&tree, dir)).into_iter();
+ tree.try_merge_from(&mut neighbor)
+ };
+ (target, edits?)
+ } else {
+ // Merge selected
+ let selection_range = ctx.selection_trimmed();
+ let parent_node = match ctx.covering_element() {
+ SyntaxElement::Node(n) => n,
+ SyntaxElement::Token(t) => t.parent()?,
+ };
+ let mut selected_nodes =
+ parent_node.children().filter(|it| selection_range.contains_range(it.text_range()));
+
+ let first_selected = selected_nodes.next()?;
+ let edits = match_ast! {
+ match first_selected {
+ ast::Use(use_item) => {
+ use_item.try_merge_from(&mut selected_nodes.filter_map(ast::Use::cast))
+ },
+ ast::UseTree(use_tree) => {
+ use_tree.try_merge_from(&mut selected_nodes.filter_map(ast::UseTree::cast))
+ },
+ _ => return None,
+ }
+ };
+ (selection_range, edits?)
+ };
+
+ acc.add(
+ AssistId("merge_imports", AssistKind::RefactorRewrite),
+ "Merge imports",
+ target,
+ |builder| {
+ let edits_mut: Vec<Edit> = edits
+ .into_iter()
+ .map(|it| match it {
+ Remove(Either::Left(it)) => Remove(Either::Left(builder.make_mut(it))),
+ Remove(Either::Right(it)) => Remove(Either::Right(builder.make_mut(it))),
+ Replace(old, new) => Replace(builder.make_syntax_mut(old), new),
+ })
+ .collect();
+ for edit in edits_mut {
+ match edit {
+ Remove(it) => it.as_ref().either(ast::Use::remove, ast::UseTree::remove),
+ Replace(old, new) => ted::replace(old, new),
+ }
+ }
+ },
+ )
+}
+
+trait Merge: AstNode + Clone {
+ fn try_merge_from(self, items: &mut dyn Iterator<Item = Self>) -> Option<Vec<Edit>> {
+ let mut edits = Vec::new();
+ let mut merged = self.clone();
+ while let Some(item) = items.next() {
+ merged = merged.try_merge(&item)?;
+ edits.push(Edit::Remove(item.into_either()));
+ }
+ if !edits.is_empty() {
+ edits.push(Edit::replace(self, merged));
+ Some(edits)
+ } else {
+ None
+ }
+ }
+ fn try_merge(&self, other: &Self) -> Option<Self>;
+ fn into_either(self) -> Either<ast::Use, ast::UseTree>;
+}
+
+impl Merge for ast::Use {
+ fn try_merge(&self, other: &Self) -> Option<Self> {
+ try_merge_imports(self, other, MergeBehavior::Crate)
+ }
+ fn into_either(self) -> Either<ast::Use, ast::UseTree> {
+ Either::Left(self)
+ }
+}
+
+impl Merge for ast::UseTree {
+ fn try_merge(&self, other: &Self) -> Option<Self> {
+ try_merge_trees(self, other, MergeBehavior::Crate)
+ }
+ fn into_either(self) -> Either<ast::Use, ast::UseTree> {
+ Either::Right(self)
+ }
+}
+
+enum Edit {
+ Remove(Either<ast::Use, ast::UseTree>),
+ Replace(SyntaxNode, SyntaxNode),
+}
+
+impl Edit {
+ fn replace(old: impl AstNode, new: impl AstNode) -> Self {
+ Edit::Replace(old.syntax().clone(), new.syntax().clone())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn test_merge_equal() {
+ check_assist(
+ merge_imports,
+ r"
+use std::fmt$0::{Display, Debug};
+use std::fmt::{Display, Debug};
+",
+ r"
+use std::fmt::{Display, Debug};
+",
+ )
+ }
+
+ #[test]
+ fn test_merge_first() {
+ check_assist(
+ merge_imports,
+ r"
+use std::fmt$0::Debug;
+use std::fmt::Display;
+",
+ r"
+use std::fmt::{Debug, Display};
+",
+ )
+ }
+
+ #[test]
+ fn test_merge_second() {
+ check_assist(
+ merge_imports,
+ r"
+use std::fmt::Debug;
+use std::fmt$0::Display;
+",
+ r"
+use std::fmt::{Display, Debug};
+",
+ );
+ }
+
+ #[test]
+ fn merge_self1() {
+ check_assist(
+ merge_imports,
+ r"
+use std::fmt$0;
+use std::fmt::Display;
+",
+ r"
+use std::fmt::{self, Display};
+",
+ );
+ }
+
+ #[test]
+ fn merge_self2() {
+ check_assist(
+ merge_imports,
+ r"
+use std::{fmt, $0fmt::Display};
+",
+ r"
+use std::{fmt::{Display, self}};
+",
+ );
+ }
+
+ #[test]
+ fn skip_pub1() {
+ check_assist_not_applicable(
+ merge_imports,
+ r"
+pub use std::fmt$0::Debug;
+use std::fmt::Display;
+",
+ );
+ }
+
+ #[test]
+ fn skip_pub_last() {
+ check_assist_not_applicable(
+ merge_imports,
+ r"
+use std::fmt$0::Debug;
+pub use std::fmt::Display;
+",
+ );
+ }
+
+ #[test]
+ fn skip_pub_crate_pub() {
+ check_assist_not_applicable(
+ merge_imports,
+ r"
+pub(crate) use std::fmt$0::Debug;
+pub use std::fmt::Display;
+",
+ );
+ }
+
+ #[test]
+ fn skip_pub_pub_crate() {
+ check_assist_not_applicable(
+ merge_imports,
+ r"
+pub use std::fmt$0::Debug;
+pub(crate) use std::fmt::Display;
+",
+ );
+ }
+
+ #[test]
+ fn merge_pub() {
+ check_assist(
+ merge_imports,
+ r"
+pub use std::fmt$0::Debug;
+pub use std::fmt::Display;
+",
+ r"
+pub use std::fmt::{Debug, Display};
+",
+ )
+ }
+
+ #[test]
+ fn merge_pub_crate() {
+ check_assist(
+ merge_imports,
+ r"
+pub(crate) use std::fmt$0::Debug;
+pub(crate) use std::fmt::Display;
+",
+ r"
+pub(crate) use std::fmt::{Debug, Display};
+",
+ )
+ }
+
+ #[test]
+ fn merge_pub_in_path_crate() {
+ check_assist(
+ merge_imports,
+ r"
+pub(in this::path) use std::fmt$0::Debug;
+pub(in this::path) use std::fmt::Display;
+",
+ r"
+pub(in this::path) use std::fmt::{Debug, Display};
+",
+ )
+ }
+
+ #[test]
+ fn test_merge_nested() {
+ check_assist(
+ merge_imports,
+ r"
+use std::{fmt$0::Debug, fmt::Display};
+",
+ r"
+use std::{fmt::{Debug, Display}};
+",
+ );
+ }
+
+ #[test]
+ fn test_merge_nested2() {
+ check_assist(
+ merge_imports,
+ r"
+use std::{fmt::Debug, fmt$0::Display};
+",
+ r"
+use std::{fmt::{Display, Debug}};
+",
+ );
+ }
+
+ #[test]
+ fn test_merge_with_nested_self_item() {
+ check_assist(
+ merge_imports,
+ r"
+use std$0::{fmt::{Write, Display}};
+use std::{fmt::{self, Debug}};
+",
+ r"
+use std::{fmt::{Write, Display, self, Debug}};
+",
+ );
+ }
+
+ #[test]
+ fn test_merge_with_nested_self_item2() {
+ check_assist(
+ merge_imports,
+ r"
+use std$0::{fmt::{self, Debug}};
+use std::{fmt::{Write, Display}};
+",
+ r"
+use std::{fmt::{self, Debug, Write, Display}};
+",
+ );
+ }
+
+ #[test]
+ fn test_merge_self_with_nested_self_item() {
+ check_assist(
+ merge_imports,
+ r"
+use std::{fmt$0::{self, Debug}, fmt::{Write, Display}};
+",
+ r"
+use std::{fmt::{self, Debug, Write, Display}};
+",
+ );
+ }
+
+ #[test]
+ fn test_merge_nested_self_and_empty() {
+ check_assist(
+ merge_imports,
+ r"
+use foo::$0{bar::{self}};
+use foo::{bar};
+",
+ r"
+use foo::{bar::{self}};
+",
+ )
+ }
+
+ #[test]
+ fn test_merge_nested_empty_and_self() {
+ check_assist(
+ merge_imports,
+ r"
+use foo::$0{bar};
+use foo::{bar::{self}};
+",
+ r"
+use foo::{bar::{self}};
+",
+ )
+ }
+
+ #[test]
+ fn test_merge_nested_list_self_and_glob() {
+ check_assist(
+ merge_imports,
+ r"
+use std$0::{fmt::*};
+use std::{fmt::{self, Display}};
+",
+ r"
+use std::{fmt::{*, self, Display}};
+",
+ )
+ }
+
+ #[test]
+ fn test_merge_single_wildcard_diff_prefixes() {
+ check_assist(
+ merge_imports,
+ r"
+use std$0::cell::*;
+use std::str;
+",
+ r"
+use std::{cell::*, str};
+",
+ )
+ }
+
+ #[test]
+ fn test_merge_both_wildcard_diff_prefixes() {
+ check_assist(
+ merge_imports,
+ r"
+use std$0::cell::*;
+use std::str::*;
+",
+ r"
+use std::{cell::*, str::*};
+",
+ )
+ }
+
+ #[test]
+ fn removes_just_enough_whitespace() {
+ check_assist(
+ merge_imports,
+ r"
+use foo$0::bar;
+use foo::baz;
+
+/// Doc comment
+",
+ r"
+use foo::{bar, baz};
+
+/// Doc comment
+",
+ );
+ }
+
+ #[test]
+ fn works_with_trailing_comma() {
+ check_assist(
+ merge_imports,
+ r"
+use {
+ foo$0::bar,
+ foo::baz,
+};
+",
+ r"
+use {
+ foo::{bar, baz},
+};
+",
+ );
+ check_assist(
+ merge_imports,
+ r"
+use {
+ foo::baz,
+ foo$0::bar,
+};
+",
+ r"
+use {
+ foo::{bar, baz},
+};
+",
+ );
+ }
+
+ #[test]
+ fn test_double_comma() {
+ check_assist(
+ merge_imports,
+ r"
+use foo::bar::baz;
+use foo::$0{
+ FooBar,
+};
+",
+ r"
+use foo::{
+ FooBar, bar::baz,
+};
+",
+ )
+ }
+
+ #[test]
+ fn test_empty_use() {
+ check_assist_not_applicable(
+ merge_imports,
+ r"
+use std::$0
+fn main() {}",
+ );
+ }
+
+ #[test]
+ fn split_glob() {
+ check_assist(
+ merge_imports,
+ r"
+use foo::$0*;
+use foo::bar::Baz;
+",
+ r"
+use foo::{*, bar::Baz};
+",
+ );
+ }
+
+ #[test]
+ fn merge_selection_uses() {
+ check_assist(
+ merge_imports,
+ r"
+use std::fmt::Error;
+$0use std::fmt::Display;
+use std::fmt::Debug;
+use std::fmt::Write;
+$0use std::fmt::Result;
+",
+ r"
+use std::fmt::Error;
+use std::fmt::{Display, Debug, Write};
+use std::fmt::Result;
+",
+ );
+ }
+
+ #[test]
+ fn merge_selection_use_trees() {
+ check_assist(
+ merge_imports,
+ r"
+use std::{
+ fmt::Error,
+ $0fmt::Display,
+ fmt::Debug,
+ fmt::Write,$0
+ fmt::Result,
+};",
+ r"
+use std::{
+ fmt::Error,
+ fmt::{Display, Debug, Write},
+ fmt::Result,
+};",
+ );
+ // FIXME: Remove redundant braces. See also unnecessary-braces diagnostic.
+ check_assist(
+ merge_imports,
+ r"use std::$0{fmt::Display, fmt::Debug}$0;",
+ r"use std::{fmt::{Display, Debug}};",
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_match_arms.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_match_arms.rs
new file mode 100644
index 000000000..c24015b1c
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_match_arms.rs
@@ -0,0 +1,822 @@
+use hir::TypeInfo;
+use std::{collections::HashMap, iter::successors};
+use syntax::{
+ algo::neighbor,
+ ast::{self, AstNode, HasName},
+ Direction,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists, TextRange};
+
+// Assist: merge_match_arms
+//
+// Merges the current match arm with the following if their bodies are identical.
+//
+// ```
+// enum Action { Move { distance: u32 }, Stop }
+//
+// fn handle(action: Action) {
+// match action {
+// $0Action::Move(..) => foo(),
+// Action::Stop => foo(),
+// }
+// }
+// ```
+// ->
+// ```
+// enum Action { Move { distance: u32 }, Stop }
+//
+// fn handle(action: Action) {
+// match action {
+// Action::Move(..) | Action::Stop => foo(),
+// }
+// }
+// ```
+pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let current_arm = ctx.find_node_at_offset::<ast::MatchArm>()?;
+ // Don't try to handle arms with guards for now - can add support for this later
+ if current_arm.guard().is_some() {
+ return None;
+ }
+ let current_expr = current_arm.expr()?;
+ let current_text_range = current_arm.syntax().text_range();
+ let current_arm_types = get_arm_types(ctx, &current_arm);
+
+ // We check if the following match arms match this one. We could, but don't,
+ // compare to the previous match arm as well.
+ let arms_to_merge = successors(Some(current_arm), |it| neighbor(it, Direction::Next))
+ .take_while(|arm| match arm.expr() {
+ Some(expr) if arm.guard().is_none() => {
+ let same_text = expr.syntax().text() == current_expr.syntax().text();
+ if !same_text {
+ return false;
+ }
+
+ are_same_types(&current_arm_types, arm, ctx)
+ }
+ _ => false,
+ })
+ .collect::<Vec<_>>();
+
+ if arms_to_merge.len() <= 1 {
+ return None;
+ }
+
+ acc.add(
+ AssistId("merge_match_arms", AssistKind::RefactorRewrite),
+ "Merge match arms",
+ current_text_range,
+ |edit| {
+ let pats = if arms_to_merge.iter().any(contains_placeholder) {
+ "_".into()
+ } else {
+ arms_to_merge
+ .iter()
+ .filter_map(ast::MatchArm::pat)
+ .map(|x| x.syntax().to_string())
+ .collect::<Vec<String>>()
+ .join(" | ")
+ };
+
+ let arm = format!("{} => {},", pats, current_expr.syntax().text());
+
+ if let [first, .., last] = &*arms_to_merge {
+ let start = first.syntax().text_range().start();
+ let end = last.syntax().text_range().end();
+
+ edit.replace(TextRange::new(start, end), arm);
+ }
+ },
+ )
+}
+
+fn contains_placeholder(a: &ast::MatchArm) -> bool {
+ matches!(a.pat(), Some(ast::Pat::WildcardPat(..)))
+}
+
+fn are_same_types(
+ current_arm_types: &HashMap<String, Option<TypeInfo>>,
+ arm: &ast::MatchArm,
+ ctx: &AssistContext<'_>,
+) -> bool {
+ let arm_types = get_arm_types(ctx, arm);
+ for (other_arm_type_name, other_arm_type) in arm_types {
+ match (current_arm_types.get(&other_arm_type_name), other_arm_type) {
+ (Some(Some(current_arm_type)), Some(other_arm_type))
+ if other_arm_type.original == current_arm_type.original => {}
+ _ => return false,
+ }
+ }
+
+ true
+}
+
+fn get_arm_types(
+ context: &AssistContext<'_>,
+ arm: &ast::MatchArm,
+) -> HashMap<String, Option<TypeInfo>> {
+ let mut mapping: HashMap<String, Option<TypeInfo>> = HashMap::new();
+
+ fn recurse(
+ map: &mut HashMap<String, Option<TypeInfo>>,
+ ctx: &AssistContext<'_>,
+ pat: &Option<ast::Pat>,
+ ) {
+ if let Some(local_pat) = pat {
+ match pat {
+ Some(ast::Pat::TupleStructPat(tuple)) => {
+ for field in tuple.fields() {
+ recurse(map, ctx, &Some(field));
+ }
+ }
+ Some(ast::Pat::TuplePat(tuple)) => {
+ for field in tuple.fields() {
+ recurse(map, ctx, &Some(field));
+ }
+ }
+ Some(ast::Pat::RecordPat(record)) => {
+ if let Some(field_list) = record.record_pat_field_list() {
+ for field in field_list.fields() {
+ recurse(map, ctx, &field.pat());
+ }
+ }
+ }
+ Some(ast::Pat::ParenPat(parentheses)) => {
+ recurse(map, ctx, &parentheses.pat());
+ }
+ Some(ast::Pat::SlicePat(slice)) => {
+ for slice_pat in slice.pats() {
+ recurse(map, ctx, &Some(slice_pat));
+ }
+ }
+ Some(ast::Pat::IdentPat(ident_pat)) => {
+ if let Some(name) = ident_pat.name() {
+ let pat_type = ctx.sema.type_of_pat(local_pat);
+ map.insert(name.text().to_string(), pat_type);
+ }
+ }
+ _ => (),
+ }
+ }
+ }
+
+ recurse(&mut mapping, context, &arm.pat());
+ mapping
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn merge_match_arms_single_patterns() {
+ check_assist(
+ merge_match_arms,
+ r#"
+#[derive(Debug)]
+enum X { A, B, C }
+
+fn main() {
+ let x = X::A;
+ let y = match x {
+ X::A => { 1i32$0 }
+ X::B => { 1i32 }
+ X::C => { 2i32 }
+ }
+}
+"#,
+ r#"
+#[derive(Debug)]
+enum X { A, B, C }
+
+fn main() {
+ let x = X::A;
+ let y = match x {
+ X::A | X::B => { 1i32 },
+ X::C => { 2i32 }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn merge_match_arms_multiple_patterns() {
+ check_assist(
+ merge_match_arms,
+ r#"
+#[derive(Debug)]
+enum X { A, B, C, D, E }
+
+fn main() {
+ let x = X::A;
+ let y = match x {
+ X::A | X::B => {$0 1i32 },
+ X::C | X::D => { 1i32 },
+ X::E => { 2i32 },
+ }
+}
+"#,
+ r#"
+#[derive(Debug)]
+enum X { A, B, C, D, E }
+
+fn main() {
+ let x = X::A;
+ let y = match x {
+ X::A | X::B | X::C | X::D => { 1i32 },
+ X::E => { 2i32 },
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn merge_match_arms_placeholder_pattern() {
+ check_assist(
+ merge_match_arms,
+ r#"
+#[derive(Debug)]
+enum X { A, B, C, D, E }
+
+fn main() {
+ let x = X::A;
+ let y = match x {
+ X::A => { 1i32 },
+ X::B => { 2i$032 },
+ _ => { 2i32 }
+ }
+}
+"#,
+ r#"
+#[derive(Debug)]
+enum X { A, B, C, D, E }
+
+fn main() {
+ let x = X::A;
+ let y = match x {
+ X::A => { 1i32 },
+ _ => { 2i32 },
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn merges_all_subsequent_arms() {
+ check_assist(
+ merge_match_arms,
+ r#"
+enum X { A, B, C, D, E }
+
+fn main() {
+ match X::A {
+ X::A$0 => 92,
+ X::B => 92,
+ X::C => 92,
+ X::D => 62,
+ _ => panic!(),
+ }
+}
+"#,
+ r#"
+enum X { A, B, C, D, E }
+
+fn main() {
+ match X::A {
+ X::A | X::B | X::C => 92,
+ X::D => 62,
+ _ => panic!(),
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn merge_match_arms_rejects_guards() {
+ check_assist_not_applicable(
+ merge_match_arms,
+ r#"
+#[derive(Debug)]
+enum X {
+ A(i32),
+ B,
+ C
+}
+
+fn main() {
+ let x = X::A;
+ let y = match x {
+ X::A(a) if a > 5 => { $01i32 },
+ X::B => { 1i32 },
+ X::C => { 2i32 }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn merge_match_arms_different_type() {
+ check_assist_not_applicable(
+ merge_match_arms,
+ r#"
+//- minicore: result
+fn func() {
+ match Result::<f64, f32>::Ok(0f64) {
+ Ok(x) => $0x.classify(),
+ Err(x) => x.classify()
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn merge_match_arms_different_type_multiple_fields() {
+ check_assist_not_applicable(
+ merge_match_arms,
+ r#"
+//- minicore: result
+fn func() {
+ match Result::<(f64, f64), (f32, f32)>::Ok((0f64, 0f64)) {
+ Ok(x) => $0x.1.classify(),
+ Err(x) => x.1.classify()
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn merge_match_arms_same_type_multiple_fields() {
+ check_assist(
+ merge_match_arms,
+ r#"
+//- minicore: result
+fn func() {
+ match Result::<(f64, f64), (f64, f64)>::Ok((0f64, 0f64)) {
+ Ok(x) => $0x.1.classify(),
+ Err(x) => x.1.classify()
+ };
+}
+"#,
+ r#"
+fn func() {
+ match Result::<(f64, f64), (f64, f64)>::Ok((0f64, 0f64)) {
+ Ok(x) | Err(x) => x.1.classify(),
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn merge_match_arms_same_type_subsequent_arm_with_different_type_in_other() {
+ check_assist(
+ merge_match_arms,
+ r#"
+enum MyEnum {
+ OptionA(f32),
+ OptionB(f32),
+ OptionC(f64)
+}
+
+fn func(e: MyEnum) {
+ match e {
+ MyEnum::OptionA(x) => $0x.classify(),
+ MyEnum::OptionB(x) => x.classify(),
+ MyEnum::OptionC(x) => x.classify(),
+ };
+}
+"#,
+ r#"
+enum MyEnum {
+ OptionA(f32),
+ OptionB(f32),
+ OptionC(f64)
+}
+
+fn func(e: MyEnum) {
+ match e {
+ MyEnum::OptionA(x) | MyEnum::OptionB(x) => x.classify(),
+ MyEnum::OptionC(x) => x.classify(),
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn merge_match_arms_same_type_skip_arm_with_different_type_in_between() {
+ check_assist_not_applicable(
+ merge_match_arms,
+ r#"
+enum MyEnum {
+ OptionA(f32),
+ OptionB(f64),
+ OptionC(f32)
+}
+
+fn func(e: MyEnum) {
+ match e {
+ MyEnum::OptionA(x) => $0x.classify(),
+ MyEnum::OptionB(x) => x.classify(),
+ MyEnum::OptionC(x) => x.classify(),
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn merge_match_arms_same_type_different_number_of_fields() {
+ check_assist_not_applicable(
+ merge_match_arms,
+ r#"
+//- minicore: result
+fn func() {
+ match Result::<(f64, f64, f64), (f64, f64)>::Ok((0f64, 0f64, 0f64)) {
+ Ok(x) => $0x.1.classify(),
+ Err(x) => x.1.classify()
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn merge_match_same_destructuring_different_types() {
+ check_assist_not_applicable(
+ merge_match_arms,
+ r#"
+struct Point {
+ x: i32,
+ y: i32,
+}
+
+fn func() {
+ let p = Point { x: 0, y: 7 };
+
+ match p {
+ Point { x, y: 0 } => $0"",
+ Point { x: 0, y } => "",
+ Point { x, y } => "",
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn merge_match_arms_range() {
+ check_assist(
+ merge_match_arms,
+ r#"
+fn func() {
+ let x = 'c';
+
+ match x {
+ 'a'..='j' => $0"",
+ 'c'..='z' => "",
+ _ => "other",
+ };
+}
+"#,
+ r#"
+fn func() {
+ let x = 'c';
+
+ match x {
+ 'a'..='j' | 'c'..='z' => "",
+ _ => "other",
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn merge_match_arms_enum_without_field() {
+ check_assist_not_applicable(
+ merge_match_arms,
+ r#"
+enum MyEnum {
+ NoField,
+ AField(u8)
+}
+
+fn func(x: MyEnum) {
+ match x {
+ MyEnum::NoField => $0"",
+ MyEnum::AField(x) => ""
+ };
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn merge_match_arms_enum_destructuring_different_types() {
+ check_assist_not_applicable(
+ merge_match_arms,
+ r#"
+enum MyEnum {
+ Move { x: i32, y: i32 },
+ Write(String),
+}
+
+fn func(x: MyEnum) {
+ match x {
+ MyEnum::Move { x, y } => $0"",
+ MyEnum::Write(text) => "",
+ };
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn merge_match_arms_enum_destructuring_same_types() {
+ check_assist(
+ merge_match_arms,
+ r#"
+enum MyEnum {
+ Move { x: i32, y: i32 },
+ Crawl { x: i32, y: i32 }
+}
+
+fn func(x: MyEnum) {
+ match x {
+ MyEnum::Move { x, y } => $0"",
+ MyEnum::Crawl { x, y } => "",
+ };
+}
+ "#,
+ r#"
+enum MyEnum {
+ Move { x: i32, y: i32 },
+ Crawl { x: i32, y: i32 }
+}
+
+fn func(x: MyEnum) {
+ match x {
+ MyEnum::Move { x, y } | MyEnum::Crawl { x, y } => "",
+ };
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn merge_match_arms_enum_destructuring_same_types_different_name() {
+ check_assist_not_applicable(
+ merge_match_arms,
+ r#"
+enum MyEnum {
+ Move { x: i32, y: i32 },
+ Crawl { a: i32, b: i32 }
+}
+
+fn func(x: MyEnum) {
+ match x {
+ MyEnum::Move { x, y } => $0"",
+ MyEnum::Crawl { a, b } => "",
+ };
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn merge_match_arms_enum_nested_pattern_different_names() {
+ check_assist_not_applicable(
+ merge_match_arms,
+ r#"
+enum Color {
+ Rgb(i32, i32, i32),
+ Hsv(i32, i32, i32),
+}
+
+enum Message {
+ Quit,
+ Move { x: i32, y: i32 },
+ Write(String),
+ ChangeColor(Color),
+}
+
+fn main(msg: Message) {
+ match msg {
+ Message::ChangeColor(Color::Rgb(r, g, b)) => $0"",
+ Message::ChangeColor(Color::Hsv(h, s, v)) => "",
+ _ => "other"
+ };
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn merge_match_arms_enum_nested_pattern_same_names() {
+ check_assist(
+ merge_match_arms,
+ r#"
+enum Color {
+ Rgb(i32, i32, i32),
+ Hsv(i32, i32, i32),
+}
+
+enum Message {
+ Quit,
+ Move { x: i32, y: i32 },
+ Write(String),
+ ChangeColor(Color),
+}
+
+fn main(msg: Message) {
+ match msg {
+ Message::ChangeColor(Color::Rgb(a, b, c)) => $0"",
+ Message::ChangeColor(Color::Hsv(a, b, c)) => "",
+ _ => "other"
+ };
+}
+ "#,
+ r#"
+enum Color {
+ Rgb(i32, i32, i32),
+ Hsv(i32, i32, i32),
+}
+
+enum Message {
+ Quit,
+ Move { x: i32, y: i32 },
+ Write(String),
+ ChangeColor(Color),
+}
+
+fn main(msg: Message) {
+ match msg {
+ Message::ChangeColor(Color::Rgb(a, b, c)) | Message::ChangeColor(Color::Hsv(a, b, c)) => "",
+ _ => "other"
+ };
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn merge_match_arms_enum_destructuring_with_ignore() {
+ check_assist(
+ merge_match_arms,
+ r#"
+enum MyEnum {
+ Move { x: i32, a: i32 },
+ Crawl { x: i32, b: i32 }
+}
+
+fn func(x: MyEnum) {
+ match x {
+ MyEnum::Move { x, .. } => $0"",
+ MyEnum::Crawl { x, .. } => "",
+ };
+}
+ "#,
+ r#"
+enum MyEnum {
+ Move { x: i32, a: i32 },
+ Crawl { x: i32, b: i32 }
+}
+
+fn func(x: MyEnum) {
+ match x {
+ MyEnum::Move { x, .. } | MyEnum::Crawl { x, .. } => "",
+ };
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn merge_match_arms_nested_with_conflicting_identifier() {
+ check_assist_not_applicable(
+ merge_match_arms,
+ r#"
+enum Color {
+ Rgb(i32, i32, i32),
+ Hsv(i32, i32, i32),
+}
+
+enum Message {
+ Move { x: i32, y: i32 },
+ ChangeColor(u8, Color),
+}
+
+fn main(msg: Message) {
+ match msg {
+ Message::ChangeColor(x, Color::Rgb(y, b, c)) => $0"",
+ Message::ChangeColor(y, Color::Hsv(x, b, c)) => "",
+ _ => "other"
+ };
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn merge_match_arms_tuple() {
+ check_assist_not_applicable(
+ merge_match_arms,
+ r#"
+fn func() {
+ match (0, "boo") {
+ (x, y) => $0"",
+ (y, x) => "",
+ };
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn merge_match_arms_parentheses() {
+ check_assist_not_applicable(
+ merge_match_arms,
+ r#"
+fn func(x: i32) {
+ let variable = 2;
+ match x {
+ 1 => $0"",
+ ((((variable)))) => "",
+ _ => "other"
+ };
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn merge_match_arms_refpat() {
+ check_assist_not_applicable(
+ merge_match_arms,
+ r#"
+fn func() {
+ let name = Some(String::from(""));
+ let n = String::from("");
+ match name {
+ Some(ref n) => $0"",
+ Some(n) => "",
+ _ => "other",
+ };
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn merge_match_arms_slice() {
+ check_assist_not_applicable(
+ merge_match_arms,
+ r#"
+fn func(binary: &[u8]) {
+ let space = b' ';
+ match binary {
+ [0x7f, b'E', b'L', b'F', ..] => $0"",
+ [space] => "",
+ _ => "other",
+ };
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn merge_match_arms_slice_identical() {
+ check_assist(
+ merge_match_arms,
+ r#"
+fn func(binary: &[u8]) {
+ let space = b' ';
+ match binary {
+ [space, 5u8] => $0"",
+ [space] => "",
+ _ => "other",
+ };
+}
+ "#,
+ r#"
+fn func(binary: &[u8]) {
+ let space = b' ';
+ match binary {
+ [space, 5u8] | [space] => "",
+ _ => "other",
+ };
+}
+ "#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_bounds.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_bounds.rs
new file mode 100644
index 000000000..176a3bf58
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_bounds.rs
@@ -0,0 +1,122 @@
+use syntax::{
+ ast::{self, edit_in_place::GenericParamsOwnerEdit, make, AstNode, HasName, HasTypeBounds},
+ match_ast,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: move_bounds_to_where_clause
+//
+// Moves inline type bounds to a where clause.
+//
+// ```
+// fn apply<T, U, $0F: FnOnce(T) -> U>(f: F, x: T) -> U {
+// f(x)
+// }
+// ```
+// ->
+// ```
+// fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U {
+// f(x)
+// }
+// ```
+pub(crate) fn move_bounds_to_where_clause(
+ acc: &mut Assists,
+ ctx: &AssistContext<'_>,
+) -> Option<()> {
+ let type_param_list = ctx.find_node_at_offset::<ast::GenericParamList>()?;
+
+ let mut type_params = type_param_list.type_or_const_params();
+ if type_params.all(|p| match p {
+ ast::TypeOrConstParam::Type(t) => t.type_bound_list().is_none(),
+ ast::TypeOrConstParam::Const(_) => true,
+ }) {
+ return None;
+ }
+
+ let parent = type_param_list.syntax().parent()?;
+
+ let target = type_param_list.syntax().text_range();
+ acc.add(
+ AssistId("move_bounds_to_where_clause", AssistKind::RefactorRewrite),
+ "Move to where clause",
+ target,
+ |edit| {
+ let type_param_list = edit.make_mut(type_param_list);
+ let parent = edit.make_syntax_mut(parent);
+
+ let where_clause: ast::WhereClause = match_ast! {
+ match parent {
+ ast::Fn(it) => it.get_or_create_where_clause(),
+ ast::Trait(it) => it.get_or_create_where_clause(),
+ ast::Impl(it) => it.get_or_create_where_clause(),
+ ast::Enum(it) => it.get_or_create_where_clause(),
+ ast::Struct(it) => it.get_or_create_where_clause(),
+ _ => return,
+ }
+ };
+
+ for toc_param in type_param_list.type_or_const_params() {
+ let type_param = match toc_param {
+ ast::TypeOrConstParam::Type(x) => x,
+ ast::TypeOrConstParam::Const(_) => continue,
+ };
+ if let Some(tbl) = type_param.type_bound_list() {
+ if let Some(predicate) = build_predicate(type_param) {
+ where_clause.add_predicate(predicate)
+ }
+ tbl.remove()
+ }
+ }
+ },
+ )
+}
+
+fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> {
+ let path = make::ext::ident_path(&param.name()?.syntax().to_string());
+ let predicate = make::where_pred(path, param.type_bound_list()?.bounds());
+ Some(predicate.clone_for_update())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::tests::check_assist;
+
+ #[test]
+ fn move_bounds_to_where_clause_fn() {
+ check_assist(
+ move_bounds_to_where_clause,
+ r#"fn foo<T: u32, $0F: FnOnce(T) -> T>() {}"#,
+ r#"fn foo<T, F>() where T: u32, F: FnOnce(T) -> T {}"#,
+ );
+ }
+
+ #[test]
+ fn move_bounds_to_where_clause_impl() {
+ check_assist(
+ move_bounds_to_where_clause,
+ r#"impl<U: u32, $0T> A<U, T> {}"#,
+ r#"impl<U, T> A<U, T> where U: u32 {}"#,
+ );
+ }
+
+ #[test]
+ fn move_bounds_to_where_clause_struct() {
+ check_assist(
+ move_bounds_to_where_clause,
+ r#"struct A<$0T: Iterator<Item = u32>> {}"#,
+ r#"struct A<T> where T: Iterator<Item = u32> {}"#,
+ );
+ }
+
+ #[test]
+ fn move_bounds_to_where_clause_tuple_struct() {
+ check_assist(
+ move_bounds_to_where_clause,
+ r#"struct Pair<$0T: u32>(T, T);"#,
+ r#"struct Pair<T>(T, T) where T: u32;"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_from_mod_rs.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_from_mod_rs.rs
new file mode 100644
index 000000000..a6c85a2b1
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_from_mod_rs.rs
@@ -0,0 +1,130 @@
+use ide_db::{
+ assists::{AssistId, AssistKind},
+ base_db::AnchoredPathBuf,
+};
+use syntax::{ast, AstNode};
+
+use crate::{
+ assist_context::{AssistContext, Assists},
+ utils::trimmed_text_range,
+};
+
+// Assist: move_from_mod_rs
+//
+// Moves xxx/mod.rs to xxx.rs.
+//
+// ```
+// //- /main.rs
+// mod a;
+// //- /a/mod.rs
+// $0fn t() {}$0
+// ```
+// ->
+// ```
+// fn t() {}
+// ```
+pub(crate) fn move_from_mod_rs(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let source_file = ctx.find_node_at_offset::<ast::SourceFile>()?;
+ let module = ctx.sema.to_module_def(ctx.file_id())?;
+ // Enable this assist if the user select all "meaningful" content in the source file
+ let trimmed_selected_range = trimmed_text_range(&source_file, ctx.selection_trimmed());
+ let trimmed_file_range = trimmed_text_range(&source_file, source_file.syntax().text_range());
+ if !module.is_mod_rs(ctx.db()) {
+ cov_mark::hit!(not_mod_rs);
+ return None;
+ }
+ if trimmed_selected_range != trimmed_file_range {
+ cov_mark::hit!(not_all_selected);
+ return None;
+ }
+
+ let target = source_file.syntax().text_range();
+ let module_name = module.name(ctx.db())?.to_string();
+ let path = format!("../{}.rs", module_name);
+ let dst = AnchoredPathBuf { anchor: ctx.file_id(), path };
+ acc.add(
+ AssistId("move_from_mod_rs", AssistKind::Refactor),
+ format!("Convert {}/mod.rs to {}.rs", module_name, module_name),
+ target,
+ |builder| {
+ builder.move_file(ctx.file_id(), dst);
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn trivial() {
+ check_assist(
+ move_from_mod_rs,
+ r#"
+//- /main.rs
+mod a;
+//- /a/mod.rs
+$0fn t() {}
+$0"#,
+ r#"
+//- /a.rs
+fn t() {}
+"#,
+ );
+ }
+
+ #[test]
+ fn must_select_all_file() {
+ cov_mark::check!(not_all_selected);
+ check_assist_not_applicable(
+ move_from_mod_rs,
+ r#"
+//- /main.rs
+mod a;
+//- /a/mod.rs
+fn t() {}$0
+"#,
+ );
+ cov_mark::check!(not_all_selected);
+ check_assist_not_applicable(
+ move_from_mod_rs,
+ r#"
+//- /main.rs
+mod a;
+//- /a/mod.rs
+$0fn$0 t() {}
+"#,
+ );
+ }
+
+ #[test]
+ fn cannot_move_not_mod_rs() {
+ cov_mark::check!(not_mod_rs);
+ check_assist_not_applicable(
+ move_from_mod_rs,
+ r#"//- /main.rs
+mod a;
+//- /a.rs
+$0fn t() {}$0
+"#,
+ );
+ }
+
+ #[test]
+ fn cannot_downgrade_main_and_lib_rs() {
+ check_assist_not_applicable(
+ move_from_mod_rs,
+ r#"//- /main.rs
+$0fn t() {}$0
+"#,
+ );
+ check_assist_not_applicable(
+ move_from_mod_rs,
+ r#"//- /lib.rs
+$0fn t() {}$0
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_guard.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_guard.rs
new file mode 100644
index 000000000..b8f1b36de
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_guard.rs
@@ -0,0 +1,997 @@
+use syntax::{
+ ast::{edit::AstNodeEdit, make, AstNode, BlockExpr, ElseBranch, Expr, IfExpr, MatchArm, Pat},
+ SyntaxKind::WHITESPACE,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: move_guard_to_arm_body
+//
+// Moves match guard into match arm body.
+//
+// ```
+// enum Action { Move { distance: u32 }, Stop }
+//
+// fn handle(action: Action) {
+// match action {
+// Action::Move { distance } $0if distance > 10 => foo(),
+// _ => (),
+// }
+// }
+// ```
+// ->
+// ```
+// enum Action { Move { distance: u32 }, Stop }
+//
+// fn handle(action: Action) {
+// match action {
+// Action::Move { distance } => if distance > 10 {
+// foo()
+// },
+// _ => (),
+// }
+// }
+// ```
+pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let match_arm = ctx.find_node_at_offset::<MatchArm>()?;
+ let guard = match_arm.guard()?;
+ if ctx.offset() > guard.syntax().text_range().end() {
+ cov_mark::hit!(move_guard_unapplicable_in_arm_body);
+ return None;
+ }
+ let space_before_guard = guard.syntax().prev_sibling_or_token();
+
+ let guard_condition = guard.condition()?;
+ let arm_expr = match_arm.expr()?;
+ let if_expr =
+ make::expr_if(guard_condition, make::block_expr(None, Some(arm_expr.clone())), None)
+ .indent(arm_expr.indent_level());
+
+ let target = guard.syntax().text_range();
+ acc.add(
+ AssistId("move_guard_to_arm_body", AssistKind::RefactorRewrite),
+ "Move guard to arm body",
+ target,
+ |edit| {
+ match space_before_guard {
+ Some(element) if element.kind() == WHITESPACE => {
+ edit.delete(element.text_range());
+ }
+ _ => (),
+ };
+
+ edit.delete(guard.syntax().text_range());
+ edit.replace_ast(arm_expr, if_expr);
+ },
+ )
+}
+
+// Assist: move_arm_cond_to_match_guard
+//
+// Moves if expression from match arm body into a guard.
+//
+// ```
+// enum Action { Move { distance: u32 }, Stop }
+//
+// fn handle(action: Action) {
+// match action {
+// Action::Move { distance } => $0if distance > 10 { foo() },
+// _ => (),
+// }
+// }
+// ```
+// ->
+// ```
+// enum Action { Move { distance: u32 }, Stop }
+//
+// fn handle(action: Action) {
+// match action {
+// Action::Move { distance } if distance > 10 => foo(),
+// _ => (),
+// }
+// }
+// ```
+pub(crate) fn move_arm_cond_to_match_guard(
+ acc: &mut Assists,
+ ctx: &AssistContext<'_>,
+) -> Option<()> {
+ let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?;
+ let match_pat = match_arm.pat()?;
+ let arm_body = match_arm.expr()?;
+
+ let mut replace_node = None;
+ let if_expr: IfExpr = IfExpr::cast(arm_body.syntax().clone()).or_else(|| {
+ let block_expr = BlockExpr::cast(arm_body.syntax().clone())?;
+ if let Expr::IfExpr(e) = block_expr.tail_expr()? {
+ replace_node = Some(block_expr.syntax().clone());
+ Some(e)
+ } else {
+ None
+ }
+ })?;
+ if ctx.offset() > if_expr.then_branch()?.syntax().text_range().start() {
+ return None;
+ }
+
+ let replace_node = replace_node.unwrap_or_else(|| if_expr.syntax().clone());
+ let needs_dedent = replace_node != *if_expr.syntax();
+ let (conds_blocks, tail) = parse_if_chain(if_expr)?;
+
+ acc.add(
+ AssistId("move_arm_cond_to_match_guard", AssistKind::RefactorRewrite),
+ "Move condition to match guard",
+ replace_node.text_range(),
+ |edit| {
+ edit.delete(match_arm.syntax().text_range());
+ // Dedent if if_expr is in a BlockExpr
+ let dedent = if needs_dedent {
+ cov_mark::hit!(move_guard_ifelse_in_block);
+ 1
+ } else {
+ cov_mark::hit!(move_guard_ifelse_else_block);
+ 0
+ };
+ let then_arm_end = match_arm.syntax().text_range().end();
+ let indent_level = match_arm.indent_level();
+ let spaces = " ".repeat(indent_level.0 as _);
+
+ let mut first = true;
+ for (cond, block) in conds_blocks {
+ if !first {
+ edit.insert(then_arm_end, format!("\n{}", spaces));
+ } else {
+ first = false;
+ }
+ let guard = format!("{} if {} => ", match_pat, cond.syntax().text());
+ edit.insert(then_arm_end, guard);
+ let only_expr = block.statements().next().is_none();
+ match &block.tail_expr() {
+ Some(then_expr) if only_expr => {
+ edit.insert(then_arm_end, then_expr.syntax().text());
+ edit.insert(then_arm_end, ",");
+ }
+ _ => {
+ let to_insert = block.dedent(dedent.into()).syntax().text();
+ edit.insert(then_arm_end, to_insert)
+ }
+ }
+ }
+ if let Some(e) = tail {
+ cov_mark::hit!(move_guard_ifelse_else_tail);
+ let guard = format!("\n{}{} => ", spaces, match_pat);
+ edit.insert(then_arm_end, guard);
+ let only_expr = e.statements().next().is_none();
+ match &e.tail_expr() {
+ Some(expr) if only_expr => {
+ cov_mark::hit!(move_guard_ifelse_expr_only);
+ edit.insert(then_arm_end, expr.syntax().text());
+ edit.insert(then_arm_end, ",");
+ }
+ _ => {
+ let to_insert = e.dedent(dedent.into()).syntax().text();
+ edit.insert(then_arm_end, to_insert)
+ }
+ }
+ } else {
+ // There's no else branch. Add a pattern without guard, unless the following match
+ // arm is `_ => ...`
+ cov_mark::hit!(move_guard_ifelse_notail);
+ match match_arm.syntax().next_sibling().and_then(MatchArm::cast) {
+ Some(next_arm)
+ if matches!(next_arm.pat(), Some(Pat::WildcardPat(_)))
+ && next_arm.guard().is_none() =>
+ {
+ cov_mark::hit!(move_guard_ifelse_has_wildcard);
+ }
+ _ => edit.insert(then_arm_end, format!("\n{}{} => {{}}", spaces, match_pat)),
+ }
+ }
+ },
+ )
+}
+
+// Parses an if-else-if chain to get the conditions and the then branches until we encounter an else
+// branch or the end.
+fn parse_if_chain(if_expr: IfExpr) -> Option<(Vec<(Expr, BlockExpr)>, Option<BlockExpr>)> {
+ let mut conds_blocks = Vec::new();
+ let mut curr_if = if_expr;
+ let tail = loop {
+ let cond = curr_if.condition()?;
+ conds_blocks.push((cond, curr_if.then_branch()?));
+ match curr_if.else_branch() {
+ Some(ElseBranch::IfExpr(e)) => {
+ curr_if = e;
+ }
+ Some(ElseBranch::Block(b)) => {
+ break Some(b);
+ }
+ None => break None,
+ }
+ };
+ Some((conds_blocks, tail))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
+
+ #[test]
+ fn move_guard_to_arm_body_range() {
+ cov_mark::check!(move_guard_unapplicable_in_arm_body);
+ check_assist_not_applicable(
+ move_guard_to_arm_body,
+ r#"
+fn main() {
+ match 92 {
+ x if x > 10 => $0false,
+ _ => true
+ }
+}
+"#,
+ );
+ }
+ #[test]
+ fn move_guard_to_arm_body_target() {
+ check_assist_target(
+ move_guard_to_arm_body,
+ r#"
+fn main() {
+ match 92 {
+ x $0if x > 10 => false,
+ _ => true
+ }
+}
+"#,
+ r#"if x > 10"#,
+ );
+ }
+
+ #[test]
+ fn move_guard_to_arm_body_works() {
+ check_assist(
+ move_guard_to_arm_body,
+ r#"
+fn main() {
+ match 92 {
+ x $0if x > 10 => false,
+ _ => true
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ x => if x > 10 {
+ false
+ },
+ _ => true
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn move_let_guard_to_arm_body_works() {
+ check_assist(
+ move_guard_to_arm_body,
+ r#"
+fn main() {
+ match 92 {
+ x $0if (let 1 = x) => false,
+ _ => true
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ x => if (let 1 = x) {
+ false
+ },
+ _ => true
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn move_guard_to_arm_body_works_complex_match() {
+ check_assist(
+ move_guard_to_arm_body,
+ r#"
+fn main() {
+ match 92 {
+ $0x @ 4 | x @ 5 if x > 5 => true,
+ _ => false
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ x @ 4 | x @ 5 => if x > 5 {
+ true
+ },
+ _ => false
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn move_arm_cond_to_match_guard_works() {
+ check_assist(
+ move_arm_cond_to_match_guard,
+ r#"
+fn main() {
+ match 92 {
+ x => if x > 10$0 { false },
+ _ => true
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ x if x > 10 => false,
+ _ => true
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn move_arm_cond_in_block_to_match_guard_works() {
+ cov_mark::check!(move_guard_ifelse_has_wildcard);
+ check_assist(
+ move_arm_cond_to_match_guard,
+ r#"
+fn main() {
+ match 92 {
+ x => {
+ $0if x > 10 {
+ false
+ }
+ },
+ _ => true
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ x if x > 10 => false,
+ _ => true
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn move_arm_cond_in_block_to_match_guard_no_wildcard_works() {
+ cov_mark::check_count!(move_guard_ifelse_has_wildcard, 0);
+ check_assist(
+ move_arm_cond_to_match_guard,
+ r#"
+fn main() {
+ match 92 {
+ x => {
+ $0if x > 10 {
+ false
+ }
+ }
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ x if x > 10 => false,
+ x => {}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn move_arm_cond_in_block_to_match_guard_wildcard_guard_works() {
+ cov_mark::check_count!(move_guard_ifelse_has_wildcard, 0);
+ check_assist(
+ move_arm_cond_to_match_guard,
+ r#"
+fn main() {
+ match 92 {
+ x => {
+ $0if x > 10 {
+ false
+ }
+ }
+ _ if x > 10 => true,
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ x if x > 10 => false,
+ x => {}
+ _ if x > 10 => true,
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn move_arm_cond_in_block_to_match_guard_add_comma_works() {
+ check_assist(
+ move_arm_cond_to_match_guard,
+ r#"
+fn main() {
+ match 92 {
+ x => {
+ $0if x > 10 {
+ false
+ }
+ }
+ _ => true
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ x if x > 10 => false,
+ _ => true
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn move_arm_cond_to_match_guard_if_let_works() {
+ check_assist(
+ move_arm_cond_to_match_guard,
+ r#"
+fn main() {
+ match 92 {
+ x => if let 62 = x $0&& true { false },
+ _ => true
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ x if let 62 = x && true => false,
+ _ => true
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn move_arm_cond_to_match_guard_if_empty_body_works() {
+ check_assist(
+ move_arm_cond_to_match_guard,
+ r#"
+fn main() {
+ match 92 {
+ x => if x $0> 10 { },
+ _ => true
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ x if x > 10 => { }
+ _ => true
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn move_arm_cond_to_match_guard_if_multiline_body_works() {
+ check_assist(
+ move_arm_cond_to_match_guard,
+ r#"
+fn main() {
+ match 92 {
+ x => if$0 x > 10 {
+ 92;
+ false
+ },
+ _ => true
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ x if x > 10 => {
+ 92;
+ false
+ }
+ _ => true
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn move_arm_cond_in_block_to_match_guard_if_multiline_body_works() {
+ check_assist(
+ move_arm_cond_to_match_guard,
+ r#"
+fn main() {
+ match 92 {
+ x => {
+ if x > $010 {
+ 92;
+ false
+ }
+ }
+ _ => true
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ x if x > 10 => {
+ 92;
+ false
+ }
+ _ => true
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn move_arm_cond_to_match_guard_with_else_works() {
+ check_assist(
+ move_arm_cond_to_match_guard,
+ r#"
+fn main() {
+ match 92 {
+ x => if x > $010 {
+ false
+ } else {
+ true
+ }
+ _ => true,
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ x if x > 10 => false,
+ x => true,
+ _ => true,
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn move_arm_cond_to_match_guard_with_else_block_works() {
+ cov_mark::check!(move_guard_ifelse_expr_only);
+ check_assist(
+ move_arm_cond_to_match_guard,
+ r#"
+fn main() {
+ match 92 {
+ x => {
+ if x $0> 10 {
+ false
+ } else {
+ true
+ }
+ }
+ _ => true
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ x if x > 10 => false,
+ x => true,
+ _ => true
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn move_arm_cond_to_match_guard_else_if_empty_body_works() {
+ check_assist(
+ move_arm_cond_to_match_guard,
+ r#"
+fn main() {
+ match 92 {
+ x => if x > $010 { } else { },
+ _ => true
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ x if x > 10 => { }
+ x => { }
+ _ => true
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn move_arm_cond_to_match_guard_with_else_multiline_works() {
+ check_assist(
+ move_arm_cond_to_match_guard,
+ r#"
+fn main() {
+ match 92 {
+ x => if$0 x > 10 {
+ 92;
+ false
+ } else {
+ true
+ }
+ _ => true
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ x if x > 10 => {
+ 92;
+ false
+ }
+ x => true,
+ _ => true
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn move_arm_cond_to_match_guard_with_else_multiline_else_works() {
+ cov_mark::check!(move_guard_ifelse_else_block);
+ check_assist(
+ move_arm_cond_to_match_guard,
+ r#"
+fn main() {
+ match 92 {
+ x => if x $0> 10 {
+ false
+ } else {
+ 42;
+ true
+ }
+ _ => true
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ x if x > 10 => false,
+ x => {
+ 42;
+ true
+ }
+ _ => true
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn move_arm_cond_to_match_guard_with_else_multiline_else_block_works() {
+ cov_mark::check!(move_guard_ifelse_in_block);
+ check_assist(
+ move_arm_cond_to_match_guard,
+ r#"
+fn main() {
+ match 92 {
+ x => {
+ if x > $010 {
+ false
+ } else {
+ 42;
+ true
+ }
+ }
+ _ => true
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ x if x > 10 => false,
+ x => {
+ 42;
+ true
+ }
+ _ => true
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn move_arm_cond_to_match_guard_with_else_last_arm_works() {
+ check_assist(
+ move_arm_cond_to_match_guard,
+ r#"
+fn main() {
+ match 92 {
+ 3 => true,
+ x => {
+ if x > $010 {
+ false
+ } else {
+ 92;
+ true
+ }
+ }
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ 3 => true,
+ x if x > 10 => false,
+ x => {
+ 92;
+ true
+ }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn move_arm_cond_to_match_guard_with_else_comma_works() {
+ check_assist(
+ move_arm_cond_to_match_guard,
+ r#"
+fn main() {
+ match 92 {
+ 3 => true,
+ x => if x > $010 {
+ false
+ } else {
+ 92;
+ true
+ },
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ 3 => true,
+ x if x > 10 => false,
+ x => {
+ 92;
+ true
+ }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn move_arm_cond_to_match_guard_elseif() {
+ check_assist(
+ move_arm_cond_to_match_guard,
+ r#"
+fn main() {
+ match 92 {
+ 3 => true,
+ x => if x $0> 10 {
+ false
+ } else if x > 5 {
+ true
+ } else if x > 4 {
+ false
+ } else {
+ true
+ },
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ 3 => true,
+ x if x > 10 => false,
+ x if x > 5 => true,
+ x if x > 4 => false,
+ x => true,
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn move_arm_cond_to_match_guard_elseif_in_block() {
+ cov_mark::check!(move_guard_ifelse_in_block);
+ check_assist(
+ move_arm_cond_to_match_guard,
+ r#"
+fn main() {
+ match 92 {
+ 3 => true,
+ x => {
+ if x > $010 {
+ false
+ } else if x > 5 {
+ true
+ } else if x > 4 {
+ false
+ } else {
+ true
+ }
+ }
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ 3 => true,
+ x if x > 10 => false,
+ x if x > 5 => true,
+ x if x > 4 => false,
+ x => true,
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn move_arm_cond_to_match_guard_elseif_chain() {
+ cov_mark::check!(move_guard_ifelse_else_tail);
+ check_assist(
+ move_arm_cond_to_match_guard,
+ r#"
+fn main() {
+ match 92 {
+ 3 => 0,
+ x => if x $0> 10 {
+ 1
+ } else if x > 5 {
+ 2
+ } else if x > 3 {
+ 42;
+ 3
+ } else {
+ 4
+ },
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ 3 => 0,
+ x if x > 10 => 1,
+ x if x > 5 => 2,
+ x if x > 3 => {
+ 42;
+ 3
+ }
+ x => 4,
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn move_arm_cond_to_match_guard_elseif_iflet() {
+ check_assist(
+ move_arm_cond_to_match_guard,
+ r#"
+fn main() {
+ match 92 {
+ 3 => 0,
+ x => if x $0> 10 {
+ 1
+ } else if x > 5 {
+ 2
+ } else if let 4 = 4 {
+ 42;
+ 3
+ } else {
+ 4
+ },
+ }
+}"#,
+ r#"
+fn main() {
+ match 92 {
+ 3 => 0,
+ x if x > 10 => 1,
+ x if x > 5 => 2,
+ x if let 4 = 4 => {
+ 42;
+ 3
+ }
+ x => 4,
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn move_arm_cond_to_match_guard_elseif_notail() {
+ cov_mark::check!(move_guard_ifelse_notail);
+ check_assist(
+ move_arm_cond_to_match_guard,
+ r#"
+fn main() {
+ match 92 {
+ 3 => 0,
+ x => if x > $010 {
+ 1
+ } else if x > 5 {
+ 2
+ } else if x > 4 {
+ 42;
+ 3
+ },
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ 3 => 0,
+ x if x > 10 => 1,
+ x if x > 5 => 2,
+ x if x > 4 => {
+ 42;
+ 3
+ }
+ x => {}
+ }
+}
+"#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_module_to_file.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_module_to_file.rs
new file mode 100644
index 000000000..7468318a5
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_module_to_file.rs
@@ -0,0 +1,337 @@
+use std::iter;
+
+use ast::edit::IndentLevel;
+use ide_db::base_db::AnchoredPathBuf;
+use itertools::Itertools;
+use stdx::format_to;
+use syntax::{
+ ast::{self, edit::AstNodeEdit, HasName},
+ AstNode, SmolStr, TextRange,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: move_module_to_file
+//
+// Moves inline module's contents to a separate file.
+//
+// ```
+// mod $0foo {
+// fn t() {}
+// }
+// ```
+// ->
+// ```
+// mod foo;
+// ```
+pub(crate) fn move_module_to_file(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let module_ast = ctx.find_node_at_offset::<ast::Module>()?;
+ let module_items = module_ast.item_list()?;
+
+ let l_curly_offset = module_items.syntax().text_range().start();
+ if l_curly_offset <= ctx.offset() {
+ cov_mark::hit!(available_before_curly);
+ return None;
+ }
+ let target = TextRange::new(module_ast.syntax().text_range().start(), l_curly_offset);
+
+ let module_name = module_ast.name()?;
+
+ // get to the outermost module syntax so we can grab the module of file we are in
+ let outermost_mod_decl =
+ iter::successors(Some(module_ast.clone()), |module| module.parent()).last()?;
+ let module_def = ctx.sema.to_def(&outermost_mod_decl)?;
+ let parent_module = module_def.parent(ctx.db())?;
+
+ acc.add(
+ AssistId("move_module_to_file", AssistKind::RefactorExtract),
+ "Extract module to file",
+ target,
+ |builder| {
+ let path = {
+ let mut buf = String::from("./");
+ match parent_module.name(ctx.db()) {
+ Some(name) if !parent_module.is_mod_rs(ctx.db()) => {
+ format_to!(buf, "{}/", name)
+ }
+ _ => (),
+ }
+ let segments = iter::successors(Some(module_ast.clone()), |module| module.parent())
+ .filter_map(|it| it.name())
+ .map(|name| SmolStr::from(name.text().trim_start_matches("r#")))
+ .collect::<Vec<_>>();
+
+ format_to!(buf, "{}", segments.into_iter().rev().format("/"));
+
+ // We need to special case mod named `r#mod` and place the file in a
+ // subdirectory as "mod.rs" would be of its parent module otherwise.
+ if module_name.text() == "r#mod" {
+ format_to!(buf, "/mod.rs");
+ } else {
+ format_to!(buf, ".rs");
+ }
+ buf
+ };
+ let contents = {
+ let items = module_items.dedent(IndentLevel(1)).to_string();
+ let mut items =
+ items.trim_start_matches('{').trim_end_matches('}').trim().to_string();
+ if !items.is_empty() {
+ items.push('\n');
+ }
+ items
+ };
+
+ let buf = format!("mod {};", module_name);
+
+ let replacement_start = match module_ast.mod_token() {
+ Some(mod_token) => mod_token.text_range(),
+ None => module_ast.syntax().text_range(),
+ }
+ .start();
+
+ builder.replace(
+ TextRange::new(replacement_start, module_ast.syntax().text_range().end()),
+ buf,
+ );
+
+ let dst = AnchoredPathBuf { anchor: ctx.file_id(), path };
+ builder.create_file(dst, contents);
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn extract_from_root() {
+ check_assist(
+ move_module_to_file,
+ r#"
+mod $0tests {
+ #[test] fn t() {}
+}
+"#,
+ r#"
+//- /main.rs
+mod tests;
+//- /tests.rs
+#[test] fn t() {}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_from_submodule() {
+ check_assist(
+ move_module_to_file,
+ r#"
+//- /main.rs
+mod submod;
+//- /submod.rs
+$0mod inner {
+ fn f() {}
+}
+fn g() {}
+"#,
+ r#"
+//- /submod.rs
+mod inner;
+fn g() {}
+//- /submod/inner.rs
+fn f() {}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_from_mod_rs() {
+ check_assist(
+ move_module_to_file,
+ r#"
+//- /main.rs
+mod submodule;
+//- /submodule/mod.rs
+mod inner$0 {
+ fn f() {}
+}
+fn g() {}
+"#,
+ r#"
+//- /submodule/mod.rs
+mod inner;
+fn g() {}
+//- /submodule/inner.rs
+fn f() {}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_public() {
+ check_assist(
+ move_module_to_file,
+ r#"
+pub mod $0tests {
+ #[test] fn t() {}
+}
+"#,
+ r#"
+//- /main.rs
+pub mod tests;
+//- /tests.rs
+#[test] fn t() {}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_public_crate() {
+ check_assist(
+ move_module_to_file,
+ r#"
+pub(crate) mod $0tests {
+ #[test] fn t() {}
+}
+"#,
+ r#"
+//- /main.rs
+pub(crate) mod tests;
+//- /tests.rs
+#[test] fn t() {}
+"#,
+ );
+ }
+
+ #[test]
+ fn available_before_curly() {
+ cov_mark::check!(available_before_curly);
+ check_assist_not_applicable(move_module_to_file, r#"mod m { $0 }"#);
+ }
+
+ #[test]
+ fn keep_outer_comments_and_attributes() {
+ check_assist(
+ move_module_to_file,
+ r#"
+/// doc comment
+#[attribute]
+mod $0tests {
+ #[test] fn t() {}
+}
+"#,
+ r#"
+//- /main.rs
+/// doc comment
+#[attribute]
+mod tests;
+//- /tests.rs
+#[test] fn t() {}
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_nested() {
+ check_assist(
+ move_module_to_file,
+ r#"
+//- /lib.rs
+mod foo;
+//- /foo.rs
+mod bar {
+ mod baz {
+ mod qux$0 {}
+ }
+}
+"#,
+ r#"
+//- /foo.rs
+mod bar {
+ mod baz {
+ mod qux;
+ }
+}
+//- /foo/bar/baz/qux.rs
+"#,
+ );
+ }
+
+ #[test]
+ fn extract_mod_with_raw_ident() {
+ check_assist(
+ move_module_to_file,
+ r#"
+//- /main.rs
+mod $0r#static {}
+"#,
+ r#"
+//- /main.rs
+mod r#static;
+//- /static.rs
+"#,
+ )
+ }
+
+ #[test]
+ fn extract_r_mod() {
+ check_assist(
+ move_module_to_file,
+ r#"
+//- /main.rs
+mod $0r#mod {}
+"#,
+ r#"
+//- /main.rs
+mod r#mod;
+//- /mod/mod.rs
+"#,
+ )
+ }
+
+ #[test]
+ fn extract_r_mod_from_mod_rs() {
+ check_assist(
+ move_module_to_file,
+ r#"
+//- /main.rs
+mod foo;
+//- /foo/mod.rs
+mod $0r#mod {}
+"#,
+ r#"
+//- /foo/mod.rs
+mod r#mod;
+//- /foo/mod/mod.rs
+"#,
+ )
+ }
+
+ #[test]
+ fn extract_nested_r_mod() {
+ check_assist(
+ move_module_to_file,
+ r#"
+//- /main.rs
+mod r#mod {
+ mod foo {
+ mod $0r#mod {}
+ }
+}
+"#,
+ r#"
+//- /main.rs
+mod r#mod {
+ mod foo {
+ mod r#mod;
+ }
+}
+//- /mod/foo/mod/mod.rs
+"#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_to_mod_rs.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_to_mod_rs.rs
new file mode 100644
index 000000000..a909ce8b2
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_to_mod_rs.rs
@@ -0,0 +1,151 @@
+use ide_db::{
+ assists::{AssistId, AssistKind},
+ base_db::AnchoredPathBuf,
+};
+use syntax::{ast, AstNode};
+
+use crate::{
+ assist_context::{AssistContext, Assists},
+ utils::trimmed_text_range,
+};
+
+// Assist: move_to_mod_rs
+//
+// Moves xxx.rs to xxx/mod.rs.
+//
+// ```
+// //- /main.rs
+// mod a;
+// //- /a.rs
+// $0fn t() {}$0
+// ```
+// ->
+// ```
+// fn t() {}
+// ```
+pub(crate) fn move_to_mod_rs(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let source_file = ctx.find_node_at_offset::<ast::SourceFile>()?;
+ let module = ctx.sema.to_module_def(ctx.file_id())?;
+ // Enable this assist if the user select all "meaningful" content in the source file
+ let trimmed_selected_range = trimmed_text_range(&source_file, ctx.selection_trimmed());
+ let trimmed_file_range = trimmed_text_range(&source_file, source_file.syntax().text_range());
+ if module.is_mod_rs(ctx.db()) {
+ cov_mark::hit!(already_mod_rs);
+ return None;
+ }
+ if trimmed_selected_range != trimmed_file_range {
+ cov_mark::hit!(not_all_selected);
+ return None;
+ }
+
+ let target = source_file.syntax().text_range();
+ let module_name = module.name(ctx.db())?.to_string();
+ let path = format!("./{}/mod.rs", module_name);
+ let dst = AnchoredPathBuf { anchor: ctx.file_id(), path };
+ acc.add(
+ AssistId("move_to_mod_rs", AssistKind::Refactor),
+ format!("Convert {}.rs to {}/mod.rs", module_name, module_name),
+ target,
+ |builder| {
+ builder.move_file(ctx.file_id(), dst);
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn trivial() {
+ check_assist(
+ move_to_mod_rs,
+ r#"
+//- /main.rs
+mod a;
+//- /a.rs
+$0fn t() {}
+$0"#,
+ r#"
+//- /a/mod.rs
+fn t() {}
+"#,
+ );
+ }
+
+ #[test]
+ fn must_select_all_file() {
+ cov_mark::check!(not_all_selected);
+ check_assist_not_applicable(
+ move_to_mod_rs,
+ r#"
+//- /main.rs
+mod a;
+//- /a.rs
+fn t() {}$0
+"#,
+ );
+ cov_mark::check!(not_all_selected);
+ check_assist_not_applicable(
+ move_to_mod_rs,
+ r#"
+//- /main.rs
+mod a;
+//- /a.rs
+$0fn$0 t() {}
+"#,
+ );
+ }
+
+ #[test]
+ fn cannot_promote_mod_rs() {
+ cov_mark::check!(already_mod_rs);
+ check_assist_not_applicable(
+ move_to_mod_rs,
+ r#"//- /main.rs
+mod a;
+//- /a/mod.rs
+$0fn t() {}$0
+"#,
+ );
+ }
+
+ #[test]
+ fn cannot_promote_main_and_lib_rs() {
+ check_assist_not_applicable(
+ move_to_mod_rs,
+ r#"//- /main.rs
+$0fn t() {}$0
+"#,
+ );
+ check_assist_not_applicable(
+ move_to_mod_rs,
+ r#"//- /lib.rs
+$0fn t() {}$0
+"#,
+ );
+ }
+
+ #[test]
+ fn works_in_mod() {
+ // note: /a/b.rs remains untouched
+ check_assist(
+ move_to_mod_rs,
+ r#"//- /main.rs
+mod a;
+//- /a.rs
+$0mod b;
+fn t() {}$0
+//- /a/b.rs
+fn t1() {}
+"#,
+ r#"
+//- /a/mod.rs
+mod b;
+fn t() {}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/number_representation.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/number_representation.rs
new file mode 100644
index 000000000..424db7437
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/number_representation.rs
@@ -0,0 +1,183 @@
+use syntax::{ast, ast::Radix, AstToken};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
+
+const MIN_NUMBER_OF_DIGITS_TO_FORMAT: usize = 5;
+
+// Assist: reformat_number_literal
+//
+// Adds or removes separators from integer literal.
+//
+// ```
+// const _: i32 = 1012345$0;
+// ```
+// ->
+// ```
+// const _: i32 = 1_012_345;
+// ```
+pub(crate) fn reformat_number_literal(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let literal = ctx.find_node_at_offset::<ast::Literal>()?;
+ let literal = match literal.kind() {
+ ast::LiteralKind::IntNumber(it) => it,
+ _ => return None,
+ };
+
+ let text = literal.text();
+ if text.contains('_') {
+ return remove_separators(acc, literal);
+ }
+
+ let (prefix, value, suffix) = literal.split_into_parts();
+ if value.len() < MIN_NUMBER_OF_DIGITS_TO_FORMAT {
+ return None;
+ }
+
+ let radix = literal.radix();
+ let mut converted = prefix.to_string();
+ converted.push_str(&add_group_separators(value, group_size(radix)));
+ converted.push_str(suffix);
+
+ let group_id = GroupLabel("Reformat number literal".into());
+ let label = format!("Convert {} to {}", literal, converted);
+ let range = literal.syntax().text_range();
+ acc.add_group(
+ &group_id,
+ AssistId("reformat_number_literal", AssistKind::RefactorInline),
+ label,
+ range,
+ |builder| builder.replace(range, converted),
+ )
+}
+
+fn remove_separators(acc: &mut Assists, literal: ast::IntNumber) -> Option<()> {
+ let group_id = GroupLabel("Reformat number literal".into());
+ let range = literal.syntax().text_range();
+ acc.add_group(
+ &group_id,
+ AssistId("reformat_number_literal", AssistKind::RefactorInline),
+ "Remove digit separators",
+ range,
+ |builder| builder.replace(range, literal.text().replace('_', "")),
+ )
+}
+
+const fn group_size(r: Radix) -> usize {
+ match r {
+ Radix::Binary => 4,
+ Radix::Octal => 3,
+ Radix::Decimal => 3,
+ Radix::Hexadecimal => 4,
+ }
+}
+
+fn add_group_separators(s: &str, group_size: usize) -> String {
+ let mut chars = Vec::new();
+ for (i, ch) in s.chars().filter(|&ch| ch != '_').rev().enumerate() {
+ if i > 0 && i % group_size == 0 {
+ chars.push('_');
+ }
+ chars.push(ch);
+ }
+
+ chars.into_iter().rev().collect()
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist_by_label, check_assist_not_applicable, check_assist_target};
+
+ use super::*;
+
+ #[test]
+ fn group_separators() {
+ let cases = vec![
+ ("", 4, ""),
+ ("1", 4, "1"),
+ ("12", 4, "12"),
+ ("123", 4, "123"),
+ ("1234", 4, "1234"),
+ ("12345", 4, "1_2345"),
+ ("123456", 4, "12_3456"),
+ ("1234567", 4, "123_4567"),
+ ("12345678", 4, "1234_5678"),
+ ("123456789", 4, "1_2345_6789"),
+ ("1234567890", 4, "12_3456_7890"),
+ ("1_2_3_4_5_6_7_8_9_0_", 4, "12_3456_7890"),
+ ("1234567890", 3, "1_234_567_890"),
+ ("1234567890", 2, "12_34_56_78_90"),
+ ("1234567890", 1, "1_2_3_4_5_6_7_8_9_0"),
+ ];
+
+ for case in cases {
+ let (input, group_size, expected) = case;
+ assert_eq!(add_group_separators(input, group_size), expected)
+ }
+ }
+
+ #[test]
+ fn good_targets() {
+ let cases = vec![
+ ("const _: i32 = 0b11111$0", "0b11111"),
+ ("const _: i32 = 0o77777$0;", "0o77777"),
+ ("const _: i32 = 10000$0;", "10000"),
+ ("const _: i32 = 0xFFFFF$0;", "0xFFFFF"),
+ ("const _: i32 = 10000i32$0;", "10000i32"),
+ ("const _: i32 = 0b_10_0i32$0;", "0b_10_0i32"),
+ ];
+
+ for case in cases {
+ check_assist_target(reformat_number_literal, case.0, case.1);
+ }
+ }
+
+ #[test]
+ fn bad_targets() {
+ let cases = vec![
+ "const _: i32 = 0b111$0",
+ "const _: i32 = 0b1111$0",
+ "const _: i32 = 0o77$0;",
+ "const _: i32 = 0o777$0;",
+ "const _: i32 = 10$0;",
+ "const _: i32 = 999$0;",
+ "const _: i32 = 0xFF$0;",
+ "const _: i32 = 0xFFFF$0;",
+ ];
+
+ for case in cases {
+ check_assist_not_applicable(reformat_number_literal, case);
+ }
+ }
+
+ #[test]
+ fn labels() {
+ let cases = vec![
+ ("const _: i32 = 10000$0", "const _: i32 = 10_000", "Convert 10000 to 10_000"),
+ (
+ "const _: i32 = 0xFF0000$0;",
+ "const _: i32 = 0xFF_0000;",
+ "Convert 0xFF0000 to 0xFF_0000",
+ ),
+ (
+ "const _: i32 = 0b11111111$0;",
+ "const _: i32 = 0b1111_1111;",
+ "Convert 0b11111111 to 0b1111_1111",
+ ),
+ (
+ "const _: i32 = 0o377211$0;",
+ "const _: i32 = 0o377_211;",
+ "Convert 0o377211 to 0o377_211",
+ ),
+ (
+ "const _: i32 = 10000i32$0;",
+ "const _: i32 = 10_000i32;",
+ "Convert 10000i32 to 10_000i32",
+ ),
+ ("const _: i32 = 1_0_0_0_i32$0;", "const _: i32 = 1000i32;", "Remove digit separators"),
+ ];
+
+ for case in cases {
+ let (before, after, label) = case;
+ check_assist_by_label(reformat_number_literal, before, after, label);
+ }
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/promote_local_to_const.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/promote_local_to_const.rs
new file mode 100644
index 000000000..cbbea6c1e
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/promote_local_to_const.rs
@@ -0,0 +1,221 @@
+use hir::{HirDisplay, ModuleDef, PathResolution, Semantics};
+use ide_db::{
+ assists::{AssistId, AssistKind},
+ defs::Definition,
+ syntax_helpers::node_ext::preorder_expr,
+ RootDatabase,
+};
+use stdx::to_upper_snake_case;
+use syntax::{
+ ast::{self, make, HasName},
+ AstNode, WalkEvent,
+};
+
+use crate::{
+ assist_context::{AssistContext, Assists},
+ utils::{render_snippet, Cursor},
+};
+
+// Assist: promote_local_to_const
+//
+// Promotes a local variable to a const item changing its name to a `SCREAMING_SNAKE_CASE` variant
+// if the local uses no non-const expressions.
+//
+// ```
+// fn main() {
+// let foo$0 = true;
+//
+// if foo {
+// println!("It's true");
+// } else {
+// println!("It's false");
+// }
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// const $0FOO: bool = true;
+//
+// if FOO {
+// println!("It's true");
+// } else {
+// println!("It's false");
+// }
+// }
+// ```
+pub(crate) fn promote_local_to_const(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let pat = ctx.find_node_at_offset::<ast::IdentPat>()?;
+ let name = pat.name()?;
+ if !pat.is_simple_ident() {
+ cov_mark::hit!(promote_local_non_simple_ident);
+ return None;
+ }
+ let let_stmt = pat.syntax().parent().and_then(ast::LetStmt::cast)?;
+
+ let module = ctx.sema.scope(pat.syntax())?.module();
+ let local = ctx.sema.to_def(&pat)?;
+ let ty = ctx.sema.type_of_pat(&pat.into())?.original;
+
+ if ty.contains_unknown() || ty.is_closure() {
+ cov_mark::hit!(promote_lcoal_not_applicable_if_ty_not_inferred);
+ return None;
+ }
+ let ty = ty.display_source_code(ctx.db(), module.into()).ok()?;
+
+ let initializer = let_stmt.initializer()?;
+ if !is_body_const(&ctx.sema, &initializer) {
+ cov_mark::hit!(promote_local_non_const);
+ return None;
+ }
+ let target = let_stmt.syntax().text_range();
+ acc.add(
+ AssistId("promote_local_to_const", AssistKind::Refactor),
+ "Promote local to constant",
+ target,
+ |builder| {
+ let name = to_upper_snake_case(&name.to_string());
+ let usages = Definition::Local(local).usages(&ctx.sema).all();
+ if let Some(usages) = usages.references.get(&ctx.file_id()) {
+ for usage in usages {
+ builder.replace(usage.range, &name);
+ }
+ }
+
+ let item = make::item_const(None, make::name(&name), make::ty(&ty), initializer);
+ match ctx.config.snippet_cap.zip(item.name()) {
+ Some((cap, name)) => builder.replace_snippet(
+ cap,
+ target,
+ render_snippet(cap, item.syntax(), Cursor::Before(name.syntax())),
+ ),
+ None => builder.replace(target, item.to_string()),
+ }
+ },
+ )
+}
+
+fn is_body_const(sema: &Semantics<'_, RootDatabase>, expr: &ast::Expr) -> bool {
+ let mut is_const = true;
+ preorder_expr(expr, &mut |ev| {
+ let expr = match ev {
+ WalkEvent::Enter(_) if !is_const => return true,
+ WalkEvent::Enter(expr) => expr,
+ WalkEvent::Leave(_) => return false,
+ };
+ match expr {
+ ast::Expr::CallExpr(call) => {
+ if let Some(ast::Expr::PathExpr(path_expr)) = call.expr() {
+ if let Some(PathResolution::Def(ModuleDef::Function(func))) =
+ path_expr.path().and_then(|path| sema.resolve_path(&path))
+ {
+ is_const &= func.is_const(sema.db);
+ }
+ }
+ }
+ ast::Expr::MethodCallExpr(call) => {
+ is_const &=
+ sema.resolve_method_call(&call).map(|it| it.is_const(sema.db)).unwrap_or(true)
+ }
+ ast::Expr::BoxExpr(_)
+ | ast::Expr::ForExpr(_)
+ | ast::Expr::ReturnExpr(_)
+ | ast::Expr::TryExpr(_)
+ | ast::Expr::YieldExpr(_)
+ | ast::Expr::AwaitExpr(_) => is_const = false,
+ _ => (),
+ }
+ !is_const
+ });
+ is_const
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn simple() {
+ check_assist(
+ promote_local_to_const,
+ r"
+fn foo() {
+ let x$0 = 0;
+ let y = x;
+}
+",
+ r"
+fn foo() {
+ const $0X: i32 = 0;
+ let y = X;
+}
+",
+ );
+ }
+
+ #[test]
+ fn not_applicable_non_const_meth_call() {
+ cov_mark::check!(promote_local_non_const);
+ check_assist_not_applicable(
+ promote_local_to_const,
+ r"
+struct Foo;
+impl Foo {
+ fn foo(self) {}
+}
+fn foo() {
+ let x$0 = Foo.foo();
+}
+",
+ );
+ }
+
+ #[test]
+ fn not_applicable_non_const_call() {
+ check_assist_not_applicable(
+ promote_local_to_const,
+ r"
+fn bar(self) {}
+fn foo() {
+ let x$0 = bar();
+}
+",
+ );
+ }
+
+ #[test]
+ fn not_applicable_unknown_ty() {
+ cov_mark::check!(promote_lcoal_not_applicable_if_ty_not_inferred);
+ check_assist_not_applicable(
+ promote_local_to_const,
+ r"
+fn foo() {
+ let x$0 = bar();
+}
+",
+ );
+ }
+
+ #[test]
+ fn not_applicable_non_simple_ident() {
+ cov_mark::check!(promote_local_non_simple_ident);
+ check_assist_not_applicable(
+ promote_local_to_const,
+ r"
+fn foo() {
+ let ref x$0 = ();
+}
+",
+ );
+ check_assist_not_applicable(
+ promote_local_to_const,
+ r"
+fn foo() {
+ let mut x$0 = ();
+}
+",
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/pull_assignment_up.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/pull_assignment_up.rs
new file mode 100644
index 000000000..4cfe6c99b
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/pull_assignment_up.rs
@@ -0,0 +1,507 @@
+use syntax::{
+ ast::{self, make},
+ ted, AstNode,
+};
+
+use crate::{
+ assist_context::{AssistContext, Assists},
+ AssistId, AssistKind,
+};
+
+// Assist: pull_assignment_up
+//
+// Extracts variable assignment to outside an if or match statement.
+//
+// ```
+// fn main() {
+// let mut foo = 6;
+//
+// if true {
+// $0foo = 5;
+// } else {
+// foo = 4;
+// }
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// let mut foo = 6;
+//
+// foo = if true {
+// 5
+// } else {
+// 4
+// };
+// }
+// ```
+pub(crate) fn pull_assignment_up(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let assign_expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
+
+ let op_kind = assign_expr.op_kind()?;
+ if op_kind != (ast::BinaryOp::Assignment { op: None }) {
+ cov_mark::hit!(test_cant_pull_non_assignments);
+ return None;
+ }
+
+ let mut collector = AssignmentsCollector {
+ sema: &ctx.sema,
+ common_lhs: assign_expr.lhs()?,
+ assignments: Vec::new(),
+ };
+
+ let tgt: ast::Expr = if let Some(if_expr) = ctx.find_node_at_offset::<ast::IfExpr>() {
+ collector.collect_if(&if_expr)?;
+ if_expr.into()
+ } else if let Some(match_expr) = ctx.find_node_at_offset::<ast::MatchExpr>() {
+ collector.collect_match(&match_expr)?;
+ match_expr.into()
+ } else {
+ return None;
+ };
+
+ if let Some(parent) = tgt.syntax().parent() {
+ if matches!(parent.kind(), syntax::SyntaxKind::BIN_EXPR | syntax::SyntaxKind::LET_STMT) {
+ return None;
+ }
+ }
+
+ acc.add(
+ AssistId("pull_assignment_up", AssistKind::RefactorExtract),
+ "Pull assignment up",
+ tgt.syntax().text_range(),
+ move |edit| {
+ let assignments: Vec<_> = collector
+ .assignments
+ .into_iter()
+ .map(|(stmt, rhs)| (edit.make_mut(stmt), rhs.clone_for_update()))
+ .collect();
+
+ let tgt = edit.make_mut(tgt);
+
+ for (stmt, rhs) in assignments {
+ let mut stmt = stmt.syntax().clone();
+ if let Some(parent) = stmt.parent() {
+ if ast::ExprStmt::cast(parent.clone()).is_some() {
+ stmt = parent.clone();
+ }
+ }
+ ted::replace(stmt, rhs.syntax());
+ }
+ let assign_expr = make::expr_assignment(collector.common_lhs, tgt.clone());
+ let assign_stmt = make::expr_stmt(assign_expr);
+
+ ted::replace(tgt.syntax(), assign_stmt.syntax().clone_for_update());
+ },
+ )
+}
+
+struct AssignmentsCollector<'a> {
+ sema: &'a hir::Semantics<'a, ide_db::RootDatabase>,
+ common_lhs: ast::Expr,
+ assignments: Vec<(ast::BinExpr, ast::Expr)>,
+}
+
+impl<'a> AssignmentsCollector<'a> {
+ fn collect_match(&mut self, match_expr: &ast::MatchExpr) -> Option<()> {
+ for arm in match_expr.match_arm_list()?.arms() {
+ match arm.expr()? {
+ ast::Expr::BlockExpr(block) => self.collect_block(&block)?,
+ ast::Expr::BinExpr(expr) => self.collect_expr(&expr)?,
+ _ => return None,
+ }
+ }
+
+ Some(())
+ }
+ fn collect_if(&mut self, if_expr: &ast::IfExpr) -> Option<()> {
+ let then_branch = if_expr.then_branch()?;
+ self.collect_block(&then_branch)?;
+
+ match if_expr.else_branch()? {
+ ast::ElseBranch::Block(block) => self.collect_block(&block),
+ ast::ElseBranch::IfExpr(expr) => {
+ cov_mark::hit!(test_pull_assignment_up_chained_if);
+ self.collect_if(&expr)
+ }
+ }
+ }
+ fn collect_block(&mut self, block: &ast::BlockExpr) -> Option<()> {
+ let last_expr = block.tail_expr().or_else(|| match block.statements().last()? {
+ ast::Stmt::ExprStmt(stmt) => stmt.expr(),
+ ast::Stmt::Item(_) | ast::Stmt::LetStmt(_) => None,
+ })?;
+
+ if let ast::Expr::BinExpr(expr) = last_expr {
+ return self.collect_expr(&expr);
+ }
+
+ None
+ }
+
+ fn collect_expr(&mut self, expr: &ast::BinExpr) -> Option<()> {
+ if expr.op_kind()? == (ast::BinaryOp::Assignment { op: None })
+ && is_equivalent(self.sema, &expr.lhs()?, &self.common_lhs)
+ {
+ self.assignments.push((expr.clone(), expr.rhs()?));
+ return Some(());
+ }
+ None
+ }
+}
+
+fn is_equivalent(
+ sema: &hir::Semantics<'_, ide_db::RootDatabase>,
+ expr0: &ast::Expr,
+ expr1: &ast::Expr,
+) -> bool {
+ match (expr0, expr1) {
+ (ast::Expr::FieldExpr(field_expr0), ast::Expr::FieldExpr(field_expr1)) => {
+ cov_mark::hit!(test_pull_assignment_up_field_assignment);
+ sema.resolve_field(field_expr0) == sema.resolve_field(field_expr1)
+ }
+ (ast::Expr::PathExpr(path0), ast::Expr::PathExpr(path1)) => {
+ let path0 = path0.path();
+ let path1 = path1.path();
+ if let (Some(path0), Some(path1)) = (path0, path1) {
+ sema.resolve_path(&path0) == sema.resolve_path(&path1)
+ } else {
+ false
+ }
+ }
+ (ast::Expr::PrefixExpr(prefix0), ast::Expr::PrefixExpr(prefix1))
+ if prefix0.op_kind() == Some(ast::UnaryOp::Deref)
+ && prefix1.op_kind() == Some(ast::UnaryOp::Deref) =>
+ {
+ cov_mark::hit!(test_pull_assignment_up_deref);
+ if let (Some(prefix0), Some(prefix1)) = (prefix0.expr(), prefix1.expr()) {
+ is_equivalent(sema, &prefix0, &prefix1)
+ } else {
+ false
+ }
+ }
+ _ => false,
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ #[test]
+ fn test_pull_assignment_up_if() {
+ check_assist(
+ pull_assignment_up,
+ r#"
+fn foo() {
+ let mut a = 1;
+
+ if true {
+ $0a = 2;
+ } else {
+ a = 3;
+ }
+}"#,
+ r#"
+fn foo() {
+ let mut a = 1;
+
+ a = if true {
+ 2
+ } else {
+ 3
+ };
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_pull_assignment_up_match() {
+ check_assist(
+ pull_assignment_up,
+ r#"
+fn foo() {
+ let mut a = 1;
+
+ match 1 {
+ 1 => {
+ $0a = 2;
+ },
+ 2 => {
+ a = 3;
+ },
+ 3 => {
+ a = 4;
+ }
+ }
+}"#,
+ r#"
+fn foo() {
+ let mut a = 1;
+
+ a = match 1 {
+ 1 => {
+ 2
+ },
+ 2 => {
+ 3
+ },
+ 3 => {
+ 4
+ }
+ };
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_pull_assignment_up_assignment_expressions() {
+ check_assist(
+ pull_assignment_up,
+ r#"
+fn foo() {
+ let mut a = 1;
+
+ match 1 {
+ 1 => { $0a = 2; },
+ 2 => a = 3,
+ 3 => {
+ a = 4
+ }
+ }
+}"#,
+ r#"
+fn foo() {
+ let mut a = 1;
+
+ a = match 1 {
+ 1 => { 2 },
+ 2 => 3,
+ 3 => {
+ 4
+ }
+ };
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_pull_assignment_up_not_last_not_applicable() {
+ check_assist_not_applicable(
+ pull_assignment_up,
+ r#"
+fn foo() {
+ let mut a = 1;
+
+ if true {
+ $0a = 2;
+ b = a;
+ } else {
+ a = 3;
+ }
+}"#,
+ )
+ }
+
+ #[test]
+ fn test_pull_assignment_up_chained_if() {
+ cov_mark::check!(test_pull_assignment_up_chained_if);
+ check_assist(
+ pull_assignment_up,
+ r#"
+fn foo() {
+ let mut a = 1;
+
+ if true {
+ $0a = 2;
+ } else if false {
+ a = 3;
+ } else {
+ a = 4;
+ }
+}"#,
+ r#"
+fn foo() {
+ let mut a = 1;
+
+ a = if true {
+ 2
+ } else if false {
+ 3
+ } else {
+ 4
+ };
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_pull_assignment_up_retains_stmts() {
+ check_assist(
+ pull_assignment_up,
+ r#"
+fn foo() {
+ let mut a = 1;
+
+ if true {
+ let b = 2;
+ $0a = 2;
+ } else {
+ let b = 3;
+ a = 3;
+ }
+}"#,
+ r#"
+fn foo() {
+ let mut a = 1;
+
+ a = if true {
+ let b = 2;
+ 2
+ } else {
+ let b = 3;
+ 3
+ };
+}"#,
+ )
+ }
+
+ #[test]
+ fn pull_assignment_up_let_stmt_not_applicable() {
+ check_assist_not_applicable(
+ pull_assignment_up,
+ r#"
+fn foo() {
+ let mut a = 1;
+
+ let b = if true {
+ $0a = 2
+ } else {
+ a = 3
+ };
+}"#,
+ )
+ }
+
+ #[test]
+ fn pull_assignment_up_if_missing_assigment_not_applicable() {
+ check_assist_not_applicable(
+ pull_assignment_up,
+ r#"
+fn foo() {
+ let mut a = 1;
+
+ if true {
+ $0a = 2;
+ } else {}
+}"#,
+ )
+ }
+
+ #[test]
+ fn pull_assignment_up_match_missing_assigment_not_applicable() {
+ check_assist_not_applicable(
+ pull_assignment_up,
+ r#"
+fn foo() {
+ let mut a = 1;
+
+ match 1 {
+ 1 => {
+ $0a = 2;
+ },
+ 2 => {
+ a = 3;
+ },
+ 3 => {},
+ }
+}"#,
+ )
+ }
+
+ #[test]
+ fn test_pull_assignment_up_field_assignment() {
+ cov_mark::check!(test_pull_assignment_up_field_assignment);
+ check_assist(
+ pull_assignment_up,
+ r#"
+struct A(usize);
+
+fn foo() {
+ let mut a = A(1);
+
+ if true {
+ $0a.0 = 2;
+ } else {
+ a.0 = 3;
+ }
+}"#,
+ r#"
+struct A(usize);
+
+fn foo() {
+ let mut a = A(1);
+
+ a.0 = if true {
+ 2
+ } else {
+ 3
+ };
+}"#,
+ )
+ }
+
+ #[test]
+ fn test_pull_assignment_up_deref() {
+ cov_mark::check!(test_pull_assignment_up_deref);
+ check_assist(
+ pull_assignment_up,
+ r#"
+fn foo() {
+ let mut a = 1;
+ let b = &mut a;
+
+ if true {
+ $0*b = 2;
+ } else {
+ *b = 3;
+ }
+}
+"#,
+ r#"
+fn foo() {
+ let mut a = 1;
+ let b = &mut a;
+
+ *b = if true {
+ 2
+ } else {
+ 3
+ };
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_cant_pull_non_assignments() {
+ cov_mark::check!(test_cant_pull_non_assignments);
+ check_assist_not_applicable(
+ pull_assignment_up,
+ r#"
+fn foo() {
+ let mut a = 1;
+ let b = &mut a;
+
+ if true {
+ $0*b + 2;
+ } else {
+ *b + 3;
+ }
+}
+"#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_method_call.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_method_call.rs
new file mode 100644
index 000000000..121f8b4a1
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_method_call.rs
@@ -0,0 +1,548 @@
+use hir::{db::HirDatabase, AsAssocItem, AssocItem, AssocItemContainer, ItemInNs, ModuleDef};
+use ide_db::assists::{AssistId, AssistKind};
+use syntax::{ast, AstNode};
+
+use crate::{
+ assist_context::{AssistContext, Assists},
+ handlers::qualify_path::QualifyCandidate,
+};
+
+// Assist: qualify_method_call
+//
+// Replaces the method call with a qualified function call.
+//
+// ```
+// struct Foo;
+// impl Foo {
+// fn foo(&self) {}
+// }
+// fn main() {
+// let foo = Foo;
+// foo.fo$0o();
+// }
+// ```
+// ->
+// ```
+// struct Foo;
+// impl Foo {
+// fn foo(&self) {}
+// }
+// fn main() {
+// let foo = Foo;
+// Foo::foo(&foo);
+// }
+// ```
+pub(crate) fn qualify_method_call(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let name: ast::NameRef = ctx.find_node_at_offset()?;
+ let call = name.syntax().parent().and_then(ast::MethodCallExpr::cast)?;
+
+ let ident = name.ident_token()?;
+
+ let range = call.syntax().text_range();
+ let resolved_call = ctx.sema.resolve_method_call(&call)?;
+
+ let current_module = ctx.sema.scope(call.syntax())?.module();
+ let target_module_def = ModuleDef::from(resolved_call);
+ let item_in_ns = ItemInNs::from(target_module_def);
+ let receiver_path = current_module
+ .find_use_path(ctx.sema.db, item_for_path_search(ctx.sema.db, item_in_ns)?)?;
+
+ let qualify_candidate = QualifyCandidate::ImplMethod(ctx.sema.db, call, resolved_call);
+
+ acc.add(
+ AssistId("qualify_method_call", AssistKind::RefactorInline),
+ format!("Qualify `{}` method call", ident.text()),
+ range,
+ |builder| {
+ qualify_candidate.qualify(
+ |replace_with: String| builder.replace(range, replace_with),
+ &receiver_path,
+ item_in_ns,
+ )
+ },
+ );
+ Some(())
+}
+
+fn item_for_path_search(db: &dyn HirDatabase, item: ItemInNs) -> Option<ItemInNs> {
+ Some(match item {
+ ItemInNs::Types(_) | ItemInNs::Values(_) => match item_as_assoc(db, item) {
+ Some(assoc_item) => match assoc_item.container(db) {
+ AssocItemContainer::Trait(trait_) => ItemInNs::from(ModuleDef::from(trait_)),
+ AssocItemContainer::Impl(impl_) => match impl_.trait_(db) {
+ None => ItemInNs::from(ModuleDef::from(impl_.self_ty(db).as_adt()?)),
+ Some(trait_) => ItemInNs::from(ModuleDef::from(trait_)),
+ },
+ },
+ None => item,
+ },
+ ItemInNs::Macros(_) => item,
+ })
+}
+
+fn item_as_assoc(db: &dyn HirDatabase, item: ItemInNs) -> Option<AssocItem> {
+ item.as_module_def().and_then(|module_def| module_def.as_assoc_item(db))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ #[test]
+ fn struct_method() {
+ check_assist(
+ qualify_method_call,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+
+fn main() {
+ let foo = Foo {};
+ foo.fo$0o()
+}
+"#,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+
+fn main() {
+ let foo = Foo {};
+ Foo::foo(&foo)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn struct_method_multi_params() {
+ check_assist(
+ qualify_method_call,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(&self, p1: i32, p2: u32) {}
+}
+
+fn main() {
+ let foo = Foo {};
+ foo.fo$0o(9, 9u)
+}
+"#,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(&self, p1: i32, p2: u32) {}
+}
+
+fn main() {
+ let foo = Foo {};
+ Foo::foo(&foo, 9, 9u)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn struct_method_consume() {
+ check_assist(
+ qualify_method_call,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(self, p1: i32, p2: u32) {}
+}
+
+fn main() {
+ let foo = Foo {};
+ foo.fo$0o(9, 9u)
+}
+"#,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(self, p1: i32, p2: u32) {}
+}
+
+fn main() {
+ let foo = Foo {};
+ Foo::foo(foo, 9, 9u)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn struct_method_exclusive() {
+ check_assist(
+ qualify_method_call,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(&mut self, p1: i32, p2: u32) {}
+}
+
+fn main() {
+ let foo = Foo {};
+ foo.fo$0o(9, 9u)
+}
+"#,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(&mut self, p1: i32, p2: u32) {}
+}
+
+fn main() {
+ let foo = Foo {};
+ Foo::foo(&mut foo, 9, 9u)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn struct_method_cross_crate() {
+ check_assist(
+ qualify_method_call,
+ r#"
+//- /main.rs crate:main deps:dep
+fn main() {
+ let foo = dep::test_mod::Foo {};
+ foo.fo$0o(9, 9u)
+}
+//- /dep.rs crate:dep
+pub mod test_mod {
+ pub struct Foo;
+ impl Foo {
+ pub fn foo(&mut self, p1: i32, p2: u32) {}
+ }
+}
+"#,
+ r#"
+fn main() {
+ let foo = dep::test_mod::Foo {};
+ dep::test_mod::Foo::foo(&mut foo, 9, 9u)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn struct_method_generic() {
+ check_assist(
+ qualify_method_call,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo<T>(&self) {}
+}
+
+fn main() {
+ let foo = Foo {};
+ foo.fo$0o::<()>()
+}
+"#,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo<T>(&self) {}
+}
+
+fn main() {
+ let foo = Foo {};
+ Foo::foo::<()>(&foo)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn trait_method() {
+ check_assist(
+ qualify_method_call,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self) {}
+ }
+}
+
+use test_mod::*;
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ test_struct.test_meth$0od()
+}
+"#,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self) {}
+ }
+}
+
+use test_mod::*;
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ TestTrait::test_method(&test_struct)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn trait_method_multi_params() {
+ check_assist(
+ qualify_method_call,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self, p1: i32, p2: u32);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self, p1: i32, p2: u32) {}
+ }
+}
+
+use test_mod::*;
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ test_struct.test_meth$0od(12, 32u)
+}
+"#,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self, p1: i32, p2: u32);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self, p1: i32, p2: u32) {}
+ }
+}
+
+use test_mod::*;
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ TestTrait::test_method(&test_struct, 12, 32u)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn trait_method_consume() {
+ check_assist(
+ qualify_method_call,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method(self, p1: i32, p2: u32);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(self, p1: i32, p2: u32) {}
+ }
+}
+
+use test_mod::*;
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ test_struct.test_meth$0od(12, 32u)
+}
+"#,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method(self, p1: i32, p2: u32);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(self, p1: i32, p2: u32) {}
+ }
+}
+
+use test_mod::*;
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ TestTrait::test_method(test_struct, 12, 32u)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn trait_method_exclusive() {
+ check_assist(
+ qualify_method_call,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&mut self, p1: i32, p2: u32);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&mut self, p1: i32, p2: u32);
+ }
+}
+
+use test_mod::*;
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ test_struct.test_meth$0od(12, 32u)
+}
+"#,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&mut self, p1: i32, p2: u32);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&mut self, p1: i32, p2: u32);
+ }
+}
+
+use test_mod::*;
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ TestTrait::test_method(&mut test_struct, 12, 32u)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn trait_method_cross_crate() {
+ check_assist(
+ qualify_method_call,
+ r#"
+//- /main.rs crate:main deps:dep
+fn main() {
+ let foo = dep::test_mod::Foo {};
+ foo.fo$0o(9, 9u)
+}
+//- /dep.rs crate:dep
+pub mod test_mod {
+ pub struct Foo;
+ impl Foo {
+ pub fn foo(&mut self, p1: i32, p2: u32) {}
+ }
+}
+"#,
+ r#"
+fn main() {
+ let foo = dep::test_mod::Foo {};
+ dep::test_mod::Foo::foo(&mut foo, 9, 9u)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn trait_method_generic() {
+ check_assist(
+ qualify_method_call,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method<T>(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method<T>(&self) {}
+ }
+}
+
+use test_mod::*;
+
+fn main() {
+ let test_struct = TestStruct {};
+ test_struct.test_meth$0od::<()>()
+}
+"#,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method<T>(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method<T>(&self) {}
+ }
+}
+
+use test_mod::*;
+
+fn main() {
+ let test_struct = TestStruct {};
+ TestTrait::test_method::<()>(&test_struct)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn struct_method_over_stuct_instance() {
+ check_assist_not_applicable(
+ qualify_method_call,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+
+fn main() {
+ let foo = Foo {};
+ f$0oo.foo()
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn trait_method_over_stuct_instance() {
+ check_assist_not_applicable(
+ qualify_method_call,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self) {}
+ }
+}
+
+use test_mod::*;
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ tes$0t_struct.test_method()
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_path.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_path.rs
new file mode 100644
index 000000000..0c2e9da38
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/qualify_path.rs
@@ -0,0 +1,1297 @@
+use std::iter;
+
+use hir::AsAssocItem;
+use ide_db::RootDatabase;
+use ide_db::{
+ helpers::mod_path_to_ast,
+ imports::import_assets::{ImportCandidate, LocatedImport},
+};
+use syntax::{
+ ast,
+ ast::{make, HasArgList},
+ AstNode, NodeOrToken,
+};
+
+use crate::{
+ assist_context::{AssistContext, Assists},
+ handlers::auto_import::find_importable_node,
+ AssistId, AssistKind, GroupLabel,
+};
+
+// Assist: qualify_path
+//
+// If the name is unresolved, provides all possible qualified paths for it.
+//
+// ```
+// fn main() {
+// let map = HashMap$0::new();
+// }
+// # pub mod std { pub mod collections { pub struct HashMap { } } }
+// ```
+// ->
+// ```
+// fn main() {
+// let map = std::collections::HashMap::new();
+// }
+// # pub mod std { pub mod collections { pub struct HashMap { } } }
+// ```
+pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let (import_assets, syntax_under_caret) = find_importable_node(ctx)?;
+ let mut proposed_imports = import_assets.search_for_relative_paths(&ctx.sema);
+ if proposed_imports.is_empty() {
+ return None;
+ }
+
+ let range = match &syntax_under_caret {
+ NodeOrToken::Node(node) => ctx.sema.original_range(node).range,
+ NodeOrToken::Token(token) => token.text_range(),
+ };
+ let candidate = import_assets.import_candidate();
+ let qualify_candidate = match syntax_under_caret {
+ NodeOrToken::Node(syntax_under_caret) => match candidate {
+ ImportCandidate::Path(candidate) if candidate.qualifier.is_some() => {
+ cov_mark::hit!(qualify_path_qualifier_start);
+ let path = ast::Path::cast(syntax_under_caret)?;
+ let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?);
+ QualifyCandidate::QualifierStart(segment, prev_segment.generic_arg_list())
+ }
+ ImportCandidate::Path(_) => {
+ cov_mark::hit!(qualify_path_unqualified_name);
+ let path = ast::Path::cast(syntax_under_caret)?;
+ let generics = path.segment()?.generic_arg_list();
+ QualifyCandidate::UnqualifiedName(generics)
+ }
+ ImportCandidate::TraitAssocItem(_) => {
+ cov_mark::hit!(qualify_path_trait_assoc_item);
+ let path = ast::Path::cast(syntax_under_caret)?;
+ let (qualifier, segment) = (path.qualifier()?, path.segment()?);
+ QualifyCandidate::TraitAssocItem(qualifier, segment)
+ }
+ ImportCandidate::TraitMethod(_) => {
+ cov_mark::hit!(qualify_path_trait_method);
+ let mcall_expr = ast::MethodCallExpr::cast(syntax_under_caret)?;
+ QualifyCandidate::TraitMethod(ctx.sema.db, mcall_expr)
+ }
+ },
+ // derive attribute path
+ NodeOrToken::Token(_) => QualifyCandidate::UnqualifiedName(None),
+ };
+
+ // we aren't interested in different namespaces
+ proposed_imports.dedup_by(|a, b| a.import_path == b.import_path);
+
+ let group_label = group_label(candidate);
+ for import in proposed_imports {
+ acc.add_group(
+ &group_label,
+ AssistId("qualify_path", AssistKind::QuickFix),
+ label(candidate, &import),
+ range,
+ |builder| {
+ qualify_candidate.qualify(
+ |replace_with: String| builder.replace(range, replace_with),
+ &import.import_path,
+ import.item_to_import,
+ )
+ },
+ );
+ }
+ Some(())
+}
+pub(crate) enum QualifyCandidate<'db> {
+ QualifierStart(ast::PathSegment, Option<ast::GenericArgList>),
+ UnqualifiedName(Option<ast::GenericArgList>),
+ TraitAssocItem(ast::Path, ast::PathSegment),
+ TraitMethod(&'db RootDatabase, ast::MethodCallExpr),
+ ImplMethod(&'db RootDatabase, ast::MethodCallExpr, hir::Function),
+}
+
+impl QualifyCandidate<'_> {
+ pub(crate) fn qualify(
+ &self,
+ mut replacer: impl FnMut(String),
+ import: &hir::ModPath,
+ item: hir::ItemInNs,
+ ) {
+ let import = mod_path_to_ast(import);
+ match self {
+ QualifyCandidate::QualifierStart(segment, generics) => {
+ let generics = generics.as_ref().map_or_else(String::new, ToString::to_string);
+ replacer(format!("{}{}::{}", import, generics, segment));
+ }
+ QualifyCandidate::UnqualifiedName(generics) => {
+ let generics = generics.as_ref().map_or_else(String::new, ToString::to_string);
+ replacer(format!("{}{}", import, generics));
+ }
+ QualifyCandidate::TraitAssocItem(qualifier, segment) => {
+ replacer(format!("<{} as {}>::{}", qualifier, import, segment));
+ }
+ QualifyCandidate::TraitMethod(db, mcall_expr) => {
+ Self::qualify_trait_method(db, mcall_expr, replacer, import, item);
+ }
+ QualifyCandidate::ImplMethod(db, mcall_expr, hir_fn) => {
+ Self::qualify_fn_call(db, mcall_expr, replacer, import, hir_fn);
+ }
+ }
+ }
+
+ fn qualify_fn_call(
+ db: &RootDatabase,
+ mcall_expr: &ast::MethodCallExpr,
+ mut replacer: impl FnMut(String),
+ import: ast::Path,
+ hir_fn: &hir::Function,
+ ) -> Option<()> {
+ let receiver = mcall_expr.receiver()?;
+ let method_name = mcall_expr.name_ref()?;
+ let generics =
+ mcall_expr.generic_arg_list().as_ref().map_or_else(String::new, ToString::to_string);
+ let arg_list = mcall_expr.arg_list().map(|arg_list| arg_list.args());
+
+ if let Some(self_access) = hir_fn.self_param(db).map(|sp| sp.access(db)) {
+ let receiver = match self_access {
+ hir::Access::Shared => make::expr_ref(receiver, false),
+ hir::Access::Exclusive => make::expr_ref(receiver, true),
+ hir::Access::Owned => receiver,
+ };
+ replacer(format!(
+ "{}::{}{}{}",
+ import,
+ method_name,
+ generics,
+ match arg_list {
+ Some(args) => make::arg_list(iter::once(receiver).chain(args)),
+ None => make::arg_list(iter::once(receiver)),
+ }
+ ));
+ }
+ Some(())
+ }
+
+ fn qualify_trait_method(
+ db: &RootDatabase,
+ mcall_expr: &ast::MethodCallExpr,
+ replacer: impl FnMut(String),
+ import: ast::Path,
+ item: hir::ItemInNs,
+ ) -> Option<()> {
+ let trait_method_name = mcall_expr.name_ref()?;
+ let trait_ = item_as_trait(db, item)?;
+ let method = find_trait_method(db, trait_, &trait_method_name)?;
+ Self::qualify_fn_call(db, mcall_expr, replacer, import, &method)
+ }
+}
+
+fn find_trait_method(
+ db: &RootDatabase,
+ trait_: hir::Trait,
+ trait_method_name: &ast::NameRef,
+) -> Option<hir::Function> {
+ if let Some(hir::AssocItem::Function(method)) =
+ trait_.items(db).into_iter().find(|item: &hir::AssocItem| {
+ item.name(db)
+ .map(|name| name.to_string() == trait_method_name.to_string())
+ .unwrap_or(false)
+ })
+ {
+ Some(method)
+ } else {
+ None
+ }
+}
+
+fn item_as_trait(db: &RootDatabase, item: hir::ItemInNs) -> Option<hir::Trait> {
+ let item_module_def = item.as_module_def()?;
+
+ match item_module_def {
+ hir::ModuleDef::Trait(trait_) => Some(trait_),
+ _ => item_module_def.as_assoc_item(db)?.containing_trait(db),
+ }
+}
+
+fn group_label(candidate: &ImportCandidate) -> GroupLabel {
+ let name = match candidate {
+ ImportCandidate::Path(it) => &it.name,
+ ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => {
+ &it.assoc_item_name
+ }
+ }
+ .text();
+ GroupLabel(format!("Qualify {}", name))
+}
+
+fn label(candidate: &ImportCandidate, import: &LocatedImport) -> String {
+ match candidate {
+ ImportCandidate::Path(candidate) if candidate.qualifier.is_none() => {
+ format!("Qualify as `{}`", import.import_path)
+ }
+ _ => format!("Qualify with `{}`", import.import_path),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
+
+ use super::*;
+
+ #[test]
+ fn applicable_when_found_an_import_partial() {
+ cov_mark::check!(qualify_path_unqualified_name);
+ check_assist(
+ qualify_path,
+ r#"
+mod std {
+ pub mod fmt {
+ pub struct Formatter;
+ }
+}
+
+use std::fmt;
+
+$0Formatter
+"#,
+ r#"
+mod std {
+ pub mod fmt {
+ pub struct Formatter;
+ }
+}
+
+use std::fmt;
+
+fmt::Formatter
+"#,
+ );
+ }
+
+ #[test]
+ fn applicable_when_found_an_import() {
+ check_assist(
+ qualify_path,
+ r#"
+$0PubStruct
+
+pub mod PubMod {
+ pub struct PubStruct;
+}
+"#,
+ r#"
+PubMod::PubStruct
+
+pub mod PubMod {
+ pub struct PubStruct;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn applicable_in_macros() {
+ check_assist(
+ qualify_path,
+ r#"
+macro_rules! foo {
+ ($i:ident) => { fn foo(a: $i) {} }
+}
+foo!(Pub$0Struct);
+
+pub mod PubMod {
+ pub struct PubStruct;
+}
+"#,
+ r#"
+macro_rules! foo {
+ ($i:ident) => { fn foo(a: $i) {} }
+}
+foo!(PubMod::PubStruct);
+
+pub mod PubMod {
+ pub struct PubStruct;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn applicable_when_found_multiple_imports() {
+ check_assist(
+ qualify_path,
+ r#"
+PubSt$0ruct
+
+pub mod PubMod1 {
+ pub struct PubStruct;
+}
+pub mod PubMod2 {
+ pub struct PubStruct;
+}
+pub mod PubMod3 {
+ pub struct PubStruct;
+}
+"#,
+ r#"
+PubMod3::PubStruct
+
+pub mod PubMod1 {
+ pub struct PubStruct;
+}
+pub mod PubMod2 {
+ pub struct PubStruct;
+}
+pub mod PubMod3 {
+ pub struct PubStruct;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_for_already_imported_types() {
+ check_assist_not_applicable(
+ qualify_path,
+ r#"
+use PubMod::PubStruct;
+
+PubStruct$0
+
+pub mod PubMod {
+ pub struct PubStruct;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_for_types_with_private_paths() {
+ check_assist_not_applicable(
+ qualify_path,
+ r#"
+PrivateStruct$0
+
+pub mod PubMod {
+ struct PrivateStruct;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_when_no_imports_found() {
+ check_assist_not_applicable(qualify_path, r#"PubStruct$0"#);
+ }
+
+ #[test]
+ fn qualify_function() {
+ check_assist(
+ qualify_path,
+ r#"
+test_function$0
+
+pub mod PubMod {
+ pub fn test_function() {};
+}
+"#,
+ r#"
+PubMod::test_function
+
+pub mod PubMod {
+ pub fn test_function() {};
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn qualify_macro() {
+ check_assist(
+ qualify_path,
+ r#"
+//- /lib.rs crate:crate_with_macro
+#[macro_export]
+macro_rules! foo {
+ () => ()
+}
+
+//- /main.rs crate:main deps:crate_with_macro
+fn main() {
+ foo$0
+}
+"#,
+ r#"
+fn main() {
+ crate_with_macro::foo
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn qualify_path_target() {
+ check_assist_target(
+ qualify_path,
+ r#"
+struct AssistInfo {
+ group_label: Option<$0GroupLabel>,
+}
+
+mod m { pub struct GroupLabel; }
+"#,
+ "GroupLabel",
+ )
+ }
+
+ #[test]
+ fn not_applicable_when_path_start_is_imported() {
+ check_assist_not_applicable(
+ qualify_path,
+ r#"
+pub mod mod1 {
+ pub mod mod2 {
+ pub mod mod3 {
+ pub struct TestStruct;
+ }
+ }
+}
+
+use mod1::mod2;
+fn main() {
+ mod2::mod3::TestStruct$0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_for_imported_function() {
+ check_assist_not_applicable(
+ qualify_path,
+ r#"
+pub mod test_mod {
+ pub fn test_function() {}
+}
+
+use test_mod::test_function;
+fn main() {
+ test_function$0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn associated_struct_function() {
+ check_assist(
+ qualify_path,
+ r#"
+mod test_mod {
+ pub struct TestStruct {}
+ impl TestStruct {
+ pub fn test_function() {}
+ }
+}
+
+fn main() {
+ TestStruct::test_function$0
+}
+"#,
+ r#"
+mod test_mod {
+ pub struct TestStruct {}
+ impl TestStruct {
+ pub fn test_function() {}
+ }
+}
+
+fn main() {
+ test_mod::TestStruct::test_function
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn associated_struct_const() {
+ cov_mark::check!(qualify_path_qualifier_start);
+ check_assist(
+ qualify_path,
+ r#"
+mod test_mod {
+ pub struct TestStruct {}
+ impl TestStruct {
+ const TEST_CONST: u8 = 42;
+ }
+}
+
+fn main() {
+ TestStruct::TEST_CONST$0
+}
+"#,
+ r#"
+mod test_mod {
+ pub struct TestStruct {}
+ impl TestStruct {
+ const TEST_CONST: u8 = 42;
+ }
+}
+
+fn main() {
+ test_mod::TestStruct::TEST_CONST
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn associated_struct_const_unqualified() {
+ // FIXME: non-trait assoc items completion is unsupported yet, see FIXME in the import_assets.rs for more details
+ check_assist_not_applicable(
+ qualify_path,
+ r#"
+mod test_mod {
+ pub struct TestStruct {}
+ impl TestStruct {
+ const TEST_CONST: u8 = 42;
+ }
+}
+
+fn main() {
+ TEST_CONST$0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn associated_trait_function() {
+ check_assist(
+ qualify_path,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_function();
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_function() {}
+ }
+}
+
+fn main() {
+ test_mod::TestStruct::test_function$0
+}
+"#,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_function();
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_function() {}
+ }
+}
+
+fn main() {
+ <test_mod::TestStruct as test_mod::TestTrait>::test_function
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_for_imported_trait_for_function() {
+ check_assist_not_applicable(
+ qualify_path,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_function();
+ }
+ pub trait TestTrait2 {
+ fn test_function();
+ }
+ pub enum TestEnum {
+ One,
+ Two,
+ }
+ impl TestTrait2 for TestEnum {
+ fn test_function() {}
+ }
+ impl TestTrait for TestEnum {
+ fn test_function() {}
+ }
+}
+
+use test_mod::TestTrait2;
+fn main() {
+ test_mod::TestEnum::test_function$0;
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn associated_trait_const() {
+ cov_mark::check!(qualify_path_trait_assoc_item);
+ check_assist(
+ qualify_path,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ const TEST_CONST: u8;
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const TEST_CONST: u8 = 42;
+ }
+}
+
+fn main() {
+ test_mod::TestStruct::TEST_CONST$0
+}
+"#,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ const TEST_CONST: u8;
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const TEST_CONST: u8 = 42;
+ }
+}
+
+fn main() {
+ <test_mod::TestStruct as test_mod::TestTrait>::TEST_CONST
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_for_imported_trait_for_const() {
+ check_assist_not_applicable(
+ qualify_path,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ const TEST_CONST: u8;
+ }
+ pub trait TestTrait2 {
+ const TEST_CONST: f64;
+ }
+ pub enum TestEnum {
+ One,
+ Two,
+ }
+ impl TestTrait2 for TestEnum {
+ const TEST_CONST: f64 = 42.0;
+ }
+ impl TestTrait for TestEnum {
+ const TEST_CONST: u8 = 42;
+ }
+}
+
+use test_mod::TestTrait2;
+fn main() {
+ test_mod::TestEnum::TEST_CONST$0;
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn trait_method() {
+ cov_mark::check!(qualify_path_trait_method);
+ check_assist(
+ qualify_path,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self) {}
+ }
+}
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ test_struct.test_meth$0od()
+}
+"#,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self) {}
+ }
+}
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ test_mod::TestTrait::test_method(&test_struct)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn trait_method_multi_params() {
+ check_assist(
+ qualify_path,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self, test: i32);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self, test: i32) {}
+ }
+}
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ test_struct.test_meth$0od(42)
+}
+"#,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self, test: i32);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self, test: i32) {}
+ }
+}
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ test_mod::TestTrait::test_method(&test_struct, 42)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn trait_method_consume() {
+ check_assist(
+ qualify_path,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method(self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(self) {}
+ }
+}
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ test_struct.test_meth$0od()
+}
+"#,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method(self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(self) {}
+ }
+}
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ test_mod::TestTrait::test_method(test_struct)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn trait_method_cross_crate() {
+ check_assist(
+ qualify_path,
+ r#"
+//- /main.rs crate:main deps:dep
+fn main() {
+ let test_struct = dep::test_mod::TestStruct {};
+ test_struct.test_meth$0od()
+}
+//- /dep.rs crate:dep
+pub mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self) {}
+ }
+}
+"#,
+ r#"
+fn main() {
+ let test_struct = dep::test_mod::TestStruct {};
+ dep::test_mod::TestTrait::test_method(&test_struct)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn assoc_fn_cross_crate() {
+ check_assist(
+ qualify_path,
+ r#"
+//- /main.rs crate:main deps:dep
+fn main() {
+ dep::test_mod::TestStruct::test_func$0tion
+}
+//- /dep.rs crate:dep
+pub mod test_mod {
+ pub trait TestTrait {
+ fn test_function();
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_function() {}
+ }
+}
+"#,
+ r#"
+fn main() {
+ <dep::test_mod::TestStruct as dep::test_mod::TestTrait>::test_function
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn assoc_const_cross_crate() {
+ check_assist(
+ qualify_path,
+ r#"
+//- /main.rs crate:main deps:dep
+fn main() {
+ dep::test_mod::TestStruct::CONST$0
+}
+//- /dep.rs crate:dep
+pub mod test_mod {
+ pub trait TestTrait {
+ const CONST: bool;
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const CONST: bool = true;
+ }
+}
+"#,
+ r#"
+fn main() {
+ <dep::test_mod::TestStruct as dep::test_mod::TestTrait>::CONST
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn assoc_fn_as_method_cross_crate() {
+ check_assist_not_applicable(
+ qualify_path,
+ r#"
+//- /main.rs crate:main deps:dep
+fn main() {
+ let test_struct = dep::test_mod::TestStruct {};
+ test_struct.test_func$0tion()
+}
+//- /dep.rs crate:dep
+pub mod test_mod {
+ pub trait TestTrait {
+ fn test_function();
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_function() {}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn private_trait_cross_crate() {
+ check_assist_not_applicable(
+ qualify_path,
+ r#"
+//- /main.rs crate:main deps:dep
+fn main() {
+ let test_struct = dep::test_mod::TestStruct {};
+ test_struct.test_meth$0od()
+}
+//- /dep.rs crate:dep
+pub mod test_mod {
+ trait TestTrait {
+ fn test_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self) {}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_for_imported_trait_for_method() {
+ check_assist_not_applicable(
+ qualify_path,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self);
+ }
+ pub trait TestTrait2 {
+ fn test_method(&self);
+ }
+ pub enum TestEnum {
+ One,
+ Two,
+ }
+ impl TestTrait2 for TestEnum {
+ fn test_method(&self) {}
+ }
+ impl TestTrait for TestEnum {
+ fn test_method(&self) {}
+ }
+}
+
+use test_mod::TestTrait2;
+fn main() {
+ let one = test_mod::TestEnum::One;
+ one.test$0_method();
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn dep_import() {
+ check_assist(
+ qualify_path,
+ r"
+//- /lib.rs crate:dep
+pub struct Struct;
+
+//- /main.rs crate:main deps:dep
+fn main() {
+ Struct$0
+}
+",
+ r"
+fn main() {
+ dep::Struct
+}
+",
+ );
+ }
+
+ #[test]
+ fn whole_segment() {
+ // Tests that only imports whose last segment matches the identifier get suggested.
+ check_assist(
+ qualify_path,
+ r"
+//- /lib.rs crate:dep
+pub mod fmt {
+ pub trait Display {}
+}
+
+pub fn panic_fmt() {}
+
+//- /main.rs crate:main deps:dep
+struct S;
+
+impl f$0mt::Display for S {}
+",
+ r"
+struct S;
+
+impl dep::fmt::Display for S {}
+",
+ );
+ }
+
+ #[test]
+ fn macro_generated() {
+ // Tests that macro-generated items are suggested from external crates.
+ check_assist(
+ qualify_path,
+ r"
+//- /lib.rs crate:dep
+macro_rules! mac {
+ () => {
+ pub struct Cheese;
+ };
+}
+
+mac!();
+
+//- /main.rs crate:main deps:dep
+fn main() {
+ Cheese$0;
+}
+",
+ r"
+fn main() {
+ dep::Cheese;
+}
+",
+ );
+ }
+
+ #[test]
+ fn casing() {
+ // Tests that differently cased names don't interfere and we only suggest the matching one.
+ check_assist(
+ qualify_path,
+ r"
+//- /lib.rs crate:dep
+pub struct FMT;
+pub struct fmt;
+
+//- /main.rs crate:main deps:dep
+fn main() {
+ FMT$0;
+}
+",
+ r"
+fn main() {
+ dep::FMT;
+}
+",
+ );
+ }
+
+ #[test]
+ fn keep_generic_annotations() {
+ check_assist(
+ qualify_path,
+ r"
+//- /lib.rs crate:dep
+pub mod generic { pub struct Thing<'a, T>(&'a T); }
+
+//- /main.rs crate:main deps:dep
+fn foo() -> Thin$0g<'static, ()> {}
+
+fn main() {}
+",
+ r"
+fn foo() -> dep::generic::Thing<'static, ()> {}
+
+fn main() {}
+",
+ );
+ }
+
+ #[test]
+ fn keep_generic_annotations_leading_colon() {
+ check_assist(
+ qualify_path,
+ r#"
+//- /lib.rs crate:dep
+pub mod generic { pub struct Thing<'a, T>(&'a T); }
+
+//- /main.rs crate:main deps:dep
+fn foo() -> Thin$0g::<'static, ()> {}
+
+fn main() {}
+"#,
+ r"
+fn foo() -> dep::generic::Thing::<'static, ()> {}
+
+fn main() {}
+",
+ );
+ }
+
+ #[test]
+ fn associated_struct_const_generic() {
+ check_assist(
+ qualify_path,
+ r#"
+mod test_mod {
+ pub struct TestStruct<T> {}
+ impl<T> TestStruct<T> {
+ const TEST_CONST: u8 = 42;
+ }
+}
+
+fn main() {
+ TestStruct::<()>::TEST_CONST$0
+}
+"#,
+ r#"
+mod test_mod {
+ pub struct TestStruct<T> {}
+ impl<T> TestStruct<T> {
+ const TEST_CONST: u8 = 42;
+ }
+}
+
+fn main() {
+ test_mod::TestStruct::<()>::TEST_CONST
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn associated_trait_const_generic() {
+ check_assist(
+ qualify_path,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ const TEST_CONST: u8;
+ }
+ pub struct TestStruct<T> {}
+ impl<T> TestTrait for TestStruct<T> {
+ const TEST_CONST: u8 = 42;
+ }
+}
+
+fn main() {
+ test_mod::TestStruct::<()>::TEST_CONST$0
+}
+"#,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ const TEST_CONST: u8;
+ }
+ pub struct TestStruct<T> {}
+ impl<T> TestTrait for TestStruct<T> {
+ const TEST_CONST: u8 = 42;
+ }
+}
+
+fn main() {
+ <test_mod::TestStruct::<()> as test_mod::TestTrait>::TEST_CONST
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn trait_method_generic() {
+ check_assist(
+ qualify_path,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method<T>(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method<T>(&self) {}
+ }
+}
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ test_struct.test_meth$0od::<()>()
+}
+"#,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method<T>(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method<T>(&self) {}
+ }
+}
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ test_mod::TestTrait::test_method::<()>(&test_struct)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn works_in_derives() {
+ check_assist(
+ qualify_path,
+ r#"
+//- minicore:derive
+mod foo {
+ #[rustc_builtin_macro]
+ pub macro Copy {}
+}
+#[derive(Copy$0)]
+struct Foo;
+"#,
+ r#"
+mod foo {
+ #[rustc_builtin_macro]
+ pub macro Copy {}
+}
+#[derive(foo::Copy)]
+struct Foo;
+"#,
+ );
+ }
+
+ #[test]
+ fn works_in_use_start() {
+ check_assist(
+ qualify_path,
+ r#"
+mod bar {
+ pub mod foo {
+ pub struct Foo;
+ }
+}
+use foo$0::Foo;
+"#,
+ r#"
+mod bar {
+ pub mod foo {
+ pub struct Foo;
+ }
+}
+use bar::foo::Foo;
+"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_in_non_start_use() {
+ check_assist_not_applicable(
+ qualify_path,
+ r"
+mod bar {
+ pub mod foo {
+ pub struct Foo;
+ }
+}
+use foo::Foo$0;
+",
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/raw_string.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/raw_string.rs
new file mode 100644
index 000000000..dbe8cb7bf
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/raw_string.rs
@@ -0,0 +1,509 @@
+use std::borrow::Cow;
+
+use syntax::{ast, ast::IsString, AstToken, TextRange, TextSize};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: make_raw_string
+//
+// Adds `r#` to a plain string literal.
+//
+// ```
+// fn main() {
+// "Hello,$0 World!";
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// r#"Hello, World!"#;
+// }
+// ```
+pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let token = ctx.find_token_at_offset::<ast::String>()?;
+ if token.is_raw() {
+ return None;
+ }
+ let value = token.value()?;
+ let target = token.syntax().text_range();
+ acc.add(
+ AssistId("make_raw_string", AssistKind::RefactorRewrite),
+ "Rewrite as raw string",
+ target,
+ |edit| {
+ let hashes = "#".repeat(required_hashes(&value).max(1));
+ if matches!(value, Cow::Borrowed(_)) {
+ // Avoid replacing the whole string to better position the cursor.
+ edit.insert(token.syntax().text_range().start(), format!("r{}", hashes));
+ edit.insert(token.syntax().text_range().end(), hashes);
+ } else {
+ edit.replace(
+ token.syntax().text_range(),
+ format!("r{}\"{}\"{}", hashes, value, hashes),
+ );
+ }
+ },
+ )
+}
+
+// Assist: make_usual_string
+//
+// Turns a raw string into a plain string.
+//
+// ```
+// fn main() {
+// r#"Hello,$0 "World!""#;
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// "Hello, \"World!\"";
+// }
+// ```
+pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let token = ctx.find_token_at_offset::<ast::String>()?;
+ if !token.is_raw() {
+ return None;
+ }
+ let value = token.value()?;
+ let target = token.syntax().text_range();
+ acc.add(
+ AssistId("make_usual_string", AssistKind::RefactorRewrite),
+ "Rewrite as regular string",
+ target,
+ |edit| {
+ // parse inside string to escape `"`
+ let escaped = value.escape_default().to_string();
+ if let Some(offsets) = token.quote_offsets() {
+ if token.text()[offsets.contents - token.syntax().text_range().start()] == escaped {
+ edit.replace(offsets.quotes.0, "\"");
+ edit.replace(offsets.quotes.1, "\"");
+ return;
+ }
+ }
+
+ edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped));
+ },
+ )
+}
+
+// Assist: add_hash
+//
+// Adds a hash to a raw string literal.
+//
+// ```
+// fn main() {
+// r#"Hello,$0 World!"#;
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// r##"Hello, World!"##;
+// }
+// ```
+pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let token = ctx.find_token_at_offset::<ast::String>()?;
+ if !token.is_raw() {
+ return None;
+ }
+ let text_range = token.syntax().text_range();
+ let target = text_range;
+ acc.add(AssistId("add_hash", AssistKind::Refactor), "Add #", target, |edit| {
+ edit.insert(text_range.start() + TextSize::of('r'), "#");
+ edit.insert(text_range.end(), "#");
+ })
+}
+
+// Assist: remove_hash
+//
+// Removes a hash from a raw string literal.
+//
+// ```
+// fn main() {
+// r#"Hello,$0 World!"#;
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// r"Hello, World!";
+// }
+// ```
+pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let token = ctx.find_token_at_offset::<ast::String>()?;
+ if !token.is_raw() {
+ return None;
+ }
+
+ let text = token.text();
+ if !text.starts_with("r#") && text.ends_with('#') {
+ return None;
+ }
+
+ let existing_hashes = text.chars().skip(1).take_while(|&it| it == '#').count();
+
+ let text_range = token.syntax().text_range();
+ let internal_text = &text[token.text_range_between_quotes()? - text_range.start()];
+
+ if existing_hashes == required_hashes(internal_text) {
+ cov_mark::hit!(cant_remove_required_hash);
+ return None;
+ }
+
+ acc.add(AssistId("remove_hash", AssistKind::RefactorRewrite), "Remove #", text_range, |edit| {
+ edit.delete(TextRange::at(text_range.start() + TextSize::of('r'), TextSize::of('#')));
+ edit.delete(TextRange::new(text_range.end() - TextSize::of('#'), text_range.end()));
+ })
+}
+
+fn required_hashes(s: &str) -> usize {
+ let mut res = 0usize;
+ for idx in s.match_indices('"').map(|(i, _)| i) {
+ let (_, sub) = s.split_at(idx + 1);
+ let n_hashes = sub.chars().take_while(|c| *c == '#').count();
+ res = res.max(n_hashes + 1)
+ }
+ res
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
+
+ use super::*;
+
+ #[test]
+ fn test_required_hashes() {
+ assert_eq!(0, required_hashes("abc"));
+ assert_eq!(0, required_hashes("###"));
+ assert_eq!(1, required_hashes("\""));
+ assert_eq!(2, required_hashes("\"#abc"));
+ assert_eq!(0, required_hashes("#abc"));
+ assert_eq!(3, required_hashes("#ab\"##c"));
+ assert_eq!(5, required_hashes("#ab\"##\"####c"));
+ }
+
+ #[test]
+ fn make_raw_string_target() {
+ check_assist_target(
+ make_raw_string,
+ r#"
+ fn f() {
+ let s = $0"random\nstring";
+ }
+ "#,
+ r#""random\nstring""#,
+ );
+ }
+
+ #[test]
+ fn make_raw_string_works() {
+ check_assist(
+ make_raw_string,
+ r#"
+fn f() {
+ let s = $0"random\nstring";
+}
+"#,
+ r##"
+fn f() {
+ let s = r#"random
+string"#;
+}
+"##,
+ )
+ }
+
+ #[test]
+ fn make_raw_string_works_inside_macros() {
+ check_assist(
+ make_raw_string,
+ r#"
+ fn f() {
+ format!($0"x = {}", 92)
+ }
+ "#,
+ r##"
+ fn f() {
+ format!(r#"x = {}"#, 92)
+ }
+ "##,
+ )
+ }
+
+ #[test]
+ fn make_raw_string_hashes_inside_works() {
+ check_assist(
+ make_raw_string,
+ r###"
+fn f() {
+ let s = $0"#random##\nstring";
+}
+"###,
+ r####"
+fn f() {
+ let s = r#"#random##
+string"#;
+}
+"####,
+ )
+ }
+
+ #[test]
+ fn make_raw_string_closing_hashes_inside_works() {
+ check_assist(
+ make_raw_string,
+ r###"
+fn f() {
+ let s = $0"#random\"##\nstring";
+}
+"###,
+ r####"
+fn f() {
+ let s = r###"#random"##
+string"###;
+}
+"####,
+ )
+ }
+
+ #[test]
+ fn make_raw_string_nothing_to_unescape_works() {
+ check_assist(
+ make_raw_string,
+ r#"
+ fn f() {
+ let s = $0"random string";
+ }
+ "#,
+ r##"
+ fn f() {
+ let s = r#"random string"#;
+ }
+ "##,
+ )
+ }
+
+ #[test]
+ fn make_raw_string_not_works_on_partial_string() {
+ check_assist_not_applicable(
+ make_raw_string,
+ r#"
+ fn f() {
+ let s = "foo$0
+ }
+ "#,
+ )
+ }
+
+ #[test]
+ fn make_usual_string_not_works_on_partial_string() {
+ check_assist_not_applicable(
+ make_usual_string,
+ r#"
+ fn main() {
+ let s = r#"bar$0
+ }
+ "#,
+ )
+ }
+
+ #[test]
+ fn add_hash_target() {
+ check_assist_target(
+ add_hash,
+ r#"
+ fn f() {
+ let s = $0r"random string";
+ }
+ "#,
+ r#"r"random string""#,
+ );
+ }
+
+ #[test]
+ fn add_hash_works() {
+ check_assist(
+ add_hash,
+ r#"
+ fn f() {
+ let s = $0r"random string";
+ }
+ "#,
+ r##"
+ fn f() {
+ let s = r#"random string"#;
+ }
+ "##,
+ )
+ }
+
+ #[test]
+ fn add_more_hash_works() {
+ check_assist(
+ add_hash,
+ r##"
+ fn f() {
+ let s = $0r#"random"string"#;
+ }
+ "##,
+ r###"
+ fn f() {
+ let s = r##"random"string"##;
+ }
+ "###,
+ )
+ }
+
+ #[test]
+ fn add_hash_not_works() {
+ check_assist_not_applicable(
+ add_hash,
+ r#"
+ fn f() {
+ let s = $0"random string";
+ }
+ "#,
+ );
+ }
+
+ #[test]
+ fn remove_hash_target() {
+ check_assist_target(
+ remove_hash,
+ r##"
+ fn f() {
+ let s = $0r#"random string"#;
+ }
+ "##,
+ r##"r#"random string"#"##,
+ );
+ }
+
+ #[test]
+ fn remove_hash_works() {
+ check_assist(
+ remove_hash,
+ r##"fn f() { let s = $0r#"random string"#; }"##,
+ r#"fn f() { let s = r"random string"; }"#,
+ )
+ }
+
+ #[test]
+ fn cant_remove_required_hash() {
+ cov_mark::check!(cant_remove_required_hash);
+ check_assist_not_applicable(
+ remove_hash,
+ r##"
+ fn f() {
+ let s = $0r#"random"str"ing"#;
+ }
+ "##,
+ )
+ }
+
+ #[test]
+ fn remove_more_hash_works() {
+ check_assist(
+ remove_hash,
+ r###"
+ fn f() {
+ let s = $0r##"random string"##;
+ }
+ "###,
+ r##"
+ fn f() {
+ let s = r#"random string"#;
+ }
+ "##,
+ )
+ }
+
+ #[test]
+ fn remove_hash_doesnt_work() {
+ check_assist_not_applicable(remove_hash, r#"fn f() { let s = $0"random string"; }"#);
+ }
+
+ #[test]
+ fn remove_hash_no_hash_doesnt_work() {
+ check_assist_not_applicable(remove_hash, r#"fn f() { let s = $0r"random string"; }"#);
+ }
+
+ #[test]
+ fn make_usual_string_target() {
+ check_assist_target(
+ make_usual_string,
+ r##"
+ fn f() {
+ let s = $0r#"random string"#;
+ }
+ "##,
+ r##"r#"random string"#"##,
+ );
+ }
+
+ #[test]
+ fn make_usual_string_works() {
+ check_assist(
+ make_usual_string,
+ r##"
+ fn f() {
+ let s = $0r#"random string"#;
+ }
+ "##,
+ r#"
+ fn f() {
+ let s = "random string";
+ }
+ "#,
+ )
+ }
+
+ #[test]
+ fn make_usual_string_with_quote_works() {
+ check_assist(
+ make_usual_string,
+ r##"
+ fn f() {
+ let s = $0r#"random"str"ing"#;
+ }
+ "##,
+ r#"
+ fn f() {
+ let s = "random\"str\"ing";
+ }
+ "#,
+ )
+ }
+
+ #[test]
+ fn make_usual_string_more_hash_works() {
+ check_assist(
+ make_usual_string,
+ r###"
+ fn f() {
+ let s = $0r##"random string"##;
+ }
+ "###,
+ r##"
+ fn f() {
+ let s = "random string";
+ }
+ "##,
+ )
+ }
+
+ #[test]
+ fn make_usual_string_not_works() {
+ check_assist_not_applicable(
+ make_usual_string,
+ r#"
+ fn f() {
+ let s = $0"random string";
+ }
+ "#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs
new file mode 100644
index 000000000..afaa7c933
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs
@@ -0,0 +1,241 @@
+use itertools::Itertools;
+use syntax::{
+ ast::{self, AstNode, AstToken},
+ match_ast, NodeOrToken, SyntaxElement, TextSize, T,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: remove_dbg
+//
+// Removes `dbg!()` macro call.
+//
+// ```
+// fn main() {
+// $0dbg!(92);
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// 92;
+// }
+// ```
+pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?;
+ let tt = macro_call.token_tree()?;
+ let r_delim = NodeOrToken::Token(tt.right_delimiter_token()?);
+ if macro_call.path()?.segment()?.name_ref()?.text() != "dbg"
+ || macro_call.excl_token().is_none()
+ {
+ return None;
+ }
+
+ let mac_input = tt.syntax().children_with_tokens().skip(1).take_while(|it| *it != r_delim);
+ let input_expressions = mac_input.group_by(|tok| tok.kind() == T![,]);
+ let input_expressions = input_expressions
+ .into_iter()
+ .filter_map(|(is_sep, group)| (!is_sep).then(|| group))
+ .map(|mut tokens| syntax::hacks::parse_expr_from_str(&tokens.join("")))
+ .collect::<Option<Vec<ast::Expr>>>()?;
+
+ let macro_expr = ast::MacroExpr::cast(macro_call.syntax().parent()?)?;
+ let parent = macro_expr.syntax().parent()?;
+ let (range, text) = match &*input_expressions {
+ // dbg!()
+ [] => {
+ match_ast! {
+ match parent {
+ ast::StmtList(__) => {
+ let range = macro_expr.syntax().text_range();
+ let range = match whitespace_start(macro_expr.syntax().prev_sibling_or_token()) {
+ Some(start) => range.cover_offset(start),
+ None => range,
+ };
+ (range, String::new())
+ },
+ ast::ExprStmt(it) => {
+ let range = it.syntax().text_range();
+ let range = match whitespace_start(it.syntax().prev_sibling_or_token()) {
+ Some(start) => range.cover_offset(start),
+ None => range,
+ };
+ (range, String::new())
+ },
+ _ => (macro_call.syntax().text_range(), "()".to_owned())
+ }
+ }
+ }
+ // dbg!(expr0)
+ [expr] => {
+ let wrap = match ast::Expr::cast(parent) {
+ Some(parent) => match (expr, parent) {
+ (ast::Expr::CastExpr(_), ast::Expr::CastExpr(_)) => false,
+ (
+ ast::Expr::BoxExpr(_) | ast::Expr::PrefixExpr(_) | ast::Expr::RefExpr(_),
+ ast::Expr::AwaitExpr(_)
+ | ast::Expr::CallExpr(_)
+ | ast::Expr::CastExpr(_)
+ | ast::Expr::FieldExpr(_)
+ | ast::Expr::IndexExpr(_)
+ | ast::Expr::MethodCallExpr(_)
+ | ast::Expr::RangeExpr(_)
+ | ast::Expr::TryExpr(_),
+ ) => true,
+ (
+ ast::Expr::BinExpr(_) | ast::Expr::CastExpr(_) | ast::Expr::RangeExpr(_),
+ ast::Expr::AwaitExpr(_)
+ | ast::Expr::BinExpr(_)
+ | ast::Expr::CallExpr(_)
+ | ast::Expr::CastExpr(_)
+ | ast::Expr::FieldExpr(_)
+ | ast::Expr::IndexExpr(_)
+ | ast::Expr::MethodCallExpr(_)
+ | ast::Expr::PrefixExpr(_)
+ | ast::Expr::RangeExpr(_)
+ | ast::Expr::RefExpr(_)
+ | ast::Expr::TryExpr(_),
+ ) => true,
+ _ => false,
+ },
+ None => false,
+ };
+ (
+ macro_call.syntax().text_range(),
+ if wrap { format!("({})", expr) } else { expr.to_string() },
+ )
+ }
+ // dbg!(expr0, expr1, ...)
+ exprs => (macro_call.syntax().text_range(), format!("({})", exprs.iter().format(", "))),
+ };
+
+ acc.add(AssistId("remove_dbg", AssistKind::Refactor), "Remove dbg!()", range, |builder| {
+ builder.replace(range, text);
+ })
+}
+
+fn whitespace_start(it: Option<SyntaxElement>) -> Option<TextSize> {
+ Some(it?.into_token().and_then(ast::Whitespace::cast)?.syntax().text_range().start())
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ fn check(ra_fixture_before: &str, ra_fixture_after: &str) {
+ check_assist(
+ remove_dbg,
+ &format!("fn main() {{\n{}\n}}", ra_fixture_before),
+ &format!("fn main() {{\n{}\n}}", ra_fixture_after),
+ );
+ }
+
+ #[test]
+ fn test_remove_dbg() {
+ check("$0dbg!(1 + 1)", "1 + 1");
+ check("dbg!$0(1 + 1)", "1 + 1");
+ check("dbg!(1 $0+ 1)", "1 + 1");
+ check("dbg![$01 + 1]", "1 + 1");
+ check("dbg!{$01 + 1}", "1 + 1");
+ }
+
+ #[test]
+ fn test_remove_dbg_not_applicable() {
+ check_assist_not_applicable(remove_dbg, "fn main() {$0vec![1, 2, 3]}");
+ check_assist_not_applicable(remove_dbg, "fn main() {$0dbg(5, 6, 7)}");
+ check_assist_not_applicable(remove_dbg, "fn main() {$0dbg!(5, 6, 7}");
+ }
+
+ #[test]
+ fn test_remove_dbg_keep_semicolon_in_let() {
+ // https://github.com/rust-lang/rust-analyzer/issues/5129#issuecomment-651399779
+ check(
+ r#"let res = $0dbg!(1 * 20); // needless comment"#,
+ r#"let res = 1 * 20; // needless comment"#,
+ );
+ check(r#"let res = $0dbg!(); // needless comment"#, r#"let res = (); // needless comment"#);
+ check(
+ r#"let res = $0dbg!(1, 2); // needless comment"#,
+ r#"let res = (1, 2); // needless comment"#,
+ );
+ }
+
+ #[test]
+ fn test_remove_dbg_cast_cast() {
+ check(r#"let res = $0dbg!(x as u32) as u32;"#, r#"let res = x as u32 as u32;"#);
+ }
+
+ #[test]
+ fn test_remove_dbg_prefix() {
+ check(r#"let res = $0dbg!(&result).foo();"#, r#"let res = (&result).foo();"#);
+ check(r#"let res = &$0dbg!(&result);"#, r#"let res = &&result;"#);
+ check(r#"let res = $0dbg!(!result) && true;"#, r#"let res = !result && true;"#);
+ }
+
+ #[test]
+ fn test_remove_dbg_post_expr() {
+ check(r#"let res = $0dbg!(fut.await).foo();"#, r#"let res = fut.await.foo();"#);
+ check(r#"let res = $0dbg!(result?).foo();"#, r#"let res = result?.foo();"#);
+ check(r#"let res = $0dbg!(foo as u32).foo();"#, r#"let res = (foo as u32).foo();"#);
+ check(r#"let res = $0dbg!(array[3]).foo();"#, r#"let res = array[3].foo();"#);
+ check(r#"let res = $0dbg!(tuple.3).foo();"#, r#"let res = tuple.3.foo();"#);
+ }
+
+ #[test]
+ fn test_remove_dbg_range_expr() {
+ check(r#"let res = $0dbg!(foo..bar).foo();"#, r#"let res = (foo..bar).foo();"#);
+ check(r#"let res = $0dbg!(foo..=bar).foo();"#, r#"let res = (foo..=bar).foo();"#);
+ }
+
+ #[test]
+ fn test_remove_empty_dbg() {
+ check_assist(remove_dbg, r#"fn foo() { $0dbg!(); }"#, r#"fn foo() { }"#);
+ check_assist(
+ remove_dbg,
+ r#"
+fn foo() {
+ $0dbg!();
+}
+"#,
+ r#"
+fn foo() {
+}
+"#,
+ );
+ check_assist(
+ remove_dbg,
+ r#"
+fn foo() {
+ let test = $0dbg!();
+}"#,
+ r#"
+fn foo() {
+ let test = ();
+}"#,
+ );
+ check_assist(
+ remove_dbg,
+ r#"
+fn foo() {
+ let t = {
+ println!("Hello, world");
+ $0dbg!()
+ };
+}"#,
+ r#"
+fn foo() {
+ let t = {
+ println!("Hello, world");
+ };
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_remove_multi_dbg() {
+ check(r#"$0dbg!(0, 1)"#, r#"(0, 1)"#);
+ check(r#"$0dbg!(0, (1, 2))"#, r#"(0, (1, 2))"#);
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_mut.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_mut.rs
new file mode 100644
index 000000000..0b299e834
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_mut.rs
@@ -0,0 +1,37 @@
+use syntax::{SyntaxKind, TextRange, T};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: remove_mut
+//
+// Removes the `mut` keyword.
+//
+// ```
+// impl Walrus {
+// fn feed(&mut$0 self, amount: u32) {}
+// }
+// ```
+// ->
+// ```
+// impl Walrus {
+// fn feed(&self, amount: u32) {}
+// }
+// ```
+pub(crate) fn remove_mut(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let mut_token = ctx.find_token_syntax_at_offset(T![mut])?;
+ let delete_from = mut_token.text_range().start();
+ let delete_to = match mut_token.next_token() {
+ Some(it) if it.kind() == SyntaxKind::WHITESPACE => it.text_range().end(),
+ _ => mut_token.text_range().end(),
+ };
+
+ let target = mut_token.text_range();
+ acc.add(
+ AssistId("remove_mut", AssistKind::Refactor),
+ "Remove `mut` keyword",
+ target,
+ |builder| {
+ builder.delete(TextRange::new(delete_from, delete_to));
+ },
+ )
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_unused_param.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_unused_param.rs
new file mode 100644
index 000000000..59ea94ea1
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_unused_param.rs
@@ -0,0 +1,409 @@
+use ide_db::{base_db::FileId, defs::Definition, search::FileReference};
+use syntax::{
+ algo::find_node_at_range,
+ ast::{self, HasArgList},
+ AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, T,
+};
+
+use SyntaxKind::WHITESPACE;
+
+use crate::{
+ assist_context::AssistBuilder, utils::next_prev, AssistContext, AssistId, AssistKind, Assists,
+};
+
+// Assist: remove_unused_param
+//
+// Removes unused function parameter.
+//
+// ```
+// fn frobnicate(x: i32$0) {}
+//
+// fn main() {
+// frobnicate(92);
+// }
+// ```
+// ->
+// ```
+// fn frobnicate() {}
+//
+// fn main() {
+// frobnicate();
+// }
+// ```
+pub(crate) fn remove_unused_param(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let param: ast::Param = ctx.find_node_at_offset()?;
+ let ident_pat = match param.pat()? {
+ ast::Pat::IdentPat(it) => it,
+ _ => return None,
+ };
+ let func = param.syntax().ancestors().find_map(ast::Fn::cast)?;
+ let is_self_present =
+ param.syntax().parent()?.children().find_map(ast::SelfParam::cast).is_some();
+
+ // check if fn is in impl Trait for ..
+ if func
+ .syntax()
+ .parent() // AssocItemList
+ .and_then(|x| x.parent())
+ .and_then(ast::Impl::cast)
+ .map_or(false, |imp| imp.trait_().is_some())
+ {
+ cov_mark::hit!(trait_impl);
+ return None;
+ }
+
+ let mut param_position = func.param_list()?.params().position(|it| it == param)?;
+ // param_list() does not take the self param into consideration, hence this additional check
+ // is required. For associated functions, param_position is incremented here. For inherent
+ // calls we revet the increment below, in process_usage, as those calls will not have an
+ // explicit self parameter.
+ if is_self_present {
+ param_position += 1;
+ }
+ let fn_def = {
+ let func = ctx.sema.to_def(&func)?;
+ Definition::Function(func)
+ };
+
+ let param_def = {
+ let local = ctx.sema.to_def(&ident_pat)?;
+ Definition::Local(local)
+ };
+ if param_def.usages(&ctx.sema).at_least_one() {
+ cov_mark::hit!(keep_used);
+ return None;
+ }
+ acc.add(
+ AssistId("remove_unused_param", AssistKind::Refactor),
+ "Remove unused parameter",
+ param.syntax().text_range(),
+ |builder| {
+ builder.delete(range_to_remove(param.syntax()));
+ for (file_id, references) in fn_def.usages(&ctx.sema).all() {
+ process_usages(ctx, builder, file_id, references, param_position, is_self_present);
+ }
+ },
+ )
+}
+
+fn process_usages(
+ ctx: &AssistContext<'_>,
+ builder: &mut AssistBuilder,
+ file_id: FileId,
+ references: Vec<FileReference>,
+ arg_to_remove: usize,
+ is_self_present: bool,
+) {
+ let source_file = ctx.sema.parse(file_id);
+ builder.edit_file(file_id);
+ let possible_ranges = references
+ .into_iter()
+ .filter_map(|usage| process_usage(&source_file, usage, arg_to_remove, is_self_present));
+
+ let mut ranges_to_delete: Vec<TextRange> = vec![];
+ for range in possible_ranges {
+ if !ranges_to_delete.iter().any(|it| it.contains_range(range)) {
+ ranges_to_delete.push(range)
+ }
+ }
+
+ for range in ranges_to_delete {
+ builder.delete(range)
+ }
+}
+
+fn process_usage(
+ source_file: &SourceFile,
+ FileReference { range, .. }: FileReference,
+ mut arg_to_remove: usize,
+ is_self_present: bool,
+) -> Option<TextRange> {
+ let call_expr_opt: Option<ast::CallExpr> = find_node_at_range(source_file.syntax(), range);
+ if let Some(call_expr) = call_expr_opt {
+ let call_expr_range = call_expr.expr()?.syntax().text_range();
+ if !call_expr_range.contains_range(range) {
+ return None;
+ }
+
+ let arg = call_expr.arg_list()?.args().nth(arg_to_remove)?;
+ return Some(range_to_remove(arg.syntax()));
+ }
+
+ let method_call_expr_opt: Option<ast::MethodCallExpr> =
+ find_node_at_range(source_file.syntax(), range);
+ if let Some(method_call_expr) = method_call_expr_opt {
+ let method_call_expr_range = method_call_expr.name_ref()?.syntax().text_range();
+ if !method_call_expr_range.contains_range(range) {
+ return None;
+ }
+
+ if is_self_present {
+ arg_to_remove -= 1;
+ }
+
+ let arg = method_call_expr.arg_list()?.args().nth(arg_to_remove)?;
+ return Some(range_to_remove(arg.syntax()));
+ }
+
+ None
+}
+
+pub(crate) fn range_to_remove(node: &SyntaxNode) -> TextRange {
+ let up_to_comma = next_prev().find_map(|dir| {
+ node.siblings_with_tokens(dir)
+ .filter_map(|it| it.into_token())
+ .find(|it| it.kind() == T![,])
+ .map(|it| (dir, it))
+ });
+ if let Some((dir, token)) = up_to_comma {
+ if node.next_sibling().is_some() {
+ let up_to_space = token
+ .siblings_with_tokens(dir)
+ .skip(1)
+ .take_while(|it| it.kind() == WHITESPACE)
+ .last()
+ .and_then(|it| it.into_token());
+ return node
+ .text_range()
+ .cover(up_to_space.map_or(token.text_range(), |it| it.text_range()));
+ }
+ node.text_range().cover(token.text_range())
+ } else {
+ node.text_range()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn remove_unused() {
+ check_assist(
+ remove_unused_param,
+ r#"
+fn a() { foo(9, 2) }
+fn foo(x: i32, $0y: i32) { x; }
+fn b() { foo(9, 2,) }
+"#,
+ r#"
+fn a() { foo(9) }
+fn foo(x: i32) { x; }
+fn b() { foo(9, ) }
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_unused_first_param() {
+ check_assist(
+ remove_unused_param,
+ r#"
+fn foo($0x: i32, y: i32) { y; }
+fn a() { foo(1, 2) }
+fn b() { foo(1, 2,) }
+"#,
+ r#"
+fn foo(y: i32) { y; }
+fn a() { foo(2) }
+fn b() { foo(2,) }
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_unused_single_param() {
+ check_assist(
+ remove_unused_param,
+ r#"
+fn foo($0x: i32) { 0; }
+fn a() { foo(1) }
+fn b() { foo(1, ) }
+"#,
+ r#"
+fn foo() { 0; }
+fn a() { foo() }
+fn b() { foo( ) }
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_unused_surrounded_by_parms() {
+ check_assist(
+ remove_unused_param,
+ r#"
+fn foo(x: i32, $0y: i32, z: i32) { x; }
+fn a() { foo(1, 2, 3) }
+fn b() { foo(1, 2, 3,) }
+"#,
+ r#"
+fn foo(x: i32, z: i32) { x; }
+fn a() { foo(1, 3) }
+fn b() { foo(1, 3,) }
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_unused_qualified_call() {
+ check_assist(
+ remove_unused_param,
+ r#"
+mod bar { pub fn foo(x: i32, $0y: i32) { x; } }
+fn b() { bar::foo(9, 2) }
+"#,
+ r#"
+mod bar { pub fn foo(x: i32) { x; } }
+fn b() { bar::foo(9) }
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_unused_turbofished_func() {
+ check_assist(
+ remove_unused_param,
+ r#"
+pub fn foo<T>(x: T, $0y: i32) { x; }
+fn b() { foo::<i32>(9, 2) }
+"#,
+ r#"
+pub fn foo<T>(x: T) { x; }
+fn b() { foo::<i32>(9) }
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_unused_generic_unused_param_func() {
+ check_assist(
+ remove_unused_param,
+ r#"
+pub fn foo<T>(x: i32, $0y: T) { x; }
+fn b() { foo::<i32>(9, 2) }
+fn b2() { foo(9, 2) }
+"#,
+ r#"
+pub fn foo<T>(x: i32) { x; }
+fn b() { foo::<i32>(9) }
+fn b2() { foo(9) }
+"#,
+ );
+ }
+
+ #[test]
+ fn keep_used() {
+ cov_mark::check!(keep_used);
+ check_assist_not_applicable(
+ remove_unused_param,
+ r#"
+fn foo(x: i32, $0y: i32) { y; }
+fn main() { foo(9, 2) }
+"#,
+ );
+ }
+
+ #[test]
+ fn trait_impl() {
+ cov_mark::check!(trait_impl);
+ check_assist_not_applicable(
+ remove_unused_param,
+ r#"
+trait Trait {
+ fn foo(x: i32);
+}
+impl Trait for () {
+ fn foo($0x: i32) {}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_across_files() {
+ check_assist(
+ remove_unused_param,
+ r#"
+//- /main.rs
+fn foo(x: i32, $0y: i32) { x; }
+
+mod foo;
+
+//- /foo.rs
+use super::foo;
+
+fn bar() {
+ let _ = foo(1, 2);
+}
+"#,
+ r#"
+//- /main.rs
+fn foo(x: i32) { x; }
+
+mod foo;
+
+//- /foo.rs
+use super::foo;
+
+fn bar() {
+ let _ = foo(1);
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_remove_method_param() {
+ check_assist(
+ remove_unused_param,
+ r#"
+struct S;
+impl S { fn f(&self, $0_unused: i32) {} }
+fn main() {
+ S.f(92);
+ S.f();
+ S.f(93, 92);
+ S::f(&S, 92);
+}
+"#,
+ r#"
+struct S;
+impl S { fn f(&self) {} }
+fn main() {
+ S.f();
+ S.f();
+ S.f(92);
+ S::f(&S);
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn nested_call() {
+ check_assist(
+ remove_unused_param,
+ r#"
+fn foo(x: i32, $0y: i32) -> i32 {
+ x
+}
+
+fn bar() {
+ foo(1, foo(2, 3));
+}
+"#,
+ r#"
+fn foo(x: i32) -> i32 {
+ x
+}
+
+fn bar() {
+ foo(1);
+}
+"#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/reorder_fields.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/reorder_fields.rs
new file mode 100644
index 000000000..a899c7a64
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/reorder_fields.rs
@@ -0,0 +1,212 @@
+use either::Either;
+use ide_db::FxHashMap;
+use itertools::Itertools;
+use syntax::{ast, ted, AstNode};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: reorder_fields
+//
+// Reorder the fields of record literals and record patterns in the same order as in
+// the definition.
+//
+// ```
+// struct Foo {foo: i32, bar: i32};
+// const test: Foo = $0Foo {bar: 0, foo: 1}
+// ```
+// ->
+// ```
+// struct Foo {foo: i32, bar: i32};
+// const test: Foo = Foo {foo: 1, bar: 0}
+// ```
+pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let record = ctx
+ .find_node_at_offset::<ast::RecordExpr>()
+ .map(Either::Left)
+ .or_else(|| ctx.find_node_at_offset::<ast::RecordPat>().map(Either::Right))?;
+
+ let path = record.as_ref().either(|it| it.path(), |it| it.path())?;
+ let ranks = compute_fields_ranks(&path, ctx)?;
+ let get_rank_of_field =
+ |of: Option<_>| *ranks.get(&of.unwrap_or_default()).unwrap_or(&usize::MAX);
+
+ let field_list = match &record {
+ Either::Left(it) => Either::Left(it.record_expr_field_list()?),
+ Either::Right(it) => Either::Right(it.record_pat_field_list()?),
+ };
+ let fields = match field_list {
+ Either::Left(it) => Either::Left((
+ it.fields()
+ .sorted_unstable_by_key(|field| {
+ get_rank_of_field(field.field_name().map(|it| it.to_string()))
+ })
+ .collect::<Vec<_>>(),
+ it,
+ )),
+ Either::Right(it) => Either::Right((
+ it.fields()
+ .sorted_unstable_by_key(|field| {
+ get_rank_of_field(field.field_name().map(|it| it.to_string()))
+ })
+ .collect::<Vec<_>>(),
+ it,
+ )),
+ };
+
+ let is_sorted = fields.as_ref().either(
+ |(sorted, field_list)| field_list.fields().zip(sorted).all(|(a, b)| a == *b),
+ |(sorted, field_list)| field_list.fields().zip(sorted).all(|(a, b)| a == *b),
+ );
+ if is_sorted {
+ cov_mark::hit!(reorder_sorted_fields);
+ return None;
+ }
+ let target = record.as_ref().either(AstNode::syntax, AstNode::syntax).text_range();
+ acc.add(
+ AssistId("reorder_fields", AssistKind::RefactorRewrite),
+ "Reorder record fields",
+ target,
+ |builder| match fields {
+ Either::Left((sorted, field_list)) => {
+ replace(builder.make_mut(field_list).fields(), sorted)
+ }
+ Either::Right((sorted, field_list)) => {
+ replace(builder.make_mut(field_list).fields(), sorted)
+ }
+ },
+ )
+}
+
+fn replace<T: AstNode + PartialEq>(
+ fields: impl Iterator<Item = T>,
+ sorted_fields: impl IntoIterator<Item = T>,
+) {
+ fields.zip(sorted_fields).for_each(|(field, sorted_field)| {
+ ted::replace(field.syntax(), sorted_field.syntax().clone_for_update())
+ });
+}
+
+fn compute_fields_ranks(
+ path: &ast::Path,
+ ctx: &AssistContext<'_>,
+) -> Option<FxHashMap<String, usize>> {
+ let strukt = match ctx.sema.resolve_path(path) {
+ Some(hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Struct(it)))) => it,
+ _ => return None,
+ };
+
+ let res = strukt
+ .fields(ctx.db())
+ .into_iter()
+ .enumerate()
+ .map(|(idx, field)| (field.name(ctx.db()).to_string(), idx))
+ .collect();
+
+ Some(res)
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn reorder_sorted_fields() {
+ cov_mark::check!(reorder_sorted_fields);
+ check_assist_not_applicable(
+ reorder_fields,
+ r#"
+struct Foo { foo: i32, bar: i32 }
+const test: Foo = $0Foo { foo: 0, bar: 0 };
+"#,
+ )
+ }
+
+ #[test]
+ fn trivial_empty_fields() {
+ check_assist_not_applicable(
+ reorder_fields,
+ r#"
+struct Foo {}
+const test: Foo = $0Foo {};
+"#,
+ )
+ }
+
+ #[test]
+ fn reorder_struct_fields() {
+ check_assist(
+ reorder_fields,
+ r#"
+struct Foo { foo: i32, bar: i32 }
+const test: Foo = $0Foo { bar: 0, foo: 1 };
+"#,
+ r#"
+struct Foo { foo: i32, bar: i32 }
+const test: Foo = Foo { foo: 1, bar: 0 };
+"#,
+ )
+ }
+ #[test]
+ fn reorder_struct_pattern() {
+ check_assist(
+ reorder_fields,
+ r#"
+struct Foo { foo: i64, bar: i64, baz: i64 }
+
+fn f(f: Foo) -> {
+ match f {
+ $0Foo { baz: 0, ref mut bar, .. } => (),
+ _ => ()
+ }
+}
+"#,
+ r#"
+struct Foo { foo: i64, bar: i64, baz: i64 }
+
+fn f(f: Foo) -> {
+ match f {
+ Foo { ref mut bar, baz: 0, .. } => (),
+ _ => ()
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn reorder_with_extra_field() {
+ check_assist(
+ reorder_fields,
+ r#"
+struct Foo { foo: String, bar: String }
+
+impl Foo {
+ fn new() -> Foo {
+ let foo = String::new();
+ $0Foo {
+ bar: foo.clone(),
+ extra: "Extra field",
+ foo,
+ }
+ }
+}
+"#,
+ r#"
+struct Foo { foo: String, bar: String }
+
+impl Foo {
+ fn new() -> Foo {
+ let foo = String::new();
+ Foo {
+ foo,
+ bar: foo.clone(),
+ extra: "Extra field",
+ }
+ }
+}
+"#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/reorder_impl_items.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/reorder_impl_items.rs
new file mode 100644
index 000000000..208c3e109
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/reorder_impl_items.rs
@@ -0,0 +1,284 @@
+use hir::{PathResolution, Semantics};
+use ide_db::{FxHashMap, RootDatabase};
+use itertools::Itertools;
+use syntax::{
+ ast::{self, HasName},
+ ted, AstNode,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: reorder_impl_items
+//
+// Reorder the items of an `impl Trait`. The items will be ordered
+// in the same order as in the trait definition.
+//
+// ```
+// trait Foo {
+// type A;
+// const B: u8;
+// fn c();
+// }
+//
+// struct Bar;
+// $0impl Foo for Bar {
+// const B: u8 = 17;
+// fn c() {}
+// type A = String;
+// }
+// ```
+// ->
+// ```
+// trait Foo {
+// type A;
+// const B: u8;
+// fn c();
+// }
+//
+// struct Bar;
+// impl Foo for Bar {
+// type A = String;
+// const B: u8 = 17;
+// fn c() {}
+// }
+// ```
+pub(crate) fn reorder_impl_items(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let impl_ast = ctx.find_node_at_offset::<ast::Impl>()?;
+ let items = impl_ast.assoc_item_list()?;
+ let assoc_items = items.assoc_items().collect::<Vec<_>>();
+
+ let path = impl_ast
+ .trait_()
+ .and_then(|t| match t {
+ ast::Type::PathType(path) => Some(path),
+ _ => None,
+ })?
+ .path()?;
+
+ let ranks = compute_item_ranks(&path, ctx)?;
+ let sorted: Vec<_> = assoc_items
+ .iter()
+ .cloned()
+ .sorted_by_key(|i| {
+ let name = match i {
+ ast::AssocItem::Const(c) => c.name(),
+ ast::AssocItem::Fn(f) => f.name(),
+ ast::AssocItem::TypeAlias(t) => t.name(),
+ ast::AssocItem::MacroCall(_) => None,
+ };
+
+ name.and_then(|n| ranks.get(&n.to_string()).copied()).unwrap_or(usize::max_value())
+ })
+ .collect();
+
+ // Don't edit already sorted methods:
+ if assoc_items == sorted {
+ cov_mark::hit!(not_applicable_if_sorted);
+ return None;
+ }
+
+ let target = items.syntax().text_range();
+ acc.add(
+ AssistId("reorder_impl_items", AssistKind::RefactorRewrite),
+ "Sort items by trait definition",
+ target,
+ |builder| {
+ let assoc_items =
+ assoc_items.into_iter().map(|item| builder.make_mut(item)).collect::<Vec<_>>();
+ assoc_items
+ .into_iter()
+ .zip(sorted)
+ .for_each(|(old, new)| ted::replace(old.syntax(), new.clone_for_update().syntax()));
+ },
+ )
+}
+
+fn compute_item_ranks(
+ path: &ast::Path,
+ ctx: &AssistContext<'_>,
+) -> Option<FxHashMap<String, usize>> {
+ let td = trait_definition(path, &ctx.sema)?;
+
+ Some(
+ td.items(ctx.db())
+ .iter()
+ .flat_map(|i| i.name(ctx.db()))
+ .enumerate()
+ .map(|(idx, name)| (name.to_string(), idx))
+ .collect(),
+ )
+}
+
+fn trait_definition(path: &ast::Path, sema: &Semantics<'_, RootDatabase>) -> Option<hir::Trait> {
+ match sema.resolve_path(path)? {
+ PathResolution::Def(hir::ModuleDef::Trait(trait_)) => Some(trait_),
+ _ => None,
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn not_applicable_if_sorted() {
+ cov_mark::check!(not_applicable_if_sorted);
+ check_assist_not_applicable(
+ reorder_impl_items,
+ r#"
+trait Bar {
+ type T;
+ const C: ();
+ fn a() {}
+ fn z() {}
+ fn b() {}
+}
+struct Foo;
+$0impl Bar for Foo {
+ type T = ();
+ const C: () = ();
+ fn a() {}
+ fn z() {}
+ fn b() {}
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn reorder_impl_trait_functions() {
+ check_assist(
+ reorder_impl_items,
+ r#"
+trait Bar {
+ fn a() {}
+ fn c() {}
+ fn b() {}
+ fn d() {}
+}
+
+struct Foo;
+$0impl Bar for Foo {
+ fn d() {}
+ fn b() {}
+ fn c() {}
+ fn a() {}
+}
+"#,
+ r#"
+trait Bar {
+ fn a() {}
+ fn c() {}
+ fn b() {}
+ fn d() {}
+}
+
+struct Foo;
+impl Bar for Foo {
+ fn a() {}
+ fn c() {}
+ fn b() {}
+ fn d() {}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn not_applicable_if_empty() {
+ check_assist_not_applicable(
+ reorder_impl_items,
+ r#"
+trait Bar {};
+struct Foo;
+$0impl Bar for Foo {}
+ "#,
+ )
+ }
+
+ #[test]
+ fn reorder_impl_trait_items() {
+ check_assist(
+ reorder_impl_items,
+ r#"
+trait Bar {
+ fn a() {}
+ type T0;
+ fn c() {}
+ const C1: ();
+ fn b() {}
+ type T1;
+ fn d() {}
+ const C0: ();
+}
+
+struct Foo;
+$0impl Bar for Foo {
+ type T1 = ();
+ fn d() {}
+ fn b() {}
+ fn c() {}
+ const C1: () = ();
+ fn a() {}
+ type T0 = ();
+ const C0: () = ();
+}
+ "#,
+ r#"
+trait Bar {
+ fn a() {}
+ type T0;
+ fn c() {}
+ const C1: ();
+ fn b() {}
+ type T1;
+ fn d() {}
+ const C0: ();
+}
+
+struct Foo;
+impl Bar for Foo {
+ fn a() {}
+ type T0 = ();
+ fn c() {}
+ const C1: () = ();
+ fn b() {}
+ type T1 = ();
+ fn d() {}
+ const C0: () = ();
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn reorder_impl_trait_items_uneven_ident_lengths() {
+ check_assist(
+ reorder_impl_items,
+ r#"
+trait Bar {
+ type Foo;
+ type Fooo;
+}
+
+struct Foo;
+impl Bar for Foo {
+ type Fooo = ();
+ type Foo = ();$0
+}"#,
+ r#"
+trait Bar {
+ type Foo;
+ type Fooo;
+}
+
+struct Foo;
+impl Bar for Foo {
+ type Foo = ();
+ type Fooo = ();
+}"#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
new file mode 100644
index 000000000..bd50208da
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
@@ -0,0 +1,1250 @@
+use hir::{InFile, ModuleDef};
+use ide_db::{
+ helpers::mod_path_to_ast, imports::import_assets::NameToImport, items_locator,
+ syntax_helpers::insert_whitespace_into_node::insert_ws_into,
+};
+use itertools::Itertools;
+use syntax::{
+ ast::{self, AstNode, HasName},
+ SyntaxKind::WHITESPACE,
+};
+
+use crate::{
+ assist_context::{AssistBuilder, AssistContext, Assists},
+ utils::{
+ add_trait_assoc_items_to_impl, filter_assoc_items, gen_trait_fn_body,
+ generate_trait_impl_text, render_snippet, Cursor, DefaultMethods,
+ },
+ AssistId, AssistKind,
+};
+
+// Assist: replace_derive_with_manual_impl
+//
+// Converts a `derive` impl into a manual one.
+//
+// ```
+// # //- minicore: derive
+// # trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; }
+// #[derive(Deb$0ug, Display)]
+// struct S;
+// ```
+// ->
+// ```
+// # trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; }
+// #[derive(Display)]
+// struct S;
+//
+// impl Debug for S {
+// $0fn fmt(&self, f: &mut Formatter) -> Result<()> {
+// f.debug_struct("S").finish()
+// }
+// }
+// ```
+pub(crate) fn replace_derive_with_manual_impl(
+ acc: &mut Assists,
+ ctx: &AssistContext<'_>,
+) -> Option<()> {
+ let attr = ctx.find_node_at_offset_with_descend::<ast::Attr>()?;
+ let path = attr.path()?;
+ let hir_file = ctx.sema.hir_file_for(attr.syntax());
+ if !hir_file.is_derive_attr_pseudo_expansion(ctx.db()) {
+ return None;
+ }
+
+ let InFile { file_id, value } = hir_file.call_node(ctx.db())?;
+ if file_id.is_macro() {
+ // FIXME: make this work in macro files
+ return None;
+ }
+ // collect the derive paths from the #[derive] expansion
+ let current_derives = ctx
+ .sema
+ .parse_or_expand(hir_file)?
+ .descendants()
+ .filter_map(ast::Attr::cast)
+ .filter_map(|attr| attr.path())
+ .collect::<Vec<_>>();
+
+ let adt = value.parent().and_then(ast::Adt::cast)?;
+ let attr = ast::Attr::cast(value)?;
+ let args = attr.token_tree()?;
+
+ let current_module = ctx.sema.scope(adt.syntax())?.module();
+ let current_crate = current_module.krate();
+
+ let found_traits = items_locator::items_with_name(
+ &ctx.sema,
+ current_crate,
+ NameToImport::exact_case_sensitive(path.segments().last()?.to_string()),
+ items_locator::AssocItemSearch::Exclude,
+ Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT.inner()),
+ )
+ .filter_map(|item| match item.as_module_def()? {
+ ModuleDef::Trait(trait_) => Some(trait_),
+ _ => None,
+ })
+ .flat_map(|trait_| {
+ current_module
+ .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_))
+ .as_ref()
+ .map(mod_path_to_ast)
+ .zip(Some(trait_))
+ });
+
+ let mut no_traits_found = true;
+ for (replace_trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) {
+ add_assist(
+ acc,
+ ctx,
+ &attr,
+ &current_derives,
+ &args,
+ &path,
+ &replace_trait_path,
+ Some(trait_),
+ &adt,
+ )?;
+ }
+ if no_traits_found {
+ add_assist(acc, ctx, &attr, &current_derives, &args, &path, &path, None, &adt)?;
+ }
+ Some(())
+}
+
+fn add_assist(
+ acc: &mut Assists,
+ ctx: &AssistContext<'_>,
+ attr: &ast::Attr,
+ old_derives: &[ast::Path],
+ old_tree: &ast::TokenTree,
+ old_trait_path: &ast::Path,
+ replace_trait_path: &ast::Path,
+ trait_: Option<hir::Trait>,
+ adt: &ast::Adt,
+) -> Option<()> {
+ let target = attr.syntax().text_range();
+ let annotated_name = adt.name()?;
+ let label = format!("Convert to manual `impl {} for {}`", replace_trait_path, annotated_name);
+
+ acc.add(
+ AssistId("replace_derive_with_manual_impl", AssistKind::Refactor),
+ label,
+ target,
+ |builder| {
+ let insert_pos = adt.syntax().text_range().end();
+ let impl_def_with_items =
+ impl_def_from_trait(&ctx.sema, adt, &annotated_name, trait_, replace_trait_path);
+ update_attribute(builder, old_derives, old_tree, old_trait_path, attr);
+ let trait_path = replace_trait_path.to_string();
+ match (ctx.config.snippet_cap, impl_def_with_items) {
+ (None, _) => {
+ builder.insert(insert_pos, generate_trait_impl_text(adt, &trait_path, ""))
+ }
+ (Some(cap), None) => builder.insert_snippet(
+ cap,
+ insert_pos,
+ generate_trait_impl_text(adt, &trait_path, " $0"),
+ ),
+ (Some(cap), Some((impl_def, first_assoc_item))) => {
+ let mut cursor = Cursor::Before(first_assoc_item.syntax());
+ let placeholder;
+ if let ast::AssocItem::Fn(ref func) = first_assoc_item {
+ if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast)
+ {
+ if m.syntax().text() == "todo!()" {
+ placeholder = m;
+ cursor = Cursor::Replace(placeholder.syntax());
+ }
+ }
+ }
+
+ builder.insert_snippet(
+ cap,
+ insert_pos,
+ format!("\n\n{}", render_snippet(cap, impl_def.syntax(), cursor)),
+ )
+ }
+ };
+ },
+ )
+}
+
+fn impl_def_from_trait(
+ sema: &hir::Semantics<'_, ide_db::RootDatabase>,
+ adt: &ast::Adt,
+ annotated_name: &ast::Name,
+ trait_: Option<hir::Trait>,
+ trait_path: &ast::Path,
+) -> Option<(ast::Impl, ast::AssocItem)> {
+ let trait_ = trait_?;
+ let target_scope = sema.scope(annotated_name.syntax())?;
+ let trait_items = filter_assoc_items(sema, &trait_.items(sema.db), DefaultMethods::No);
+ if trait_items.is_empty() {
+ return None;
+ }
+ let impl_def = {
+ use syntax::ast::Impl;
+ let text = generate_trait_impl_text(adt, trait_path.to_string().as_str(), "");
+ let parse = syntax::SourceFile::parse(&text);
+ let node = match parse.tree().syntax().descendants().find_map(Impl::cast) {
+ Some(it) => it,
+ None => {
+ panic!(
+ "Failed to make ast node `{}` from text {}",
+ std::any::type_name::<Impl>(),
+ text
+ )
+ }
+ };
+ let node = node.clone_subtree();
+ assert_eq!(node.syntax().text_range().start(), 0.into());
+ node
+ };
+
+ let trait_items = trait_items
+ .into_iter()
+ .map(|it| {
+ if sema.hir_file_for(it.syntax()).is_macro() {
+ if let Some(it) = ast::AssocItem::cast(insert_ws_into(it.syntax().clone())) {
+ return it;
+ }
+ }
+ it.clone_for_update()
+ })
+ .collect();
+ let (impl_def, first_assoc_item) =
+ add_trait_assoc_items_to_impl(sema, trait_items, trait_, impl_def, target_scope);
+
+ // Generate a default `impl` function body for the derived trait.
+ if let ast::AssocItem::Fn(ref func) = first_assoc_item {
+ let _ = gen_trait_fn_body(func, trait_path, adt);
+ };
+
+ Some((impl_def, first_assoc_item))
+}
+
+fn update_attribute(
+ builder: &mut AssistBuilder,
+ old_derives: &[ast::Path],
+ old_tree: &ast::TokenTree,
+ old_trait_path: &ast::Path,
+ attr: &ast::Attr,
+) {
+ let new_derives = old_derives
+ .iter()
+ .filter(|t| t.to_string() != old_trait_path.to_string())
+ .collect::<Vec<_>>();
+ let has_more_derives = !new_derives.is_empty();
+
+ if has_more_derives {
+ let new_derives = format!("({})", new_derives.iter().format(", "));
+ builder.replace(old_tree.syntax().text_range(), new_derives);
+ } else {
+ let attr_range = attr.syntax().text_range();
+ builder.delete(attr_range);
+
+ if let Some(line_break_range) = attr
+ .syntax()
+ .next_sibling_or_token()
+ .filter(|t| t.kind() == WHITESPACE)
+ .map(|t| t.text_range())
+ {
+ builder.delete(line_break_range);
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn add_custom_impl_debug_record_struct() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: fmt, derive
+#[derive(Debu$0g)]
+struct Foo {
+ bar: String,
+}
+"#,
+ r#"
+struct Foo {
+ bar: String,
+}
+
+impl core::fmt::Debug for Foo {
+ $0fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ f.debug_struct("Foo").field("bar", &self.bar).finish()
+ }
+}
+"#,
+ )
+ }
+ #[test]
+ fn add_custom_impl_debug_tuple_struct() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: fmt, derive
+#[derive(Debu$0g)]
+struct Foo(String, usize);
+"#,
+ r#"struct Foo(String, usize);
+
+impl core::fmt::Debug for Foo {
+ $0fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ f.debug_tuple("Foo").field(&self.0).field(&self.1).finish()
+ }
+}
+"#,
+ )
+ }
+ #[test]
+ fn add_custom_impl_debug_empty_struct() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: fmt, derive
+#[derive(Debu$0g)]
+struct Foo;
+"#,
+ r#"
+struct Foo;
+
+impl core::fmt::Debug for Foo {
+ $0fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ f.debug_struct("Foo").finish()
+ }
+}
+"#,
+ )
+ }
+ #[test]
+ fn add_custom_impl_debug_enum() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: fmt, derive
+#[derive(Debu$0g)]
+enum Foo {
+ Bar,
+ Baz,
+}
+"#,
+ r#"
+enum Foo {
+ Bar,
+ Baz,
+}
+
+impl core::fmt::Debug for Foo {
+ $0fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self {
+ Self::Bar => write!(f, "Bar"),
+ Self::Baz => write!(f, "Baz"),
+ }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn add_custom_impl_debug_tuple_enum() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: fmt, derive
+#[derive(Debu$0g)]
+enum Foo {
+ Bar(usize, usize),
+ Baz,
+}
+"#,
+ r#"
+enum Foo {
+ Bar(usize, usize),
+ Baz,
+}
+
+impl core::fmt::Debug for Foo {
+ $0fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self {
+ Self::Bar(arg0, arg1) => f.debug_tuple("Bar").field(arg0).field(arg1).finish(),
+ Self::Baz => write!(f, "Baz"),
+ }
+ }
+}
+"#,
+ )
+ }
+ #[test]
+ fn add_custom_impl_debug_record_enum() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: fmt, derive
+#[derive(Debu$0g)]
+enum Foo {
+ Bar {
+ baz: usize,
+ qux: usize,
+ },
+ Baz,
+}
+"#,
+ r#"
+enum Foo {
+ Bar {
+ baz: usize,
+ qux: usize,
+ },
+ Baz,
+}
+
+impl core::fmt::Debug for Foo {
+ $0fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self {
+ Self::Bar { baz, qux } => f.debug_struct("Bar").field("baz", baz).field("qux", qux).finish(),
+ Self::Baz => write!(f, "Baz"),
+ }
+ }
+}
+"#,
+ )
+ }
+ #[test]
+ fn add_custom_impl_default_record_struct() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: default, derive
+#[derive(Defau$0lt)]
+struct Foo {
+ foo: usize,
+}
+"#,
+ r#"
+struct Foo {
+ foo: usize,
+}
+
+impl Default for Foo {
+ $0fn default() -> Self {
+ Self { foo: Default::default() }
+ }
+}
+"#,
+ )
+ }
+ #[test]
+ fn add_custom_impl_default_tuple_struct() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: default, derive
+#[derive(Defau$0lt)]
+struct Foo(usize);
+"#,
+ r#"
+struct Foo(usize);
+
+impl Default for Foo {
+ $0fn default() -> Self {
+ Self(Default::default())
+ }
+}
+"#,
+ )
+ }
+ #[test]
+ fn add_custom_impl_default_empty_struct() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: default, derive
+#[derive(Defau$0lt)]
+struct Foo;
+"#,
+ r#"
+struct Foo;
+
+impl Default for Foo {
+ $0fn default() -> Self {
+ Self { }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn add_custom_impl_hash_record_struct() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: hash, derive
+#[derive(Has$0h)]
+struct Foo {
+ bin: usize,
+ bar: usize,
+}
+"#,
+ r#"
+struct Foo {
+ bin: usize,
+ bar: usize,
+}
+
+impl core::hash::Hash for Foo {
+ $0fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
+ self.bin.hash(state);
+ self.bar.hash(state);
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn add_custom_impl_hash_tuple_struct() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: hash, derive
+#[derive(Has$0h)]
+struct Foo(usize, usize);
+"#,
+ r#"
+struct Foo(usize, usize);
+
+impl core::hash::Hash for Foo {
+ $0fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
+ self.0.hash(state);
+ self.1.hash(state);
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn add_custom_impl_hash_enum() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: hash, derive
+#[derive(Has$0h)]
+enum Foo {
+ Bar,
+ Baz,
+}
+"#,
+ r#"
+enum Foo {
+ Bar,
+ Baz,
+}
+
+impl core::hash::Hash for Foo {
+ $0fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
+ core::mem::discriminant(self).hash(state);
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn add_custom_impl_clone_record_struct() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: clone, derive
+#[derive(Clo$0ne)]
+struct Foo {
+ bin: usize,
+ bar: usize,
+}
+"#,
+ r#"
+struct Foo {
+ bin: usize,
+ bar: usize,
+}
+
+impl Clone for Foo {
+ $0fn clone(&self) -> Self {
+ Self { bin: self.bin.clone(), bar: self.bar.clone() }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn add_custom_impl_clone_tuple_struct() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: clone, derive
+#[derive(Clo$0ne)]
+struct Foo(usize, usize);
+"#,
+ r#"
+struct Foo(usize, usize);
+
+impl Clone for Foo {
+ $0fn clone(&self) -> Self {
+ Self(self.0.clone(), self.1.clone())
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn add_custom_impl_clone_empty_struct() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: clone, derive
+#[derive(Clo$0ne)]
+struct Foo;
+"#,
+ r#"
+struct Foo;
+
+impl Clone for Foo {
+ $0fn clone(&self) -> Self {
+ Self { }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn add_custom_impl_clone_enum() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: clone, derive
+#[derive(Clo$0ne)]
+enum Foo {
+ Bar,
+ Baz,
+}
+"#,
+ r#"
+enum Foo {
+ Bar,
+ Baz,
+}
+
+impl Clone for Foo {
+ $0fn clone(&self) -> Self {
+ match self {
+ Self::Bar => Self::Bar,
+ Self::Baz => Self::Baz,
+ }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn add_custom_impl_clone_tuple_enum() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: clone, derive
+#[derive(Clo$0ne)]
+enum Foo {
+ Bar(String),
+ Baz,
+}
+"#,
+ r#"
+enum Foo {
+ Bar(String),
+ Baz,
+}
+
+impl Clone for Foo {
+ $0fn clone(&self) -> Self {
+ match self {
+ Self::Bar(arg0) => Self::Bar(arg0.clone()),
+ Self::Baz => Self::Baz,
+ }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn add_custom_impl_clone_record_enum() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: clone, derive
+#[derive(Clo$0ne)]
+enum Foo {
+ Bar {
+ bin: String,
+ },
+ Baz,
+}
+"#,
+ r#"
+enum Foo {
+ Bar {
+ bin: String,
+ },
+ Baz,
+}
+
+impl Clone for Foo {
+ $0fn clone(&self) -> Self {
+ match self {
+ Self::Bar { bin } => Self::Bar { bin: bin.clone() },
+ Self::Baz => Self::Baz,
+ }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn add_custom_impl_partial_ord_record_struct() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: ord, derive
+#[derive(Partial$0Ord)]
+struct Foo {
+ bin: usize,
+}
+"#,
+ r#"
+struct Foo {
+ bin: usize,
+}
+
+impl PartialOrd for Foo {
+ $0fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
+ self.bin.partial_cmp(&other.bin)
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn add_custom_impl_partial_ord_record_struct_multi_field() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: ord, derive
+#[derive(Partial$0Ord)]
+struct Foo {
+ bin: usize,
+ bar: usize,
+ baz: usize,
+}
+"#,
+ r#"
+struct Foo {
+ bin: usize,
+ bar: usize,
+ baz: usize,
+}
+
+impl PartialOrd for Foo {
+ $0fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
+ match self.bin.partial_cmp(&other.bin) {
+ Some(core::cmp::Ordering::Equal) => {}
+ ord => return ord,
+ }
+ match self.bar.partial_cmp(&other.bar) {
+ Some(core::cmp::Ordering::Equal) => {}
+ ord => return ord,
+ }
+ self.baz.partial_cmp(&other.baz)
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn add_custom_impl_partial_ord_tuple_struct() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: ord, derive
+#[derive(Partial$0Ord)]
+struct Foo(usize, usize, usize);
+"#,
+ r#"
+struct Foo(usize, usize, usize);
+
+impl PartialOrd for Foo {
+ $0fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
+ match self.0.partial_cmp(&other.0) {
+ Some(core::cmp::Ordering::Equal) => {}
+ ord => return ord,
+ }
+ match self.1.partial_cmp(&other.1) {
+ Some(core::cmp::Ordering::Equal) => {}
+ ord => return ord,
+ }
+ self.2.partial_cmp(&other.2)
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn add_custom_impl_partial_eq_record_struct() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: eq, derive
+#[derive(Partial$0Eq)]
+struct Foo {
+ bin: usize,
+ bar: usize,
+}
+"#,
+ r#"
+struct Foo {
+ bin: usize,
+ bar: usize,
+}
+
+impl PartialEq for Foo {
+ $0fn eq(&self, other: &Self) -> bool {
+ self.bin == other.bin && self.bar == other.bar
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn add_custom_impl_partial_eq_tuple_struct() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: eq, derive
+#[derive(Partial$0Eq)]
+struct Foo(usize, usize);
+"#,
+ r#"
+struct Foo(usize, usize);
+
+impl PartialEq for Foo {
+ $0fn eq(&self, other: &Self) -> bool {
+ self.0 == other.0 && self.1 == other.1
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn add_custom_impl_partial_eq_empty_struct() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: eq, derive
+#[derive(Partial$0Eq)]
+struct Foo;
+"#,
+ r#"
+struct Foo;
+
+impl PartialEq for Foo {
+ $0fn eq(&self, other: &Self) -> bool {
+ true
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn add_custom_impl_partial_eq_enum() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: eq, derive
+#[derive(Partial$0Eq)]
+enum Foo {
+ Bar,
+ Baz,
+}
+"#,
+ r#"
+enum Foo {
+ Bar,
+ Baz,
+}
+
+impl PartialEq for Foo {
+ $0fn eq(&self, other: &Self) -> bool {
+ core::mem::discriminant(self) == core::mem::discriminant(other)
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn add_custom_impl_partial_eq_tuple_enum() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: eq, derive
+#[derive(Partial$0Eq)]
+enum Foo {
+ Bar(String),
+ Baz,
+}
+"#,
+ r#"
+enum Foo {
+ Bar(String),
+ Baz,
+}
+
+impl PartialEq for Foo {
+ $0fn eq(&self, other: &Self) -> bool {
+ match (self, other) {
+ (Self::Bar(l0), Self::Bar(r0)) => l0 == r0,
+ _ => core::mem::discriminant(self) == core::mem::discriminant(other),
+ }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn add_custom_impl_partial_eq_record_enum() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: eq, derive
+#[derive(Partial$0Eq)]
+enum Foo {
+ Bar {
+ bin: String,
+ },
+ Baz {
+ qux: String,
+ fez: String,
+ },
+ Qux {},
+ Bin,
+}
+"#,
+ r#"
+enum Foo {
+ Bar {
+ bin: String,
+ },
+ Baz {
+ qux: String,
+ fez: String,
+ },
+ Qux {},
+ Bin,
+}
+
+impl PartialEq for Foo {
+ $0fn eq(&self, other: &Self) -> bool {
+ match (self, other) {
+ (Self::Bar { bin: l_bin }, Self::Bar { bin: r_bin }) => l_bin == r_bin,
+ (Self::Baz { qux: l_qux, fez: l_fez }, Self::Baz { qux: r_qux, fez: r_fez }) => l_qux == r_qux && l_fez == r_fez,
+ _ => core::mem::discriminant(self) == core::mem::discriminant(other),
+ }
+ }
+}
+"#,
+ )
+ }
+ #[test]
+ fn add_custom_impl_all() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: derive
+mod foo {
+ pub trait Bar {
+ type Qux;
+ const Baz: usize = 42;
+ const Fez: usize;
+ fn foo();
+ fn bar() {}
+ }
+}
+
+#[derive($0Bar)]
+struct Foo {
+ bar: String,
+}
+"#,
+ r#"
+mod foo {
+ pub trait Bar {
+ type Qux;
+ const Baz: usize = 42;
+ const Fez: usize;
+ fn foo();
+ fn bar() {}
+ }
+}
+
+struct Foo {
+ bar: String,
+}
+
+impl foo::Bar for Foo {
+ $0type Qux;
+
+ const Baz: usize = 42;
+
+ const Fez: usize;
+
+ fn foo() {
+ todo!()
+ }
+}
+"#,
+ )
+ }
+ #[test]
+ fn add_custom_impl_for_unique_input_unknown() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: derive
+#[derive(Debu$0g)]
+struct Foo {
+ bar: String,
+}
+ "#,
+ r#"
+struct Foo {
+ bar: String,
+}
+
+impl Debug for Foo {
+ $0
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn add_custom_impl_for_with_visibility_modifier() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: derive
+#[derive(Debug$0)]
+pub struct Foo {
+ bar: String,
+}
+ "#,
+ r#"
+pub struct Foo {
+ bar: String,
+}
+
+impl Debug for Foo {
+ $0
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn add_custom_impl_when_multiple_inputs() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: derive
+#[derive(Display, Debug$0, Serialize)]
+struct Foo {}
+ "#,
+ r#"
+#[derive(Display, Serialize)]
+struct Foo {}
+
+impl Debug for Foo {
+ $0
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn add_custom_impl_default_generic_record_struct() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: default, derive
+#[derive(Defau$0lt)]
+struct Foo<T, U> {
+ foo: T,
+ bar: U,
+}
+"#,
+ r#"
+struct Foo<T, U> {
+ foo: T,
+ bar: U,
+}
+
+impl<T, U> Default for Foo<T, U> {
+ $0fn default() -> Self {
+ Self { foo: Default::default(), bar: Default::default() }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn add_custom_impl_clone_generic_tuple_struct_with_bounds() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: clone, derive
+#[derive(Clo$0ne)]
+struct Foo<T: Clone>(T, usize);
+"#,
+ r#"
+struct Foo<T: Clone>(T, usize);
+
+impl<T: Clone> Clone for Foo<T> {
+ $0fn clone(&self) -> Self {
+ Self(self.0.clone(), self.1.clone())
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_ignore_derive_macro_without_input() {
+ check_assist_not_applicable(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: derive
+#[derive($0)]
+struct Foo {}
+ "#,
+ )
+ }
+
+ #[test]
+ fn test_ignore_if_cursor_on_param() {
+ check_assist_not_applicable(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: derive, fmt
+#[derive$0(Debug)]
+struct Foo {}
+ "#,
+ );
+
+ check_assist_not_applicable(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: derive, fmt
+#[derive(Debug)$0]
+struct Foo {}
+ "#,
+ )
+ }
+
+ #[test]
+ fn test_ignore_if_not_derive() {
+ check_assist_not_applicable(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: derive
+#[allow(non_camel_$0case_types)]
+struct Foo {}
+ "#,
+ )
+ }
+
+ #[test]
+ fn works_at_start_of_file() {
+ check_assist_not_applicable(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: derive, fmt
+$0#[derive(Debug)]
+struct S;
+ "#,
+ );
+ }
+
+ #[test]
+ fn add_custom_impl_keep_path() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: clone, derive
+#[derive(std::fmt::Debug, Clo$0ne)]
+pub struct Foo;
+"#,
+ r#"
+#[derive(std::fmt::Debug)]
+pub struct Foo;
+
+impl Clone for Foo {
+ $0fn clone(&self) -> Self {
+ Self { }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn add_custom_impl_replace_path() {
+ check_assist(
+ replace_derive_with_manual_impl,
+ r#"
+//- minicore: fmt, derive
+#[derive(core::fmt::Deb$0ug, Clone)]
+pub struct Foo;
+"#,
+ r#"
+#[derive(Clone)]
+pub struct Foo;
+
+impl core::fmt::Debug for Foo {
+ $0fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ f.debug_struct("Foo").finish()
+ }
+}
+"#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs
new file mode 100644
index 000000000..484c27387
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs
@@ -0,0 +1,999 @@
+use std::iter::{self, successors};
+
+use either::Either;
+use ide_db::{
+ defs::NameClass,
+ syntax_helpers::node_ext::{is_pattern_cond, single_let},
+ ty_filter::TryEnum,
+ RootDatabase,
+};
+use syntax::{
+ ast::{
+ self,
+ edit::{AstNodeEdit, IndentLevel},
+ make, HasName,
+ },
+ AstNode, TextRange,
+};
+
+use crate::{
+ utils::{does_nested_pattern, does_pat_match_variant, unwrap_trivial_block},
+ AssistContext, AssistId, AssistKind, Assists,
+};
+
+// Assist: replace_if_let_with_match
+//
+// Replaces a `if let` expression with a `match` expression.
+//
+// ```
+// enum Action { Move { distance: u32 }, Stop }
+//
+// fn handle(action: Action) {
+// $0if let Action::Move { distance } = action {
+// foo(distance)
+// } else {
+// bar()
+// }
+// }
+// ```
+// ->
+// ```
+// enum Action { Move { distance: u32 }, Stop }
+//
+// fn handle(action: Action) {
+// match action {
+// Action::Move { distance } => foo(distance),
+// _ => bar(),
+// }
+// }
+// ```
+pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
+ let available_range = TextRange::new(
+ if_expr.syntax().text_range().start(),
+ if_expr.then_branch()?.syntax().text_range().start(),
+ );
+ let cursor_in_range = available_range.contains_range(ctx.selection_trimmed());
+ if !cursor_in_range {
+ return None;
+ }
+ let mut else_block = None;
+ let if_exprs = successors(Some(if_expr.clone()), |expr| match expr.else_branch()? {
+ ast::ElseBranch::IfExpr(expr) => Some(expr),
+ ast::ElseBranch::Block(block) => {
+ else_block = Some(block);
+ None
+ }
+ });
+ let scrutinee_to_be_expr = if_expr.condition()?;
+ let scrutinee_to_be_expr = match single_let(scrutinee_to_be_expr.clone()) {
+ Some(cond) => cond.expr()?,
+ None => scrutinee_to_be_expr,
+ };
+
+ let mut pat_seen = false;
+ let mut cond_bodies = Vec::new();
+ for if_expr in if_exprs {
+ let cond = if_expr.condition()?;
+ let cond = match single_let(cond.clone()) {
+ Some(let_) => {
+ let pat = let_.pat()?;
+ let expr = let_.expr()?;
+ // FIXME: If one `let` is wrapped in parentheses and the second is not,
+ // we'll exit here.
+ if scrutinee_to_be_expr.syntax().text() != expr.syntax().text() {
+ // Only if all condition expressions are equal we can merge them into a match
+ return None;
+ }
+ pat_seen = true;
+ Either::Left(pat)
+ }
+ // Multiple `let`, unsupported.
+ None if is_pattern_cond(cond.clone()) => return None,
+ None => Either::Right(cond),
+ };
+ let body = if_expr.then_branch()?;
+ cond_bodies.push((cond, body));
+ }
+
+ if !pat_seen {
+ // Don't offer turning an if (chain) without patterns into a match
+ return None;
+ }
+
+ acc.add(
+ AssistId("replace_if_let_with_match", AssistKind::RefactorRewrite),
+ "Replace if let with match",
+ available_range,
+ move |edit| {
+ let match_expr = {
+ let else_arm = make_else_arm(ctx, else_block, &cond_bodies);
+ let make_match_arm = |(pat, body): (_, ast::BlockExpr)| {
+ let body = body.reset_indent().indent(IndentLevel(1));
+ match pat {
+ Either::Left(pat) => {
+ make::match_arm(iter::once(pat), None, unwrap_trivial_block(body))
+ }
+ Either::Right(expr) => make::match_arm(
+ iter::once(make::wildcard_pat().into()),
+ Some(expr),
+ unwrap_trivial_block(body),
+ ),
+ }
+ };
+ let arms = cond_bodies.into_iter().map(make_match_arm).chain(iter::once(else_arm));
+ let match_expr = make::expr_match(scrutinee_to_be_expr, make::match_arm_list(arms));
+ match_expr.indent(IndentLevel::from_node(if_expr.syntax()))
+ };
+
+ let has_preceding_if_expr =
+ if_expr.syntax().parent().map_or(false, |it| ast::IfExpr::can_cast(it.kind()));
+ let expr = if has_preceding_if_expr {
+ // make sure we replace the `else if let ...` with a block so we don't end up with `else expr`
+ make::block_expr(None, Some(match_expr)).into()
+ } else {
+ match_expr
+ };
+ edit.replace_ast::<ast::Expr>(if_expr.into(), expr);
+ },
+ )
+}
+
+fn make_else_arm(
+ ctx: &AssistContext<'_>,
+ else_block: Option<ast::BlockExpr>,
+ conditionals: &[(Either<ast::Pat, ast::Expr>, ast::BlockExpr)],
+) -> ast::MatchArm {
+ if let Some(else_block) = else_block {
+ let pattern = if let [(Either::Left(pat), _)] = conditionals {
+ ctx.sema
+ .type_of_pat(pat)
+ .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty.adjusted()))
+ .zip(Some(pat))
+ } else {
+ None
+ };
+ let pattern = match pattern {
+ Some((it, pat)) => {
+ if does_pat_match_variant(pat, &it.sad_pattern()) {
+ it.happy_pattern_wildcard()
+ } else if does_nested_pattern(pat) {
+ make::wildcard_pat().into()
+ } else {
+ it.sad_pattern()
+ }
+ }
+ None => make::wildcard_pat().into(),
+ };
+ make::match_arm(iter::once(pattern), None, unwrap_trivial_block(else_block))
+ } else {
+ make::match_arm(iter::once(make::wildcard_pat().into()), None, make::expr_unit())
+ }
+}
+
+// Assist: replace_match_with_if_let
+//
+// Replaces a binary `match` with a wildcard pattern and no guards with an `if let` expression.
+//
+// ```
+// enum Action { Move { distance: u32 }, Stop }
+//
+// fn handle(action: Action) {
+// $0match action {
+// Action::Move { distance } => foo(distance),
+// _ => bar(),
+// }
+// }
+// ```
+// ->
+// ```
+// enum Action { Move { distance: u32 }, Stop }
+//
+// fn handle(action: Action) {
+// if let Action::Move { distance } = action {
+// foo(distance)
+// } else {
+// bar()
+// }
+// }
+// ```
+pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let match_expr: ast::MatchExpr = ctx.find_node_at_offset()?;
+
+ let mut arms = match_expr.match_arm_list()?.arms();
+ let (first_arm, second_arm) = (arms.next()?, arms.next()?);
+ if arms.next().is_some() || first_arm.guard().is_some() || second_arm.guard().is_some() {
+ return None;
+ }
+
+ let (if_let_pat, then_expr, else_expr) = pick_pattern_and_expr_order(
+ &ctx.sema,
+ first_arm.pat()?,
+ second_arm.pat()?,
+ first_arm.expr()?,
+ second_arm.expr()?,
+ )?;
+ let scrutinee = match_expr.expr()?;
+
+ let target = match_expr.syntax().text_range();
+ acc.add(
+ AssistId("replace_match_with_if_let", AssistKind::RefactorRewrite),
+ "Replace match with if let",
+ target,
+ move |edit| {
+ fn make_block_expr(expr: ast::Expr) -> ast::BlockExpr {
+ // Blocks with modifiers (unsafe, async, etc.) are parsed as BlockExpr, but are
+ // formatted without enclosing braces. If we encounter such block exprs,
+ // wrap them in another BlockExpr.
+ match expr {
+ ast::Expr::BlockExpr(block) if block.modifier().is_none() => block,
+ expr => make::block_expr(iter::empty(), Some(expr)),
+ }
+ }
+
+ let condition = make::expr_let(if_let_pat, scrutinee);
+ let then_block = make_block_expr(then_expr.reset_indent());
+ let else_expr = if is_empty_expr(&else_expr) { None } else { Some(else_expr) };
+ let if_let_expr = make::expr_if(
+ condition.into(),
+ then_block,
+ else_expr.map(make_block_expr).map(ast::ElseBranch::Block),
+ )
+ .indent(IndentLevel::from_node(match_expr.syntax()));
+
+ edit.replace_ast::<ast::Expr>(match_expr.into(), if_let_expr);
+ },
+ )
+}
+
+/// Pick the pattern for the if let condition and return the expressions for the `then` body and `else` body in that order.
+fn pick_pattern_and_expr_order(
+ sema: &hir::Semantics<'_, RootDatabase>,
+ pat: ast::Pat,
+ pat2: ast::Pat,
+ expr: ast::Expr,
+ expr2: ast::Expr,
+) -> Option<(ast::Pat, ast::Expr, ast::Expr)> {
+ let res = match (pat, pat2) {
+ (ast::Pat::WildcardPat(_), _) => return None,
+ (pat, ast::Pat::WildcardPat(_)) => (pat, expr, expr2),
+ (pat, _) if is_empty_expr(&expr2) => (pat, expr, expr2),
+ (_, pat) if is_empty_expr(&expr) => (pat, expr2, expr),
+ (pat, pat2) => match (binds_name(sema, &pat), binds_name(sema, &pat2)) {
+ (true, true) => return None,
+ (true, false) => (pat, expr, expr2),
+ (false, true) => (pat2, expr2, expr),
+ _ if is_sad_pat(sema, &pat) => (pat2, expr2, expr),
+ (false, false) => (pat, expr, expr2),
+ },
+ };
+ Some(res)
+}
+
+fn is_empty_expr(expr: &ast::Expr) -> bool {
+ match expr {
+ ast::Expr::BlockExpr(expr) => match expr.stmt_list() {
+ Some(it) => it.statements().next().is_none() && it.tail_expr().is_none(),
+ None => true,
+ },
+ ast::Expr::TupleExpr(expr) => expr.fields().next().is_none(),
+ _ => false,
+ }
+}
+
+fn binds_name(sema: &hir::Semantics<'_, RootDatabase>, pat: &ast::Pat) -> bool {
+ let binds_name_v = |pat| binds_name(sema, &pat);
+ match pat {
+ ast::Pat::IdentPat(pat) => !matches!(
+ pat.name().and_then(|name| NameClass::classify(sema, &name)),
+ Some(NameClass::ConstReference(_))
+ ),
+ ast::Pat::MacroPat(_) => true,
+ ast::Pat::OrPat(pat) => pat.pats().any(binds_name_v),
+ ast::Pat::SlicePat(pat) => pat.pats().any(binds_name_v),
+ ast::Pat::TuplePat(it) => it.fields().any(binds_name_v),
+ ast::Pat::TupleStructPat(it) => it.fields().any(binds_name_v),
+ ast::Pat::RecordPat(it) => it
+ .record_pat_field_list()
+ .map_or(false, |rpfl| rpfl.fields().flat_map(|rpf| rpf.pat()).any(binds_name_v)),
+ ast::Pat::RefPat(pat) => pat.pat().map_or(false, binds_name_v),
+ ast::Pat::BoxPat(pat) => pat.pat().map_or(false, binds_name_v),
+ ast::Pat::ParenPat(pat) => pat.pat().map_or(false, binds_name_v),
+ _ => false,
+ }
+}
+
+fn is_sad_pat(sema: &hir::Semantics<'_, RootDatabase>, pat: &ast::Pat) -> bool {
+ sema.type_of_pat(pat)
+ .and_then(|ty| TryEnum::from_ty(sema, &ty.adjusted()))
+ .map_or(false, |it| does_pat_match_variant(pat, &it.sad_pattern()))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
+
+ #[test]
+ fn test_if_let_with_match_unapplicable_for_simple_ifs() {
+ check_assist_not_applicable(
+ replace_if_let_with_match,
+ r#"
+fn main() {
+ if $0true {} else if false {} else {}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_if_let_with_match_no_else() {
+ check_assist(
+ replace_if_let_with_match,
+ r#"
+impl VariantData {
+ pub fn foo(&self) {
+ if $0let VariantData::Struct(..) = *self {
+ self.foo();
+ }
+ }
+}
+"#,
+ r#"
+impl VariantData {
+ pub fn foo(&self) {
+ match *self {
+ VariantData::Struct(..) => {
+ self.foo();
+ }
+ _ => (),
+ }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_if_let_with_match_available_range_left() {
+ check_assist_not_applicable(
+ replace_if_let_with_match,
+ r#"
+impl VariantData {
+ pub fn foo(&self) {
+ $0 if let VariantData::Struct(..) = *self {
+ self.foo();
+ }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_if_let_with_match_available_range_right() {
+ check_assist_not_applicable(
+ replace_if_let_with_match,
+ r#"
+impl VariantData {
+ pub fn foo(&self) {
+ if let VariantData::Struct(..) = *self {$0
+ self.foo();
+ }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_if_let_with_match_let_chain() {
+ check_assist_not_applicable(
+ replace_if_let_with_match,
+ r#"
+fn main() {
+ if $0let true = true && let Some(1) = None {}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_if_let_with_match_basic() {
+ check_assist(
+ replace_if_let_with_match,
+ r#"
+impl VariantData {
+ pub fn is_struct(&self) -> bool {
+ if $0let VariantData::Struct(..) = *self {
+ true
+ } else if let VariantData::Tuple(..) = *self {
+ false
+ } else if cond() {
+ true
+ } else {
+ bar(
+ 123
+ )
+ }
+ }
+}
+"#,
+ r#"
+impl VariantData {
+ pub fn is_struct(&self) -> bool {
+ match *self {
+ VariantData::Struct(..) => true,
+ VariantData::Tuple(..) => false,
+ _ if cond() => true,
+ _ => {
+ bar(
+ 123
+ )
+ }
+ }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_if_let_with_match_on_tail_if_let() {
+ check_assist(
+ replace_if_let_with_match,
+ r#"
+impl VariantData {
+ pub fn is_struct(&self) -> bool {
+ if let VariantData::Struct(..) = *self {
+ true
+ } else if let$0 VariantData::Tuple(..) = *self {
+ false
+ } else {
+ false
+ }
+ }
+}
+"#,
+ r#"
+impl VariantData {
+ pub fn is_struct(&self) -> bool {
+ if let VariantData::Struct(..) = *self {
+ true
+ } else {
+ match *self {
+ VariantData::Tuple(..) => false,
+ _ => false,
+ }
+}
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn special_case_option() {
+ check_assist(
+ replace_if_let_with_match,
+ r#"
+//- minicore: option
+fn foo(x: Option<i32>) {
+ $0if let Some(x) = x {
+ println!("{}", x)
+ } else {
+ println!("none")
+ }
+}
+"#,
+ r#"
+fn foo(x: Option<i32>) {
+ match x {
+ Some(x) => println!("{}", x),
+ None => println!("none"),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn special_case_inverted_option() {
+ check_assist(
+ replace_if_let_with_match,
+ r#"
+//- minicore: option
+fn foo(x: Option<i32>) {
+ $0if let None = x {
+ println!("none")
+ } else {
+ println!("some")
+ }
+}
+"#,
+ r#"
+fn foo(x: Option<i32>) {
+ match x {
+ None => println!("none"),
+ Some(_) => println!("some"),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn special_case_result() {
+ check_assist(
+ replace_if_let_with_match,
+ r#"
+//- minicore: result
+fn foo(x: Result<i32, ()>) {
+ $0if let Ok(x) = x {
+ println!("{}", x)
+ } else {
+ println!("none")
+ }
+}
+"#,
+ r#"
+fn foo(x: Result<i32, ()>) {
+ match x {
+ Ok(x) => println!("{}", x),
+ Err(_) => println!("none"),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn special_case_inverted_result() {
+ check_assist(
+ replace_if_let_with_match,
+ r#"
+//- minicore: result
+fn foo(x: Result<i32, ()>) {
+ $0if let Err(x) = x {
+ println!("{}", x)
+ } else {
+ println!("ok")
+ }
+}
+"#,
+ r#"
+fn foo(x: Result<i32, ()>) {
+ match x {
+ Err(x) => println!("{}", x),
+ Ok(_) => println!("ok"),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn nested_indent() {
+ check_assist(
+ replace_if_let_with_match,
+ r#"
+fn main() {
+ if true {
+ $0if let Ok(rel_path) = path.strip_prefix(root_path) {
+ let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
+ Some((*id, rel_path))
+ } else {
+ None
+ }
+ }
+}
+"#,
+ r#"
+fn main() {
+ if true {
+ match path.strip_prefix(root_path) {
+ Ok(rel_path) => {
+ let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
+ Some((*id, rel_path))
+ }
+ _ => None,
+ }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn nested_type() {
+ check_assist(
+ replace_if_let_with_match,
+ r#"
+//- minicore: result
+fn foo(x: Result<i32, ()>) {
+ let bar: Result<_, ()> = Ok(Some(1));
+ $0if let Ok(Some(_)) = bar {
+ ()
+ } else {
+ ()
+ }
+}
+"#,
+ r#"
+fn foo(x: Result<i32, ()>) {
+ let bar: Result<_, ()> = Ok(Some(1));
+ match bar {
+ Ok(Some(_)) => (),
+ _ => (),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_replace_match_with_if_let_unwraps_simple_expressions() {
+ check_assist(
+ replace_match_with_if_let,
+ r#"
+impl VariantData {
+ pub fn is_struct(&self) -> bool {
+ $0match *self {
+ VariantData::Struct(..) => true,
+ _ => false,
+ }
+ }
+} "#,
+ r#"
+impl VariantData {
+ pub fn is_struct(&self) -> bool {
+ if let VariantData::Struct(..) = *self {
+ true
+ } else {
+ false
+ }
+ }
+} "#,
+ )
+ }
+
+ #[test]
+ fn test_replace_match_with_if_let_doesnt_unwrap_multiline_expressions() {
+ check_assist(
+ replace_match_with_if_let,
+ r#"
+fn foo() {
+ $0match a {
+ VariantData::Struct(..) => {
+ bar(
+ 123
+ )
+ }
+ _ => false,
+ }
+} "#,
+ r#"
+fn foo() {
+ if let VariantData::Struct(..) = a {
+ bar(
+ 123
+ )
+ } else {
+ false
+ }
+} "#,
+ )
+ }
+
+ #[test]
+ fn replace_match_with_if_let_target() {
+ check_assist_target(
+ replace_match_with_if_let,
+ r#"
+impl VariantData {
+ pub fn is_struct(&self) -> bool {
+ $0match *self {
+ VariantData::Struct(..) => true,
+ _ => false,
+ }
+ }
+} "#,
+ r#"match *self {
+ VariantData::Struct(..) => true,
+ _ => false,
+ }"#,
+ );
+ }
+
+ #[test]
+ fn special_case_option_match_to_if_let() {
+ check_assist(
+ replace_match_with_if_let,
+ r#"
+//- minicore: option
+fn foo(x: Option<i32>) {
+ $0match x {
+ Some(x) => println!("{}", x),
+ None => println!("none"),
+ }
+}
+"#,
+ r#"
+fn foo(x: Option<i32>) {
+ if let Some(x) = x {
+ println!("{}", x)
+ } else {
+ println!("none")
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn special_case_result_match_to_if_let() {
+ check_assist(
+ replace_match_with_if_let,
+ r#"
+//- minicore: result
+fn foo(x: Result<i32, ()>) {
+ $0match x {
+ Ok(x) => println!("{}", x),
+ Err(_) => println!("none"),
+ }
+}
+"#,
+ r#"
+fn foo(x: Result<i32, ()>) {
+ if let Ok(x) = x {
+ println!("{}", x)
+ } else {
+ println!("none")
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn nested_indent_match_to_if_let() {
+ check_assist(
+ replace_match_with_if_let,
+ r#"
+fn main() {
+ if true {
+ $0match path.strip_prefix(root_path) {
+ Ok(rel_path) => {
+ let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
+ Some((*id, rel_path))
+ }
+ _ => None,
+ }
+ }
+}
+"#,
+ r#"
+fn main() {
+ if true {
+ if let Ok(rel_path) = path.strip_prefix(root_path) {
+ let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
+ Some((*id, rel_path))
+ } else {
+ None
+ }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn replace_match_with_if_let_empty_wildcard_expr() {
+ check_assist(
+ replace_match_with_if_let,
+ r#"
+fn main() {
+ $0match path.strip_prefix(root_path) {
+ Ok(rel_path) => println!("{}", rel_path),
+ _ => (),
+ }
+}
+"#,
+ r#"
+fn main() {
+ if let Ok(rel_path) = path.strip_prefix(root_path) {
+ println!("{}", rel_path)
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn replace_match_with_if_let_number_body() {
+ check_assist(
+ replace_match_with_if_let,
+ r#"
+fn main() {
+ $0match Ok(()) {
+ Ok(()) => {},
+ Err(_) => 0,
+ }
+}
+"#,
+ r#"
+fn main() {
+ if let Err(_) = Ok(()) {
+ 0
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn replace_match_with_if_let_exhaustive() {
+ check_assist(
+ replace_match_with_if_let,
+ r#"
+fn print_source(def_source: ModuleSource) {
+ match def_so$0urce {
+ ModuleSource::SourceFile(..) => { println!("source file"); }
+ ModuleSource::Module(..) => { println!("module"); }
+ }
+}
+"#,
+ r#"
+fn print_source(def_source: ModuleSource) {
+ if let ModuleSource::SourceFile(..) = def_source { println!("source file"); } else { println!("module"); }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn replace_match_with_if_let_prefer_name_bind() {
+ check_assist(
+ replace_match_with_if_let,
+ r#"
+fn foo() {
+ match $0Foo(0) {
+ Foo(_) => (),
+ Bar(bar) => println!("bar {}", bar),
+ }
+}
+"#,
+ r#"
+fn foo() {
+ if let Bar(bar) = Foo(0) {
+ println!("bar {}", bar)
+ }
+}
+"#,
+ );
+ check_assist(
+ replace_match_with_if_let,
+ r#"
+fn foo() {
+ match $0Foo(0) {
+ Bar(bar) => println!("bar {}", bar),
+ Foo(_) => (),
+ }
+}
+"#,
+ r#"
+fn foo() {
+ if let Bar(bar) = Foo(0) {
+ println!("bar {}", bar)
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn replace_match_with_if_let_prefer_nonempty_body() {
+ check_assist(
+ replace_match_with_if_let,
+ r#"
+fn foo() {
+ match $0Ok(0) {
+ Ok(value) => {},
+ Err(err) => eprintln!("{}", err),
+ }
+}
+"#,
+ r#"
+fn foo() {
+ if let Err(err) = Ok(0) {
+ eprintln!("{}", err)
+ }
+}
+"#,
+ );
+ check_assist(
+ replace_match_with_if_let,
+ r#"
+fn foo() {
+ match $0Ok(0) {
+ Err(err) => eprintln!("{}", err),
+ Ok(value) => {},
+ }
+}
+"#,
+ r#"
+fn foo() {
+ if let Err(err) = Ok(0) {
+ eprintln!("{}", err)
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn replace_match_with_if_let_rejects_double_name_bindings() {
+ check_assist_not_applicable(
+ replace_match_with_if_let,
+ r#"
+fn foo() {
+ match $0Foo(0) {
+ Foo(foo) => println!("bar {}", foo),
+ Bar(bar) => println!("bar {}", bar),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_replace_match_with_if_let_keeps_unsafe_block() {
+ check_assist(
+ replace_match_with_if_let,
+ r#"
+impl VariantData {
+ pub fn is_struct(&self) -> bool {
+ $0match *self {
+ VariantData::Struct(..) => true,
+ _ => unsafe { unreachable_unchecked() },
+ }
+ }
+} "#,
+ r#"
+impl VariantData {
+ pub fn is_struct(&self) -> bool {
+ if let VariantData::Struct(..) = *self {
+ true
+ } else {
+ unsafe { unreachable_unchecked() }
+ }
+ }
+} "#,
+ )
+ }
+
+ #[test]
+ fn test_replace_match_with_if_let_forces_else() {
+ check_assist(
+ replace_match_with_if_let,
+ r#"
+fn main() {
+ match$0 0 {
+ 0 => (),
+ _ => code(),
+ }
+}
+"#,
+ r#"
+fn main() {
+ if let 0 = 0 {
+ ()
+ } else {
+ code()
+ }
+}
+"#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_let_with_if_let.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_let_with_if_let.rs
new file mode 100644
index 000000000..c2be4593b
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_let_with_if_let.rs
@@ -0,0 +1,100 @@
+use std::iter::once;
+
+use ide_db::ty_filter::TryEnum;
+use syntax::{
+ ast::{
+ self,
+ edit::{AstNodeEdit, IndentLevel},
+ make,
+ },
+ AstNode, T,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: replace_let_with_if_let
+//
+// Replaces `let` with an `if let`.
+//
+// ```
+// # enum Option<T> { Some(T), None }
+//
+// fn main(action: Action) {
+// $0let x = compute();
+// }
+//
+// fn compute() -> Option<i32> { None }
+// ```
+// ->
+// ```
+// # enum Option<T> { Some(T), None }
+//
+// fn main(action: Action) {
+// if let Some(x) = compute() {
+// }
+// }
+//
+// fn compute() -> Option<i32> { None }
+// ```
+pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let let_kw = ctx.find_token_syntax_at_offset(T![let])?;
+ let let_stmt = let_kw.parent().and_then(ast::LetStmt::cast)?;
+ let init = let_stmt.initializer()?;
+ let original_pat = let_stmt.pat()?;
+
+ let target = let_kw.text_range();
+ acc.add(
+ AssistId("replace_let_with_if_let", AssistKind::RefactorRewrite),
+ "Replace let with if let",
+ target,
+ |edit| {
+ let ty = ctx.sema.type_of_expr(&init);
+ let happy_variant = ty
+ .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty.adjusted()))
+ .map(|it| it.happy_case());
+ let pat = match happy_variant {
+ None => original_pat,
+ Some(var_name) => {
+ make::tuple_struct_pat(make::ext::ident_path(var_name), once(original_pat))
+ .into()
+ }
+ };
+
+ let block =
+ make::ext::empty_block_expr().indent(IndentLevel::from_node(let_stmt.syntax()));
+ let if_ = make::expr_if(make::expr_let(pat, init).into(), block, None);
+ let stmt = make::expr_stmt(if_);
+
+ edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt));
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::check_assist;
+
+ use super::*;
+
+ #[test]
+ fn replace_let_unknown_enum() {
+ check_assist(
+ replace_let_with_if_let,
+ r"
+enum E<T> { X(T), Y(T) }
+
+fn main() {
+ $0let x = E::X(92);
+}
+ ",
+ r"
+enum E<T> { X(T), Y(T) }
+
+fn main() {
+ if let x = E::X(92) {
+ }
+}
+ ",
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs
new file mode 100644
index 000000000..2419fa11c
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs
@@ -0,0 +1,438 @@
+use hir::AsAssocItem;
+use ide_db::{
+ helpers::mod_path_to_ast,
+ imports::insert_use::{insert_use, ImportScope},
+};
+use syntax::{
+ ast::{self, make},
+ match_ast, ted, AstNode, SyntaxNode,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: replace_qualified_name_with_use
+//
+// Adds a use statement for a given fully-qualified name.
+//
+// ```
+// # mod std { pub mod collections { pub struct HashMap<T, U>(T, U); } }
+// fn process(map: std::collections::$0HashMap<String, String>) {}
+// ```
+// ->
+// ```
+// use std::collections::HashMap;
+//
+// # mod std { pub mod collections { pub struct HashMap<T, U>(T, U); } }
+// fn process(map: HashMap<String, String>) {}
+// ```
+pub(crate) fn replace_qualified_name_with_use(
+ acc: &mut Assists,
+ ctx: &AssistContext<'_>,
+) -> Option<()> {
+ let path: ast::Path = ctx.find_node_at_offset()?;
+ // We don't want to mess with use statements
+ if path.syntax().ancestors().find_map(ast::UseTree::cast).is_some() {
+ cov_mark::hit!(not_applicable_in_use);
+ return None;
+ }
+
+ if path.qualifier().is_none() {
+ cov_mark::hit!(dont_import_trivial_paths);
+ return None;
+ }
+
+ // only offer replacement for non assoc items
+ match ctx.sema.resolve_path(&path)? {
+ hir::PathResolution::Def(def) if def.as_assoc_item(ctx.sema.db).is_none() => (),
+ _ => return None,
+ }
+ // then search for an import for the first path segment of what we want to replace
+ // that way it is less likely that we import the item from a different location due re-exports
+ let module = match ctx.sema.resolve_path(&path.first_qualifier_or_self())? {
+ hir::PathResolution::Def(module @ hir::ModuleDef::Module(_)) => module,
+ _ => return None,
+ };
+
+ let starts_with_name_ref = !matches!(
+ path.first_segment().and_then(|it| it.kind()),
+ Some(
+ ast::PathSegmentKind::CrateKw
+ | ast::PathSegmentKind::SuperKw
+ | ast::PathSegmentKind::SelfKw
+ )
+ );
+ let path_to_qualifier = starts_with_name_ref
+ .then(|| {
+ ctx.sema.scope(path.syntax())?.module().find_use_path_prefixed(
+ ctx.sema.db,
+ module,
+ ctx.config.insert_use.prefix_kind,
+ )
+ })
+ .flatten();
+
+ let scope = ImportScope::find_insert_use_container(path.syntax(), &ctx.sema)?;
+ let target = path.syntax().text_range();
+ acc.add(
+ AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
+ "Replace qualified path with use",
+ target,
+ |builder| {
+ // Now that we've brought the name into scope, re-qualify all paths that could be
+ // affected (that is, all paths inside the node we added the `use` to).
+ let scope = match scope {
+ ImportScope::File(it) => ImportScope::File(builder.make_mut(it)),
+ ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
+ ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
+ };
+ shorten_paths(scope.as_syntax_node(), &path);
+ let path = drop_generic_args(&path);
+ // stick the found import in front of the to be replaced path
+ let path = match path_to_qualifier.and_then(|it| mod_path_to_ast(&it).qualifier()) {
+ Some(qualifier) => make::path_concat(qualifier, path),
+ None => path,
+ };
+ insert_use(&scope, path, &ctx.config.insert_use);
+ },
+ )
+}
+
+fn drop_generic_args(path: &ast::Path) -> ast::Path {
+ let path = path.clone_for_update();
+ if let Some(segment) = path.segment() {
+ if let Some(generic_args) = segment.generic_arg_list() {
+ ted::remove(generic_args.syntax());
+ }
+ }
+ path
+}
+
+/// Mutates `node` to shorten `path` in all descendants of `node`.
+fn shorten_paths(node: &SyntaxNode, path: &ast::Path) {
+ for child in node.children() {
+ match_ast! {
+ match child {
+ // Don't modify `use` items, as this can break the `use` item when injecting a new
+ // import into the use tree.
+ ast::Use(_) => continue,
+ // Don't descend into submodules, they don't have the same `use` items in scope.
+ // FIXME: This isn't true due to `super::*` imports?
+ ast::Module(_) => continue,
+ ast::Path(p) => if maybe_replace_path(p.clone(), path.clone()).is_none() {
+ shorten_paths(p.syntax(), path);
+ },
+ _ => shorten_paths(&child, path),
+ }
+ }
+ }
+}
+
+fn maybe_replace_path(path: ast::Path, target: ast::Path) -> Option<()> {
+ if !path_eq_no_generics(path.clone(), target) {
+ return None;
+ }
+
+ // Shorten `path`, leaving only its last segment.
+ if let Some(parent) = path.qualifier() {
+ ted::remove(parent.syntax());
+ }
+ if let Some(double_colon) = path.coloncolon_token() {
+ ted::remove(&double_colon);
+ }
+
+ Some(())
+}
+
+fn path_eq_no_generics(lhs: ast::Path, rhs: ast::Path) -> bool {
+ let mut lhs_curr = lhs;
+ let mut rhs_curr = rhs;
+ loop {
+ match lhs_curr.segment().zip(rhs_curr.segment()) {
+ Some((lhs, rhs))
+ if lhs.coloncolon_token().is_some() == rhs.coloncolon_token().is_some()
+ && lhs
+ .name_ref()
+ .zip(rhs.name_ref())
+ .map_or(false, |(lhs, rhs)| lhs.text() == rhs.text()) => {}
+ _ => return false,
+ }
+
+ match (lhs_curr.qualifier(), rhs_curr.qualifier()) {
+ (Some(lhs), Some(rhs)) => {
+ lhs_curr = lhs;
+ rhs_curr = rhs;
+ }
+ (None, None) => return true,
+ _ => return false,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn test_replace_already_imported() {
+ check_assist(
+ replace_qualified_name_with_use,
+ r"
+mod std { pub mod fs { pub struct Path; } }
+use std::fs;
+
+fn main() {
+ std::f$0s::Path
+}",
+ r"
+mod std { pub mod fs { pub struct Path; } }
+use std::fs;
+
+fn main() {
+ fs::Path
+}",
+ )
+ }
+
+ #[test]
+ fn test_replace_add_use_no_anchor() {
+ check_assist(
+ replace_qualified_name_with_use,
+ r"
+mod std { pub mod fs { pub struct Path; } }
+std::fs::Path$0
+ ",
+ r"
+use std::fs::Path;
+
+mod std { pub mod fs { pub struct Path; } }
+Path
+ ",
+ );
+ }
+
+ #[test]
+ fn test_replace_add_use_no_anchor_middle_segment() {
+ check_assist(
+ replace_qualified_name_with_use,
+ r"
+mod std { pub mod fs { pub struct Path; } }
+std::fs$0::Path
+ ",
+ r"
+use std::fs;
+
+mod std { pub mod fs { pub struct Path; } }
+fs::Path
+ ",
+ );
+ }
+
+ #[test]
+ fn dont_import_trivial_paths() {
+ cov_mark::check!(dont_import_trivial_paths);
+ check_assist_not_applicable(replace_qualified_name_with_use, r"impl foo$0 for () {}");
+ }
+
+ #[test]
+ fn test_replace_not_applicable_in_use() {
+ cov_mark::check!(not_applicable_in_use);
+ check_assist_not_applicable(replace_qualified_name_with_use, r"use std::fmt$0;");
+ }
+
+ #[test]
+ fn replaces_all_affected_paths() {
+ check_assist(
+ replace_qualified_name_with_use,
+ r"
+mod std { pub mod fmt { pub trait Debug {} } }
+fn main() {
+ std::fmt::Debug$0;
+ let x: std::fmt::Debug = std::fmt::Debug;
+}
+ ",
+ r"
+use std::fmt::Debug;
+
+mod std { pub mod fmt { pub trait Debug {} } }
+fn main() {
+ Debug;
+ let x: Debug = Debug;
+}
+ ",
+ );
+ }
+
+ #[test]
+ fn does_not_replace_in_submodules() {
+ check_assist(
+ replace_qualified_name_with_use,
+ r"
+mod std { pub mod fmt { pub trait Debug {} } }
+fn main() {
+ std::fmt::Debug$0;
+}
+
+mod sub {
+ fn f() {
+ std::fmt::Debug;
+ }
+}
+ ",
+ r"
+use std::fmt::Debug;
+
+mod std { pub mod fmt { pub trait Debug {} } }
+fn main() {
+ Debug;
+}
+
+mod sub {
+ fn f() {
+ std::fmt::Debug;
+ }
+}
+ ",
+ );
+ }
+
+ #[test]
+ fn does_not_replace_in_use() {
+ check_assist(
+ replace_qualified_name_with_use,
+ r"
+mod std { pub mod fmt { pub trait Display {} } }
+use std::fmt::Display;
+
+fn main() {
+ std::fmt$0;
+}
+ ",
+ r"
+mod std { pub mod fmt { pub trait Display {} } }
+use std::fmt::{Display, self};
+
+fn main() {
+ fmt;
+}
+ ",
+ );
+ }
+
+ #[test]
+ fn does_not_replace_assoc_item_path() {
+ check_assist_not_applicable(
+ replace_qualified_name_with_use,
+ r"
+pub struct Foo;
+impl Foo {
+ pub fn foo() {}
+}
+
+fn main() {
+ Foo::foo$0();
+}
+",
+ );
+ }
+
+ #[test]
+ fn replace_reuses_path_qualifier() {
+ check_assist(
+ replace_qualified_name_with_use,
+ r"
+pub mod foo {
+ pub struct Foo;
+}
+
+mod bar {
+ pub use super::foo::Foo as Bar;
+}
+
+fn main() {
+ foo::Foo$0;
+}
+",
+ r"
+use foo::Foo;
+
+pub mod foo {
+ pub struct Foo;
+}
+
+mod bar {
+ pub use super::foo::Foo as Bar;
+}
+
+fn main() {
+ Foo;
+}
+",
+ );
+ }
+
+ #[test]
+ fn replace_does_not_always_try_to_replace_by_full_item_path() {
+ check_assist(
+ replace_qualified_name_with_use,
+ r"
+use std::mem;
+
+mod std {
+ pub mod mem {
+ pub fn drop<T>(_: T) {}
+ }
+}
+
+fn main() {
+ mem::drop$0(0);
+}
+",
+ r"
+use std::mem::{self, drop};
+
+mod std {
+ pub mod mem {
+ pub fn drop<T>(_: T) {}
+ }
+}
+
+fn main() {
+ drop(0);
+}
+",
+ );
+ }
+
+ #[test]
+ fn replace_should_drop_generic_args_in_use() {
+ check_assist(
+ replace_qualified_name_with_use,
+ r"
+mod std {
+ pub mod mem {
+ pub fn drop<T>(_: T) {}
+ }
+}
+
+fn main() {
+ std::mem::drop::<usize>$0(0);
+}
+",
+ r"
+use std::mem::drop;
+
+mod std {
+ pub mod mem {
+ pub fn drop<T>(_: T) {}
+ }
+}
+
+fn main() {
+ drop::<usize>(0);
+}
+",
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_string_with_char.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_string_with_char.rs
new file mode 100644
index 000000000..decb5fb62
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_string_with_char.rs
@@ -0,0 +1,307 @@
+use syntax::{
+ ast,
+ ast::IsString,
+ AstToken,
+ SyntaxKind::{CHAR, STRING},
+ TextRange, TextSize,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: replace_string_with_char
+//
+// Replace string literal with char literal.
+//
+// ```
+// fn main() {
+// find("{$0");
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// find('{');
+// }
+// ```
+pub(crate) fn replace_string_with_char(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let token = ctx.find_token_syntax_at_offset(STRING).and_then(ast::String::cast)?;
+ let value = token.value()?;
+ let target = token.syntax().text_range();
+
+ if value.chars().take(2).count() != 1 {
+ return None;
+ }
+ let quote_offets = token.quote_offsets()?;
+
+ acc.add(
+ AssistId("replace_string_with_char", AssistKind::RefactorRewrite),
+ "Replace string with char",
+ target,
+ |edit| {
+ let (left, right) = quote_offets.quotes;
+ edit.replace(left, '\'');
+ edit.replace(right, '\'');
+ if value == "'" {
+ edit.insert(left.end(), '\\');
+ }
+ },
+ )
+}
+
+// Assist: replace_char_with_string
+//
+// Replace a char literal with a string literal.
+//
+// ```
+// fn main() {
+// find('{$0');
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// find("{");
+// }
+// ```
+pub(crate) fn replace_char_with_string(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let token = ctx.find_token_syntax_at_offset(CHAR)?;
+ let target = token.text_range();
+
+ acc.add(
+ AssistId("replace_char_with_string", AssistKind::RefactorRewrite),
+ "Replace char with string",
+ target,
+ |edit| {
+ if token.text() == "'\"'" {
+ edit.replace(token.text_range(), r#""\"""#);
+ } else {
+ let len = TextSize::of('\'');
+ edit.replace(TextRange::at(target.start(), len), '"');
+ edit.replace(TextRange::at(target.end() - len, len), '"');
+ }
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn replace_string_with_char_assist() {
+ check_assist(
+ replace_string_with_char,
+ r#"
+fn f() {
+ let s = "$0c";
+}
+"#,
+ r##"
+fn f() {
+ let s = 'c';
+}
+"##,
+ )
+ }
+
+ #[test]
+ fn replace_string_with_char_assist_with_multi_byte_char() {
+ check_assist(
+ replace_string_with_char,
+ r#"
+fn f() {
+ let s = "$0😀";
+}
+"#,
+ r##"
+fn f() {
+ let s = '😀';
+}
+"##,
+ )
+ }
+
+ #[test]
+ fn replace_string_with_char_multiple_chars() {
+ check_assist_not_applicable(
+ replace_string_with_char,
+ r#"
+fn f() {
+ let s = "$0test";
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn replace_string_with_char_works_inside_macros() {
+ check_assist(
+ replace_string_with_char,
+ r#"
+fn f() {
+ format!($0"x", 92)
+}
+"#,
+ r##"
+fn f() {
+ format!('x', 92)
+}
+"##,
+ )
+ }
+
+ #[test]
+ fn replace_string_with_char_newline() {
+ check_assist(
+ replace_string_with_char,
+ r#"
+fn f() {
+ find($0"\n");
+}
+"#,
+ r##"
+fn f() {
+ find('\n');
+}
+"##,
+ )
+ }
+
+ #[test]
+ fn replace_string_with_char_unicode_escape() {
+ check_assist(
+ replace_string_with_char,
+ r#"
+fn f() {
+ find($0"\u{7FFF}");
+}
+"#,
+ r##"
+fn f() {
+ find('\u{7FFF}');
+}
+"##,
+ )
+ }
+
+ #[test]
+ fn replace_raw_string_with_char() {
+ check_assist(
+ replace_string_with_char,
+ r##"
+fn f() {
+ $0r#"X"#
+}
+"##,
+ r##"
+fn f() {
+ 'X'
+}
+"##,
+ )
+ }
+
+ #[test]
+ fn replace_char_with_string_assist() {
+ check_assist(
+ replace_char_with_string,
+ r"
+fn f() {
+ let s = '$0c';
+}
+",
+ r#"
+fn f() {
+ let s = "c";
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn replace_char_with_string_assist_with_multi_byte_char() {
+ check_assist(
+ replace_char_with_string,
+ r"
+fn f() {
+ let s = '$0😀';
+}
+",
+ r#"
+fn f() {
+ let s = "😀";
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn replace_char_with_string_newline() {
+ check_assist(
+ replace_char_with_string,
+ r"
+fn f() {
+ find($0'\n');
+}
+",
+ r#"
+fn f() {
+ find("\n");
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn replace_char_with_string_unicode_escape() {
+ check_assist(
+ replace_char_with_string,
+ r"
+fn f() {
+ find($0'\u{7FFF}');
+}
+",
+ r#"
+fn f() {
+ find("\u{7FFF}");
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn replace_char_with_string_quote() {
+ check_assist(
+ replace_char_with_string,
+ r#"
+fn f() {
+ find($0'"');
+}
+"#,
+ r#"
+fn f() {
+ find("\"");
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn replace_string_with_char_quote() {
+ check_assist(
+ replace_string_with_char,
+ r#"
+fn f() {
+ find($0"'");
+}
+"#,
+ r#"
+fn f() {
+ find('\'');
+}
+"#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_try_expr_with_match.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_try_expr_with_match.rs
new file mode 100644
index 000000000..38fccb338
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_try_expr_with_match.rs
@@ -0,0 +1,150 @@
+use std::iter;
+
+use ide_db::{
+ assists::{AssistId, AssistKind},
+ ty_filter::TryEnum,
+};
+use syntax::{
+ ast::{
+ self,
+ edit::{AstNodeEdit, IndentLevel},
+ make,
+ },
+ AstNode, T,
+};
+
+use crate::assist_context::{AssistContext, Assists};
+
+// Assist: replace_try_expr_with_match
+//
+// Replaces a `try` expression with a `match` expression.
+//
+// ```
+// # //- minicore:option
+// fn handle() {
+// let pat = Some(true)$0?;
+// }
+// ```
+// ->
+// ```
+// fn handle() {
+// let pat = match Some(true) {
+// Some(it) => it,
+// None => return None,
+// };
+// }
+// ```
+pub(crate) fn replace_try_expr_with_match(
+ acc: &mut Assists,
+ ctx: &AssistContext<'_>,
+) -> Option<()> {
+ let qm_kw = ctx.find_token_syntax_at_offset(T![?])?;
+ let qm_kw_parent = qm_kw.parent().and_then(ast::TryExpr::cast)?;
+
+ let expr = qm_kw_parent.expr()?;
+ let expr_type_info = ctx.sema.type_of_expr(&expr)?;
+
+ let try_enum = TryEnum::from_ty(&ctx.sema, &expr_type_info.original)?;
+
+ let target = qm_kw_parent.syntax().text_range();
+ acc.add(
+ AssistId("replace_try_expr_with_match", AssistKind::RefactorRewrite),
+ "Replace try expression with match",
+ target,
+ |edit| {
+ let sad_pat = match try_enum {
+ TryEnum::Option => make::path_pat(make::ext::ident_path("None")),
+ TryEnum::Result => make::tuple_struct_pat(
+ make::ext::ident_path("Err"),
+ iter::once(make::path_pat(make::ext::ident_path("err"))),
+ )
+ .into(),
+ };
+ let sad_expr = match try_enum {
+ TryEnum::Option => {
+ make::expr_return(Some(make::expr_path(make::ext::ident_path("None"))))
+ }
+ TryEnum::Result => make::expr_return(Some(make::expr_call(
+ make::expr_path(make::ext::ident_path("Err")),
+ make::arg_list(iter::once(make::expr_path(make::ext::ident_path("err")))),
+ ))),
+ };
+
+ let happy_arm = make::match_arm(
+ iter::once(
+ try_enum.happy_pattern(make::ident_pat(false, false, make::name("it")).into()),
+ ),
+ None,
+ make::expr_path(make::ext::ident_path("it")),
+ );
+ let sad_arm = make::match_arm(iter::once(sad_pat), None, sad_expr);
+
+ let match_arm_list = make::match_arm_list([happy_arm, sad_arm]);
+
+ let expr_match = make::expr_match(expr, match_arm_list)
+ .indent(IndentLevel::from_node(qm_kw_parent.syntax()));
+ edit.replace_ast::<ast::Expr>(qm_kw_parent.into(), expr_match);
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ #[test]
+ fn test_replace_try_expr_with_match_not_applicable() {
+ check_assist_not_applicable(
+ replace_try_expr_with_match,
+ r#"
+ fn test() {
+ let pat: u32 = 25$0;
+ }
+ "#,
+ );
+ }
+
+ #[test]
+ fn test_replace_try_expr_with_match_option() {
+ check_assist(
+ replace_try_expr_with_match,
+ r#"
+//- minicore:option
+fn test() {
+ let pat = Some(true)$0?;
+}
+ "#,
+ r#"
+fn test() {
+ let pat = match Some(true) {
+ Some(it) => it,
+ None => return None,
+ };
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn test_replace_try_expr_with_match_result() {
+ check_assist(
+ replace_try_expr_with_match,
+ r#"
+//- minicore:result
+fn test() {
+ let pat = Ok(true)$0?;
+}
+ "#,
+ r#"
+fn test() {
+ let pat = match Ok(true) {
+ Ok(it) => it,
+ Err(err) => return Err(err),
+ };
+}
+ "#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_turbofish_with_explicit_type.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_turbofish_with_explicit_type.rs
new file mode 100644
index 000000000..6112e0945
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_turbofish_with_explicit_type.rs
@@ -0,0 +1,243 @@
+use syntax::{
+ ast::{Expr, GenericArg},
+ ast::{LetStmt, Type::InferType},
+ AstNode, TextRange,
+};
+
+use crate::{
+ assist_context::{AssistContext, Assists},
+ AssistId, AssistKind,
+};
+
+// Assist: replace_turbofish_with_explicit_type
+//
+// Converts `::<_>` to an explicit type assignment.
+//
+// ```
+// fn make<T>() -> T { ) }
+// fn main() {
+// let a = make$0::<i32>();
+// }
+// ```
+// ->
+// ```
+// fn make<T>() -> T { ) }
+// fn main() {
+// let a: i32 = make();
+// }
+// ```
+pub(crate) fn replace_turbofish_with_explicit_type(
+ acc: &mut Assists,
+ ctx: &AssistContext<'_>,
+) -> Option<()> {
+ let let_stmt = ctx.find_node_at_offset::<LetStmt>()?;
+
+ let initializer = let_stmt.initializer()?;
+
+ let generic_args = match &initializer {
+ Expr::MethodCallExpr(ce) => ce.generic_arg_list()?,
+ Expr::CallExpr(ce) => {
+ if let Expr::PathExpr(pe) = ce.expr()? {
+ pe.path()?.segment()?.generic_arg_list()?
+ } else {
+ cov_mark::hit!(not_applicable_if_non_path_function_call);
+ return None;
+ }
+ }
+ _ => {
+ cov_mark::hit!(not_applicable_if_non_function_call_initializer);
+ return None;
+ }
+ };
+
+ // Find range of ::<_>
+ let colon2 = generic_args.coloncolon_token()?;
+ let r_angle = generic_args.r_angle_token()?;
+ let turbofish_range = TextRange::new(colon2.text_range().start(), r_angle.text_range().end());
+
+ let turbofish_args: Vec<GenericArg> = generic_args.generic_args().into_iter().collect();
+
+ // Find type of ::<_>
+ if turbofish_args.len() != 1 {
+ cov_mark::hit!(not_applicable_if_not_single_arg);
+ return None;
+ }
+
+ // An improvement would be to check that this is correctly part of the return value of the
+ // function call, or sub in the actual return type.
+ let turbofish_type = &turbofish_args[0];
+
+ let initializer_start = initializer.syntax().text_range().start();
+ if ctx.offset() > turbofish_range.end() || ctx.offset() < initializer_start {
+ cov_mark::hit!(not_applicable_outside_turbofish);
+ return None;
+ }
+
+ if let None = let_stmt.colon_token() {
+ // If there's no colon in a let statement, then there is no explicit type.
+ // let x = fn::<...>();
+ let ident_range = let_stmt.pat()?.syntax().text_range();
+
+ return acc.add(
+ AssistId("replace_turbofish_with_explicit_type", AssistKind::RefactorRewrite),
+ "Replace turbofish with explicit type",
+ TextRange::new(initializer_start, turbofish_range.end()),
+ |builder| {
+ builder.insert(ident_range.end(), format!(": {}", turbofish_type));
+ builder.delete(turbofish_range);
+ },
+ );
+ } else if let Some(InferType(t)) = let_stmt.ty() {
+ // If there's a type inferrence underscore, we can offer to replace it with the type in
+ // the turbofish.
+ // let x: _ = fn::<...>();
+ let underscore_range = t.syntax().text_range();
+
+ return acc.add(
+ AssistId("replace_turbofish_with_explicit_type", AssistKind::RefactorRewrite),
+ "Replace `_` with turbofish type",
+ turbofish_range,
+ |builder| {
+ builder.replace(underscore_range, turbofish_type.to_string());
+ builder.delete(turbofish_range);
+ },
+ );
+ }
+
+ None
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
+
+ #[test]
+ fn replaces_turbofish_for_vec_string() {
+ check_assist(
+ replace_turbofish_with_explicit_type,
+ r#"
+fn make<T>() -> T {}
+fn main() {
+ let a = make$0::<Vec<String>>();
+}
+"#,
+ r#"
+fn make<T>() -> T {}
+fn main() {
+ let a: Vec<String> = make();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn replaces_method_calls() {
+ // foo.make() is a method call which uses a different expr in the let initializer
+ check_assist(
+ replace_turbofish_with_explicit_type,
+ r#"
+fn make<T>() -> T {}
+fn main() {
+ let a = foo.make$0::<Vec<String>>();
+}
+"#,
+ r#"
+fn make<T>() -> T {}
+fn main() {
+ let a: Vec<String> = foo.make();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn replace_turbofish_target() {
+ check_assist_target(
+ replace_turbofish_with_explicit_type,
+ r#"
+fn make<T>() -> T {}
+fn main() {
+ let a = $0make::<Vec<String>>();
+}
+"#,
+ r#"make::<Vec<String>>"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_outside_turbofish() {
+ cov_mark::check!(not_applicable_outside_turbofish);
+ check_assist_not_applicable(
+ replace_turbofish_with_explicit_type,
+ r#"
+fn make<T>() -> T {}
+fn main() {
+ let $0a = make::<Vec<String>>();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn replace_inferred_type_placeholder() {
+ check_assist(
+ replace_turbofish_with_explicit_type,
+ r#"
+fn make<T>() -> T {}
+fn main() {
+ let a: _ = make$0::<Vec<String>>();
+}
+"#,
+ r#"
+fn make<T>() -> T {}
+fn main() {
+ let a: Vec<String> = make();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_constant_initializer() {
+ cov_mark::check!(not_applicable_if_non_function_call_initializer);
+ check_assist_not_applicable(
+ replace_turbofish_with_explicit_type,
+ r#"
+fn make<T>() -> T {}
+fn main() {
+ let a = "foo"$0;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_non_path_function_call() {
+ cov_mark::check!(not_applicable_if_non_path_function_call);
+ check_assist_not_applicable(
+ replace_turbofish_with_explicit_type,
+ r#"
+fn make<T>() -> T {}
+fn main() {
+ $0let a = (|| {})();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn non_applicable_multiple_generic_args() {
+ cov_mark::check!(not_applicable_if_not_single_arg);
+ check_assist_not_applicable(
+ replace_turbofish_with_explicit_type,
+ r#"
+fn make<T>() -> T {}
+fn main() {
+ let a = make$0::<Vec<String>, i32>();
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/sort_items.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/sort_items.rs
new file mode 100644
index 000000000..a93704b39
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/sort_items.rs
@@ -0,0 +1,588 @@
+use std::cmp::Ordering;
+
+use itertools::Itertools;
+
+use syntax::{
+ ast::{self, HasName},
+ ted, AstNode, TextRange,
+};
+
+use crate::{utils::get_methods, AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: sort_items
+//
+// Sorts item members alphabetically: fields, enum variants and methods.
+//
+// ```
+// struct $0Foo$0 { second: u32, first: String }
+// ```
+// ->
+// ```
+// struct Foo { first: String, second: u32 }
+// ```
+// ---
+// ```
+// trait $0Bar$0 {
+// fn second(&self) -> u32;
+// fn first(&self) -> String;
+// }
+// ```
+// ->
+// ```
+// trait Bar {
+// fn first(&self) -> String;
+// fn second(&self) -> u32;
+// }
+// ```
+// ---
+// ```
+// struct Baz;
+// impl $0Baz$0 {
+// fn second(&self) -> u32;
+// fn first(&self) -> String;
+// }
+// ```
+// ->
+// ```
+// struct Baz;
+// impl Baz {
+// fn first(&self) -> String;
+// fn second(&self) -> u32;
+// }
+// ```
+// ---
+// There is a difference between sorting enum variants:
+//
+// ```
+// enum $0Animal$0 {
+// Dog(String, f64),
+// Cat { weight: f64, name: String },
+// }
+// ```
+// ->
+// ```
+// enum Animal {
+// Cat { weight: f64, name: String },
+// Dog(String, f64),
+// }
+// ```
+// and sorting a single enum struct variant:
+//
+// ```
+// enum Animal {
+// Dog(String, f64),
+// Cat $0{ weight: f64, name: String }$0,
+// }
+// ```
+// ->
+// ```
+// enum Animal {
+// Dog(String, f64),
+// Cat { name: String, weight: f64 },
+// }
+// ```
+pub(crate) fn sort_items(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ if ctx.has_empty_selection() {
+ cov_mark::hit!(not_applicable_if_no_selection);
+ return None;
+ }
+
+ if let Some(trait_ast) = ctx.find_node_at_offset::<ast::Trait>() {
+ add_sort_methods_assist(acc, trait_ast.assoc_item_list()?)
+ } else if let Some(impl_ast) = ctx.find_node_at_offset::<ast::Impl>() {
+ add_sort_methods_assist(acc, impl_ast.assoc_item_list()?)
+ } else if let Some(struct_ast) = ctx.find_node_at_offset::<ast::Struct>() {
+ add_sort_field_list_assist(acc, struct_ast.field_list())
+ } else if let Some(union_ast) = ctx.find_node_at_offset::<ast::Union>() {
+ add_sort_fields_assist(acc, union_ast.record_field_list()?)
+ } else if let Some(variant_ast) = ctx.find_node_at_offset::<ast::Variant>() {
+ add_sort_field_list_assist(acc, variant_ast.field_list())
+ } else if let Some(enum_struct_variant_ast) = ctx.find_node_at_offset::<ast::RecordFieldList>()
+ {
+ // should be above enum and below struct
+ add_sort_fields_assist(acc, enum_struct_variant_ast)
+ } else if let Some(enum_ast) = ctx.find_node_at_offset::<ast::Enum>() {
+ add_sort_variants_assist(acc, enum_ast.variant_list()?)
+ } else {
+ None
+ }
+}
+
+trait AddRewrite {
+ fn add_rewrite<T: AstNode>(
+ &mut self,
+ label: &str,
+ old: Vec<T>,
+ new: Vec<T>,
+ target: TextRange,
+ ) -> Option<()>;
+}
+
+impl AddRewrite for Assists {
+ fn add_rewrite<T: AstNode>(
+ &mut self,
+ label: &str,
+ old: Vec<T>,
+ new: Vec<T>,
+ target: TextRange,
+ ) -> Option<()> {
+ self.add(AssistId("sort_items", AssistKind::RefactorRewrite), label, target, |builder| {
+ let mutable: Vec<T> = old.into_iter().map(|it| builder.make_mut(it)).collect();
+ mutable
+ .into_iter()
+ .zip(new)
+ .for_each(|(old, new)| ted::replace(old.syntax(), new.clone_for_update().syntax()));
+ })
+ }
+}
+
+fn add_sort_field_list_assist(acc: &mut Assists, field_list: Option<ast::FieldList>) -> Option<()> {
+ match field_list {
+ Some(ast::FieldList::RecordFieldList(it)) => add_sort_fields_assist(acc, it),
+ _ => {
+ cov_mark::hit!(not_applicable_if_sorted_or_empty_or_single);
+ None
+ }
+ }
+}
+
+fn add_sort_methods_assist(acc: &mut Assists, item_list: ast::AssocItemList) -> Option<()> {
+ let methods = get_methods(&item_list);
+ let sorted = sort_by_name(&methods);
+
+ if methods == sorted {
+ cov_mark::hit!(not_applicable_if_sorted_or_empty_or_single);
+ return None;
+ }
+
+ acc.add_rewrite("Sort methods alphabetically", methods, sorted, item_list.syntax().text_range())
+}
+
+fn add_sort_fields_assist(
+ acc: &mut Assists,
+ record_field_list: ast::RecordFieldList,
+) -> Option<()> {
+ let fields: Vec<_> = record_field_list.fields().collect();
+ let sorted = sort_by_name(&fields);
+
+ if fields == sorted {
+ cov_mark::hit!(not_applicable_if_sorted_or_empty_or_single);
+ return None;
+ }
+
+ acc.add_rewrite(
+ "Sort fields alphabetically",
+ fields,
+ sorted,
+ record_field_list.syntax().text_range(),
+ )
+}
+
+fn add_sort_variants_assist(acc: &mut Assists, variant_list: ast::VariantList) -> Option<()> {
+ let variants: Vec<_> = variant_list.variants().collect();
+ let sorted = sort_by_name(&variants);
+
+ if variants == sorted {
+ cov_mark::hit!(not_applicable_if_sorted_or_empty_or_single);
+ return None;
+ }
+
+ acc.add_rewrite(
+ "Sort variants alphabetically",
+ variants,
+ sorted,
+ variant_list.syntax().text_range(),
+ )
+}
+
+fn sort_by_name<T: HasName + Clone>(initial: &[T]) -> Vec<T> {
+ initial
+ .iter()
+ .cloned()
+ .sorted_by(|a, b| match (a.name(), b.name()) {
+ (Some(a), Some(b)) => Ord::cmp(&a.to_string(), &b.to_string()),
+
+ // unexpected, but just in case
+ (None, None) => Ordering::Equal,
+ (None, Some(_)) => Ordering::Less,
+ (Some(_), None) => Ordering::Greater,
+ })
+ .collect()
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn not_applicable_if_no_selection() {
+ cov_mark::check!(not_applicable_if_no_selection);
+
+ check_assist_not_applicable(
+ sort_items,
+ r#"
+t$0rait Bar {
+ fn b();
+ fn a();
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn not_applicable_if_trait_empty() {
+ cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
+
+ check_assist_not_applicable(
+ sort_items,
+ r#"
+t$0rait Bar$0 {
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn not_applicable_if_impl_empty() {
+ cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
+
+ check_assist_not_applicable(
+ sort_items,
+ r#"
+struct Bar;
+$0impl Bar$0 {
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn not_applicable_if_struct_empty() {
+ cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
+
+ check_assist_not_applicable(
+ sort_items,
+ r#"
+$0struct Bar$0 ;
+ "#,
+ )
+ }
+
+ #[test]
+ fn not_applicable_if_struct_empty2() {
+ cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
+
+ check_assist_not_applicable(
+ sort_items,
+ r#"
+$0struct Bar$0 { };
+ "#,
+ )
+ }
+
+ #[test]
+ fn not_applicable_if_enum_empty() {
+ cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
+
+ check_assist_not_applicable(
+ sort_items,
+ r#"
+$0enum ZeroVariants$0 {};
+ "#,
+ )
+ }
+
+ #[test]
+ fn not_applicable_if_trait_sorted() {
+ cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
+
+ check_assist_not_applicable(
+ sort_items,
+ r#"
+t$0rait Bar$0 {
+ fn a() {}
+ fn b() {}
+ fn c() {}
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn not_applicable_if_impl_sorted() {
+ cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
+
+ check_assist_not_applicable(
+ sort_items,
+ r#"
+struct Bar;
+$0impl Bar$0 {
+ fn a() {}
+ fn b() {}
+ fn c() {}
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn not_applicable_if_struct_sorted() {
+ cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
+
+ check_assist_not_applicable(
+ sort_items,
+ r#"
+$0struct Bar$0 {
+ a: u32,
+ b: u8,
+ c: u64,
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn not_applicable_if_union_sorted() {
+ cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
+
+ check_assist_not_applicable(
+ sort_items,
+ r#"
+$0union Bar$0 {
+ a: u32,
+ b: u8,
+ c: u64,
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn not_applicable_if_enum_sorted() {
+ cov_mark::check!(not_applicable_if_sorted_or_empty_or_single);
+
+ check_assist_not_applicable(
+ sort_items,
+ r#"
+$0enum Bar$0 {
+ a,
+ b,
+ c,
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn sort_trait() {
+ check_assist(
+ sort_items,
+ r#"
+$0trait Bar$0 {
+ fn a() {
+
+ }
+
+ // comment for c
+ fn c() {}
+ fn z() {}
+ fn b() {}
+}
+ "#,
+ r#"
+trait Bar {
+ fn a() {
+
+ }
+
+ fn b() {}
+ // comment for c
+ fn c() {}
+ fn z() {}
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn sort_impl() {
+ check_assist(
+ sort_items,
+ r#"
+struct Bar;
+$0impl Bar$0 {
+ fn c() {}
+ fn a() {}
+ /// long
+ /// doc
+ /// comment
+ fn z() {}
+ fn d() {}
+}
+ "#,
+ r#"
+struct Bar;
+impl Bar {
+ fn a() {}
+ fn c() {}
+ fn d() {}
+ /// long
+ /// doc
+ /// comment
+ fn z() {}
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn sort_struct() {
+ check_assist(
+ sort_items,
+ r#"
+$0struct Bar$0 {
+ b: u8,
+ a: u32,
+ c: u64,
+}
+ "#,
+ r#"
+struct Bar {
+ a: u32,
+ b: u8,
+ c: u64,
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn sort_generic_struct_with_lifetime() {
+ check_assist(
+ sort_items,
+ r#"
+$0struct Bar<'a,$0 T> {
+ d: &'a str,
+ b: u8,
+ a: T,
+ c: u64,
+}
+ "#,
+ r#"
+struct Bar<'a, T> {
+ a: T,
+ b: u8,
+ c: u64,
+ d: &'a str,
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn sort_struct_fields_diff_len() {
+ check_assist(
+ sort_items,
+ r#"
+$0struct Bar $0{
+ aaa: u8,
+ a: usize,
+ b: u8,
+}
+ "#,
+ r#"
+struct Bar {
+ a: usize,
+ aaa: u8,
+ b: u8,
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn sort_union() {
+ check_assist(
+ sort_items,
+ r#"
+$0union Bar$0 {
+ b: u8,
+ a: u32,
+ c: u64,
+}
+ "#,
+ r#"
+union Bar {
+ a: u32,
+ b: u8,
+ c: u64,
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn sort_enum() {
+ check_assist(
+ sort_items,
+ r#"
+$0enum Bar $0{
+ d{ first: u32, second: usize},
+ b = 14,
+ a,
+ c(u32, usize),
+}
+ "#,
+ r#"
+enum Bar {
+ a,
+ b = 14,
+ c(u32, usize),
+ d{ first: u32, second: usize},
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn sort_struct_enum_variant_fields() {
+ check_assist(
+ sort_items,
+ r#"
+enum Bar {
+ d$0{ second: usize, first: u32 }$0,
+ b = 14,
+ a,
+ c(u32, usize),
+}
+ "#,
+ r#"
+enum Bar {
+ d{ first: u32, second: usize },
+ b = 14,
+ a,
+ c(u32, usize),
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn sort_struct_enum_variant() {
+ check_assist(
+ sort_items,
+ r#"
+enum Bar {
+ $0d$0{ second: usize, first: u32 },
+}
+ "#,
+ r#"
+enum Bar {
+ d{ first: u32, second: usize },
+}
+ "#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/split_import.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/split_import.rs
new file mode 100644
index 000000000..775ededec
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/split_import.rs
@@ -0,0 +1,82 @@
+use syntax::{ast, AstNode, T};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: split_import
+//
+// Wraps the tail of import into braces.
+//
+// ```
+// use std::$0collections::HashMap;
+// ```
+// ->
+// ```
+// use std::{collections::HashMap};
+// ```
+pub(crate) fn split_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let colon_colon = ctx.find_token_syntax_at_offset(T![::])?;
+ let path = ast::Path::cast(colon_colon.parent()?)?.qualifier()?;
+
+ let use_tree = path.top_path().syntax().ancestors().find_map(ast::UseTree::cast)?;
+
+ let has_errors = use_tree
+ .syntax()
+ .descendants_with_tokens()
+ .any(|it| it.kind() == syntax::SyntaxKind::ERROR);
+ let last_segment = use_tree.path().and_then(|it| it.segment());
+ if has_errors || last_segment.is_none() {
+ return None;
+ }
+
+ let target = colon_colon.text_range();
+ acc.add(AssistId("split_import", AssistKind::RefactorRewrite), "Split import", target, |edit| {
+ let use_tree = edit.make_mut(use_tree.clone());
+ let path = edit.make_mut(path);
+ use_tree.split_prefix(&path);
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
+
+ use super::*;
+
+ #[test]
+ fn test_split_import() {
+ check_assist(
+ split_import,
+ "use crate::$0db::RootDatabase;",
+ "use crate::{db::RootDatabase};",
+ )
+ }
+
+ #[test]
+ fn split_import_works_with_trees() {
+ check_assist(
+ split_import,
+ "use crate:$0:db::{RootDatabase, FileSymbol}",
+ "use crate::{db::{RootDatabase, FileSymbol}}",
+ )
+ }
+
+ #[test]
+ fn split_import_target() {
+ check_assist_target(split_import, "use crate::$0db::{RootDatabase, FileSymbol}", "::");
+ }
+
+ #[test]
+ fn issue4044() {
+ check_assist_not_applicable(split_import, "use crate::$0:::self;")
+ }
+
+ #[test]
+ fn test_empty_use() {
+ check_assist_not_applicable(
+ split_import,
+ r"
+use std::$0
+fn main() {}",
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_ignore.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_ignore.rs
new file mode 100644
index 000000000..b7d57f02b
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_ignore.rs
@@ -0,0 +1,98 @@
+use syntax::{
+ ast::{self, HasAttrs},
+ AstNode, AstToken,
+};
+
+use crate::{utils::test_related_attribute, AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: toggle_ignore
+//
+// Adds `#[ignore]` attribute to the test.
+//
+// ```
+// $0#[test]
+// fn arithmetics {
+// assert_eq!(2 + 2, 5);
+// }
+// ```
+// ->
+// ```
+// #[test]
+// #[ignore]
+// fn arithmetics {
+// assert_eq!(2 + 2, 5);
+// }
+// ```
+pub(crate) fn toggle_ignore(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let attr: ast::Attr = ctx.find_node_at_offset()?;
+ let func = attr.syntax().parent().and_then(ast::Fn::cast)?;
+ let attr = test_related_attribute(&func)?;
+
+ match has_ignore_attribute(&func) {
+ None => acc.add(
+ AssistId("toggle_ignore", AssistKind::None),
+ "Ignore this test",
+ attr.syntax().text_range(),
+ |builder| builder.insert(attr.syntax().text_range().end(), "\n#[ignore]"),
+ ),
+ Some(ignore_attr) => acc.add(
+ AssistId("toggle_ignore", AssistKind::None),
+ "Re-enable this test",
+ ignore_attr.syntax().text_range(),
+ |builder| {
+ builder.delete(ignore_attr.syntax().text_range());
+ let whitespace = ignore_attr
+ .syntax()
+ .next_sibling_or_token()
+ .and_then(|x| x.into_token())
+ .and_then(ast::Whitespace::cast);
+ if let Some(whitespace) = whitespace {
+ builder.delete(whitespace.syntax().text_range());
+ }
+ },
+ ),
+ }
+}
+
+fn has_ignore_attribute(fn_def: &ast::Fn) -> Option<ast::Attr> {
+ fn_def.attrs().find(|attr| attr.path().map(|it| it.syntax().text() == "ignore") == Some(true))
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::check_assist;
+
+ use super::*;
+
+ #[test]
+ fn test_base_case() {
+ check_assist(
+ toggle_ignore,
+ r#"
+ #[test$0]
+ fn test() {}
+ "#,
+ r#"
+ #[test]
+ #[ignore]
+ fn test() {}
+ "#,
+ )
+ }
+
+ #[test]
+ fn test_unignore() {
+ check_assist(
+ toggle_ignore,
+ r#"
+ #[test$0]
+ #[ignore]
+ fn test() {}
+ "#,
+ r#"
+ #[test]
+ fn test() {}
+ "#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unmerge_use.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unmerge_use.rs
new file mode 100644
index 000000000..3ce028e93
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unmerge_use.rs
@@ -0,0 +1,237 @@
+use syntax::{
+ ast::{self, make, HasVisibility},
+ ted::{self, Position},
+ AstNode, SyntaxKind,
+};
+
+use crate::{
+ assist_context::{AssistContext, Assists},
+ AssistId, AssistKind,
+};
+
+// Assist: unmerge_use
+//
+// Extracts single use item from use list.
+//
+// ```
+// use std::fmt::{Debug, Display$0};
+// ```
+// ->
+// ```
+// use std::fmt::{Debug};
+// use std::fmt::Display;
+// ```
+pub(crate) fn unmerge_use(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let tree: ast::UseTree = ctx.find_node_at_offset::<ast::UseTree>()?.clone_for_update();
+
+ let tree_list = tree.syntax().parent().and_then(ast::UseTreeList::cast)?;
+ if tree_list.use_trees().count() < 2 {
+ cov_mark::hit!(skip_single_use_item);
+ return None;
+ }
+
+ let use_: ast::Use = tree_list.syntax().ancestors().find_map(ast::Use::cast)?;
+ let path = resolve_full_path(&tree)?;
+
+ let old_parent_range = use_.syntax().parent()?.text_range();
+ let new_parent = use_.syntax().parent()?;
+
+ let target = tree.syntax().text_range();
+ acc.add(
+ AssistId("unmerge_use", AssistKind::RefactorRewrite),
+ "Unmerge use",
+ target,
+ |builder| {
+ let new_use = make::use_(
+ use_.visibility(),
+ make::use_tree(
+ path,
+ tree.use_tree_list(),
+ tree.rename(),
+ tree.star_token().is_some(),
+ ),
+ )
+ .clone_for_update();
+
+ tree.remove();
+ ted::insert(Position::after(use_.syntax()), new_use.syntax());
+
+ builder.replace(old_parent_range, new_parent.to_string());
+ },
+ )
+}
+
+fn resolve_full_path(tree: &ast::UseTree) -> Option<ast::Path> {
+ let paths = tree
+ .syntax()
+ .ancestors()
+ .take_while(|n| n.kind() != SyntaxKind::USE)
+ .filter_map(ast::UseTree::cast)
+ .filter_map(|t| t.path());
+
+ let final_path = paths.reduce(|prev, next| make::path_concat(next, prev))?;
+ if final_path.segment().map_or(false, |it| it.self_token().is_some()) {
+ final_path.qualifier()
+ } else {
+ Some(final_path)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn skip_single_use_item() {
+ cov_mark::check!(skip_single_use_item);
+ check_assist_not_applicable(
+ unmerge_use,
+ r"
+use std::fmt::Debug$0;
+",
+ );
+ check_assist_not_applicable(
+ unmerge_use,
+ r"
+use std::fmt::{Debug$0};
+",
+ );
+ check_assist_not_applicable(
+ unmerge_use,
+ r"
+use std::fmt::Debug as Dbg$0;
+",
+ );
+ }
+
+ #[test]
+ fn skip_single_glob_import() {
+ check_assist_not_applicable(
+ unmerge_use,
+ r"
+use std::fmt::*$0;
+",
+ );
+ }
+
+ #[test]
+ fn unmerge_use_item() {
+ check_assist(
+ unmerge_use,
+ r"
+use std::fmt::{Debug, Display$0};
+",
+ r"
+use std::fmt::{Debug};
+use std::fmt::Display;
+",
+ );
+
+ check_assist(
+ unmerge_use,
+ r"
+use std::fmt::{Debug, format$0, Display};
+",
+ r"
+use std::fmt::{Debug, Display};
+use std::fmt::format;
+",
+ );
+ }
+
+ #[test]
+ fn unmerge_glob_import() {
+ check_assist(
+ unmerge_use,
+ r"
+use std::fmt::{*$0, Display};
+",
+ r"
+use std::fmt::{Display};
+use std::fmt::*;
+",
+ );
+ }
+
+ #[test]
+ fn unmerge_renamed_use_item() {
+ check_assist(
+ unmerge_use,
+ r"
+use std::fmt::{Debug, Display as Disp$0};
+",
+ r"
+use std::fmt::{Debug};
+use std::fmt::Display as Disp;
+",
+ );
+ }
+
+ #[test]
+ fn unmerge_indented_use_item() {
+ check_assist(
+ unmerge_use,
+ r"
+mod format {
+ use std::fmt::{Debug, Display$0 as Disp, format};
+}
+",
+ r"
+mod format {
+ use std::fmt::{Debug, format};
+ use std::fmt::Display as Disp;
+}
+",
+ );
+ }
+
+ #[test]
+ fn unmerge_nested_use_item() {
+ check_assist(
+ unmerge_use,
+ r"
+use foo::bar::{baz::{qux$0, foobar}, barbaz};
+",
+ r"
+use foo::bar::{baz::{foobar}, barbaz};
+use foo::bar::baz::qux;
+",
+ );
+ check_assist(
+ unmerge_use,
+ r"
+use foo::bar::{baz$0::{qux, foobar}, barbaz};
+",
+ r"
+use foo::bar::{barbaz};
+use foo::bar::baz::{qux, foobar};
+",
+ );
+ }
+
+ #[test]
+ fn unmerge_use_item_with_visibility() {
+ check_assist(
+ unmerge_use,
+ r"
+pub use std::fmt::{Debug, Display$0};
+",
+ r"
+pub use std::fmt::{Debug};
+pub use std::fmt::Display;
+",
+ );
+ }
+
+ #[test]
+ fn unmerge_use_item_on_self() {
+ check_assist(
+ unmerge_use,
+ r"use std::process::{Command, self$0};",
+ r"use std::process::{Command};
+use std::process;",
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unnecessary_async.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unnecessary_async.rs
new file mode 100644
index 000000000..d5cd2d551
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unnecessary_async.rs
@@ -0,0 +1,257 @@
+use ide_db::{
+ assists::{AssistId, AssistKind},
+ base_db::FileId,
+ defs::Definition,
+ search::FileReference,
+ syntax_helpers::node_ext::full_path_of_name_ref,
+};
+use syntax::{
+ ast::{self, NameLike, NameRef},
+ AstNode, SyntaxKind, TextRange,
+};
+
+use crate::{AssistContext, Assists};
+
+// Assist: unnecessary_async
+//
+// Removes the `async` mark from functions which have no `.await` in their body.
+// Looks for calls to the functions and removes the `.await` on the call site.
+//
+// ```
+// pub async f$0n foo() {}
+// pub async fn bar() { foo().await }
+// ```
+// ->
+// ```
+// pub fn foo() {}
+// pub async fn bar() { foo() }
+// ```
+pub(crate) fn unnecessary_async(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let function: ast::Fn = ctx.find_node_at_offset()?;
+
+ // Do nothing if the cursor is not on the prototype. This is so that the check does not pollute
+ // when the user asks us for assists when in the middle of the function body.
+ // We consider the prototype to be anything that is before the body of the function.
+ let cursor_position = ctx.offset();
+ if cursor_position >= function.body()?.syntax().text_range().start() {
+ return None;
+ }
+ // Do nothing if the function isn't async.
+ if let None = function.async_token() {
+ return None;
+ }
+ // Do nothing if the function has an `await` expression in its body.
+ if function.body()?.syntax().descendants().find_map(ast::AwaitExpr::cast).is_some() {
+ return None;
+ }
+
+ // Remove the `async` keyword plus whitespace after it, if any.
+ let async_range = {
+ let async_token = function.async_token()?;
+ let next_token = async_token.next_token()?;
+ if matches!(next_token.kind(), SyntaxKind::WHITESPACE) {
+ TextRange::new(async_token.text_range().start(), next_token.text_range().end())
+ } else {
+ async_token.text_range()
+ }
+ };
+
+ // Otherwise, we may remove the `async` keyword.
+ acc.add(
+ AssistId("unnecessary_async", AssistKind::QuickFix),
+ "Remove unnecessary async",
+ async_range,
+ |edit| {
+ // Remove async on the function definition.
+ edit.replace(async_range, "");
+
+ // Remove all `.await`s from calls to the function we remove `async` from.
+ if let Some(fn_def) = ctx.sema.to_def(&function) {
+ for await_expr in find_all_references(ctx, &Definition::Function(fn_def))
+ // Keep only references that correspond NameRefs.
+ .filter_map(|(_, reference)| match reference.name {
+ NameLike::NameRef(nameref) => Some(nameref),
+ _ => None,
+ })
+ // Keep only references that correspond to await expressions
+ .filter_map(|nameref| find_await_expression(ctx, &nameref))
+ {
+ if let Some(await_token) = &await_expr.await_token() {
+ edit.replace(await_token.text_range(), "");
+ }
+ if let Some(dot_token) = &await_expr.dot_token() {
+ edit.replace(dot_token.text_range(), "");
+ }
+ }
+ }
+ },
+ )
+}
+
+fn find_all_references(
+ ctx: &AssistContext<'_>,
+ def: &Definition,
+) -> impl Iterator<Item = (FileId, FileReference)> {
+ def.usages(&ctx.sema).all().into_iter().flat_map(|(file_id, references)| {
+ references.into_iter().map(move |reference| (file_id, reference))
+ })
+}
+
+/// Finds the await expression for the given `NameRef`.
+/// If no await expression is found, returns None.
+fn find_await_expression(ctx: &AssistContext<'_>, nameref: &NameRef) -> Option<ast::AwaitExpr> {
+ // From the nameref, walk up the tree to the await expression.
+ let await_expr = if let Some(path) = full_path_of_name_ref(&nameref) {
+ // Function calls.
+ path.syntax()
+ .parent()
+ .and_then(ast::PathExpr::cast)?
+ .syntax()
+ .parent()
+ .and_then(ast::CallExpr::cast)?
+ .syntax()
+ .parent()
+ .and_then(ast::AwaitExpr::cast)
+ } else {
+ // Method calls.
+ nameref
+ .syntax()
+ .parent()
+ .and_then(ast::MethodCallExpr::cast)?
+ .syntax()
+ .parent()
+ .and_then(ast::AwaitExpr::cast)
+ };
+
+ ctx.sema.original_ast_node(await_expr?)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ #[test]
+ fn applies_on_empty_function() {
+ check_assist(unnecessary_async, "pub async f$0n f() {}", "pub fn f() {}")
+ }
+
+ #[test]
+ fn applies_and_removes_whitespace() {
+ check_assist(unnecessary_async, "pub async f$0n f() {}", "pub fn f() {}")
+ }
+
+ #[test]
+ fn does_not_apply_on_non_async_function() {
+ check_assist_not_applicable(unnecessary_async, "pub f$0n f() {}")
+ }
+
+ #[test]
+ fn applies_on_function_with_a_non_await_expr() {
+ check_assist(unnecessary_async, "pub async f$0n f() { f2() }", "pub fn f() { f2() }")
+ }
+
+ #[test]
+ fn does_not_apply_on_function_with_an_await_expr() {
+ check_assist_not_applicable(unnecessary_async, "pub async f$0n f() { f2().await }")
+ }
+
+ #[test]
+ fn applies_and_removes_await_on_reference() {
+ check_assist(
+ unnecessary_async,
+ r#"
+pub async fn f4() { }
+pub async f$0n f2() { }
+pub async fn f() { f2().await }
+pub async fn f3() { f2().await }"#,
+ r#"
+pub async fn f4() { }
+pub fn f2() { }
+pub async fn f() { f2() }
+pub async fn f3() { f2() }"#,
+ )
+ }
+
+ #[test]
+ fn applies_and_removes_await_from_within_module() {
+ check_assist(
+ unnecessary_async,
+ r#"
+pub async fn f4() { }
+mod a { pub async f$0n f2() { } }
+pub async fn f() { a::f2().await }
+pub async fn f3() { a::f2().await }"#,
+ r#"
+pub async fn f4() { }
+mod a { pub fn f2() { } }
+pub async fn f() { a::f2() }
+pub async fn f3() { a::f2() }"#,
+ )
+ }
+
+ #[test]
+ fn applies_and_removes_await_on_inner_await() {
+ check_assist(
+ unnecessary_async,
+ // Ensure that it is the first await on the 3rd line that is removed
+ r#"
+pub async fn f() { f2().await }
+pub async f$0n f2() -> i32 { 1 }
+pub async fn f3() { f4(f2().await).await }
+pub async fn f4(i: i32) { }"#,
+ r#"
+pub async fn f() { f2() }
+pub fn f2() -> i32 { 1 }
+pub async fn f3() { f4(f2()).await }
+pub async fn f4(i: i32) { }"#,
+ )
+ }
+
+ #[test]
+ fn applies_and_removes_await_on_outer_await() {
+ check_assist(
+ unnecessary_async,
+ // Ensure that it is the second await on the 3rd line that is removed
+ r#"
+pub async fn f() { f2().await }
+pub async f$0n f2(i: i32) { }
+pub async fn f3() { f2(f4().await).await }
+pub async fn f4() -> i32 { 1 }"#,
+ r#"
+pub async fn f() { f2() }
+pub fn f2(i: i32) { }
+pub async fn f3() { f2(f4().await) }
+pub async fn f4() -> i32 { 1 }"#,
+ )
+ }
+
+ #[test]
+ fn applies_on_method_call() {
+ check_assist(
+ unnecessary_async,
+ r#"
+pub struct S { }
+impl S { pub async f$0n f2(&self) { } }
+pub async fn f(s: &S) { s.f2().await }"#,
+ r#"
+pub struct S { }
+impl S { pub fn f2(&self) { } }
+pub async fn f(s: &S) { s.f2() }"#,
+ )
+ }
+
+ #[test]
+ fn does_not_apply_on_function_with_a_nested_await_expr() {
+ check_assist_not_applicable(
+ unnecessary_async,
+ "async f$0n f() { if true { loop { f2().await } } }",
+ )
+ }
+
+ #[test]
+ fn does_not_apply_when_not_on_prototype() {
+ check_assist_not_applicable(unnecessary_async, "pub async fn f() { $0f2() }")
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs
new file mode 100644
index 000000000..7969a4918
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs
@@ -0,0 +1,719 @@
+use syntax::{
+ ast::{
+ self,
+ edit::{AstNodeEdit, IndentLevel},
+ },
+ AstNode, SyntaxKind, TextRange, T,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: unwrap_block
+//
+// This assist removes if...else, for, while and loop control statements to just keep the body.
+//
+// ```
+// fn foo() {
+// if true {$0
+// println!("foo");
+// }
+// }
+// ```
+// ->
+// ```
+// fn foo() {
+// println!("foo");
+// }
+// ```
+pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let assist_id = AssistId("unwrap_block", AssistKind::RefactorRewrite);
+ let assist_label = "Unwrap block";
+
+ let l_curly_token = ctx.find_token_syntax_at_offset(T!['{'])?;
+ let mut block = ast::BlockExpr::cast(l_curly_token.parent_ancestors().nth(1)?)?;
+ let target = block.syntax().text_range();
+ let mut parent = block.syntax().parent()?;
+ if ast::MatchArm::can_cast(parent.kind()) {
+ parent = parent.ancestors().find(|it| ast::MatchExpr::can_cast(it.kind()))?
+ }
+
+ if matches!(parent.kind(), SyntaxKind::STMT_LIST | SyntaxKind::EXPR_STMT) {
+ return acc.add(assist_id, assist_label, target, |builder| {
+ builder.replace(block.syntax().text_range(), update_expr_string(block.to_string()));
+ });
+ }
+
+ let parent = ast::Expr::cast(parent)?;
+
+ match parent.clone() {
+ ast::Expr::ForExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::LoopExpr(_) => (),
+ ast::Expr::MatchExpr(_) => block = block.dedent(IndentLevel(1)),
+ ast::Expr::IfExpr(if_expr) => {
+ let then_branch = if_expr.then_branch()?;
+ if then_branch == block {
+ if let Some(ancestor) = if_expr.syntax().parent().and_then(ast::IfExpr::cast) {
+ // For `else if` blocks
+ let ancestor_then_branch = ancestor.then_branch()?;
+
+ return acc.add(assist_id, assist_label, target, |edit| {
+ let range_to_del_else_if = TextRange::new(
+ ancestor_then_branch.syntax().text_range().end(),
+ l_curly_token.text_range().start(),
+ );
+ let range_to_del_rest = TextRange::new(
+ then_branch.syntax().text_range().end(),
+ if_expr.syntax().text_range().end(),
+ );
+
+ edit.delete(range_to_del_rest);
+ edit.delete(range_to_del_else_if);
+ edit.replace(
+ target,
+ update_expr_string_without_newline(then_branch.to_string()),
+ );
+ });
+ }
+ } else {
+ return acc.add(assist_id, assist_label, target, |edit| {
+ let range_to_del = TextRange::new(
+ then_branch.syntax().text_range().end(),
+ l_curly_token.text_range().start(),
+ );
+
+ edit.delete(range_to_del);
+ edit.replace(target, update_expr_string_without_newline(block.to_string()));
+ });
+ }
+ }
+ _ => return None,
+ };
+
+ acc.add(assist_id, assist_label, target, |builder| {
+ builder.replace(parent.syntax().text_range(), update_expr_string(block.to_string()));
+ })
+}
+
+fn update_expr_string(expr_string: String) -> String {
+ update_expr_string_with_pat(expr_string, &[' ', '\n'])
+}
+
+fn update_expr_string_without_newline(expr_string: String) -> String {
+ update_expr_string_with_pat(expr_string, &[' '])
+}
+
+fn update_expr_string_with_pat(expr_str: String, whitespace_pat: &[char]) -> String {
+ // Remove leading whitespace, index [1..] to remove the leading '{',
+ // then continue to remove leading whitespace.
+ let expr_str =
+ expr_str.trim_start_matches(whitespace_pat)[1..].trim_start_matches(whitespace_pat);
+
+ // Remove trailing whitespace, index [..expr_str.len() - 1] to remove the trailing '}',
+ // then continue to remove trailing whitespace.
+ let expr_str = expr_str.trim_end_matches(whitespace_pat);
+ let expr_str = expr_str[..expr_str.len() - 1].trim_end_matches(whitespace_pat);
+
+ expr_str
+ .lines()
+ .map(|line| line.replacen(" ", "", 1)) // Delete indentation
+ .collect::<Vec<String>>()
+ .join("\n")
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn unwrap_tail_expr_block() {
+ check_assist(
+ unwrap_block,
+ r#"
+fn main() {
+ $0{
+ 92
+ }
+}
+"#,
+ r#"
+fn main() {
+ 92
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn unwrap_stmt_expr_block() {
+ check_assist(
+ unwrap_block,
+ r#"
+fn main() {
+ $0{
+ 92;
+ }
+ ()
+}
+"#,
+ r#"
+fn main() {
+ 92;
+ ()
+}
+"#,
+ );
+ // Pedantically, we should add an `;` here...
+ check_assist(
+ unwrap_block,
+ r#"
+fn main() {
+ $0{
+ 92
+ }
+ ()
+}
+"#,
+ r#"
+fn main() {
+ 92
+ ()
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn simple_if() {
+ check_assist(
+ unwrap_block,
+ r#"
+fn main() {
+ bar();
+ if true {$0
+ foo();
+
+ // comment
+ bar();
+ } else {
+ println!("bar");
+ }
+}
+"#,
+ r#"
+fn main() {
+ bar();
+ foo();
+
+ // comment
+ bar();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn simple_if_else() {
+ check_assist(
+ unwrap_block,
+ r#"
+fn main() {
+ bar();
+ if true {
+ foo();
+
+ // comment
+ bar();
+ } else {$0
+ println!("bar");
+ }
+}
+"#,
+ r#"
+fn main() {
+ bar();
+ if true {
+ foo();
+
+ // comment
+ bar();
+ }
+ println!("bar");
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn simple_if_else_if() {
+ check_assist(
+ unwrap_block,
+ r#"
+fn main() {
+ // bar();
+ if true {
+ println!("true");
+
+ // comment
+ // bar();
+ } else if false {$0
+ println!("bar");
+ } else {
+ println!("foo");
+ }
+}
+"#,
+ r#"
+fn main() {
+ // bar();
+ if true {
+ println!("true");
+
+ // comment
+ // bar();
+ }
+ println!("bar");
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn simple_if_else_if_nested() {
+ check_assist(
+ unwrap_block,
+ r#"
+fn main() {
+ // bar();
+ if true {
+ println!("true");
+
+ // comment
+ // bar();
+ } else if false {
+ println!("bar");
+ } else if true {$0
+ println!("foo");
+ }
+}
+"#,
+ r#"
+fn main() {
+ // bar();
+ if true {
+ println!("true");
+
+ // comment
+ // bar();
+ } else if false {
+ println!("bar");
+ }
+ println!("foo");
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn simple_if_else_if_nested_else() {
+ check_assist(
+ unwrap_block,
+ r#"
+fn main() {
+ // bar();
+ if true {
+ println!("true");
+
+ // comment
+ // bar();
+ } else if false {
+ println!("bar");
+ } else if true {
+ println!("foo");
+ } else {$0
+ println!("else");
+ }
+}
+"#,
+ r#"
+fn main() {
+ // bar();
+ if true {
+ println!("true");
+
+ // comment
+ // bar();
+ } else if false {
+ println!("bar");
+ } else if true {
+ println!("foo");
+ }
+ println!("else");
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn simple_if_else_if_nested_middle() {
+ check_assist(
+ unwrap_block,
+ r#"
+fn main() {
+ // bar();
+ if true {
+ println!("true");
+
+ // comment
+ // bar();
+ } else if false {
+ println!("bar");
+ } else if true {$0
+ println!("foo");
+ } else {
+ println!("else");
+ }
+}
+"#,
+ r#"
+fn main() {
+ // bar();
+ if true {
+ println!("true");
+
+ // comment
+ // bar();
+ } else if false {
+ println!("bar");
+ }
+ println!("foo");
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn simple_if_bad_cursor_position() {
+ check_assist_not_applicable(
+ unwrap_block,
+ r#"
+fn main() {
+ bar();$0
+ if true {
+ foo();
+
+ // comment
+ bar();
+ } else {
+ println!("bar");
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn simple_for() {
+ check_assist(
+ unwrap_block,
+ r#"
+fn main() {
+ for i in 0..5 {$0
+ if true {
+ foo();
+
+ // comment
+ bar();
+ } else {
+ println!("bar");
+ }
+ }
+}
+"#,
+ r#"
+fn main() {
+ if true {
+ foo();
+
+ // comment
+ bar();
+ } else {
+ println!("bar");
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn simple_if_in_for() {
+ check_assist(
+ unwrap_block,
+ r#"
+fn main() {
+ for i in 0..5 {
+ if true {$0
+ foo();
+
+ // comment
+ bar();
+ } else {
+ println!("bar");
+ }
+ }
+}
+"#,
+ r#"
+fn main() {
+ for i in 0..5 {
+ foo();
+
+ // comment
+ bar();
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn simple_loop() {
+ check_assist(
+ unwrap_block,
+ r#"
+fn main() {
+ loop {$0
+ if true {
+ foo();
+
+ // comment
+ bar();
+ } else {
+ println!("bar");
+ }
+ }
+}
+"#,
+ r#"
+fn main() {
+ if true {
+ foo();
+
+ // comment
+ bar();
+ } else {
+ println!("bar");
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn simple_while() {
+ check_assist(
+ unwrap_block,
+ r#"
+fn main() {
+ while true {$0
+ if true {
+ foo();
+
+ // comment
+ bar();
+ } else {
+ println!("bar");
+ }
+ }
+}
+"#,
+ r#"
+fn main() {
+ if true {
+ foo();
+
+ // comment
+ bar();
+ } else {
+ println!("bar");
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_match_arm() {
+ check_assist(
+ unwrap_block,
+ r#"
+fn main() {
+ match rel_path {
+ Ok(rel_path) => {$0
+ let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
+ Some((*id, rel_path))
+ }
+ Err(_) => None,
+ }
+}
+"#,
+ r#"
+fn main() {
+ let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
+ Some((*id, rel_path))
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn simple_if_in_while_bad_cursor_position() {
+ check_assist_not_applicable(
+ unwrap_block,
+ r#"
+fn main() {
+ while true {
+ if true {
+ foo();$0
+
+ // comment
+ bar();
+ } else {
+ println!("bar");
+ }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn simple_single_line() {
+ check_assist(
+ unwrap_block,
+ r#"
+fn main() {
+ {$0 0 }
+}
+"#,
+ r#"
+fn main() {
+ 0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn simple_nested_block() {
+ check_assist(
+ unwrap_block,
+ r#"
+fn main() {
+ $0{
+ {
+ 3
+ }
+ }
+}
+"#,
+ r#"
+fn main() {
+ {
+ 3
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn nested_single_line() {
+ check_assist(
+ unwrap_block,
+ r#"
+fn main() {
+ {$0 { println!("foo"); } }
+}
+"#,
+ r#"
+fn main() {
+ { println!("foo"); }
+}
+"#,
+ );
+
+ check_assist(
+ unwrap_block,
+ r#"
+fn main() {
+ {$0 { 0 } }
+}
+"#,
+ r#"
+fn main() {
+ { 0 }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn simple_if_single_line() {
+ check_assist(
+ unwrap_block,
+ r#"
+fn main() {
+ if true {$0 /* foo */ foo() } else { bar() /* bar */}
+}
+"#,
+ r#"
+fn main() {
+ /* foo */ foo()
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn if_single_statement() {
+ check_assist(
+ unwrap_block,
+ r#"
+fn main() {
+ if true {$0
+ return 3;
+ }
+}
+"#,
+ r#"
+fn main() {
+ return 3;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn multiple_statements() {
+ check_assist(
+ unwrap_block,
+ r#"
+fn main() -> i32 {
+ if 2 > 1 {$0
+ let a = 5;
+ return 3;
+ }
+ 5
+}
+"#,
+ r#"
+fn main() -> i32 {
+ let a = 5;
+ return 3;
+ 5
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_result_return_type.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_result_return_type.rs
new file mode 100644
index 000000000..9ef4ae047
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_result_return_type.rs
@@ -0,0 +1,1020 @@
+use ide_db::{
+ famous_defs::FamousDefs,
+ syntax_helpers::node_ext::{for_each_tail_expr, walk_expr},
+};
+use itertools::Itertools;
+use syntax::{
+ ast::{self, Expr},
+ match_ast, AstNode, TextRange, TextSize,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: unwrap_result_return_type
+//
+// Unwrap the function's return type.
+//
+// ```
+// # //- minicore: result
+// fn foo() -> Result<i32>$0 { Ok(42i32) }
+// ```
+// ->
+// ```
+// fn foo() -> i32 { 42i32 }
+// ```
+pub(crate) fn unwrap_result_return_type(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let ret_type = ctx.find_node_at_offset::<ast::RetType>()?;
+ let parent = ret_type.syntax().parent()?;
+ let body = match_ast! {
+ match parent {
+ ast::Fn(func) => func.body()?,
+ ast::ClosureExpr(closure) => match closure.body()? {
+ Expr::BlockExpr(block) => block,
+ // closures require a block when a return type is specified
+ _ => return None,
+ },
+ _ => return None,
+ }
+ };
+
+ let type_ref = &ret_type.ty()?;
+ let ty = ctx.sema.resolve_type(type_ref)?.as_adt();
+ let result_enum =
+ FamousDefs(&ctx.sema, ctx.sema.scope(type_ref.syntax())?.krate()).core_result_Result()?;
+
+ if !matches!(ty, Some(hir::Adt::Enum(ret_type)) if ret_type == result_enum) {
+ return None;
+ }
+
+ acc.add(
+ AssistId("unwrap_result_return_type", AssistKind::RefactorRewrite),
+ "Unwrap Result return type",
+ type_ref.syntax().text_range(),
+ |builder| {
+ let body = ast::Expr::BlockExpr(body);
+
+ let mut exprs_to_unwrap = Vec::new();
+ let tail_cb = &mut |e: &_| tail_cb_impl(&mut exprs_to_unwrap, e);
+ walk_expr(&body, &mut |expr| {
+ if let Expr::ReturnExpr(ret_expr) = expr {
+ if let Some(ret_expr_arg) = &ret_expr.expr() {
+ for_each_tail_expr(ret_expr_arg, tail_cb);
+ }
+ }
+ });
+ for_each_tail_expr(&body, tail_cb);
+
+ let mut is_unit_type = false;
+ if let Some((_, inner_type)) = type_ref.to_string().split_once('<') {
+ let inner_type = match inner_type.split_once(',') {
+ Some((success_inner_type, _)) => success_inner_type,
+ None => inner_type,
+ };
+ let new_ret_type = inner_type.strip_suffix('>').unwrap_or(inner_type);
+ if new_ret_type == "()" {
+ is_unit_type = true;
+ let text_range = TextRange::new(
+ ret_type.syntax().text_range().start(),
+ ret_type.syntax().text_range().end() + TextSize::from(1u32),
+ );
+ builder.delete(text_range)
+ } else {
+ builder.replace(
+ type_ref.syntax().text_range(),
+ inner_type.strip_suffix('>').unwrap_or(inner_type),
+ )
+ }
+ }
+
+ for ret_expr_arg in exprs_to_unwrap {
+ let ret_expr_str = ret_expr_arg.to_string();
+ if ret_expr_str.starts_with("Ok(") || ret_expr_str.starts_with("Err(") {
+ let arg_list = ret_expr_arg.syntax().children().find_map(ast::ArgList::cast);
+ if let Some(arg_list) = arg_list {
+ if is_unit_type {
+ match ret_expr_arg.syntax().prev_sibling_or_token() {
+ // Useful to delete the entire line without leaving trailing whitespaces
+ Some(whitespace) => {
+ let new_range = TextRange::new(
+ whitespace.text_range().start(),
+ ret_expr_arg.syntax().text_range().end(),
+ );
+ builder.delete(new_range);
+ }
+ None => {
+ builder.delete(ret_expr_arg.syntax().text_range());
+ }
+ }
+ } else {
+ builder.replace(
+ ret_expr_arg.syntax().text_range(),
+ arg_list.args().join(", "),
+ );
+ }
+ }
+ }
+ }
+ },
+ )
+}
+
+fn tail_cb_impl(acc: &mut Vec<ast::Expr>, e: &ast::Expr) {
+ match e {
+ Expr::BreakExpr(break_expr) => {
+ if let Some(break_expr_arg) = break_expr.expr() {
+ for_each_tail_expr(&break_expr_arg, &mut |e| tail_cb_impl(acc, e))
+ }
+ }
+ Expr::ReturnExpr(ret_expr) => {
+ if let Some(ret_expr_arg) = &ret_expr.expr() {
+ for_each_tail_expr(ret_expr_arg, &mut |e| tail_cb_impl(acc, e));
+ }
+ }
+ e => acc.push(e.clone()),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn unwrap_result_return_type_simple() {
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() -> Result<i3$02> {
+ let test = "test";
+ return Ok(42i32);
+}
+"#,
+ r#"
+fn foo() -> i32 {
+ let test = "test";
+ return 42i32;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_result_return_type_unit_type() {
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() -> Result<(), Box<dyn Error$0>> {
+ Ok(())
+}
+"#,
+ r#"
+fn foo() {
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_result_return_type_ending_with_parent() {
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() -> Result<i32, Box<dyn Error$0>> {
+ if true {
+ Ok(42)
+ } else {
+ foo()
+ }
+}
+"#,
+ r#"
+fn foo() -> i32 {
+ if true {
+ 42
+ } else {
+ foo()
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_return_type_break_split_tail() {
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() -> Result<i3$02, String> {
+ loop {
+ break if true {
+ Ok(1)
+ } else {
+ Ok(0)
+ };
+ }
+}
+"#,
+ r#"
+fn foo() -> i32 {
+ loop {
+ break if true {
+ 1
+ } else {
+ 0
+ };
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_result_return_type_simple_closure() {
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() {
+ || -> Result<i32$0> {
+ let test = "test";
+ return Ok(42i32);
+ };
+}
+"#,
+ r#"
+fn foo() {
+ || -> i32 {
+ let test = "test";
+ return 42i32;
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_result_return_type_simple_return_type_bad_cursor() {
+ check_assist_not_applicable(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() -> i32 {
+ let test = "test";$0
+ return 42i32;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_result_return_type_simple_return_type_bad_cursor_closure() {
+ check_assist_not_applicable(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() {
+ || -> i32 {
+ let test = "test";$0
+ return 42i32;
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_result_return_type_closure_non_block() {
+ check_assist_not_applicable(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() { || -> i$032 3; }
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_result_return_type_simple_return_type_already_not_result_std() {
+ check_assist_not_applicable(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() -> i32$0 {
+ let test = "test";
+ return 42i32;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_result_return_type_simple_return_type_already_not_result_closure() {
+ check_assist_not_applicable(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() {
+ || -> i32$0 {
+ let test = "test";
+ return 42i32;
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_result_return_type_simple_with_tail() {
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() ->$0 Result<i32> {
+ let test = "test";
+ Ok(42i32)
+}
+"#,
+ r#"
+fn foo() -> i32 {
+ let test = "test";
+ 42i32
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_result_return_type_simple_with_tail_closure() {
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() {
+ || ->$0 Result<i32, String> {
+ let test = "test";
+ Ok(42i32)
+ };
+}
+"#,
+ r#"
+fn foo() {
+ || -> i32 {
+ let test = "test";
+ 42i32
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_result_return_type_simple_with_tail_only() {
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() -> Result<i32$0> { Ok(42i32) }
+"#,
+ r#"
+fn foo() -> i32 { 42i32 }
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_result_return_type_simple_with_tail_block_like() {
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() -> Result<i32>$0 {
+ if true {
+ Ok(42i32)
+ } else {
+ Ok(24i32)
+ }
+}
+"#,
+ r#"
+fn foo() -> i32 {
+ if true {
+ 42i32
+ } else {
+ 24i32
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_result_return_type_simple_without_block_closure() {
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() {
+ || -> Result<i32, String>$0 {
+ if true {
+ Ok(42i32)
+ } else {
+ Ok(24i32)
+ }
+ };
+}
+"#,
+ r#"
+fn foo() {
+ || -> i32 {
+ if true {
+ 42i32
+ } else {
+ 24i32
+ }
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_result_return_type_simple_with_nested_if() {
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() -> Result<i32>$0 {
+ if true {
+ if false {
+ Ok(1)
+ } else {
+ Ok(2)
+ }
+ } else {
+ Ok(24i32)
+ }
+}
+"#,
+ r#"
+fn foo() -> i32 {
+ if true {
+ if false {
+ 1
+ } else {
+ 2
+ }
+ } else {
+ 24i32
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_result_return_type_simple_with_await() {
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+async fn foo() -> Result<i$032> {
+ if true {
+ if false {
+ Ok(1.await)
+ } else {
+ Ok(2.await)
+ }
+ } else {
+ Ok(24i32.await)
+ }
+}
+"#,
+ r#"
+async fn foo() -> i32 {
+ if true {
+ if false {
+ 1.await
+ } else {
+ 2.await
+ }
+ } else {
+ 24i32.await
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_result_return_type_simple_with_array() {
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() -> Result<[i32; 3]$0> { Ok([1, 2, 3]) }
+"#,
+ r#"
+fn foo() -> [i32; 3] { [1, 2, 3] }
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_result_return_type_simple_with_cast() {
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() -$0> Result<i32> {
+ if true {
+ if false {
+ Ok(1 as i32)
+ } else {
+ Ok(2 as i32)
+ }
+ } else {
+ Ok(24 as i32)
+ }
+}
+"#,
+ r#"
+fn foo() -> i32 {
+ if true {
+ if false {
+ 1 as i32
+ } else {
+ 2 as i32
+ }
+ } else {
+ 24 as i32
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_result_return_type_simple_with_tail_block_like_match() {
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() -> Result<i32$0> {
+ let my_var = 5;
+ match my_var {
+ 5 => Ok(42i32),
+ _ => Ok(24i32),
+ }
+}
+"#,
+ r#"
+fn foo() -> i32 {
+ let my_var = 5;
+ match my_var {
+ 5 => 42i32,
+ _ => 24i32,
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_result_return_type_simple_with_loop_with_tail() {
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() -> Result<i32$0> {
+ let my_var = 5;
+ loop {
+ println!("test");
+ 5
+ }
+ Ok(my_var)
+}
+"#,
+ r#"
+fn foo() -> i32 {
+ let my_var = 5;
+ loop {
+ println!("test");
+ 5
+ }
+ my_var
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_result_return_type_simple_with_loop_in_let_stmt() {
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() -> Result<i32$0> {
+ let my_var = let x = loop {
+ break 1;
+ };
+ Ok(my_var)
+}
+"#,
+ r#"
+fn foo() -> i32 {
+ let my_var = let x = loop {
+ break 1;
+ };
+ my_var
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_result_return_type_simple_with_tail_block_like_match_return_expr() {
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() -> Result<i32>$0 {
+ let my_var = 5;
+ let res = match my_var {
+ 5 => 42i32,
+ _ => return Ok(24i32),
+ };
+ Ok(res)
+}
+"#,
+ r#"
+fn foo() -> i32 {
+ let my_var = 5;
+ let res = match my_var {
+ 5 => 42i32,
+ _ => return 24i32,
+ };
+ res
+}
+"#,
+ );
+
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() -> Result<i32$0> {
+ let my_var = 5;
+ let res = if my_var == 5 {
+ 42i32
+ } else {
+ return Ok(24i32);
+ };
+ Ok(res)
+}
+"#,
+ r#"
+fn foo() -> i32 {
+ let my_var = 5;
+ let res = if my_var == 5 {
+ 42i32
+ } else {
+ return 24i32;
+ };
+ res
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_result_return_type_simple_with_tail_block_like_match_deeper() {
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() -> Result<i32$0> {
+ let my_var = 5;
+ match my_var {
+ 5 => {
+ if true {
+ Ok(42i32)
+ } else {
+ Ok(25i32)
+ }
+ },
+ _ => {
+ let test = "test";
+ if test == "test" {
+ return Ok(bar());
+ }
+ Ok(53i32)
+ },
+ }
+}
+"#,
+ r#"
+fn foo() -> i32 {
+ let my_var = 5;
+ match my_var {
+ 5 => {
+ if true {
+ 42i32
+ } else {
+ 25i32
+ }
+ },
+ _ => {
+ let test = "test";
+ if test == "test" {
+ return bar();
+ }
+ 53i32
+ },
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_result_return_type_simple_with_tail_block_like_early_return() {
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() -> Result<i32$0> {
+ let test = "test";
+ if test == "test" {
+ return Ok(24i32);
+ }
+ Ok(53i32)
+}
+"#,
+ r#"
+fn foo() -> i32 {
+ let test = "test";
+ if test == "test" {
+ return 24i32;
+ }
+ 53i32
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_result_return_type_simple_with_closure() {
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo(the_field: u32) -> Result<u32$0> {
+ let true_closure = || { return true; };
+ if the_field < 5 {
+ let mut i = 0;
+ if true_closure() {
+ return Ok(99);
+ } else {
+ return Ok(0);
+ }
+ }
+ Ok(the_field)
+}
+"#,
+ r#"
+fn foo(the_field: u32) -> u32 {
+ let true_closure = || { return true; };
+ if the_field < 5 {
+ let mut i = 0;
+ if true_closure() {
+ return 99;
+ } else {
+ return 0;
+ }
+ }
+ the_field
+}
+"#,
+ );
+
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo(the_field: u32) -> Result<u32$0> {
+ let true_closure = || {
+ return true;
+ };
+ if the_field < 5 {
+ let mut i = 0;
+
+
+ if true_closure() {
+ return Ok(99);
+ } else {
+ return Ok(0);
+ }
+ }
+ let t = None;
+
+ Ok(t.unwrap_or_else(|| the_field))
+}
+"#,
+ r#"
+fn foo(the_field: u32) -> u32 {
+ let true_closure = || {
+ return true;
+ };
+ if the_field < 5 {
+ let mut i = 0;
+
+
+ if true_closure() {
+ return 99;
+ } else {
+ return 0;
+ }
+ }
+ let t = None;
+
+ t.unwrap_or_else(|| the_field)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_result_return_type_simple_with_weird_forms() {
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo() -> Result<i32$0> {
+ let test = "test";
+ if test == "test" {
+ return Ok(24i32);
+ }
+ let mut i = 0;
+ loop {
+ if i == 1 {
+ break Ok(55);
+ }
+ i += 1;
+ }
+}
+"#,
+ r#"
+fn foo() -> i32 {
+ let test = "test";
+ if test == "test" {
+ return 24i32;
+ }
+ let mut i = 0;
+ loop {
+ if i == 1 {
+ break 55;
+ }
+ i += 1;
+ }
+}
+"#,
+ );
+
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo(the_field: u32) -> Result<u32$0> {
+ if the_field < 5 {
+ let mut i = 0;
+ loop {
+ if i > 5 {
+ return Ok(55u32);
+ }
+ i += 3;
+ }
+ match i {
+ 5 => return Ok(99),
+ _ => return Ok(0),
+ };
+ }
+ Ok(the_field)
+}
+"#,
+ r#"
+fn foo(the_field: u32) -> u32 {
+ if the_field < 5 {
+ let mut i = 0;
+ loop {
+ if i > 5 {
+ return 55u32;
+ }
+ i += 3;
+ }
+ match i {
+ 5 => return 99,
+ _ => return 0,
+ };
+ }
+ the_field
+}
+"#,
+ );
+
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo(the_field: u32) -> Result<u32$0> {
+ if the_field < 5 {
+ let mut i = 0;
+ match i {
+ 5 => return Ok(99),
+ _ => return Ok(0),
+ }
+ }
+ Ok(the_field)
+}
+"#,
+ r#"
+fn foo(the_field: u32) -> u32 {
+ if the_field < 5 {
+ let mut i = 0;
+ match i {
+ 5 => return 99,
+ _ => return 0,
+ }
+ }
+ the_field
+}
+"#,
+ );
+
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo(the_field: u32) -> Result<u32$0> {
+ if the_field < 5 {
+ let mut i = 0;
+ if i == 5 {
+ return Ok(99)
+ } else {
+ return Ok(0)
+ }
+ }
+ Ok(the_field)
+}
+"#,
+ r#"
+fn foo(the_field: u32) -> u32 {
+ if the_field < 5 {
+ let mut i = 0;
+ if i == 5 {
+ return 99
+ } else {
+ return 0
+ }
+ }
+ the_field
+}
+"#,
+ );
+
+ check_assist(
+ unwrap_result_return_type,
+ r#"
+//- minicore: result
+fn foo(the_field: u32) -> Result<u3$02> {
+ if the_field < 5 {
+ let mut i = 0;
+ if i == 5 {
+ return Ok(99);
+ } else {
+ return Ok(0);
+ }
+ }
+ Ok(the_field)
+}
+"#,
+ r#"
+fn foo(the_field: u32) -> u32 {
+ if the_field < 5 {
+ let mut i = 0;
+ if i == 5 {
+ return 99;
+ } else {
+ return 0;
+ }
+ }
+ the_field
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_return_type_in_result.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_return_type_in_result.rs
new file mode 100644
index 000000000..83446387d
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_return_type_in_result.rs
@@ -0,0 +1,980 @@
+use std::iter;
+
+use ide_db::{
+ famous_defs::FamousDefs,
+ syntax_helpers::node_ext::{for_each_tail_expr, walk_expr},
+};
+use syntax::{
+ ast::{self, make, Expr},
+ match_ast, AstNode,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: wrap_return_type_in_result
+//
+// Wrap the function's return type into Result.
+//
+// ```
+// # //- minicore: result
+// fn foo() -> i32$0 { 42i32 }
+// ```
+// ->
+// ```
+// fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }
+// ```
+pub(crate) fn wrap_return_type_in_result(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let ret_type = ctx.find_node_at_offset::<ast::RetType>()?;
+ let parent = ret_type.syntax().parent()?;
+ let body = match_ast! {
+ match parent {
+ ast::Fn(func) => func.body()?,
+ ast::ClosureExpr(closure) => match closure.body()? {
+ Expr::BlockExpr(block) => block,
+ // closures require a block when a return type is specified
+ _ => return None,
+ },
+ _ => return None,
+ }
+ };
+
+ let type_ref = &ret_type.ty()?;
+ let ty = ctx.sema.resolve_type(type_ref)?.as_adt();
+ let result_enum =
+ FamousDefs(&ctx.sema, ctx.sema.scope(type_ref.syntax())?.krate()).core_result_Result()?;
+
+ if matches!(ty, Some(hir::Adt::Enum(ret_type)) if ret_type == result_enum) {
+ cov_mark::hit!(wrap_return_type_in_result_simple_return_type_already_result);
+ return None;
+ }
+
+ acc.add(
+ AssistId("wrap_return_type_in_result", AssistKind::RefactorRewrite),
+ "Wrap return type in Result",
+ type_ref.syntax().text_range(),
+ |builder| {
+ let body = ast::Expr::BlockExpr(body);
+
+ let mut exprs_to_wrap = Vec::new();
+ let tail_cb = &mut |e: &_| tail_cb_impl(&mut exprs_to_wrap, e);
+ walk_expr(&body, &mut |expr| {
+ if let Expr::ReturnExpr(ret_expr) = expr {
+ if let Some(ret_expr_arg) = &ret_expr.expr() {
+ for_each_tail_expr(ret_expr_arg, tail_cb);
+ }
+ }
+ });
+ for_each_tail_expr(&body, tail_cb);
+
+ for ret_expr_arg in exprs_to_wrap {
+ let ok_wrapped = make::expr_call(
+ make::expr_path(make::ext::ident_path("Ok")),
+ make::arg_list(iter::once(ret_expr_arg.clone())),
+ );
+ builder.replace_ast(ret_expr_arg, ok_wrapped);
+ }
+
+ match ctx.config.snippet_cap {
+ Some(cap) => {
+ let snippet = format!("Result<{}, ${{0:_}}>", type_ref);
+ builder.replace_snippet(cap, type_ref.syntax().text_range(), snippet)
+ }
+ None => builder
+ .replace(type_ref.syntax().text_range(), format!("Result<{}, _>", type_ref)),
+ }
+ },
+ )
+}
+
+fn tail_cb_impl(acc: &mut Vec<ast::Expr>, e: &ast::Expr) {
+ match e {
+ Expr::BreakExpr(break_expr) => {
+ if let Some(break_expr_arg) = break_expr.expr() {
+ for_each_tail_expr(&break_expr_arg, &mut |e| tail_cb_impl(acc, e))
+ }
+ }
+ Expr::ReturnExpr(ret_expr) => {
+ if let Some(ret_expr_arg) = &ret_expr.expr() {
+ for_each_tail_expr(ret_expr_arg, &mut |e| tail_cb_impl(acc, e));
+ }
+ }
+ e => acc.push(e.clone()),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn wrap_return_type_in_result_simple() {
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() -> i3$02 {
+ let test = "test";
+ return 42i32;
+}
+"#,
+ r#"
+fn foo() -> Result<i32, ${0:_}> {
+ let test = "test";
+ return Ok(42i32);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_break_split_tail() {
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() -> i3$02 {
+ loop {
+ break if true {
+ 1
+ } else {
+ 0
+ };
+ }
+}
+"#,
+ r#"
+fn foo() -> Result<i32, ${0:_}> {
+ loop {
+ break if true {
+ Ok(1)
+ } else {
+ Ok(0)
+ };
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_in_result_simple_closure() {
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() {
+ || -> i32$0 {
+ let test = "test";
+ return 42i32;
+ };
+}
+"#,
+ r#"
+fn foo() {
+ || -> Result<i32, ${0:_}> {
+ let test = "test";
+ return Ok(42i32);
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_in_result_simple_return_type_bad_cursor() {
+ check_assist_not_applicable(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() -> i32 {
+ let test = "test";$0
+ return 42i32;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_in_result_simple_return_type_bad_cursor_closure() {
+ check_assist_not_applicable(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() {
+ || -> i32 {
+ let test = "test";$0
+ return 42i32;
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_in_result_closure_non_block() {
+ check_assist_not_applicable(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() { || -> i$032 3; }
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_in_result_simple_return_type_already_result_std() {
+ check_assist_not_applicable(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() -> core::result::Result<i32$0, String> {
+ let test = "test";
+ return 42i32;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_in_result_simple_return_type_already_result() {
+ cov_mark::check!(wrap_return_type_in_result_simple_return_type_already_result);
+ check_assist_not_applicable(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() -> Result<i32$0, String> {
+ let test = "test";
+ return 42i32;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_in_result_simple_return_type_already_result_closure() {
+ check_assist_not_applicable(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() {
+ || -> Result<i32$0, String> {
+ let test = "test";
+ return 42i32;
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_in_result_simple_with_cursor() {
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() -> $0i32 {
+ let test = "test";
+ return 42i32;
+}
+"#,
+ r#"
+fn foo() -> Result<i32, ${0:_}> {
+ let test = "test";
+ return Ok(42i32);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_in_result_simple_with_tail() {
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() ->$0 i32 {
+ let test = "test";
+ 42i32
+}
+"#,
+ r#"
+fn foo() -> Result<i32, ${0:_}> {
+ let test = "test";
+ Ok(42i32)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_in_result_simple_with_tail_closure() {
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() {
+ || ->$0 i32 {
+ let test = "test";
+ 42i32
+ };
+}
+"#,
+ r#"
+fn foo() {
+ || -> Result<i32, ${0:_}> {
+ let test = "test";
+ Ok(42i32)
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_in_result_simple_with_tail_only() {
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() -> i32$0 { 42i32 }
+"#,
+ r#"
+fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_in_result_simple_with_tail_block_like() {
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() -> i32$0 {
+ if true {
+ 42i32
+ } else {
+ 24i32
+ }
+}
+"#,
+ r#"
+fn foo() -> Result<i32, ${0:_}> {
+ if true {
+ Ok(42i32)
+ } else {
+ Ok(24i32)
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_in_result_simple_without_block_closure() {
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() {
+ || -> i32$0 {
+ if true {
+ 42i32
+ } else {
+ 24i32
+ }
+ };
+}
+"#,
+ r#"
+fn foo() {
+ || -> Result<i32, ${0:_}> {
+ if true {
+ Ok(42i32)
+ } else {
+ Ok(24i32)
+ }
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_in_result_simple_with_nested_if() {
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() -> i32$0 {
+ if true {
+ if false {
+ 1
+ } else {
+ 2
+ }
+ } else {
+ 24i32
+ }
+}
+"#,
+ r#"
+fn foo() -> Result<i32, ${0:_}> {
+ if true {
+ if false {
+ Ok(1)
+ } else {
+ Ok(2)
+ }
+ } else {
+ Ok(24i32)
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_in_result_simple_with_await() {
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+async fn foo() -> i$032 {
+ if true {
+ if false {
+ 1.await
+ } else {
+ 2.await
+ }
+ } else {
+ 24i32.await
+ }
+}
+"#,
+ r#"
+async fn foo() -> Result<i32, ${0:_}> {
+ if true {
+ if false {
+ Ok(1.await)
+ } else {
+ Ok(2.await)
+ }
+ } else {
+ Ok(24i32.await)
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_in_result_simple_with_array() {
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() -> [i32;$0 3] { [1, 2, 3] }
+"#,
+ r#"
+fn foo() -> Result<[i32; 3], ${0:_}> { Ok([1, 2, 3]) }
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_in_result_simple_with_cast() {
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() -$0> i32 {
+ if true {
+ if false {
+ 1 as i32
+ } else {
+ 2 as i32
+ }
+ } else {
+ 24 as i32
+ }
+}
+"#,
+ r#"
+fn foo() -> Result<i32, ${0:_}> {
+ if true {
+ if false {
+ Ok(1 as i32)
+ } else {
+ Ok(2 as i32)
+ }
+ } else {
+ Ok(24 as i32)
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_in_result_simple_with_tail_block_like_match() {
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() -> i32$0 {
+ let my_var = 5;
+ match my_var {
+ 5 => 42i32,
+ _ => 24i32,
+ }
+}
+"#,
+ r#"
+fn foo() -> Result<i32, ${0:_}> {
+ let my_var = 5;
+ match my_var {
+ 5 => Ok(42i32),
+ _ => Ok(24i32),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_in_result_simple_with_loop_with_tail() {
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() -> i32$0 {
+ let my_var = 5;
+ loop {
+ println!("test");
+ 5
+ }
+ my_var
+}
+"#,
+ r#"
+fn foo() -> Result<i32, ${0:_}> {
+ let my_var = 5;
+ loop {
+ println!("test");
+ 5
+ }
+ Ok(my_var)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_in_result_simple_with_loop_in_let_stmt() {
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() -> i32$0 {
+ let my_var = let x = loop {
+ break 1;
+ };
+ my_var
+}
+"#,
+ r#"
+fn foo() -> Result<i32, ${0:_}> {
+ let my_var = let x = loop {
+ break 1;
+ };
+ Ok(my_var)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_in_result_simple_with_tail_block_like_match_return_expr() {
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() -> i32$0 {
+ let my_var = 5;
+ let res = match my_var {
+ 5 => 42i32,
+ _ => return 24i32,
+ };
+ res
+}
+"#,
+ r#"
+fn foo() -> Result<i32, ${0:_}> {
+ let my_var = 5;
+ let res = match my_var {
+ 5 => 42i32,
+ _ => return Ok(24i32),
+ };
+ Ok(res)
+}
+"#,
+ );
+
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() -> i32$0 {
+ let my_var = 5;
+ let res = if my_var == 5 {
+ 42i32
+ } else {
+ return 24i32;
+ };
+ res
+}
+"#,
+ r#"
+fn foo() -> Result<i32, ${0:_}> {
+ let my_var = 5;
+ let res = if my_var == 5 {
+ 42i32
+ } else {
+ return Ok(24i32);
+ };
+ Ok(res)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_in_result_simple_with_tail_block_like_match_deeper() {
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() -> i32$0 {
+ let my_var = 5;
+ match my_var {
+ 5 => {
+ if true {
+ 42i32
+ } else {
+ 25i32
+ }
+ },
+ _ => {
+ let test = "test";
+ if test == "test" {
+ return bar();
+ }
+ 53i32
+ },
+ }
+}
+"#,
+ r#"
+fn foo() -> Result<i32, ${0:_}> {
+ let my_var = 5;
+ match my_var {
+ 5 => {
+ if true {
+ Ok(42i32)
+ } else {
+ Ok(25i32)
+ }
+ },
+ _ => {
+ let test = "test";
+ if test == "test" {
+ return Ok(bar());
+ }
+ Ok(53i32)
+ },
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_in_result_simple_with_tail_block_like_early_return() {
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() -> i$032 {
+ let test = "test";
+ if test == "test" {
+ return 24i32;
+ }
+ 53i32
+}
+"#,
+ r#"
+fn foo() -> Result<i32, ${0:_}> {
+ let test = "test";
+ if test == "test" {
+ return Ok(24i32);
+ }
+ Ok(53i32)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_in_result_simple_with_closure() {
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo(the_field: u32) ->$0 u32 {
+ let true_closure = || { return true; };
+ if the_field < 5 {
+ let mut i = 0;
+ if true_closure() {
+ return 99;
+ } else {
+ return 0;
+ }
+ }
+ the_field
+}
+"#,
+ r#"
+fn foo(the_field: u32) -> Result<u32, ${0:_}> {
+ let true_closure = || { return true; };
+ if the_field < 5 {
+ let mut i = 0;
+ if true_closure() {
+ return Ok(99);
+ } else {
+ return Ok(0);
+ }
+ }
+ Ok(the_field)
+}
+"#,
+ );
+
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo(the_field: u32) -> u32$0 {
+ let true_closure = || {
+ return true;
+ };
+ if the_field < 5 {
+ let mut i = 0;
+
+
+ if true_closure() {
+ return 99;
+ } else {
+ return 0;
+ }
+ }
+ let t = None;
+
+ t.unwrap_or_else(|| the_field)
+}
+"#,
+ r#"
+fn foo(the_field: u32) -> Result<u32, ${0:_}> {
+ let true_closure = || {
+ return true;
+ };
+ if the_field < 5 {
+ let mut i = 0;
+
+
+ if true_closure() {
+ return Ok(99);
+ } else {
+ return Ok(0);
+ }
+ }
+ let t = None;
+
+ Ok(t.unwrap_or_else(|| the_field))
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wrap_return_type_in_result_simple_with_weird_forms() {
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo() -> i32$0 {
+ let test = "test";
+ if test == "test" {
+ return 24i32;
+ }
+ let mut i = 0;
+ loop {
+ if i == 1 {
+ break 55;
+ }
+ i += 1;
+ }
+}
+"#,
+ r#"
+fn foo() -> Result<i32, ${0:_}> {
+ let test = "test";
+ if test == "test" {
+ return Ok(24i32);
+ }
+ let mut i = 0;
+ loop {
+ if i == 1 {
+ break Ok(55);
+ }
+ i += 1;
+ }
+}
+"#,
+ );
+
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo(the_field: u32) -> u32$0 {
+ if the_field < 5 {
+ let mut i = 0;
+ loop {
+ if i > 5 {
+ return 55u32;
+ }
+ i += 3;
+ }
+ match i {
+ 5 => return 99,
+ _ => return 0,
+ };
+ }
+ the_field
+}
+"#,
+ r#"
+fn foo(the_field: u32) -> Result<u32, ${0:_}> {
+ if the_field < 5 {
+ let mut i = 0;
+ loop {
+ if i > 5 {
+ return Ok(55u32);
+ }
+ i += 3;
+ }
+ match i {
+ 5 => return Ok(99),
+ _ => return Ok(0),
+ };
+ }
+ Ok(the_field)
+}
+"#,
+ );
+
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo(the_field: u32) -> u3$02 {
+ if the_field < 5 {
+ let mut i = 0;
+ match i {
+ 5 => return 99,
+ _ => return 0,
+ }
+ }
+ the_field
+}
+"#,
+ r#"
+fn foo(the_field: u32) -> Result<u32, ${0:_}> {
+ if the_field < 5 {
+ let mut i = 0;
+ match i {
+ 5 => return Ok(99),
+ _ => return Ok(0),
+ }
+ }
+ Ok(the_field)
+}
+"#,
+ );
+
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo(the_field: u32) -> u32$0 {
+ if the_field < 5 {
+ let mut i = 0;
+ if i == 5 {
+ return 99
+ } else {
+ return 0
+ }
+ }
+ the_field
+}
+"#,
+ r#"
+fn foo(the_field: u32) -> Result<u32, ${0:_}> {
+ if the_field < 5 {
+ let mut i = 0;
+ if i == 5 {
+ return Ok(99)
+ } else {
+ return Ok(0)
+ }
+ }
+ Ok(the_field)
+}
+"#,
+ );
+
+ check_assist(
+ wrap_return_type_in_result,
+ r#"
+//- minicore: result
+fn foo(the_field: u32) -> $0u32 {
+ if the_field < 5 {
+ let mut i = 0;
+ if i == 5 {
+ return 99;
+ } else {
+ return 0;
+ }
+ }
+ the_field
+}
+"#,
+ r#"
+fn foo(the_field: u32) -> Result<u32, ${0:_}> {
+ if the_field < 5 {
+ let mut i = 0;
+ if i == 5 {
+ return Ok(99);
+ } else {
+ return Ok(0);
+ }
+ }
+ Ok(the_field)
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs
new file mode 100644
index 000000000..fe87aa15f
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs
@@ -0,0 +1,309 @@
+//! `assists` crate provides a bunch of code assists, also known as code actions
+//! (in LSP) or intentions (in IntelliJ).
+//!
+//! An assist is a micro-refactoring, which is automatically activated in
+//! certain context. For example, if the cursor is over `,`, a "swap `,`" assist
+//! becomes available.
+//!
+//! ## Assists Guidelines
+//!
+//! Assists are the main mechanism to deliver advanced IDE features to the user,
+//! so we should pay extra attention to the UX.
+//!
+//! The power of assists comes from their context-awareness. The main problem
+//! with IDE features is that there are a lot of them, and it's hard to teach
+//! the user what's available. Assists solve this problem nicely: 💡 signifies
+//! that *something* is possible, and clicking on it reveals a *short* list of
+//! actions. Contrast it with Emacs `M-x`, which just spits an infinite list of
+//! all the features.
+//!
+//! Here are some considerations when creating a new assist:
+//!
+//! * It's good to preserve semantics, and it's good to keep the code compiling,
+//! but it isn't necessary. Example: "flip binary operation" might change
+//! semantics.
+//! * Assist shouldn't necessary make the code "better". A lot of assist come in
+//! pairs: "if let <-> match".
+//! * Assists should have as narrow scope as possible. Each new assists greatly
+//! improves UX for cases where the user actually invokes it, but it makes UX
+//! worse for every case where the user clicks 💡 to invoke some *other*
+//! assist. So, a rarely useful assist which is always applicable can be a net
+//! negative.
+//! * Rarely useful actions are tricky. Sometimes there are features which are
+//! clearly useful to some users, but are just noise most of the time. We
+//! don't have a good solution here, our current approach is to make this
+//! functionality available only if assist is applicable to the whole
+//! selection. Example: `sort_items` sorts items alphabetically. Naively, it
+//! should be available more or less everywhere, which isn't useful. So
+//! instead we only show it if the user *selects* the items they want to sort.
+//! * Consider grouping related assists together (see [`Assists::add_group`]).
+//! * Make assists robust. If the assist depends on results of type-inference too
+//! much, it might only fire in fully-correct code. This makes assist less
+//! useful and (worse) less predictable. The user should have a clear
+//! intuition when each particular assist is available.
+//! * Make small assists, which compose. Example: rather than auto-importing
+//! enums in `add_missing_match_arms`, we use fully-qualified names. There's a
+//! separate assist to shorten a fully-qualified name.
+//! * Distinguish between assists and fixits for diagnostics. Internally, fixits
+//! and assists are equivalent. They have the same "show a list + invoke a
+//! single element" workflow, and both use [`Assist`] data structure. The main
+//! difference is in the UX: while 💡 looks only at the cursor position,
+//! diagnostics squigglies and fixits are calculated for the whole file and
+//! are presented to the user eagerly. So, diagnostics should be fixable
+//! errors, while assists can be just suggestions for an alternative way to do
+//! something. If something *could* be a diagnostic, it should be a
+//! diagnostic. Conversely, it might be valuable to turn a diagnostic with a
+//! lot of false errors into an assist.
+//!
+//! See also this post:
+//! <https://rust-analyzer.github.io/blog/2020/09/28/how-to-make-a-light-bulb.html>
+
+#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
+
+#[allow(unused)]
+macro_rules! eprintln {
+ ($($tt:tt)*) => { stdx::eprintln!($($tt)*) };
+}
+
+mod assist_config;
+mod assist_context;
+#[cfg(test)]
+mod tests;
+pub mod utils;
+
+use hir::Semantics;
+use ide_db::{base_db::FileRange, RootDatabase};
+use syntax::TextRange;
+
+pub(crate) use crate::assist_context::{AssistContext, Assists};
+
+pub use assist_config::AssistConfig;
+pub use ide_db::assists::{
+ Assist, AssistId, AssistKind, AssistResolveStrategy, GroupLabel, SingleResolve,
+};
+
+/// Return all the assists applicable at the given position.
+///
+// NOTE: We don't have a `Feature: ` section for assists, they are special-cased
+// in the manual.
+pub fn assists(
+ db: &RootDatabase,
+ config: &AssistConfig,
+ resolve: AssistResolveStrategy,
+ range: FileRange,
+) -> Vec<Assist> {
+ let sema = Semantics::new(db);
+ let ctx = AssistContext::new(sema, config, range);
+ let mut acc = Assists::new(&ctx, resolve);
+ handlers::all().iter().for_each(|handler| {
+ handler(&mut acc, &ctx);
+ });
+ acc.finish()
+}
+
+mod handlers {
+ use crate::{AssistContext, Assists};
+
+ pub(crate) type Handler = fn(&mut Assists, &AssistContext<'_>) -> Option<()>;
+
+ mod add_explicit_type;
+ mod add_label_to_loop;
+ mod add_lifetime_to_type;
+ mod add_missing_impl_members;
+ mod add_turbo_fish;
+ mod apply_demorgan;
+ mod auto_import;
+ mod change_visibility;
+ mod convert_bool_then;
+ mod convert_comment_block;
+ mod convert_integer_literal;
+ mod convert_into_to_from;
+ mod convert_iter_for_each_to_for;
+ mod convert_let_else_to_match;
+ mod convert_tuple_struct_to_named_struct;
+ mod convert_to_guarded_return;
+ mod convert_while_to_loop;
+ mod destructure_tuple_binding;
+ mod expand_glob_import;
+ mod extract_function;
+ mod extract_module;
+ mod extract_struct_from_enum_variant;
+ mod extract_type_alias;
+ mod extract_variable;
+ mod add_missing_match_arms;
+ mod fix_visibility;
+ mod flip_binexpr;
+ mod flip_comma;
+ mod flip_trait_bound;
+ mod generate_constant;
+ mod generate_default_from_enum_variant;
+ mod generate_default_from_new;
+ mod generate_deref;
+ mod generate_derive;
+ mod generate_documentation_template;
+ mod generate_enum_is_method;
+ mod generate_enum_projection_method;
+ mod generate_enum_variant;
+ mod generate_from_impl_for_enum;
+ mod generate_function;
+ mod generate_getter;
+ mod generate_impl;
+ mod generate_is_empty_from_len;
+ mod generate_new;
+ mod generate_setter;
+ mod generate_delegate_methods;
+ mod add_return_type;
+ mod inline_call;
+ mod inline_local_variable;
+ mod inline_type_alias;
+ mod introduce_named_lifetime;
+ mod invert_if;
+ mod merge_imports;
+ mod merge_match_arms;
+ mod move_bounds;
+ mod move_guard;
+ mod move_module_to_file;
+ mod move_to_mod_rs;
+ mod move_from_mod_rs;
+ mod number_representation;
+ mod promote_local_to_const;
+ mod pull_assignment_up;
+ mod qualify_path;
+ mod qualify_method_call;
+ mod raw_string;
+ mod remove_dbg;
+ mod remove_mut;
+ mod remove_unused_param;
+ mod reorder_fields;
+ mod reorder_impl_items;
+ mod replace_try_expr_with_match;
+ mod replace_derive_with_manual_impl;
+ mod replace_if_let_with_match;
+ mod introduce_named_generic;
+ mod replace_let_with_if_let;
+ mod replace_qualified_name_with_use;
+ mod replace_string_with_char;
+ mod replace_turbofish_with_explicit_type;
+ mod split_import;
+ mod sort_items;
+ mod toggle_ignore;
+ mod unmerge_use;
+ mod unnecessary_async;
+ mod unwrap_block;
+ mod unwrap_result_return_type;
+ mod wrap_return_type_in_result;
+
+ pub(crate) fn all() -> &'static [Handler] {
+ &[
+ // These are alphabetic for the foolish consistency
+ add_explicit_type::add_explicit_type,
+ add_label_to_loop::add_label_to_loop,
+ add_missing_match_arms::add_missing_match_arms,
+ add_lifetime_to_type::add_lifetime_to_type,
+ add_return_type::add_return_type,
+ add_turbo_fish::add_turbo_fish,
+ apply_demorgan::apply_demorgan,
+ auto_import::auto_import,
+ change_visibility::change_visibility,
+ convert_bool_then::convert_bool_then_to_if,
+ convert_bool_then::convert_if_to_bool_then,
+ convert_comment_block::convert_comment_block,
+ convert_integer_literal::convert_integer_literal,
+ convert_into_to_from::convert_into_to_from,
+ convert_iter_for_each_to_for::convert_iter_for_each_to_for,
+ convert_iter_for_each_to_for::convert_for_loop_with_for_each,
+ convert_let_else_to_match::convert_let_else_to_match,
+ convert_to_guarded_return::convert_to_guarded_return,
+ convert_tuple_struct_to_named_struct::convert_tuple_struct_to_named_struct,
+ convert_while_to_loop::convert_while_to_loop,
+ destructure_tuple_binding::destructure_tuple_binding,
+ expand_glob_import::expand_glob_import,
+ extract_struct_from_enum_variant::extract_struct_from_enum_variant,
+ extract_type_alias::extract_type_alias,
+ fix_visibility::fix_visibility,
+ flip_binexpr::flip_binexpr,
+ flip_comma::flip_comma,
+ flip_trait_bound::flip_trait_bound,
+ generate_constant::generate_constant,
+ generate_default_from_enum_variant::generate_default_from_enum_variant,
+ generate_default_from_new::generate_default_from_new,
+ generate_derive::generate_derive,
+ generate_documentation_template::generate_documentation_template,
+ generate_documentation_template::generate_doc_example,
+ generate_enum_is_method::generate_enum_is_method,
+ generate_enum_projection_method::generate_enum_as_method,
+ generate_enum_projection_method::generate_enum_try_into_method,
+ generate_enum_variant::generate_enum_variant,
+ generate_from_impl_for_enum::generate_from_impl_for_enum,
+ generate_function::generate_function,
+ generate_impl::generate_impl,
+ generate_is_empty_from_len::generate_is_empty_from_len,
+ generate_new::generate_new,
+ inline_call::inline_call,
+ inline_call::inline_into_callers,
+ inline_local_variable::inline_local_variable,
+ inline_type_alias::inline_type_alias,
+ introduce_named_generic::introduce_named_generic,
+ introduce_named_lifetime::introduce_named_lifetime,
+ invert_if::invert_if,
+ merge_imports::merge_imports,
+ merge_match_arms::merge_match_arms,
+ move_bounds::move_bounds_to_where_clause,
+ move_guard::move_arm_cond_to_match_guard,
+ move_guard::move_guard_to_arm_body,
+ move_module_to_file::move_module_to_file,
+ move_to_mod_rs::move_to_mod_rs,
+ move_from_mod_rs::move_from_mod_rs,
+ number_representation::reformat_number_literal,
+ pull_assignment_up::pull_assignment_up,
+ promote_local_to_const::promote_local_to_const,
+ qualify_path::qualify_path,
+ qualify_method_call::qualify_method_call,
+ raw_string::add_hash,
+ raw_string::make_usual_string,
+ raw_string::remove_hash,
+ remove_dbg::remove_dbg,
+ remove_mut::remove_mut,
+ remove_unused_param::remove_unused_param,
+ reorder_fields::reorder_fields,
+ reorder_impl_items::reorder_impl_items,
+ replace_try_expr_with_match::replace_try_expr_with_match,
+ replace_derive_with_manual_impl::replace_derive_with_manual_impl,
+ replace_if_let_with_match::replace_if_let_with_match,
+ replace_if_let_with_match::replace_match_with_if_let,
+ replace_let_with_if_let::replace_let_with_if_let,
+ replace_turbofish_with_explicit_type::replace_turbofish_with_explicit_type,
+ replace_qualified_name_with_use::replace_qualified_name_with_use,
+ sort_items::sort_items,
+ split_import::split_import,
+ toggle_ignore::toggle_ignore,
+ unmerge_use::unmerge_use,
+ unnecessary_async::unnecessary_async,
+ unwrap_block::unwrap_block,
+ unwrap_result_return_type::unwrap_result_return_type,
+ wrap_return_type_in_result::wrap_return_type_in_result,
+ // These are manually sorted for better priorities. By default,
+ // priority is determined by the size of the target range (smaller
+ // target wins). If the ranges are equal, position in this list is
+ // used as a tie-breaker.
+ add_missing_impl_members::add_missing_impl_members,
+ add_missing_impl_members::add_missing_default_members,
+ //
+ replace_string_with_char::replace_string_with_char,
+ replace_string_with_char::replace_char_with_string,
+ raw_string::make_raw_string,
+ //
+ extract_variable::extract_variable,
+ extract_function::extract_function,
+ extract_module::extract_module,
+ //
+ generate_getter::generate_getter,
+ generate_getter::generate_getter_mut,
+ generate_setter::generate_setter,
+ generate_delegate_methods::generate_delegate_methods,
+ generate_deref::generate_deref,
+ // Are you sure you want to add new assist here, and not to the
+ // sorted list above?
+ ]
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs
new file mode 100644
index 000000000..9cd66c6b3
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs
@@ -0,0 +1,558 @@
+mod generated;
+#[cfg(not(feature = "in-rust-tree"))]
+mod sourcegen;
+
+use expect_test::expect;
+use hir::{db::DefDatabase, Semantics};
+use ide_db::{
+ base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt},
+ imports::insert_use::{ImportGranularity, InsertUseConfig},
+ source_change::FileSystemEdit,
+ RootDatabase, SnippetCap,
+};
+use stdx::{format_to, trim_indent};
+use syntax::TextRange;
+use test_utils::{assert_eq_text, extract_offset};
+
+use crate::{
+ assists, handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind,
+ AssistResolveStrategy, Assists, SingleResolve,
+};
+
+pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig {
+ snippet_cap: SnippetCap::new(true),
+ allowed: None,
+ insert_use: InsertUseConfig {
+ granularity: ImportGranularity::Crate,
+ prefix_kind: hir::PrefixKind::Plain,
+ enforce_granularity: true,
+ group: true,
+ skip_glob_imports: true,
+ },
+};
+
+pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
+ RootDatabase::with_single_file(text)
+}
+
+#[track_caller]
+pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) {
+ let ra_fixture_after = trim_indent(ra_fixture_after);
+ check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after), None);
+}
+
+// There is no way to choose what assist within a group you want to test against,
+// so this is here to allow you choose.
+pub(crate) fn check_assist_by_label(
+ assist: Handler,
+ ra_fixture_before: &str,
+ ra_fixture_after: &str,
+ label: &str,
+) {
+ let ra_fixture_after = trim_indent(ra_fixture_after);
+ check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after), Some(label));
+}
+
+// FIXME: instead of having a separate function here, maybe use
+// `extract_ranges` and mark the target as `<target> </target>` in the
+// fixture?
+#[track_caller]
+pub(crate) fn check_assist_target(assist: Handler, ra_fixture: &str, target: &str) {
+ check(assist, ra_fixture, ExpectedResult::Target(target), None);
+}
+
+#[track_caller]
+pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) {
+ check(assist, ra_fixture, ExpectedResult::NotApplicable, None);
+}
+
+/// Check assist in unresolved state. Useful to check assists for lazy computation.
+#[track_caller]
+pub(crate) fn check_assist_unresolved(assist: Handler, ra_fixture: &str) {
+ check(assist, ra_fixture, ExpectedResult::Unresolved, None);
+}
+
+#[track_caller]
+fn check_doc_test(assist_id: &str, before: &str, after: &str) {
+ let after = trim_indent(after);
+ let (db, file_id, selection) = RootDatabase::with_range_or_offset(before);
+ let before = db.file_text(file_id).to_string();
+ let frange = FileRange { file_id, range: selection.into() };
+
+ let assist = assists(&db, &TEST_CONFIG, AssistResolveStrategy::All, frange)
+ .into_iter()
+ .find(|assist| assist.id.0 == assist_id)
+ .unwrap_or_else(|| {
+ panic!(
+ "\n\nAssist is not applicable: {}\nAvailable assists: {}",
+ assist_id,
+ assists(&db, &TEST_CONFIG, AssistResolveStrategy::None, frange)
+ .into_iter()
+ .map(|assist| assist.id.0)
+ .collect::<Vec<_>>()
+ .join(", ")
+ )
+ });
+
+ let actual = {
+ let source_change =
+ assist.source_change.expect("Assist did not contain any source changes");
+ let mut actual = before;
+ if let Some(source_file_edit) = source_change.get_source_edit(file_id) {
+ source_file_edit.apply(&mut actual);
+ }
+ actual
+ };
+ assert_eq_text!(&after, &actual);
+}
+
+enum ExpectedResult<'a> {
+ NotApplicable,
+ Unresolved,
+ After(&'a str),
+ Target(&'a str),
+}
+
+#[track_caller]
+fn check(handler: Handler, before: &str, expected: ExpectedResult<'_>, assist_label: Option<&str>) {
+ let (mut db, file_with_caret_id, range_or_offset) = RootDatabase::with_range_or_offset(before);
+ db.set_enable_proc_attr_macros(true);
+ let text_without_caret = db.file_text(file_with_caret_id).to_string();
+
+ let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() };
+
+ let sema = Semantics::new(&db);
+ let config = TEST_CONFIG;
+ let ctx = AssistContext::new(sema, &config, frange);
+ let resolve = match expected {
+ ExpectedResult::Unresolved => AssistResolveStrategy::None,
+ _ => AssistResolveStrategy::All,
+ };
+ let mut acc = Assists::new(&ctx, resolve);
+ handler(&mut acc, &ctx);
+ let mut res = acc.finish();
+
+ let assist = match assist_label {
+ Some(label) => res.into_iter().find(|resolved| resolved.label == label),
+ None => res.pop(),
+ };
+
+ match (assist, expected) {
+ (Some(assist), ExpectedResult::After(after)) => {
+ let source_change =
+ assist.source_change.expect("Assist did not contain any source changes");
+ let skip_header = source_change.source_file_edits.len() == 1
+ && source_change.file_system_edits.len() == 0;
+
+ let mut buf = String::new();
+ for (file_id, edit) in source_change.source_file_edits {
+ let mut text = db.file_text(file_id).as_ref().to_owned();
+ edit.apply(&mut text);
+ if !skip_header {
+ let sr = db.file_source_root(file_id);
+ let sr = db.source_root(sr);
+ let path = sr.path_for_file(&file_id).unwrap();
+ format_to!(buf, "//- {}\n", path)
+ }
+ buf.push_str(&text);
+ }
+
+ for file_system_edit in source_change.file_system_edits {
+ let (dst, contents) = match file_system_edit {
+ FileSystemEdit::CreateFile { dst, initial_contents } => (dst, initial_contents),
+ FileSystemEdit::MoveFile { src, dst } => {
+ (dst, db.file_text(src).as_ref().to_owned())
+ }
+ FileSystemEdit::MoveDir { src, src_id, dst } => {
+ // temporary placeholder for MoveDir since we are not using MoveDir in ide assists yet.
+ (dst, format!("{:?}\n{:?}", src_id, src))
+ }
+ };
+ let sr = db.file_source_root(dst.anchor);
+ let sr = db.source_root(sr);
+ let mut base = sr.path_for_file(&dst.anchor).unwrap().clone();
+ base.pop();
+ let created_file_path = base.join(&dst.path).unwrap();
+ format_to!(buf, "//- {}\n", created_file_path);
+ buf.push_str(&contents);
+ }
+
+ assert_eq_text!(after, &buf);
+ }
+ (Some(assist), ExpectedResult::Target(target)) => {
+ let range = assist.target;
+ assert_eq_text!(&text_without_caret[range], target);
+ }
+ (Some(assist), ExpectedResult::Unresolved) => assert!(
+ assist.source_change.is_none(),
+ "unresolved assist should not contain source changes"
+ ),
+ (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"),
+ (
+ None,
+ ExpectedResult::After(_) | ExpectedResult::Target(_) | ExpectedResult::Unresolved,
+ ) => {
+ panic!("code action is not applicable")
+ }
+ (None, ExpectedResult::NotApplicable) => (),
+ };
+}
+
+fn labels(assists: &[Assist]) -> String {
+ let mut labels = assists
+ .iter()
+ .map(|assist| {
+ let mut label = match &assist.group {
+ Some(g) => g.0.clone(),
+ None => assist.label.to_string(),
+ };
+ label.push('\n');
+ label
+ })
+ .collect::<Vec<_>>();
+ labels.dedup();
+ labels.into_iter().collect::<String>()
+}
+
+#[test]
+fn assist_order_field_struct() {
+ let before = "struct Foo { $0bar: u32 }";
+ let (before_cursor_pos, before) = extract_offset(before);
+ let (db, file_id) = with_single_file(&before);
+ let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) };
+ let assists = assists(&db, &TEST_CONFIG, AssistResolveStrategy::None, frange);
+ let mut assists = assists.iter();
+
+ assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)");
+ assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method");
+ assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method");
+ assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method");
+ assert_eq!(assists.next().expect("expected assist").label, "Add `#[derive]`");
+}
+
+#[test]
+fn assist_order_if_expr() {
+ let (db, frange) = RootDatabase::with_range(
+ r#"
+pub fn test_some_range(a: int) -> bool {
+ if let 2..6 = $05$0 {
+ true
+ } else {
+ false
+ }
+}
+"#,
+ );
+
+ let assists = assists(&db, &TEST_CONFIG, AssistResolveStrategy::None, frange);
+ let expected = labels(&assists);
+
+ expect![[r#"
+ Convert integer base
+ Extract into variable
+ Extract into function
+ Replace if let with match
+ "#]]
+ .assert_eq(&expected);
+}
+
+#[test]
+fn assist_filter_works() {
+ let (db, frange) = RootDatabase::with_range(
+ r#"
+pub fn test_some_range(a: int) -> bool {
+ if let 2..6 = $05$0 {
+ true
+ } else {
+ false
+ }
+}
+"#,
+ );
+ {
+ let mut cfg = TEST_CONFIG;
+ cfg.allowed = Some(vec![AssistKind::Refactor]);
+
+ let assists = assists(&db, &cfg, AssistResolveStrategy::None, frange);
+ let expected = labels(&assists);
+
+ expect![[r#"
+ Convert integer base
+ Extract into variable
+ Extract into function
+ Replace if let with match
+ "#]]
+ .assert_eq(&expected);
+ }
+
+ {
+ let mut cfg = TEST_CONFIG;
+ cfg.allowed = Some(vec![AssistKind::RefactorExtract]);
+ let assists = assists(&db, &cfg, AssistResolveStrategy::None, frange);
+ let expected = labels(&assists);
+
+ expect![[r#"
+ Extract into variable
+ Extract into function
+ "#]]
+ .assert_eq(&expected);
+ }
+
+ {
+ let mut cfg = TEST_CONFIG;
+ cfg.allowed = Some(vec![AssistKind::QuickFix]);
+ let assists = assists(&db, &cfg, AssistResolveStrategy::None, frange);
+ let expected = labels(&assists);
+
+ expect![[r#""#]].assert_eq(&expected);
+ }
+}
+
+#[test]
+fn various_resolve_strategies() {
+ let (db, frange) = RootDatabase::with_range(
+ r#"
+pub fn test_some_range(a: int) -> bool {
+ if let 2..6 = $05$0 {
+ true
+ } else {
+ false
+ }
+}
+"#,
+ );
+
+ let mut cfg = TEST_CONFIG;
+ cfg.allowed = Some(vec![AssistKind::RefactorExtract]);
+
+ {
+ let assists = assists(&db, &cfg, AssistResolveStrategy::None, frange);
+ assert_eq!(2, assists.len());
+ let mut assists = assists.into_iter();
+
+ let extract_into_variable_assist = assists.next().unwrap();
+ expect![[r#"
+ Assist {
+ id: AssistId(
+ "extract_variable",
+ RefactorExtract,
+ ),
+ label: "Extract into variable",
+ group: None,
+ target: 59..60,
+ source_change: None,
+ trigger_signature_help: false,
+ }
+ "#]]
+ .assert_debug_eq(&extract_into_variable_assist);
+
+ let extract_into_function_assist = assists.next().unwrap();
+ expect![[r#"
+ Assist {
+ id: AssistId(
+ "extract_function",
+ RefactorExtract,
+ ),
+ label: "Extract into function",
+ group: None,
+ target: 59..60,
+ source_change: None,
+ trigger_signature_help: false,
+ }
+ "#]]
+ .assert_debug_eq(&extract_into_function_assist);
+ }
+
+ {
+ let assists = assists(
+ &db,
+ &cfg,
+ AssistResolveStrategy::Single(SingleResolve {
+ assist_id: "SOMETHING_MISMATCHING".to_string(),
+ assist_kind: AssistKind::RefactorExtract,
+ }),
+ frange,
+ );
+ assert_eq!(2, assists.len());
+ let mut assists = assists.into_iter();
+
+ let extract_into_variable_assist = assists.next().unwrap();
+ expect![[r#"
+ Assist {
+ id: AssistId(
+ "extract_variable",
+ RefactorExtract,
+ ),
+ label: "Extract into variable",
+ group: None,
+ target: 59..60,
+ source_change: None,
+ trigger_signature_help: false,
+ }
+ "#]]
+ .assert_debug_eq(&extract_into_variable_assist);
+
+ let extract_into_function_assist = assists.next().unwrap();
+ expect![[r#"
+ Assist {
+ id: AssistId(
+ "extract_function",
+ RefactorExtract,
+ ),
+ label: "Extract into function",
+ group: None,
+ target: 59..60,
+ source_change: None,
+ trigger_signature_help: false,
+ }
+ "#]]
+ .assert_debug_eq(&extract_into_function_assist);
+ }
+
+ {
+ let assists = assists(
+ &db,
+ &cfg,
+ AssistResolveStrategy::Single(SingleResolve {
+ assist_id: "extract_variable".to_string(),
+ assist_kind: AssistKind::RefactorExtract,
+ }),
+ frange,
+ );
+ assert_eq!(2, assists.len());
+ let mut assists = assists.into_iter();
+
+ let extract_into_variable_assist = assists.next().unwrap();
+ expect![[r#"
+ Assist {
+ id: AssistId(
+ "extract_variable",
+ RefactorExtract,
+ ),
+ label: "Extract into variable",
+ group: None,
+ target: 59..60,
+ source_change: Some(
+ SourceChange {
+ source_file_edits: {
+ FileId(
+ 0,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "let $0var_name = 5;\n ",
+ delete: 45..45,
+ },
+ Indel {
+ insert: "var_name",
+ delete: 59..60,
+ },
+ ],
+ },
+ },
+ file_system_edits: [],
+ is_snippet: true,
+ },
+ ),
+ trigger_signature_help: false,
+ }
+ "#]]
+ .assert_debug_eq(&extract_into_variable_assist);
+
+ let extract_into_function_assist = assists.next().unwrap();
+ expect![[r#"
+ Assist {
+ id: AssistId(
+ "extract_function",
+ RefactorExtract,
+ ),
+ label: "Extract into function",
+ group: None,
+ target: 59..60,
+ source_change: None,
+ trigger_signature_help: false,
+ }
+ "#]]
+ .assert_debug_eq(&extract_into_function_assist);
+ }
+
+ {
+ let assists = assists(&db, &cfg, AssistResolveStrategy::All, frange);
+ assert_eq!(2, assists.len());
+ let mut assists = assists.into_iter();
+
+ let extract_into_variable_assist = assists.next().unwrap();
+ expect![[r#"
+ Assist {
+ id: AssistId(
+ "extract_variable",
+ RefactorExtract,
+ ),
+ label: "Extract into variable",
+ group: None,
+ target: 59..60,
+ source_change: Some(
+ SourceChange {
+ source_file_edits: {
+ FileId(
+ 0,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "let $0var_name = 5;\n ",
+ delete: 45..45,
+ },
+ Indel {
+ insert: "var_name",
+ delete: 59..60,
+ },
+ ],
+ },
+ },
+ file_system_edits: [],
+ is_snippet: true,
+ },
+ ),
+ trigger_signature_help: false,
+ }
+ "#]]
+ .assert_debug_eq(&extract_into_variable_assist);
+
+ let extract_into_function_assist = assists.next().unwrap();
+ expect![[r#"
+ Assist {
+ id: AssistId(
+ "extract_function",
+ RefactorExtract,
+ ),
+ label: "Extract into function",
+ group: None,
+ target: 59..60,
+ source_change: Some(
+ SourceChange {
+ source_file_edits: {
+ FileId(
+ 0,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "fun_name()",
+ delete: 59..60,
+ },
+ Indel {
+ insert: "\n\nfn $0fun_name() -> i32 {\n 5\n}",
+ delete: 110..110,
+ },
+ ],
+ },
+ },
+ file_system_edits: [],
+ is_snippet: true,
+ },
+ ),
+ trigger_signature_help: false,
+ }
+ "#]]
+ .assert_debug_eq(&extract_into_function_assist);
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs
new file mode 100644
index 000000000..6eaab48a3
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs
@@ -0,0 +1,2259 @@
+//! Generated by `sourcegen_assists_docs`, do not edit by hand.
+
+use super::check_doc_test;
+
+#[test]
+fn doctest_add_explicit_type() {
+ check_doc_test(
+ "add_explicit_type",
+ r#####"
+fn main() {
+ let x$0 = 92;
+}
+"#####,
+ r#####"
+fn main() {
+ let x: i32 = 92;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_add_hash() {
+ check_doc_test(
+ "add_hash",
+ r#####"
+fn main() {
+ r#"Hello,$0 World!"#;
+}
+"#####,
+ r#####"
+fn main() {
+ r##"Hello, World!"##;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_add_impl_default_members() {
+ check_doc_test(
+ "add_impl_default_members",
+ r#####"
+trait Trait {
+ type X;
+ fn foo(&self);
+ fn bar(&self) {}
+}
+
+impl Trait for () {
+ type X = ();
+ fn foo(&self) {}$0
+}
+"#####,
+ r#####"
+trait Trait {
+ type X;
+ fn foo(&self);
+ fn bar(&self) {}
+}
+
+impl Trait for () {
+ type X = ();
+ fn foo(&self) {}
+
+ $0fn bar(&self) {}
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_add_impl_missing_members() {
+ check_doc_test(
+ "add_impl_missing_members",
+ r#####"
+trait Trait<T> {
+ type X;
+ fn foo(&self) -> T;
+ fn bar(&self) {}
+}
+
+impl Trait<u32> for () {$0
+
+}
+"#####,
+ r#####"
+trait Trait<T> {
+ type X;
+ fn foo(&self) -> T;
+ fn bar(&self) {}
+}
+
+impl Trait<u32> for () {
+ $0type X;
+
+ fn foo(&self) -> u32 {
+ todo!()
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_add_label_to_loop() {
+ check_doc_test(
+ "add_label_to_loop",
+ r#####"
+fn main() {
+ loop$0 {
+ break;
+ continue;
+ }
+}
+"#####,
+ r#####"
+fn main() {
+ 'l: loop {
+ break 'l;
+ continue 'l;
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_add_lifetime_to_type() {
+ check_doc_test(
+ "add_lifetime_to_type",
+ r#####"
+struct Point {
+ x: &$0u32,
+ y: u32,
+}
+"#####,
+ r#####"
+struct Point<'a> {
+ x: &'a u32,
+ y: u32,
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_add_missing_match_arms() {
+ check_doc_test(
+ "add_missing_match_arms",
+ r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ $0
+ }
+}
+"#####,
+ r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ $0Action::Move { distance } => todo!(),
+ Action::Stop => todo!(),
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_add_return_type() {
+ check_doc_test(
+ "add_return_type",
+ r#####"
+fn foo() { 4$02i32 }
+"#####,
+ r#####"
+fn foo() -> i32 { 42i32 }
+"#####,
+ )
+}
+
+#[test]
+fn doctest_add_turbo_fish() {
+ check_doc_test(
+ "add_turbo_fish",
+ r#####"
+fn make<T>() -> T { todo!() }
+fn main() {
+ let x = make$0();
+}
+"#####,
+ r#####"
+fn make<T>() -> T { todo!() }
+fn main() {
+ let x = make::<${0:_}>();
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_apply_demorgan() {
+ check_doc_test(
+ "apply_demorgan",
+ r#####"
+fn main() {
+ if x != 4 ||$0 y < 3.14 {}
+}
+"#####,
+ r#####"
+fn main() {
+ if !(x == 4 && y >= 3.14) {}
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_auto_import() {
+ check_doc_test(
+ "auto_import",
+ r#####"
+fn main() {
+ let map = HashMap$0::new();
+}
+pub mod std { pub mod collections { pub struct HashMap { } } }
+"#####,
+ r#####"
+use std::collections::HashMap;
+
+fn main() {
+ let map = HashMap::new();
+}
+pub mod std { pub mod collections { pub struct HashMap { } } }
+"#####,
+ )
+}
+
+#[test]
+fn doctest_change_visibility() {
+ check_doc_test(
+ "change_visibility",
+ r#####"
+$0fn frobnicate() {}
+"#####,
+ r#####"
+pub(crate) fn frobnicate() {}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_convert_bool_then_to_if() {
+ check_doc_test(
+ "convert_bool_then_to_if",
+ r#####"
+//- minicore: bool_impl
+fn main() {
+ (0 == 0).then$0(|| val)
+}
+"#####,
+ r#####"
+fn main() {
+ if 0 == 0 {
+ Some(val)
+ } else {
+ None
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_convert_for_loop_with_for_each() {
+ check_doc_test(
+ "convert_for_loop_with_for_each",
+ r#####"
+fn main() {
+ let x = vec![1, 2, 3];
+ for$0 v in x {
+ let y = v * 2;
+ }
+}
+"#####,
+ r#####"
+fn main() {
+ let x = vec![1, 2, 3];
+ x.into_iter().for_each(|v| {
+ let y = v * 2;
+ });
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_convert_if_to_bool_then() {
+ check_doc_test(
+ "convert_if_to_bool_then",
+ r#####"
+//- minicore: option
+fn main() {
+ if$0 cond {
+ Some(val)
+ } else {
+ None
+ }
+}
+"#####,
+ r#####"
+fn main() {
+ cond.then(|| val)
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_convert_integer_literal() {
+ check_doc_test(
+ "convert_integer_literal",
+ r#####"
+const _: i32 = 10$0;
+"#####,
+ r#####"
+const _: i32 = 0b1010;
+"#####,
+ )
+}
+
+#[test]
+fn doctest_convert_into_to_from() {
+ check_doc_test(
+ "convert_into_to_from",
+ r#####"
+//- minicore: from
+impl $0Into<Thing> for usize {
+ fn into(self) -> Thing {
+ Thing {
+ b: self.to_string(),
+ a: self
+ }
+ }
+}
+"#####,
+ r#####"
+impl From<usize> for Thing {
+ fn from(val: usize) -> Self {
+ Thing {
+ b: val.to_string(),
+ a: val
+ }
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_convert_iter_for_each_to_for() {
+ check_doc_test(
+ "convert_iter_for_each_to_for",
+ r#####"
+//- minicore: iterators
+use core::iter;
+fn main() {
+ let iter = iter::repeat((9, 2));
+ iter.for_each$0(|(x, y)| {
+ println!("x: {}, y: {}", x, y);
+ });
+}
+"#####,
+ r#####"
+use core::iter;
+fn main() {
+ let iter = iter::repeat((9, 2));
+ for (x, y) in iter {
+ println!("x: {}, y: {}", x, y);
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_convert_let_else_to_match() {
+ check_doc_test(
+ "convert_let_else_to_match",
+ r#####"
+fn main() {
+ let Ok(mut x) = f() else$0 { return };
+}
+"#####,
+ r#####"
+fn main() {
+ let mut x = match f() {
+ Ok(x) => x,
+ _ => return,
+ };
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_convert_to_guarded_return() {
+ check_doc_test(
+ "convert_to_guarded_return",
+ r#####"
+fn main() {
+ $0if cond {
+ foo();
+ bar();
+ }
+}
+"#####,
+ r#####"
+fn main() {
+ if !cond {
+ return;
+ }
+ foo();
+ bar();
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_convert_tuple_struct_to_named_struct() {
+ check_doc_test(
+ "convert_tuple_struct_to_named_struct",
+ r#####"
+struct Point$0(f32, f32);
+
+impl Point {
+ pub fn new(x: f32, y: f32) -> Self {
+ Point(x, y)
+ }
+
+ pub fn x(&self) -> f32 {
+ self.0
+ }
+
+ pub fn y(&self) -> f32 {
+ self.1
+ }
+}
+"#####,
+ r#####"
+struct Point { field1: f32, field2: f32 }
+
+impl Point {
+ pub fn new(x: f32, y: f32) -> Self {
+ Point { field1: x, field2: y }
+ }
+
+ pub fn x(&self) -> f32 {
+ self.field1
+ }
+
+ pub fn y(&self) -> f32 {
+ self.field2
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_convert_while_to_loop() {
+ check_doc_test(
+ "convert_while_to_loop",
+ r#####"
+fn main() {
+ $0while cond {
+ foo();
+ }
+}
+"#####,
+ r#####"
+fn main() {
+ loop {
+ if !cond {
+ break;
+ }
+ foo();
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_destructure_tuple_binding() {
+ check_doc_test(
+ "destructure_tuple_binding",
+ r#####"
+fn main() {
+ let $0t = (1,2);
+ let v = t.0;
+}
+"#####,
+ r#####"
+fn main() {
+ let ($0_0, _1) = (1,2);
+ let v = _0;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_expand_glob_import() {
+ check_doc_test(
+ "expand_glob_import",
+ r#####"
+mod foo {
+ pub struct Bar;
+ pub struct Baz;
+}
+
+use foo::*$0;
+
+fn qux(bar: Bar, baz: Baz) {}
+"#####,
+ r#####"
+mod foo {
+ pub struct Bar;
+ pub struct Baz;
+}
+
+use foo::{Bar, Baz};
+
+fn qux(bar: Bar, baz: Baz) {}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_extract_function() {
+ check_doc_test(
+ "extract_function",
+ r#####"
+fn main() {
+ let n = 1;
+ $0let m = n + 2;
+ // calculate
+ let k = m + n;$0
+ let g = 3;
+}
+"#####,
+ r#####"
+fn main() {
+ let n = 1;
+ fun_name(n);
+ let g = 3;
+}
+
+fn $0fun_name(n: i32) {
+ let m = n + 2;
+ // calculate
+ let k = m + n;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_extract_module() {
+ check_doc_test(
+ "extract_module",
+ r#####"
+$0fn foo(name: i32) -> i32 {
+ name + 1
+}$0
+
+fn bar(name: i32) -> i32 {
+ name + 2
+}
+"#####,
+ r#####"
+mod modname {
+ pub(crate) fn foo(name: i32) -> i32 {
+ name + 1
+ }
+}
+
+fn bar(name: i32) -> i32 {
+ name + 2
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_extract_struct_from_enum_variant() {
+ check_doc_test(
+ "extract_struct_from_enum_variant",
+ r#####"
+enum A { $0One(u32, u32) }
+"#####,
+ r#####"
+struct One(u32, u32);
+
+enum A { One(One) }
+"#####,
+ )
+}
+
+#[test]
+fn doctest_extract_type_alias() {
+ check_doc_test(
+ "extract_type_alias",
+ r#####"
+struct S {
+ field: $0(u8, u8, u8)$0,
+}
+"#####,
+ r#####"
+type $0Type = (u8, u8, u8);
+
+struct S {
+ field: Type,
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_extract_variable() {
+ check_doc_test(
+ "extract_variable",
+ r#####"
+fn main() {
+ $0(1 + 2)$0 * 4;
+}
+"#####,
+ r#####"
+fn main() {
+ let $0var_name = (1 + 2);
+ var_name * 4;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_fix_visibility() {
+ check_doc_test(
+ "fix_visibility",
+ r#####"
+mod m {
+ fn frobnicate() {}
+}
+fn main() {
+ m::frobnicate$0() {}
+}
+"#####,
+ r#####"
+mod m {
+ $0pub(crate) fn frobnicate() {}
+}
+fn main() {
+ m::frobnicate() {}
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_flip_binexpr() {
+ check_doc_test(
+ "flip_binexpr",
+ r#####"
+fn main() {
+ let _ = 90 +$0 2;
+}
+"#####,
+ r#####"
+fn main() {
+ let _ = 2 + 90;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_flip_comma() {
+ check_doc_test(
+ "flip_comma",
+ r#####"
+fn main() {
+ ((1, 2),$0 (3, 4));
+}
+"#####,
+ r#####"
+fn main() {
+ ((3, 4), (1, 2));
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_flip_trait_bound() {
+ check_doc_test(
+ "flip_trait_bound",
+ r#####"
+fn foo<T: Clone +$0 Copy>() { }
+"#####,
+ r#####"
+fn foo<T: Copy + Clone>() { }
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_constant() {
+ check_doc_test(
+ "generate_constant",
+ r#####"
+struct S { i: usize }
+impl S { pub fn new(n: usize) {} }
+fn main() {
+ let v = S::new(CAPA$0CITY);
+}
+"#####,
+ r#####"
+struct S { i: usize }
+impl S { pub fn new(n: usize) {} }
+fn main() {
+ const CAPACITY: usize = $0;
+ let v = S::new(CAPACITY);
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_default_from_enum_variant() {
+ check_doc_test(
+ "generate_default_from_enum_variant",
+ r#####"
+enum Version {
+ Undefined,
+ Minor$0,
+ Major,
+}
+"#####,
+ r#####"
+enum Version {
+ Undefined,
+ Minor,
+ Major,
+}
+
+impl Default for Version {
+ fn default() -> Self {
+ Self::Minor
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_default_from_new() {
+ check_doc_test(
+ "generate_default_from_new",
+ r#####"
+struct Example { _inner: () }
+
+impl Example {
+ pub fn n$0ew() -> Self {
+ Self { _inner: () }
+ }
+}
+"#####,
+ r#####"
+struct Example { _inner: () }
+
+impl Example {
+ pub fn new() -> Self {
+ Self { _inner: () }
+ }
+}
+
+impl Default for Example {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_delegate_methods() {
+ check_doc_test(
+ "generate_delegate_methods",
+ r#####"
+struct Age(u8);
+impl Age {
+ fn age(&self) -> u8 {
+ self.0
+ }
+}
+
+struct Person {
+ ag$0e: Age,
+}
+"#####,
+ r#####"
+struct Age(u8);
+impl Age {
+ fn age(&self) -> u8 {
+ self.0
+ }
+}
+
+struct Person {
+ age: Age,
+}
+
+impl Person {
+ $0fn age(&self) -> u8 {
+ self.age.age()
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_deref() {
+ check_doc_test(
+ "generate_deref",
+ r#####"
+//- minicore: deref, deref_mut
+struct A;
+struct B {
+ $0a: A
+}
+"#####,
+ r#####"
+struct A;
+struct B {
+ a: A
+}
+
+impl core::ops::Deref for B {
+ type Target = A;
+
+ fn deref(&self) -> &Self::Target {
+ &self.a
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_derive() {
+ check_doc_test(
+ "generate_derive",
+ r#####"
+struct Point {
+ x: u32,
+ y: u32,$0
+}
+"#####,
+ r#####"
+#[derive($0)]
+struct Point {
+ x: u32,
+ y: u32,
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_doc_example() {
+ check_doc_test(
+ "generate_doc_example",
+ r#####"
+/// Adds two numbers.$0
+pub fn add(a: i32, b: i32) -> i32 { a + b }
+"#####,
+ r#####"
+/// Adds two numbers.
+///
+/// # Examples
+///
+/// ```
+/// use test::add;
+///
+/// assert_eq!(add(a, b), );
+/// ```
+pub fn add(a: i32, b: i32) -> i32 { a + b }
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_documentation_template() {
+ check_doc_test(
+ "generate_documentation_template",
+ r#####"
+pub struct S;
+impl S {
+ pub unsafe fn set_len$0(&mut self, len: usize) -> Result<(), std::io::Error> {
+ /* ... */
+ }
+}
+"#####,
+ r#####"
+pub struct S;
+impl S {
+ /// Sets the length of this [`S`].
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if .
+ ///
+ /// # Safety
+ ///
+ /// .
+ pub unsafe fn set_len(&mut self, len: usize) -> Result<(), std::io::Error> {
+ /* ... */
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_enum_as_method() {
+ check_doc_test(
+ "generate_enum_as_method",
+ r#####"
+enum Value {
+ Number(i32),
+ Text(String)$0,
+}
+"#####,
+ r#####"
+enum Value {
+ Number(i32),
+ Text(String),
+}
+
+impl Value {
+ fn as_text(&self) -> Option<&String> {
+ if let Self::Text(v) = self {
+ Some(v)
+ } else {
+ None
+ }
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_enum_is_method() {
+ check_doc_test(
+ "generate_enum_is_method",
+ r#####"
+enum Version {
+ Undefined,
+ Minor$0,
+ Major,
+}
+"#####,
+ r#####"
+enum Version {
+ Undefined,
+ Minor,
+ Major,
+}
+
+impl Version {
+ /// Returns `true` if the version is [`Minor`].
+ ///
+ /// [`Minor`]: Version::Minor
+ #[must_use]
+ fn is_minor(&self) -> bool {
+ matches!(self, Self::Minor)
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_enum_try_into_method() {
+ check_doc_test(
+ "generate_enum_try_into_method",
+ r#####"
+enum Value {
+ Number(i32),
+ Text(String)$0,
+}
+"#####,
+ r#####"
+enum Value {
+ Number(i32),
+ Text(String),
+}
+
+impl Value {
+ fn try_into_text(self) -> Result<String, Self> {
+ if let Self::Text(v) = self {
+ Ok(v)
+ } else {
+ Err(self)
+ }
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_enum_variant() {
+ check_doc_test(
+ "generate_enum_variant",
+ r#####"
+enum Countries {
+ Ghana,
+}
+
+fn main() {
+ let country = Countries::Lesotho$0;
+}
+"#####,
+ r#####"
+enum Countries {
+ Ghana,
+ Lesotho,
+}
+
+fn main() {
+ let country = Countries::Lesotho;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_from_impl_for_enum() {
+ check_doc_test(
+ "generate_from_impl_for_enum",
+ r#####"
+enum A { $0One(u32) }
+"#####,
+ r#####"
+enum A { One(u32) }
+
+impl From<u32> for A {
+ fn from(v: u32) -> Self {
+ Self::One(v)
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_function() {
+ check_doc_test(
+ "generate_function",
+ r#####"
+struct Baz;
+fn baz() -> Baz { Baz }
+fn foo() {
+ bar$0("", baz());
+}
+
+"#####,
+ r#####"
+struct Baz;
+fn baz() -> Baz { Baz }
+fn foo() {
+ bar("", baz());
+}
+
+fn bar(arg: &str, baz: Baz) ${0:-> _} {
+ todo!()
+}
+
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_getter() {
+ check_doc_test(
+ "generate_getter",
+ r#####"
+//- minicore: as_ref
+pub struct String;
+impl AsRef<str> for String {
+ fn as_ref(&self) -> &str {
+ ""
+ }
+}
+
+struct Person {
+ nam$0e: String,
+}
+"#####,
+ r#####"
+pub struct String;
+impl AsRef<str> for String {
+ fn as_ref(&self) -> &str {
+ ""
+ }
+}
+
+struct Person {
+ name: String,
+}
+
+impl Person {
+ fn $0name(&self) -> &str {
+ self.name.as_ref()
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_getter_mut() {
+ check_doc_test(
+ "generate_getter_mut",
+ r#####"
+struct Person {
+ nam$0e: String,
+}
+"#####,
+ r#####"
+struct Person {
+ name: String,
+}
+
+impl Person {
+ fn $0name_mut(&mut self) -> &mut String {
+ &mut self.name
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_impl() {
+ check_doc_test(
+ "generate_impl",
+ r#####"
+struct Ctx<T: Clone> {
+ data: T,$0
+}
+"#####,
+ r#####"
+struct Ctx<T: Clone> {
+ data: T,
+}
+
+impl<T: Clone> Ctx<T> {
+ $0
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_is_empty_from_len() {
+ check_doc_test(
+ "generate_is_empty_from_len",
+ r#####"
+struct MyStruct { data: Vec<String> }
+
+impl MyStruct {
+ #[must_use]
+ p$0ub fn len(&self) -> usize {
+ self.data.len()
+ }
+}
+"#####,
+ r#####"
+struct MyStruct { data: Vec<String> }
+
+impl MyStruct {
+ #[must_use]
+ pub fn len(&self) -> usize {
+ self.data.len()
+ }
+
+ #[must_use]
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_new() {
+ check_doc_test(
+ "generate_new",
+ r#####"
+struct Ctx<T: Clone> {
+ data: T,$0
+}
+"#####,
+ r#####"
+struct Ctx<T: Clone> {
+ data: T,
+}
+
+impl<T: Clone> Ctx<T> {
+ fn $0new(data: T) -> Self { Self { data } }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_setter() {
+ check_doc_test(
+ "generate_setter",
+ r#####"
+struct Person {
+ nam$0e: String,
+}
+"#####,
+ r#####"
+struct Person {
+ name: String,
+}
+
+impl Person {
+ fn set_name(&mut self, name: String) {
+ self.name = name;
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_inline_call() {
+ check_doc_test(
+ "inline_call",
+ r#####"
+//- minicore: option
+fn foo(name: Option<&str>) {
+ let name = name.unwrap$0();
+}
+"#####,
+ r#####"
+fn foo(name: Option<&str>) {
+ let name = match name {
+ Some(val) => val,
+ None => panic!("called `Option::unwrap()` on a `None` value"),
+ };
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_inline_into_callers() {
+ check_doc_test(
+ "inline_into_callers",
+ r#####"
+fn print(_: &str) {}
+fn foo$0(word: &str) {
+ if !word.is_empty() {
+ print(word);
+ }
+}
+fn bar() {
+ foo("안녕하세요");
+ foo("여러분");
+}
+"#####,
+ r#####"
+fn print(_: &str) {}
+
+fn bar() {
+ {
+ let word = "안녕하세요";
+ if !word.is_empty() {
+ print(word);
+ }
+ };
+ {
+ let word = "여러분";
+ if !word.is_empty() {
+ print(word);
+ }
+ };
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_inline_local_variable() {
+ check_doc_test(
+ "inline_local_variable",
+ r#####"
+fn main() {
+ let x$0 = 1 + 2;
+ x * 4;
+}
+"#####,
+ r#####"
+fn main() {
+ (1 + 2) * 4;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_inline_type_alias() {
+ check_doc_test(
+ "inline_type_alias",
+ r#####"
+type A<T = u32> = Vec<T>;
+
+fn main() {
+ let a: $0A;
+}
+"#####,
+ r#####"
+type A<T = u32> = Vec<T>;
+
+fn main() {
+ let a: Vec<u32>;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_introduce_named_generic() {
+ check_doc_test(
+ "introduce_named_generic",
+ r#####"
+fn foo(bar: $0impl Bar) {}
+"#####,
+ r#####"
+fn foo<B: Bar>(bar: B) {}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_introduce_named_lifetime() {
+ check_doc_test(
+ "introduce_named_lifetime",
+ r#####"
+impl Cursor<'_$0> {
+ fn node(self) -> &SyntaxNode {
+ match self {
+ Cursor::Replace(node) | Cursor::Before(node) => node,
+ }
+ }
+}
+"#####,
+ r#####"
+impl<'a> Cursor<'a> {
+ fn node(self) -> &SyntaxNode {
+ match self {
+ Cursor::Replace(node) | Cursor::Before(node) => node,
+ }
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_invert_if() {
+ check_doc_test(
+ "invert_if",
+ r#####"
+fn main() {
+ if$0 !y { A } else { B }
+}
+"#####,
+ r#####"
+fn main() {
+ if y { B } else { A }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_line_to_block() {
+ check_doc_test(
+ "line_to_block",
+ r#####"
+ // Multi-line$0
+ // comment
+"#####,
+ r#####"
+ /*
+ Multi-line
+ comment
+ */
+"#####,
+ )
+}
+
+#[test]
+fn doctest_make_raw_string() {
+ check_doc_test(
+ "make_raw_string",
+ r#####"
+fn main() {
+ "Hello,$0 World!";
+}
+"#####,
+ r#####"
+fn main() {
+ r#"Hello, World!"#;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_make_usual_string() {
+ check_doc_test(
+ "make_usual_string",
+ r#####"
+fn main() {
+ r#"Hello,$0 "World!""#;
+}
+"#####,
+ r#####"
+fn main() {
+ "Hello, \"World!\"";
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_merge_imports() {
+ check_doc_test(
+ "merge_imports",
+ r#####"
+use std::$0fmt::Formatter;
+use std::io;
+"#####,
+ r#####"
+use std::{fmt::Formatter, io};
+"#####,
+ )
+}
+
+#[test]
+fn doctest_merge_match_arms() {
+ check_doc_test(
+ "merge_match_arms",
+ r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ $0Action::Move(..) => foo(),
+ Action::Stop => foo(),
+ }
+}
+"#####,
+ r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ Action::Move(..) | Action::Stop => foo(),
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_move_arm_cond_to_match_guard() {
+ check_doc_test(
+ "move_arm_cond_to_match_guard",
+ r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ Action::Move { distance } => $0if distance > 10 { foo() },
+ _ => (),
+ }
+}
+"#####,
+ r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ Action::Move { distance } if distance > 10 => foo(),
+ _ => (),
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_move_bounds_to_where_clause() {
+ check_doc_test(
+ "move_bounds_to_where_clause",
+ r#####"
+fn apply<T, U, $0F: FnOnce(T) -> U>(f: F, x: T) -> U {
+ f(x)
+}
+"#####,
+ r#####"
+fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U {
+ f(x)
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_move_from_mod_rs() {
+ check_doc_test(
+ "move_from_mod_rs",
+ r#####"
+//- /main.rs
+mod a;
+//- /a/mod.rs
+$0fn t() {}$0
+"#####,
+ r#####"
+fn t() {}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_move_guard_to_arm_body() {
+ check_doc_test(
+ "move_guard_to_arm_body",
+ r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ Action::Move { distance } $0if distance > 10 => foo(),
+ _ => (),
+ }
+}
+"#####,
+ r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ Action::Move { distance } => if distance > 10 {
+ foo()
+ },
+ _ => (),
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_move_module_to_file() {
+ check_doc_test(
+ "move_module_to_file",
+ r#####"
+mod $0foo {
+ fn t() {}
+}
+"#####,
+ r#####"
+mod foo;
+"#####,
+ )
+}
+
+#[test]
+fn doctest_move_to_mod_rs() {
+ check_doc_test(
+ "move_to_mod_rs",
+ r#####"
+//- /main.rs
+mod a;
+//- /a.rs
+$0fn t() {}$0
+"#####,
+ r#####"
+fn t() {}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_promote_local_to_const() {
+ check_doc_test(
+ "promote_local_to_const",
+ r#####"
+fn main() {
+ let foo$0 = true;
+
+ if foo {
+ println!("It's true");
+ } else {
+ println!("It's false");
+ }
+}
+"#####,
+ r#####"
+fn main() {
+ const $0FOO: bool = true;
+
+ if FOO {
+ println!("It's true");
+ } else {
+ println!("It's false");
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_pull_assignment_up() {
+ check_doc_test(
+ "pull_assignment_up",
+ r#####"
+fn main() {
+ let mut foo = 6;
+
+ if true {
+ $0foo = 5;
+ } else {
+ foo = 4;
+ }
+}
+"#####,
+ r#####"
+fn main() {
+ let mut foo = 6;
+
+ foo = if true {
+ 5
+ } else {
+ 4
+ };
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_qualify_method_call() {
+ check_doc_test(
+ "qualify_method_call",
+ r#####"
+struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+fn main() {
+ let foo = Foo;
+ foo.fo$0o();
+}
+"#####,
+ r#####"
+struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+fn main() {
+ let foo = Foo;
+ Foo::foo(&foo);
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_qualify_path() {
+ check_doc_test(
+ "qualify_path",
+ r#####"
+fn main() {
+ let map = HashMap$0::new();
+}
+pub mod std { pub mod collections { pub struct HashMap { } } }
+"#####,
+ r#####"
+fn main() {
+ let map = std::collections::HashMap::new();
+}
+pub mod std { pub mod collections { pub struct HashMap { } } }
+"#####,
+ )
+}
+
+#[test]
+fn doctest_reformat_number_literal() {
+ check_doc_test(
+ "reformat_number_literal",
+ r#####"
+const _: i32 = 1012345$0;
+"#####,
+ r#####"
+const _: i32 = 1_012_345;
+"#####,
+ )
+}
+
+#[test]
+fn doctest_remove_dbg() {
+ check_doc_test(
+ "remove_dbg",
+ r#####"
+fn main() {
+ $0dbg!(92);
+}
+"#####,
+ r#####"
+fn main() {
+ 92;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_remove_hash() {
+ check_doc_test(
+ "remove_hash",
+ r#####"
+fn main() {
+ r#"Hello,$0 World!"#;
+}
+"#####,
+ r#####"
+fn main() {
+ r"Hello, World!";
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_remove_mut() {
+ check_doc_test(
+ "remove_mut",
+ r#####"
+impl Walrus {
+ fn feed(&mut$0 self, amount: u32) {}
+}
+"#####,
+ r#####"
+impl Walrus {
+ fn feed(&self, amount: u32) {}
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_remove_unused_param() {
+ check_doc_test(
+ "remove_unused_param",
+ r#####"
+fn frobnicate(x: i32$0) {}
+
+fn main() {
+ frobnicate(92);
+}
+"#####,
+ r#####"
+fn frobnicate() {}
+
+fn main() {
+ frobnicate();
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_reorder_fields() {
+ check_doc_test(
+ "reorder_fields",
+ r#####"
+struct Foo {foo: i32, bar: i32};
+const test: Foo = $0Foo {bar: 0, foo: 1}
+"#####,
+ r#####"
+struct Foo {foo: i32, bar: i32};
+const test: Foo = Foo {foo: 1, bar: 0}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_reorder_impl_items() {
+ check_doc_test(
+ "reorder_impl_items",
+ r#####"
+trait Foo {
+ type A;
+ const B: u8;
+ fn c();
+}
+
+struct Bar;
+$0impl Foo for Bar {
+ const B: u8 = 17;
+ fn c() {}
+ type A = String;
+}
+"#####,
+ r#####"
+trait Foo {
+ type A;
+ const B: u8;
+ fn c();
+}
+
+struct Bar;
+impl Foo for Bar {
+ type A = String;
+ const B: u8 = 17;
+ fn c() {}
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_replace_char_with_string() {
+ check_doc_test(
+ "replace_char_with_string",
+ r#####"
+fn main() {
+ find('{$0');
+}
+"#####,
+ r#####"
+fn main() {
+ find("{");
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_replace_derive_with_manual_impl() {
+ check_doc_test(
+ "replace_derive_with_manual_impl",
+ r#####"
+//- minicore: derive
+trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; }
+#[derive(Deb$0ug, Display)]
+struct S;
+"#####,
+ r#####"
+trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; }
+#[derive(Display)]
+struct S;
+
+impl Debug for S {
+ $0fn fmt(&self, f: &mut Formatter) -> Result<()> {
+ f.debug_struct("S").finish()
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_replace_if_let_with_match() {
+ check_doc_test(
+ "replace_if_let_with_match",
+ r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ $0if let Action::Move { distance } = action {
+ foo(distance)
+ } else {
+ bar()
+ }
+}
+"#####,
+ r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ Action::Move { distance } => foo(distance),
+ _ => bar(),
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_replace_let_with_if_let() {
+ check_doc_test(
+ "replace_let_with_if_let",
+ r#####"
+enum Option<T> { Some(T), None }
+
+fn main(action: Action) {
+ $0let x = compute();
+}
+
+fn compute() -> Option<i32> { None }
+"#####,
+ r#####"
+enum Option<T> { Some(T), None }
+
+fn main(action: Action) {
+ if let Some(x) = compute() {
+ }
+}
+
+fn compute() -> Option<i32> { None }
+"#####,
+ )
+}
+
+#[test]
+fn doctest_replace_match_with_if_let() {
+ check_doc_test(
+ "replace_match_with_if_let",
+ r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ $0match action {
+ Action::Move { distance } => foo(distance),
+ _ => bar(),
+ }
+}
+"#####,
+ r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ if let Action::Move { distance } = action {
+ foo(distance)
+ } else {
+ bar()
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_replace_qualified_name_with_use() {
+ check_doc_test(
+ "replace_qualified_name_with_use",
+ r#####"
+mod std { pub mod collections { pub struct HashMap<T, U>(T, U); } }
+fn process(map: std::collections::$0HashMap<String, String>) {}
+"#####,
+ r#####"
+use std::collections::HashMap;
+
+mod std { pub mod collections { pub struct HashMap<T, U>(T, U); } }
+fn process(map: HashMap<String, String>) {}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_replace_string_with_char() {
+ check_doc_test(
+ "replace_string_with_char",
+ r#####"
+fn main() {
+ find("{$0");
+}
+"#####,
+ r#####"
+fn main() {
+ find('{');
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_replace_try_expr_with_match() {
+ check_doc_test(
+ "replace_try_expr_with_match",
+ r#####"
+//- minicore:option
+fn handle() {
+ let pat = Some(true)$0?;
+}
+"#####,
+ r#####"
+fn handle() {
+ let pat = match Some(true) {
+ Some(it) => it,
+ None => return None,
+ };
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_replace_turbofish_with_explicit_type() {
+ check_doc_test(
+ "replace_turbofish_with_explicit_type",
+ r#####"
+fn make<T>() -> T { ) }
+fn main() {
+ let a = make$0::<i32>();
+}
+"#####,
+ r#####"
+fn make<T>() -> T { ) }
+fn main() {
+ let a: i32 = make();
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_sort_items() {
+ check_doc_test(
+ "sort_items",
+ r#####"
+struct $0Foo$0 { second: u32, first: String }
+"#####,
+ r#####"
+struct Foo { first: String, second: u32 }
+"#####,
+ )
+}
+
+#[test]
+fn doctest_sort_items_1() {
+ check_doc_test(
+ "sort_items",
+ r#####"
+trait $0Bar$0 {
+ fn second(&self) -> u32;
+ fn first(&self) -> String;
+}
+"#####,
+ r#####"
+trait Bar {
+ fn first(&self) -> String;
+ fn second(&self) -> u32;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_sort_items_2() {
+ check_doc_test(
+ "sort_items",
+ r#####"
+struct Baz;
+impl $0Baz$0 {
+ fn second(&self) -> u32;
+ fn first(&self) -> String;
+}
+"#####,
+ r#####"
+struct Baz;
+impl Baz {
+ fn first(&self) -> String;
+ fn second(&self) -> u32;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_sort_items_3() {
+ check_doc_test(
+ "sort_items",
+ r#####"
+enum $0Animal$0 {
+ Dog(String, f64),
+ Cat { weight: f64, name: String },
+}
+"#####,
+ r#####"
+enum Animal {
+ Cat { weight: f64, name: String },
+ Dog(String, f64),
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_sort_items_4() {
+ check_doc_test(
+ "sort_items",
+ r#####"
+enum Animal {
+ Dog(String, f64),
+ Cat $0{ weight: f64, name: String }$0,
+}
+"#####,
+ r#####"
+enum Animal {
+ Dog(String, f64),
+ Cat { name: String, weight: f64 },
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_split_import() {
+ check_doc_test(
+ "split_import",
+ r#####"
+use std::$0collections::HashMap;
+"#####,
+ r#####"
+use std::{collections::HashMap};
+"#####,
+ )
+}
+
+#[test]
+fn doctest_toggle_ignore() {
+ check_doc_test(
+ "toggle_ignore",
+ r#####"
+$0#[test]
+fn arithmetics {
+ assert_eq!(2 + 2, 5);
+}
+"#####,
+ r#####"
+#[test]
+#[ignore]
+fn arithmetics {
+ assert_eq!(2 + 2, 5);
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_unmerge_use() {
+ check_doc_test(
+ "unmerge_use",
+ r#####"
+use std::fmt::{Debug, Display$0};
+"#####,
+ r#####"
+use std::fmt::{Debug};
+use std::fmt::Display;
+"#####,
+ )
+}
+
+#[test]
+fn doctest_unnecessary_async() {
+ check_doc_test(
+ "unnecessary_async",
+ r#####"
+pub async f$0n foo() {}
+pub async fn bar() { foo().await }
+"#####,
+ r#####"
+pub fn foo() {}
+pub async fn bar() { foo() }
+"#####,
+ )
+}
+
+#[test]
+fn doctest_unwrap_block() {
+ check_doc_test(
+ "unwrap_block",
+ r#####"
+fn foo() {
+ if true {$0
+ println!("foo");
+ }
+}
+"#####,
+ r#####"
+fn foo() {
+ println!("foo");
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_unwrap_result_return_type() {
+ check_doc_test(
+ "unwrap_result_return_type",
+ r#####"
+//- minicore: result
+fn foo() -> Result<i32>$0 { Ok(42i32) }
+"#####,
+ r#####"
+fn foo() -> i32 { 42i32 }
+"#####,
+ )
+}
+
+#[test]
+fn doctest_wrap_return_type_in_result() {
+ check_doc_test(
+ "wrap_return_type_in_result",
+ r#####"
+//- minicore: result
+fn foo() -> i32$0 { 42i32 }
+"#####,
+ r#####"
+fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }
+"#####,
+ )
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests/sourcegen.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests/sourcegen.rs
new file mode 100644
index 000000000..070b83d3c
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests/sourcegen.rs
@@ -0,0 +1,195 @@
+//! Generates `assists.md` documentation.
+
+use std::{fmt, fs, path::Path};
+
+use test_utils::project_root;
+
+#[test]
+fn sourcegen_assists_docs() {
+ let assists = Assist::collect();
+
+ {
+ // Generate doctests.
+
+ let mut buf = "
+use super::check_doc_test;
+"
+ .to_string();
+ for assist in assists.iter() {
+ for (idx, section) in assist.sections.iter().enumerate() {
+ let test_id =
+ if idx == 0 { assist.id.clone() } else { format!("{}_{}", &assist.id, idx) };
+ let test = format!(
+ r######"
+#[test]
+fn doctest_{}() {{
+ check_doc_test(
+ "{}",
+r#####"
+{}"#####, r#####"
+{}"#####)
+}}
+"######,
+ &test_id,
+ &assist.id,
+ reveal_hash_comments(&section.before),
+ reveal_hash_comments(&section.after)
+ );
+
+ buf.push_str(&test)
+ }
+ }
+ let buf = sourcegen::add_preamble("sourcegen_assists_docs", sourcegen::reformat(buf));
+ sourcegen::ensure_file_contents(
+ &project_root().join("crates/ide-assists/src/tests/generated.rs"),
+ &buf,
+ );
+ }
+
+ {
+ // Generate assists manual. Note that we do _not_ commit manual to the
+ // git repo. Instead, `cargo xtask release` runs this test before making
+ // a release.
+
+ let contents = sourcegen::add_preamble(
+ "sourcegen_assists_docs",
+ assists.into_iter().map(|it| it.to_string()).collect::<Vec<_>>().join("\n\n"),
+ );
+ let dst = project_root().join("docs/user/generated_assists.adoc");
+ fs::write(dst, contents).unwrap();
+ }
+}
+
+#[derive(Debug)]
+struct Section {
+ doc: String,
+ before: String,
+ after: String,
+}
+
+#[derive(Debug)]
+struct Assist {
+ id: String,
+ location: sourcegen::Location,
+ sections: Vec<Section>,
+}
+
+impl Assist {
+ fn collect() -> Vec<Assist> {
+ let handlers_dir = project_root().join("crates/ide-assists/src/handlers");
+
+ let mut res = Vec::new();
+ for path in sourcegen::list_rust_files(&handlers_dir) {
+ collect_file(&mut res, path.as_path());
+ }
+ res.sort_by(|lhs, rhs| lhs.id.cmp(&rhs.id));
+ return res;
+
+ fn collect_file(acc: &mut Vec<Assist>, path: &Path) {
+ let text = fs::read_to_string(path).unwrap();
+ let comment_blocks = sourcegen::CommentBlock::extract("Assist", &text);
+
+ for block in comment_blocks {
+ // FIXME: doesn't support blank lines yet, need to tweak
+ // `extract_comment_blocks` for that.
+ let id = block.id;
+ assert!(
+ id.chars().all(|it| it.is_ascii_lowercase() || it == '_'),
+ "invalid assist id: {:?}",
+ id
+ );
+ let mut lines = block.contents.iter().peekable();
+ let location = sourcegen::Location { file: path.to_path_buf(), line: block.line };
+ let mut assist = Assist { id, location, sections: Vec::new() };
+
+ while lines.peek().is_some() {
+ let doc = take_until(lines.by_ref(), "```").trim().to_string();
+ assert!(
+ (doc.chars().next().unwrap().is_ascii_uppercase() && doc.ends_with('.'))
+ || assist.sections.len() > 0,
+ "\n\n{}: assist docs should be proper sentences, with capitalization and a full stop at the end.\n\n{}\n\n",
+ &assist.id,
+ doc,
+ );
+
+ let before = take_until(lines.by_ref(), "```");
+
+ assert_eq!(lines.next().unwrap().as_str(), "->");
+ assert_eq!(lines.next().unwrap().as_str(), "```");
+ let after = take_until(lines.by_ref(), "```");
+
+ assist.sections.push(Section { doc, before, after });
+ }
+
+ acc.push(assist)
+ }
+ }
+
+ fn take_until<'a>(lines: impl Iterator<Item = &'a String>, marker: &str) -> String {
+ let mut buf = Vec::new();
+ for line in lines {
+ if line == marker {
+ break;
+ }
+ buf.push(line.clone());
+ }
+ buf.join("\n")
+ }
+ }
+}
+
+impl fmt::Display for Assist {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let _ = writeln!(
+ f,
+ "[discrete]\n=== `{}`
+**Source:** {}",
+ self.id, self.location,
+ );
+
+ for section in &self.sections {
+ let before = section.before.replace("$0", "┃"); // Unicode pseudo-graphics bar
+ let after = section.after.replace("$0", "┃");
+ let _ = writeln!(
+ f,
+ "
+{}
+
+.Before
+```rust
+{}```
+
+.After
+```rust
+{}```",
+ section.doc,
+ hide_hash_comments(&before),
+ hide_hash_comments(&after)
+ );
+ }
+
+ Ok(())
+ }
+}
+
+fn hide_hash_comments(text: &str) -> String {
+ text.split('\n') // want final newline
+ .filter(|&it| !(it.starts_with("# ") || it == "#"))
+ .map(|it| format!("{}\n", it))
+ .collect()
+}
+
+fn reveal_hash_comments(text: &str) -> String {
+ text.split('\n') // want final newline
+ .map(|it| {
+ if let Some(stripped) = it.strip_prefix("# ") {
+ stripped
+ } else if it == "#" {
+ ""
+ } else {
+ it
+ }
+ })
+ .map(|it| format!("{}\n", it))
+ .collect()
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
new file mode 100644
index 000000000..3e61d0741
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
@@ -0,0 +1,703 @@
+//! Assorted functions shared by several assists.
+
+use std::ops;
+
+use itertools::Itertools;
+
+pub(crate) use gen_trait_fn_body::gen_trait_fn_body;
+use hir::{db::HirDatabase, HirDisplay, Semantics};
+use ide_db::{famous_defs::FamousDefs, path_transform::PathTransform, RootDatabase, SnippetCap};
+use stdx::format_to;
+use syntax::{
+ ast::{
+ self,
+ edit::{self, AstNodeEdit},
+ edit_in_place::AttrsOwnerEdit,
+ make, HasArgList, HasAttrs, HasGenericParams, HasName, HasTypeBounds, Whitespace,
+ },
+ ted, AstNode, AstToken, Direction, SmolStr, SourceFile,
+ SyntaxKind::*,
+ SyntaxNode, TextRange, TextSize, T,
+};
+
+use crate::assist_context::{AssistBuilder, AssistContext};
+
+pub(crate) mod suggest_name;
+mod gen_trait_fn_body;
+
+pub(crate) fn unwrap_trivial_block(block_expr: ast::BlockExpr) -> ast::Expr {
+ extract_trivial_expression(&block_expr)
+ .filter(|expr| !expr.syntax().text().contains_char('\n'))
+ .unwrap_or_else(|| block_expr.into())
+}
+
+pub fn extract_trivial_expression(block_expr: &ast::BlockExpr) -> Option<ast::Expr> {
+ if block_expr.modifier().is_some() {
+ return None;
+ }
+ let stmt_list = block_expr.stmt_list()?;
+ let has_anything_else = |thing: &SyntaxNode| -> bool {
+ let mut non_trivial_children =
+ stmt_list.syntax().children_with_tokens().filter(|it| match it.kind() {
+ WHITESPACE | T!['{'] | T!['}'] => false,
+ _ => it.as_node() != Some(thing),
+ });
+ non_trivial_children.next().is_some()
+ };
+
+ if let Some(expr) = stmt_list.tail_expr() {
+ if has_anything_else(expr.syntax()) {
+ return None;
+ }
+ return Some(expr);
+ }
+ // Unwrap `{ continue; }`
+ let stmt = stmt_list.statements().next()?;
+ if let ast::Stmt::ExprStmt(expr_stmt) = stmt {
+ if has_anything_else(expr_stmt.syntax()) {
+ return None;
+ }
+ let expr = expr_stmt.expr()?;
+ if matches!(expr.syntax().kind(), CONTINUE_EXPR | BREAK_EXPR | RETURN_EXPR) {
+ return Some(expr);
+ }
+ }
+ None
+}
+
+/// This is a method with a heuristics to support test methods annotated with custom test annotations, such as
+/// `#[test_case(...)]`, `#[tokio::test]` and similar.
+/// Also a regular `#[test]` annotation is supported.
+///
+/// It may produce false positives, for example, `#[wasm_bindgen_test]` requires a different command to run the test,
+/// but it's better than not to have the runnables for the tests at all.
+pub fn test_related_attribute(fn_def: &ast::Fn) -> Option<ast::Attr> {
+ fn_def.attrs().find_map(|attr| {
+ let path = attr.path()?;
+ let text = path.syntax().text().to_string();
+ if text.starts_with("test") || text.ends_with("test") {
+ Some(attr)
+ } else {
+ None
+ }
+ })
+}
+
+#[derive(Copy, Clone, PartialEq)]
+pub enum DefaultMethods {
+ Only,
+ No,
+}
+
+pub fn filter_assoc_items(
+ sema: &Semantics<'_, RootDatabase>,
+ items: &[hir::AssocItem],
+ default_methods: DefaultMethods,
+) -> Vec<ast::AssocItem> {
+ fn has_def_name(item: &ast::AssocItem) -> bool {
+ match item {
+ ast::AssocItem::Fn(def) => def.name(),
+ ast::AssocItem::TypeAlias(def) => def.name(),
+ ast::AssocItem::Const(def) => def.name(),
+ ast::AssocItem::MacroCall(_) => None,
+ }
+ .is_some()
+ }
+
+ items
+ .iter()
+ // Note: This throws away items with no source.
+ .filter_map(|&i| {
+ let item = match i {
+ hir::AssocItem::Function(i) => ast::AssocItem::Fn(sema.source(i)?.value),
+ hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAlias(sema.source(i)?.value),
+ hir::AssocItem::Const(i) => ast::AssocItem::Const(sema.source(i)?.value),
+ };
+ Some(item)
+ })
+ .filter(has_def_name)
+ .filter(|it| match it {
+ ast::AssocItem::Fn(def) => matches!(
+ (default_methods, def.body()),
+ (DefaultMethods::Only, Some(_)) | (DefaultMethods::No, None)
+ ),
+ _ => default_methods == DefaultMethods::No,
+ })
+ .collect::<Vec<_>>()
+}
+
+pub fn add_trait_assoc_items_to_impl(
+ sema: &Semantics<'_, RootDatabase>,
+ items: Vec<ast::AssocItem>,
+ trait_: hir::Trait,
+ impl_: ast::Impl,
+ target_scope: hir::SemanticsScope<'_>,
+) -> (ast::Impl, ast::AssocItem) {
+ let source_scope = sema.scope_for_def(trait_);
+
+ let transform = PathTransform::trait_impl(&target_scope, &source_scope, trait_, impl_.clone());
+
+ let items = items.into_iter().map(|assoc_item| {
+ transform.apply(assoc_item.syntax());
+ assoc_item.remove_attrs_and_docs();
+ assoc_item
+ });
+
+ let res = impl_.clone_for_update();
+
+ let assoc_item_list = res.get_or_create_assoc_item_list();
+ let mut first_item = None;
+ for item in items {
+ first_item.get_or_insert_with(|| item.clone());
+ match &item {
+ ast::AssocItem::Fn(fn_) if fn_.body().is_none() => {
+ let body = make::block_expr(None, Some(make::ext::expr_todo()))
+ .indent(edit::IndentLevel(1));
+ ted::replace(fn_.get_or_create_body().syntax(), body.clone_for_update().syntax())
+ }
+ ast::AssocItem::TypeAlias(type_alias) => {
+ if let Some(type_bound_list) = type_alias.type_bound_list() {
+ type_bound_list.remove()
+ }
+ }
+ _ => {}
+ }
+
+ assoc_item_list.add_item(item)
+ }
+
+ (res, first_item.unwrap())
+}
+
+#[derive(Clone, Copy, Debug)]
+pub(crate) enum Cursor<'a> {
+ Replace(&'a SyntaxNode),
+ Before(&'a SyntaxNode),
+}
+
+impl<'a> Cursor<'a> {
+ fn node(self) -> &'a SyntaxNode {
+ match self {
+ Cursor::Replace(node) | Cursor::Before(node) => node,
+ }
+ }
+}
+
+pub(crate) fn render_snippet(_cap: SnippetCap, node: &SyntaxNode, cursor: Cursor<'_>) -> String {
+ assert!(cursor.node().ancestors().any(|it| it == *node));
+ let range = cursor.node().text_range() - node.text_range().start();
+ let range: ops::Range<usize> = range.into();
+
+ let mut placeholder = cursor.node().to_string();
+ escape(&mut placeholder);
+ let tab_stop = match cursor {
+ Cursor::Replace(placeholder) => format!("${{0:{}}}", placeholder),
+ Cursor::Before(placeholder) => format!("$0{}", placeholder),
+ };
+
+ let mut buf = node.to_string();
+ buf.replace_range(range, &tab_stop);
+ return buf;
+
+ fn escape(buf: &mut String) {
+ stdx::replace(buf, '{', r"\{");
+ stdx::replace(buf, '}', r"\}");
+ stdx::replace(buf, '$', r"\$");
+ }
+}
+
+pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize {
+ node.children_with_tokens()
+ .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR))
+ .map(|it| it.text_range().start())
+ .unwrap_or_else(|| node.text_range().start())
+}
+
+pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr {
+ invert_special_case(&expr).unwrap_or_else(|| make::expr_prefix(T![!], expr))
+}
+
+fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> {
+ match expr {
+ ast::Expr::BinExpr(bin) => {
+ let bin = bin.clone_for_update();
+ let op_token = bin.op_token()?;
+ let rev_token = match op_token.kind() {
+ T![==] => T![!=],
+ T![!=] => T![==],
+ T![<] => T![>=],
+ T![<=] => T![>],
+ T![>] => T![<=],
+ T![>=] => T![<],
+ // Parenthesize other expressions before prefixing `!`
+ _ => return Some(make::expr_prefix(T![!], make::expr_paren(expr.clone()))),
+ };
+ ted::replace(op_token, make::token(rev_token));
+ Some(bin.into())
+ }
+ ast::Expr::MethodCallExpr(mce) => {
+ let receiver = mce.receiver()?;
+ let method = mce.name_ref()?;
+ let arg_list = mce.arg_list()?;
+
+ let method = match method.text().as_str() {
+ "is_some" => "is_none",
+ "is_none" => "is_some",
+ "is_ok" => "is_err",
+ "is_err" => "is_ok",
+ _ => return None,
+ };
+ Some(make::expr_method_call(receiver, make::name_ref(method), arg_list))
+ }
+ ast::Expr::PrefixExpr(pe) if pe.op_kind()? == ast::UnaryOp::Not => match pe.expr()? {
+ ast::Expr::ParenExpr(parexpr) => parexpr.expr(),
+ _ => pe.expr(),
+ },
+ ast::Expr::Literal(lit) => match lit.kind() {
+ ast::LiteralKind::Bool(b) => match b {
+ true => Some(ast::Expr::Literal(make::expr_literal("false"))),
+ false => Some(ast::Expr::Literal(make::expr_literal("true"))),
+ },
+ _ => None,
+ },
+ _ => None,
+ }
+}
+
+pub(crate) fn next_prev() -> impl Iterator<Item = Direction> {
+ [Direction::Next, Direction::Prev].into_iter()
+}
+
+pub(crate) fn does_pat_match_variant(pat: &ast::Pat, var: &ast::Pat) -> bool {
+ let first_node_text = |pat: &ast::Pat| pat.syntax().first_child().map(|node| node.text());
+
+ let pat_head = match pat {
+ ast::Pat::IdentPat(bind_pat) => match bind_pat.pat() {
+ Some(p) => first_node_text(&p),
+ None => return pat.syntax().text() == var.syntax().text(),
+ },
+ pat => first_node_text(pat),
+ };
+
+ let var_head = first_node_text(var);
+
+ pat_head == var_head
+}
+
+pub(crate) fn does_nested_pattern(pat: &ast::Pat) -> bool {
+ let depth = calc_depth(pat, 0);
+
+ if 1 < depth {
+ return true;
+ }
+ false
+}
+
+fn calc_depth(pat: &ast::Pat, depth: usize) -> usize {
+ match pat {
+ ast::Pat::IdentPat(_)
+ | ast::Pat::BoxPat(_)
+ | ast::Pat::RestPat(_)
+ | ast::Pat::LiteralPat(_)
+ | ast::Pat::MacroPat(_)
+ | ast::Pat::OrPat(_)
+ | ast::Pat::ParenPat(_)
+ | ast::Pat::PathPat(_)
+ | ast::Pat::WildcardPat(_)
+ | ast::Pat::RangePat(_)
+ | ast::Pat::RecordPat(_)
+ | ast::Pat::RefPat(_)
+ | ast::Pat::SlicePat(_)
+ | ast::Pat::TuplePat(_)
+ | ast::Pat::ConstBlockPat(_) => depth,
+
+ // FIXME: Other patterns may also be nested. Currently it simply supports only `TupleStructPat`
+ ast::Pat::TupleStructPat(pat) => {
+ let mut max_depth = depth;
+ for p in pat.fields() {
+ let d = calc_depth(&p, depth + 1);
+ if d > max_depth {
+ max_depth = d
+ }
+ }
+ max_depth
+ }
+ }
+}
+
+// Uses a syntax-driven approach to find any impl blocks for the struct that
+// exist within the module/file
+//
+// Returns `None` if we've found an existing fn
+//
+// FIXME: change the new fn checking to a more semantic approach when that's more
+// viable (e.g. we process proc macros, etc)
+// FIXME: this partially overlaps with `find_impl_block_*`
+pub(crate) fn find_struct_impl(
+ ctx: &AssistContext<'_>,
+ adt: &ast::Adt,
+ name: &str,
+) -> Option<Option<ast::Impl>> {
+ let db = ctx.db();
+ let module = adt.syntax().parent()?;
+
+ let struct_def = ctx.sema.to_def(adt)?;
+
+ let block = module.descendants().filter_map(ast::Impl::cast).find_map(|impl_blk| {
+ let blk = ctx.sema.to_def(&impl_blk)?;
+
+ // FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}`
+ // (we currently use the wrong type parameter)
+ // also we wouldn't want to use e.g. `impl S<u32>`
+
+ let same_ty = match blk.self_ty(db).as_adt() {
+ Some(def) => def == struct_def,
+ None => false,
+ };
+ let not_trait_impl = blk.trait_(db).is_none();
+
+ if !(same_ty && not_trait_impl) {
+ None
+ } else {
+ Some(impl_blk)
+ }
+ });
+
+ if let Some(ref impl_blk) = block {
+ if has_fn(impl_blk, name) {
+ return None;
+ }
+ }
+
+ Some(block)
+}
+
+fn has_fn(imp: &ast::Impl, rhs_name: &str) -> bool {
+ if let Some(il) = imp.assoc_item_list() {
+ for item in il.assoc_items() {
+ if let ast::AssocItem::Fn(f) = item {
+ if let Some(name) = f.name() {
+ if name.text().eq_ignore_ascii_case(rhs_name) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ false
+}
+
+/// Find the start of the `impl` block for the given `ast::Impl`.
+//
+// FIXME: this partially overlaps with `find_struct_impl`
+pub(crate) fn find_impl_block_start(impl_def: ast::Impl, buf: &mut String) -> Option<TextSize> {
+ buf.push('\n');
+ let start = impl_def.assoc_item_list().and_then(|it| it.l_curly_token())?.text_range().end();
+ Some(start)
+}
+
+/// Find the end of the `impl` block for the given `ast::Impl`.
+//
+// FIXME: this partially overlaps with `find_struct_impl`
+pub(crate) fn find_impl_block_end(impl_def: ast::Impl, buf: &mut String) -> Option<TextSize> {
+ buf.push('\n');
+ let end = impl_def
+ .assoc_item_list()
+ .and_then(|it| it.r_curly_token())?
+ .prev_sibling_or_token()?
+ .text_range()
+ .end();
+ Some(end)
+}
+
+// Generates the surrounding `impl Type { <code> }` including type and lifetime
+// parameters
+pub(crate) fn generate_impl_text(adt: &ast::Adt, code: &str) -> String {
+ generate_impl_text_inner(adt, None, code)
+}
+
+// Generates the surrounding `impl <trait> for Type { <code> }` including type
+// and lifetime parameters
+pub(crate) fn generate_trait_impl_text(adt: &ast::Adt, trait_text: &str, code: &str) -> String {
+ generate_impl_text_inner(adt, Some(trait_text), code)
+}
+
+fn generate_impl_text_inner(adt: &ast::Adt, trait_text: Option<&str>, code: &str) -> String {
+ let generic_params = adt.generic_param_list();
+ let mut buf = String::with_capacity(code.len());
+ buf.push_str("\n\n");
+ adt.attrs()
+ .filter(|attr| attr.as_simple_call().map(|(name, _arg)| name == "cfg").unwrap_or(false))
+ .for_each(|attr| buf.push_str(format!("{}\n", attr).as_str()));
+ buf.push_str("impl");
+ if let Some(generic_params) = &generic_params {
+ let lifetimes = generic_params.lifetime_params().map(|lt| format!("{}", lt.syntax()));
+ let toc_params = generic_params.type_or_const_params().map(|toc_param| {
+ let type_param = match toc_param {
+ ast::TypeOrConstParam::Type(x) => x,
+ ast::TypeOrConstParam::Const(x) => return x.syntax().to_string(),
+ };
+ let mut buf = String::new();
+ if let Some(it) = type_param.name() {
+ format_to!(buf, "{}", it.syntax());
+ }
+ if let Some(it) = type_param.colon_token() {
+ format_to!(buf, "{} ", it);
+ }
+ if let Some(it) = type_param.type_bound_list() {
+ format_to!(buf, "{}", it.syntax());
+ }
+ buf
+ });
+ let generics = lifetimes.chain(toc_params).format(", ");
+ format_to!(buf, "<{}>", generics);
+ }
+ buf.push(' ');
+ if let Some(trait_text) = trait_text {
+ buf.push_str(trait_text);
+ buf.push_str(" for ");
+ }
+ buf.push_str(&adt.name().unwrap().text());
+ if let Some(generic_params) = generic_params {
+ let lifetime_params = generic_params
+ .lifetime_params()
+ .filter_map(|it| it.lifetime())
+ .map(|it| SmolStr::from(it.text()));
+ let toc_params = generic_params
+ .type_or_const_params()
+ .filter_map(|it| it.name())
+ .map(|it| SmolStr::from(it.text()));
+ format_to!(buf, "<{}>", lifetime_params.chain(toc_params).format(", "))
+ }
+
+ match adt.where_clause() {
+ Some(where_clause) => {
+ format_to!(buf, "\n{}\n{{\n{}\n}}", where_clause, code);
+ }
+ None => {
+ format_to!(buf, " {{\n{}\n}}", code);
+ }
+ }
+
+ buf
+}
+
+pub(crate) fn add_method_to_adt(
+ builder: &mut AssistBuilder,
+ adt: &ast::Adt,
+ impl_def: Option<ast::Impl>,
+ method: &str,
+) {
+ let mut buf = String::with_capacity(method.len() + 2);
+ if impl_def.is_some() {
+ buf.push('\n');
+ }
+ buf.push_str(method);
+
+ let start_offset = impl_def
+ .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf))
+ .unwrap_or_else(|| {
+ buf = generate_impl_text(adt, &buf);
+ adt.syntax().text_range().end()
+ });
+
+ builder.insert(start_offset, buf);
+}
+
+#[derive(Debug)]
+pub(crate) struct ReferenceConversion {
+ conversion: ReferenceConversionType,
+ ty: hir::Type,
+}
+
+#[derive(Debug)]
+enum ReferenceConversionType {
+ // reference can be stripped if the type is Copy
+ Copy,
+ // &String -> &str
+ AsRefStr,
+ // &Vec<T> -> &[T]
+ AsRefSlice,
+ // &Box<T> -> &T
+ Dereferenced,
+ // &Option<T> -> Option<&T>
+ Option,
+ // &Result<T, E> -> Result<&T, &E>
+ Result,
+}
+
+impl ReferenceConversion {
+ pub(crate) fn convert_type(&self, db: &dyn HirDatabase) -> String {
+ match self.conversion {
+ ReferenceConversionType::Copy => self.ty.display(db).to_string(),
+ ReferenceConversionType::AsRefStr => "&str".to_string(),
+ ReferenceConversionType::AsRefSlice => {
+ let type_argument_name =
+ self.ty.type_arguments().next().unwrap().display(db).to_string();
+ format!("&[{}]", type_argument_name)
+ }
+ ReferenceConversionType::Dereferenced => {
+ let type_argument_name =
+ self.ty.type_arguments().next().unwrap().display(db).to_string();
+ format!("&{}", type_argument_name)
+ }
+ ReferenceConversionType::Option => {
+ let type_argument_name =
+ self.ty.type_arguments().next().unwrap().display(db).to_string();
+ format!("Option<&{}>", type_argument_name)
+ }
+ ReferenceConversionType::Result => {
+ let mut type_arguments = self.ty.type_arguments();
+ let first_type_argument_name =
+ type_arguments.next().unwrap().display(db).to_string();
+ let second_type_argument_name =
+ type_arguments.next().unwrap().display(db).to_string();
+ format!("Result<&{}, &{}>", first_type_argument_name, second_type_argument_name)
+ }
+ }
+ }
+
+ pub(crate) fn getter(&self, field_name: String) -> String {
+ match self.conversion {
+ ReferenceConversionType::Copy => format!("self.{}", field_name),
+ ReferenceConversionType::AsRefStr
+ | ReferenceConversionType::AsRefSlice
+ | ReferenceConversionType::Dereferenced
+ | ReferenceConversionType::Option
+ | ReferenceConversionType::Result => format!("self.{}.as_ref()", field_name),
+ }
+ }
+}
+
+// FIXME: It should return a new hir::Type, but currently constructing new types is too cumbersome
+// and all users of this function operate on string type names, so they can do the conversion
+// itself themselves.
+pub(crate) fn convert_reference_type(
+ ty: hir::Type,
+ db: &RootDatabase,
+ famous_defs: &FamousDefs<'_, '_>,
+) -> Option<ReferenceConversion> {
+ handle_copy(&ty, db)
+ .or_else(|| handle_as_ref_str(&ty, db, famous_defs))
+ .or_else(|| handle_as_ref_slice(&ty, db, famous_defs))
+ .or_else(|| handle_dereferenced(&ty, db, famous_defs))
+ .or_else(|| handle_option_as_ref(&ty, db, famous_defs))
+ .or_else(|| handle_result_as_ref(&ty, db, famous_defs))
+ .map(|conversion| ReferenceConversion { ty, conversion })
+}
+
+fn handle_copy(ty: &hir::Type, db: &dyn HirDatabase) -> Option<ReferenceConversionType> {
+ ty.is_copy(db).then(|| ReferenceConversionType::Copy)
+}
+
+fn handle_as_ref_str(
+ ty: &hir::Type,
+ db: &dyn HirDatabase,
+ famous_defs: &FamousDefs<'_, '_>,
+) -> Option<ReferenceConversionType> {
+ let str_type = hir::BuiltinType::str().ty(db);
+
+ ty.impls_trait(db, famous_defs.core_convert_AsRef()?, &[str_type])
+ .then(|| ReferenceConversionType::AsRefStr)
+}
+
+fn handle_as_ref_slice(
+ ty: &hir::Type,
+ db: &dyn HirDatabase,
+ famous_defs: &FamousDefs<'_, '_>,
+) -> Option<ReferenceConversionType> {
+ let type_argument = ty.type_arguments().next()?;
+ let slice_type = hir::Type::new_slice(type_argument);
+
+ ty.impls_trait(db, famous_defs.core_convert_AsRef()?, &[slice_type])
+ .then(|| ReferenceConversionType::AsRefSlice)
+}
+
+fn handle_dereferenced(
+ ty: &hir::Type,
+ db: &dyn HirDatabase,
+ famous_defs: &FamousDefs<'_, '_>,
+) -> Option<ReferenceConversionType> {
+ let type_argument = ty.type_arguments().next()?;
+
+ ty.impls_trait(db, famous_defs.core_convert_AsRef()?, &[type_argument])
+ .then(|| ReferenceConversionType::Dereferenced)
+}
+
+fn handle_option_as_ref(
+ ty: &hir::Type,
+ db: &dyn HirDatabase,
+ famous_defs: &FamousDefs<'_, '_>,
+) -> Option<ReferenceConversionType> {
+ if ty.as_adt() == famous_defs.core_option_Option()?.ty(db).as_adt() {
+ Some(ReferenceConversionType::Option)
+ } else {
+ None
+ }
+}
+
+fn handle_result_as_ref(
+ ty: &hir::Type,
+ db: &dyn HirDatabase,
+ famous_defs: &FamousDefs<'_, '_>,
+) -> Option<ReferenceConversionType> {
+ if ty.as_adt() == famous_defs.core_result_Result()?.ty(db).as_adt() {
+ Some(ReferenceConversionType::Result)
+ } else {
+ None
+ }
+}
+
+pub(crate) fn get_methods(items: &ast::AssocItemList) -> Vec<ast::Fn> {
+ items
+ .assoc_items()
+ .flat_map(|i| match i {
+ ast::AssocItem::Fn(f) => Some(f),
+ _ => None,
+ })
+ .filter(|f| f.name().is_some())
+ .collect()
+}
+
+/// Trim(remove leading and trailing whitespace) `initial_range` in `source_file`, return the trimmed range.
+pub(crate) fn trimmed_text_range(source_file: &SourceFile, initial_range: TextRange) -> TextRange {
+ let mut trimmed_range = initial_range;
+ while source_file
+ .syntax()
+ .token_at_offset(trimmed_range.start())
+ .find_map(Whitespace::cast)
+ .is_some()
+ && trimmed_range.start() < trimmed_range.end()
+ {
+ let start = trimmed_range.start() + TextSize::from(1);
+ trimmed_range = TextRange::new(start, trimmed_range.end());
+ }
+ while source_file
+ .syntax()
+ .token_at_offset(trimmed_range.end())
+ .find_map(Whitespace::cast)
+ .is_some()
+ && trimmed_range.start() < trimmed_range.end()
+ {
+ let end = trimmed_range.end() - TextSize::from(1);
+ trimmed_range = TextRange::new(trimmed_range.start(), end);
+ }
+ trimmed_range
+}
+
+/// Convert a list of function params to a list of arguments that can be passed
+/// into a function call.
+pub(crate) fn convert_param_list_to_arg_list(list: ast::ParamList) -> ast::ArgList {
+ let mut args = vec![];
+ for param in list.params() {
+ if let Some(ast::Pat::IdentPat(pat)) = param.pat() {
+ if let Some(name) = pat.name() {
+ let name = name.to_string();
+ let expr = make::expr_path(make::ext::ident_path(&name));
+ args.push(expr);
+ }
+ }
+ }
+ make::arg_list(args)
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs
new file mode 100644
index 000000000..7a0c91295
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs
@@ -0,0 +1,661 @@
+//! This module contains functions to generate default trait impl function bodies where possible.
+
+use syntax::{
+ ast::{self, edit::AstNodeEdit, make, AstNode, BinaryOp, CmpOp, HasName, LogicOp},
+ ted,
+};
+
+/// Generate custom trait bodies without default implementation where possible.
+///
+/// Returns `Option` so that we can use `?` rather than `if let Some`. Returning
+/// `None` means that generating a custom trait body failed, and the body will remain
+/// as `todo!` instead.
+pub(crate) fn gen_trait_fn_body(
+ func: &ast::Fn,
+ trait_path: &ast::Path,
+ adt: &ast::Adt,
+) -> Option<()> {
+ match trait_path.segment()?.name_ref()?.text().as_str() {
+ "Clone" => gen_clone_impl(adt, func),
+ "Debug" => gen_debug_impl(adt, func),
+ "Default" => gen_default_impl(adt, func),
+ "Hash" => gen_hash_impl(adt, func),
+ "PartialEq" => gen_partial_eq(adt, func),
+ "PartialOrd" => gen_partial_ord(adt, func),
+ _ => None,
+ }
+}
+
+/// Generate a `Clone` impl based on the fields and members of the target type.
+fn gen_clone_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
+ stdx::always!(func.name().map_or(false, |name| name.text() == "clone"));
+ fn gen_clone_call(target: ast::Expr) -> ast::Expr {
+ let method = make::name_ref("clone");
+ make::expr_method_call(target, method, make::arg_list(None))
+ }
+ let expr = match adt {
+ // `Clone` cannot be derived for unions, so no default impl can be provided.
+ ast::Adt::Union(_) => return None,
+ ast::Adt::Enum(enum_) => {
+ let list = enum_.variant_list()?;
+ let mut arms = vec![];
+ for variant in list.variants() {
+ let name = variant.name()?;
+ let variant_name = make::ext::path_from_idents(["Self", &format!("{}", name)])?;
+
+ match variant.field_list() {
+ // => match self { Self::Name { x } => Self::Name { x: x.clone() } }
+ Some(ast::FieldList::RecordFieldList(list)) => {
+ let mut pats = vec![];
+ let mut fields = vec![];
+ for field in list.fields() {
+ let field_name = field.name()?;
+ let pat = make::ident_pat(false, false, field_name.clone());
+ pats.push(pat.into());
+
+ let path = make::ext::ident_path(&field_name.to_string());
+ let method_call = gen_clone_call(make::expr_path(path));
+ let name_ref = make::name_ref(&field_name.to_string());
+ let field = make::record_expr_field(name_ref, Some(method_call));
+ fields.push(field);
+ }
+ let pat = make::record_pat(variant_name.clone(), pats.into_iter());
+ let fields = make::record_expr_field_list(fields);
+ let record_expr = make::record_expr(variant_name, fields).into();
+ arms.push(make::match_arm(Some(pat.into()), None, record_expr));
+ }
+
+ // => match self { Self::Name(arg1) => Self::Name(arg1.clone()) }
+ Some(ast::FieldList::TupleFieldList(list)) => {
+ let mut pats = vec![];
+ let mut fields = vec![];
+ for (i, _) in list.fields().enumerate() {
+ let field_name = format!("arg{}", i);
+ let pat = make::ident_pat(false, false, make::name(&field_name));
+ pats.push(pat.into());
+
+ let f_path = make::expr_path(make::ext::ident_path(&field_name));
+ fields.push(gen_clone_call(f_path));
+ }
+ let pat = make::tuple_struct_pat(variant_name.clone(), pats.into_iter());
+ let struct_name = make::expr_path(variant_name);
+ let tuple_expr = make::expr_call(struct_name, make::arg_list(fields));
+ arms.push(make::match_arm(Some(pat.into()), None, tuple_expr));
+ }
+
+ // => match self { Self::Name => Self::Name }
+ None => {
+ let pattern = make::path_pat(variant_name.clone());
+ let variant_expr = make::expr_path(variant_name);
+ arms.push(make::match_arm(Some(pattern), None, variant_expr));
+ }
+ }
+ }
+
+ let match_target = make::expr_path(make::ext::ident_path("self"));
+ let list = make::match_arm_list(arms).indent(ast::edit::IndentLevel(1));
+ make::expr_match(match_target, list)
+ }
+ ast::Adt::Struct(strukt) => {
+ match strukt.field_list() {
+ // => Self { name: self.name.clone() }
+ Some(ast::FieldList::RecordFieldList(field_list)) => {
+ let mut fields = vec![];
+ for field in field_list.fields() {
+ let base = make::expr_path(make::ext::ident_path("self"));
+ let target = make::expr_field(base, &field.name()?.to_string());
+ let method_call = gen_clone_call(target);
+ let name_ref = make::name_ref(&field.name()?.to_string());
+ let field = make::record_expr_field(name_ref, Some(method_call));
+ fields.push(field);
+ }
+ let struct_name = make::ext::ident_path("Self");
+ let fields = make::record_expr_field_list(fields);
+ make::record_expr(struct_name, fields).into()
+ }
+ // => Self(self.0.clone(), self.1.clone())
+ Some(ast::FieldList::TupleFieldList(field_list)) => {
+ let mut fields = vec![];
+ for (i, _) in field_list.fields().enumerate() {
+ let f_path = make::expr_path(make::ext::ident_path("self"));
+ let target = make::expr_field(f_path, &format!("{}", i));
+ fields.push(gen_clone_call(target));
+ }
+ let struct_name = make::expr_path(make::ext::ident_path("Self"));
+ make::expr_call(struct_name, make::arg_list(fields))
+ }
+ // => Self { }
+ None => {
+ let struct_name = make::ext::ident_path("Self");
+ let fields = make::record_expr_field_list(None);
+ make::record_expr(struct_name, fields).into()
+ }
+ }
+ }
+ };
+ let body = make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1));
+ ted::replace(func.body()?.syntax(), body.clone_for_update().syntax());
+ Some(())
+}
+
+/// Generate a `Debug` impl based on the fields and members of the target type.
+fn gen_debug_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
+ let annotated_name = adt.name()?;
+ match adt {
+ // `Debug` cannot be derived for unions, so no default impl can be provided.
+ ast::Adt::Union(_) => None,
+
+ // => match self { Self::Variant => write!(f, "Variant") }
+ ast::Adt::Enum(enum_) => {
+ let list = enum_.variant_list()?;
+ let mut arms = vec![];
+ for variant in list.variants() {
+ let name = variant.name()?;
+ let variant_name = make::ext::path_from_idents(["Self", &format!("{}", name)])?;
+ let target = make::expr_path(make::ext::ident_path("f"));
+
+ match variant.field_list() {
+ Some(ast::FieldList::RecordFieldList(list)) => {
+ // => f.debug_struct(name)
+ let target = make::expr_path(make::ext::ident_path("f"));
+ let method = make::name_ref("debug_struct");
+ let struct_name = format!("\"{}\"", name);
+ let args = make::arg_list(Some(make::expr_literal(&struct_name).into()));
+ let mut expr = make::expr_method_call(target, method, args);
+
+ let mut pats = vec![];
+ for field in list.fields() {
+ let field_name = field.name()?;
+
+ // create a field pattern for use in `MyStruct { fields.. }`
+ let pat = make::ident_pat(false, false, field_name.clone());
+ pats.push(pat.into());
+
+ // => <expr>.field("field_name", field)
+ let method_name = make::name_ref("field");
+ let name = make::expr_literal(&(format!("\"{}\"", field_name))).into();
+ let path = &format!("{}", field_name);
+ let path = make::expr_path(make::ext::ident_path(path));
+ let args = make::arg_list(vec![name, path]);
+ expr = make::expr_method_call(expr, method_name, args);
+ }
+
+ // => <expr>.finish()
+ let method = make::name_ref("finish");
+ let expr = make::expr_method_call(expr, method, make::arg_list(None));
+
+ // => MyStruct { fields.. } => f.debug_struct("MyStruct")...finish(),
+ let pat = make::record_pat(variant_name.clone(), pats.into_iter());
+ arms.push(make::match_arm(Some(pat.into()), None, expr));
+ }
+ Some(ast::FieldList::TupleFieldList(list)) => {
+ // => f.debug_tuple(name)
+ let target = make::expr_path(make::ext::ident_path("f"));
+ let method = make::name_ref("debug_tuple");
+ let struct_name = format!("\"{}\"", name);
+ let args = make::arg_list(Some(make::expr_literal(&struct_name).into()));
+ let mut expr = make::expr_method_call(target, method, args);
+
+ let mut pats = vec![];
+ for (i, _) in list.fields().enumerate() {
+ let name = format!("arg{}", i);
+
+ // create a field pattern for use in `MyStruct(fields..)`
+ let field_name = make::name(&name);
+ let pat = make::ident_pat(false, false, field_name.clone());
+ pats.push(pat.into());
+
+ // => <expr>.field(field)
+ let method_name = make::name_ref("field");
+ let field_path = &name.to_string();
+ let field_path = make::expr_path(make::ext::ident_path(field_path));
+ let args = make::arg_list(vec![field_path]);
+ expr = make::expr_method_call(expr, method_name, args);
+ }
+
+ // => <expr>.finish()
+ let method = make::name_ref("finish");
+ let expr = make::expr_method_call(expr, method, make::arg_list(None));
+
+ // => MyStruct (fields..) => f.debug_tuple("MyStruct")...finish(),
+ let pat = make::tuple_struct_pat(variant_name.clone(), pats.into_iter());
+ arms.push(make::match_arm(Some(pat.into()), None, expr));
+ }
+ None => {
+ let fmt_string = make::expr_literal(&(format!("\"{}\"", name))).into();
+ let args = make::arg_list([target, fmt_string]);
+ let macro_name = make::expr_path(make::ext::ident_path("write"));
+ let macro_call = make::expr_macro_call(macro_name, args);
+
+ let variant_name = make::path_pat(variant_name);
+ arms.push(make::match_arm(Some(variant_name), None, macro_call));
+ }
+ }
+ }
+
+ let match_target = make::expr_path(make::ext::ident_path("self"));
+ let list = make::match_arm_list(arms).indent(ast::edit::IndentLevel(1));
+ let match_expr = make::expr_match(match_target, list);
+
+ let body = make::block_expr(None, Some(match_expr));
+ let body = body.indent(ast::edit::IndentLevel(1));
+ ted::replace(func.body()?.syntax(), body.clone_for_update().syntax());
+ Some(())
+ }
+
+ ast::Adt::Struct(strukt) => {
+ let name = format!("\"{}\"", annotated_name);
+ let args = make::arg_list(Some(make::expr_literal(&name).into()));
+ let target = make::expr_path(make::ext::ident_path("f"));
+
+ let expr = match strukt.field_list() {
+ // => f.debug_struct("Name").finish()
+ None => make::expr_method_call(target, make::name_ref("debug_struct"), args),
+
+ // => f.debug_struct("Name").field("foo", &self.foo).finish()
+ Some(ast::FieldList::RecordFieldList(field_list)) => {
+ let method = make::name_ref("debug_struct");
+ let mut expr = make::expr_method_call(target, method, args);
+ for field in field_list.fields() {
+ let name = field.name()?;
+ let f_name = make::expr_literal(&(format!("\"{}\"", name))).into();
+ let f_path = make::expr_path(make::ext::ident_path("self"));
+ let f_path = make::expr_ref(f_path, false);
+ let f_path = make::expr_field(f_path, &format!("{}", name));
+ let args = make::arg_list([f_name, f_path]);
+ expr = make::expr_method_call(expr, make::name_ref("field"), args);
+ }
+ expr
+ }
+
+ // => f.debug_tuple("Name").field(self.0).finish()
+ Some(ast::FieldList::TupleFieldList(field_list)) => {
+ let method = make::name_ref("debug_tuple");
+ let mut expr = make::expr_method_call(target, method, args);
+ for (i, _) in field_list.fields().enumerate() {
+ let f_path = make::expr_path(make::ext::ident_path("self"));
+ let f_path = make::expr_ref(f_path, false);
+ let f_path = make::expr_field(f_path, &format!("{}", i));
+ let method = make::name_ref("field");
+ expr = make::expr_method_call(expr, method, make::arg_list(Some(f_path)));
+ }
+ expr
+ }
+ };
+
+ let method = make::name_ref("finish");
+ let expr = make::expr_method_call(expr, method, make::arg_list(None));
+ let body = make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1));
+ ted::replace(func.body()?.syntax(), body.clone_for_update().syntax());
+ Some(())
+ }
+ }
+}
+
+/// Generate a `Debug` impl based on the fields and members of the target type.
+fn gen_default_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
+ fn gen_default_call() -> Option<ast::Expr> {
+ let fn_name = make::ext::path_from_idents(["Default", "default"])?;
+ Some(make::expr_call(make::expr_path(fn_name), make::arg_list(None)))
+ }
+ match adt {
+ // `Debug` cannot be derived for unions, so no default impl can be provided.
+ ast::Adt::Union(_) => None,
+ // Deriving `Debug` for enums is not stable yet.
+ ast::Adt::Enum(_) => None,
+ ast::Adt::Struct(strukt) => {
+ let expr = match strukt.field_list() {
+ Some(ast::FieldList::RecordFieldList(field_list)) => {
+ let mut fields = vec![];
+ for field in field_list.fields() {
+ let method_call = gen_default_call()?;
+ let name_ref = make::name_ref(&field.name()?.to_string());
+ let field = make::record_expr_field(name_ref, Some(method_call));
+ fields.push(field);
+ }
+ let struct_name = make::ext::ident_path("Self");
+ let fields = make::record_expr_field_list(fields);
+ make::record_expr(struct_name, fields).into()
+ }
+ Some(ast::FieldList::TupleFieldList(field_list)) => {
+ let struct_name = make::expr_path(make::ext::ident_path("Self"));
+ let fields = field_list
+ .fields()
+ .map(|_| gen_default_call())
+ .collect::<Option<Vec<ast::Expr>>>()?;
+ make::expr_call(struct_name, make::arg_list(fields))
+ }
+ None => {
+ let struct_name = make::ext::ident_path("Self");
+ let fields = make::record_expr_field_list(None);
+ make::record_expr(struct_name, fields).into()
+ }
+ };
+ let body = make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1));
+ ted::replace(func.body()?.syntax(), body.clone_for_update().syntax());
+ Some(())
+ }
+ }
+}
+
+/// Generate a `Hash` impl based on the fields and members of the target type.
+fn gen_hash_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
+ stdx::always!(func.name().map_or(false, |name| name.text() == "hash"));
+ fn gen_hash_call(target: ast::Expr) -> ast::Stmt {
+ let method = make::name_ref("hash");
+ let arg = make::expr_path(make::ext::ident_path("state"));
+ let expr = make::expr_method_call(target, method, make::arg_list(Some(arg)));
+ make::expr_stmt(expr).into()
+ }
+
+ let body = match adt {
+ // `Hash` cannot be derived for unions, so no default impl can be provided.
+ ast::Adt::Union(_) => return None,
+
+ // => std::mem::discriminant(self).hash(state);
+ ast::Adt::Enum(_) => {
+ let fn_name = make_discriminant()?;
+
+ let arg = make::expr_path(make::ext::ident_path("self"));
+ let fn_call = make::expr_call(fn_name, make::arg_list(Some(arg)));
+ let stmt = gen_hash_call(fn_call);
+
+ make::block_expr(Some(stmt), None).indent(ast::edit::IndentLevel(1))
+ }
+ ast::Adt::Struct(strukt) => match strukt.field_list() {
+ // => self.<field>.hash(state);
+ Some(ast::FieldList::RecordFieldList(field_list)) => {
+ let mut stmts = vec![];
+ for field in field_list.fields() {
+ let base = make::expr_path(make::ext::ident_path("self"));
+ let target = make::expr_field(base, &field.name()?.to_string());
+ stmts.push(gen_hash_call(target));
+ }
+ make::block_expr(stmts, None).indent(ast::edit::IndentLevel(1))
+ }
+
+ // => self.<field_index>.hash(state);
+ Some(ast::FieldList::TupleFieldList(field_list)) => {
+ let mut stmts = vec![];
+ for (i, _) in field_list.fields().enumerate() {
+ let base = make::expr_path(make::ext::ident_path("self"));
+ let target = make::expr_field(base, &format!("{}", i));
+ stmts.push(gen_hash_call(target));
+ }
+ make::block_expr(stmts, None).indent(ast::edit::IndentLevel(1))
+ }
+
+ // No fields in the body means there's nothing to hash.
+ None => return None,
+ },
+ };
+
+ ted::replace(func.body()?.syntax(), body.clone_for_update().syntax());
+ Some(())
+}
+
+/// Generate a `PartialEq` impl based on the fields and members of the target type.
+fn gen_partial_eq(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
+ stdx::always!(func.name().map_or(false, |name| name.text() == "eq"));
+ fn gen_eq_chain(expr: Option<ast::Expr>, cmp: ast::Expr) -> Option<ast::Expr> {
+ match expr {
+ Some(expr) => Some(make::expr_bin_op(expr, BinaryOp::LogicOp(LogicOp::And), cmp)),
+ None => Some(cmp),
+ }
+ }
+
+ fn gen_record_pat_field(field_name: &str, pat_name: &str) -> ast::RecordPatField {
+ let pat = make::ext::simple_ident_pat(make::name(pat_name));
+ let name_ref = make::name_ref(field_name);
+ make::record_pat_field(name_ref, pat.into())
+ }
+
+ fn gen_record_pat(record_name: ast::Path, fields: Vec<ast::RecordPatField>) -> ast::RecordPat {
+ let list = make::record_pat_field_list(fields);
+ make::record_pat_with_fields(record_name, list)
+ }
+
+ fn gen_variant_path(variant: &ast::Variant) -> Option<ast::Path> {
+ make::ext::path_from_idents(["Self", &variant.name()?.to_string()])
+ }
+
+ fn gen_tuple_field(field_name: &String) -> ast::Pat {
+ ast::Pat::IdentPat(make::ident_pat(false, false, make::name(field_name)))
+ }
+
+ // FIXME: return `None` if the trait carries a generic type; we can only
+ // generate this code `Self` for the time being.
+
+ let body = match adt {
+ // `PartialEq` cannot be derived for unions, so no default impl can be provided.
+ ast::Adt::Union(_) => return None,
+
+ ast::Adt::Enum(enum_) => {
+ // => std::mem::discriminant(self) == std::mem::discriminant(other)
+ let lhs_name = make::expr_path(make::ext::ident_path("self"));
+ let lhs = make::expr_call(make_discriminant()?, make::arg_list(Some(lhs_name.clone())));
+ let rhs_name = make::expr_path(make::ext::ident_path("other"));
+ let rhs = make::expr_call(make_discriminant()?, make::arg_list(Some(rhs_name.clone())));
+ let eq_check =
+ make::expr_bin_op(lhs, BinaryOp::CmpOp(CmpOp::Eq { negated: false }), rhs);
+
+ let mut n_cases = 0;
+ let mut arms = vec![];
+ for variant in enum_.variant_list()?.variants() {
+ n_cases += 1;
+ match variant.field_list() {
+ // => (Self::Bar { bin: l_bin }, Self::Bar { bin: r_bin }) => l_bin == r_bin,
+ Some(ast::FieldList::RecordFieldList(list)) => {
+ let mut expr = None;
+ let mut l_fields = vec![];
+ let mut r_fields = vec![];
+
+ for field in list.fields() {
+ let field_name = field.name()?.to_string();
+
+ let l_name = &format!("l_{}", field_name);
+ l_fields.push(gen_record_pat_field(&field_name, l_name));
+
+ let r_name = &format!("r_{}", field_name);
+ r_fields.push(gen_record_pat_field(&field_name, r_name));
+
+ let lhs = make::expr_path(make::ext::ident_path(l_name));
+ let rhs = make::expr_path(make::ext::ident_path(r_name));
+ let cmp = make::expr_bin_op(
+ lhs,
+ BinaryOp::CmpOp(CmpOp::Eq { negated: false }),
+ rhs,
+ );
+ expr = gen_eq_chain(expr, cmp);
+ }
+
+ let left = gen_record_pat(gen_variant_path(&variant)?, l_fields);
+ let right = gen_record_pat(gen_variant_path(&variant)?, r_fields);
+ let tuple = make::tuple_pat(vec![left.into(), right.into()]);
+
+ if let Some(expr) = expr {
+ arms.push(make::match_arm(Some(tuple.into()), None, expr));
+ }
+ }
+
+ Some(ast::FieldList::TupleFieldList(list)) => {
+ let mut expr = None;
+ let mut l_fields = vec![];
+ let mut r_fields = vec![];
+
+ for (i, _) in list.fields().enumerate() {
+ let field_name = format!("{}", i);
+
+ let l_name = format!("l{}", field_name);
+ l_fields.push(gen_tuple_field(&l_name));
+
+ let r_name = format!("r{}", field_name);
+ r_fields.push(gen_tuple_field(&r_name));
+
+ let lhs = make::expr_path(make::ext::ident_path(&l_name));
+ let rhs = make::expr_path(make::ext::ident_path(&r_name));
+ let cmp = make::expr_bin_op(
+ lhs,
+ BinaryOp::CmpOp(CmpOp::Eq { negated: false }),
+ rhs,
+ );
+ expr = gen_eq_chain(expr, cmp);
+ }
+
+ let left = make::tuple_struct_pat(gen_variant_path(&variant)?, l_fields);
+ let right = make::tuple_struct_pat(gen_variant_path(&variant)?, r_fields);
+ let tuple = make::tuple_pat(vec![left.into(), right.into()]);
+
+ if let Some(expr) = expr {
+ arms.push(make::match_arm(Some(tuple.into()), None, expr));
+ }
+ }
+ None => continue,
+ }
+ }
+
+ let expr = match arms.len() {
+ 0 => eq_check,
+ _ => {
+ if n_cases > arms.len() {
+ let lhs = make::wildcard_pat().into();
+ arms.push(make::match_arm(Some(lhs), None, eq_check));
+ }
+
+ let match_target = make::expr_tuple(vec![lhs_name, rhs_name]);
+ let list = make::match_arm_list(arms).indent(ast::edit::IndentLevel(1));
+ make::expr_match(match_target, list)
+ }
+ };
+
+ make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1))
+ }
+ ast::Adt::Struct(strukt) => match strukt.field_list() {
+ Some(ast::FieldList::RecordFieldList(field_list)) => {
+ let mut expr = None;
+ for field in field_list.fields() {
+ let lhs = make::expr_path(make::ext::ident_path("self"));
+ let lhs = make::expr_field(lhs, &field.name()?.to_string());
+ let rhs = make::expr_path(make::ext::ident_path("other"));
+ let rhs = make::expr_field(rhs, &field.name()?.to_string());
+ let cmp =
+ make::expr_bin_op(lhs, BinaryOp::CmpOp(CmpOp::Eq { negated: false }), rhs);
+ expr = gen_eq_chain(expr, cmp);
+ }
+ make::block_expr(None, expr).indent(ast::edit::IndentLevel(1))
+ }
+
+ Some(ast::FieldList::TupleFieldList(field_list)) => {
+ let mut expr = None;
+ for (i, _) in field_list.fields().enumerate() {
+ let idx = format!("{}", i);
+ let lhs = make::expr_path(make::ext::ident_path("self"));
+ let lhs = make::expr_field(lhs, &idx);
+ let rhs = make::expr_path(make::ext::ident_path("other"));
+ let rhs = make::expr_field(rhs, &idx);
+ let cmp =
+ make::expr_bin_op(lhs, BinaryOp::CmpOp(CmpOp::Eq { negated: false }), rhs);
+ expr = gen_eq_chain(expr, cmp);
+ }
+ make::block_expr(None, expr).indent(ast::edit::IndentLevel(1))
+ }
+
+ // No fields in the body means there's nothing to hash.
+ None => {
+ let expr = make::expr_literal("true").into();
+ make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1))
+ }
+ },
+ };
+
+ ted::replace(func.body()?.syntax(), body.clone_for_update().syntax());
+ Some(())
+}
+
+fn gen_partial_ord(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
+ stdx::always!(func.name().map_or(false, |name| name.text() == "partial_cmp"));
+ fn gen_partial_eq_match(match_target: ast::Expr) -> Option<ast::Stmt> {
+ let mut arms = vec![];
+
+ let variant_name =
+ make::path_pat(make::ext::path_from_idents(["core", "cmp", "Ordering", "Equal"])?);
+ let lhs = make::tuple_struct_pat(make::ext::path_from_idents(["Some"])?, [variant_name]);
+ arms.push(make::match_arm(Some(lhs.into()), None, make::expr_empty_block()));
+
+ arms.push(make::match_arm(
+ [make::ident_pat(false, false, make::name("ord")).into()],
+ None,
+ make::expr_return(Some(make::expr_path(make::ext::ident_path("ord")))),
+ ));
+ let list = make::match_arm_list(arms).indent(ast::edit::IndentLevel(1));
+ Some(make::expr_stmt(make::expr_match(match_target, list)).into())
+ }
+
+ fn gen_partial_cmp_call(lhs: ast::Expr, rhs: ast::Expr) -> ast::Expr {
+ let rhs = make::expr_ref(rhs, false);
+ let method = make::name_ref("partial_cmp");
+ make::expr_method_call(lhs, method, make::arg_list(Some(rhs)))
+ }
+
+ // FIXME: return `None` if the trait carries a generic type; we can only
+ // generate this code `Self` for the time being.
+
+ let body = match adt {
+ // `PartialOrd` cannot be derived for unions, so no default impl can be provided.
+ ast::Adt::Union(_) => return None,
+ // `core::mem::Discriminant` does not implement `PartialOrd` in stable Rust today.
+ ast::Adt::Enum(_) => return None,
+ ast::Adt::Struct(strukt) => match strukt.field_list() {
+ Some(ast::FieldList::RecordFieldList(field_list)) => {
+ let mut exprs = vec![];
+ for field in field_list.fields() {
+ let lhs = make::expr_path(make::ext::ident_path("self"));
+ let lhs = make::expr_field(lhs, &field.name()?.to_string());
+ let rhs = make::expr_path(make::ext::ident_path("other"));
+ let rhs = make::expr_field(rhs, &field.name()?.to_string());
+ let ord = gen_partial_cmp_call(lhs, rhs);
+ exprs.push(ord);
+ }
+
+ let tail = exprs.pop();
+ let stmts = exprs
+ .into_iter()
+ .map(gen_partial_eq_match)
+ .collect::<Option<Vec<ast::Stmt>>>()?;
+ make::block_expr(stmts.into_iter(), tail).indent(ast::edit::IndentLevel(1))
+ }
+
+ Some(ast::FieldList::TupleFieldList(field_list)) => {
+ let mut exprs = vec![];
+ for (i, _) in field_list.fields().enumerate() {
+ let idx = format!("{}", i);
+ let lhs = make::expr_path(make::ext::ident_path("self"));
+ let lhs = make::expr_field(lhs, &idx);
+ let rhs = make::expr_path(make::ext::ident_path("other"));
+ let rhs = make::expr_field(rhs, &idx);
+ let ord = gen_partial_cmp_call(lhs, rhs);
+ exprs.push(ord);
+ }
+ let tail = exprs.pop();
+ let stmts = exprs
+ .into_iter()
+ .map(gen_partial_eq_match)
+ .collect::<Option<Vec<ast::Stmt>>>()?;
+ make::block_expr(stmts.into_iter(), tail).indent(ast::edit::IndentLevel(1))
+ }
+
+ // No fields in the body means there's nothing to compare.
+ None => {
+ let expr = make::expr_literal("true").into();
+ make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1))
+ }
+ },
+ };
+
+ ted::replace(func.body()?.syntax(), body.clone_for_update().syntax());
+ Some(())
+}
+
+fn make_discriminant() -> Option<ast::Expr> {
+ Some(make::expr_path(make::ext::path_from_idents(["core", "mem", "discriminant"])?))
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils/suggest_name.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils/suggest_name.rs
new file mode 100644
index 000000000..779cdbc93
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils/suggest_name.rs
@@ -0,0 +1,775 @@
+//! This module contains functions to suggest names for expressions, functions and other items
+
+use hir::Semantics;
+use ide_db::RootDatabase;
+use itertools::Itertools;
+use stdx::to_lower_snake_case;
+use syntax::{
+ ast::{self, HasName},
+ match_ast, AstNode, SmolStr,
+};
+
+/// Trait names, that will be ignored when in `impl Trait` and `dyn Trait`
+const USELESS_TRAITS: &[&str] = &["Send", "Sync", "Copy", "Clone", "Eq", "PartialEq"];
+
+/// Identifier names that won't be suggested, ever
+///
+/// **NOTE**: they all must be snake lower case
+const USELESS_NAMES: &[&str] =
+ &["new", "default", "option", "some", "none", "ok", "err", "str", "string"];
+
+/// Generic types replaced by their first argument
+///
+/// # Examples
+/// `Option<Name>` -> `Name`
+/// `Result<User, Error>` -> `User`
+const WRAPPER_TYPES: &[&str] = &["Box", "Option", "Result"];
+
+/// Prefixes to strip from methods names
+///
+/// # Examples
+/// `vec.as_slice()` -> `slice`
+/// `args.into_config()` -> `config`
+/// `bytes.to_vec()` -> `vec`
+const USELESS_METHOD_PREFIXES: &[&str] = &["into_", "as_", "to_"];
+
+/// Useless methods that are stripped from expression
+///
+/// # Examples
+/// `var.name().to_string()` -> `var.name()`
+const USELESS_METHODS: &[&str] = &[
+ "to_string",
+ "as_str",
+ "to_owned",
+ "as_ref",
+ "clone",
+ "cloned",
+ "expect",
+ "expect_none",
+ "unwrap",
+ "unwrap_none",
+ "unwrap_or",
+ "unwrap_or_default",
+ "unwrap_or_else",
+ "unwrap_unchecked",
+ "iter",
+ "into_iter",
+ "iter_mut",
+];
+
+pub(crate) fn for_generic_parameter(ty: &ast::ImplTraitType) -> SmolStr {
+ let c = ty
+ .type_bound_list()
+ .and_then(|bounds| bounds.syntax().text().char_at(0.into()))
+ .unwrap_or('T');
+ c.encode_utf8(&mut [0; 4]).into()
+}
+
+/// Suggest name of variable for given expression
+///
+/// **NOTE**: it is caller's responsibility to guarantee uniqueness of the name.
+/// I.e. it doesn't look for names in scope.
+///
+/// # Current implementation
+///
+/// In current implementation, the function tries to get the name from
+/// the following sources:
+///
+/// * if expr is an argument to function/method, use paramter name
+/// * if expr is a function/method call, use function name
+/// * expression type name if it exists (E.g. `()`, `fn() -> ()` or `!` do not have names)
+/// * fallback: `var_name`
+///
+/// It also applies heuristics to filter out less informative names
+///
+/// Currently it sticks to the first name found.
+// FIXME: Microoptimize and return a `SmolStr` here.
+pub(crate) fn for_variable(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> String {
+ // `from_param` does not benifit from stripping
+ // it need the largest context possible
+ // so we check firstmost
+ if let Some(name) = from_param(expr, sema) {
+ return name;
+ }
+
+ let mut next_expr = Some(expr.clone());
+ while let Some(expr) = next_expr {
+ let name =
+ from_call(&expr).or_else(|| from_type(&expr, sema)).or_else(|| from_field_name(&expr));
+ if let Some(name) = name {
+ return name;
+ }
+
+ match expr {
+ ast::Expr::RefExpr(inner) => next_expr = inner.expr(),
+ ast::Expr::BoxExpr(inner) => next_expr = inner.expr(),
+ ast::Expr::AwaitExpr(inner) => next_expr = inner.expr(),
+ // ast::Expr::BlockExpr(block) => expr = block.tail_expr(),
+ ast::Expr::CastExpr(inner) => next_expr = inner.expr(),
+ ast::Expr::MethodCallExpr(method) if is_useless_method(&method) => {
+ next_expr = method.receiver();
+ }
+ ast::Expr::ParenExpr(inner) => next_expr = inner.expr(),
+ ast::Expr::TryExpr(inner) => next_expr = inner.expr(),
+ ast::Expr::PrefixExpr(prefix) if prefix.op_kind() == Some(ast::UnaryOp::Deref) => {
+ next_expr = prefix.expr()
+ }
+ _ => break,
+ }
+ }
+
+ "var_name".to_string()
+}
+
+fn normalize(name: &str) -> Option<String> {
+ let name = to_lower_snake_case(name);
+
+ if USELESS_NAMES.contains(&name.as_str()) {
+ return None;
+ }
+
+ if !is_valid_name(&name) {
+ return None;
+ }
+
+ Some(name)
+}
+
+fn is_valid_name(name: &str) -> bool {
+ match ide_db::syntax_helpers::LexedStr::single_token(name) {
+ Some((syntax::SyntaxKind::IDENT, _error)) => true,
+ _ => false,
+ }
+}
+
+fn is_useless_method(method: &ast::MethodCallExpr) -> bool {
+ let ident = method.name_ref().and_then(|it| it.ident_token());
+
+ match ident {
+ Some(ident) => USELESS_METHODS.contains(&ident.text()),
+ None => false,
+ }
+}
+
+fn from_call(expr: &ast::Expr) -> Option<String> {
+ from_func_call(expr).or_else(|| from_method_call(expr))
+}
+
+fn from_func_call(expr: &ast::Expr) -> Option<String> {
+ let call = match expr {
+ ast::Expr::CallExpr(call) => call,
+ _ => return None,
+ };
+ let func = match call.expr()? {
+ ast::Expr::PathExpr(path) => path,
+ _ => return None,
+ };
+ let ident = func.path()?.segment()?.name_ref()?.ident_token()?;
+ normalize(ident.text())
+}
+
+fn from_method_call(expr: &ast::Expr) -> Option<String> {
+ let method = match expr {
+ ast::Expr::MethodCallExpr(call) => call,
+ _ => return None,
+ };
+ let ident = method.name_ref()?.ident_token()?;
+ let mut name = ident.text();
+
+ if USELESS_METHODS.contains(&name) {
+ return None;
+ }
+
+ for prefix in USELESS_METHOD_PREFIXES {
+ if let Some(suffix) = name.strip_prefix(prefix) {
+ name = suffix;
+ break;
+ }
+ }
+
+ normalize(name)
+}
+
+fn from_param(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> Option<String> {
+ let arg_list = expr.syntax().parent().and_then(ast::ArgList::cast)?;
+ let args_parent = arg_list.syntax().parent()?;
+ let func = match_ast! {
+ match args_parent {
+ ast::CallExpr(call) => {
+ let func = call.expr()?;
+ let func_ty = sema.type_of_expr(&func)?.adjusted();
+ func_ty.as_callable(sema.db)?
+ },
+ ast::MethodCallExpr(method) => sema.resolve_method_call_as_callable(&method)?,
+ _ => return None,
+ }
+ };
+
+ let (idx, _) = arg_list.args().find_position(|it| it == expr).unwrap();
+ let (pat, _) = func.params(sema.db).into_iter().nth(idx)?;
+ let pat = match pat? {
+ either::Either::Right(pat) => pat,
+ _ => return None,
+ };
+ let name = var_name_from_pat(&pat)?;
+ normalize(&name.to_string())
+}
+
+fn var_name_from_pat(pat: &ast::Pat) -> Option<ast::Name> {
+ match pat {
+ ast::Pat::IdentPat(var) => var.name(),
+ ast::Pat::RefPat(ref_pat) => var_name_from_pat(&ref_pat.pat()?),
+ ast::Pat::BoxPat(box_pat) => var_name_from_pat(&box_pat.pat()?),
+ _ => None,
+ }
+}
+
+fn from_type(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> Option<String> {
+ let ty = sema.type_of_expr(expr)?.adjusted();
+ let ty = ty.remove_ref().unwrap_or(ty);
+
+ name_of_type(&ty, sema.db)
+}
+
+fn name_of_type(ty: &hir::Type, db: &RootDatabase) -> Option<String> {
+ let name = if let Some(adt) = ty.as_adt() {
+ let name = adt.name(db).to_string();
+
+ if WRAPPER_TYPES.contains(&name.as_str()) {
+ let inner_ty = ty.type_arguments().next()?;
+ return name_of_type(&inner_ty, db);
+ }
+
+ name
+ } else if let Some(trait_) = ty.as_dyn_trait() {
+ trait_name(&trait_, db)?
+ } else if let Some(traits) = ty.as_impl_traits(db) {
+ let mut iter = traits.filter_map(|t| trait_name(&t, db));
+ let name = iter.next()?;
+ if iter.next().is_some() {
+ return None;
+ }
+ name
+ } else {
+ return None;
+ };
+ normalize(&name)
+}
+
+fn trait_name(trait_: &hir::Trait, db: &RootDatabase) -> Option<String> {
+ let name = trait_.name(db).to_string();
+ if USELESS_TRAITS.contains(&name.as_str()) {
+ return None;
+ }
+ Some(name)
+}
+
+fn from_field_name(expr: &ast::Expr) -> Option<String> {
+ let field = match expr {
+ ast::Expr::FieldExpr(field) => field,
+ _ => return None,
+ };
+ let ident = field.name_ref()?.ident_token()?;
+ normalize(ident.text())
+}
+
+#[cfg(test)]
+mod tests {
+ use ide_db::base_db::{fixture::WithFixture, FileRange};
+
+ use super::*;
+
+ #[track_caller]
+ fn check(ra_fixture: &str, expected: &str) {
+ let (db, file_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture);
+ let frange = FileRange { file_id, range: range_or_offset.into() };
+
+ let sema = Semantics::new(&db);
+ let source_file = sema.parse(frange.file_id);
+ let element = source_file.syntax().covering_element(frange.range);
+ let expr =
+ element.ancestors().find_map(ast::Expr::cast).expect("selection is not an expression");
+ assert_eq!(
+ expr.syntax().text_range(),
+ frange.range,
+ "selection is not an expression(yet contained in one)"
+ );
+ let name = for_variable(&expr, &sema);
+ assert_eq!(&name, expected);
+ }
+
+ #[test]
+ fn no_args() {
+ check(r#"fn foo() { $0bar()$0 }"#, "bar");
+ check(r#"fn foo() { $0bar.frobnicate()$0 }"#, "frobnicate");
+ }
+
+ #[test]
+ fn single_arg() {
+ check(r#"fn foo() { $0bar(1)$0 }"#, "bar");
+ }
+
+ #[test]
+ fn many_args() {
+ check(r#"fn foo() { $0bar(1, 2, 3)$0 }"#, "bar");
+ }
+
+ #[test]
+ fn path() {
+ check(r#"fn foo() { $0i32::bar(1, 2, 3)$0 }"#, "bar");
+ }
+
+ #[test]
+ fn generic_params() {
+ check(r#"fn foo() { $0bar::<i32>(1, 2, 3)$0 }"#, "bar");
+ check(r#"fn foo() { $0bar.frobnicate::<i32, u32>()$0 }"#, "frobnicate");
+ }
+
+ #[test]
+ fn to_name() {
+ check(
+ r#"
+struct Args;
+struct Config;
+impl Args {
+ fn to_config(&self) -> Config {}
+}
+fn foo() {
+ $0Args.to_config()$0;
+}
+"#,
+ "config",
+ );
+ }
+
+ #[test]
+ fn plain_func() {
+ check(
+ r#"
+fn bar(n: i32, m: u32);
+fn foo() { bar($01$0, 2) }
+"#,
+ "n",
+ );
+ }
+
+ #[test]
+ fn mut_param() {
+ check(
+ r#"
+fn bar(mut n: i32, m: u32);
+fn foo() { bar($01$0, 2) }
+"#,
+ "n",
+ );
+ }
+
+ #[test]
+ fn func_does_not_exist() {
+ check(r#"fn foo() { bar($01$0, 2) }"#, "var_name");
+ }
+
+ #[test]
+ fn unnamed_param() {
+ check(
+ r#"
+fn bar(_: i32, m: u32);
+fn foo() { bar($01$0, 2) }
+"#,
+ "var_name",
+ );
+ }
+
+ #[test]
+ fn tuple_pat() {
+ check(
+ r#"
+fn bar((n, k): (i32, i32), m: u32);
+fn foo() {
+ bar($0(1, 2)$0, 3)
+}
+"#,
+ "var_name",
+ );
+ }
+
+ #[test]
+ fn ref_pat() {
+ check(
+ r#"
+fn bar(&n: &i32, m: u32);
+fn foo() { bar($0&1$0, 3) }
+"#,
+ "n",
+ );
+ }
+
+ #[test]
+ fn box_pat() {
+ check(
+ r#"
+fn bar(box n: &i32, m: u32);
+fn foo() { bar($01$0, 3) }
+"#,
+ "n",
+ );
+ }
+
+ #[test]
+ fn param_out_of_index() {
+ check(
+ r#"
+fn bar(n: i32, m: u32);
+fn foo() { bar(1, 2, $03$0) }
+"#,
+ "var_name",
+ );
+ }
+
+ #[test]
+ fn generic_param_resolved() {
+ check(
+ r#"
+fn bar<T>(n: T, m: u32);
+fn foo() { bar($01$0, 2) }
+"#,
+ "n",
+ );
+ }
+
+ #[test]
+ fn generic_param_unresolved() {
+ check(
+ r#"
+fn bar<T>(n: T, m: u32);
+fn foo<T>(x: T) { bar($0x$0, 2) }
+"#,
+ "n",
+ );
+ }
+
+ #[test]
+ fn method() {
+ check(
+ r#"
+struct S;
+impl S { fn bar(&self, n: i32, m: u32); }
+fn foo() { S.bar($01$0, 2) }
+"#,
+ "n",
+ );
+ }
+
+ #[test]
+ fn method_on_impl_trait() {
+ check(
+ r#"
+struct S;
+trait T {
+ fn bar(&self, n: i32, m: u32);
+}
+impl T for S { fn bar(&self, n: i32, m: u32); }
+fn foo() { S.bar($01$0, 2) }
+"#,
+ "n",
+ );
+ }
+
+ #[test]
+ fn method_ufcs() {
+ check(
+ r#"
+struct S;
+impl S { fn bar(&self, n: i32, m: u32); }
+fn foo() { S::bar(&S, $01$0, 2) }
+"#,
+ "n",
+ );
+ }
+
+ #[test]
+ fn method_self() {
+ check(
+ r#"
+struct S;
+impl S { fn bar(&self, n: i32, m: u32); }
+fn foo() { S::bar($0&S$0, 1, 2) }
+"#,
+ "s",
+ );
+ }
+
+ #[test]
+ fn method_self_named() {
+ check(
+ r#"
+struct S;
+impl S { fn bar(strukt: &Self, n: i32, m: u32); }
+fn foo() { S::bar($0&S$0, 1, 2) }
+"#,
+ "strukt",
+ );
+ }
+
+ #[test]
+ fn i32() {
+ check(r#"fn foo() { let _: i32 = $01$0; }"#, "var_name");
+ }
+
+ #[test]
+ fn u64() {
+ check(r#"fn foo() { let _: u64 = $01$0; }"#, "var_name");
+ }
+
+ #[test]
+ fn bool() {
+ check(r#"fn foo() { let _: bool = $0true$0; }"#, "var_name");
+ }
+
+ #[test]
+ fn struct_unit() {
+ check(
+ r#"
+struct Seed;
+fn foo() { let _ = $0Seed$0; }
+"#,
+ "seed",
+ );
+ }
+
+ #[test]
+ fn struct_unit_to_snake() {
+ check(
+ r#"
+struct SeedState;
+fn foo() { let _ = $0SeedState$0; }
+"#,
+ "seed_state",
+ );
+ }
+
+ #[test]
+ fn struct_single_arg() {
+ check(
+ r#"
+struct Seed(u32);
+fn foo() { let _ = $0Seed(0)$0; }
+"#,
+ "seed",
+ );
+ }
+
+ #[test]
+ fn struct_with_fields() {
+ check(
+ r#"
+struct Seed { value: u32 }
+fn foo() { let _ = $0Seed { value: 0 }$0; }
+"#,
+ "seed",
+ );
+ }
+
+ #[test]
+ fn enum_() {
+ check(
+ r#"
+enum Kind { A, B }
+fn foo() { let _ = $0Kind::A$0; }
+"#,
+ "kind",
+ );
+ }
+
+ #[test]
+ fn enum_generic_resolved() {
+ check(
+ r#"
+enum Kind<T> { A { x: T }, B }
+fn foo() { let _ = $0Kind::A { x:1 }$0; }
+"#,
+ "kind",
+ );
+ }
+
+ #[test]
+ fn enum_generic_unresolved() {
+ check(
+ r#"
+enum Kind<T> { A { x: T }, B }
+fn foo<T>(x: T) { let _ = $0Kind::A { x }$0; }
+"#,
+ "kind",
+ );
+ }
+
+ #[test]
+ fn dyn_trait() {
+ check(
+ r#"
+trait DynHandler {}
+fn bar() -> dyn DynHandler {}
+fn foo() { $0(bar())$0; }
+"#,
+ "dyn_handler",
+ );
+ }
+
+ #[test]
+ fn impl_trait() {
+ check(
+ r#"
+trait StaticHandler {}
+fn bar() -> impl StaticHandler {}
+fn foo() { $0(bar())$0; }
+"#,
+ "static_handler",
+ );
+ }
+
+ #[test]
+ fn impl_trait_plus_clone() {
+ check(
+ r#"
+trait StaticHandler {}
+trait Clone {}
+fn bar() -> impl StaticHandler + Clone {}
+fn foo() { $0(bar())$0; }
+"#,
+ "static_handler",
+ );
+ }
+
+ #[test]
+ fn impl_trait_plus_lifetime() {
+ check(
+ r#"
+trait StaticHandler {}
+trait Clone {}
+fn bar<'a>(&'a i32) -> impl StaticHandler + 'a {}
+fn foo() { $0(bar(&1))$0; }
+"#,
+ "static_handler",
+ );
+ }
+
+ #[test]
+ fn impl_trait_plus_trait() {
+ check(
+ r#"
+trait Handler {}
+trait StaticHandler {}
+fn bar() -> impl StaticHandler + Handler {}
+fn foo() { $0(bar())$0; }
+"#,
+ "bar",
+ );
+ }
+
+ #[test]
+ fn ref_value() {
+ check(
+ r#"
+struct Seed;
+fn bar() -> &Seed {}
+fn foo() { $0(bar())$0; }
+"#,
+ "seed",
+ );
+ }
+
+ #[test]
+ fn box_value() {
+ check(
+ r#"
+struct Box<T>(*const T);
+struct Seed;
+fn bar() -> Box<Seed> {}
+fn foo() { $0(bar())$0; }
+"#,
+ "seed",
+ );
+ }
+
+ #[test]
+ fn box_generic() {
+ check(
+ r#"
+struct Box<T>(*const T);
+fn bar<T>() -> Box<T> {}
+fn foo<T>() { $0(bar::<T>())$0; }
+"#,
+ "bar",
+ );
+ }
+
+ #[test]
+ fn option_value() {
+ check(
+ r#"
+enum Option<T> { Some(T) }
+struct Seed;
+fn bar() -> Option<Seed> {}
+fn foo() { $0(bar())$0; }
+"#,
+ "seed",
+ );
+ }
+
+ #[test]
+ fn result_value() {
+ check(
+ r#"
+enum Result<T, E> { Ok(T), Err(E) }
+struct Seed;
+struct Error;
+fn bar() -> Result<Seed, Error> {}
+fn foo() { $0(bar())$0; }
+"#,
+ "seed",
+ );
+ }
+
+ #[test]
+ fn ref_call() {
+ check(
+ r#"
+fn foo() { $0&bar(1, 3)$0 }
+"#,
+ "bar",
+ );
+ }
+
+ #[test]
+ fn name_to_string() {
+ check(
+ r#"
+fn foo() { $0function.name().to_string()$0 }
+"#,
+ "name",
+ );
+ }
+
+ #[test]
+ fn nested_useless_method() {
+ check(
+ r#"
+fn foo() { $0function.name().as_ref().unwrap().to_string()$0 }
+"#,
+ "name",
+ );
+ }
+
+ #[test]
+ fn struct_field_name() {
+ check(
+ r#"
+struct S<T> {
+ some_field: T;
+}
+fn foo<T>(some_struct: S<T>) { $0some_struct.some_field$0 }
+"#,
+ "some_field",
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/Cargo.toml b/src/tools/rust-analyzer/crates/ide-completion/Cargo.toml
new file mode 100644
index 000000000..8c9d6b228
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/Cargo.toml
@@ -0,0 +1,33 @@
+[package]
+name = "ide-completion"
+version = "0.0.0"
+description = "TBD"
+license = "MIT OR Apache-2.0"
+edition = "2021"
+rust-version = "1.57"
+
+[lib]
+doctest = false
+
+[dependencies]
+cov-mark = "2.0.0-pre.1"
+itertools = "0.10.3"
+
+once_cell = "1.12.0"
+smallvec = "1.9.0"
+
+stdx = { path = "../stdx", version = "0.0.0" }
+syntax = { path = "../syntax", version = "0.0.0" }
+text-edit = { path = "../text-edit", version = "0.0.0" }
+base-db = { path = "../base-db", version = "0.0.0" }
+ide-db = { path = "../ide-db", version = "0.0.0" }
+profile = { path = "../profile", version = "0.0.0" }
+
+# completions crate should depend only on the top-level `hir` package. if you need
+# something from some `hir-xxx` subpackage, reexport the API via `hir`.
+hir = { path = "../hir", version = "0.0.0" }
+
+[dev-dependencies]
+expect-test = "1.4.0"
+
+test-utils = { path = "../test-utils" }
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs
new file mode 100644
index 000000000..72579e602
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs
@@ -0,0 +1,691 @@
+//! This module defines an accumulator for completions which are going to be presented to user.
+
+pub(crate) mod attribute;
+pub(crate) mod dot;
+pub(crate) mod expr;
+pub(crate) mod extern_abi;
+pub(crate) mod field;
+pub(crate) mod flyimport;
+pub(crate) mod fn_param;
+pub(crate) mod format_string;
+pub(crate) mod item_list;
+pub(crate) mod keyword;
+pub(crate) mod lifetime;
+pub(crate) mod mod_;
+pub(crate) mod pattern;
+pub(crate) mod postfix;
+pub(crate) mod record;
+pub(crate) mod snippet;
+pub(crate) mod r#type;
+pub(crate) mod use_;
+pub(crate) mod vis;
+
+use std::iter;
+
+use hir::{known, ScopeDef};
+use ide_db::{imports::import_assets::LocatedImport, SymbolKind};
+use syntax::ast;
+
+use crate::{
+ context::{
+ DotAccess, ItemListKind, NameContext, NameKind, NameRefContext, NameRefKind,
+ PathCompletionCtx, PathKind, PatternContext, TypeLocation, Visible,
+ },
+ item::Builder,
+ render::{
+ const_::render_const,
+ function::{render_fn, render_method},
+ literal::{render_struct_literal, render_variant_lit},
+ macro_::render_macro,
+ pattern::{render_struct_pat, render_variant_pat},
+ render_field, render_path_resolution, render_pattern_resolution, render_tuple_field,
+ type_alias::{render_type_alias, render_type_alias_with_eq},
+ union_literal::render_union_literal,
+ RenderContext,
+ },
+ CompletionContext, CompletionItem, CompletionItemKind,
+};
+
+/// Represents an in-progress set of completions being built.
+#[derive(Debug, Default)]
+pub struct Completions {
+ buf: Vec<CompletionItem>,
+}
+
+impl From<Completions> for Vec<CompletionItem> {
+ fn from(val: Completions) -> Self {
+ val.buf
+ }
+}
+
+impl Builder {
+ /// Convenience method, which allows to add a freshly created completion into accumulator
+ /// without binding it to the variable.
+ pub(crate) fn add_to(self, acc: &mut Completions) {
+ acc.add(self.build())
+ }
+}
+
+impl Completions {
+ fn add(&mut self, item: CompletionItem) {
+ self.buf.push(item)
+ }
+
+ fn add_opt(&mut self, item: Option<CompletionItem>) {
+ if let Some(item) = item {
+ self.buf.push(item)
+ }
+ }
+
+ pub(crate) fn add_all<I>(&mut self, items: I)
+ where
+ I: IntoIterator,
+ I::Item: Into<CompletionItem>,
+ {
+ items.into_iter().for_each(|item| self.add(item.into()))
+ }
+
+ pub(crate) fn add_keyword(&mut self, ctx: &CompletionContext<'_>, keyword: &'static str) {
+ let item = CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), keyword);
+ item.add_to(self);
+ }
+
+ pub(crate) fn add_nameref_keywords_with_colon(&mut self, ctx: &CompletionContext<'_>) {
+ ["self::", "crate::"].into_iter().for_each(|kw| self.add_keyword(ctx, kw));
+
+ if ctx.depth_from_crate_root > 0 {
+ self.add_keyword(ctx, "super::");
+ }
+ }
+
+ pub(crate) fn add_nameref_keywords(&mut self, ctx: &CompletionContext<'_>) {
+ ["self", "crate"].into_iter().for_each(|kw| self.add_keyword(ctx, kw));
+
+ if ctx.depth_from_crate_root > 0 {
+ self.add_keyword(ctx, "super");
+ }
+ }
+
+ pub(crate) fn add_super_keyword(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ super_chain_len: Option<usize>,
+ ) {
+ if let Some(len) = super_chain_len {
+ if len > 0 && len < ctx.depth_from_crate_root {
+ self.add_keyword(ctx, "super::");
+ }
+ }
+ }
+
+ pub(crate) fn add_keyword_snippet_expr(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ incomplete_let: bool,
+ kw: &str,
+ snippet: &str,
+ ) {
+ let mut item = CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), kw);
+
+ match ctx.config.snippet_cap {
+ Some(cap) => {
+ if incomplete_let && snippet.ends_with('}') {
+ // complete block expression snippets with a trailing semicolon, if inside an incomplete let
+ cov_mark::hit!(let_semi);
+ item.insert_snippet(cap, format!("{};", snippet));
+ } else {
+ item.insert_snippet(cap, snippet);
+ }
+ }
+ None => {
+ item.insert_text(if snippet.contains('$') { kw } else { snippet });
+ }
+ };
+ item.add_to(self);
+ }
+
+ pub(crate) fn add_keyword_snippet(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ kw: &str,
+ snippet: &str,
+ ) {
+ let mut item = CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), kw);
+
+ match ctx.config.snippet_cap {
+ Some(cap) => item.insert_snippet(cap, snippet),
+ None => item.insert_text(if snippet.contains('$') { kw } else { snippet }),
+ };
+ item.add_to(self);
+ }
+
+ pub(crate) fn add_crate_roots(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ ) {
+ ctx.process_all_names(&mut |name, res| match res {
+ ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) if m.is_crate_root(ctx.db) => {
+ self.add_module(ctx, path_ctx, m, name);
+ }
+ _ => (),
+ });
+ }
+
+ pub(crate) fn add_path_resolution(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ local_name: hir::Name,
+ resolution: hir::ScopeDef,
+ ) {
+ let is_private_editable = match ctx.def_is_visible(&resolution) {
+ Visible::Yes => false,
+ Visible::Editable => true,
+ Visible::No => return,
+ };
+ self.add(
+ render_path_resolution(
+ RenderContext::new(ctx).private_editable(is_private_editable),
+ path_ctx,
+ local_name,
+ resolution,
+ )
+ .build(),
+ );
+ }
+
+ pub(crate) fn add_pattern_resolution(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ pattern_ctx: &PatternContext,
+ local_name: hir::Name,
+ resolution: hir::ScopeDef,
+ ) {
+ let is_private_editable = match ctx.def_is_visible(&resolution) {
+ Visible::Yes => false,
+ Visible::Editable => true,
+ Visible::No => return,
+ };
+ self.add(
+ render_pattern_resolution(
+ RenderContext::new(ctx).private_editable(is_private_editable),
+ pattern_ctx,
+ local_name,
+ resolution,
+ )
+ .build(),
+ );
+ }
+
+ pub(crate) fn add_enum_variants(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ e: hir::Enum,
+ ) {
+ e.variants(ctx.db)
+ .into_iter()
+ .for_each(|variant| self.add_enum_variant(ctx, path_ctx, variant, None));
+ }
+
+ pub(crate) fn add_module(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ module: hir::Module,
+ local_name: hir::Name,
+ ) {
+ self.add_path_resolution(
+ ctx,
+ path_ctx,
+ local_name,
+ hir::ScopeDef::ModuleDef(module.into()),
+ );
+ }
+
+ pub(crate) fn add_macro(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ mac: hir::Macro,
+ local_name: hir::Name,
+ ) {
+ let is_private_editable = match ctx.is_visible(&mac) {
+ Visible::Yes => false,
+ Visible::Editable => true,
+ Visible::No => return,
+ };
+ self.add(
+ render_macro(
+ RenderContext::new(ctx).private_editable(is_private_editable),
+ path_ctx,
+ local_name,
+ mac,
+ )
+ .build(),
+ );
+ }
+
+ pub(crate) fn add_function(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ func: hir::Function,
+ local_name: Option<hir::Name>,
+ ) {
+ let is_private_editable = match ctx.is_visible(&func) {
+ Visible::Yes => false,
+ Visible::Editable => true,
+ Visible::No => return,
+ };
+ self.add(
+ render_fn(
+ RenderContext::new(ctx).private_editable(is_private_editable),
+ path_ctx,
+ local_name,
+ func,
+ )
+ .build(),
+ );
+ }
+
+ pub(crate) fn add_method(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ dot_access: &DotAccess,
+ func: hir::Function,
+ receiver: Option<hir::Name>,
+ local_name: Option<hir::Name>,
+ ) {
+ let is_private_editable = match ctx.is_visible(&func) {
+ Visible::Yes => false,
+ Visible::Editable => true,
+ Visible::No => return,
+ };
+ self.add(
+ render_method(
+ RenderContext::new(ctx).private_editable(is_private_editable),
+ dot_access,
+ receiver,
+ local_name,
+ func,
+ )
+ .build(),
+ );
+ }
+
+ pub(crate) fn add_method_with_import(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ dot_access: &DotAccess,
+ func: hir::Function,
+ import: LocatedImport,
+ ) {
+ let is_private_editable = match ctx.is_visible(&func) {
+ Visible::Yes => false,
+ Visible::Editable => true,
+ Visible::No => return,
+ };
+ self.add(
+ render_method(
+ RenderContext::new(ctx)
+ .private_editable(is_private_editable)
+ .import_to_add(Some(import)),
+ dot_access,
+ None,
+ None,
+ func,
+ )
+ .build(),
+ );
+ }
+
+ pub(crate) fn add_const(&mut self, ctx: &CompletionContext<'_>, konst: hir::Const) {
+ let is_private_editable = match ctx.is_visible(&konst) {
+ Visible::Yes => false,
+ Visible::Editable => true,
+ Visible::No => return,
+ };
+ self.add_opt(render_const(
+ RenderContext::new(ctx).private_editable(is_private_editable),
+ konst,
+ ));
+ }
+
+ pub(crate) fn add_type_alias(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ type_alias: hir::TypeAlias,
+ ) {
+ let is_private_editable = match ctx.is_visible(&type_alias) {
+ Visible::Yes => false,
+ Visible::Editable => true,
+ Visible::No => return,
+ };
+ self.add_opt(render_type_alias(
+ RenderContext::new(ctx).private_editable(is_private_editable),
+ type_alias,
+ ));
+ }
+
+ pub(crate) fn add_type_alias_with_eq(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ type_alias: hir::TypeAlias,
+ ) {
+ self.add_opt(render_type_alias_with_eq(RenderContext::new(ctx), type_alias));
+ }
+
+ pub(crate) fn add_qualified_enum_variant(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ variant: hir::Variant,
+ path: hir::ModPath,
+ ) {
+ if let Some(builder) =
+ render_variant_lit(RenderContext::new(ctx), path_ctx, None, variant, Some(path))
+ {
+ self.add(builder.build());
+ }
+ }
+
+ pub(crate) fn add_enum_variant(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ variant: hir::Variant,
+ local_name: Option<hir::Name>,
+ ) {
+ if let PathCompletionCtx { kind: PathKind::Pat { pat_ctx }, .. } = path_ctx {
+ cov_mark::hit!(enum_variant_pattern_path);
+ self.add_variant_pat(ctx, pat_ctx, Some(path_ctx), variant, local_name);
+ return;
+ }
+
+ if let Some(builder) =
+ render_variant_lit(RenderContext::new(ctx), path_ctx, local_name, variant, None)
+ {
+ self.add(builder.build());
+ }
+ }
+
+ pub(crate) fn add_field(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ dot_access: &DotAccess,
+ receiver: Option<hir::Name>,
+ field: hir::Field,
+ ty: &hir::Type,
+ ) {
+ let is_private_editable = match ctx.is_visible(&field) {
+ Visible::Yes => false,
+ Visible::Editable => true,
+ Visible::No => return,
+ };
+ let item = render_field(
+ RenderContext::new(ctx).private_editable(is_private_editable),
+ dot_access,
+ receiver,
+ field,
+ ty,
+ );
+ self.add(item);
+ }
+
+ pub(crate) fn add_struct_literal(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ strukt: hir::Struct,
+ path: Option<hir::ModPath>,
+ local_name: Option<hir::Name>,
+ ) {
+ if let Some(builder) =
+ render_struct_literal(RenderContext::new(ctx), path_ctx, strukt, path, local_name)
+ {
+ self.add(builder.build());
+ }
+ }
+
+ pub(crate) fn add_union_literal(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ un: hir::Union,
+ path: Option<hir::ModPath>,
+ local_name: Option<hir::Name>,
+ ) {
+ let item = render_union_literal(RenderContext::new(ctx), un, path, local_name);
+ self.add_opt(item);
+ }
+
+ pub(crate) fn add_tuple_field(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ receiver: Option<hir::Name>,
+ field: usize,
+ ty: &hir::Type,
+ ) {
+ let item = render_tuple_field(RenderContext::new(ctx), receiver, field, ty);
+ self.add(item);
+ }
+
+ pub(crate) fn add_lifetime(&mut self, ctx: &CompletionContext<'_>, name: hir::Name) {
+ CompletionItem::new(SymbolKind::LifetimeParam, ctx.source_range(), name.to_smol_str())
+ .add_to(self)
+ }
+
+ pub(crate) fn add_label(&mut self, ctx: &CompletionContext<'_>, name: hir::Name) {
+ CompletionItem::new(SymbolKind::Label, ctx.source_range(), name.to_smol_str()).add_to(self)
+ }
+
+ pub(crate) fn add_variant_pat(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ pattern_ctx: &PatternContext,
+ path_ctx: Option<&PathCompletionCtx>,
+ variant: hir::Variant,
+ local_name: Option<hir::Name>,
+ ) {
+ self.add_opt(render_variant_pat(
+ RenderContext::new(ctx),
+ pattern_ctx,
+ path_ctx,
+ variant,
+ local_name.clone(),
+ None,
+ ));
+ }
+
+ pub(crate) fn add_qualified_variant_pat(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ pattern_ctx: &PatternContext,
+ variant: hir::Variant,
+ path: hir::ModPath,
+ ) {
+ let path = Some(&path);
+ self.add_opt(render_variant_pat(
+ RenderContext::new(ctx),
+ pattern_ctx,
+ None,
+ variant,
+ None,
+ path,
+ ));
+ }
+
+ pub(crate) fn add_struct_pat(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ pattern_ctx: &PatternContext,
+ strukt: hir::Struct,
+ local_name: Option<hir::Name>,
+ ) {
+ self.add_opt(render_struct_pat(RenderContext::new(ctx), pattern_ctx, strukt, local_name));
+ }
+}
+
+/// Calls the callback for each variant of the provided enum with the path to the variant.
+/// Skips variants that are visible with single segment paths.
+fn enum_variants_with_paths(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ enum_: hir::Enum,
+ impl_: &Option<ast::Impl>,
+ cb: impl Fn(&mut Completions, &CompletionContext<'_>, hir::Variant, hir::ModPath),
+) {
+ let variants = enum_.variants(ctx.db);
+
+ if let Some(impl_) = impl_.as_ref().and_then(|impl_| ctx.sema.to_def(impl_)) {
+ if impl_.self_ty(ctx.db).as_adt() == Some(hir::Adt::Enum(enum_)) {
+ for &variant in &variants {
+ let self_path = hir::ModPath::from_segments(
+ hir::PathKind::Plain,
+ iter::once(known::SELF_TYPE).chain(iter::once(variant.name(ctx.db))),
+ );
+ cb(acc, ctx, variant, self_path);
+ }
+ }
+ }
+
+ for variant in variants {
+ if let Some(path) = ctx.module.find_use_path(ctx.db, hir::ModuleDef::from(variant)) {
+ // Variants with trivial paths are already added by the existing completion logic,
+ // so we should avoid adding these twice
+ if path.segments().len() > 1 {
+ cb(acc, ctx, variant, path);
+ }
+ }
+ }
+}
+
+pub(super) fn complete_name(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ NameContext { name, kind }: &NameContext,
+) {
+ match kind {
+ NameKind::Const => {
+ item_list::trait_impl::complete_trait_impl_const(acc, ctx, name);
+ }
+ NameKind::Function => {
+ item_list::trait_impl::complete_trait_impl_fn(acc, ctx, name);
+ }
+ NameKind::IdentPat(pattern_ctx) => {
+ if ctx.token.kind() != syntax::T![_] {
+ complete_patterns(acc, ctx, pattern_ctx)
+ }
+ }
+ NameKind::Module(mod_under_caret) => {
+ mod_::complete_mod(acc, ctx, mod_under_caret);
+ }
+ NameKind::TypeAlias => {
+ item_list::trait_impl::complete_trait_impl_type_alias(acc, ctx, name);
+ }
+ NameKind::RecordField => {
+ field::complete_field_list_record_variant(acc, ctx);
+ }
+ NameKind::ConstParam
+ | NameKind::Enum
+ | NameKind::MacroDef
+ | NameKind::MacroRules
+ | NameKind::Rename
+ | NameKind::SelfParam
+ | NameKind::Static
+ | NameKind::Struct
+ | NameKind::Trait
+ | NameKind::TypeParam
+ | NameKind::Union
+ | NameKind::Variant => (),
+ }
+}
+
+pub(super) fn complete_name_ref(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ NameRefContext { nameref, kind }: &NameRefContext,
+) {
+ match kind {
+ NameRefKind::Path(path_ctx) => {
+ flyimport::import_on_the_fly_path(acc, ctx, path_ctx);
+
+ match &path_ctx.kind {
+ PathKind::Expr { expr_ctx } => {
+ expr::complete_expr_path(acc, ctx, path_ctx, expr_ctx);
+
+ dot::complete_undotted_self(acc, ctx, path_ctx, expr_ctx);
+ item_list::complete_item_list_in_expr(acc, ctx, path_ctx, expr_ctx);
+ record::complete_record_expr_func_update(acc, ctx, path_ctx, expr_ctx);
+ snippet::complete_expr_snippet(acc, ctx, path_ctx, expr_ctx);
+ }
+ PathKind::Type { location } => {
+ r#type::complete_type_path(acc, ctx, path_ctx, location);
+
+ match location {
+ TypeLocation::TupleField => {
+ field::complete_field_list_tuple_variant(acc, ctx, path_ctx);
+ }
+ TypeLocation::TypeAscription(ascription) => {
+ r#type::complete_ascribed_type(acc, ctx, path_ctx, ascription);
+ }
+ TypeLocation::GenericArgList(_)
+ | TypeLocation::TypeBound
+ | TypeLocation::ImplTarget
+ | TypeLocation::ImplTrait
+ | TypeLocation::Other => (),
+ }
+ }
+ PathKind::Attr { attr_ctx } => {
+ attribute::complete_attribute_path(acc, ctx, path_ctx, attr_ctx);
+ }
+ PathKind::Derive { existing_derives } => {
+ attribute::complete_derive_path(acc, ctx, path_ctx, existing_derives);
+ }
+ PathKind::Item { kind } => {
+ item_list::complete_item_list(acc, ctx, path_ctx, kind);
+
+ snippet::complete_item_snippet(acc, ctx, path_ctx, kind);
+ if let ItemListKind::TraitImpl(impl_) = kind {
+ item_list::trait_impl::complete_trait_impl_item_by_name(
+ acc, ctx, path_ctx, nameref, impl_,
+ );
+ }
+ }
+ PathKind::Pat { .. } => {
+ pattern::complete_pattern_path(acc, ctx, path_ctx);
+ }
+ PathKind::Vis { has_in_token } => {
+ vis::complete_vis_path(acc, ctx, path_ctx, has_in_token);
+ }
+ PathKind::Use => {
+ use_::complete_use_path(acc, ctx, path_ctx, nameref);
+ }
+ }
+ }
+ NameRefKind::DotAccess(dot_access) => {
+ flyimport::import_on_the_fly_dot(acc, ctx, dot_access);
+ dot::complete_dot(acc, ctx, dot_access);
+ postfix::complete_postfix(acc, ctx, dot_access);
+ }
+ NameRefKind::Keyword(item) => {
+ keyword::complete_for_and_where(acc, ctx, item);
+ }
+ NameRefKind::RecordExpr { dot_prefix, expr } => {
+ record::complete_record_expr_fields(acc, ctx, expr, dot_prefix);
+ }
+ NameRefKind::Pattern(pattern_ctx) => complete_patterns(acc, ctx, pattern_ctx),
+ }
+}
+
+fn complete_patterns(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ pattern_ctx: &PatternContext,
+) {
+ flyimport::import_on_the_fly_pat(acc, ctx, pattern_ctx);
+ fn_param::complete_fn_param(acc, ctx, pattern_ctx);
+ pattern::complete_pattern(acc, ctx, pattern_ctx);
+ record::complete_record_pattern_fields(acc, ctx, pattern_ctx);
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs
new file mode 100644
index 000000000..d9fe94cb4
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs
@@ -0,0 +1,380 @@
+//! Completion for (built-in) attributes, derives and lints.
+//!
+//! This module uses a bit of static metadata to provide completions for builtin-in attributes and lints.
+
+use ide_db::{
+ generated::lints::{
+ Lint, CLIPPY_LINTS, CLIPPY_LINT_GROUPS, DEFAULT_LINTS, FEATURES, RUSTDOC_LINTS,
+ },
+ syntax_helpers::node_ext::parse_tt_as_comma_sep_paths,
+ FxHashMap, SymbolKind,
+};
+use itertools::Itertools;
+use once_cell::sync::Lazy;
+use syntax::{
+ ast::{self, AttrKind},
+ AstNode, SyntaxKind, T,
+};
+
+use crate::{
+ context::{AttrCtx, CompletionContext, PathCompletionCtx, Qualified},
+ item::CompletionItem,
+ Completions,
+};
+
+mod cfg;
+mod derive;
+mod lint;
+mod repr;
+
+pub(crate) use self::derive::complete_derive_path;
+
+/// Complete inputs to known builtin attributes as well as derive attributes
+pub(crate) fn complete_known_attribute_input(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ &colon_prefix: &bool,
+ fake_attribute_under_caret: &ast::Attr,
+) -> Option<()> {
+ let attribute = fake_attribute_under_caret;
+ let name_ref = match attribute.path() {
+ Some(p) => Some(p.as_single_name_ref()?),
+ None => None,
+ };
+ let (path, tt) = name_ref.zip(attribute.token_tree())?;
+ if tt.l_paren_token().is_none() {
+ return None;
+ }
+
+ match path.text().as_str() {
+ "repr" => repr::complete_repr(acc, ctx, tt),
+ "feature" => {
+ lint::complete_lint(acc, ctx, colon_prefix, &parse_tt_as_comma_sep_paths(tt)?, FEATURES)
+ }
+ "allow" | "warn" | "deny" | "forbid" => {
+ let existing_lints = parse_tt_as_comma_sep_paths(tt)?;
+
+ let lints: Vec<Lint> = CLIPPY_LINT_GROUPS
+ .iter()
+ .map(|g| &g.lint)
+ .chain(DEFAULT_LINTS)
+ .chain(CLIPPY_LINTS)
+ .chain(RUSTDOC_LINTS)
+ .cloned()
+ .collect();
+
+ lint::complete_lint(acc, ctx, colon_prefix, &existing_lints, &lints);
+ }
+ "cfg" => cfg::complete_cfg(acc, ctx),
+ _ => (),
+ }
+ Some(())
+}
+
+pub(crate) fn complete_attribute_path(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx @ PathCompletionCtx { qualified, .. }: &PathCompletionCtx,
+ &AttrCtx { kind, annotated_item_kind }: &AttrCtx,
+) {
+ let is_inner = kind == AttrKind::Inner;
+
+ match qualified {
+ Qualified::With {
+ resolution: Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))),
+ super_chain_len,
+ ..
+ } => {
+ acc.add_super_keyword(ctx, *super_chain_len);
+
+ for (name, def) in module.scope(ctx.db, Some(ctx.module)) {
+ match def {
+ hir::ScopeDef::ModuleDef(hir::ModuleDef::Macro(m)) if m.is_attr(ctx.db) => {
+ acc.add_macro(ctx, path_ctx, m, name)
+ }
+ hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
+ acc.add_module(ctx, path_ctx, m, name)
+ }
+ _ => (),
+ }
+ }
+ return;
+ }
+ // fresh use tree with leading colon2, only show crate roots
+ Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
+ // only show modules in a fresh UseTree
+ Qualified::No => {
+ ctx.process_all_names(&mut |name, def| match def {
+ hir::ScopeDef::ModuleDef(hir::ModuleDef::Macro(m)) if m.is_attr(ctx.db) => {
+ acc.add_macro(ctx, path_ctx, m, name)
+ }
+ hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
+ acc.add_module(ctx, path_ctx, m, name)
+ }
+ _ => (),
+ });
+ acc.add_nameref_keywords_with_colon(ctx);
+ }
+ Qualified::TypeAnchor { .. } | Qualified::With { .. } => {}
+ }
+
+ let attributes = annotated_item_kind.and_then(|kind| {
+ if ast::Expr::can_cast(kind) {
+ Some(EXPR_ATTRIBUTES)
+ } else {
+ KIND_TO_ATTRIBUTES.get(&kind).copied()
+ }
+ });
+
+ let add_completion = |attr_completion: &AttrCompletion| {
+ let mut item =
+ CompletionItem::new(SymbolKind::Attribute, ctx.source_range(), attr_completion.label);
+
+ if let Some(lookup) = attr_completion.lookup {
+ item.lookup_by(lookup);
+ }
+
+ if let Some((snippet, cap)) = attr_completion.snippet.zip(ctx.config.snippet_cap) {
+ item.insert_snippet(cap, snippet);
+ }
+
+ if is_inner || !attr_completion.prefer_inner {
+ item.add_to(acc);
+ }
+ };
+
+ match attributes {
+ Some(applicable) => applicable
+ .iter()
+ .flat_map(|name| ATTRIBUTES.binary_search_by(|attr| attr.key().cmp(name)).ok())
+ .flat_map(|idx| ATTRIBUTES.get(idx))
+ .for_each(add_completion),
+ None if is_inner => ATTRIBUTES.iter().for_each(add_completion),
+ None => ATTRIBUTES.iter().filter(|compl| !compl.prefer_inner).for_each(add_completion),
+ }
+}
+
+struct AttrCompletion {
+ label: &'static str,
+ lookup: Option<&'static str>,
+ snippet: Option<&'static str>,
+ prefer_inner: bool,
+}
+
+impl AttrCompletion {
+ fn key(&self) -> &'static str {
+ self.lookup.unwrap_or(self.label)
+ }
+
+ const fn prefer_inner(self) -> AttrCompletion {
+ AttrCompletion { prefer_inner: true, ..self }
+ }
+}
+
+const fn attr(
+ label: &'static str,
+ lookup: Option<&'static str>,
+ snippet: Option<&'static str>,
+) -> AttrCompletion {
+ AttrCompletion { label, lookup, snippet, prefer_inner: false }
+}
+
+macro_rules! attrs {
+ // attributes applicable to all items
+ [@ { item $($tt:tt)* } {$($acc:tt)*}] => {
+ attrs!(@ { $($tt)* } { $($acc)*, "deprecated", "doc", "dochidden", "docalias", "must_use", "no_mangle" })
+ };
+ // attributes applicable to all adts
+ [@ { adt $($tt:tt)* } {$($acc:tt)*}] => {
+ attrs!(@ { $($tt)* } { $($acc)*, "derive", "repr" })
+ };
+ // attributes applicable to all linkable things aka functions/statics
+ [@ { linkable $($tt:tt)* } {$($acc:tt)*}] => {
+ attrs!(@ { $($tt)* } { $($acc)*, "export_name", "link_name", "link_section" })
+ };
+ // error fallback for nicer error message
+ [@ { $ty:ident $($tt:tt)* } {$($acc:tt)*}] => {
+ compile_error!(concat!("unknown attr subtype ", stringify!($ty)))
+ };
+ // general push down accumulation
+ [@ { $lit:literal $($tt:tt)*} {$($acc:tt)*}] => {
+ attrs!(@ { $($tt)* } { $($acc)*, $lit })
+ };
+ [@ {$($tt:tt)+} {$($tt2:tt)*}] => {
+ compile_error!(concat!("Unexpected input ", stringify!($($tt)+)))
+ };
+ // final output construction
+ [@ {} {$($tt:tt)*}] => { &[$($tt)*] as _ };
+ // starting matcher
+ [$($tt:tt),*] => {
+ attrs!(@ { $($tt)* } { "allow", "cfg", "cfg_attr", "deny", "forbid", "warn" })
+ };
+}
+
+#[rustfmt::skip]
+static KIND_TO_ATTRIBUTES: Lazy<FxHashMap<SyntaxKind, &[&str]>> = Lazy::new(|| {
+ use SyntaxKind::*;
+ [
+ (
+ SOURCE_FILE,
+ attrs!(
+ item,
+ "crate_name", "feature", "no_implicit_prelude", "no_main", "no_std",
+ "recursion_limit", "type_length_limit", "windows_subsystem"
+ ),
+ ),
+ (MODULE, attrs!(item, "macro_use", "no_implicit_prelude", "path")),
+ (ITEM_LIST, attrs!(item, "no_implicit_prelude")),
+ (MACRO_RULES, attrs!(item, "macro_export", "macro_use")),
+ (MACRO_DEF, attrs!(item)),
+ (EXTERN_CRATE, attrs!(item, "macro_use", "no_link")),
+ (USE, attrs!(item)),
+ (TYPE_ALIAS, attrs!(item)),
+ (STRUCT, attrs!(item, adt, "non_exhaustive")),
+ (ENUM, attrs!(item, adt, "non_exhaustive")),
+ (UNION, attrs!(item, adt)),
+ (CONST, attrs!(item)),
+ (
+ FN,
+ attrs!(
+ item, linkable,
+ "cold", "ignore", "inline", "must_use", "panic_handler", "proc_macro",
+ "proc_macro_derive", "proc_macro_attribute", "should_panic", "target_feature",
+ "test", "track_caller"
+ ),
+ ),
+ (STATIC, attrs!(item, linkable, "global_allocator", "used")),
+ (TRAIT, attrs!(item, "must_use")),
+ (IMPL, attrs!(item, "automatically_derived")),
+ (ASSOC_ITEM_LIST, attrs!(item)),
+ (EXTERN_BLOCK, attrs!(item, "link")),
+ (EXTERN_ITEM_LIST, attrs!(item, "link")),
+ (MACRO_CALL, attrs!()),
+ (SELF_PARAM, attrs!()),
+ (PARAM, attrs!()),
+ (RECORD_FIELD, attrs!()),
+ (VARIANT, attrs!("non_exhaustive")),
+ (TYPE_PARAM, attrs!()),
+ (CONST_PARAM, attrs!()),
+ (LIFETIME_PARAM, attrs!()),
+ (LET_STMT, attrs!()),
+ (EXPR_STMT, attrs!()),
+ (LITERAL, attrs!()),
+ (RECORD_EXPR_FIELD_LIST, attrs!()),
+ (RECORD_EXPR_FIELD, attrs!()),
+ (MATCH_ARM_LIST, attrs!()),
+ (MATCH_ARM, attrs!()),
+ (IDENT_PAT, attrs!()),
+ (RECORD_PAT_FIELD, attrs!()),
+ ]
+ .into_iter()
+ .collect()
+});
+const EXPR_ATTRIBUTES: &[&str] = attrs!();
+
+/// <https://doc.rust-lang.org/reference/attributes.html#built-in-attributes-index>
+// Keep these sorted for the binary search!
+const ATTRIBUTES: &[AttrCompletion] = &[
+ attr("allow(…)", Some("allow"), Some("allow(${0:lint})")),
+ attr("automatically_derived", None, None),
+ attr("cfg(…)", Some("cfg"), Some("cfg(${0:predicate})")),
+ attr("cfg_attr(…)", Some("cfg_attr"), Some("cfg_attr(${1:predicate}, ${0:attr})")),
+ attr("cold", None, None),
+ attr(r#"crate_name = """#, Some("crate_name"), Some(r#"crate_name = "${0:crate_name}""#))
+ .prefer_inner(),
+ attr("deny(…)", Some("deny"), Some("deny(${0:lint})")),
+ attr(r#"deprecated"#, Some("deprecated"), Some(r#"deprecated"#)),
+ attr("derive(…)", Some("derive"), Some(r#"derive(${0:Debug})"#)),
+ attr(r#"doc = "…""#, Some("doc"), Some(r#"doc = "${0:docs}""#)),
+ attr(r#"doc(alias = "…")"#, Some("docalias"), Some(r#"doc(alias = "${0:docs}")"#)),
+ attr(r#"doc(hidden)"#, Some("dochidden"), Some(r#"doc(hidden)"#)),
+ attr(
+ r#"export_name = "…""#,
+ Some("export_name"),
+ Some(r#"export_name = "${0:exported_symbol_name}""#),
+ ),
+ attr("feature(…)", Some("feature"), Some("feature(${0:flag})")).prefer_inner(),
+ attr("forbid(…)", Some("forbid"), Some("forbid(${0:lint})")),
+ attr("global_allocator", None, None),
+ attr(r#"ignore = "…""#, Some("ignore"), Some(r#"ignore = "${0:reason}""#)),
+ attr("inline", Some("inline"), Some("inline")),
+ attr("link", None, None),
+ attr(r#"link_name = "…""#, Some("link_name"), Some(r#"link_name = "${0:symbol_name}""#)),
+ attr(
+ r#"link_section = "…""#,
+ Some("link_section"),
+ Some(r#"link_section = "${0:section_name}""#),
+ ),
+ attr("macro_export", None, None),
+ attr("macro_use", None, None),
+ attr(r#"must_use"#, Some("must_use"), Some(r#"must_use"#)),
+ attr("no_implicit_prelude", None, None).prefer_inner(),
+ attr("no_link", None, None).prefer_inner(),
+ attr("no_main", None, None).prefer_inner(),
+ attr("no_mangle", None, None),
+ attr("no_std", None, None).prefer_inner(),
+ attr("non_exhaustive", None, None),
+ attr("panic_handler", None, None),
+ attr(r#"path = "…""#, Some("path"), Some(r#"path ="${0:path}""#)),
+ attr("proc_macro", None, None),
+ attr("proc_macro_attribute", None, None),
+ attr("proc_macro_derive(…)", Some("proc_macro_derive"), Some("proc_macro_derive(${0:Trait})")),
+ attr(
+ r#"recursion_limit = "…""#,
+ Some("recursion_limit"),
+ Some(r#"recursion_limit = "${0:128}""#),
+ )
+ .prefer_inner(),
+ attr("repr(…)", Some("repr"), Some("repr(${0:C})")),
+ attr("should_panic", Some("should_panic"), Some(r#"should_panic"#)),
+ attr(
+ r#"target_feature(enable = "…")"#,
+ Some("target_feature"),
+ Some(r#"target_feature(enable = "${0:feature}")"#),
+ ),
+ attr("test", None, None),
+ attr("track_caller", None, None),
+ attr("type_length_limit = …", Some("type_length_limit"), Some("type_length_limit = ${0:128}"))
+ .prefer_inner(),
+ attr("used", None, None),
+ attr("warn(…)", Some("warn"), Some("warn(${0:lint})")),
+ attr(
+ r#"windows_subsystem = "…""#,
+ Some("windows_subsystem"),
+ Some(r#"windows_subsystem = "${0:subsystem}""#),
+ )
+ .prefer_inner(),
+];
+
+fn parse_comma_sep_expr(input: ast::TokenTree) -> Option<Vec<ast::Expr>> {
+ let r_paren = input.r_paren_token()?;
+ let tokens = input
+ .syntax()
+ .children_with_tokens()
+ .skip(1)
+ .take_while(|it| it.as_token() != Some(&r_paren));
+ let input_expressions = tokens.group_by(|tok| tok.kind() == T![,]);
+ Some(
+ input_expressions
+ .into_iter()
+ .filter_map(|(is_sep, group)| (!is_sep).then(|| group))
+ .filter_map(|mut tokens| syntax::hacks::parse_expr_from_str(&tokens.join("")))
+ .collect::<Vec<ast::Expr>>(),
+ )
+}
+
+#[test]
+fn attributes_are_sorted() {
+ let mut attrs = ATTRIBUTES.iter().map(|attr| attr.key());
+ let mut prev = attrs.next().unwrap();
+
+ attrs.for_each(|next| {
+ assert!(
+ prev < next,
+ r#"ATTRIBUTES array is not sorted, "{}" should come after "{}""#,
+ prev,
+ next
+ );
+ prev = next;
+ });
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/cfg.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/cfg.rs
new file mode 100644
index 000000000..311060143
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/cfg.rs
@@ -0,0 +1,93 @@
+//! Completion for cfg
+
+use std::iter;
+
+use ide_db::SymbolKind;
+use itertools::Itertools;
+use syntax::SyntaxKind;
+
+use crate::{completions::Completions, context::CompletionContext, CompletionItem};
+
+pub(crate) fn complete_cfg(acc: &mut Completions, ctx: &CompletionContext<'_>) {
+ let add_completion = |item: &str| {
+ let mut completion = CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), item);
+ completion.insert_text(format!(r#""{}""#, item));
+ acc.add(completion.build());
+ };
+
+ let previous = iter::successors(ctx.original_token.prev_token(), |t| {
+ (matches!(t.kind(), SyntaxKind::EQ) || t.kind().is_trivia())
+ .then(|| t.prev_token())
+ .flatten()
+ })
+ .find(|t| matches!(t.kind(), SyntaxKind::IDENT));
+
+ match previous.as_ref().map(|p| p.text()) {
+ Some("target_arch") => KNOWN_ARCH.iter().copied().for_each(add_completion),
+ Some("target_env") => KNOWN_ENV.iter().copied().for_each(add_completion),
+ Some("target_os") => KNOWN_OS.iter().copied().for_each(add_completion),
+ Some("target_vendor") => KNOWN_VENDOR.iter().copied().for_each(add_completion),
+ Some("target_endian") => ["little", "big"].into_iter().for_each(add_completion),
+ Some(name) => ctx.krate.potential_cfg(ctx.db).get_cfg_values(name).cloned().for_each(|s| {
+ let insert_text = format!(r#""{}""#, s);
+ let mut item = CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), s);
+ item.insert_text(insert_text);
+
+ acc.add(item.build());
+ }),
+ None => ctx.krate.potential_cfg(ctx.db).get_cfg_keys().cloned().unique().for_each(|s| {
+ let item = CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), s);
+ acc.add(item.build());
+ }),
+ };
+}
+
+const KNOWN_ARCH: [&str; 19] = [
+ "aarch64",
+ "arm",
+ "avr",
+ "hexagon",
+ "mips",
+ "mips64",
+ "msp430",
+ "nvptx64",
+ "powerpc",
+ "powerpc64",
+ "riscv32",
+ "riscv64",
+ "s390x",
+ "sparc",
+ "sparc64",
+ "wasm32",
+ "wasm64",
+ "x86",
+ "x86_64",
+];
+
+const KNOWN_ENV: [&str; 7] = ["eabihf", "gnu", "gnueabihf", "msvc", "relibc", "sgx", "uclibc"];
+
+const KNOWN_OS: [&str; 20] = [
+ "cuda",
+ "dragonfly",
+ "emscripten",
+ "freebsd",
+ "fuchsia",
+ "haiku",
+ "hermit",
+ "illumos",
+ "l4re",
+ "linux",
+ "netbsd",
+ "none",
+ "openbsd",
+ "psp",
+ "redox",
+ "solaris",
+ "uefi",
+ "unknown",
+ "vxworks",
+ "windows",
+];
+
+const KNOWN_VENDOR: [&str; 8] =
+ ["apple", "fortanix", "nvidia", "pc", "sony", "unknown", "wrs", "uwp"];
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/derive.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/derive.rs
new file mode 100644
index 000000000..793c22630
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/derive.rs
@@ -0,0 +1,116 @@
+//! Completion for derives
+use hir::{HasAttrs, ScopeDef};
+use ide_db::SymbolKind;
+use itertools::Itertools;
+use syntax::SmolStr;
+
+use crate::{
+ context::{CompletionContext, ExistingDerives, PathCompletionCtx, Qualified},
+ item::CompletionItem,
+ Completions,
+};
+
+pub(crate) fn complete_derive_path(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx @ PathCompletionCtx { qualified, .. }: &PathCompletionCtx,
+ existing_derives: &ExistingDerives,
+) {
+ let core = ctx.famous_defs().core();
+
+ match qualified {
+ Qualified::With {
+ resolution: Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))),
+ super_chain_len,
+ ..
+ } => {
+ acc.add_super_keyword(ctx, *super_chain_len);
+
+ for (name, def) in module.scope(ctx.db, Some(ctx.module)) {
+ match def {
+ ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac))
+ if !existing_derives.contains(&mac) && mac.is_derive(ctx.db) =>
+ {
+ acc.add_macro(ctx, path_ctx, mac, name)
+ }
+ ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
+ acc.add_module(ctx, path_ctx, m, name)
+ }
+ _ => (),
+ }
+ }
+ }
+ Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
+ // only show modules in a fresh UseTree
+ Qualified::No => {
+ ctx.process_all_names(&mut |name, def| {
+ let mac = match def {
+ ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac))
+ if !existing_derives.contains(&mac) && mac.is_derive(ctx.db) =>
+ {
+ mac
+ }
+ ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
+ return acc.add_module(ctx, path_ctx, m, name);
+ }
+ _ => return,
+ };
+
+ match (core, mac.module(ctx.db).krate()) {
+ // show derive dependencies for `core`/`std` derives
+ (Some(core), mac_krate) if core == mac_krate => {}
+ _ => return acc.add_macro(ctx, path_ctx, mac, name),
+ };
+
+ let name_ = name.to_smol_str();
+ let find = DEFAULT_DERIVE_DEPENDENCIES
+ .iter()
+ .find(|derive_completion| derive_completion.label == name_);
+
+ match find {
+ Some(derive_completion) => {
+ let mut components = vec![derive_completion.label];
+ components.extend(derive_completion.dependencies.iter().filter(
+ |&&dependency| {
+ !existing_derives
+ .iter()
+ .map(|it| it.name(ctx.db))
+ .any(|it| it.to_smol_str() == dependency)
+ },
+ ));
+ let lookup = components.join(", ");
+ let label = Itertools::intersperse(components.into_iter().rev(), ", ");
+
+ let mut item = CompletionItem::new(
+ SymbolKind::Derive,
+ ctx.source_range(),
+ SmolStr::from_iter(label),
+ );
+ if let Some(docs) = mac.docs(ctx.db) {
+ item.documentation(docs);
+ }
+ item.lookup_by(lookup);
+ item.add_to(acc);
+ }
+ None => acc.add_macro(ctx, path_ctx, mac, name),
+ }
+ });
+ acc.add_nameref_keywords_with_colon(ctx);
+ }
+ Qualified::TypeAnchor { .. } | Qualified::With { .. } => {}
+ }
+}
+
+struct DeriveDependencies {
+ label: &'static str,
+ dependencies: &'static [&'static str],
+}
+
+/// Standard Rust derives that have dependencies
+/// (the dependencies are needed so that the main derive don't break the compilation when added)
+const DEFAULT_DERIVE_DEPENDENCIES: &[DeriveDependencies] = &[
+ DeriveDependencies { label: "Copy", dependencies: &["Clone"] },
+ DeriveDependencies { label: "Eq", dependencies: &["PartialEq"] },
+ DeriveDependencies { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] },
+ DeriveDependencies { label: "PartialOrd", dependencies: &["PartialEq"] },
+];
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/lint.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/lint.rs
new file mode 100644
index 000000000..967f6ddd9
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/lint.rs
@@ -0,0 +1,61 @@
+//! Completion for lints
+use ide_db::{generated::lints::Lint, SymbolKind};
+use syntax::ast;
+
+use crate::{context::CompletionContext, item::CompletionItem, Completions};
+
+pub(super) fn complete_lint(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ is_qualified: bool,
+ existing_lints: &[ast::Path],
+ lints_completions: &[Lint],
+) {
+ for &Lint { label, description } in lints_completions {
+ let (qual, name) = {
+ // FIXME: change `Lint`'s label to not store a path in it but split the prefix off instead?
+ let mut parts = label.split("::");
+ let ns_or_label = match parts.next() {
+ Some(it) => it,
+ None => continue,
+ };
+ let label = parts.next();
+ match label {
+ Some(label) => (Some(ns_or_label), label),
+ None => (None, ns_or_label),
+ }
+ };
+ if qual.is_none() && is_qualified {
+ // qualified completion requested, but this lint is unqualified
+ continue;
+ }
+ let lint_already_annotated = existing_lints
+ .iter()
+ .filter_map(|path| {
+ let q = path.qualifier();
+ if q.as_ref().and_then(|it| it.qualifier()).is_some() {
+ return None;
+ }
+ Some((q.and_then(|it| it.as_single_name_ref()), path.segment()?.name_ref()?))
+ })
+ .any(|(q, name_ref)| {
+ let qualifier_matches = match (q, qual) {
+ (None, None) => true,
+ (None, Some(_)) => false,
+ (Some(_), None) => false,
+ (Some(q), Some(ns)) => q.text() == ns,
+ };
+ qualifier_matches && name_ref.text() == name
+ });
+ if lint_already_annotated {
+ continue;
+ }
+ let label = match qual {
+ Some(qual) if !is_qualified => format!("{}::{}", qual, name),
+ _ => name.to_owned(),
+ };
+ let mut item = CompletionItem::new(SymbolKind::Attribute, ctx.source_range(), label);
+ item.documentation(hir::Documentation::new(description.to_owned()));
+ item.add_to(acc)
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/repr.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/repr.rs
new file mode 100644
index 000000000..a29417133
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/repr.rs
@@ -0,0 +1,74 @@
+//! Completion for representations.
+
+use ide_db::SymbolKind;
+use syntax::ast;
+
+use crate::{context::CompletionContext, item::CompletionItem, Completions};
+
+pub(super) fn complete_repr(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ input: ast::TokenTree,
+) {
+ if let Some(existing_reprs) = super::parse_comma_sep_expr(input) {
+ for &ReprCompletion { label, snippet, lookup, collides } in REPR_COMPLETIONS {
+ let repr_already_annotated = existing_reprs
+ .iter()
+ .filter_map(|expr| match expr {
+ ast::Expr::PathExpr(path) => path.path()?.as_single_name_ref(),
+ ast::Expr::CallExpr(call) => match call.expr()? {
+ ast::Expr::PathExpr(path) => path.path()?.as_single_name_ref(),
+ _ => None,
+ },
+ _ => None,
+ })
+ .any(|it| {
+ let text = it.text();
+ lookup.unwrap_or(label) == text || collides.contains(&text.as_str())
+ });
+ if repr_already_annotated {
+ continue;
+ }
+
+ let mut item = CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), label);
+ if let Some(lookup) = lookup {
+ item.lookup_by(lookup);
+ }
+ if let Some((snippet, cap)) = snippet.zip(ctx.config.snippet_cap) {
+ item.insert_snippet(cap, snippet);
+ }
+ item.add_to(acc);
+ }
+ }
+}
+
+struct ReprCompletion {
+ label: &'static str,
+ snippet: Option<&'static str>,
+ lookup: Option<&'static str>,
+ collides: &'static [&'static str],
+}
+
+const fn attr(label: &'static str, collides: &'static [&'static str]) -> ReprCompletion {
+ ReprCompletion { label, snippet: None, lookup: None, collides }
+}
+
+#[rustfmt::skip]
+const REPR_COMPLETIONS: &[ReprCompletion] = &[
+ ReprCompletion { label: "align($0)", snippet: Some("align($0)"), lookup: Some("align"), collides: &["transparent", "packed"] },
+ attr("packed", &["transparent", "align"]),
+ attr("transparent", &["C", "u8", "u16", "u32", "u64", "u128", "usize", "i8", "i16", "i32", "i64", "i128", "isize"]),
+ attr("C", &["transparent"]),
+ attr("u8", &["transparent", "u16", "u32", "u64", "u128", "usize", "i8", "i16", "i32", "i64", "i128", "isize"]),
+ attr("u16", &["transparent", "u8", "u32", "u64", "u128", "usize", "i8", "i16", "i32", "i64", "i128", "isize"]),
+ attr("u32", &["transparent", "u8", "u16", "u64", "u128", "usize", "i8", "i16", "i32", "i64", "i128", "isize"]),
+ attr("u64", &["transparent", "u8", "u16", "u32", "u128", "usize", "i8", "i16", "i32", "i64", "i128", "isize"]),
+ attr("u128", &["transparent", "u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", "i128", "isize"]),
+ attr("usize", &["transparent", "u8", "u16", "u32", "u64", "u128", "i8", "i16", "i32", "i64", "i128", "isize"]),
+ attr("i8", &["transparent", "u8", "u16", "u32", "u64", "u128", "usize", "i16", "i32", "i64", "i128", "isize"]),
+ attr("i16", &["transparent", "u8", "u16", "u32", "u64", "u128", "usize", "i8", "i32", "i64", "i128", "isize"]),
+ attr("i32", &["transparent", "u8", "u16", "u32", "u64", "u128", "usize", "i8", "i16", "i64", "i128", "isize"]),
+ attr("i64", &["transparent", "u8", "u16", "u32", "u64", "u128", "usize", "i8", "i16", "i32", "i128", "isize"]),
+ attr("i28", &["transparent", "u8", "u16", "u32", "u64", "u128", "usize", "i8", "i16", "i32", "i64", "isize"]),
+ attr("isize", &["transparent", "u8", "u16", "u32", "u64", "u128", "usize", "i8", "i16", "i32", "i64", "i128"]),
+];
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs
new file mode 100644
index 000000000..cf40ca489
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs
@@ -0,0 +1,947 @@
+//! Completes references after dot (fields and method calls).
+
+use ide_db::FxHashSet;
+
+use crate::{
+ context::{CompletionContext, DotAccess, DotAccessKind, ExprCtx, PathCompletionCtx, Qualified},
+ CompletionItem, CompletionItemKind, Completions,
+};
+
+/// Complete dot accesses, i.e. fields or methods.
+pub(crate) fn complete_dot(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ dot_access: &DotAccess,
+) {
+ let receiver_ty = match dot_access {
+ DotAccess { receiver_ty: Some(receiver_ty), .. } => &receiver_ty.original,
+ _ => return,
+ };
+
+ // Suggest .await syntax for types that implement Future trait
+ if receiver_ty.impls_future(ctx.db) {
+ let mut item =
+ CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), "await");
+ item.detail("expr.await");
+ item.add_to(acc);
+ }
+
+ if let DotAccessKind::Method { .. } = dot_access.kind {
+ cov_mark::hit!(test_no_struct_field_completion_for_method_call);
+ } else {
+ complete_fields(
+ acc,
+ ctx,
+ &receiver_ty,
+ |acc, field, ty| acc.add_field(ctx, dot_access, None, field, &ty),
+ |acc, field, ty| acc.add_tuple_field(ctx, None, field, &ty),
+ );
+ }
+ complete_methods(ctx, &receiver_ty, |func| acc.add_method(ctx, dot_access, func, None, None));
+}
+
+pub(crate) fn complete_undotted_self(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ expr_ctx: &ExprCtx,
+) {
+ if !ctx.config.enable_self_on_the_fly {
+ return;
+ }
+ if !path_ctx.is_trivial_path() {
+ return;
+ }
+ if !ctx.qualifier_ctx.none() {
+ return;
+ }
+ if !matches!(path_ctx.qualified, Qualified::No) {
+ return;
+ }
+ let self_param = match expr_ctx {
+ ExprCtx { self_param: Some(self_param), .. } => self_param,
+ _ => return,
+ };
+
+ let ty = self_param.ty(ctx.db);
+ complete_fields(
+ acc,
+ ctx,
+ &ty,
+ |acc, field, ty| {
+ acc.add_field(
+ ctx,
+ &DotAccess {
+ receiver: None,
+ receiver_ty: None,
+ kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal: false },
+ },
+ Some(hir::known::SELF_PARAM),
+ field,
+ &ty,
+ )
+ },
+ |acc, field, ty| acc.add_tuple_field(ctx, Some(hir::known::SELF_PARAM), field, &ty),
+ );
+ complete_methods(ctx, &ty, |func| {
+ acc.add_method(
+ ctx,
+ &DotAccess {
+ receiver: None,
+ receiver_ty: None,
+ kind: DotAccessKind::Method { has_parens: false },
+ },
+ func,
+ Some(hir::known::SELF_PARAM),
+ None,
+ )
+ });
+}
+
+fn complete_fields(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ receiver: &hir::Type,
+ mut named_field: impl FnMut(&mut Completions, hir::Field, hir::Type),
+ mut tuple_index: impl FnMut(&mut Completions, usize, hir::Type),
+) {
+ for receiver in receiver.autoderef(ctx.db) {
+ for (field, ty) in receiver.fields(ctx.db) {
+ named_field(acc, field, ty);
+ }
+ for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() {
+ // Tuple fields are always public (tuple struct fields are handled above).
+ tuple_index(acc, i, ty);
+ }
+ }
+}
+
+fn complete_methods(
+ ctx: &CompletionContext<'_>,
+ receiver: &hir::Type,
+ mut f: impl FnMut(hir::Function),
+) {
+ let mut seen_methods = FxHashSet::default();
+ receiver.iterate_method_candidates(
+ ctx.db,
+ &ctx.scope,
+ &ctx.traits_in_scope(),
+ Some(ctx.module),
+ None,
+ |func| {
+ if func.self_param(ctx.db).is_some() && seen_methods.insert(func.name(ctx.db)) {
+ f(func);
+ }
+ None::<()>
+ },
+ );
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+
+ use crate::tests::{
+ check_edit, completion_list_no_kw, completion_list_no_kw_with_private_editable,
+ };
+
+ fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list_no_kw(ra_fixture);
+ expect.assert_eq(&actual);
+ }
+
+ fn check_with_private_editable(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list_no_kw_with_private_editable(ra_fixture);
+ expect.assert_eq(&actual);
+ }
+
+ #[test]
+ fn test_struct_field_and_method_completion() {
+ check(
+ r#"
+struct S { foo: u32 }
+impl S {
+ fn bar(&self) {}
+}
+fn foo(s: S) { s.$0 }
+"#,
+ expect![[r#"
+ fd foo u32
+ me bar() fn(&self)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_struct_field_completion_self() {
+ check(
+ r#"
+struct S { the_field: (u32,) }
+impl S {
+ fn foo(self) { self.$0 }
+}
+"#,
+ expect![[r#"
+ fd the_field (u32,)
+ me foo() fn(self)
+ "#]],
+ )
+ }
+
+ #[test]
+ fn test_struct_field_completion_autoderef() {
+ check(
+ r#"
+struct A { the_field: (u32, i32) }
+impl A {
+ fn foo(&self) { self.$0 }
+}
+"#,
+ expect![[r#"
+ fd the_field (u32, i32)
+ me foo() fn(&self)
+ "#]],
+ )
+ }
+
+ #[test]
+ fn test_no_struct_field_completion_for_method_call() {
+ cov_mark::check!(test_no_struct_field_completion_for_method_call);
+ check(
+ r#"
+struct A { the_field: u32 }
+fn foo(a: A) { a.$0() }
+"#,
+ expect![[r#""#]],
+ );
+ }
+
+ #[test]
+ fn test_visibility_filtering() {
+ check(
+ r#"
+//- /lib.rs crate:lib new_source_root:local
+pub mod m {
+ pub struct A {
+ private_field: u32,
+ pub pub_field: u32,
+ pub(crate) crate_field: u32,
+ pub(super) super_field: u32,
+ }
+}
+//- /main.rs crate:main deps:lib new_source_root:local
+fn foo(a: lib::m::A) { a.$0 }
+"#,
+ expect![[r#"
+ fd pub_field u32
+ "#]],
+ );
+
+ check(
+ r#"
+//- /lib.rs crate:lib new_source_root:library
+pub mod m {
+ pub struct A {
+ private_field: u32,
+ pub pub_field: u32,
+ pub(crate) crate_field: u32,
+ pub(super) super_field: u32,
+ }
+}
+//- /main.rs crate:main deps:lib new_source_root:local
+fn foo(a: lib::m::A) { a.$0 }
+"#,
+ expect![[r#"
+ fd pub_field u32
+ "#]],
+ );
+
+ check(
+ r#"
+//- /lib.rs crate:lib new_source_root:library
+pub mod m {
+ pub struct A(
+ i32,
+ pub f64,
+ );
+}
+//- /main.rs crate:main deps:lib new_source_root:local
+fn foo(a: lib::m::A) { a.$0 }
+"#,
+ expect![[r#"
+ fd 1 f64
+ "#]],
+ );
+
+ check(
+ r#"
+//- /lib.rs crate:lib new_source_root:local
+pub struct A {}
+mod m {
+ impl super::A {
+ fn private_method(&self) {}
+ pub(crate) fn crate_method(&self) {}
+ pub fn pub_method(&self) {}
+ }
+}
+//- /main.rs crate:main deps:lib new_source_root:local
+fn foo(a: lib::A) { a.$0 }
+"#,
+ expect![[r#"
+ me pub_method() fn(&self)
+ "#]],
+ );
+ check(
+ r#"
+//- /lib.rs crate:lib new_source_root:library
+pub struct A {}
+mod m {
+ impl super::A {
+ fn private_method(&self) {}
+ pub(crate) fn crate_method(&self) {}
+ pub fn pub_method(&self) {}
+ }
+}
+//- /main.rs crate:main deps:lib new_source_root:local
+fn foo(a: lib::A) { a.$0 }
+"#,
+ expect![[r#"
+ me pub_method() fn(&self)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_visibility_filtering_with_private_editable_enabled() {
+ check_with_private_editable(
+ r#"
+//- /lib.rs crate:lib new_source_root:local
+pub mod m {
+ pub struct A {
+ private_field: u32,
+ pub pub_field: u32,
+ pub(crate) crate_field: u32,
+ pub(super) super_field: u32,
+ }
+}
+//- /main.rs crate:main deps:lib new_source_root:local
+fn foo(a: lib::m::A) { a.$0 }
+"#,
+ expect![[r#"
+ fd crate_field u32
+ fd private_field u32
+ fd pub_field u32
+ fd super_field u32
+ "#]],
+ );
+
+ check_with_private_editable(
+ r#"
+//- /lib.rs crate:lib new_source_root:library
+pub mod m {
+ pub struct A {
+ private_field: u32,
+ pub pub_field: u32,
+ pub(crate) crate_field: u32,
+ pub(super) super_field: u32,
+ }
+}
+//- /main.rs crate:main deps:lib new_source_root:local
+fn foo(a: lib::m::A) { a.$0 }
+"#,
+ expect![[r#"
+ fd pub_field u32
+ "#]],
+ );
+
+ check_with_private_editable(
+ r#"
+//- /lib.rs crate:lib new_source_root:library
+pub mod m {
+ pub struct A(
+ i32,
+ pub f64,
+ );
+}
+//- /main.rs crate:main deps:lib new_source_root:local
+fn foo(a: lib::m::A) { a.$0 }
+"#,
+ expect![[r#"
+ fd 1 f64
+ "#]],
+ );
+
+ check_with_private_editable(
+ r#"
+//- /lib.rs crate:lib new_source_root:local
+pub struct A {}
+mod m {
+ impl super::A {
+ fn private_method(&self) {}
+ pub(crate) fn crate_method(&self) {}
+ pub fn pub_method(&self) {}
+ }
+}
+//- /main.rs crate:main deps:lib new_source_root:local
+fn foo(a: lib::A) { a.$0 }
+"#,
+ expect![[r#"
+ me crate_method() fn(&self)
+ me private_method() fn(&self)
+ me pub_method() fn(&self)
+ "#]],
+ );
+ check_with_private_editable(
+ r#"
+//- /lib.rs crate:lib new_source_root:library
+pub struct A {}
+mod m {
+ impl super::A {
+ fn private_method(&self) {}
+ pub(crate) fn crate_method(&self) {}
+ pub fn pub_method(&self) {}
+ }
+}
+//- /main.rs crate:main deps:lib new_source_root:local
+fn foo(a: lib::A) { a.$0 }
+"#,
+ expect![[r#"
+ me pub_method() fn(&self)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_local_impls() {
+ check(
+ r#"
+//- /lib.rs crate:lib
+pub struct A {}
+mod m {
+ impl super::A {
+ pub fn pub_module_method(&self) {}
+ }
+ fn f() {
+ impl super::A {
+ pub fn pub_foreign_local_method(&self) {}
+ }
+ }
+}
+//- /main.rs crate:main deps:lib
+fn foo(a: lib::A) {
+ impl lib::A {
+ fn local_method(&self) {}
+ }
+ a.$0
+}
+"#,
+ expect![[r#"
+ me local_method() fn(&self)
+ me pub_module_method() fn(&self)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_doc_hidden_filtering() {
+ check(
+ r#"
+//- /lib.rs crate:lib deps:dep
+fn foo(a: dep::A) { a.$0 }
+//- /dep.rs crate:dep
+pub struct A {
+ #[doc(hidden)]
+ pub hidden_field: u32,
+ pub pub_field: u32,
+}
+
+impl A {
+ pub fn pub_method(&self) {}
+
+ #[doc(hidden)]
+ pub fn hidden_method(&self) {}
+}
+ "#,
+ expect![[r#"
+ fd pub_field u32
+ me pub_method() fn(&self)
+ "#]],
+ )
+ }
+
+ #[test]
+ fn test_union_field_completion() {
+ check(
+ r#"
+union U { field: u8, other: u16 }
+fn foo(u: U) { u.$0 }
+"#,
+ expect![[r#"
+ fd field u8
+ fd other u16
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_method_completion_only_fitting_impls() {
+ check(
+ r#"
+struct A<T> {}
+impl A<u32> {
+ fn the_method(&self) {}
+}
+impl A<i32> {
+ fn the_other_method(&self) {}
+}
+fn foo(a: A<u32>) { a.$0 }
+"#,
+ expect![[r#"
+ me the_method() fn(&self)
+ "#]],
+ )
+ }
+
+ #[test]
+ fn test_trait_method_completion() {
+ check(
+ r#"
+struct A {}
+trait Trait { fn the_method(&self); }
+impl Trait for A {}
+fn foo(a: A) { a.$0 }
+"#,
+ expect![[r#"
+ me the_method() (as Trait) fn(&self)
+ "#]],
+ );
+ check_edit(
+ "the_method",
+ r#"
+struct A {}
+trait Trait { fn the_method(&self); }
+impl Trait for A {}
+fn foo(a: A) { a.$0 }
+"#,
+ r#"
+struct A {}
+trait Trait { fn the_method(&self); }
+impl Trait for A {}
+fn foo(a: A) { a.the_method()$0 }
+"#,
+ );
+ }
+
+ #[test]
+ fn test_trait_method_completion_deduplicated() {
+ check(
+ r"
+struct A {}
+trait Trait { fn the_method(&self); }
+impl<T> Trait for T {}
+fn foo(a: &A) { a.$0 }
+",
+ expect![[r#"
+ me the_method() (as Trait) fn(&self)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn completes_trait_method_from_other_module() {
+ check(
+ r"
+struct A {}
+mod m {
+ pub trait Trait { fn the_method(&self); }
+}
+use m::Trait;
+impl Trait for A {}
+fn foo(a: A) { a.$0 }
+",
+ expect![[r#"
+ me the_method() (as Trait) fn(&self)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_no_non_self_method() {
+ check(
+ r#"
+struct A {}
+impl A {
+ fn the_method() {}
+}
+fn foo(a: A) {
+ a.$0
+}
+"#,
+ expect![[r#""#]],
+ );
+ }
+
+ #[test]
+ fn test_tuple_field_completion() {
+ check(
+ r#"
+fn foo() {
+ let b = (0, 3.14);
+ b.$0
+}
+"#,
+ expect![[r#"
+ fd 0 i32
+ fd 1 f64
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_tuple_struct_field_completion() {
+ check(
+ r#"
+struct S(i32, f64);
+fn foo() {
+ let b = S(0, 3.14);
+ b.$0
+}
+"#,
+ expect![[r#"
+ fd 0 i32
+ fd 1 f64
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_tuple_field_inference() {
+ check(
+ r#"
+pub struct S;
+impl S { pub fn blah(&self) {} }
+
+struct T(S);
+
+impl T {
+ fn foo(&self) {
+ // FIXME: This doesn't work without the trailing `a` as `0.` is a float
+ self.0.a$0
+ }
+}
+"#,
+ expect![[r#"
+ me blah() fn(&self)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_completion_works_in_consts() {
+ check(
+ r#"
+struct A { the_field: u32 }
+const X: u32 = {
+ A { the_field: 92 }.$0
+};
+"#,
+ expect![[r#"
+ fd the_field u32
+ "#]],
+ );
+ }
+
+ #[test]
+ fn works_in_simple_macro_1() {
+ check(
+ r#"
+macro_rules! m { ($e:expr) => { $e } }
+struct A { the_field: u32 }
+fn foo(a: A) {
+ m!(a.x$0)
+}
+"#,
+ expect![[r#"
+ fd the_field u32
+ "#]],
+ );
+ }
+
+ #[test]
+ fn works_in_simple_macro_2() {
+ // this doesn't work yet because the macro doesn't expand without the token -- maybe it can be fixed with better recovery
+ check(
+ r#"
+macro_rules! m { ($e:expr) => { $e } }
+struct A { the_field: u32 }
+fn foo(a: A) {
+ m!(a.$0)
+}
+"#,
+ expect![[r#"
+ fd the_field u32
+ "#]],
+ );
+ }
+
+ #[test]
+ fn works_in_simple_macro_recursive_1() {
+ check(
+ r#"
+macro_rules! m { ($e:expr) => { $e } }
+struct A { the_field: u32 }
+fn foo(a: A) {
+ m!(m!(m!(a.x$0)))
+}
+"#,
+ expect![[r#"
+ fd the_field u32
+ "#]],
+ );
+ }
+
+ #[test]
+ fn macro_expansion_resilient() {
+ check(
+ r#"
+macro_rules! d {
+ () => {};
+ ($val:expr) => {
+ match $val { tmp => { tmp } }
+ };
+ // Trailing comma with single argument is ignored
+ ($val:expr,) => { $crate::d!($val) };
+ ($($val:expr),+ $(,)?) => {
+ ($($crate::d!($val)),+,)
+ };
+}
+struct A { the_field: u32 }
+fn foo(a: A) {
+ d!(a.$0)
+}
+"#,
+ expect![[r#"
+ fd the_field u32
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_method_completion_issue_3547() {
+ check(
+ r#"
+struct HashSet<T> {}
+impl<T> HashSet<T> {
+ pub fn the_method(&self) {}
+}
+fn foo() {
+ let s: HashSet<_>;
+ s.$0
+}
+"#,
+ expect![[r#"
+ me the_method() fn(&self)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn completes_method_call_when_receiver_is_a_macro_call() {
+ check(
+ r#"
+struct S;
+impl S { fn foo(&self) {} }
+macro_rules! make_s { () => { S }; }
+fn main() { make_s!().f$0; }
+"#,
+ expect![[r#"
+ me foo() fn(&self)
+ "#]],
+ )
+ }
+
+ #[test]
+ fn completes_after_macro_call_in_submodule() {
+ check(
+ r#"
+macro_rules! empty {
+ () => {};
+}
+
+mod foo {
+ #[derive(Debug, Default)]
+ struct Template2 {}
+
+ impl Template2 {
+ fn private(&self) {}
+ }
+ fn baz() {
+ let goo: Template2 = Template2 {};
+ empty!();
+ goo.$0
+ }
+}
+ "#,
+ expect![[r#"
+ me private() fn(&self)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn issue_8931() {
+ check(
+ r#"
+//- minicore: fn
+struct S;
+
+struct Foo;
+impl Foo {
+ fn foo(&self) -> &[u8] { loop {} }
+}
+
+impl S {
+ fn indented(&mut self, f: impl FnOnce(&mut Self)) {
+ }
+
+ fn f(&mut self, v: Foo) {
+ self.indented(|this| v.$0)
+ }
+}
+ "#,
+ expect![[r#"
+ me foo() fn(&self) -> &[u8]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn completes_bare_fields_and_methods_in_methods() {
+ check(
+ r#"
+struct Foo { field: i32 }
+
+impl Foo { fn foo(&self) { $0 } }"#,
+ expect![[r#"
+ fd self.field i32
+ lc self &Foo
+ sp Self
+ st Foo
+ bt u32
+ me self.foo() fn(&self)
+ "#]],
+ );
+ check(
+ r#"
+struct Foo(i32);
+
+impl Foo { fn foo(&mut self) { $0 } }"#,
+ expect![[r#"
+ fd self.0 i32
+ lc self &mut Foo
+ sp Self
+ st Foo
+ bt u32
+ me self.foo() fn(&mut self)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn macro_completion_after_dot() {
+ check(
+ r#"
+macro_rules! m {
+ ($e:expr) => { $e };
+}
+
+struct Completable;
+
+impl Completable {
+ fn method(&self) {}
+}
+
+fn f() {
+ let c = Completable;
+ m!(c.$0);
+}
+ "#,
+ expect![[r#"
+ me method() fn(&self)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn completes_method_call_when_receiver_type_has_errors_issue_10297() {
+ check(
+ r#"
+//- minicore: iterator, sized
+struct Vec<T>;
+impl<T> IntoIterator for Vec<T> {
+ type Item = ();
+ type IntoIter = ();
+ fn into_iter(self);
+}
+fn main() {
+ let x: Vec<_>;
+ x.$0;
+}
+"#,
+ expect![[r#"
+ me into_iter() (as IntoIterator) fn(self) -> <Self as IntoIterator>::IntoIter
+ "#]],
+ )
+ }
+
+ #[test]
+ fn postfix_drop_completion() {
+ cov_mark::check!(postfix_drop_completion);
+ check_edit(
+ "drop",
+ r#"
+//- minicore: drop
+struct Vec<T>(T);
+impl<T> Drop for Vec<T> {
+ fn drop(&mut self) {}
+}
+fn main() {
+ let x = Vec(0u32)
+ x.$0;
+}
+"#,
+ r"
+struct Vec<T>(T);
+impl<T> Drop for Vec<T> {
+ fn drop(&mut self) {}
+}
+fn main() {
+ let x = Vec(0u32)
+ drop($0x);
+}
+",
+ )
+ }
+
+ #[test]
+ fn issue_12484() {
+ check(
+ r#"
+//- minicore: sized
+trait SizeUser {
+ type Size;
+}
+trait Closure: SizeUser {}
+trait Encrypt: SizeUser {
+ fn encrypt(self, _: impl Closure<Size = Self::Size>);
+}
+fn test(thing: impl Encrypt) {
+ thing.$0;
+}
+ "#,
+ expect![[r#"
+ me encrypt(…) (as Encrypt) fn(self, impl Closure<Size = <Self as SizeUser>::Size>)
+ "#]],
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/expr.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/expr.rs
new file mode 100644
index 000000000..5d0ddaaf2
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/expr.rs
@@ -0,0 +1,280 @@
+//! Completion of names from the current scope in expression position.
+
+use hir::ScopeDef;
+
+use crate::{
+ context::{ExprCtx, PathCompletionCtx, Qualified},
+ CompletionContext, Completions,
+};
+
+pub(crate) fn complete_expr_path(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx @ PathCompletionCtx { qualified, .. }: &PathCompletionCtx,
+ expr_ctx: &ExprCtx,
+) {
+ let _p = profile::span("complete_expr_path");
+ if !ctx.qualifier_ctx.none() {
+ return;
+ }
+
+ let &ExprCtx {
+ in_block_expr,
+ in_loop_body,
+ after_if_expr,
+ in_condition,
+ incomplete_let,
+ ref ref_expr_parent,
+ ref is_func_update,
+ ref innermost_ret_ty,
+ ref impl_,
+ in_match_guard,
+ ..
+ } = expr_ctx;
+
+ let wants_mut_token =
+ ref_expr_parent.as_ref().map(|it| it.mut_token().is_none()).unwrap_or(false);
+
+ let scope_def_applicable = |def| match def {
+ ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_)) | ScopeDef::Label(_) => false,
+ ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => mac.is_fn_like(ctx.db),
+ _ => true,
+ };
+
+ let add_assoc_item = |acc: &mut Completions, item| match item {
+ hir::AssocItem::Function(func) => acc.add_function(ctx, path_ctx, func, None),
+ hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
+ hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
+ };
+
+ match qualified {
+ Qualified::TypeAnchor { ty: None, trait_: None } => ctx
+ .traits_in_scope()
+ .iter()
+ .flat_map(|&it| hir::Trait::from(it).items(ctx.sema.db))
+ .for_each(|item| add_assoc_item(acc, item)),
+ Qualified::TypeAnchor { trait_: Some(trait_), .. } => {
+ trait_.items(ctx.sema.db).into_iter().for_each(|item| add_assoc_item(acc, item))
+ }
+ Qualified::TypeAnchor { ty: Some(ty), trait_: None } => {
+ if let Some(hir::Adt::Enum(e)) = ty.as_adt() {
+ cov_mark::hit!(completes_variant_through_alias);
+ acc.add_enum_variants(ctx, path_ctx, e);
+ }
+
+ ctx.iterate_path_candidates(&ty, |item| {
+ add_assoc_item(acc, item);
+ });
+
+ // Iterate assoc types separately
+ ty.iterate_assoc_items(ctx.db, ctx.krate, |item| {
+ if let hir::AssocItem::TypeAlias(ty) = item {
+ acc.add_type_alias(ctx, ty)
+ }
+ None::<()>
+ });
+ }
+ Qualified::With { resolution: None, .. } => {}
+ Qualified::With { resolution: Some(resolution), .. } => {
+ // Add associated types on type parameters and `Self`.
+ ctx.scope.assoc_type_shorthand_candidates(resolution, |_, alias| {
+ acc.add_type_alias(ctx, alias);
+ None::<()>
+ });
+ match resolution {
+ hir::PathResolution::Def(hir::ModuleDef::Module(module)) => {
+ let module_scope = module.scope(ctx.db, Some(ctx.module));
+ for (name, def) in module_scope {
+ if scope_def_applicable(def) {
+ acc.add_path_resolution(ctx, path_ctx, name, def);
+ }
+ }
+ }
+ hir::PathResolution::Def(
+ def @ (hir::ModuleDef::Adt(_)
+ | hir::ModuleDef::TypeAlias(_)
+ | hir::ModuleDef::BuiltinType(_)),
+ ) => {
+ let ty = match def {
+ hir::ModuleDef::Adt(adt) => adt.ty(ctx.db),
+ hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db),
+ hir::ModuleDef::BuiltinType(builtin) => {
+ cov_mark::hit!(completes_primitive_assoc_const);
+ builtin.ty(ctx.db)
+ }
+ _ => return,
+ };
+
+ if let Some(hir::Adt::Enum(e)) = ty.as_adt() {
+ cov_mark::hit!(completes_variant_through_alias);
+ acc.add_enum_variants(ctx, path_ctx, e);
+ }
+
+ // XXX: For parity with Rust bug #22519, this does not complete Ty::AssocType.
+ // (where AssocType is defined on a trait, not an inherent impl)
+
+ ctx.iterate_path_candidates(&ty, |item| {
+ add_assoc_item(acc, item);
+ });
+
+ // Iterate assoc types separately
+ ty.iterate_assoc_items(ctx.db, ctx.krate, |item| {
+ if let hir::AssocItem::TypeAlias(ty) = item {
+ acc.add_type_alias(ctx, ty)
+ }
+ None::<()>
+ });
+ }
+ hir::PathResolution::Def(hir::ModuleDef::Trait(t)) => {
+ // Handles `Trait::assoc` as well as `<Ty as Trait>::assoc`.
+ for item in t.items(ctx.db) {
+ add_assoc_item(acc, item);
+ }
+ }
+ hir::PathResolution::TypeParam(_) | hir::PathResolution::SelfType(_) => {
+ let ty = match resolution {
+ hir::PathResolution::TypeParam(param) => param.ty(ctx.db),
+ hir::PathResolution::SelfType(impl_def) => impl_def.self_ty(ctx.db),
+ _ => return,
+ };
+
+ if let Some(hir::Adt::Enum(e)) = ty.as_adt() {
+ cov_mark::hit!(completes_variant_through_self);
+ acc.add_enum_variants(ctx, path_ctx, e);
+ }
+
+ ctx.iterate_path_candidates(&ty, |item| {
+ add_assoc_item(acc, item);
+ });
+ }
+ _ => (),
+ }
+ }
+ Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
+ Qualified::No => {
+ acc.add_nameref_keywords_with_colon(ctx);
+ if let Some(adt) =
+ ctx.expected_type.as_ref().and_then(|ty| ty.strip_references().as_adt())
+ {
+ let self_ty = (|| ctx.sema.to_def(impl_.as_ref()?)?.self_ty(ctx.db).as_adt())();
+ let complete_self = self_ty == Some(adt);
+
+ match adt {
+ hir::Adt::Struct(strukt) => {
+ let path = ctx
+ .module
+ .find_use_path(ctx.db, hir::ModuleDef::from(strukt))
+ .filter(|it| it.len() > 1);
+
+ acc.add_struct_literal(ctx, path_ctx, strukt, path, None);
+
+ if complete_self {
+ acc.add_struct_literal(
+ ctx,
+ path_ctx,
+ strukt,
+ None,
+ Some(hir::known::SELF_TYPE),
+ );
+ }
+ }
+ hir::Adt::Union(un) => {
+ let path = ctx
+ .module
+ .find_use_path(ctx.db, hir::ModuleDef::from(un))
+ .filter(|it| it.len() > 1);
+
+ acc.add_union_literal(ctx, un, path, None);
+ if complete_self {
+ acc.add_union_literal(ctx, un, None, Some(hir::known::SELF_TYPE));
+ }
+ }
+ hir::Adt::Enum(e) => {
+ super::enum_variants_with_paths(
+ acc,
+ ctx,
+ e,
+ impl_,
+ |acc, ctx, variant, path| {
+ acc.add_qualified_enum_variant(ctx, path_ctx, variant, path)
+ },
+ );
+ }
+ }
+ }
+ ctx.process_all_names(&mut |name, def| match def {
+ ScopeDef::ModuleDef(hir::ModuleDef::Trait(t)) => {
+ let assocs = t.items_with_supertraits(ctx.db);
+ match &*assocs {
+ // traits with no assoc items are unusable as expressions since
+ // there is no associated item path that can be constructed with them
+ [] => (),
+ // FIXME: Render the assoc item with the trait qualified
+ &[_item] => acc.add_path_resolution(ctx, path_ctx, name, def),
+ // FIXME: Append `::` to the thing here, since a trait on its own won't work
+ [..] => acc.add_path_resolution(ctx, path_ctx, name, def),
+ }
+ }
+ _ if scope_def_applicable(def) => acc.add_path_resolution(ctx, path_ctx, name, def),
+ _ => (),
+ });
+
+ if is_func_update.is_none() {
+ let mut add_keyword =
+ |kw, snippet| acc.add_keyword_snippet_expr(ctx, incomplete_let, kw, snippet);
+
+ if !in_block_expr {
+ add_keyword("unsafe", "unsafe {\n $0\n}");
+ }
+ add_keyword("match", "match $1 {\n $0\n}");
+ add_keyword("while", "while $1 {\n $0\n}");
+ add_keyword("while let", "while let $1 = $2 {\n $0\n}");
+ add_keyword("loop", "loop {\n $0\n}");
+ if in_match_guard {
+ add_keyword("if", "if $0");
+ } else {
+ add_keyword("if", "if $1 {\n $0\n}");
+ }
+ add_keyword("if let", "if let $1 = $2 {\n $0\n}");
+ add_keyword("for", "for $1 in $2 {\n $0\n}");
+ add_keyword("true", "true");
+ add_keyword("false", "false");
+
+ if in_condition || in_block_expr {
+ add_keyword("let", "let");
+ }
+
+ if after_if_expr {
+ add_keyword("else", "else {\n $0\n}");
+ add_keyword("else if", "else if $1 {\n $0\n}");
+ }
+
+ if wants_mut_token {
+ add_keyword("mut", "mut ");
+ }
+
+ if in_loop_body {
+ if in_block_expr {
+ add_keyword("continue", "continue;");
+ add_keyword("break", "break;");
+ } else {
+ add_keyword("continue", "continue");
+ add_keyword("break", "break");
+ }
+ }
+
+ if let Some(ty) = innermost_ret_ty {
+ add_keyword(
+ "return",
+ match (in_block_expr, ty.is_unit()) {
+ (true, true) => "return ;",
+ (true, false) => "return;",
+ (false, true) => "return $0",
+ (false, false) => "return",
+ },
+ );
+ }
+ }
+ }
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/extern_abi.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/extern_abi.rs
new file mode 100644
index 000000000..4e89ef696
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/extern_abi.rs
@@ -0,0 +1,108 @@
+//! Completes function abi strings.
+use syntax::{
+ ast::{self, IsString},
+ AstNode, AstToken,
+};
+
+use crate::{
+ completions::Completions, context::CompletionContext, CompletionItem, CompletionItemKind,
+};
+
+// Most of these are feature gated, we should filter/add feature gate completions once we have them.
+const SUPPORTED_CALLING_CONVENTIONS: &[&str] = &[
+ "Rust",
+ "C",
+ "C-unwind",
+ "cdecl",
+ "stdcall",
+ "stdcall-unwind",
+ "fastcall",
+ "vectorcall",
+ "thiscall",
+ "thiscall-unwind",
+ "aapcs",
+ "win64",
+ "sysv64",
+ "ptx-kernel",
+ "msp430-interrupt",
+ "x86-interrupt",
+ "amdgpu-kernel",
+ "efiapi",
+ "avr-interrupt",
+ "avr-non-blocking-interrupt",
+ "C-cmse-nonsecure-call",
+ "wasm",
+ "system",
+ "system-unwind",
+ "rust-intrinsic",
+ "rust-call",
+ "platform-intrinsic",
+ "unadjusted",
+];
+
+pub(crate) fn complete_extern_abi(
+ acc: &mut Completions,
+ _ctx: &CompletionContext<'_>,
+ expanded: &ast::String,
+) -> Option<()> {
+ if !expanded.syntax().parent().map_or(false, |it| ast::Abi::can_cast(it.kind())) {
+ return None;
+ }
+ let abi_str = expanded;
+ let source_range = abi_str.text_range_between_quotes()?;
+ for &abi in SUPPORTED_CALLING_CONVENTIONS {
+ CompletionItem::new(CompletionItemKind::Keyword, source_range, abi).add_to(acc);
+ }
+ Some(())
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+
+ use crate::tests::{check_edit, completion_list_no_kw};
+
+ fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list_no_kw(ra_fixture);
+ expect.assert_eq(&actual);
+ }
+
+ #[test]
+ fn only_completes_in_string_literals() {
+ check(
+ r#"
+$0 fn foo {}
+"#,
+ expect![[]],
+ );
+ }
+
+ #[test]
+ fn requires_extern_prefix() {
+ check(
+ r#"
+"$0" fn foo {}
+"#,
+ expect![[]],
+ );
+ }
+
+ #[test]
+ fn works() {
+ check(
+ r#"
+extern "$0" fn foo {}
+"#,
+ expect![[]],
+ );
+ check_edit(
+ "Rust",
+ r#"
+extern "$0" fn foo {}
+"#,
+ r#"
+extern "Rust" fn foo {}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/field.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/field.rs
new file mode 100644
index 000000000..870df63b7
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/field.rs
@@ -0,0 +1,43 @@
+//! Completion of field list position.
+
+use crate::{
+ context::{PathCompletionCtx, Qualified},
+ CompletionContext, Completions,
+};
+
+pub(crate) fn complete_field_list_tuple_variant(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+) {
+ if ctx.qualifier_ctx.vis_node.is_some() {
+ return;
+ }
+ match path_ctx {
+ PathCompletionCtx {
+ has_macro_bang: false,
+ qualified: Qualified::No,
+ parent: None,
+ has_type_args: false,
+ ..
+ } => {
+ let mut add_keyword = |kw, snippet| acc.add_keyword_snippet(ctx, kw, snippet);
+ add_keyword("pub(crate)", "pub(crate)");
+ add_keyword("pub(super)", "pub(super)");
+ add_keyword("pub", "pub");
+ }
+ _ => (),
+ }
+}
+
+pub(crate) fn complete_field_list_record_variant(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+) {
+ if ctx.qualifier_ctx.vis_node.is_none() {
+ let mut add_keyword = |kw, snippet| acc.add_keyword_snippet(ctx, kw, snippet);
+ add_keyword("pub(crate)", "pub(crate)");
+ add_keyword("pub(super)", "pub(super)");
+ add_keyword("pub", "pub");
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/flyimport.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/flyimport.rs
new file mode 100644
index 000000000..f04cc15d7
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/flyimport.rs
@@ -0,0 +1,407 @@
+//! See [`import_on_the_fly`].
+use hir::{ItemInNs, ModuleDef};
+use ide_db::imports::{
+ import_assets::{ImportAssets, LocatedImport},
+ insert_use::ImportScope,
+};
+use itertools::Itertools;
+use syntax::{
+ ast::{self},
+ AstNode, SyntaxNode, T,
+};
+
+use crate::{
+ context::{
+ CompletionContext, DotAccess, PathCompletionCtx, PathKind, PatternContext, Qualified,
+ TypeLocation,
+ },
+ render::{render_resolution_with_import, render_resolution_with_import_pat, RenderContext},
+};
+
+use super::Completions;
+
+// Feature: Completion With Autoimport
+//
+// When completing names in the current scope, proposes additional imports from other modules or crates,
+// if they can be qualified in the scope, and their name contains all symbols from the completion input.
+//
+// To be considered applicable, the name must contain all input symbols in the given order, not necessarily adjacent.
+// If any input symbol is not lowercased, the name must contain all symbols in exact case; otherwise the containing is checked case-insensitively.
+//
+// ```
+// fn main() {
+// pda$0
+// }
+// # pub mod std { pub mod marker { pub struct PhantomData { } } }
+// ```
+// ->
+// ```
+// use std::marker::PhantomData;
+//
+// fn main() {
+// PhantomData
+// }
+// # pub mod std { pub mod marker { pub struct PhantomData { } } }
+// ```
+//
+// Also completes associated items, that require trait imports.
+// If any unresolved and/or partially-qualified path precedes the input, it will be taken into account.
+// Currently, only the imports with their import path ending with the whole qualifier will be proposed
+// (no fuzzy matching for qualifier).
+//
+// ```
+// mod foo {
+// pub mod bar {
+// pub struct Item;
+//
+// impl Item {
+// pub const TEST_ASSOC: usize = 3;
+// }
+// }
+// }
+//
+// fn main() {
+// bar::Item::TEST_A$0
+// }
+// ```
+// ->
+// ```
+// use foo::bar;
+//
+// mod foo {
+// pub mod bar {
+// pub struct Item;
+//
+// impl Item {
+// pub const TEST_ASSOC: usize = 3;
+// }
+// }
+// }
+//
+// fn main() {
+// bar::Item::TEST_ASSOC
+// }
+// ```
+//
+// NOTE: currently, if an assoc item comes from a trait that's not currently imported, and it also has an unresolved and/or partially-qualified path,
+// no imports will be proposed.
+//
+// .Fuzzy search details
+//
+// To avoid an excessive amount of the results returned, completion input is checked for inclusion in the names only
+// (i.e. in `HashMap` in the `std::collections::HashMap` path).
+// For the same reasons, avoids searching for any path imports for inputs with their length less than 2 symbols
+// (but shows all associated items for any input length).
+//
+// .Import configuration
+//
+// It is possible to configure how use-trees are merged with the `imports.granularity.group` setting.
+// Mimics the corresponding behavior of the `Auto Import` feature.
+//
+// .LSP and performance implications
+//
+// The feature is enabled only if the LSP client supports LSP protocol version 3.16+ and reports the `additionalTextEdits`
+// (case-sensitive) resolve client capability in its client capabilities.
+// This way the server is able to defer the costly computations, doing them for a selected completion item only.
+// For clients with no such support, all edits have to be calculated on the completion request, including the fuzzy search completion ones,
+// which might be slow ergo the feature is automatically disabled.
+//
+// .Feature toggle
+//
+// The feature can be forcefully turned off in the settings with the `rust-analyzer.completion.autoimport.enable` flag.
+// Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corresponding
+// capability enabled.
+pub(crate) fn import_on_the_fly_path(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+) -> Option<()> {
+ if !ctx.config.enable_imports_on_the_fly {
+ return None;
+ }
+ let qualified = match path_ctx {
+ PathCompletionCtx {
+ kind:
+ PathKind::Expr { .. }
+ | PathKind::Type { .. }
+ | PathKind::Attr { .. }
+ | PathKind::Derive { .. }
+ | PathKind::Item { .. }
+ | PathKind::Pat { .. },
+ qualified,
+ ..
+ } => qualified,
+ _ => return None,
+ };
+ let potential_import_name = import_name(ctx);
+ let qualifier = match qualified {
+ Qualified::With { path, .. } => Some(path.clone()),
+ _ => None,
+ };
+ let import_assets = import_assets_for_path(ctx, &potential_import_name, qualifier.clone())?;
+
+ import_on_the_fly(
+ acc,
+ ctx,
+ path_ctx,
+ import_assets,
+ qualifier.map(|it| it.syntax().clone()).or_else(|| ctx.original_token.parent())?,
+ potential_import_name,
+ )
+}
+
+pub(crate) fn import_on_the_fly_pat(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ pattern_ctx: &PatternContext,
+) -> Option<()> {
+ if !ctx.config.enable_imports_on_the_fly {
+ return None;
+ }
+ if let PatternContext { record_pat: Some(_), .. } = pattern_ctx {
+ return None;
+ }
+
+ let potential_import_name = import_name(ctx);
+ let import_assets = import_assets_for_path(ctx, &potential_import_name, None)?;
+
+ import_on_the_fly_pat_(
+ acc,
+ ctx,
+ pattern_ctx,
+ import_assets,
+ ctx.original_token.parent()?,
+ potential_import_name,
+ )
+}
+
+pub(crate) fn import_on_the_fly_dot(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ dot_access: &DotAccess,
+) -> Option<()> {
+ if !ctx.config.enable_imports_on_the_fly {
+ return None;
+ }
+ let receiver = dot_access.receiver.as_ref()?;
+ let ty = dot_access.receiver_ty.as_ref()?;
+ let potential_import_name = import_name(ctx);
+ let import_assets = ImportAssets::for_fuzzy_method_call(
+ ctx.module,
+ ty.original.clone(),
+ potential_import_name.clone(),
+ receiver.syntax().clone(),
+ )?;
+
+ import_on_the_fly_method(
+ acc,
+ ctx,
+ dot_access,
+ import_assets,
+ receiver.syntax().clone(),
+ potential_import_name,
+ )
+}
+
+fn import_on_the_fly(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx @ PathCompletionCtx { kind, .. }: &PathCompletionCtx,
+ import_assets: ImportAssets,
+ position: SyntaxNode,
+ potential_import_name: String,
+) -> Option<()> {
+ let _p = profile::span("import_on_the_fly").detail(|| potential_import_name.clone());
+
+ if ImportScope::find_insert_use_container(&position, &ctx.sema).is_none() {
+ return None;
+ }
+
+ let ns_filter = |import: &LocatedImport| {
+ match (kind, import.original_item) {
+ // Aren't handled in flyimport
+ (PathKind::Vis { .. } | PathKind::Use, _) => false,
+ // modules are always fair game
+ (_, ItemInNs::Types(hir::ModuleDef::Module(_))) => true,
+ // and so are macros(except for attributes)
+ (
+ PathKind::Expr { .. }
+ | PathKind::Type { .. }
+ | PathKind::Item { .. }
+ | PathKind::Pat { .. },
+ ItemInNs::Macros(mac),
+ ) => mac.is_fn_like(ctx.db),
+ (PathKind::Item { .. }, ..) => false,
+
+ (PathKind::Expr { .. }, ItemInNs::Types(_) | ItemInNs::Values(_)) => true,
+
+ (PathKind::Pat { .. }, ItemInNs::Types(_)) => true,
+ (PathKind::Pat { .. }, ItemInNs::Values(def)) => {
+ matches!(def, hir::ModuleDef::Const(_))
+ }
+
+ (PathKind::Type { location }, ItemInNs::Types(ty)) => {
+ if matches!(location, TypeLocation::TypeBound) {
+ matches!(ty, ModuleDef::Trait(_))
+ } else {
+ true
+ }
+ }
+ (PathKind::Type { .. }, ItemInNs::Values(_)) => false,
+
+ (PathKind::Attr { .. }, ItemInNs::Macros(mac)) => mac.is_attr(ctx.db),
+ (PathKind::Attr { .. }, _) => false,
+
+ (PathKind::Derive { existing_derives }, ItemInNs::Macros(mac)) => {
+ mac.is_derive(ctx.db) && !existing_derives.contains(&mac)
+ }
+ (PathKind::Derive { .. }, _) => false,
+ }
+ };
+ let user_input_lowercased = potential_import_name.to_lowercase();
+
+ acc.add_all(
+ import_assets
+ .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind)
+ .into_iter()
+ .filter(ns_filter)
+ .filter(|import| {
+ !ctx.is_item_hidden(&import.item_to_import)
+ && !ctx.is_item_hidden(&import.original_item)
+ })
+ .sorted_by_key(|located_import| {
+ compute_fuzzy_completion_order_key(
+ &located_import.import_path,
+ &user_input_lowercased,
+ )
+ })
+ .filter_map(|import| {
+ render_resolution_with_import(RenderContext::new(ctx), path_ctx, import)
+ })
+ .map(|builder| builder.build()),
+ );
+ Some(())
+}
+
+fn import_on_the_fly_pat_(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ pattern_ctx: &PatternContext,
+ import_assets: ImportAssets,
+ position: SyntaxNode,
+ potential_import_name: String,
+) -> Option<()> {
+ let _p = profile::span("import_on_the_fly_pat").detail(|| potential_import_name.clone());
+
+ if ImportScope::find_insert_use_container(&position, &ctx.sema).is_none() {
+ return None;
+ }
+
+ let ns_filter = |import: &LocatedImport| match import.original_item {
+ ItemInNs::Macros(mac) => mac.is_fn_like(ctx.db),
+ ItemInNs::Types(_) => true,
+ ItemInNs::Values(def) => matches!(def, hir::ModuleDef::Const(_)),
+ };
+ let user_input_lowercased = potential_import_name.to_lowercase();
+
+ acc.add_all(
+ import_assets
+ .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind)
+ .into_iter()
+ .filter(ns_filter)
+ .filter(|import| {
+ !ctx.is_item_hidden(&import.item_to_import)
+ && !ctx.is_item_hidden(&import.original_item)
+ })
+ .sorted_by_key(|located_import| {
+ compute_fuzzy_completion_order_key(
+ &located_import.import_path,
+ &user_input_lowercased,
+ )
+ })
+ .filter_map(|import| {
+ render_resolution_with_import_pat(RenderContext::new(ctx), pattern_ctx, import)
+ })
+ .map(|builder| builder.build()),
+ );
+ Some(())
+}
+
+fn import_on_the_fly_method(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ dot_access: &DotAccess,
+ import_assets: ImportAssets,
+ position: SyntaxNode,
+ potential_import_name: String,
+) -> Option<()> {
+ let _p = profile::span("import_on_the_fly_method").detail(|| potential_import_name.clone());
+
+ if ImportScope::find_insert_use_container(&position, &ctx.sema).is_none() {
+ return None;
+ }
+
+ let user_input_lowercased = potential_import_name.to_lowercase();
+
+ import_assets
+ .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind)
+ .into_iter()
+ .filter(|import| {
+ !ctx.is_item_hidden(&import.item_to_import)
+ && !ctx.is_item_hidden(&import.original_item)
+ })
+ .sorted_by_key(|located_import| {
+ compute_fuzzy_completion_order_key(&located_import.import_path, &user_input_lowercased)
+ })
+ .for_each(|import| match import.original_item {
+ ItemInNs::Values(hir::ModuleDef::Function(f)) => {
+ acc.add_method_with_import(ctx, dot_access, f, import);
+ }
+ _ => (),
+ });
+ Some(())
+}
+
+fn import_name(ctx: &CompletionContext<'_>) -> String {
+ let token_kind = ctx.token.kind();
+ if matches!(token_kind, T![.] | T![::]) {
+ String::new()
+ } else {
+ ctx.token.to_string()
+ }
+}
+
+fn import_assets_for_path(
+ ctx: &CompletionContext<'_>,
+ potential_import_name: &str,
+ qualifier: Option<ast::Path>,
+) -> Option<ImportAssets> {
+ let fuzzy_name_length = potential_import_name.len();
+ let mut assets_for_path = ImportAssets::for_fuzzy_path(
+ ctx.module,
+ qualifier,
+ potential_import_name.to_owned(),
+ &ctx.sema,
+ ctx.token.parent()?,
+ )?;
+ if fuzzy_name_length < 3 {
+ cov_mark::hit!(flyimport_exact_on_short_path);
+ assets_for_path.path_fuzzy_name_to_exact(false);
+ }
+ Some(assets_for_path)
+}
+
+fn compute_fuzzy_completion_order_key(
+ proposed_mod_path: &hir::ModPath,
+ user_input_lowercased: &str,
+) -> usize {
+ cov_mark::hit!(certain_fuzzy_order_test);
+ let import_name = match proposed_mod_path.segments().last() {
+ Some(name) => name.to_smol_str().to_lowercase(),
+ None => return usize::MAX,
+ };
+ match import_name.match_indices(user_input_lowercased).next() {
+ Some((first_matching_index, _)) => first_matching_index,
+ None => usize::MAX,
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/fn_param.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/fn_param.rs
new file mode 100644
index 000000000..f0ecc595a
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/fn_param.rs
@@ -0,0 +1,196 @@
+//! See [`complete_fn_param`].
+
+use hir::HirDisplay;
+use ide_db::FxHashMap;
+use syntax::{
+ algo,
+ ast::{self, HasModuleItem},
+ match_ast, AstNode, Direction, SyntaxKind, TextRange, TextSize,
+};
+
+use crate::{
+ context::{ParamContext, ParamKind, PatternContext},
+ CompletionContext, CompletionItem, CompletionItemKind, Completions,
+};
+
+// FIXME: Make this a submodule of [`pattern`]
+/// Complete repeated parameters, both name and type. For example, if all
+/// functions in a file have a `spam: &mut Spam` parameter, a completion with
+/// `spam: &mut Spam` insert text/label will be suggested.
+///
+/// Also complete parameters for closure or local functions from the surrounding defined locals.
+pub(crate) fn complete_fn_param(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ pattern_ctx: &PatternContext,
+) -> Option<()> {
+ let (ParamContext { param_list, kind, .. }, impl_) = match pattern_ctx {
+ PatternContext { param_ctx: Some(kind), impl_, .. } => (kind, impl_),
+ _ => return None,
+ };
+
+ let comma_wrapper = comma_wrapper(ctx);
+ let mut add_new_item_to_acc = |label: &str| {
+ let mk_item = |label: &str, range: TextRange| {
+ CompletionItem::new(CompletionItemKind::Binding, range, label)
+ };
+ let item = match &comma_wrapper {
+ Some((fmt, range)) => mk_item(&fmt(label), *range),
+ None => mk_item(label, ctx.source_range()),
+ };
+ // Completion lookup is omitted intentionally here.
+ // See the full discussion: https://github.com/rust-lang/rust-analyzer/issues/12073
+ item.add_to(acc)
+ };
+
+ match kind {
+ ParamKind::Function(function) => {
+ fill_fn_params(ctx, function, param_list, impl_, add_new_item_to_acc);
+ }
+ ParamKind::Closure(closure) => {
+ let stmt_list = closure.syntax().ancestors().find_map(ast::StmtList::cast)?;
+ params_from_stmt_list_scope(ctx, stmt_list, |name, ty| {
+ add_new_item_to_acc(&format!("{name}: {ty}"));
+ });
+ }
+ }
+
+ Some(())
+}
+
+fn fill_fn_params(
+ ctx: &CompletionContext<'_>,
+ function: &ast::Fn,
+ param_list: &ast::ParamList,
+ impl_: &Option<ast::Impl>,
+ mut add_new_item_to_acc: impl FnMut(&str),
+) {
+ let mut file_params = FxHashMap::default();
+
+ let mut extract_params = |f: ast::Fn| {
+ f.param_list().into_iter().flat_map(|it| it.params()).for_each(|param| {
+ if let Some(pat) = param.pat() {
+ // FIXME: We should be able to turn these into SmolStr without having to allocate a String
+ let whole_param = param.syntax().text().to_string();
+ let binding = pat.syntax().text().to_string();
+ file_params.entry(whole_param).or_insert(binding);
+ }
+ });
+ };
+
+ for node in ctx.token.parent_ancestors() {
+ match_ast! {
+ match node {
+ ast::SourceFile(it) => it.items().filter_map(|item| match item {
+ ast::Item::Fn(it) => Some(it),
+ _ => None,
+ }).for_each(&mut extract_params),
+ ast::ItemList(it) => it.items().filter_map(|item| match item {
+ ast::Item::Fn(it) => Some(it),
+ _ => None,
+ }).for_each(&mut extract_params),
+ ast::AssocItemList(it) => it.assoc_items().filter_map(|item| match item {
+ ast::AssocItem::Fn(it) => Some(it),
+ _ => None,
+ }).for_each(&mut extract_params),
+ _ => continue,
+ }
+ };
+ }
+
+ if let Some(stmt_list) = function.syntax().parent().and_then(ast::StmtList::cast) {
+ params_from_stmt_list_scope(ctx, stmt_list, |name, ty| {
+ file_params.entry(format!("{name}: {ty}")).or_insert(name.to_string());
+ });
+ }
+ remove_duplicated(&mut file_params, param_list.params());
+ let self_completion_items = ["self", "&self", "mut self", "&mut self"];
+ if should_add_self_completions(ctx.token.text_range().start(), param_list, impl_) {
+ self_completion_items.into_iter().for_each(|self_item| add_new_item_to_acc(self_item));
+ }
+
+ file_params.keys().for_each(|whole_param| add_new_item_to_acc(whole_param));
+}
+
+fn params_from_stmt_list_scope(
+ ctx: &CompletionContext<'_>,
+ stmt_list: ast::StmtList,
+ mut cb: impl FnMut(hir::Name, String),
+) {
+ let syntax_node = match stmt_list.syntax().last_child() {
+ Some(it) => it,
+ None => return,
+ };
+ if let Some(scope) =
+ ctx.sema.scope_at_offset(stmt_list.syntax(), syntax_node.text_range().end())
+ {
+ let module = scope.module().into();
+ scope.process_all_names(&mut |name, def| {
+ if let hir::ScopeDef::Local(local) = def {
+ if let Ok(ty) = local.ty(ctx.db).display_source_code(ctx.db, module) {
+ cb(name, ty);
+ }
+ }
+ });
+ }
+}
+
+fn remove_duplicated(
+ file_params: &mut FxHashMap<String, String>,
+ fn_params: ast::AstChildren<ast::Param>,
+) {
+ fn_params.for_each(|param| {
+ let whole_param = param.syntax().text().to_string();
+ file_params.remove(&whole_param);
+
+ match param.pat() {
+ // remove suggestions for patterns that already exist
+ // if the type is missing we are checking the current param to be completed
+ // in which case this would find itself removing the suggestions due to itself
+ Some(pattern) if param.ty().is_some() => {
+ let binding = pattern.syntax().text().to_string();
+ file_params.retain(|_, v| v != &binding);
+ }
+ _ => (),
+ }
+ })
+}
+
+fn should_add_self_completions(
+ cursor: TextSize,
+ param_list: &ast::ParamList,
+ impl_: &Option<ast::Impl>,
+) -> bool {
+ if impl_.is_none() || param_list.self_param().is_some() {
+ return false;
+ }
+ match param_list.params().next() {
+ Some(first) => first.pat().map_or(false, |pat| pat.syntax().text_range().contains(cursor)),
+ None => true,
+ }
+}
+
+fn comma_wrapper(ctx: &CompletionContext<'_>) -> Option<(impl Fn(&str) -> String, TextRange)> {
+ let param = ctx.token.parent_ancestors().find(|node| node.kind() == SyntaxKind::PARAM)?;
+
+ let next_token_kind = {
+ let t = param.last_token()?.next_token()?;
+ let t = algo::skip_whitespace_token(t, Direction::Next)?;
+ t.kind()
+ };
+ let prev_token_kind = {
+ let t = param.first_token()?.prev_token()?;
+ let t = algo::skip_whitespace_token(t, Direction::Prev)?;
+ t.kind()
+ };
+
+ let has_trailing_comma =
+ matches!(next_token_kind, SyntaxKind::COMMA | SyntaxKind::R_PAREN | SyntaxKind::PIPE);
+ let trailing = if has_trailing_comma { "" } else { "," };
+
+ let has_leading_comma =
+ matches!(prev_token_kind, SyntaxKind::COMMA | SyntaxKind::L_PAREN | SyntaxKind::PIPE);
+ let leading = if has_leading_comma { "" } else { ", " };
+
+ Some((move |label: &_| (format!("{}{}{}", leading, label, trailing)), param.text_range()))
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/format_string.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/format_string.rs
new file mode 100644
index 000000000..038bdb427
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/format_string.rs
@@ -0,0 +1,130 @@
+//! Completes identifiers in format string literals.
+
+use ide_db::syntax_helpers::format_string::is_format_string;
+use itertools::Itertools;
+use syntax::{ast, AstToken, TextRange, TextSize};
+
+use crate::{context::CompletionContext, CompletionItem, CompletionItemKind, Completions};
+
+/// Complete identifiers in format strings.
+pub(crate) fn format_string(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ original: &ast::String,
+ expanded: &ast::String,
+) {
+ if !is_format_string(&expanded) {
+ return;
+ }
+ let cursor = ctx.position.offset;
+ let lit_start = ctx.original_token.text_range().start();
+ let cursor_in_lit = cursor - lit_start;
+
+ let prefix = &original.text()[..cursor_in_lit.into()];
+ let braces = prefix.char_indices().rev().skip_while(|&(_, c)| c.is_alphanumeric()).next_tuple();
+ let brace_offset = match braces {
+ // escaped brace
+ Some(((_, '{'), (_, '{'))) => return,
+ Some(((idx, '{'), _)) => lit_start + TextSize::from(idx as u32 + 1),
+ _ => return,
+ };
+
+ let source_range = TextRange::new(brace_offset, cursor);
+ ctx.locals.iter().for_each(|(name, _)| {
+ CompletionItem::new(CompletionItemKind::Binding, source_range, name.to_smol_str())
+ .add_to(acc);
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+
+ use crate::tests::{check_edit, completion_list_no_kw};
+
+ fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list_no_kw(ra_fixture);
+ expect.assert_eq(&actual);
+ }
+
+ #[test]
+ fn works_when_wrapped() {
+ check(
+ r#"
+macro_rules! format_args {
+ ($lit:literal $(tt:tt)*) => { 0 },
+}
+macro_rules! print {
+ ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
+}
+fn main() {
+ let foobar = 1;
+ print!("f$0");
+}
+"#,
+ expect![[]],
+ );
+ }
+
+ #[test]
+ fn no_completion_without_brace() {
+ check(
+ r#"
+macro_rules! format_args {
+ ($lit:literal $(tt:tt)*) => { 0 },
+}
+fn main() {
+ let foobar = 1;
+ format_args!("f$0");
+}
+"#,
+ expect![[]],
+ );
+ }
+
+ #[test]
+ fn completes_locals() {
+ check_edit(
+ "foobar",
+ r#"
+macro_rules! format_args {
+ ($lit:literal $(tt:tt)*) => { 0 },
+}
+fn main() {
+ let foobar = 1;
+ format_args!("{f$0");
+}
+"#,
+ r#"
+macro_rules! format_args {
+ ($lit:literal $(tt:tt)*) => { 0 },
+}
+fn main() {
+ let foobar = 1;
+ format_args!("{foobar");
+}
+"#,
+ );
+ check_edit(
+ "foobar",
+ r#"
+macro_rules! format_args {
+ ($lit:literal $(tt:tt)*) => { 0 },
+}
+fn main() {
+ let foobar = 1;
+ format_args!("{$0");
+}
+"#,
+ r#"
+macro_rules! format_args {
+ ($lit:literal $(tt:tt)*) => { 0 },
+}
+fn main() {
+ let foobar = 1;
+ format_args!("{foobar");
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list.rs
new file mode 100644
index 000000000..60d05ae46
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list.rs
@@ -0,0 +1,133 @@
+//! Completion of paths and keywords at item list position.
+
+use crate::{
+ context::{ExprCtx, ItemListKind, PathCompletionCtx, Qualified},
+ CompletionContext, Completions,
+};
+
+pub(crate) mod trait_impl;
+
+pub(crate) fn complete_item_list_in_expr(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ expr_ctx: &ExprCtx,
+) {
+ if !expr_ctx.in_block_expr {
+ return;
+ }
+ if !path_ctx.is_trivial_path() {
+ return;
+ }
+ add_keywords(acc, ctx, None);
+}
+
+pub(crate) fn complete_item_list(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx @ PathCompletionCtx { qualified, .. }: &PathCompletionCtx,
+ kind: &ItemListKind,
+) {
+ let _p = profile::span("complete_item_list");
+ if path_ctx.is_trivial_path() {
+ add_keywords(acc, ctx, Some(kind));
+ }
+
+ match qualified {
+ Qualified::With {
+ resolution: Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))),
+ super_chain_len,
+ ..
+ } => {
+ for (name, def) in module.scope(ctx.db, Some(ctx.module)) {
+ match def {
+ hir::ScopeDef::ModuleDef(hir::ModuleDef::Macro(m)) if m.is_fn_like(ctx.db) => {
+ acc.add_macro(ctx, path_ctx, m, name)
+ }
+ hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
+ acc.add_module(ctx, path_ctx, m, name)
+ }
+ _ => (),
+ }
+ }
+
+ acc.add_super_keyword(ctx, *super_chain_len);
+ }
+ Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
+ Qualified::No if ctx.qualifier_ctx.none() => {
+ ctx.process_all_names(&mut |name, def| match def {
+ hir::ScopeDef::ModuleDef(hir::ModuleDef::Macro(m)) if m.is_fn_like(ctx.db) => {
+ acc.add_macro(ctx, path_ctx, m, name)
+ }
+ hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
+ acc.add_module(ctx, path_ctx, m, name)
+ }
+ _ => (),
+ });
+ acc.add_nameref_keywords_with_colon(ctx);
+ }
+ Qualified::TypeAnchor { .. } | Qualified::No | Qualified::With { .. } => {}
+ }
+}
+
+fn add_keywords(acc: &mut Completions, ctx: &CompletionContext<'_>, kind: Option<&ItemListKind>) {
+ let mut add_keyword = |kw, snippet| acc.add_keyword_snippet(ctx, kw, snippet);
+
+ let in_item_list = matches!(kind, Some(ItemListKind::SourceFile | ItemListKind::Module) | None);
+ let in_assoc_non_trait_impl = matches!(kind, Some(ItemListKind::Impl | ItemListKind::Trait));
+ let in_extern_block = matches!(kind, Some(ItemListKind::ExternBlock));
+ let in_trait = matches!(kind, Some(ItemListKind::Trait));
+ let in_trait_impl = matches!(kind, Some(ItemListKind::TraitImpl(_)));
+ let in_inherent_impl = matches!(kind, Some(ItemListKind::Impl));
+ let no_qualifiers = ctx.qualifier_ctx.vis_node.is_none();
+ let in_block = matches!(kind, None);
+
+ if !in_trait_impl {
+ if ctx.qualifier_ctx.unsafe_tok.is_some() {
+ if in_item_list || in_assoc_non_trait_impl {
+ add_keyword("fn", "fn $1($2) {\n $0\n}");
+ }
+ if in_item_list {
+ add_keyword("trait", "trait $1 {\n $0\n}");
+ if no_qualifiers {
+ add_keyword("impl", "impl $1 {\n $0\n}");
+ }
+ }
+ return;
+ }
+
+ if in_item_list {
+ add_keyword("enum", "enum $1 {\n $0\n}");
+ add_keyword("mod", "mod $0");
+ add_keyword("static", "static $0");
+ add_keyword("struct", "struct $0");
+ add_keyword("trait", "trait $1 {\n $0\n}");
+ add_keyword("union", "union $1 {\n $0\n}");
+ add_keyword("use", "use $0");
+ if no_qualifiers {
+ add_keyword("impl", "impl $1 {\n $0\n}");
+ }
+ }
+
+ if !in_trait && !in_block && no_qualifiers {
+ add_keyword("pub(crate)", "pub(crate)");
+ add_keyword("pub(super)", "pub(super)");
+ add_keyword("pub", "pub");
+ }
+
+ if in_extern_block {
+ add_keyword("fn", "fn $1($2);");
+ } else {
+ if !in_inherent_impl {
+ if !in_trait {
+ add_keyword("extern", "extern $0");
+ }
+ add_keyword("type", "type $0");
+ }
+
+ add_keyword("fn", "fn $1($2) {\n $0\n}");
+ add_keyword("unsafe", "unsafe");
+ add_keyword("const", "const $0");
+ }
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs
new file mode 100644
index 000000000..e9256803c
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs
@@ -0,0 +1,1160 @@
+//! Completion for associated items in a trait implementation.
+//!
+//! This module adds the completion items related to implementing associated
+//! items within an `impl Trait for Struct` block. The current context node
+//! must be within either a `FN`, `TYPE_ALIAS`, or `CONST` node
+//! and an direct child of an `IMPL`.
+//!
+//! # Examples
+//!
+//! Considering the following trait `impl`:
+//!
+//! ```ignore
+//! trait SomeTrait {
+//! fn foo();
+//! }
+//!
+//! impl SomeTrait for () {
+//! fn f$0
+//! }
+//! ```
+//!
+//! may result in the completion of the following method:
+//!
+//! ```ignore
+//! # trait SomeTrait {
+//! # fn foo();
+//! # }
+//!
+//! impl SomeTrait for () {
+//! fn foo() {}$0
+//! }
+//! ```
+
+use hir::{self, HasAttrs};
+use ide_db::{
+ path_transform::PathTransform, syntax_helpers::insert_whitespace_into_node,
+ traits::get_missing_assoc_items, SymbolKind,
+};
+use syntax::{
+ ast::{self, edit_in_place::AttrsOwnerEdit},
+ AstNode, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, T,
+};
+use text_edit::TextEdit;
+
+use crate::{
+ context::PathCompletionCtx, CompletionContext, CompletionItem, CompletionItemKind,
+ CompletionRelevance, Completions,
+};
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+enum ImplCompletionKind {
+ All,
+ Fn,
+ TypeAlias,
+ Const,
+}
+
+pub(crate) fn complete_trait_impl_const(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ name: &Option<ast::Name>,
+) -> Option<()> {
+ complete_trait_impl_name(acc, ctx, name, ImplCompletionKind::Const)
+}
+
+pub(crate) fn complete_trait_impl_type_alias(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ name: &Option<ast::Name>,
+) -> Option<()> {
+ complete_trait_impl_name(acc, ctx, name, ImplCompletionKind::TypeAlias)
+}
+
+pub(crate) fn complete_trait_impl_fn(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ name: &Option<ast::Name>,
+) -> Option<()> {
+ complete_trait_impl_name(acc, ctx, name, ImplCompletionKind::Fn)
+}
+
+fn complete_trait_impl_name(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ name: &Option<ast::Name>,
+ kind: ImplCompletionKind,
+) -> Option<()> {
+ let token = ctx.token.clone();
+ let item = match name {
+ Some(name) => name.syntax().parent(),
+ None => if token.kind() == SyntaxKind::WHITESPACE { token.prev_token()? } else { token }
+ .parent(),
+ }?;
+ complete_trait_impl(
+ acc,
+ ctx,
+ kind,
+ replacement_range(ctx, &item),
+ // item -> ASSOC_ITEM_LIST -> IMPL
+ &ast::Impl::cast(item.parent()?.parent()?)?,
+ );
+ Some(())
+}
+
+pub(crate) fn complete_trait_impl_item_by_name(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ name_ref: &Option<ast::NameRef>,
+ impl_: &Option<ast::Impl>,
+) {
+ if !path_ctx.is_trivial_path() {
+ return;
+ }
+ if let Some(impl_) = impl_ {
+ complete_trait_impl(
+ acc,
+ ctx,
+ ImplCompletionKind::All,
+ match name_ref {
+ Some(name) => name.syntax().text_range(),
+ None => ctx.source_range(),
+ },
+ impl_,
+ );
+ }
+}
+
+fn complete_trait_impl(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ kind: ImplCompletionKind,
+ replacement_range: TextRange,
+ impl_def: &ast::Impl,
+) {
+ if let Some(hir_impl) = ctx.sema.to_def(impl_def) {
+ get_missing_assoc_items(&ctx.sema, impl_def).into_iter().for_each(|item| {
+ use self::ImplCompletionKind::*;
+ match (item, kind) {
+ (hir::AssocItem::Function(func), All | Fn) => {
+ add_function_impl(acc, ctx, replacement_range, func, hir_impl)
+ }
+ (hir::AssocItem::TypeAlias(type_alias), All | TypeAlias) => {
+ add_type_alias_impl(acc, ctx, replacement_range, type_alias)
+ }
+ (hir::AssocItem::Const(const_), All | Const) => {
+ add_const_impl(acc, ctx, replacement_range, const_, hir_impl)
+ }
+ _ => {}
+ }
+ });
+ }
+}
+
+fn add_function_impl(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ replacement_range: TextRange,
+ func: hir::Function,
+ impl_def: hir::Impl,
+) {
+ let fn_name = func.name(ctx.db);
+
+ let label = format!(
+ "fn {}({})",
+ fn_name,
+ if func.assoc_fn_params(ctx.db).is_empty() { "" } else { ".." }
+ );
+
+ let completion_kind = if func.has_self_param(ctx.db) {
+ CompletionItemKind::Method
+ } else {
+ CompletionItemKind::SymbolKind(SymbolKind::Function)
+ };
+
+ let mut item = CompletionItem::new(completion_kind, replacement_range, label);
+ item.lookup_by(format!("fn {}", fn_name))
+ .set_documentation(func.docs(ctx.db))
+ .set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
+
+ if let Some(source) = ctx.sema.source(func) {
+ let assoc_item = ast::AssocItem::Fn(source.value);
+ if let Some(transformed_item) = get_transformed_assoc_item(ctx, assoc_item, impl_def) {
+ let transformed_fn = match transformed_item {
+ ast::AssocItem::Fn(func) => func,
+ _ => unreachable!(),
+ };
+
+ let function_decl = function_declaration(&transformed_fn, source.file_id.is_macro());
+ match ctx.config.snippet_cap {
+ Some(cap) => {
+ let snippet = format!("{} {{\n $0\n}}", function_decl);
+ item.snippet_edit(cap, TextEdit::replace(replacement_range, snippet));
+ }
+ None => {
+ let header = format!("{} {{", function_decl);
+ item.text_edit(TextEdit::replace(replacement_range, header));
+ }
+ };
+ item.add_to(acc);
+ }
+ }
+}
+
+/// Transform a relevant associated item to inline generics from the impl, remove attrs and docs, etc.
+fn get_transformed_assoc_item(
+ ctx: &CompletionContext<'_>,
+ assoc_item: ast::AssocItem,
+ impl_def: hir::Impl,
+) -> Option<ast::AssocItem> {
+ let assoc_item = assoc_item.clone_for_update();
+ let trait_ = impl_def.trait_(ctx.db)?;
+ let source_scope = &ctx.sema.scope_for_def(trait_);
+ let target_scope = &ctx.sema.scope(ctx.sema.source(impl_def)?.syntax().value)?;
+ let transform = PathTransform::trait_impl(
+ target_scope,
+ source_scope,
+ trait_,
+ ctx.sema.source(impl_def)?.value,
+ );
+
+ transform.apply(assoc_item.syntax());
+ if let ast::AssocItem::Fn(func) = &assoc_item {
+ func.remove_attrs_and_docs();
+ }
+ Some(assoc_item)
+}
+
+fn add_type_alias_impl(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ replacement_range: TextRange,
+ type_alias: hir::TypeAlias,
+) {
+ let alias_name = type_alias.name(ctx.db);
+ let (alias_name, escaped_name) = (alias_name.to_smol_str(), alias_name.escaped().to_smol_str());
+
+ let label = format!("type {} =", alias_name);
+ let replacement = format!("type {} = ", escaped_name);
+
+ let mut item = CompletionItem::new(SymbolKind::TypeAlias, replacement_range, label);
+ item.lookup_by(format!("type {}", alias_name))
+ .set_documentation(type_alias.docs(ctx.db))
+ .set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
+ match ctx.config.snippet_cap {
+ Some(cap) => item
+ .snippet_edit(cap, TextEdit::replace(replacement_range, format!("{}$0;", replacement))),
+ None => item.text_edit(TextEdit::replace(replacement_range, replacement)),
+ };
+ item.add_to(acc);
+}
+
+fn add_const_impl(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ replacement_range: TextRange,
+ const_: hir::Const,
+ impl_def: hir::Impl,
+) {
+ let const_name = const_.name(ctx.db).map(|n| n.to_smol_str());
+
+ if let Some(const_name) = const_name {
+ if let Some(source) = ctx.sema.source(const_) {
+ let assoc_item = ast::AssocItem::Const(source.value);
+ if let Some(transformed_item) = get_transformed_assoc_item(ctx, assoc_item, impl_def) {
+ let transformed_const = match transformed_item {
+ ast::AssocItem::Const(const_) => const_,
+ _ => unreachable!(),
+ };
+
+ let label = make_const_compl_syntax(&transformed_const, source.file_id.is_macro());
+ let replacement = format!("{} ", label);
+
+ let mut item = CompletionItem::new(SymbolKind::Const, replacement_range, label);
+ item.lookup_by(format!("const {}", const_name))
+ .set_documentation(const_.docs(ctx.db))
+ .set_relevance(CompletionRelevance {
+ is_item_from_trait: true,
+ ..Default::default()
+ });
+ match ctx.config.snippet_cap {
+ Some(cap) => item.snippet_edit(
+ cap,
+ TextEdit::replace(replacement_range, format!("{}$0;", replacement)),
+ ),
+ None => item.text_edit(TextEdit::replace(replacement_range, replacement)),
+ };
+ item.add_to(acc);
+ }
+ }
+ }
+}
+
+fn make_const_compl_syntax(const_: &ast::Const, needs_whitespace: bool) -> String {
+ const_.remove_attrs_and_docs();
+ let const_ = if needs_whitespace {
+ insert_whitespace_into_node::insert_ws_into(const_.syntax().clone())
+ } else {
+ const_.syntax().clone()
+ };
+
+ let start = const_.text_range().start();
+ let const_end = const_.text_range().end();
+
+ let end = const_
+ .children_with_tokens()
+ .find(|s| s.kind() == T![;] || s.kind() == T![=])
+ .map_or(const_end, |f| f.text_range().start());
+
+ let len = end - start;
+ let range = TextRange::new(0.into(), len);
+
+ let syntax = const_.text().slice(range).to_string();
+
+ format!("{} =", syntax.trim_end())
+}
+
+fn function_declaration(node: &ast::Fn, needs_whitespace: bool) -> String {
+ node.remove_attrs_and_docs();
+
+ let node = if needs_whitespace {
+ insert_whitespace_into_node::insert_ws_into(node.syntax().clone())
+ } else {
+ node.syntax().clone()
+ };
+
+ let start = node.text_range().start();
+ let end = node.text_range().end();
+
+ let end = node
+ .last_child_or_token()
+ .filter(|s| s.kind() == T![;] || s.kind() == SyntaxKind::BLOCK_EXPR)
+ .map_or(end, |f| f.text_range().start());
+
+ let len = end - start;
+ let range = TextRange::new(0.into(), len);
+
+ let syntax = node.text().slice(range).to_string();
+
+ syntax.trim_end().to_owned()
+}
+
+fn replacement_range(ctx: &CompletionContext<'_>, item: &SyntaxNode) -> TextRange {
+ let first_child = item
+ .children_with_tokens()
+ .find(|child| {
+ !matches!(child.kind(), SyntaxKind::COMMENT | SyntaxKind::WHITESPACE | SyntaxKind::ATTR)
+ })
+ .unwrap_or_else(|| SyntaxElement::Node(item.clone()));
+
+ TextRange::new(first_child.text_range().start(), ctx.source_range().end())
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+
+ use crate::tests::{check_edit, completion_list_no_kw};
+
+ fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list_no_kw(ra_fixture);
+ expect.assert_eq(&actual)
+ }
+
+ #[test]
+ fn no_completion_inside_fn() {
+ check(
+ r"
+trait Test { fn test(); fn test2(); }
+struct T;
+
+impl Test for T {
+ fn test() {
+ t$0
+ }
+}
+",
+ expect![[r#"
+ sp Self
+ st T
+ tt Test
+ bt u32
+ "#]],
+ );
+
+ check(
+ r"
+trait Test { fn test(); fn test2(); }
+struct T;
+
+impl Test for T {
+ fn test() {
+ fn t$0
+ }
+}
+",
+ expect![[""]],
+ );
+
+ check(
+ r"
+trait Test { fn test(); fn test2(); }
+struct T;
+
+impl Test for T {
+ fn test() {
+ fn $0
+ }
+}
+",
+ expect![[""]],
+ );
+
+ // https://github.com/rust-lang/rust-analyzer/pull/5976#issuecomment-692332191
+ check(
+ r"
+trait Test { fn test(); fn test2(); }
+struct T;
+
+impl Test for T {
+ fn test() {
+ foo.$0
+ }
+}
+",
+ expect![[r#""#]],
+ );
+
+ check(
+ r"
+trait Test { fn test(_: i32); fn test2(); }
+struct T;
+
+impl Test for T {
+ fn test(t$0)
+}
+",
+ expect![[r#"
+ sp Self
+ st T
+ bn &mut self
+ bn &self
+ bn mut self
+ bn self
+ "#]],
+ );
+
+ check(
+ r"
+trait Test { fn test(_: fn()); fn test2(); }
+struct T;
+
+impl Test for T {
+ fn test(f: fn $0)
+}
+",
+ expect![[r#"
+ sp Self
+ st T
+ "#]],
+ );
+ }
+
+ #[test]
+ fn no_completion_inside_const() {
+ check(
+ r"
+trait Test { const TEST: fn(); const TEST2: u32; type Test; fn test(); }
+struct T;
+
+impl Test for T {
+ const TEST: fn $0
+}
+",
+ expect![[r#""#]],
+ );
+
+ check(
+ r"
+trait Test { const TEST: u32; const TEST2: u32; type Test; fn test(); }
+struct T;
+
+impl Test for T {
+ const TEST: T$0
+}
+",
+ expect![[r#"
+ sp Self
+ st T
+ tt Test
+ bt u32
+ "#]],
+ );
+
+ check(
+ r"
+trait Test { const TEST: u32; const TEST2: u32; type Test; fn test(); }
+struct T;
+
+impl Test for T {
+ const TEST: u32 = f$0
+}
+",
+ expect![[r#"
+ sp Self
+ st T
+ tt Test
+ bt u32
+ "#]],
+ );
+
+ check(
+ r"
+trait Test { const TEST: u32; const TEST2: u32; type Test; fn test(); }
+struct T;
+
+impl Test for T {
+ const TEST: u32 = {
+ t$0
+ };
+}
+",
+ expect![[r#"
+ sp Self
+ st T
+ tt Test
+ bt u32
+ "#]],
+ );
+
+ check(
+ r"
+trait Test { const TEST: u32; const TEST2: u32; type Test; fn test(); }
+struct T;
+
+impl Test for T {
+ const TEST: u32 = {
+ fn $0
+ };
+}
+",
+ expect![[""]],
+ );
+
+ check(
+ r"
+trait Test { const TEST: u32; const TEST2: u32; type Test; fn test(); }
+struct T;
+
+impl Test for T {
+ const TEST: u32 = {
+ fn t$0
+ };
+}
+",
+ expect![[""]],
+ );
+ }
+
+ #[test]
+ fn no_completion_inside_type() {
+ check(
+ r"
+trait Test { type Test; type Test2; fn test(); }
+struct T;
+
+impl Test for T {
+ type Test = T$0;
+}
+",
+ expect![[r#"
+ sp Self
+ st T
+ tt Test
+ bt u32
+ "#]],
+ );
+
+ check(
+ r"
+trait Test { type Test; type Test2; fn test(); }
+struct T;
+
+impl Test for T {
+ type Test = fn $0;
+}
+",
+ expect![[r#""#]],
+ );
+ }
+
+ #[test]
+ fn name_ref_single_function() {
+ check_edit(
+ "fn test",
+ r#"
+trait Test {
+ fn test();
+}
+struct T;
+
+impl Test for T {
+ t$0
+}
+"#,
+ r#"
+trait Test {
+ fn test();
+}
+struct T;
+
+impl Test for T {
+ fn test() {
+ $0
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn single_function() {
+ check_edit(
+ "fn test",
+ r#"
+trait Test {
+ fn test();
+}
+struct T;
+
+impl Test for T {
+ fn t$0
+}
+"#,
+ r#"
+trait Test {
+ fn test();
+}
+struct T;
+
+impl Test for T {
+ fn test() {
+ $0
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn generic_fn() {
+ check_edit(
+ "fn foo",
+ r#"
+trait Test {
+ fn foo<T>();
+}
+struct T;
+
+impl Test for T {
+ fn f$0
+}
+"#,
+ r#"
+trait Test {
+ fn foo<T>();
+}
+struct T;
+
+impl Test for T {
+ fn foo<T>() {
+ $0
+}
+}
+"#,
+ );
+ check_edit(
+ "fn foo",
+ r#"
+trait Test {
+ fn foo<T>() where T: Into<String>;
+}
+struct T;
+
+impl Test for T {
+ fn f$0
+}
+"#,
+ r#"
+trait Test {
+ fn foo<T>() where T: Into<String>;
+}
+struct T;
+
+impl Test for T {
+ fn foo<T>() where T: Into<String> {
+ $0
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn associated_type() {
+ check_edit(
+ "type SomeType",
+ r#"
+trait Test {
+ type SomeType;
+}
+
+impl Test for () {
+ type S$0
+}
+"#,
+ "
+trait Test {
+ type SomeType;
+}
+
+impl Test for () {
+ type SomeType = $0;\n\
+}
+",
+ );
+ check_edit(
+ "type SomeType",
+ r#"
+trait Test {
+ type SomeType;
+}
+
+impl Test for () {
+ type$0
+}
+"#,
+ "
+trait Test {
+ type SomeType;
+}
+
+impl Test for () {
+ type SomeType = $0;\n\
+}
+",
+ );
+ }
+
+ #[test]
+ fn associated_const() {
+ check_edit(
+ "const SOME_CONST",
+ r#"
+trait Test {
+ const SOME_CONST: u16;
+}
+
+impl Test for () {
+ const S$0
+}
+"#,
+ "
+trait Test {
+ const SOME_CONST: u16;
+}
+
+impl Test for () {
+ const SOME_CONST: u16 = $0;\n\
+}
+",
+ );
+
+ check_edit(
+ "const SOME_CONST",
+ r#"
+trait Test {
+ const SOME_CONST: u16 = 92;
+}
+
+impl Test for () {
+ const S$0
+}
+"#,
+ "
+trait Test {
+ const SOME_CONST: u16 = 92;
+}
+
+impl Test for () {
+ const SOME_CONST: u16 = $0;\n\
+}
+",
+ );
+ }
+
+ #[test]
+ fn complete_without_name() {
+ let test = |completion: &str, hint: &str, completed: &str, next_sibling: &str| {
+ check_edit(
+ completion,
+ &format!(
+ r#"
+trait Test {{
+ type Foo;
+ const CONST: u16;
+ fn bar();
+}}
+struct T;
+
+impl Test for T {{
+ {}
+ {}
+}}
+"#,
+ hint, next_sibling
+ ),
+ &format!(
+ r#"
+trait Test {{
+ type Foo;
+ const CONST: u16;
+ fn bar();
+}}
+struct T;
+
+impl Test for T {{
+ {}
+ {}
+}}
+"#,
+ completed, next_sibling
+ ),
+ )
+ };
+
+ // Enumerate some possible next siblings.
+ for next_sibling in &[
+ "",
+ "fn other_fn() {}", // `const $0 fn` -> `const fn`
+ "type OtherType = i32;",
+ "const OTHER_CONST: i32 = 0;",
+ "async fn other_fn() {}",
+ "unsafe fn other_fn() {}",
+ "default fn other_fn() {}",
+ "default type OtherType = i32;",
+ "default const OTHER_CONST: i32 = 0;",
+ ] {
+ test("fn bar", "fn $0", "fn bar() {\n $0\n}", next_sibling);
+ test("type Foo", "type $0", "type Foo = $0;", next_sibling);
+ test("const CONST", "const $0", "const CONST: u16 = $0;", next_sibling);
+ }
+ }
+
+ #[test]
+ fn snippet_does_not_overwrite_comment_or_attr() {
+ let test = |completion: &str, hint: &str, completed: &str| {
+ check_edit(
+ completion,
+ &format!(
+ r#"
+trait Foo {{
+ type Type;
+ fn function();
+ const CONST: i32 = 0;
+}}
+struct T;
+
+impl Foo for T {{
+ // Comment
+ #[bar]
+ {}
+}}
+"#,
+ hint
+ ),
+ &format!(
+ r#"
+trait Foo {{
+ type Type;
+ fn function();
+ const CONST: i32 = 0;
+}}
+struct T;
+
+impl Foo for T {{
+ // Comment
+ #[bar]
+ {}
+}}
+"#,
+ completed
+ ),
+ )
+ };
+ test("fn function", "fn f$0", "fn function() {\n $0\n}");
+ test("type Type", "type T$0", "type Type = $0;");
+ test("const CONST", "const C$0", "const CONST: i32 = $0;");
+ }
+
+ #[test]
+ fn generics_are_inlined_in_return_type() {
+ check_edit(
+ "fn function",
+ r#"
+trait Foo<T> {
+ fn function() -> T;
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ fn f$0
+}
+"#,
+ r#"
+trait Foo<T> {
+ fn function() -> T;
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ fn function() -> u32 {
+ $0
+}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn generics_are_inlined_in_parameter() {
+ check_edit(
+ "fn function",
+ r#"
+trait Foo<T> {
+ fn function(bar: T);
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ fn f$0
+}
+"#,
+ r#"
+trait Foo<T> {
+ fn function(bar: T);
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ fn function(bar: u32) {
+ $0
+}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn generics_are_inlined_when_part_of_other_types() {
+ check_edit(
+ "fn function",
+ r#"
+trait Foo<T> {
+ fn function(bar: Vec<T>);
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ fn f$0
+}
+"#,
+ r#"
+trait Foo<T> {
+ fn function(bar: Vec<T>);
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ fn function(bar: Vec<u32>) {
+ $0
+}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn generics_are_inlined_complex() {
+ check_edit(
+ "fn function",
+ r#"
+trait Foo<T, U, V> {
+ fn function(bar: Vec<T>, baz: U) -> Arc<Vec<V>>;
+}
+struct Bar;
+
+impl Foo<u32, Vec<usize>, u8> for Bar {
+ fn f$0
+}
+"#,
+ r#"
+trait Foo<T, U, V> {
+ fn function(bar: Vec<T>, baz: U) -> Arc<Vec<V>>;
+}
+struct Bar;
+
+impl Foo<u32, Vec<usize>, u8> for Bar {
+ fn function(bar: Vec<u32>, baz: Vec<usize>) -> Arc<Vec<u8>> {
+ $0
+}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn generics_are_inlined_in_associated_const() {
+ check_edit(
+ "const BAR",
+ r#"
+trait Foo<T> {
+ const BAR: T;
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ const B$0
+}
+"#,
+ r#"
+trait Foo<T> {
+ const BAR: T;
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ const BAR: u32 = $0;
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn generics_are_inlined_in_where_clause() {
+ check_edit(
+ "fn function",
+ r#"
+trait SomeTrait<T> {}
+
+trait Foo<T> {
+ fn function()
+ where Self: SomeTrait<T>;
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ fn f$0
+}
+"#,
+ r#"
+trait SomeTrait<T> {}
+
+trait Foo<T> {
+ fn function()
+ where Self: SomeTrait<T>;
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ fn function()
+ where Self: SomeTrait<u32> {
+ $0
+}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn works_directly_in_impl() {
+ check(
+ r#"
+trait Tr {
+ fn required();
+}
+
+impl Tr for () {
+ $0
+}
+"#,
+ expect![[r#"
+ fn fn required()
+ "#]],
+ );
+ check(
+ r#"
+trait Tr {
+ fn provided() {}
+ fn required();
+}
+
+impl Tr for () {
+ fn provided() {}
+ $0
+}
+"#,
+ expect![[r#"
+ fn fn required()
+ "#]],
+ );
+ }
+
+ #[test]
+ fn fixes_up_macro_generated() {
+ check_edit(
+ "fn foo",
+ r#"
+macro_rules! noop {
+ ($($item: item)*) => {
+ $($item)*
+ }
+}
+
+noop! {
+ trait Foo {
+ fn foo(&mut self, bar: i64, baz: &mut u32) -> Result<(), u32>;
+ }
+}
+
+struct Test;
+
+impl Foo for Test {
+ $0
+}
+"#,
+ r#"
+macro_rules! noop {
+ ($($item: item)*) => {
+ $($item)*
+ }
+}
+
+noop! {
+ trait Foo {
+ fn foo(&mut self, bar: i64, baz: &mut u32) -> Result<(), u32>;
+ }
+}
+
+struct Test;
+
+impl Foo for Test {
+ fn foo(&mut self,bar:i64,baz: &mut u32) -> Result<(),u32> {
+ $0
+}
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs
new file mode 100644
index 000000000..3989a451b
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs
@@ -0,0 +1,237 @@
+//! Completes `where` and `for` keywords.
+
+use syntax::ast::{self, Item};
+
+use crate::{CompletionContext, Completions};
+
+pub(crate) fn complete_for_and_where(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ keyword_item: &ast::Item,
+) {
+ let mut add_keyword = |kw, snippet| acc.add_keyword_snippet(ctx, kw, snippet);
+
+ match keyword_item {
+ Item::Impl(it) => {
+ if it.for_token().is_none() && it.trait_().is_none() && it.self_ty().is_some() {
+ add_keyword("for", "for");
+ }
+ add_keyword("where", "where");
+ }
+ Item::Enum(_)
+ | Item::Fn(_)
+ | Item::Struct(_)
+ | Item::Trait(_)
+ | Item::TypeAlias(_)
+ | Item::Union(_) => {
+ add_keyword("where", "where");
+ }
+ _ => (),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+
+ use crate::tests::{check_edit, completion_list};
+
+ fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual)
+ }
+
+ #[test]
+ fn test_else_edit_after_if() {
+ check_edit(
+ "else",
+ r#"fn quux() { if true { () } $0 }"#,
+ r#"fn quux() { if true { () } else {
+ $0
+} }"#,
+ );
+ }
+
+ #[test]
+ fn test_keywords_after_unsafe_in_block_expr() {
+ check(
+ r"fn my_fn() { unsafe $0 }",
+ expect![[r#"
+ kw fn
+ kw impl
+ kw trait
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_completion_await_impls_future() {
+ check(
+ r#"
+//- minicore: future
+use core::future::*;
+struct A {}
+impl Future for A {}
+fn foo(a: A) { a.$0 }
+"#,
+ expect![[r#"
+ kw await expr.await
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn ref &expr
+ sn refm &mut expr
+ "#]],
+ );
+
+ check(
+ r#"
+//- minicore: future
+use std::future::*;
+fn foo() {
+ let a = async {};
+ a.$0
+}
+"#,
+ expect![[r#"
+ kw await expr.await
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn ref &expr
+ sn refm &mut expr
+ "#]],
+ )
+ }
+
+ #[test]
+ fn let_semi() {
+ cov_mark::check!(let_semi);
+ check_edit(
+ "match",
+ r#"
+fn main() { let x = $0 }
+"#,
+ r#"
+fn main() { let x = match $1 {
+ $0
+}; }
+"#,
+ );
+
+ check_edit(
+ "if",
+ r#"
+fn main() {
+ let x = $0
+ let y = 92;
+}
+"#,
+ r#"
+fn main() {
+ let x = if $1 {
+ $0
+};
+ let y = 92;
+}
+"#,
+ );
+
+ check_edit(
+ "loop",
+ r#"
+fn main() {
+ let x = $0
+ bar();
+}
+"#,
+ r#"
+fn main() {
+ let x = loop {
+ $0
+};
+ bar();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn if_completion_in_match_guard() {
+ check_edit(
+ "if",
+ r"
+fn main() {
+ match () {
+ () $0
+ }
+}
+",
+ r"
+fn main() {
+ match () {
+ () if $0
+ }
+}
+",
+ )
+ }
+
+ #[test]
+ fn if_completion_in_match_arm_expr() {
+ check_edit(
+ "if",
+ r"
+fn main() {
+ match () {
+ () => $0
+ }
+}
+",
+ r"
+fn main() {
+ match () {
+ () => if $1 {
+ $0
+}
+ }
+}
+",
+ )
+ }
+
+ #[test]
+ fn if_completion_in_match_arm_expr_block() {
+ check_edit(
+ "if",
+ r"
+fn main() {
+ match () {
+ () => {
+ $0
+ }
+ }
+}
+",
+ r"
+fn main() {
+ match () {
+ () => {
+ if $1 {
+ $0
+}
+ }
+ }
+}
+",
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/lifetime.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/lifetime.rs
new file mode 100644
index 000000000..3b79def63
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/lifetime.rs
@@ -0,0 +1,341 @@
+//! Completes lifetimes and labels.
+//!
+//! These completions work a bit differently in that they are only shown when what the user types
+//! has a `'` preceding it, as our fake syntax tree is invalid otherwise (due to us not inserting
+//! a lifetime but an ident for obvious reasons).
+//! Due to this all the tests for lifetimes and labels live in this module for the time being as
+//! there is no value in lifting these out into the outline module test since they will either not
+//! show up for normal completions, or they won't show completions other than lifetimes depending
+//! on the fixture input.
+use hir::{known, ScopeDef};
+use syntax::{ast, TokenText};
+
+use crate::{
+ completions::Completions,
+ context::{CompletionContext, LifetimeContext, LifetimeKind},
+};
+
+/// Completes lifetimes.
+pub(crate) fn complete_lifetime(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ lifetime_ctx: &LifetimeContext,
+) {
+ let (lp, lifetime) = match lifetime_ctx {
+ LifetimeContext { kind: LifetimeKind::Lifetime, lifetime } => (None, lifetime),
+ LifetimeContext {
+ kind: LifetimeKind::LifetimeParam { is_decl: false, param },
+ lifetime,
+ } => (Some(param), lifetime),
+ _ => return,
+ };
+ let param_lifetime = match (lifetime, lp.and_then(|lp| lp.lifetime())) {
+ (Some(lt), Some(lp)) if lp == lt.clone() => return,
+ (Some(_), Some(lp)) => Some(lp),
+ _ => None,
+ };
+ let param_lifetime = param_lifetime.as_ref().map(ast::Lifetime::text);
+ let param_lifetime = param_lifetime.as_ref().map(TokenText::as_str);
+
+ ctx.process_all_names_raw(&mut |name, res| {
+ if matches!(
+ res,
+ ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_))
+ if param_lifetime != Some(&*name.to_smol_str())
+ ) {
+ acc.add_lifetime(ctx, name);
+ }
+ });
+ if param_lifetime.is_none() {
+ acc.add_lifetime(ctx, known::STATIC_LIFETIME);
+ }
+}
+
+/// Completes labels.
+pub(crate) fn complete_label(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ lifetime_ctx: &LifetimeContext,
+) {
+ if !matches!(lifetime_ctx, LifetimeContext { kind: LifetimeKind::LabelRef, .. }) {
+ return;
+ }
+ ctx.process_all_names_raw(&mut |name, res| {
+ if let ScopeDef::Label(_) = res {
+ acc.add_label(ctx, name);
+ }
+ });
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+
+ use crate::tests::{check_edit, completion_list};
+
+ fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual);
+ }
+
+ #[test]
+ fn check_lifetime_edit() {
+ check_edit(
+ "'lifetime",
+ r#"
+fn func<'lifetime>(foo: &'li$0) {}
+"#,
+ r#"
+fn func<'lifetime>(foo: &'lifetime) {}
+"#,
+ );
+ cov_mark::check!(completes_if_lifetime_without_idents);
+ check_edit(
+ "'lifetime",
+ r#"
+fn func<'lifetime>(foo: &'$0) {}
+"#,
+ r#"
+fn func<'lifetime>(foo: &'lifetime) {}
+"#,
+ );
+ }
+
+ #[test]
+ fn complete_lifetime_in_ref() {
+ check(
+ r#"
+fn foo<'lifetime>(foo: &'a$0 usize) {}
+"#,
+ expect![[r#"
+ lt 'lifetime
+ lt 'static
+ "#]],
+ );
+ }
+
+ #[test]
+ fn complete_lifetime_in_ref_missing_ty() {
+ check(
+ r#"
+fn foo<'lifetime>(foo: &'a$0) {}
+"#,
+ expect![[r#"
+ lt 'lifetime
+ lt 'static
+ "#]],
+ );
+ }
+ #[test]
+ fn complete_lifetime_in_self_ref() {
+ check(
+ r#"
+struct Foo;
+impl<'impl> Foo {
+ fn foo<'func>(&'a$0 self) {}
+}
+"#,
+ expect![[r#"
+ lt 'func
+ lt 'impl
+ lt 'static
+ "#]],
+ );
+ }
+
+ #[test]
+ fn complete_lifetime_in_arg_list() {
+ check(
+ r#"
+struct Foo<'lt>;
+fn foo<'lifetime>(_: Foo<'a$0>) {}
+"#,
+ expect![[r#"
+ lt 'lifetime
+ lt 'static
+ "#]],
+ );
+ }
+
+ #[test]
+ fn complete_lifetime_in_where_pred() {
+ check(
+ r#"
+fn foo2<'lifetime, T>() where 'a$0 {}
+"#,
+ expect![[r#"
+ lt 'lifetime
+ lt 'static
+ "#]],
+ );
+ }
+
+ #[test]
+ fn complete_lifetime_in_ty_bound() {
+ check(
+ r#"
+fn foo2<'lifetime, T>() where T: 'a$0 {}
+"#,
+ expect![[r#"
+ lt 'lifetime
+ lt 'static
+ "#]],
+ );
+ check(
+ r#"
+fn foo2<'lifetime, T>() where T: Trait<'a$0> {}
+"#,
+ expect![[r#"
+ lt 'lifetime
+ lt 'static
+ "#]],
+ );
+ }
+
+ #[test]
+ fn dont_complete_lifetime_in_assoc_ty_bound() {
+ check(
+ r#"
+fn foo2<'lifetime, T>() where T: Trait<Item = 'a$0> {}
+"#,
+ expect![[r#""#]],
+ );
+ }
+
+ #[test]
+ fn complete_lifetime_in_param_list() {
+ check(
+ r#"
+fn foo<'$0>() {}
+"#,
+ expect![[r#""#]],
+ );
+ check(
+ r#"
+fn foo<'a$0>() {}
+"#,
+ expect![[r#""#]],
+ );
+ check(
+ r#"
+fn foo<'footime, 'lifetime: 'a$0>() {}
+"#,
+ expect![[r#"
+ lt 'footime
+ "#]],
+ );
+ }
+
+ #[test]
+ fn check_label_edit() {
+ check_edit(
+ "'label",
+ r#"
+fn foo() {
+ 'label: loop {
+ break '$0
+ }
+}
+"#,
+ r#"
+fn foo() {
+ 'label: loop {
+ break 'label
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn complete_label_in_loop() {
+ check(
+ r#"
+fn foo() {
+ 'foop: loop {
+ break '$0
+ }
+}
+"#,
+ expect![[r#"
+ lb 'foop
+ "#]],
+ );
+ check(
+ r#"
+fn foo() {
+ 'foop: loop {
+ continue '$0
+ }
+}
+"#,
+ expect![[r#"
+ lb 'foop
+ "#]],
+ );
+ }
+
+ #[test]
+ fn complete_label_in_block_nested() {
+ check(
+ r#"
+fn foo() {
+ 'foop: {
+ 'baap: {
+ break '$0
+ }
+ }
+}
+"#,
+ expect![[r#"
+ lb 'baap
+ lb 'foop
+ "#]],
+ );
+ }
+
+ #[test]
+ fn complete_label_in_loop_with_value() {
+ check(
+ r#"
+fn foo() {
+ 'foop: loop {
+ break '$0 i32;
+ }
+}
+"#,
+ expect![[r#"
+ lb 'foop
+ "#]],
+ );
+ }
+
+ #[test]
+ fn complete_label_in_while_cond() {
+ check(
+ r#"
+fn foo() {
+ 'outer: while { 'inner: loop { break '$0 } } {}
+}
+"#,
+ expect![[r#"
+ lb 'inner
+ lb 'outer
+ "#]],
+ );
+ }
+
+ #[test]
+ fn complete_label_in_for_iterable() {
+ check(
+ r#"
+fn foo() {
+ 'outer: for _ in [{ 'inner: loop { break '$0 } }] {}
+}
+"#,
+ expect![[r#"
+ lb 'inner
+ "#]],
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/mod_.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/mod_.rs
new file mode 100644
index 000000000..9c975b929
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/mod_.rs
@@ -0,0 +1,354 @@
+//! Completes mod declarations.
+
+use std::iter;
+
+use hir::{Module, ModuleSource};
+use ide_db::{
+ base_db::{SourceDatabaseExt, VfsPath},
+ FxHashSet, RootDatabase, SymbolKind,
+};
+use syntax::{ast, AstNode, SyntaxKind};
+
+use crate::{context::CompletionContext, CompletionItem, Completions};
+
+/// Complete mod declaration, i.e. `mod $0;`
+pub(crate) fn complete_mod(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ mod_under_caret: &ast::Module,
+) -> Option<()> {
+ if mod_under_caret.item_list().is_some() {
+ return None;
+ }
+
+ let _p = profile::span("completion::complete_mod");
+
+ let mut current_module = ctx.module;
+ // For `mod $0`, `ctx.module` is its parent, but for `mod f$0`, it's `mod f` itself, but we're
+ // interested in its parent.
+ if ctx.original_token.kind() == SyntaxKind::IDENT {
+ if let Some(module) =
+ ctx.original_token.parent_ancestors().nth(1).and_then(ast::Module::cast)
+ {
+ match ctx.sema.to_def(&module) {
+ Some(module) if module == current_module => {
+ if let Some(parent) = current_module.parent(ctx.db) {
+ current_module = parent;
+ }
+ }
+ _ => {}
+ }
+ }
+ }
+
+ let module_definition_file =
+ current_module.definition_source(ctx.db).file_id.original_file(ctx.db);
+ let source_root = ctx.db.source_root(ctx.db.file_source_root(module_definition_file));
+ let directory_to_look_for_submodules = directory_to_look_for_submodules(
+ current_module,
+ ctx.db,
+ source_root.path_for_file(&module_definition_file)?,
+ )?;
+
+ let existing_mod_declarations = current_module
+ .children(ctx.db)
+ .filter_map(|module| Some(module.name(ctx.db)?.to_string()))
+ .collect::<FxHashSet<_>>();
+
+ let module_declaration_file =
+ current_module.declaration_source(ctx.db).map(|module_declaration_source_file| {
+ module_declaration_source_file.file_id.original_file(ctx.db)
+ });
+
+ source_root
+ .iter()
+ .filter(|submodule_candidate_file| submodule_candidate_file != &module_definition_file)
+ .filter(|submodule_candidate_file| {
+ Some(submodule_candidate_file) != module_declaration_file.as_ref()
+ })
+ .filter_map(|submodule_file| {
+ let submodule_path = source_root.path_for_file(&submodule_file)?;
+ let directory_with_submodule = submodule_path.parent()?;
+ let (name, ext) = submodule_path.name_and_extension()?;
+ if ext != Some("rs") {
+ return None;
+ }
+ match name {
+ "lib" | "main" => None,
+ "mod" => {
+ if directory_with_submodule.parent()? == directory_to_look_for_submodules {
+ match directory_with_submodule.name_and_extension()? {
+ (directory_name, None) => Some(directory_name.to_owned()),
+ _ => None,
+ }
+ } else {
+ None
+ }
+ }
+ file_name if directory_with_submodule == directory_to_look_for_submodules => {
+ Some(file_name.to_owned())
+ }
+ _ => None,
+ }
+ })
+ .filter(|name| !existing_mod_declarations.contains(name))
+ .for_each(|submodule_name| {
+ let mut label = submodule_name;
+ if mod_under_caret.semicolon_token().is_none() {
+ label.push(';');
+ }
+ let item = CompletionItem::new(SymbolKind::Module, ctx.source_range(), &label);
+ item.add_to(acc)
+ });
+
+ Some(())
+}
+
+fn directory_to_look_for_submodules(
+ module: Module,
+ db: &RootDatabase,
+ module_file_path: &VfsPath,
+) -> Option<VfsPath> {
+ let directory_with_module_path = module_file_path.parent()?;
+ let (name, ext) = module_file_path.name_and_extension()?;
+ if ext != Some("rs") {
+ return None;
+ }
+ let base_directory = match name {
+ "mod" | "lib" | "main" => Some(directory_with_module_path),
+ regular_rust_file_name => {
+ if matches!(
+ (
+ directory_with_module_path
+ .parent()
+ .as_ref()
+ .and_then(|path| path.name_and_extension()),
+ directory_with_module_path.name_and_extension(),
+ ),
+ (Some(("src", None)), Some(("bin", None)))
+ ) {
+ // files in /src/bin/ can import each other directly
+ Some(directory_with_module_path)
+ } else {
+ directory_with_module_path.join(regular_rust_file_name)
+ }
+ }
+ }?;
+
+ module_chain_to_containing_module_file(module, db)
+ .into_iter()
+ .filter_map(|module| module.name(db))
+ .try_fold(base_directory, |path, name| path.join(&name.to_smol_str()))
+}
+
+fn module_chain_to_containing_module_file(
+ current_module: Module,
+ db: &RootDatabase,
+) -> Vec<Module> {
+ let mut path =
+ iter::successors(Some(current_module), |current_module| current_module.parent(db))
+ .take_while(|current_module| {
+ matches!(current_module.definition_source(db).value, ModuleSource::Module(_))
+ })
+ .collect::<Vec<_>>();
+ path.reverse();
+ path
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+
+ use crate::tests::completion_list;
+
+ fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual);
+ }
+
+ #[test]
+ fn lib_module_completion() {
+ check(
+ r#"
+//- /lib.rs
+mod $0
+//- /foo.rs
+fn foo() {}
+//- /foo/ignored_foo.rs
+fn ignored_foo() {}
+//- /bar/mod.rs
+fn bar() {}
+//- /bar/ignored_bar.rs
+fn ignored_bar() {}
+"#,
+ expect![[r#"
+ md bar;
+ md foo;
+ "#]],
+ );
+ }
+
+ #[test]
+ fn no_module_completion_with_module_body() {
+ check(
+ r#"
+//- /lib.rs
+mod $0 {
+
+}
+//- /foo.rs
+fn foo() {}
+"#,
+ expect![[r#""#]],
+ );
+ }
+
+ #[test]
+ fn main_module_completion() {
+ check(
+ r#"
+//- /main.rs
+mod $0
+//- /foo.rs
+fn foo() {}
+//- /foo/ignored_foo.rs
+fn ignored_foo() {}
+//- /bar/mod.rs
+fn bar() {}
+//- /bar/ignored_bar.rs
+fn ignored_bar() {}
+"#,
+ expect![[r#"
+ md bar;
+ md foo;
+ "#]],
+ );
+ }
+
+ #[test]
+ fn main_test_module_completion() {
+ check(
+ r#"
+//- /main.rs
+mod tests {
+ mod $0;
+}
+//- /tests/foo.rs
+fn foo() {}
+"#,
+ expect![[r#"
+ md foo
+ "#]],
+ );
+ }
+
+ #[test]
+ fn directly_nested_module_completion() {
+ check(
+ r#"
+//- /lib.rs
+mod foo;
+//- /foo.rs
+mod $0;
+//- /foo/bar.rs
+fn bar() {}
+//- /foo/bar/ignored_bar.rs
+fn ignored_bar() {}
+//- /foo/baz/mod.rs
+fn baz() {}
+//- /foo/moar/ignored_moar.rs
+fn ignored_moar() {}
+"#,
+ expect![[r#"
+ md bar
+ md baz
+ "#]],
+ );
+ }
+
+ #[test]
+ fn nested_in_source_module_completion() {
+ check(
+ r#"
+//- /lib.rs
+mod foo;
+//- /foo.rs
+mod bar {
+ mod $0
+}
+//- /foo/bar/baz.rs
+fn baz() {}
+"#,
+ expect![[r#"
+ md baz;
+ "#]],
+ );
+ }
+
+ // FIXME binary modules are not supported in tests properly
+ // Binary modules are a bit special, they allow importing the modules from `/src/bin`
+ // and that's why are good to test two things:
+ // * no cycles are allowed in mod declarations
+ // * no modules from the parent directory are proposed
+ // Unfortunately, binary modules support is in cargo not rustc,
+ // hence the test does not work now
+ //
+ // #[test]
+ // fn regular_bin_module_completion() {
+ // check(
+ // r#"
+ // //- /src/bin.rs
+ // fn main() {}
+ // //- /src/bin/foo.rs
+ // mod $0
+ // //- /src/bin/bar.rs
+ // fn bar() {}
+ // //- /src/bin/bar/bar_ignored.rs
+ // fn bar_ignored() {}
+ // "#,
+ // expect![[r#"
+ // md bar;
+ // "#]],foo
+ // );
+ // }
+
+ #[test]
+ fn already_declared_bin_module_completion_omitted() {
+ check(
+ r#"
+//- /src/bin.rs crate:main
+fn main() {}
+//- /src/bin/foo.rs
+mod $0
+//- /src/bin/bar.rs
+mod foo;
+fn bar() {}
+//- /src/bin/bar/bar_ignored.rs
+fn bar_ignored() {}
+"#,
+ expect![[r#""#]],
+ );
+ }
+
+ #[test]
+ fn name_partially_typed() {
+ check(
+ r#"
+//- /lib.rs
+mod f$0
+//- /foo.rs
+fn foo() {}
+//- /foo/ignored_foo.rs
+fn ignored_foo() {}
+//- /bar/mod.rs
+fn bar() {}
+//- /bar/ignored_bar.rs
+fn ignored_bar() {}
+"#,
+ expect![[r#"
+ md bar;
+ md foo;
+ "#]],
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs
new file mode 100644
index 000000000..71d2d9d43
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs
@@ -0,0 +1,185 @@
+//! Completes constants and paths in unqualified patterns.
+
+use hir::{db::DefDatabase, AssocItem, ScopeDef};
+use syntax::ast::Pat;
+
+use crate::{
+ context::{PathCompletionCtx, PatternContext, PatternRefutability, Qualified},
+ CompletionContext, Completions,
+};
+
+/// Completes constants and paths in unqualified patterns.
+pub(crate) fn complete_pattern(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ pattern_ctx: &PatternContext,
+) {
+ match pattern_ctx.parent_pat.as_ref() {
+ Some(Pat::RangePat(_) | Pat::BoxPat(_)) => (),
+ Some(Pat::RefPat(r)) => {
+ if r.mut_token().is_none() {
+ acc.add_keyword(ctx, "mut");
+ }
+ }
+ _ => {
+ let tok = ctx.token.text_range().start();
+ match (pattern_ctx.ref_token.as_ref(), pattern_ctx.mut_token.as_ref()) {
+ (None, None) => {
+ acc.add_keyword(ctx, "ref");
+ acc.add_keyword(ctx, "mut");
+ }
+ (None, Some(m)) if tok < m.text_range().start() => {
+ acc.add_keyword(ctx, "ref");
+ }
+ (Some(r), None) if tok > r.text_range().end() => {
+ acc.add_keyword(ctx, "mut");
+ }
+ _ => (),
+ }
+ }
+ }
+
+ if pattern_ctx.record_pat.is_some() {
+ return;
+ }
+
+ let refutable = pattern_ctx.refutability == PatternRefutability::Refutable;
+ let single_variant_enum = |enum_: hir::Enum| ctx.db.enum_data(enum_.into()).variants.len() == 1;
+
+ if let Some(hir::Adt::Enum(e)) =
+ ctx.expected_type.as_ref().and_then(|ty| ty.strip_references().as_adt())
+ {
+ if refutable || single_variant_enum(e) {
+ super::enum_variants_with_paths(
+ acc,
+ ctx,
+ e,
+ &pattern_ctx.impl_,
+ |acc, ctx, variant, path| {
+ acc.add_qualified_variant_pat(ctx, pattern_ctx, variant, path);
+ },
+ );
+ }
+ }
+
+ // FIXME: ideally, we should look at the type we are matching against and
+ // suggest variants + auto-imports
+ ctx.process_all_names(&mut |name, res| {
+ let add_simple_path = match res {
+ hir::ScopeDef::ModuleDef(def) => match def {
+ hir::ModuleDef::Adt(hir::Adt::Struct(strukt)) => {
+ acc.add_struct_pat(ctx, pattern_ctx, strukt, Some(name.clone()));
+ true
+ }
+ hir::ModuleDef::Variant(variant)
+ if refutable || single_variant_enum(variant.parent_enum(ctx.db)) =>
+ {
+ acc.add_variant_pat(ctx, pattern_ctx, None, variant, Some(name.clone()));
+ true
+ }
+ hir::ModuleDef::Adt(hir::Adt::Enum(e)) => refutable || single_variant_enum(e),
+ hir::ModuleDef::Const(..) => refutable,
+ hir::ModuleDef::Module(..) => true,
+ hir::ModuleDef::Macro(mac) => mac.is_fn_like(ctx.db),
+ _ => false,
+ },
+ hir::ScopeDef::ImplSelfType(impl_) => match impl_.self_ty(ctx.db).as_adt() {
+ Some(hir::Adt::Struct(strukt)) => {
+ acc.add_struct_pat(ctx, pattern_ctx, strukt, Some(name.clone()));
+ true
+ }
+ Some(hir::Adt::Enum(e)) => refutable || single_variant_enum(e),
+ Some(hir::Adt::Union(_)) => true,
+ _ => false,
+ },
+ ScopeDef::GenericParam(hir::GenericParam::ConstParam(_)) => true,
+ ScopeDef::GenericParam(_)
+ | ScopeDef::AdtSelfType(_)
+ | ScopeDef::Local(_)
+ | ScopeDef::Label(_)
+ | ScopeDef::Unknown => false,
+ };
+ if add_simple_path {
+ acc.add_pattern_resolution(ctx, pattern_ctx, name, res);
+ }
+ });
+}
+
+pub(crate) fn complete_pattern_path(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx @ PathCompletionCtx { qualified, .. }: &PathCompletionCtx,
+) {
+ match qualified {
+ Qualified::With { resolution: Some(resolution), super_chain_len, .. } => {
+ acc.add_super_keyword(ctx, *super_chain_len);
+
+ match resolution {
+ hir::PathResolution::Def(hir::ModuleDef::Module(module)) => {
+ let module_scope = module.scope(ctx.db, Some(ctx.module));
+ for (name, def) in module_scope {
+ let add_resolution = match def {
+ ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => {
+ mac.is_fn_like(ctx.db)
+ }
+ ScopeDef::ModuleDef(_) => true,
+ _ => false,
+ };
+
+ if add_resolution {
+ acc.add_path_resolution(ctx, path_ctx, name, def);
+ }
+ }
+ }
+ res => {
+ let ty = match res {
+ hir::PathResolution::TypeParam(param) => param.ty(ctx.db),
+ hir::PathResolution::SelfType(impl_def) => impl_def.self_ty(ctx.db),
+ hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Struct(s))) => {
+ s.ty(ctx.db)
+ }
+ hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Enum(e))) => {
+ e.ty(ctx.db)
+ }
+ hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Union(u))) => {
+ u.ty(ctx.db)
+ }
+ hir::PathResolution::Def(hir::ModuleDef::BuiltinType(ty)) => ty.ty(ctx.db),
+ _ => return,
+ };
+
+ if let Some(hir::Adt::Enum(e)) = ty.as_adt() {
+ acc.add_enum_variants(ctx, path_ctx, e);
+ }
+
+ ctx.iterate_path_candidates(&ty, |item| match item {
+ AssocItem::TypeAlias(ta) => acc.add_type_alias(ctx, ta),
+ AssocItem::Const(c) => acc.add_const(ctx, c),
+ _ => {}
+ });
+ }
+ }
+ }
+ Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
+ Qualified::No => {
+ // this will only be hit if there are brackets or braces, otherwise this will be parsed as an ident pattern
+ ctx.process_all_names(&mut |name, res| {
+ // FIXME: we should check what kind of pattern we are in and filter accordingly
+ let add_completion = match res {
+ ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => mac.is_fn_like(ctx.db),
+ ScopeDef::ModuleDef(hir::ModuleDef::Adt(_)) => true,
+ ScopeDef::ModuleDef(hir::ModuleDef::Variant(_)) => true,
+ ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) => true,
+ ScopeDef::ImplSelfType(_) => true,
+ _ => false,
+ };
+ if add_completion {
+ acc.add_path_resolution(ctx, path_ctx, name, res);
+ }
+ });
+
+ acc.add_nameref_keywords_with_colon(ctx);
+ }
+ Qualified::TypeAnchor { .. } | Qualified::With { .. } => {}
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs
new file mode 100644
index 000000000..9a891cea2
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs
@@ -0,0 +1,616 @@
+//! Postfix completions, like `Ok(10).ifl$0` => `if let Ok() = Ok(10) { $0 }`.
+
+mod format_like;
+
+use hir::{Documentation, HasAttrs};
+use ide_db::{imports::insert_use::ImportScope, ty_filter::TryEnum, SnippetCap};
+use syntax::{
+ ast::{self, AstNode, AstToken},
+ SyntaxKind::{EXPR_STMT, STMT_LIST},
+ TextRange, TextSize,
+};
+use text_edit::TextEdit;
+
+use crate::{
+ completions::postfix::format_like::add_format_like_completions,
+ context::{CompletionContext, DotAccess, DotAccessKind},
+ item::{Builder, CompletionRelevancePostfixMatch},
+ CompletionItem, CompletionItemKind, CompletionRelevance, Completions, SnippetScope,
+};
+
+pub(crate) fn complete_postfix(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ dot_access: &DotAccess,
+) {
+ if !ctx.config.enable_postfix_completions {
+ return;
+ }
+
+ let (dot_receiver, receiver_ty, receiver_is_ambiguous_float_literal) = match dot_access {
+ DotAccess { receiver_ty: Some(ty), receiver: Some(it), kind, .. } => (
+ it,
+ &ty.original,
+ match *kind {
+ DotAccessKind::Field { receiver_is_ambiguous_float_literal } => {
+ receiver_is_ambiguous_float_literal
+ }
+ DotAccessKind::Method { .. } => false,
+ },
+ ),
+ _ => return,
+ };
+
+ let receiver_text = get_receiver_text(dot_receiver, receiver_is_ambiguous_float_literal);
+
+ let cap = match ctx.config.snippet_cap {
+ Some(it) => it,
+ None => return,
+ };
+
+ let postfix_snippet = match build_postfix_snippet_builder(ctx, cap, dot_receiver) {
+ Some(it) => it,
+ None => return,
+ };
+
+ if let Some(drop_trait) = ctx.famous_defs().core_ops_Drop() {
+ if receiver_ty.impls_trait(ctx.db, drop_trait, &[]) {
+ if let &[hir::AssocItem::Function(drop_fn)] = &*drop_trait.items(ctx.db) {
+ cov_mark::hit!(postfix_drop_completion);
+ // FIXME: check that `drop` is in scope, use fully qualified path if it isn't/if shadowed
+ let mut item = postfix_snippet(
+ "drop",
+ "fn drop(&mut self)",
+ &format!("drop($0{})", receiver_text),
+ );
+ item.set_documentation(drop_fn.docs(ctx.db));
+ item.add_to(acc);
+ }
+ }
+ }
+
+ if !ctx.config.snippets.is_empty() {
+ add_custom_postfix_completions(acc, ctx, &postfix_snippet, &receiver_text);
+ }
+
+ let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty.strip_references());
+ if let Some(try_enum) = &try_enum {
+ match try_enum {
+ TryEnum::Result => {
+ postfix_snippet(
+ "ifl",
+ "if let Ok {}",
+ &format!("if let Ok($1) = {} {{\n $0\n}}", receiver_text),
+ )
+ .add_to(acc);
+
+ postfix_snippet(
+ "while",
+ "while let Ok {}",
+ &format!("while let Ok($1) = {} {{\n $0\n}}", receiver_text),
+ )
+ .add_to(acc);
+ }
+ TryEnum::Option => {
+ postfix_snippet(
+ "ifl",
+ "if let Some {}",
+ &format!("if let Some($1) = {} {{\n $0\n}}", receiver_text),
+ )
+ .add_to(acc);
+
+ postfix_snippet(
+ "while",
+ "while let Some {}",
+ &format!("while let Some($1) = {} {{\n $0\n}}", receiver_text),
+ )
+ .add_to(acc);
+ }
+ }
+ } else if receiver_ty.is_bool() || receiver_ty.is_unknown() {
+ postfix_snippet("if", "if expr {}", &format!("if {} {{\n $0\n}}", receiver_text))
+ .add_to(acc);
+ postfix_snippet(
+ "while",
+ "while expr {}",
+ &format!("while {} {{\n $0\n}}", receiver_text),
+ )
+ .add_to(acc);
+ postfix_snippet("not", "!expr", &format!("!{}", receiver_text)).add_to(acc);
+ } else if let Some(trait_) = ctx.famous_defs().core_iter_IntoIterator() {
+ if receiver_ty.impls_trait(ctx.db, trait_, &[]) {
+ postfix_snippet(
+ "for",
+ "for ele in expr {}",
+ &format!("for ele in {} {{\n $0\n}}", receiver_text),
+ )
+ .add_to(acc);
+ }
+ }
+
+ postfix_snippet("ref", "&expr", &format!("&{}", receiver_text)).add_to(acc);
+ postfix_snippet("refm", "&mut expr", &format!("&mut {}", receiver_text)).add_to(acc);
+
+ // The rest of the postfix completions create an expression that moves an argument,
+ // so it's better to consider references now to avoid breaking the compilation
+ let dot_receiver = include_references(dot_receiver);
+ let receiver_text = get_receiver_text(&dot_receiver, receiver_is_ambiguous_float_literal);
+ let postfix_snippet = match build_postfix_snippet_builder(ctx, cap, &dot_receiver) {
+ Some(it) => it,
+ None => return,
+ };
+
+ match try_enum {
+ Some(try_enum) => match try_enum {
+ TryEnum::Result => {
+ postfix_snippet(
+ "match",
+ "match expr {}",
+ &format!("match {} {{\n Ok(${{1:_}}) => {{$2}},\n Err(${{3:_}}) => {{$0}},\n}}", receiver_text),
+ )
+ .add_to(acc);
+ }
+ TryEnum::Option => {
+ postfix_snippet(
+ "match",
+ "match expr {}",
+ &format!(
+ "match {} {{\n Some(${{1:_}}) => {{$2}},\n None => {{$0}},\n}}",
+ receiver_text
+ ),
+ )
+ .add_to(acc);
+ }
+ },
+ None => {
+ postfix_snippet(
+ "match",
+ "match expr {}",
+ &format!("match {} {{\n ${{1:_}} => {{$0}},\n}}", receiver_text),
+ )
+ .add_to(acc);
+ }
+ }
+
+ postfix_snippet("box", "Box::new(expr)", &format!("Box::new({})", receiver_text)).add_to(acc);
+ postfix_snippet("dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text)).add_to(acc); // fixme
+ postfix_snippet("dbgr", "dbg!(&expr)", &format!("dbg!(&{})", receiver_text)).add_to(acc);
+ postfix_snippet("call", "function(expr)", &format!("${{1}}({})", receiver_text)).add_to(acc);
+
+ if let Some(parent) = dot_receiver.syntax().parent().and_then(|p| p.parent()) {
+ if matches!(parent.kind(), STMT_LIST | EXPR_STMT) {
+ postfix_snippet("let", "let", &format!("let $0 = {};", receiver_text)).add_to(acc);
+ postfix_snippet("letm", "let mut", &format!("let mut $0 = {};", receiver_text))
+ .add_to(acc);
+ }
+ }
+
+ if let ast::Expr::Literal(literal) = dot_receiver.clone() {
+ if let Some(literal_text) = ast::String::cast(literal.token()) {
+ add_format_like_completions(acc, ctx, &dot_receiver, cap, &literal_text);
+ }
+ }
+}
+
+fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String {
+ let text = if receiver_is_ambiguous_float_literal {
+ let text = receiver.syntax().text();
+ let without_dot = ..text.len() - TextSize::of('.');
+ text.slice(without_dot).to_string()
+ } else {
+ receiver.to_string()
+ };
+
+ // The receiver texts should be interpreted as-is, as they are expected to be
+ // normal Rust expressions. We escape '\' and '$' so they don't get treated as
+ // snippet-specific constructs.
+ //
+ // Note that we don't need to escape the other characters that can be escaped,
+ // because they wouldn't be treated as snippet-specific constructs without '$'.
+ text.replace('\\', "\\\\").replace('$', "\\$")
+}
+
+fn include_references(initial_element: &ast::Expr) -> ast::Expr {
+ let mut resulting_element = initial_element.clone();
+ while let Some(parent_ref_element) =
+ resulting_element.syntax().parent().and_then(ast::RefExpr::cast)
+ {
+ resulting_element = ast::Expr::from(parent_ref_element);
+ }
+ resulting_element
+}
+
+fn build_postfix_snippet_builder<'ctx>(
+ ctx: &'ctx CompletionContext<'_>,
+ cap: SnippetCap,
+ receiver: &'ctx ast::Expr,
+) -> Option<impl Fn(&str, &str, &str) -> Builder + 'ctx> {
+ let receiver_syntax = receiver.syntax();
+ let receiver_range = ctx.sema.original_range_opt(receiver_syntax)?.range;
+ if ctx.source_range().end() < receiver_range.start() {
+ // This shouldn't happen, yet it does. I assume this might be due to an incorrect token mapping.
+ return None;
+ }
+ let delete_range = TextRange::new(receiver_range.start(), ctx.source_range().end());
+
+ // Wrapping impl Fn in an option ruins lifetime inference for the parameters in a way that
+ // can't be annotated for the closure, hence fix it by constructing it without the Option first
+ fn build<'ctx>(
+ ctx: &'ctx CompletionContext<'_>,
+ cap: SnippetCap,
+ delete_range: TextRange,
+ ) -> impl Fn(&str, &str, &str) -> Builder + 'ctx {
+ move |label, detail, snippet| {
+ let edit = TextEdit::replace(delete_range, snippet.to_string());
+ let mut item =
+ CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), label);
+ item.detail(detail).snippet_edit(cap, edit);
+ let postfix_match = if ctx.original_token.text() == label {
+ cov_mark::hit!(postfix_exact_match_is_high_priority);
+ Some(CompletionRelevancePostfixMatch::Exact)
+ } else {
+ cov_mark::hit!(postfix_inexact_match_is_low_priority);
+ Some(CompletionRelevancePostfixMatch::NonExact)
+ };
+ let relevance = CompletionRelevance { postfix_match, ..Default::default() };
+ item.set_relevance(relevance);
+ item
+ }
+ }
+ Some(build(ctx, cap, delete_range))
+}
+
+fn add_custom_postfix_completions(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ postfix_snippet: impl Fn(&str, &str, &str) -> Builder,
+ receiver_text: &str,
+) -> Option<()> {
+ if ImportScope::find_insert_use_container(&ctx.token.parent()?, &ctx.sema).is_none() {
+ return None;
+ }
+ ctx.config.postfix_snippets().filter(|(_, snip)| snip.scope == SnippetScope::Expr).for_each(
+ |(trigger, snippet)| {
+ let imports = match snippet.imports(ctx) {
+ Some(imports) => imports,
+ None => return,
+ };
+ let body = snippet.postfix_snippet(receiver_text);
+ let mut builder =
+ postfix_snippet(trigger, snippet.description.as_deref().unwrap_or_default(), &body);
+ builder.documentation(Documentation::new(format!("```rust\n{}\n```", body)));
+ for import in imports.into_iter() {
+ builder.add_import(import);
+ }
+ builder.add_to(acc);
+ },
+ );
+ None
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+
+ use crate::{
+ tests::{check_edit, check_edit_with_config, completion_list, TEST_CONFIG},
+ CompletionConfig, Snippet,
+ };
+
+ fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual)
+ }
+
+ #[test]
+ fn postfix_completion_works_for_trivial_path_expression() {
+ check(
+ r#"
+fn main() {
+ let bar = true;
+ bar.$0
+}
+"#,
+ expect![[r#"
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn if if expr {}
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn not !expr
+ sn ref &expr
+ sn refm &mut expr
+ sn while while expr {}
+ "#]],
+ );
+ }
+
+ #[test]
+ fn postfix_completion_works_for_function_calln() {
+ check(
+ r#"
+fn foo(elt: bool) -> bool {
+ !elt
+}
+
+fn main() {
+ let bar = true;
+ foo(bar.$0)
+}
+"#,
+ expect![[r#"
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn if if expr {}
+ sn match match expr {}
+ sn not !expr
+ sn ref &expr
+ sn refm &mut expr
+ sn while while expr {}
+ "#]],
+ );
+ }
+
+ #[test]
+ fn postfix_type_filtering() {
+ check(
+ r#"
+fn main() {
+ let bar: u8 = 12;
+ bar.$0
+}
+"#,
+ expect![[r#"
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn ref &expr
+ sn refm &mut expr
+ "#]],
+ )
+ }
+
+ #[test]
+ fn let_middle_block() {
+ check(
+ r#"
+fn main() {
+ baz.l$0
+ res
+}
+"#,
+ expect![[r#"
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn if if expr {}
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn not !expr
+ sn ref &expr
+ sn refm &mut expr
+ sn while while expr {}
+ "#]],
+ );
+ }
+
+ #[test]
+ fn option_iflet() {
+ check_edit(
+ "ifl",
+ r#"
+//- minicore: option
+fn main() {
+ let bar = Some(true);
+ bar.$0
+}
+"#,
+ r#"
+fn main() {
+ let bar = Some(true);
+ if let Some($1) = bar {
+ $0
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn result_match() {
+ check_edit(
+ "match",
+ r#"
+//- minicore: result
+fn main() {
+ let bar = Ok(true);
+ bar.$0
+}
+"#,
+ r#"
+fn main() {
+ let bar = Ok(true);
+ match bar {
+ Ok(${1:_}) => {$2},
+ Err(${3:_}) => {$0},
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn postfix_completion_works_for_ambiguous_float_literal() {
+ check_edit("refm", r#"fn main() { 42.$0 }"#, r#"fn main() { &mut 42 }"#)
+ }
+
+ #[test]
+ fn works_in_simple_macro() {
+ check_edit(
+ "dbg",
+ r#"
+macro_rules! m { ($e:expr) => { $e } }
+fn main() {
+ let bar: u8 = 12;
+ m!(bar.d$0)
+}
+"#,
+ r#"
+macro_rules! m { ($e:expr) => { $e } }
+fn main() {
+ let bar: u8 = 12;
+ m!(dbg!(bar))
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn postfix_completion_for_references() {
+ check_edit("dbg", r#"fn main() { &&42.$0 }"#, r#"fn main() { dbg!(&&42) }"#);
+ check_edit("refm", r#"fn main() { &&42.$0 }"#, r#"fn main() { &&&mut 42 }"#);
+ check_edit(
+ "ifl",
+ r#"
+//- minicore: option
+fn main() {
+ let bar = &Some(true);
+ bar.$0
+}
+"#,
+ r#"
+fn main() {
+ let bar = &Some(true);
+ if let Some($1) = bar {
+ $0
+}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn custom_postfix_completion() {
+ let config = CompletionConfig {
+ snippets: vec![Snippet::new(
+ &[],
+ &["break".into()],
+ &["ControlFlow::Break(${receiver})".into()],
+ "",
+ &["core::ops::ControlFlow".into()],
+ crate::SnippetScope::Expr,
+ )
+ .unwrap()],
+ ..TEST_CONFIG
+ };
+
+ check_edit_with_config(
+ config.clone(),
+ "break",
+ r#"
+//- minicore: try
+fn main() { 42.$0 }
+"#,
+ r#"
+use core::ops::ControlFlow;
+
+fn main() { ControlFlow::Break(42) }
+"#,
+ );
+
+ // The receiver texts should be escaped, see comments in `get_receiver_text()`
+ // for detail.
+ //
+ // Note that the last argument is what *lsp clients would see* rather than
+ // what users would see. Unescaping happens thereafter.
+ check_edit_with_config(
+ config.clone(),
+ "break",
+ r#"
+//- minicore: try
+fn main() { '\\'.$0 }
+"#,
+ r#"
+use core::ops::ControlFlow;
+
+fn main() { ControlFlow::Break('\\\\') }
+"#,
+ );
+
+ check_edit_with_config(
+ config.clone(),
+ "break",
+ r#"
+//- minicore: try
+fn main() {
+ match true {
+ true => "${1:placeholder}",
+ false => "\$",
+ }.$0
+}
+"#,
+ r#"
+use core::ops::ControlFlow;
+
+fn main() {
+ ControlFlow::Break(match true {
+ true => "\${1:placeholder}",
+ false => "\\\$",
+ })
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn postfix_completion_for_format_like_strings() {
+ check_edit(
+ "format",
+ r#"fn main() { "{some_var:?}".$0 }"#,
+ r#"fn main() { format!("{:?}", some_var) }"#,
+ );
+ check_edit(
+ "panic",
+ r#"fn main() { "Panic with {a}".$0 }"#,
+ r#"fn main() { panic!("Panic with {}", a) }"#,
+ );
+ check_edit(
+ "println",
+ r#"fn main() { "{ 2+2 } { SomeStruct { val: 1, other: 32 } :?}".$0 }"#,
+ r#"fn main() { println!("{} {:?}", 2+2, SomeStruct { val: 1, other: 32 }) }"#,
+ );
+ check_edit(
+ "loge",
+ r#"fn main() { "{2+2}".$0 }"#,
+ r#"fn main() { log::error!("{}", 2+2) }"#,
+ );
+ check_edit(
+ "logt",
+ r#"fn main() { "{2+2}".$0 }"#,
+ r#"fn main() { log::trace!("{}", 2+2) }"#,
+ );
+ check_edit(
+ "logd",
+ r#"fn main() { "{2+2}".$0 }"#,
+ r#"fn main() { log::debug!("{}", 2+2) }"#,
+ );
+ check_edit("logi", r#"fn main() { "{2+2}".$0 }"#, r#"fn main() { log::info!("{}", 2+2) }"#);
+ check_edit("logw", r#"fn main() { "{2+2}".$0 }"#, r#"fn main() { log::warn!("{}", 2+2) }"#);
+ check_edit(
+ "loge",
+ r#"fn main() { "{2+2}".$0 }"#,
+ r#"fn main() { log::error!("{}", 2+2) }"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs
new file mode 100644
index 000000000..6b94347e0
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs
@@ -0,0 +1,311 @@
+// Feature: Format String Completion
+//
+// `"Result {result} is {2 + 2}"` is expanded to the `"Result {} is {}", result, 2 + 2`.
+//
+// The following postfix snippets are available:
+//
+// * `format` -> `format!(...)`
+// * `panic` -> `panic!(...)`
+// * `println` -> `println!(...)`
+// * `log`:
+// ** `logd` -> `log::debug!(...)`
+// ** `logt` -> `log::trace!(...)`
+// ** `logi` -> `log::info!(...)`
+// ** `logw` -> `log::warn!(...)`
+// ** `loge` -> `log::error!(...)`
+//
+// image::https://user-images.githubusercontent.com/48062697/113020656-b560f500-917a-11eb-87de-02991f61beb8.gif[]
+
+use ide_db::SnippetCap;
+use syntax::ast::{self, AstToken};
+
+use crate::{
+ completions::postfix::build_postfix_snippet_builder, context::CompletionContext, Completions,
+};
+
+/// Mapping ("postfix completion item" => "macro to use")
+static KINDS: &[(&str, &str)] = &[
+ ("format", "format!"),
+ ("panic", "panic!"),
+ ("println", "println!"),
+ ("eprintln", "eprintln!"),
+ ("logd", "log::debug!"),
+ ("logt", "log::trace!"),
+ ("logi", "log::info!"),
+ ("logw", "log::warn!"),
+ ("loge", "log::error!"),
+];
+
+pub(crate) fn add_format_like_completions(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ dot_receiver: &ast::Expr,
+ cap: SnippetCap,
+ receiver_text: &ast::String,
+) {
+ let input = match string_literal_contents(receiver_text) {
+ // It's not a string literal, do not parse input.
+ Some(input) => input,
+ None => return,
+ };
+
+ let postfix_snippet = match build_postfix_snippet_builder(ctx, cap, dot_receiver) {
+ Some(it) => it,
+ None => return,
+ };
+ let mut parser = FormatStrParser::new(input);
+
+ if parser.parse().is_ok() {
+ for (label, macro_name) in KINDS {
+ let snippet = parser.to_suggestion(macro_name);
+
+ postfix_snippet(label, macro_name, &snippet).add_to(acc);
+ }
+ }
+}
+
+/// Checks whether provided item is a string literal.
+fn string_literal_contents(item: &ast::String) -> Option<String> {
+ let item = item.text();
+ if item.len() >= 2 && item.starts_with('\"') && item.ends_with('\"') {
+ return Some(item[1..item.len() - 1].to_owned());
+ }
+
+ None
+}
+
+/// Parser for a format-like string. It is more allowing in terms of string contents,
+/// as we expect variable placeholders to be filled with expressions.
+#[derive(Debug)]
+pub(crate) struct FormatStrParser {
+ input: String,
+ output: String,
+ extracted_expressions: Vec<String>,
+ state: State,
+ parsed: bool,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+enum State {
+ NotExpr,
+ MaybeExpr,
+ Expr,
+ MaybeIncorrect,
+ FormatOpts,
+}
+
+impl FormatStrParser {
+ pub(crate) fn new(input: String) -> Self {
+ Self {
+ input,
+ output: String::new(),
+ extracted_expressions: Vec::new(),
+ state: State::NotExpr,
+ parsed: false,
+ }
+ }
+
+ pub(crate) fn parse(&mut self) -> Result<(), ()> {
+ let mut current_expr = String::new();
+
+ let mut placeholder_id = 1;
+
+ // Count of open braces inside of an expression.
+ // We assume that user knows what they're doing, thus we treat it like a correct pattern, e.g.
+ // "{MyStruct { val_a: 0, val_b: 1 }}".
+ let mut inexpr_open_count = 0;
+
+ // We need to escape '\' and '$'. See the comments on `get_receiver_text()` for detail.
+ let mut chars = self.input.chars().peekable();
+ while let Some(chr) = chars.next() {
+ match (self.state, chr) {
+ (State::NotExpr, '{') => {
+ self.output.push(chr);
+ self.state = State::MaybeExpr;
+ }
+ (State::NotExpr, '}') => {
+ self.output.push(chr);
+ self.state = State::MaybeIncorrect;
+ }
+ (State::NotExpr, _) => {
+ if matches!(chr, '\\' | '$') {
+ self.output.push('\\');
+ }
+ self.output.push(chr);
+ }
+ (State::MaybeIncorrect, '}') => {
+ // It's okay, we met "}}".
+ self.output.push(chr);
+ self.state = State::NotExpr;
+ }
+ (State::MaybeIncorrect, _) => {
+ // Error in the string.
+ return Err(());
+ }
+ (State::MaybeExpr, '{') => {
+ self.output.push(chr);
+ self.state = State::NotExpr;
+ }
+ (State::MaybeExpr, '}') => {
+ // This is an empty sequence '{}'. Replace it with placeholder.
+ self.output.push(chr);
+ self.extracted_expressions.push(format!("${}", placeholder_id));
+ placeholder_id += 1;
+ self.state = State::NotExpr;
+ }
+ (State::MaybeExpr, _) => {
+ if matches!(chr, '\\' | '$') {
+ current_expr.push('\\');
+ }
+ current_expr.push(chr);
+ self.state = State::Expr;
+ }
+ (State::Expr, '}') => {
+ if inexpr_open_count == 0 {
+ self.output.push(chr);
+ self.extracted_expressions.push(current_expr.trim().into());
+ current_expr = String::new();
+ self.state = State::NotExpr;
+ } else {
+ // We're closing one brace met before inside of the expression.
+ current_expr.push(chr);
+ inexpr_open_count -= 1;
+ }
+ }
+ (State::Expr, ':') if chars.peek().copied() == Some(':') => {
+ // path seperator
+ current_expr.push_str("::");
+ chars.next();
+ }
+ (State::Expr, ':') => {
+ if inexpr_open_count == 0 {
+ // We're outside of braces, thus assume that it's a specifier, like "{Some(value):?}"
+ self.output.push(chr);
+ self.extracted_expressions.push(current_expr.trim().into());
+ current_expr = String::new();
+ self.state = State::FormatOpts;
+ } else {
+ // We're inside of braced expression, assume that it's a struct field name/value delimeter.
+ current_expr.push(chr);
+ }
+ }
+ (State::Expr, '{') => {
+ current_expr.push(chr);
+ inexpr_open_count += 1;
+ }
+ (State::Expr, _) => {
+ if matches!(chr, '\\' | '$') {
+ current_expr.push('\\');
+ }
+ current_expr.push(chr);
+ }
+ (State::FormatOpts, '}') => {
+ self.output.push(chr);
+ self.state = State::NotExpr;
+ }
+ (State::FormatOpts, _) => {
+ if matches!(chr, '\\' | '$') {
+ self.output.push('\\');
+ }
+ self.output.push(chr);
+ }
+ }
+ }
+
+ if self.state != State::NotExpr {
+ return Err(());
+ }
+
+ self.parsed = true;
+ Ok(())
+ }
+
+ pub(crate) fn to_suggestion(&self, macro_name: &str) -> String {
+ assert!(self.parsed, "Attempt to get a suggestion from not parsed expression");
+
+ let expressions_as_string = self.extracted_expressions.join(", ");
+ format!(r#"{}("{}", {})"#, macro_name, self.output, expressions_as_string)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use expect_test::{expect, Expect};
+
+ fn check(input: &str, expect: &Expect) {
+ let mut parser = FormatStrParser::new((*input).to_owned());
+ let outcome_repr = if parser.parse().is_ok() {
+ // Parsing should be OK, expected repr is "string; expr_1, expr_2".
+ if parser.extracted_expressions.is_empty() {
+ parser.output
+ } else {
+ format!("{}; {}", parser.output, parser.extracted_expressions.join(", "))
+ }
+ } else {
+ // Parsing should fail, expected repr is "-".
+ "-".to_owned()
+ };
+
+ expect.assert_eq(&outcome_repr);
+ }
+
+ #[test]
+ fn format_str_parser() {
+ let test_vector = &[
+ ("no expressions", expect![["no expressions"]]),
+ (r"no expressions with \$0$1", expect![r"no expressions with \\\$0\$1"]),
+ ("{expr} is {2 + 2}", expect![["{} is {}; expr, 2 + 2"]]),
+ ("{expr:?}", expect![["{:?}; expr"]]),
+ ("{expr:1$}", expect![[r"{:1\$}; expr"]]),
+ ("{$0}", expect![[r"{}; \$0"]]),
+ ("{malformed", expect![["-"]]),
+ ("malformed}", expect![["-"]]),
+ ("{{correct", expect![["{{correct"]]),
+ ("correct}}", expect![["correct}}"]]),
+ ("{correct}}}", expect![["{}}}; correct"]]),
+ ("{correct}}}}}", expect![["{}}}}}; correct"]]),
+ ("{incorrect}}", expect![["-"]]),
+ ("placeholders {} {}", expect![["placeholders {} {}; $1, $2"]]),
+ ("mixed {} {2 + 2} {}", expect![["mixed {} {} {}; $1, 2 + 2, $2"]]),
+ (
+ "{SomeStruct { val_a: 0, val_b: 1 }}",
+ expect![["{}; SomeStruct { val_a: 0, val_b: 1 }"]],
+ ),
+ ("{expr:?} is {2.32f64:.5}", expect![["{:?} is {:.5}; expr, 2.32f64"]]),
+ (
+ "{SomeStruct { val_a: 0, val_b: 1 }:?}",
+ expect![["{:?}; SomeStruct { val_a: 0, val_b: 1 }"]],
+ ),
+ ("{ 2 + 2 }", expect![["{}; 2 + 2"]]),
+ ("{strsim::jaro_winkle(a)}", expect![["{}; strsim::jaro_winkle(a)"]]),
+ ("{foo::bar::baz()}", expect![["{}; foo::bar::baz()"]]),
+ ("{foo::bar():?}", expect![["{:?}; foo::bar()"]]),
+ ];
+
+ for (input, output) in test_vector {
+ check(input, output)
+ }
+ }
+
+ #[test]
+ fn test_into_suggestion() {
+ let test_vector = &[
+ ("println!", "{}", r#"println!("{}", $1)"#),
+ ("eprintln!", "{}", r#"eprintln!("{}", $1)"#),
+ (
+ "log::info!",
+ "{} {expr} {} {2 + 2}",
+ r#"log::info!("{} {} {} {}", $1, expr, $2, 2 + 2)"#,
+ ),
+ ("format!", "{expr:?}", r#"format!("{:?}", expr)"#),
+ ];
+
+ for (kind, input, output) in test_vector {
+ let mut parser = FormatStrParser::new((*input).to_owned());
+ parser.parse().expect("Parsing must succeed");
+
+ assert_eq!(&parser.to_suggestion(*kind), output);
+ }
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/record.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/record.rs
new file mode 100644
index 000000000..1c9042390
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/record.rs
@@ -0,0 +1,369 @@
+//! Complete fields in record literals and patterns.
+use ide_db::SymbolKind;
+use syntax::ast::{self, Expr};
+
+use crate::{
+ context::{DotAccess, DotAccessKind, ExprCtx, PathCompletionCtx, PatternContext, Qualified},
+ CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance,
+ CompletionRelevancePostfixMatch, Completions,
+};
+
+pub(crate) fn complete_record_pattern_fields(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ pattern_ctx: &PatternContext,
+) {
+ if let PatternContext { record_pat: Some(record_pat), .. } = pattern_ctx {
+ complete_fields(acc, ctx, ctx.sema.record_pattern_missing_fields(record_pat));
+ }
+}
+
+pub(crate) fn complete_record_expr_fields(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ record_expr: &ast::RecordExpr,
+ &dot_prefix: &bool,
+) {
+ let ty = ctx.sema.type_of_expr(&Expr::RecordExpr(record_expr.clone()));
+
+ let missing_fields = match ty.as_ref().and_then(|t| t.original.as_adt()) {
+ Some(hir::Adt::Union(un)) => {
+ // ctx.sema.record_literal_missing_fields will always return
+ // an empty Vec on a union literal. This is normally
+ // reasonable, but here we'd like to present the full list
+ // of fields if the literal is empty.
+ let were_fields_specified =
+ record_expr.record_expr_field_list().and_then(|fl| fl.fields().next()).is_some();
+
+ match were_fields_specified {
+ false => un.fields(ctx.db).into_iter().map(|f| (f, f.ty(ctx.db))).collect(),
+ true => return,
+ }
+ }
+ _ => {
+ let missing_fields = ctx.sema.record_literal_missing_fields(record_expr);
+ add_default_update(acc, ctx, ty, &missing_fields);
+ if dot_prefix {
+ let mut item =
+ CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), "..");
+ item.insert_text(".");
+ item.add_to(acc);
+ return;
+ }
+ missing_fields
+ }
+ };
+ complete_fields(acc, ctx, missing_fields);
+}
+
+// FIXME: This should probably be part of complete_path_expr
+pub(crate) fn complete_record_expr_func_update(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ expr_ctx: &ExprCtx,
+) {
+ if !matches!(path_ctx.qualified, Qualified::No) {
+ return;
+ }
+ if let ExprCtx { is_func_update: Some(record_expr), .. } = expr_ctx {
+ let ty = ctx.sema.type_of_expr(&Expr::RecordExpr(record_expr.clone()));
+
+ match ty.as_ref().and_then(|t| t.original.as_adt()) {
+ Some(hir::Adt::Union(_)) => (),
+ _ => {
+ let missing_fields = ctx.sema.record_literal_missing_fields(record_expr);
+ add_default_update(acc, ctx, ty, &missing_fields);
+ }
+ };
+ }
+}
+
+fn add_default_update(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ ty: Option<hir::TypeInfo>,
+ missing_fields: &[(hir::Field, hir::Type)],
+) {
+ let default_trait = ctx.famous_defs().core_default_Default();
+ let impl_default_trait = default_trait
+ .zip(ty.as_ref())
+ .map_or(false, |(default_trait, ty)| ty.original.impls_trait(ctx.db, default_trait, &[]));
+ if impl_default_trait && !missing_fields.is_empty() {
+ // FIXME: This should make use of scope_def like completions so we get all the other goodies
+ let completion_text = "..Default::default()";
+ let mut item = CompletionItem::new(SymbolKind::Field, ctx.source_range(), completion_text);
+ let completion_text =
+ completion_text.strip_prefix(ctx.token.text()).unwrap_or(completion_text);
+ item.insert_text(completion_text).set_relevance(CompletionRelevance {
+ postfix_match: Some(CompletionRelevancePostfixMatch::Exact),
+ ..Default::default()
+ });
+ item.add_to(acc);
+ }
+}
+
+fn complete_fields(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ missing_fields: Vec<(hir::Field, hir::Type)>,
+) {
+ for (field, ty) in missing_fields {
+ acc.add_field(
+ ctx,
+ &DotAccess {
+ receiver: None,
+ receiver_ty: None,
+ kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal: false },
+ },
+ None,
+ field,
+ &ty,
+ );
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::check_edit;
+
+ #[test]
+ fn literal_struct_completion_edit() {
+ check_edit(
+ "FooDesc {…}",
+ r#"
+struct FooDesc { pub bar: bool }
+
+fn create_foo(foo_desc: &FooDesc) -> () { () }
+
+fn baz() {
+ let foo = create_foo(&$0);
+}
+ "#,
+ r#"
+struct FooDesc { pub bar: bool }
+
+fn create_foo(foo_desc: &FooDesc) -> () { () }
+
+fn baz() {
+ let foo = create_foo(&FooDesc { bar: ${1:()} }$0);
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn literal_struct_impl_self_completion() {
+ check_edit(
+ "Self {…}",
+ r#"
+struct Foo {
+ bar: u64,
+}
+
+impl Foo {
+ fn new() -> Foo {
+ Self$0
+ }
+}
+ "#,
+ r#"
+struct Foo {
+ bar: u64,
+}
+
+impl Foo {
+ fn new() -> Foo {
+ Self { bar: ${1:()} }$0
+ }
+}
+ "#,
+ );
+
+ check_edit(
+ "Self(…)",
+ r#"
+mod submod {
+ pub struct Foo(pub u64);
+}
+
+impl submod::Foo {
+ fn new() -> submod::Foo {
+ Self$0
+ }
+}
+ "#,
+ r#"
+mod submod {
+ pub struct Foo(pub u64);
+}
+
+impl submod::Foo {
+ fn new() -> submod::Foo {
+ Self(${1:()})$0
+ }
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn literal_struct_completion_from_sub_modules() {
+ check_edit(
+ "submod::Struct {…}",
+ r#"
+mod submod {
+ pub struct Struct {
+ pub a: u64,
+ }
+}
+
+fn f() -> submod::Struct {
+ Stru$0
+}
+ "#,
+ r#"
+mod submod {
+ pub struct Struct {
+ pub a: u64,
+ }
+}
+
+fn f() -> submod::Struct {
+ submod::Struct { a: ${1:()} }$0
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn literal_struct_complexion_module() {
+ check_edit(
+ "FooDesc {…}",
+ r#"
+mod _69latrick {
+ pub struct FooDesc { pub six: bool, pub neuf: Vec<String>, pub bar: bool }
+ pub fn create_foo(foo_desc: &FooDesc) -> () { () }
+}
+
+fn baz() {
+ use _69latrick::*;
+
+ let foo = create_foo(&$0);
+}
+ "#,
+ r#"
+mod _69latrick {
+ pub struct FooDesc { pub six: bool, pub neuf: Vec<String>, pub bar: bool }
+ pub fn create_foo(foo_desc: &FooDesc) -> () { () }
+}
+
+fn baz() {
+ use _69latrick::*;
+
+ let foo = create_foo(&FooDesc { six: ${1:()}, neuf: ${2:()}, bar: ${3:()} }$0);
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn default_completion_edit() {
+ check_edit(
+ "..Default::default()",
+ r#"
+//- minicore: default
+struct Struct { foo: u32, bar: usize }
+
+impl Default for Struct {
+ fn default() -> Self {}
+}
+
+fn foo() {
+ let other = Struct {
+ foo: 5,
+ .$0
+ };
+}
+"#,
+ r#"
+struct Struct { foo: u32, bar: usize }
+
+impl Default for Struct {
+ fn default() -> Self {}
+}
+
+fn foo() {
+ let other = Struct {
+ foo: 5,
+ ..Default::default()
+ };
+}
+"#,
+ );
+ check_edit(
+ "..Default::default()",
+ r#"
+//- minicore: default
+struct Struct { foo: u32, bar: usize }
+
+impl Default for Struct {
+ fn default() -> Self {}
+}
+
+fn foo() {
+ let other = Struct {
+ foo: 5,
+ $0
+ };
+}
+"#,
+ r#"
+struct Struct { foo: u32, bar: usize }
+
+impl Default for Struct {
+ fn default() -> Self {}
+}
+
+fn foo() {
+ let other = Struct {
+ foo: 5,
+ ..Default::default()
+ };
+}
+"#,
+ );
+ check_edit(
+ "..Default::default()",
+ r#"
+//- minicore: default
+struct Struct { foo: u32, bar: usize }
+
+impl Default for Struct {
+ fn default() -> Self {}
+}
+
+fn foo() {
+ let other = Struct {
+ foo: 5,
+ ..$0
+ };
+}
+"#,
+ r#"
+struct Struct { foo: u32, bar: usize }
+
+impl Default for Struct {
+ fn default() -> Self {}
+}
+
+fn foo() {
+ let other = Struct {
+ foo: 5,
+ ..Default::default()
+ };
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/snippet.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/snippet.rs
new file mode 100644
index 000000000..66adb4286
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/snippet.rs
@@ -0,0 +1,189 @@
+//! This file provides snippet completions, like `pd` => `eprintln!(...)`.
+
+use hir::Documentation;
+use ide_db::{imports::insert_use::ImportScope, SnippetCap};
+
+use crate::{
+ context::{ExprCtx, ItemListKind, PathCompletionCtx, Qualified},
+ item::Builder,
+ CompletionContext, CompletionItem, CompletionItemKind, Completions, SnippetScope,
+};
+
+pub(crate) fn complete_expr_snippet(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ &ExprCtx { in_block_expr, .. }: &ExprCtx,
+) {
+ if !matches!(path_ctx.qualified, Qualified::No) {
+ return;
+ }
+ if !ctx.qualifier_ctx.none() {
+ return;
+ }
+
+ let cap = match ctx.config.snippet_cap {
+ Some(it) => it,
+ None => return,
+ };
+
+ if !ctx.config.snippets.is_empty() {
+ add_custom_completions(acc, ctx, cap, SnippetScope::Expr);
+ }
+
+ if in_block_expr {
+ snippet(ctx, cap, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc);
+ snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc);
+ let item = snippet(
+ ctx,
+ cap,
+ "macro_rules",
+ "\
+macro_rules! $1 {
+ ($2) => {
+ $0
+ };
+}",
+ );
+ item.add_to(acc);
+ }
+}
+
+pub(crate) fn complete_item_snippet(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ kind: &ItemListKind,
+) {
+ if !matches!(path_ctx.qualified, Qualified::No) {
+ return;
+ }
+ if !ctx.qualifier_ctx.none() {
+ return;
+ }
+ let cap = match ctx.config.snippet_cap {
+ Some(it) => it,
+ None => return,
+ };
+
+ if !ctx.config.snippets.is_empty() {
+ add_custom_completions(acc, ctx, cap, SnippetScope::Item);
+ }
+
+ // Test-related snippets shouldn't be shown in blocks.
+ if let ItemListKind::SourceFile | ItemListKind::Module = kind {
+ let mut item = snippet(
+ ctx,
+ cap,
+ "tmod (Test module)",
+ "\
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn ${1:test_name}() {
+ $0
+ }
+}",
+ );
+ item.lookup_by("tmod");
+ item.add_to(acc);
+
+ let mut item = snippet(
+ ctx,
+ cap,
+ "tfn (Test function)",
+ "\
+#[test]
+fn ${1:feature}() {
+ $0
+}",
+ );
+ item.lookup_by("tfn");
+ item.add_to(acc);
+
+ let item = snippet(
+ ctx,
+ cap,
+ "macro_rules",
+ "\
+macro_rules! $1 {
+ ($2) => {
+ $0
+ };
+}",
+ );
+ item.add_to(acc);
+ }
+}
+
+fn snippet(ctx: &CompletionContext<'_>, cap: SnippetCap, label: &str, snippet: &str) -> Builder {
+ let mut item = CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), label);
+ item.insert_snippet(cap, snippet);
+ item
+}
+
+fn add_custom_completions(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ cap: SnippetCap,
+ scope: SnippetScope,
+) -> Option<()> {
+ if ImportScope::find_insert_use_container(&ctx.token.parent()?, &ctx.sema).is_none() {
+ return None;
+ }
+ ctx.config.prefix_snippets().filter(|(_, snip)| snip.scope == scope).for_each(
+ |(trigger, snip)| {
+ let imports = match snip.imports(ctx) {
+ Some(imports) => imports,
+ None => return,
+ };
+ let body = snip.snippet();
+ let mut builder = snippet(ctx, cap, trigger, &body);
+ builder.documentation(Documentation::new(format!("```rust\n{}\n```", body)));
+ for import in imports.into_iter() {
+ builder.add_import(import);
+ }
+ builder.set_detail(snip.description.clone());
+ builder.add_to(acc);
+ },
+ );
+ None
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{
+ tests::{check_edit_with_config, TEST_CONFIG},
+ CompletionConfig, Snippet,
+ };
+
+ #[test]
+ fn custom_snippet_completion() {
+ check_edit_with_config(
+ CompletionConfig {
+ snippets: vec![Snippet::new(
+ &["break".into()],
+ &[],
+ &["ControlFlow::Break(())".into()],
+ "",
+ &["core::ops::ControlFlow".into()],
+ crate::SnippetScope::Expr,
+ )
+ .unwrap()],
+ ..TEST_CONFIG
+ },
+ "break",
+ r#"
+//- minicore: try
+fn main() { $0 }
+"#,
+ r#"
+use core::ops::ControlFlow;
+
+fn main() { ControlFlow::Break(()) }
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/type.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/type.rs
new file mode 100644
index 000000000..8f9db2f94
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/type.rs
@@ -0,0 +1,246 @@
+//! Completion of names from the current scope in type position.
+
+use hir::{HirDisplay, ScopeDef};
+use syntax::{ast, AstNode, SyntaxKind};
+
+use crate::{
+ context::{PathCompletionCtx, Qualified, TypeAscriptionTarget, TypeLocation},
+ render::render_type_inference,
+ CompletionContext, Completions,
+};
+
+pub(crate) fn complete_type_path(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx @ PathCompletionCtx { qualified, .. }: &PathCompletionCtx,
+ location: &TypeLocation,
+) {
+ let _p = profile::span("complete_type_path");
+
+ let scope_def_applicable = |def| {
+ use hir::{GenericParam::*, ModuleDef::*};
+ match def {
+ ScopeDef::GenericParam(LifetimeParam(_)) | ScopeDef::Label(_) => false,
+ // no values in type places
+ ScopeDef::ModuleDef(Function(_) | Variant(_) | Static(_)) | ScopeDef::Local(_) => false,
+ // unless its a constant in a generic arg list position
+ ScopeDef::ModuleDef(Const(_)) | ScopeDef::GenericParam(ConstParam(_)) => {
+ matches!(location, TypeLocation::GenericArgList(_))
+ }
+ ScopeDef::ImplSelfType(_) => {
+ !matches!(location, TypeLocation::ImplTarget | TypeLocation::ImplTrait)
+ }
+ // Don't suggest attribute macros and derives.
+ ScopeDef::ModuleDef(Macro(mac)) => mac.is_fn_like(ctx.db),
+ // Type things are fine
+ ScopeDef::ModuleDef(BuiltinType(_) | Adt(_) | Module(_) | Trait(_) | TypeAlias(_))
+ | ScopeDef::AdtSelfType(_)
+ | ScopeDef::Unknown
+ | ScopeDef::GenericParam(TypeParam(_)) => true,
+ }
+ };
+
+ let add_assoc_item = |acc: &mut Completions, item| match item {
+ hir::AssocItem::Const(ct) if matches!(location, TypeLocation::GenericArgList(_)) => {
+ acc.add_const(ctx, ct)
+ }
+ hir::AssocItem::Function(_) | hir::AssocItem::Const(_) => (),
+ hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
+ };
+
+ match qualified {
+ Qualified::TypeAnchor { ty: None, trait_: None } => ctx
+ .traits_in_scope()
+ .iter()
+ .flat_map(|&it| hir::Trait::from(it).items(ctx.sema.db))
+ .for_each(|item| add_assoc_item(acc, item)),
+ Qualified::TypeAnchor { trait_: Some(trait_), .. } => {
+ trait_.items(ctx.sema.db).into_iter().for_each(|item| add_assoc_item(acc, item))
+ }
+ Qualified::TypeAnchor { ty: Some(ty), trait_: None } => {
+ ctx.iterate_path_candidates(&ty, |item| {
+ add_assoc_item(acc, item);
+ });
+
+ // Iterate assoc types separately
+ ty.iterate_assoc_items(ctx.db, ctx.krate, |item| {
+ if let hir::AssocItem::TypeAlias(ty) = item {
+ acc.add_type_alias(ctx, ty)
+ }
+ None::<()>
+ });
+ }
+ Qualified::With { resolution: None, .. } => {}
+ Qualified::With { resolution: Some(resolution), .. } => {
+ // Add associated types on type parameters and `Self`.
+ ctx.scope.assoc_type_shorthand_candidates(resolution, |_, alias| {
+ acc.add_type_alias(ctx, alias);
+ None::<()>
+ });
+
+ match resolution {
+ hir::PathResolution::Def(hir::ModuleDef::Module(module)) => {
+ let module_scope = module.scope(ctx.db, Some(ctx.module));
+ for (name, def) in module_scope {
+ if scope_def_applicable(def) {
+ acc.add_path_resolution(ctx, path_ctx, name, def);
+ }
+ }
+ }
+ hir::PathResolution::Def(
+ def @ (hir::ModuleDef::Adt(_)
+ | hir::ModuleDef::TypeAlias(_)
+ | hir::ModuleDef::BuiltinType(_)),
+ ) => {
+ let ty = match def {
+ hir::ModuleDef::Adt(adt) => adt.ty(ctx.db),
+ hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db),
+ hir::ModuleDef::BuiltinType(builtin) => builtin.ty(ctx.db),
+ _ => return,
+ };
+
+ // XXX: For parity with Rust bug #22519, this does not complete Ty::AssocType.
+ // (where AssocType is defined on a trait, not an inherent impl)
+
+ ctx.iterate_path_candidates(&ty, |item| {
+ add_assoc_item(acc, item);
+ });
+
+ // Iterate assoc types separately
+ ty.iterate_assoc_items(ctx.db, ctx.krate, |item| {
+ if let hir::AssocItem::TypeAlias(ty) = item {
+ acc.add_type_alias(ctx, ty)
+ }
+ None::<()>
+ });
+ }
+ hir::PathResolution::Def(hir::ModuleDef::Trait(t)) => {
+ // Handles `Trait::assoc` as well as `<Ty as Trait>::assoc`.
+ for item in t.items(ctx.db) {
+ add_assoc_item(acc, item);
+ }
+ }
+ hir::PathResolution::TypeParam(_) | hir::PathResolution::SelfType(_) => {
+ let ty = match resolution {
+ hir::PathResolution::TypeParam(param) => param.ty(ctx.db),
+ hir::PathResolution::SelfType(impl_def) => impl_def.self_ty(ctx.db),
+ _ => return,
+ };
+
+ ctx.iterate_path_candidates(&ty, |item| {
+ add_assoc_item(acc, item);
+ });
+ }
+ _ => (),
+ }
+ }
+ Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
+ Qualified::No => {
+ match location {
+ TypeLocation::TypeBound => {
+ acc.add_nameref_keywords_with_colon(ctx);
+ ctx.process_all_names(&mut |name, res| {
+ let add_resolution = match res {
+ ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => {
+ mac.is_fn_like(ctx.db)
+ }
+ ScopeDef::ModuleDef(
+ hir::ModuleDef::Trait(_) | hir::ModuleDef::Module(_),
+ ) => true,
+ _ => false,
+ };
+ if add_resolution {
+ acc.add_path_resolution(ctx, path_ctx, name, res);
+ }
+ });
+ return;
+ }
+ TypeLocation::GenericArgList(Some(arg_list)) => {
+ let in_assoc_type_arg = ctx
+ .original_token
+ .parent_ancestors()
+ .any(|node| node.kind() == SyntaxKind::ASSOC_TYPE_ARG);
+
+ if !in_assoc_type_arg {
+ if let Some(path_seg) =
+ arg_list.syntax().parent().and_then(ast::PathSegment::cast)
+ {
+ if path_seg
+ .syntax()
+ .ancestors()
+ .find_map(ast::TypeBound::cast)
+ .is_some()
+ {
+ if let Some(hir::PathResolution::Def(hir::ModuleDef::Trait(
+ trait_,
+ ))) = ctx.sema.resolve_path(&path_seg.parent_path())
+ {
+ let arg_idx = arg_list
+ .generic_args()
+ .filter(|arg| {
+ arg.syntax().text_range().end()
+ < ctx.original_token.text_range().start()
+ })
+ .count();
+
+ let n_required_params =
+ trait_.type_or_const_param_count(ctx.sema.db, true);
+ if arg_idx >= n_required_params {
+ trait_
+ .items_with_supertraits(ctx.sema.db)
+ .into_iter()
+ .for_each(|it| {
+ if let hir::AssocItem::TypeAlias(alias) = it {
+ cov_mark::hit!(
+ complete_assoc_type_in_generics_list
+ );
+ acc.add_type_alias_with_eq(ctx, alias);
+ }
+ });
+
+ let n_params =
+ trait_.type_or_const_param_count(ctx.sema.db, false);
+ if arg_idx >= n_params {
+ return; // only show assoc types
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ _ => {}
+ };
+
+ acc.add_nameref_keywords_with_colon(ctx);
+ ctx.process_all_names(&mut |name, def| {
+ if scope_def_applicable(def) {
+ acc.add_path_resolution(ctx, path_ctx, name, def);
+ }
+ });
+ }
+ }
+}
+
+pub(crate) fn complete_ascribed_type(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ ascription: &TypeAscriptionTarget,
+) -> Option<()> {
+ if !path_ctx.is_trivial_path() {
+ return None;
+ }
+ let x = match ascription {
+ TypeAscriptionTarget::Let(pat) | TypeAscriptionTarget::FnParam(pat) => {
+ ctx.sema.type_of_pat(pat.as_ref()?)
+ }
+ TypeAscriptionTarget::Const(exp) | TypeAscriptionTarget::RetType(exp) => {
+ ctx.sema.type_of_expr(exp.as_ref()?)
+ }
+ }?
+ .adjusted();
+ let ty_string = x.display_source_code(ctx.db, ctx.module.into()).ok()?;
+ acc.add(render_type_inference(ty_string, ctx));
+ None
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/use_.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/use_.rs
new file mode 100644
index 000000000..2555c34aa
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/use_.rs
@@ -0,0 +1,120 @@
+//! Completion for use trees
+
+use hir::ScopeDef;
+use ide_db::{FxHashSet, SymbolKind};
+use syntax::{ast, AstNode};
+
+use crate::{
+ context::{CompletionContext, PathCompletionCtx, Qualified},
+ item::Builder,
+ CompletionItem, CompletionItemKind, CompletionRelevance, Completions,
+};
+
+pub(crate) fn complete_use_path(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx @ PathCompletionCtx { qualified, use_tree_parent, .. }: &PathCompletionCtx,
+ name_ref: &Option<ast::NameRef>,
+) {
+ match qualified {
+ Qualified::With { path, resolution: Some(resolution), super_chain_len } => {
+ acc.add_super_keyword(ctx, *super_chain_len);
+
+ // only show `self` in a new use-tree when the qualifier doesn't end in self
+ let not_preceded_by_self = *use_tree_parent
+ && !matches!(
+ path.segment().and_then(|it| it.kind()),
+ Some(ast::PathSegmentKind::SelfKw)
+ );
+ if not_preceded_by_self {
+ acc.add_keyword(ctx, "self");
+ }
+
+ let mut already_imported_names = FxHashSet::default();
+ if let Some(list) = ctx.token.parent_ancestors().find_map(ast::UseTreeList::cast) {
+ let use_tree = list.parent_use_tree();
+ if use_tree.path().as_ref() == Some(path) {
+ for tree in list.use_trees().filter(|tree| tree.is_simple_path()) {
+ if let Some(name) = tree.path().and_then(|path| path.as_single_name_ref()) {
+ already_imported_names.insert(name.to_string());
+ }
+ }
+ }
+ }
+
+ match resolution {
+ hir::PathResolution::Def(hir::ModuleDef::Module(module)) => {
+ let module_scope = module.scope(ctx.db, Some(ctx.module));
+ let unknown_is_current = |name: &hir::Name| {
+ matches!(
+ name_ref,
+ Some(name_ref) if name_ref.syntax().text() == name.to_smol_str().as_str()
+ )
+ };
+ for (name, def) in module_scope {
+ let is_name_already_imported = name
+ .as_text()
+ .map_or(false, |text| already_imported_names.contains(text.as_str()));
+
+ let add_resolution = match def {
+ ScopeDef::Unknown if unknown_is_current(&name) => {
+ // for `use self::foo$0`, don't suggest `foo` as a completion
+ cov_mark::hit!(dont_complete_current_use);
+ continue;
+ }
+ ScopeDef::ModuleDef(_) | ScopeDef::Unknown => true,
+ _ => false,
+ };
+
+ if add_resolution {
+ let mut builder = Builder::from_resolution(ctx, path_ctx, name, def);
+ builder.set_relevance(CompletionRelevance {
+ is_name_already_imported,
+ ..Default::default()
+ });
+ acc.add(builder.build());
+ }
+ }
+ }
+ hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Enum(e))) => {
+ cov_mark::hit!(enum_plain_qualified_use_tree);
+ acc.add_enum_variants(ctx, path_ctx, *e);
+ }
+ _ => {}
+ }
+ }
+ // fresh use tree with leading colon2, only show crate roots
+ Qualified::Absolute => {
+ cov_mark::hit!(use_tree_crate_roots_only);
+ acc.add_crate_roots(ctx, path_ctx);
+ }
+ // only show modules and non-std enum in a fresh UseTree
+ Qualified::No => {
+ cov_mark::hit!(unqualified_path_selected_only);
+ ctx.process_all_names(&mut |name, res| {
+ match res {
+ ScopeDef::ModuleDef(hir::ModuleDef::Module(module)) => {
+ acc.add_module(ctx, path_ctx, module, name);
+ }
+ ScopeDef::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(e))) => {
+ // exclude prelude enum
+ let is_builtin =
+ res.krate(ctx.db).map_or(false, |krate| krate.is_builtin(ctx.db));
+
+ if !is_builtin {
+ let item = CompletionItem::new(
+ CompletionItemKind::SymbolKind(SymbolKind::Enum),
+ ctx.source_range(),
+ format!("{}::", e.name(ctx.db)),
+ );
+ acc.add(item.build());
+ }
+ }
+ _ => {}
+ };
+ });
+ acc.add_nameref_keywords_with_colon(ctx);
+ }
+ Qualified::TypeAnchor { .. } | Qualified::With { resolution: None, .. } => {}
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/vis.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/vis.rs
new file mode 100644
index 000000000..5e6cf4bf9
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/vis.rs
@@ -0,0 +1,41 @@
+//! Completion for visibility specifiers.
+
+use crate::{
+ context::{CompletionContext, PathCompletionCtx, Qualified},
+ Completions,
+};
+
+pub(crate) fn complete_vis_path(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx @ PathCompletionCtx { qualified, .. }: &PathCompletionCtx,
+ &has_in_token: &bool,
+) {
+ match qualified {
+ Qualified::With {
+ resolution: Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))),
+ super_chain_len,
+ ..
+ } => {
+ // Try completing next child module of the path that is still a parent of the current module
+ let next_towards_current =
+ ctx.module.path_to_root(ctx.db).into_iter().take_while(|it| it != module).last();
+ if let Some(next) = next_towards_current {
+ if let Some(name) = next.name(ctx.db) {
+ cov_mark::hit!(visibility_qualified);
+ acc.add_module(ctx, path_ctx, next, name);
+ }
+ }
+
+ acc.add_super_keyword(ctx, *super_chain_len);
+ }
+ Qualified::Absolute | Qualified::TypeAnchor { .. } | Qualified::With { .. } => {}
+ Qualified::No => {
+ if !has_in_token {
+ cov_mark::hit!(kw_completion_in);
+ acc.add_keyword(ctx, "in");
+ }
+ acc.add_nameref_keywords(ctx);
+ }
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/config.rs b/src/tools/rust-analyzer/crates/ide-completion/src/config.rs
new file mode 100644
index 000000000..80d6af281
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/config.rs
@@ -0,0 +1,41 @@
+//! Settings for tweaking completion.
+//!
+//! The fun thing here is `SnippetCap` -- this type can only be created in this
+//! module, and we use to statically check that we only produce snippet
+//! completions if we are allowed to.
+
+use ide_db::{imports::insert_use::InsertUseConfig, SnippetCap};
+
+use crate::snippet::Snippet;
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct CompletionConfig {
+ pub enable_postfix_completions: bool,
+ pub enable_imports_on_the_fly: bool,
+ pub enable_self_on_the_fly: bool,
+ pub enable_private_editable: bool,
+ pub callable: Option<CallableSnippets>,
+ pub snippet_cap: Option<SnippetCap>,
+ pub insert_use: InsertUseConfig,
+ pub snippets: Vec<Snippet>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum CallableSnippets {
+ FillArguments,
+ AddParentheses,
+}
+
+impl CompletionConfig {
+ pub fn postfix_snippets(&self) -> impl Iterator<Item = (&str, &Snippet)> {
+ self.snippets
+ .iter()
+ .flat_map(|snip| snip.postfix_triggers.iter().map(move |trigger| (&**trigger, snip)))
+ }
+
+ pub fn prefix_snippets(&self) -> impl Iterator<Item = (&str, &Snippet)> {
+ self.snippets
+ .iter()
+ .flat_map(|snip| snip.prefix_triggers.iter().map(move |trigger| (&**trigger, snip)))
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs
new file mode 100644
index 000000000..e35f79d2b
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs
@@ -0,0 +1,639 @@
+//! See `CompletionContext` structure.
+
+mod analysis;
+#[cfg(test)]
+mod tests;
+
+use std::iter;
+
+use base_db::SourceDatabaseExt;
+use hir::{
+ HasAttrs, Local, Name, PathResolution, ScopeDef, Semantics, SemanticsScope, Type, TypeInfo,
+};
+use ide_db::{
+ base_db::{FilePosition, SourceDatabase},
+ famous_defs::FamousDefs,
+ FxHashMap, FxHashSet, RootDatabase,
+};
+use syntax::{
+ ast::{self, AttrKind, NameOrNameRef},
+ AstNode,
+ SyntaxKind::{self, *},
+ SyntaxToken, TextRange, TextSize,
+};
+use text_edit::Indel;
+
+use crate::CompletionConfig;
+
+const COMPLETION_MARKER: &str = "intellijRulezz";
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub(crate) enum PatternRefutability {
+ Refutable,
+ Irrefutable,
+}
+
+#[derive(Debug)]
+pub(crate) enum Visible {
+ Yes,
+ Editable,
+ No,
+}
+
+/// Existing qualifiers for the thing we are currently completing.
+#[derive(Debug, Default)]
+pub(super) struct QualifierCtx {
+ pub(super) unsafe_tok: Option<SyntaxToken>,
+ pub(super) vis_node: Option<ast::Visibility>,
+}
+
+impl QualifierCtx {
+ pub(super) fn none(&self) -> bool {
+ self.unsafe_tok.is_none() && self.vis_node.is_none()
+ }
+}
+
+/// The state of the path we are currently completing.
+#[derive(Debug)]
+pub(crate) struct PathCompletionCtx {
+ /// If this is a call with () already there (or {} in case of record patterns)
+ pub(super) has_call_parens: bool,
+ /// If this has a macro call bang !
+ pub(super) has_macro_bang: bool,
+ /// The qualifier of the current path.
+ pub(super) qualified: Qualified,
+ /// The parent of the path we are completing.
+ pub(super) parent: Option<ast::Path>,
+ /// The path of which we are completing the segment
+ pub(super) path: ast::Path,
+ pub(super) kind: PathKind,
+ /// Whether the path segment has type args or not.
+ pub(super) has_type_args: bool,
+ /// Whether the qualifier comes from a use tree parent or not
+ pub(crate) use_tree_parent: bool,
+}
+
+impl PathCompletionCtx {
+ pub(super) fn is_trivial_path(&self) -> bool {
+ matches!(
+ self,
+ PathCompletionCtx {
+ has_call_parens: false,
+ has_macro_bang: false,
+ qualified: Qualified::No,
+ parent: None,
+ has_type_args: false,
+ ..
+ }
+ )
+ }
+}
+
+/// The kind of path we are completing right now.
+#[derive(Debug, PartialEq, Eq)]
+pub(super) enum PathKind {
+ Expr {
+ expr_ctx: ExprCtx,
+ },
+ Type {
+ location: TypeLocation,
+ },
+ Attr {
+ attr_ctx: AttrCtx,
+ },
+ Derive {
+ existing_derives: ExistingDerives,
+ },
+ /// Path in item position, that is inside an (Assoc)ItemList
+ Item {
+ kind: ItemListKind,
+ },
+ Pat {
+ pat_ctx: PatternContext,
+ },
+ Vis {
+ has_in_token: bool,
+ },
+ Use,
+}
+
+pub(crate) type ExistingDerives = FxHashSet<hir::Macro>;
+
+#[derive(Debug, PartialEq, Eq)]
+pub(crate) struct AttrCtx {
+ pub(crate) kind: AttrKind,
+ pub(crate) annotated_item_kind: Option<SyntaxKind>,
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub(crate) struct ExprCtx {
+ pub(crate) in_block_expr: bool,
+ pub(crate) in_loop_body: bool,
+ pub(crate) after_if_expr: bool,
+ /// Whether this expression is the direct condition of an if or while expression
+ pub(crate) in_condition: bool,
+ pub(crate) incomplete_let: bool,
+ pub(crate) ref_expr_parent: Option<ast::RefExpr>,
+ pub(crate) is_func_update: Option<ast::RecordExpr>,
+ pub(crate) self_param: Option<hir::SelfParam>,
+ pub(crate) innermost_ret_ty: Option<hir::Type>,
+ pub(crate) impl_: Option<ast::Impl>,
+ /// Whether this expression occurs in match arm guard position: before the
+ /// fat arrow token
+ pub(crate) in_match_guard: bool,
+}
+
+/// Original file ast nodes
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub(crate) enum TypeLocation {
+ TupleField,
+ TypeAscription(TypeAscriptionTarget),
+ GenericArgList(Option<ast::GenericArgList>),
+ TypeBound,
+ ImplTarget,
+ ImplTrait,
+ Other,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub(crate) enum TypeAscriptionTarget {
+ Let(Option<ast::Pat>),
+ FnParam(Option<ast::Pat>),
+ RetType(Option<ast::Expr>),
+ Const(Option<ast::Expr>),
+}
+
+/// The kind of item list a [`PathKind::Item`] belongs to.
+#[derive(Debug, PartialEq, Eq)]
+pub(super) enum ItemListKind {
+ SourceFile,
+ Module,
+ Impl,
+ TraitImpl(Option<ast::Impl>),
+ Trait,
+ ExternBlock,
+}
+
+#[derive(Debug)]
+pub(super) enum Qualified {
+ No,
+ With {
+ path: ast::Path,
+ resolution: Option<PathResolution>,
+ /// How many `super` segments are present in the path
+ ///
+ /// This would be None, if path is not solely made of
+ /// `super` segments, e.g.
+ ///
+ /// ```rust
+ /// use super::foo;
+ /// ```
+ ///
+ /// Otherwise it should be Some(count of `super`)
+ super_chain_len: Option<usize>,
+ },
+ /// <_>::
+ TypeAnchor {
+ ty: Option<hir::Type>,
+ trait_: Option<hir::Trait>,
+ },
+ /// Whether the path is an absolute path
+ Absolute,
+}
+
+/// The state of the pattern we are completing.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(super) struct PatternContext {
+ pub(super) refutability: PatternRefutability,
+ pub(super) param_ctx: Option<ParamContext>,
+ pub(super) has_type_ascription: bool,
+ pub(super) parent_pat: Option<ast::Pat>,
+ pub(super) ref_token: Option<SyntaxToken>,
+ pub(super) mut_token: Option<SyntaxToken>,
+ /// The record pattern this name or ref is a field of
+ pub(super) record_pat: Option<ast::RecordPat>,
+ pub(super) impl_: Option<ast::Impl>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(super) struct ParamContext {
+ pub(super) param_list: ast::ParamList,
+ pub(super) param: ast::Param,
+ pub(super) kind: ParamKind,
+}
+
+/// The state of the lifetime we are completing.
+#[derive(Debug)]
+pub(super) struct LifetimeContext {
+ pub(super) lifetime: Option<ast::Lifetime>,
+ pub(super) kind: LifetimeKind,
+}
+
+/// The kind of lifetime we are completing.
+#[derive(Debug)]
+pub(super) enum LifetimeKind {
+ LifetimeParam { is_decl: bool, param: ast::LifetimeParam },
+ Lifetime,
+ LabelRef,
+ LabelDef,
+}
+
+/// The state of the name we are completing.
+#[derive(Debug)]
+pub(super) struct NameContext {
+ #[allow(dead_code)]
+ pub(super) name: Option<ast::Name>,
+ pub(super) kind: NameKind,
+}
+
+/// The kind of the name we are completing.
+#[derive(Debug)]
+#[allow(dead_code)]
+pub(super) enum NameKind {
+ Const,
+ ConstParam,
+ Enum,
+ Function,
+ IdentPat(PatternContext),
+ MacroDef,
+ MacroRules,
+ /// Fake node
+ Module(ast::Module),
+ RecordField,
+ Rename,
+ SelfParam,
+ Static,
+ Struct,
+ Trait,
+ TypeAlias,
+ TypeParam,
+ Union,
+ Variant,
+}
+
+/// The state of the NameRef we are completing.
+#[derive(Debug)]
+pub(super) struct NameRefContext {
+ /// NameRef syntax in the original file
+ pub(super) nameref: Option<ast::NameRef>,
+ pub(super) kind: NameRefKind,
+}
+
+/// The kind of the NameRef we are completing.
+#[derive(Debug)]
+pub(super) enum NameRefKind {
+ Path(PathCompletionCtx),
+ DotAccess(DotAccess),
+ /// Position where we are only interested in keyword completions
+ Keyword(ast::Item),
+ /// The record expression this nameref is a field of and whether a dot precedes the completion identifier.
+ RecordExpr {
+ dot_prefix: bool,
+ expr: ast::RecordExpr,
+ },
+ Pattern(PatternContext),
+}
+
+/// The identifier we are currently completing.
+#[derive(Debug)]
+pub(super) enum CompletionAnalysis {
+ Name(NameContext),
+ NameRef(NameRefContext),
+ Lifetime(LifetimeContext),
+ /// The string the cursor is currently inside
+ String {
+ /// original token
+ original: ast::String,
+ /// fake token
+ expanded: Option<ast::String>,
+ },
+ /// Set if we are currently completing in an unexpanded attribute, this usually implies a builtin attribute like `allow($0)`
+ UnexpandedAttrTT {
+ colon_prefix: bool,
+ fake_attribute_under_caret: Option<ast::Attr>,
+ },
+}
+
+/// Information about the field or method access we are completing.
+#[derive(Debug)]
+pub(super) struct DotAccess {
+ pub(super) receiver: Option<ast::Expr>,
+ pub(super) receiver_ty: Option<TypeInfo>,
+ pub(super) kind: DotAccessKind,
+}
+
+#[derive(Debug)]
+pub(super) enum DotAccessKind {
+ Field {
+ /// True if the receiver is an integer and there is no ident in the original file after it yet
+ /// like `0.$0`
+ receiver_is_ambiguous_float_literal: bool,
+ },
+ Method {
+ has_parens: bool,
+ },
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub(crate) enum ParamKind {
+ Function(ast::Fn),
+ Closure(ast::ClosureExpr),
+}
+
+/// `CompletionContext` is created early during completion to figure out, where
+/// exactly is the cursor, syntax-wise.
+#[derive(Debug)]
+pub(crate) struct CompletionContext<'a> {
+ pub(super) sema: Semantics<'a, RootDatabase>,
+ pub(super) scope: SemanticsScope<'a>,
+ pub(super) db: &'a RootDatabase,
+ pub(super) config: &'a CompletionConfig,
+ pub(super) position: FilePosition,
+
+ /// The token before the cursor, in the original file.
+ pub(super) original_token: SyntaxToken,
+ /// The token before the cursor, in the macro-expanded file.
+ pub(super) token: SyntaxToken,
+ /// The crate of the current file.
+ pub(super) krate: hir::Crate,
+ /// The module of the `scope`.
+ pub(super) module: hir::Module,
+
+ /// The expected name of what we are completing.
+ /// This is usually the parameter name of the function argument we are completing.
+ pub(super) expected_name: Option<NameOrNameRef>,
+ /// The expected type of what we are completing.
+ pub(super) expected_type: Option<Type>,
+
+ pub(super) qualifier_ctx: QualifierCtx,
+
+ pub(super) locals: FxHashMap<Name, Local>,
+
+ /// The module depth of the current module of the cursor position.
+ /// - crate-root
+ /// - mod foo
+ /// - mod bar
+ /// Here depth will be 2
+ pub(super) depth_from_crate_root: usize,
+}
+
+impl<'a> CompletionContext<'a> {
+ /// The range of the identifier that is being completed.
+ pub(crate) fn source_range(&self) -> TextRange {
+ // check kind of macro-expanded token, but use range of original token
+ let kind = self.token.kind();
+ match kind {
+ CHAR => {
+ // assume we are completing a lifetime but the user has only typed the '
+ cov_mark::hit!(completes_if_lifetime_without_idents);
+ TextRange::at(self.original_token.text_range().start(), TextSize::from(1))
+ }
+ IDENT | LIFETIME_IDENT | UNDERSCORE => self.original_token.text_range(),
+ _ if kind.is_keyword() => self.original_token.text_range(),
+ _ => TextRange::empty(self.position.offset),
+ }
+ }
+
+ pub(crate) fn famous_defs(&self) -> FamousDefs<'_, '_> {
+ FamousDefs(&self.sema, self.krate)
+ }
+
+ /// Checks if an item is visible and not `doc(hidden)` at the completion site.
+ pub(crate) fn def_is_visible(&self, item: &ScopeDef) -> Visible {
+ match item {
+ ScopeDef::ModuleDef(def) => match def {
+ hir::ModuleDef::Module(it) => self.is_visible(it),
+ hir::ModuleDef::Function(it) => self.is_visible(it),
+ hir::ModuleDef::Adt(it) => self.is_visible(it),
+ hir::ModuleDef::Variant(it) => self.is_visible(it),
+ hir::ModuleDef::Const(it) => self.is_visible(it),
+ hir::ModuleDef::Static(it) => self.is_visible(it),
+ hir::ModuleDef::Trait(it) => self.is_visible(it),
+ hir::ModuleDef::TypeAlias(it) => self.is_visible(it),
+ hir::ModuleDef::Macro(it) => self.is_visible(it),
+ hir::ModuleDef::BuiltinType(_) => Visible::Yes,
+ },
+ ScopeDef::GenericParam(_)
+ | ScopeDef::ImplSelfType(_)
+ | ScopeDef::AdtSelfType(_)
+ | ScopeDef::Local(_)
+ | ScopeDef::Label(_)
+ | ScopeDef::Unknown => Visible::Yes,
+ }
+ }
+
+ /// Checks if an item is visible and not `doc(hidden)` at the completion site.
+ pub(crate) fn is_visible<I>(&self, item: &I) -> Visible
+ where
+ I: hir::HasVisibility + hir::HasAttrs + hir::HasCrate + Copy,
+ {
+ let vis = item.visibility(self.db);
+ let attrs = item.attrs(self.db);
+ self.is_visible_impl(&vis, &attrs, item.krate(self.db))
+ }
+
+ /// Check if an item is `#[doc(hidden)]`.
+ pub(crate) fn is_item_hidden(&self, item: &hir::ItemInNs) -> bool {
+ let attrs = item.attrs(self.db);
+ let krate = item.krate(self.db);
+ match (attrs, krate) {
+ (Some(attrs), Some(krate)) => self.is_doc_hidden(&attrs, krate),
+ _ => false,
+ }
+ }
+
+ /// Whether the given trait is an operator trait or not.
+ pub(crate) fn is_ops_trait(&self, trait_: hir::Trait) -> bool {
+ match trait_.attrs(self.db).lang() {
+ Some(lang) => OP_TRAIT_LANG_NAMES.contains(&lang.as_str()),
+ None => false,
+ }
+ }
+
+ /// Returns the traits in scope, with the [`Drop`] trait removed.
+ pub(crate) fn traits_in_scope(&self) -> hir::VisibleTraits {
+ let mut traits_in_scope = self.scope.visible_traits();
+ if let Some(drop) = self.famous_defs().core_ops_Drop() {
+ traits_in_scope.0.remove(&drop.into());
+ }
+ traits_in_scope
+ }
+
+ pub(crate) fn iterate_path_candidates(
+ &self,
+ ty: &hir::Type,
+ mut cb: impl FnMut(hir::AssocItem),
+ ) {
+ let mut seen = FxHashSet::default();
+ ty.iterate_path_candidates(
+ self.db,
+ &self.scope,
+ &self.traits_in_scope(),
+ Some(self.module),
+ None,
+ |item| {
+ // We might iterate candidates of a trait multiple times here, so deduplicate
+ // them.
+ if seen.insert(item) {
+ cb(item)
+ }
+ None::<()>
+ },
+ );
+ }
+
+ /// A version of [`SemanticsScope::process_all_names`] that filters out `#[doc(hidden)]` items.
+ pub(crate) fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
+ let _p = profile::span("CompletionContext::process_all_names");
+ self.scope.process_all_names(&mut |name, def| {
+ if self.is_scope_def_hidden(def) {
+ return;
+ }
+
+ f(name, def);
+ });
+ }
+
+ pub(crate) fn process_all_names_raw(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
+ let _p = profile::span("CompletionContext::process_all_names_raw");
+ self.scope.process_all_names(&mut |name, def| f(name, def));
+ }
+
+ fn is_scope_def_hidden(&self, scope_def: ScopeDef) -> bool {
+ if let (Some(attrs), Some(krate)) = (scope_def.attrs(self.db), scope_def.krate(self.db)) {
+ return self.is_doc_hidden(&attrs, krate);
+ }
+
+ false
+ }
+
+ fn is_visible_impl(
+ &self,
+ vis: &hir::Visibility,
+ attrs: &hir::Attrs,
+ defining_crate: hir::Crate,
+ ) -> Visible {
+ if !vis.is_visible_from(self.db, self.module.into()) {
+ if !self.config.enable_private_editable {
+ return Visible::No;
+ }
+ // If the definition location is editable, also show private items
+ let root_file = defining_crate.root_file(self.db);
+ let source_root_id = self.db.file_source_root(root_file);
+ let is_editable = !self.db.source_root(source_root_id).is_library;
+ return if is_editable { Visible::Editable } else { Visible::No };
+ }
+
+ if self.is_doc_hidden(attrs, defining_crate) {
+ Visible::No
+ } else {
+ Visible::Yes
+ }
+ }
+
+ fn is_doc_hidden(&self, attrs: &hir::Attrs, defining_crate: hir::Crate) -> bool {
+ // `doc(hidden)` items are only completed within the defining crate.
+ self.krate != defining_crate && attrs.has_doc_hidden()
+ }
+}
+
+// CompletionContext construction
+impl<'a> CompletionContext<'a> {
+ pub(super) fn new(
+ db: &'a RootDatabase,
+ position @ FilePosition { file_id, offset }: FilePosition,
+ config: &'a CompletionConfig,
+ ) -> Option<(CompletionContext<'a>, CompletionAnalysis)> {
+ let _p = profile::span("CompletionContext::new");
+ let sema = Semantics::new(db);
+
+ let original_file = sema.parse(file_id);
+
+ // Insert a fake ident to get a valid parse tree. We will use this file
+ // to determine context, though the original_file will be used for
+ // actual completion.
+ let file_with_fake_ident = {
+ let parse = db.parse(file_id);
+ let edit = Indel::insert(offset, COMPLETION_MARKER.to_string());
+ parse.reparse(&edit).tree()
+ };
+ let fake_ident_token =
+ file_with_fake_ident.syntax().token_at_offset(offset).right_biased()?;
+
+ let original_token = original_file.syntax().token_at_offset(offset).left_biased()?;
+ let token = sema.descend_into_macros_single(original_token.clone());
+
+ // adjust for macro input, this still fails if there is no token written yet
+ let scope_offset = if original_token == token { offset } else { token.text_range().end() };
+ let scope = sema.scope_at_offset(&token.parent()?, scope_offset)?;
+
+ let krate = scope.krate();
+ let module = scope.module();
+
+ let mut locals = FxHashMap::default();
+ scope.process_all_names(&mut |name, scope| {
+ if let ScopeDef::Local(local) = scope {
+ locals.insert(name, local);
+ }
+ });
+
+ let depth_from_crate_root = iter::successors(module.parent(db), |m| m.parent(db)).count();
+
+ let mut ctx = CompletionContext {
+ sema,
+ scope,
+ db,
+ config,
+ position,
+ original_token,
+ token,
+ krate,
+ module,
+ expected_name: None,
+ expected_type: None,
+ qualifier_ctx: Default::default(),
+ locals,
+ depth_from_crate_root,
+ };
+ let ident_ctx = ctx.expand_and_analyze(
+ original_file.syntax().clone(),
+ file_with_fake_ident.syntax().clone(),
+ offset,
+ fake_ident_token,
+ )?;
+ Some((ctx, ident_ctx))
+ }
+}
+
+const OP_TRAIT_LANG_NAMES: &[&str] = &[
+ "add_assign",
+ "add",
+ "bitand_assign",
+ "bitand",
+ "bitor_assign",
+ "bitor",
+ "bitxor_assign",
+ "bitxor",
+ "deref_mut",
+ "deref",
+ "div_assign",
+ "div",
+ "eq",
+ "fn_mut",
+ "fn_once",
+ "fn",
+ "index_mut",
+ "index",
+ "mul_assign",
+ "mul",
+ "neg",
+ "not",
+ "partial_ord",
+ "rem_assign",
+ "rem",
+ "shl_assign",
+ "shl",
+ "shr_assign",
+ "shr",
+ "sub",
+];
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs
new file mode 100644
index 000000000..22ec7cead
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs
@@ -0,0 +1,1293 @@
+//! Module responsible for analyzing the code surrounding the cursor for completion.
+use std::iter;
+
+use hir::{Semantics, Type, TypeInfo};
+use ide_db::{active_parameter::ActiveParameter, RootDatabase};
+use syntax::{
+ algo::{find_node_at_offset, non_trivia_sibling},
+ ast::{self, AttrKind, HasArgList, HasLoopBody, HasName, NameOrNameRef},
+ match_ast, AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode,
+ SyntaxToken, TextRange, TextSize, T,
+};
+
+use crate::context::{
+ AttrCtx, CompletionAnalysis, CompletionContext, DotAccess, DotAccessKind, ExprCtx,
+ ItemListKind, LifetimeContext, LifetimeKind, NameContext, NameKind, NameRefContext,
+ NameRefKind, ParamContext, ParamKind, PathCompletionCtx, PathKind, PatternContext,
+ PatternRefutability, Qualified, QualifierCtx, TypeAscriptionTarget, TypeLocation,
+ COMPLETION_MARKER,
+};
+
+impl<'a> CompletionContext<'a> {
+ /// Expand attributes and macro calls at the current cursor position for both the original file
+ /// and fake file repeatedly. As soon as one of the two expansions fail we stop so the original
+ /// and speculative states stay in sync.
+ pub(super) fn expand_and_analyze(
+ &mut self,
+ mut original_file: SyntaxNode,
+ mut speculative_file: SyntaxNode,
+ mut offset: TextSize,
+ mut fake_ident_token: SyntaxToken,
+ ) -> Option<CompletionAnalysis> {
+ let _p = profile::span("CompletionContext::expand_and_fill");
+ let mut derive_ctx = None;
+
+ 'expansion: loop {
+ let parent_item =
+ |item: &ast::Item| item.syntax().ancestors().skip(1).find_map(ast::Item::cast);
+ let ancestor_items = iter::successors(
+ Option::zip(
+ find_node_at_offset::<ast::Item>(&original_file, offset),
+ find_node_at_offset::<ast::Item>(&speculative_file, offset),
+ ),
+ |(a, b)| parent_item(a).zip(parent_item(b)),
+ );
+
+ // first try to expand attributes as these are always the outermost macro calls
+ 'ancestors: for (actual_item, item_with_fake_ident) in ancestor_items {
+ match (
+ self.sema.expand_attr_macro(&actual_item),
+ self.sema.speculative_expand_attr_macro(
+ &actual_item,
+ &item_with_fake_ident,
+ fake_ident_token.clone(),
+ ),
+ ) {
+ // maybe parent items have attributes, so continue walking the ancestors
+ (None, None) => continue 'ancestors,
+ // successful expansions
+ (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => {
+ let new_offset = fake_mapped_token.text_range().start();
+ if new_offset > actual_expansion.text_range().end() {
+ // offset outside of bounds from the original expansion,
+ // stop here to prevent problems from happening
+ break 'expansion;
+ }
+ original_file = actual_expansion;
+ speculative_file = fake_expansion;
+ fake_ident_token = fake_mapped_token;
+ offset = new_offset;
+ continue 'expansion;
+ }
+ // exactly one expansion failed, inconsistent state so stop expanding completely
+ _ => break 'expansion,
+ }
+ }
+
+ // No attributes have been expanded, so look for macro_call! token trees or derive token trees
+ let orig_tt = match find_node_at_offset::<ast::TokenTree>(&original_file, offset) {
+ Some(it) => it,
+ None => break 'expansion,
+ };
+ let spec_tt = match find_node_at_offset::<ast::TokenTree>(&speculative_file, offset) {
+ Some(it) => it,
+ None => break 'expansion,
+ };
+
+ // Expand pseudo-derive expansion
+ if let (Some(orig_attr), Some(spec_attr)) = (
+ orig_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_attr()),
+ spec_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_attr()),
+ ) {
+ if let (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) = (
+ self.sema.expand_derive_as_pseudo_attr_macro(&orig_attr),
+ self.sema.speculative_expand_derive_as_pseudo_attr_macro(
+ &orig_attr,
+ &spec_attr,
+ fake_ident_token.clone(),
+ ),
+ ) {
+ derive_ctx = Some((
+ actual_expansion,
+ fake_expansion,
+ fake_mapped_token.text_range().start(),
+ orig_attr,
+ ));
+ }
+ // at this point we won't have any more successful expansions, so stop
+ break 'expansion;
+ }
+
+ // Expand fn-like macro calls
+ if let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = (
+ orig_tt.syntax().ancestors().find_map(ast::MacroCall::cast),
+ spec_tt.syntax().ancestors().find_map(ast::MacroCall::cast),
+ ) {
+ let mac_call_path0 = actual_macro_call.path().as_ref().map(|s| s.syntax().text());
+ let mac_call_path1 =
+ macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text());
+
+ // inconsistent state, stop expanding
+ if mac_call_path0 != mac_call_path1 {
+ break 'expansion;
+ }
+ let speculative_args = match macro_call_with_fake_ident.token_tree() {
+ Some(tt) => tt,
+ None => break 'expansion,
+ };
+
+ match (
+ self.sema.expand(&actual_macro_call),
+ self.sema.speculative_expand(
+ &actual_macro_call,
+ &speculative_args,
+ fake_ident_token.clone(),
+ ),
+ ) {
+ // successful expansions
+ (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => {
+ let new_offset = fake_mapped_token.text_range().start();
+ if new_offset > actual_expansion.text_range().end() {
+ // offset outside of bounds from the original expansion,
+ // stop here to prevent problems from happening
+ break 'expansion;
+ }
+ original_file = actual_expansion;
+ speculative_file = fake_expansion;
+ fake_ident_token = fake_mapped_token;
+ offset = new_offset;
+ continue 'expansion;
+ }
+ // at least on expansion failed, we won't have anything to expand from this point
+ // onwards so break out
+ _ => break 'expansion,
+ }
+ }
+
+ // none of our states have changed so stop the loop
+ break 'expansion;
+ }
+
+ self.analyze(&original_file, speculative_file, offset, derive_ctx)
+ }
+
+ /// Calculate the expected type and name of the cursor position.
+ fn expected_type_and_name(
+ &self,
+ name_like: &ast::NameLike,
+ ) -> (Option<Type>, Option<NameOrNameRef>) {
+ let mut node = match self.token.parent() {
+ Some(it) => it,
+ None => return (None, None),
+ };
+
+ let strip_refs = |mut ty: Type| match name_like {
+ ast::NameLike::NameRef(n) => {
+ let p = match n.syntax().parent() {
+ Some(it) => it,
+ None => return ty,
+ };
+ let top_syn = match_ast! {
+ match p {
+ ast::FieldExpr(e) => e
+ .syntax()
+ .ancestors()
+ .map_while(ast::FieldExpr::cast)
+ .last()
+ .map(|it| it.syntax().clone()),
+ ast::PathSegment(e) => e
+ .syntax()
+ .ancestors()
+ .skip(1)
+ .take_while(|it| ast::Path::can_cast(it.kind()) || ast::PathExpr::can_cast(it.kind()))
+ .find_map(ast::PathExpr::cast)
+ .map(|it| it.syntax().clone()),
+ _ => None
+ }
+ };
+ let top_syn = match top_syn {
+ Some(it) => it,
+ None => return ty,
+ };
+ for _ in top_syn.ancestors().skip(1).map_while(ast::RefExpr::cast) {
+ cov_mark::hit!(expected_type_fn_param_ref);
+ ty = ty.strip_reference();
+ }
+ ty
+ }
+ _ => ty,
+ };
+
+ loop {
+ break match_ast! {
+ match node {
+ ast::LetStmt(it) => {
+ cov_mark::hit!(expected_type_let_with_leading_char);
+ cov_mark::hit!(expected_type_let_without_leading_char);
+ let ty = it.pat()
+ .and_then(|pat| self.sema.type_of_pat(&pat))
+ .or_else(|| it.initializer().and_then(|it| self.sema.type_of_expr(&it)))
+ .map(TypeInfo::original);
+ let name = match it.pat() {
+ Some(ast::Pat::IdentPat(ident)) => ident.name().map(NameOrNameRef::Name),
+ Some(_) | None => None,
+ };
+
+ (ty, name)
+ },
+ ast::LetExpr(it) => {
+ cov_mark::hit!(expected_type_if_let_without_leading_char);
+ let ty = it.pat()
+ .and_then(|pat| self.sema.type_of_pat(&pat))
+ .or_else(|| it.expr().and_then(|it| self.sema.type_of_expr(&it)))
+ .map(TypeInfo::original);
+ (ty, None)
+ },
+ ast::ArgList(_) => {
+ cov_mark::hit!(expected_type_fn_param);
+ ActiveParameter::at_token(
+ &self.sema,
+ self.token.clone(),
+ ).map(|ap| {
+ let name = ap.ident().map(NameOrNameRef::Name);
+
+ let ty = strip_refs(ap.ty);
+ (Some(ty), name)
+ })
+ .unwrap_or((None, None))
+ },
+ ast::RecordExprFieldList(it) => {
+ // wouldn't try {} be nice...
+ (|| {
+ if self.token.kind() == T![..]
+ || self.token.prev_token().map(|t| t.kind()) == Some(T![..])
+ {
+ cov_mark::hit!(expected_type_struct_func_update);
+ let record_expr = it.syntax().parent().and_then(ast::RecordExpr::cast)?;
+ let ty = self.sema.type_of_expr(&record_expr.into())?;
+ Some((
+ Some(ty.original),
+ None
+ ))
+ } else {
+ cov_mark::hit!(expected_type_struct_field_without_leading_char);
+ let expr_field = self.token.prev_sibling_or_token()?
+ .into_node()
+ .and_then(ast::RecordExprField::cast)?;
+ let (_, _, ty) = self.sema.resolve_record_field(&expr_field)?;
+ Some((
+ Some(ty),
+ expr_field.field_name().map(NameOrNameRef::NameRef),
+ ))
+ }
+ })().unwrap_or((None, None))
+ },
+ ast::RecordExprField(it) => {
+ if let Some(expr) = it.expr() {
+ cov_mark::hit!(expected_type_struct_field_with_leading_char);
+ (
+ self.sema.type_of_expr(&expr).map(TypeInfo::original),
+ it.field_name().map(NameOrNameRef::NameRef),
+ )
+ } else {
+ cov_mark::hit!(expected_type_struct_field_followed_by_comma);
+ let ty = self.sema.resolve_record_field(&it)
+ .map(|(_, _, ty)| ty);
+ (
+ ty,
+ it.field_name().map(NameOrNameRef::NameRef),
+ )
+ }
+ },
+ // match foo { $0 }
+ // match foo { ..., pat => $0 }
+ ast::MatchExpr(it) => {
+ let on_arrow = previous_non_trivia_token(self.token.clone()).map_or(false, |it| T![=>] == it.kind());
+
+ let ty = if on_arrow {
+ // match foo { ..., pat => $0 }
+ cov_mark::hit!(expected_type_match_arm_body_without_leading_char);
+ cov_mark::hit!(expected_type_match_arm_body_with_leading_char);
+ self.sema.type_of_expr(&it.into())
+ } else {
+ // match foo { $0 }
+ cov_mark::hit!(expected_type_match_arm_without_leading_char);
+ it.expr().and_then(|e| self.sema.type_of_expr(&e))
+ }.map(TypeInfo::original);
+ (ty, None)
+ },
+ ast::IfExpr(it) => {
+ let ty = it.condition()
+ .and_then(|e| self.sema.type_of_expr(&e))
+ .map(TypeInfo::original);
+ (ty, None)
+ },
+ ast::IdentPat(it) => {
+ cov_mark::hit!(expected_type_if_let_with_leading_char);
+ cov_mark::hit!(expected_type_match_arm_with_leading_char);
+ let ty = self.sema.type_of_pat(&ast::Pat::from(it)).map(TypeInfo::original);
+ (ty, None)
+ },
+ ast::Fn(it) => {
+ cov_mark::hit!(expected_type_fn_ret_with_leading_char);
+ cov_mark::hit!(expected_type_fn_ret_without_leading_char);
+ let def = self.sema.to_def(&it);
+ (def.map(|def| def.ret_type(self.db)), None)
+ },
+ ast::ClosureExpr(it) => {
+ let ty = self.sema.type_of_expr(&it.into());
+ ty.and_then(|ty| ty.original.as_callable(self.db))
+ .map(|c| (Some(c.return_type()), None))
+ .unwrap_or((None, None))
+ },
+ ast::ParamList(_) => (None, None),
+ ast::Stmt(_) => (None, None),
+ ast::Item(_) => (None, None),
+ _ => {
+ match node.parent() {
+ Some(n) => {
+ node = n;
+ continue;
+ },
+ None => (None, None),
+ }
+ },
+ }
+ };
+ }
+ }
+
+ /// Fill the completion context, this is what does semantic reasoning about the surrounding context
+ /// of the completion location.
+ fn analyze(
+ &mut self,
+ original_file: &SyntaxNode,
+ file_with_fake_ident: SyntaxNode,
+ offset: TextSize,
+ derive_ctx: Option<(SyntaxNode, SyntaxNode, TextSize, ast::Attr)>,
+ ) -> Option<CompletionAnalysis> {
+ let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased()?;
+ let syntax_element = NodeOrToken::Token(fake_ident_token);
+ if is_in_token_of_for_loop(syntax_element.clone()) {
+ // for pat $0
+ // there is nothing to complete here except `in` keyword
+ // don't bother populating the context
+ // FIXME: the completion calculations should end up good enough
+ // such that this special case becomes unnecessary
+ return None;
+ }
+
+ // Overwrite the path kind for derives
+ if let Some((original_file, file_with_fake_ident, offset, origin_attr)) = derive_ctx {
+ if let Some(ast::NameLike::NameRef(name_ref)) =
+ find_node_at_offset(&file_with_fake_ident, offset)
+ {
+ let parent = name_ref.syntax().parent()?;
+ let (mut nameref_ctx, _) =
+ Self::classify_name_ref(&self.sema, &original_file, name_ref, parent)?;
+ if let NameRefKind::Path(path_ctx) = &mut nameref_ctx.kind {
+ path_ctx.kind = PathKind::Derive {
+ existing_derives: self
+ .sema
+ .resolve_derive_macro(&origin_attr)
+ .into_iter()
+ .flatten()
+ .flatten()
+ .collect(),
+ };
+ }
+ return Some(CompletionAnalysis::NameRef(nameref_ctx));
+ }
+ return None;
+ }
+
+ let name_like = match find_node_at_offset(&file_with_fake_ident, offset) {
+ Some(it) => it,
+ None => {
+ let analysis =
+ if let Some(original) = ast::String::cast(self.original_token.clone()) {
+ CompletionAnalysis::String {
+ original,
+ expanded: ast::String::cast(self.token.clone()),
+ }
+ } else {
+ // Fix up trailing whitespace problem
+ // #[attr(foo = $0
+ let token =
+ syntax::algo::skip_trivia_token(self.token.clone(), Direction::Prev)?;
+ let p = token.parent()?;
+ if p.kind() == SyntaxKind::TOKEN_TREE
+ && p.ancestors().any(|it| it.kind() == SyntaxKind::META)
+ {
+ let colon_prefix = previous_non_trivia_token(self.token.clone())
+ .map_or(false, |it| T![:] == it.kind());
+ CompletionAnalysis::UnexpandedAttrTT {
+ fake_attribute_under_caret: syntax_element
+ .ancestors()
+ .find_map(ast::Attr::cast),
+ colon_prefix,
+ }
+ } else {
+ return None;
+ }
+ };
+ return Some(analysis);
+ }
+ };
+ (self.expected_type, self.expected_name) = self.expected_type_and_name(&name_like);
+ let analysis = match name_like {
+ ast::NameLike::Lifetime(lifetime) => CompletionAnalysis::Lifetime(
+ Self::classify_lifetime(&self.sema, original_file, lifetime)?,
+ ),
+ ast::NameLike::NameRef(name_ref) => {
+ let parent = name_ref.syntax().parent()?;
+ let (nameref_ctx, qualifier_ctx) =
+ Self::classify_name_ref(&self.sema, &original_file, name_ref, parent.clone())?;
+
+ self.qualifier_ctx = qualifier_ctx;
+ CompletionAnalysis::NameRef(nameref_ctx)
+ }
+ ast::NameLike::Name(name) => {
+ let name_ctx = Self::classify_name(&self.sema, original_file, name)?;
+ CompletionAnalysis::Name(name_ctx)
+ }
+ };
+ Some(analysis)
+ }
+
+ fn classify_lifetime(
+ _sema: &Semantics<'_, RootDatabase>,
+ original_file: &SyntaxNode,
+ lifetime: ast::Lifetime,
+ ) -> Option<LifetimeContext> {
+ let parent = lifetime.syntax().parent()?;
+ if parent.kind() == SyntaxKind::ERROR {
+ return None;
+ }
+
+ let kind = match_ast! {
+ match parent {
+ ast::LifetimeParam(param) => LifetimeKind::LifetimeParam {
+ is_decl: param.lifetime().as_ref() == Some(&lifetime),
+ param
+ },
+ ast::BreakExpr(_) => LifetimeKind::LabelRef,
+ ast::ContinueExpr(_) => LifetimeKind::LabelRef,
+ ast::Label(_) => LifetimeKind::LabelDef,
+ _ => LifetimeKind::Lifetime,
+ }
+ };
+ let lifetime = find_node_at_offset(&original_file, lifetime.syntax().text_range().start());
+
+ Some(LifetimeContext { lifetime, kind })
+ }
+
+ fn classify_name(
+ sema: &Semantics<'_, RootDatabase>,
+ original_file: &SyntaxNode,
+ name: ast::Name,
+ ) -> Option<NameContext> {
+ let parent = name.syntax().parent()?;
+ let kind = match_ast! {
+ match parent {
+ ast::Const(_) => NameKind::Const,
+ ast::ConstParam(_) => NameKind::ConstParam,
+ ast::Enum(_) => NameKind::Enum,
+ ast::Fn(_) => NameKind::Function,
+ ast::IdentPat(bind_pat) => {
+ let mut pat_ctx = pattern_context_for(sema, original_file, bind_pat.into());
+ if let Some(record_field) = ast::RecordPatField::for_field_name(&name) {
+ pat_ctx.record_pat = find_node_in_file_compensated(sema, original_file, &record_field.parent_record_pat());
+ }
+
+ NameKind::IdentPat(pat_ctx)
+ },
+ ast::MacroDef(_) => NameKind::MacroDef,
+ ast::MacroRules(_) => NameKind::MacroRules,
+ ast::Module(module) => NameKind::Module(module),
+ ast::RecordField(_) => NameKind::RecordField,
+ ast::Rename(_) => NameKind::Rename,
+ ast::SelfParam(_) => NameKind::SelfParam,
+ ast::Static(_) => NameKind::Static,
+ ast::Struct(_) => NameKind::Struct,
+ ast::Trait(_) => NameKind::Trait,
+ ast::TypeAlias(_) => NameKind::TypeAlias,
+ ast::TypeParam(_) => NameKind::TypeParam,
+ ast::Union(_) => NameKind::Union,
+ ast::Variant(_) => NameKind::Variant,
+ _ => return None,
+ }
+ };
+ let name = find_node_at_offset(&original_file, name.syntax().text_range().start());
+ Some(NameContext { name, kind })
+ }
+
+ fn classify_name_ref(
+ sema: &Semantics<'_, RootDatabase>,
+ original_file: &SyntaxNode,
+ name_ref: ast::NameRef,
+ parent: SyntaxNode,
+ ) -> Option<(NameRefContext, QualifierCtx)> {
+ let nameref = find_node_at_offset(&original_file, name_ref.syntax().text_range().start());
+
+ let make_res =
+ |kind| (NameRefContext { nameref: nameref.clone(), kind }, Default::default());
+
+ if let Some(record_field) = ast::RecordExprField::for_field_name(&name_ref) {
+ let dot_prefix = previous_non_trivia_token(name_ref.syntax().clone())
+ .map_or(false, |it| T![.] == it.kind());
+
+ return find_node_in_file_compensated(
+ sema,
+ original_file,
+ &record_field.parent_record_lit(),
+ )
+ .map(|expr| NameRefKind::RecordExpr { expr, dot_prefix })
+ .map(make_res);
+ }
+ if let Some(record_field) = ast::RecordPatField::for_field_name_ref(&name_ref) {
+ let kind = NameRefKind::Pattern(PatternContext {
+ param_ctx: None,
+ has_type_ascription: false,
+ ref_token: None,
+ mut_token: None,
+ record_pat: find_node_in_file_compensated(
+ sema,
+ original_file,
+ &record_field.parent_record_pat(),
+ ),
+ ..pattern_context_for(
+ sema,
+ original_file,
+ record_field.parent_record_pat().clone().into(),
+ )
+ });
+ return Some(make_res(kind));
+ }
+
+ let segment = match_ast! {
+ match parent {
+ ast::PathSegment(segment) => segment,
+ ast::FieldExpr(field) => {
+ let receiver = find_opt_node_in_file(original_file, field.expr());
+ let receiver_is_ambiguous_float_literal = match &receiver {
+ Some(ast::Expr::Literal(l)) => matches! {
+ l.kind(),
+ ast::LiteralKind::FloatNumber { .. } if l.syntax().last_token().map_or(false, |it| it.text().ends_with('.'))
+ },
+ _ => false,
+ };
+ let kind = NameRefKind::DotAccess(DotAccess {
+ receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)),
+ kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal },
+ receiver
+ });
+ return Some(make_res(kind));
+ },
+ ast::MethodCallExpr(method) => {
+ let receiver = find_opt_node_in_file(original_file, method.receiver());
+ let kind = NameRefKind::DotAccess(DotAccess {
+ receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)),
+ kind: DotAccessKind::Method { has_parens: method.arg_list().map_or(false, |it| it.l_paren_token().is_some()) },
+ receiver
+ });
+ return Some(make_res(kind));
+ },
+ _ => return None,
+ }
+ };
+
+ let path = segment.parent_path();
+ let mut path_ctx = PathCompletionCtx {
+ has_call_parens: false,
+ has_macro_bang: false,
+ qualified: Qualified::No,
+ parent: None,
+ path: path.clone(),
+ kind: PathKind::Item { kind: ItemListKind::SourceFile },
+ has_type_args: false,
+ use_tree_parent: false,
+ };
+
+ let is_in_block = |it: &SyntaxNode| {
+ it.parent()
+ .map(|node| {
+ ast::ExprStmt::can_cast(node.kind()) || ast::StmtList::can_cast(node.kind())
+ })
+ .unwrap_or(false)
+ };
+ let func_update_record = |syn: &SyntaxNode| {
+ if let Some(record_expr) = syn.ancestors().nth(2).and_then(ast::RecordExpr::cast) {
+ find_node_in_file_compensated(sema, original_file, &record_expr)
+ } else {
+ None
+ }
+ };
+ let after_if_expr = |node: SyntaxNode| {
+ let prev_expr = (|| {
+ let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?;
+ ast::ExprStmt::cast(prev_sibling)?.expr()
+ })();
+ matches!(prev_expr, Some(ast::Expr::IfExpr(_)))
+ };
+
+ // We do not want to generate path completions when we are sandwiched between an item decl signature and its body.
+ // ex. trait Foo $0 {}
+ // in these cases parser recovery usually kicks in for our inserted identifier, causing it
+ // to either be parsed as an ExprStmt or a MacroCall, depending on whether it is in a block
+ // expression or an item list.
+ // The following code checks if the body is missing, if it is we either cut off the body
+ // from the item or it was missing in the first place
+ let inbetween_body_and_decl_check = |node: SyntaxNode| {
+ if let Some(NodeOrToken::Node(n)) =
+ syntax::algo::non_trivia_sibling(node.into(), syntax::Direction::Prev)
+ {
+ if let Some(item) = ast::Item::cast(n) {
+ let is_inbetween = match &item {
+ ast::Item::Const(it) => it.body().is_none(),
+ ast::Item::Enum(it) => it.variant_list().is_none(),
+ ast::Item::ExternBlock(it) => it.extern_item_list().is_none(),
+ ast::Item::Fn(it) => it.body().is_none(),
+ ast::Item::Impl(it) => it.assoc_item_list().is_none(),
+ ast::Item::Module(it) => it.item_list().is_none(),
+ ast::Item::Static(it) => it.body().is_none(),
+ ast::Item::Struct(it) => it.field_list().is_none(),
+ ast::Item::Trait(it) => it.assoc_item_list().is_none(),
+ ast::Item::TypeAlias(it) => it.ty().is_none(),
+ ast::Item::Union(it) => it.record_field_list().is_none(),
+ _ => false,
+ };
+ if is_inbetween {
+ return Some(item);
+ }
+ }
+ }
+ None
+ };
+
+ let type_location = |node: &SyntaxNode| {
+ let parent = node.parent()?;
+ let res = match_ast! {
+ match parent {
+ ast::Const(it) => {
+ let name = find_opt_node_in_file(original_file, it.name())?;
+ let original = ast::Const::cast(name.syntax().parent()?)?;
+ TypeLocation::TypeAscription(TypeAscriptionTarget::Const(original.body()))
+ },
+ ast::RetType(it) => {
+ if it.thin_arrow_token().is_none() {
+ return None;
+ }
+ let parent = match ast::Fn::cast(parent.parent()?) {
+ Some(x) => x.param_list(),
+ None => ast::ClosureExpr::cast(parent.parent()?)?.param_list(),
+ };
+
+ let parent = find_opt_node_in_file(original_file, parent)?.syntax().parent()?;
+ TypeLocation::TypeAscription(TypeAscriptionTarget::RetType(match_ast! {
+ match parent {
+ ast::ClosureExpr(it) => {
+ it.body()
+ },
+ ast::Fn(it) => {
+ it.body().map(ast::Expr::BlockExpr)
+ },
+ _ => return None,
+ }
+ }))
+ },
+ ast::Param(it) => {
+ if it.colon_token().is_none() {
+ return None;
+ }
+ TypeLocation::TypeAscription(TypeAscriptionTarget::FnParam(find_opt_node_in_file(original_file, it.pat())))
+ },
+ ast::LetStmt(it) => {
+ if it.colon_token().is_none() {
+ return None;
+ }
+ TypeLocation::TypeAscription(TypeAscriptionTarget::Let(find_opt_node_in_file(original_file, it.pat())))
+ },
+ ast::Impl(it) => {
+ match it.trait_() {
+ Some(t) if t.syntax() == node => TypeLocation::ImplTrait,
+ _ => match it.self_ty() {
+ Some(t) if t.syntax() == node => TypeLocation::ImplTarget,
+ _ => return None,
+ },
+ }
+ },
+ ast::TypeBound(_) => TypeLocation::TypeBound,
+ // is this case needed?
+ ast::TypeBoundList(_) => TypeLocation::TypeBound,
+ ast::GenericArg(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, it.syntax().parent().and_then(ast::GenericArgList::cast))),
+ // is this case needed?
+ ast::GenericArgList(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, Some(it))),
+ ast::TupleField(_) => TypeLocation::TupleField,
+ _ => return None,
+ }
+ };
+ Some(res)
+ };
+
+ let is_in_condition = |it: &ast::Expr| {
+ (|| {
+ let parent = it.syntax().parent()?;
+ if let Some(expr) = ast::WhileExpr::cast(parent.clone()) {
+ Some(expr.condition()? == *it)
+ } else if let Some(expr) = ast::IfExpr::cast(parent) {
+ Some(expr.condition()? == *it)
+ } else {
+ None
+ }
+ })()
+ .unwrap_or(false)
+ };
+
+ let make_path_kind_expr = |expr: ast::Expr| {
+ let it = expr.syntax();
+ let in_block_expr = is_in_block(it);
+ let in_loop_body = is_in_loop_body(it);
+ let after_if_expr = after_if_expr(it.clone());
+ let ref_expr_parent =
+ path.as_single_name_ref().and_then(|_| it.parent()).and_then(ast::RefExpr::cast);
+ let (innermost_ret_ty, self_param) = {
+ let find_ret_ty = |it: SyntaxNode| {
+ if let Some(item) = ast::Item::cast(it.clone()) {
+ match item {
+ ast::Item::Fn(f) => {
+ Some(sema.to_def(&f).map(|it| it.ret_type(sema.db)))
+ }
+ ast::Item::MacroCall(_) => None,
+ _ => Some(None),
+ }
+ } else {
+ let expr = ast::Expr::cast(it)?;
+ let callable = match expr {
+ // FIXME
+ // ast::Expr::BlockExpr(b) if b.async_token().is_some() || b.try_token().is_some() => sema.type_of_expr(b),
+ ast::Expr::ClosureExpr(_) => sema.type_of_expr(&expr),
+ _ => return None,
+ };
+ Some(
+ callable
+ .and_then(|c| c.adjusted().as_callable(sema.db))
+ .map(|it| it.return_type()),
+ )
+ }
+ };
+ let find_fn_self_param = |it| match it {
+ ast::Item::Fn(fn_) => {
+ Some(sema.to_def(&fn_).and_then(|it| it.self_param(sema.db)))
+ }
+ ast::Item::MacroCall(_) => None,
+ _ => Some(None),
+ };
+
+ match find_node_in_file_compensated(sema, original_file, &expr) {
+ Some(it) => {
+ let innermost_ret_ty = sema
+ .ancestors_with_macros(it.syntax().clone())
+ .find_map(find_ret_ty)
+ .flatten();
+
+ let self_param = sema
+ .ancestors_with_macros(it.syntax().clone())
+ .filter_map(ast::Item::cast)
+ .find_map(find_fn_self_param)
+ .flatten();
+ (innermost_ret_ty, self_param)
+ }
+ None => (None, None),
+ }
+ };
+ let is_func_update = func_update_record(it);
+ let in_condition = is_in_condition(&expr);
+ let incomplete_let = it
+ .parent()
+ .and_then(ast::LetStmt::cast)
+ .map_or(false, |it| it.semicolon_token().is_none());
+ let impl_ = fetch_immediate_impl(sema, original_file, expr.syntax());
+
+ let in_match_guard = match it.parent().and_then(ast::MatchArm::cast) {
+ Some(arm) => arm
+ .fat_arrow_token()
+ .map_or(true, |arrow| it.text_range().start() < arrow.text_range().start()),
+ None => false,
+ };
+
+ PathKind::Expr {
+ expr_ctx: ExprCtx {
+ in_block_expr,
+ in_loop_body,
+ after_if_expr,
+ in_condition,
+ ref_expr_parent,
+ is_func_update,
+ innermost_ret_ty,
+ self_param,
+ incomplete_let,
+ impl_,
+ in_match_guard,
+ },
+ }
+ };
+ let make_path_kind_type = |ty: ast::Type| {
+ let location = type_location(ty.syntax());
+ PathKind::Type { location: location.unwrap_or(TypeLocation::Other) }
+ };
+
+ let mut kind_macro_call = |it: ast::MacroCall| {
+ path_ctx.has_macro_bang = it.excl_token().is_some();
+ let parent = it.syntax().parent()?;
+ // Any path in an item list will be treated as a macro call by the parser
+ let kind = match_ast! {
+ match parent {
+ ast::MacroExpr(expr) => make_path_kind_expr(expr.into()),
+ ast::MacroPat(it) => PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())},
+ ast::MacroType(ty) => make_path_kind_type(ty.into()),
+ ast::ItemList(_) => PathKind::Item { kind: ItemListKind::Module },
+ ast::AssocItemList(_) => PathKind::Item { kind: match parent.parent() {
+ Some(it) => match_ast! {
+ match it {
+ ast::Trait(_) => ItemListKind::Trait,
+ ast::Impl(it) => if it.trait_().is_some() {
+ ItemListKind::TraitImpl(find_node_in_file_compensated(sema, original_file, &it))
+ } else {
+ ItemListKind::Impl
+ },
+ _ => return None
+ }
+ },
+ None => return None,
+ } },
+ ast::ExternItemList(_) => PathKind::Item { kind: ItemListKind::ExternBlock },
+ ast::SourceFile(_) => PathKind::Item { kind: ItemListKind::SourceFile },
+ _ => return None,
+ }
+ };
+ Some(kind)
+ };
+ let make_path_kind_attr = |meta: ast::Meta| {
+ let attr = meta.parent_attr()?;
+ let kind = attr.kind();
+ let attached = attr.syntax().parent()?;
+ let is_trailing_outer_attr = kind != AttrKind::Inner
+ && non_trivia_sibling(attr.syntax().clone().into(), syntax::Direction::Next)
+ .is_none();
+ let annotated_item_kind =
+ if is_trailing_outer_attr { None } else { Some(attached.kind()) };
+ Some(PathKind::Attr { attr_ctx: AttrCtx { kind, annotated_item_kind } })
+ };
+
+ // Infer the path kind
+ let parent = path.syntax().parent()?;
+ let kind = match_ast! {
+ match parent {
+ ast::PathType(it) => make_path_kind_type(it.into()),
+ ast::PathExpr(it) => {
+ if let Some(p) = it.syntax().parent() {
+ if ast::ExprStmt::can_cast(p.kind()) {
+ if let Some(kind) = inbetween_body_and_decl_check(p) {
+ return Some(make_res(NameRefKind::Keyword(kind)));
+ }
+ }
+ }
+
+ path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind()));
+
+ make_path_kind_expr(it.into())
+ },
+ ast::TupleStructPat(it) => {
+ path_ctx.has_call_parens = true;
+ PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) }
+ },
+ ast::RecordPat(it) => {
+ path_ctx.has_call_parens = true;
+ PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) }
+ },
+ ast::PathPat(it) => {
+ PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())}
+ },
+ ast::MacroCall(it) => {
+ // A macro call in this position is usually a result of parsing recovery, so check that
+ if let Some(kind) = inbetween_body_and_decl_check(it.syntax().clone()) {
+ return Some(make_res(NameRefKind::Keyword(kind)));
+ }
+
+ kind_macro_call(it)?
+ },
+ ast::Meta(meta) => make_path_kind_attr(meta)?,
+ ast::Visibility(it) => PathKind::Vis { has_in_token: it.in_token().is_some() },
+ ast::UseTree(_) => PathKind::Use,
+ // completing inside a qualifier
+ ast::Path(parent) => {
+ path_ctx.parent = Some(parent.clone());
+ let parent = iter::successors(Some(parent), |it| it.parent_path()).last()?.syntax().parent()?;
+ match_ast! {
+ match parent {
+ ast::PathType(it) => make_path_kind_type(it.into()),
+ ast::PathExpr(it) => {
+ path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind()));
+
+ make_path_kind_expr(it.into())
+ },
+ ast::TupleStructPat(it) => {
+ path_ctx.has_call_parens = true;
+ PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) }
+ },
+ ast::RecordPat(it) => {
+ path_ctx.has_call_parens = true;
+ PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) }
+ },
+ ast::PathPat(it) => {
+ PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())}
+ },
+ ast::MacroCall(it) => {
+ kind_macro_call(it)?
+ },
+ ast::Meta(meta) => make_path_kind_attr(meta)?,
+ ast::Visibility(it) => PathKind::Vis { has_in_token: it.in_token().is_some() },
+ ast::UseTree(_) => PathKind::Use,
+ ast::RecordExpr(it) => make_path_kind_expr(it.into()),
+ _ => return None,
+ }
+ }
+ },
+ ast::RecordExpr(it) => make_path_kind_expr(it.into()),
+ _ => return None,
+ }
+ };
+
+ path_ctx.kind = kind;
+ path_ctx.has_type_args = segment.generic_arg_list().is_some();
+
+ // calculate the qualifier context
+ if let Some((qualifier, use_tree_parent)) = path_or_use_tree_qualifier(&path) {
+ path_ctx.use_tree_parent = use_tree_parent;
+ if !use_tree_parent && segment.coloncolon_token().is_some() {
+ path_ctx.qualified = Qualified::Absolute;
+ } else {
+ let qualifier = qualifier
+ .segment()
+ .and_then(|it| find_node_in_file(original_file, &it))
+ .map(|it| it.parent_path());
+ if let Some(qualifier) = qualifier {
+ let type_anchor = match qualifier.segment().and_then(|it| it.kind()) {
+ Some(ast::PathSegmentKind::Type {
+ type_ref: Some(type_ref),
+ trait_ref,
+ }) if qualifier.qualifier().is_none() => Some((type_ref, trait_ref)),
+ _ => None,
+ };
+
+ path_ctx.qualified = if let Some((ty, trait_ref)) = type_anchor {
+ let ty = match ty {
+ ast::Type::InferType(_) => None,
+ ty => sema.resolve_type(&ty),
+ };
+ let trait_ = trait_ref.and_then(|it| sema.resolve_trait(&it.path()?));
+ Qualified::TypeAnchor { ty, trait_ }
+ } else {
+ let res = sema.resolve_path(&qualifier);
+
+ // For understanding how and why super_chain_len is calculated the way it
+ // is check the documentation at it's definition
+ let mut segment_count = 0;
+ let super_count =
+ iter::successors(Some(qualifier.clone()), |p| p.qualifier())
+ .take_while(|p| {
+ p.segment()
+ .and_then(|s| {
+ segment_count += 1;
+ s.super_token()
+ })
+ .is_some()
+ })
+ .count();
+
+ let super_chain_len =
+ if segment_count > super_count { None } else { Some(super_count) };
+
+ Qualified::With { path: qualifier, resolution: res, super_chain_len }
+ }
+ };
+ }
+ } else if let Some(segment) = path.segment() {
+ if segment.coloncolon_token().is_some() {
+ path_ctx.qualified = Qualified::Absolute;
+ }
+ }
+
+ let mut qualifier_ctx = QualifierCtx::default();
+ if path_ctx.is_trivial_path() {
+ // fetch the full expression that may have qualifiers attached to it
+ let top_node = match path_ctx.kind {
+ PathKind::Expr { expr_ctx: ExprCtx { in_block_expr: true, .. } } => {
+ parent.ancestors().find(|it| ast::PathExpr::can_cast(it.kind())).and_then(|p| {
+ let parent = p.parent()?;
+ if ast::StmtList::can_cast(parent.kind()) {
+ Some(p)
+ } else if ast::ExprStmt::can_cast(parent.kind()) {
+ Some(parent)
+ } else {
+ None
+ }
+ })
+ }
+ PathKind::Item { .. } => {
+ parent.ancestors().find(|it| ast::MacroCall::can_cast(it.kind()))
+ }
+ _ => None,
+ };
+ if let Some(top) = top_node {
+ if let Some(NodeOrToken::Node(error_node)) =
+ syntax::algo::non_trivia_sibling(top.clone().into(), syntax::Direction::Prev)
+ {
+ if error_node.kind() == SyntaxKind::ERROR {
+ qualifier_ctx.unsafe_tok = error_node
+ .children_with_tokens()
+ .filter_map(NodeOrToken::into_token)
+ .find(|it| it.kind() == T![unsafe]);
+ qualifier_ctx.vis_node =
+ error_node.children().find_map(ast::Visibility::cast);
+ }
+ }
+
+ if let PathKind::Item { .. } = path_ctx.kind {
+ if qualifier_ctx.none() {
+ if let Some(t) = top.first_token() {
+ if let Some(prev) = t
+ .prev_token()
+ .and_then(|t| syntax::algo::skip_trivia_token(t, Direction::Prev))
+ {
+ if ![T![;], T!['}'], T!['{']].contains(&prev.kind()) {
+ // This was inferred to be an item position path, but it seems
+ // to be part of some other broken node which leaked into an item
+ // list
+ return None;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ Some((NameRefContext { nameref, kind: NameRefKind::Path(path_ctx) }, qualifier_ctx))
+ }
+}
+
+fn pattern_context_for(
+ sema: &Semantics<'_, RootDatabase>,
+ original_file: &SyntaxNode,
+ pat: ast::Pat,
+) -> PatternContext {
+ let mut param_ctx = None;
+ let (refutability, has_type_ascription) =
+ pat
+ .syntax()
+ .ancestors()
+ .skip_while(|it| ast::Pat::can_cast(it.kind()))
+ .next()
+ .map_or((PatternRefutability::Irrefutable, false), |node| {
+ let refutability = match_ast! {
+ match node {
+ ast::LetStmt(let_) => return (PatternRefutability::Irrefutable, let_.ty().is_some()),
+ ast::Param(param) => {
+ let has_type_ascription = param.ty().is_some();
+ param_ctx = (|| {
+ let fake_param_list = param.syntax().parent().and_then(ast::ParamList::cast)?;
+ let param_list = find_node_in_file_compensated(sema, original_file, &fake_param_list)?;
+ let param_list_owner = param_list.syntax().parent()?;
+ let kind = match_ast! {
+ match param_list_owner {
+ ast::ClosureExpr(closure) => ParamKind::Closure(closure),
+ ast::Fn(fn_) => ParamKind::Function(fn_),
+ _ => return None,
+ }
+ };
+ Some(ParamContext {
+ param_list, param, kind
+ })
+ })();
+ return (PatternRefutability::Irrefutable, has_type_ascription)
+ },
+ ast::MatchArm(_) => PatternRefutability::Refutable,
+ ast::LetExpr(_) => PatternRefutability::Refutable,
+ ast::ForExpr(_) => PatternRefutability::Irrefutable,
+ _ => PatternRefutability::Irrefutable,
+ }
+ };
+ (refutability, false)
+ });
+ let (ref_token, mut_token) = match &pat {
+ ast::Pat::IdentPat(it) => (it.ref_token(), it.mut_token()),
+ _ => (None, None),
+ };
+
+ PatternContext {
+ refutability,
+ param_ctx,
+ has_type_ascription,
+ parent_pat: pat.syntax().parent().and_then(ast::Pat::cast),
+ mut_token,
+ ref_token,
+ record_pat: None,
+ impl_: fetch_immediate_impl(sema, original_file, pat.syntax()),
+ }
+}
+
+fn fetch_immediate_impl(
+ sema: &Semantics<'_, RootDatabase>,
+ original_file: &SyntaxNode,
+ node: &SyntaxNode,
+) -> Option<ast::Impl> {
+ let mut ancestors = ancestors_in_file_compensated(sema, original_file, node)?
+ .filter_map(ast::Item::cast)
+ .filter(|it| !matches!(it, ast::Item::MacroCall(_)));
+
+ match ancestors.next()? {
+ ast::Item::Const(_) | ast::Item::Fn(_) | ast::Item::TypeAlias(_) => (),
+ ast::Item::Impl(it) => return Some(it),
+ _ => return None,
+ }
+ match ancestors.next()? {
+ ast::Item::Impl(it) => Some(it),
+ _ => None,
+ }
+}
+
+/// Attempts to find `node` inside `syntax` via `node`'s text range.
+/// If the fake identifier has been inserted after this node or inside of this node use the `_compensated` version instead.
+fn find_opt_node_in_file<N: AstNode>(syntax: &SyntaxNode, node: Option<N>) -> Option<N> {
+ find_node_in_file(syntax, &node?)
+}
+
+/// Attempts to find `node` inside `syntax` via `node`'s text range.
+/// If the fake identifier has been inserted after this node or inside of this node use the `_compensated` version instead.
+fn find_node_in_file<N: AstNode>(syntax: &SyntaxNode, node: &N) -> Option<N> {
+ let syntax_range = syntax.text_range();
+ let range = node.syntax().text_range();
+ let intersection = range.intersect(syntax_range)?;
+ syntax.covering_element(intersection).ancestors().find_map(N::cast)
+}
+
+/// Attempts to find `node` inside `syntax` via `node`'s text range while compensating
+/// for the offset introduced by the fake ident.
+/// This is wrong if `node` comes before the insertion point! Use `find_node_in_file` instead.
+fn find_node_in_file_compensated<N: AstNode>(
+ sema: &Semantics<'_, RootDatabase>,
+ in_file: &SyntaxNode,
+ node: &N,
+) -> Option<N> {
+ ancestors_in_file_compensated(sema, in_file, node.syntax())?.find_map(N::cast)
+}
+
+fn ancestors_in_file_compensated<'sema>(
+ sema: &'sema Semantics<'_, RootDatabase>,
+ in_file: &SyntaxNode,
+ node: &SyntaxNode,
+) -> Option<impl Iterator<Item = SyntaxNode> + 'sema> {
+ let syntax_range = in_file.text_range();
+ let range = node.text_range();
+ let end = range.end().checked_sub(TextSize::try_from(COMPLETION_MARKER.len()).ok()?)?;
+ if end < range.start() {
+ return None;
+ }
+ let range = TextRange::new(range.start(), end);
+ // our inserted ident could cause `range` to go outside of the original syntax, so cap it
+ let intersection = range.intersect(syntax_range)?;
+ let node = match in_file.covering_element(intersection) {
+ NodeOrToken::Node(node) => node,
+ NodeOrToken::Token(tok) => tok.parent()?,
+ };
+ Some(sema.ancestors_with_macros(node))
+}
+
+/// Attempts to find `node` inside `syntax` via `node`'s text range while compensating
+/// for the offset introduced by the fake ident..
+/// This is wrong if `node` comes before the insertion point! Use `find_node_in_file` instead.
+fn find_opt_node_in_file_compensated<N: AstNode>(
+ sema: &Semantics<'_, RootDatabase>,
+ syntax: &SyntaxNode,
+ node: Option<N>,
+) -> Option<N> {
+ find_node_in_file_compensated(sema, syntax, &node?)
+}
+
+fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<(ast::Path, bool)> {
+ if let Some(qual) = path.qualifier() {
+ return Some((qual, false));
+ }
+ let use_tree_list = path.syntax().ancestors().find_map(ast::UseTreeList::cast)?;
+ let use_tree = use_tree_list.syntax().parent().and_then(ast::UseTree::cast)?;
+ Some((use_tree.path()?, true))
+}
+
+pub(crate) fn is_in_token_of_for_loop(element: SyntaxElement) -> bool {
+ // oh my ...
+ (|| {
+ let syntax_token = element.into_token()?;
+ let range = syntax_token.text_range();
+ let for_expr = syntax_token.parent_ancestors().find_map(ast::ForExpr::cast)?;
+
+ // check if the current token is the `in` token of a for loop
+ if let Some(token) = for_expr.in_token() {
+ return Some(syntax_token == token);
+ }
+ let pat = for_expr.pat()?;
+ if range.end() < pat.syntax().text_range().end() {
+ // if we are inside or before the pattern we can't be at the `in` token position
+ return None;
+ }
+ let next_sibl = next_non_trivia_sibling(pat.syntax().clone().into())?;
+ Some(match next_sibl {
+ // the loop body is some node, if our token is at the start we are at the `in` position,
+ // otherwise we could be in a recovered expression, we don't wanna ruin completions there
+ syntax::NodeOrToken::Node(n) => n.text_range().start() == range.start(),
+ // the loop body consists of a single token, if we are this we are certainly at the `in` token position
+ syntax::NodeOrToken::Token(t) => t == syntax_token,
+ })
+ })()
+ .unwrap_or(false)
+}
+
+#[test]
+fn test_for_is_prev2() {
+ crate::tests::check_pattern_is_applicable(r"fn __() { for i i$0 }", is_in_token_of_for_loop);
+}
+
+pub(crate) fn is_in_loop_body(node: &SyntaxNode) -> bool {
+ node.ancestors()
+ .take_while(|it| it.kind() != SyntaxKind::FN && it.kind() != SyntaxKind::CLOSURE_EXPR)
+ .find_map(|it| {
+ let loop_body = match_ast! {
+ match it {
+ ast::ForExpr(it) => it.loop_body(),
+ ast::WhileExpr(it) => it.loop_body(),
+ ast::LoopExpr(it) => it.loop_body(),
+ _ => None,
+ }
+ };
+ loop_body.filter(|it| it.syntax().text_range().contains_range(node.text_range()))
+ })
+ .is_some()
+}
+
+fn previous_non_trivia_token(e: impl Into<SyntaxElement>) -> Option<SyntaxToken> {
+ let mut token = match e.into() {
+ SyntaxElement::Node(n) => n.first_token()?,
+ SyntaxElement::Token(t) => t,
+ }
+ .prev_token();
+ while let Some(inner) = token {
+ if !inner.kind().is_trivia() {
+ return Some(inner);
+ } else {
+ token = inner.prev_token();
+ }
+ }
+ None
+}
+
+fn next_non_trivia_sibling(ele: SyntaxElement) -> Option<SyntaxElement> {
+ let mut e = ele.next_sibling_or_token();
+ while let Some(inner) = e {
+ if !inner.kind().is_trivia() {
+ return Some(inner);
+ } else {
+ e = inner.next_sibling_or_token();
+ }
+ }
+ None
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs
new file mode 100644
index 000000000..50845b388
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs
@@ -0,0 +1,413 @@
+use expect_test::{expect, Expect};
+use hir::HirDisplay;
+
+use crate::{
+ context::CompletionContext,
+ tests::{position, TEST_CONFIG},
+};
+
+fn check_expected_type_and_name(ra_fixture: &str, expect: Expect) {
+ let (db, pos) = position(ra_fixture);
+ let config = TEST_CONFIG;
+ let (completion_context, _analysis) = CompletionContext::new(&db, pos, &config).unwrap();
+
+ let ty = completion_context
+ .expected_type
+ .map(|t| t.display_test(&db).to_string())
+ .unwrap_or("?".to_owned());
+
+ let name =
+ completion_context.expected_name.map_or_else(|| "?".to_owned(), |name| name.to_string());
+
+ expect.assert_eq(&format!("ty: {}, name: {}", ty, name));
+}
+
+#[test]
+fn expected_type_let_without_leading_char() {
+ cov_mark::check!(expected_type_let_without_leading_char);
+ check_expected_type_and_name(
+ r#"
+fn foo() {
+ let x: u32 = $0;
+}
+"#,
+ expect![[r#"ty: u32, name: x"#]],
+ );
+}
+
+#[test]
+fn expected_type_let_with_leading_char() {
+ cov_mark::check!(expected_type_let_with_leading_char);
+ check_expected_type_and_name(
+ r#"
+fn foo() {
+ let x: u32 = c$0;
+}
+"#,
+ expect![[r#"ty: u32, name: x"#]],
+ );
+}
+
+#[test]
+fn expected_type_let_pat() {
+ check_expected_type_and_name(
+ r#"
+fn foo() {
+ let x$0 = 0u32;
+}
+"#,
+ expect![[r#"ty: u32, name: ?"#]],
+ );
+ check_expected_type_and_name(
+ r#"
+fn foo() {
+ let $0 = 0u32;
+}
+"#,
+ expect![[r#"ty: u32, name: ?"#]],
+ );
+}
+
+#[test]
+fn expected_type_fn_param() {
+ cov_mark::check!(expected_type_fn_param);
+ check_expected_type_and_name(
+ r#"
+fn foo() { bar($0); }
+fn bar(x: u32) {}
+"#,
+ expect![[r#"ty: u32, name: x"#]],
+ );
+ check_expected_type_and_name(
+ r#"
+fn foo() { bar(c$0); }
+fn bar(x: u32) {}
+"#,
+ expect![[r#"ty: u32, name: x"#]],
+ );
+}
+
+#[test]
+fn expected_type_fn_param_ref() {
+ cov_mark::check!(expected_type_fn_param_ref);
+ check_expected_type_and_name(
+ r#"
+fn foo() { bar(&$0); }
+fn bar(x: &u32) {}
+"#,
+ expect![[r#"ty: u32, name: x"#]],
+ );
+ check_expected_type_and_name(
+ r#"
+fn foo() { bar(&mut $0); }
+fn bar(x: &mut u32) {}
+"#,
+ expect![[r#"ty: u32, name: x"#]],
+ );
+ check_expected_type_and_name(
+ r#"
+fn foo() { bar(& c$0); }
+fn bar(x: &u32) {}
+ "#,
+ expect![[r#"ty: u32, name: x"#]],
+ );
+ check_expected_type_and_name(
+ r#"
+fn foo() { bar(&mut c$0); }
+fn bar(x: &mut u32) {}
+"#,
+ expect![[r#"ty: u32, name: x"#]],
+ );
+ check_expected_type_and_name(
+ r#"
+fn foo() { bar(&c$0); }
+fn bar(x: &u32) {}
+ "#,
+ expect![[r#"ty: u32, name: x"#]],
+ );
+}
+
+#[test]
+fn expected_type_struct_field_without_leading_char() {
+ cov_mark::check!(expected_type_struct_field_without_leading_char);
+ check_expected_type_and_name(
+ r#"
+struct Foo { a: u32 }
+fn foo() {
+ Foo { a: $0 };
+}
+"#,
+ expect![[r#"ty: u32, name: a"#]],
+ )
+}
+
+#[test]
+fn expected_type_struct_field_followed_by_comma() {
+ cov_mark::check!(expected_type_struct_field_followed_by_comma);
+ check_expected_type_and_name(
+ r#"
+struct Foo { a: u32 }
+fn foo() {
+ Foo { a: $0, };
+}
+"#,
+ expect![[r#"ty: u32, name: a"#]],
+ )
+}
+
+#[test]
+fn expected_type_generic_struct_field() {
+ check_expected_type_and_name(
+ r#"
+struct Foo<T> { a: T }
+fn foo() -> Foo<u32> {
+ Foo { a: $0 }
+}
+"#,
+ expect![[r#"ty: u32, name: a"#]],
+ )
+}
+
+#[test]
+fn expected_type_struct_field_with_leading_char() {
+ cov_mark::check!(expected_type_struct_field_with_leading_char);
+ check_expected_type_and_name(
+ r#"
+struct Foo { a: u32 }
+fn foo() {
+ Foo { a: c$0 };
+}
+"#,
+ expect![[r#"ty: u32, name: a"#]],
+ );
+}
+
+#[test]
+fn expected_type_match_arm_without_leading_char() {
+ cov_mark::check!(expected_type_match_arm_without_leading_char);
+ check_expected_type_and_name(
+ r#"
+enum E { X }
+fn foo() {
+ match E::X { $0 }
+}
+"#,
+ expect![[r#"ty: E, name: ?"#]],
+ );
+}
+
+#[test]
+fn expected_type_match_arm_with_leading_char() {
+ cov_mark::check!(expected_type_match_arm_with_leading_char);
+ check_expected_type_and_name(
+ r#"
+enum E { X }
+fn foo() {
+ match E::X { c$0 }
+}
+"#,
+ expect![[r#"ty: E, name: ?"#]],
+ );
+}
+
+#[test]
+fn expected_type_match_arm_body_without_leading_char() {
+ cov_mark::check!(expected_type_match_arm_body_without_leading_char);
+ check_expected_type_and_name(
+ r#"
+struct Foo;
+enum E { X }
+fn foo() -> Foo {
+ match E::X { E::X => $0 }
+}
+"#,
+ expect![[r#"ty: Foo, name: ?"#]],
+ );
+}
+
+#[test]
+fn expected_type_match_body_arm_with_leading_char() {
+ cov_mark::check!(expected_type_match_arm_body_with_leading_char);
+ check_expected_type_and_name(
+ r#"
+struct Foo;
+enum E { X }
+fn foo() -> Foo {
+ match E::X { E::X => c$0 }
+}
+"#,
+ expect![[r#"ty: Foo, name: ?"#]],
+ );
+}
+
+#[test]
+fn expected_type_if_let_without_leading_char() {
+ cov_mark::check!(expected_type_if_let_without_leading_char);
+ check_expected_type_and_name(
+ r#"
+enum Foo { Bar, Baz, Quux }
+
+fn foo() {
+ let f = Foo::Quux;
+ if let $0 = f { }
+}
+"#,
+ expect![[r#"ty: Foo, name: ?"#]],
+ )
+}
+
+#[test]
+fn expected_type_if_let_with_leading_char() {
+ cov_mark::check!(expected_type_if_let_with_leading_char);
+ check_expected_type_and_name(
+ r#"
+enum Foo { Bar, Baz, Quux }
+
+fn foo() {
+ let f = Foo::Quux;
+ if let c$0 = f { }
+}
+"#,
+ expect![[r#"ty: Foo, name: ?"#]],
+ )
+}
+
+#[test]
+fn expected_type_fn_ret_without_leading_char() {
+ cov_mark::check!(expected_type_fn_ret_without_leading_char);
+ check_expected_type_and_name(
+ r#"
+fn foo() -> u32 {
+ $0
+}
+"#,
+ expect![[r#"ty: u32, name: ?"#]],
+ )
+}
+
+#[test]
+fn expected_type_fn_ret_with_leading_char() {
+ cov_mark::check!(expected_type_fn_ret_with_leading_char);
+ check_expected_type_and_name(
+ r#"
+fn foo() -> u32 {
+ c$0
+}
+"#,
+ expect![[r#"ty: u32, name: ?"#]],
+ )
+}
+
+#[test]
+fn expected_type_fn_ret_fn_ref_fully_typed() {
+ check_expected_type_and_name(
+ r#"
+fn foo() -> u32 {
+ foo$0
+}
+"#,
+ expect![[r#"ty: u32, name: ?"#]],
+ )
+}
+
+#[test]
+fn expected_type_closure_param_return() {
+ // FIXME: make this work with `|| $0`
+ check_expected_type_and_name(
+ r#"
+//- minicore: fn
+fn foo() {
+ bar(|| a$0);
+}
+
+fn bar(f: impl FnOnce() -> u32) {}
+"#,
+ expect![[r#"ty: u32, name: ?"#]],
+ );
+}
+
+#[test]
+fn expected_type_generic_function() {
+ check_expected_type_and_name(
+ r#"
+fn foo() {
+ bar::<u32>($0);
+}
+
+fn bar<T>(t: T) {}
+"#,
+ expect![[r#"ty: u32, name: t"#]],
+ );
+}
+
+#[test]
+fn expected_type_generic_method() {
+ check_expected_type_and_name(
+ r#"
+fn foo() {
+ S(1u32).bar($0);
+}
+
+struct S<T>(T);
+impl<T> S<T> {
+ fn bar(self, t: T) {}
+}
+"#,
+ expect![[r#"ty: u32, name: t"#]],
+ );
+}
+
+#[test]
+fn expected_type_functional_update() {
+ cov_mark::check!(expected_type_struct_func_update);
+ check_expected_type_and_name(
+ r#"
+struct Foo { field: u32 }
+fn foo() {
+ Foo {
+ ..$0
+ }
+}
+"#,
+ expect![[r#"ty: Foo, name: ?"#]],
+ );
+}
+
+#[test]
+fn expected_type_param_pat() {
+ check_expected_type_and_name(
+ r#"
+struct Foo { field: u32 }
+fn foo(a$0: Foo) {}
+"#,
+ expect![[r#"ty: Foo, name: ?"#]],
+ );
+ check_expected_type_and_name(
+ r#"
+struct Foo { field: u32 }
+fn foo($0: Foo) {}
+"#,
+ // FIXME make this work, currently fails due to pattern recovery eating the `:`
+ expect![[r#"ty: ?, name: ?"#]],
+ );
+}
+
+#[test]
+fn expected_type_ref_prefix_on_field() {
+ check_expected_type_and_name(
+ r#"
+fn foo(_: &mut i32) {}
+struct S {
+ field: i32,
+}
+
+fn main() {
+ let s = S {
+ field: 100,
+ };
+ foo(&mut s.f$0);
+}
+"#,
+ expect!["ty: i32, name: ?"],
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/item.rs b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs
new file mode 100644
index 000000000..27c3ccb35
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs
@@ -0,0 +1,637 @@
+//! See `CompletionItem` structure.
+
+use std::fmt;
+
+use hir::{Documentation, Mutability};
+use ide_db::{imports::import_assets::LocatedImport, SnippetCap, SymbolKind};
+use smallvec::SmallVec;
+use stdx::{impl_from, never};
+use syntax::{SmolStr, TextRange, TextSize};
+use text_edit::TextEdit;
+
+use crate::{
+ context::{CompletionContext, PathCompletionCtx},
+ render::{render_path_resolution, RenderContext},
+};
+
+/// `CompletionItem` describes a single completion variant in the editor pop-up.
+/// It is basically a POD with various properties. To construct a
+/// `CompletionItem`, use `new` method and the `Builder` struct.
+#[derive(Clone)]
+pub struct CompletionItem {
+ /// Label in the completion pop up which identifies completion.
+ label: SmolStr,
+ /// Range of identifier that is being completed.
+ ///
+ /// It should be used primarily for UI, but we also use this to convert
+ /// generic TextEdit into LSP's completion edit (see conv.rs).
+ ///
+ /// `source_range` must contain the completion offset. `text_edit` should
+ /// start with what `source_range` points to, or VSCode will filter out the
+ /// completion silently.
+ source_range: TextRange,
+ /// What happens when user selects this item.
+ ///
+ /// Typically, replaces `source_range` with new identifier.
+ text_edit: TextEdit,
+ is_snippet: bool,
+
+ /// What item (struct, function, etc) are we completing.
+ kind: CompletionItemKind,
+
+ /// Lookup is used to check if completion item indeed can complete current
+ /// ident.
+ ///
+ /// That is, in `foo.bar$0` lookup of `abracadabra` will be accepted (it
+ /// contains `bar` sub sequence), and `quux` will rejected.
+ lookup: Option<SmolStr>,
+
+ /// Additional info to show in the UI pop up.
+ detail: Option<String>,
+ documentation: Option<Documentation>,
+
+ /// Whether this item is marked as deprecated
+ deprecated: bool,
+
+ /// If completing a function call, ask the editor to show parameter popup
+ /// after completion.
+ trigger_call_info: bool,
+
+ /// We use this to sort completion. Relevance records facts like "do the
+ /// types align precisely?". We can't sort by relevances directly, they are
+ /// only partially ordered.
+ ///
+ /// Note that Relevance ignores fuzzy match score. We compute Relevance for
+ /// all possible items, and then separately build an ordered completion list
+ /// based on relevance and fuzzy matching with the already typed identifier.
+ relevance: CompletionRelevance,
+
+ /// Indicates that a reference or mutable reference to this variable is a
+ /// possible match.
+ ref_match: Option<(Mutability, TextSize)>,
+
+ /// The import data to add to completion's edits.
+ import_to_add: SmallVec<[LocatedImport; 1]>,
+}
+
+// We use custom debug for CompletionItem to make snapshot tests more readable.
+impl fmt::Debug for CompletionItem {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let mut s = f.debug_struct("CompletionItem");
+ s.field("label", &self.label()).field("source_range", &self.source_range());
+ if self.text_edit().len() == 1 {
+ let atom = &self.text_edit().iter().next().unwrap();
+ s.field("delete", &atom.delete);
+ s.field("insert", &atom.insert);
+ } else {
+ s.field("text_edit", &self.text_edit);
+ }
+ s.field("kind", &self.kind());
+ if self.lookup() != self.label() {
+ s.field("lookup", &self.lookup());
+ }
+ if let Some(detail) = self.detail() {
+ s.field("detail", &detail);
+ }
+ if let Some(documentation) = self.documentation() {
+ s.field("documentation", &documentation);
+ }
+ if self.deprecated {
+ s.field("deprecated", &true);
+ }
+
+ if self.relevance != CompletionRelevance::default() {
+ s.field("relevance", &self.relevance);
+ }
+
+ if let Some((mutability, offset)) = &self.ref_match {
+ s.field("ref_match", &format!("&{}@{offset:?}", mutability.as_keyword_for_ref()));
+ }
+ if self.trigger_call_info {
+ s.field("trigger_call_info", &true);
+ }
+ s.finish()
+ }
+}
+
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
+pub struct CompletionRelevance {
+ /// This is set in cases like these:
+ ///
+ /// ```
+ /// fn f(spam: String) {}
+ /// fn main {
+ /// let spam = 92;
+ /// f($0) // name of local matches the name of param
+ /// }
+ /// ```
+ pub exact_name_match: bool,
+ /// See CompletionRelevanceTypeMatch doc comments for cases where this is set.
+ pub type_match: Option<CompletionRelevanceTypeMatch>,
+ /// This is set in cases like these:
+ ///
+ /// ```
+ /// fn foo(a: u32) {
+ /// let b = 0;
+ /// $0 // `a` and `b` are local
+ /// }
+ /// ```
+ pub is_local: bool,
+ /// This is set when trait items are completed in an impl of that trait.
+ pub is_item_from_trait: bool,
+ /// This is set when an import is suggested whose name is already imported.
+ pub is_name_already_imported: bool,
+ /// This is set for completions that will insert a `use` item.
+ pub requires_import: bool,
+ /// Set for method completions of the `core::ops` and `core::cmp` family.
+ pub is_op_method: bool,
+ /// Set for item completions that are private but in the workspace.
+ pub is_private_editable: bool,
+ /// Set for postfix snippet item completions
+ pub postfix_match: Option<CompletionRelevancePostfixMatch>,
+ /// This is set for type inference results
+ pub is_definite: bool,
+}
+
+#[derive(Debug, Clone, Copy, Eq, PartialEq)]
+pub enum CompletionRelevanceTypeMatch {
+ /// This is set in cases like these:
+ ///
+ /// ```
+ /// enum Option<T> { Some(T), None }
+ /// fn f(a: Option<u32>) {}
+ /// fn main {
+ /// f(Option::N$0) // type `Option<T>` could unify with `Option<u32>`
+ /// }
+ /// ```
+ CouldUnify,
+ /// This is set in cases like these:
+ ///
+ /// ```
+ /// fn f(spam: String) {}
+ /// fn main {
+ /// let foo = String::new();
+ /// f($0) // type of local matches the type of param
+ /// }
+ /// ```
+ Exact,
+}
+
+#[derive(Debug, Clone, Copy, Eq, PartialEq)]
+pub enum CompletionRelevancePostfixMatch {
+ /// Set in cases when item is postfix, but not exact
+ NonExact,
+ /// This is set in cases like these:
+ ///
+ /// ```
+ /// (a > b).not$0
+ /// ```
+ ///
+ /// Basically, we want to guarantee that postfix snippets always takes
+ /// precedence over everything else.
+ Exact,
+}
+
+impl CompletionRelevance {
+ /// Provides a relevance score. Higher values are more relevant.
+ ///
+ /// The absolute value of the relevance score is not meaningful, for
+ /// example a value of 0 doesn't mean "not relevant", rather
+ /// it means "least relevant". The score value should only be used
+ /// for relative ordering.
+ ///
+ /// See is_relevant if you need to make some judgement about score
+ /// in an absolute sense.
+ pub fn score(self) -> u32 {
+ let mut score = 0;
+ let CompletionRelevance {
+ exact_name_match,
+ type_match,
+ is_local,
+ is_item_from_trait,
+ is_name_already_imported,
+ requires_import,
+ is_op_method,
+ is_private_editable,
+ postfix_match,
+ is_definite,
+ } = self;
+
+ // lower rank private things
+ if !is_private_editable {
+ score += 1;
+ }
+ // lower rank trait op methods
+ if !is_op_method {
+ score += 10;
+ }
+ // lower rank for conflicting import names
+ if !is_name_already_imported {
+ score += 1;
+ }
+ // lower rank for items that don't need an import
+ if !requires_import {
+ score += 1;
+ }
+ if exact_name_match {
+ score += 10;
+ }
+ score += match postfix_match {
+ Some(CompletionRelevancePostfixMatch::Exact) => 100,
+ Some(CompletionRelevancePostfixMatch::NonExact) => 0,
+ None => 3,
+ };
+ score += match type_match {
+ Some(CompletionRelevanceTypeMatch::Exact) => 8,
+ Some(CompletionRelevanceTypeMatch::CouldUnify) => 3,
+ None => 0,
+ };
+ // slightly prefer locals
+ if is_local {
+ score += 1;
+ }
+ if is_item_from_trait {
+ score += 1;
+ }
+ if is_definite {
+ score += 10;
+ }
+ score
+ }
+
+ /// Returns true when the score for this threshold is above
+ /// some threshold such that we think it is especially likely
+ /// to be relevant.
+ pub fn is_relevant(&self) -> bool {
+ self.score() > 0
+ }
+}
+
+/// The type of the completion item.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub enum CompletionItemKind {
+ SymbolKind(SymbolKind),
+ Binding,
+ BuiltinType,
+ InferredType,
+ Keyword,
+ Method,
+ Snippet,
+ UnresolvedReference,
+}
+
+impl_from!(SymbolKind for CompletionItemKind);
+
+impl CompletionItemKind {
+ #[cfg(test)]
+ pub(crate) fn tag(&self) -> &'static str {
+ match self {
+ CompletionItemKind::SymbolKind(kind) => match kind {
+ SymbolKind::Attribute => "at",
+ SymbolKind::BuiltinAttr => "ba",
+ SymbolKind::Const => "ct",
+ SymbolKind::ConstParam => "cp",
+ SymbolKind::Derive => "de",
+ SymbolKind::DeriveHelper => "dh",
+ SymbolKind::Enum => "en",
+ SymbolKind::Field => "fd",
+ SymbolKind::Function => "fn",
+ SymbolKind::Impl => "im",
+ SymbolKind::Label => "lb",
+ SymbolKind::LifetimeParam => "lt",
+ SymbolKind::Local => "lc",
+ SymbolKind::Macro => "ma",
+ SymbolKind::Module => "md",
+ SymbolKind::SelfParam => "sp",
+ SymbolKind::SelfType => "sy",
+ SymbolKind::Static => "sc",
+ SymbolKind::Struct => "st",
+ SymbolKind::ToolModule => "tm",
+ SymbolKind::Trait => "tt",
+ SymbolKind::TypeAlias => "ta",
+ SymbolKind::TypeParam => "tp",
+ SymbolKind::Union => "un",
+ SymbolKind::ValueParam => "vp",
+ SymbolKind::Variant => "ev",
+ },
+ CompletionItemKind::Binding => "bn",
+ CompletionItemKind::BuiltinType => "bt",
+ CompletionItemKind::InferredType => "it",
+ CompletionItemKind::Keyword => "kw",
+ CompletionItemKind::Method => "me",
+ CompletionItemKind::Snippet => "sn",
+ CompletionItemKind::UnresolvedReference => "??",
+ }
+ }
+}
+
+impl CompletionItem {
+ pub(crate) fn new(
+ kind: impl Into<CompletionItemKind>,
+ source_range: TextRange,
+ label: impl Into<SmolStr>,
+ ) -> Builder {
+ let label = label.into();
+ Builder {
+ source_range,
+ label,
+ insert_text: None,
+ is_snippet: false,
+ trait_name: None,
+ detail: None,
+ documentation: None,
+ lookup: None,
+ kind: kind.into(),
+ text_edit: None,
+ deprecated: false,
+ trigger_call_info: false,
+ relevance: CompletionRelevance::default(),
+ ref_match: None,
+ imports_to_add: Default::default(),
+ }
+ }
+
+ /// What user sees in pop-up in the UI.
+ pub fn label(&self) -> &str {
+ &self.label
+ }
+ pub fn source_range(&self) -> TextRange {
+ self.source_range
+ }
+
+ pub fn text_edit(&self) -> &TextEdit {
+ &self.text_edit
+ }
+ /// Whether `text_edit` is a snippet (contains `$0` markers).
+ pub fn is_snippet(&self) -> bool {
+ self.is_snippet
+ }
+
+ /// Short one-line additional information, like a type
+ pub fn detail(&self) -> Option<&str> {
+ self.detail.as_deref()
+ }
+ /// A doc-comment
+ pub fn documentation(&self) -> Option<Documentation> {
+ self.documentation.clone()
+ }
+ /// What string is used for filtering.
+ pub fn lookup(&self) -> &str {
+ self.lookup.as_deref().unwrap_or(&self.label)
+ }
+
+ pub fn kind(&self) -> CompletionItemKind {
+ self.kind
+ }
+
+ pub fn deprecated(&self) -> bool {
+ self.deprecated
+ }
+
+ pub fn relevance(&self) -> CompletionRelevance {
+ self.relevance
+ }
+
+ pub fn trigger_call_info(&self) -> bool {
+ self.trigger_call_info
+ }
+
+ pub fn ref_match(&self) -> Option<(Mutability, TextSize, CompletionRelevance)> {
+ // Relevance of the ref match should be the same as the original
+ // match, but with exact type match set because self.ref_match
+ // is only set if there is an exact type match.
+ let mut relevance = self.relevance;
+ relevance.type_match = Some(CompletionRelevanceTypeMatch::Exact);
+
+ self.ref_match.map(|(mutability, offset)| (mutability, offset, relevance))
+ }
+
+ pub fn imports_to_add(&self) -> &[LocatedImport] {
+ &self.import_to_add
+ }
+}
+
+/// A helper to make `CompletionItem`s.
+#[must_use]
+#[derive(Clone)]
+pub(crate) struct Builder {
+ source_range: TextRange,
+ imports_to_add: SmallVec<[LocatedImport; 1]>,
+ trait_name: Option<SmolStr>,
+ label: SmolStr,
+ insert_text: Option<String>,
+ is_snippet: bool,
+ detail: Option<String>,
+ documentation: Option<Documentation>,
+ lookup: Option<SmolStr>,
+ kind: CompletionItemKind,
+ text_edit: Option<TextEdit>,
+ deprecated: bool,
+ trigger_call_info: bool,
+ relevance: CompletionRelevance,
+ ref_match: Option<(Mutability, TextSize)>,
+}
+
+impl Builder {
+ pub(crate) fn from_resolution(
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ local_name: hir::Name,
+ resolution: hir::ScopeDef,
+ ) -> Self {
+ render_path_resolution(RenderContext::new(ctx), path_ctx, local_name, resolution)
+ }
+
+ pub(crate) fn build(self) -> CompletionItem {
+ let _p = profile::span("item::Builder::build");
+
+ let mut label = self.label;
+ let mut lookup = self.lookup;
+ let insert_text = self.insert_text.unwrap_or_else(|| label.to_string());
+
+ if let [import_edit] = &*self.imports_to_add {
+ // snippets can have multiple imports, but normal completions only have up to one
+ if let Some(original_path) = import_edit.original_path.as_ref() {
+ lookup = lookup.or_else(|| Some(label.clone()));
+ label = SmolStr::from(format!("{} (use {})", label, original_path));
+ }
+ } else if let Some(trait_name) = self.trait_name {
+ label = SmolStr::from(format!("{} (as {})", label, trait_name));
+ }
+
+ let text_edit = match self.text_edit {
+ Some(it) => it,
+ None => TextEdit::replace(self.source_range, insert_text),
+ };
+
+ CompletionItem {
+ source_range: self.source_range,
+ label,
+ text_edit,
+ is_snippet: self.is_snippet,
+ detail: self.detail,
+ documentation: self.documentation,
+ lookup,
+ kind: self.kind,
+ deprecated: self.deprecated,
+ trigger_call_info: self.trigger_call_info,
+ relevance: self.relevance,
+ ref_match: self.ref_match,
+ import_to_add: self.imports_to_add,
+ }
+ }
+ pub(crate) fn lookup_by(&mut self, lookup: impl Into<SmolStr>) -> &mut Builder {
+ self.lookup = Some(lookup.into());
+ self
+ }
+ pub(crate) fn label(&mut self, label: impl Into<SmolStr>) -> &mut Builder {
+ self.label = label.into();
+ self
+ }
+ pub(crate) fn trait_name(&mut self, trait_name: SmolStr) -> &mut Builder {
+ self.trait_name = Some(trait_name);
+ self
+ }
+ pub(crate) fn insert_text(&mut self, insert_text: impl Into<String>) -> &mut Builder {
+ self.insert_text = Some(insert_text.into());
+ self
+ }
+ pub(crate) fn insert_snippet(
+ &mut self,
+ cap: SnippetCap,
+ snippet: impl Into<String>,
+ ) -> &mut Builder {
+ let _ = cap;
+ self.is_snippet = true;
+ self.insert_text(snippet)
+ }
+ pub(crate) fn text_edit(&mut self, edit: TextEdit) -> &mut Builder {
+ self.text_edit = Some(edit);
+ self
+ }
+ pub(crate) fn snippet_edit(&mut self, _cap: SnippetCap, edit: TextEdit) -> &mut Builder {
+ self.is_snippet = true;
+ self.text_edit(edit)
+ }
+ pub(crate) fn detail(&mut self, detail: impl Into<String>) -> &mut Builder {
+ self.set_detail(Some(detail))
+ }
+ pub(crate) fn set_detail(&mut self, detail: Option<impl Into<String>>) -> &mut Builder {
+ self.detail = detail.map(Into::into);
+ if let Some(detail) = &self.detail {
+ if never!(detail.contains('\n'), "multiline detail:\n{}", detail) {
+ self.detail = Some(detail.splitn(2, '\n').next().unwrap().to_string());
+ }
+ }
+ self
+ }
+ #[allow(unused)]
+ pub(crate) fn documentation(&mut self, docs: Documentation) -> &mut Builder {
+ self.set_documentation(Some(docs))
+ }
+ pub(crate) fn set_documentation(&mut self, docs: Option<Documentation>) -> &mut Builder {
+ self.documentation = docs.map(Into::into);
+ self
+ }
+ pub(crate) fn set_deprecated(&mut self, deprecated: bool) -> &mut Builder {
+ self.deprecated = deprecated;
+ self
+ }
+ pub(crate) fn set_relevance(&mut self, relevance: CompletionRelevance) -> &mut Builder {
+ self.relevance = relevance;
+ self
+ }
+ pub(crate) fn trigger_call_info(&mut self) -> &mut Builder {
+ self.trigger_call_info = true;
+ self
+ }
+ pub(crate) fn add_import(&mut self, import_to_add: LocatedImport) -> &mut Builder {
+ self.imports_to_add.push(import_to_add);
+ self
+ }
+ pub(crate) fn ref_match(&mut self, mutability: Mutability, offset: TextSize) -> &mut Builder {
+ self.ref_match = Some((mutability, offset));
+ self
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use itertools::Itertools;
+ use test_utils::assert_eq_text;
+
+ use super::{
+ CompletionRelevance, CompletionRelevancePostfixMatch, CompletionRelevanceTypeMatch,
+ };
+
+ /// Check that these are CompletionRelevance are sorted in ascending order
+ /// by their relevance score.
+ ///
+ /// We want to avoid making assertions about the absolute score of any
+ /// item, but we do want to assert whether each is >, <, or == to the
+ /// others.
+ ///
+ /// If provided vec![vec![a], vec![b, c], vec![d]], then this will assert:
+ /// a.score < b.score == c.score < d.score
+ fn check_relevance_score_ordered(expected_relevance_order: Vec<Vec<CompletionRelevance>>) {
+ let expected = format!("{:#?}", &expected_relevance_order);
+
+ let actual_relevance_order = expected_relevance_order
+ .into_iter()
+ .flatten()
+ .map(|r| (r.score(), r))
+ .sorted_by_key(|(score, _r)| *score)
+ .fold(
+ (u32::MIN, vec![vec![]]),
+ |(mut currently_collecting_score, mut out), (score, r)| {
+ if currently_collecting_score == score {
+ out.last_mut().unwrap().push(r);
+ } else {
+ currently_collecting_score = score;
+ out.push(vec![r]);
+ }
+ (currently_collecting_score, out)
+ },
+ )
+ .1;
+
+ let actual = format!("{:#?}", &actual_relevance_order);
+
+ assert_eq_text!(&expected, &actual);
+ }
+
+ #[test]
+ fn relevance_score() {
+ use CompletionRelevance as Cr;
+ let default = Cr::default();
+ // This test asserts that the relevance score for these items is ascending, and
+ // that any items in the same vec have the same score.
+ let expected_relevance_order = vec![
+ vec![],
+ vec![Cr { is_op_method: true, is_private_editable: true, ..default }],
+ vec![Cr { is_op_method: true, ..default }],
+ vec![Cr { postfix_match: Some(CompletionRelevancePostfixMatch::NonExact), ..default }],
+ vec![Cr { is_private_editable: true, ..default }],
+ vec![default],
+ vec![Cr { is_local: true, ..default }],
+ vec![Cr { type_match: Some(CompletionRelevanceTypeMatch::CouldUnify), ..default }],
+ vec![Cr { type_match: Some(CompletionRelevanceTypeMatch::Exact), ..default }],
+ vec![Cr { exact_name_match: true, ..default }],
+ vec![Cr { exact_name_match: true, is_local: true, ..default }],
+ vec![Cr {
+ exact_name_match: true,
+ type_match: Some(CompletionRelevanceTypeMatch::Exact),
+ ..default
+ }],
+ vec![Cr {
+ exact_name_match: true,
+ type_match: Some(CompletionRelevanceTypeMatch::Exact),
+ is_local: true,
+ ..default
+ }],
+ vec![Cr { postfix_match: Some(CompletionRelevancePostfixMatch::Exact), ..default }],
+ ];
+
+ check_relevance_score_ordered(expected_relevance_order);
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs b/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs
new file mode 100644
index 000000000..ae1a440d0
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs
@@ -0,0 +1,247 @@
+//! `completions` crate provides utilities for generating completions of user input.
+
+#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
+
+mod completions;
+mod config;
+mod context;
+mod item;
+mod render;
+
+#[cfg(test)]
+mod tests;
+mod snippet;
+
+use ide_db::{
+ base_db::FilePosition,
+ helpers::mod_path_to_ast,
+ imports::{
+ import_assets::NameToImport,
+ insert_use::{self, ImportScope},
+ },
+ items_locator, RootDatabase,
+};
+use syntax::algo;
+use text_edit::TextEdit;
+
+use crate::{
+ completions::Completions,
+ context::{
+ CompletionAnalysis, CompletionContext, NameRefContext, NameRefKind, PathCompletionCtx,
+ PathKind,
+ },
+};
+
+pub use crate::{
+ config::{CallableSnippets, CompletionConfig},
+ item::{
+ CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevancePostfixMatch,
+ },
+ snippet::{Snippet, SnippetScope},
+};
+
+//FIXME: split the following feature into fine-grained features.
+
+// Feature: Magic Completions
+//
+// In addition to usual reference completion, rust-analyzer provides some ✨magic✨
+// completions as well:
+//
+// Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor
+// is placed at the appropriate position. Even though `if` is easy to type, you
+// still want to complete it, to get ` { }` for free! `return` is inserted with a
+// space or `;` depending on the return type of the function.
+//
+// When completing a function call, `()` are automatically inserted. If a function
+// takes arguments, the cursor is positioned inside the parenthesis.
+//
+// There are postfix completions, which can be triggered by typing something like
+// `foo().if`. The word after `.` determines postfix completion. Possible variants are:
+//
+// - `expr.if` -> `if expr {}` or `if let ... {}` for `Option` or `Result`
+// - `expr.match` -> `match expr {}`
+// - `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result`
+// - `expr.ref` -> `&expr`
+// - `expr.refm` -> `&mut expr`
+// - `expr.let` -> `let $0 = expr;`
+// - `expr.letm` -> `let mut $0 = expr;`
+// - `expr.not` -> `!expr`
+// - `expr.dbg` -> `dbg!(expr)`
+// - `expr.dbgr` -> `dbg!(&expr)`
+// - `expr.call` -> `(expr)`
+//
+// There also snippet completions:
+//
+// .Expressions
+// - `pd` -> `eprintln!(" = {:?}", );`
+// - `ppd` -> `eprintln!(" = {:#?}", );`
+//
+// .Items
+// - `tfn` -> `#[test] fn feature(){}`
+// - `tmod` ->
+// ```rust
+// #[cfg(test)]
+// mod tests {
+// use super::*;
+//
+// #[test]
+// fn test_name() {}
+// }
+// ```
+//
+// And the auto import completions, enabled with the `rust-analyzer.completion.autoimport.enable` setting and the corresponding LSP client capabilities.
+// Those are the additional completion options with automatic `use` import and options from all project importable items,
+// fuzzy matched against the completion input.
+//
+// image::https://user-images.githubusercontent.com/48062697/113020667-b72ab880-917a-11eb-8778-716cf26a0eb3.gif[]
+
+/// Main entry point for completion. We run completion as a two-phase process.
+///
+/// First, we look at the position and collect a so-called `CompletionContext.
+/// This is a somewhat messy process, because, during completion, syntax tree is
+/// incomplete and can look really weird.
+///
+/// Once the context is collected, we run a series of completion routines which
+/// look at the context and produce completion items. One subtlety about this
+/// phase is that completion engine should not filter by the substring which is
+/// already present, it should give all possible variants for the identifier at
+/// the caret. In other words, for
+///
+/// ```no_run
+/// fn f() {
+/// let foo = 92;
+/// let _ = bar$0
+/// }
+/// ```
+///
+/// `foo` *should* be present among the completion variants. Filtering by
+/// identifier prefix/fuzzy match should be done higher in the stack, together
+/// with ordering of completions (currently this is done by the client).
+///
+/// # Speculative Completion Problem
+///
+/// There's a curious unsolved problem in the current implementation. Often, you
+/// want to compute completions on a *slightly different* text document.
+///
+/// In the simplest case, when the code looks like `let x = `, you want to
+/// insert a fake identifier to get a better syntax tree: `let x = complete_me`.
+///
+/// We do this in `CompletionContext`, and it works OK-enough for *syntax*
+/// analysis. However, we might want to, eg, ask for the type of `complete_me`
+/// variable, and that's where our current infrastructure breaks down. salsa
+/// doesn't allow such "phantom" inputs.
+///
+/// Another case where this would be instrumental is macro expansion. We want to
+/// insert a fake ident and re-expand code. There's `expand_speculative` as a
+/// work-around for this.
+///
+/// A different use-case is completion of injection (examples and links in doc
+/// comments). When computing completion for a path in a doc-comment, you want
+/// to inject a fake path expression into the item being documented and complete
+/// that.
+///
+/// IntelliJ has CodeFragment/Context infrastructure for that. You can create a
+/// temporary PSI node, and say that the context ("parent") of this node is some
+/// existing node. Asking for, eg, type of this `CodeFragment` node works
+/// correctly, as the underlying infrastructure makes use of contexts to do
+/// analysis.
+pub fn completions(
+ db: &RootDatabase,
+ config: &CompletionConfig,
+ position: FilePosition,
+ trigger_character: Option<char>,
+) -> Option<Vec<CompletionItem>> {
+ let (ctx, analysis) = &CompletionContext::new(db, position, config)?;
+ let mut completions = Completions::default();
+
+ // prevent `(` from triggering unwanted completion noise
+ if trigger_character == Some('(') {
+ if let CompletionAnalysis::NameRef(NameRefContext { kind, .. }) = &analysis {
+ if let NameRefKind::Path(
+ path_ctx @ PathCompletionCtx { kind: PathKind::Vis { has_in_token }, .. },
+ ) = kind
+ {
+ completions::vis::complete_vis_path(&mut completions, ctx, path_ctx, has_in_token);
+ }
+ }
+ // prevent `(` from triggering unwanted completion noise
+ return Some(completions.into());
+ }
+
+ {
+ let acc = &mut completions;
+
+ match &analysis {
+ CompletionAnalysis::Name(name_ctx) => completions::complete_name(acc, ctx, name_ctx),
+ CompletionAnalysis::NameRef(name_ref_ctx) => {
+ completions::complete_name_ref(acc, ctx, name_ref_ctx)
+ }
+ CompletionAnalysis::Lifetime(lifetime_ctx) => {
+ completions::lifetime::complete_label(acc, ctx, lifetime_ctx);
+ completions::lifetime::complete_lifetime(acc, ctx, lifetime_ctx);
+ }
+ CompletionAnalysis::String { original, expanded: Some(expanded) } => {
+ completions::extern_abi::complete_extern_abi(acc, ctx, expanded);
+ completions::format_string::format_string(acc, ctx, original, expanded);
+ }
+ CompletionAnalysis::UnexpandedAttrTT {
+ colon_prefix,
+ fake_attribute_under_caret: Some(attr),
+ } => {
+ completions::attribute::complete_known_attribute_input(
+ acc,
+ ctx,
+ colon_prefix,
+ attr,
+ );
+ }
+ CompletionAnalysis::UnexpandedAttrTT { .. } | CompletionAnalysis::String { .. } => (),
+ }
+ }
+
+ Some(completions.into())
+}
+
+/// Resolves additional completion data at the position given.
+/// This is used for import insertion done via completions like flyimport and custom user snippets.
+pub fn resolve_completion_edits(
+ db: &RootDatabase,
+ config: &CompletionConfig,
+ FilePosition { file_id, offset }: FilePosition,
+ imports: impl IntoIterator<Item = (String, String)>,
+) -> Option<Vec<TextEdit>> {
+ let _p = profile::span("resolve_completion_edits");
+ let sema = hir::Semantics::new(db);
+
+ let original_file = sema.parse(file_id);
+ let original_token =
+ syntax::AstNode::syntax(&original_file).token_at_offset(offset).left_biased()?;
+ let position_for_import = &original_token.parent()?;
+ let scope = ImportScope::find_insert_use_container(position_for_import, &sema)?;
+
+ let current_module = sema.scope(position_for_import)?.module();
+ let current_crate = current_module.krate();
+ let new_ast = scope.clone_for_update();
+ let mut import_insert = TextEdit::builder();
+
+ imports.into_iter().for_each(|(full_import_path, imported_name)| {
+ let items_with_name = items_locator::items_with_name(
+ &sema,
+ current_crate,
+ NameToImport::exact_case_sensitive(imported_name),
+ items_locator::AssocItemSearch::Include,
+ Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT.inner()),
+ );
+ let import = items_with_name
+ .filter_map(|candidate| {
+ current_module.find_use_path_prefixed(db, candidate, config.insert_use.prefix_kind)
+ })
+ .find(|mod_path| mod_path.to_string() == full_import_path);
+ if let Some(import_path) = import {
+ insert_use::insert_use(&new_ast, mod_path_to_ast(&import_path), &config.insert_use);
+ }
+ });
+
+ algo::diff(scope.as_syntax_node(), new_ast.as_syntax_node()).into_text_edit(&mut import_insert);
+ Some(vec![import_insert.finish()])
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render.rs
new file mode 100644
index 000000000..946134b0f
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/render.rs
@@ -0,0 +1,1910 @@
+//! `render` module provides utilities for rendering completion suggestions
+//! into code pieces that will be presented to user.
+
+pub(crate) mod macro_;
+pub(crate) mod function;
+pub(crate) mod const_;
+pub(crate) mod pattern;
+pub(crate) mod type_alias;
+pub(crate) mod variant;
+pub(crate) mod union_literal;
+pub(crate) mod literal;
+
+use hir::{AsAssocItem, HasAttrs, HirDisplay, ScopeDef};
+use ide_db::{
+ helpers::item_name, imports::import_assets::LocatedImport, RootDatabase, SnippetCap, SymbolKind,
+};
+use syntax::{AstNode, SmolStr, SyntaxKind, TextRange};
+
+use crate::{
+ context::{DotAccess, PathCompletionCtx, PathKind, PatternContext},
+ item::{Builder, CompletionRelevanceTypeMatch},
+ render::{
+ function::render_fn,
+ literal::render_variant_lit,
+ macro_::{render_macro, render_macro_pat},
+ },
+ CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance,
+};
+/// Interface for data and methods required for items rendering.
+#[derive(Debug, Clone)]
+pub(crate) struct RenderContext<'a> {
+ completion: &'a CompletionContext<'a>,
+ is_private_editable: bool,
+ import_to_add: Option<LocatedImport>,
+}
+
+impl<'a> RenderContext<'a> {
+ pub(crate) fn new(completion: &'a CompletionContext<'a>) -> RenderContext<'a> {
+ RenderContext { completion, is_private_editable: false, import_to_add: None }
+ }
+
+ pub(crate) fn private_editable(mut self, private_editable: bool) -> Self {
+ self.is_private_editable = private_editable;
+ self
+ }
+
+ pub(crate) fn import_to_add(mut self, import_to_add: Option<LocatedImport>) -> Self {
+ self.import_to_add = import_to_add;
+ self
+ }
+
+ fn snippet_cap(&self) -> Option<SnippetCap> {
+ self.completion.config.snippet_cap
+ }
+
+ fn db(&self) -> &'a RootDatabase {
+ self.completion.db
+ }
+
+ fn source_range(&self) -> TextRange {
+ self.completion.source_range()
+ }
+
+ fn completion_relevance(&self) -> CompletionRelevance {
+ CompletionRelevance {
+ is_private_editable: self.is_private_editable,
+ requires_import: self.import_to_add.is_some(),
+ ..Default::default()
+ }
+ }
+
+ fn is_immediately_after_macro_bang(&self) -> bool {
+ self.completion.token.kind() == SyntaxKind::BANG
+ && self
+ .completion
+ .token
+ .parent()
+ .map_or(false, |it| it.kind() == SyntaxKind::MACRO_CALL)
+ }
+
+ fn is_deprecated(&self, def: impl HasAttrs) -> bool {
+ let attrs = def.attrs(self.db());
+ attrs.by_key("deprecated").exists()
+ }
+
+ fn is_deprecated_assoc_item(&self, as_assoc_item: impl AsAssocItem) -> bool {
+ let db = self.db();
+ let assoc = match as_assoc_item.as_assoc_item(db) {
+ Some(assoc) => assoc,
+ None => return false,
+ };
+
+ let is_assoc_deprecated = match assoc {
+ hir::AssocItem::Function(it) => self.is_deprecated(it),
+ hir::AssocItem::Const(it) => self.is_deprecated(it),
+ hir::AssocItem::TypeAlias(it) => self.is_deprecated(it),
+ };
+ is_assoc_deprecated
+ || assoc
+ .containing_trait_or_trait_impl(db)
+ .map(|trait_| self.is_deprecated(trait_))
+ .unwrap_or(false)
+ }
+
+ // FIXME: remove this
+ fn docs(&self, def: impl HasAttrs) -> Option<hir::Documentation> {
+ def.docs(self.db())
+ }
+}
+
+pub(crate) fn render_field(
+ ctx: RenderContext<'_>,
+ dot_access: &DotAccess,
+ receiver: Option<hir::Name>,
+ field: hir::Field,
+ ty: &hir::Type,
+) -> CompletionItem {
+ let is_deprecated = ctx.is_deprecated(field);
+ let name = field.name(ctx.db());
+ let (name, escaped_name) = (name.to_smol_str(), name.escaped().to_smol_str());
+ let mut item = CompletionItem::new(
+ SymbolKind::Field,
+ ctx.source_range(),
+ field_with_receiver(receiver.as_ref(), &name),
+ );
+ item.set_relevance(CompletionRelevance {
+ type_match: compute_type_match(ctx.completion, ty),
+ exact_name_match: compute_exact_name_match(ctx.completion, name.as_str()),
+ ..CompletionRelevance::default()
+ });
+ item.detail(ty.display(ctx.db()).to_string())
+ .set_documentation(field.docs(ctx.db()))
+ .set_deprecated(is_deprecated)
+ .lookup_by(name.clone());
+ item.insert_text(field_with_receiver(receiver.as_ref(), &escaped_name));
+ if let Some(receiver) = &dot_access.receiver {
+ if let Some(original) = ctx.completion.sema.original_ast_node(receiver.clone()) {
+ if let Some(ref_match) = compute_ref_match(ctx.completion, ty) {
+ item.ref_match(ref_match, original.syntax().text_range().start());
+ }
+ }
+ }
+ item.build()
+}
+
+fn field_with_receiver(receiver: Option<&hir::Name>, field_name: &str) -> SmolStr {
+ receiver
+ .map_or_else(|| field_name.into(), |receiver| format!("{}.{}", receiver, field_name).into())
+}
+
+pub(crate) fn render_tuple_field(
+ ctx: RenderContext<'_>,
+ receiver: Option<hir::Name>,
+ field: usize,
+ ty: &hir::Type,
+) -> CompletionItem {
+ let mut item = CompletionItem::new(
+ SymbolKind::Field,
+ ctx.source_range(),
+ field_with_receiver(receiver.as_ref(), &field.to_string()),
+ );
+ item.detail(ty.display(ctx.db()).to_string()).lookup_by(field.to_string());
+ item.build()
+}
+
+pub(crate) fn render_type_inference(
+ ty_string: String,
+ ctx: &CompletionContext<'_>,
+) -> CompletionItem {
+ let mut builder =
+ CompletionItem::new(CompletionItemKind::InferredType, ctx.source_range(), ty_string);
+ builder.set_relevance(CompletionRelevance { is_definite: true, ..Default::default() });
+ builder.build()
+}
+
+pub(crate) fn render_path_resolution(
+ ctx: RenderContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ local_name: hir::Name,
+ resolution: ScopeDef,
+) -> Builder {
+ render_resolution_path(ctx, path_ctx, local_name, None, resolution)
+}
+
+pub(crate) fn render_pattern_resolution(
+ ctx: RenderContext<'_>,
+ pattern_ctx: &PatternContext,
+ local_name: hir::Name,
+ resolution: ScopeDef,
+) -> Builder {
+ render_resolution_pat(ctx, pattern_ctx, local_name, None, resolution)
+}
+
+pub(crate) fn render_resolution_with_import(
+ ctx: RenderContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ import_edit: LocatedImport,
+) -> Option<Builder> {
+ let resolution = ScopeDef::from(import_edit.original_item);
+ let local_name = scope_def_to_name(resolution, &ctx, &import_edit)?;
+
+ Some(render_resolution_path(ctx, path_ctx, local_name, Some(import_edit), resolution))
+}
+
+pub(crate) fn render_resolution_with_import_pat(
+ ctx: RenderContext<'_>,
+ pattern_ctx: &PatternContext,
+ import_edit: LocatedImport,
+) -> Option<Builder> {
+ let resolution = ScopeDef::from(import_edit.original_item);
+ let local_name = scope_def_to_name(resolution, &ctx, &import_edit)?;
+ Some(render_resolution_pat(ctx, pattern_ctx, local_name, Some(import_edit), resolution))
+}
+
+fn scope_def_to_name(
+ resolution: ScopeDef,
+ ctx: &RenderContext<'_>,
+ import_edit: &LocatedImport,
+) -> Option<hir::Name> {
+ Some(match resolution {
+ ScopeDef::ModuleDef(hir::ModuleDef::Function(f)) => f.name(ctx.completion.db),
+ ScopeDef::ModuleDef(hir::ModuleDef::Const(c)) => c.name(ctx.completion.db)?,
+ ScopeDef::ModuleDef(hir::ModuleDef::TypeAlias(t)) => t.name(ctx.completion.db),
+ _ => item_name(ctx.db(), import_edit.original_item)?,
+ })
+}
+
+fn render_resolution_pat(
+ ctx: RenderContext<'_>,
+ pattern_ctx: &PatternContext,
+ local_name: hir::Name,
+ import_to_add: Option<LocatedImport>,
+ resolution: ScopeDef,
+) -> Builder {
+ let _p = profile::span("render_resolution");
+ use hir::ModuleDef::*;
+
+ match resolution {
+ ScopeDef::ModuleDef(Macro(mac)) => {
+ let ctx = ctx.import_to_add(import_to_add);
+ return render_macro_pat(ctx, pattern_ctx, local_name, mac);
+ }
+ _ => (),
+ }
+
+ render_resolution_simple_(ctx, &local_name, import_to_add, resolution)
+}
+
+fn render_resolution_path(
+ ctx: RenderContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ local_name: hir::Name,
+ import_to_add: Option<LocatedImport>,
+ resolution: ScopeDef,
+) -> Builder {
+ let _p = profile::span("render_resolution");
+ use hir::ModuleDef::*;
+
+ match resolution {
+ ScopeDef::ModuleDef(Macro(mac)) => {
+ let ctx = ctx.import_to_add(import_to_add);
+ return render_macro(ctx, path_ctx, local_name, mac);
+ }
+ ScopeDef::ModuleDef(Function(func)) => {
+ let ctx = ctx.import_to_add(import_to_add);
+ return render_fn(ctx, path_ctx, Some(local_name), func);
+ }
+ ScopeDef::ModuleDef(Variant(var)) => {
+ let ctx = ctx.clone().import_to_add(import_to_add.clone());
+ if let Some(item) =
+ render_variant_lit(ctx, path_ctx, Some(local_name.clone()), var, None)
+ {
+ return item;
+ }
+ }
+ _ => (),
+ }
+
+ let completion = ctx.completion;
+ let cap = ctx.snippet_cap();
+ let db = completion.db;
+ let config = completion.config;
+
+ let name = local_name.to_smol_str();
+ let mut item = render_resolution_simple_(ctx, &local_name, import_to_add, resolution);
+ if local_name.escaped().is_escaped() {
+ item.insert_text(local_name.escaped().to_smol_str());
+ }
+ // Add `<>` for generic types
+ let type_path_no_ty_args = matches!(
+ path_ctx,
+ PathCompletionCtx { kind: PathKind::Type { .. }, has_type_args: false, .. }
+ ) && config.callable.is_some();
+ if type_path_no_ty_args {
+ if let Some(cap) = cap {
+ let has_non_default_type_params = match resolution {
+ ScopeDef::ModuleDef(hir::ModuleDef::Adt(it)) => it.has_non_default_type_params(db),
+ ScopeDef::ModuleDef(hir::ModuleDef::TypeAlias(it)) => {
+ it.has_non_default_type_params(db)
+ }
+ _ => false,
+ };
+
+ if has_non_default_type_params {
+ cov_mark::hit!(inserts_angle_brackets_for_generics);
+ item.lookup_by(name.clone())
+ .label(SmolStr::from_iter([&name, "<…>"]))
+ .trigger_call_info()
+ .insert_snippet(cap, format!("{}<$0>", local_name.escaped()));
+ }
+ }
+ }
+ if let ScopeDef::Local(local) = resolution {
+ let ty = local.ty(db);
+ if !ty.is_unknown() {
+ item.detail(ty.display(db).to_string());
+ }
+
+ item.set_relevance(CompletionRelevance {
+ type_match: compute_type_match(completion, &ty),
+ exact_name_match: compute_exact_name_match(completion, &name),
+ is_local: true,
+ ..CompletionRelevance::default()
+ });
+
+ if let Some(ref_match) = compute_ref_match(completion, &ty) {
+ item.ref_match(ref_match, path_ctx.path.syntax().text_range().start());
+ }
+ };
+ item
+}
+
+fn render_resolution_simple_(
+ ctx: RenderContext<'_>,
+ local_name: &hir::Name,
+ import_to_add: Option<LocatedImport>,
+ resolution: ScopeDef,
+) -> Builder {
+ let _p = profile::span("render_resolution");
+
+ let db = ctx.db();
+ let ctx = ctx.import_to_add(import_to_add);
+ let kind = res_to_kind(resolution);
+
+ let mut item = CompletionItem::new(kind, ctx.source_range(), local_name.to_smol_str());
+ item.set_relevance(ctx.completion_relevance())
+ .set_documentation(scope_def_docs(db, resolution))
+ .set_deprecated(scope_def_is_deprecated(&ctx, resolution));
+
+ if let Some(import_to_add) = ctx.import_to_add {
+ item.add_import(import_to_add);
+ }
+ item
+}
+
+fn res_to_kind(resolution: ScopeDef) -> CompletionItemKind {
+ use hir::ModuleDef::*;
+ match resolution {
+ ScopeDef::Unknown => CompletionItemKind::UnresolvedReference,
+ ScopeDef::ModuleDef(Function(_)) => CompletionItemKind::SymbolKind(SymbolKind::Function),
+ ScopeDef::ModuleDef(Variant(_)) => CompletionItemKind::SymbolKind(SymbolKind::Variant),
+ ScopeDef::ModuleDef(Macro(_)) => CompletionItemKind::SymbolKind(SymbolKind::Macro),
+ ScopeDef::ModuleDef(Module(..)) => CompletionItemKind::SymbolKind(SymbolKind::Module),
+ ScopeDef::ModuleDef(Adt(adt)) => CompletionItemKind::SymbolKind(match adt {
+ hir::Adt::Struct(_) => SymbolKind::Struct,
+ hir::Adt::Union(_) => SymbolKind::Union,
+ hir::Adt::Enum(_) => SymbolKind::Enum,
+ }),
+ ScopeDef::ModuleDef(Const(..)) => CompletionItemKind::SymbolKind(SymbolKind::Const),
+ ScopeDef::ModuleDef(Static(..)) => CompletionItemKind::SymbolKind(SymbolKind::Static),
+ ScopeDef::ModuleDef(Trait(..)) => CompletionItemKind::SymbolKind(SymbolKind::Trait),
+ ScopeDef::ModuleDef(TypeAlias(..)) => CompletionItemKind::SymbolKind(SymbolKind::TypeAlias),
+ ScopeDef::ModuleDef(BuiltinType(..)) => CompletionItemKind::BuiltinType,
+ ScopeDef::GenericParam(param) => CompletionItemKind::SymbolKind(match param {
+ hir::GenericParam::TypeParam(_) => SymbolKind::TypeParam,
+ hir::GenericParam::ConstParam(_) => SymbolKind::ConstParam,
+ hir::GenericParam::LifetimeParam(_) => SymbolKind::LifetimeParam,
+ }),
+ ScopeDef::Local(..) => CompletionItemKind::SymbolKind(SymbolKind::Local),
+ ScopeDef::Label(..) => CompletionItemKind::SymbolKind(SymbolKind::Label),
+ ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => {
+ CompletionItemKind::SymbolKind(SymbolKind::SelfParam)
+ }
+ }
+}
+
+fn scope_def_docs(db: &RootDatabase, resolution: ScopeDef) -> Option<hir::Documentation> {
+ use hir::ModuleDef::*;
+ match resolution {
+ ScopeDef::ModuleDef(Module(it)) => it.docs(db),
+ ScopeDef::ModuleDef(Adt(it)) => it.docs(db),
+ ScopeDef::ModuleDef(Variant(it)) => it.docs(db),
+ ScopeDef::ModuleDef(Const(it)) => it.docs(db),
+ ScopeDef::ModuleDef(Static(it)) => it.docs(db),
+ ScopeDef::ModuleDef(Trait(it)) => it.docs(db),
+ ScopeDef::ModuleDef(TypeAlias(it)) => it.docs(db),
+ _ => None,
+ }
+}
+
+fn scope_def_is_deprecated(ctx: &RenderContext<'_>, resolution: ScopeDef) -> bool {
+ match resolution {
+ ScopeDef::ModuleDef(it) => ctx.is_deprecated_assoc_item(it),
+ ScopeDef::GenericParam(it) => ctx.is_deprecated(it),
+ ScopeDef::AdtSelfType(it) => ctx.is_deprecated(it),
+ _ => false,
+ }
+}
+
+fn compute_type_match(
+ ctx: &CompletionContext<'_>,
+ completion_ty: &hir::Type,
+) -> Option<CompletionRelevanceTypeMatch> {
+ let expected_type = ctx.expected_type.as_ref()?;
+
+ // We don't ever consider unit type to be an exact type match, since
+ // nearly always this is not meaningful to the user.
+ if expected_type.is_unit() {
+ return None;
+ }
+
+ if completion_ty == expected_type {
+ Some(CompletionRelevanceTypeMatch::Exact)
+ } else if expected_type.could_unify_with(ctx.db, completion_ty) {
+ Some(CompletionRelevanceTypeMatch::CouldUnify)
+ } else {
+ None
+ }
+}
+
+fn compute_exact_name_match(ctx: &CompletionContext<'_>, completion_name: &str) -> bool {
+ ctx.expected_name.as_ref().map_or(false, |name| name.text() == completion_name)
+}
+
+fn compute_ref_match(
+ ctx: &CompletionContext<'_>,
+ completion_ty: &hir::Type,
+) -> Option<hir::Mutability> {
+ let expected_type = ctx.expected_type.as_ref()?;
+ if completion_ty != expected_type {
+ let expected_type_without_ref = expected_type.remove_ref()?;
+ if completion_ty.autoderef(ctx.db).any(|deref_ty| deref_ty == expected_type_without_ref) {
+ cov_mark::hit!(suggest_ref);
+ let mutability = if expected_type.is_mutable_reference() {
+ hir::Mutability::Mut
+ } else {
+ hir::Mutability::Shared
+ };
+ return Some(mutability);
+ };
+ }
+ None
+}
+
+#[cfg(test)]
+mod tests {
+ use std::cmp;
+
+ use expect_test::{expect, Expect};
+ use ide_db::SymbolKind;
+ use itertools::Itertools;
+
+ use crate::{
+ item::CompletionRelevanceTypeMatch,
+ tests::{check_edit, do_completion, get_all_items, TEST_CONFIG},
+ CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevancePostfixMatch,
+ };
+
+ #[track_caller]
+ fn check(ra_fixture: &str, kind: impl Into<CompletionItemKind>, expect: Expect) {
+ let actual = do_completion(ra_fixture, kind.into());
+ expect.assert_debug_eq(&actual);
+ }
+
+ #[track_caller]
+ fn check_kinds(ra_fixture: &str, kinds: &[CompletionItemKind], expect: Expect) {
+ let actual: Vec<_> =
+ kinds.iter().flat_map(|&kind| do_completion(ra_fixture, kind)).collect();
+ expect.assert_debug_eq(&actual);
+ }
+
+ #[track_caller]
+ fn check_relevance_for_kinds(ra_fixture: &str, kinds: &[CompletionItemKind], expect: Expect) {
+ let mut actual = get_all_items(TEST_CONFIG, ra_fixture, None);
+ actual.retain(|it| kinds.contains(&it.kind()));
+ actual.sort_by_key(|it| cmp::Reverse(it.relevance().score()));
+ check_relevance_(actual, expect);
+ }
+
+ #[track_caller]
+ fn check_relevance(ra_fixture: &str, expect: Expect) {
+ let mut actual = get_all_items(TEST_CONFIG, ra_fixture, None);
+ actual.retain(|it| it.kind() != CompletionItemKind::Snippet);
+ actual.retain(|it| it.kind() != CompletionItemKind::Keyword);
+ actual.retain(|it| it.kind() != CompletionItemKind::BuiltinType);
+ actual.sort_by_key(|it| cmp::Reverse(it.relevance().score()));
+ check_relevance_(actual, expect);
+ }
+
+ #[track_caller]
+ fn check_relevance_(actual: Vec<CompletionItem>, expect: Expect) {
+ let actual = actual
+ .into_iter()
+ .flat_map(|it| {
+ let mut items = vec![];
+
+ let tag = it.kind().tag();
+ let relevance = display_relevance(it.relevance());
+ items.push(format!("{} {} {}\n", tag, it.label(), relevance));
+
+ if let Some((mutability, _offset, relevance)) = it.ref_match() {
+ let label = format!("&{}{}", mutability.as_keyword_for_ref(), it.label());
+ let relevance = display_relevance(relevance);
+
+ items.push(format!("{} {} {}\n", tag, label, relevance));
+ }
+
+ items
+ })
+ .collect::<String>();
+
+ expect.assert_eq(&actual);
+
+ fn display_relevance(relevance: CompletionRelevance) -> String {
+ let relevance_factors = vec![
+ (relevance.type_match == Some(CompletionRelevanceTypeMatch::Exact), "type"),
+ (
+ relevance.type_match == Some(CompletionRelevanceTypeMatch::CouldUnify),
+ "type_could_unify",
+ ),
+ (relevance.exact_name_match, "name"),
+ (relevance.is_local, "local"),
+ (
+ relevance.postfix_match == Some(CompletionRelevancePostfixMatch::Exact),
+ "snippet",
+ ),
+ (relevance.is_op_method, "op_method"),
+ (relevance.requires_import, "requires_import"),
+ ]
+ .into_iter()
+ .filter_map(|(cond, desc)| if cond { Some(desc) } else { None })
+ .join("+");
+
+ format!("[{}]", relevance_factors)
+ }
+ }
+
+ #[test]
+ fn enum_detail_includes_record_fields() {
+ check(
+ r#"
+enum Foo { Foo { x: i32, y: i32 } }
+
+fn main() { Foo::Fo$0 }
+"#,
+ SymbolKind::Variant,
+ expect![[r#"
+ [
+ CompletionItem {
+ label: "Foo {…}",
+ source_range: 54..56,
+ delete: 54..56,
+ insert: "Foo { x: ${1:()}, y: ${2:()} }$0",
+ kind: SymbolKind(
+ Variant,
+ ),
+ detail: "Foo { x: i32, y: i32 }",
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn enum_detail_includes_tuple_fields() {
+ check(
+ r#"
+enum Foo { Foo (i32, i32) }
+
+fn main() { Foo::Fo$0 }
+"#,
+ SymbolKind::Variant,
+ expect![[r#"
+ [
+ CompletionItem {
+ label: "Foo(…)",
+ source_range: 46..48,
+ delete: 46..48,
+ insert: "Foo(${1:()}, ${2:()})$0",
+ kind: SymbolKind(
+ Variant,
+ ),
+ detail: "Foo(i32, i32)",
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn fn_detail_includes_args_and_return_type() {
+ check(
+ r#"
+fn foo<T>(a: u32, b: u32, t: T) -> (u32, T) { (a, t) }
+
+fn main() { fo$0 }
+"#,
+ SymbolKind::Function,
+ expect![[r#"
+ [
+ CompletionItem {
+ label: "foo(…)",
+ source_range: 68..70,
+ delete: 68..70,
+ insert: "foo(${1:a}, ${2:b}, ${3:t})$0",
+ kind: SymbolKind(
+ Function,
+ ),
+ lookup: "foo",
+ detail: "fn(u32, u32, T) -> (u32, T)",
+ trigger_call_info: true,
+ },
+ CompletionItem {
+ label: "main()",
+ source_range: 68..70,
+ delete: 68..70,
+ insert: "main()$0",
+ kind: SymbolKind(
+ Function,
+ ),
+ lookup: "main",
+ detail: "fn()",
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn enum_detail_just_name_for_unit() {
+ check(
+ r#"
+enum Foo { Foo }
+
+fn main() { Foo::Fo$0 }
+"#,
+ SymbolKind::Variant,
+ expect![[r#"
+ [
+ CompletionItem {
+ label: "Foo",
+ source_range: 35..37,
+ delete: 35..37,
+ insert: "Foo$0",
+ kind: SymbolKind(
+ Variant,
+ ),
+ detail: "Foo",
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn lookup_enums_by_two_qualifiers() {
+ check_kinds(
+ r#"
+mod m {
+ pub enum Spam { Foo, Bar(i32) }
+}
+fn main() { let _: m::Spam = S$0 }
+"#,
+ &[
+ CompletionItemKind::SymbolKind(SymbolKind::Function),
+ CompletionItemKind::SymbolKind(SymbolKind::Module),
+ CompletionItemKind::SymbolKind(SymbolKind::Variant),
+ ],
+ expect![[r#"
+ [
+ CompletionItem {
+ label: "main()",
+ source_range: 75..76,
+ delete: 75..76,
+ insert: "main()$0",
+ kind: SymbolKind(
+ Function,
+ ),
+ lookup: "main",
+ detail: "fn()",
+ },
+ CompletionItem {
+ label: "m",
+ source_range: 75..76,
+ delete: 75..76,
+ insert: "m",
+ kind: SymbolKind(
+ Module,
+ ),
+ },
+ CompletionItem {
+ label: "m::Spam::Bar(…)",
+ source_range: 75..76,
+ delete: 75..76,
+ insert: "m::Spam::Bar(${1:()})$0",
+ kind: SymbolKind(
+ Variant,
+ ),
+ lookup: "Spam::Bar(…)",
+ detail: "m::Spam::Bar(i32)",
+ relevance: CompletionRelevance {
+ exact_name_match: false,
+ type_match: Some(
+ Exact,
+ ),
+ is_local: false,
+ is_item_from_trait: false,
+ is_name_already_imported: false,
+ requires_import: false,
+ is_op_method: false,
+ is_private_editable: false,
+ postfix_match: None,
+ is_definite: false,
+ },
+ },
+ CompletionItem {
+ label: "m::Spam::Foo",
+ source_range: 75..76,
+ delete: 75..76,
+ insert: "m::Spam::Foo$0",
+ kind: SymbolKind(
+ Variant,
+ ),
+ lookup: "Spam::Foo",
+ detail: "m::Spam::Foo",
+ relevance: CompletionRelevance {
+ exact_name_match: false,
+ type_match: Some(
+ Exact,
+ ),
+ is_local: false,
+ is_item_from_trait: false,
+ is_name_already_imported: false,
+ requires_import: false,
+ is_op_method: false,
+ is_private_editable: false,
+ postfix_match: None,
+ is_definite: false,
+ },
+ },
+ ]
+ "#]],
+ )
+ }
+
+ #[test]
+ fn sets_deprecated_flag_in_items() {
+ check(
+ r#"
+#[deprecated]
+fn something_deprecated() {}
+
+fn main() { som$0 }
+"#,
+ SymbolKind::Function,
+ expect![[r#"
+ [
+ CompletionItem {
+ label: "main()",
+ source_range: 56..59,
+ delete: 56..59,
+ insert: "main()$0",
+ kind: SymbolKind(
+ Function,
+ ),
+ lookup: "main",
+ detail: "fn()",
+ },
+ CompletionItem {
+ label: "something_deprecated()",
+ source_range: 56..59,
+ delete: 56..59,
+ insert: "something_deprecated()$0",
+ kind: SymbolKind(
+ Function,
+ ),
+ lookup: "something_deprecated",
+ detail: "fn()",
+ deprecated: true,
+ },
+ ]
+ "#]],
+ );
+
+ check(
+ r#"
+struct A { #[deprecated] the_field: u32 }
+fn foo() { A { the$0 } }
+"#,
+ SymbolKind::Field,
+ expect![[r#"
+ [
+ CompletionItem {
+ label: "the_field",
+ source_range: 57..60,
+ delete: 57..60,
+ insert: "the_field",
+ kind: SymbolKind(
+ Field,
+ ),
+ detail: "u32",
+ deprecated: true,
+ relevance: CompletionRelevance {
+ exact_name_match: false,
+ type_match: Some(
+ CouldUnify,
+ ),
+ is_local: false,
+ is_item_from_trait: false,
+ is_name_already_imported: false,
+ requires_import: false,
+ is_op_method: false,
+ is_private_editable: false,
+ postfix_match: None,
+ is_definite: false,
+ },
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn renders_docs() {
+ check_kinds(
+ r#"
+struct S {
+ /// Field docs
+ foo:
+}
+impl S {
+ /// Method docs
+ fn bar(self) { self.$0 }
+}"#,
+ &[CompletionItemKind::Method, CompletionItemKind::SymbolKind(SymbolKind::Field)],
+ expect![[r#"
+ [
+ CompletionItem {
+ label: "bar()",
+ source_range: 94..94,
+ delete: 94..94,
+ insert: "bar()$0",
+ kind: Method,
+ lookup: "bar",
+ detail: "fn(self)",
+ documentation: Documentation(
+ "Method docs",
+ ),
+ },
+ CompletionItem {
+ label: "foo",
+ source_range: 94..94,
+ delete: 94..94,
+ insert: "foo",
+ kind: SymbolKind(
+ Field,
+ ),
+ detail: "{unknown}",
+ documentation: Documentation(
+ "Field docs",
+ ),
+ },
+ ]
+ "#]],
+ );
+
+ check_kinds(
+ r#"
+use self::my$0;
+
+/// mod docs
+mod my { }
+
+/// enum docs
+enum E {
+ /// variant docs
+ V
+}
+use self::E::*;
+"#,
+ &[
+ CompletionItemKind::SymbolKind(SymbolKind::Module),
+ CompletionItemKind::SymbolKind(SymbolKind::Variant),
+ CompletionItemKind::SymbolKind(SymbolKind::Enum),
+ ],
+ expect![[r#"
+ [
+ CompletionItem {
+ label: "my",
+ source_range: 10..12,
+ delete: 10..12,
+ insert: "my",
+ kind: SymbolKind(
+ Module,
+ ),
+ documentation: Documentation(
+ "mod docs",
+ ),
+ },
+ CompletionItem {
+ label: "V",
+ source_range: 10..12,
+ delete: 10..12,
+ insert: "V$0",
+ kind: SymbolKind(
+ Variant,
+ ),
+ detail: "V",
+ documentation: Documentation(
+ "variant docs",
+ ),
+ },
+ CompletionItem {
+ label: "E",
+ source_range: 10..12,
+ delete: 10..12,
+ insert: "E",
+ kind: SymbolKind(
+ Enum,
+ ),
+ documentation: Documentation(
+ "enum docs",
+ ),
+ },
+ ]
+ "#]],
+ )
+ }
+
+ #[test]
+ fn dont_render_attrs() {
+ check(
+ r#"
+struct S;
+impl S {
+ #[inline]
+ fn the_method(&self) { }
+}
+fn foo(s: S) { s.$0 }
+"#,
+ CompletionItemKind::Method,
+ expect![[r#"
+ [
+ CompletionItem {
+ label: "the_method()",
+ source_range: 81..81,
+ delete: 81..81,
+ insert: "the_method()$0",
+ kind: Method,
+ lookup: "the_method",
+ detail: "fn(&self)",
+ },
+ ]
+ "#]],
+ )
+ }
+
+ #[test]
+ fn no_call_parens_if_fn_ptr_needed() {
+ cov_mark::check!(no_call_parens_if_fn_ptr_needed);
+ check_edit(
+ "foo",
+ r#"
+fn foo(foo: u8, bar: u8) {}
+struct ManualVtable { f: fn(u8, u8) }
+
+fn main() -> ManualVtable {
+ ManualVtable { f: f$0 }
+}
+"#,
+ r#"
+fn foo(foo: u8, bar: u8) {}
+struct ManualVtable { f: fn(u8, u8) }
+
+fn main() -> ManualVtable {
+ ManualVtable { f: foo }
+}
+"#,
+ );
+ check_edit(
+ "type",
+ r#"
+struct RawIdentTable { r#type: u32 }
+
+fn main() -> RawIdentTable {
+ RawIdentTable { t$0: 42 }
+}
+"#,
+ r#"
+struct RawIdentTable { r#type: u32 }
+
+fn main() -> RawIdentTable {
+ RawIdentTable { r#type: 42 }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_parens_in_use_item() {
+ check_edit(
+ "foo",
+ r#"
+mod m { pub fn foo() {} }
+use crate::m::f$0;
+"#,
+ r#"
+mod m { pub fn foo() {} }
+use crate::m::foo;
+"#,
+ );
+ }
+
+ #[test]
+ fn no_parens_in_call() {
+ check_edit(
+ "foo",
+ r#"
+fn foo(x: i32) {}
+fn main() { f$0(); }
+"#,
+ r#"
+fn foo(x: i32) {}
+fn main() { foo(); }
+"#,
+ );
+ check_edit(
+ "foo",
+ r#"
+struct Foo;
+impl Foo { fn foo(&self){} }
+fn f(foo: &Foo) { foo.f$0(); }
+"#,
+ r#"
+struct Foo;
+impl Foo { fn foo(&self){} }
+fn f(foo: &Foo) { foo.foo(); }
+"#,
+ );
+ }
+
+ #[test]
+ fn inserts_angle_brackets_for_generics() {
+ cov_mark::check!(inserts_angle_brackets_for_generics);
+ check_edit(
+ "Vec",
+ r#"
+struct Vec<T> {}
+fn foo(xs: Ve$0)
+"#,
+ r#"
+struct Vec<T> {}
+fn foo(xs: Vec<$0>)
+"#,
+ );
+ check_edit(
+ "Vec",
+ r#"
+type Vec<T> = (T,);
+fn foo(xs: Ve$0)
+"#,
+ r#"
+type Vec<T> = (T,);
+fn foo(xs: Vec<$0>)
+"#,
+ );
+ check_edit(
+ "Vec",
+ r#"
+struct Vec<T = i128> {}
+fn foo(xs: Ve$0)
+"#,
+ r#"
+struct Vec<T = i128> {}
+fn foo(xs: Vec)
+"#,
+ );
+ check_edit(
+ "Vec",
+ r#"
+struct Vec<T> {}
+fn foo(xs: Ve$0<i128>)
+"#,
+ r#"
+struct Vec<T> {}
+fn foo(xs: Vec<i128>)
+"#,
+ );
+ }
+
+ #[test]
+ fn active_param_relevance() {
+ check_relevance(
+ r#"
+struct S { foo: i64, bar: u32, baz: u32 }
+fn test(bar: u32) { }
+fn foo(s: S) { test(s.$0) }
+"#,
+ expect![[r#"
+ fd bar [type+name]
+ fd baz [type]
+ fd foo []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn record_field_relevances() {
+ check_relevance(
+ r#"
+struct A { foo: i64, bar: u32, baz: u32 }
+struct B { x: (), y: f32, bar: u32 }
+fn foo(a: A) { B { bar: a.$0 }; }
+"#,
+ expect![[r#"
+ fd bar [type+name]
+ fd baz [type]
+ fd foo []
+ "#]],
+ )
+ }
+
+ #[test]
+ fn record_field_and_call_relevances() {
+ check_relevance(
+ r#"
+struct A { foo: i64, bar: u32, baz: u32 }
+struct B { x: (), y: f32, bar: u32 }
+fn f(foo: i64) { }
+fn foo(a: A) { B { bar: f(a.$0) }; }
+"#,
+ expect![[r#"
+ fd foo [type+name]
+ fd bar []
+ fd baz []
+ "#]],
+ );
+ check_relevance(
+ r#"
+struct A { foo: i64, bar: u32, baz: u32 }
+struct B { x: (), y: f32, bar: u32 }
+fn f(foo: i64) { }
+fn foo(a: A) { f(B { bar: a.$0 }); }
+"#,
+ expect![[r#"
+ fd bar [type+name]
+ fd baz [type]
+ fd foo []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn prioritize_exact_ref_match() {
+ check_relevance(
+ r#"
+struct WorldSnapshot { _f: () };
+fn go(world: &WorldSnapshot) { go(w$0) }
+"#,
+ expect![[r#"
+ lc world [type+name+local]
+ st WorldSnapshot {…} []
+ st &WorldSnapshot {…} [type]
+ st WorldSnapshot []
+ fn go(…) []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn too_many_arguments() {
+ cov_mark::check!(too_many_arguments);
+ check_relevance(
+ r#"
+struct Foo;
+fn f(foo: &Foo) { f(foo, w$0) }
+"#,
+ expect![[r#"
+ lc foo [local]
+ st Foo []
+ fn f(…) []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn score_fn_type_and_name_match() {
+ check_relevance(
+ r#"
+struct A { bar: u8 }
+fn baz() -> u8 { 0 }
+fn bar() -> u8 { 0 }
+fn f() { A { bar: b$0 }; }
+"#,
+ expect![[r#"
+ fn bar() [type+name]
+ fn baz() [type]
+ st A []
+ fn f() []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn score_method_type_and_name_match() {
+ check_relevance(
+ r#"
+fn baz(aaa: u32){}
+struct Foo;
+impl Foo {
+fn aaa(&self) -> u32 { 0 }
+fn bbb(&self) -> u32 { 0 }
+fn ccc(&self) -> u64 { 0 }
+}
+fn f() {
+ baz(Foo.$0
+}
+"#,
+ expect![[r#"
+ me aaa() [type+name]
+ me bbb() [type]
+ me ccc() []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn score_method_name_match_only() {
+ check_relevance(
+ r#"
+fn baz(aaa: u32){}
+struct Foo;
+impl Foo {
+fn aaa(&self) -> u64 { 0 }
+}
+fn f() {
+ baz(Foo.$0
+}
+"#,
+ expect![[r#"
+ me aaa() [name]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn suggest_ref_mut() {
+ cov_mark::check!(suggest_ref);
+ check_relevance(
+ r#"
+struct S;
+fn foo(s: &mut S) {}
+fn main() {
+ let mut s = S;
+ foo($0);
+}
+ "#,
+ expect![[r#"
+ lc s [name+local]
+ lc &mut s [type+name+local]
+ st S []
+ st &mut S [type]
+ st S []
+ fn foo(…) []
+ fn main() []
+ "#]],
+ );
+ check_relevance(
+ r#"
+struct S;
+fn foo(s: &mut S) {}
+fn main() {
+ let mut s = S;
+ foo(&mut $0);
+}
+ "#,
+ expect![[r#"
+ lc s [type+name+local]
+ st S [type]
+ st S []
+ fn foo(…) []
+ fn main() []
+ "#]],
+ );
+ check_relevance(
+ r#"
+struct S;
+fn foo(s: &mut S) {}
+fn main() {
+ let mut ssss = S;
+ foo(&mut s$0);
+}
+ "#,
+ expect![[r#"
+ lc ssss [type+local]
+ st S [type]
+ st S []
+ fn foo(…) []
+ fn main() []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn suggest_deref() {
+ check_relevance(
+ r#"
+//- minicore: deref
+struct S;
+struct T(S);
+
+impl core::ops::Deref for T {
+ type Target = S;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+fn foo(s: &S) {}
+
+fn main() {
+ let t = T(S);
+ let m = 123;
+
+ foo($0);
+}
+ "#,
+ expect![[r#"
+ lc m [local]
+ lc t [local]
+ lc &t [type+local]
+ st S []
+ st &S [type]
+ st S []
+ st T []
+ fn foo(…) []
+ fn main() []
+ md core []
+ "#]],
+ )
+ }
+
+ #[test]
+ fn suggest_deref_mut() {
+ check_relevance(
+ r#"
+//- minicore: deref_mut
+struct S;
+struct T(S);
+
+impl core::ops::Deref for T {
+ type Target = S;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl core::ops::DerefMut for T {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+}
+
+fn foo(s: &mut S) {}
+
+fn main() {
+ let t = T(S);
+ let m = 123;
+
+ foo($0);
+}
+ "#,
+ expect![[r#"
+ lc m [local]
+ lc t [local]
+ lc &mut t [type+local]
+ st S []
+ st &mut S [type]
+ st S []
+ st T []
+ fn foo(…) []
+ fn main() []
+ md core []
+ "#]],
+ )
+ }
+
+ #[test]
+ fn locals() {
+ check_relevance(
+ r#"
+fn foo(bar: u32) {
+ let baz = 0;
+
+ f$0
+}
+"#,
+ expect![[r#"
+ lc baz [local]
+ lc bar [local]
+ fn foo(…) []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn enum_owned() {
+ check_relevance(
+ r#"
+enum Foo { A, B }
+fn foo() {
+ bar($0);
+}
+fn bar(t: Foo) {}
+"#,
+ expect![[r#"
+ ev Foo::A [type]
+ ev Foo::B [type]
+ en Foo []
+ fn bar(…) []
+ fn foo() []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn enum_ref() {
+ check_relevance(
+ r#"
+enum Foo { A, B }
+fn foo() {
+ bar($0);
+}
+fn bar(t: &Foo) {}
+"#,
+ expect![[r#"
+ ev Foo::A []
+ ev &Foo::A [type]
+ ev Foo::B []
+ ev &Foo::B [type]
+ en Foo []
+ fn bar(…) []
+ fn foo() []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn suggest_deref_fn_ret() {
+ check_relevance(
+ r#"
+//- minicore: deref
+struct S;
+struct T(S);
+
+impl core::ops::Deref for T {
+ type Target = S;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+fn foo(s: &S) {}
+fn bar() -> T {}
+
+fn main() {
+ foo($0);
+}
+"#,
+ expect![[r#"
+ st S []
+ st &S [type]
+ st S []
+ st T []
+ fn bar() []
+ fn &bar() [type]
+ fn foo(…) []
+ fn main() []
+ md core []
+ "#]],
+ )
+ }
+
+ #[test]
+ fn op_function_relevances() {
+ check_relevance(
+ r#"
+#[lang = "sub"]
+trait Sub {
+ fn sub(self, other: Self) -> Self { self }
+}
+impl Sub for u32 {}
+fn foo(a: u32) { a.$0 }
+"#,
+ expect![[r#"
+ me sub(…) (as Sub) [op_method]
+ "#]],
+ );
+ check_relevance(
+ r#"
+struct Foo;
+impl Foo {
+ fn new() -> Self {}
+}
+#[lang = "eq"]
+pub trait PartialEq<Rhs: ?Sized = Self> {
+ fn eq(&self, other: &Rhs) -> bool;
+ fn ne(&self, other: &Rhs) -> bool;
+}
+
+impl PartialEq for Foo {}
+fn main() {
+ Foo::$0
+}
+"#,
+ expect![[r#"
+ fn new() []
+ me eq(…) (as PartialEq) [op_method]
+ me ne(…) (as PartialEq) [op_method]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn struct_field_method_ref() {
+ check_kinds(
+ r#"
+struct Foo { bar: u32 }
+impl Foo { fn baz(&self) -> u32 { 0 } }
+
+fn foo(f: Foo) { let _: &u32 = f.b$0 }
+"#,
+ &[CompletionItemKind::Method, CompletionItemKind::SymbolKind(SymbolKind::Field)],
+ expect![[r#"
+ [
+ CompletionItem {
+ label: "baz()",
+ source_range: 98..99,
+ delete: 98..99,
+ insert: "baz()$0",
+ kind: Method,
+ lookup: "baz",
+ detail: "fn(&self) -> u32",
+ ref_match: "&@96",
+ },
+ CompletionItem {
+ label: "bar",
+ source_range: 98..99,
+ delete: 98..99,
+ insert: "bar",
+ kind: SymbolKind(
+ Field,
+ ),
+ detail: "u32",
+ ref_match: "&@96",
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn qualified_path_ref() {
+ check_kinds(
+ r#"
+struct S;
+
+struct T;
+impl T {
+ fn foo() -> S {}
+}
+
+fn bar(s: &S) {}
+
+fn main() {
+ bar(T::$0);
+}
+"#,
+ &[CompletionItemKind::SymbolKind(SymbolKind::Function)],
+ expect![[r#"
+ [
+ CompletionItem {
+ label: "foo()",
+ source_range: 95..95,
+ delete: 95..95,
+ insert: "foo()$0",
+ kind: SymbolKind(
+ Function,
+ ),
+ lookup: "foo",
+ detail: "fn() -> S",
+ ref_match: "&@92",
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn generic_enum() {
+ check_relevance(
+ r#"
+enum Foo<T> { A(T), B }
+// bar() should not be an exact type match
+// because the generic parameters are different
+fn bar() -> Foo<u8> { Foo::B }
+// FIXME baz() should be an exact type match
+// because the types could unify, but it currently
+// is not. This is due to the T here being
+// TyKind::Placeholder rather than TyKind::Missing.
+fn baz<T>() -> Foo<T> { Foo::B }
+fn foo() {
+ let foo: Foo<u32> = Foo::B;
+ let _: Foo<u32> = f$0;
+}
+"#,
+ expect![[r#"
+ lc foo [type+local]
+ ev Foo::A(…) [type_could_unify]
+ ev Foo::B [type_could_unify]
+ fn foo() []
+ en Foo []
+ fn bar() []
+ fn baz() []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn postfix_exact_match_is_high_priority() {
+ cov_mark::check!(postfix_exact_match_is_high_priority);
+ check_relevance_for_kinds(
+ r#"
+mod ops {
+ pub trait Not {
+ type Output;
+ fn not(self) -> Self::Output;
+ }
+
+ impl Not for bool {
+ type Output = bool;
+ fn not(self) -> bool { if self { false } else { true }}
+ }
+}
+
+fn main() {
+ let _: bool = (9 > 2).not$0;
+}
+ "#,
+ &[CompletionItemKind::Snippet, CompletionItemKind::Method],
+ expect![[r#"
+ sn not [snippet]
+ me not() (use ops::Not) [type_could_unify+requires_import]
+ sn if []
+ sn while []
+ sn ref []
+ sn refm []
+ sn match []
+ sn box []
+ sn dbg []
+ sn dbgr []
+ sn call []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn postfix_inexact_match_is_low_priority() {
+ cov_mark::check!(postfix_inexact_match_is_low_priority);
+ check_relevance_for_kinds(
+ r#"
+struct S;
+impl S {
+ fn f(&self) {}
+}
+fn main() {
+ S.$0
+}
+ "#,
+ &[CompletionItemKind::Snippet, CompletionItemKind::Method],
+ expect![[r#"
+ me f() []
+ sn ref []
+ sn refm []
+ sn match []
+ sn box []
+ sn dbg []
+ sn dbgr []
+ sn call []
+ sn let []
+ sn letm []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn flyimport_reduced_relevance() {
+ check_relevance(
+ r#"
+mod std {
+ pub mod io {
+ pub trait BufRead {}
+ pub struct BufReader;
+ pub struct BufWriter;
+ }
+}
+struct Buffer;
+
+fn f() {
+ Buf$0
+}
+"#,
+ expect![[r#"
+ st Buffer []
+ fn f() []
+ md std []
+ tt BufRead (use std::io::BufRead) [requires_import]
+ st BufReader (use std::io::BufReader) [requires_import]
+ st BufWriter (use std::io::BufWriter) [requires_import]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn completes_struct_with_raw_identifier() {
+ check_edit(
+ "type",
+ r#"
+mod m { pub struct r#type {} }
+fn main() {
+ let r#type = m::t$0;
+}
+"#,
+ r#"
+mod m { pub struct r#type {} }
+fn main() {
+ let r#type = m::r#type;
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn completes_fn_with_raw_identifier() {
+ check_edit(
+ "type",
+ r#"
+mod m { pub fn r#type {} }
+fn main() {
+ m::t$0
+}
+"#,
+ r#"
+mod m { pub fn r#type {} }
+fn main() {
+ m::r#type()$0
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn completes_macro_with_raw_identifier() {
+ check_edit(
+ "let!",
+ r#"
+macro_rules! r#let { () => {} }
+fn main() {
+ $0
+}
+"#,
+ r#"
+macro_rules! r#let { () => {} }
+fn main() {
+ r#let!($0)
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn completes_variant_with_raw_identifier() {
+ check_edit(
+ "type",
+ r#"
+enum A { r#type }
+fn main() {
+ let a = A::t$0
+}
+"#,
+ r#"
+enum A { r#type }
+fn main() {
+ let a = A::r#type$0
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn completes_field_with_raw_identifier() {
+ check_edit(
+ "fn",
+ r#"
+mod r#type {
+ pub struct r#struct {
+ pub r#fn: u32
+ }
+}
+
+fn main() {
+ let a = r#type::r#struct {};
+ a.$0
+}
+"#,
+ r#"
+mod r#type {
+ pub struct r#struct {
+ pub r#fn: u32
+ }
+}
+
+fn main() {
+ let a = r#type::r#struct {};
+ a.r#fn
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn completes_const_with_raw_identifier() {
+ check_edit(
+ "type",
+ r#"
+struct r#struct {}
+impl r#struct { pub const r#type: u8 = 1; }
+fn main() {
+ r#struct::t$0
+}
+"#,
+ r#"
+struct r#struct {}
+impl r#struct { pub const r#type: u8 = 1; }
+fn main() {
+ r#struct::r#type
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn completes_type_alias_with_raw_identifier() {
+ check_edit(
+ "type type",
+ r#"
+struct r#struct {}
+trait r#trait { type r#type; }
+impl r#trait for r#struct { type t$0 }
+"#,
+ r#"
+struct r#struct {}
+trait r#trait { type r#type; }
+impl r#trait for r#struct { type r#type = $0; }
+"#,
+ )
+ }
+
+ #[test]
+ fn field_access_includes_self() {
+ check_edit(
+ "length",
+ r#"
+struct S {
+ length: i32
+}
+
+impl S {
+ fn some_fn(&self) {
+ let l = len$0
+ }
+}
+"#,
+ r#"
+struct S {
+ length: i32
+}
+
+impl S {
+ fn some_fn(&self) {
+ let l = self.length
+ }
+}
+"#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/const_.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/const_.rs
new file mode 100644
index 000000000..a810eef18
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/const_.rs
@@ -0,0 +1,33 @@
+//! Renderer for `const` fields.
+
+use hir::{AsAssocItem, HirDisplay};
+use ide_db::SymbolKind;
+
+use crate::{item::CompletionItem, render::RenderContext};
+
+pub(crate) fn render_const(ctx: RenderContext<'_>, const_: hir::Const) -> Option<CompletionItem> {
+ let _p = profile::span("render_const");
+ render(ctx, const_)
+}
+
+fn render(ctx: RenderContext<'_>, const_: hir::Const) -> Option<CompletionItem> {
+ let db = ctx.db();
+ let name = const_.name(db)?;
+ let (name, escaped_name) = (name.to_smol_str(), name.escaped().to_smol_str());
+ let detail = const_.display(db).to_string();
+
+ let mut item = CompletionItem::new(SymbolKind::Const, ctx.source_range(), name.clone());
+ item.set_documentation(ctx.docs(const_))
+ .set_deprecated(ctx.is_deprecated(const_) || ctx.is_deprecated_assoc_item(const_))
+ .detail(detail)
+ .set_relevance(ctx.completion_relevance());
+
+ if let Some(actm) = const_.as_assoc_item(db) {
+ if let Some(trt) = actm.containing_trait_or_trait_impl(db) {
+ item.trait_name(trt.name(db).to_smol_str());
+ }
+ }
+ item.insert_text(escaped_name);
+
+ Some(item.build())
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs
new file mode 100644
index 000000000..4b5535718
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs
@@ -0,0 +1,671 @@
+//! Renderer for function calls.
+
+use hir::{db::HirDatabase, AsAssocItem, HirDisplay};
+use ide_db::{SnippetCap, SymbolKind};
+use itertools::Itertools;
+use stdx::{format_to, to_lower_snake_case};
+use syntax::{AstNode, SmolStr};
+
+use crate::{
+ context::{CompletionContext, DotAccess, DotAccessKind, PathCompletionCtx, PathKind},
+ item::{Builder, CompletionItem, CompletionItemKind, CompletionRelevance},
+ render::{compute_exact_name_match, compute_ref_match, compute_type_match, RenderContext},
+ CallableSnippets,
+};
+
+#[derive(Debug)]
+enum FuncKind<'ctx> {
+ Function(&'ctx PathCompletionCtx),
+ Method(&'ctx DotAccess, Option<hir::Name>),
+}
+
+pub(crate) fn render_fn(
+ ctx: RenderContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ local_name: Option<hir::Name>,
+ func: hir::Function,
+) -> Builder {
+ let _p = profile::span("render_fn");
+ render(ctx, local_name, func, FuncKind::Function(path_ctx))
+}
+
+pub(crate) fn render_method(
+ ctx: RenderContext<'_>,
+ dot_access: &DotAccess,
+ receiver: Option<hir::Name>,
+ local_name: Option<hir::Name>,
+ func: hir::Function,
+) -> Builder {
+ let _p = profile::span("render_method");
+ render(ctx, local_name, func, FuncKind::Method(dot_access, receiver))
+}
+
+fn render(
+ ctx @ RenderContext { completion, .. }: RenderContext<'_>,
+ local_name: Option<hir::Name>,
+ func: hir::Function,
+ func_kind: FuncKind<'_>,
+) -> Builder {
+ let db = completion.db;
+
+ let name = local_name.unwrap_or_else(|| func.name(db));
+
+ let (call, escaped_call) = match &func_kind {
+ FuncKind::Method(_, Some(receiver)) => (
+ format!("{}.{}", receiver, &name).into(),
+ format!("{}.{}", receiver.escaped(), name.escaped()).into(),
+ ),
+ _ => (name.to_smol_str(), name.escaped().to_smol_str()),
+ };
+ let mut item = CompletionItem::new(
+ if func.self_param(db).is_some() {
+ CompletionItemKind::Method
+ } else {
+ CompletionItemKind::SymbolKind(SymbolKind::Function)
+ },
+ ctx.source_range(),
+ call.clone(),
+ );
+
+ let ret_type = func.ret_type(db);
+ let is_op_method = func
+ .as_assoc_item(ctx.db())
+ .and_then(|trait_| trait_.containing_trait_or_trait_impl(ctx.db()))
+ .map_or(false, |trait_| completion.is_ops_trait(trait_));
+ item.set_relevance(CompletionRelevance {
+ type_match: compute_type_match(completion, &ret_type),
+ exact_name_match: compute_exact_name_match(completion, &call),
+ is_op_method,
+ ..ctx.completion_relevance()
+ });
+
+ if let Some(ref_match) = compute_ref_match(completion, &ret_type) {
+ match func_kind {
+ FuncKind::Function(path_ctx) => {
+ item.ref_match(ref_match, path_ctx.path.syntax().text_range().start());
+ }
+ FuncKind::Method(DotAccess { receiver: Some(receiver), .. }, _) => {
+ if let Some(original_expr) = completion.sema.original_ast_node(receiver.clone()) {
+ item.ref_match(ref_match, original_expr.syntax().text_range().start());
+ }
+ }
+ _ => (),
+ }
+ }
+
+ item.set_documentation(ctx.docs(func))
+ .set_deprecated(ctx.is_deprecated(func) || ctx.is_deprecated_assoc_item(func))
+ .detail(detail(db, func))
+ .lookup_by(name.to_smol_str());
+
+ match ctx.completion.config.snippet_cap {
+ Some(cap) => {
+ let complete_params = match func_kind {
+ FuncKind::Function(PathCompletionCtx {
+ kind: PathKind::Expr { .. },
+ has_call_parens: false,
+ ..
+ }) => Some(false),
+ FuncKind::Method(
+ DotAccess {
+ kind:
+ DotAccessKind::Method { has_parens: false } | DotAccessKind::Field { .. },
+ ..
+ },
+ _,
+ ) => Some(true),
+ _ => None,
+ };
+ if let Some(has_dot_receiver) = complete_params {
+ if let Some((self_param, params)) =
+ params(ctx.completion, func, &func_kind, has_dot_receiver)
+ {
+ add_call_parens(
+ &mut item,
+ completion,
+ cap,
+ call,
+ escaped_call,
+ self_param,
+ params,
+ );
+ }
+ }
+ }
+ _ => (),
+ };
+
+ match ctx.import_to_add {
+ Some(import_to_add) => {
+ item.add_import(import_to_add);
+ }
+ None => {
+ if let Some(actm) = func.as_assoc_item(db) {
+ if let Some(trt) = actm.containing_trait_or_trait_impl(db) {
+ item.trait_name(trt.name(db).to_smol_str());
+ }
+ }
+ }
+ }
+ item
+}
+
+pub(super) fn add_call_parens<'b>(
+ builder: &'b mut Builder,
+ ctx: &CompletionContext<'_>,
+ cap: SnippetCap,
+ name: SmolStr,
+ escaped_name: SmolStr,
+ self_param: Option<hir::SelfParam>,
+ params: Vec<hir::Param>,
+) -> &'b mut Builder {
+ cov_mark::hit!(inserts_parens_for_function_calls);
+
+ let (snippet, label_suffix) = if self_param.is_none() && params.is_empty() {
+ (format!("{}()$0", escaped_name), "()")
+ } else {
+ builder.trigger_call_info();
+ let snippet = if let Some(CallableSnippets::FillArguments) = ctx.config.callable {
+ let offset = if self_param.is_some() { 2 } else { 1 };
+ let function_params_snippet =
+ params.iter().enumerate().format_with(", ", |(index, param), f| {
+ match param.name(ctx.db) {
+ Some(n) => {
+ let smol_str = n.to_smol_str();
+ let text = smol_str.as_str().trim_start_matches('_');
+ let ref_ = ref_of_param(ctx, text, param.ty());
+ f(&format_args!("${{{}:{}{}}}", index + offset, ref_, text))
+ }
+ None => {
+ let name = match param.ty().as_adt() {
+ None => "_".to_string(),
+ Some(adt) => adt
+ .name(ctx.db)
+ .as_text()
+ .map(|s| to_lower_snake_case(s.as_str()))
+ .unwrap_or_else(|| "_".to_string()),
+ };
+ f(&format_args!("${{{}:{}}}", index + offset, name))
+ }
+ }
+ });
+ match self_param {
+ Some(self_param) => {
+ format!(
+ "{}(${{1:{}}}{}{})$0",
+ escaped_name,
+ self_param.display(ctx.db),
+ if params.is_empty() { "" } else { ", " },
+ function_params_snippet
+ )
+ }
+ None => {
+ format!("{}({})$0", escaped_name, function_params_snippet)
+ }
+ }
+ } else {
+ cov_mark::hit!(suppress_arg_snippets);
+ format!("{}($0)", escaped_name)
+ };
+
+ (snippet, "(…)")
+ };
+ builder.label(SmolStr::from_iter([&name, label_suffix])).insert_snippet(cap, snippet)
+}
+
+fn ref_of_param(ctx: &CompletionContext<'_>, arg: &str, ty: &hir::Type) -> &'static str {
+ if let Some(derefed_ty) = ty.remove_ref() {
+ for (name, local) in ctx.locals.iter() {
+ if name.as_text().as_deref() == Some(arg) {
+ return if local.ty(ctx.db) == derefed_ty {
+ if ty.is_mutable_reference() {
+ "&mut "
+ } else {
+ "&"
+ }
+ } else {
+ ""
+ };
+ }
+ }
+ }
+ ""
+}
+
+fn detail(db: &dyn HirDatabase, func: hir::Function) -> String {
+ let mut ret_ty = func.ret_type(db);
+ let mut detail = String::new();
+
+ if func.is_const(db) {
+ format_to!(detail, "const ");
+ }
+ if func.is_async(db) {
+ format_to!(detail, "async ");
+ if let Some(async_ret) = func.async_ret_type(db) {
+ ret_ty = async_ret;
+ }
+ }
+ if func.is_unsafe_to_call(db) {
+ format_to!(detail, "unsafe ");
+ }
+
+ format_to!(detail, "fn({})", params_display(db, func));
+ if !ret_ty.is_unit() {
+ format_to!(detail, " -> {}", ret_ty.display(db));
+ }
+ detail
+}
+
+fn params_display(db: &dyn HirDatabase, func: hir::Function) -> String {
+ if let Some(self_param) = func.self_param(db) {
+ let assoc_fn_params = func.assoc_fn_params(db);
+ let params = assoc_fn_params
+ .iter()
+ .skip(1) // skip the self param because we are manually handling that
+ .map(|p| p.ty().display(db));
+ format!(
+ "{}{}",
+ self_param.display(db),
+ params.format_with("", |display, f| {
+ f(&", ")?;
+ f(&display)
+ })
+ )
+ } else {
+ let assoc_fn_params = func.assoc_fn_params(db);
+ assoc_fn_params.iter().map(|p| p.ty().display(db)).join(", ")
+ }
+}
+
+fn params(
+ ctx: &CompletionContext<'_>,
+ func: hir::Function,
+ func_kind: &FuncKind<'_>,
+ has_dot_receiver: bool,
+) -> Option<(Option<hir::SelfParam>, Vec<hir::Param>)> {
+ if ctx.config.callable.is_none() {
+ return None;
+ }
+
+ // Don't add parentheses if the expected type is some function reference.
+ if let Some(ty) = &ctx.expected_type {
+ // FIXME: check signature matches?
+ if ty.is_fn() {
+ cov_mark::hit!(no_call_parens_if_fn_ptr_needed);
+ return None;
+ }
+ }
+
+ let self_param = if has_dot_receiver || matches!(func_kind, FuncKind::Method(_, Some(_))) {
+ None
+ } else {
+ func.self_param(ctx.db)
+ };
+ Some((self_param, func.params_without_self(ctx.db)))
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{
+ tests::{check_edit, check_edit_with_config, TEST_CONFIG},
+ CallableSnippets, CompletionConfig,
+ };
+
+ #[test]
+ fn inserts_parens_for_function_calls() {
+ cov_mark::check!(inserts_parens_for_function_calls);
+ check_edit(
+ "no_args",
+ r#"
+fn no_args() {}
+fn main() { no_$0 }
+"#,
+ r#"
+fn no_args() {}
+fn main() { no_args()$0 }
+"#,
+ );
+
+ check_edit(
+ "with_args",
+ r#"
+fn with_args(x: i32, y: String) {}
+fn main() { with_$0 }
+"#,
+ r#"
+fn with_args(x: i32, y: String) {}
+fn main() { with_args(${1:x}, ${2:y})$0 }
+"#,
+ );
+
+ check_edit(
+ "foo",
+ r#"
+struct S;
+impl S {
+ fn foo(&self) {}
+}
+fn bar(s: &S) { s.f$0 }
+"#,
+ r#"
+struct S;
+impl S {
+ fn foo(&self) {}
+}
+fn bar(s: &S) { s.foo()$0 }
+"#,
+ );
+
+ check_edit(
+ "foo",
+ r#"
+struct S {}
+impl S {
+ fn foo(&self, x: i32) {}
+}
+fn bar(s: &S) {
+ s.f$0
+}
+"#,
+ r#"
+struct S {}
+impl S {
+ fn foo(&self, x: i32) {}
+}
+fn bar(s: &S) {
+ s.foo(${1:x})$0
+}
+"#,
+ );
+
+ check_edit(
+ "foo",
+ r#"
+struct S {}
+impl S {
+ fn foo(&self, x: i32) {
+ $0
+ }
+}
+"#,
+ r#"
+struct S {}
+impl S {
+ fn foo(&self, x: i32) {
+ self.foo(${1:x})$0
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn parens_for_method_call_as_assoc_fn() {
+ check_edit(
+ "foo",
+ r#"
+struct S;
+impl S {
+ fn foo(&self) {}
+}
+fn main() { S::f$0 }
+"#,
+ r#"
+struct S;
+impl S {
+ fn foo(&self) {}
+}
+fn main() { S::foo(${1:&self})$0 }
+"#,
+ );
+ }
+
+ #[test]
+ fn suppress_arg_snippets() {
+ cov_mark::check!(suppress_arg_snippets);
+ check_edit_with_config(
+ CompletionConfig { callable: Some(CallableSnippets::AddParentheses), ..TEST_CONFIG },
+ "with_args",
+ r#"
+fn with_args(x: i32, y: String) {}
+fn main() { with_$0 }
+"#,
+ r#"
+fn with_args(x: i32, y: String) {}
+fn main() { with_args($0) }
+"#,
+ );
+ }
+
+ #[test]
+ fn strips_underscores_from_args() {
+ check_edit(
+ "foo",
+ r#"
+fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
+fn main() { f$0 }
+"#,
+ r#"
+fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
+fn main() { foo(${1:foo}, ${2:bar}, ${3:ho_ge_})$0 }
+"#,
+ );
+ }
+
+ #[test]
+ fn insert_ref_when_matching_local_in_scope() {
+ check_edit(
+ "ref_arg",
+ r#"
+struct Foo {}
+fn ref_arg(x: &Foo) {}
+fn main() {
+ let x = Foo {};
+ ref_ar$0
+}
+"#,
+ r#"
+struct Foo {}
+fn ref_arg(x: &Foo) {}
+fn main() {
+ let x = Foo {};
+ ref_arg(${1:&x})$0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn insert_mut_ref_when_matching_local_in_scope() {
+ check_edit(
+ "ref_arg",
+ r#"
+struct Foo {}
+fn ref_arg(x: &mut Foo) {}
+fn main() {
+ let x = Foo {};
+ ref_ar$0
+}
+"#,
+ r#"
+struct Foo {}
+fn ref_arg(x: &mut Foo) {}
+fn main() {
+ let x = Foo {};
+ ref_arg(${1:&mut x})$0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn insert_ref_when_matching_local_in_scope_for_method() {
+ check_edit(
+ "apply_foo",
+ r#"
+struct Foo {}
+struct Bar {}
+impl Bar {
+ fn apply_foo(&self, x: &Foo) {}
+}
+
+fn main() {
+ let x = Foo {};
+ let y = Bar {};
+ y.$0
+}
+"#,
+ r#"
+struct Foo {}
+struct Bar {}
+impl Bar {
+ fn apply_foo(&self, x: &Foo) {}
+}
+
+fn main() {
+ let x = Foo {};
+ let y = Bar {};
+ y.apply_foo(${1:&x})$0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn trim_mut_keyword_in_func_completion() {
+ check_edit(
+ "take_mutably",
+ r#"
+fn take_mutably(mut x: &i32) {}
+
+fn main() {
+ take_m$0
+}
+"#,
+ r#"
+fn take_mutably(mut x: &i32) {}
+
+fn main() {
+ take_mutably(${1:x})$0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn complete_pattern_args_with_type_name_if_adt() {
+ check_edit(
+ "qux",
+ r#"
+struct Foo {
+ bar: i32
+}
+
+fn qux(Foo { bar }: Foo) {
+ println!("{}", bar);
+}
+
+fn main() {
+ qu$0
+}
+"#,
+ r#"
+struct Foo {
+ bar: i32
+}
+
+fn qux(Foo { bar }: Foo) {
+ println!("{}", bar);
+}
+
+fn main() {
+ qux(${1:foo})$0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn complete_fn_param() {
+ // has mut kw
+ check_edit(
+ "mut bar: u32",
+ r#"
+fn f(foo: (), mut bar: u32) {}
+fn g(foo: (), mut ba$0)
+"#,
+ r#"
+fn f(foo: (), mut bar: u32) {}
+fn g(foo: (), mut bar: u32)
+"#,
+ );
+
+ // has type param
+ check_edit(
+ "mut bar: u32",
+ r#"
+fn g(foo: (), mut ba$0: u32)
+fn f(foo: (), mut bar: u32) {}
+"#,
+ r#"
+fn g(foo: (), mut bar: u32)
+fn f(foo: (), mut bar: u32) {}
+"#,
+ );
+ }
+
+ #[test]
+ fn complete_fn_mut_param_add_comma() {
+ // add leading and trailing comma
+ check_edit(
+ ", mut bar: u32,",
+ r#"
+fn f(foo: (), mut bar: u32) {}
+fn g(foo: ()mut ba$0 baz: ())
+"#,
+ r#"
+fn f(foo: (), mut bar: u32) {}
+fn g(foo: (), mut bar: u32, baz: ())
+"#,
+ );
+ }
+
+ #[test]
+ fn complete_fn_mut_param_has_attribute() {
+ check_edit(
+ r#"#[baz = "qux"] mut bar: u32"#,
+ r#"
+fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
+fn g(foo: (), mut ba$0)
+"#,
+ r#"
+fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
+fn g(foo: (), #[baz = "qux"] mut bar: u32)
+"#,
+ );
+
+ check_edit(
+ r#"#[baz = "qux"] mut bar: u32"#,
+ r#"
+fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
+fn g(foo: (), #[baz = "qux"] mut ba$0)
+"#,
+ r#"
+fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
+fn g(foo: (), #[baz = "qux"] mut bar: u32)
+"#,
+ );
+
+ check_edit(
+ r#", #[baz = "qux"] mut bar: u32"#,
+ r#"
+fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
+fn g(foo: ()#[baz = "qux"] mut ba$0)
+"#,
+ r#"
+fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
+fn g(foo: (), #[baz = "qux"] mut bar: u32)
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/literal.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/literal.rs
new file mode 100644
index 000000000..91a253f8f
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/literal.rs
@@ -0,0 +1,191 @@
+//! Renderer for `enum` variants.
+
+use hir::{db::HirDatabase, Documentation, HasAttrs, StructKind};
+use ide_db::SymbolKind;
+use syntax::AstNode;
+
+use crate::{
+ context::{CompletionContext, PathCompletionCtx, PathKind},
+ item::{Builder, CompletionItem},
+ render::{
+ compute_ref_match, compute_type_match,
+ variant::{
+ format_literal_label, render_record_lit, render_tuple_lit, visible_fields,
+ RenderedLiteral,
+ },
+ RenderContext,
+ },
+ CompletionItemKind, CompletionRelevance,
+};
+
+pub(crate) fn render_variant_lit(
+ ctx: RenderContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ local_name: Option<hir::Name>,
+ variant: hir::Variant,
+ path: Option<hir::ModPath>,
+) -> Option<Builder> {
+ let _p = profile::span("render_enum_variant");
+ let db = ctx.db();
+
+ let name = local_name.unwrap_or_else(|| variant.name(db));
+ render(ctx, path_ctx, Variant::EnumVariant(variant), name, path)
+}
+
+pub(crate) fn render_struct_literal(
+ ctx: RenderContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ strukt: hir::Struct,
+ path: Option<hir::ModPath>,
+ local_name: Option<hir::Name>,
+) -> Option<Builder> {
+ let _p = profile::span("render_struct_literal");
+ let db = ctx.db();
+
+ let name = local_name.unwrap_or_else(|| strukt.name(db));
+ render(ctx, path_ctx, Variant::Struct(strukt), name, path)
+}
+
+fn render(
+ ctx @ RenderContext { completion, .. }: RenderContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ thing: Variant,
+ name: hir::Name,
+ path: Option<hir::ModPath>,
+) -> Option<Builder> {
+ let db = completion.db;
+ let mut kind = thing.kind(db);
+ let should_add_parens = match &path_ctx {
+ PathCompletionCtx { has_call_parens: true, .. } => false,
+ PathCompletionCtx { kind: PathKind::Use | PathKind::Type { .. }, .. } => false,
+ _ => true,
+ };
+
+ let fields = thing.fields(completion)?;
+ let (qualified_name, short_qualified_name, qualified) = match path {
+ Some(path) => {
+ let short = hir::ModPath::from_segments(
+ hir::PathKind::Plain,
+ path.segments().iter().skip(path.segments().len().saturating_sub(2)).cloned(),
+ );
+ (path, short, true)
+ }
+ None => (name.clone().into(), name.into(), false),
+ };
+ let (qualified_name, escaped_qualified_name) =
+ (qualified_name.to_string(), qualified_name.escaped().to_string());
+ let snippet_cap = ctx.snippet_cap();
+
+ let mut rendered = match kind {
+ StructKind::Tuple if should_add_parens => {
+ render_tuple_lit(db, snippet_cap, &fields, &escaped_qualified_name)
+ }
+ StructKind::Record if should_add_parens => {
+ render_record_lit(db, snippet_cap, &fields, &escaped_qualified_name)
+ }
+ _ => RenderedLiteral {
+ literal: escaped_qualified_name.clone(),
+ detail: escaped_qualified_name.clone(),
+ },
+ };
+
+ if snippet_cap.is_some() {
+ rendered.literal.push_str("$0");
+ }
+
+ // only show name in label if not adding parens
+ if !should_add_parens {
+ kind = StructKind::Unit;
+ }
+
+ let mut item = CompletionItem::new(
+ CompletionItemKind::SymbolKind(thing.symbol_kind()),
+ ctx.source_range(),
+ format_literal_label(&qualified_name, kind),
+ );
+
+ item.detail(rendered.detail);
+
+ match snippet_cap {
+ Some(snippet_cap) => item.insert_snippet(snippet_cap, rendered.literal),
+ None => item.insert_text(rendered.literal),
+ };
+
+ if qualified {
+ item.lookup_by(format_literal_label(&short_qualified_name.to_string(), kind));
+ }
+ item.set_documentation(thing.docs(db)).set_deprecated(thing.is_deprecated(&ctx));
+
+ let ty = thing.ty(db);
+ item.set_relevance(CompletionRelevance {
+ type_match: compute_type_match(ctx.completion, &ty),
+ ..ctx.completion_relevance()
+ });
+ if let Some(ref_match) = compute_ref_match(completion, &ty) {
+ item.ref_match(ref_match, path_ctx.path.syntax().text_range().start());
+ }
+
+ if let Some(import_to_add) = ctx.import_to_add {
+ item.add_import(import_to_add);
+ }
+ Some(item)
+}
+
+#[derive(Clone, Copy)]
+enum Variant {
+ Struct(hir::Struct),
+ EnumVariant(hir::Variant),
+}
+
+impl Variant {
+ fn fields(self, ctx: &CompletionContext<'_>) -> Option<Vec<hir::Field>> {
+ let fields = match self {
+ Variant::Struct(it) => it.fields(ctx.db),
+ Variant::EnumVariant(it) => it.fields(ctx.db),
+ };
+ let (visible_fields, fields_omitted) = match self {
+ Variant::Struct(it) => visible_fields(ctx, &fields, it)?,
+ Variant::EnumVariant(it) => visible_fields(ctx, &fields, it)?,
+ };
+ if !fields_omitted {
+ Some(visible_fields)
+ } else {
+ None
+ }
+ }
+
+ fn kind(self, db: &dyn HirDatabase) -> StructKind {
+ match self {
+ Variant::Struct(it) => it.kind(db),
+ Variant::EnumVariant(it) => it.kind(db),
+ }
+ }
+
+ fn symbol_kind(self) -> SymbolKind {
+ match self {
+ Variant::Struct(_) => SymbolKind::Struct,
+ Variant::EnumVariant(_) => SymbolKind::Variant,
+ }
+ }
+
+ fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> {
+ match self {
+ Variant::Struct(it) => it.docs(db),
+ Variant::EnumVariant(it) => it.docs(db),
+ }
+ }
+
+ fn is_deprecated(self, ctx: &RenderContext<'_>) -> bool {
+ match self {
+ Variant::Struct(it) => ctx.is_deprecated(it),
+ Variant::EnumVariant(it) => ctx.is_deprecated(it),
+ }
+ }
+
+ fn ty(self, db: &dyn HirDatabase) -> hir::Type {
+ match self {
+ Variant::Struct(it) => it.ty(db),
+ Variant::EnumVariant(it) => it.parent_enum(db).ty(db),
+ }
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/macro_.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/macro_.rs
new file mode 100644
index 000000000..ca2269f13
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/macro_.rs
@@ -0,0 +1,270 @@
+//! Renderer for macro invocations.
+
+use hir::{Documentation, HirDisplay};
+use ide_db::SymbolKind;
+use syntax::SmolStr;
+
+use crate::{
+ context::{PathCompletionCtx, PathKind, PatternContext},
+ item::{Builder, CompletionItem},
+ render::RenderContext,
+};
+
+pub(crate) fn render_macro(
+ ctx: RenderContext<'_>,
+ PathCompletionCtx { kind, has_macro_bang, has_call_parens, .. }: &PathCompletionCtx,
+
+ name: hir::Name,
+ macro_: hir::Macro,
+) -> Builder {
+ let _p = profile::span("render_macro");
+ render(ctx, *kind == PathKind::Use, *has_macro_bang, *has_call_parens, name, macro_)
+}
+
+pub(crate) fn render_macro_pat(
+ ctx: RenderContext<'_>,
+ _pattern_ctx: &PatternContext,
+ name: hir::Name,
+ macro_: hir::Macro,
+) -> Builder {
+ let _p = profile::span("render_macro");
+ render(ctx, false, false, false, name, macro_)
+}
+
+fn render(
+ ctx @ RenderContext { completion, .. }: RenderContext<'_>,
+ is_use_path: bool,
+ has_macro_bang: bool,
+ has_call_parens: bool,
+ name: hir::Name,
+ macro_: hir::Macro,
+) -> Builder {
+ let source_range = if ctx.is_immediately_after_macro_bang() {
+ cov_mark::hit!(completes_macro_call_if_cursor_at_bang_token);
+ completion.token.parent().map_or_else(|| ctx.source_range(), |it| it.text_range())
+ } else {
+ ctx.source_range()
+ };
+
+ let (name, escaped_name) = (name.to_smol_str(), name.escaped().to_smol_str());
+ let docs = ctx.docs(macro_);
+ let docs_str = docs.as_ref().map(Documentation::as_str).unwrap_or_default();
+ let is_fn_like = macro_.is_fn_like(completion.db);
+ let (bra, ket) = if is_fn_like { guess_macro_braces(&name, docs_str) } else { ("", "") };
+
+ let needs_bang = is_fn_like && !is_use_path && !has_macro_bang;
+
+ let mut item = CompletionItem::new(
+ SymbolKind::from(macro_.kind(completion.db)),
+ source_range,
+ label(&ctx, needs_bang, bra, ket, &name),
+ );
+ item.set_deprecated(ctx.is_deprecated(macro_))
+ .detail(macro_.display(completion.db).to_string())
+ .set_documentation(docs)
+ .set_relevance(ctx.completion_relevance());
+
+ match ctx.snippet_cap() {
+ Some(cap) if needs_bang && !has_call_parens => {
+ let snippet = format!("{}!{}$0{}", escaped_name, bra, ket);
+ let lookup = banged_name(&name);
+ item.insert_snippet(cap, snippet).lookup_by(lookup);
+ }
+ _ if needs_bang => {
+ item.insert_text(banged_name(&escaped_name)).lookup_by(banged_name(&name));
+ }
+ _ => {
+ cov_mark::hit!(dont_insert_macro_call_parens_unncessary);
+ item.insert_text(escaped_name);
+ }
+ };
+ if let Some(import_to_add) = ctx.import_to_add {
+ item.add_import(import_to_add);
+ }
+
+ item
+}
+
+fn label(
+ ctx: &RenderContext<'_>,
+ needs_bang: bool,
+ bra: &str,
+ ket: &str,
+ name: &SmolStr,
+) -> SmolStr {
+ if needs_bang {
+ if ctx.snippet_cap().is_some() {
+ SmolStr::from_iter([&*name, "!", bra, "…", ket])
+ } else {
+ banged_name(name)
+ }
+ } else {
+ name.clone()
+ }
+}
+
+fn banged_name(name: &str) -> SmolStr {
+ SmolStr::from_iter([name, "!"])
+}
+
+fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) {
+ let mut votes = [0, 0, 0];
+ for (idx, s) in docs.match_indices(&macro_name) {
+ let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
+ // Ensure to match the full word
+ if after.starts_with('!')
+ && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric())
+ {
+ // It may have spaces before the braces like `foo! {}`
+ match after[1..].chars().find(|&c| !c.is_whitespace()) {
+ Some('{') => votes[0] += 1,
+ Some('[') => votes[1] += 1,
+ Some('(') => votes[2] += 1,
+ _ => {}
+ }
+ }
+ }
+
+ // Insert a space before `{}`.
+ // We prefer the last one when some votes equal.
+ let (_vote, (bra, ket)) = votes
+ .iter()
+ .zip(&[(" {", "}"), ("[", "]"), ("(", ")")])
+ .max_by_key(|&(&vote, _)| vote)
+ .unwrap();
+ (*bra, *ket)
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::check_edit;
+
+ #[test]
+ fn dont_insert_macro_call_parens_unncessary() {
+ cov_mark::check!(dont_insert_macro_call_parens_unncessary);
+ check_edit(
+ "frobnicate",
+ r#"
+//- /main.rs crate:main deps:foo
+use foo::$0;
+//- /foo/lib.rs crate:foo
+#[macro_export]
+macro_rules! frobnicate { () => () }
+"#,
+ r#"
+use foo::frobnicate;
+"#,
+ );
+
+ check_edit(
+ "frobnicate",
+ r#"
+macro_rules! frobnicate { () => () }
+fn main() { frob$0!(); }
+"#,
+ r#"
+macro_rules! frobnicate { () => () }
+fn main() { frobnicate!(); }
+"#,
+ );
+ }
+
+ #[test]
+ fn add_bang_to_parens() {
+ check_edit(
+ "frobnicate!",
+ r#"
+macro_rules! frobnicate { () => () }
+fn main() {
+ frob$0()
+}
+"#,
+ r#"
+macro_rules! frobnicate { () => () }
+fn main() {
+ frobnicate!()
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn guesses_macro_braces() {
+ check_edit(
+ "vec!",
+ r#"
+/// Creates a [`Vec`] containing the arguments.
+///
+/// ```
+/// let v = vec![1, 2, 3];
+/// assert_eq!(v[0], 1);
+/// assert_eq!(v[1], 2);
+/// assert_eq!(v[2], 3);
+/// ```
+macro_rules! vec { () => {} }
+
+fn main() { v$0 }
+"#,
+ r#"
+/// Creates a [`Vec`] containing the arguments.
+///
+/// ```
+/// let v = vec![1, 2, 3];
+/// assert_eq!(v[0], 1);
+/// assert_eq!(v[1], 2);
+/// assert_eq!(v[2], 3);
+/// ```
+macro_rules! vec { () => {} }
+
+fn main() { vec![$0] }
+"#,
+ );
+
+ check_edit(
+ "foo!",
+ r#"
+/// Foo
+///
+/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
+/// call as `let _=foo! { hello world };`
+macro_rules! foo { () => {} }
+fn main() { $0 }
+"#,
+ r#"
+/// Foo
+///
+/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
+/// call as `let _=foo! { hello world };`
+macro_rules! foo { () => {} }
+fn main() { foo! {$0} }
+"#,
+ )
+ }
+
+ #[test]
+ fn completes_macro_call_if_cursor_at_bang_token() {
+ // Regression test for https://github.com/rust-lang/rust-analyzer/issues/9904
+ cov_mark::check!(completes_macro_call_if_cursor_at_bang_token);
+ check_edit(
+ "foo!",
+ r#"
+macro_rules! foo {
+ () => {}
+}
+
+fn main() {
+ foo!$0
+}
+"#,
+ r#"
+macro_rules! foo {
+ () => {}
+}
+
+fn main() {
+ foo!($0)
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/pattern.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/pattern.rs
new file mode 100644
index 000000000..34a384f2f
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/pattern.rs
@@ -0,0 +1,193 @@
+//! Renderer for patterns.
+
+use hir::{db::HirDatabase, HasAttrs, Name, StructKind};
+use ide_db::SnippetCap;
+use itertools::Itertools;
+use syntax::SmolStr;
+
+use crate::{
+ context::{ParamContext, ParamKind, PathCompletionCtx, PatternContext},
+ render::{
+ variant::{format_literal_label, visible_fields},
+ RenderContext,
+ },
+ CompletionItem, CompletionItemKind,
+};
+
+pub(crate) fn render_struct_pat(
+ ctx: RenderContext<'_>,
+ pattern_ctx: &PatternContext,
+ strukt: hir::Struct,
+ local_name: Option<Name>,
+) -> Option<CompletionItem> {
+ let _p = profile::span("render_struct_pat");
+
+ let fields = strukt.fields(ctx.db());
+ let (visible_fields, fields_omitted) = visible_fields(ctx.completion, &fields, strukt)?;
+
+ if visible_fields.is_empty() {
+ // Matching a struct without matching its fields is pointless, unlike matching a Variant without its fields
+ return None;
+ }
+
+ let name = local_name.unwrap_or_else(|| strukt.name(ctx.db()));
+ let (name, escaped_name) = (name.to_smol_str(), name.escaped().to_smol_str());
+ let kind = strukt.kind(ctx.db());
+ let label = format_literal_label(name.as_str(), kind);
+ let pat = render_pat(&ctx, pattern_ctx, &escaped_name, kind, &visible_fields, fields_omitted)?;
+
+ Some(build_completion(ctx, label, pat, strukt))
+}
+
+pub(crate) fn render_variant_pat(
+ ctx: RenderContext<'_>,
+ pattern_ctx: &PatternContext,
+ path_ctx: Option<&PathCompletionCtx>,
+ variant: hir::Variant,
+ local_name: Option<Name>,
+ path: Option<&hir::ModPath>,
+) -> Option<CompletionItem> {
+ let _p = profile::span("render_variant_pat");
+
+ let fields = variant.fields(ctx.db());
+ let (visible_fields, fields_omitted) = visible_fields(ctx.completion, &fields, variant)?;
+
+ let (name, escaped_name) = match path {
+ Some(path) => (path.to_string().into(), path.escaped().to_string().into()),
+ None => {
+ let name = local_name.unwrap_or_else(|| variant.name(ctx.db()));
+ (name.to_smol_str(), name.escaped().to_smol_str())
+ }
+ };
+
+ let (label, pat) = match path_ctx {
+ Some(PathCompletionCtx { has_call_parens: true, .. }) => (name, escaped_name.to_string()),
+ _ => {
+ let kind = variant.kind(ctx.db());
+ let label = format_literal_label(name.as_str(), kind);
+ let pat = render_pat(
+ &ctx,
+ pattern_ctx,
+ &escaped_name,
+ kind,
+ &visible_fields,
+ fields_omitted,
+ )?;
+ (label, pat)
+ }
+ };
+
+ Some(build_completion(ctx, label, pat, variant))
+}
+
+fn build_completion(
+ ctx: RenderContext<'_>,
+ label: SmolStr,
+ pat: String,
+ def: impl HasAttrs + Copy,
+) -> CompletionItem {
+ let mut item = CompletionItem::new(CompletionItemKind::Binding, ctx.source_range(), label);
+ item.set_documentation(ctx.docs(def))
+ .set_deprecated(ctx.is_deprecated(def))
+ .detail(&pat)
+ .set_relevance(ctx.completion_relevance());
+ match ctx.snippet_cap() {
+ Some(snippet_cap) => item.insert_snippet(snippet_cap, pat),
+ None => item.insert_text(pat),
+ };
+ item.build()
+}
+
+fn render_pat(
+ ctx: &RenderContext<'_>,
+ pattern_ctx: &PatternContext,
+ name: &str,
+ kind: StructKind,
+ fields: &[hir::Field],
+ fields_omitted: bool,
+) -> Option<String> {
+ let mut pat = match kind {
+ StructKind::Tuple => render_tuple_as_pat(ctx.snippet_cap(), fields, name, fields_omitted),
+ StructKind::Record => {
+ render_record_as_pat(ctx.db(), ctx.snippet_cap(), fields, name, fields_omitted)
+ }
+ StructKind::Unit => name.to_string(),
+ };
+
+ let needs_ascription = matches!(
+ pattern_ctx,
+ PatternContext {
+ param_ctx: Some(ParamContext { kind: ParamKind::Function(_), .. }),
+ has_type_ascription: false,
+ ..
+ }
+ );
+ if needs_ascription {
+ pat.push(':');
+ pat.push(' ');
+ pat.push_str(name);
+ }
+ if ctx.snippet_cap().is_some() {
+ pat.push_str("$0");
+ }
+ Some(pat)
+}
+
+fn render_record_as_pat(
+ db: &dyn HirDatabase,
+ snippet_cap: Option<SnippetCap>,
+ fields: &[hir::Field],
+ name: &str,
+ fields_omitted: bool,
+) -> String {
+ let fields = fields.iter();
+ match snippet_cap {
+ Some(_) => {
+ format!(
+ "{name} {{ {}{} }}",
+ fields.enumerate().format_with(", ", |(idx, field), f| {
+ f(&format_args!("{}${}", field.name(db).escaped(), idx + 1))
+ }),
+ if fields_omitted { ", .." } else { "" },
+ name = name
+ )
+ }
+ None => {
+ format!(
+ "{name} {{ {}{} }}",
+ fields.map(|field| field.name(db).escaped().to_smol_str()).format(", "),
+ if fields_omitted { ", .." } else { "" },
+ name = name
+ )
+ }
+ }
+}
+
+fn render_tuple_as_pat(
+ snippet_cap: Option<SnippetCap>,
+ fields: &[hir::Field],
+ name: &str,
+ fields_omitted: bool,
+) -> String {
+ let fields = fields.iter();
+ match snippet_cap {
+ Some(_) => {
+ format!(
+ "{name}({}{})",
+ fields
+ .enumerate()
+ .format_with(", ", |(idx, _), f| { f(&format_args!("${}", idx + 1)) }),
+ if fields_omitted { ", .." } else { "" },
+ name = name
+ )
+ }
+ None => {
+ format!(
+ "{name}({}{})",
+ fields.enumerate().map(|(idx, _)| idx).format(", "),
+ if fields_omitted { ", .." } else { "" },
+ name = name
+ )
+ }
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/type_alias.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/type_alias.rs
new file mode 100644
index 000000000..f1b23c76e
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/type_alias.rs
@@ -0,0 +1,57 @@
+//! Renderer for type aliases.
+
+use hir::{AsAssocItem, HirDisplay};
+use ide_db::SymbolKind;
+use syntax::SmolStr;
+
+use crate::{item::CompletionItem, render::RenderContext};
+
+pub(crate) fn render_type_alias(
+ ctx: RenderContext<'_>,
+ type_alias: hir::TypeAlias,
+) -> Option<CompletionItem> {
+ let _p = profile::span("render_type_alias");
+ render(ctx, type_alias, false)
+}
+
+pub(crate) fn render_type_alias_with_eq(
+ ctx: RenderContext<'_>,
+ type_alias: hir::TypeAlias,
+) -> Option<CompletionItem> {
+ let _p = profile::span("render_type_alias_with_eq");
+ render(ctx, type_alias, true)
+}
+
+fn render(
+ ctx: RenderContext<'_>,
+ type_alias: hir::TypeAlias,
+ with_eq: bool,
+) -> Option<CompletionItem> {
+ let db = ctx.db();
+
+ let name = type_alias.name(db);
+ let (name, escaped_name) = if with_eq {
+ (
+ SmolStr::from_iter([&name.to_smol_str(), " = "]),
+ SmolStr::from_iter([&name.escaped().to_smol_str(), " = "]),
+ )
+ } else {
+ (name.to_smol_str(), name.escaped().to_smol_str())
+ };
+ let detail = type_alias.display(db).to_string();
+
+ let mut item = CompletionItem::new(SymbolKind::TypeAlias, ctx.source_range(), name.clone());
+ item.set_documentation(ctx.docs(type_alias))
+ .set_deprecated(ctx.is_deprecated(type_alias) || ctx.is_deprecated_assoc_item(type_alias))
+ .detail(detail)
+ .set_relevance(ctx.completion_relevance());
+
+ if let Some(actm) = type_alias.as_assoc_item(db) {
+ if let Some(trt) = actm.containing_trait_or_trait_impl(db) {
+ item.trait_name(trt.name(db).to_smol_str());
+ }
+ }
+ item.insert_text(escaped_name);
+
+ Some(item.build())
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/union_literal.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/union_literal.rs
new file mode 100644
index 000000000..9c9540a9b
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/union_literal.rs
@@ -0,0 +1,77 @@
+//! Renderer for `union` literals.
+
+use hir::{HirDisplay, Name, StructKind};
+use ide_db::SymbolKind;
+use itertools::Itertools;
+
+use crate::{
+ render::{
+ variant::{format_literal_label, visible_fields},
+ RenderContext,
+ },
+ CompletionItem, CompletionItemKind,
+};
+
+pub(crate) fn render_union_literal(
+ ctx: RenderContext<'_>,
+ un: hir::Union,
+ path: Option<hir::ModPath>,
+ local_name: Option<Name>,
+) -> Option<CompletionItem> {
+ let name = local_name.unwrap_or_else(|| un.name(ctx.db()));
+
+ let (qualified_name, escaped_qualified_name) = match path {
+ Some(p) => (p.to_string(), p.escaped().to_string()),
+ None => (name.to_string(), name.escaped().to_string()),
+ };
+
+ let mut item = CompletionItem::new(
+ CompletionItemKind::SymbolKind(SymbolKind::Union),
+ ctx.source_range(),
+ format_literal_label(&name.to_smol_str(), StructKind::Record),
+ );
+
+ let fields = un.fields(ctx.db());
+ let (fields, fields_omitted) = visible_fields(ctx.completion, &fields, un)?;
+
+ if fields.is_empty() {
+ return None;
+ }
+
+ let literal = if ctx.snippet_cap().is_some() {
+ format!(
+ "{} {{ ${{1|{}|}}: ${{2:()}} }}$0",
+ escaped_qualified_name,
+ fields.iter().map(|field| field.name(ctx.db()).escaped().to_smol_str()).format(",")
+ )
+ } else {
+ format!(
+ "{} {{ {} }}",
+ escaped_qualified_name,
+ fields.iter().format_with(", ", |field, f| {
+ f(&format_args!("{}: ()", field.name(ctx.db()).escaped()))
+ })
+ )
+ };
+
+ let detail = format!(
+ "{} {{ {}{} }}",
+ qualified_name,
+ fields.iter().format_with(", ", |field, f| {
+ f(&format_args!("{}: {}", field.name(ctx.db()), field.ty(ctx.db()).display(ctx.db())))
+ }),
+ if fields_omitted { ", .." } else { "" }
+ );
+
+ item.set_documentation(ctx.docs(un))
+ .set_deprecated(ctx.is_deprecated(un))
+ .detail(&detail)
+ .set_relevance(ctx.completion_relevance());
+
+ match ctx.snippet_cap() {
+ Some(snippet_cap) => item.insert_snippet(snippet_cap, literal),
+ None => item.insert_text(literal),
+ };
+
+ Some(item.build())
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/variant.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/variant.rs
new file mode 100644
index 000000000..003a0c11e
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/variant.rs
@@ -0,0 +1,96 @@
+//! Code common to structs, unions, and enum variants.
+
+use crate::context::CompletionContext;
+use hir::{db::HirDatabase, HasAttrs, HasCrate, HasVisibility, HirDisplay, StructKind};
+use ide_db::SnippetCap;
+use itertools::Itertools;
+use syntax::SmolStr;
+
+/// A rendered struct, union, or enum variant, split into fields for actual
+/// auto-completion (`literal`, using `field: ()`) and display in the
+/// completions menu (`detail`, using `field: type`).
+pub(crate) struct RenderedLiteral {
+ pub(crate) literal: String,
+ pub(crate) detail: String,
+}
+
+/// Render a record type (or sub-type) to a `RenderedCompound`. Use `None` for
+/// the `name` argument for an anonymous type.
+pub(crate) fn render_record_lit(
+ db: &dyn HirDatabase,
+ snippet_cap: Option<SnippetCap>,
+ fields: &[hir::Field],
+ path: &str,
+) -> RenderedLiteral {
+ let completions = fields.iter().enumerate().format_with(", ", |(idx, field), f| {
+ if snippet_cap.is_some() {
+ f(&format_args!("{}: ${{{}:()}}", field.name(db).escaped(), idx + 1))
+ } else {
+ f(&format_args!("{}: ()", field.name(db).escaped()))
+ }
+ });
+
+ let types = fields.iter().format_with(", ", |field, f| {
+ f(&format_args!("{}: {}", field.name(db), field.ty(db).display(db)))
+ });
+
+ RenderedLiteral {
+ literal: format!("{} {{ {} }}", path, completions),
+ detail: format!("{} {{ {} }}", path, types),
+ }
+}
+
+/// Render a tuple type (or sub-type) to a `RenderedCompound`. Use `None` for
+/// the `name` argument for an anonymous type.
+pub(crate) fn render_tuple_lit(
+ db: &dyn HirDatabase,
+ snippet_cap: Option<SnippetCap>,
+ fields: &[hir::Field],
+ path: &str,
+) -> RenderedLiteral {
+ let completions = fields.iter().enumerate().format_with(", ", |(idx, _), f| {
+ if snippet_cap.is_some() {
+ f(&format_args!("${{{}:()}}", idx + 1))
+ } else {
+ f(&format_args!("()"))
+ }
+ });
+
+ let types = fields.iter().format_with(", ", |field, f| f(&field.ty(db).display(db)));
+
+ RenderedLiteral {
+ literal: format!("{}({})", path, completions),
+ detail: format!("{}({})", path, types),
+ }
+}
+
+/// Find all the visible fields in a given list. Returns the list of visible
+/// fields, plus a boolean for whether the list is comprehensive (contains no
+/// private fields and its item is not marked `#[non_exhaustive]`).
+pub(crate) fn visible_fields(
+ ctx: &CompletionContext<'_>,
+ fields: &[hir::Field],
+ item: impl HasAttrs + HasCrate + Copy,
+) -> Option<(Vec<hir::Field>, bool)> {
+ let module = ctx.module;
+ let n_fields = fields.len();
+ let fields = fields
+ .iter()
+ .filter(|field| field.is_visible_from(ctx.db, module))
+ .copied()
+ .collect::<Vec<_>>();
+ let has_invisible_field = n_fields - fields.len() > 0;
+ let is_foreign_non_exhaustive = item.attrs(ctx.db).by_key("non_exhaustive").exists()
+ && item.krate(ctx.db) != module.krate();
+ let fields_omitted = has_invisible_field || is_foreign_non_exhaustive;
+ Some((fields, fields_omitted))
+}
+
+/// Format a struct, etc. literal option for display in the completions menu.
+pub(crate) fn format_literal_label(name: &str, kind: StructKind) -> SmolStr {
+ match kind {
+ StructKind::Tuple => SmolStr::from_iter([name, "(…)"]),
+ StructKind::Record => SmolStr::from_iter([name, " {…}"]),
+ StructKind::Unit => name.into(),
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/snippet.rs b/src/tools/rust-analyzer/crates/ide-completion/src/snippet.rs
new file mode 100644
index 000000000..dc1039fa6
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/snippet.rs
@@ -0,0 +1,214 @@
+//! User (postfix)-snippet definitions.
+//!
+//! Actual logic is implemented in [`crate::completions::postfix`] and [`crate::completions::snippet`] respectively.
+
+// Feature: User Snippet Completions
+//
+// rust-analyzer allows the user to define custom (postfix)-snippets that may depend on items to be accessible for the current scope to be applicable.
+//
+// A custom snippet can be defined by adding it to the `rust-analyzer.completion.snippets.custom` object respectively.
+//
+// [source,json]
+// ----
+// {
+// "rust-analyzer.completion.snippets.custom": {
+// "thread spawn": {
+// "prefix": ["spawn", "tspawn"],
+// "body": [
+// "thread::spawn(move || {",
+// "\t$0",
+// "});",
+// ],
+// "description": "Insert a thread::spawn call",
+// "requires": "std::thread",
+// "scope": "expr",
+// }
+// }
+// }
+// ----
+//
+// In the example above:
+//
+// * `"thread spawn"` is the name of the snippet.
+//
+// * `prefix` defines one or more trigger words that will trigger the snippets completion.
+// Using `postfix` will instead create a postfix snippet.
+//
+// * `body` is one or more lines of content joined via newlines for the final output.
+//
+// * `description` is an optional description of the snippet, if unset the snippet name will be used.
+//
+// * `requires` is an optional list of item paths that have to be resolvable in the current crate where the completion is rendered.
+// On failure of resolution the snippet won't be applicable, otherwise the snippet will insert an import for the items on insertion if
+// the items aren't yet in scope.
+//
+// * `scope` is an optional filter for when the snippet should be applicable. Possible values are:
+// ** for Snippet-Scopes: `expr`, `item` (default: `item`)
+// ** for Postfix-Snippet-Scopes: `expr`, `type` (default: `expr`)
+//
+// The `body` field also has access to placeholders as visible in the example as `$0`.
+// These placeholders take the form of `$number` or `${number:placeholder_text}` which can be traversed as tabstop in ascending order starting from 1,
+// with `$0` being a special case that always comes last.
+//
+// There is also a special placeholder, `${receiver}`, which will be replaced by the receiver expression for postfix snippets, or a `$0` tabstop in case of normal snippets.
+// This replacement for normal snippets allows you to reuse a snippet for both post- and prefix in a single definition.
+//
+// For the VSCode editor, rust-analyzer also ships with a small set of defaults which can be removed
+// by overwriting the settings object mentioned above, the defaults are:
+// [source,json]
+// ----
+// {
+// "Arc::new": {
+// "postfix": "arc",
+// "body": "Arc::new(${receiver})",
+// "requires": "std::sync::Arc",
+// "description": "Put the expression into an `Arc`",
+// "scope": "expr"
+// },
+// "Rc::new": {
+// "postfix": "rc",
+// "body": "Rc::new(${receiver})",
+// "requires": "std::rc::Rc",
+// "description": "Put the expression into an `Rc`",
+// "scope": "expr"
+// },
+// "Box::pin": {
+// "postfix": "pinbox",
+// "body": "Box::pin(${receiver})",
+// "requires": "std::boxed::Box",
+// "description": "Put the expression into a pinned `Box`",
+// "scope": "expr"
+// },
+// "Ok": {
+// "postfix": "ok",
+// "body": "Ok(${receiver})",
+// "description": "Wrap the expression in a `Result::Ok`",
+// "scope": "expr"
+// },
+// "Err": {
+// "postfix": "err",
+// "body": "Err(${receiver})",
+// "description": "Wrap the expression in a `Result::Err`",
+// "scope": "expr"
+// },
+// "Some": {
+// "postfix": "some",
+// "body": "Some(${receiver})",
+// "description": "Wrap the expression in an `Option::Some`",
+// "scope": "expr"
+// }
+// }
+// ----
+
+use ide_db::imports::import_assets::LocatedImport;
+use itertools::Itertools;
+use syntax::{ast, AstNode, GreenNode, SyntaxNode};
+
+use crate::context::CompletionContext;
+
+/// A snippet scope describing where a snippet may apply to.
+/// These may differ slightly in meaning depending on the snippet trigger.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum SnippetScope {
+ Item,
+ Expr,
+ Type,
+}
+
+/// A user supplied snippet.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct Snippet {
+ pub postfix_triggers: Box<[Box<str>]>,
+ pub prefix_triggers: Box<[Box<str>]>,
+ pub scope: SnippetScope,
+ pub description: Option<Box<str>>,
+ snippet: String,
+ // These are `ast::Path`'s but due to SyntaxNodes not being Send we store these
+ // and reconstruct them on demand instead. This is cheaper than reparsing them
+ // from strings
+ requires: Box<[GreenNode]>,
+}
+
+impl Snippet {
+ pub fn new(
+ prefix_triggers: &[String],
+ postfix_triggers: &[String],
+ snippet: &[String],
+ description: &str,
+ requires: &[String],
+ scope: SnippetScope,
+ ) -> Option<Self> {
+ if prefix_triggers.is_empty() && postfix_triggers.is_empty() {
+ return None;
+ }
+ let (requires, snippet, description) = validate_snippet(snippet, description, requires)?;
+ Some(Snippet {
+ // Box::into doesn't work as that has a Copy bound 😒
+ postfix_triggers: postfix_triggers.iter().map(String::as_str).map(Into::into).collect(),
+ prefix_triggers: prefix_triggers.iter().map(String::as_str).map(Into::into).collect(),
+ scope,
+ snippet,
+ description,
+ requires,
+ })
+ }
+
+ /// Returns [`None`] if the required items do not resolve.
+ pub(crate) fn imports(&self, ctx: &CompletionContext<'_>) -> Option<Vec<LocatedImport>> {
+ import_edits(ctx, &self.requires)
+ }
+
+ pub fn snippet(&self) -> String {
+ self.snippet.replace("${receiver}", "$0")
+ }
+
+ pub fn postfix_snippet(&self, receiver: &str) -> String {
+ self.snippet.replace("${receiver}", receiver)
+ }
+}
+
+fn import_edits(ctx: &CompletionContext<'_>, requires: &[GreenNode]) -> Option<Vec<LocatedImport>> {
+ let resolve = |import: &GreenNode| {
+ let path = ast::Path::cast(SyntaxNode::new_root(import.clone()))?;
+ let item = match ctx.scope.speculative_resolve(&path)? {
+ hir::PathResolution::Def(def) => def.into(),
+ _ => return None,
+ };
+ let path =
+ ctx.module.find_use_path_prefixed(ctx.db, item, ctx.config.insert_use.prefix_kind)?;
+ Some((path.len() > 1).then(|| LocatedImport::new(path.clone(), item, item, None)))
+ };
+ let mut res = Vec::with_capacity(requires.len());
+ for import in requires {
+ match resolve(import) {
+ Some(first) => res.extend(first),
+ None => return None,
+ }
+ }
+ Some(res)
+}
+
+fn validate_snippet(
+ snippet: &[String],
+ description: &str,
+ requires: &[String],
+) -> Option<(Box<[GreenNode]>, String, Option<Box<str>>)> {
+ let mut imports = Vec::with_capacity(requires.len());
+ for path in requires.iter() {
+ let use_path = ast::SourceFile::parse(&format!("use {};", path))
+ .syntax_node()
+ .descendants()
+ .find_map(ast::Path::cast)?;
+ if use_path.syntax().text() != path.as_str() {
+ return None;
+ }
+ let green = use_path.syntax().green().into_owned();
+ imports.push(green);
+ }
+ let snippet = snippet.iter().join("\n");
+ let description = (!description.is_empty())
+ .then(|| description.split_once('\n').map_or(description, |(it, _)| it))
+ .map(ToOwned::to_owned)
+ .map(Into::into);
+ Some((imports.into_boxed_slice(), snippet, description))
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs
new file mode 100644
index 000000000..cf826648d
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs
@@ -0,0 +1,305 @@
+//! Tests and test utilities for completions.
+//!
+//! Most tests live in this module or its submodules. The tests in these submodules are "location"
+//! oriented, that is they try to check completions for something like type position, param position
+//! etc.
+//! Tests that are more orientated towards specific completion types like visibility checks of path
+//! completions or `check_edit` tests usually live in their respective completion modules instead.
+//! This gives this test module and its submodules here the main purpose of giving the developer an
+//! overview of whats being completed where, not how.
+
+mod attribute;
+mod expression;
+mod flyimport;
+mod fn_param;
+mod item_list;
+mod item;
+mod pattern;
+mod predicate;
+mod proc_macros;
+mod record;
+mod special;
+mod type_pos;
+mod use_tree;
+mod visibility;
+
+use hir::{db::DefDatabase, PrefixKind, Semantics};
+use ide_db::{
+ base_db::{fixture::ChangeFixture, FileLoader, FilePosition},
+ imports::insert_use::{ImportGranularity, InsertUseConfig},
+ RootDatabase, SnippetCap,
+};
+use itertools::Itertools;
+use stdx::{format_to, trim_indent};
+use syntax::{AstNode, NodeOrToken, SyntaxElement};
+use test_utils::assert_eq_text;
+
+use crate::{
+ resolve_completion_edits, CallableSnippets, CompletionConfig, CompletionItem,
+ CompletionItemKind,
+};
+
+/// Lots of basic item definitions
+const BASE_ITEMS_FIXTURE: &str = r#"
+enum Enum { TupleV(u32), RecordV { field: u32 }, UnitV }
+use self::Enum::TupleV;
+mod module {}
+
+trait Trait {}
+static STATIC: Unit = Unit;
+const CONST: Unit = Unit;
+struct Record { field: u32 }
+struct Tuple(u32);
+struct Unit;
+#[macro_export]
+macro_rules! makro {}
+#[rustc_builtin_macro]
+pub macro Clone {}
+fn function() {}
+union Union { field: i32 }
+"#;
+
+pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
+ enable_postfix_completions: true,
+ enable_imports_on_the_fly: true,
+ enable_self_on_the_fly: true,
+ enable_private_editable: false,
+ callable: Some(CallableSnippets::FillArguments),
+ snippet_cap: SnippetCap::new(true),
+ insert_use: InsertUseConfig {
+ granularity: ImportGranularity::Crate,
+ prefix_kind: PrefixKind::Plain,
+ enforce_granularity: true,
+ group: true,
+ skip_glob_imports: true,
+ },
+ snippets: Vec::new(),
+};
+
+pub(crate) fn completion_list(ra_fixture: &str) -> String {
+ completion_list_with_config(TEST_CONFIG, ra_fixture, true, None)
+}
+
+pub(crate) fn completion_list_no_kw(ra_fixture: &str) -> String {
+ completion_list_with_config(TEST_CONFIG, ra_fixture, false, None)
+}
+
+pub(crate) fn completion_list_no_kw_with_private_editable(ra_fixture: &str) -> String {
+ let mut config = TEST_CONFIG.clone();
+ config.enable_private_editable = true;
+ completion_list_with_config(config, ra_fixture, false, None)
+}
+
+pub(crate) fn completion_list_with_trigger_character(
+ ra_fixture: &str,
+ trigger_character: Option<char>,
+) -> String {
+ completion_list_with_config(TEST_CONFIG, ra_fixture, true, trigger_character)
+}
+
+fn completion_list_with_config(
+ config: CompletionConfig,
+ ra_fixture: &str,
+ include_keywords: bool,
+ trigger_character: Option<char>,
+) -> String {
+ // filter out all but one builtintype completion for smaller test outputs
+ let items = get_all_items(config, ra_fixture, trigger_character);
+ let items = items
+ .into_iter()
+ .filter(|it| it.kind() != CompletionItemKind::BuiltinType || it.label() == "u32")
+ .filter(|it| include_keywords || it.kind() != CompletionItemKind::Keyword)
+ .filter(|it| include_keywords || it.kind() != CompletionItemKind::Snippet)
+ .sorted_by_key(|it| (it.kind(), it.label().to_owned(), it.detail().map(ToOwned::to_owned)))
+ .collect();
+ render_completion_list(items)
+}
+
+/// Creates analysis from a multi-file fixture, returns positions marked with $0.
+pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
+ let change_fixture = ChangeFixture::parse(ra_fixture);
+ let mut database = RootDatabase::default();
+ database.set_enable_proc_attr_macros(true);
+ database.apply_change(change_fixture.change);
+ let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
+ let offset = range_or_offset.expect_offset();
+ (database, FilePosition { file_id, offset })
+}
+
+pub(crate) fn do_completion(code: &str, kind: CompletionItemKind) -> Vec<CompletionItem> {
+ do_completion_with_config(TEST_CONFIG, code, kind)
+}
+
+pub(crate) fn do_completion_with_config(
+ config: CompletionConfig,
+ code: &str,
+ kind: CompletionItemKind,
+) -> Vec<CompletionItem> {
+ get_all_items(config, code, None)
+ .into_iter()
+ .filter(|c| c.kind() == kind)
+ .sorted_by(|l, r| l.label().cmp(r.label()))
+ .collect()
+}
+
+fn render_completion_list(completions: Vec<CompletionItem>) -> String {
+ fn monospace_width(s: &str) -> usize {
+ s.chars().count()
+ }
+ let label_width =
+ completions.iter().map(|it| monospace_width(it.label())).max().unwrap_or_default().min(22);
+ completions
+ .into_iter()
+ .map(|it| {
+ let tag = it.kind().tag();
+ let var_name = format!("{} {}", tag, it.label());
+ let mut buf = var_name;
+ if let Some(detail) = it.detail() {
+ let width = label_width.saturating_sub(monospace_width(it.label()));
+ format_to!(buf, "{:width$} {}", "", detail, width = width);
+ }
+ if it.deprecated() {
+ format_to!(buf, " DEPRECATED");
+ }
+ format_to!(buf, "\n");
+ buf
+ })
+ .collect()
+}
+
+#[track_caller]
+pub(crate) fn check_edit(what: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
+ check_edit_with_config(TEST_CONFIG, what, ra_fixture_before, ra_fixture_after)
+}
+
+#[track_caller]
+pub(crate) fn check_edit_with_config(
+ config: CompletionConfig,
+ what: &str,
+ ra_fixture_before: &str,
+ ra_fixture_after: &str,
+) {
+ let ra_fixture_after = trim_indent(ra_fixture_after);
+ let (db, position) = position(ra_fixture_before);
+ let completions: Vec<CompletionItem> =
+ crate::completions(&db, &config, position, None).unwrap().into();
+ let (completion,) = completions
+ .iter()
+ .filter(|it| it.lookup() == what)
+ .collect_tuple()
+ .unwrap_or_else(|| panic!("can't find {:?} completion in {:#?}", what, completions));
+ let mut actual = db.file_text(position.file_id).to_string();
+
+ let mut combined_edit = completion.text_edit().to_owned();
+
+ resolve_completion_edits(
+ &db,
+ &config,
+ position,
+ completion.imports_to_add().iter().filter_map(|import_edit| {
+ let import_path = &import_edit.import_path;
+ let import_name = import_path.segments().last()?;
+ Some((import_path.to_string(), import_name.to_string()))
+ }),
+ )
+ .into_iter()
+ .flatten()
+ .for_each(|text_edit| {
+ combined_edit.union(text_edit).expect(
+ "Failed to apply completion resolve changes: change ranges overlap, but should not",
+ )
+ });
+
+ combined_edit.apply(&mut actual);
+ assert_eq_text!(&ra_fixture_after, &actual)
+}
+
+pub(crate) fn check_pattern_is_applicable(code: &str, check: impl FnOnce(SyntaxElement) -> bool) {
+ let (db, pos) = position(code);
+
+ let sema = Semantics::new(&db);
+ let original_file = sema.parse(pos.file_id);
+ let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap();
+ assert!(check(NodeOrToken::Token(token)));
+}
+
+pub(crate) fn get_all_items(
+ config: CompletionConfig,
+ code: &str,
+ trigger_character: Option<char>,
+) -> Vec<CompletionItem> {
+ let (db, position) = position(code);
+ let res = crate::completions(&db, &config, position, trigger_character)
+ .map_or_else(Vec::default, Into::into);
+ // validate
+ res.iter().for_each(|it| {
+ let sr = it.source_range();
+ assert!(
+ sr.contains_inclusive(position.offset),
+ "source range {sr:?} does not contain the offset {:?} of the completion request: {it:?}",
+ position.offset
+ );
+ });
+ res
+}
+
+#[test]
+fn test_no_completions_required() {
+ assert_eq!(completion_list(r#"fn foo() { for i i$0 }"#), String::new());
+}
+
+#[test]
+fn regression_10042() {
+ completion_list(
+ r#"
+macro_rules! preset {
+ ($($x:ident)&&*) => {
+ {
+ let mut v = Vec::new();
+ $(
+ v.push($x.into());
+ )*
+ v
+ }
+ };
+}
+
+fn foo() {
+ preset!(foo$0);
+}
+"#,
+ );
+}
+
+#[test]
+fn no_completions_in_comments() {
+ assert_eq!(
+ completion_list(
+ r#"
+fn test() {
+let x = 2; // A comment$0
+}
+"#,
+ ),
+ String::new(),
+ );
+ assert_eq!(
+ completion_list(
+ r#"
+/*
+Some multi-line comment$0
+*/
+"#,
+ ),
+ String::new(),
+ );
+ assert_eq!(
+ completion_list(
+ r#"
+/// Some doc comment
+/// let test$0 = 1
+"#,
+ ),
+ String::new(),
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs
new file mode 100644
index 000000000..1578ba2c3
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs
@@ -0,0 +1,1016 @@
+//! Completion tests for attributes.
+use expect_test::{expect, Expect};
+
+use crate::tests::{check_edit, completion_list};
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual);
+}
+
+#[test]
+fn proc_macros() {
+ check(
+ r#"
+//- proc_macros: identity
+#[$0]
+struct Foo;
+"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at derive(…)
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at must_use
+ at no_mangle
+ at non_exhaustive
+ at repr(…)
+ at warn(…)
+ md proc_macros
+ kw crate::
+ kw self::
+ "#]],
+ )
+}
+
+#[test]
+fn proc_macros_on_comment() {
+ check(
+ r#"
+//- proc_macros: identity
+/// $0
+#[proc_macros::identity]
+struct Foo;
+"#,
+ expect![[r#""#]],
+ )
+}
+
+#[test]
+fn proc_macros_qualified() {
+ check(
+ r#"
+//- proc_macros: identity
+#[proc_macros::$0]
+struct Foo;
+"#,
+ expect![[r#"
+ at identity proc_macro identity
+ "#]],
+ )
+}
+
+#[test]
+fn inside_nested_attr() {
+ check(r#"#[cfg($0)]"#, expect![[]])
+}
+
+#[test]
+fn with_existing_attr() {
+ check(
+ r#"#[no_mangle] #[$0] mcall!();"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at forbid(…)
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ )
+}
+
+#[test]
+fn attr_on_source_file() {
+ check(
+ r#"#![$0]"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at crate_name = ""
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at feature(…)
+ at forbid(…)
+ at must_use
+ at no_implicit_prelude
+ at no_main
+ at no_mangle
+ at no_std
+ at recursion_limit = "…"
+ at type_length_limit = …
+ at warn(…)
+ at windows_subsystem = "…"
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_module() {
+ check(
+ r#"#[$0] mod foo;"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at macro_use
+ at must_use
+ at no_mangle
+ at path = "…"
+ at warn(…)
+ kw crate::
+ kw self::
+ kw super::
+ "#]],
+ );
+ check(
+ r#"mod foo {#![$0]}"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at must_use
+ at no_implicit_prelude
+ at no_mangle
+ at warn(…)
+ kw crate::
+ kw self::
+ kw super::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_macro_rules() {
+ check(
+ r#"#[$0] macro_rules! foo {}"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at macro_export
+ at macro_use
+ at must_use
+ at no_mangle
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_macro_def() {
+ check(
+ r#"#[$0] macro foo {}"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at must_use
+ at no_mangle
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_extern_crate() {
+ check(
+ r#"#[$0] extern crate foo;"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at macro_use
+ at must_use
+ at no_mangle
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_use() {
+ check(
+ r#"#[$0] use foo;"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at must_use
+ at no_mangle
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_type_alias() {
+ check(
+ r#"#[$0] type foo = ();"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at must_use
+ at no_mangle
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_struct() {
+ check(
+ r#"
+//- minicore:derive
+#[$0]
+struct Foo;
+"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at derive macro derive
+ at derive(…)
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at must_use
+ at no_mangle
+ at non_exhaustive
+ at repr(…)
+ at warn(…)
+ md core
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_enum() {
+ check(
+ r#"#[$0] enum Foo {}"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at derive(…)
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at must_use
+ at no_mangle
+ at non_exhaustive
+ at repr(…)
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_const() {
+ check(
+ r#"#[$0] const FOO: () = ();"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at must_use
+ at no_mangle
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_static() {
+ check(
+ r#"#[$0] static FOO: () = ()"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at export_name = "…"
+ at forbid(…)
+ at global_allocator
+ at link_name = "…"
+ at link_section = "…"
+ at must_use
+ at no_mangle
+ at used
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_trait() {
+ check(
+ r#"#[$0] trait Foo {}"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at must_use
+ at must_use
+ at no_mangle
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_impl() {
+ check(
+ r#"#[$0] impl () {}"#,
+ expect![[r#"
+ at allow(…)
+ at automatically_derived
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at must_use
+ at no_mangle
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+ check(
+ r#"impl () {#![$0]}"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at must_use
+ at no_mangle
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_extern_block() {
+ check(
+ r#"#[$0] extern {}"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at link
+ at must_use
+ at no_mangle
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+ check(
+ r#"extern {#![$0]}"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at link
+ at must_use
+ at no_mangle
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_variant() {
+ check(
+ r#"enum Foo { #[$0] Bar }"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at forbid(…)
+ at non_exhaustive
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_fn() {
+ check(
+ r#"#[$0] fn main() {}"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at cold
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at export_name = "…"
+ at forbid(…)
+ at ignore = "…"
+ at inline
+ at link_name = "…"
+ at link_section = "…"
+ at must_use
+ at must_use
+ at no_mangle
+ at panic_handler
+ at proc_macro
+ at proc_macro_attribute
+ at proc_macro_derive(…)
+ at should_panic
+ at target_feature(enable = "…")
+ at test
+ at track_caller
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_in_source_file_end() {
+ check(
+ r#"#[$0]"#,
+ expect![[r#"
+ at allow(…)
+ at automatically_derived
+ at cfg(…)
+ at cfg_attr(…)
+ at cold
+ at deny(…)
+ at deprecated
+ at derive(…)
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at export_name = "…"
+ at forbid(…)
+ at global_allocator
+ at ignore = "…"
+ at inline
+ at link
+ at link_name = "…"
+ at link_section = "…"
+ at macro_export
+ at macro_use
+ at must_use
+ at no_mangle
+ at non_exhaustive
+ at panic_handler
+ at path = "…"
+ at proc_macro
+ at proc_macro_attribute
+ at proc_macro_derive(…)
+ at repr(…)
+ at should_panic
+ at target_feature(enable = "…")
+ at test
+ at track_caller
+ at used
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+mod cfg {
+ use super::*;
+
+ #[test]
+ fn cfg_target_endian() {
+ check(
+ r#"#[cfg(target_endian = $0"#,
+ expect![[r#"
+ ba big
+ ba little
+ "#]],
+ );
+ }
+}
+
+mod derive {
+ use super::*;
+
+ fn check_derive(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual);
+ }
+
+ #[test]
+ fn no_completion_for_incorrect_derive() {
+ check_derive(
+ r#"
+//- minicore: derive, copy, clone, ord, eq, default, fmt
+#[derive{$0)] struct Test;
+"#,
+ expect![[]],
+ )
+ }
+
+ #[test]
+ fn empty_derive() {
+ check_derive(
+ r#"
+//- minicore: derive, copy, clone, ord, eq, default, fmt
+#[derive($0)] struct Test;
+"#,
+ expect![[r#"
+ de Clone macro Clone
+ de Clone, Copy
+ de Default macro Default
+ de PartialEq macro PartialEq
+ de PartialEq, Eq
+ de PartialEq, Eq, PartialOrd, Ord
+ de PartialEq, PartialOrd
+ md core
+ kw crate::
+ kw self::
+ "#]],
+ );
+ }
+
+ #[test]
+ fn derive_with_input_before() {
+ check_derive(
+ r#"
+//- minicore: derive, copy, clone, ord, eq, default, fmt
+#[derive(serde::Serialize, PartialEq, $0)] struct Test;
+"#,
+ expect![[r#"
+ de Clone macro Clone
+ de Clone, Copy
+ de Default macro Default
+ de Eq
+ de Eq, PartialOrd, Ord
+ de PartialOrd
+ md core
+ kw crate::
+ kw self::
+ "#]],
+ )
+ }
+
+ #[test]
+ fn derive_with_input_after() {
+ check_derive(
+ r#"
+//- minicore: derive, copy, clone, ord, eq, default, fmt
+#[derive($0 serde::Serialize, PartialEq)] struct Test;
+"#,
+ expect![[r#"
+ de Clone macro Clone
+ de Clone, Copy
+ de Default macro Default
+ de Eq
+ de Eq, PartialOrd, Ord
+ de PartialOrd
+ md core
+ kw crate::
+ kw self::
+ "#]],
+ );
+ }
+
+ #[test]
+ fn derive_with_existing_derives() {
+ check_derive(
+ r#"
+//- minicore: derive, copy, clone, ord, eq, default, fmt
+#[derive(PartialEq, Eq, Or$0)] struct Test;
+"#,
+ expect![[r#"
+ de Clone macro Clone
+ de Clone, Copy
+ de Default macro Default
+ de PartialOrd
+ de PartialOrd, Ord
+ md core
+ kw crate::
+ kw self::
+ "#]],
+ );
+ }
+
+ #[test]
+ fn derive_flyimport() {
+ check_derive(
+ r#"
+//- proc_macros: derive_identity
+//- minicore: derive
+#[derive(der$0)] struct Test;
+"#,
+ expect![[r#"
+ de DeriveIdentity (use proc_macros::DeriveIdentity) proc_macro DeriveIdentity
+ md core
+ md proc_macros
+ kw crate::
+ kw self::
+ "#]],
+ );
+ check_derive(
+ r#"
+//- proc_macros: derive_identity
+//- minicore: derive
+use proc_macros::DeriveIdentity;
+#[derive(der$0)] struct Test;
+"#,
+ expect![[r#"
+ de DeriveIdentity proc_macro DeriveIdentity
+ md core
+ md proc_macros
+ kw crate::
+ kw self::
+ "#]],
+ );
+ }
+
+ #[test]
+ fn derive_flyimport_edit() {
+ check_edit(
+ "DeriveIdentity",
+ r#"
+//- proc_macros: derive_identity
+//- minicore: derive
+#[derive(der$0)] struct Test;
+"#,
+ r#"
+use proc_macros::DeriveIdentity;
+
+#[derive(DeriveIdentity)] struct Test;
+"#,
+ );
+ }
+
+ #[test]
+ fn qualified() {
+ check_derive(
+ r#"
+//- proc_macros: derive_identity
+//- minicore: derive, copy, clone
+#[derive(proc_macros::$0)] struct Test;
+"#,
+ expect![[r#"
+ de DeriveIdentity proc_macro DeriveIdentity
+ "#]],
+ );
+ check_derive(
+ r#"
+//- proc_macros: derive_identity
+//- minicore: derive, copy, clone
+#[derive(proc_macros::C$0)] struct Test;
+"#,
+ expect![[r#"
+ de DeriveIdentity proc_macro DeriveIdentity
+ "#]],
+ );
+ }
+}
+
+mod lint {
+ use super::*;
+
+ #[test]
+ fn lint_empty() {
+ check_edit(
+ "deprecated",
+ r#"#[allow($0)] struct Test;"#,
+ r#"#[allow(deprecated)] struct Test;"#,
+ )
+ }
+
+ #[test]
+ fn lint_with_existing() {
+ check_edit(
+ "deprecated",
+ r#"#[allow(keyword_idents, $0)] struct Test;"#,
+ r#"#[allow(keyword_idents, deprecated)] struct Test;"#,
+ )
+ }
+
+ #[test]
+ fn lint_qualified() {
+ check_edit(
+ "deprecated",
+ r#"#[allow(keyword_idents, $0)] struct Test;"#,
+ r#"#[allow(keyword_idents, deprecated)] struct Test;"#,
+ )
+ }
+
+ #[test]
+ fn lint_feature() {
+ check_edit(
+ "box_syntax",
+ r#"#[feature(box_$0)] struct Test;"#,
+ r#"#[feature(box_syntax)] struct Test;"#,
+ )
+ }
+
+ #[test]
+ fn lint_clippy_unqualified() {
+ check_edit(
+ "clippy::as_conversions",
+ r#"#[allow($0)] struct Test;"#,
+ r#"#[allow(clippy::as_conversions)] struct Test;"#,
+ );
+ }
+
+ #[test]
+ fn lint_clippy_qualified() {
+ check_edit(
+ "as_conversions",
+ r#"#[allow(clippy::$0)] struct Test;"#,
+ r#"#[allow(clippy::as_conversions)] struct Test;"#,
+ );
+ }
+
+ #[test]
+ fn lint_rustdoc_unqualified() {
+ check_edit(
+ "rustdoc::bare_urls",
+ r#"#[allow($0)] struct Test;"#,
+ r#"#[allow(rustdoc::bare_urls)] struct Test;"#,
+ );
+ }
+
+ #[test]
+ fn lint_rustdoc_qualified() {
+ check_edit(
+ "bare_urls",
+ r#"#[allow(rustdoc::$0)] struct Test;"#,
+ r#"#[allow(rustdoc::bare_urls)] struct Test;"#,
+ );
+ }
+
+ #[test]
+ fn lint_unclosed() {
+ check_edit(
+ "deprecated",
+ r#"#[allow(dep$0 struct Test;"#,
+ r#"#[allow(deprecated struct Test;"#,
+ );
+ check_edit(
+ "bare_urls",
+ r#"#[allow(rustdoc::$0 struct Test;"#,
+ r#"#[allow(rustdoc::bare_urls struct Test;"#,
+ );
+ }
+}
+
+mod repr {
+ use super::*;
+
+ fn check_repr(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual);
+ }
+
+ #[test]
+ fn no_completion_for_incorrect_repr() {
+ check_repr(r#"#[repr{$0)] struct Test;"#, expect![[]])
+ }
+
+ #[test]
+ fn empty() {
+ check_repr(
+ r#"#[repr($0)] struct Test;"#,
+ expect![[r#"
+ ba C
+ ba align($0)
+ ba i16
+ ba i28
+ ba i32
+ ba i64
+ ba i8
+ ba isize
+ ba packed
+ ba transparent
+ ba u128
+ ba u16
+ ba u32
+ ba u64
+ ba u8
+ ba usize
+ "#]],
+ );
+ }
+
+ #[test]
+ fn transparent() {
+ check_repr(r#"#[repr(transparent, $0)] struct Test;"#, expect![[r#""#]]);
+ }
+
+ #[test]
+ fn align() {
+ check_repr(
+ r#"#[repr(align(1), $0)] struct Test;"#,
+ expect![[r#"
+ ba C
+ ba i16
+ ba i28
+ ba i32
+ ba i64
+ ba i8
+ ba isize
+ ba transparent
+ ba u128
+ ba u16
+ ba u32
+ ba u64
+ ba u8
+ ba usize
+ "#]],
+ );
+ }
+
+ #[test]
+ fn packed() {
+ check_repr(
+ r#"#[repr(packed, $0)] struct Test;"#,
+ expect![[r#"
+ ba C
+ ba i16
+ ba i28
+ ba i32
+ ba i64
+ ba i8
+ ba isize
+ ba transparent
+ ba u128
+ ba u16
+ ba u32
+ ba u64
+ ba u8
+ ba usize
+ "#]],
+ );
+ }
+
+ #[test]
+ fn c() {
+ check_repr(
+ r#"#[repr(C, $0)] struct Test;"#,
+ expect![[r#"
+ ba align($0)
+ ba i16
+ ba i28
+ ba i32
+ ba i64
+ ba i8
+ ba isize
+ ba packed
+ ba u128
+ ba u16
+ ba u32
+ ba u64
+ ba u8
+ ba usize
+ "#]],
+ );
+ }
+
+ #[test]
+ fn prim() {
+ check_repr(
+ r#"#[repr(usize, $0)] struct Test;"#,
+ expect![[r#"
+ ba C
+ ba align($0)
+ ba packed
+ "#]],
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs
new file mode 100644
index 000000000..925081ebf
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs
@@ -0,0 +1,672 @@
+//! Completion tests for expressions.
+use expect_test::{expect, Expect};
+
+use crate::tests::{completion_list, BASE_ITEMS_FIXTURE};
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(&format!("{}{}", BASE_ITEMS_FIXTURE, ra_fixture));
+ expect.assert_eq(&actual)
+}
+
+fn check_empty(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual);
+}
+
+#[test]
+fn complete_literal_struct_with_a_private_field() {
+ // `FooDesc.bar` is private, the completion should not be triggered.
+ check(
+ r#"
+mod _69latrick {
+ pub struct FooDesc { pub six: bool, pub neuf: Vec<String>, bar: bool }
+ pub fn create_foo(foo_desc: &FooDesc) -> () { () }
+}
+
+fn baz() {
+ use _69latrick::*;
+
+ let foo = create_foo(&$0);
+}
+ "#,
+ // This should not contain `FooDesc {…}`.
+ expect![[r#"
+ ct CONST
+ en Enum
+ fn baz() fn()
+ fn create_foo(…) fn(&FooDesc)
+ fn function() fn()
+ ma makro!(…) macro_rules! makro
+ md _69latrick
+ md module
+ sc STATIC
+ st FooDesc
+ st Record
+ st Tuple
+ st Unit
+ un Union
+ ev TupleV(…) TupleV(u32)
+ bt u32
+ kw crate::
+ kw false
+ kw for
+ kw if
+ kw if let
+ kw loop
+ kw match
+ kw mut
+ kw return
+ kw self::
+ kw true
+ kw unsafe
+ kw while
+ kw while let
+ "#]],
+ )
+}
+
+#[test]
+fn completes_various_bindings() {
+ check_empty(
+ r#"
+fn func(param0 @ (param1, param2): (i32, i32)) {
+ let letlocal = 92;
+ if let ifletlocal = 100 {
+ match 0 {
+ matcharm => 1 + $0,
+ otherwise => (),
+ }
+ }
+ let letlocal2 = 44;
+}
+"#,
+ expect![[r#"
+ fn func(…) fn((i32, i32))
+ lc ifletlocal i32
+ lc letlocal i32
+ lc matcharm i32
+ lc param0 (i32, i32)
+ lc param1 i32
+ lc param2 i32
+ bt u32
+ kw crate::
+ kw false
+ kw for
+ kw if
+ kw if let
+ kw loop
+ kw match
+ kw return
+ kw self::
+ kw true
+ kw unsafe
+ kw while
+ kw while let
+ "#]],
+ );
+}
+
+#[test]
+fn completes_all_the_things_in_fn_body() {
+ check(
+ r#"
+use non_existant::Unresolved;
+mod qualified { pub enum Enum { Variant } }
+
+impl Unit {
+ fn foo<'lifetime, TypeParam, const CONST_PARAM: usize>(self) {
+ fn local_func() {}
+ $0
+ }
+}
+"#,
+ // `self` is in here twice, once as the module, once as the local
+ expect![[r#"
+ ct CONST
+ cp CONST_PARAM
+ en Enum
+ fn function() fn()
+ fn local_func() fn()
+ lc self Unit
+ ma makro!(…) macro_rules! makro
+ md module
+ md qualified
+ sp Self
+ sc STATIC
+ st Record
+ st Tuple
+ st Unit
+ tp TypeParam
+ un Union
+ ev TupleV(…) TupleV(u32)
+ bt u32
+ kw const
+ kw crate::
+ kw enum
+ kw extern
+ kw false
+ kw fn
+ kw for
+ kw if
+ kw if let
+ kw impl
+ kw let
+ kw loop
+ kw match
+ kw mod
+ kw return
+ kw self::
+ kw static
+ kw struct
+ kw trait
+ kw true
+ kw type
+ kw union
+ kw unsafe
+ kw use
+ kw while
+ kw while let
+ me self.foo() fn(self)
+ sn macro_rules
+ sn pd
+ sn ppd
+ ?? Unresolved
+ "#]],
+ );
+ check(
+ r#"
+use non_existant::Unresolved;
+mod qualified { pub enum Enum { Variant } }
+
+impl Unit {
+ fn foo<'lifetime, TypeParam, const CONST_PARAM: usize>(self) {
+ fn local_func() {}
+ self::$0
+ }
+}
+"#,
+ expect![[r#"
+ ct CONST
+ en Enum
+ fn function() fn()
+ ma makro!(…) macro_rules! makro
+ md module
+ md qualified
+ sc STATIC
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ ev TupleV(…) TupleV(u32)
+ ?? Unresolved
+ "#]],
+ );
+}
+
+#[test]
+fn complete_in_block() {
+ check_empty(
+ r#"
+ fn foo() {
+ if true {
+ $0
+ }
+ }
+"#,
+ expect![[r#"
+ fn foo() fn()
+ bt u32
+ kw const
+ kw crate::
+ kw enum
+ kw extern
+ kw false
+ kw fn
+ kw for
+ kw if
+ kw if let
+ kw impl
+ kw let
+ kw loop
+ kw match
+ kw mod
+ kw return
+ kw self::
+ kw static
+ kw struct
+ kw trait
+ kw true
+ kw type
+ kw union
+ kw unsafe
+ kw use
+ kw while
+ kw while let
+ sn macro_rules
+ sn pd
+ sn ppd
+ "#]],
+ )
+}
+
+#[test]
+fn complete_after_if_expr() {
+ check_empty(
+ r#"
+ fn foo() {
+ if true {}
+ $0
+ }
+"#,
+ expect![[r#"
+ fn foo() fn()
+ bt u32
+ kw const
+ kw crate::
+ kw else
+ kw else if
+ kw enum
+ kw extern
+ kw false
+ kw fn
+ kw for
+ kw if
+ kw if let
+ kw impl
+ kw let
+ kw loop
+ kw match
+ kw mod
+ kw return
+ kw self::
+ kw static
+ kw struct
+ kw trait
+ kw true
+ kw type
+ kw union
+ kw unsafe
+ kw use
+ kw while
+ kw while let
+ sn macro_rules
+ sn pd
+ sn ppd
+ "#]],
+ )
+}
+
+#[test]
+fn complete_in_match_arm() {
+ check_empty(
+ r#"
+ fn foo() {
+ match () {
+ () => $0
+ }
+ }
+"#,
+ expect![[r#"
+ fn foo() fn()
+ bt u32
+ kw crate::
+ kw false
+ kw for
+ kw if
+ kw if let
+ kw loop
+ kw match
+ kw return
+ kw self::
+ kw true
+ kw unsafe
+ kw while
+ kw while let
+ "#]],
+ )
+}
+
+#[test]
+fn completes_in_loop_ctx() {
+ check_empty(
+ r"fn my() { loop { $0 } }",
+ expect![[r#"
+ fn my() fn()
+ bt u32
+ kw break
+ kw const
+ kw continue
+ kw crate::
+ kw enum
+ kw extern
+ kw false
+ kw fn
+ kw for
+ kw if
+ kw if let
+ kw impl
+ kw let
+ kw loop
+ kw match
+ kw mod
+ kw return
+ kw self::
+ kw static
+ kw struct
+ kw trait
+ kw true
+ kw type
+ kw union
+ kw unsafe
+ kw use
+ kw while
+ kw while let
+ sn macro_rules
+ sn pd
+ sn ppd
+ "#]],
+ );
+}
+
+#[test]
+fn completes_in_let_initializer() {
+ check_empty(
+ r#"fn main() { let _ = $0 }"#,
+ expect![[r#"
+ fn main() fn()
+ bt u32
+ kw crate::
+ kw false
+ kw for
+ kw if
+ kw if let
+ kw loop
+ kw match
+ kw return
+ kw self::
+ kw true
+ kw unsafe
+ kw while
+ kw while let
+ "#]],
+ )
+}
+
+#[test]
+fn struct_initializer_field_expr() {
+ check_empty(
+ r#"
+struct Foo {
+ pub f: i32,
+}
+fn foo() {
+ Foo {
+ f: $0
+ }
+}
+"#,
+ expect![[r#"
+ fn foo() fn()
+ st Foo
+ bt u32
+ kw crate::
+ kw false
+ kw for
+ kw if
+ kw if let
+ kw loop
+ kw match
+ kw return
+ kw self::
+ kw true
+ kw unsafe
+ kw while
+ kw while let
+ "#]],
+ );
+}
+
+#[test]
+fn shadowing_shows_single_completion() {
+ cov_mark::check!(shadowing_shows_single_completion);
+
+ check_empty(
+ r#"
+fn foo() {
+ let bar = 92;
+ {
+ let bar = 62;
+ drop($0)
+ }
+}
+"#,
+ expect![[r#"
+ fn foo() fn()
+ lc bar i32
+ bt u32
+ kw crate::
+ kw false
+ kw for
+ kw if
+ kw if let
+ kw loop
+ kw match
+ kw return
+ kw self::
+ kw true
+ kw unsafe
+ kw while
+ kw while let
+ "#]],
+ );
+}
+
+#[test]
+fn in_macro_expr_frag() {
+ check_empty(
+ r#"
+macro_rules! m { ($e:expr) => { $e } }
+fn quux(x: i32) {
+ m!($0);
+}
+"#,
+ expect![[r#"
+ fn quux(…) fn(i32)
+ lc x i32
+ ma m!(…) macro_rules! m
+ bt u32
+ kw crate::
+ kw false
+ kw for
+ kw if
+ kw if let
+ kw loop
+ kw match
+ kw return
+ kw self::
+ kw true
+ kw unsafe
+ kw while
+ kw while let
+ "#]],
+ );
+ check_empty(
+ r"
+macro_rules! m { ($e:expr) => { $e } }
+fn quux(x: i32) {
+ m!(x$0);
+}
+",
+ expect![[r#"
+ fn quux(…) fn(i32)
+ lc x i32
+ ma m!(…) macro_rules! m
+ bt u32
+ kw crate::
+ kw false
+ kw for
+ kw if
+ kw if let
+ kw loop
+ kw match
+ kw return
+ kw self::
+ kw true
+ kw unsafe
+ kw while
+ kw while let
+ "#]],
+ );
+ check_empty(
+ r#"
+macro_rules! m { ($e:expr) => { $e } }
+fn quux(x: i32) {
+ let y = 92;
+ m!(x$0
+}
+"#,
+ expect![[r#""#]],
+ );
+}
+
+#[test]
+fn enum_qualified() {
+ check(
+ r#"
+impl Enum {
+ type AssocType = ();
+ const ASSOC_CONST: () = ();
+ fn assoc_fn() {}
+}
+fn func() {
+ Enum::$0
+}
+"#,
+ expect![[r#"
+ ct ASSOC_CONST const ASSOC_CONST: ()
+ fn assoc_fn() fn()
+ ta AssocType type AssocType = ()
+ ev RecordV {…} RecordV { field: u32 }
+ ev TupleV(…) TupleV(u32)
+ ev UnitV UnitV
+ "#]],
+ );
+}
+
+#[test]
+fn ty_qualified_no_drop() {
+ check_empty(
+ r#"
+//- minicore: drop
+struct Foo;
+impl Drop for Foo {
+ fn drop(&mut self) {}
+}
+fn func() {
+ Foo::$0
+}
+"#,
+ expect![[r#""#]],
+ );
+}
+
+#[test]
+fn with_parens() {
+ check_empty(
+ r#"
+enum Enum {
+ Variant()
+}
+impl Enum {
+ fn variant() -> Self { Enum::Variant() }
+}
+fn func() {
+ Enum::$0()
+}
+"#,
+ expect![[r#"
+ fn variant fn() -> Enum
+ ev Variant Variant
+ "#]],
+ );
+}
+
+#[test]
+fn detail_impl_trait_in_return_position() {
+ check_empty(
+ r"
+//- minicore: sized
+trait Trait<T> {}
+fn foo<U>() -> impl Trait<U> {}
+fn main() {
+ self::$0
+}
+",
+ expect![[r#"
+ fn foo() fn() -> impl Trait<U>
+ fn main() fn()
+ tt Trait
+ "#]],
+ );
+}
+
+#[test]
+fn detail_async_fn() {
+ check_empty(
+ r#"
+//- minicore: future, sized
+trait Trait<T> {}
+async fn foo() -> u8 {}
+async fn bar<U>() -> impl Trait<U> {}
+fn main() {
+ self::$0
+}
+"#,
+ expect![[r#"
+ fn bar() async fn() -> impl Trait<U>
+ fn foo() async fn() -> u8
+ fn main() fn()
+ tt Trait
+ "#]],
+ );
+}
+
+#[test]
+fn detail_impl_trait_in_argument_position() {
+ check_empty(
+ r"
+//- minicore: sized
+trait Trait<T> {}
+struct Foo;
+impl Foo {
+ fn bar<U>(_: impl Trait<U>) {}
+}
+fn main() {
+ Foo::$0
+}
+",
+ expect![[r"
+ fn bar(…) fn(impl Trait<U>)
+ "]],
+ );
+}
+
+#[test]
+fn complete_record_expr_path() {
+ check(
+ r#"
+struct Zulu;
+impl Zulu {
+ fn test() -> Self { }
+}
+fn boi(val: Zulu) { }
+fn main() {
+ boi(Zulu:: $0 {});
+}
+"#,
+ expect![[r#"
+ fn test() fn() -> Zulu
+ "#]],
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs
new file mode 100644
index 000000000..0bba7f245
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs
@@ -0,0 +1,1232 @@
+use expect_test::{expect, Expect};
+
+use crate::{
+ context::{CompletionAnalysis, NameContext, NameKind, NameRefKind},
+ tests::{check_edit, check_edit_with_config, TEST_CONFIG},
+};
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let config = TEST_CONFIG;
+ let (db, position) = crate::tests::position(ra_fixture);
+ let (ctx, analysis) = crate::context::CompletionContext::new(&db, position, &config).unwrap();
+
+ let mut acc = crate::completions::Completions::default();
+ if let CompletionAnalysis::Name(NameContext { kind: NameKind::IdentPat(pat_ctx), .. }) =
+ &analysis
+ {
+ crate::completions::flyimport::import_on_the_fly_pat(&mut acc, &ctx, pat_ctx);
+ }
+ if let CompletionAnalysis::NameRef(name_ref_ctx) = &analysis {
+ match &name_ref_ctx.kind {
+ NameRefKind::Path(path) => {
+ crate::completions::flyimport::import_on_the_fly_path(&mut acc, &ctx, path);
+ }
+ NameRefKind::DotAccess(dot_access) => {
+ crate::completions::flyimport::import_on_the_fly_dot(&mut acc, &ctx, dot_access);
+ }
+ NameRefKind::Pattern(pattern) => {
+ crate::completions::flyimport::import_on_the_fly_pat(&mut acc, &ctx, pattern);
+ }
+ _ => (),
+ }
+ }
+
+ expect.assert_eq(&super::render_completion_list(Vec::from(acc)));
+}
+
+#[test]
+fn function_fuzzy_completion() {
+ check_edit(
+ "stdin",
+ r#"
+//- /lib.rs crate:dep
+pub mod io {
+ pub fn stdin() {}
+};
+
+//- /main.rs crate:main deps:dep
+fn main() {
+ stdi$0
+}
+"#,
+ r#"
+use dep::io::stdin;
+
+fn main() {
+ stdin()$0
+}
+"#,
+ );
+}
+
+#[test]
+fn macro_fuzzy_completion() {
+ check_edit(
+ "macro_with_curlies!",
+ r#"
+//- /lib.rs crate:dep
+/// Please call me as macro_with_curlies! {}
+#[macro_export]
+macro_rules! macro_with_curlies {
+ () => {}
+}
+
+//- /main.rs crate:main deps:dep
+fn main() {
+ curli$0
+}
+"#,
+ r#"
+use dep::macro_with_curlies;
+
+fn main() {
+ macro_with_curlies! {$0}
+}
+"#,
+ );
+}
+
+#[test]
+fn struct_fuzzy_completion() {
+ check_edit(
+ "ThirdStruct",
+ r#"
+//- /lib.rs crate:dep
+pub struct FirstStruct;
+pub mod some_module {
+ pub struct SecondStruct;
+ pub struct ThirdStruct;
+}
+
+//- /main.rs crate:main deps:dep
+use dep::{FirstStruct, some_module::SecondStruct};
+
+fn main() {
+ this$0
+}
+"#,
+ r#"
+use dep::{FirstStruct, some_module::{SecondStruct, ThirdStruct}};
+
+fn main() {
+ ThirdStruct
+}
+"#,
+ );
+}
+
+#[test]
+fn short_paths_are_ignored() {
+ cov_mark::check!(flyimport_exact_on_short_path);
+
+ check(
+ r#"
+//- /lib.rs crate:dep
+pub struct Bar;
+pub struct Rcar;
+pub struct Rc;
+pub mod some_module {
+ pub struct Bar;
+ pub struct Rcar;
+ pub struct Rc;
+}
+
+//- /main.rs crate:main deps:dep
+fn main() {
+ rc$0
+}
+"#,
+ expect![[r#"
+ st Rc (use dep::Rc)
+ st Rc (use dep::some_module::Rc)
+ "#]],
+ );
+}
+
+#[test]
+fn fuzzy_completions_come_in_specific_order() {
+ cov_mark::check!(certain_fuzzy_order_test);
+ check(
+ r#"
+//- /lib.rs crate:dep
+pub struct FirstStruct;
+pub mod some_module {
+ // already imported, omitted
+ pub struct SecondStruct;
+ // does not contain all letters from the query, omitted
+ pub struct UnrelatedOne;
+ // contains all letters from the query, but not in sequence, displayed last
+ pub struct ThiiiiiirdStruct;
+ // contains all letters from the query, but not in the beginning, displayed second
+ pub struct AfterThirdStruct;
+ // contains all letters from the query in the begginning, displayed first
+ pub struct ThirdStruct;
+}
+
+//- /main.rs crate:main deps:dep
+use dep::{FirstStruct, some_module::SecondStruct};
+
+fn main() {
+ hir$0
+}
+"#,
+ expect![[r#"
+ st ThirdStruct (use dep::some_module::ThirdStruct)
+ st AfterThirdStruct (use dep::some_module::AfterThirdStruct)
+ st ThiiiiiirdStruct (use dep::some_module::ThiiiiiirdStruct)
+ "#]],
+ );
+}
+
+#[test]
+fn trait_function_fuzzy_completion() {
+ let fixture = r#"
+ //- /lib.rs crate:dep
+ pub mod test_mod {
+ pub trait TestTrait {
+ const SPECIAL_CONST: u8;
+ type HumbleType;
+ fn weird_function();
+ fn random_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const SPECIAL_CONST: u8 = 42;
+ type HumbleType = ();
+ fn weird_function() {}
+ fn random_method(&self) {}
+ }
+ }
+
+ //- /main.rs crate:main deps:dep
+ fn main() {
+ dep::test_mod::TestStruct::wei$0
+ }
+ "#;
+
+ check(
+ fixture,
+ expect![[r#"
+ fn weird_function() (use dep::test_mod::TestTrait) fn()
+ "#]],
+ );
+
+ check_edit(
+ "weird_function",
+ fixture,
+ r#"
+use dep::test_mod::TestTrait;
+
+fn main() {
+ dep::test_mod::TestStruct::weird_function()$0
+}
+"#,
+ );
+}
+
+#[test]
+fn trait_const_fuzzy_completion() {
+ let fixture = r#"
+ //- /lib.rs crate:dep
+ pub mod test_mod {
+ pub trait TestTrait {
+ const SPECIAL_CONST: u8;
+ type HumbleType;
+ fn weird_function();
+ fn random_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const SPECIAL_CONST: u8 = 42;
+ type HumbleType = ();
+ fn weird_function() {}
+ fn random_method(&self) {}
+ }
+ }
+
+ //- /main.rs crate:main deps:dep
+ fn main() {
+ dep::test_mod::TestStruct::spe$0
+ }
+ "#;
+
+ check(
+ fixture,
+ expect![[r#"
+ ct SPECIAL_CONST (use dep::test_mod::TestTrait)
+ "#]],
+ );
+
+ check_edit(
+ "SPECIAL_CONST",
+ fixture,
+ r#"
+use dep::test_mod::TestTrait;
+
+fn main() {
+ dep::test_mod::TestStruct::SPECIAL_CONST
+}
+"#,
+ );
+}
+
+#[test]
+fn trait_method_fuzzy_completion() {
+ let fixture = r#"
+ //- /lib.rs crate:dep
+ pub mod test_mod {
+ pub trait TestTrait {
+ const SPECIAL_CONST: u8;
+ type HumbleType;
+ fn weird_function();
+ fn random_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const SPECIAL_CONST: u8 = 42;
+ type HumbleType = ();
+ fn weird_function() {}
+ fn random_method(&self) {}
+ }
+ }
+
+ //- /main.rs crate:main deps:dep
+ fn main() {
+ let test_struct = dep::test_mod::TestStruct {};
+ test_struct.ran$0
+ }
+ "#;
+
+ check(
+ fixture,
+ expect![[r#"
+ me random_method() (use dep::test_mod::TestTrait) fn(&self)
+ "#]],
+ );
+
+ check_edit(
+ "random_method",
+ fixture,
+ r#"
+use dep::test_mod::TestTrait;
+
+fn main() {
+ let test_struct = dep::test_mod::TestStruct {};
+ test_struct.random_method()$0
+}
+"#,
+ );
+}
+
+#[test]
+fn trait_method_from_alias() {
+ let fixture = r#"
+//- /lib.rs crate:dep
+pub mod test_mod {
+ pub trait TestTrait {
+ fn random_method();
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn random_method() {}
+ }
+ pub type TestAlias = TestStruct;
+}
+
+//- /main.rs crate:main deps:dep
+fn main() {
+ dep::test_mod::TestAlias::ran$0
+}
+"#;
+
+ check(
+ fixture,
+ expect![[r#"
+ fn random_method() (use dep::test_mod::TestTrait) fn()
+ "#]],
+ );
+
+ check_edit(
+ "random_method",
+ fixture,
+ r#"
+use dep::test_mod::TestTrait;
+
+fn main() {
+ dep::test_mod::TestAlias::random_method()$0
+}
+"#,
+ );
+}
+
+#[test]
+fn no_trait_type_fuzzy_completion() {
+ check(
+ r#"
+//- /lib.rs crate:dep
+pub mod test_mod {
+ pub trait TestTrait {
+ const SPECIAL_CONST: u8;
+ type HumbleType;
+ fn weird_function();
+ fn random_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const SPECIAL_CONST: u8 = 42;
+ type HumbleType = ();
+ fn weird_function() {}
+ fn random_method(&self) {}
+ }
+}
+
+//- /main.rs crate:main deps:dep
+fn main() {
+ dep::test_mod::TestStruct::hum$0
+}
+"#,
+ expect![[r#""#]],
+ );
+}
+
+#[test]
+fn does_not_propose_names_in_scope() {
+ check(
+ r#"
+//- /lib.rs crate:dep
+pub mod test_mod {
+ pub trait TestTrait {
+ const SPECIAL_CONST: u8;
+ type HumbleType;
+ fn weird_function();
+ fn random_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const SPECIAL_CONST: u8 = 42;
+ type HumbleType = ();
+ fn weird_function() {}
+ fn random_method(&self) {}
+ }
+}
+
+//- /main.rs crate:main deps:dep
+use dep::test_mod::TestStruct;
+fn main() {
+ TestSt$0
+}
+"#,
+ expect![[r#""#]],
+ );
+}
+
+#[test]
+fn does_not_propose_traits_in_scope() {
+ check(
+ r#"
+//- /lib.rs crate:dep
+pub mod test_mod {
+ pub trait TestTrait {
+ const SPECIAL_CONST: u8;
+ type HumbleType;
+ fn weird_function();
+ fn random_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const SPECIAL_CONST: u8 = 42;
+ type HumbleType = ();
+ fn weird_function() {}
+ fn random_method(&self) {}
+ }
+}
+
+//- /main.rs crate:main deps:dep
+use dep::test_mod::{TestStruct, TestTrait};
+fn main() {
+ dep::test_mod::TestStruct::hum$0
+}
+"#,
+ expect![[r#""#]],
+ );
+}
+
+#[test]
+fn blanket_trait_impl_import() {
+ check_edit(
+ "another_function",
+ r#"
+//- /lib.rs crate:dep
+pub mod test_mod {
+ pub struct TestStruct {}
+ pub trait TestTrait {
+ fn another_function();
+ }
+ impl<T> TestTrait for T {
+ fn another_function() {}
+ }
+}
+
+//- /main.rs crate:main deps:dep
+fn main() {
+ dep::test_mod::TestStruct::ano$0
+}
+"#,
+ r#"
+use dep::test_mod::TestTrait;
+
+fn main() {
+ dep::test_mod::TestStruct::another_function()$0
+}
+"#,
+ );
+}
+
+#[test]
+fn zero_input_deprecated_assoc_item_completion() {
+ check(
+ r#"
+//- /lib.rs crate:dep
+pub mod test_mod {
+ #[deprecated]
+ pub trait TestTrait {
+ const SPECIAL_CONST: u8;
+ type HumbleType;
+ fn weird_function();
+ fn random_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const SPECIAL_CONST: u8 = 42;
+ type HumbleType = ();
+ fn weird_function() {}
+ fn random_method(&self) {}
+ }
+}
+
+//- /main.rs crate:main deps:dep
+fn main() {
+ let test_struct = dep::test_mod::TestStruct {};
+ test_struct.$0
+}
+ "#,
+ expect![[r#"
+ me random_method() (use dep::test_mod::TestTrait) fn(&self) DEPRECATED
+ "#]],
+ );
+
+ check(
+ r#"
+//- /lib.rs crate:dep
+pub mod test_mod {
+ #[deprecated]
+ pub trait TestTrait {
+ const SPECIAL_CONST: u8;
+ type HumbleType;
+ fn weird_function();
+ fn random_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const SPECIAL_CONST: u8 = 42;
+ type HumbleType = ();
+ fn weird_function() {}
+ fn random_method(&self) {}
+ }
+}
+
+//- /main.rs crate:main deps:dep
+fn main() {
+ dep::test_mod::TestStruct::$0
+}
+"#,
+ expect![[r#"
+ fn weird_function() (use dep::test_mod::TestTrait) fn() DEPRECATED
+ ct SPECIAL_CONST (use dep::test_mod::TestTrait) DEPRECATED
+ "#]],
+ );
+}
+
+#[test]
+fn no_completions_in_use_statements() {
+ check(
+ r#"
+//- /lib.rs crate:dep
+pub mod io {
+ pub fn stdin() {}
+};
+
+//- /main.rs crate:main deps:dep
+use stdi$0
+
+fn main() {}
+"#,
+ expect![[]],
+ );
+}
+
+#[test]
+fn prefix_config_usage() {
+ let fixture = r#"
+mod foo {
+ pub mod bar {
+ pub struct Item;
+ }
+}
+
+use crate::foo::bar;
+
+fn main() {
+ Ite$0
+}"#;
+ let mut config = TEST_CONFIG;
+
+ config.insert_use.prefix_kind = hir::PrefixKind::ByCrate;
+ check_edit_with_config(
+ config.clone(),
+ "Item",
+ fixture,
+ r#"
+mod foo {
+ pub mod bar {
+ pub struct Item;
+ }
+}
+
+use crate::foo::bar::{self, Item};
+
+fn main() {
+ Item
+}"#,
+ );
+
+ config.insert_use.prefix_kind = hir::PrefixKind::BySelf;
+ check_edit_with_config(
+ config.clone(),
+ "Item",
+ fixture,
+ r#"
+mod foo {
+ pub mod bar {
+ pub struct Item;
+ }
+}
+
+use crate::foo::bar;
+
+use self::foo::bar::Item;
+
+fn main() {
+ Item
+}"#,
+ );
+
+ config.insert_use.prefix_kind = hir::PrefixKind::Plain;
+ check_edit_with_config(
+ config,
+ "Item",
+ fixture,
+ r#"
+mod foo {
+ pub mod bar {
+ pub struct Item;
+ }
+}
+
+use foo::bar::Item;
+
+use crate::foo::bar;
+
+fn main() {
+ Item
+}"#,
+ );
+}
+
+#[test]
+fn unresolved_qualifier() {
+ let fixture = r#"
+mod foo {
+ pub mod bar {
+ pub mod baz {
+ pub struct Item;
+ }
+ }
+}
+
+fn main() {
+ bar::baz::Ite$0
+}"#;
+
+ check(
+ fixture,
+ expect![[r#"
+ st Item (use foo::bar::baz::Item)
+ "#]],
+ );
+
+ check_edit(
+ "Item",
+ fixture,
+ r#"
+ use foo::bar;
+
+ mod foo {
+ pub mod bar {
+ pub mod baz {
+ pub struct Item;
+ }
+ }
+ }
+
+ fn main() {
+ bar::baz::Item
+ }"#,
+ );
+}
+
+#[test]
+fn unresolved_assoc_item_container() {
+ let fixture = r#"
+mod foo {
+ pub struct Item;
+
+ impl Item {
+ pub const TEST_ASSOC: usize = 3;
+ }
+}
+
+fn main() {
+ Item::TEST_A$0
+}"#;
+
+ check(
+ fixture,
+ expect![[r#"
+ ct TEST_ASSOC (use foo::Item)
+ "#]],
+ );
+
+ check_edit(
+ "TEST_ASSOC",
+ fixture,
+ r#"
+use foo::Item;
+
+mod foo {
+ pub struct Item;
+
+ impl Item {
+ pub const TEST_ASSOC: usize = 3;
+ }
+}
+
+fn main() {
+ Item::TEST_ASSOC
+}"#,
+ );
+}
+
+#[test]
+fn unresolved_assoc_item_container_with_path() {
+ let fixture = r#"
+mod foo {
+ pub mod bar {
+ pub struct Item;
+
+ impl Item {
+ pub const TEST_ASSOC: usize = 3;
+ }
+ }
+}
+
+fn main() {
+ bar::Item::TEST_A$0
+}"#;
+
+ check(
+ fixture,
+ expect![[r#"
+ ct TEST_ASSOC (use foo::bar::Item)
+ "#]],
+ );
+
+ check_edit(
+ "TEST_ASSOC",
+ fixture,
+ r#"
+use foo::bar;
+
+mod foo {
+ pub mod bar {
+ pub struct Item;
+
+ impl Item {
+ pub const TEST_ASSOC: usize = 3;
+ }
+ }
+}
+
+fn main() {
+ bar::Item::TEST_ASSOC
+}"#,
+ );
+}
+
+#[test]
+fn fuzzy_unresolved_path() {
+ check(
+ r#"
+mod foo {
+ pub mod bar {
+ pub struct Item;
+
+ impl Item {
+ pub const TEST_ASSOC: usize = 3;
+ }
+ }
+}
+
+fn main() {
+ bar::ASS$0
+}"#,
+ expect![[]],
+ )
+}
+
+#[test]
+fn unqualified_assoc_items_are_omitted() {
+ check(
+ r#"
+mod something {
+ pub trait BaseTrait {
+ fn test_function() -> i32;
+ }
+
+ pub struct Item1;
+ pub struct Item2;
+
+ impl BaseTrait for Item1 {
+ fn test_function() -> i32 {
+ 1
+ }
+ }
+
+ impl BaseTrait for Item2 {
+ fn test_function() -> i32 {
+ 2
+ }
+ }
+}
+
+fn main() {
+ test_f$0
+}"#,
+ expect![[]],
+ )
+}
+
+#[test]
+fn case_matters() {
+ check(
+ r#"
+mod foo {
+ pub const TEST_CONST: usize = 3;
+ pub fn test_function() -> i32 {
+ 4
+ }
+}
+
+fn main() {
+ TES$0
+}"#,
+ expect![[r#"
+ ct TEST_CONST (use foo::TEST_CONST)
+ "#]],
+ );
+
+ check(
+ r#"
+mod foo {
+ pub const TEST_CONST: usize = 3;
+ pub fn test_function() -> i32 {
+ 4
+ }
+}
+
+fn main() {
+ tes$0
+}"#,
+ expect![[r#"
+ ct TEST_CONST (use foo::TEST_CONST)
+ fn test_function() (use foo::test_function) fn() -> i32
+ "#]],
+ );
+
+ check(
+ r#"
+mod foo {
+ pub const TEST_CONST: usize = 3;
+ pub fn test_function() -> i32 {
+ 4
+ }
+}
+
+fn main() {
+ Te$0
+}"#,
+ expect![[]],
+ );
+}
+
+#[test]
+fn no_fuzzy_during_fields_of_record_lit_syntax() {
+ check(
+ r#"
+mod m {
+ pub fn some_fn() -> i32 {
+ 42
+ }
+}
+struct Foo {
+ some_field: i32,
+}
+fn main() {
+ let _ = Foo { so$0 };
+}
+"#,
+ expect![[]],
+ );
+}
+
+#[test]
+fn fuzzy_after_fields_of_record_lit_syntax() {
+ check(
+ r#"
+mod m {
+ pub fn some_fn() -> i32 {
+ 42
+ }
+}
+struct Foo {
+ some_field: i32,
+}
+fn main() {
+ let _ = Foo { some_field: som$0 };
+}
+"#,
+ expect![[r#"
+ fn some_fn() (use m::some_fn) fn() -> i32
+ "#]],
+ );
+}
+
+#[test]
+fn no_flyimports_in_traits_and_impl_declarations() {
+ check(
+ r#"
+mod m {
+ pub fn some_fn() -> i32 {
+ 42
+ }
+}
+trait Foo {
+ som$0
+}
+"#,
+ expect![[r#""#]],
+ );
+
+ check(
+ r#"
+mod m {
+ pub fn some_fn() -> i32 {
+ 42
+ }
+}
+struct Foo;
+impl Foo {
+ som$0
+}
+"#,
+ expect![[r#""#]],
+ );
+
+ check(
+ r#"
+mod m {
+ pub fn some_fn() -> i32 {
+ 42
+ }
+}
+struct Foo;
+trait Bar {}
+impl Bar for Foo {
+ som$0
+}
+"#,
+ expect![[r#""#]],
+ );
+}
+
+#[test]
+fn no_inherent_candidates_proposed() {
+ check(
+ r#"
+mod baz {
+ pub trait DefDatabase {
+ fn method1(&self);
+ }
+ pub trait HirDatabase: DefDatabase {
+ fn method2(&self);
+ }
+}
+
+mod bar {
+ fn test(db: &dyn crate::baz::HirDatabase) {
+ db.metho$0
+ }
+}
+ "#,
+ expect![[r#""#]],
+ );
+ check(
+ r#"
+mod baz {
+ pub trait DefDatabase {
+ fn method1(&self);
+ }
+ pub trait HirDatabase: DefDatabase {
+ fn method2(&self);
+ }
+}
+
+mod bar {
+ fn test(db: &impl crate::baz::HirDatabase) {
+ db.metho$0
+ }
+}
+"#,
+ expect![[r#""#]],
+ );
+ check(
+ r#"
+mod baz {
+ pub trait DefDatabase {
+ fn method1(&self);
+ }
+ pub trait HirDatabase: DefDatabase {
+ fn method2(&self);
+ }
+}
+
+mod bar {
+ fn test<T: crate::baz::HirDatabase>(db: T) {
+ db.metho$0
+ }
+}
+"#,
+ expect![[r#""#]],
+ );
+}
+
+#[test]
+fn respects_doc_hidden() {
+ check(
+ r#"
+//- /lib.rs crate:lib deps:dep
+fn f() {
+ ().fro$0
+}
+
+//- /dep.rs crate:dep
+#[doc(hidden)]
+pub trait Private {
+ fn frob(&self) {}
+}
+
+impl<T> Private for T {}
+ "#,
+ expect![[r#""#]],
+ );
+ check(
+ r#"
+//- /lib.rs crate:lib deps:dep
+fn f() {
+ ().fro$0
+}
+
+//- /dep.rs crate:dep
+pub trait Private {
+ #[doc(hidden)]
+ fn frob(&self) {}
+}
+
+impl<T> Private for T {}
+ "#,
+ expect![[r#""#]],
+ );
+}
+
+#[test]
+fn regression_9760() {
+ check(
+ r#"
+struct Struct;
+fn main() {}
+
+mod mud {
+ fn func() {
+ let struct_instance = Stru$0
+ }
+}
+"#,
+ expect![[r#"
+ st Struct (use crate::Struct)
+ "#]],
+ );
+}
+
+#[test]
+fn flyimport_pattern() {
+ check(
+ r#"
+mod module {
+ pub struct FooStruct {}
+ pub const FooConst: () = ();
+ pub fn foo_fun() {}
+}
+fn function() {
+ let foo$0
+}
+"#,
+ expect![[r#"
+ ct FooConst (use module::FooConst)
+ st FooStruct (use module::FooStruct)
+ "#]],
+ );
+}
+
+#[test]
+fn flyimport_item_name() {
+ check(
+ r#"
+mod module {
+ pub struct Struct;
+}
+struct Str$0
+ "#,
+ expect![[r#""#]],
+ );
+}
+
+#[test]
+fn flyimport_rename() {
+ check(
+ r#"
+mod module {
+ pub struct Struct;
+}
+use self as Str$0;
+ "#,
+ expect![[r#""#]],
+ );
+}
+
+#[test]
+fn flyimport_enum_variant() {
+ check(
+ r#"
+mod foo {
+ pub struct Barbara;
+}
+
+enum Foo {
+ Barba$0()
+}
+}"#,
+ expect![[r#""#]],
+ );
+
+ check(
+ r#"
+mod foo {
+ pub struct Barbara;
+}
+
+enum Foo {
+ Barba(Barba$0)
+}
+}"#,
+ expect![[r#"
+ st Barbara (use foo::Barbara)
+ "#]],
+ )
+}
+
+#[test]
+fn flyimport_attribute() {
+ check(
+ r#"
+//- proc_macros:identity
+#[ide$0]
+struct Foo;
+"#,
+ expect![[r#"
+ at identity (use proc_macros::identity) proc_macro identity
+ "#]],
+ );
+ check_edit(
+ "identity",
+ r#"
+//- proc_macros:identity
+#[ide$0]
+struct Foo;
+"#,
+ r#"
+use proc_macros::identity;
+
+#[identity]
+struct Foo;
+"#,
+ );
+}
+
+#[test]
+fn flyimport_in_type_bound_omits_types() {
+ check(
+ r#"
+mod module {
+ pub struct CompletemeStruct;
+ pub type CompletemeType = ();
+ pub enum CompletemeEnum {}
+ pub trait CompletemeTrait {}
+}
+
+fn f<T>() where T: Comp$0
+"#,
+ expect![[r#"
+ tt CompletemeTrait (use module::CompletemeTrait)
+ "#]],
+ );
+}
+
+#[test]
+fn flyimport_source_file() {
+ check(
+ r#"
+//- /main.rs crate:main deps:dep
+def$0
+//- /lib.rs crate:dep
+#[macro_export]
+macro_rules! define_struct {
+ () => {
+ pub struct Foo;
+ };
+}
+"#,
+ expect![[r#"
+ ma define_struct!(…) (use dep::define_struct) macro_rules! define_struct
+ "#]],
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/fn_param.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/fn_param.rs
new file mode 100644
index 000000000..cce74604c
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/fn_param.rs
@@ -0,0 +1,274 @@
+use expect_test::{expect, Expect};
+
+use crate::tests::{completion_list, completion_list_with_trigger_character};
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual);
+}
+
+fn check_with_trigger_character(ra_fixture: &str, trigger_character: char, expect: Expect) {
+ let actual = completion_list_with_trigger_character(ra_fixture, Some(trigger_character));
+ expect.assert_eq(&actual)
+}
+
+#[test]
+fn only_param() {
+ check(
+ r#"
+fn foo(file_id: usize) {}
+fn bar(file_id: usize) {}
+fn baz(file$0) {}
+"#,
+ expect![[r#"
+ bn file_id: usize
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn last_param() {
+ check(
+ r#"
+fn foo(file_id: usize) {}
+fn bar(file_id: usize) {}
+fn baz(foo: (), file$0) {}
+"#,
+ expect![[r#"
+ bn file_id: usize
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn first_param() {
+ check(
+ r#"
+fn foo(file_id: usize) {}
+fn bar(file_id: usize) {}
+fn baz(file$0 id: u32) {}
+"#,
+ expect![[r#"
+ bn file_id: usize,
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn repeated_param_name() {
+ check(
+ r#"
+fn foo(file_id: usize) {}
+fn bar(file_id: u32, $0) {}
+"#,
+ expect![[r#"
+ kw mut
+ kw ref
+ "#]],
+ );
+
+ check(
+ r#"
+fn f(#[foo = "bar"] baz: u32,) {}
+fn g(baz: (), ba$0)
+"#,
+ expect![[r#"
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn trait_param() {
+ check(
+ r#"
+pub(crate) trait SourceRoot {
+ pub fn contains(file_id: usize) -> bool;
+ pub fn syntax(file$0)
+}
+"#,
+ expect![[r#"
+ bn file_id: usize
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn in_inner_function() {
+ check(
+ r#"
+fn outer(text: &str) {
+ fn inner($0)
+}
+"#,
+ expect![[r#"
+ bn text: &str
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn trigger_by_l_paren() {
+ check_with_trigger_character(
+ r#"
+fn foo($0)
+"#,
+ '(',
+ expect![[]],
+ )
+}
+
+#[test]
+fn shows_non_ident_pat_param() {
+ check(
+ r#"
+struct Bar { bar: u32 }
+fn foo(Bar { bar }: Bar) {}
+fn foo2($0) {}
+"#,
+ expect![[r#"
+ st Bar
+ bn Bar { bar }: Bar
+ bn Bar {…} Bar { bar$1 }: Bar$0
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn in_impl_only_param() {
+ check(
+ r#"
+struct A {}
+
+impl A {
+ fn foo(file_id: usize) {}
+ fn new($0) {}
+}
+"#,
+ expect![[r#"
+ sp Self
+ st A
+ bn &mut self
+ bn &self
+ bn file_id: usize
+ bn mut self
+ bn self
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn in_impl_after_self() {
+ check(
+ r#"
+struct A {}
+
+impl A {
+ fn foo(file_id: usize) {}
+ fn new(self, $0) {}
+}
+"#,
+ expect![[r#"
+ sp Self
+ st A
+ bn file_id: usize
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+// doesn't complete qux due to there being no expression after
+// see source_analyzer::adjust comment
+#[test]
+fn local_fn_shows_locals_for_params() {
+ check(
+ r#"
+fn outer() {
+ let foo = 3;
+ {
+ let bar = 3;
+ fn inner($0) {}
+ let baz = 3;
+ let qux = 3;
+ }
+ let fez = 3;
+}
+"#,
+ expect![[r#"
+ bn bar: i32
+ bn baz: i32
+ bn foo: i32
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn closure_shows_locals_for_params() {
+ check(
+ r#"
+fn outer() {
+ let foo = 3;
+ {
+ let bar = 3;
+ |$0| {};
+ let baz = 3;
+ let qux = 3;
+ }
+ let fez = 3;
+}
+"#,
+ expect![[r#"
+ bn bar: i32
+ bn baz: i32
+ bn foo: i32
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn completes_fully_equal() {
+ check(
+ r#"
+fn foo(bar: u32) {}
+fn bar(bar$0) {}
+"#,
+ expect![[r#"
+ bn bar: u32
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn completes_for_params_with_attributes() {
+ check(
+ r#"
+fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
+fn g(foo: (), #[baz = "qux"] mut ba$0)
+"#,
+ expect![[r##"
+ bn #[baz = "qux"] mut bar: u32
+ "##]],
+ )
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/item.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/item.rs
new file mode 100644
index 000000000..409413c1d
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/item.rs
@@ -0,0 +1,154 @@
+//! Completion tests for item specifics overall.
+//!
+//! Except for use items which are tested in [super::use_tree] and mod declarations with are tested
+//! in [crate::completions::mod_].
+use expect_test::{expect, Expect};
+
+use crate::tests::{completion_list, BASE_ITEMS_FIXTURE};
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(&format!("{}{}", BASE_ITEMS_FIXTURE, ra_fixture));
+ expect.assert_eq(&actual)
+}
+
+#[test]
+fn target_type_or_trait_in_impl_block() {
+ check(
+ r#"
+impl Tra$0
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ )
+}
+
+#[test]
+fn target_type_in_trait_impl_block() {
+ check(
+ r#"
+impl Trait for Str$0
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ )
+}
+
+#[test]
+fn after_trait_name_in_trait_def() {
+ check(
+ r"trait A $0",
+ expect![[r#"
+ kw where
+ "#]],
+ );
+}
+
+#[test]
+fn after_target_name_in_impl() {
+ check(
+ r"impl Trait $0",
+ expect![[r#"
+ kw for
+ kw where
+ "#]],
+ );
+ check(
+ r"impl Trait f$0",
+ expect![[r#"
+ kw for
+ kw where
+ "#]],
+ );
+ check(
+ r"impl Trait for Type $0",
+ expect![[r#"
+ kw where
+ "#]],
+ );
+}
+
+#[test]
+fn completes_where() {
+ check(
+ r"struct Struct $0",
+ expect![[r#"
+ kw where
+ "#]],
+ );
+ check(
+ r"struct Struct $0 {}",
+ expect![[r#"
+ kw where
+ "#]],
+ );
+ // FIXME: This shouldn't be completed here
+ check(
+ r"struct Struct $0 ()",
+ expect![[r#"
+ kw where
+ "#]],
+ );
+ check(
+ r"fn func() $0",
+ expect![[r#"
+ kw where
+ "#]],
+ );
+ check(
+ r"enum Enum $0",
+ expect![[r#"
+ kw where
+ "#]],
+ );
+ check(
+ r"enum Enum $0 {}",
+ expect![[r#"
+ kw where
+ "#]],
+ );
+ check(
+ r"trait Trait $0 {}",
+ expect![[r#"
+ kw where
+ "#]],
+ );
+}
+
+#[test]
+fn before_record_field() {
+ check(
+ r#"
+struct Foo {
+ $0
+ pub f: i32,
+}
+"#,
+ expect![[r#"
+ kw pub
+ kw pub(crate)
+ kw pub(super)
+ "#]],
+ )
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs
new file mode 100644
index 000000000..5076c6e86
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs
@@ -0,0 +1,247 @@
+//! Completion tests for item list position.
+use expect_test::{expect, Expect};
+
+use crate::tests::{completion_list, BASE_ITEMS_FIXTURE};
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(&format!("{}{}", BASE_ITEMS_FIXTURE, ra_fixture));
+ expect.assert_eq(&actual)
+}
+
+#[test]
+fn in_mod_item_list() {
+ check(
+ r#"mod tests { $0 }"#,
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ kw const
+ kw crate::
+ kw enum
+ kw extern
+ kw fn
+ kw impl
+ kw mod
+ kw pub
+ kw pub(crate)
+ kw pub(super)
+ kw self::
+ kw static
+ kw struct
+ kw super::
+ kw trait
+ kw type
+ kw union
+ kw unsafe
+ kw use
+ sn macro_rules
+ sn tfn (Test function)
+ sn tmod (Test module)
+ "#]],
+ )
+}
+
+#[test]
+fn in_source_file_item_list() {
+ check(
+ r#"$0"#,
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ md module
+ kw const
+ kw crate::
+ kw enum
+ kw extern
+ kw fn
+ kw impl
+ kw mod
+ kw pub
+ kw pub(crate)
+ kw pub(super)
+ kw self::
+ kw static
+ kw struct
+ kw trait
+ kw type
+ kw union
+ kw unsafe
+ kw use
+ sn macro_rules
+ sn tfn (Test function)
+ sn tmod (Test module)
+ "#]],
+ )
+}
+
+#[test]
+fn in_item_list_after_attr() {
+ check(
+ r#"#[attr] $0"#,
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ md module
+ kw const
+ kw crate::
+ kw enum
+ kw extern
+ kw fn
+ kw impl
+ kw mod
+ kw pub
+ kw pub(crate)
+ kw pub(super)
+ kw self::
+ kw static
+ kw struct
+ kw trait
+ kw type
+ kw union
+ kw unsafe
+ kw use
+ sn macro_rules
+ sn tfn (Test function)
+ sn tmod (Test module)
+ "#]],
+ )
+}
+
+#[test]
+fn in_qualified_path() {
+ check(
+ r#"crate::$0"#,
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ md module
+ "#]],
+ )
+}
+
+#[test]
+fn after_unsafe_token() {
+ check(
+ r#"unsafe $0"#,
+ expect![[r#"
+ kw fn
+ kw impl
+ kw trait
+ "#]],
+ );
+}
+
+#[test]
+fn after_visibility() {
+ check(
+ r#"pub $0"#,
+ expect![[r#"
+ kw const
+ kw enum
+ kw extern
+ kw fn
+ kw mod
+ kw static
+ kw struct
+ kw trait
+ kw type
+ kw union
+ kw unsafe
+ kw use
+ "#]],
+ );
+}
+
+#[test]
+fn after_visibility_unsafe() {
+ check(
+ r#"pub unsafe $0"#,
+ expect![[r#"
+ kw fn
+ kw trait
+ "#]],
+ );
+}
+
+#[test]
+fn in_impl_assoc_item_list() {
+ check(
+ r#"impl Struct { $0 }"#,
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ md module
+ kw const
+ kw crate::
+ kw fn
+ kw pub
+ kw pub(crate)
+ kw pub(super)
+ kw self::
+ kw unsafe
+ "#]],
+ )
+}
+
+#[test]
+fn in_impl_assoc_item_list_after_attr() {
+ check(
+ r#"impl Struct { #[attr] $0 }"#,
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ md module
+ kw const
+ kw crate::
+ kw fn
+ kw pub
+ kw pub(crate)
+ kw pub(super)
+ kw self::
+ kw unsafe
+ "#]],
+ )
+}
+
+#[test]
+fn in_trait_assoc_item_list() {
+ check(
+ r"trait Foo { $0 }",
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ md module
+ kw const
+ kw crate::
+ kw fn
+ kw self::
+ kw type
+ kw unsafe
+ "#]],
+ );
+}
+
+#[test]
+fn in_trait_impl_assoc_item_list() {
+ check(
+ r#"
+trait Test {
+ type Type0;
+ type Type1;
+ const CONST0: ();
+ const CONST1: ();
+ fn function0();
+ fn function1();
+}
+
+impl Test for () {
+ type Type0 = ();
+ const CONST0: () = ();
+ fn function0() {}
+ $0
+}
+"#,
+ expect![[r#"
+ ct const CONST1: () =
+ fn fn function1()
+ ma makro!(…) macro_rules! makro
+ md module
+ ta type Type1 =
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs
new file mode 100644
index 000000000..30ddbe2dc
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs
@@ -0,0 +1,716 @@
+//! Completion tests for pattern position.
+use expect_test::{expect, Expect};
+
+use crate::tests::{check_edit, completion_list, BASE_ITEMS_FIXTURE};
+
+fn check_empty(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual)
+}
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(&format!("{}\n{}", BASE_ITEMS_FIXTURE, ra_fixture));
+ expect.assert_eq(&actual)
+}
+
+#[test]
+fn wildcard() {
+ check(
+ r#"
+fn quux() {
+ let _$0
+}
+"#,
+ expect![""],
+ );
+}
+
+#[test]
+fn ident_rebind_pat() {
+ check_empty(
+ r#"
+fn quux() {
+ let en$0 @ x
+}
+"#,
+ expect![[r#"
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn ident_ref_pat() {
+ check_empty(
+ r#"
+fn quux() {
+ let ref en$0
+}
+"#,
+ expect![[r#"
+ kw mut
+ "#]],
+ );
+ check_empty(
+ r#"
+fn quux() {
+ let ref en$0 @ x
+}
+"#,
+ expect![[r#"
+ kw mut
+ "#]],
+ );
+}
+
+#[test]
+fn ident_ref_mut_pat() {
+ check_empty(
+ r#"
+fn quux() {
+ let ref mut en$0
+}
+"#,
+ expect![[r#""#]],
+ );
+ check_empty(
+ r#"
+fn quux() {
+ let ref mut en$0 @ x
+}
+"#,
+ expect![[r#""#]],
+ );
+}
+
+#[test]
+fn ref_pat() {
+ check_empty(
+ r#"
+fn quux() {
+ let &en$0
+}
+"#,
+ expect![[r#"
+ kw mut
+ "#]],
+ );
+ check_empty(
+ r#"
+fn quux() {
+ let &mut en$0
+}
+"#,
+ expect![[r#""#]],
+ );
+ check_empty(
+ r#"
+fn foo() {
+ for &$0 in () {}
+}
+"#,
+ expect![[r#"
+ kw mut
+ "#]],
+ );
+}
+
+#[test]
+fn refutable() {
+ check(
+ r#"
+fn foo() {
+ if let a$0
+}
+"#,
+ expect![[r#"
+ ct CONST
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ ev TupleV
+ bn Record {…} Record { field$1 }$0
+ bn Tuple(…) Tuple($1)$0
+ bn TupleV(…) TupleV($1)$0
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn irrefutable() {
+ check(
+ r#"
+enum SingleVariantEnum {
+ Variant
+}
+use SingleVariantEnum::Variant;
+fn foo() {
+ let a$0
+}
+"#,
+ expect![[r#"
+ en SingleVariantEnum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ ev Variant
+ bn Record {…} Record { field$1 }$0
+ bn Tuple(…) Tuple($1)$0
+ bn Variant Variant$0
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn in_param() {
+ check(
+ r#"
+fn foo(a$0) {
+}
+"#,
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ bn Record {…} Record { field$1 }: Record$0
+ bn Tuple(…) Tuple($1): Tuple$0
+ kw mut
+ kw ref
+ "#]],
+ );
+ check(
+ r#"
+fn foo(a$0: Tuple) {
+}
+"#,
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ bn Record {…} Record { field$1 }$0
+ bn Tuple(…) Tuple($1)$0
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn only_fn_like_macros() {
+ check_empty(
+ r#"
+macro_rules! m { ($e:expr) => { $e } }
+
+#[rustc_builtin_macro]
+macro Clone {}
+
+fn foo() {
+ let x$0
+}
+"#,
+ expect![[r#"
+ ma m!(…) macro_rules! m
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn in_simple_macro_call() {
+ check_empty(
+ r#"
+macro_rules! m { ($e:expr) => { $e } }
+enum E { X }
+
+fn foo() {
+ m!(match E::X { a$0 })
+}
+"#,
+ expect![[r#"
+ en E
+ ma m!(…) macro_rules! m
+ bn E::X E::X$0
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn omits_private_fields_pat() {
+ check_empty(
+ r#"
+mod foo {
+ pub struct Record { pub field: i32, _field: i32 }
+ pub struct Tuple(pub u32, u32);
+ pub struct Invisible(u32, u32);
+}
+use foo::*;
+
+fn outer() {
+ if let a$0
+}
+"#,
+ expect![[r#"
+ md foo
+ st Invisible
+ st Record
+ st Tuple
+ bn Record {…} Record { field$1, .. }$0
+ bn Tuple(…) Tuple($1, ..)$0
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn completes_self_pats() {
+ check_empty(
+ r#"
+struct Foo(i32);
+impl Foo {
+ fn foo() {
+ match Foo(0) {
+ a$0
+ }
+ }
+}
+ "#,
+ expect![[r#"
+ sp Self
+ st Foo
+ bn Foo(…) Foo($1)$0
+ bn Self(…) Self($1)$0
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn enum_qualified() {
+ check(
+ r#"
+impl Enum {
+ type AssocType = ();
+ const ASSOC_CONST: () = ();
+ fn assoc_fn() {}
+}
+fn func() {
+ if let Enum::$0 = unknown {}
+}
+"#,
+ expect![[r#"
+ ct ASSOC_CONST const ASSOC_CONST: ()
+ bn RecordV {…} RecordV { field$1 }$0
+ bn TupleV(…) TupleV($1)$0
+ bn UnitV UnitV$0
+ "#]],
+ );
+}
+
+#[test]
+fn completes_in_record_field_pat() {
+ check_empty(
+ r#"
+struct Foo { bar: Bar }
+struct Bar(u32);
+fn outer(Foo { bar: $0 }: Foo) {}
+"#,
+ expect![[r#"
+ st Bar
+ st Foo
+ bn Bar(…) Bar($1)$0
+ bn Foo {…} Foo { bar$1 }$0
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn skips_in_record_field_pat_name() {
+ check_empty(
+ r#"
+struct Foo { bar: Bar }
+struct Bar(u32);
+fn outer(Foo { bar$0 }: Foo) {}
+"#,
+ expect![[r#"
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn completes_in_fn_param() {
+ check_empty(
+ r#"
+struct Foo { bar: Bar }
+struct Bar(u32);
+fn foo($0) {}
+"#,
+ expect![[r#"
+ st Bar
+ st Foo
+ bn Bar(…) Bar($1): Bar$0
+ bn Foo {…} Foo { bar$1 }: Foo$0
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn completes_in_closure_param() {
+ check_empty(
+ r#"
+struct Foo { bar: Bar }
+struct Bar(u32);
+fn foo() {
+ |$0| {};
+}
+"#,
+ expect![[r#"
+ st Bar
+ st Foo
+ bn Bar(…) Bar($1)$0
+ bn Foo {…} Foo { bar$1 }$0
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn completes_no_delims_if_existing() {
+ check_empty(
+ r#"
+struct Bar(u32);
+fn foo() {
+ match Bar(0) {
+ B$0(b) => {}
+ }
+}
+"#,
+ expect![[r#"
+ st Bar
+ kw crate::
+ kw self::
+ "#]],
+ );
+ check_empty(
+ r#"
+struct Foo { bar: u32 }
+fn foo() {
+ match (Foo { bar: 0 }) {
+ F$0 { bar } => {}
+ }
+}
+"#,
+ expect![[r#"
+ st Foo
+ kw crate::
+ kw self::
+ "#]],
+ );
+ check_empty(
+ r#"
+enum Enum {
+ TupleVariant(u32)
+}
+fn foo() {
+ match Enum::TupleVariant(0) {
+ Enum::T$0(b) => {}
+ }
+}
+"#,
+ expect![[r#"
+ bn TupleVariant TupleVariant
+ "#]],
+ );
+ check_empty(
+ r#"
+enum Enum {
+ RecordVariant { field: u32 }
+}
+fn foo() {
+ match (Enum::RecordVariant { field: 0 }) {
+ Enum::RecordV$0 { field } => {}
+ }
+}
+"#,
+ expect![[r#"
+ bn RecordVariant RecordVariant
+ "#]],
+ );
+}
+
+#[test]
+fn completes_enum_variant_pat() {
+ cov_mark::check!(enum_variant_pattern_path);
+ check_edit(
+ "RecordVariant {…}",
+ r#"
+enum Enum {
+ RecordVariant { field: u32 }
+}
+fn foo() {
+ match (Enum::RecordVariant { field: 0 }) {
+ Enum::RecordV$0
+ }
+}
+"#,
+ r#"
+enum Enum {
+ RecordVariant { field: u32 }
+}
+fn foo() {
+ match (Enum::RecordVariant { field: 0 }) {
+ Enum::RecordVariant { field$1 }$0
+ }
+}
+"#,
+ );
+}
+
+#[test]
+fn completes_enum_variant_pat_escape() {
+ cov_mark::check!(enum_variant_pattern_path);
+ check_empty(
+ r#"
+enum Enum {
+ A,
+ B { r#type: i32 },
+ r#type,
+ r#struct { r#type: i32 },
+}
+fn foo() {
+ match (Enum::A) {
+ $0
+ }
+}
+"#,
+ expect![[r#"
+ en Enum
+ bn Enum::A Enum::A$0
+ bn Enum::B {…} Enum::B { r#type$1 }$0
+ bn Enum::struct {…} Enum::r#struct { r#type$1 }$0
+ bn Enum::type Enum::r#type$0
+ kw mut
+ kw ref
+ "#]],
+ );
+
+ check_empty(
+ r#"
+enum Enum {
+ A,
+ B { r#type: i32 },
+ r#type,
+ r#struct { r#type: i32 },
+}
+fn foo() {
+ match (Enum::A) {
+ Enum::$0
+ }
+}
+"#,
+ expect![[r#"
+ bn A A$0
+ bn B {…} B { r#type$1 }$0
+ bn struct {…} r#struct { r#type$1 }$0
+ bn type r#type$0
+ "#]],
+ );
+}
+
+#[test]
+fn completes_associated_const() {
+ check_empty(
+ r#"
+#[derive(PartialEq, Eq)]
+struct Ty(u8);
+
+impl Ty {
+ const ABC: Self = Self(0);
+}
+
+fn f(t: Ty) {
+ match t {
+ Ty::$0 => {}
+ _ => {}
+ }
+}
+"#,
+ expect![[r#"
+ ct ABC const ABC: Self
+ "#]],
+ );
+
+ check_empty(
+ r#"
+enum MyEnum {}
+
+impl MyEnum {
+ pub const A: i32 = 123;
+ pub const B: i32 = 456;
+}
+
+fn f(e: MyEnum) {
+ match e {
+ MyEnum::$0 => {}
+ _ => {}
+ }
+}
+"#,
+ expect![[r#"
+ ct A pub const A: i32
+ ct B pub const B: i32
+ "#]],
+ );
+
+ check_empty(
+ r#"
+union U {
+ i: i32,
+ f: f32,
+}
+
+impl U {
+ pub const C: i32 = 123;
+ pub const D: i32 = 456;
+}
+
+fn f(u: U) {
+ match u {
+ U::$0 => {}
+ _ => {}
+ }
+}
+"#,
+ expect![[r#"
+ ct C pub const C: i32
+ ct D pub const D: i32
+ "#]],
+ );
+
+ check_empty(
+ r#"
+#[lang = "u32"]
+impl u32 {
+ pub const MIN: Self = 0;
+}
+
+fn f(v: u32) {
+ match v {
+ u32::$0
+ }
+}
+ "#,
+ expect![[r#"
+ ct MIN pub const MIN: Self
+ "#]],
+ );
+}
+
+#[test]
+fn in_method_param() {
+ check_empty(
+ r#"
+struct Ty(u8);
+
+impl Ty {
+ fn foo($0)
+}
+"#,
+ expect![[r#"
+ sp Self
+ st Ty
+ bn &mut self
+ bn &self
+ bn Self(…) Self($1): Self$0
+ bn Ty(…) Ty($1): Ty$0
+ bn mut self
+ bn self
+ kw mut
+ kw ref
+ "#]],
+ );
+ check_empty(
+ r#"
+struct Ty(u8);
+
+impl Ty {
+ fn foo(s$0)
+}
+"#,
+ expect![[r#"
+ sp Self
+ st Ty
+ bn &mut self
+ bn &self
+ bn Self(…) Self($1): Self$0
+ bn Ty(…) Ty($1): Ty$0
+ bn mut self
+ bn self
+ kw mut
+ kw ref
+ "#]],
+ );
+ check_empty(
+ r#"
+struct Ty(u8);
+
+impl Ty {
+ fn foo(s$0, foo: u8)
+}
+"#,
+ expect![[r#"
+ sp Self
+ st Ty
+ bn &mut self
+ bn &self
+ bn Self(…) Self($1): Self$0
+ bn Ty(…) Ty($1): Ty$0
+ bn mut self
+ bn self
+ kw mut
+ kw ref
+ "#]],
+ );
+ check_empty(
+ r#"
+struct Ty(u8);
+
+impl Ty {
+ fn foo(foo: u8, b$0)
+}
+"#,
+ expect![[r#"
+ sp Self
+ st Ty
+ bn Self(…) Self($1): Self$0
+ bn Ty(…) Ty($1): Ty$0
+ kw mut
+ kw ref
+ "#]],
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/predicate.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/predicate.rs
new file mode 100644
index 000000000..a8676e2f2
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/predicate.rs
@@ -0,0 +1,131 @@
+//! Completion tests for predicates and bounds.
+use expect_test::{expect, Expect};
+
+use crate::tests::{completion_list, BASE_ITEMS_FIXTURE};
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(&format!("{}\n{}", BASE_ITEMS_FIXTURE, ra_fixture));
+ expect.assert_eq(&actual)
+}
+
+#[test]
+fn predicate_start() {
+ // FIXME: `for` kw
+ check(
+ r#"
+struct Foo<'lt, T, const C: usize> where $0 {}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Foo<…>
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn bound_for_type_pred() {
+ check(
+ r#"
+struct Foo<'lt, T, const C: usize> where T: $0 {}
+"#,
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ md module
+ tt Trait
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn bound_for_lifetime_pred() {
+ // FIXME: should only show lifetimes here, that is we shouldn't get any completions here when not typing
+ // a `'`
+ check(
+ r#"
+struct Foo<'lt, T, const C: usize> where 'lt: $0 {}
+"#,
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ md module
+ tt Trait
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn bound_for_for_pred() {
+ check(
+ r#"
+struct Foo<'lt, T, const C: usize> where for<'a> T: $0 {}
+"#,
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ md module
+ tt Trait
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn param_list_for_for_pred() {
+ check(
+ r#"
+struct Foo<'lt, T, const C: usize> where for<'a> $0 {}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Foo<…>
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn pred_on_fn_in_impl() {
+ check(
+ r#"
+impl Record {
+ fn method(self) where $0 {}
+}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ sp Self
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/proc_macros.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/proc_macros.rs
new file mode 100644
index 000000000..9eae6f849
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/proc_macros.rs
@@ -0,0 +1,133 @@
+//! Completion tests for expressions.
+use expect_test::{expect, Expect};
+
+use crate::tests::completion_list;
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual)
+}
+
+#[test]
+fn complete_dot_in_attr() {
+ check(
+ r#"
+//- proc_macros: identity
+pub struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+
+#[proc_macros::identity]
+fn main() {
+ Foo.$0
+}
+"#,
+ expect![[r#"
+ me foo() fn(&self)
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn ref &expr
+ sn refm &mut expr
+ "#]],
+ )
+}
+
+#[test]
+fn complete_dot_in_attr2() {
+ check(
+ r#"
+//- proc_macros: identity
+pub struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+
+#[proc_macros::identity]
+fn main() {
+ Foo.f$0
+}
+"#,
+ expect![[r#"
+ me foo() fn(&self)
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn ref &expr
+ sn refm &mut expr
+ "#]],
+ )
+}
+
+#[test]
+fn complete_dot_in_attr_input() {
+ check(
+ r#"
+//- proc_macros: input_replace
+pub struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+
+#[proc_macros::input_replace(
+ fn suprise() {
+ Foo.$0
+ }
+)]
+fn main() {}
+"#,
+ expect![[r#"
+ me foo() fn(&self)
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn ref &expr
+ sn refm &mut expr
+ "#]],
+ )
+}
+
+#[test]
+fn complete_dot_in_attr_input2() {
+ check(
+ r#"
+//- proc_macros: input_replace
+pub struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+
+#[proc_macros::input_replace(
+ fn suprise() {
+ Foo.f$0
+ }
+)]
+fn main() {}
+"#,
+ expect![[r#"
+ me foo() fn(&self)
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn ref &expr
+ sn refm &mut expr
+ "#]],
+ )
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/record.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/record.rs
new file mode 100644
index 000000000..f6accc68e
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/record.rs
@@ -0,0 +1,229 @@
+use expect_test::{expect, Expect};
+
+use crate::tests::completion_list;
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual);
+}
+
+#[test]
+fn without_default_impl() {
+ check(
+ r#"
+struct Struct { foo: u32, bar: usize }
+
+fn foo() {
+ let other = Struct {
+ foo: 5,
+ $0
+ };
+}
+"#,
+ expect![[r#"
+ fd bar usize
+ "#]],
+ );
+}
+
+#[test]
+fn record_pattern_field() {
+ check(
+ r#"
+struct Struct { foo: u32, bar: u32 }
+
+fn foo(s: Struct) {
+ match s {
+ Struct { foo, $0: 92 } => (),
+ }
+}
+"#,
+ expect![[r#"
+ fd bar u32
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn pattern_enum_variant() {
+ check(
+ r#"
+enum Enum { Variant { foo: u32, bar: u32 } }
+fn foo(e: Enum) {
+ match e {
+ Enum::Variant { foo, $0 } => (),
+ }
+}
+"#,
+ expect![[r#"
+ fd bar u32
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn record_literal_field_in_macro() {
+ check(
+ r#"
+macro_rules! m { ($e:expr) => { $e } }
+struct Struct { field: u32 }
+fn foo() {
+ m!(Struct { fie$0 })
+}
+"#,
+ expect![[r#"
+ fd field u32
+ "#]],
+ );
+}
+
+#[test]
+fn record_pattern_field_in_macro() {
+ check(
+ r"
+macro_rules! m { ($e:expr) => { $e } }
+struct Struct { field: u32 }
+
+fn foo(f: Struct) {
+ m!(match f {
+ Struct { f$0: 92 } => (),
+ })
+}
+",
+ expect![[r#"
+ fd field u32
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn functional_update() {
+ // FIXME: This should filter out all completions that do not have the type `Foo`
+ check(
+ r#"
+//- minicore:default
+struct Foo { foo1: u32, foo2: u32 }
+impl Default for Foo {
+ fn default() -> Self { loop {} }
+}
+
+fn main() {
+ let thing = 1;
+ let foo = Foo { foo1: 0, foo2: 0 };
+ let foo2 = Foo { thing, $0 }
+}
+"#,
+ expect![[r#"
+ fd ..Default::default()
+ fd foo1 u32
+ fd foo2 u32
+ "#]],
+ );
+ check(
+ r#"
+//- minicore:default
+struct Foo { foo1: u32, foo2: u32 }
+impl Default for Foo {
+ fn default() -> Self { loop {} }
+}
+
+fn main() {
+ let thing = 1;
+ let foo = Foo { foo1: 0, foo2: 0 };
+ let foo2 = Foo { thing, .$0 }
+}
+"#,
+ expect![[r#"
+ fd ..Default::default()
+ sn ..
+ "#]],
+ );
+ check(
+ r#"
+//- minicore:default
+struct Foo { foo1: u32, foo2: u32 }
+impl Default for Foo {
+ fn default() -> Self { loop {} }
+}
+
+fn main() {
+ let thing = 1;
+ let foo = Foo { foo1: 0, foo2: 0 };
+ let foo2 = Foo { thing, ..$0 }
+}
+"#,
+ expect![[r#"
+ fd ..Default::default()
+ fn main() fn()
+ lc foo Foo
+ lc thing i32
+ md core
+ st Foo
+ st Foo {…} Foo { foo1: u32, foo2: u32 }
+ tt Default
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+ check(
+ r#"
+//- minicore:default
+struct Foo { foo1: u32, foo2: u32 }
+impl Default for Foo {
+ fn default() -> Self { loop {} }
+}
+
+fn main() {
+ let thing = 1;
+ let foo = Foo { foo1: 0, foo2: 0 };
+ let foo2 = Foo { thing, ..Default::$0 }
+}
+"#,
+ expect![[r#"
+ fn default() (as Default) fn() -> Self
+ "#]],
+ );
+}
+
+#[test]
+fn empty_union_literal() {
+ check(
+ r#"
+union Union { foo: u32, bar: f32 }
+
+fn foo() {
+ let other = Union {
+ $0
+ };
+}
+ "#,
+ expect![[r#"
+ fd bar f32
+ fd foo u32
+ "#]],
+ )
+}
+
+#[test]
+fn dont_suggest_additional_union_fields() {
+ check(
+ r#"
+union Union { foo: u32, bar: f32 }
+
+fn foo() {
+ let other = Union {
+ foo: 1,
+ $0
+ };
+}
+ "#,
+ expect![[r#""#]],
+ )
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs
new file mode 100644
index 000000000..033dc99c2
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs
@@ -0,0 +1,895 @@
+//! Tests that don't fit into a specific category.
+
+use expect_test::{expect, Expect};
+
+use crate::tests::{check_edit, completion_list_no_kw};
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list_no_kw(ra_fixture);
+ expect.assert_eq(&actual)
+}
+
+#[test]
+fn completes_if_prefix_is_keyword() {
+ check_edit(
+ "wherewolf",
+ r#"
+fn main() {
+ let wherewolf = 92;
+ drop(where$0)
+}
+"#,
+ r#"
+fn main() {
+ let wherewolf = 92;
+ drop(wherewolf)
+}
+"#,
+ )
+}
+
+/// Regression test for issue #6091.
+#[test]
+fn correctly_completes_module_items_prefixed_with_underscore() {
+ check_edit(
+ "_alpha",
+ r#"
+fn main() {
+ _$0
+}
+fn _alpha() {}
+"#,
+ r#"
+fn main() {
+ _alpha()$0
+}
+fn _alpha() {}
+"#,
+ )
+}
+
+#[test]
+fn completes_prelude() {
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+fn foo() { let x: $0 }
+
+//- /std/lib.rs crate:std
+pub mod prelude {
+ pub mod rust_2018 {
+ pub struct Option;
+ }
+}
+"#,
+ expect![[r#"
+ md std
+ st Option
+ bt u32
+ "#]],
+ );
+}
+
+#[test]
+fn completes_prelude_macros() {
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+fn f() {$0}
+
+//- /std/lib.rs crate:std
+pub mod prelude {
+ pub mod rust_2018 {
+ pub use crate::concat;
+ }
+}
+
+mod macros {
+ #[rustc_builtin_macro]
+ #[macro_export]
+ macro_rules! concat { }
+}
+"#,
+ expect![[r#"
+ fn f() fn()
+ ma concat!(…) macro_rules! concat
+ md std
+ bt u32
+ "#]],
+ );
+}
+
+#[test]
+fn completes_std_prelude_if_core_is_defined() {
+ check(
+ r#"
+//- /main.rs crate:main deps:core,std
+fn foo() { let x: $0 }
+
+//- /core/lib.rs crate:core
+pub mod prelude {
+ pub mod rust_2018 {
+ pub struct Option;
+ }
+}
+
+//- /std/lib.rs crate:std deps:core
+pub mod prelude {
+ pub mod rust_2018 {
+ pub struct String;
+ }
+}
+"#,
+ expect![[r#"
+ md core
+ md std
+ st String
+ bt u32
+ "#]],
+ );
+}
+
+#[test]
+fn respects_doc_hidden() {
+ check(
+ r#"
+//- /lib.rs crate:lib deps:std
+fn f() {
+ format_$0
+}
+
+//- /std.rs crate:std
+#[doc(hidden)]
+#[macro_export]
+macro_rules! format_args_nl {
+ () => {}
+}
+
+pub mod prelude {
+ pub mod rust_2018 {}
+}
+ "#,
+ expect![[r#"
+ fn f() fn()
+ md std
+ bt u32
+ "#]],
+ );
+}
+
+#[test]
+fn respects_doc_hidden_in_assoc_item_list() {
+ check(
+ r#"
+//- /lib.rs crate:lib deps:std
+struct S;
+impl S {
+ format_$0
+}
+
+//- /std.rs crate:std
+#[doc(hidden)]
+#[macro_export]
+macro_rules! format_args_nl {
+ () => {}
+}
+
+pub mod prelude {
+ pub mod rust_2018 {}
+}
+ "#,
+ expect![[r#"
+ md std
+ "#]],
+ );
+}
+
+#[test]
+fn associated_item_visibility() {
+ check(
+ r#"
+//- /lib.rs crate:lib new_source_root:library
+pub struct S;
+
+impl S {
+ pub fn public_method() { }
+ fn private_method() { }
+ pub type PublicType = u32;
+ type PrivateType = u32;
+ pub const PUBLIC_CONST: u32 = 1;
+ const PRIVATE_CONST: u32 = 1;
+}
+
+//- /main.rs crate:main deps:lib new_source_root:local
+fn foo() { let _ = lib::S::$0 }
+"#,
+ expect![[r#"
+ ct PUBLIC_CONST pub const PUBLIC_CONST: u32
+ fn public_method() fn()
+ ta PublicType pub type PublicType = u32
+ "#]],
+ );
+}
+
+#[test]
+fn completes_union_associated_method() {
+ check(
+ r#"
+union U {};
+impl U { fn m() { } }
+
+fn foo() { let _ = U::$0 }
+"#,
+ expect![[r#"
+ fn m() fn()
+ "#]],
+ );
+}
+
+#[test]
+fn completes_trait_associated_method_1() {
+ check(
+ r#"
+trait Trait { fn m(); }
+
+fn foo() { let _ = Trait::$0 }
+"#,
+ expect![[r#"
+ fn m() (as Trait) fn()
+ "#]],
+ );
+}
+
+#[test]
+fn completes_trait_associated_method_2() {
+ check(
+ r#"
+trait Trait { fn m(); }
+
+struct S;
+impl Trait for S {}
+
+fn foo() { let _ = S::$0 }
+"#,
+ expect![[r#"
+ fn m() (as Trait) fn()
+ "#]],
+ );
+}
+
+#[test]
+fn completes_trait_associated_method_3() {
+ check(
+ r#"
+trait Trait { fn m(); }
+
+struct S;
+impl Trait for S {}
+
+fn foo() { let _ = <S as Trait>::$0 }
+"#,
+ expect![[r#"
+ fn m() (as Trait) fn()
+ "#]],
+ );
+}
+
+#[test]
+fn completes_ty_param_assoc_ty() {
+ check(
+ r#"
+trait Super {
+ type Ty;
+ const CONST: u8;
+ fn func() {}
+ fn method(&self) {}
+}
+
+trait Sub: Super {
+ type SubTy;
+ const C2: ();
+ fn subfunc() {}
+ fn submethod(&self) {}
+}
+
+fn foo<T: Sub>() { T::$0 }
+"#,
+ expect![[r#"
+ ct C2 (as Sub) const C2: ()
+ ct CONST (as Super) const CONST: u8
+ fn func() (as Super) fn()
+ fn subfunc() (as Sub) fn()
+ ta SubTy (as Sub) type SubTy
+ ta Ty (as Super) type Ty
+ me method(…) (as Super) fn(&self)
+ me submethod(…) (as Sub) fn(&self)
+ "#]],
+ );
+}
+
+#[test]
+fn completes_self_param_assoc_ty() {
+ check(
+ r#"
+trait Super {
+ type Ty;
+ const CONST: u8 = 0;
+ fn func() {}
+ fn method(&self) {}
+}
+
+trait Sub: Super {
+ type SubTy;
+ const C2: () = ();
+ fn subfunc() {}
+ fn submethod(&self) {}
+}
+
+struct Wrap<T>(T);
+impl<T> Super for Wrap<T> {}
+impl<T> Sub for Wrap<T> {
+ fn subfunc() {
+ // Should be able to assume `Self: Sub + Super`
+ Self::$0
+ }
+}
+"#,
+ expect![[r#"
+ ct C2 (as Sub) const C2: ()
+ ct CONST (as Super) const CONST: u8
+ fn func() (as Super) fn()
+ fn subfunc() (as Sub) fn()
+ ta SubTy (as Sub) type SubTy
+ ta Ty (as Super) type Ty
+ me method(…) (as Super) fn(&self)
+ me submethod(…) (as Sub) fn(&self)
+ "#]],
+ );
+}
+
+#[test]
+fn completes_type_alias() {
+ check(
+ r#"
+struct S;
+impl S { fn foo() {} }
+type T = S;
+impl T { fn bar() {} }
+
+fn main() { T::$0; }
+"#,
+ expect![[r#"
+ fn bar() fn()
+ fn foo() fn()
+ "#]],
+ );
+}
+
+#[test]
+fn completes_qualified_macros() {
+ check(
+ r#"
+#[macro_export]
+macro_rules! foo { () => {} }
+
+fn main() { let _ = crate::$0 }
+"#,
+ expect![[r#"
+ fn main() fn()
+ ma foo!(…) macro_rules! foo
+ "#]],
+ );
+}
+
+#[test]
+fn does_not_complete_non_fn_macros() {
+ check(
+ r#"
+mod m {
+ #[rustc_builtin_macro]
+ pub macro Clone {}
+}
+
+fn f() {m::$0}
+"#,
+ expect![[r#""#]],
+ );
+ check(
+ r#"
+mod m {
+ #[rustc_builtin_macro]
+ pub macro bench {}
+}
+
+fn f() {m::$0}
+"#,
+ expect![[r#""#]],
+ );
+}
+
+#[test]
+fn completes_reexported_items_under_correct_name() {
+ check(
+ r#"
+fn foo() { self::m::$0 }
+
+mod m {
+ pub use super::p::wrong_fn as right_fn;
+ pub use super::p::WRONG_CONST as RIGHT_CONST;
+ pub use super::p::WrongType as RightType;
+}
+mod p {
+ pub fn wrong_fn() {}
+ pub const WRONG_CONST: u32 = 1;
+ pub struct WrongType {};
+}
+"#,
+ expect![[r#"
+ ct RIGHT_CONST
+ fn right_fn() fn()
+ st RightType
+ "#]],
+ );
+
+ check_edit(
+ "RightType",
+ r#"
+fn foo() { self::m::$0 }
+
+mod m {
+ pub use super::p::wrong_fn as right_fn;
+ pub use super::p::WRONG_CONST as RIGHT_CONST;
+ pub use super::p::WrongType as RightType;
+}
+mod p {
+ pub fn wrong_fn() {}
+ pub const WRONG_CONST: u32 = 1;
+ pub struct WrongType {};
+}
+"#,
+ r#"
+fn foo() { self::m::RightType }
+
+mod m {
+ pub use super::p::wrong_fn as right_fn;
+ pub use super::p::WRONG_CONST as RIGHT_CONST;
+ pub use super::p::WrongType as RightType;
+}
+mod p {
+ pub fn wrong_fn() {}
+ pub const WRONG_CONST: u32 = 1;
+ pub struct WrongType {};
+}
+"#,
+ );
+}
+
+#[test]
+fn completes_in_simple_macro_call() {
+ check(
+ r#"
+macro_rules! m { ($e:expr) => { $e } }
+fn main() { m!(self::f$0); }
+fn foo() {}
+"#,
+ expect![[r#"
+ fn foo() fn()
+ fn main() fn()
+ "#]],
+ );
+}
+
+#[test]
+fn function_mod_share_name() {
+ check(
+ r#"
+fn foo() { self::m::$0 }
+
+mod m {
+ pub mod z {}
+ pub fn z() {}
+}
+"#,
+ expect![[r#"
+ fn z() fn()
+ md z
+ "#]],
+ );
+}
+
+#[test]
+fn completes_hashmap_new() {
+ check(
+ r#"
+struct RandomState;
+struct HashMap<K, V, S = RandomState> {}
+
+impl<K, V> HashMap<K, V, RandomState> {
+ pub fn new() -> HashMap<K, V, RandomState> { }
+}
+fn foo() {
+ HashMap::$0
+}
+"#,
+ expect![[r#"
+ fn new() fn() -> HashMap<K, V, RandomState>
+ "#]],
+ );
+}
+
+#[test]
+fn completes_variant_through_self() {
+ cov_mark::check!(completes_variant_through_self);
+ check(
+ r#"
+enum Foo {
+ Bar,
+ Baz,
+}
+
+impl Foo {
+ fn foo(self) {
+ Self::$0
+ }
+}
+"#,
+ expect![[r#"
+ ev Bar Bar
+ ev Baz Baz
+ me foo(…) fn(self)
+ "#]],
+ );
+}
+
+#[test]
+fn completes_non_exhaustive_variant_within_the_defining_crate() {
+ check(
+ r#"
+enum Foo {
+ #[non_exhaustive]
+ Bar,
+ Baz,
+}
+
+fn foo(self) {
+ Foo::$0
+}
+"#,
+ expect![[r#"
+ ev Bar Bar
+ ev Baz Baz
+ "#]],
+ );
+
+ check(
+ r#"
+//- /main.rs crate:main deps:e
+fn foo(self) {
+ e::Foo::$0
+}
+
+//- /e.rs crate:e
+enum Foo {
+ #[non_exhaustive]
+ Bar,
+ Baz,
+}
+"#,
+ expect![[r#"
+ ev Baz Baz
+ "#]],
+ );
+}
+
+#[test]
+fn completes_primitive_assoc_const() {
+ cov_mark::check!(completes_primitive_assoc_const);
+ check(
+ r#"
+//- /lib.rs crate:lib deps:core
+fn f() {
+ u8::$0
+}
+
+//- /core.rs crate:core
+#[lang = "u8"]
+impl u8 {
+ pub const MAX: Self = 255;
+
+ pub fn func(self) {}
+}
+"#,
+ expect![[r#"
+ ct MAX pub const MAX: Self
+ me func(…) fn(self)
+ "#]],
+ );
+}
+
+#[test]
+fn completes_variant_through_alias() {
+ cov_mark::check!(completes_variant_through_alias);
+ check(
+ r#"
+enum Foo {
+ Bar
+}
+type Foo2 = Foo;
+fn main() {
+ Foo2::$0
+}
+"#,
+ expect![[r#"
+ ev Bar Bar
+ "#]],
+ );
+}
+
+#[test]
+fn respects_doc_hidden2() {
+ check(
+ r#"
+//- /lib.rs crate:lib deps:dep
+fn f() {
+ dep::$0
+}
+
+//- /dep.rs crate:dep
+#[doc(hidden)]
+#[macro_export]
+macro_rules! m {
+ () => {}
+}
+
+#[doc(hidden)]
+pub fn f() {}
+
+#[doc(hidden)]
+pub struct S;
+
+#[doc(hidden)]
+pub mod m {}
+ "#,
+ expect![[r#""#]],
+ )
+}
+
+#[test]
+fn type_anchor_empty() {
+ check(
+ r#"
+trait Foo {
+ fn foo() -> Self;
+}
+struct Bar;
+impl Foo for Bar {
+ fn foo() -> {
+ Bar
+ }
+}
+fn bar() -> Bar {
+ <_>::$0
+}
+"#,
+ expect![[r#"
+ fn foo() (as Foo) fn() -> Self
+ "#]],
+ );
+}
+
+#[test]
+fn type_anchor_type() {
+ check(
+ r#"
+trait Foo {
+ fn foo() -> Self;
+}
+struct Bar;
+impl Bar {
+ fn bar() {}
+}
+impl Foo for Bar {
+ fn foo() -> {
+ Bar
+ }
+}
+fn bar() -> Bar {
+ <Bar>::$0
+}
+"#,
+ expect![[r#"
+ fn bar() fn()
+ fn foo() (as Foo) fn() -> Self
+ "#]],
+ );
+}
+
+#[test]
+fn type_anchor_type_trait() {
+ check(
+ r#"
+trait Foo {
+ fn foo() -> Self;
+}
+struct Bar;
+impl Bar {
+ fn bar() {}
+}
+impl Foo for Bar {
+ fn foo() -> {
+ Bar
+ }
+}
+fn bar() -> Bar {
+ <Bar as Foo>::$0
+}
+"#,
+ expect![[r#"
+ fn foo() (as Foo) fn() -> Self
+ "#]],
+ );
+}
+
+#[test]
+fn completes_fn_in_pub_trait_generated_by_macro() {
+ check(
+ r#"
+mod other_mod {
+ macro_rules! make_method {
+ ($name:ident) => {
+ fn $name(&self) {}
+ };
+ }
+
+ pub trait MyTrait {
+ make_method! { by_macro }
+ fn not_by_macro(&self) {}
+ }
+
+ pub struct Foo {}
+
+ impl MyTrait for Foo {}
+}
+
+fn main() {
+ use other_mod::{Foo, MyTrait};
+ let f = Foo {};
+ f.$0
+}
+"#,
+ expect![[r#"
+ me by_macro() (as MyTrait) fn(&self)
+ me not_by_macro() (as MyTrait) fn(&self)
+ "#]],
+ )
+}
+
+#[test]
+fn completes_fn_in_pub_trait_generated_by_recursive_macro() {
+ check(
+ r#"
+mod other_mod {
+ macro_rules! make_method {
+ ($name:ident) => {
+ fn $name(&self) {}
+ };
+ }
+
+ macro_rules! make_trait {
+ () => {
+ pub trait MyTrait {
+ make_method! { by_macro }
+ fn not_by_macro(&self) {}
+ }
+ }
+ }
+
+ make_trait!();
+
+ pub struct Foo {}
+
+ impl MyTrait for Foo {}
+}
+
+fn main() {
+ use other_mod::{Foo, MyTrait};
+ let f = Foo {};
+ f.$0
+}
+"#,
+ expect![[r#"
+ me by_macro() (as MyTrait) fn(&self)
+ me not_by_macro() (as MyTrait) fn(&self)
+ "#]],
+ )
+}
+
+#[test]
+fn completes_const_in_pub_trait_generated_by_macro() {
+ check(
+ r#"
+mod other_mod {
+ macro_rules! make_const {
+ ($name:ident) => {
+ const $name: u8 = 1;
+ };
+ }
+
+ pub trait MyTrait {
+ make_const! { by_macro }
+ }
+
+ pub struct Foo {}
+
+ impl MyTrait for Foo {}
+}
+
+fn main() {
+ use other_mod::{Foo, MyTrait};
+ let f = Foo {};
+ Foo::$0
+}
+"#,
+ expect![[r#"
+ ct by_macro (as MyTrait) pub const by_macro: u8
+ "#]],
+ )
+}
+
+#[test]
+fn completes_locals_from_macros() {
+ check(
+ r#"
+
+macro_rules! x {
+ ($x:ident, $expr:expr) => {
+ let $x = 0;
+ $expr
+ };
+}
+fn main() {
+ x! {
+ foobar, {
+ f$0
+ }
+ };
+}
+"#,
+ expect![[r#"
+ fn main() fn()
+ lc foobar i32
+ ma x!(…) macro_rules! x
+ bt u32
+ "#]],
+ )
+}
+
+#[test]
+fn regression_12644() {
+ check(
+ r#"
+macro_rules! __rust_force_expr {
+ ($e:expr) => {
+ $e
+ };
+}
+macro_rules! vec {
+ ($elem:expr) => {
+ __rust_force_expr!($elem)
+ };
+}
+
+struct Struct;
+impl Struct {
+ fn foo(self) {}
+}
+
+fn f() {
+ vec![Struct].$0;
+}
+"#,
+ expect![[r#"
+ me foo() fn(self)
+ "#]],
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/type_pos.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/type_pos.rs
new file mode 100644
index 000000000..f0b7726c5
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/type_pos.rs
@@ -0,0 +1,671 @@
+//! Completion tests for type position.
+use expect_test::{expect, Expect};
+
+use crate::tests::{completion_list, BASE_ITEMS_FIXTURE};
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(&format!("{}\n{}", BASE_ITEMS_FIXTURE, ra_fixture));
+ expect.assert_eq(&actual)
+}
+
+#[test]
+fn record_field_ty() {
+ check(
+ r#"
+struct Foo<'lt, T, const C: usize> {
+ f: $0
+}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ sp Self
+ st Foo<…>
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ tp T
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ )
+}
+
+#[test]
+fn tuple_struct_field() {
+ check(
+ r#"
+struct Foo<'lt, T, const C: usize>(f$0);
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ sp Self
+ st Foo<…>
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ tp T
+ un Union
+ bt u32
+ kw crate::
+ kw pub
+ kw pub(crate)
+ kw pub(super)
+ kw self::
+ "#]],
+ )
+}
+
+#[test]
+fn fn_return_type() {
+ check(
+ r#"
+fn x<'lt, T, const C: usize>() -> $0
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ tp T
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn fn_return_type_no_local_items() {
+ check(
+ r#"
+fn foo() -> B$0 {
+ struct Bar;
+ enum Baz {}
+ union Bax {
+ i: i32,
+ f: f32
+ }
+}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ bt u32
+ it ()
+ kw crate::
+ kw self::
+ "#]],
+ )
+}
+
+#[test]
+fn inferred_type_const() {
+ check(
+ r#"
+struct Foo<T>(T);
+const FOO: $0 = Foo(2);
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Foo<…>
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ bt u32
+ it Foo<i32>
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn inferred_type_closure_param() {
+ check(
+ r#"
+fn f1(f: fn(i32) -> i32) {}
+fn f2() {
+ f1(|x: $0);
+}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ bt u32
+ it i32
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn inferred_type_closure_return() {
+ check(
+ r#"
+fn f1(f: fn(u64) -> u64) {}
+fn f2() {
+ f1(|x| -> $0 {
+ x + 5
+ });
+}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ bt u32
+ it u64
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn inferred_type_fn_return() {
+ check(
+ r#"
+fn f2(x: u64) -> $0 {
+ x + 5
+}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ bt u32
+ it u64
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn inferred_type_fn_param() {
+ check(
+ r#"
+fn f1(x: i32) {}
+fn f2(x: $0) {
+ f1(x);
+}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ bt u32
+ it i32
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn inferred_type_not_in_the_scope() {
+ check(
+ r#"
+mod a {
+ pub struct Foo<T>(T);
+ pub fn x() -> Foo<Foo<i32>> {
+ Foo(Foo(2))
+ }
+}
+fn foo<'lt, T, const C: usize>() {
+ let local = ();
+ let foo: $0 = a::x();
+}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md a
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ tp T
+ un Union
+ bt u32
+ it a::Foo<a::Foo<i32>>
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn inferred_type_let() {
+ check(
+ r#"
+struct Foo<T>(T);
+fn foo<'lt, T, const C: usize>() {
+ let local = ();
+ let foo: $0 = Foo(2);
+}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Foo<…>
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ tp T
+ un Union
+ bt u32
+ it Foo<i32>
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn body_type_pos() {
+ check(
+ r#"
+fn foo<'lt, T, const C: usize>() {
+ let local = ();
+ let _: $0;
+}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ tp T
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+ check(
+ r#"
+fn foo<'lt, T, const C: usize>() {
+ let local = ();
+ let _: self::$0;
+}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ "#]],
+ );
+}
+
+#[test]
+fn completes_types_and_const_in_arg_list() {
+ cov_mark::check!(complete_assoc_type_in_generics_list);
+ check(
+ r#"
+trait Trait1 {
+ type Super;
+}
+trait Trait2: Trait1 {
+ type Foo;
+}
+
+fn foo<'lt, T: Trait2<$0>, const CONST_PARAM: usize>(_: T) {}
+"#,
+ expect![[r#"
+ ta Foo = (as Trait2) type Foo
+ ta Super = (as Trait1) type Super
+ "#]],
+ );
+ check(
+ r#"
+trait Trait1 {
+ type Super;
+}
+trait Trait2<T>: Trait1 {
+ type Foo;
+}
+
+fn foo<'lt, T: Trait2<$0>, const CONST_PARAM: usize>(_: T) {}
+"#,
+ expect![[r#"
+ ct CONST
+ cp CONST_PARAM
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ tt Trait1
+ tt Trait2
+ tp T
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+ check(
+ r#"
+trait Trait2 {
+ type Foo;
+}
+
+fn foo<'lt, T: Trait2<self::$0>, const CONST_PARAM: usize>(_: T) {}
+ "#,
+ expect![[r#"
+ ct CONST
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ tt Trait2
+ un Union
+ "#]],
+ );
+}
+
+#[test]
+fn no_assoc_completion_outside_type_bounds() {
+ check(
+ r#"
+struct S;
+trait Tr<T> {
+ type Ty;
+}
+
+impl Tr<$0
+ "#,
+ expect![[r#"
+ ct CONST
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ sp Self
+ st Record
+ st S
+ st Tuple
+ st Unit
+ tt Tr
+ tt Trait
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn enum_qualified() {
+ check(
+ r#"
+impl Enum {
+ type AssocType = ();
+ const ASSOC_CONST: () = ();
+ fn assoc_fn() {}
+}
+fn func(_: Enum::$0) {}
+"#,
+ expect![[r#"
+ ta AssocType type AssocType = ()
+ "#]],
+ );
+}
+
+#[test]
+fn completes_type_parameter_or_associated_type() {
+ check(
+ r#"
+trait MyTrait<T, U> {
+ type Item1;
+ type Item2;
+};
+
+fn f(t: impl MyTrait<u$0
+"#,
+ expect![[r#"
+ ct CONST
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt MyTrait
+ tt Trait
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+
+ check(
+ r#"
+trait MyTrait<T, U> {
+ type Item1;
+ type Item2;
+};
+
+fn f(t: impl MyTrait<u8, u$0
+"#,
+ expect![[r#"
+ ct CONST
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt MyTrait
+ tt Trait
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+
+ check(
+ r#"
+trait MyTrait<T, U> {
+ type Item1;
+ type Item2;
+};
+
+fn f(t: impl MyTrait<u8, u8, I$0
+"#,
+ expect![[r#"
+ ta Item1 = (as MyTrait) type Item1
+ ta Item2 = (as MyTrait) type Item2
+ "#]],
+ );
+}
+
+#[test]
+fn completes_type_parameter_or_associated_type_with_default_value() {
+ check(
+ r#"
+trait MyTrait<T, U = u8> {
+ type Item1;
+ type Item2;
+};
+
+fn f(t: impl MyTrait<u$0
+"#,
+ expect![[r#"
+ ct CONST
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt MyTrait
+ tt Trait
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+
+ check(
+ r#"
+trait MyTrait<T, U = u8> {
+ type Item1;
+ type Item2;
+};
+
+fn f(t: impl MyTrait<u8, u$0
+"#,
+ expect![[r#"
+ ct CONST
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt MyTrait
+ tt Trait
+ ta Item1 = (as MyTrait) type Item1
+ ta Item2 = (as MyTrait) type Item2
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+
+ check(
+ r#"
+trait MyTrait<T, U = u8> {
+ type Item1;
+ type Item2;
+};
+
+fn f(t: impl MyTrait<u8, u8, I$0
+"#,
+ expect![[r#"
+ ta Item1 = (as MyTrait) type Item1
+ ta Item2 = (as MyTrait) type Item2
+ "#]],
+ );
+}
+
+#[test]
+fn completes_types_after_associated_type() {
+ check(
+ r#"
+trait MyTrait {
+ type Item1;
+ type Item2;
+};
+
+fn f(t: impl MyTrait<Item1 = $0
+"#,
+ expect![[r#"
+ ct CONST
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt MyTrait
+ tt Trait
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+
+ check(
+ r#"
+trait MyTrait {
+ type Item1;
+ type Item2;
+};
+
+fn f(t: impl MyTrait<Item1 = u8, Item2 = $0
+"#,
+ expect![[r#"
+ ct CONST
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt MyTrait
+ tt Trait
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/use_tree.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/use_tree.rs
new file mode 100644
index 000000000..037d7dce5
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/use_tree.rs
@@ -0,0 +1,384 @@
+//! Completion tests for use trees.
+use expect_test::{expect, Expect};
+
+use crate::tests::completion_list;
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual)
+}
+
+#[test]
+fn use_tree_start() {
+ cov_mark::check!(unqualified_path_selected_only);
+ check(
+ r#"
+//- /lib.rs crate:main deps:other_crate
+use f$0
+
+struct Foo;
+enum FooBar {
+ Foo,
+ Bar
+}
+mod foo {}
+//- /other_crate/lib.rs crate:other_crate
+// nothing here
+"#,
+ expect![[r#"
+ en FooBar::
+ md foo
+ md other_crate
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn use_tree_start_abs() {
+ cov_mark::check!(use_tree_crate_roots_only);
+ check(
+ r#"
+//- /lib.rs crate:main deps:other_crate
+use ::f$0
+
+struct Foo;
+mod foo {}
+//- /other_crate/lib.rs crate:other_crate
+// nothing here
+"#,
+ expect![[r#"
+ md other_crate
+ "#]],
+ );
+}
+
+#[test]
+fn dont_complete_current_use() {
+ cov_mark::check!(dont_complete_current_use);
+ check(r#"use self::foo$0;"#, expect![[r#""#]]);
+ check(
+ r#"
+mod foo { pub struct S; }
+use self::{foo::*, bar$0};
+"#,
+ expect![[r#"
+ md foo
+ st S
+ "#]],
+ );
+}
+
+#[test]
+fn nested_use_tree() {
+ check(
+ r#"
+mod foo {
+ pub mod bar {
+ pub struct FooBar;
+ }
+}
+use foo::{bar::$0}
+"#,
+ expect![[r#"
+ st FooBar
+ "#]],
+ );
+ check(
+ r#"
+mod foo {
+ pub mod bar {
+ pub struct FooBar;
+ }
+}
+use foo::{$0}
+"#,
+ expect![[r#"
+ md bar
+ kw self
+ "#]],
+ );
+}
+
+#[test]
+fn deeply_nested_use_tree() {
+ check(
+ r#"
+mod foo {
+ pub mod bar {
+ pub mod baz {
+ pub struct FooBarBaz;
+ }
+ }
+}
+use foo::{bar::{baz::$0}}
+"#,
+ expect![[r#"
+ st FooBarBaz
+ "#]],
+ );
+ check(
+ r#"
+mod foo {
+ pub mod bar {
+ pub mod baz {
+ pub struct FooBarBaz;
+ }
+ }
+}
+use foo::{bar::{$0}}
+"#,
+ expect![[r#"
+ md baz
+ kw self
+ "#]],
+ );
+}
+
+#[test]
+fn plain_qualified_use_tree() {
+ check(
+ r#"
+use foo::$0
+
+mod foo {
+ struct Private;
+ pub struct Foo;
+ macro_rules! foo_ { {} => {} }
+ pub use foo_ as foo;
+}
+struct Bar;
+"#,
+ expect![[r#"
+ ma foo macro_rules! foo_
+ st Foo
+ "#]],
+ );
+}
+
+#[test]
+fn enum_plain_qualified_use_tree() {
+ cov_mark::check!(enum_plain_qualified_use_tree);
+ check(
+ r#"
+use Foo::$0
+
+enum Foo {
+ UnitVariant,
+ TupleVariant(),
+ RecordVariant {},
+}
+impl Foo {
+ const CONST: () = ()
+ fn func() {}
+}
+"#,
+ expect![[r#"
+ ev RecordVariant RecordVariant
+ ev TupleVariant TupleVariant
+ ev UnitVariant UnitVariant
+ "#]],
+ );
+}
+
+#[test]
+fn self_qualified_use_tree() {
+ check(
+ r#"
+use self::$0
+
+mod foo {}
+struct Bar;
+"#,
+ expect![[r#"
+ md foo
+ st Bar
+ "#]],
+ );
+}
+
+#[test]
+fn super_qualified_use_tree() {
+ check(
+ r#"
+mod bar {
+ use super::$0
+}
+
+mod foo {}
+struct Bar;
+"#,
+ expect![[r#"
+ md bar
+ md foo
+ st Bar
+ "#]],
+ );
+}
+
+#[test]
+fn super_super_qualified_use_tree() {
+ check(
+ r#"
+mod a {
+ const A: usize = 0;
+ mod b {
+ const B: usize = 0;
+ mod c { use super::super::$0 }
+ }
+}
+"#,
+ expect![[r#"
+ ct A
+ md b
+ kw super::
+ "#]],
+ );
+}
+
+#[test]
+fn crate_qualified_use_tree() {
+ check(
+ r#"
+use crate::$0
+
+mod foo {}
+struct Bar;
+"#,
+ expect![[r#"
+ md foo
+ st Bar
+ "#]],
+ );
+}
+
+#[test]
+fn extern_crate_qualified_use_tree() {
+ check(
+ r#"
+//- /lib.rs crate:main deps:other_crate
+use other_crate::$0
+//- /other_crate/lib.rs crate:other_crate
+pub struct Foo;
+pub mod foo {}
+"#,
+ expect![[r#"
+ md foo
+ st Foo
+ "#]],
+ );
+}
+
+#[test]
+fn pub_use_tree() {
+ check(
+ r#"
+pub struct X;
+pub mod bar {}
+pub use $0;
+"#,
+ expect![[r#"
+ md bar
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn pub_suggest_use_tree_super_acc_to_depth_in_tree() {
+ // https://github.com/rust-lang/rust-analyzer/issues/12439
+ // Check discussion in https://github.com/rust-lang/rust-analyzer/pull/12447
+
+ check(
+ r#"
+mod foo {
+ mod bar {
+ pub use super::$0;
+ }
+}
+"#,
+ expect![[r#"
+ md bar
+ kw super::
+ "#]],
+ );
+
+ // Not suggest super when at crate root
+ check(
+ r#"
+mod foo {
+ mod bar {
+ pub use super::super::$0;
+ }
+}
+"#,
+ expect![[r#"
+ md foo
+ "#]],
+ );
+
+ check(
+ r#"
+mod foo {
+ use $0;
+}
+"#,
+ expect![[r#"
+ kw crate::
+ kw self::
+ kw super::
+ "#]],
+ );
+
+ // Not suggest super after another kw in path ( here it is foo1 )
+ check(
+ r#"
+mod foo {
+ mod bar {
+ use super::super::foo1::$0;
+ }
+}
+
+mod foo1 {
+ pub mod bar1 {}
+}
+"#,
+ expect![[r#"
+ md bar1
+ "#]],
+ );
+}
+
+#[test]
+fn use_tree_braces_at_start() {
+ check(
+ r#"
+struct X;
+mod bar {}
+use {$0};
+"#,
+ expect![[r#"
+ md bar
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn impl_prefix_does_not_add_fn_snippet() {
+ // regression test for 7222
+ check(
+ r#"
+mod foo {
+ pub fn bar(x: u32) {}
+}
+use self::foo::impl$0
+"#,
+ expect![[r#"
+ fn bar fn(u32)
+ "#]],
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/visibility.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/visibility.rs
new file mode 100644
index 000000000..c18d6e66d
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/visibility.rs
@@ -0,0 +1,90 @@
+//! Completion tests for visibility modifiers.
+use expect_test::{expect, Expect};
+
+use crate::tests::{completion_list, completion_list_with_trigger_character};
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual)
+}
+
+fn check_with_trigger_character(ra_fixture: &str, trigger_character: char, expect: Expect) {
+ let actual = completion_list_with_trigger_character(ra_fixture, Some(trigger_character));
+ expect.assert_eq(&actual)
+}
+
+#[test]
+fn empty_pub() {
+ cov_mark::check!(kw_completion_in);
+ check_with_trigger_character(
+ r#"
+pub($0)
+"#,
+ '(',
+ expect![[r#"
+ kw crate
+ kw in
+ kw self
+ "#]],
+ );
+}
+
+#[test]
+fn after_in_kw() {
+ check(
+ r#"
+pub(in $0)
+"#,
+ expect![[r#"
+ kw crate
+ kw self
+ "#]],
+ );
+}
+
+#[test]
+fn qualified() {
+ cov_mark::check!(visibility_qualified);
+ check(
+ r#"
+mod foo {
+ pub(in crate::$0)
+}
+
+mod bar {}
+"#,
+ expect![[r#"
+ md foo
+ "#]],
+ );
+ check(
+ r#"
+mod qux {
+ mod foo {
+ pub(in crate::$0)
+ }
+ mod baz {}
+}
+
+mod bar {}
+"#,
+ expect![[r#"
+ md qux
+ "#]],
+ );
+ check(
+ r#"
+mod qux {
+ mod foo {
+ pub(in crate::qux::$0)
+ }
+ mod baz {}
+}
+
+mod bar {}
+"#,
+ expect![[r#"
+ md foo
+ "#]],
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/Cargo.toml b/src/tools/rust-analyzer/crates/ide-db/Cargo.toml
new file mode 100644
index 000000000..a1b0bd6cb
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/Cargo.toml
@@ -0,0 +1,39 @@
+[package]
+name = "ide-db"
+version = "0.0.0"
+description = "TBD"
+license = "MIT OR Apache-2.0"
+edition = "2021"
+rust-version = "1.57"
+
+[lib]
+doctest = false
+
+[dependencies]
+cov-mark = "2.0.0-pre.1"
+tracing = "0.1.35"
+rayon = "1.5.3"
+fst = { version = "0.4.7", default-features = false }
+rustc-hash = "1.1.0"
+once_cell = "1.12.0"
+either = "1.7.0"
+itertools = "0.10.3"
+arrayvec = "0.7.2"
+indexmap = "1.9.1"
+
+stdx = { path = "../stdx", version = "0.0.0" }
+parser = { path = "../parser", version = "0.0.0" }
+syntax = { path = "../syntax", version = "0.0.0" }
+text-edit = { path = "../text-edit", version = "0.0.0" }
+base-db = { path = "../base-db", version = "0.0.0" }
+profile = { path = "../profile", version = "0.0.0" }
+# ide should depend only on the top-level `hir` package. if you need
+# something from some `hir-xxx` subpackage, reexport the API via `hir`.
+hir = { path = "../hir", version = "0.0.0" }
+limit = { path = "../limit", version = "0.0.0" }
+
+[dev-dependencies]
+test-utils = { path = "../test-utils" }
+sourcegen = { path = "../sourcegen" }
+xshell = "0.2.2"
+expect-test = "1.4.0"
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/active_parameter.rs b/src/tools/rust-analyzer/crates/ide-db/src/active_parameter.rs
new file mode 100644
index 000000000..7303ef8b7
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/active_parameter.rs
@@ -0,0 +1,78 @@
+//! This module provides functionality for querying callable information about a token.
+
+use either::Either;
+use hir::{Semantics, Type};
+use syntax::{
+ ast::{self, HasArgList, HasName},
+ AstNode, SyntaxToken,
+};
+
+use crate::RootDatabase;
+
+#[derive(Debug)]
+pub struct ActiveParameter {
+ pub ty: Type,
+ pub pat: Either<ast::SelfParam, ast::Pat>,
+}
+
+impl ActiveParameter {
+ /// Returns information about the call argument this token is part of.
+ pub fn at_token(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> Option<Self> {
+ let (signature, active_parameter) = callable_for_token(sema, token)?;
+
+ let idx = active_parameter?;
+ let mut params = signature.params(sema.db);
+ if !(idx < params.len()) {
+ cov_mark::hit!(too_many_arguments);
+ return None;
+ }
+ let (pat, ty) = params.swap_remove(idx);
+ pat.map(|pat| ActiveParameter { ty, pat })
+ }
+
+ pub fn ident(&self) -> Option<ast::Name> {
+ self.pat.as_ref().right().and_then(|param| match param {
+ ast::Pat::IdentPat(ident) => ident.name(),
+ _ => None,
+ })
+ }
+}
+
+/// Returns a [`hir::Callable`] this token is a part of and its argument index of said callable.
+pub fn callable_for_token(
+ sema: &Semantics<'_, RootDatabase>,
+ token: SyntaxToken,
+) -> Option<(hir::Callable, Option<usize>)> {
+ // Find the calling expression and its NameRef
+ let parent = token.parent()?;
+ let calling_node = parent.ancestors().filter_map(ast::CallableExpr::cast).find(|it| {
+ it.arg_list()
+ .map_or(false, |it| it.syntax().text_range().contains(token.text_range().start()))
+ })?;
+
+ callable_for_node(sema, &calling_node, &token)
+}
+
+pub fn callable_for_node(
+ sema: &Semantics<'_, RootDatabase>,
+ calling_node: &ast::CallableExpr,
+ token: &SyntaxToken,
+) -> Option<(hir::Callable, Option<usize>)> {
+ let callable = match &calling_node {
+ ast::CallableExpr::Call(call) => {
+ let expr = call.expr()?;
+ sema.type_of_expr(&expr)?.adjusted().as_callable(sema.db)
+ }
+ ast::CallableExpr::MethodCall(call) => sema.resolve_method_call_as_callable(call),
+ }?;
+ let active_param = if let Some(arg_list) = calling_node.arg_list() {
+ let param = arg_list
+ .args()
+ .take_while(|arg| arg.syntax().text_range().end() <= token.text_range().start())
+ .count();
+ Some(param)
+ } else {
+ None
+ };
+ Some((callable, active_param))
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/apply_change.rs b/src/tools/rust-analyzer/crates/ide-db/src/apply_change.rs
new file mode 100644
index 000000000..98b0e9c94
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/apply_change.rs
@@ -0,0 +1,163 @@
+//! Applies changes to the IDE state transactionally.
+
+use std::sync::Arc;
+
+use base_db::{
+ salsa::{Database, Durability},
+ Change, SourceRootId,
+};
+use profile::{memory_usage, Bytes};
+use rustc_hash::FxHashSet;
+
+use crate::{symbol_index::SymbolsDatabase, RootDatabase};
+
+impl RootDatabase {
+ pub fn request_cancellation(&mut self) {
+ let _p = profile::span("RootDatabase::request_cancellation");
+ self.salsa_runtime_mut().synthetic_write(Durability::LOW);
+ }
+
+ pub fn apply_change(&mut self, change: Change) {
+ let _p = profile::span("RootDatabase::apply_change");
+ self.request_cancellation();
+ tracing::info!("apply_change {:?}", change);
+ if let Some(roots) = &change.roots {
+ let mut local_roots = FxHashSet::default();
+ let mut library_roots = FxHashSet::default();
+ for (idx, root) in roots.iter().enumerate() {
+ let root_id = SourceRootId(idx as u32);
+ if root.is_library {
+ library_roots.insert(root_id);
+ } else {
+ local_roots.insert(root_id);
+ }
+ }
+ self.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH);
+ self.set_library_roots_with_durability(Arc::new(library_roots), Durability::HIGH);
+ }
+ change.apply(self);
+ }
+
+ // Feature: Memory Usage
+ //
+ // Clears rust-analyzer's internal database and prints memory usage statistics.
+ //
+ // |===
+ // | Editor | Action Name
+ //
+ // | VS Code | **Rust Analyzer: Memory Usage (Clears Database)**
+ // |===
+ // image::https://user-images.githubusercontent.com/48062697/113065592-08559f00-91b1-11eb-8c96-64b88068ec02.gif[]
+ pub fn per_query_memory_usage(&mut self) -> Vec<(String, Bytes)> {
+ let mut acc: Vec<(String, Bytes)> = vec![];
+ macro_rules! purge_each_query {
+ ($($q:path)*) => {$(
+ let before = memory_usage().allocated;
+ $q.in_db(self).purge();
+ let after = memory_usage().allocated;
+ let q: $q = Default::default();
+ let name = format!("{:?}", q);
+ acc.push((name, before - after));
+ )*}
+ }
+ purge_each_query![
+ // SourceDatabase
+ base_db::ParseQuery
+ base_db::CrateGraphQuery
+
+ // SourceDatabaseExt
+ base_db::FileTextQuery
+ base_db::FileSourceRootQuery
+ base_db::SourceRootQuery
+ base_db::SourceRootCratesQuery
+
+ // AstDatabase
+ hir::db::AstIdMapQuery
+ hir::db::MacroArgTextQuery
+ hir::db::MacroDefQuery
+ hir::db::ParseMacroExpansionQuery
+ hir::db::MacroExpandQuery
+ hir::db::HygieneFrameQuery
+ hir::db::InternMacroCallQuery
+
+ // DefDatabase
+ hir::db::FileItemTreeQuery
+ hir::db::BlockDefMapQuery
+ hir::db::CrateDefMapQueryQuery
+ hir::db::FieldsAttrsQuery
+ hir::db::VariantsAttrsQuery
+ hir::db::FieldsAttrsSourceMapQuery
+ hir::db::VariantsAttrsSourceMapQuery
+ hir::db::StructDataQuery
+ hir::db::UnionDataQuery
+ hir::db::EnumDataQuery
+ hir::db::ImplDataQuery
+ hir::db::TraitDataQuery
+ hir::db::TypeAliasDataQuery
+ hir::db::FunctionDataQuery
+ hir::db::ConstDataQuery
+ hir::db::StaticDataQuery
+ hir::db::BodyWithSourceMapQuery
+ hir::db::BodyQuery
+ hir::db::ExprScopesQuery
+ hir::db::GenericParamsQuery
+ hir::db::AttrsQuery
+ hir::db::CrateLangItemsQuery
+ hir::db::LangItemQuery
+ hir::db::ImportMapQuery
+
+ // HirDatabase
+ hir::db::InferQueryQuery
+ hir::db::TyQuery
+ hir::db::ValueTyQuery
+ hir::db::ImplSelfTyQuery
+ hir::db::ImplTraitQuery
+ hir::db::FieldTypesQuery
+ hir::db::CallableItemSignatureQuery
+ hir::db::GenericPredicatesForParamQuery
+ hir::db::GenericPredicatesQuery
+ hir::db::GenericDefaultsQuery
+ hir::db::InherentImplsInCrateQuery
+ hir::db::TraitEnvironmentQuery
+ hir::db::TraitImplsInCrateQuery
+ hir::db::TraitImplsInDepsQuery
+ hir::db::AssociatedTyDataQuery
+ hir::db::AssociatedTyDataQuery
+ hir::db::TraitDatumQuery
+ hir::db::StructDatumQuery
+ hir::db::ImplDatumQuery
+ hir::db::FnDefDatumQuery
+ hir::db::ReturnTypeImplTraitsQuery
+ hir::db::InternCallableDefQuery
+ hir::db::InternTypeOrConstParamIdQuery
+ hir::db::InternImplTraitIdQuery
+ hir::db::InternClosureQuery
+ hir::db::AssociatedTyValueQuery
+ hir::db::TraitSolveQueryQuery
+ hir::db::InternTypeOrConstParamIdQuery
+
+ // SymbolsDatabase
+ crate::symbol_index::ModuleSymbolsQuery
+ crate::symbol_index::LibrarySymbolsQuery
+ crate::symbol_index::LocalRootsQuery
+ crate::symbol_index::LibraryRootsQuery
+
+ // LineIndexDatabase
+ crate::LineIndexQuery
+
+ // InternDatabase
+ hir::db::InternFunctionQuery
+ hir::db::InternStructQuery
+ hir::db::InternUnionQuery
+ hir::db::InternEnumQuery
+ hir::db::InternConstQuery
+ hir::db::InternStaticQuery
+ hir::db::InternTraitQuery
+ hir::db::InternTypeAliasQuery
+ hir::db::InternImplQuery
+ ];
+
+ acc.sort_by_key(|it| std::cmp::Reverse(it.1));
+ acc
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/assists.rs b/src/tools/rust-analyzer/crates/ide-db/src/assists.rs
new file mode 100644
index 000000000..da23763dc
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/assists.rs
@@ -0,0 +1,137 @@
+//! This module defines the `Assist` data structure. The actual assist live in
+//! the `ide_assists` downstream crate. We want to define the data structures in
+//! this low-level crate though, because `ide_diagnostics` also need them
+//! (fixits for diagnostics and assists are the same thing under the hood). We
+//! want to compile `ide_assists` and `ide_diagnostics` in parallel though, so
+//! we pull the common definitions upstream, to this crate.
+
+use std::str::FromStr;
+
+use syntax::TextRange;
+
+use crate::{label::Label, source_change::SourceChange};
+
+#[derive(Debug, Clone)]
+pub struct Assist {
+ pub id: AssistId,
+ /// Short description of the assist, as shown in the UI.
+ pub label: Label,
+ pub group: Option<GroupLabel>,
+ /// Target ranges are used to sort assists: the smaller the target range,
+ /// the more specific assist is, and so it should be sorted first.
+ pub target: TextRange,
+ /// Computing source change sometimes is much more costly then computing the
+ /// other fields. Additionally, the actual change is not required to show
+ /// the lightbulb UI, it only is needed when the user tries to apply an
+ /// assist. So, we compute it lazily: the API allow requesting assists with
+ /// or without source change. We could (and in fact, used to) distinguish
+ /// between resolved and unresolved assists at the type level, but this is
+ /// cumbersome, especially if you want to embed an assist into another data
+ /// structure, such as a diagnostic.
+ pub source_change: Option<SourceChange>,
+ pub trigger_signature_help: bool,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum AssistKind {
+ // FIXME: does the None variant make sense? Probably not.
+ None,
+
+ QuickFix,
+ Generate,
+ Refactor,
+ RefactorExtract,
+ RefactorInline,
+ RefactorRewrite,
+}
+
+impl AssistKind {
+ pub fn contains(self, other: AssistKind) -> bool {
+ if self == other {
+ return true;
+ }
+
+ match self {
+ AssistKind::None | AssistKind::Generate => true,
+ AssistKind::Refactor => matches!(
+ other,
+ AssistKind::RefactorExtract
+ | AssistKind::RefactorInline
+ | AssistKind::RefactorRewrite
+ ),
+ _ => false,
+ }
+ }
+
+ pub fn name(&self) -> &str {
+ match self {
+ AssistKind::None => "None",
+ AssistKind::QuickFix => "QuickFix",
+ AssistKind::Generate => "Generate",
+ AssistKind::Refactor => "Refactor",
+ AssistKind::RefactorExtract => "RefactorExtract",
+ AssistKind::RefactorInline => "RefactorInline",
+ AssistKind::RefactorRewrite => "RefactorRewrite",
+ }
+ }
+}
+
+impl FromStr for AssistKind {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "None" => Ok(AssistKind::None),
+ "QuickFix" => Ok(AssistKind::QuickFix),
+ "Generate" => Ok(AssistKind::Generate),
+ "Refactor" => Ok(AssistKind::Refactor),
+ "RefactorExtract" => Ok(AssistKind::RefactorExtract),
+ "RefactorInline" => Ok(AssistKind::RefactorInline),
+ "RefactorRewrite" => Ok(AssistKind::RefactorRewrite),
+ unknown => Err(format!("Unknown AssistKind: '{}'", unknown)),
+ }
+ }
+}
+
+/// Unique identifier of the assist, should not be shown to the user
+/// directly.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct AssistId(pub &'static str, pub AssistKind);
+
+/// A way to control how many asssist to resolve during the assist resolution.
+/// When an assist is resolved, its edits are calculated that might be costly to always do by default.
+#[derive(Debug)]
+pub enum AssistResolveStrategy {
+ /// No assists should be resolved.
+ None,
+ /// All assists should be resolved.
+ All,
+ /// Only a certain assist should be resolved.
+ Single(SingleResolve),
+}
+
+/// Hold the [`AssistId`] data of a certain assist to resolve.
+/// The original id object cannot be used due to a `'static` lifetime
+/// and the requirement to construct this struct dynamically during the resolve handling.
+#[derive(Debug)]
+pub struct SingleResolve {
+ /// The id of the assist.
+ pub assist_id: String,
+ // The kind of the assist.
+ pub assist_kind: AssistKind,
+}
+
+impl AssistResolveStrategy {
+ pub fn should_resolve(&self, id: &AssistId) -> bool {
+ match self {
+ AssistResolveStrategy::None => false,
+ AssistResolveStrategy::All => true,
+ AssistResolveStrategy::Single(single_resolve) => {
+ single_resolve.assist_id == id.0 && single_resolve.assist_kind == id.1
+ }
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct GroupLabel(pub String);
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/defs.rs b/src/tools/rust-analyzer/crates/ide-db/src/defs.rs
new file mode 100644
index 000000000..aeaca00ec
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/defs.rs
@@ -0,0 +1,545 @@
+//! `NameDefinition` keeps information about the element we want to search references for.
+//! The element is represented by `NameKind`. It's located inside some `container` and
+//! has a `visibility`, which defines a search scope.
+//! Note that the reference search is possible for not all of the classified items.
+
+// FIXME: this badly needs rename/rewrite (matklad, 2020-02-06).
+
+use arrayvec::ArrayVec;
+use hir::{
+ Adt, AsAssocItem, AssocItem, BuiltinAttr, BuiltinType, Const, Crate, DeriveHelper, Field,
+ Function, GenericParam, HasVisibility, Impl, ItemInNs, Label, Local, Macro, Module, ModuleDef,
+ Name, PathResolution, Semantics, Static, ToolModule, Trait, TypeAlias, Variant, Visibility,
+};
+use stdx::impl_from;
+use syntax::{
+ ast::{self, AstNode},
+ match_ast, SyntaxKind, SyntaxNode, SyntaxToken,
+};
+
+use crate::RootDatabase;
+
+// FIXME: a more precise name would probably be `Symbol`?
+#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
+pub enum Definition {
+ Macro(Macro),
+ Field(Field),
+ Module(Module),
+ Function(Function),
+ Adt(Adt),
+ Variant(Variant),
+ Const(Const),
+ Static(Static),
+ Trait(Trait),
+ TypeAlias(TypeAlias),
+ BuiltinType(BuiltinType),
+ SelfType(Impl),
+ Local(Local),
+ GenericParam(GenericParam),
+ Label(Label),
+ DeriveHelper(DeriveHelper),
+ BuiltinAttr(BuiltinAttr),
+ ToolModule(ToolModule),
+}
+
+impl Definition {
+ pub fn canonical_module_path(&self, db: &RootDatabase) -> Option<impl Iterator<Item = Module>> {
+ self.module(db).map(|it| it.path_to_root(db).into_iter().rev())
+ }
+
+ pub fn krate(&self, db: &RootDatabase) -> Option<Crate> {
+ Some(match self {
+ Definition::Module(m) => m.krate(),
+ _ => self.module(db)?.krate(),
+ })
+ }
+
+ pub fn module(&self, db: &RootDatabase) -> Option<Module> {
+ let module = match self {
+ Definition::Macro(it) => it.module(db),
+ Definition::Module(it) => it.parent(db)?,
+ Definition::Field(it) => it.parent_def(db).module(db),
+ Definition::Function(it) => it.module(db),
+ Definition::Adt(it) => it.module(db),
+ Definition::Const(it) => it.module(db),
+ Definition::Static(it) => it.module(db),
+ Definition::Trait(it) => it.module(db),
+ Definition::TypeAlias(it) => it.module(db),
+ Definition::Variant(it) => it.module(db),
+ Definition::SelfType(it) => it.module(db),
+ Definition::Local(it) => it.module(db),
+ Definition::GenericParam(it) => it.module(db),
+ Definition::Label(it) => it.module(db),
+ Definition::DeriveHelper(it) => it.derive().module(db),
+ Definition::BuiltinAttr(_) | Definition::BuiltinType(_) | Definition::ToolModule(_) => {
+ return None
+ }
+ };
+ Some(module)
+ }
+
+ pub fn visibility(&self, db: &RootDatabase) -> Option<Visibility> {
+ let vis = match self {
+ Definition::Field(sf) => sf.visibility(db),
+ Definition::Module(it) => it.visibility(db),
+ Definition::Function(it) => it.visibility(db),
+ Definition::Adt(it) => it.visibility(db),
+ Definition::Const(it) => it.visibility(db),
+ Definition::Static(it) => it.visibility(db),
+ Definition::Trait(it) => it.visibility(db),
+ Definition::TypeAlias(it) => it.visibility(db),
+ Definition::Variant(it) => it.visibility(db),
+ Definition::BuiltinType(_) => Visibility::Public,
+ Definition::Macro(_) => return None,
+ Definition::BuiltinAttr(_)
+ | Definition::ToolModule(_)
+ | Definition::SelfType(_)
+ | Definition::Local(_)
+ | Definition::GenericParam(_)
+ | Definition::Label(_)
+ | Definition::DeriveHelper(_) => return None,
+ };
+ Some(vis)
+ }
+
+ pub fn name(&self, db: &RootDatabase) -> Option<Name> {
+ let name = match self {
+ Definition::Macro(it) => it.name(db),
+ Definition::Field(it) => it.name(db),
+ Definition::Module(it) => it.name(db)?,
+ Definition::Function(it) => it.name(db),
+ Definition::Adt(it) => it.name(db),
+ Definition::Variant(it) => it.name(db),
+ Definition::Const(it) => it.name(db)?,
+ Definition::Static(it) => it.name(db),
+ Definition::Trait(it) => it.name(db),
+ Definition::TypeAlias(it) => it.name(db),
+ Definition::BuiltinType(it) => it.name(),
+ Definition::SelfType(_) => return None,
+ Definition::Local(it) => it.name(db),
+ Definition::GenericParam(it) => it.name(db),
+ Definition::Label(it) => it.name(db),
+ Definition::BuiltinAttr(_) => return None, // FIXME
+ Definition::ToolModule(_) => return None, // FIXME
+ Definition::DeriveHelper(it) => it.name(db),
+ };
+ Some(name)
+ }
+}
+
+#[derive(Debug)]
+pub enum IdentClass {
+ NameClass(NameClass),
+ NameRefClass(NameRefClass),
+}
+
+impl IdentClass {
+ pub fn classify_node(
+ sema: &Semantics<'_, RootDatabase>,
+ node: &SyntaxNode,
+ ) -> Option<IdentClass> {
+ match_ast! {
+ match node {
+ ast::Name(name) => NameClass::classify(sema, &name).map(IdentClass::NameClass),
+ ast::NameRef(name_ref) => NameRefClass::classify(sema, &name_ref).map(IdentClass::NameRefClass),
+ ast::Lifetime(lifetime) => {
+ NameClass::classify_lifetime(sema, &lifetime)
+ .map(IdentClass::NameClass)
+ .or_else(|| NameRefClass::classify_lifetime(sema, &lifetime).map(IdentClass::NameRefClass))
+ },
+ _ => None,
+ }
+ }
+ }
+
+ pub fn classify_token(
+ sema: &Semantics<'_, RootDatabase>,
+ token: &SyntaxToken,
+ ) -> Option<IdentClass> {
+ let parent = token.parent()?;
+ Self::classify_node(sema, &parent)
+ }
+
+ pub fn classify_lifetime(
+ sema: &Semantics<'_, RootDatabase>,
+ lifetime: &ast::Lifetime,
+ ) -> Option<IdentClass> {
+ NameRefClass::classify_lifetime(sema, lifetime)
+ .map(IdentClass::NameRefClass)
+ .or_else(|| NameClass::classify_lifetime(sema, lifetime).map(IdentClass::NameClass))
+ }
+
+ pub fn definitions(self) -> ArrayVec<Definition, 2> {
+ let mut res = ArrayVec::new();
+ match self {
+ IdentClass::NameClass(NameClass::Definition(it) | NameClass::ConstReference(it)) => {
+ res.push(it)
+ }
+ IdentClass::NameClass(NameClass::PatFieldShorthand { local_def, field_ref }) => {
+ res.push(Definition::Local(local_def));
+ res.push(Definition::Field(field_ref));
+ }
+ IdentClass::NameRefClass(NameRefClass::Definition(it)) => res.push(it),
+ IdentClass::NameRefClass(NameRefClass::FieldShorthand { local_ref, field_ref }) => {
+ res.push(Definition::Local(local_ref));
+ res.push(Definition::Field(field_ref));
+ }
+ }
+ res
+ }
+}
+
+/// On a first blush, a single `ast::Name` defines a single definition at some
+/// scope. That is, that, by just looking at the syntactical category, we can
+/// unambiguously define the semantic category.
+///
+/// Sadly, that's not 100% true, there are special cases. To make sure that
+/// callers handle all the special cases correctly via exhaustive matching, we
+/// add a [`NameClass`] enum which lists all of them!
+///
+/// A model special case is `None` constant in pattern.
+#[derive(Debug)]
+pub enum NameClass {
+ Definition(Definition),
+ /// `None` in `if let None = Some(82) {}`.
+ /// Syntactically, it is a name, but semantically it is a reference.
+ ConstReference(Definition),
+ /// `field` in `if let Foo { field } = foo`. Here, `ast::Name` both introduces
+ /// a definition into a local scope, and refers to an existing definition.
+ PatFieldShorthand {
+ local_def: Local,
+ field_ref: Field,
+ },
+}
+
+impl NameClass {
+ /// `Definition` defined by this name.
+ pub fn defined(self) -> Option<Definition> {
+ let res = match self {
+ NameClass::Definition(it) => it,
+ NameClass::ConstReference(_) => return None,
+ NameClass::PatFieldShorthand { local_def, field_ref: _ } => {
+ Definition::Local(local_def)
+ }
+ };
+ Some(res)
+ }
+
+ pub fn classify(sema: &Semantics<'_, RootDatabase>, name: &ast::Name) -> Option<NameClass> {
+ let _p = profile::span("classify_name");
+
+ let parent = name.syntax().parent()?;
+
+ let definition = match_ast! {
+ match parent {
+ ast::Item(it) => classify_item(sema, it)?,
+ ast::IdentPat(it) => return classify_ident_pat(sema, it),
+ ast::Rename(it) => classify_rename(sema, it)?,
+ ast::SelfParam(it) => Definition::Local(sema.to_def(&it)?),
+ ast::RecordField(it) => Definition::Field(sema.to_def(&it)?),
+ ast::Variant(it) => Definition::Variant(sema.to_def(&it)?),
+ ast::TypeParam(it) => Definition::GenericParam(sema.to_def(&it)?.into()),
+ ast::ConstParam(it) => Definition::GenericParam(sema.to_def(&it)?.into()),
+ _ => return None,
+ }
+ };
+ return Some(NameClass::Definition(definition));
+
+ fn classify_item(
+ sema: &Semantics<'_, RootDatabase>,
+ item: ast::Item,
+ ) -> Option<Definition> {
+ let definition = match item {
+ ast::Item::MacroRules(it) => {
+ Definition::Macro(sema.to_def(&ast::Macro::MacroRules(it))?)
+ }
+ ast::Item::MacroDef(it) => {
+ Definition::Macro(sema.to_def(&ast::Macro::MacroDef(it))?)
+ }
+ ast::Item::Const(it) => Definition::Const(sema.to_def(&it)?),
+ ast::Item::Fn(it) => {
+ let def = sema.to_def(&it)?;
+ def.as_proc_macro(sema.db)
+ .map(Definition::Macro)
+ .unwrap_or(Definition::Function(def))
+ }
+ ast::Item::Module(it) => Definition::Module(sema.to_def(&it)?),
+ ast::Item::Static(it) => Definition::Static(sema.to_def(&it)?),
+ ast::Item::Trait(it) => Definition::Trait(sema.to_def(&it)?),
+ ast::Item::TypeAlias(it) => Definition::TypeAlias(sema.to_def(&it)?),
+ ast::Item::Enum(it) => Definition::Adt(hir::Adt::Enum(sema.to_def(&it)?)),
+ ast::Item::Struct(it) => Definition::Adt(hir::Adt::Struct(sema.to_def(&it)?)),
+ ast::Item::Union(it) => Definition::Adt(hir::Adt::Union(sema.to_def(&it)?)),
+ _ => return None,
+ };
+ Some(definition)
+ }
+
+ fn classify_ident_pat(
+ sema: &Semantics<'_, RootDatabase>,
+ ident_pat: ast::IdentPat,
+ ) -> Option<NameClass> {
+ if let Some(def) = sema.resolve_bind_pat_to_const(&ident_pat) {
+ return Some(NameClass::ConstReference(Definition::from(def)));
+ }
+
+ let local = sema.to_def(&ident_pat)?;
+ let pat_parent = ident_pat.syntax().parent();
+ if let Some(record_pat_field) = pat_parent.and_then(ast::RecordPatField::cast) {
+ if record_pat_field.name_ref().is_none() {
+ if let Some(field) = sema.resolve_record_pat_field(&record_pat_field) {
+ return Some(NameClass::PatFieldShorthand {
+ local_def: local,
+ field_ref: field,
+ });
+ }
+ }
+ }
+ Some(NameClass::Definition(Definition::Local(local)))
+ }
+
+ fn classify_rename(
+ sema: &Semantics<'_, RootDatabase>,
+ rename: ast::Rename,
+ ) -> Option<Definition> {
+ if let Some(use_tree) = rename.syntax().parent().and_then(ast::UseTree::cast) {
+ let path = use_tree.path()?;
+ sema.resolve_path(&path).map(Definition::from)
+ } else {
+ let extern_crate = rename.syntax().parent().and_then(ast::ExternCrate::cast)?;
+ let krate = sema.resolve_extern_crate(&extern_crate)?;
+ let root_module = krate.root_module(sema.db);
+ Some(Definition::Module(root_module))
+ }
+ }
+ }
+
+ pub fn classify_lifetime(
+ sema: &Semantics<'_, RootDatabase>,
+ lifetime: &ast::Lifetime,
+ ) -> Option<NameClass> {
+ let _p = profile::span("classify_lifetime").detail(|| lifetime.to_string());
+ let parent = lifetime.syntax().parent()?;
+
+ if let Some(it) = ast::LifetimeParam::cast(parent.clone()) {
+ sema.to_def(&it).map(Into::into).map(Definition::GenericParam)
+ } else if let Some(it) = ast::Label::cast(parent) {
+ sema.to_def(&it).map(Definition::Label)
+ } else {
+ None
+ }
+ .map(NameClass::Definition)
+ }
+}
+
+/// This is similar to [`NameClass`], but works for [`ast::NameRef`] rather than
+/// for [`ast::Name`]. Similarly, what looks like a reference in syntax is a
+/// reference most of the time, but there are a couple of annoying exceptions.
+///
+/// A model special case is field shorthand syntax, which uses a single
+/// reference to point to two different defs.
+#[derive(Debug)]
+pub enum NameRefClass {
+ Definition(Definition),
+ FieldShorthand { local_ref: Local, field_ref: Field },
+}
+
+impl NameRefClass {
+ // Note: we don't have unit-tests for this rather important function.
+ // It is primarily exercised via goto definition tests in `ide`.
+ pub fn classify(
+ sema: &Semantics<'_, RootDatabase>,
+ name_ref: &ast::NameRef,
+ ) -> Option<NameRefClass> {
+ let _p = profile::span("classify_name_ref").detail(|| name_ref.to_string());
+
+ let parent = name_ref.syntax().parent()?;
+
+ if let Some(record_field) = ast::RecordExprField::for_field_name(name_ref) {
+ if let Some((field, local, _)) = sema.resolve_record_field(&record_field) {
+ let res = match local {
+ None => NameRefClass::Definition(Definition::Field(field)),
+ Some(local) => {
+ NameRefClass::FieldShorthand { field_ref: field, local_ref: local }
+ }
+ };
+ return Some(res);
+ }
+ }
+
+ if let Some(path) = ast::PathSegment::cast(parent.clone()).map(|it| it.parent_path()) {
+ if path.parent_path().is_none() {
+ if let Some(macro_call) = path.syntax().parent().and_then(ast::MacroCall::cast) {
+ // Only use this to resolve to macro calls for last segments as qualifiers resolve
+ // to modules below.
+ if let Some(macro_def) = sema.resolve_macro_call(&macro_call) {
+ return Some(NameRefClass::Definition(Definition::Macro(macro_def)));
+ }
+ }
+ }
+ return sema.resolve_path(&path).map(Into::into).map(NameRefClass::Definition);
+ }
+
+ match_ast! {
+ match parent {
+ ast::MethodCallExpr(method_call) => {
+ sema.resolve_method_call(&method_call)
+ .map(Definition::Function)
+ .map(NameRefClass::Definition)
+ },
+ ast::FieldExpr(field_expr) => {
+ sema.resolve_field(&field_expr)
+ .map(Definition::Field)
+ .map(NameRefClass::Definition)
+ },
+ ast::RecordPatField(record_pat_field) => {
+ sema.resolve_record_pat_field(&record_pat_field)
+ .map(Definition::Field)
+ .map(NameRefClass::Definition)
+ },
+ ast::AssocTypeArg(_) => {
+ // `Trait<Assoc = Ty>`
+ // ^^^^^
+ let containing_path = name_ref.syntax().ancestors().find_map(ast::Path::cast)?;
+ let resolved = sema.resolve_path(&containing_path)?;
+ if let PathResolution::Def(ModuleDef::Trait(tr)) = resolved {
+ if let Some(ty) = tr
+ .items_with_supertraits(sema.db)
+ .iter()
+ .filter_map(|&assoc| match assoc {
+ hir::AssocItem::TypeAlias(it) => Some(it),
+ _ => None,
+ })
+ .find(|alias| alias.name(sema.db).to_smol_str() == name_ref.text().as_str())
+ {
+ return Some(NameRefClass::Definition(Definition::TypeAlias(ty)));
+ }
+ }
+ None
+ },
+ ast::ExternCrate(extern_crate) => {
+ let krate = sema.resolve_extern_crate(&extern_crate)?;
+ let root_module = krate.root_module(sema.db);
+ Some(NameRefClass::Definition(Definition::Module(root_module)))
+ },
+ _ => None
+ }
+ }
+ }
+
+ pub fn classify_lifetime(
+ sema: &Semantics<'_, RootDatabase>,
+ lifetime: &ast::Lifetime,
+ ) -> Option<NameRefClass> {
+ let _p = profile::span("classify_lifetime_ref").detail(|| lifetime.to_string());
+ let parent = lifetime.syntax().parent()?;
+ match parent.kind() {
+ SyntaxKind::BREAK_EXPR | SyntaxKind::CONTINUE_EXPR => {
+ sema.resolve_label(lifetime).map(Definition::Label).map(NameRefClass::Definition)
+ }
+ SyntaxKind::LIFETIME_ARG
+ | SyntaxKind::SELF_PARAM
+ | SyntaxKind::TYPE_BOUND
+ | SyntaxKind::WHERE_PRED
+ | SyntaxKind::REF_TYPE => sema
+ .resolve_lifetime_param(lifetime)
+ .map(GenericParam::LifetimeParam)
+ .map(Definition::GenericParam)
+ .map(NameRefClass::Definition),
+ // lifetime bounds, as in the 'b in 'a: 'b aren't wrapped in TypeBound nodes so we gotta check
+ // if our lifetime is in a LifetimeParam without being the constrained lifetime
+ _ if ast::LifetimeParam::cast(parent).and_then(|param| param.lifetime()).as_ref()
+ != Some(lifetime) =>
+ {
+ sema.resolve_lifetime_param(lifetime)
+ .map(GenericParam::LifetimeParam)
+ .map(Definition::GenericParam)
+ .map(NameRefClass::Definition)
+ }
+ _ => None,
+ }
+ }
+}
+
+impl_from!(
+ Field, Module, Function, Adt, Variant, Const, Static, Trait, TypeAlias, BuiltinType, Local,
+ GenericParam, Label, Macro
+ for Definition
+);
+
+impl From<Impl> for Definition {
+ fn from(impl_: Impl) -> Self {
+ Definition::SelfType(impl_)
+ }
+}
+
+impl AsAssocItem for Definition {
+ fn as_assoc_item(self, db: &dyn hir::db::HirDatabase) -> Option<AssocItem> {
+ match self {
+ Definition::Function(it) => it.as_assoc_item(db),
+ Definition::Const(it) => it.as_assoc_item(db),
+ Definition::TypeAlias(it) => it.as_assoc_item(db),
+ _ => None,
+ }
+ }
+}
+
+impl From<AssocItem> for Definition {
+ fn from(assoc_item: AssocItem) -> Self {
+ match assoc_item {
+ AssocItem::Function(it) => Definition::Function(it),
+ AssocItem::Const(it) => Definition::Const(it),
+ AssocItem::TypeAlias(it) => Definition::TypeAlias(it),
+ }
+ }
+}
+
+impl From<PathResolution> for Definition {
+ fn from(path_resolution: PathResolution) -> Self {
+ match path_resolution {
+ PathResolution::Def(def) => def.into(),
+ PathResolution::Local(local) => Definition::Local(local),
+ PathResolution::TypeParam(par) => Definition::GenericParam(par.into()),
+ PathResolution::ConstParam(par) => Definition::GenericParam(par.into()),
+ PathResolution::SelfType(impl_def) => Definition::SelfType(impl_def),
+ PathResolution::BuiltinAttr(attr) => Definition::BuiltinAttr(attr),
+ PathResolution::ToolModule(tool) => Definition::ToolModule(tool),
+ PathResolution::DeriveHelper(helper) => Definition::DeriveHelper(helper),
+ }
+ }
+}
+
+impl From<ModuleDef> for Definition {
+ fn from(def: ModuleDef) -> Self {
+ match def {
+ ModuleDef::Module(it) => Definition::Module(it),
+ ModuleDef::Function(it) => Definition::Function(it),
+ ModuleDef::Adt(it) => Definition::Adt(it),
+ ModuleDef::Variant(it) => Definition::Variant(it),
+ ModuleDef::Const(it) => Definition::Const(it),
+ ModuleDef::Static(it) => Definition::Static(it),
+ ModuleDef::Trait(it) => Definition::Trait(it),
+ ModuleDef::TypeAlias(it) => Definition::TypeAlias(it),
+ ModuleDef::Macro(it) => Definition::Macro(it),
+ ModuleDef::BuiltinType(it) => Definition::BuiltinType(it),
+ }
+ }
+}
+
+impl From<Definition> for Option<ItemInNs> {
+ fn from(def: Definition) -> Self {
+ let item = match def {
+ Definition::Module(it) => ModuleDef::Module(it),
+ Definition::Function(it) => ModuleDef::Function(it),
+ Definition::Adt(it) => ModuleDef::Adt(it),
+ Definition::Variant(it) => ModuleDef::Variant(it),
+ Definition::Const(it) => ModuleDef::Const(it),
+ Definition::Static(it) => ModuleDef::Static(it),
+ Definition::Trait(it) => ModuleDef::Trait(it),
+ Definition::TypeAlias(it) => ModuleDef::TypeAlias(it),
+ Definition::BuiltinType(it) => ModuleDef::BuiltinType(it),
+ _ => return None,
+ };
+ Some(ItemInNs::from(item))
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/famous_defs.rs b/src/tools/rust-analyzer/crates/ide-db/src/famous_defs.rs
new file mode 100644
index 000000000..c8341fed1
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/famous_defs.rs
@@ -0,0 +1,185 @@
+//! See [`FamousDefs`].
+
+use base_db::{CrateOrigin, LangCrateOrigin, SourceDatabase};
+use hir::{Crate, Enum, Macro, Module, ScopeDef, Semantics, Trait};
+
+use crate::RootDatabase;
+
+/// Helps with finding well-know things inside the standard library. This is
+/// somewhat similar to the known paths infra inside hir, but it different; We
+/// want to make sure that IDE specific paths don't become interesting inside
+/// the compiler itself as well.
+///
+/// Note that, by default, rust-analyzer tests **do not** include core or std
+/// libraries. If you are writing tests for functionality using [`FamousDefs`],
+/// you'd want to include minicore (see `test_utils::MiniCore`) declaration at
+/// the start of your tests:
+///
+/// ```
+/// //- minicore: iterator, ord, derive
+/// ```
+pub struct FamousDefs<'a, 'b>(pub &'a Semantics<'b, RootDatabase>, pub Crate);
+
+#[allow(non_snake_case)]
+impl FamousDefs<'_, '_> {
+ pub fn std(&self) -> Option<Crate> {
+ self.find_lang_crate(LangCrateOrigin::Std)
+ }
+
+ pub fn core(&self) -> Option<Crate> {
+ self.find_lang_crate(LangCrateOrigin::Core)
+ }
+
+ pub fn alloc(&self) -> Option<Crate> {
+ self.find_lang_crate(LangCrateOrigin::Alloc)
+ }
+
+ pub fn test(&self) -> Option<Crate> {
+ self.find_lang_crate(LangCrateOrigin::Test)
+ }
+
+ pub fn proc_macro(&self) -> Option<Crate> {
+ self.find_lang_crate(LangCrateOrigin::ProcMacro)
+ }
+
+ pub fn core_cmp_Ord(&self) -> Option<Trait> {
+ self.find_trait("core:cmp:Ord")
+ }
+
+ pub fn core_convert_From(&self) -> Option<Trait> {
+ self.find_trait("core:convert:From")
+ }
+
+ pub fn core_convert_Into(&self) -> Option<Trait> {
+ self.find_trait("core:convert:Into")
+ }
+
+ pub fn core_option_Option(&self) -> Option<Enum> {
+ self.find_enum("core:option:Option")
+ }
+
+ pub fn core_result_Result(&self) -> Option<Enum> {
+ self.find_enum("core:result:Result")
+ }
+
+ pub fn core_default_Default(&self) -> Option<Trait> {
+ self.find_trait("core:default:Default")
+ }
+
+ pub fn core_iter_Iterator(&self) -> Option<Trait> {
+ self.find_trait("core:iter:traits:iterator:Iterator")
+ }
+
+ pub fn core_iter_IntoIterator(&self) -> Option<Trait> {
+ self.find_trait("core:iter:traits:collect:IntoIterator")
+ }
+
+ pub fn core_iter(&self) -> Option<Module> {
+ self.find_module("core:iter")
+ }
+
+ pub fn core_ops_Deref(&self) -> Option<Trait> {
+ self.find_trait("core:ops:Deref")
+ }
+
+ pub fn core_ops_DerefMut(&self) -> Option<Trait> {
+ self.find_trait("core:ops:DerefMut")
+ }
+
+ pub fn core_convert_AsRef(&self) -> Option<Trait> {
+ self.find_trait("core:convert:AsRef")
+ }
+
+ pub fn core_ops_ControlFlow(&self) -> Option<Enum> {
+ self.find_enum("core:ops:ControlFlow")
+ }
+
+ pub fn core_ops_Drop(&self) -> Option<Trait> {
+ self.find_trait("core:ops:Drop")
+ }
+
+ pub fn core_marker_Copy(&self) -> Option<Trait> {
+ self.find_trait("core:marker:Copy")
+ }
+
+ pub fn core_macros_builtin_derive(&self) -> Option<Macro> {
+ self.find_macro("core:macros:builtin:derive")
+ }
+
+ pub fn builtin_crates(&self) -> impl Iterator<Item = Crate> {
+ IntoIterator::into_iter([
+ self.std(),
+ self.core(),
+ self.alloc(),
+ self.test(),
+ self.proc_macro(),
+ ])
+ .flatten()
+ }
+
+ fn find_trait(&self, path: &str) -> Option<Trait> {
+ match self.find_def(path)? {
+ hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it),
+ _ => None,
+ }
+ }
+
+ fn find_macro(&self, path: &str) -> Option<Macro> {
+ match self.find_def(path)? {
+ hir::ScopeDef::ModuleDef(hir::ModuleDef::Macro(it)) => Some(it),
+ _ => None,
+ }
+ }
+
+ fn find_enum(&self, path: &str) -> Option<Enum> {
+ match self.find_def(path)? {
+ hir::ScopeDef::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(it))) => Some(it),
+ _ => None,
+ }
+ }
+
+ fn find_module(&self, path: &str) -> Option<Module> {
+ match self.find_def(path)? {
+ hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(it)) => Some(it),
+ _ => None,
+ }
+ }
+
+ fn find_lang_crate(&self, origin: LangCrateOrigin) -> Option<Crate> {
+ let krate = self.1;
+ let db = self.0.db;
+ let crate_graph = self.0.db.crate_graph();
+ let res = krate
+ .dependencies(db)
+ .into_iter()
+ .find(|dep| crate_graph[dep.krate.into()].origin == CrateOrigin::Lang(origin))?
+ .krate;
+ Some(res)
+ }
+
+ fn find_def(&self, path: &str) -> Option<ScopeDef> {
+ let db = self.0.db;
+ let mut path = path.split(':');
+ let trait_ = path.next_back()?;
+ let lang_crate = path.next()?;
+ let lang_crate = match LangCrateOrigin::from(lang_crate) {
+ LangCrateOrigin::Other => return None,
+ lang_crate => lang_crate,
+ };
+ let std_crate = self.find_lang_crate(lang_crate)?;
+ let mut module = std_crate.root_module(db);
+ for segment in path {
+ module = module.children(db).find_map(|child| {
+ let name = child.name(db)?;
+ if name.to_smol_str() == segment {
+ Some(child)
+ } else {
+ None
+ }
+ })?;
+ }
+ let def =
+ module.scope(db, None).into_iter().find(|(name, _def)| name.to_smol_str() == trait_)?.1;
+ Some(def)
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs b/src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs
new file mode 100644
index 000000000..64dd2bb5f
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs
@@ -0,0 +1,7682 @@
+//! Generated by `sourcegen_lints`, do not edit by hand.
+
+#[derive(Clone)]
+pub struct Lint {
+ pub label: &'static str,
+ pub description: &'static str,
+}
+pub struct LintGroup {
+ pub lint: Lint,
+ pub children: &'static [&'static str],
+}
+pub const DEFAULT_LINTS: &[Lint] = &[
+ Lint {
+ label: "absolute_paths_not_starting_with_crate",
+ description: r##"fully qualified paths that start with a module name instead of `crate`, `self`, or an extern crate name"##,
+ },
+ Lint { label: "ambiguous_associated_items", description: r##"ambiguous associated items"## },
+ Lint { label: "anonymous_parameters", description: r##"detects anonymous parameters"## },
+ Lint { label: "arithmetic_overflow", description: r##"arithmetic operation overflows"## },
+ Lint {
+ label: "array_into_iter",
+ description: r##"detects calling `into_iter` on arrays in Rust 2015 and 2018"##,
+ },
+ Lint {
+ label: "asm_sub_register",
+ description: r##"using only a subset of a register for inline asm inputs"##,
+ },
+ Lint { label: "bad_asm_style", description: r##"incorrect use of inline assembly"## },
+ Lint {
+ label: "bare_trait_objects",
+ description: r##"suggest using `dyn Trait` for trait objects"##,
+ },
+ Lint {
+ label: "bindings_with_variant_name",
+ description: r##"detects pattern bindings with the same name as one of the matched variants"##,
+ },
+ Lint { label: "box_pointers", description: r##"use of owned (Box type) heap memory"## },
+ Lint {
+ label: "break_with_label_and_loop",
+ description: r##"`break` expression with label and unlabeled loop as value expression"##,
+ },
+ Lint {
+ label: "cenum_impl_drop_cast",
+ description: r##"a C-like enum implementing Drop is cast"##,
+ },
+ Lint {
+ label: "clashing_extern_declarations",
+ description: r##"detects when an extern fn has been declared with the same name but different types"##,
+ },
+ Lint {
+ label: "coherence_leak_check",
+ description: r##"distinct impls distinguished only by the leak-check code"##,
+ },
+ Lint {
+ label: "conflicting_repr_hints",
+ description: r##"conflicts between `#[repr(..)]` hints that were previously accepted and used in practice"##,
+ },
+ Lint {
+ label: "confusable_idents",
+ description: r##"detects visually confusable pairs between identifiers"##,
+ },
+ Lint {
+ label: "const_err",
+ description: r##"constant evaluation encountered erroneous expression"##,
+ },
+ Lint {
+ label: "const_evaluatable_unchecked",
+ description: r##"detects a generic constant is used in a type without a emitting a warning"##,
+ },
+ Lint {
+ label: "const_item_mutation",
+ description: r##"detects attempts to mutate a `const` item"##,
+ },
+ Lint { label: "dead_code", description: r##"detect unused, unexported items"## },
+ Lint { label: "deprecated", description: r##"detects use of deprecated items"## },
+ Lint {
+ label: "deprecated_in_future",
+ description: r##"detects use of items that will be deprecated in a future version"##,
+ },
+ Lint {
+ label: "deref_into_dyn_supertrait",
+ description: r##"`Deref` implementation usage with a supertrait trait object for output might be shadowed in the future"##,
+ },
+ Lint {
+ label: "deref_nullptr",
+ description: r##"detects when an null pointer is dereferenced"##,
+ },
+ Lint {
+ label: "drop_bounds",
+ description: r##"bounds of the form `T: Drop` are most likely incorrect"##,
+ },
+ Lint {
+ label: "dyn_drop",
+ description: r##"trait objects of the form `dyn Drop` are useless"##,
+ },
+ Lint {
+ label: "elided_lifetimes_in_paths",
+ description: r##"hidden lifetime parameters in types are deprecated"##,
+ },
+ Lint {
+ label: "ellipsis_inclusive_range_patterns",
+ description: r##"`...` range patterns are deprecated"##,
+ },
+ Lint {
+ label: "enum_intrinsics_non_enums",
+ description: r##"detects calls to `core::mem::discriminant` and `core::mem::variant_count` with non-enum types"##,
+ },
+ Lint {
+ label: "explicit_outlives_requirements",
+ description: r##"outlives requirements can be inferred"##,
+ },
+ Lint {
+ label: "exported_private_dependencies",
+ description: r##"public interface leaks type from a private dependency"##,
+ },
+ Lint { label: "forbidden_lint_groups", description: r##"applying forbid to lint-groups"## },
+ Lint {
+ label: "function_item_references",
+ description: r##"suggest casting to a function pointer when attempting to take references to function items"##,
+ },
+ Lint {
+ label: "future_incompatible",
+ description: r##"lint group for: forbidden-lint-groups, illegal-floating-point-literal-pattern, private-in-public, pub-use-of-private-extern-crate, invalid-type-param-default, const-err, unaligned-references, patterns-in-fns-without-body, missing-fragment-specifier, late-bound-lifetime-arguments, order-dependent-trait-objects, coherence-leak-check, unstable-name-collisions, where-clauses-object-safety, proc-macro-derive-resolution-fallback, macro-expanded-macro-exports-accessed-by-absolute-paths, ill-formed-attribute-input, conflicting-repr-hints, ambiguous-associated-items, mutable-borrow-reservation-conflict, indirect-structural-match, pointer-structural-match, nontrivial-structural-match, soft-unstable, cenum-impl-drop-cast, const-evaluatable-unchecked, uninhabited-static, unsupported-naked-functions, invalid-doc-attributes, semicolon-in-expressions-from-macros, legacy-derive-helpers, proc-macro-back-compat, unsupported-calling-conventions, deref-into-dyn-supertrait"##,
+ },
+ Lint {
+ label: "ill_formed_attribute_input",
+ description: r##"ill-formed attribute inputs that were previously accepted and used in practice"##,
+ },
+ Lint {
+ label: "illegal_floating_point_literal_pattern",
+ description: r##"floating-point literals cannot be used in patterns"##,
+ },
+ Lint {
+ label: "improper_ctypes",
+ description: r##"proper use of libc types in foreign modules"##,
+ },
+ Lint {
+ label: "improper_ctypes_definitions",
+ description: r##"proper use of libc types in foreign item definitions"##,
+ },
+ Lint {
+ label: "incomplete_features",
+ description: r##"incomplete features that may function improperly in some or all cases"##,
+ },
+ Lint { label: "incomplete_include", description: r##"trailing content in included file"## },
+ Lint {
+ label: "indirect_structural_match",
+ description: r##"constant used in pattern contains value of non-structural-match type in a field or a variant"##,
+ },
+ Lint {
+ label: "ineffective_unstable_trait_impl",
+ description: r##"detects `#[unstable]` on stable trait implementations for stable types"##,
+ },
+ Lint {
+ label: "inline_no_sanitize",
+ description: r##"detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]`"##,
+ },
+ Lint {
+ label: "invalid_atomic_ordering",
+ description: r##"usage of invalid atomic ordering in atomic operations and memory fences"##,
+ },
+ Lint {
+ label: "invalid_doc_attributes",
+ description: r##"detects invalid `#[doc(...)]` attributes"##,
+ },
+ Lint {
+ label: "invalid_type_param_default",
+ description: r##"type parameter default erroneously allowed in invalid location"##,
+ },
+ Lint {
+ label: "invalid_value",
+ description: r##"an invalid value is being created (such as a null reference)"##,
+ },
+ Lint {
+ label: "irrefutable_let_patterns",
+ description: r##"detects irrefutable patterns in `if let` and `while let` statements"##,
+ },
+ Lint {
+ label: "keyword_idents",
+ description: r##"detects edition keywords being used as an identifier"##,
+ },
+ Lint { label: "large_assignments", description: r##"detects large moves or copies"## },
+ Lint {
+ label: "late_bound_lifetime_arguments",
+ description: r##"detects generic lifetime arguments in path segments with late bound lifetime parameters"##,
+ },
+ Lint {
+ label: "legacy_derive_helpers",
+ description: r##"detects derive helper attributes that are used before they are introduced"##,
+ },
+ Lint {
+ label: "macro_expanded_macro_exports_accessed_by_absolute_paths",
+ description: r##"macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths"##,
+ },
+ Lint {
+ label: "macro_use_extern_crate",
+ description: r##"the `#[macro_use]` attribute is now deprecated in favor of using macros via the module system"##,
+ },
+ Lint {
+ label: "meta_variable_misuse",
+ description: r##"possible meta-variable misuse at macro definition"##,
+ },
+ Lint { label: "missing_abi", description: r##"No declared ABI for extern declaration"## },
+ Lint {
+ label: "missing_copy_implementations",
+ description: r##"detects potentially-forgotten implementations of `Copy`"##,
+ },
+ Lint {
+ label: "missing_debug_implementations",
+ description: r##"detects missing implementations of Debug"##,
+ },
+ Lint {
+ label: "missing_docs",
+ description: r##"detects missing documentation for public members"##,
+ },
+ Lint {
+ label: "missing_fragment_specifier",
+ description: r##"detects missing fragment specifiers in unused `macro_rules!` patterns"##,
+ },
+ Lint {
+ label: "mixed_script_confusables",
+ description: r##"detects Unicode scripts whose mixed script confusables codepoints are solely used"##,
+ },
+ Lint {
+ label: "must_not_suspend",
+ description: r##"use of a `#[must_not_suspend]` value across a yield point"##,
+ },
+ Lint {
+ label: "mutable_borrow_reservation_conflict",
+ description: r##"reservation of a two-phased borrow conflicts with other shared borrows"##,
+ },
+ Lint {
+ label: "mutable_transmutes",
+ description: r##"mutating transmuted &mut T from &T may cause undefined behavior"##,
+ },
+ Lint { label: "named_asm_labels", description: r##"named labels in inline assembly"## },
+ Lint {
+ label: "no_mangle_const_items",
+ description: r##"const items will not have their symbols exported"##,
+ },
+ Lint { label: "no_mangle_generic_items", description: r##"generic items must be mangled"## },
+ Lint { label: "non_ascii_idents", description: r##"detects non-ASCII identifiers"## },
+ Lint {
+ label: "non_camel_case_types",
+ description: r##"types, variants, traits and type parameters should have camel case names"##,
+ },
+ Lint {
+ label: "non_exhaustive_omitted_patterns",
+ description: r##"detect when patterns of types marked `non_exhaustive` are missed"##,
+ },
+ Lint {
+ label: "non_fmt_panics",
+ description: r##"detect single-argument panic!() invocations in which the argument is not a format string"##,
+ },
+ Lint {
+ label: "non_shorthand_field_patterns",
+ description: r##"using `Struct { x: x }` instead of `Struct { x }` in a pattern"##,
+ },
+ Lint {
+ label: "non_snake_case",
+ description: r##"variables, methods, functions, lifetime parameters and modules should have snake case names"##,
+ },
+ Lint {
+ label: "non_upper_case_globals",
+ description: r##"static constants should have uppercase identifiers"##,
+ },
+ Lint {
+ label: "nonstandard_style",
+ description: r##"lint group for: non-camel-case-types, non-snake-case, non-upper-case-globals"##,
+ },
+ Lint {
+ label: "nontrivial_structural_match",
+ description: r##"constant used in pattern of non-structural-match type and the constant's initializer expression contains values of non-structural-match types"##,
+ },
+ Lint {
+ label: "noop_method_call",
+ description: r##"detects the use of well-known noop methods"##,
+ },
+ Lint {
+ label: "order_dependent_trait_objects",
+ description: r##"trait-object types were treated as different depending on marker-trait order"##,
+ },
+ Lint { label: "overflowing_literals", description: r##"literal out of range for its type"## },
+ Lint {
+ label: "overlapping_range_endpoints",
+ description: r##"detects range patterns with overlapping endpoints"##,
+ },
+ Lint { label: "path_statements", description: r##"path statements with no effect"## },
+ Lint {
+ label: "patterns_in_fns_without_body",
+ description: r##"patterns in functions without body were erroneously allowed"##,
+ },
+ Lint {
+ label: "pointer_structural_match",
+ description: r##"pointers are not structural-match"##,
+ },
+ Lint {
+ label: "private_in_public",
+ description: r##"detect private items in public interfaces not caught by the old implementation"##,
+ },
+ Lint {
+ label: "proc_macro_back_compat",
+ description: r##"detects usage of old versions of certain proc-macro crates"##,
+ },
+ Lint {
+ label: "proc_macro_derive_resolution_fallback",
+ description: r##"detects proc macro derives using inaccessible names from parent modules"##,
+ },
+ Lint {
+ label: "pub_use_of_private_extern_crate",
+ description: r##"detect public re-exports of private extern crates"##,
+ },
+ Lint {
+ label: "redundant_semicolons",
+ description: r##"detects unnecessary trailing semicolons"##,
+ },
+ Lint {
+ label: "renamed_and_removed_lints",
+ description: r##"lints that have been renamed or removed"##,
+ },
+ Lint {
+ label: "rust_2018_compatibility",
+ description: r##"lint group for: keyword-idents, anonymous-parameters, tyvar-behind-raw-pointer, absolute-paths-not-starting-with-crate"##,
+ },
+ Lint {
+ label: "rust_2018_idioms",
+ description: r##"lint group for: bare-trait-objects, unused-extern-crates, ellipsis-inclusive-range-patterns, elided-lifetimes-in-paths, explicit-outlives-requirements"##,
+ },
+ Lint {
+ label: "rust_2021_compatibility",
+ description: r##"lint group for: ellipsis-inclusive-range-patterns, bare-trait-objects, rust-2021-incompatible-closure-captures, rust-2021-incompatible-or-patterns, rust-2021-prelude-collisions, rust-2021-prefixes-incompatible-syntax, array-into-iter, non-fmt-panics"##,
+ },
+ Lint {
+ label: "rust_2021_incompatible_closure_captures",
+ description: r##"detects closures affected by Rust 2021 changes"##,
+ },
+ Lint {
+ label: "rust_2021_incompatible_or_patterns",
+ description: r##"detects usage of old versions of or-patterns"##,
+ },
+ Lint {
+ label: "rust_2021_prefixes_incompatible_syntax",
+ description: r##"identifiers that will be parsed as a prefix in Rust 2021"##,
+ },
+ Lint {
+ label: "rust_2021_prelude_collisions",
+ description: r##"detects the usage of trait methods which are ambiguous with traits added to the prelude in future editions"##,
+ },
+ Lint {
+ label: "semicolon_in_expressions_from_macros",
+ description: r##"trailing semicolon in macro body used as expression"##,
+ },
+ Lint {
+ label: "single_use_lifetimes",
+ description: r##"detects lifetime parameters that are only used once"##,
+ },
+ Lint {
+ label: "soft_unstable",
+ description: r##"a feature gate that doesn't break dependent crates"##,
+ },
+ Lint {
+ label: "stable_features",
+ description: r##"stable features found in `#[feature]` directive"##,
+ },
+ Lint {
+ label: "temporary_cstring_as_ptr",
+ description: r##"detects getting the inner pointer of a temporary `CString`"##,
+ },
+ Lint {
+ label: "text_direction_codepoint_in_comment",
+ description: r##"invisible directionality-changing codepoints in comment"##,
+ },
+ Lint {
+ label: "text_direction_codepoint_in_literal",
+ description: r##"detect special Unicode codepoints that affect the visual representation of text on screen, changing the direction in which text flows"##,
+ },
+ Lint {
+ label: "trivial_bounds",
+ description: r##"these bounds don't depend on an type parameters"##,
+ },
+ Lint {
+ label: "trivial_casts",
+ description: r##"detects trivial casts which could be removed"##,
+ },
+ Lint {
+ label: "trivial_numeric_casts",
+ description: r##"detects trivial casts of numeric types which could be removed"##,
+ },
+ Lint {
+ label: "type_alias_bounds",
+ description: r##"bounds in type aliases are not enforced"##,
+ },
+ Lint {
+ label: "tyvar_behind_raw_pointer",
+ description: r##"raw pointer to an inference variable"##,
+ },
+ Lint {
+ label: "unaligned_references",
+ description: r##"detects unaligned references to fields of packed structs"##,
+ },
+ Lint {
+ label: "uncommon_codepoints",
+ description: r##"detects uncommon Unicode codepoints in identifiers"##,
+ },
+ Lint {
+ label: "unconditional_panic",
+ description: r##"operation will cause a panic at runtime"##,
+ },
+ Lint {
+ label: "unconditional_recursion",
+ description: r##"functions that cannot return without calling themselves"##,
+ },
+ Lint { label: "uninhabited_static", description: r##"uninhabited static"## },
+ Lint {
+ label: "unknown_crate_types",
+ description: r##"unknown crate type found in `#[crate_type]` directive"##,
+ },
+ Lint { label: "unknown_lints", description: r##"unrecognized lint attribute"## },
+ Lint {
+ label: "unnameable_test_items",
+ description: r##"detects an item that cannot be named being marked as `#[test_case]`"##,
+ },
+ Lint { label: "unreachable_code", description: r##"detects unreachable code paths"## },
+ Lint { label: "unreachable_patterns", description: r##"detects unreachable patterns"## },
+ Lint {
+ label: "unreachable_pub",
+ description: r##"`pub` items not reachable from crate root"##,
+ },
+ Lint { label: "unsafe_code", description: r##"usage of `unsafe` code"## },
+ Lint {
+ label: "unsafe_op_in_unsafe_fn",
+ description: r##"unsafe operations in unsafe functions without an explicit unsafe block are deprecated"##,
+ },
+ Lint {
+ label: "unstable_features",
+ description: r##"enabling unstable features (deprecated. do not use)"##,
+ },
+ Lint {
+ label: "unstable_name_collisions",
+ description: r##"detects name collision with an existing but unstable method"##,
+ },
+ Lint {
+ label: "unsupported_calling_conventions",
+ description: r##"use of unsupported calling convention"##,
+ },
+ Lint {
+ label: "unsupported_naked_functions",
+ description: r##"unsupported naked function definitions"##,
+ },
+ Lint {
+ label: "unused",
+ description: r##"lint group for: unused-imports, unused-variables, unused-assignments, dead-code, unused-mut, unreachable-code, unreachable-patterns, unused-must-use, unused-unsafe, path-statements, unused-attributes, unused-macros, unused-allocation, unused-doc-comments, unused-extern-crates, unused-features, unused-labels, unused-parens, unused-braces, redundant-semicolons"##,
+ },
+ Lint {
+ label: "unused_allocation",
+ description: r##"detects unnecessary allocations that can be eliminated"##,
+ },
+ Lint {
+ label: "unused_assignments",
+ description: r##"detect assignments that will never be read"##,
+ },
+ Lint {
+ label: "unused_attributes",
+ description: r##"detects attributes that were not used by the compiler"##,
+ },
+ Lint { label: "unused_braces", description: r##"unnecessary braces around an expression"## },
+ Lint {
+ label: "unused_comparisons",
+ description: r##"comparisons made useless by limits of the types involved"##,
+ },
+ Lint {
+ label: "unused_crate_dependencies",
+ description: r##"crate dependencies that are never used"##,
+ },
+ Lint {
+ label: "unused_doc_comments",
+ description: r##"detects doc comments that aren't used by rustdoc"##,
+ },
+ Lint { label: "unused_extern_crates", description: r##"extern crates that are never used"## },
+ Lint {
+ label: "unused_features",
+ description: r##"unused features found in crate-level `#[feature]` directives"##,
+ },
+ Lint {
+ label: "unused_import_braces",
+ description: r##"unnecessary braces around an imported item"##,
+ },
+ Lint { label: "unused_imports", description: r##"imports that are never used"## },
+ Lint { label: "unused_labels", description: r##"detects labels that are never used"## },
+ Lint {
+ label: "unused_lifetimes",
+ description: r##"detects lifetime parameters that are never used"##,
+ },
+ Lint { label: "unused_macros", description: r##"detects macros that were not used"## },
+ Lint {
+ label: "unused_must_use",
+ description: r##"unused result of a type flagged as `#[must_use]`"##,
+ },
+ Lint {
+ label: "unused_mut",
+ description: r##"detect mut variables which don't need to be mutable"##,
+ },
+ Lint {
+ label: "unused_parens",
+ description: r##"`if`, `match`, `while` and `return` do not need parentheses"##,
+ },
+ Lint {
+ label: "unused_qualifications",
+ description: r##"detects unnecessarily qualified names"##,
+ },
+ Lint {
+ label: "unused_results",
+ description: r##"unused result of an expression in a statement"##,
+ },
+ Lint { label: "unused_unsafe", description: r##"unnecessary use of an `unsafe` block"## },
+ Lint {
+ label: "unused_variables",
+ description: r##"detect variables which are not used in any way"##,
+ },
+ Lint {
+ label: "useless_deprecated",
+ description: r##"detects deprecation attributes with no effect"##,
+ },
+ Lint {
+ label: "variant_size_differences",
+ description: r##"detects enums with widely varying variant sizes"##,
+ },
+ Lint {
+ label: "warnings",
+ description: r##"mass-change the level for lints which produce warnings"##,
+ },
+ Lint {
+ label: "warnings",
+ description: r##"lint group for: all lints that are set to issue warnings"##,
+ },
+ Lint {
+ label: "where_clauses_object_safety",
+ description: r##"checks the object safety of where clauses"##,
+ },
+ Lint {
+ label: "while_true",
+ description: r##"suggest using `loop { }` instead of `while true { }`"##,
+ },
+];
+pub const DEFAULT_LINT_GROUPS: &[LintGroup] = &[
+ LintGroup {
+ lint: Lint {
+ label: "future_incompatible",
+ description: r##"lint group for: forbidden-lint-groups, illegal-floating-point-literal-pattern, private-in-public, pub-use-of-private-extern-crate, invalid-type-param-default, const-err, unaligned-references, patterns-in-fns-without-body, missing-fragment-specifier, late-bound-lifetime-arguments, order-dependent-trait-objects, coherence-leak-check, unstable-name-collisions, where-clauses-object-safety, proc-macro-derive-resolution-fallback, macro-expanded-macro-exports-accessed-by-absolute-paths, ill-formed-attribute-input, conflicting-repr-hints, ambiguous-associated-items, mutable-borrow-reservation-conflict, indirect-structural-match, pointer-structural-match, nontrivial-structural-match, soft-unstable, cenum-impl-drop-cast, const-evaluatable-unchecked, uninhabited-static, unsupported-naked-functions, invalid-doc-attributes, semicolon-in-expressions-from-macros, legacy-derive-helpers, proc-macro-back-compat, unsupported-calling-conventions, deref-into-dyn-supertrait"##,
+ },
+ children: &[
+ "forbidden_lint_groups",
+ "illegal_floating_point_literal_pattern",
+ "private_in_public",
+ "pub_use_of_private_extern_crate",
+ "invalid_type_param_default",
+ "const_err",
+ "unaligned_references",
+ "patterns_in_fns_without_body",
+ "missing_fragment_specifier",
+ "late_bound_lifetime_arguments",
+ "order_dependent_trait_objects",
+ "coherence_leak_check",
+ "unstable_name_collisions",
+ "where_clauses_object_safety",
+ "proc_macro_derive_resolution_fallback",
+ "macro_expanded_macro_exports_accessed_by_absolute_paths",
+ "ill_formed_attribute_input",
+ "conflicting_repr_hints",
+ "ambiguous_associated_items",
+ "mutable_borrow_reservation_conflict",
+ "indirect_structural_match",
+ "pointer_structural_match",
+ "nontrivial_structural_match",
+ "soft_unstable",
+ "cenum_impl_drop_cast",
+ "const_evaluatable_unchecked",
+ "uninhabited_static",
+ "unsupported_naked_functions",
+ "invalid_doc_attributes",
+ "semicolon_in_expressions_from_macros",
+ "legacy_derive_helpers",
+ "proc_macro_back_compat",
+ "unsupported_calling_conventions",
+ "deref_into_dyn_supertrait",
+ ],
+ },
+ LintGroup {
+ lint: Lint {
+ label: "nonstandard_style",
+ description: r##"lint group for: non-camel-case-types, non-snake-case, non-upper-case-globals"##,
+ },
+ children: &["non_camel_case_types", "non_snake_case", "non_upper_case_globals"],
+ },
+ LintGroup {
+ lint: Lint {
+ label: "rust_2018_compatibility",
+ description: r##"lint group for: keyword-idents, anonymous-parameters, tyvar-behind-raw-pointer, absolute-paths-not-starting-with-crate"##,
+ },
+ children: &[
+ "keyword_idents",
+ "anonymous_parameters",
+ "tyvar_behind_raw_pointer",
+ "absolute_paths_not_starting_with_crate",
+ ],
+ },
+ LintGroup {
+ lint: Lint {
+ label: "rust_2018_idioms",
+ description: r##"lint group for: bare-trait-objects, unused-extern-crates, ellipsis-inclusive-range-patterns, elided-lifetimes-in-paths, explicit-outlives-requirements"##,
+ },
+ children: &[
+ "bare_trait_objects",
+ "unused_extern_crates",
+ "ellipsis_inclusive_range_patterns",
+ "elided_lifetimes_in_paths",
+ "explicit_outlives_requirements",
+ ],
+ },
+ LintGroup {
+ lint: Lint {
+ label: "rust_2021_compatibility",
+ description: r##"lint group for: ellipsis-inclusive-range-patterns, bare-trait-objects, rust-2021-incompatible-closure-captures, rust-2021-incompatible-or-patterns, rust-2021-prelude-collisions, rust-2021-prefixes-incompatible-syntax, array-into-iter, non-fmt-panics"##,
+ },
+ children: &[
+ "ellipsis_inclusive_range_patterns",
+ "bare_trait_objects",
+ "rust_2021_incompatible_closure_captures",
+ "rust_2021_incompatible_or_patterns",
+ "rust_2021_prelude_collisions",
+ "rust_2021_prefixes_incompatible_syntax",
+ "array_into_iter",
+ "non_fmt_panics",
+ ],
+ },
+ LintGroup {
+ lint: Lint {
+ label: "unused",
+ description: r##"lint group for: unused-imports, unused-variables, unused-assignments, dead-code, unused-mut, unreachable-code, unreachable-patterns, unused-must-use, unused-unsafe, path-statements, unused-attributes, unused-macros, unused-allocation, unused-doc-comments, unused-extern-crates, unused-features, unused-labels, unused-parens, unused-braces, redundant-semicolons"##,
+ },
+ children: &[
+ "unused_imports",
+ "unused_variables",
+ "unused_assignments",
+ "dead_code",
+ "unused_mut",
+ "unreachable_code",
+ "unreachable_patterns",
+ "unused_must_use",
+ "unused_unsafe",
+ "path_statements",
+ "unused_attributes",
+ "unused_macros",
+ "unused_allocation",
+ "unused_doc_comments",
+ "unused_extern_crates",
+ "unused_features",
+ "unused_labels",
+ "unused_parens",
+ "unused_braces",
+ "redundant_semicolons",
+ ],
+ },
+ LintGroup {
+ lint: Lint {
+ label: "warnings",
+ description: r##"lint group for: all lints that are set to issue warnings"##,
+ },
+ children: &[],
+ },
+];
+
+pub const RUSTDOC_LINTS: &[Lint] = &[
+ Lint {
+ label: "rustdoc::all",
+ description: r##"lint group for: rustdoc::broken-intra-doc-links, rustdoc::private-intra-doc-links, rustdoc::missing-doc-code-examples, rustdoc::private-doc-tests, rustdoc::invalid-codeblock-attributes, rustdoc::invalid-rust-codeblocks, rustdoc::invalid-html-tags, rustdoc::bare-urls, rustdoc::missing-crate-level-docs"##,
+ },
+ Lint { label: "rustdoc::bare_urls", description: r##"detects URLs that are not hyperlinks"## },
+ Lint {
+ label: "rustdoc::broken_intra_doc_links",
+ description: r##"failures in resolving intra-doc link targets"##,
+ },
+ Lint {
+ label: "rustdoc::invalid_codeblock_attributes",
+ description: r##"codeblock attribute looks a lot like a known one"##,
+ },
+ Lint {
+ label: "rustdoc::invalid_html_tags",
+ description: r##"detects invalid HTML tags in doc comments"##,
+ },
+ Lint {
+ label: "rustdoc::invalid_rust_codeblocks",
+ description: r##"codeblock could not be parsed as valid Rust or is empty"##,
+ },
+ Lint {
+ label: "rustdoc::missing_crate_level_docs",
+ description: r##"detects crates with no crate-level documentation"##,
+ },
+ Lint {
+ label: "rustdoc::missing_doc_code_examples",
+ description: r##"detects publicly-exported items without code samples in their documentation"##,
+ },
+ Lint {
+ label: "rustdoc::private_doc_tests",
+ description: r##"detects code samples in docs of private items not documented by rustdoc"##,
+ },
+ Lint {
+ label: "rustdoc::private_intra_doc_links",
+ description: r##"linking from a public item to a private one"##,
+ },
+];
+pub const RUSTDOC_LINT_GROUPS: &[LintGroup] = &[LintGroup {
+ lint: Lint {
+ label: "rustdoc::all",
+ description: r##"lint group for: rustdoc::broken-intra-doc-links, rustdoc::private-intra-doc-links, rustdoc::missing-doc-code-examples, rustdoc::private-doc-tests, rustdoc::invalid-codeblock-attributes, rustdoc::invalid-rust-codeblocks, rustdoc::invalid-html-tags, rustdoc::bare-urls, rustdoc::missing-crate-level-docs"##,
+ },
+ children: &[
+ "rustdoc::broken_intra_doc_links",
+ "rustdoc::private_intra_doc_links",
+ "rustdoc::missing_doc_code_examples",
+ "rustdoc::private_doc_tests",
+ "rustdoc::invalid_codeblock_attributes",
+ "rustdoc::invalid_rust_codeblocks",
+ "rustdoc::invalid_html_tags",
+ "rustdoc::bare_urls",
+ "rustdoc::missing_crate_level_docs",
+ ],
+}];
+
+pub const FEATURES: &[Lint] = &[
+ Lint {
+ label: "abi_c_cmse_nonsecure_call",
+ description: r##"# `abi_c_cmse_nonsecure_call`
+
+The tracking issue for this feature is: [#81391]
+
+[#81391]: https://github.com/rust-lang/rust/issues/81391
+
+------------------------
+
+The [TrustZone-M
+feature](https://developer.arm.com/documentation/100690/latest/) is available
+for targets with the Armv8-M architecture profile (`thumbv8m` in their target
+name).
+LLVM, the Rust compiler and the linker are providing
+[support](https://developer.arm.com/documentation/ecm0359818/latest/) for the
+TrustZone-M feature.
+
+One of the things provided, with this unstable feature, is the
+`C-cmse-nonsecure-call` function ABI. This ABI is used on function pointers to
+non-secure code to mark a non-secure function call (see [section
+5.5](https://developer.arm.com/documentation/ecm0359818/latest/) for details).
+
+With this ABI, the compiler will do the following to perform the call:
+* save registers needed after the call to Secure memory
+* clear all registers that might contain confidential information
+* clear the Least Significant Bit of the function address
+* branches using the BLXNS instruction
+
+To avoid using the non-secure stack, the compiler will constrain the number and
+type of parameters/return value.
+
+The `extern "C-cmse-nonsecure-call"` ABI is otherwise equivalent to the
+`extern "C"` ABI.
+
+<!-- NOTE(ignore) this example is specific to thumbv8m targets -->
+
+``` rust,ignore
+#![no_std]
+#![feature(abi_c_cmse_nonsecure_call)]
+
+#[no_mangle]
+pub fn call_nonsecure_function(addr: usize) -> u32 {
+ let non_secure_function =
+ unsafe { core::mem::transmute::<usize, extern "C-cmse-nonsecure-call" fn() -> u32>(addr) };
+ non_secure_function()
+}
+```
+
+``` text
+$ rustc --emit asm --crate-type lib --target thumbv8m.main-none-eabi function.rs
+
+call_nonsecure_function:
+ .fnstart
+ .save {r7, lr}
+ push {r7, lr}
+ .setfp r7, sp
+ mov r7, sp
+ .pad #16
+ sub sp, #16
+ str r0, [sp, #12]
+ ldr r0, [sp, #12]
+ str r0, [sp, #8]
+ b .LBB0_1
+.LBB0_1:
+ ldr r0, [sp, #8]
+ push.w {r4, r5, r6, r7, r8, r9, r10, r11}
+ bic r0, r0, #1
+ mov r1, r0
+ mov r2, r0
+ mov r3, r0
+ mov r4, r0
+ mov r5, r0
+ mov r6, r0
+ mov r7, r0
+ mov r8, r0
+ mov r9, r0
+ mov r10, r0
+ mov r11, r0
+ mov r12, r0
+ msr apsr_nzcvq, r0
+ blxns r0
+ pop.w {r4, r5, r6, r7, r8, r9, r10, r11}
+ str r0, [sp, #4]
+ b .LBB0_2
+.LBB0_2:
+ ldr r0, [sp, #4]
+ add sp, #16
+ pop {r7, pc}
+```
+"##,
+ },
+ Lint {
+ label: "abi_msp430_interrupt",
+ description: r##"# `abi_msp430_interrupt`
+
+The tracking issue for this feature is: [#38487]
+
+[#38487]: https://github.com/rust-lang/rust/issues/38487
+
+------------------------
+
+In the MSP430 architecture, interrupt handlers have a special calling
+convention. You can use the `"msp430-interrupt"` ABI to make the compiler apply
+the right calling convention to the interrupt handlers you define.
+
+<!-- NOTE(ignore) this example is specific to the msp430 target -->
+
+``` rust,ignore
+#![feature(abi_msp430_interrupt)]
+#![no_std]
+
+// Place the interrupt handler at the appropriate memory address
+// (Alternatively, you can use `#[used]` and remove `pub` and `#[no_mangle]`)
+#[link_section = "__interrupt_vector_10"]
+#[no_mangle]
+pub static TIM0_VECTOR: extern "msp430-interrupt" fn() = tim0;
+
+// The interrupt handler
+extern "msp430-interrupt" fn tim0() {
+ // ..
+}
+```
+
+``` text
+$ msp430-elf-objdump -CD ./target/msp430/release/app
+Disassembly of section __interrupt_vector_10:
+
+0000fff2 <TIM0_VECTOR>:
+ fff2: 00 c0 interrupt service routine at 0xc000
+
+Disassembly of section .text:
+
+0000c000 <int::tim0>:
+ c000: 00 13 reti
+```
+"##,
+ },
+ Lint {
+ label: "abi_ptx",
+ description: r##"# `abi_ptx`
+
+The tracking issue for this feature is: [#38788]
+
+[#38788]: https://github.com/rust-lang/rust/issues/38788
+
+------------------------
+
+When emitting PTX code, all vanilla Rust functions (`fn`) get translated to
+"device" functions. These functions are *not* callable from the host via the
+CUDA API so a crate with only device functions is not too useful!
+
+OTOH, "global" functions *can* be called by the host; you can think of them
+as the real public API of your crate. To produce a global function use the
+`"ptx-kernel"` ABI.
+
+<!-- NOTE(ignore) this example is specific to the nvptx targets -->
+
+``` rust,ignore
+#![feature(abi_ptx)]
+#![no_std]
+
+pub unsafe extern "ptx-kernel" fn global_function() {
+ device_function();
+}
+
+pub fn device_function() {
+ // ..
+}
+```
+
+``` text
+$ xargo rustc --target nvptx64-nvidia-cuda --release -- --emit=asm
+
+$ cat $(find -name '*.s')
+//
+// Generated by LLVM NVPTX Back-End
+//
+
+.version 3.2
+.target sm_20
+.address_size 64
+
+ // .globl _ZN6kernel15global_function17h46111ebe6516b382E
+
+.visible .entry _ZN6kernel15global_function17h46111ebe6516b382E()
+{
+
+
+ ret;
+}
+
+ // .globl _ZN6kernel15device_function17hd6a0e4993bbf3f78E
+.visible .func _ZN6kernel15device_function17hd6a0e4993bbf3f78E()
+{
+
+
+ ret;
+}
+```
+"##,
+ },
+ Lint {
+ label: "abi_thiscall",
+ description: r##"# `abi_thiscall`
+
+The tracking issue for this feature is: [#42202]
+
+[#42202]: https://github.com/rust-lang/rust/issues/42202
+
+------------------------
+
+The MSVC ABI on x86 Windows uses the `thiscall` calling convention for C++
+instance methods by default; it is identical to the usual (C) calling
+convention on x86 Windows except that the first parameter of the method,
+the `this` pointer, is passed in the ECX register.
+"##,
+ },
+ Lint {
+ label: "allocator_api",
+ description: r##"# `allocator_api`
+
+The tracking issue for this feature is [#32838]
+
+[#32838]: https://github.com/rust-lang/rust/issues/32838
+
+------------------------
+
+Sometimes you want the memory for one collection to use a different
+allocator than the memory for another collection. In this case,
+replacing the global allocator is not a workable option. Instead,
+you need to pass in an instance of an `AllocRef` to each collection
+for which you want a custom allocator.
+
+TBD
+"##,
+ },
+ Lint {
+ label: "allocator_internals",
+ description: r##"# `allocator_internals`
+
+This feature does not have a tracking issue, it is an unstable implementation
+detail of the `global_allocator` feature not intended for use outside the
+compiler.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "arbitrary_enum_discriminant",
+ description: r##"# `arbitrary_enum_discriminant`
+
+The tracking issue for this feature is: [#60553]
+
+[#60553]: https://github.com/rust-lang/rust/issues/60553
+
+------------------------
+
+The `arbitrary_enum_discriminant` feature permits tuple-like and
+struct-like enum variants with `#[repr(<int-type>)]` to have explicit discriminants.
+
+## Examples
+
+```rust
+#![feature(arbitrary_enum_discriminant)]
+
+#[allow(dead_code)]
+#[repr(u8)]
+enum Enum {
+ Unit = 3,
+ Tuple(u16) = 2,
+ Struct {
+ a: u8,
+ b: u16,
+ } = 1,
+}
+
+impl Enum {
+ fn tag(&self) -> u8 {
+ unsafe { *(self as *const Self as *const u8) }
+ }
+}
+
+assert_eq!(3, Enum::Unit.tag());
+assert_eq!(2, Enum::Tuple(5).tag());
+assert_eq!(1, Enum::Struct{a: 7, b: 11}.tag());
+```
+"##,
+ },
+ Lint {
+ label: "asm_const",
+ description: r##"# `asm_const`
+
+The tracking issue for this feature is: [#72016]
+
+[#72016]: https://github.com/rust-lang/rust/issues/72016
+
+------------------------
+
+This feature adds a `const <expr>` operand type to `asm!` and `global_asm!`.
+- `<expr>` must be an integer constant expression.
+- The value of the expression is formatted as a string and substituted directly into the asm template string.
+"##,
+ },
+ Lint {
+ label: "asm_experimental_arch",
+ description: r##"# `asm_experimental_arch`
+
+The tracking issue for this feature is: [#72016]
+
+[#72016]: https://github.com/rust-lang/rust/issues/72016
+
+------------------------
+
+This feature tracks `asm!` and `global_asm!` support for the following architectures:
+- NVPTX
+- PowerPC
+- Hexagon
+- MIPS32r2 and MIPS64r2
+- wasm32
+- BPF
+- SPIR-V
+- AVR
+
+## Register classes
+
+| Architecture | Register class | Registers | LLVM constraint code |
+| ------------ | -------------- | ---------------------------------- | -------------------- |
+| MIPS | `reg` | `$[2-25]` | `r` |
+| MIPS | `freg` | `$f[0-31]` | `f` |
+| NVPTX | `reg16` | None\* | `h` |
+| NVPTX | `reg32` | None\* | `r` |
+| NVPTX | `reg64` | None\* | `l` |
+| Hexagon | `reg` | `r[0-28]` | `r` |
+| PowerPC | `reg` | `r[0-31]` | `r` |
+| PowerPC | `reg_nonzero` | `r[1-31]` | `b` |
+| PowerPC | `freg` | `f[0-31]` | `f` |
+| PowerPC | `cr` | `cr[0-7]`, `cr` | Only clobbers |
+| PowerPC | `xer` | `xer` | Only clobbers |
+| wasm32 | `local` | None\* | `r` |
+| BPF | `reg` | `r[0-10]` | `r` |
+| BPF | `wreg` | `w[0-10]` | `w` |
+| AVR | `reg` | `r[2-25]`, `XH`, `XL`, `ZH`, `ZL` | `r` |
+| AVR | `reg_upper` | `r[16-25]`, `XH`, `XL`, `ZH`, `ZL` | `d` |
+| AVR | `reg_pair` | `r3r2` .. `r25r24`, `X`, `Z` | `r` |
+| AVR | `reg_iw` | `r25r24`, `X`, `Z` | `w` |
+| AVR | `reg_ptr` | `X`, `Z` | `e` |
+
+> **Notes**:
+> - NVPTX doesn't have a fixed register set, so named registers are not supported.
+>
+> - WebAssembly doesn't have registers, so named registers are not supported.
+
+# Register class supported types
+
+| Architecture | Register class | Target feature | Allowed types |
+| ------------ | ------------------------------- | -------------- | --------------------------------------- |
+| MIPS32 | `reg` | None | `i8`, `i16`, `i32`, `f32` |
+| MIPS32 | `freg` | None | `f32`, `f64` |
+| MIPS64 | `reg` | None | `i8`, `i16`, `i32`, `i64`, `f32`, `f64` |
+| MIPS64 | `freg` | None | `f32`, `f64` |
+| NVPTX | `reg16` | None | `i8`, `i16` |
+| NVPTX | `reg32` | None | `i8`, `i16`, `i32`, `f32` |
+| NVPTX | `reg64` | None | `i8`, `i16`, `i32`, `f32`, `i64`, `f64` |
+| Hexagon | `reg` | None | `i8`, `i16`, `i32`, `f32` |
+| PowerPC | `reg` | None | `i8`, `i16`, `i32` |
+| PowerPC | `reg_nonzero` | None | `i8`, `i16`, `i32` |
+| PowerPC | `freg` | None | `f32`, `f64` |
+| PowerPC | `cr` | N/A | Only clobbers |
+| PowerPC | `xer` | N/A | Only clobbers |
+| wasm32 | `local` | None | `i8` `i16` `i32` `i64` `f32` `f64` |
+| BPF | `reg` | None | `i8` `i16` `i32` `i64` |
+| BPF | `wreg` | `alu32` | `i8` `i16` `i32` |
+| AVR | `reg`, `reg_upper` | None | `i8` |
+| AVR | `reg_pair`, `reg_iw`, `reg_ptr` | None | `i16` |
+
+## Register aliases
+
+| Architecture | Base register | Aliases |
+| ------------ | ------------- | --------- |
+| Hexagon | `r29` | `sp` |
+| Hexagon | `r30` | `fr` |
+| Hexagon | `r31` | `lr` |
+| BPF | `r[0-10]` | `w[0-10]` |
+| AVR | `XH` | `r27` |
+| AVR | `XL` | `r26` |
+| AVR | `ZH` | `r31` |
+| AVR | `ZL` | `r30` |
+
+## Unsupported registers
+
+| Architecture | Unsupported register | Reason |
+| ------------ | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| All | `sp` | The stack pointer must be restored to its original value at the end of an asm code block. |
+| All | `fr` (Hexagon), `$fp` (MIPS), `Y` (AVR) | The frame pointer cannot be used as an input or output. |
+| All | `r19` (Hexagon) | This is used internally by LLVM as a "base pointer" for functions with complex stack frames. |
+| MIPS | `$0` or `$zero` | This is a constant zero register which can't be modified. |
+| MIPS | `$1` or `$at` | Reserved for assembler. |
+| MIPS | `$26`/`$k0`, `$27`/`$k1` | OS-reserved registers. |
+| MIPS | `$28`/`$gp` | Global pointer cannot be used as inputs or outputs. |
+| MIPS | `$ra` | Return address cannot be used as inputs or outputs. |
+| Hexagon | `lr` | This is the link register which cannot be used as an input or output. |
+| AVR | `r0`, `r1`, `r1r0` | Due to an issue in LLVM, the `r0` and `r1` registers cannot be used as inputs or outputs. If modified, they must be restored to their original values before the end of the block. |
+
+## Template modifiers
+
+| Architecture | Register class | Modifier | Example output | LLVM modifier |
+| ------------ | -------------- | -------- | -------------- | ------------- |
+| MIPS | `reg` | None | `$2` | None |
+| MIPS | `freg` | None | `$f0` | None |
+| NVPTX | `reg16` | None | `rs0` | None |
+| NVPTX | `reg32` | None | `r0` | None |
+| NVPTX | `reg64` | None | `rd0` | None |
+| Hexagon | `reg` | None | `r0` | None |
+| PowerPC | `reg` | None | `0` | None |
+| PowerPC | `reg_nonzero` | None | `3` | `b` |
+| PowerPC | `freg` | None | `0` | None |
+
+# Flags covered by `preserves_flags`
+
+These flags registers must be restored upon exiting the asm block if the `preserves_flags` option is set:
+- AVR
+ - The status register `SREG`.
+"##,
+ },
+ Lint {
+ label: "asm_sym",
+ description: r##"# `asm_sym`
+
+The tracking issue for this feature is: [#72016]
+
+[#72016]: https://github.com/rust-lang/rust/issues/72016
+
+------------------------
+
+This feature adds a `sym <path>` operand type to `asm!` and `global_asm!`.
+- `<path>` must refer to a `fn` or `static`.
+- A mangled symbol name referring to the item is substituted into the asm template string.
+- The substituted string does not include any modifiers (e.g. GOT, PLT, relocations, etc).
+- `<path>` is allowed to point to a `#[thread_local]` static, in which case the asm code can combine the symbol with relocations (e.g. `@plt`, `@TPOFF`) to read from thread-local data.
+"##,
+ },
+ Lint {
+ label: "asm_unwind",
+ description: r##"# `asm_unwind`
+
+The tracking issue for this feature is: [#72016]
+
+[#72016]: https://github.com/rust-lang/rust/issues/72016
+
+------------------------
+
+This feature adds a `may_unwind` option to `asm!` which allows an `asm` block to unwind stack and be part of the stack unwinding process. This option is only supported by the LLVM backend right now.
+"##,
+ },
+ Lint {
+ label: "auto_traits",
+ description: r##"# `auto_traits`
+
+The tracking issue for this feature is [#13231]
+
+[#13231]: https://github.com/rust-lang/rust/issues/13231
+
+----
+
+The `auto_traits` feature gate allows you to define auto traits.
+
+Auto traits, like [`Send`] or [`Sync`] in the standard library, are marker traits
+that are automatically implemented for every type, unless the type, or a type it contains,
+has explicitly opted out via a negative impl. (Negative impls are separately controlled
+by the `negative_impls` feature.)
+
+[`Send`]: https://doc.rust-lang.org/std/marker/trait.Send.html
+[`Sync`]: https://doc.rust-lang.org/std/marker/trait.Sync.html
+
+```rust,ignore (partial-example)
+impl !Trait for Type {}
+```
+
+Example:
+
+```rust
+#![feature(negative_impls)]
+#![feature(auto_traits)]
+
+auto trait Valid {}
+
+struct True;
+struct False;
+
+impl !Valid for False {}
+
+struct MaybeValid<T>(T);
+
+fn must_be_valid<T: Valid>(_t: T) { }
+
+fn main() {
+ // works
+ must_be_valid( MaybeValid(True) );
+
+ // compiler error - trait bound not satisfied
+ // must_be_valid( MaybeValid(False) );
+}
+```
+
+## Automatic trait implementations
+
+When a type is declared as an `auto trait`, we will automatically
+create impls for every struct/enum/union, unless an explicit impl is
+provided. These automatic impls contain a where clause for each field
+of the form `T: AutoTrait`, where `T` is the type of the field and
+`AutoTrait` is the auto trait in question. As an example, consider the
+struct `List` and the auto trait `Send`:
+
+```rust
+struct List<T> {
+ data: T,
+ next: Option<Box<List<T>>>,
+}
+```
+
+Presuming that there is no explicit impl of `Send` for `List`, the
+compiler will supply an automatic impl of the form:
+
+```rust
+struct List<T> {
+ data: T,
+ next: Option<Box<List<T>>>,
+}
+
+unsafe impl<T> Send for List<T>
+where
+ T: Send, // from the field `data`
+ Option<Box<List<T>>>: Send, // from the field `next`
+{ }
+```
+
+Explicit impls may be either positive or negative. They take the form:
+
+```rust,ignore (partial-example)
+impl<...> AutoTrait for StructName<..> { }
+impl<...> !AutoTrait for StructName<..> { }
+```
+
+## Coinduction: Auto traits permit cyclic matching
+
+Unlike ordinary trait matching, auto traits are **coinductive**. This
+means, in short, that cycles which occur in trait matching are
+considered ok. As an example, consider the recursive struct `List`
+introduced in the previous section. In attempting to determine whether
+`List: Send`, we would wind up in a cycle: to apply the impl, we must
+show that `Option<Box<List>>: Send`, which will in turn require
+`Box<List>: Send` and then finally `List: Send` again. Under ordinary
+trait matching, this cycle would be an error, but for an auto trait it
+is considered a successful match.
+
+## Items
+
+Auto traits cannot have any trait items, such as methods or associated types. This ensures that we can generate default implementations.
+
+## Supertraits
+
+Auto traits cannot have supertraits. This is for soundness reasons, as the interaction of coinduction with implied bounds is difficult to reconcile.
+"##,
+ },
+ Lint {
+ label: "box_patterns",
+ description: r##"# `box_patterns`
+
+The tracking issue for this feature is: [#29641]
+
+[#29641]: https://github.com/rust-lang/rust/issues/29641
+
+See also [`box_syntax`](box-syntax.md)
+
+------------------------
+
+Box patterns let you match on `Box<T>`s:
+
+
+```rust
+#![feature(box_patterns)]
+
+fn main() {
+ let b = Some(Box::new(5));
+ match b {
+ Some(box n) if n < 0 => {
+ println!("Box contains negative number {}", n);
+ },
+ Some(box n) if n >= 0 => {
+ println!("Box contains non-negative number {}", n);
+ },
+ None => {
+ println!("No box");
+ },
+ _ => unreachable!()
+ }
+}
+```
+"##,
+ },
+ Lint {
+ label: "box_syntax",
+ description: r##"# `box_syntax`
+
+The tracking issue for this feature is: [#49733]
+
+[#49733]: https://github.com/rust-lang/rust/issues/49733
+
+See also [`box_patterns`](box-patterns.md)
+
+------------------------
+
+Currently the only stable way to create a `Box` is via the `Box::new` method.
+Also it is not possible in stable Rust to destructure a `Box` in a match
+pattern. The unstable `box` keyword can be used to create a `Box`. An example
+usage would be:
+
+```rust
+#![feature(box_syntax)]
+
+fn main() {
+ let b = box 5;
+}
+```
+"##,
+ },
+ Lint {
+ label: "c_unwind",
+ description: r##"# `c_unwind`
+
+The tracking issue for this feature is: [#74990]
+
+[#74990]: https://github.com/rust-lang/rust/issues/74990
+
+------------------------
+
+Introduces four new ABI strings: "C-unwind", "stdcall-unwind",
+"thiscall-unwind", and "system-unwind". These enable unwinding from other
+languages (such as C++) into Rust frames and from Rust into other languages.
+
+See [RFC 2945] for more information.
+
+[RFC 2945]: https://github.com/rust-lang/rfcs/blob/master/text/2945-c-unwind-abi.md
+"##,
+ },
+ Lint {
+ label: "c_variadic",
+ description: r##"# `c_variadic`
+
+The tracking issue for this feature is: [#44930]
+
+[#44930]: https://github.com/rust-lang/rust/issues/44930
+
+------------------------
+
+The `c_variadic` language feature enables C-variadic functions to be
+defined in Rust. The may be called both from within Rust and via FFI.
+
+## Examples
+
+```rust
+#![feature(c_variadic)]
+
+pub unsafe extern "C" fn add(n: usize, mut args: ...) -> usize {
+ let mut sum = 0;
+ for _ in 0..n {
+ sum += args.arg::<usize>();
+ }
+ sum
+}
+```
+"##,
+ },
+ Lint {
+ label: "c_variadic",
+ description: r##"# `c_variadic`
+
+The tracking issue for this feature is: [#44930]
+
+[#44930]: https://github.com/rust-lang/rust/issues/44930
+
+------------------------
+
+The `c_variadic` library feature exposes the `VaList` structure,
+Rust's analogue of C's `va_list` type.
+
+## Examples
+
+```rust
+#![feature(c_variadic)]
+
+use std::ffi::VaList;
+
+pub unsafe extern "C" fn vadd(n: usize, mut args: VaList) -> usize {
+ let mut sum = 0;
+ for _ in 0..n {
+ sum += args.arg::<usize>();
+ }
+ sum
+}
+```
+"##,
+ },
+ Lint {
+ label: "c_void_variant",
+ description: r##"# `c_void_variant`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "cfg_panic",
+ description: r##"# `cfg_panic`
+
+The tracking issue for this feature is: [#77443]
+
+[#77443]: https://github.com/rust-lang/rust/issues/77443
+
+------------------------
+
+The `cfg_panic` feature makes it possible to execute different code
+depending on the panic strategy.
+
+Possible values at the moment are `"unwind"` or `"abort"`, although
+it is possible that new panic strategies may be added to Rust in the
+future.
+
+## Examples
+
+```rust
+#![feature(cfg_panic)]
+
+#[cfg(panic = "unwind")]
+fn a() {
+ // ...
+}
+
+#[cfg(not(panic = "unwind"))]
+fn a() {
+ // ...
+}
+
+fn b() {
+ if cfg!(panic = "abort") {
+ // ...
+ } else {
+ // ...
+ }
+}
+```
+"##,
+ },
+ Lint {
+ label: "cfg_sanitize",
+ description: r##"# `cfg_sanitize`
+
+The tracking issue for this feature is: [#39699]
+
+[#39699]: https://github.com/rust-lang/rust/issues/39699
+
+------------------------
+
+The `cfg_sanitize` feature makes it possible to execute different code
+depending on whether a particular sanitizer is enabled or not.
+
+## Examples
+
+```rust
+#![feature(cfg_sanitize)]
+
+#[cfg(sanitize = "thread")]
+fn a() {
+ // ...
+}
+
+#[cfg(not(sanitize = "thread"))]
+fn a() {
+ // ...
+}
+
+fn b() {
+ if cfg!(sanitize = "leak") {
+ // ...
+ } else {
+ // ...
+ }
+}
+```
+"##,
+ },
+ Lint {
+ label: "cfg_version",
+ description: r##"# `cfg_version`
+
+The tracking issue for this feature is: [#64796]
+
+[#64796]: https://github.com/rust-lang/rust/issues/64796
+
+------------------------
+
+The `cfg_version` feature makes it possible to execute different code
+depending on the compiler version. It will return true if the compiler
+version is greater than or equal to the specified version.
+
+## Examples
+
+```rust
+#![feature(cfg_version)]
+
+#[cfg(version("1.42"))] // 1.42 and above
+fn a() {
+ // ...
+}
+
+#[cfg(not(version("1.42")))] // 1.41 and below
+fn a() {
+ // ...
+}
+
+fn b() {
+ if cfg!(version("1.42")) {
+ // ...
+ } else {
+ // ...
+ }
+}
+```
+"##,
+ },
+ Lint {
+ label: "char_error_internals",
+ description: r##"# `char_error_internals`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "closure_track_caller",
+ description: r##"# `closure_track_caller`
+
+The tracking issue for this feature is: [#87417]
+
+[#87417]: https://github.com/rust-lang/rust/issues/87417
+
+------------------------
+
+Allows using the `#[track_caller]` attribute on closures and generators.
+Calls made to the closure or generator will have caller information
+available through `std::panic::Location::caller()`, just like using
+`#[track_caller]` on a function.
+"##,
+ },
+ Lint {
+ label: "cmse_nonsecure_entry",
+ description: r##"# `cmse_nonsecure_entry`
+
+The tracking issue for this feature is: [#75835]
+
+[#75835]: https://github.com/rust-lang/rust/issues/75835
+
+------------------------
+
+The [TrustZone-M
+feature](https://developer.arm.com/documentation/100690/latest/) is available
+for targets with the Armv8-M architecture profile (`thumbv8m` in their target
+name).
+LLVM, the Rust compiler and the linker are providing
+[support](https://developer.arm.com/documentation/ecm0359818/latest/) for the
+TrustZone-M feature.
+
+One of the things provided, with this unstable feature, is the
+`cmse_nonsecure_entry` attribute. This attribute marks a Secure function as an
+entry function (see [section
+5.4](https://developer.arm.com/documentation/ecm0359818/latest/) for details).
+With this attribute, the compiler will do the following:
+* add a special symbol on the function which is the `__acle_se_` prefix and the
+ standard function name
+* constrain the number of parameters to avoid using the Non-Secure stack
+* before returning from the function, clear registers that might contain Secure
+ information
+* use the `BXNS` instruction to return
+
+Because the stack can not be used to pass parameters, there will be compilation
+errors if:
+* the total size of all parameters is too big (for example more than four 32
+ bits integers)
+* the entry function is not using a C ABI
+
+The special symbol `__acle_se_` will be used by the linker to generate a secure
+gateway veneer.
+
+<!-- NOTE(ignore) this example is specific to thumbv8m targets -->
+
+``` rust,ignore
+#![feature(cmse_nonsecure_entry)]
+
+#[no_mangle]
+#[cmse_nonsecure_entry]
+pub extern "C" fn entry_function(input: u32) -> u32 {
+ input + 6
+}
+```
+
+``` text
+$ rustc --emit obj --crate-type lib --target thumbv8m.main-none-eabi function.rs
+$ arm-none-eabi-objdump -D function.o
+
+00000000 <entry_function>:
+ 0: b580 push {r7, lr}
+ 2: 466f mov r7, sp
+ 4: b082 sub sp, #8
+ 6: 9001 str r0, [sp, #4]
+ 8: 1d81 adds r1, r0, #6
+ a: 460a mov r2, r1
+ c: 4281 cmp r1, r0
+ e: 9200 str r2, [sp, #0]
+ 10: d30b bcc.n 2a <entry_function+0x2a>
+ 12: e7ff b.n 14 <entry_function+0x14>
+ 14: 9800 ldr r0, [sp, #0]
+ 16: b002 add sp, #8
+ 18: e8bd 4080 ldmia.w sp!, {r7, lr}
+ 1c: 4671 mov r1, lr
+ 1e: 4672 mov r2, lr
+ 20: 4673 mov r3, lr
+ 22: 46f4 mov ip, lr
+ 24: f38e 8800 msr CPSR_f, lr
+ 28: 4774 bxns lr
+ 2a: f240 0000 movw r0, #0
+ 2e: f2c0 0000 movt r0, #0
+ 32: f240 0200 movw r2, #0
+ 36: f2c0 0200 movt r2, #0
+ 3a: 211c movs r1, #28
+ 3c: f7ff fffe bl 0 <_ZN4core9panicking5panic17h5c028258ca2fb3f5E>
+ 40: defe udf #254 ; 0xfe
+```
+"##,
+ },
+ Lint {
+ label: "compiler_builtins",
+ description: r##"# `compiler_builtins`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "concat_idents",
+ description: r##"# `concat_idents`
+
+The tracking issue for this feature is: [#29599]
+
+[#29599]: https://github.com/rust-lang/rust/issues/29599
+
+------------------------
+
+The `concat_idents` feature adds a macro for concatenating multiple identifiers
+into one identifier.
+
+## Examples
+
+```rust
+#![feature(concat_idents)]
+
+fn main() {
+ fn foobar() -> u32 { 23 }
+ let f = concat_idents!(foo, bar);
+ assert_eq!(f(), 23);
+}
+```
+"##,
+ },
+ Lint {
+ label: "const_eval_limit",
+ description: r##"# `const_eval_limit`
+
+The tracking issue for this feature is: [#67217]
+
+[#67217]: https://github.com/rust-lang/rust/issues/67217
+
+The `const_eval_limit` allows someone to limit the evaluation steps the CTFE undertakes to evaluate a `const fn`.
+"##,
+ },
+ Lint {
+ label: "core_intrinsics",
+ description: r##"# `core_intrinsics`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "core_panic",
+ description: r##"# `core_panic`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "core_private_bignum",
+ description: r##"# `core_private_bignum`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "core_private_diy_float",
+ description: r##"# `core_private_diy_float`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "crate_visibility_modifier",
+ description: r##"# `crate_visibility_modifier`
+
+The tracking issue for this feature is: [#53120]
+
+[#53120]: https://github.com/rust-lang/rust/issues/53120
+
+-----
+
+The `crate_visibility_modifier` feature allows the `crate` keyword to be used
+as a visibility modifier synonymous to `pub(crate)`, indicating that a type
+(function, _&c._) is to be visible to the entire enclosing crate, but not to
+other crates.
+
+```rust
+#![feature(crate_visibility_modifier)]
+
+crate struct Foo {
+ bar: usize,
+}
+```
+"##,
+ },
+ Lint {
+ label: "custom_test_frameworks",
+ description: r##"# `custom_test_frameworks`
+
+The tracking issue for this feature is: [#50297]
+
+[#50297]: https://github.com/rust-lang/rust/issues/50297
+
+------------------------
+
+The `custom_test_frameworks` feature allows the use of `#[test_case]` and `#![test_runner]`.
+Any function, const, or static can be annotated with `#[test_case]` causing it to be aggregated (like `#[test]`)
+and be passed to the test runner determined by the `#![test_runner]` crate attribute.
+
+```rust
+#![feature(custom_test_frameworks)]
+#![test_runner(my_runner)]
+
+fn my_runner(tests: &[&i32]) {
+ for t in tests {
+ if **t == 0 {
+ println!("PASSED");
+ } else {
+ println!("FAILED");
+ }
+ }
+}
+
+#[test_case]
+const WILL_PASS: i32 = 0;
+
+#[test_case]
+const WILL_FAIL: i32 = 4;
+```
+"##,
+ },
+ Lint {
+ label: "dec2flt",
+ description: r##"# `dec2flt`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "default_free_fn",
+ description: r##"# `default_free_fn`
+
+The tracking issue for this feature is: [#73014]
+
+[#73014]: https://github.com/rust-lang/rust/issues/73014
+
+------------------------
+
+Adds a free `default()` function to the `std::default` module. This function
+just forwards to [`Default::default()`], but may remove repetition of the word
+"default" from the call site.
+
+[`Default::default()`]: https://doc.rust-lang.org/nightly/std/default/trait.Default.html#tymethod.default
+
+Here is an example:
+
+```rust
+#![feature(default_free_fn)]
+use std::default::default;
+
+#[derive(Default)]
+struct AppConfig {
+ foo: FooConfig,
+ bar: BarConfig,
+}
+
+#[derive(Default)]
+struct FooConfig {
+ foo: i32,
+}
+
+#[derive(Default)]
+struct BarConfig {
+ bar: f32,
+ baz: u8,
+}
+
+fn main() {
+ let options = AppConfig {
+ foo: default(),
+ bar: BarConfig {
+ bar: 10.1,
+ ..default()
+ },
+ };
+}
+```
+"##,
+ },
+ Lint {
+ label: "derive_clone_copy",
+ description: r##"# `derive_clone_copy`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "derive_eq",
+ description: r##"# `derive_eq`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "doc_cfg",
+ description: r##"# `doc_cfg`
+
+The tracking issue for this feature is: [#43781]
+
+------
+
+The `doc_cfg` feature allows an API be documented as only available in some specific platforms.
+This attribute has two effects:
+
+1. In the annotated item's documentation, there will be a message saying "This is supported on
+ (platform) only".
+
+2. The item's doc-tests will only run on the specific platform.
+
+In addition to allowing the use of the `#[doc(cfg)]` attribute, this feature enables the use of a
+special conditional compilation flag, `#[cfg(doc)]`, set whenever building documentation on your
+crate.
+
+This feature was introduced as part of PR [#43348] to allow the platform-specific parts of the
+standard library be documented.
+
+```rust
+#![feature(doc_cfg)]
+
+#[cfg(any(windows, doc))]
+#[doc(cfg(windows))]
+/// The application's icon in the notification area (a.k.a. system tray).
+///
+/// # Examples
+///
+/// ```no_run
+/// extern crate my_awesome_ui_library;
+/// use my_awesome_ui_library::current_app;
+/// use my_awesome_ui_library::windows::notification;
+///
+/// let icon = current_app().get::<notification::Icon>();
+/// icon.show();
+/// icon.show_message("Hello");
+/// ```
+pub struct Icon {
+ // ...
+}
+```
+
+[#43781]: https://github.com/rust-lang/rust/issues/43781
+[#43348]: https://github.com/rust-lang/rust/issues/43348
+"##,
+ },
+ Lint {
+ label: "doc_masked",
+ description: r##"# `doc_masked`
+
+The tracking issue for this feature is: [#44027]
+
+-----
+
+The `doc_masked` feature allows a crate to exclude types from a given crate from appearing in lists
+of trait implementations. The specifics of the feature are as follows:
+
+1. When rustdoc encounters an `extern crate` statement annotated with a `#[doc(masked)]` attribute,
+ it marks the crate as being masked.
+
+2. When listing traits a given type implements, rustdoc ensures that traits from masked crates are
+ not emitted into the documentation.
+
+3. When listing types that implement a given trait, rustdoc ensures that types from masked crates
+ are not emitted into the documentation.
+
+This feature was introduced in PR [#44026] to ensure that compiler-internal and
+implementation-specific types and traits were not included in the standard library's documentation.
+Such types would introduce broken links into the documentation.
+
+[#44026]: https://github.com/rust-lang/rust/pull/44026
+[#44027]: https://github.com/rust-lang/rust/pull/44027
+"##,
+ },
+ Lint {
+ label: "doc_notable_trait",
+ description: r##"# `doc_notable_trait`
+
+The tracking issue for this feature is: [#45040]
+
+The `doc_notable_trait` feature allows the use of the `#[doc(notable_trait)]`
+attribute, which will display the trait in a "Notable traits" dialog for
+functions returning types that implement the trait. For example, this attribute
+is applied to the `Iterator`, `Future`, `io::Read`, and `io::Write` traits in
+the standard library.
+
+You can do this on your own traits like so:
+
+```
+#![feature(doc_notable_trait)]
+
+#[doc(notable_trait)]
+pub trait MyTrait {}
+
+pub struct MyStruct;
+impl MyTrait for MyStruct {}
+
+/// The docs for this function will have a button that displays a dialog about
+/// `MyStruct` implementing `MyTrait`.
+pub fn my_fn() -> MyStruct { MyStruct }
+```
+
+This feature was originally implemented in PR [#45039].
+
+See also its documentation in [the rustdoc book][rustdoc-book-notable_trait].
+
+[#45040]: https://github.com/rust-lang/rust/issues/45040
+[#45039]: https://github.com/rust-lang/rust/pull/45039
+[rustdoc-book-notable_trait]: ../../rustdoc/unstable-features.html#adding-your-trait-to-the-notable-traits-dialog
+"##,
+ },
+ Lint {
+ label: "exclusive_range_pattern",
+ description: r##"# `exclusive_range_pattern`
+
+The tracking issue for this feature is: [#37854].
+
+
+[#67264]: https://github.com/rust-lang/rust/issues/67264
+[#37854]: https://github.com/rust-lang/rust/issues/37854
+-----
+
+The `exclusive_range_pattern` feature allows non-inclusive range
+patterns (`0..10`) to be used in appropriate pattern matching
+contexts. It also can be combined with `#![feature(half_open_range_patterns]`
+to be able to use RangeTo patterns (`..10`).
+
+It also enabled RangeFrom patterns but that has since been
+stabilized.
+
+```rust
+#![feature(exclusive_range_pattern)]
+ let x = 5;
+ match x {
+ 0..10 => println!("single digit"),
+ 10 => println!("ten isn't part of the above range"),
+ _ => println!("nor is everything else.")
+ }
+```
+"##,
+ },
+ Lint {
+ label: "explicit_generic_args_with_impl_trait",
+ description: r##"# `explicit_generic_args_with_impl_trait`
+
+The tracking issue for this feature is: [#83701]
+
+[#83701]: https://github.com/rust-lang/rust/issues/83701
+
+------------------------
+
+The `explicit_generic_args_with_impl_trait` feature gate lets you specify generic arguments even
+when `impl Trait` is used in argument position.
+
+A simple example is:
+
+```rust
+#![feature(explicit_generic_args_with_impl_trait)]
+
+fn foo<T: ?Sized>(_f: impl AsRef<T>) {}
+
+fn main() {
+ foo::<str>("".to_string());
+}
+```
+
+This is currently rejected:
+
+```text
+error[E0632]: cannot provide explicit generic arguments when `impl Trait` is used in argument position
+ --> src/main.rs:6:11
+ |
+6 | foo::<str>("".to_string());
+ | ^^^ explicit generic argument not allowed
+
+```
+
+However it would compile if `explicit_generic_args_with_impl_trait` is enabled.
+
+Note that the synthetic type parameters from `impl Trait` are still implicit and you
+cannot explicitly specify these:
+
+```rust,compile_fail
+#![feature(explicit_generic_args_with_impl_trait)]
+
+fn foo<T: ?Sized>(_f: impl AsRef<T>) {}
+fn bar<T: ?Sized, F: AsRef<T>>(_f: F) {}
+
+fn main() {
+ bar::<str, _>("".to_string()); // Okay
+ bar::<str, String>("".to_string()); // Okay
+
+ foo::<str>("".to_string()); // Okay
+ foo::<str, String>("".to_string()); // Error, you cannot specify `impl Trait` explicitly
+}
+```
+"##,
+ },
+ Lint {
+ label: "fd",
+ description: r##"# `fd`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "fd_read",
+ description: r##"# `fd_read`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "ffi_const",
+ description: r##"# `ffi_const`
+
+The tracking issue for this feature is: [#58328]
+
+------
+
+The `#[ffi_const]` attribute applies clang's `const` attribute to foreign
+functions declarations.
+
+That is, `#[ffi_const]` functions shall have no effects except for its return
+value, which can only depend on the values of the function parameters, and is
+not affected by changes to the observable state of the program.
+
+Applying the `#[ffi_const]` attribute to a function that violates these
+requirements is undefined behaviour.
+
+This attribute enables Rust to perform common optimizations, like sub-expression
+elimination, and it can avoid emitting some calls in repeated invocations of the
+function with the same argument values regardless of other operations being
+performed in between these functions calls (as opposed to `#[ffi_pure]`
+functions).
+
+## Pitfalls
+
+A `#[ffi_const]` function can only read global memory that would not affect
+its return value for the whole execution of the program (e.g. immutable global
+memory). `#[ffi_const]` functions are referentially-transparent and therefore
+more strict than `#[ffi_pure]` functions.
+
+A common pitfall involves applying the `#[ffi_const]` attribute to a
+function that reads memory through pointer arguments which do not necessarily
+point to immutable global memory.
+
+A `#[ffi_const]` function that returns unit has no effect on the abstract
+machine's state, and a `#[ffi_const]` function cannot be `#[ffi_pure]`.
+
+A `#[ffi_const]` function must not diverge, neither via a side effect (e.g. a
+call to `abort`) nor by infinite loops.
+
+When translating C headers to Rust FFI, it is worth verifying for which targets
+the `const` attribute is enabled in those headers, and using the appropriate
+`cfg` macros in the Rust side to match those definitions. While the semantics of
+`const` are implemented identically by many C and C++ compilers, e.g., clang,
+[GCC], [ARM C/C++ compiler], [IBM ILE C/C++], etc. they are not necessarily
+implemented in this way on all of them. It is therefore also worth verifying
+that the semantics of the C toolchain used to compile the binary being linked
+against are compatible with those of the `#[ffi_const]`.
+
+[#58328]: https://github.com/rust-lang/rust/issues/58328
+[ARM C/C++ compiler]: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0491c/Cacgigch.html
+[GCC]: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-const-function-attribute
+[IBM ILE C/C++]: https://www.ibm.com/support/knowledgecenter/fr/ssw_ibm_i_71/rzarg/fn_attrib_const.htm
+"##,
+ },
+ Lint {
+ label: "ffi_pure",
+ description: r##"# `ffi_pure`
+
+The tracking issue for this feature is: [#58329]
+
+------
+
+The `#[ffi_pure]` attribute applies clang's `pure` attribute to foreign
+functions declarations.
+
+That is, `#[ffi_pure]` functions shall have no effects except for its return
+value, which shall not change across two consecutive function calls with
+the same parameters.
+
+Applying the `#[ffi_pure]` attribute to a function that violates these
+requirements is undefined behavior.
+
+This attribute enables Rust to perform common optimizations, like sub-expression
+elimination and loop optimizations. Some common examples of pure functions are
+`strlen` or `memcmp`.
+
+These optimizations are only applicable when the compiler can prove that no
+program state observable by the `#[ffi_pure]` function has changed between calls
+of the function, which could alter the result. See also the `#[ffi_const]`
+attribute, which provides stronger guarantees regarding the allowable behavior
+of a function, enabling further optimization.
+
+## Pitfalls
+
+A `#[ffi_pure]` function can read global memory through the function
+parameters (e.g. pointers), globals, etc. `#[ffi_pure]` functions are not
+referentially-transparent, and are therefore more relaxed than `#[ffi_const]`
+functions.
+
+However, accessing global memory through volatile or atomic reads can violate the
+requirement that two consecutive function calls shall return the same value.
+
+A `pure` function that returns unit has no effect on the abstract machine's
+state.
+
+A `#[ffi_pure]` function must not diverge, neither via a side effect (e.g. a
+call to `abort`) nor by infinite loops.
+
+When translating C headers to Rust FFI, it is worth verifying for which targets
+the `pure` attribute is enabled in those headers, and using the appropriate
+`cfg` macros in the Rust side to match those definitions. While the semantics of
+`pure` are implemented identically by many C and C++ compilers, e.g., clang,
+[GCC], [ARM C/C++ compiler], [IBM ILE C/C++], etc. they are not necessarily
+implemented in this way on all of them. It is therefore also worth verifying
+that the semantics of the C toolchain used to compile the binary being linked
+against are compatible with those of the `#[ffi_pure]`.
+
+
+[#58329]: https://github.com/rust-lang/rust/issues/58329
+[ARM C/C++ compiler]: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0491c/Cacigdac.html
+[GCC]: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-pure-function-attribute
+[IBM ILE C/C++]: https://www.ibm.com/support/knowledgecenter/fr/ssw_ibm_i_71/rzarg/fn_attrib_pure.htm
+"##,
+ },
+ Lint {
+ label: "flt2dec",
+ description: r##"# `flt2dec`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "fmt_internals",
+ description: r##"# `fmt_internals`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "fn_traits",
+ description: r##"# `fn_traits`
+
+The tracking issue for this feature is [#29625]
+
+See Also: [`unboxed_closures`](../language-features/unboxed-closures.md)
+
+[#29625]: https://github.com/rust-lang/rust/issues/29625
+
+----
+
+The `fn_traits` feature allows for implementation of the [`Fn*`] traits
+for creating custom closure-like types.
+
+[`Fn*`]: https://doc.rust-lang.org/std/ops/trait.Fn.html
+
+```rust
+#![feature(unboxed_closures)]
+#![feature(fn_traits)]
+
+struct Adder {
+ a: u32
+}
+
+impl FnOnce<(u32, )> for Adder {
+ type Output = u32;
+ extern "rust-call" fn call_once(self, b: (u32, )) -> Self::Output {
+ self.a + b.0
+ }
+}
+
+fn main() {
+ let adder = Adder { a: 3 };
+ assert_eq!(adder(2), 5);
+}
+```
+"##,
+ },
+ Lint {
+ label: "generators",
+ description: r##"# `generators`
+
+The tracking issue for this feature is: [#43122]
+
+[#43122]: https://github.com/rust-lang/rust/issues/43122
+
+------------------------
+
+The `generators` feature gate in Rust allows you to define generator or
+coroutine literals. A generator is a "resumable function" that syntactically
+resembles a closure but compiles to much different semantics in the compiler
+itself. The primary feature of a generator is that it can be suspended during
+execution to be resumed at a later date. Generators use the `yield` keyword to
+"return", and then the caller can `resume` a generator to resume execution just
+after the `yield` keyword.
+
+Generators are an extra-unstable feature in the compiler right now. Added in
+[RFC 2033] they're mostly intended right now as a information/constraint
+gathering phase. The intent is that experimentation can happen on the nightly
+compiler before actual stabilization. A further RFC will be required to
+stabilize generators/coroutines and will likely contain at least a few small
+tweaks to the overall design.
+
+[RFC 2033]: https://github.com/rust-lang/rfcs/pull/2033
+
+A syntactical example of a generator is:
+
+```rust
+#![feature(generators, generator_trait)]
+
+use std::ops::{Generator, GeneratorState};
+use std::pin::Pin;
+
+fn main() {
+ let mut generator = || {
+ yield 1;
+ return "foo"
+ };
+
+ match Pin::new(&mut generator).resume(()) {
+ GeneratorState::Yielded(1) => {}
+ _ => panic!("unexpected value from resume"),
+ }
+ match Pin::new(&mut generator).resume(()) {
+ GeneratorState::Complete("foo") => {}
+ _ => panic!("unexpected value from resume"),
+ }
+}
+```
+
+Generators are closure-like literals which can contain a `yield` statement. The
+`yield` statement takes an optional expression of a value to yield out of the
+generator. All generator literals implement the `Generator` trait in the
+`std::ops` module. The `Generator` trait has one main method, `resume`, which
+resumes execution of the generator at the previous suspension point.
+
+An example of the control flow of generators is that the following example
+prints all numbers in order:
+
+```rust
+#![feature(generators, generator_trait)]
+
+use std::ops::Generator;
+use std::pin::Pin;
+
+fn main() {
+ let mut generator = || {
+ println!("2");
+ yield;
+ println!("4");
+ };
+
+ println!("1");
+ Pin::new(&mut generator).resume(());
+ println!("3");
+ Pin::new(&mut generator).resume(());
+ println!("5");
+}
+```
+
+At this time the main intended use case of generators is an implementation
+primitive for async/await syntax, but generators will likely be extended to
+ergonomic implementations of iterators and other primitives in the future.
+Feedback on the design and usage is always appreciated!
+
+### The `Generator` trait
+
+The `Generator` trait in `std::ops` currently looks like:
+
+```rust
+# #![feature(arbitrary_self_types, generator_trait)]
+# use std::ops::GeneratorState;
+# use std::pin::Pin;
+
+pub trait Generator<R = ()> {
+ type Yield;
+ type Return;
+ fn resume(self: Pin<&mut Self>, resume: R) -> GeneratorState<Self::Yield, Self::Return>;
+}
+```
+
+The `Generator::Yield` type is the type of values that can be yielded with the
+`yield` statement. The `Generator::Return` type is the returned type of the
+generator. This is typically the last expression in a generator's definition or
+any value passed to `return` in a generator. The `resume` function is the entry
+point for executing the `Generator` itself.
+
+The return value of `resume`, `GeneratorState`, looks like:
+
+```rust
+pub enum GeneratorState<Y, R> {
+ Yielded(Y),
+ Complete(R),
+}
+```
+
+The `Yielded` variant indicates that the generator can later be resumed. This
+corresponds to a `yield` point in a generator. The `Complete` variant indicates
+that the generator is complete and cannot be resumed again. Calling `resume`
+after a generator has returned `Complete` will likely result in a panic of the
+program.
+
+### Closure-like semantics
+
+The closure-like syntax for generators alludes to the fact that they also have
+closure-like semantics. Namely:
+
+* When created, a generator executes no code. A closure literal does not
+ actually execute any of the closure's code on construction, and similarly a
+ generator literal does not execute any code inside the generator when
+ constructed.
+
+* Generators can capture outer variables by reference or by move, and this can
+ be tweaked with the `move` keyword at the beginning of the closure. Like
+ closures all generators will have an implicit environment which is inferred by
+ the compiler. Outer variables can be moved into a generator for use as the
+ generator progresses.
+
+* Generator literals produce a value with a unique type which implements the
+ `std::ops::Generator` trait. This allows actual execution of the generator
+ through the `Generator::resume` method as well as also naming it in return
+ types and such.
+
+* Traits like `Send` and `Sync` are automatically implemented for a `Generator`
+ depending on the captured variables of the environment. Unlike closures,
+ generators also depend on variables live across suspension points. This means
+ that although the ambient environment may be `Send` or `Sync`, the generator
+ itself may not be due to internal variables live across `yield` points being
+ not-`Send` or not-`Sync`. Note that generators do
+ not implement traits like `Copy` or `Clone` automatically.
+
+* Whenever a generator is dropped it will drop all captured environment
+ variables.
+
+### Generators as state machines
+
+In the compiler, generators are currently compiled as state machines. Each
+`yield` expression will correspond to a different state that stores all live
+variables over that suspension point. Resumption of a generator will dispatch on
+the current state and then execute internally until a `yield` is reached, at
+which point all state is saved off in the generator and a value is returned.
+
+Let's take a look at an example to see what's going on here:
+
+```rust
+#![feature(generators, generator_trait)]
+
+use std::ops::Generator;
+use std::pin::Pin;
+
+fn main() {
+ let ret = "foo";
+ let mut generator = move || {
+ yield 1;
+ return ret
+ };
+
+ Pin::new(&mut generator).resume(());
+ Pin::new(&mut generator).resume(());
+}
+```
+
+This generator literal will compile down to something similar to:
+
+```rust
+#![feature(arbitrary_self_types, generators, generator_trait)]
+
+use std::ops::{Generator, GeneratorState};
+use std::pin::Pin;
+
+fn main() {
+ let ret = "foo";
+ let mut generator = {
+ enum __Generator {
+ Start(&'static str),
+ Yield1(&'static str),
+ Done,
+ }
+
+ impl Generator for __Generator {
+ type Yield = i32;
+ type Return = &'static str;
+
+ fn resume(mut self: Pin<&mut Self>, resume: ()) -> GeneratorState<i32, &'static str> {
+ use std::mem;
+ match mem::replace(&mut *self, __Generator::Done) {
+ __Generator::Start(s) => {
+ *self = __Generator::Yield1(s);
+ GeneratorState::Yielded(1)
+ }
+
+ __Generator::Yield1(s) => {
+ *self = __Generator::Done;
+ GeneratorState::Complete(s)
+ }
+
+ __Generator::Done => {
+ panic!("generator resumed after completion")
+ }
+ }
+ }
+ }
+
+ __Generator::Start(ret)
+ };
+
+ Pin::new(&mut generator).resume(());
+ Pin::new(&mut generator).resume(());
+}
+```
+
+Notably here we can see that the compiler is generating a fresh type,
+`__Generator` in this case. This type has a number of states (represented here
+as an `enum`) corresponding to each of the conceptual states of the generator.
+At the beginning we're closing over our outer variable `foo` and then that
+variable is also live over the `yield` point, so it's stored in both states.
+
+When the generator starts it'll immediately yield 1, but it saves off its state
+just before it does so indicating that it has reached the yield point. Upon
+resuming again we'll execute the `return ret` which returns the `Complete`
+state.
+
+Here we can also note that the `Done` state, if resumed, panics immediately as
+it's invalid to resume a completed generator. It's also worth noting that this
+is just a rough desugaring, not a normative specification for what the compiler
+does.
+"##,
+ },
+ Lint {
+ label: "half_open_range_patterns",
+ description: r##"# `half_open_range_patterns`
+
+The tracking issue for this feature is: [#67264]
+It is part of the `#![exclusive_range_pattern]` feature,
+tracked at [#37854].
+
+[#67264]: https://github.com/rust-lang/rust/issues/67264
+[#37854]: https://github.com/rust-lang/rust/issues/37854
+-----
+
+The `half_open_range_patterns` feature allows RangeTo patterns
+(`..10`) to be used in appropriate pattern matching contexts.
+This requires also enabling the `exclusive_range_pattern` feature.
+
+It also enabled RangeFrom patterns but that has since been
+stabilized.
+
+```rust
+#![feature(half_open_range_patterns)]
+#![feature(exclusive_range_pattern)]
+ let x = 5;
+ match x {
+ ..0 => println!("negative!"), // "RangeTo" pattern. Unstable.
+ 0 => println!("zero!"),
+ 1.. => println!("positive!"), // "RangeFrom" pattern. Stable.
+ }
+```
+"##,
+ },
+ Lint {
+ label: "infer_static_outlives_requirements",
+ description: r##"# `infer_static_outlives_requirements`
+
+The tracking issue for this feature is: [#54185]
+
+[#54185]: https://github.com/rust-lang/rust/issues/54185
+
+------------------------
+The `infer_static_outlives_requirements` feature indicates that certain
+`'static` outlives requirements can be inferred by the compiler rather than
+stating them explicitly.
+
+Note: It is an accompanying feature to `infer_outlives_requirements`,
+which must be enabled to infer outlives requirements.
+
+For example, currently generic struct definitions that contain
+references, require where-clauses of the form T: 'static. By using
+this feature the outlives predicates will be inferred, although
+they may still be written explicitly.
+
+```rust,ignore (pseudo-Rust)
+struct Foo<U> where U: 'static { // <-- currently required
+ bar: Bar<U>
+}
+struct Bar<T: 'static> {
+ x: T,
+}
+```
+
+
+## Examples:
+
+```rust,ignore (pseudo-Rust)
+#![feature(infer_outlives_requirements)]
+#![feature(infer_static_outlives_requirements)]
+
+#[rustc_outlives]
+// Implicitly infer U: 'static
+struct Foo<U> {
+ bar: Bar<U>
+}
+struct Bar<T: 'static> {
+ x: T,
+}
+```
+"##,
+ },
+ Lint {
+ label: "inline_const",
+ description: r##"# `inline_const`
+
+The tracking issue for this feature is: [#76001]
+
+See also [`inline_const_pat`](inline-const-pat.md)
+
+------
+
+This feature allows you to use inline constant expressions. For example, you can
+turn this code:
+
+```rust
+# fn add_one(x: i32) -> i32 { x + 1 }
+const MY_COMPUTATION: i32 = 1 + 2 * 3 / 4;
+
+fn main() {
+ let x = add_one(MY_COMPUTATION);
+}
+```
+
+into this code:
+
+```rust
+#![feature(inline_const)]
+
+# fn add_one(x: i32) -> i32 { x + 1 }
+fn main() {
+ let x = add_one(const { 1 + 2 * 3 / 4 });
+}
+```
+
+[#76001]: https://github.com/rust-lang/rust/issues/76001
+"##,
+ },
+ Lint {
+ label: "inline_const_pat",
+ description: r##"# `inline_const_pat`
+
+The tracking issue for this feature is: [#76001]
+
+See also [`inline_const`](inline-const.md)
+
+------
+
+This feature allows you to use inline constant expressions in pattern position:
+
+```rust
+#![feature(inline_const_pat)]
+
+const fn one() -> i32 { 1 }
+
+let some_int = 3;
+match some_int {
+ const { 1 + 2 } => println!("Matched 1 + 2"),
+ const { one() } => println!("Matched const fn returning 1"),
+ _ => println!("Didn't match anything :("),
+}
+```
+
+[#76001]: https://github.com/rust-lang/rust/issues/76001
+"##,
+ },
+ Lint {
+ label: "int_error_internals",
+ description: r##"# `int_error_internals`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "internal_output_capture",
+ description: r##"# `internal_output_capture`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "intra_doc_pointers",
+ description: r##"# `intra-doc-pointers`
+
+The tracking issue for this feature is: [#80896]
+
+[#80896]: https://github.com/rust-lang/rust/issues/80896
+
+------------------------
+
+Rustdoc does not currently allow disambiguating between `*const` and `*mut`, and
+raw pointers in intra-doc links are unstable until it does.
+
+```rust
+#![feature(intra_doc_pointers)]
+//! [pointer::add]
+```
+"##,
+ },
+ Lint {
+ label: "intrinsics",
+ description: r##"# `intrinsics`
+
+The tracking issue for this feature is: None.
+
+Intrinsics are never intended to be stable directly, but intrinsics are often
+exported in some sort of stable manner. Prefer using the stable interfaces to
+the intrinsic directly when you can.
+
+------------------------
+
+
+These are imported as if they were FFI functions, with the special
+`rust-intrinsic` ABI. For example, if one was in a freestanding
+context, but wished to be able to `transmute` between types, and
+perform efficient pointer arithmetic, one would import those functions
+via a declaration like
+
+```rust
+#![feature(intrinsics)]
+# fn main() {}
+
+extern "rust-intrinsic" {
+ fn transmute<T, U>(x: T) -> U;
+
+ fn offset<T>(dst: *const T, offset: isize) -> *const T;
+}
+```
+
+As with any other FFI functions, these are always `unsafe` to call.
+"##,
+ },
+ Lint {
+ label: "is_sorted",
+ description: r##"# `is_sorted`
+
+The tracking issue for this feature is: [#53485]
+
+[#53485]: https://github.com/rust-lang/rust/issues/53485
+
+------------------------
+
+Add the methods `is_sorted`, `is_sorted_by` and `is_sorted_by_key` to `[T]`;
+add the methods `is_sorted`, `is_sorted_by` and `is_sorted_by_key` to
+`Iterator`.
+"##,
+ },
+ Lint {
+ label: "lang_items",
+ description: r##"# `lang_items`
+
+The tracking issue for this feature is: None.
+
+------------------------
+
+The `rustc` compiler has certain pluggable operations, that is,
+functionality that isn't hard-coded into the language, but is
+implemented in libraries, with a special marker to tell the compiler
+it exists. The marker is the attribute `#[lang = "..."]` and there are
+various different values of `...`, i.e. various different 'lang
+items'.
+
+For example, `Box` pointers require two lang items, one for allocation
+and one for deallocation. A freestanding program that uses the `Box`
+sugar for dynamic allocations via `malloc` and `free`:
+
+```rust,ignore (libc-is-finicky)
+#![feature(lang_items, box_syntax, start, libc, core_intrinsics, rustc_private)]
+#![no_std]
+use core::intrinsics;
+use core::panic::PanicInfo;
+
+extern crate libc;
+
+#[lang = "owned_box"]
+pub struct Box<T>(*mut T);
+
+#[lang = "exchange_malloc"]
+unsafe fn allocate(size: usize, _align: usize) -> *mut u8 {
+ let p = libc::malloc(size as libc::size_t) as *mut u8;
+
+ // Check if `malloc` failed:
+ if p as usize == 0 {
+ intrinsics::abort();
+ }
+
+ p
+}
+
+#[lang = "box_free"]
+unsafe fn box_free<T: ?Sized>(ptr: *mut T) {
+ libc::free(ptr as *mut libc::c_void)
+}
+
+#[start]
+fn main(_argc: isize, _argv: *const *const u8) -> isize {
+ let _x = box 1;
+
+ 0
+}
+
+#[lang = "eh_personality"] extern fn rust_eh_personality() {}
+#[lang = "panic_impl"] extern fn rust_begin_panic(info: &PanicInfo) -> ! { unsafe { intrinsics::abort() } }
+#[no_mangle] pub extern fn rust_eh_register_frames () {}
+#[no_mangle] pub extern fn rust_eh_unregister_frames () {}
+```
+
+Note the use of `abort`: the `exchange_malloc` lang item is assumed to
+return a valid pointer, and so needs to do the check internally.
+
+Other features provided by lang items include:
+
+- overloadable operators via traits: the traits corresponding to the
+ `==`, `<`, dereferencing (`*`) and `+` (etc.) operators are all
+ marked with lang items; those specific four are `eq`, `ord`,
+ `deref`, and `add` respectively.
+- stack unwinding and general failure; the `eh_personality`,
+ `panic` and `panic_bounds_check` lang items.
+- the traits in `std::marker` used to indicate types of
+ various kinds; lang items `send`, `sync` and `copy`.
+- the marker types and variance indicators found in
+ `std::marker`; lang items `covariant_type`,
+ `contravariant_lifetime`, etc.
+
+Lang items are loaded lazily by the compiler; e.g. if one never uses
+`Box` then there is no need to define functions for `exchange_malloc`
+and `box_free`. `rustc` will emit an error when an item is needed
+but not found in the current crate or any that it depends on.
+
+Most lang items are defined by `libcore`, but if you're trying to build
+an executable without the standard library, you'll run into the need
+for lang items. The rest of this page focuses on this use-case, even though
+lang items are a bit broader than that.
+
+### Using libc
+
+In order to build a `#[no_std]` executable we will need libc as a dependency.
+We can specify this using our `Cargo.toml` file:
+
+```toml
+[dependencies]
+libc = { version = "0.2.14", default-features = false }
+```
+
+Note that the default features have been disabled. This is a critical step -
+**the default features of libc include the standard library and so must be
+disabled.**
+
+### Writing an executable without stdlib
+
+Controlling the entry point is possible in two ways: the `#[start]` attribute,
+or overriding the default shim for the C `main` function with your own.
+
+The function marked `#[start]` is passed the command line parameters
+in the same format as C:
+
+```rust,ignore (libc-is-finicky)
+#![feature(lang_items, core_intrinsics, rustc_private)]
+#![feature(start)]
+#![no_std]
+use core::intrinsics;
+use core::panic::PanicInfo;
+
+// Pull in the system libc library for what crt0.o likely requires.
+extern crate libc;
+
+// Entry point for this program.
+#[start]
+fn start(_argc: isize, _argv: *const *const u8) -> isize {
+ 0
+}
+
+// These functions are used by the compiler, but not
+// for a bare-bones hello world. These are normally
+// provided by libstd.
+#[lang = "eh_personality"]
+#[no_mangle]
+pub extern fn rust_eh_personality() {
+}
+
+#[lang = "panic_impl"]
+#[no_mangle]
+pub extern fn rust_begin_panic(info: &PanicInfo) -> ! {
+ unsafe { intrinsics::abort() }
+}
+```
+
+To override the compiler-inserted `main` shim, one has to disable it
+with `#![no_main]` and then create the appropriate symbol with the
+correct ABI and the correct name, which requires overriding the
+compiler's name mangling too:
+
+```rust,ignore (libc-is-finicky)
+#![feature(lang_items, core_intrinsics, rustc_private)]
+#![feature(start)]
+#![no_std]
+#![no_main]
+use core::intrinsics;
+use core::panic::PanicInfo;
+
+// Pull in the system libc library for what crt0.o likely requires.
+extern crate libc;
+
+// Entry point for this program.
+#[no_mangle] // ensure that this symbol is called `main` in the output
+pub extern fn main(_argc: i32, _argv: *const *const u8) -> i32 {
+ 0
+}
+
+// These functions are used by the compiler, but not
+// for a bare-bones hello world. These are normally
+// provided by libstd.
+#[lang = "eh_personality"]
+#[no_mangle]
+pub extern fn rust_eh_personality() {
+}
+
+#[lang = "panic_impl"]
+#[no_mangle]
+pub extern fn rust_begin_panic(info: &PanicInfo) -> ! {
+ unsafe { intrinsics::abort() }
+}
+```
+
+In many cases, you may need to manually link to the `compiler_builtins` crate
+when building a `no_std` binary. You may observe this via linker error messages
+such as "```undefined reference to `__rust_probestack'```".
+
+## More about the language items
+
+The compiler currently makes a few assumptions about symbols which are
+available in the executable to call. Normally these functions are provided by
+the standard library, but without it you must define your own. These symbols
+are called "language items", and they each have an internal name, and then a
+signature that an implementation must conform to.
+
+The first of these functions, `rust_eh_personality`, is used by the failure
+mechanisms of the compiler. This is often mapped to GCC's personality function
+(see the [libstd implementation][unwind] for more information), but crates
+which do not trigger a panic can be assured that this function is never
+called. The language item's name is `eh_personality`.
+
+[unwind]: https://github.com/rust-lang/rust/blob/master/library/panic_unwind/src/gcc.rs
+
+The second function, `rust_begin_panic`, is also used by the failure mechanisms of the
+compiler. When a panic happens, this controls the message that's displayed on
+the screen. While the language item's name is `panic_impl`, the symbol name is
+`rust_begin_panic`.
+
+Finally, a `eh_catch_typeinfo` static is needed for certain targets which
+implement Rust panics on top of C++ exceptions.
+
+## List of all language items
+
+This is a list of all language items in Rust along with where they are located in
+the source code.
+
+- Primitives
+ - `i8`: `libcore/num/mod.rs`
+ - `i16`: `libcore/num/mod.rs`
+ - `i32`: `libcore/num/mod.rs`
+ - `i64`: `libcore/num/mod.rs`
+ - `i128`: `libcore/num/mod.rs`
+ - `isize`: `libcore/num/mod.rs`
+ - `u8`: `libcore/num/mod.rs`
+ - `u16`: `libcore/num/mod.rs`
+ - `u32`: `libcore/num/mod.rs`
+ - `u64`: `libcore/num/mod.rs`
+ - `u128`: `libcore/num/mod.rs`
+ - `usize`: `libcore/num/mod.rs`
+ - `f32`: `libstd/f32.rs`
+ - `f64`: `libstd/f64.rs`
+ - `char`: `libcore/char.rs`
+ - `slice`: `liballoc/slice.rs`
+ - `str`: `liballoc/str.rs`
+ - `const_ptr`: `libcore/ptr.rs`
+ - `mut_ptr`: `libcore/ptr.rs`
+ - `unsafe_cell`: `libcore/cell.rs`
+- Runtime
+ - `start`: `libstd/rt.rs`
+ - `eh_personality`: `libpanic_unwind/emcc.rs` (EMCC)
+ - `eh_personality`: `libpanic_unwind/gcc.rs` (GNU)
+ - `eh_personality`: `libpanic_unwind/seh.rs` (SEH)
+ - `eh_catch_typeinfo`: `libpanic_unwind/emcc.rs` (EMCC)
+ - `panic`: `libcore/panicking.rs`
+ - `panic_bounds_check`: `libcore/panicking.rs`
+ - `panic_impl`: `libcore/panicking.rs`
+ - `panic_impl`: `libstd/panicking.rs`
+- Allocations
+ - `owned_box`: `liballoc/boxed.rs`
+ - `exchange_malloc`: `liballoc/heap.rs`
+ - `box_free`: `liballoc/heap.rs`
+- Operands
+ - `not`: `libcore/ops/bit.rs`
+ - `bitand`: `libcore/ops/bit.rs`
+ - `bitor`: `libcore/ops/bit.rs`
+ - `bitxor`: `libcore/ops/bit.rs`
+ - `shl`: `libcore/ops/bit.rs`
+ - `shr`: `libcore/ops/bit.rs`
+ - `bitand_assign`: `libcore/ops/bit.rs`
+ - `bitor_assign`: `libcore/ops/bit.rs`
+ - `bitxor_assign`: `libcore/ops/bit.rs`
+ - `shl_assign`: `libcore/ops/bit.rs`
+ - `shr_assign`: `libcore/ops/bit.rs`
+ - `deref`: `libcore/ops/deref.rs`
+ - `deref_mut`: `libcore/ops/deref.rs`
+ - `index`: `libcore/ops/index.rs`
+ - `index_mut`: `libcore/ops/index.rs`
+ - `add`: `libcore/ops/arith.rs`
+ - `sub`: `libcore/ops/arith.rs`
+ - `mul`: `libcore/ops/arith.rs`
+ - `div`: `libcore/ops/arith.rs`
+ - `rem`: `libcore/ops/arith.rs`
+ - `neg`: `libcore/ops/arith.rs`
+ - `add_assign`: `libcore/ops/arith.rs`
+ - `sub_assign`: `libcore/ops/arith.rs`
+ - `mul_assign`: `libcore/ops/arith.rs`
+ - `div_assign`: `libcore/ops/arith.rs`
+ - `rem_assign`: `libcore/ops/arith.rs`
+ - `eq`: `libcore/cmp.rs`
+ - `ord`: `libcore/cmp.rs`
+- Functions
+ - `fn`: `libcore/ops/function.rs`
+ - `fn_mut`: `libcore/ops/function.rs`
+ - `fn_once`: `libcore/ops/function.rs`
+ - `generator_state`: `libcore/ops/generator.rs`
+ - `generator`: `libcore/ops/generator.rs`
+- Other
+ - `coerce_unsized`: `libcore/ops/unsize.rs`
+ - `drop`: `libcore/ops/drop.rs`
+ - `drop_in_place`: `libcore/ptr.rs`
+ - `clone`: `libcore/clone.rs`
+ - `copy`: `libcore/marker.rs`
+ - `send`: `libcore/marker.rs`
+ - `sized`: `libcore/marker.rs`
+ - `unsize`: `libcore/marker.rs`
+ - `sync`: `libcore/marker.rs`
+ - `phantom_data`: `libcore/marker.rs`
+ - `discriminant_kind`: `libcore/marker.rs`
+ - `freeze`: `libcore/marker.rs`
+ - `debug_trait`: `libcore/fmt/mod.rs`
+ - `non_zero`: `libcore/nonzero.rs`
+ - `arc`: `liballoc/sync.rs`
+ - `rc`: `liballoc/rc.rs`
+"##,
+ },
+ Lint {
+ label: "libstd_sys_internals",
+ description: r##"# `libstd_sys_internals`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "libstd_thread_internals",
+ description: r##"# `libstd_thread_internals`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "link_cfg",
+ description: r##"# `link_cfg`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "llvm_asm",
+ description: r##"# `llvm_asm`
+
+The tracking issue for this feature is: [#70173]
+
+[#70173]: https://github.com/rust-lang/rust/issues/70173
+
+------------------------
+
+For extremely low-level manipulations and performance reasons, one
+might wish to control the CPU directly. Rust supports using inline
+assembly to do this via the `llvm_asm!` macro.
+
+```rust,ignore (pseudo-code)
+llvm_asm!(assembly template
+ : output operands
+ : input operands
+ : clobbers
+ : options
+ );
+```
+
+Any use of `llvm_asm` is feature gated (requires `#![feature(llvm_asm)]` on the
+crate to allow) and of course requires an `unsafe` block.
+
+> **Note**: the examples here are given in x86/x86-64 assembly, but
+> all platforms are supported.
+
+## Assembly template
+
+The `assembly template` is the only required parameter and must be a
+literal string (i.e. `""`)
+
+```rust
+#![feature(llvm_asm)]
+
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+fn foo() {
+ unsafe {
+ llvm_asm!("NOP");
+ }
+}
+
+// Other platforms:
+#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
+fn foo() { /* ... */ }
+
+fn main() {
+ // ...
+ foo();
+ // ...
+}
+```
+
+(The `feature(llvm_asm)` and `#[cfg]`s are omitted from now on.)
+
+Output operands, input operands, clobbers and options are all optional
+but you must add the right number of `:` if you skip them:
+
+```rust
+# #![feature(llvm_asm)]
+# #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+# fn main() { unsafe {
+llvm_asm!("xor %eax, %eax"
+ :
+ :
+ : "eax"
+ );
+# } }
+# #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
+# fn main() {}
+```
+
+Whitespace also doesn't matter:
+
+```rust
+# #![feature(llvm_asm)]
+# #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+# fn main() { unsafe {
+llvm_asm!("xor %eax, %eax" ::: "eax");
+# } }
+# #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
+# fn main() {}
+```
+
+## Operands
+
+Input and output operands follow the same format: `:
+"constraints1"(expr1), "constraints2"(expr2), ..."`. Output operand
+expressions must be mutable place, or not yet assigned:
+
+```rust
+# #![feature(llvm_asm)]
+# #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+fn add(a: i32, b: i32) -> i32 {
+ let c: i32;
+ unsafe {
+ llvm_asm!("add $2, $0"
+ : "=r"(c)
+ : "0"(a), "r"(b)
+ );
+ }
+ c
+}
+# #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
+# fn add(a: i32, b: i32) -> i32 { a + b }
+
+fn main() {
+ assert_eq!(add(3, 14159), 14162)
+}
+```
+
+If you would like to use real operands in this position, however,
+you are required to put curly braces `{}` around the register that
+you want, and you are required to put the specific size of the
+operand. This is useful for very low level programming, where
+which register you use is important:
+
+```rust
+# #![feature(llvm_asm)]
+# #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+# unsafe fn read_byte_in(port: u16) -> u8 {
+let result: u8;
+llvm_asm!("in %dx, %al" : "={al}"(result) : "{dx}"(port));
+result
+# }
+```
+
+## Clobbers
+
+Some instructions modify registers which might otherwise have held
+different values so we use the clobbers list to indicate to the
+compiler not to assume any values loaded into those registers will
+stay valid.
+
+```rust
+# #![feature(llvm_asm)]
+# #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+# fn main() { unsafe {
+// Put the value 0x200 in eax:
+llvm_asm!("mov $$0x200, %eax" : /* no outputs */ : /* no inputs */ : "eax");
+# } }
+# #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
+# fn main() {}
+```
+
+Input and output registers need not be listed since that information
+is already communicated by the given constraints. Otherwise, any other
+registers used either implicitly or explicitly should be listed.
+
+If the assembly changes the condition code register `cc` should be
+specified as one of the clobbers. Similarly, if the assembly modifies
+memory, `memory` should also be specified.
+
+## Options
+
+The last section, `options` is specific to Rust. The format is comma
+separated literal strings (i.e. `:"foo", "bar", "baz"`). It's used to
+specify some extra info about the inline assembly:
+
+Current valid options are:
+
+1. `volatile` - specifying this is analogous to
+ `__asm__ __volatile__ (...)` in gcc/clang.
+2. `alignstack` - certain instructions expect the stack to be
+ aligned a certain way (i.e. SSE) and specifying this indicates to
+ the compiler to insert its usual stack alignment code
+3. `intel` - use intel syntax instead of the default AT&T.
+
+```rust
+# #![feature(llvm_asm)]
+# #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+# fn main() {
+let result: i32;
+unsafe {
+ llvm_asm!("mov eax, 2" : "={eax}"(result) : : : "intel")
+}
+println!("eax is currently {}", result);
+# }
+# #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
+# fn main() {}
+```
+
+## More Information
+
+The current implementation of the `llvm_asm!` macro is a direct binding to [LLVM's
+inline assembler expressions][llvm-docs], so be sure to check out [their
+documentation as well][llvm-docs] for more information about clobbers,
+constraints, etc.
+
+[llvm-docs]: http://llvm.org/docs/LangRef.html#inline-assembler-expressions
+"##,
+ },
+ Lint {
+ label: "marker_trait_attr",
+ description: r##"# `marker_trait_attr`
+
+The tracking issue for this feature is: [#29864]
+
+[#29864]: https://github.com/rust-lang/rust/issues/29864
+
+------------------------
+
+Normally, Rust keeps you from adding trait implementations that could
+overlap with each other, as it would be ambiguous which to use. This
+feature, however, carves out an exception to that rule: a trait can
+opt-in to having overlapping implementations, at the cost that those
+implementations are not allowed to override anything (and thus the
+trait itself cannot have any associated items, as they're pointless
+when they'd need to do the same thing for every type anyway).
+
+```rust
+#![feature(marker_trait_attr)]
+
+#[marker] trait CheapToClone: Clone {}
+
+impl<T: Copy> CheapToClone for T {}
+
+// These could potentially overlap with the blanket implementation above,
+// so are only allowed because CheapToClone is a marker trait.
+impl<T: CheapToClone, U: CheapToClone> CheapToClone for (T, U) {}
+impl<T: CheapToClone> CheapToClone for std::ops::Range<T> {}
+
+fn cheap_clone<T: CheapToClone>(t: T) -> T {
+ t.clone()
+}
+```
+
+This is expected to replace the unstable `overlapping_marker_traits`
+feature, which applied to all empty traits (without needing an opt-in).
+"##,
+ },
+ Lint {
+ label: "more_qualified_paths",
+ description: r##"# `more_qualified_paths`
+
+The `more_qualified_paths` feature can be used in order to enable the
+use of qualified paths in patterns.
+
+## Example
+
+```rust
+#![feature(more_qualified_paths)]
+
+fn main() {
+ // destructure through a qualified path
+ let <Foo as A>::Assoc { br } = StructStruct { br: 2 };
+}
+
+struct StructStruct {
+ br: i8,
+}
+
+struct Foo;
+
+trait A {
+ type Assoc;
+}
+
+impl A for Foo {
+ type Assoc = StructStruct;
+}
+```
+"##,
+ },
+ Lint {
+ label: "native_link_modifiers",
+ description: r##"# `native_link_modifiers`
+
+The tracking issue for this feature is: [#81490]
+
+[#81490]: https://github.com/rust-lang/rust/issues/81490
+
+------------------------
+
+The `native_link_modifiers` feature allows you to use the `modifiers` syntax with the `#[link(..)]` attribute.
+
+Modifiers are specified as a comma-delimited string with each modifier prefixed with either a `+` or `-` to indicate that the modifier is enabled or disabled, respectively. The last boolean value specified for a given modifier wins.
+"##,
+ },
+ Lint {
+ label: "native_link_modifiers_as_needed",
+ description: r##"# `native_link_modifiers_as_needed`
+
+The tracking issue for this feature is: [#81490]
+
+[#81490]: https://github.com/rust-lang/rust/issues/81490
+
+------------------------
+
+The `native_link_modifiers_as_needed` feature allows you to use the `as-needed` modifier.
+
+`as-needed` is only compatible with the `dynamic` and `framework` linking kinds. Using any other kind will result in a compiler error.
+
+`+as-needed` means that the library will be actually linked only if it satisfies some undefined symbols at the point at which it is specified on the command line, making it similar to static libraries in this regard.
+
+This modifier translates to `--as-needed` for ld-like linkers, and to `-dead_strip_dylibs` / `-needed_library` / `-needed_framework` for ld64.
+The modifier does nothing for linkers that don't support it (e.g. `link.exe`).
+
+The default for this modifier is unclear, some targets currently specify it as `+as-needed`, some do not. We may want to try making `+as-needed` a default for all targets.
+"##,
+ },
+ Lint {
+ label: "native_link_modifiers_bundle",
+ description: r##"# `native_link_modifiers_bundle`
+
+The tracking issue for this feature is: [#81490]
+
+[#81490]: https://github.com/rust-lang/rust/issues/81490
+
+------------------------
+
+The `native_link_modifiers_bundle` feature allows you to use the `bundle` modifier.
+
+Only compatible with the `static` linking kind. Using any other kind will result in a compiler error.
+
+`+bundle` means objects from the static library are bundled into the produced crate (a rlib, for example) and are used from this crate later during linking of the final binary.
+
+`-bundle` means the static library is included into the produced rlib "by name" and object files from it are included only during linking of the final binary, the file search by that name is also performed during final linking.
+
+This modifier is supposed to supersede the `static-nobundle` linking kind defined by [RFC 1717](https://github.com/rust-lang/rfcs/pull/1717).
+
+The default for this modifier is currently `+bundle`, but it could be changed later on some future edition boundary.
+"##,
+ },
+ Lint {
+ label: "native_link_modifiers_verbatim",
+ description: r##"# `native_link_modifiers_verbatim`
+
+The tracking issue for this feature is: [#81490]
+
+[#81490]: https://github.com/rust-lang/rust/issues/81490
+
+------------------------
+
+The `native_link_modifiers_verbatim` feature allows you to use the `verbatim` modifier.
+
+`+verbatim` means that rustc itself won't add any target-specified library prefixes or suffixes (like `lib` or `.a`) to the library name, and will try its best to ask for the same thing from the linker.
+
+For `ld`-like linkers rustc will use the `-l:filename` syntax (note the colon) when passing the library, so the linker won't add any prefixes or suffixes as well.
+See [`-l namespec`](https://sourceware.org/binutils/docs/ld/Options.html) in ld documentation for more details.
+For linkers not supporting any verbatim modifiers (e.g. `link.exe` or `ld64`) the library name will be passed as is.
+
+The default for this modifier is `-verbatim`.
+
+This RFC changes the behavior of `raw-dylib` linking kind specified by [RFC 2627](https://github.com/rust-lang/rfcs/pull/2627). The `.dll` suffix (or other target-specified suffixes for other targets) is now added automatically.
+If your DLL doesn't have the `.dll` suffix, it can be specified with `+verbatim`.
+"##,
+ },
+ Lint {
+ label: "native_link_modifiers_whole_archive",
+ description: r##"# `native_link_modifiers_whole_archive`
+
+The tracking issue for this feature is: [#81490]
+
+[#81490]: https://github.com/rust-lang/rust/issues/81490
+
+------------------------
+
+The `native_link_modifiers_whole_archive` feature allows you to use the `whole-archive` modifier.
+
+Only compatible with the `static` linking kind. Using any other kind will result in a compiler error.
+
+`+whole-archive` means that the static library is linked as a whole archive without throwing any object files away.
+
+This modifier translates to `--whole-archive` for `ld`-like linkers, to `/WHOLEARCHIVE` for `link.exe`, and to `-force_load` for `ld64`.
+The modifier does nothing for linkers that don't support it.
+
+The default for this modifier is `-whole-archive`.
+"##,
+ },
+ Lint {
+ label: "negative_impls",
+ description: r##"# `negative_impls`
+
+The tracking issue for this feature is [#68318].
+
+[#68318]: https://github.com/rust-lang/rust/issues/68318
+
+----
+
+With the feature gate `negative_impls`, you can write negative impls as well as positive ones:
+
+```rust
+#![feature(negative_impls)]
+trait DerefMut { }
+impl<T: ?Sized> !DerefMut for &T { }
+```
+
+Negative impls indicate a semver guarantee that the given trait will not be implemented for the given types. Negative impls play an additional purpose for auto traits, described below.
+
+Negative impls have the following characteristics:
+
+* They do not have any items.
+* They must obey the orphan rules as if they were a positive impl.
+* They cannot "overlap" with any positive impls.
+
+## Semver interaction
+
+It is a breaking change to remove a negative impl. Negative impls are a commitment not to implement the given trait for the named types.
+
+## Orphan and overlap rules
+
+Negative impls must obey the same orphan rules as a positive impl. This implies you cannot add a negative impl for types defined in upstream crates and so forth.
+
+Similarly, negative impls cannot overlap with positive impls, again using the same "overlap" check that we ordinarily use to determine if two impls overlap. (Note that positive impls typically cannot overlap with one another either, except as permitted by specialization.)
+
+## Interaction with auto traits
+
+Declaring a negative impl `impl !SomeAutoTrait for SomeType` for an
+auto-trait serves two purposes:
+
+* as with any trait, it declares that `SomeType` will never implement `SomeAutoTrait`;
+* it disables the automatic `SomeType: SomeAutoTrait` impl that would otherwise have been generated.
+
+Note that, at present, there is no way to indicate that a given type
+does not implement an auto trait *but that it may do so in the
+future*. For ordinary types, this is done by simply not declaring any
+impl at all, but that is not an option for auto traits. A workaround
+is that one could embed a marker type as one of the fields, where the
+marker type is `!AutoTrait`.
+
+## Immediate uses
+
+Negative impls are used to declare that `&T: !DerefMut` and `&mut T: !Clone`, as required to fix the soundness of `Pin` described in [#66544](https://github.com/rust-lang/rust/issues/66544).
+
+This serves two purposes:
+
+* For proving the correctness of unsafe code, we can use that impl as evidence that no `DerefMut` or `Clone` impl exists.
+* It prevents downstream crates from creating such impls.
+"##,
+ },
+ Lint {
+ label: "no_coverage",
+ description: r##"# `no_coverage`
+
+The tracking issue for this feature is: [#84605]
+
+[#84605]: https://github.com/rust-lang/rust/issues/84605
+
+---
+
+The `no_coverage` attribute can be used to selectively disable coverage
+instrumentation in an annotated function. This might be useful to:
+
+- Avoid instrumentation overhead in a performance critical function
+- Avoid generating coverage for a function that is not meant to be executed,
+ but still target 100% coverage for the rest of the program.
+
+## Example
+
+```rust
+#![feature(no_coverage)]
+
+// `foo()` will get coverage instrumentation (by default)
+fn foo() {
+ // ...
+}
+
+#[no_coverage]
+fn bar() {
+ // ...
+}
+```
+"##,
+ },
+ Lint {
+ label: "no_sanitize",
+ description: r##"# `no_sanitize`
+
+The tracking issue for this feature is: [#39699]
+
+[#39699]: https://github.com/rust-lang/rust/issues/39699
+
+------------------------
+
+The `no_sanitize` attribute can be used to selectively disable sanitizer
+instrumentation in an annotated function. This might be useful to: avoid
+instrumentation overhead in a performance critical function, or avoid
+instrumenting code that contains constructs unsupported by given sanitizer.
+
+The precise effect of this annotation depends on particular sanitizer in use.
+For example, with `no_sanitize(thread)`, the thread sanitizer will no longer
+instrument non-atomic store / load operations, but it will instrument atomic
+operations to avoid reporting false positives and provide meaning full stack
+traces.
+
+## Examples
+
+``` rust
+#![feature(no_sanitize)]
+
+#[no_sanitize(address)]
+fn foo() {
+ // ...
+}
+```
+"##,
+ },
+ Lint {
+ label: "plugin",
+ description: r##"# `plugin`
+
+The tracking issue for this feature is: [#29597]
+
+[#29597]: https://github.com/rust-lang/rust/issues/29597
+
+
+This feature is part of "compiler plugins." It will often be used with the
+`rustc_private` feature.
+
+------------------------
+
+`rustc` can load compiler plugins, which are user-provided libraries that
+extend the compiler's behavior with new lint checks, etc.
+
+A plugin is a dynamic library crate with a designated *registrar* function that
+registers extensions with `rustc`. Other crates can load these extensions using
+the crate attribute `#![plugin(...)]`. See the
+`rustc_driver::plugin` documentation for more about the
+mechanics of defining and loading a plugin.
+
+In the vast majority of cases, a plugin should *only* be used through
+`#![plugin]` and not through an `extern crate` item. Linking a plugin would
+pull in all of librustc_ast and librustc as dependencies of your crate. This is
+generally unwanted unless you are building another plugin.
+
+The usual practice is to put compiler plugins in their own crate, separate from
+any `macro_rules!` macros or ordinary Rust code meant to be used by consumers
+of a library.
+
+# Lint plugins
+
+Plugins can extend [Rust's lint
+infrastructure](../../reference/attributes/diagnostics.md#lint-check-attributes) with
+additional checks for code style, safety, etc. Now let's write a plugin
+[`lint-plugin-test.rs`](https://github.com/rust-lang/rust/blob/master/src/test/ui-fulldeps/auxiliary/lint-plugin-test.rs)
+that warns about any item named `lintme`.
+
+```rust,ignore (requires-stage-2)
+#![feature(box_syntax, rustc_private)]
+
+extern crate rustc_ast;
+
+// Load rustc as a plugin to get macros
+extern crate rustc_driver;
+#[macro_use]
+extern crate rustc_lint;
+#[macro_use]
+extern crate rustc_session;
+
+use rustc_driver::plugin::Registry;
+use rustc_lint::{EarlyContext, EarlyLintPass, LintArray, LintContext, LintPass};
+use rustc_ast::ast;
+declare_lint!(TEST_LINT, Warn, "Warn about items named 'lintme'");
+
+declare_lint_pass!(Pass => [TEST_LINT]);
+
+impl EarlyLintPass for Pass {
+ fn check_item(&mut self, cx: &EarlyContext, it: &ast::Item) {
+ if it.ident.name.as_str() == "lintme" {
+ cx.lint(TEST_LINT, |lint| {
+ lint.build("item is named 'lintme'").set_span(it.span).emit()
+ });
+ }
+ }
+}
+
+#[no_mangle]
+fn __rustc_plugin_registrar(reg: &mut Registry) {
+ reg.lint_store.register_lints(&[&TEST_LINT]);
+ reg.lint_store.register_early_pass(|| box Pass);
+}
+```
+
+Then code like
+
+```rust,ignore (requires-plugin)
+#![feature(plugin)]
+#![plugin(lint_plugin_test)]
+
+fn lintme() { }
+```
+
+will produce a compiler warning:
+
+```txt
+foo.rs:4:1: 4:16 warning: item is named 'lintme', #[warn(test_lint)] on by default
+foo.rs:4 fn lintme() { }
+ ^~~~~~~~~~~~~~~
+```
+
+The components of a lint plugin are:
+
+* one or more `declare_lint!` invocations, which define static `Lint` structs;
+
+* a struct holding any state needed by the lint pass (here, none);
+
+* a `LintPass`
+ implementation defining how to check each syntax element. A single
+ `LintPass` may call `span_lint` for several different `Lint`s, but should
+ register them all through the `get_lints` method.
+
+Lint passes are syntax traversals, but they run at a late stage of compilation
+where type information is available. `rustc`'s [built-in
+lints](https://github.com/rust-lang/rust/blob/master/src/librustc_session/lint/builtin.rs)
+mostly use the same infrastructure as lint plugins, and provide examples of how
+to access type information.
+
+Lints defined by plugins are controlled by the usual [attributes and compiler
+flags](../../reference/attributes/diagnostics.md#lint-check-attributes), e.g.
+`#[allow(test_lint)]` or `-A test-lint`. These identifiers are derived from the
+first argument to `declare_lint!`, with appropriate case and punctuation
+conversion.
+
+You can run `rustc -W help foo.rs` to see a list of lints known to `rustc`,
+including those provided by plugins loaded by `foo.rs`.
+"##,
+ },
+ Lint {
+ label: "print_internals",
+ description: r##"# `print_internals`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "profiler_runtime",
+ description: r##"# `profiler_runtime`
+
+The tracking issue for this feature is: [#42524](https://github.com/rust-lang/rust/issues/42524).
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "profiler_runtime_lib",
+ description: r##"# `profiler_runtime_lib`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "raw_dylib",
+ description: r##"# `raw_dylib`
+
+The tracking issue for this feature is: [#58713]
+
+[#58713]: https://github.com/rust-lang/rust/issues/58713
+
+------------------------
+
+The `raw_dylib` feature allows you to link against the implementations of functions in an `extern`
+block without, on Windows, linking against an import library.
+
+```rust,ignore (partial-example)
+#![feature(raw_dylib)]
+
+#[link(name="library", kind="raw-dylib")]
+extern {
+ fn extern_function(x: i32);
+}
+
+fn main() {
+ unsafe {
+ extern_function(14);
+ }
+}
+```
+
+## Limitations
+
+Currently, this feature is only supported on `-windows-msvc` targets. Non-Windows platforms don't have import
+libraries, and an incompatibility between LLVM and the BFD linker means that it is not currently supported on
+`-windows-gnu` targets.
+
+On the `i686-pc-windows-msvc` target, this feature supports only the `cdecl`, `stdcall`, `system`, and `fastcall`
+calling conventions.
+"##,
+ },
+ Lint {
+ label: "repr128",
+ description: r##"# `repr128`
+
+The tracking issue for this feature is: [#56071]
+
+[#56071]: https://github.com/rust-lang/rust/issues/56071
+
+------------------------
+
+The `repr128` feature adds support for `#[repr(u128)]` on `enum`s.
+
+```rust
+#![feature(repr128)]
+
+#[repr(u128)]
+enum Foo {
+ Bar(u64),
+}
+```
+"##,
+ },
+ Lint {
+ label: "rt",
+ description: r##"# `rt`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "rustc_attrs",
+ description: r##"# `rustc_attrs`
+
+This feature has no tracking issue, and is therefore internal to
+the compiler, not being intended for general use.
+
+Note: `rustc_attrs` enables many rustc-internal attributes and this page
+only discuss a few of them.
+
+------------------------
+
+The `rustc_attrs` feature allows debugging rustc type layouts by using
+`#[rustc_layout(...)]` to debug layout at compile time (it even works
+with `cargo check`) as an alternative to `rustc -Z print-type-sizes`
+that is way more verbose.
+
+Options provided by `#[rustc_layout(...)]` are `debug`, `size`, `align`,
+`abi`. Note that it only works on sized types without generics.
+
+## Examples
+
+```rust,compile_fail
+#![feature(rustc_attrs)]
+
+#[rustc_layout(abi, size)]
+pub enum X {
+ Y(u8, u8, u8),
+ Z(isize),
+}
+```
+
+When that is compiled, the compiler will error with something like
+
+```text
+error: abi: Aggregate { sized: true }
+ --> src/lib.rs:4:1
+ |
+4 | / pub enum T {
+5 | | Y(u8, u8, u8),
+6 | | Z(isize),
+7 | | }
+ | |_^
+
+error: size: Size { raw: 16 }
+ --> src/lib.rs:4:1
+ |
+4 | / pub enum T {
+5 | | Y(u8, u8, u8),
+6 | | Z(isize),
+7 | | }
+ | |_^
+
+error: aborting due to 2 previous errors
+```
+"##,
+ },
+ Lint {
+ label: "sort_internals",
+ description: r##"# `sort_internals`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "str_internals",
+ description: r##"# `str_internals`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "test",
+ description: r##"# `test`
+
+The tracking issue for this feature is: None.
+
+------------------------
+
+The internals of the `test` crate are unstable, behind the `test` flag. The
+most widely used part of the `test` crate are benchmark tests, which can test
+the performance of your code. Let's make our `src/lib.rs` look like this
+(comments elided):
+
+```rust,no_run
+#![feature(test)]
+
+extern crate test;
+
+pub fn add_two(a: i32) -> i32 {
+ a + 2
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use test::Bencher;
+
+ #[test]
+ fn it_works() {
+ assert_eq!(4, add_two(2));
+ }
+
+ #[bench]
+ fn bench_add_two(b: &mut Bencher) {
+ b.iter(|| add_two(2));
+ }
+}
+```
+
+Note the `test` feature gate, which enables this unstable feature.
+
+We've imported the `test` crate, which contains our benchmarking support.
+We have a new function as well, with the `bench` attribute. Unlike regular
+tests, which take no arguments, benchmark tests take a `&mut Bencher`. This
+`Bencher` provides an `iter` method, which takes a closure. This closure
+contains the code we'd like to benchmark.
+
+We can run benchmark tests with `cargo bench`:
+
+```bash
+$ cargo bench
+ Compiling adder v0.0.1 (file:///home/steve/tmp/adder)
+ Running target/release/adder-91b3e234d4ed382a
+
+running 2 tests
+test tests::it_works ... ignored
+test tests::bench_add_two ... bench: 1 ns/iter (+/- 0)
+
+test result: ok. 0 passed; 0 failed; 1 ignored; 1 measured
+```
+
+Our non-benchmark test was ignored. You may have noticed that `cargo bench`
+takes a bit longer than `cargo test`. This is because Rust runs our benchmark
+a number of times, and then takes the average. Because we're doing so little
+work in this example, we have a `1 ns/iter (+/- 0)`, but this would show
+the variance if there was one.
+
+Advice on writing benchmarks:
+
+
+* Move setup code outside the `iter` loop; only put the part you want to measure inside
+* Make the code do "the same thing" on each iteration; do not accumulate or change state
+* Make the outer function idempotent too; the benchmark runner is likely to run
+ it many times
+* Make the inner `iter` loop short and fast so benchmark runs are fast and the
+ calibrator can adjust the run-length at fine resolution
+* Make the code in the `iter` loop do something simple, to assist in pinpointing
+ performance improvements (or regressions)
+
+## Gotcha: optimizations
+
+There's another tricky part to writing benchmarks: benchmarks compiled with
+optimizations activated can be dramatically changed by the optimizer so that
+the benchmark is no longer benchmarking what one expects. For example, the
+compiler might recognize that some calculation has no external effects and
+remove it entirely.
+
+```rust,no_run
+#![feature(test)]
+
+extern crate test;
+use test::Bencher;
+
+#[bench]
+fn bench_xor_1000_ints(b: &mut Bencher) {
+ b.iter(|| {
+ (0..1000).fold(0, |old, new| old ^ new);
+ });
+}
+```
+
+gives the following results
+
+```text
+running 1 test
+test bench_xor_1000_ints ... bench: 0 ns/iter (+/- 0)
+
+test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured
+```
+
+The benchmarking runner offers two ways to avoid this. Either, the closure that
+the `iter` method receives can return an arbitrary value which forces the
+optimizer to consider the result used and ensures it cannot remove the
+computation entirely. This could be done for the example above by adjusting the
+`b.iter` call to
+
+```rust
+# struct X;
+# impl X { fn iter<T, F>(&self, _: F) where F: FnMut() -> T {} } let b = X;
+b.iter(|| {
+ // Note lack of `;` (could also use an explicit `return`).
+ (0..1000).fold(0, |old, new| old ^ new)
+});
+```
+
+Or, the other option is to call the generic `test::black_box` function, which
+is an opaque "black box" to the optimizer and so forces it to consider any
+argument as used.
+
+```rust
+#![feature(test)]
+
+extern crate test;
+
+# fn main() {
+# struct X;
+# impl X { fn iter<T, F>(&self, _: F) where F: FnMut() -> T {} } let b = X;
+b.iter(|| {
+ let n = test::black_box(1000);
+
+ (0..n).fold(0, |a, b| a ^ b)
+})
+# }
+```
+
+Neither of these read or modify the value, and are very cheap for small values.
+Larger values can be passed indirectly to reduce overhead (e.g.
+`black_box(&huge_struct)`).
+
+Performing either of the above changes gives the following benchmarking results
+
+```text
+running 1 test
+test bench_xor_1000_ints ... bench: 131 ns/iter (+/- 3)
+
+test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured
+```
+
+However, the optimizer can still modify a testcase in an undesirable manner
+even when using either of the above.
+"##,
+ },
+ Lint {
+ label: "thread_local_internals",
+ description: r##"# `thread_local_internals`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "trace_macros",
+ description: r##"# `trace_macros`
+
+The tracking issue for this feature is [#29598].
+
+[#29598]: https://github.com/rust-lang/rust/issues/29598
+
+------------------------
+
+With `trace_macros` you can trace the expansion of macros in your code.
+
+## Examples
+
+```rust
+#![feature(trace_macros)]
+
+fn main() {
+ trace_macros!(true);
+ println!("Hello, Rust!");
+ trace_macros!(false);
+}
+```
+
+The `cargo build` output:
+
+```txt
+note: trace_macro
+ --> src/main.rs:5:5
+ |
+5 | println!("Hello, Rust!");
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: expanding `println! { "Hello, Rust!" }`
+ = note: to `print ! ( concat ! ( "Hello, Rust!" , "\n" ) )`
+ = note: expanding `print! { concat ! ( "Hello, Rust!" , "\n" ) }`
+ = note: to `$crate :: io :: _print ( format_args ! ( concat ! ( "Hello, Rust!" , "\n" ) )
+ )`
+
+ Finished dev [unoptimized + debuginfo] target(s) in 0.60 secs
+```
+"##,
+ },
+ Lint {
+ label: "trait_alias",
+ description: r##"# `trait_alias`
+
+The tracking issue for this feature is: [#41517]
+
+[#41517]: https://github.com/rust-lang/rust/issues/41517
+
+------------------------
+
+The `trait_alias` feature adds support for trait aliases. These allow aliases
+to be created for one or more traits (currently just a single regular trait plus
+any number of auto-traits), and used wherever traits would normally be used as
+either bounds or trait objects.
+
+```rust
+#![feature(trait_alias)]
+
+trait Foo = std::fmt::Debug + Send;
+trait Bar = Foo + Sync;
+
+// Use trait alias as bound on type parameter.
+fn foo<T: Foo>(v: &T) {
+ println!("{:?}", v);
+}
+
+pub fn main() {
+ foo(&1);
+
+ // Use trait alias for trait objects.
+ let a: &Bar = &123;
+ println!("{:?}", a);
+ let b = Box::new(456) as Box<dyn Foo>;
+ println!("{:?}", b);
+}
+```
+"##,
+ },
+ Lint {
+ label: "trait_upcasting",
+ description: r##"# `trait_upcasting`
+
+The tracking issue for this feature is: [#65991]
+
+[#65991]: https://github.com/rust-lang/rust/issues/65991
+
+------------------------
+
+The `trait_upcasting` feature adds support for trait upcasting coercion. This allows a
+trait object of type `dyn Bar` to be cast to a trait object of type `dyn Foo`
+so long as `Bar: Foo`.
+
+```rust,edition2018
+#![feature(trait_upcasting)]
+#![allow(incomplete_features)]
+
+trait Foo {}
+
+trait Bar: Foo {}
+
+impl Foo for i32 {}
+
+impl<T: Foo + ?Sized> Bar for T {}
+
+let bar: &dyn Bar = &123;
+let foo: &dyn Foo = bar;
+```
+"##,
+ },
+ Lint {
+ label: "transparent_unions",
+ description: r##"# `transparent_unions`
+
+The tracking issue for this feature is [#60405]
+
+[#60405]: https://github.com/rust-lang/rust/issues/60405
+
+----
+
+The `transparent_unions` feature allows you mark `union`s as
+`#[repr(transparent)]`. A `union` may be `#[repr(transparent)]` in exactly the
+same conditions in which a `struct` may be `#[repr(transparent)]` (generally,
+this means the `union` must have exactly one non-zero-sized field). Some
+concrete illustrations follow.
+
+```rust
+#![feature(transparent_unions)]
+
+// This union has the same representation as `f32`.
+#[repr(transparent)]
+union SingleFieldUnion {
+ field: f32,
+}
+
+// This union has the same representation as `usize`.
+#[repr(transparent)]
+union MultiFieldUnion {
+ field: usize,
+ nothing: (),
+}
+```
+
+For consistency with transparent `struct`s, `union`s must have exactly one
+non-zero-sized field. If all fields are zero-sized, the `union` must not be
+`#[repr(transparent)]`:
+
+```rust
+#![feature(transparent_unions)]
+
+// This (non-transparent) union is already valid in stable Rust:
+pub union GoodUnion {
+ pub nothing: (),
+}
+
+// Error: transparent union needs exactly one non-zero-sized field, but has 0
+// #[repr(transparent)]
+// pub union BadUnion {
+// pub nothing: (),
+// }
+```
+
+The one exception is if the `union` is generic over `T` and has a field of type
+`T`, it may be `#[repr(transparent)]` even if `T` is a zero-sized type:
+
+```rust
+#![feature(transparent_unions)]
+
+// This union has the same representation as `T`.
+#[repr(transparent)]
+pub union GenericUnion<T: Copy> { // Unions with non-`Copy` fields are unstable.
+ pub field: T,
+ pub nothing: (),
+}
+
+// This is okay even though `()` is a zero-sized type.
+pub const THIS_IS_OKAY: GenericUnion<()> = GenericUnion { field: () };
+```
+
+Like transarent `struct`s, a transparent `union` of type `U` has the same
+layout, size, and ABI as its single non-ZST field. If it is generic over a type
+`T`, and all its fields are ZSTs except for exactly one field of type `T`, then
+it has the same layout and ABI as `T` (even if `T` is a ZST when monomorphized).
+
+Like transparent `struct`s, transparent `union`s are FFI-safe if and only if
+their underlying representation type is also FFI-safe.
+
+A `union` may not be eligible for the same nonnull-style optimizations that a
+`struct` or `enum` (with the same fields) are eligible for. Adding
+`#[repr(transparent)]` to `union` does not change this. To give a more concrete
+example, it is unspecified whether `size_of::<T>()` is equal to
+`size_of::<Option<T>>()`, where `T` is a `union` (regardless of whether or not
+it is transparent). The Rust compiler is free to perform this optimization if
+possible, but is not required to, and different compiler versions may differ in
+their application of these optimizations.
+"##,
+ },
+ Lint {
+ label: "try_blocks",
+ description: r##"# `try_blocks`
+
+The tracking issue for this feature is: [#31436]
+
+[#31436]: https://github.com/rust-lang/rust/issues/31436
+
+------------------------
+
+The `try_blocks` feature adds support for `try` blocks. A `try`
+block creates a new scope one can use the `?` operator in.
+
+```rust,edition2018
+#![feature(try_blocks)]
+
+use std::num::ParseIntError;
+
+let result: Result<i32, ParseIntError> = try {
+ "1".parse::<i32>()?
+ + "2".parse::<i32>()?
+ + "3".parse::<i32>()?
+};
+assert_eq!(result, Ok(6));
+
+let result: Result<i32, ParseIntError> = try {
+ "1".parse::<i32>()?
+ + "foo".parse::<i32>()?
+ + "3".parse::<i32>()?
+};
+assert!(result.is_err());
+```
+"##,
+ },
+ Lint {
+ label: "type_changing_struct_update",
+ description: r##"# `type_changing_struct_update`
+
+The tracking issue for this feature is: [#86555]
+
+[#86555]: https://github.com/rust-lang/rust/issues/86555
+
+------------------------
+
+This implements [RFC2528]. When turned on, you can create instances of the same struct
+that have different generic type or lifetime parameters.
+
+[RFC2528]: https://github.com/rust-lang/rfcs/blob/master/text/2528-type-changing-struct-update-syntax.md
+
+```rust
+#![allow(unused_variables, dead_code)]
+#![feature(type_changing_struct_update)]
+
+fn main () {
+ struct Foo<T, U> {
+ field1: T,
+ field2: U,
+ }
+
+ let base: Foo<String, i32> = Foo {
+ field1: String::from("hello"),
+ field2: 1234,
+ };
+ let updated: Foo<f64, i32> = Foo {
+ field1: 3.14,
+ ..base
+ };
+}
+```
+"##,
+ },
+ Lint {
+ label: "unboxed_closures",
+ description: r##"# `unboxed_closures`
+
+The tracking issue for this feature is [#29625]
+
+See Also: [`fn_traits`](../library-features/fn-traits.md)
+
+[#29625]: https://github.com/rust-lang/rust/issues/29625
+
+----
+
+The `unboxed_closures` feature allows you to write functions using the `"rust-call"` ABI,
+required for implementing the [`Fn*`] family of traits. `"rust-call"` functions must have
+exactly one (non self) argument, a tuple representing the argument list.
+
+[`Fn*`]: https://doc.rust-lang.org/std/ops/trait.Fn.html
+
+```rust
+#![feature(unboxed_closures)]
+
+extern "rust-call" fn add_args(args: (u32, u32)) -> u32 {
+ args.0 + args.1
+}
+
+fn main() {}
+```
+"##,
+ },
+ Lint {
+ label: "unsized_locals",
+ description: r##"# `unsized_locals`
+
+The tracking issue for this feature is: [#48055]
+
+[#48055]: https://github.com/rust-lang/rust/issues/48055
+
+------------------------
+
+This implements [RFC1909]. When turned on, you can have unsized arguments and locals:
+
+[RFC1909]: https://github.com/rust-lang/rfcs/blob/master/text/1909-unsized-rvalues.md
+
+```rust
+#![allow(incomplete_features)]
+#![feature(unsized_locals, unsized_fn_params)]
+
+use std::any::Any;
+
+fn main() {
+ let x: Box<dyn Any> = Box::new(42);
+ let x: dyn Any = *x;
+ // ^ unsized local variable
+ // ^^ unsized temporary
+ foo(x);
+}
+
+fn foo(_: dyn Any) {}
+// ^^^^^^ unsized argument
+```
+
+The RFC still forbids the following unsized expressions:
+
+```rust,compile_fail
+#![feature(unsized_locals)]
+
+use std::any::Any;
+
+struct MyStruct<T: ?Sized> {
+ content: T,
+}
+
+struct MyTupleStruct<T: ?Sized>(T);
+
+fn answer() -> Box<dyn Any> {
+ Box::new(42)
+}
+
+fn main() {
+ // You CANNOT have unsized statics.
+ static X: dyn Any = *answer(); // ERROR
+ const Y: dyn Any = *answer(); // ERROR
+
+ // You CANNOT have struct initialized unsized.
+ MyStruct { content: *answer() }; // ERROR
+ MyTupleStruct(*answer()); // ERROR
+ (42, *answer()); // ERROR
+
+ // You CANNOT have unsized return types.
+ fn my_function() -> dyn Any { *answer() } // ERROR
+
+ // You CAN have unsized local variables...
+ let mut x: dyn Any = *answer(); // OK
+ // ...but you CANNOT reassign to them.
+ x = *answer(); // ERROR
+
+ // You CANNOT even initialize them separately.
+ let y: dyn Any; // OK
+ y = *answer(); // ERROR
+
+ // Not mentioned in the RFC, but by-move captured variables are also Sized.
+ let x: dyn Any = *answer();
+ (move || { // ERROR
+ let y = x;
+ })();
+
+ // You CAN create a closure with unsized arguments,
+ // but you CANNOT call it.
+ // This is an implementation detail and may be changed in the future.
+ let f = |x: dyn Any| {};
+ f(*answer()); // ERROR
+}
+```
+
+## By-value trait objects
+
+With this feature, you can have by-value `self` arguments without `Self: Sized` bounds.
+
+```rust
+#![feature(unsized_fn_params)]
+
+trait Foo {
+ fn foo(self) {}
+}
+
+impl<T: ?Sized> Foo for T {}
+
+fn main() {
+ let slice: Box<[i32]> = Box::new([1, 2, 3]);
+ <[i32] as Foo>::foo(*slice);
+}
+```
+
+And `Foo` will also be object-safe.
+
+```rust
+#![feature(unsized_fn_params)]
+
+trait Foo {
+ fn foo(self) {}
+}
+
+impl<T: ?Sized> Foo for T {}
+
+fn main () {
+ let slice: Box<dyn Foo> = Box::new([1, 2, 3]);
+ // doesn't compile yet
+ <dyn Foo as Foo>::foo(*slice);
+}
+```
+
+One of the objectives of this feature is to allow `Box<dyn FnOnce>`.
+
+## Variable length arrays
+
+The RFC also describes an extension to the array literal syntax: `[e; dyn n]`. In the syntax, `n` isn't necessarily a constant expression. The array is dynamically allocated on the stack and has the type of `[T]`, instead of `[T; n]`.
+
+```rust,ignore (not-yet-implemented)
+#![feature(unsized_locals)]
+
+fn mergesort<T: Ord>(a: &mut [T]) {
+ let mut tmp = [T; dyn a.len()];
+ // ...
+}
+
+fn main() {
+ let mut a = [3, 1, 5, 6];
+ mergesort(&mut a);
+ assert_eq!(a, [1, 3, 5, 6]);
+}
+```
+
+VLAs are not implemented yet. The syntax isn't final, either. We may need an alternative syntax for Rust 2015 because, in Rust 2015, expressions like `[e; dyn(1)]` would be ambiguous. One possible alternative proposed in the RFC is `[e; n]`: if `n` captures one or more local variables, then it is considered as `[e; dyn n]`.
+
+## Advisory on stack usage
+
+It's advised not to casually use the `#![feature(unsized_locals)]` feature. Typical use-cases are:
+
+- When you need a by-value trait objects.
+- When you really need a fast allocation of small temporary arrays.
+
+Another pitfall is repetitive allocation and temporaries. Currently the compiler simply extends the stack frame every time it encounters an unsized assignment. So for example, the code
+
+```rust
+#![feature(unsized_locals)]
+
+fn main() {
+ let x: Box<[i32]> = Box::new([1, 2, 3, 4, 5]);
+ let _x = {{{{{{{{{{*x}}}}}}}}}};
+}
+```
+
+and the code
+
+```rust
+#![feature(unsized_locals)]
+
+fn main() {
+ for _ in 0..10 {
+ let x: Box<[i32]> = Box::new([1, 2, 3, 4, 5]);
+ let _x = *x;
+ }
+}
+```
+
+will unnecessarily extend the stack frame.
+"##,
+ },
+ Lint {
+ label: "unsized_tuple_coercion",
+ description: r##"# `unsized_tuple_coercion`
+
+The tracking issue for this feature is: [#42877]
+
+[#42877]: https://github.com/rust-lang/rust/issues/42877
+
+------------------------
+
+This is a part of [RFC0401]. According to the RFC, there should be an implementation like this:
+
+```rust,ignore (partial-example)
+impl<..., T, U: ?Sized> Unsized<(..., U)> for (..., T) where T: Unsized<U> {}
+```
+
+This implementation is currently gated behind `#[feature(unsized_tuple_coercion)]` to avoid insta-stability. Therefore you can use it like this:
+
+```rust
+#![feature(unsized_tuple_coercion)]
+
+fn main() {
+ let x : ([i32; 3], [i32; 3]) = ([1, 2, 3], [4, 5, 6]);
+ let y : &([i32; 3], [i32]) = &x;
+ assert_eq!(y.1[0], 4);
+}
+```
+
+[RFC0401]: https://github.com/rust-lang/rfcs/blob/master/text/0401-coercions.md
+"##,
+ },
+ Lint {
+ label: "update_panic_count",
+ description: r##"# `update_panic_count`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "windows_c",
+ description: r##"# `windows_c`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "windows_handle",
+ description: r##"# `windows_handle`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "windows_net",
+ description: r##"# `windows_net`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+ Lint {
+ label: "windows_stdio",
+ description: r##"# `windows_stdio`
+
+This feature is internal to the Rust compiler and is not intended for general use.
+
+------------------------
+"##,
+ },
+];
+
+pub const CLIPPY_LINTS: &[Lint] = &[
+ Lint {
+ label: "clippy::absurd_extreme_comparisons",
+ description: r##"Checks for comparisons where one side of the relation is
+either the minimum or maximum value for its type and warns if it involves a
+case that is always true or always false. Only integer and boolean types are
+checked."##,
+ },
+ Lint {
+ label: "clippy::almost_swapped",
+ description: r##"Checks for `foo = bar; bar = foo` sequences."##,
+ },
+ Lint {
+ label: "clippy::approx_constant",
+ description: r##"Checks for floating point literals that approximate
+constants which are defined in
+[`std::f32::consts`](https://doc.rust-lang.org/stable/std/f32/consts/#constants)
+or
+[`std::f64::consts`](https://doc.rust-lang.org/stable/std/f64/consts/#constants),
+respectively, suggesting to use the predefined constant."##,
+ },
+ Lint {
+ label: "clippy::as_conversions",
+ description: r##"Checks for usage of `as` conversions.
+
+Note that this lint is specialized in linting *every single* use of `as`
+regardless of whether good alternatives exist or not.
+If you want more precise lints for `as`, please consider using these separate lints:
+`unnecessary_cast`, `cast_lossless/possible_truncation/possible_wrap/precision_loss/sign_loss`,
+`fn_to_numeric_cast(_with_truncation)`, `char_lit_as_u8`, `ref_to_mut` and `ptr_as_ptr`.
+There is a good explanation the reason why this lint should work in this way and how it is useful
+[in this issue](https://github.com/rust-lang/rust-clippy/issues/5122)."##,
+ },
+ Lint {
+ label: "clippy::assertions_on_constants",
+ description: r##"Checks for `assert!(true)` and `assert!(false)` calls."##,
+ },
+ Lint {
+ label: "clippy::assign_op_pattern",
+ description: r##"Checks for `a = a op b` or `a = b commutative_op a`
+patterns."##,
+ },
+ Lint {
+ label: "clippy::assign_ops",
+ description: r##"Nothing. This lint has been deprecated."##,
+ },
+ Lint {
+ label: "clippy::async_yields_async",
+ description: r##"Checks for async blocks that yield values of types
+that can themselves be awaited."##,
+ },
+ Lint {
+ label: "clippy::await_holding_lock",
+ description: r##"Checks for calls to await while holding a
+non-async-aware MutexGuard."##,
+ },
+ Lint {
+ label: "clippy::await_holding_refcell_ref",
+ description: r##"Checks for calls to await while holding a
+`RefCell` `Ref` or `RefMut`."##,
+ },
+ Lint {
+ label: "clippy::bad_bit_mask",
+ description: r##"Checks for incompatible bit masks in comparisons.
+
+The formula for detecting if an expression of the type `_ <bit_op> m
+<cmp_op> c` (where `<bit_op>` is one of {`&`, `|`} and `<cmp_op>` is one of
+{`!=`, `>=`, `>`, `!=`, `>=`, `>`}) can be determined from the following
+table:
+
+|Comparison |Bit Op|Example |is always|Formula |
+|------------|------|------------|---------|----------------------|
+|`==` or `!=`| `&` |`x & 2 == 3`|`false` |`c & m != c` |
+|`<` or `>=`| `&` |`x & 2 < 3` |`true` |`m < c` |
+|`>` or `<=`| `&` |`x & 1 > 1` |`false` |`m <= c` |
+|`==` or `!=`| `|` |`x | 1 == 0`|`false` |`c | m != c` |
+|`<` or `>=`| `|` |`x | 1 < 1` |`false` |`m >= c` |
+|`<=` or `>` | `|` |`x | 1 > 0` |`true` |`m > c` |"##,
+ },
+ Lint {
+ label: "clippy::bind_instead_of_map",
+ description: r##"Checks for usage of `_.and_then(|x| Some(y))`, `_.and_then(|x| Ok(y))` or
+`_.or_else(|x| Err(y))`."##,
+ },
+ Lint {
+ label: "clippy::blacklisted_name",
+ description: r##"Checks for usage of blacklisted names for variables, such
+as `foo`."##,
+ },
+ Lint {
+ label: "clippy::blanket_clippy_restriction_lints",
+ description: r##"Checks for `warn`/`deny`/`forbid` attributes targeting the whole clippy::restriction category."##,
+ },
+ Lint {
+ label: "clippy::blocks_in_if_conditions",
+ description: r##"Checks for `if` conditions that use blocks containing an
+expression, statements or conditions that use closures with blocks."##,
+ },
+ Lint {
+ label: "clippy::bool_assert_comparison",
+ description: r##"This lint warns about boolean comparisons in assert-like macros."##,
+ },
+ Lint {
+ label: "clippy::bool_comparison",
+ description: r##"Checks for expressions of the form `x == true`,
+`x != true` and order comparisons such as `x < true` (or vice versa) and
+suggest using the variable directly."##,
+ },
+ Lint {
+ label: "clippy::borrow_interior_mutable_const",
+ description: r##"Checks if `const` items which is interior mutable (e.g.,
+contains a `Cell`, `Mutex`, `AtomicXxxx`, etc.) has been borrowed directly."##,
+ },
+ Lint {
+ label: "clippy::borrowed_box",
+ description: r##"Checks for use of `&Box<T>` anywhere in the code.
+Check the [Box documentation](https://doc.rust-lang.org/std/boxed/index.html) for more information."##,
+ },
+ Lint {
+ label: "clippy::box_collection",
+ description: r##"Checks for use of `Box<T>` where T is a collection such as Vec anywhere in the code.
+Check the [Box documentation](https://doc.rust-lang.org/std/boxed/index.html) for more information."##,
+ },
+ Lint {
+ label: "clippy::boxed_local",
+ description: r##"Checks for usage of `Box<T>` where an unboxed `T` would
+work fine."##,
+ },
+ Lint {
+ label: "clippy::branches_sharing_code",
+ description: r##"Checks if the `if` and `else` block contain shared code that can be
+moved out of the blocks."##,
+ },
+ Lint {
+ label: "clippy::builtin_type_shadow",
+ description: r##"Warns if a generic shadows a built-in type."##,
+ },
+ Lint {
+ label: "clippy::bytes_nth",
+ description: r##"Checks for the use of `.bytes().nth()`."##,
+ },
+ Lint {
+ label: "clippy::cargo_common_metadata",
+ description: r##"Checks to see if all common metadata is defined in
+`Cargo.toml`. See: https://rust-lang-nursery.github.io/api-guidelines/documentation.html#cargotoml-includes-all-common-metadata-c-metadata"##,
+ },
+ Lint {
+ label: "clippy::case_sensitive_file_extension_comparisons",
+ description: r##"Checks for calls to `ends_with` with possible file extensions
+and suggests to use a case-insensitive approach instead."##,
+ },
+ Lint {
+ label: "clippy::cast_lossless",
+ description: r##"Checks for casts between numerical types that may
+be replaced by safe conversion functions."##,
+ },
+ Lint {
+ label: "clippy::cast_possible_truncation",
+ description: r##"Checks for casts between numerical types that may
+truncate large values. This is expected behavior, so the cast is `Allow` by
+default."##,
+ },
+ Lint {
+ label: "clippy::cast_possible_wrap",
+ description: r##"Checks for casts from an unsigned type to a signed type of
+the same size. Performing such a cast is a 'no-op' for the compiler,
+i.e., nothing is changed at the bit level, and the binary representation of
+the value is reinterpreted. This can cause wrapping if the value is too big
+for the target signed type. However, the cast works as defined, so this lint
+is `Allow` by default."##,
+ },
+ Lint {
+ label: "clippy::cast_precision_loss",
+ description: r##"Checks for casts from any numerical to a float type where
+the receiving type cannot store all values from the original type without
+rounding errors. This possible rounding is to be expected, so this lint is
+`Allow` by default.
+
+Basically, this warns on casting any integer with 32 or more bits to `f32`
+or any 64-bit integer to `f64`."##,
+ },
+ Lint {
+ label: "clippy::cast_ptr_alignment",
+ description: r##"Checks for casts, using `as` or `pointer::cast`,
+from a less-strictly-aligned pointer to a more-strictly-aligned pointer"##,
+ },
+ Lint {
+ label: "clippy::cast_ref_to_mut",
+ description: r##"Checks for casts of `&T` to `&mut T` anywhere in the code."##,
+ },
+ Lint {
+ label: "clippy::cast_sign_loss",
+ description: r##"Checks for casts from a signed to an unsigned numerical
+type. In this case, negative values wrap around to large positive values,
+which can be quite surprising in practice. However, as the cast works as
+defined, this lint is `Allow` by default."##,
+ },
+ Lint {
+ label: "clippy::char_lit_as_u8",
+ description: r##"Checks for expressions where a character literal is cast
+to `u8` and suggests using a byte literal instead."##,
+ },
+ Lint {
+ label: "clippy::chars_last_cmp",
+ description: r##"Checks for usage of `_.chars().last()` or
+`_.chars().next_back()` on a `str` to check if it ends with a given char."##,
+ },
+ Lint {
+ label: "clippy::chars_next_cmp",
+ description: r##"Checks for usage of `.chars().next()` on a `str` to check
+if it starts with a given char."##,
+ },
+ Lint {
+ label: "clippy::checked_conversions",
+ description: r##"Checks for explicit bounds checking when casting."##,
+ },
+ Lint {
+ label: "clippy::clone_double_ref",
+ description: r##"Checks for usage of `.clone()` on an `&&T`."##,
+ },
+ Lint {
+ label: "clippy::clone_on_copy",
+ description: r##"Checks for usage of `.clone()` on a `Copy` type."##,
+ },
+ Lint {
+ label: "clippy::clone_on_ref_ptr",
+ description: r##"Checks for usage of `.clone()` on a ref-counted pointer,
+(`Rc`, `Arc`, `rc::Weak`, or `sync::Weak`), and suggests calling Clone via unified
+function syntax instead (e.g., `Rc::clone(foo)`)."##,
+ },
+ Lint {
+ label: "clippy::cloned_instead_of_copied",
+ description: r##"Checks for usages of `cloned()` on an `Iterator` or `Option` where
+`copied()` could be used instead."##,
+ },
+ Lint { label: "clippy::cmp_nan", description: r##"Checks for comparisons to NaN."## },
+ Lint {
+ label: "clippy::cmp_null",
+ description: r##"This lint checks for equality comparisons with `ptr::null`"##,
+ },
+ Lint {
+ label: "clippy::cmp_owned",
+ description: r##"Checks for conversions to owned values just for the sake
+of a comparison."##,
+ },
+ Lint {
+ label: "clippy::cognitive_complexity",
+ description: r##"Checks for methods with high cognitive complexity."##,
+ },
+ Lint {
+ label: "clippy::collapsible_else_if",
+ description: r##"Checks for collapsible `else { if ... }` expressions
+that can be collapsed to `else if ...`."##,
+ },
+ Lint {
+ label: "clippy::collapsible_if",
+ description: r##"Checks for nested `if` statements which can be collapsed
+by `&&`-combining their conditions."##,
+ },
+ Lint {
+ label: "clippy::collapsible_match",
+ description: r##"Finds nested `match` or `if let` expressions where the patterns may be collapsed together
+without adding any branches.
+
+Note that this lint is not intended to find _all_ cases where nested match patterns can be merged, but only
+cases where merging would most likely make the code more readable."##,
+ },
+ Lint {
+ label: "clippy::comparison_chain",
+ description: r##"Checks comparison chains written with `if` that can be
+rewritten with `match` and `cmp`."##,
+ },
+ Lint {
+ label: "clippy::comparison_to_empty",
+ description: r##"Checks for comparing to an empty slice such as `` or `[]`,
+and suggests using `.is_empty()` where applicable."##,
+ },
+ Lint {
+ label: "clippy::copy_iterator",
+ description: r##"Checks for types that implement `Copy` as well as
+`Iterator`."##,
+ },
+ Lint {
+ label: "clippy::create_dir",
+ description: r##"Checks usage of `std::fs::create_dir` and suggest using `std::fs::create_dir_all` instead."##,
+ },
+ Lint {
+ label: "clippy::crosspointer_transmute",
+ description: r##"Checks for transmutes between a type `T` and `*T`."##,
+ },
+ Lint { label: "clippy::dbg_macro", description: r##"Checks for usage of dbg!() macro."## },
+ Lint {
+ label: "clippy::debug_assert_with_mut_call",
+ description: r##"Checks for function/method calls with a mutable
+parameter in `debug_assert!`, `debug_assert_eq!` and `debug_assert_ne!` macros."##,
+ },
+ Lint {
+ label: "clippy::decimal_literal_representation",
+ description: r##"Warns if there is a better representation for a numeric literal."##,
+ },
+ Lint {
+ label: "clippy::declare_interior_mutable_const",
+ description: r##"Checks for declaration of `const` items which is interior
+mutable (e.g., contains a `Cell`, `Mutex`, `AtomicXxxx`, etc.)."##,
+ },
+ Lint {
+ label: "clippy::default_numeric_fallback",
+ description: r##"Checks for usage of unconstrained numeric literals which may cause default numeric fallback in type
+inference.
+
+Default numeric fallback means that if numeric types have not yet been bound to concrete
+types at the end of type inference, then integer type is bound to `i32`, and similarly
+floating type is bound to `f64`.
+
+See [RFC0212](https://github.com/rust-lang/rfcs/blob/master/text/0212-restore-int-fallback.md) for more information about the fallback."##,
+ },
+ Lint {
+ label: "clippy::default_trait_access",
+ description: r##"Checks for literal calls to `Default::default()`."##,
+ },
+ Lint {
+ label: "clippy::deprecated_cfg_attr",
+ description: r##"Checks for `#[cfg_attr(rustfmt, rustfmt_skip)]` and suggests to replace it
+with `#[rustfmt::skip]`."##,
+ },
+ Lint {
+ label: "clippy::deprecated_semver",
+ description: r##"Checks for `#[deprecated]` annotations with a `since`
+field that is not a valid semantic version."##,
+ },
+ Lint {
+ label: "clippy::deref_addrof",
+ description: r##"Checks for usage of `*&` and `*&mut` in expressions."##,
+ },
+ Lint {
+ label: "clippy::derivable_impls",
+ description: r##"Detects manual `std::default::Default` implementations that are identical to a derived implementation."##,
+ },
+ Lint {
+ label: "clippy::derive_hash_xor_eq",
+ description: r##"Checks for deriving `Hash` but implementing `PartialEq`
+explicitly or vice versa."##,
+ },
+ Lint {
+ label: "clippy::derive_ord_xor_partial_ord",
+ description: r##"Checks for deriving `Ord` but implementing `PartialOrd`
+explicitly or vice versa."##,
+ },
+ Lint {
+ label: "clippy::disallowed_methods",
+ description: r##"Denies the configured methods and functions in clippy.toml"##,
+ },
+ Lint {
+ label: "clippy::disallowed_script_idents",
+ description: r##"Checks for usage of unicode scripts other than those explicitly allowed
+by the lint config.
+
+This lint doesn't take into account non-text scripts such as `Unknown` and `Linear_A`.
+It also ignores the `Common` script type.
+While configuring, be sure to use official script name [aliases] from
+[the list of supported scripts][supported_scripts].
+
+See also: [`non_ascii_idents`].
+
+[aliases]: http://www.unicode.org/reports/tr24/tr24-31.html#Script_Value_Aliases
+[supported_scripts]: https://www.unicode.org/iso15924/iso15924-codes.html"##,
+ },
+ Lint {
+ label: "clippy::disallowed_types",
+ description: r##"Denies the configured types in clippy.toml."##,
+ },
+ Lint {
+ label: "clippy::diverging_sub_expression",
+ description: r##"Checks for diverging calls that are not match arms or
+statements."##,
+ },
+ Lint {
+ label: "clippy::doc_markdown",
+ description: r##"Checks for the presence of `_`, `::` or camel-case words
+outside ticks in documentation."##,
+ },
+ Lint {
+ label: "clippy::double_comparisons",
+ description: r##"Checks for double comparisons that could be simplified to a single expression."##,
+ },
+ Lint {
+ label: "clippy::double_must_use",
+ description: r##"Checks for a `#[must_use]` attribute without
+further information on functions and methods that return a type already
+marked as `#[must_use]`."##,
+ },
+ Lint {
+ label: "clippy::double_neg",
+ description: r##"Detects expressions of the form `--x`."##,
+ },
+ Lint {
+ label: "clippy::double_parens",
+ description: r##"Checks for unnecessary double parentheses."##,
+ },
+ Lint {
+ label: "clippy::drop_copy",
+ description: r##"Checks for calls to `std::mem::drop` with a value
+that derives the Copy trait"##,
+ },
+ Lint {
+ label: "clippy::drop_ref",
+ description: r##"Checks for calls to `std::mem::drop` with a reference
+instead of an owned value."##,
+ },
+ Lint {
+ label: "clippy::duplicate_underscore_argument",
+ description: r##"Checks for function arguments having the similar names
+differing by an underscore."##,
+ },
+ Lint {
+ label: "clippy::duration_subsec",
+ description: r##"Checks for calculation of subsecond microseconds or milliseconds
+from other `Duration` methods."##,
+ },
+ Lint {
+ label: "clippy::else_if_without_else",
+ description: r##"Checks for usage of if expressions with an `else if` branch,
+but without a final `else` branch."##,
+ },
+ Lint {
+ label: "clippy::empty_enum",
+ description: r##"Checks for `enum`s with no variants.
+
+As of this writing, the `never_type` is still a
+nightly-only experimental API. Therefore, this lint is only triggered
+if the `never_type` is enabled."##,
+ },
+ Lint {
+ label: "clippy::empty_line_after_outer_attr",
+ description: r##"Checks for empty lines after outer attributes"##,
+ },
+ Lint { label: "clippy::empty_loop", description: r##"Checks for empty `loop` expressions."## },
+ Lint {
+ label: "clippy::enum_clike_unportable_variant",
+ description: r##"Checks for C-like enumerations that are
+`repr(isize/usize)` and have values that don't fit into an `i32`."##,
+ },
+ Lint { label: "clippy::enum_glob_use", description: r##"Checks for `use Enum::*`."## },
+ Lint {
+ label: "clippy::enum_variant_names",
+ description: r##"Detects enumeration variants that are prefixed or suffixed
+by the same characters."##,
+ },
+ Lint {
+ label: "clippy::eq_op",
+ description: r##"Checks for equal operands to comparison, logical and
+bitwise, difference and division binary operators (`==`, `>`, etc., `&&`,
+`||`, `&`, `|`, `^`, `-` and `/`)."##,
+ },
+ Lint {
+ label: "clippy::equatable_if_let",
+ description: r##"Checks for pattern matchings that can be expressed using equality."##,
+ },
+ Lint {
+ label: "clippy::erasing_op",
+ description: r##"Checks for erasing operations, e.g., `x * 0`."##,
+ },
+ Lint {
+ label: "clippy::eval_order_dependence",
+ description: r##"Checks for a read and a write to the same variable where
+whether the read occurs before or after the write depends on the evaluation
+order of sub-expressions."##,
+ },
+ Lint {
+ label: "clippy::excessive_precision",
+ description: r##"Checks for float literals with a precision greater
+than that supported by the underlying type."##,
+ },
+ Lint {
+ label: "clippy::exhaustive_enums",
+ description: r##"Warns on any exported `enum`s that are not tagged `#[non_exhaustive]`"##,
+ },
+ Lint {
+ label: "clippy::exhaustive_structs",
+ description: r##"Warns on any exported `structs`s that are not tagged `#[non_exhaustive]`"##,
+ },
+ Lint {
+ label: "clippy::exit",
+ description: r##"`exit()` terminates the program and doesn't provide a
+stack trace."##,
+ },
+ Lint {
+ label: "clippy::expect_fun_call",
+ description: r##"Checks for calls to `.expect(&format!(...))`, `.expect(foo(..))`,
+etc., and suggests to use `unwrap_or_else` instead"##,
+ },
+ Lint {
+ label: "clippy::expect_used",
+ description: r##"Checks for `.expect()` calls on `Option`s and `Result`s."##,
+ },
+ Lint {
+ label: "clippy::expl_impl_clone_on_copy",
+ description: r##"Checks for explicit `Clone` implementations for `Copy`
+types."##,
+ },
+ Lint {
+ label: "clippy::explicit_counter_loop",
+ description: r##"Checks `for` loops over slices with an explicit counter
+and suggests the use of `.enumerate()`."##,
+ },
+ Lint {
+ label: "clippy::explicit_deref_methods",
+ description: r##"Checks for explicit `deref()` or `deref_mut()` method calls."##,
+ },
+ Lint {
+ label: "clippy::explicit_into_iter_loop",
+ description: r##"Checks for loops on `y.into_iter()` where `y` will do, and
+suggests the latter."##,
+ },
+ Lint {
+ label: "clippy::explicit_iter_loop",
+ description: r##"Checks for loops on `x.iter()` where `&x` will do, and
+suggests the latter."##,
+ },
+ Lint {
+ label: "clippy::explicit_write",
+ description: r##"Checks for usage of `write!()` / `writeln()!` which can be
+replaced with `(e)print!()` / `(e)println!()`"##,
+ },
+ Lint {
+ label: "clippy::extend_from_slice",
+ description: r##"Nothing. This lint has been deprecated."##,
+ },
+ Lint {
+ label: "clippy::extend_with_drain",
+ description: r##"Checks for occurrences where one vector gets extended instead of append"##,
+ },
+ Lint {
+ label: "clippy::extra_unused_lifetimes",
+ description: r##"Checks for lifetimes in generics that are never used
+anywhere else."##,
+ },
+ Lint {
+ label: "clippy::fallible_impl_from",
+ description: r##"Checks for impls of `From<..>` that contain `panic!()` or `unwrap()`"##,
+ },
+ Lint {
+ label: "clippy::field_reassign_with_default",
+ description: r##"Checks for immediate reassignment of fields initialized
+with Default::default()."##,
+ },
+ Lint {
+ label: "clippy::filetype_is_file",
+ description: r##"Checks for `FileType::is_file()`."##,
+ },
+ Lint {
+ label: "clippy::filter_map",
+ description: r##"Nothing. This lint has been deprecated."##,
+ },
+ Lint {
+ label: "clippy::filter_map_identity",
+ description: r##"Checks for usage of `filter_map(|x| x)`."##,
+ },
+ Lint {
+ label: "clippy::filter_map_next",
+ description: r##"Checks for usage of `_.filter_map(_).next()`."##,
+ },
+ Lint {
+ label: "clippy::filter_next",
+ description: r##"Checks for usage of `_.filter(_).next()`."##,
+ },
+ Lint { label: "clippy::find_map", description: r##"Nothing. This lint has been deprecated."## },
+ Lint {
+ label: "clippy::flat_map_identity",
+ description: r##"Checks for usage of `flat_map(|x| x)`."##,
+ },
+ Lint {
+ label: "clippy::flat_map_option",
+ description: r##"Checks for usages of `Iterator::flat_map()` where `filter_map()` could be
+used instead."##,
+ },
+ Lint { label: "clippy::float_arithmetic", description: r##"Checks for float arithmetic."## },
+ Lint {
+ label: "clippy::float_cmp",
+ description: r##"Checks for (in-)equality comparisons on floating-point
+values (apart from zero), except in functions called `*eq*` (which probably
+implement equality for a type involving floats)."##,
+ },
+ Lint {
+ label: "clippy::float_cmp_const",
+ description: r##"Checks for (in-)equality comparisons on floating-point
+value and constant, except in functions called `*eq*` (which probably
+implement equality for a type involving floats)."##,
+ },
+ Lint {
+ label: "clippy::float_equality_without_abs",
+ description: r##"Checks for statements of the form `(a - b) < f32::EPSILON` or
+`(a - b) < f64::EPSILON`. Notes the missing `.abs()`."##,
+ },
+ Lint {
+ label: "clippy::fn_address_comparisons",
+ description: r##"Checks for comparisons with an address of a function item."##,
+ },
+ Lint {
+ label: "clippy::fn_params_excessive_bools",
+ description: r##"Checks for excessive use of
+bools in function definitions."##,
+ },
+ Lint {
+ label: "clippy::fn_to_numeric_cast",
+ description: r##"Checks for casts of function pointers to something other than usize"##,
+ },
+ Lint {
+ label: "clippy::fn_to_numeric_cast_any",
+ description: r##"Checks for casts of a function pointer to any integer type."##,
+ },
+ Lint {
+ label: "clippy::fn_to_numeric_cast_with_truncation",
+ description: r##"Checks for casts of a function pointer to a numeric type not wide enough to
+store address."##,
+ },
+ Lint {
+ label: "clippy::for_kv_map",
+ description: r##"Checks for iterating a map (`HashMap` or `BTreeMap`) and
+ignoring either the keys or values."##,
+ },
+ Lint {
+ label: "clippy::for_loops_over_fallibles",
+ description: r##"Checks for `for` loops over `Option` or `Result` values."##,
+ },
+ Lint {
+ label: "clippy::forget_copy",
+ description: r##"Checks for calls to `std::mem::forget` with a value that
+derives the Copy trait"##,
+ },
+ Lint {
+ label: "clippy::forget_ref",
+ description: r##"Checks for calls to `std::mem::forget` with a reference
+instead of an owned value."##,
+ },
+ Lint {
+ label: "clippy::format_in_format_args",
+ description: r##"Detects `format!` within the arguments of another macro that does
+formatting such as `format!` itself, `write!` or `println!`. Suggests
+inlining the `format!` call."##,
+ },
+ Lint {
+ label: "clippy::from_iter_instead_of_collect",
+ description: r##"Checks for `from_iter()` function calls on types that implement the `FromIterator`
+trait."##,
+ },
+ Lint {
+ label: "clippy::from_over_into",
+ description: r##"Searches for implementations of the `Into<..>` trait and suggests to implement `From<..>` instead."##,
+ },
+ Lint {
+ label: "clippy::from_str_radix_10",
+ description: r##"Checks for function invocations of the form `primitive::from_str_radix(s, 10)`"##,
+ },
+ Lint {
+ label: "clippy::future_not_send",
+ description: r##"This lint requires Future implementations returned from
+functions and methods to implement the `Send` marker trait. It is mostly
+used by library authors (public and internal) that target an audience where
+multithreaded executors are likely to be used for running these Futures."##,
+ },
+ Lint {
+ label: "clippy::get_last_with_len",
+ description: r##"Checks for using `x.get(x.len() - 1)` instead of
+`x.last()`."##,
+ },
+ Lint {
+ label: "clippy::get_unwrap",
+ description: r##"Checks for use of `.get().unwrap()` (or
+`.get_mut().unwrap`) on a standard library type which implements `Index`"##,
+ },
+ Lint {
+ label: "clippy::identity_op",
+ description: r##"Checks for identity operations, e.g., `x + 0`."##,
+ },
+ Lint {
+ label: "clippy::if_let_mutex",
+ description: r##"Checks for `Mutex::lock` calls in `if let` expression
+with lock calls in any of the else blocks."##,
+ },
+ Lint {
+ label: "clippy::if_let_redundant_pattern_matching",
+ description: r##"Nothing. This lint has been deprecated."##,
+ },
+ Lint {
+ label: "clippy::if_not_else",
+ description: r##"Checks for usage of `!` or `!=` in an if condition with an
+else branch."##,
+ },
+ Lint {
+ label: "clippy::if_same_then_else",
+ description: r##"Checks for `if/else` with the same body as the *then* part
+and the *else* part."##,
+ },
+ Lint {
+ label: "clippy::if_then_some_else_none",
+ description: r##"Checks for if-else that could be written to `bool::then`."##,
+ },
+ Lint {
+ label: "clippy::ifs_same_cond",
+ description: r##"Checks for consecutive `if`s with the same condition."##,
+ },
+ Lint {
+ label: "clippy::implicit_clone",
+ description: r##"Checks for the usage of `_.to_owned()`, `vec.to_vec()`, or similar when calling `_.clone()` would be clearer."##,
+ },
+ Lint {
+ label: "clippy::implicit_hasher",
+ description: r##"Checks for public `impl` or `fn` missing generalization
+over different hashers and implicitly defaulting to the default hashing
+algorithm (`SipHash`)."##,
+ },
+ Lint {
+ label: "clippy::implicit_return",
+ description: r##"Checks for missing return statements at the end of a block."##,
+ },
+ Lint {
+ label: "clippy::implicit_saturating_sub",
+ description: r##"Checks for implicit saturating subtraction."##,
+ },
+ Lint {
+ label: "clippy::imprecise_flops",
+ description: r##"Looks for floating-point expressions that
+can be expressed using built-in methods to improve accuracy
+at the cost of performance."##,
+ },
+ Lint {
+ label: "clippy::inconsistent_digit_grouping",
+ description: r##"Warns if an integral or floating-point constant is
+grouped inconsistently with underscores."##,
+ },
+ Lint {
+ label: "clippy::inconsistent_struct_constructor",
+ description: r##"Checks for struct constructors where all fields are shorthand and
+the order of the field init shorthand in the constructor is inconsistent
+with the order in the struct definition."##,
+ },
+ Lint {
+ label: "clippy::index_refutable_slice",
+ description: r##"The lint checks for slice bindings in patterns that are only used to
+access individual slice values."##,
+ },
+ Lint {
+ label: "clippy::indexing_slicing",
+ description: r##"Checks for usage of indexing or slicing. Arrays are special cases, this lint
+does report on arrays if we can tell that slicing operations are in bounds and does not
+lint on constant `usize` indexing on arrays because that is handled by rustc's `const_err` lint."##,
+ },
+ Lint {
+ label: "clippy::ineffective_bit_mask",
+ description: r##"Checks for bit masks in comparisons which can be removed
+without changing the outcome. The basic structure can be seen in the
+following table:
+
+|Comparison| Bit Op |Example |equals |
+|----------|---------|-----------|-------|
+|`>` / `<=`|`|` / `^`|`x | 2 > 3`|`x > 3`|
+|`<` / `>=`|`|` / `^`|`x ^ 1 < 4`|`x < 4`|"##,
+ },
+ Lint {
+ label: "clippy::inefficient_to_string",
+ description: r##"Checks for usage of `.to_string()` on an `&&T` where
+`T` implements `ToString` directly (like `&&str` or `&&String`)."##,
+ },
+ Lint {
+ label: "clippy::infallible_destructuring_match",
+ description: r##"Checks for matches being used to destructure a single-variant enum
+or tuple struct where a `let` will suffice."##,
+ },
+ Lint {
+ label: "clippy::infinite_iter",
+ description: r##"Checks for iteration that is guaranteed to be infinite."##,
+ },
+ Lint {
+ label: "clippy::inherent_to_string",
+ description: r##"Checks for the definition of inherent methods with a signature of `to_string(&self) -> String`."##,
+ },
+ Lint {
+ label: "clippy::inherent_to_string_shadow_display",
+ description: r##"Checks for the definition of inherent methods with a signature of `to_string(&self) -> String` and if the type implementing this method also implements the `Display` trait."##,
+ },
+ Lint {
+ label: "clippy::inline_always",
+ description: r##"Checks for items annotated with `#[inline(always)]`,
+unless the annotated function is empty or simply panics."##,
+ },
+ Lint {
+ label: "clippy::inline_asm_x86_att_syntax",
+ description: r##"Checks for usage of AT&T x86 assembly syntax."##,
+ },
+ Lint {
+ label: "clippy::inline_asm_x86_intel_syntax",
+ description: r##"Checks for usage of Intel x86 assembly syntax."##,
+ },
+ Lint {
+ label: "clippy::inline_fn_without_body",
+ description: r##"Checks for `#[inline]` on trait methods without bodies"##,
+ },
+ Lint {
+ label: "clippy::inspect_for_each",
+ description: r##"Checks for usage of `inspect().for_each()`."##,
+ },
+ Lint {
+ label: "clippy::int_plus_one",
+ description: r##"Checks for usage of `x >= y + 1` or `x - 1 >= y` (and `<=`) in a block"##,
+ },
+ Lint {
+ label: "clippy::integer_arithmetic",
+ description: r##"Checks for integer arithmetic operations which could overflow or panic.
+
+Specifically, checks for any operators (`+`, `-`, `*`, `<<`, etc) which are capable
+of overflowing according to the [Rust
+Reference](https://doc.rust-lang.org/reference/expressions/operator-expr.html#overflow),
+or which can panic (`/`, `%`). No bounds analysis or sophisticated reasoning is
+attempted."##,
+ },
+ Lint { label: "clippy::integer_division", description: r##"Checks for division of integers"## },
+ Lint {
+ label: "clippy::into_iter_on_ref",
+ description: r##"Checks for `into_iter` calls on references which should be replaced by `iter`
+or `iter_mut`."##,
+ },
+ Lint {
+ label: "clippy::invalid_null_ptr_usage",
+ description: r##"This lint checks for invalid usages of `ptr::null`."##,
+ },
+ Lint {
+ label: "clippy::invalid_regex",
+ description: r##"Checks [regex](https://crates.io/crates/regex) creation
+(with `Regex::new`, `RegexBuilder::new`, or `RegexSet::new`) for correct
+regex syntax."##,
+ },
+ Lint {
+ label: "clippy::invalid_upcast_comparisons",
+ description: r##"Checks for comparisons where the relation is always either
+true or false, but where one side has been upcast so that the comparison is
+necessary. Only integer types are checked."##,
+ },
+ Lint {
+ label: "clippy::invisible_characters",
+ description: r##"Checks for invisible Unicode characters in the code."##,
+ },
+ Lint {
+ label: "clippy::items_after_statements",
+ description: r##"Checks for items declared after some statement in a block."##,
+ },
+ Lint {
+ label: "clippy::iter_cloned_collect",
+ description: r##"Checks for the use of `.cloned().collect()` on slice to
+create a `Vec`."##,
+ },
+ Lint {
+ label: "clippy::iter_count",
+ description: r##"Checks for the use of `.iter().count()`."##,
+ },
+ Lint { label: "clippy::iter_next_loop", description: r##"Checks for loops on `x.next()`."## },
+ Lint {
+ label: "clippy::iter_next_slice",
+ description: r##"Checks for usage of `iter().next()` on a Slice or an Array"##,
+ },
+ Lint {
+ label: "clippy::iter_not_returning_iterator",
+ description: r##"Detects methods named `iter` or `iter_mut` that do not have a return type that implements `Iterator`."##,
+ },
+ Lint {
+ label: "clippy::iter_nth",
+ description: r##"Checks for use of `.iter().nth()` (and the related
+`.iter_mut().nth()`) on standard library types with *O*(1) element access."##,
+ },
+ Lint {
+ label: "clippy::iter_nth_zero",
+ description: r##"Checks for the use of `iter.nth(0)`."##,
+ },
+ Lint {
+ label: "clippy::iter_skip_next",
+ description: r##"Checks for use of `.skip(x).next()` on iterators."##,
+ },
+ Lint {
+ label: "clippy::iterator_step_by_zero",
+ description: r##"Checks for calling `.step_by(0)` on iterators which panics."##,
+ },
+ Lint {
+ label: "clippy::just_underscores_and_digits",
+ description: r##"Checks if you have variables whose name consists of just
+underscores and digits."##,
+ },
+ Lint {
+ label: "clippy::large_const_arrays",
+ description: r##"Checks for large `const` arrays that should
+be defined as `static` instead."##,
+ },
+ Lint {
+ label: "clippy::large_digit_groups",
+ description: r##"Warns if the digits of an integral or floating-point
+constant are grouped into groups that
+are too large."##,
+ },
+ Lint {
+ label: "clippy::large_enum_variant",
+ description: r##"Checks for large size differences between variants on
+`enum`s."##,
+ },
+ Lint {
+ label: "clippy::large_stack_arrays",
+ description: r##"Checks for local arrays that may be too large."##,
+ },
+ Lint {
+ label: "clippy::large_types_passed_by_value",
+ description: r##"Checks for functions taking arguments by value, where
+the argument type is `Copy` and large enough to be worth considering
+passing by reference. Does not trigger if the function is being exported,
+because that might induce API breakage, if the parameter is declared as mutable,
+or if the argument is a `self`."##,
+ },
+ Lint {
+ label: "clippy::len_without_is_empty",
+ description: r##"Checks for items that implement `.len()` but not
+`.is_empty()`."##,
+ },
+ Lint {
+ label: "clippy::len_zero",
+ description: r##"Checks for getting the length of something via `.len()`
+just to compare to zero, and suggests using `.is_empty()` where applicable."##,
+ },
+ Lint {
+ label: "clippy::let_and_return",
+ description: r##"Checks for `let`-bindings, which are subsequently
+returned."##,
+ },
+ Lint {
+ label: "clippy::let_underscore_drop",
+ description: r##"Checks for `let _ = <expr>`
+where expr has a type that implements `Drop`"##,
+ },
+ Lint {
+ label: "clippy::let_underscore_lock",
+ description: r##"Checks for `let _ = sync_lock`.
+This supports `mutex` and `rwlock` in `std::sync` and `parking_lot`."##,
+ },
+ Lint {
+ label: "clippy::let_underscore_must_use",
+ description: r##"Checks for `let _ = <expr>` where expr is `#[must_use]`"##,
+ },
+ Lint { label: "clippy::let_unit_value", description: r##"Checks for binding a unit value."## },
+ Lint {
+ label: "clippy::linkedlist",
+ description: r##"Checks for usage of any `LinkedList`, suggesting to use a
+`Vec` or a `VecDeque` (formerly called `RingBuf`)."##,
+ },
+ Lint {
+ label: "clippy::logic_bug",
+ description: r##"Checks for boolean expressions that contain terminals that
+can be eliminated."##,
+ },
+ Lint {
+ label: "clippy::lossy_float_literal",
+ description: r##"Checks for whole number float literals that
+cannot be represented as the underlying type without loss."##,
+ },
+ Lint {
+ label: "clippy::macro_use_imports",
+ description: r##"Checks for `#[macro_use] use...`."##,
+ },
+ Lint {
+ label: "clippy::main_recursion",
+ description: r##"Checks for recursion using the entrypoint."##,
+ },
+ Lint {
+ label: "clippy::manual_assert",
+ description: r##"Detects `if`-then-`panic!` that can be replaced with `assert!`."##,
+ },
+ Lint {
+ label: "clippy::manual_async_fn",
+ description: r##"It checks for manual implementations of `async` functions."##,
+ },
+ Lint {
+ label: "clippy::manual_filter_map",
+ description: r##"Checks for usage of `_.filter(_).map(_)` that can be written more simply
+as `filter_map(_)`."##,
+ },
+ Lint {
+ label: "clippy::manual_find_map",
+ description: r##"Checks for usage of `_.find(_).map(_)` that can be written more simply
+as `find_map(_)`."##,
+ },
+ Lint {
+ label: "clippy::manual_flatten",
+ description: r##"Check for unnecessary `if let` usage in a for loop
+where only the `Some` or `Ok` variant of the iterator element is used."##,
+ },
+ Lint {
+ label: "clippy::manual_map",
+ description: r##"Checks for usages of `match` which could be implemented using `map`"##,
+ },
+ Lint {
+ label: "clippy::manual_memcpy",
+ description: r##"Checks for for-loops that manually copy items between
+slices that could be optimized by having a memcpy."##,
+ },
+ Lint {
+ label: "clippy::manual_non_exhaustive",
+ description: r##"Checks for manual implementations of the non-exhaustive pattern."##,
+ },
+ Lint {
+ label: "clippy::manual_ok_or",
+ description: r##"Finds patterns that reimplement `Option::ok_or`."##,
+ },
+ Lint {
+ label: "clippy::manual_range_contains",
+ description: r##"Checks for expressions like `x >= 3 && x < 8` that could
+be more readably expressed as `(3..8).contains(x)`."##,
+ },
+ Lint {
+ label: "clippy::manual_saturating_arithmetic",
+ description: r##"Checks for `.checked_add/sub(x).unwrap_or(MAX/MIN)`."##,
+ },
+ Lint {
+ label: "clippy::manual_split_once",
+ description: r##"Checks for usages of `str::splitn(2, _)`"##,
+ },
+ Lint {
+ label: "clippy::manual_str_repeat",
+ description: r##"Checks for manual implementations of `str::repeat`"##,
+ },
+ Lint {
+ label: "clippy::manual_strip",
+ description: r##"Suggests using `strip_{prefix,suffix}` over `str::{starts,ends}_with` and slicing using
+the pattern's length."##,
+ },
+ Lint { label: "clippy::manual_swap", description: r##"Checks for manual swapping."## },
+ Lint {
+ label: "clippy::manual_unwrap_or",
+ description: r##"Finds patterns that reimplement `Option::unwrap_or` or `Result::unwrap_or`."##,
+ },
+ Lint {
+ label: "clippy::many_single_char_names",
+ description: r##"Checks for too many variables whose name consists of a
+single character."##,
+ },
+ Lint {
+ label: "clippy::map_clone",
+ description: r##"Checks for usage of `map(|x| x.clone())` or
+dereferencing closures for `Copy` types, on `Iterator` or `Option`,
+and suggests `cloned()` or `copied()` instead"##,
+ },
+ Lint {
+ label: "clippy::map_collect_result_unit",
+ description: r##"Checks for usage of `_.map(_).collect::<Result<(), _>()`."##,
+ },
+ Lint {
+ label: "clippy::map_entry",
+ description: r##"Checks for uses of `contains_key` + `insert` on `HashMap`
+or `BTreeMap`."##,
+ },
+ Lint {
+ label: "clippy::map_err_ignore",
+ description: r##"Checks for instances of `map_err(|_| Some::Enum)`"##,
+ },
+ Lint {
+ label: "clippy::map_flatten",
+ description: r##"Checks for usage of `_.map(_).flatten(_)` on `Iterator` and `Option`"##,
+ },
+ Lint {
+ label: "clippy::map_identity",
+ description: r##"Checks for instances of `map(f)` where `f` is the identity function."##,
+ },
+ Lint {
+ label: "clippy::map_unwrap_or",
+ description: r##"Checks for usage of `option.map(_).unwrap_or(_)` or `option.map(_).unwrap_or_else(_)` or
+`result.map(_).unwrap_or_else(_)`."##,
+ },
+ Lint {
+ label: "clippy::match_as_ref",
+ description: r##"Checks for match which is used to add a reference to an
+`Option` value."##,
+ },
+ Lint {
+ label: "clippy::match_bool",
+ description: r##"Checks for matches where match expression is a `bool`. It
+suggests to replace the expression with an `if...else` block."##,
+ },
+ Lint {
+ label: "clippy::match_like_matches_macro",
+ description: r##"Checks for `match` or `if let` expressions producing a
+`bool` that could be written using `matches!`"##,
+ },
+ Lint {
+ label: "clippy::match_on_vec_items",
+ description: r##"Checks for `match vec[idx]` or `match vec[n..m]`."##,
+ },
+ Lint {
+ label: "clippy::match_overlapping_arm",
+ description: r##"Checks for overlapping match arms."##,
+ },
+ Lint {
+ label: "clippy::match_ref_pats",
+ description: r##"Checks for matches where all arms match a reference,
+suggesting to remove the reference and deref the matched expression
+instead. It also checks for `if let &foo = bar` blocks."##,
+ },
+ Lint {
+ label: "clippy::match_result_ok",
+ description: r##"Checks for unnecessary `ok()` in `while let`."##,
+ },
+ Lint {
+ label: "clippy::match_same_arms",
+ description: r##"Checks for `match` with identical arm bodies."##,
+ },
+ Lint {
+ label: "clippy::match_single_binding",
+ description: r##"Checks for useless match that binds to only one value."##,
+ },
+ Lint {
+ label: "clippy::match_str_case_mismatch",
+ description: r##"Checks for `match` expressions modifying the case of a string with non-compliant arms"##,
+ },
+ Lint {
+ label: "clippy::match_wild_err_arm",
+ description: r##"Checks for arm which matches all errors with `Err(_)`
+and take drastic actions like `panic!`."##,
+ },
+ Lint {
+ label: "clippy::match_wildcard_for_single_variants",
+ description: r##"Checks for wildcard enum matches for a single variant."##,
+ },
+ Lint {
+ label: "clippy::maybe_infinite_iter",
+ description: r##"Checks for iteration that may be infinite."##,
+ },
+ Lint {
+ label: "clippy::mem_forget",
+ description: r##"Checks for usage of `std::mem::forget(t)` where `t` is
+`Drop`."##,
+ },
+ Lint {
+ label: "clippy::mem_replace_option_with_none",
+ description: r##"Checks for `mem::replace()` on an `Option` with
+`None`."##,
+ },
+ Lint {
+ label: "clippy::mem_replace_with_default",
+ description: r##"Checks for `std::mem::replace` on a value of type
+`T` with `T::default()`."##,
+ },
+ Lint {
+ label: "clippy::mem_replace_with_uninit",
+ description: r##"Checks for `mem::replace(&mut _, mem::uninitialized())`
+and `mem::replace(&mut _, mem::zeroed())`."##,
+ },
+ Lint {
+ label: "clippy::min_max",
+ description: r##"Checks for expressions where `std::cmp::min` and `max` are
+used to clamp values, but switched so that the result is constant."##,
+ },
+ Lint {
+ label: "clippy::misaligned_transmute",
+ description: r##"Nothing. This lint has been deprecated."##,
+ },
+ Lint {
+ label: "clippy::mismatched_target_os",
+ description: r##"Checks for cfg attributes having operating systems used in target family position."##,
+ },
+ Lint {
+ label: "clippy::misrefactored_assign_op",
+ description: r##"Checks for `a op= a op b` or `a op= b op a` patterns."##,
+ },
+ Lint {
+ label: "clippy::missing_const_for_fn",
+ description: r##"Suggests the use of `const` in functions and methods where possible."##,
+ },
+ Lint {
+ label: "clippy::missing_docs_in_private_items",
+ description: r##"Warns if there is missing doc for any documentable item
+(public or private)."##,
+ },
+ Lint {
+ label: "clippy::missing_enforced_import_renames",
+ description: r##"Checks for imports that do not rename the item as specified
+in the `enforce-import-renames` config option."##,
+ },
+ Lint {
+ label: "clippy::missing_errors_doc",
+ description: r##"Checks the doc comments of publicly visible functions that
+return a `Result` type and warns if there is no `# Errors` section."##,
+ },
+ Lint {
+ label: "clippy::missing_inline_in_public_items",
+ description: r##"It lints if an exported function, method, trait method with default impl,
+or trait method impl is not `#[inline]`."##,
+ },
+ Lint {
+ label: "clippy::missing_panics_doc",
+ description: r##"Checks the doc comments of publicly visible functions that
+may panic and warns if there is no `# Panics` section."##,
+ },
+ Lint {
+ label: "clippy::missing_safety_doc",
+ description: r##"Checks for the doc comments of publicly visible
+unsafe functions and warns if there is no `# Safety` section."##,
+ },
+ Lint {
+ label: "clippy::mistyped_literal_suffixes",
+ description: r##"Warns for mistyped suffix in literals"##,
+ },
+ Lint {
+ label: "clippy::mixed_case_hex_literals",
+ description: r##"Warns on hexadecimal literals with mixed-case letter
+digits."##,
+ },
+ Lint {
+ label: "clippy::mod_module_files",
+ description: r##"Checks that module layout uses only self named module files, bans mod.rs files."##,
+ },
+ Lint {
+ label: "clippy::module_inception",
+ description: r##"Checks for modules that have the same name as their
+parent module"##,
+ },
+ Lint {
+ label: "clippy::module_name_repetitions",
+ description: r##"Detects type names that are prefixed or suffixed by the
+containing module's name."##,
+ },
+ Lint { label: "clippy::modulo_arithmetic", description: r##"Checks for modulo arithmetic."## },
+ Lint {
+ label: "clippy::modulo_one",
+ description: r##"Checks for getting the remainder of a division by one or minus
+one."##,
+ },
+ Lint {
+ label: "clippy::multiple_crate_versions",
+ description: r##"Checks to see if multiple versions of a crate are being
+used."##,
+ },
+ Lint {
+ label: "clippy::multiple_inherent_impl",
+ description: r##"Checks for multiple inherent implementations of a struct"##,
+ },
+ Lint {
+ label: "clippy::must_use_candidate",
+ description: r##"Checks for public functions that have no
+`#[must_use]` attribute, but return something not already marked
+must-use, have no mutable arg and mutate no statics."##,
+ },
+ Lint {
+ label: "clippy::must_use_unit",
+ description: r##"Checks for a `#[must_use]` attribute on
+unit-returning functions and methods."##,
+ },
+ Lint {
+ label: "clippy::mut_from_ref",
+ description: r##"This lint checks for functions that take immutable
+references and return mutable ones."##,
+ },
+ Lint {
+ label: "clippy::mut_mut",
+ description: r##"Checks for instances of `mut mut` references."##,
+ },
+ Lint {
+ label: "clippy::mut_mutex_lock",
+ description: r##"Checks for `&mut Mutex::lock` calls"##,
+ },
+ Lint {
+ label: "clippy::mut_range_bound",
+ description: r##"Checks for loops which have a range bound that is a mutable variable"##,
+ },
+ Lint {
+ label: "clippy::mutable_key_type",
+ description: r##"Checks for sets/maps with mutable key types."##,
+ },
+ Lint {
+ label: "clippy::mutex_atomic",
+ description: r##"Checks for usages of `Mutex<X>` where an atomic will do."##,
+ },
+ Lint {
+ label: "clippy::mutex_integer",
+ description: r##"Checks for usages of `Mutex<X>` where `X` is an integral
+type."##,
+ },
+ Lint { label: "clippy::naive_bytecount", description: r##"Checks for naive byte counts"## },
+ Lint {
+ label: "clippy::needless_arbitrary_self_type",
+ description: r##"The lint checks for `self` in fn parameters that
+specify the `Self`-type explicitly"##,
+ },
+ Lint {
+ label: "clippy::needless_bitwise_bool",
+ description: r##"Checks for uses of bitwise and/or operators between booleans, where performance may be improved by using
+a lazy and."##,
+ },
+ Lint {
+ label: "clippy::needless_bool",
+ description: r##"Checks for expressions of the form `if c { true } else {
+false }` (or vice versa) and suggests using the condition directly."##,
+ },
+ Lint {
+ label: "clippy::needless_borrow",
+ description: r##"Checks for address of operations (`&`) that are going to
+be dereferenced immediately by the compiler."##,
+ },
+ Lint {
+ label: "clippy::needless_borrowed_reference",
+ description: r##"Checks for bindings that destructure a reference and borrow the inner
+value with `&ref`."##,
+ },
+ Lint {
+ label: "clippy::needless_collect",
+ description: r##"Checks for functions collecting an iterator when collect
+is not needed."##,
+ },
+ Lint {
+ label: "clippy::needless_continue",
+ description: r##"The lint checks for `if`-statements appearing in loops
+that contain a `continue` statement in either their main blocks or their
+`else`-blocks, when omitting the `else`-block possibly with some
+rearrangement of code can make the code easier to understand."##,
+ },
+ Lint {
+ label: "clippy::needless_doctest_main",
+ description: r##"Checks for `fn main() { .. }` in doctests"##,
+ },
+ Lint {
+ label: "clippy::needless_for_each",
+ description: r##"Checks for usage of `for_each` that would be more simply written as a
+`for` loop."##,
+ },
+ Lint {
+ label: "clippy::needless_late_init",
+ description: r##"Checks for late initializations that can be replaced by a `let` statement
+with an initializer."##,
+ },
+ Lint {
+ label: "clippy::needless_lifetimes",
+ description: r##"Checks for lifetime annotations which can be removed by
+relying on lifetime elision."##,
+ },
+ Lint {
+ label: "clippy::needless_option_as_deref",
+ description: r##"Checks for no-op uses of Option::{as_deref,as_deref_mut},
+for example, `Option<&T>::as_deref()` returns the same type."##,
+ },
+ Lint {
+ label: "clippy::needless_pass_by_value",
+ description: r##"Checks for functions taking arguments by value, but not
+consuming them in its
+body."##,
+ },
+ Lint {
+ label: "clippy::needless_question_mark",
+ description: r##"Suggests alternatives for useless applications of `?` in terminating expressions"##,
+ },
+ Lint {
+ label: "clippy::needless_range_loop",
+ description: r##"Checks for looping over the range of `0..len` of some
+collection just to get the values by index."##,
+ },
+ Lint {
+ label: "clippy::needless_return",
+ description: r##"Checks for return statements at the end of a block."##,
+ },
+ Lint {
+ label: "clippy::needless_splitn",
+ description: r##"Checks for usages of `str::splitn` (or `str::rsplitn`) where using `str::split` would be the same."##,
+ },
+ Lint {
+ label: "clippy::needless_update",
+ description: r##"Checks for needlessly including a base struct on update
+when all fields are changed anyway.
+
+This lint is not applied to structs marked with
+[non_exhaustive](https://doc.rust-lang.org/reference/attributes/type_system.html)."##,
+ },
+ Lint {
+ label: "clippy::neg_cmp_op_on_partial_ord",
+ description: r##"Checks for the usage of negated comparison operators on types which only implement
+`PartialOrd` (e.g., `f64`)."##,
+ },
+ Lint {
+ label: "clippy::neg_multiply",
+ description: r##"Checks for multiplication by -1 as a form of negation."##,
+ },
+ Lint {
+ label: "clippy::negative_feature_names",
+ description: r##"Checks for negative feature names with prefix `no-` or `not-`"##,
+ },
+ Lint {
+ label: "clippy::never_loop",
+ description: r##"Checks for loops that will always `break`, `return` or
+`continue` an outer loop."##,
+ },
+ Lint {
+ label: "clippy::new_ret_no_self",
+ description: r##"Checks for `new` not returning a type that contains `Self`."##,
+ },
+ Lint {
+ label: "clippy::new_without_default",
+ description: r##"Checks for types with a `fn new() -> Self` method and no
+implementation of
+[`Default`](https://doc.rust-lang.org/std/default/trait.Default.html)."##,
+ },
+ Lint {
+ label: "clippy::no_effect",
+ description: r##"Checks for statements which have no effect."##,
+ },
+ Lint {
+ label: "clippy::no_effect_underscore_binding",
+ description: r##"Checks for binding to underscore prefixed variable without side-effects."##,
+ },
+ Lint {
+ label: "clippy::non_ascii_literal",
+ description: r##"Checks for non-ASCII characters in string and char literals."##,
+ },
+ Lint {
+ label: "clippy::non_octal_unix_permissions",
+ description: r##"Checks for non-octal values used to set Unix file permissions."##,
+ },
+ Lint {
+ label: "clippy::non_send_fields_in_send_ty",
+ description: r##"This lint warns about a `Send` implementation for a type that
+contains fields that are not safe to be sent across threads.
+It tries to detect fields that can cause a soundness issue
+when sent to another thread (e.g., `Rc`) while allowing `!Send` fields
+that are expected to exist in a `Send` type, such as raw pointers."##,
+ },
+ Lint {
+ label: "clippy::nonminimal_bool",
+ description: r##"Checks for boolean expressions that can be written more
+concisely."##,
+ },
+ Lint {
+ label: "clippy::nonsensical_open_options",
+ description: r##"Checks for duplicate open options as well as combinations
+that make no sense."##,
+ },
+ Lint {
+ label: "clippy::nonstandard_macro_braces",
+ description: r##"Checks that common macros are used with consistent bracing."##,
+ },
+ Lint {
+ label: "clippy::not_unsafe_ptr_arg_deref",
+ description: r##"Checks for public functions that dereference raw pointer
+arguments but are not marked `unsafe`."##,
+ },
+ Lint {
+ label: "clippy::octal_escapes",
+ description: r##"Checks for `\\0` escapes in string and byte literals that look like octal
+character escapes in C."##,
+ },
+ Lint { label: "clippy::ok_expect", description: r##"Checks for usage of `ok().expect(..)`."## },
+ Lint {
+ label: "clippy::op_ref",
+ description: r##"Checks for arguments to `==` which have their address
+taken to satisfy a bound
+and suggests to dereference the other argument instead"##,
+ },
+ Lint {
+ label: "clippy::option_as_ref_deref",
+ description: r##"Checks for usage of `_.as_ref().map(Deref::deref)` or it's aliases (such as String::as_str)."##,
+ },
+ Lint {
+ label: "clippy::option_env_unwrap",
+ description: r##"Checks for usage of `option_env!(...).unwrap()` and
+suggests usage of the `env!` macro."##,
+ },
+ Lint {
+ label: "clippy::option_filter_map",
+ description: r##"Checks for indirect collection of populated `Option`"##,
+ },
+ Lint {
+ label: "clippy::option_if_let_else",
+ description: r##"Lints usage of `if let Some(v) = ... { y } else { x }` which is more
+idiomatically done with `Option::map_or` (if the else bit is a pure
+expression) or `Option::map_or_else` (if the else bit is an impure
+expression)."##,
+ },
+ Lint {
+ label: "clippy::option_map_or_none",
+ description: r##"Checks for usage of `_.map_or(None, _)`."##,
+ },
+ Lint {
+ label: "clippy::option_map_unit_fn",
+ description: r##"Checks for usage of `option.map(f)` where f is a function
+or closure that returns the unit type `()`."##,
+ },
+ Lint {
+ label: "clippy::option_option",
+ description: r##"Checks for use of `Option<Option<_>>` in function signatures and type
+definitions"##,
+ },
+ Lint {
+ label: "clippy::or_fun_call",
+ description: r##"Checks for calls to `.or(foo(..))`, `.unwrap_or(foo(..))`,
+etc., and suggests to use `or_else`, `unwrap_or_else`, etc., or
+`unwrap_or_default` instead."##,
+ },
+ Lint {
+ label: "clippy::out_of_bounds_indexing",
+ description: r##"Checks for out of bounds array indexing with a constant
+index."##,
+ },
+ Lint {
+ label: "clippy::overflow_check_conditional",
+ description: r##"Detects classic underflow/overflow checks."##,
+ },
+ Lint { label: "clippy::panic", description: r##"Checks for usage of `panic!`."## },
+ Lint {
+ label: "clippy::panic_in_result_fn",
+ description: r##"Checks for usage of `panic!`, `unimplemented!`, `todo!`, `unreachable!` or assertions in a function of type result."##,
+ },
+ Lint {
+ label: "clippy::panicking_unwrap",
+ description: r##"Checks for calls of `unwrap[_err]()` that will always fail."##,
+ },
+ Lint {
+ label: "clippy::partialeq_ne_impl",
+ description: r##"Checks for manual re-implementations of `PartialEq::ne`."##,
+ },
+ Lint {
+ label: "clippy::path_buf_push_overwrite",
+ description: r##"* Checks for [push](https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.push)
+calls on `PathBuf` that can cause overwrites."##,
+ },
+ Lint {
+ label: "clippy::pattern_type_mismatch",
+ description: r##"Checks for patterns that aren't exact representations of the types
+they are applied to.
+
+To satisfy this lint, you will have to adjust either the expression that is matched
+against or the pattern itself, as well as the bindings that are introduced by the
+adjusted patterns. For matching you will have to either dereference the expression
+with the `*` operator, or amend the patterns to explicitly match against `&<pattern>`
+or `&mut <pattern>` depending on the reference mutability. For the bindings you need
+to use the inverse. You can leave them as plain bindings if you wish for the value
+to be copied, but you must use `ref mut <variable>` or `ref <variable>` to construct
+a reference into the matched structure.
+
+If you are looking for a way to learn about ownership semantics in more detail, it
+is recommended to look at IDE options available to you to highlight types, lifetimes
+and reference semantics in your code. The available tooling would expose these things
+in a general way even outside of the various pattern matching mechanics. Of course
+this lint can still be used to highlight areas of interest and ensure a good understanding
+of ownership semantics."##,
+ },
+ Lint {
+ label: "clippy::possible_missing_comma",
+ description: r##"Checks for possible missing comma in an array. It lints if
+an array element is a binary operator expression and it lies on two lines."##,
+ },
+ Lint {
+ label: "clippy::precedence",
+ description: r##"Checks for operations where precedence may be unclear
+and suggests to add parentheses. Currently it catches the following:
+* mixed usage of arithmetic and bit shifting/combining operators without
+parentheses
+* a negative numeric literal (which is really a unary `-` followed by a
+numeric literal)
+ followed by a method call"##,
+ },
+ Lint {
+ label: "clippy::print_literal",
+ description: r##"This lint warns about the use of literals as `print!`/`println!` args."##,
+ },
+ Lint {
+ label: "clippy::print_stderr",
+ description: r##"Checks for printing on *stderr*. The purpose of this lint
+is to catch debugging remnants."##,
+ },
+ Lint {
+ label: "clippy::print_stdout",
+ description: r##"Checks for printing on *stdout*. The purpose of this lint
+is to catch debugging remnants."##,
+ },
+ Lint {
+ label: "clippy::print_with_newline",
+ description: r##"This lint warns when you use `print!()` with a format
+string that ends in a newline."##,
+ },
+ Lint {
+ label: "clippy::println_empty_string",
+ description: r##"This lint warns when you use `println!()` to
+print a newline."##,
+ },
+ Lint {
+ label: "clippy::ptr_arg",
+ description: r##"This lint checks for function arguments of type `&String`
+or `&Vec` unless the references are mutable. It will also suggest you
+replace `.clone()` calls with the appropriate `.to_owned()`/`to_string()`
+calls."##,
+ },
+ Lint {
+ label: "clippy::ptr_as_ptr",
+ description: r##"Checks for `as` casts between raw pointers without changing its mutability,
+namely `*const T` to `*const U` and `*mut T` to `*mut U`."##,
+ },
+ Lint { label: "clippy::ptr_eq", description: r##"Use `std::ptr::eq` when applicable"## },
+ Lint {
+ label: "clippy::ptr_offset_with_cast",
+ description: r##"Checks for usage of the `offset` pointer method with a `usize` casted to an
+`isize`."##,
+ },
+ Lint {
+ label: "clippy::pub_enum_variant_names",
+ description: r##"Nothing. This lint has been deprecated."##,
+ },
+ Lint {
+ label: "clippy::question_mark",
+ description: r##"Checks for expressions that could be replaced by the question mark operator."##,
+ },
+ Lint {
+ label: "clippy::range_minus_one",
+ description: r##"Checks for inclusive ranges where 1 is subtracted from
+the upper bound, e.g., `x..=(y-1)`."##,
+ },
+ Lint {
+ label: "clippy::range_plus_one",
+ description: r##"Checks for exclusive ranges where 1 is added to the
+upper bound, e.g., `x..(y+1)`."##,
+ },
+ Lint {
+ label: "clippy::range_step_by_zero",
+ description: r##"Nothing. This lint has been deprecated."##,
+ },
+ Lint {
+ label: "clippy::range_zip_with_len",
+ description: r##"Checks for zipping a collection with the range of
+`0.._.len()`."##,
+ },
+ Lint {
+ label: "clippy::rc_buffer",
+ description: r##"Checks for `Rc<T>` and `Arc<T>` when `T` is a mutable buffer type such as `String` or `Vec`."##,
+ },
+ Lint { label: "clippy::rc_mutex", description: r##"Checks for `Rc<Mutex<T>>`."## },
+ Lint {
+ label: "clippy::redundant_allocation",
+ description: r##"Checks for use of redundant allocations anywhere in the code."##,
+ },
+ Lint {
+ label: "clippy::redundant_clone",
+ description: r##"Checks for a redundant `clone()` (and its relatives) which clones an owned
+value that is going to be dropped without further use."##,
+ },
+ Lint {
+ label: "clippy::redundant_closure",
+ description: r##"Checks for closures which just call another function where
+the function can be called directly. `unsafe` functions or calls where types
+get adjusted are ignored."##,
+ },
+ Lint {
+ label: "clippy::redundant_closure_call",
+ description: r##"Detects closures called in the same expression where they
+are defined."##,
+ },
+ Lint {
+ label: "clippy::redundant_closure_for_method_calls",
+ description: r##"Checks for closures which only invoke a method on the closure
+argument and can be replaced by referencing the method directly."##,
+ },
+ Lint {
+ label: "clippy::redundant_else",
+ description: r##"Checks for `else` blocks that can be removed without changing semantics."##,
+ },
+ Lint {
+ label: "clippy::redundant_feature_names",
+ description: r##"Checks for feature names with prefix `use-`, `with-` or suffix `-support`"##,
+ },
+ Lint {
+ label: "clippy::redundant_field_names",
+ description: r##"Checks for fields in struct literals where shorthands
+could be used."##,
+ },
+ Lint {
+ label: "clippy::redundant_pattern",
+ description: r##"Checks for patterns in the form `name @ _`."##,
+ },
+ Lint {
+ label: "clippy::redundant_pattern_matching",
+ description: r##"Lint for redundant pattern matching over `Result`, `Option`,
+`std::task::Poll` or `std::net::IpAddr`"##,
+ },
+ Lint {
+ label: "clippy::redundant_pub_crate",
+ description: r##"Checks for items declared `pub(crate)` that are not crate visible because they
+are inside a private module."##,
+ },
+ Lint {
+ label: "clippy::redundant_slicing",
+ description: r##"Checks for redundant slicing expressions which use the full range, and
+do not change the type."##,
+ },
+ Lint {
+ label: "clippy::redundant_static_lifetimes",
+ description: r##"Checks for constants and statics with an explicit `'static` lifetime."##,
+ },
+ Lint {
+ label: "clippy::ref_binding_to_reference",
+ description: r##"Checks for `ref` bindings which create a reference to a reference."##,
+ },
+ Lint {
+ label: "clippy::ref_in_deref",
+ description: r##"Checks for references in expressions that use
+auto dereference."##,
+ },
+ Lint {
+ label: "clippy::ref_option_ref",
+ description: r##"Checks for usage of `&Option<&T>`."##,
+ },
+ Lint {
+ label: "clippy::regex_macro",
+ description: r##"Nothing. This lint has been deprecated."##,
+ },
+ Lint {
+ label: "clippy::repeat_once",
+ description: r##"Checks for usage of `.repeat(1)` and suggest the following method for each types.
+- `.to_string()` for `str`
+- `.clone()` for `String`
+- `.to_vec()` for `slice`
+
+The lint will evaluate constant expressions and values as arguments of `.repeat(..)` and emit a message if
+they are equivalent to `1`. (Related discussion in [rust-clippy#7306](https://github.com/rust-lang/rust-clippy/issues/7306))"##,
+ },
+ Lint {
+ label: "clippy::replace_consts",
+ description: r##"Nothing. This lint has been deprecated."##,
+ },
+ Lint {
+ label: "clippy::rest_pat_in_fully_bound_structs",
+ description: r##"Checks for unnecessary '..' pattern binding on struct when all fields are explicitly matched."##,
+ },
+ Lint {
+ label: "clippy::result_map_or_into_option",
+ description: r##"Checks for usage of `_.map_or(None, Some)`."##,
+ },
+ Lint {
+ label: "clippy::result_map_unit_fn",
+ description: r##"Checks for usage of `result.map(f)` where f is a function
+or closure that returns the unit type `()`."##,
+ },
+ Lint {
+ label: "clippy::result_unit_err",
+ description: r##"Checks for public functions that return a `Result`
+with an `Err` type of `()`. It suggests using a custom type that
+implements `std::error::Error`."##,
+ },
+ Lint {
+ label: "clippy::return_self_not_must_use",
+ description: r##"This lint warns when a method returning `Self` doesn't have the `#[must_use]` attribute."##,
+ },
+ Lint {
+ label: "clippy::reversed_empty_ranges",
+ description: r##"Checks for range expressions `x..y` where both `x` and `y`
+are constant and `x` is greater or equal to `y`."##,
+ },
+ Lint {
+ label: "clippy::same_functions_in_if_condition",
+ description: r##"Checks for consecutive `if`s with the same function call."##,
+ },
+ Lint {
+ label: "clippy::same_item_push",
+ description: r##"Checks whether a for loop is being used to push a constant
+value into a Vec."##,
+ },
+ Lint {
+ label: "clippy::same_name_method",
+ description: r##"It lints if a struct has two methods with the same name:
+one from a trait, another not from trait."##,
+ },
+ Lint {
+ label: "clippy::search_is_some",
+ description: r##"Checks for an iterator or string search (such as `find()`,
+`position()`, or `rposition()`) followed by a call to `is_some()` or `is_none()`."##,
+ },
+ Lint {
+ label: "clippy::self_assignment",
+ description: r##"Checks for explicit self-assignments."##,
+ },
+ Lint {
+ label: "clippy::self_named_constructors",
+ description: r##"Warns when constructors have the same name as their types."##,
+ },
+ Lint {
+ label: "clippy::self_named_module_files",
+ description: r##"Checks that module layout uses only mod.rs files."##,
+ },
+ Lint {
+ label: "clippy::semicolon_if_nothing_returned",
+ description: r##"Looks for blocks of expressions and fires if the last expression returns
+`()` but is not followed by a semicolon."##,
+ },
+ Lint {
+ label: "clippy::separated_literal_suffix",
+ description: r##"Warns if literal suffixes are separated by an underscore.
+To enforce separated literal suffix style,
+see the `unseparated_literal_suffix` lint."##,
+ },
+ Lint {
+ label: "clippy::serde_api_misuse",
+ description: r##"Checks for mis-uses of the serde API."##,
+ },
+ Lint {
+ label: "clippy::shadow_reuse",
+ description: r##"Checks for bindings that shadow other bindings already in
+scope, while reusing the original value."##,
+ },
+ Lint {
+ label: "clippy::shadow_same",
+ description: r##"Checks for bindings that shadow other bindings already in
+scope, while just changing reference level or mutability."##,
+ },
+ Lint {
+ label: "clippy::shadow_unrelated",
+ description: r##"Checks for bindings that shadow other bindings already in
+scope, either without an initialization or with one that does not even use
+the original value."##,
+ },
+ Lint {
+ label: "clippy::short_circuit_statement",
+ description: r##"Checks for the use of short circuit boolean conditions as
+a
+statement."##,
+ },
+ Lint {
+ label: "clippy::should_assert_eq",
+ description: r##"Nothing. This lint has been deprecated."##,
+ },
+ Lint {
+ label: "clippy::should_implement_trait",
+ description: r##"Checks for methods that should live in a trait
+implementation of a `std` trait (see [llogiq's blog
+post](http://llogiq.github.io/2015/07/30/traits.html) for further
+information) instead of an inherent implementation."##,
+ },
+ Lint {
+ label: "clippy::similar_names",
+ description: r##"Checks for names that are very similar and thus confusing."##,
+ },
+ Lint {
+ label: "clippy::single_char_add_str",
+ description: r##"Warns when using `push_str`/`insert_str` with a single-character string literal
+where `push`/`insert` with a `char` would work fine."##,
+ },
+ Lint {
+ label: "clippy::single_char_pattern",
+ description: r##"Checks for string methods that receive a single-character
+`str` as an argument, e.g., `_.split(x)`."##,
+ },
+ Lint {
+ label: "clippy::single_component_path_imports",
+ description: r##"Checking for imports with single component use path."##,
+ },
+ Lint {
+ label: "clippy::single_element_loop",
+ description: r##"Checks whether a for loop has a single element."##,
+ },
+ Lint {
+ label: "clippy::single_match",
+ description: r##"Checks for matches with a single arm where an `if let`
+will usually suffice."##,
+ },
+ Lint {
+ label: "clippy::single_match_else",
+ description: r##"Checks for matches with two arms where an `if let else` will
+usually suffice."##,
+ },
+ Lint {
+ label: "clippy::size_of_in_element_count",
+ description: r##"Detects expressions where
+`size_of::<T>` or `size_of_val::<T>` is used as a
+count of elements of type `T`"##,
+ },
+ Lint {
+ label: "clippy::skip_while_next",
+ description: r##"Checks for usage of `_.skip_while(condition).next()`."##,
+ },
+ Lint {
+ label: "clippy::slow_vector_initialization",
+ description: r##"Checks slow zero-filled vector initialization"##,
+ },
+ Lint {
+ label: "clippy::stable_sort_primitive",
+ description: r##"When sorting primitive values (integers, bools, chars, as well
+as arrays, slices, and tuples of such items), it is better to
+use an unstable sort than a stable sort."##,
+ },
+ Lint {
+ label: "clippy::str_to_string",
+ description: r##"This lint checks for `.to_string()` method calls on values of type `&str`."##,
+ },
+ Lint {
+ label: "clippy::string_add",
+ description: r##"Checks for all instances of `x + _` where `x` is of type
+`String`, but only if [`string_add_assign`](#string_add_assign) does *not*
+match."##,
+ },
+ Lint {
+ label: "clippy::string_add_assign",
+ description: r##"Checks for string appends of the form `x = x + y` (without
+`let`!)."##,
+ },
+ Lint {
+ label: "clippy::string_extend_chars",
+ description: r##"Checks for the use of `.extend(s.chars())` where s is a
+`&str` or `String`."##,
+ },
+ Lint {
+ label: "clippy::string_from_utf8_as_bytes",
+ description: r##"Check if the string is transformed to byte array and casted back to string."##,
+ },
+ Lint {
+ label: "clippy::string_lit_as_bytes",
+ description: r##"Checks for the `as_bytes` method called on string literals
+that contain only ASCII characters."##,
+ },
+ Lint {
+ label: "clippy::string_slice",
+ description: r##"Checks for slice operations on strings"##,
+ },
+ Lint {
+ label: "clippy::string_to_string",
+ description: r##"This lint checks for `.to_string()` method calls on values of type `String`."##,
+ },
+ Lint {
+ label: "clippy::strlen_on_c_strings",
+ description: r##"Checks for usage of `libc::strlen` on a `CString` or `CStr` value,
+and suggest calling `as_bytes().len()` or `to_bytes().len()` respectively instead."##,
+ },
+ Lint {
+ label: "clippy::struct_excessive_bools",
+ description: r##"Checks for excessive
+use of bools in structs."##,
+ },
+ Lint {
+ label: "clippy::suboptimal_flops",
+ description: r##"Looks for floating-point expressions that
+can be expressed using built-in methods to improve both
+accuracy and performance."##,
+ },
+ Lint {
+ label: "clippy::suspicious_arithmetic_impl",
+ description: r##"Lints for suspicious operations in impls of arithmetic operators, e.g.
+subtracting elements in an Add impl."##,
+ },
+ Lint {
+ label: "clippy::suspicious_assignment_formatting",
+ description: r##"Checks for use of the non-existent `=*`, `=!` and `=-`
+operators."##,
+ },
+ Lint {
+ label: "clippy::suspicious_else_formatting",
+ description: r##"Checks for formatting of `else`. It lints if the `else`
+is followed immediately by a newline or the `else` seems to be missing."##,
+ },
+ Lint {
+ label: "clippy::suspicious_map",
+ description: r##"Checks for calls to `map` followed by a `count`."##,
+ },
+ Lint {
+ label: "clippy::suspicious_op_assign_impl",
+ description: r##"Lints for suspicious operations in impls of OpAssign, e.g.
+subtracting elements in an AddAssign impl."##,
+ },
+ Lint {
+ label: "clippy::suspicious_operation_groupings",
+ description: r##"Checks for unlikely usages of binary operators that are almost
+certainly typos and/or copy/paste errors, given the other usages
+of binary operators nearby."##,
+ },
+ Lint {
+ label: "clippy::suspicious_splitn",
+ description: r##"Checks for calls to [`splitn`]
+(https://doc.rust-lang.org/std/primitive.str.html#method.splitn) and
+related functions with either zero or one splits."##,
+ },
+ Lint {
+ label: "clippy::suspicious_unary_op_formatting",
+ description: r##"Checks the formatting of a unary operator on the right hand side
+of a binary operator. It lints if there is no space between the binary and unary operators,
+but there is a space between the unary and its operand."##,
+ },
+ Lint {
+ label: "clippy::tabs_in_doc_comments",
+ description: r##"Checks doc comments for usage of tab characters."##,
+ },
+ Lint {
+ label: "clippy::temporary_assignment",
+ description: r##"Checks for construction of a structure or tuple just to
+assign a value in it."##,
+ },
+ Lint {
+ label: "clippy::to_digit_is_some",
+ description: r##"Checks for `.to_digit(..).is_some()` on `char`s."##,
+ },
+ Lint {
+ label: "clippy::to_string_in_display",
+ description: r##"Checks for uses of `to_string()` in `Display` traits."##,
+ },
+ Lint {
+ label: "clippy::to_string_in_format_args",
+ description: r##"Checks for [`ToString::to_string`](https://doc.rust-lang.org/std/string/trait.ToString.html#tymethod.to_string)
+applied to a type that implements [`Display`](https://doc.rust-lang.org/std/fmt/trait.Display.html)
+in a macro that does formatting."##,
+ },
+ Lint { label: "clippy::todo", description: r##"Checks for usage of `todo!`."## },
+ Lint {
+ label: "clippy::too_many_arguments",
+ description: r##"Checks for functions with too many parameters."##,
+ },
+ Lint {
+ label: "clippy::too_many_lines",
+ description: r##"Checks for functions with a large amount of lines."##,
+ },
+ Lint {
+ label: "clippy::toplevel_ref_arg",
+ description: r##"Checks for function arguments and let bindings denoted as
+`ref`."##,
+ },
+ Lint {
+ label: "clippy::trailing_empty_array",
+ description: r##"Displays a warning when a struct with a trailing zero-sized array is declared without a `repr` attribute."##,
+ },
+ Lint {
+ label: "clippy::trait_duplication_in_bounds",
+ description: r##"Checks for cases where generics are being used and multiple
+syntax specifications for trait bounds are used simultaneously."##,
+ },
+ Lint {
+ label: "clippy::transmute_bytes_to_str",
+ description: r##"Checks for transmutes from a `&[u8]` to a `&str`."##,
+ },
+ Lint {
+ label: "clippy::transmute_float_to_int",
+ description: r##"Checks for transmutes from a float to an integer."##,
+ },
+ Lint {
+ label: "clippy::transmute_int_to_bool",
+ description: r##"Checks for transmutes from an integer to a `bool`."##,
+ },
+ Lint {
+ label: "clippy::transmute_int_to_char",
+ description: r##"Checks for transmutes from an integer to a `char`."##,
+ },
+ Lint {
+ label: "clippy::transmute_int_to_float",
+ description: r##"Checks for transmutes from an integer to a float."##,
+ },
+ Lint {
+ label: "clippy::transmute_num_to_bytes",
+ description: r##"Checks for transmutes from a number to an array of `u8`"##,
+ },
+ Lint {
+ label: "clippy::transmute_ptr_to_ptr",
+ description: r##"Checks for transmutes from a pointer to a pointer, or
+from a reference to a reference."##,
+ },
+ Lint {
+ label: "clippy::transmute_ptr_to_ref",
+ description: r##"Checks for transmutes from a pointer to a reference."##,
+ },
+ Lint {
+ label: "clippy::transmutes_expressible_as_ptr_casts",
+ description: r##"Checks for transmutes that could be a pointer cast."##,
+ },
+ Lint {
+ label: "clippy::transmuting_null",
+ description: r##"Checks for transmute calls which would receive a null pointer."##,
+ },
+ Lint {
+ label: "clippy::trivial_regex",
+ description: r##"Checks for trivial [regex](https://crates.io/crates/regex)
+creation (with `Regex::new`, `RegexBuilder::new`, or `RegexSet::new`)."##,
+ },
+ Lint {
+ label: "clippy::trivially_copy_pass_by_ref",
+ description: r##"Checks for functions taking arguments by reference, where
+the argument type is `Copy` and small enough to be more efficient to always
+pass by value."##,
+ },
+ Lint { label: "clippy::try_err", description: r##"Checks for usages of `Err(x)?`."## },
+ Lint {
+ label: "clippy::type_complexity",
+ description: r##"Checks for types used in structs, parameters and `let`
+declarations above a certain complexity threshold."##,
+ },
+ Lint {
+ label: "clippy::type_repetition_in_bounds",
+ description: r##"This lint warns about unnecessary type repetitions in trait bounds"##,
+ },
+ Lint {
+ label: "clippy::undocumented_unsafe_blocks",
+ description: r##"Checks for `unsafe` blocks without a `// Safety: ` comment
+explaining why the unsafe operations performed inside
+the block are safe."##,
+ },
+ Lint {
+ label: "clippy::undropped_manually_drops",
+ description: r##"Prevents the safe `std::mem::drop` function from being called on `std::mem::ManuallyDrop`."##,
+ },
+ Lint {
+ label: "clippy::unicode_not_nfc",
+ description: r##"Checks for string literals that contain Unicode in a form
+that is not equal to its
+[NFC-recomposition](http://www.unicode.org/reports/tr15/#Norm_Forms)."##,
+ },
+ Lint {
+ label: "clippy::unimplemented",
+ description: r##"Checks for usage of `unimplemented!`."##,
+ },
+ Lint {
+ label: "clippy::uninit_assumed_init",
+ description: r##"Checks for `MaybeUninit::uninit().assume_init()`."##,
+ },
+ Lint {
+ label: "clippy::uninit_vec",
+ description: r##"Checks for `set_len()` call that creates `Vec` with uninitialized elements.
+This is commonly caused by calling `set_len()` right after allocating or
+reserving a buffer with `new()`, `default()`, `with_capacity()`, or `reserve()`."##,
+ },
+ Lint {
+ label: "clippy::unit_arg",
+ description: r##"Checks for passing a unit value as an argument to a function without using a
+unit literal (`()`)."##,
+ },
+ Lint {
+ label: "clippy::unit_cmp",
+ description: r##"Checks for comparisons to unit. This includes all binary
+comparisons (like `==` and `<`) and asserts."##,
+ },
+ Lint { label: "clippy::unit_hash", description: r##"Detects `().hash(_)`."## },
+ Lint {
+ label: "clippy::unit_return_expecting_ord",
+ description: r##"Checks for functions that expect closures of type
+Fn(...) -> Ord where the implemented closure returns the unit type.
+The lint also suggests to remove the semi-colon at the end of the statement if present."##,
+ },
+ Lint {
+ label: "clippy::unnecessary_cast",
+ description: r##"Checks for casts to the same type, casts of int literals to integer types
+and casts of float literals to float types."##,
+ },
+ Lint {
+ label: "clippy::unnecessary_filter_map",
+ description: r##"Checks for `filter_map` calls which could be replaced by `filter` or `map`.
+More specifically it checks if the closure provided is only performing one of the
+filter or map operations and suggests the appropriate option."##,
+ },
+ Lint {
+ label: "clippy::unnecessary_fold",
+ description: r##"Checks for using `fold` when a more succinct alternative exists.
+Specifically, this checks for `fold`s which could be replaced by `any`, `all`,
+`sum` or `product`."##,
+ },
+ Lint {
+ label: "clippy::unnecessary_lazy_evaluations",
+ description: r##"As the counterpart to `or_fun_call`, this lint looks for unnecessary
+lazily evaluated closures on `Option` and `Result`.
+
+This lint suggests changing the following functions, when eager evaluation results in
+simpler code:
+ - `unwrap_or_else` to `unwrap_or`
+ - `and_then` to `and`
+ - `or_else` to `or`
+ - `get_or_insert_with` to `get_or_insert`
+ - `ok_or_else` to `ok_or`"##,
+ },
+ Lint {
+ label: "clippy::unnecessary_mut_passed",
+ description: r##"Detects passing a mutable reference to a function that only
+requires an immutable reference."##,
+ },
+ Lint {
+ label: "clippy::unnecessary_operation",
+ description: r##"Checks for expression statements that can be reduced to a
+sub-expression."##,
+ },
+ Lint {
+ label: "clippy::unnecessary_self_imports",
+ description: r##"Checks for imports ending in `::{self}`."##,
+ },
+ Lint {
+ label: "clippy::unnecessary_sort_by",
+ description: r##"Detects uses of `Vec::sort_by` passing in a closure
+which compares the two arguments, either directly or indirectly."##,
+ },
+ Lint {
+ label: "clippy::unnecessary_to_owned",
+ description: r##"Checks for unnecessary calls to [`ToOwned::to_owned`](https://doc.rust-lang.org/std/borrow/trait.ToOwned.html#tymethod.to_owned)
+and other `to_owned`-like functions."##,
+ },
+ Lint {
+ label: "clippy::unnecessary_unwrap",
+ description: r##"Checks for calls of `unwrap[_err]()` that cannot fail."##,
+ },
+ Lint {
+ label: "clippy::unnecessary_wraps",
+ description: r##"Checks for private functions that only return `Ok` or `Some`."##,
+ },
+ Lint {
+ label: "clippy::unneeded_field_pattern",
+ description: r##"Checks for structure field patterns bound to wildcards."##,
+ },
+ Lint {
+ label: "clippy::unneeded_wildcard_pattern",
+ description: r##"Checks for tuple patterns with a wildcard
+pattern (`_`) is next to a rest pattern (`..`).
+
+_NOTE_: While `_, ..` means there is at least one element left, `..`
+means there are 0 or more elements left. This can make a difference
+when refactoring, but shouldn't result in errors in the refactored code,
+since the wildcard pattern isn't used anyway."##,
+ },
+ Lint {
+ label: "clippy::unnested_or_patterns",
+ description: r##"Checks for unnested or-patterns, e.g., `Some(0) | Some(2)` and
+suggests replacing the pattern with a nested one, `Some(0 | 2)`.
+
+Another way to think of this is that it rewrites patterns in
+*disjunctive normal form (DNF)* into *conjunctive normal form (CNF)*."##,
+ },
+ Lint { label: "clippy::unreachable", description: r##"Checks for usage of `unreachable!`."## },
+ Lint {
+ label: "clippy::unreadable_literal",
+ description: r##"Warns if a long integral or floating-point constant does
+not contain underscores."##,
+ },
+ Lint {
+ label: "clippy::unsafe_derive_deserialize",
+ description: r##"Checks for deriving `serde::Deserialize` on a type that
+has methods using `unsafe`."##,
+ },
+ Lint {
+ label: "clippy::unsafe_removed_from_name",
+ description: r##"Checks for imports that remove unsafe from an item's
+name."##,
+ },
+ Lint {
+ label: "clippy::unsafe_vector_initialization",
+ description: r##"Nothing. This lint has been deprecated."##,
+ },
+ Lint {
+ label: "clippy::unseparated_literal_suffix",
+ description: r##"Warns if literal suffixes are not separated by an
+underscore.
+To enforce unseparated literal suffix style,
+see the `separated_literal_suffix` lint."##,
+ },
+ Lint {
+ label: "clippy::unsound_collection_transmute",
+ description: r##"Checks for transmutes between collections whose
+types have different ABI, size or alignment."##,
+ },
+ Lint {
+ label: "clippy::unstable_as_mut_slice",
+ description: r##"Nothing. This lint has been deprecated."##,
+ },
+ Lint {
+ label: "clippy::unstable_as_slice",
+ description: r##"Nothing. This lint has been deprecated."##,
+ },
+ Lint {
+ label: "clippy::unused_async",
+ description: r##"Checks for functions that are declared `async` but have no `.await`s inside of them."##,
+ },
+ Lint {
+ label: "clippy::unused_collect",
+ description: r##"Nothing. This lint has been deprecated."##,
+ },
+ Lint {
+ label: "clippy::unused_io_amount",
+ description: r##"Checks for unused written/read amount."##,
+ },
+ Lint {
+ label: "clippy::unused_self",
+ description: r##"Checks methods that contain a `self` argument but don't use it"##,
+ },
+ Lint {
+ label: "clippy::unused_unit",
+ description: r##"Checks for unit (`()`) expressions that can be removed."##,
+ },
+ Lint {
+ label: "clippy::unusual_byte_groupings",
+ description: r##"Warns if hexadecimal or binary literals are not grouped
+by nibble or byte."##,
+ },
+ Lint {
+ label: "clippy::unwrap_in_result",
+ description: r##"Checks for functions of type `Result` that contain `expect()` or `unwrap()`"##,
+ },
+ Lint {
+ label: "clippy::unwrap_or_else_default",
+ description: r##"Checks for usages of `_.unwrap_or_else(Default::default)` on `Option` and
+`Result` values."##,
+ },
+ Lint {
+ label: "clippy::unwrap_used",
+ description: r##"Checks for `.unwrap()` calls on `Option`s and on `Result`s."##,
+ },
+ Lint {
+ label: "clippy::upper_case_acronyms",
+ description: r##"Checks for fully capitalized names and optionally names containing a capitalized acronym."##,
+ },
+ Lint {
+ label: "clippy::use_debug",
+ description: r##"Checks for use of `Debug` formatting. The purpose of this
+lint is to catch debugging remnants."##,
+ },
+ Lint {
+ label: "clippy::use_self",
+ description: r##"Checks for unnecessary repetition of structure name when a
+replacement with `Self` is applicable."##,
+ },
+ Lint {
+ label: "clippy::used_underscore_binding",
+ description: r##"Checks for the use of bindings with a single leading
+underscore."##,
+ },
+ Lint {
+ label: "clippy::useless_asref",
+ description: r##"Checks for usage of `.as_ref()` or `.as_mut()` where the
+types before and after the call are the same."##,
+ },
+ Lint {
+ label: "clippy::useless_attribute",
+ description: r##"Checks for `extern crate` and `use` items annotated with
+lint attributes.
+
+This lint permits `#[allow(unused_imports)]`, `#[allow(deprecated)]`,
+`#[allow(unreachable_pub)]`, `#[allow(clippy::wildcard_imports)]` and
+`#[allow(clippy::enum_glob_use)]` on `use` items and `#[allow(unused_imports)]` on
+`extern crate` items with a `#[macro_use]` attribute."##,
+ },
+ Lint {
+ label: "clippy::useless_conversion",
+ description: r##"Checks for `Into`, `TryInto`, `From`, `TryFrom`, or `IntoIter` calls
+which uselessly convert to the same type."##,
+ },
+ Lint {
+ label: "clippy::useless_format",
+ description: r##"Checks for the use of `format!(string literal with no
+argument)` and `format!({}, foo)` where `foo` is a string."##,
+ },
+ Lint {
+ label: "clippy::useless_let_if_seq",
+ description: r##"Checks for variable declarations immediately followed by a
+conditional affectation."##,
+ },
+ Lint {
+ label: "clippy::useless_transmute",
+ description: r##"Checks for transmutes to the original type of the object
+and transmutes that could be a cast."##,
+ },
+ Lint {
+ label: "clippy::useless_vec",
+ description: r##"Checks for usage of `&vec![..]` when using `&[..]` would
+be possible."##,
+ },
+ Lint {
+ label: "clippy::vec_box",
+ description: r##"Checks for use of `Vec<Box<T>>` where T: Sized anywhere in the code.
+Check the [Box documentation](https://doc.rust-lang.org/std/boxed/index.html) for more information."##,
+ },
+ Lint {
+ label: "clippy::vec_init_then_push",
+ description: r##"Checks for calls to `push` immediately after creating a new `Vec`."##,
+ },
+ Lint {
+ label: "clippy::vec_resize_to_zero",
+ description: r##"Finds occurrences of `Vec::resize(0, an_int)`"##,
+ },
+ Lint {
+ label: "clippy::verbose_bit_mask",
+ description: r##"Checks for bit masks that can be replaced by a call
+to `trailing_zeros`"##,
+ },
+ Lint {
+ label: "clippy::verbose_file_reads",
+ description: r##"Checks for use of File::read_to_end and File::read_to_string."##,
+ },
+ Lint {
+ label: "clippy::vtable_address_comparisons",
+ description: r##"Checks for comparisons with an address of a trait vtable."##,
+ },
+ Lint {
+ label: "clippy::while_immutable_condition",
+ description: r##"Checks whether variables used within while loop condition
+can be (and are) mutated in the body."##,
+ },
+ Lint {
+ label: "clippy::while_let_loop",
+ description: r##"Detects `loop + match` combinations that are easier
+written as a `while let` loop."##,
+ },
+ Lint {
+ label: "clippy::while_let_on_iterator",
+ description: r##"Checks for `while let` expressions on iterators."##,
+ },
+ Lint {
+ label: "clippy::wildcard_dependencies",
+ description: r##"Checks for wildcard dependencies in the `Cargo.toml`."##,
+ },
+ Lint {
+ label: "clippy::wildcard_enum_match_arm",
+ description: r##"Checks for wildcard enum matches using `_`."##,
+ },
+ Lint {
+ label: "clippy::wildcard_imports",
+ description: r##"Checks for wildcard imports `use _::*`."##,
+ },
+ Lint {
+ label: "clippy::wildcard_in_or_patterns",
+ description: r##"Checks for wildcard pattern used with others patterns in same match arm."##,
+ },
+ Lint {
+ label: "clippy::write_literal",
+ description: r##"This lint warns about the use of literals as `write!`/`writeln!` args."##,
+ },
+ Lint {
+ label: "clippy::write_with_newline",
+ description: r##"This lint warns when you use `write!()` with a format
+string that
+ends in a newline."##,
+ },
+ Lint {
+ label: "clippy::writeln_empty_string",
+ description: r##"This lint warns when you use `writeln!(buf, )` to
+print a newline."##,
+ },
+ Lint {
+ label: "clippy::wrong_pub_self_convention",
+ description: r##"Nothing. This lint has been deprecated."##,
+ },
+ Lint {
+ label: "clippy::wrong_self_convention",
+ description: r##"Checks for methods with certain name prefixes and which
+doesn't match how self is taken. The actual rules are:
+
+|Prefix |Postfix |`self` taken | `self` type |
+|-------|------------|-----------------------|--------------|
+|`as_` | none |`&self` or `&mut self` | any |
+|`from_`| none | none | any |
+|`into_`| none |`self` | any |
+|`is_` | none |`&self` or none | any |
+|`to_` | `_mut` |`&mut self` | any |
+|`to_` | not `_mut` |`self` | `Copy` |
+|`to_` | not `_mut` |`&self` | not `Copy` |
+
+Note: Clippy doesn't trigger methods with `to_` prefix in:
+- Traits definition.
+Clippy can not tell if a type that implements a trait is `Copy` or not.
+- Traits implementation, when `&self` is taken.
+The method signature is controlled by the trait and often `&self` is required for all types that implement the trait
+(see e.g. the `std::string::ToString` trait).
+
+Clippy allows `Pin<&Self>` and `Pin<&mut Self>` if `&self` and `&mut self` is required.
+
+Please find more info here:
+https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv"##,
+ },
+ Lint {
+ label: "clippy::wrong_transmute",
+ description: r##"Checks for transmutes that can't ever be correct on any
+architecture."##,
+ },
+ Lint { label: "clippy::zero_divided_by_zero", description: r##"Checks for `0.0 / 0.0`."## },
+ Lint {
+ label: "clippy::zero_prefixed_literal",
+ description: r##"Warns if an integral constant literal starts with `0`."##,
+ },
+ Lint {
+ label: "clippy::zero_ptr",
+ description: r##"Catch casts from `0` to some pointer type"##,
+ },
+ Lint {
+ label: "clippy::zero_sized_map_values",
+ description: r##"Checks for maps with zero-sized value types anywhere in the code."##,
+ },
+ Lint {
+ label: "clippy::zst_offset",
+ description: r##"Checks for `offset(_)`, `wrapping_`{`add`, `sub`}, etc. on raw pointers to
+zero-sized types"##,
+ },
+];
+pub const CLIPPY_LINT_GROUPS: &[LintGroup] = &[
+ LintGroup {
+ lint: Lint {
+ label: "clippy::cargo",
+ description: r##"lint group for: clippy::cargo_common_metadata, clippy::multiple_crate_versions, clippy::negative_feature_names, clippy::redundant_feature_names, clippy::wildcard_dependencies"##,
+ },
+ children: &[
+ "clippy::cargo_common_metadata",
+ "clippy::multiple_crate_versions",
+ "clippy::negative_feature_names",
+ "clippy::redundant_feature_names",
+ "clippy::wildcard_dependencies",
+ ],
+ },
+ LintGroup {
+ lint: Lint {
+ label: "clippy::complexity",
+ description: r##"lint group for: clippy::bind_instead_of_map, clippy::bool_comparison, clippy::borrowed_box, clippy::char_lit_as_u8, clippy::clone_on_copy, clippy::crosspointer_transmute, clippy::deprecated_cfg_attr, clippy::deref_addrof, clippy::derivable_impls, clippy::diverging_sub_expression, clippy::double_comparisons, clippy::double_parens, clippy::duration_subsec, clippy::explicit_counter_loop, clippy::explicit_write, clippy::extra_unused_lifetimes, clippy::filter_map_identity, clippy::filter_next, clippy::flat_map_identity, clippy::get_last_with_len, clippy::identity_op, clippy::inspect_for_each, clippy::int_plus_one, clippy::iter_count, clippy::manual_filter_map, clippy::manual_find_map, clippy::manual_flatten, clippy::manual_split_once, clippy::manual_strip, clippy::manual_swap, clippy::manual_unwrap_or, clippy::map_flatten, clippy::map_identity, clippy::match_as_ref, clippy::match_single_binding, clippy::needless_arbitrary_self_type, clippy::needless_bool, clippy::needless_borrowed_reference, clippy::needless_lifetimes, clippy::needless_option_as_deref, clippy::needless_question_mark, clippy::needless_splitn, clippy::needless_update, clippy::neg_cmp_op_on_partial_ord, clippy::no_effect, clippy::nonminimal_bool, clippy::option_as_ref_deref, clippy::option_filter_map, clippy::option_map_unit_fn, clippy::overflow_check_conditional, clippy::partialeq_ne_impl, clippy::precedence, clippy::ptr_offset_with_cast, clippy::range_zip_with_len, clippy::redundant_closure_call, clippy::redundant_slicing, clippy::ref_in_deref, clippy::repeat_once, clippy::result_map_unit_fn, clippy::search_is_some, clippy::short_circuit_statement, clippy::single_element_loop, clippy::skip_while_next, clippy::string_from_utf8_as_bytes, clippy::strlen_on_c_strings, clippy::temporary_assignment, clippy::too_many_arguments, clippy::transmute_bytes_to_str, clippy::transmute_float_to_int, clippy::transmute_int_to_bool, clippy::transmute_int_to_char, clippy::transmute_int_to_float, clippy::transmute_num_to_bytes, clippy::transmute_ptr_to_ref, clippy::transmutes_expressible_as_ptr_casts, clippy::type_complexity, clippy::unit_arg, clippy::unnecessary_cast, clippy::unnecessary_filter_map, clippy::unnecessary_operation, clippy::unnecessary_sort_by, clippy::unnecessary_unwrap, clippy::unneeded_wildcard_pattern, clippy::useless_asref, clippy::useless_conversion, clippy::useless_format, clippy::vec_box, clippy::while_let_loop, clippy::wildcard_in_or_patterns, clippy::zero_divided_by_zero, clippy::zero_prefixed_literal"##,
+ },
+ children: &[
+ "clippy::bind_instead_of_map",
+ "clippy::bool_comparison",
+ "clippy::borrowed_box",
+ "clippy::char_lit_as_u8",
+ "clippy::clone_on_copy",
+ "clippy::crosspointer_transmute",
+ "clippy::deprecated_cfg_attr",
+ "clippy::deref_addrof",
+ "clippy::derivable_impls",
+ "clippy::diverging_sub_expression",
+ "clippy::double_comparisons",
+ "clippy::double_parens",
+ "clippy::duration_subsec",
+ "clippy::explicit_counter_loop",
+ "clippy::explicit_write",
+ "clippy::extra_unused_lifetimes",
+ "clippy::filter_map_identity",
+ "clippy::filter_next",
+ "clippy::flat_map_identity",
+ "clippy::get_last_with_len",
+ "clippy::identity_op",
+ "clippy::inspect_for_each",
+ "clippy::int_plus_one",
+ "clippy::iter_count",
+ "clippy::manual_filter_map",
+ "clippy::manual_find_map",
+ "clippy::manual_flatten",
+ "clippy::manual_split_once",
+ "clippy::manual_strip",
+ "clippy::manual_swap",
+ "clippy::manual_unwrap_or",
+ "clippy::map_flatten",
+ "clippy::map_identity",
+ "clippy::match_as_ref",
+ "clippy::match_single_binding",
+ "clippy::needless_arbitrary_self_type",
+ "clippy::needless_bool",
+ "clippy::needless_borrowed_reference",
+ "clippy::needless_lifetimes",
+ "clippy::needless_option_as_deref",
+ "clippy::needless_question_mark",
+ "clippy::needless_splitn",
+ "clippy::needless_update",
+ "clippy::neg_cmp_op_on_partial_ord",
+ "clippy::no_effect",
+ "clippy::nonminimal_bool",
+ "clippy::option_as_ref_deref",
+ "clippy::option_filter_map",
+ "clippy::option_map_unit_fn",
+ "clippy::overflow_check_conditional",
+ "clippy::partialeq_ne_impl",
+ "clippy::precedence",
+ "clippy::ptr_offset_with_cast",
+ "clippy::range_zip_with_len",
+ "clippy::redundant_closure_call",
+ "clippy::redundant_slicing",
+ "clippy::ref_in_deref",
+ "clippy::repeat_once",
+ "clippy::result_map_unit_fn",
+ "clippy::search_is_some",
+ "clippy::short_circuit_statement",
+ "clippy::single_element_loop",
+ "clippy::skip_while_next",
+ "clippy::string_from_utf8_as_bytes",
+ "clippy::strlen_on_c_strings",
+ "clippy::temporary_assignment",
+ "clippy::too_many_arguments",
+ "clippy::transmute_bytes_to_str",
+ "clippy::transmute_float_to_int",
+ "clippy::transmute_int_to_bool",
+ "clippy::transmute_int_to_char",
+ "clippy::transmute_int_to_float",
+ "clippy::transmute_num_to_bytes",
+ "clippy::transmute_ptr_to_ref",
+ "clippy::transmutes_expressible_as_ptr_casts",
+ "clippy::type_complexity",
+ "clippy::unit_arg",
+ "clippy::unnecessary_cast",
+ "clippy::unnecessary_filter_map",
+ "clippy::unnecessary_operation",
+ "clippy::unnecessary_sort_by",
+ "clippy::unnecessary_unwrap",
+ "clippy::unneeded_wildcard_pattern",
+ "clippy::useless_asref",
+ "clippy::useless_conversion",
+ "clippy::useless_format",
+ "clippy::vec_box",
+ "clippy::while_let_loop",
+ "clippy::wildcard_in_or_patterns",
+ "clippy::zero_divided_by_zero",
+ "clippy::zero_prefixed_literal",
+ ],
+ },
+ LintGroup {
+ lint: Lint {
+ label: "clippy::correctness",
+ description: r##"lint group for: clippy::absurd_extreme_comparisons, clippy::almost_swapped, clippy::approx_constant, clippy::async_yields_async, clippy::bad_bit_mask, clippy::cast_ref_to_mut, clippy::clone_double_ref, clippy::cmp_nan, clippy::deprecated_semver, clippy::derive_hash_xor_eq, clippy::derive_ord_xor_partial_ord, clippy::drop_copy, clippy::drop_ref, clippy::enum_clike_unportable_variant, clippy::eq_op, clippy::erasing_op, clippy::fn_address_comparisons, clippy::forget_copy, clippy::forget_ref, clippy::if_let_mutex, clippy::if_same_then_else, clippy::ifs_same_cond, clippy::ineffective_bit_mask, clippy::infinite_iter, clippy::inherent_to_string_shadow_display, clippy::inline_fn_without_body, clippy::invalid_null_ptr_usage, clippy::invalid_regex, clippy::invisible_characters, clippy::iter_next_loop, clippy::iterator_step_by_zero, clippy::let_underscore_lock, clippy::logic_bug, clippy::match_str_case_mismatch, clippy::mem_replace_with_uninit, clippy::min_max, clippy::mismatched_target_os, clippy::mistyped_literal_suffixes, clippy::modulo_one, clippy::mut_from_ref, clippy::never_loop, clippy::non_octal_unix_permissions, clippy::nonsensical_open_options, clippy::not_unsafe_ptr_arg_deref, clippy::option_env_unwrap, clippy::out_of_bounds_indexing, clippy::panicking_unwrap, clippy::possible_missing_comma, clippy::reversed_empty_ranges, clippy::self_assignment, clippy::serde_api_misuse, clippy::size_of_in_element_count, clippy::suspicious_splitn, clippy::to_string_in_display, clippy::transmuting_null, clippy::undropped_manually_drops, clippy::uninit_assumed_init, clippy::uninit_vec, clippy::unit_cmp, clippy::unit_hash, clippy::unit_return_expecting_ord, clippy::unsound_collection_transmute, clippy::unused_io_amount, clippy::useless_attribute, clippy::vec_resize_to_zero, clippy::vtable_address_comparisons, clippy::while_immutable_condition, clippy::wrong_transmute, clippy::zst_offset"##,
+ },
+ children: &[
+ "clippy::absurd_extreme_comparisons",
+ "clippy::almost_swapped",
+ "clippy::approx_constant",
+ "clippy::async_yields_async",
+ "clippy::bad_bit_mask",
+ "clippy::cast_ref_to_mut",
+ "clippy::clone_double_ref",
+ "clippy::cmp_nan",
+ "clippy::deprecated_semver",
+ "clippy::derive_hash_xor_eq",
+ "clippy::derive_ord_xor_partial_ord",
+ "clippy::drop_copy",
+ "clippy::drop_ref",
+ "clippy::enum_clike_unportable_variant",
+ "clippy::eq_op",
+ "clippy::erasing_op",
+ "clippy::fn_address_comparisons",
+ "clippy::forget_copy",
+ "clippy::forget_ref",
+ "clippy::if_let_mutex",
+ "clippy::if_same_then_else",
+ "clippy::ifs_same_cond",
+ "clippy::ineffective_bit_mask",
+ "clippy::infinite_iter",
+ "clippy::inherent_to_string_shadow_display",
+ "clippy::inline_fn_without_body",
+ "clippy::invalid_null_ptr_usage",
+ "clippy::invalid_regex",
+ "clippy::invisible_characters",
+ "clippy::iter_next_loop",
+ "clippy::iterator_step_by_zero",
+ "clippy::let_underscore_lock",
+ "clippy::logic_bug",
+ "clippy::match_str_case_mismatch",
+ "clippy::mem_replace_with_uninit",
+ "clippy::min_max",
+ "clippy::mismatched_target_os",
+ "clippy::mistyped_literal_suffixes",
+ "clippy::modulo_one",
+ "clippy::mut_from_ref",
+ "clippy::never_loop",
+ "clippy::non_octal_unix_permissions",
+ "clippy::nonsensical_open_options",
+ "clippy::not_unsafe_ptr_arg_deref",
+ "clippy::option_env_unwrap",
+ "clippy::out_of_bounds_indexing",
+ "clippy::panicking_unwrap",
+ "clippy::possible_missing_comma",
+ "clippy::reversed_empty_ranges",
+ "clippy::self_assignment",
+ "clippy::serde_api_misuse",
+ "clippy::size_of_in_element_count",
+ "clippy::suspicious_splitn",
+ "clippy::to_string_in_display",
+ "clippy::transmuting_null",
+ "clippy::undropped_manually_drops",
+ "clippy::uninit_assumed_init",
+ "clippy::uninit_vec",
+ "clippy::unit_cmp",
+ "clippy::unit_hash",
+ "clippy::unit_return_expecting_ord",
+ "clippy::unsound_collection_transmute",
+ "clippy::unused_io_amount",
+ "clippy::useless_attribute",
+ "clippy::vec_resize_to_zero",
+ "clippy::vtable_address_comparisons",
+ "clippy::while_immutable_condition",
+ "clippy::wrong_transmute",
+ "clippy::zst_offset",
+ ],
+ },
+ LintGroup {
+ lint: Lint {
+ label: "clippy::deprecated",
+ description: r##"lint group for: clippy::assign_ops, clippy::extend_from_slice, clippy::filter_map, clippy::find_map, clippy::if_let_redundant_pattern_matching, clippy::misaligned_transmute, clippy::pub_enum_variant_names, clippy::range_step_by_zero, clippy::regex_macro, clippy::replace_consts, clippy::should_assert_eq, clippy::unsafe_vector_initialization, clippy::unstable_as_mut_slice, clippy::unstable_as_slice, clippy::unused_collect, clippy::wrong_pub_self_convention"##,
+ },
+ children: &[
+ "clippy::assign_ops",
+ "clippy::extend_from_slice",
+ "clippy::filter_map",
+ "clippy::find_map",
+ "clippy::if_let_redundant_pattern_matching",
+ "clippy::misaligned_transmute",
+ "clippy::pub_enum_variant_names",
+ "clippy::range_step_by_zero",
+ "clippy::regex_macro",
+ "clippy::replace_consts",
+ "clippy::should_assert_eq",
+ "clippy::unsafe_vector_initialization",
+ "clippy::unstable_as_mut_slice",
+ "clippy::unstable_as_slice",
+ "clippy::unused_collect",
+ "clippy::wrong_pub_self_convention",
+ ],
+ },
+ LintGroup {
+ lint: Lint {
+ label: "clippy::nursery",
+ description: r##"lint group for: clippy::branches_sharing_code, clippy::cognitive_complexity, clippy::debug_assert_with_mut_call, clippy::disallowed_methods, clippy::disallowed_types, clippy::empty_line_after_outer_attr, clippy::equatable_if_let, clippy::fallible_impl_from, clippy::future_not_send, clippy::imprecise_flops, clippy::index_refutable_slice, clippy::missing_const_for_fn, clippy::mutex_integer, clippy::non_send_fields_in_send_ty, clippy::nonstandard_macro_braces, clippy::option_if_let_else, clippy::path_buf_push_overwrite, clippy::redundant_pub_crate, clippy::string_lit_as_bytes, clippy::suboptimal_flops, clippy::suspicious_operation_groupings, clippy::trailing_empty_array, clippy::trivial_regex, clippy::use_self, clippy::useless_let_if_seq, clippy::useless_transmute"##,
+ },
+ children: &[
+ "clippy::branches_sharing_code",
+ "clippy::cognitive_complexity",
+ "clippy::debug_assert_with_mut_call",
+ "clippy::disallowed_methods",
+ "clippy::disallowed_types",
+ "clippy::empty_line_after_outer_attr",
+ "clippy::equatable_if_let",
+ "clippy::fallible_impl_from",
+ "clippy::future_not_send",
+ "clippy::imprecise_flops",
+ "clippy::index_refutable_slice",
+ "clippy::missing_const_for_fn",
+ "clippy::mutex_integer",
+ "clippy::non_send_fields_in_send_ty",
+ "clippy::nonstandard_macro_braces",
+ "clippy::option_if_let_else",
+ "clippy::path_buf_push_overwrite",
+ "clippy::redundant_pub_crate",
+ "clippy::string_lit_as_bytes",
+ "clippy::suboptimal_flops",
+ "clippy::suspicious_operation_groupings",
+ "clippy::trailing_empty_array",
+ "clippy::trivial_regex",
+ "clippy::use_self",
+ "clippy::useless_let_if_seq",
+ "clippy::useless_transmute",
+ ],
+ },
+ LintGroup {
+ lint: Lint {
+ label: "clippy::pedantic",
+ description: r##"lint group for: clippy::await_holding_lock, clippy::await_holding_refcell_ref, clippy::case_sensitive_file_extension_comparisons, clippy::cast_lossless, clippy::cast_possible_truncation, clippy::cast_possible_wrap, clippy::cast_precision_loss, clippy::cast_ptr_alignment, clippy::cast_sign_loss, clippy::checked_conversions, clippy::cloned_instead_of_copied, clippy::copy_iterator, clippy::default_trait_access, clippy::doc_markdown, clippy::empty_enum, clippy::enum_glob_use, clippy::expl_impl_clone_on_copy, clippy::explicit_deref_methods, clippy::explicit_into_iter_loop, clippy::explicit_iter_loop, clippy::filter_map_next, clippy::flat_map_option, clippy::float_cmp, clippy::fn_params_excessive_bools, clippy::from_iter_instead_of_collect, clippy::if_not_else, clippy::implicit_clone, clippy::implicit_hasher, clippy::implicit_saturating_sub, clippy::inconsistent_struct_constructor, clippy::inefficient_to_string, clippy::inline_always, clippy::invalid_upcast_comparisons, clippy::items_after_statements, clippy::iter_not_returning_iterator, clippy::large_digit_groups, clippy::large_stack_arrays, clippy::large_types_passed_by_value, clippy::let_underscore_drop, clippy::let_unit_value, clippy::linkedlist, clippy::macro_use_imports, clippy::manual_assert, clippy::manual_ok_or, clippy::many_single_char_names, clippy::map_unwrap_or, clippy::match_bool, clippy::match_on_vec_items, clippy::match_same_arms, clippy::match_wild_err_arm, clippy::match_wildcard_for_single_variants, clippy::maybe_infinite_iter, clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::module_name_repetitions, clippy::must_use_candidate, clippy::mut_mut, clippy::naive_bytecount, clippy::needless_bitwise_bool, clippy::needless_continue, clippy::needless_for_each, clippy::needless_pass_by_value, clippy::no_effect_underscore_binding, clippy::option_option, clippy::ptr_as_ptr, clippy::range_minus_one, clippy::range_plus_one, clippy::redundant_closure_for_method_calls, clippy::redundant_else, clippy::ref_binding_to_reference, clippy::ref_option_ref, clippy::same_functions_in_if_condition, clippy::semicolon_if_nothing_returned, clippy::similar_names, clippy::single_match_else, clippy::string_add_assign, clippy::struct_excessive_bools, clippy::too_many_lines, clippy::trait_duplication_in_bounds, clippy::transmute_ptr_to_ptr, clippy::trivially_copy_pass_by_ref, clippy::type_repetition_in_bounds, clippy::unicode_not_nfc, clippy::unnecessary_wraps, clippy::unnested_or_patterns, clippy::unreadable_literal, clippy::unsafe_derive_deserialize, clippy::unused_async, clippy::unused_self, clippy::used_underscore_binding, clippy::verbose_bit_mask, clippy::wildcard_imports, clippy::zero_sized_map_values"##,
+ },
+ children: &[
+ "clippy::await_holding_lock",
+ "clippy::await_holding_refcell_ref",
+ "clippy::case_sensitive_file_extension_comparisons",
+ "clippy::cast_lossless",
+ "clippy::cast_possible_truncation",
+ "clippy::cast_possible_wrap",
+ "clippy::cast_precision_loss",
+ "clippy::cast_ptr_alignment",
+ "clippy::cast_sign_loss",
+ "clippy::checked_conversions",
+ "clippy::cloned_instead_of_copied",
+ "clippy::copy_iterator",
+ "clippy::default_trait_access",
+ "clippy::doc_markdown",
+ "clippy::empty_enum",
+ "clippy::enum_glob_use",
+ "clippy::expl_impl_clone_on_copy",
+ "clippy::explicit_deref_methods",
+ "clippy::explicit_into_iter_loop",
+ "clippy::explicit_iter_loop",
+ "clippy::filter_map_next",
+ "clippy::flat_map_option",
+ "clippy::float_cmp",
+ "clippy::fn_params_excessive_bools",
+ "clippy::from_iter_instead_of_collect",
+ "clippy::if_not_else",
+ "clippy::implicit_clone",
+ "clippy::implicit_hasher",
+ "clippy::implicit_saturating_sub",
+ "clippy::inconsistent_struct_constructor",
+ "clippy::inefficient_to_string",
+ "clippy::inline_always",
+ "clippy::invalid_upcast_comparisons",
+ "clippy::items_after_statements",
+ "clippy::iter_not_returning_iterator",
+ "clippy::large_digit_groups",
+ "clippy::large_stack_arrays",
+ "clippy::large_types_passed_by_value",
+ "clippy::let_underscore_drop",
+ "clippy::let_unit_value",
+ "clippy::linkedlist",
+ "clippy::macro_use_imports",
+ "clippy::manual_assert",
+ "clippy::manual_ok_or",
+ "clippy::many_single_char_names",
+ "clippy::map_unwrap_or",
+ "clippy::match_bool",
+ "clippy::match_on_vec_items",
+ "clippy::match_same_arms",
+ "clippy::match_wild_err_arm",
+ "clippy::match_wildcard_for_single_variants",
+ "clippy::maybe_infinite_iter",
+ "clippy::missing_errors_doc",
+ "clippy::missing_panics_doc",
+ "clippy::module_name_repetitions",
+ "clippy::must_use_candidate",
+ "clippy::mut_mut",
+ "clippy::naive_bytecount",
+ "clippy::needless_bitwise_bool",
+ "clippy::needless_continue",
+ "clippy::needless_for_each",
+ "clippy::needless_pass_by_value",
+ "clippy::no_effect_underscore_binding",
+ "clippy::option_option",
+ "clippy::ptr_as_ptr",
+ "clippy::range_minus_one",
+ "clippy::range_plus_one",
+ "clippy::redundant_closure_for_method_calls",
+ "clippy::redundant_else",
+ "clippy::ref_binding_to_reference",
+ "clippy::ref_option_ref",
+ "clippy::same_functions_in_if_condition",
+ "clippy::semicolon_if_nothing_returned",
+ "clippy::similar_names",
+ "clippy::single_match_else",
+ "clippy::string_add_assign",
+ "clippy::struct_excessive_bools",
+ "clippy::too_many_lines",
+ "clippy::trait_duplication_in_bounds",
+ "clippy::transmute_ptr_to_ptr",
+ "clippy::trivially_copy_pass_by_ref",
+ "clippy::type_repetition_in_bounds",
+ "clippy::unicode_not_nfc",
+ "clippy::unnecessary_wraps",
+ "clippy::unnested_or_patterns",
+ "clippy::unreadable_literal",
+ "clippy::unsafe_derive_deserialize",
+ "clippy::unused_async",
+ "clippy::unused_self",
+ "clippy::used_underscore_binding",
+ "clippy::verbose_bit_mask",
+ "clippy::wildcard_imports",
+ "clippy::zero_sized_map_values",
+ ],
+ },
+ LintGroup {
+ lint: Lint {
+ label: "clippy::perf",
+ description: r##"lint group for: clippy::box_collection, clippy::boxed_local, clippy::cmp_owned, clippy::expect_fun_call, clippy::extend_with_drain, clippy::format_in_format_args, clippy::iter_nth, clippy::large_const_arrays, clippy::large_enum_variant, clippy::manual_memcpy, clippy::manual_str_repeat, clippy::map_entry, clippy::mutex_atomic, clippy::needless_collect, clippy::or_fun_call, clippy::redundant_allocation, clippy::redundant_clone, clippy::single_char_pattern, clippy::slow_vector_initialization, clippy::stable_sort_primitive, clippy::to_string_in_format_args, clippy::unnecessary_to_owned, clippy::useless_vec, clippy::vec_init_then_push"##,
+ },
+ children: &[
+ "clippy::box_collection",
+ "clippy::boxed_local",
+ "clippy::cmp_owned",
+ "clippy::expect_fun_call",
+ "clippy::extend_with_drain",
+ "clippy::format_in_format_args",
+ "clippy::iter_nth",
+ "clippy::large_const_arrays",
+ "clippy::large_enum_variant",
+ "clippy::manual_memcpy",
+ "clippy::manual_str_repeat",
+ "clippy::map_entry",
+ "clippy::mutex_atomic",
+ "clippy::needless_collect",
+ "clippy::or_fun_call",
+ "clippy::redundant_allocation",
+ "clippy::redundant_clone",
+ "clippy::single_char_pattern",
+ "clippy::slow_vector_initialization",
+ "clippy::stable_sort_primitive",
+ "clippy::to_string_in_format_args",
+ "clippy::unnecessary_to_owned",
+ "clippy::useless_vec",
+ "clippy::vec_init_then_push",
+ ],
+ },
+ LintGroup {
+ lint: Lint {
+ label: "clippy::restriction",
+ description: r##"lint group for: clippy::as_conversions, clippy::clone_on_ref_ptr, clippy::create_dir, clippy::dbg_macro, clippy::decimal_literal_representation, clippy::default_numeric_fallback, clippy::disallowed_script_idents, clippy::else_if_without_else, clippy::exhaustive_enums, clippy::exhaustive_structs, clippy::exit, clippy::expect_used, clippy::filetype_is_file, clippy::float_arithmetic, clippy::float_cmp_const, clippy::fn_to_numeric_cast_any, clippy::get_unwrap, clippy::if_then_some_else_none, clippy::implicit_return, clippy::indexing_slicing, clippy::inline_asm_x86_att_syntax, clippy::inline_asm_x86_intel_syntax, clippy::integer_arithmetic, clippy::integer_division, clippy::let_underscore_must_use, clippy::lossy_float_literal, clippy::map_err_ignore, clippy::mem_forget, clippy::missing_docs_in_private_items, clippy::missing_enforced_import_renames, clippy::missing_inline_in_public_items, clippy::mod_module_files, clippy::modulo_arithmetic, clippy::multiple_inherent_impl, clippy::non_ascii_literal, clippy::panic, clippy::panic_in_result_fn, clippy::pattern_type_mismatch, clippy::print_stderr, clippy::print_stdout, clippy::rc_buffer, clippy::rc_mutex, clippy::rest_pat_in_fully_bound_structs, clippy::same_name_method, clippy::self_named_module_files, clippy::separated_literal_suffix, clippy::shadow_reuse, clippy::shadow_same, clippy::shadow_unrelated, clippy::str_to_string, clippy::string_add, clippy::string_slice, clippy::string_to_string, clippy::todo, clippy::undocumented_unsafe_blocks, clippy::unimplemented, clippy::unnecessary_self_imports, clippy::unneeded_field_pattern, clippy::unreachable, clippy::unseparated_literal_suffix, clippy::unwrap_in_result, clippy::unwrap_used, clippy::use_debug, clippy::verbose_file_reads, clippy::wildcard_enum_match_arm"##,
+ },
+ children: &[
+ "clippy::as_conversions",
+ "clippy::clone_on_ref_ptr",
+ "clippy::create_dir",
+ "clippy::dbg_macro",
+ "clippy::decimal_literal_representation",
+ "clippy::default_numeric_fallback",
+ "clippy::disallowed_script_idents",
+ "clippy::else_if_without_else",
+ "clippy::exhaustive_enums",
+ "clippy::exhaustive_structs",
+ "clippy::exit",
+ "clippy::expect_used",
+ "clippy::filetype_is_file",
+ "clippy::float_arithmetic",
+ "clippy::float_cmp_const",
+ "clippy::fn_to_numeric_cast_any",
+ "clippy::get_unwrap",
+ "clippy::if_then_some_else_none",
+ "clippy::implicit_return",
+ "clippy::indexing_slicing",
+ "clippy::inline_asm_x86_att_syntax",
+ "clippy::inline_asm_x86_intel_syntax",
+ "clippy::integer_arithmetic",
+ "clippy::integer_division",
+ "clippy::let_underscore_must_use",
+ "clippy::lossy_float_literal",
+ "clippy::map_err_ignore",
+ "clippy::mem_forget",
+ "clippy::missing_docs_in_private_items",
+ "clippy::missing_enforced_import_renames",
+ "clippy::missing_inline_in_public_items",
+ "clippy::mod_module_files",
+ "clippy::modulo_arithmetic",
+ "clippy::multiple_inherent_impl",
+ "clippy::non_ascii_literal",
+ "clippy::panic",
+ "clippy::panic_in_result_fn",
+ "clippy::pattern_type_mismatch",
+ "clippy::print_stderr",
+ "clippy::print_stdout",
+ "clippy::rc_buffer",
+ "clippy::rc_mutex",
+ "clippy::rest_pat_in_fully_bound_structs",
+ "clippy::same_name_method",
+ "clippy::self_named_module_files",
+ "clippy::separated_literal_suffix",
+ "clippy::shadow_reuse",
+ "clippy::shadow_same",
+ "clippy::shadow_unrelated",
+ "clippy::str_to_string",
+ "clippy::string_add",
+ "clippy::string_slice",
+ "clippy::string_to_string",
+ "clippy::todo",
+ "clippy::undocumented_unsafe_blocks",
+ "clippy::unimplemented",
+ "clippy::unnecessary_self_imports",
+ "clippy::unneeded_field_pattern",
+ "clippy::unreachable",
+ "clippy::unseparated_literal_suffix",
+ "clippy::unwrap_in_result",
+ "clippy::unwrap_used",
+ "clippy::use_debug",
+ "clippy::verbose_file_reads",
+ "clippy::wildcard_enum_match_arm",
+ ],
+ },
+ LintGroup {
+ lint: Lint {
+ label: "clippy::style",
+ description: r##"lint group for: clippy::assertions_on_constants, clippy::assign_op_pattern, clippy::blacklisted_name, clippy::blocks_in_if_conditions, clippy::bool_assert_comparison, clippy::borrow_interior_mutable_const, clippy::builtin_type_shadow, clippy::bytes_nth, clippy::chars_last_cmp, clippy::chars_next_cmp, clippy::cmp_null, clippy::collapsible_else_if, clippy::collapsible_if, clippy::collapsible_match, clippy::comparison_chain, clippy::comparison_to_empty, clippy::declare_interior_mutable_const, clippy::double_must_use, clippy::double_neg, clippy::duplicate_underscore_argument, clippy::enum_variant_names, clippy::excessive_precision, clippy::field_reassign_with_default, clippy::fn_to_numeric_cast, clippy::fn_to_numeric_cast_with_truncation, clippy::for_kv_map, clippy::from_over_into, clippy::from_str_radix_10, clippy::inconsistent_digit_grouping, clippy::infallible_destructuring_match, clippy::inherent_to_string, clippy::into_iter_on_ref, clippy::iter_cloned_collect, clippy::iter_next_slice, clippy::iter_nth_zero, clippy::iter_skip_next, clippy::just_underscores_and_digits, clippy::len_without_is_empty, clippy::len_zero, clippy::let_and_return, clippy::main_recursion, clippy::manual_async_fn, clippy::manual_map, clippy::manual_non_exhaustive, clippy::manual_range_contains, clippy::manual_saturating_arithmetic, clippy::map_clone, clippy::map_collect_result_unit, clippy::match_like_matches_macro, clippy::match_overlapping_arm, clippy::match_ref_pats, clippy::match_result_ok, clippy::mem_replace_option_with_none, clippy::mem_replace_with_default, clippy::missing_safety_doc, clippy::mixed_case_hex_literals, clippy::module_inception, clippy::must_use_unit, clippy::mut_mutex_lock, clippy::needless_borrow, clippy::needless_doctest_main, clippy::needless_late_init, clippy::needless_range_loop, clippy::needless_return, clippy::neg_multiply, clippy::new_ret_no_self, clippy::new_without_default, clippy::ok_expect, clippy::op_ref, clippy::option_map_or_none, clippy::print_literal, clippy::print_with_newline, clippy::println_empty_string, clippy::ptr_arg, clippy::ptr_eq, clippy::question_mark, clippy::redundant_closure, clippy::redundant_field_names, clippy::redundant_pattern, clippy::redundant_pattern_matching, clippy::redundant_static_lifetimes, clippy::result_map_or_into_option, clippy::result_unit_err, clippy::same_item_push, clippy::self_named_constructors, clippy::should_implement_trait, clippy::single_char_add_str, clippy::single_component_path_imports, clippy::single_match, clippy::string_extend_chars, clippy::tabs_in_doc_comments, clippy::to_digit_is_some, clippy::toplevel_ref_arg, clippy::try_err, clippy::unnecessary_fold, clippy::unnecessary_lazy_evaluations, clippy::unnecessary_mut_passed, clippy::unsafe_removed_from_name, clippy::unused_unit, clippy::unusual_byte_groupings, clippy::unwrap_or_else_default, clippy::upper_case_acronyms, clippy::while_let_on_iterator, clippy::write_literal, clippy::write_with_newline, clippy::writeln_empty_string, clippy::wrong_self_convention, clippy::zero_ptr"##,
+ },
+ children: &[
+ "clippy::assertions_on_constants",
+ "clippy::assign_op_pattern",
+ "clippy::blacklisted_name",
+ "clippy::blocks_in_if_conditions",
+ "clippy::bool_assert_comparison",
+ "clippy::borrow_interior_mutable_const",
+ "clippy::builtin_type_shadow",
+ "clippy::bytes_nth",
+ "clippy::chars_last_cmp",
+ "clippy::chars_next_cmp",
+ "clippy::cmp_null",
+ "clippy::collapsible_else_if",
+ "clippy::collapsible_if",
+ "clippy::collapsible_match",
+ "clippy::comparison_chain",
+ "clippy::comparison_to_empty",
+ "clippy::declare_interior_mutable_const",
+ "clippy::double_must_use",
+ "clippy::double_neg",
+ "clippy::duplicate_underscore_argument",
+ "clippy::enum_variant_names",
+ "clippy::excessive_precision",
+ "clippy::field_reassign_with_default",
+ "clippy::fn_to_numeric_cast",
+ "clippy::fn_to_numeric_cast_with_truncation",
+ "clippy::for_kv_map",
+ "clippy::from_over_into",
+ "clippy::from_str_radix_10",
+ "clippy::inconsistent_digit_grouping",
+ "clippy::infallible_destructuring_match",
+ "clippy::inherent_to_string",
+ "clippy::into_iter_on_ref",
+ "clippy::iter_cloned_collect",
+ "clippy::iter_next_slice",
+ "clippy::iter_nth_zero",
+ "clippy::iter_skip_next",
+ "clippy::just_underscores_and_digits",
+ "clippy::len_without_is_empty",
+ "clippy::len_zero",
+ "clippy::let_and_return",
+ "clippy::main_recursion",
+ "clippy::manual_async_fn",
+ "clippy::manual_map",
+ "clippy::manual_non_exhaustive",
+ "clippy::manual_range_contains",
+ "clippy::manual_saturating_arithmetic",
+ "clippy::map_clone",
+ "clippy::map_collect_result_unit",
+ "clippy::match_like_matches_macro",
+ "clippy::match_overlapping_arm",
+ "clippy::match_ref_pats",
+ "clippy::match_result_ok",
+ "clippy::mem_replace_option_with_none",
+ "clippy::mem_replace_with_default",
+ "clippy::missing_safety_doc",
+ "clippy::mixed_case_hex_literals",
+ "clippy::module_inception",
+ "clippy::must_use_unit",
+ "clippy::mut_mutex_lock",
+ "clippy::needless_borrow",
+ "clippy::needless_doctest_main",
+ "clippy::needless_late_init",
+ "clippy::needless_range_loop",
+ "clippy::needless_return",
+ "clippy::neg_multiply",
+ "clippy::new_ret_no_self",
+ "clippy::new_without_default",
+ "clippy::ok_expect",
+ "clippy::op_ref",
+ "clippy::option_map_or_none",
+ "clippy::print_literal",
+ "clippy::print_with_newline",
+ "clippy::println_empty_string",
+ "clippy::ptr_arg",
+ "clippy::ptr_eq",
+ "clippy::question_mark",
+ "clippy::redundant_closure",
+ "clippy::redundant_field_names",
+ "clippy::redundant_pattern",
+ "clippy::redundant_pattern_matching",
+ "clippy::redundant_static_lifetimes",
+ "clippy::result_map_or_into_option",
+ "clippy::result_unit_err",
+ "clippy::same_item_push",
+ "clippy::self_named_constructors",
+ "clippy::should_implement_trait",
+ "clippy::single_char_add_str",
+ "clippy::single_component_path_imports",
+ "clippy::single_match",
+ "clippy::string_extend_chars",
+ "clippy::tabs_in_doc_comments",
+ "clippy::to_digit_is_some",
+ "clippy::toplevel_ref_arg",
+ "clippy::try_err",
+ "clippy::unnecessary_fold",
+ "clippy::unnecessary_lazy_evaluations",
+ "clippy::unnecessary_mut_passed",
+ "clippy::unsafe_removed_from_name",
+ "clippy::unused_unit",
+ "clippy::unusual_byte_groupings",
+ "clippy::unwrap_or_else_default",
+ "clippy::upper_case_acronyms",
+ "clippy::while_let_on_iterator",
+ "clippy::write_literal",
+ "clippy::write_with_newline",
+ "clippy::writeln_empty_string",
+ "clippy::wrong_self_convention",
+ "clippy::zero_ptr",
+ ],
+ },
+ LintGroup {
+ lint: Lint {
+ label: "clippy::suspicious",
+ description: r##"lint group for: clippy::blanket_clippy_restriction_lints, clippy::empty_loop, clippy::eval_order_dependence, clippy::float_equality_without_abs, clippy::for_loops_over_fallibles, clippy::misrefactored_assign_op, clippy::mut_range_bound, clippy::mutable_key_type, clippy::octal_escapes, clippy::return_self_not_must_use, clippy::suspicious_arithmetic_impl, clippy::suspicious_assignment_formatting, clippy::suspicious_else_formatting, clippy::suspicious_map, clippy::suspicious_op_assign_impl, clippy::suspicious_unary_op_formatting"##,
+ },
+ children: &[
+ "clippy::blanket_clippy_restriction_lints",
+ "clippy::empty_loop",
+ "clippy::eval_order_dependence",
+ "clippy::float_equality_without_abs",
+ "clippy::for_loops_over_fallibles",
+ "clippy::misrefactored_assign_op",
+ "clippy::mut_range_bound",
+ "clippy::mutable_key_type",
+ "clippy::octal_escapes",
+ "clippy::return_self_not_must_use",
+ "clippy::suspicious_arithmetic_impl",
+ "clippy::suspicious_assignment_formatting",
+ "clippy::suspicious_else_formatting",
+ "clippy::suspicious_map",
+ "clippy::suspicious_op_assign_impl",
+ "clippy::suspicious_unary_op_formatting",
+ ],
+ },
+];
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/helpers.rs b/src/tools/rust-analyzer/crates/ide-db/src/helpers.rs
new file mode 100644
index 000000000..6e56efe34
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/helpers.rs
@@ -0,0 +1,105 @@
+//! Random assortment of ide helpers for high-level ide features that don't fit in any other module.
+
+use std::collections::VecDeque;
+
+use base_db::FileId;
+use hir::{ItemInNs, ModuleDef, Name, Semantics};
+use syntax::{
+ ast::{self, make},
+ AstToken, SyntaxKind, SyntaxToken, TokenAtOffset,
+};
+
+use crate::{defs::Definition, generated, RootDatabase};
+
+pub fn item_name(db: &RootDatabase, item: ItemInNs) -> Option<Name> {
+ match item {
+ ItemInNs::Types(module_def_id) => module_def_id.name(db),
+ ItemInNs::Values(module_def_id) => module_def_id.name(db),
+ ItemInNs::Macros(macro_def_id) => Some(macro_def_id.name(db)),
+ }
+}
+
+/// Picks the token with the highest rank returned by the passed in function.
+pub fn pick_best_token(
+ tokens: TokenAtOffset<SyntaxToken>,
+ f: impl Fn(SyntaxKind) -> usize,
+) -> Option<SyntaxToken> {
+ tokens.max_by_key(move |t| f(t.kind()))
+}
+pub fn pick_token<T: AstToken>(mut tokens: TokenAtOffset<SyntaxToken>) -> Option<T> {
+ tokens.find_map(T::cast)
+}
+
+/// Converts the mod path struct into its ast representation.
+pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path {
+ let _p = profile::span("mod_path_to_ast");
+
+ let mut segments = Vec::new();
+ let mut is_abs = false;
+ match path.kind {
+ hir::PathKind::Plain => {}
+ hir::PathKind::Super(0) => segments.push(make::path_segment_self()),
+ hir::PathKind::Super(n) => segments.extend((0..n).map(|_| make::path_segment_super())),
+ hir::PathKind::DollarCrate(_) | hir::PathKind::Crate => {
+ segments.push(make::path_segment_crate())
+ }
+ hir::PathKind::Abs => is_abs = true,
+ }
+
+ segments.extend(
+ path.segments()
+ .iter()
+ .map(|segment| make::path_segment(make::name_ref(&segment.to_smol_str()))),
+ );
+ make::path_from_segments(segments, is_abs)
+}
+
+/// Iterates all `ModuleDef`s and `Impl` blocks of the given file.
+pub fn visit_file_defs(
+ sema: &Semantics<'_, RootDatabase>,
+ file_id: FileId,
+ cb: &mut dyn FnMut(Definition),
+) {
+ let db = sema.db;
+ let module = match sema.to_module_def(file_id) {
+ Some(it) => it,
+ None => return,
+ };
+ let mut defs: VecDeque<_> = module.declarations(db).into();
+ while let Some(def) = defs.pop_front() {
+ if let ModuleDef::Module(submodule) = def {
+ if let hir::ModuleSource::Module(_) = submodule.definition_source(db).value {
+ defs.extend(submodule.declarations(db));
+ submodule.impl_defs(db).into_iter().for_each(|impl_| cb(impl_.into()));
+ }
+ }
+ cb(def.into());
+ }
+ module.impl_defs(db).into_iter().for_each(|impl_| cb(impl_.into()));
+
+ let is_root = module.is_crate_root(db);
+ module
+ .legacy_macros(db)
+ .into_iter()
+ // don't show legacy macros declared in the crate-root that were already covered in declarations earlier
+ .filter(|it| !(is_root && it.is_macro_export(db)))
+ .for_each(|mac| cb(mac.into()));
+}
+
+/// Checks if the given lint is equal or is contained by the other lint which may or may not be a group.
+pub fn lint_eq_or_in_group(lint: &str, lint_is: &str) -> bool {
+ if lint == lint_is {
+ return true;
+ }
+
+ if let Some(group) = generated::lints::DEFAULT_LINT_GROUPS
+ .iter()
+ .chain(generated::lints::CLIPPY_LINT_GROUPS.iter())
+ .chain(generated::lints::RUSTDOC_LINT_GROUPS.iter())
+ .find(|&check| check.lint.label == lint_is)
+ {
+ group.children.contains(&lint)
+ } else {
+ false
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs b/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs
new file mode 100644
index 000000000..26ef86155
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs
@@ -0,0 +1,674 @@
+//! Look up accessible paths for items.
+use hir::{
+ AsAssocItem, AssocItem, AssocItemContainer, Crate, ItemInNs, ModPath, Module, ModuleDef,
+ PathResolution, PrefixKind, ScopeDef, Semantics, SemanticsScope, Type,
+};
+use itertools::Itertools;
+use rustc_hash::FxHashSet;
+use syntax::{
+ ast::{self, HasName},
+ utils::path_to_string_stripping_turbo_fish,
+ AstNode, SyntaxNode,
+};
+
+use crate::{
+ helpers::item_name,
+ items_locator::{self, AssocItemSearch, DEFAULT_QUERY_SEARCH_LIMIT},
+ RootDatabase,
+};
+
+/// A candidate for import, derived during various IDE activities:
+/// * completion with imports on the fly proposals
+/// * completion edit resolve requests
+/// * assists
+/// * etc.
+#[derive(Debug)]
+pub enum ImportCandidate {
+ /// A path, qualified (`std::collections::HashMap`) or not (`HashMap`).
+ Path(PathImportCandidate),
+ /// A trait associated function (with no self parameter) or an associated constant.
+ /// For 'test_mod::TestEnum::test_function', `ty` is the `test_mod::TestEnum` expression type
+ /// and `name` is the `test_function`
+ TraitAssocItem(TraitImportCandidate),
+ /// A trait method with self parameter.
+ /// For 'test_enum.test_method()', `ty` is the `test_enum` expression type
+ /// and `name` is the `test_method`
+ TraitMethod(TraitImportCandidate),
+}
+
+/// A trait import needed for a given associated item access.
+/// For `some::path::SomeStruct::ASSOC_`, contains the
+/// type of `some::path::SomeStruct` and `ASSOC_` as the item name.
+#[derive(Debug)]
+pub struct TraitImportCandidate {
+ /// A type of the item that has the associated item accessed at.
+ pub receiver_ty: Type,
+ /// The associated item name that the trait to import should contain.
+ pub assoc_item_name: NameToImport,
+}
+
+/// Path import for a given name, qualified or not.
+#[derive(Debug)]
+pub struct PathImportCandidate {
+ /// Optional qualifier before name.
+ pub qualifier: Option<FirstSegmentUnresolved>,
+ /// The name the item (struct, trait, enum, etc.) should have.
+ pub name: NameToImport,
+}
+
+/// A qualifier that has a first segment and it's unresolved.
+#[derive(Debug)]
+pub struct FirstSegmentUnresolved {
+ fist_segment: ast::NameRef,
+ full_qualifier: ast::Path,
+}
+
+/// A name that will be used during item lookups.
+#[derive(Debug, Clone)]
+pub enum NameToImport {
+ /// Requires items with names that exactly match the given string, bool indicates case-sensitivity.
+ Exact(String, bool),
+ /// Requires items with names that case-insensitively contain all letters from the string,
+ /// in the same order, but not necessary adjacent.
+ Fuzzy(String),
+}
+
+impl NameToImport {
+ pub fn exact_case_sensitive(s: String) -> NameToImport {
+ NameToImport::Exact(s, true)
+ }
+}
+
+impl NameToImport {
+ pub fn text(&self) -> &str {
+ match self {
+ NameToImport::Exact(text, _) => text.as_str(),
+ NameToImport::Fuzzy(text) => text.as_str(),
+ }
+ }
+}
+
+/// A struct to find imports in the project, given a certain name (or its part) and the context.
+#[derive(Debug)]
+pub struct ImportAssets {
+ import_candidate: ImportCandidate,
+ candidate_node: SyntaxNode,
+ module_with_candidate: Module,
+}
+
+impl ImportAssets {
+ pub fn for_method_call(
+ method_call: &ast::MethodCallExpr,
+ sema: &Semantics<'_, RootDatabase>,
+ ) -> Option<Self> {
+ let candidate_node = method_call.syntax().clone();
+ Some(Self {
+ import_candidate: ImportCandidate::for_method_call(sema, method_call)?,
+ module_with_candidate: sema.scope(&candidate_node)?.module(),
+ candidate_node,
+ })
+ }
+
+ pub fn for_exact_path(
+ fully_qualified_path: &ast::Path,
+ sema: &Semantics<'_, RootDatabase>,
+ ) -> Option<Self> {
+ let candidate_node = fully_qualified_path.syntax().clone();
+ if let Some(use_tree) = candidate_node.ancestors().find_map(ast::UseTree::cast) {
+ // Path is inside a use tree, then only continue if it is the first segment of a use statement.
+ if use_tree.syntax().parent().and_then(ast::Use::cast).is_none()
+ || fully_qualified_path.qualifier().is_some()
+ {
+ return None;
+ }
+ }
+ Some(Self {
+ import_candidate: ImportCandidate::for_regular_path(sema, fully_qualified_path)?,
+ module_with_candidate: sema.scope(&candidate_node)?.module(),
+ candidate_node,
+ })
+ }
+
+ pub fn for_ident_pat(sema: &Semantics<'_, RootDatabase>, pat: &ast::IdentPat) -> Option<Self> {
+ if !pat.is_simple_ident() {
+ return None;
+ }
+ let name = pat.name()?;
+ let candidate_node = pat.syntax().clone();
+ Some(Self {
+ import_candidate: ImportCandidate::for_name(sema, &name)?,
+ module_with_candidate: sema.scope(&candidate_node)?.module(),
+ candidate_node,
+ })
+ }
+
+ pub fn for_fuzzy_path(
+ module_with_candidate: Module,
+ qualifier: Option<ast::Path>,
+ fuzzy_name: String,
+ sema: &Semantics<'_, RootDatabase>,
+ candidate_node: SyntaxNode,
+ ) -> Option<Self> {
+ Some(Self {
+ import_candidate: ImportCandidate::for_fuzzy_path(qualifier, fuzzy_name, sema)?,
+ module_with_candidate,
+ candidate_node,
+ })
+ }
+
+ pub fn for_fuzzy_method_call(
+ module_with_method_call: Module,
+ receiver_ty: Type,
+ fuzzy_method_name: String,
+ candidate_node: SyntaxNode,
+ ) -> Option<Self> {
+ Some(Self {
+ import_candidate: ImportCandidate::TraitMethod(TraitImportCandidate {
+ receiver_ty,
+ assoc_item_name: NameToImport::Fuzzy(fuzzy_method_name),
+ }),
+ module_with_candidate: module_with_method_call,
+ candidate_node,
+ })
+ }
+}
+
+/// An import (not necessary the only one) that corresponds a certain given [`PathImportCandidate`].
+/// (the structure is not entirely correct, since there can be situations requiring two imports, see FIXME below for the details)
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct LocatedImport {
+ /// The path to use in the `use` statement for a given candidate to be imported.
+ pub import_path: ModPath,
+ /// An item that will be imported with the import path given.
+ pub item_to_import: ItemInNs,
+ /// The path import candidate, resolved.
+ ///
+ /// Not necessary matches the import:
+ /// For any associated constant from the trait, we try to access as `some::path::SomeStruct::ASSOC_`
+ /// the original item is the associated constant, but the import has to be a trait that
+ /// defines this constant.
+ pub original_item: ItemInNs,
+ /// A path of the original item.
+ pub original_path: Option<ModPath>,
+}
+
+impl LocatedImport {
+ pub fn new(
+ import_path: ModPath,
+ item_to_import: ItemInNs,
+ original_item: ItemInNs,
+ original_path: Option<ModPath>,
+ ) -> Self {
+ Self { import_path, item_to_import, original_item, original_path }
+ }
+}
+
+impl ImportAssets {
+ pub fn import_candidate(&self) -> &ImportCandidate {
+ &self.import_candidate
+ }
+
+ pub fn search_for_imports(
+ &self,
+ sema: &Semantics<'_, RootDatabase>,
+ prefix_kind: PrefixKind,
+ ) -> Vec<LocatedImport> {
+ let _p = profile::span("import_assets::search_for_imports");
+ self.search_for(sema, Some(prefix_kind))
+ }
+
+ /// This may return non-absolute paths if a part of the returned path is already imported into scope.
+ pub fn search_for_relative_paths(
+ &self,
+ sema: &Semantics<'_, RootDatabase>,
+ ) -> Vec<LocatedImport> {
+ let _p = profile::span("import_assets::search_for_relative_paths");
+ self.search_for(sema, None)
+ }
+
+ pub fn path_fuzzy_name_to_exact(&mut self, case_sensitive: bool) {
+ if let ImportCandidate::Path(PathImportCandidate { name: to_import, .. }) =
+ &mut self.import_candidate
+ {
+ let name = match to_import {
+ NameToImport::Fuzzy(name) => std::mem::take(name),
+ _ => return,
+ };
+ *to_import = NameToImport::Exact(name, case_sensitive);
+ }
+ }
+
+ fn search_for(
+ &self,
+ sema: &Semantics<'_, RootDatabase>,
+ prefixed: Option<PrefixKind>,
+ ) -> Vec<LocatedImport> {
+ let _p = profile::span("import_assets::search_for");
+
+ let scope_definitions = self.scope_definitions(sema);
+ let mod_path = |item| {
+ get_mod_path(
+ sema.db,
+ item_for_path_search(sema.db, item)?,
+ &self.module_with_candidate,
+ prefixed,
+ )
+ };
+
+ let krate = self.module_with_candidate.krate();
+ let scope = match sema.scope(&self.candidate_node) {
+ Some(it) => it,
+ None => return Vec::new(),
+ };
+
+ match &self.import_candidate {
+ ImportCandidate::Path(path_candidate) => {
+ path_applicable_imports(sema, krate, path_candidate, mod_path)
+ }
+ ImportCandidate::TraitAssocItem(trait_candidate) => {
+ trait_applicable_items(sema, krate, &scope, trait_candidate, true, mod_path)
+ }
+ ImportCandidate::TraitMethod(trait_candidate) => {
+ trait_applicable_items(sema, krate, &scope, trait_candidate, false, mod_path)
+ }
+ }
+ .into_iter()
+ .filter(|import| import.import_path.len() > 1)
+ .filter(|import| !scope_definitions.contains(&ScopeDef::from(import.item_to_import)))
+ .sorted_by(|a, b| a.import_path.cmp(&b.import_path))
+ .collect()
+ }
+
+ fn scope_definitions(&self, sema: &Semantics<'_, RootDatabase>) -> FxHashSet<ScopeDef> {
+ let _p = profile::span("import_assets::scope_definitions");
+ let mut scope_definitions = FxHashSet::default();
+ if let Some(scope) = sema.scope(&self.candidate_node) {
+ scope.process_all_names(&mut |_, scope_def| {
+ scope_definitions.insert(scope_def);
+ });
+ }
+ scope_definitions
+ }
+}
+
+fn path_applicable_imports(
+ sema: &Semantics<'_, RootDatabase>,
+ current_crate: Crate,
+ path_candidate: &PathImportCandidate,
+ mod_path: impl Fn(ItemInNs) -> Option<ModPath> + Copy,
+) -> FxHashSet<LocatedImport> {
+ let _p = profile::span("import_assets::path_applicable_imports");
+
+ match &path_candidate.qualifier {
+ None => {
+ items_locator::items_with_name(
+ sema,
+ current_crate,
+ path_candidate.name.clone(),
+ // FIXME: we could look up assoc items by the input and propose those in completion,
+ // but that requires more preparation first:
+ // * store non-trait assoc items in import_map to fully enable this lookup
+ // * ensure that does not degrade the performance (benchmark it)
+ // * write more logic to check for corresponding trait presence requirement (we're unable to flyimport multiple item right now)
+ // * improve the associated completion item matching and/or scoring to ensure no noisy completions appear
+ //
+ // see also an ignored test under FIXME comment in the qualify_path.rs module
+ AssocItemSearch::Exclude,
+ Some(DEFAULT_QUERY_SEARCH_LIMIT.inner()),
+ )
+ .filter_map(|item| {
+ let mod_path = mod_path(item)?;
+ Some(LocatedImport::new(mod_path.clone(), item, item, Some(mod_path)))
+ })
+ .collect()
+ }
+ Some(first_segment_unresolved) => {
+ let unresolved_qualifier =
+ path_to_string_stripping_turbo_fish(&first_segment_unresolved.full_qualifier);
+ let unresolved_first_segment = first_segment_unresolved.fist_segment.text();
+ items_locator::items_with_name(
+ sema,
+ current_crate,
+ path_candidate.name.clone(),
+ AssocItemSearch::Include,
+ Some(DEFAULT_QUERY_SEARCH_LIMIT.inner()),
+ )
+ .filter_map(|item| {
+ import_for_item(
+ sema.db,
+ mod_path,
+ &unresolved_first_segment,
+ &unresolved_qualifier,
+ item,
+ )
+ })
+ .collect()
+ }
+ }
+}
+
+fn import_for_item(
+ db: &RootDatabase,
+ mod_path: impl Fn(ItemInNs) -> Option<ModPath>,
+ unresolved_first_segment: &str,
+ unresolved_qualifier: &str,
+ original_item: ItemInNs,
+) -> Option<LocatedImport> {
+ let _p = profile::span("import_assets::import_for_item");
+
+ let original_item_candidate = item_for_path_search(db, original_item)?;
+ let import_path_candidate = mod_path(original_item_candidate)?;
+ let import_path_string = import_path_candidate.to_string();
+
+ let expected_import_end = if item_as_assoc(db, original_item).is_some() {
+ unresolved_qualifier.to_string()
+ } else {
+ format!("{}::{}", unresolved_qualifier, item_name(db, original_item)?)
+ };
+ if !import_path_string.contains(unresolved_first_segment)
+ || !import_path_string.ends_with(&expected_import_end)
+ {
+ return None;
+ }
+
+ let segment_import =
+ find_import_for_segment(db, original_item_candidate, unresolved_first_segment)?;
+ let trait_item_to_import = item_as_assoc(db, original_item)
+ .and_then(|assoc| assoc.containing_trait(db))
+ .map(|trait_| ItemInNs::from(ModuleDef::from(trait_)));
+ Some(match (segment_import == original_item_candidate, trait_item_to_import) {
+ (true, Some(_)) => {
+ // FIXME we should be able to import both the trait and the segment,
+ // but it's unclear what to do with overlapping edits (merge imports?)
+ // especially in case of lazy completion edit resolutions.
+ return None;
+ }
+ (false, Some(trait_to_import)) => LocatedImport::new(
+ mod_path(trait_to_import)?,
+ trait_to_import,
+ original_item,
+ mod_path(original_item),
+ ),
+ (true, None) => LocatedImport::new(
+ import_path_candidate,
+ original_item_candidate,
+ original_item,
+ mod_path(original_item),
+ ),
+ (false, None) => LocatedImport::new(
+ mod_path(segment_import)?,
+ segment_import,
+ original_item,
+ mod_path(original_item),
+ ),
+ })
+}
+
+pub fn item_for_path_search(db: &RootDatabase, item: ItemInNs) -> Option<ItemInNs> {
+ Some(match item {
+ ItemInNs::Types(_) | ItemInNs::Values(_) => match item_as_assoc(db, item) {
+ Some(assoc_item) => match assoc_item.container(db) {
+ AssocItemContainer::Trait(trait_) => ItemInNs::from(ModuleDef::from(trait_)),
+ AssocItemContainer::Impl(impl_) => {
+ ItemInNs::from(ModuleDef::from(impl_.self_ty(db).as_adt()?))
+ }
+ },
+ None => item,
+ },
+ ItemInNs::Macros(_) => item,
+ })
+}
+
+fn find_import_for_segment(
+ db: &RootDatabase,
+ original_item: ItemInNs,
+ unresolved_first_segment: &str,
+) -> Option<ItemInNs> {
+ let segment_is_name = item_name(db, original_item)
+ .map(|name| name.to_smol_str() == unresolved_first_segment)
+ .unwrap_or(false);
+
+ Some(if segment_is_name {
+ original_item
+ } else {
+ let matching_module =
+ module_with_segment_name(db, unresolved_first_segment, original_item)?;
+ ItemInNs::from(ModuleDef::from(matching_module))
+ })
+}
+
+fn module_with_segment_name(
+ db: &RootDatabase,
+ segment_name: &str,
+ candidate: ItemInNs,
+) -> Option<Module> {
+ let mut current_module = match candidate {
+ ItemInNs::Types(module_def_id) => module_def_id.module(db),
+ ItemInNs::Values(module_def_id) => module_def_id.module(db),
+ ItemInNs::Macros(macro_def_id) => ModuleDef::from(macro_def_id).module(db),
+ };
+ while let Some(module) = current_module {
+ if let Some(module_name) = module.name(db) {
+ if module_name.to_smol_str() == segment_name {
+ return Some(module);
+ }
+ }
+ current_module = module.parent(db);
+ }
+ None
+}
+
+fn trait_applicable_items(
+ sema: &Semantics<'_, RootDatabase>,
+ current_crate: Crate,
+ scope: &SemanticsScope<'_>,
+ trait_candidate: &TraitImportCandidate,
+ trait_assoc_item: bool,
+ mod_path: impl Fn(ItemInNs) -> Option<ModPath>,
+) -> FxHashSet<LocatedImport> {
+ let _p = profile::span("import_assets::trait_applicable_items");
+
+ let db = sema.db;
+
+ let inherent_traits = trait_candidate.receiver_ty.applicable_inherent_traits(db);
+ let env_traits = trait_candidate.receiver_ty.env_traits(db);
+ let related_traits = inherent_traits.chain(env_traits).collect::<FxHashSet<_>>();
+
+ let mut required_assoc_items = FxHashSet::default();
+ let trait_candidates = items_locator::items_with_name(
+ sema,
+ current_crate,
+ trait_candidate.assoc_item_name.clone(),
+ AssocItemSearch::AssocItemsOnly,
+ Some(DEFAULT_QUERY_SEARCH_LIMIT.inner()),
+ )
+ .filter_map(|input| item_as_assoc(db, input))
+ .filter_map(|assoc| {
+ let assoc_item_trait = assoc.containing_trait(db)?;
+ if related_traits.contains(&assoc_item_trait) {
+ None
+ } else {
+ required_assoc_items.insert(assoc);
+ Some(assoc_item_trait.into())
+ }
+ })
+ .collect();
+
+ let mut located_imports = FxHashSet::default();
+
+ if trait_assoc_item {
+ trait_candidate.receiver_ty.iterate_path_candidates(
+ db,
+ scope,
+ &trait_candidates,
+ None,
+ None,
+ |assoc| {
+ if required_assoc_items.contains(&assoc) {
+ if let AssocItem::Function(f) = assoc {
+ if f.self_param(db).is_some() {
+ return None;
+ }
+ }
+ let located_trait = assoc.containing_trait(db)?;
+ let trait_item = ItemInNs::from(ModuleDef::from(located_trait));
+ let original_item = assoc_to_item(assoc);
+ located_imports.insert(LocatedImport::new(
+ mod_path(trait_item)?,
+ trait_item,
+ original_item,
+ mod_path(original_item),
+ ));
+ }
+ None::<()>
+ },
+ )
+ } else {
+ trait_candidate.receiver_ty.iterate_method_candidates(
+ db,
+ scope,
+ &trait_candidates,
+ None,
+ None,
+ |function| {
+ let assoc = function.as_assoc_item(db)?;
+ if required_assoc_items.contains(&assoc) {
+ let located_trait = assoc.containing_trait(db)?;
+ let trait_item = ItemInNs::from(ModuleDef::from(located_trait));
+ let original_item = assoc_to_item(assoc);
+ located_imports.insert(LocatedImport::new(
+ mod_path(trait_item)?,
+ trait_item,
+ original_item,
+ mod_path(original_item),
+ ));
+ }
+ None::<()>
+ },
+ )
+ };
+
+ located_imports
+}
+
+fn assoc_to_item(assoc: AssocItem) -> ItemInNs {
+ match assoc {
+ AssocItem::Function(f) => ItemInNs::from(ModuleDef::from(f)),
+ AssocItem::Const(c) => ItemInNs::from(ModuleDef::from(c)),
+ AssocItem::TypeAlias(t) => ItemInNs::from(ModuleDef::from(t)),
+ }
+}
+
+fn get_mod_path(
+ db: &RootDatabase,
+ item_to_search: ItemInNs,
+ module_with_candidate: &Module,
+ prefixed: Option<PrefixKind>,
+) -> Option<ModPath> {
+ if let Some(prefix_kind) = prefixed {
+ module_with_candidate.find_use_path_prefixed(db, item_to_search, prefix_kind)
+ } else {
+ module_with_candidate.find_use_path(db, item_to_search)
+ }
+}
+
+impl ImportCandidate {
+ fn for_method_call(
+ sema: &Semantics<'_, RootDatabase>,
+ method_call: &ast::MethodCallExpr,
+ ) -> Option<Self> {
+ match sema.resolve_method_call(method_call) {
+ Some(_) => None,
+ None => Some(Self::TraitMethod(TraitImportCandidate {
+ receiver_ty: sema.type_of_expr(&method_call.receiver()?)?.adjusted(),
+ assoc_item_name: NameToImport::exact_case_sensitive(
+ method_call.name_ref()?.to_string(),
+ ),
+ })),
+ }
+ }
+
+ fn for_regular_path(sema: &Semantics<'_, RootDatabase>, path: &ast::Path) -> Option<Self> {
+ if sema.resolve_path(path).is_some() {
+ return None;
+ }
+ path_import_candidate(
+ sema,
+ path.qualifier(),
+ NameToImport::exact_case_sensitive(path.segment()?.name_ref()?.to_string()),
+ )
+ }
+
+ fn for_name(sema: &Semantics<'_, RootDatabase>, name: &ast::Name) -> Option<Self> {
+ if sema
+ .scope(name.syntax())?
+ .speculative_resolve(&ast::make::ext::ident_path(&name.text()))
+ .is_some()
+ {
+ return None;
+ }
+ Some(ImportCandidate::Path(PathImportCandidate {
+ qualifier: None,
+ name: NameToImport::exact_case_sensitive(name.to_string()),
+ }))
+ }
+
+ fn for_fuzzy_path(
+ qualifier: Option<ast::Path>,
+ fuzzy_name: String,
+ sema: &Semantics<'_, RootDatabase>,
+ ) -> Option<Self> {
+ path_import_candidate(sema, qualifier, NameToImport::Fuzzy(fuzzy_name))
+ }
+}
+
+fn path_import_candidate(
+ sema: &Semantics<'_, RootDatabase>,
+ qualifier: Option<ast::Path>,
+ name: NameToImport,
+) -> Option<ImportCandidate> {
+ Some(match qualifier {
+ Some(qualifier) => match sema.resolve_path(&qualifier) {
+ None => {
+ let qualifier_start =
+ qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
+ let qualifier_start_path =
+ qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
+ if sema.resolve_path(&qualifier_start_path).is_none() {
+ ImportCandidate::Path(PathImportCandidate {
+ qualifier: Some(FirstSegmentUnresolved {
+ fist_segment: qualifier_start,
+ full_qualifier: qualifier,
+ }),
+ name,
+ })
+ } else {
+ return None;
+ }
+ }
+ Some(PathResolution::Def(ModuleDef::Adt(assoc_item_path))) => {
+ ImportCandidate::TraitAssocItem(TraitImportCandidate {
+ receiver_ty: assoc_item_path.ty(sema.db),
+ assoc_item_name: name,
+ })
+ }
+ Some(PathResolution::Def(ModuleDef::TypeAlias(alias))) => {
+ let ty = alias.ty(sema.db);
+ if ty.as_adt().is_some() {
+ ImportCandidate::TraitAssocItem(TraitImportCandidate {
+ receiver_ty: ty,
+ assoc_item_name: name,
+ })
+ } else {
+ return None;
+ }
+ }
+ Some(_) => return None,
+ },
+ None => ImportCandidate::Path(PathImportCandidate { qualifier: None, name }),
+ })
+}
+
+fn item_as_assoc(db: &RootDatabase, item: ItemInNs) -> Option<AssocItem> {
+ item.as_module_def().and_then(|module_def| module_def.as_assoc_item(db))
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs b/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs
new file mode 100644
index 000000000..c14182279
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs
@@ -0,0 +1,446 @@
+//! Handle syntactic aspects of inserting a new `use` item.
+#[cfg(test)]
+mod tests;
+
+use std::cmp::Ordering;
+
+use hir::Semantics;
+use syntax::{
+ algo,
+ ast::{self, make, AstNode, HasAttrs, HasModuleItem, HasVisibility, PathSegmentKind},
+ ted, Direction, NodeOrToken, SyntaxKind, SyntaxNode,
+};
+
+use crate::{
+ imports::merge_imports::{
+ common_prefix, eq_attrs, eq_visibility, try_merge_imports, use_tree_path_cmp, MergeBehavior,
+ },
+ RootDatabase,
+};
+
+pub use hir::PrefixKind;
+
+/// How imports should be grouped into use statements.
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum ImportGranularity {
+ /// Do not change the granularity of any imports and preserve the original structure written by the developer.
+ Preserve,
+ /// Merge imports from the same crate into a single use statement.
+ Crate,
+ /// Merge imports from the same module into a single use statement.
+ Module,
+ /// Flatten imports so that each has its own use statement.
+ Item,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct InsertUseConfig {
+ pub granularity: ImportGranularity,
+ pub enforce_granularity: bool,
+ pub prefix_kind: PrefixKind,
+ pub group: bool,
+ pub skip_glob_imports: bool,
+}
+
+#[derive(Debug, Clone)]
+pub enum ImportScope {
+ File(ast::SourceFile),
+ Module(ast::ItemList),
+ Block(ast::StmtList),
+}
+
+impl ImportScope {
+ // FIXME: Remove this?
+ #[cfg(test)]
+ fn from(syntax: SyntaxNode) -> Option<Self> {
+ use syntax::match_ast;
+ fn contains_cfg_attr(attrs: &dyn HasAttrs) -> bool {
+ attrs
+ .attrs()
+ .any(|attr| attr.as_simple_call().map_or(false, |(ident, _)| ident == "cfg"))
+ }
+ match_ast! {
+ match syntax {
+ ast::Module(module) => module.item_list().map(ImportScope::Module),
+ ast::SourceFile(file) => Some(ImportScope::File(file)),
+ ast::Fn(func) => contains_cfg_attr(&func).then(|| func.body().and_then(|it| it.stmt_list().map(ImportScope::Block))).flatten(),
+ ast::Const(konst) => contains_cfg_attr(&konst).then(|| match konst.body()? {
+ ast::Expr::BlockExpr(block) => Some(block),
+ _ => None,
+ }).flatten().and_then(|it| it.stmt_list().map(ImportScope::Block)),
+ ast::Static(statik) => contains_cfg_attr(&statik).then(|| match statik.body()? {
+ ast::Expr::BlockExpr(block) => Some(block),
+ _ => None,
+ }).flatten().and_then(|it| it.stmt_list().map(ImportScope::Block)),
+ _ => None,
+
+ }
+ }
+ }
+
+ /// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
+ /// Returns the original source node inside attributes.
+ pub fn find_insert_use_container(
+ position: &SyntaxNode,
+ sema: &Semantics<'_, RootDatabase>,
+ ) -> Option<Self> {
+ fn contains_cfg_attr(attrs: &dyn HasAttrs) -> bool {
+ attrs
+ .attrs()
+ .any(|attr| attr.as_simple_call().map_or(false, |(ident, _)| ident == "cfg"))
+ }
+
+ // Walk up the ancestor tree searching for a suitable node to do insertions on
+ // with special handling on cfg-gated items, in which case we want to insert imports locally
+ // or FIXME: annotate inserted imports with the same cfg
+ for syntax in sema.ancestors_with_macros(position.clone()) {
+ if let Some(file) = ast::SourceFile::cast(syntax.clone()) {
+ return Some(ImportScope::File(file));
+ } else if let Some(item) = ast::Item::cast(syntax) {
+ return match item {
+ ast::Item::Const(konst) if contains_cfg_attr(&konst) => {
+ // FIXME: Instead of bailing out with None, we should note down that
+ // this import needs an attribute added
+ match sema.original_ast_node(konst)?.body()? {
+ ast::Expr::BlockExpr(block) => block,
+ _ => return None,
+ }
+ .stmt_list()
+ .map(ImportScope::Block)
+ }
+ ast::Item::Fn(func) if contains_cfg_attr(&func) => {
+ // FIXME: Instead of bailing out with None, we should note down that
+ // this import needs an attribute added
+ sema.original_ast_node(func)?.body()?.stmt_list().map(ImportScope::Block)
+ }
+ ast::Item::Static(statik) if contains_cfg_attr(&statik) => {
+ // FIXME: Instead of bailing out with None, we should note down that
+ // this import needs an attribute added
+ match sema.original_ast_node(statik)?.body()? {
+ ast::Expr::BlockExpr(block) => block,
+ _ => return None,
+ }
+ .stmt_list()
+ .map(ImportScope::Block)
+ }
+ ast::Item::Module(module) => {
+ // early return is important here, if we can't find the original module
+ // in the input there is no way for us to insert an import anywhere.
+ sema.original_ast_node(module)?.item_list().map(ImportScope::Module)
+ }
+ _ => continue,
+ };
+ }
+ }
+ None
+ }
+
+ pub fn as_syntax_node(&self) -> &SyntaxNode {
+ match self {
+ ImportScope::File(file) => file.syntax(),
+ ImportScope::Module(item_list) => item_list.syntax(),
+ ImportScope::Block(block) => block.syntax(),
+ }
+ }
+
+ pub fn clone_for_update(&self) -> Self {
+ match self {
+ ImportScope::File(file) => ImportScope::File(file.clone_for_update()),
+ ImportScope::Module(item_list) => ImportScope::Module(item_list.clone_for_update()),
+ ImportScope::Block(block) => ImportScope::Block(block.clone_for_update()),
+ }
+ }
+}
+
+/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
+pub fn insert_use(scope: &ImportScope, path: ast::Path, cfg: &InsertUseConfig) {
+ let _p = profile::span("insert_use");
+ let mut mb = match cfg.granularity {
+ ImportGranularity::Crate => Some(MergeBehavior::Crate),
+ ImportGranularity::Module => Some(MergeBehavior::Module),
+ ImportGranularity::Item | ImportGranularity::Preserve => None,
+ };
+ if !cfg.enforce_granularity {
+ let file_granularity = guess_granularity_from_scope(scope);
+ mb = match file_granularity {
+ ImportGranularityGuess::Unknown => mb,
+ ImportGranularityGuess::Item => None,
+ ImportGranularityGuess::Module => Some(MergeBehavior::Module),
+ ImportGranularityGuess::ModuleOrItem => mb.and(Some(MergeBehavior::Module)),
+ ImportGranularityGuess::Crate => Some(MergeBehavior::Crate),
+ ImportGranularityGuess::CrateOrModule => mb.or(Some(MergeBehavior::Crate)),
+ };
+ }
+
+ let use_item =
+ make::use_(None, make::use_tree(path.clone(), None, None, false)).clone_for_update();
+ // merge into existing imports if possible
+ if let Some(mb) = mb {
+ let filter = |it: &_| !(cfg.skip_glob_imports && ast::Use::is_simple_glob(it));
+ for existing_use in
+ scope.as_syntax_node().children().filter_map(ast::Use::cast).filter(filter)
+ {
+ if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
+ ted::replace(existing_use.syntax(), merged.syntax());
+ return;
+ }
+ }
+ }
+
+ // either we weren't allowed to merge or there is no import that fits the merge conditions
+ // so look for the place we have to insert to
+ insert_use_(scope, &path, cfg.group, use_item);
+}
+
+pub fn remove_path_if_in_use_stmt(path: &ast::Path) {
+ // FIXME: improve this
+ if path.parent_path().is_some() {
+ return;
+ }
+ if let Some(use_tree) = path.syntax().parent().and_then(ast::UseTree::cast) {
+ if use_tree.use_tree_list().is_some() || use_tree.star_token().is_some() {
+ return;
+ }
+ if let Some(use_) = use_tree.syntax().parent().and_then(ast::Use::cast) {
+ use_.remove();
+ return;
+ }
+ use_tree.remove();
+ }
+}
+
+#[derive(Eq, PartialEq, PartialOrd, Ord)]
+enum ImportGroup {
+ // the order here defines the order of new group inserts
+ Std,
+ ExternCrate,
+ ThisCrate,
+ ThisModule,
+ SuperModule,
+}
+
+impl ImportGroup {
+ fn new(path: &ast::Path) -> ImportGroup {
+ let default = ImportGroup::ExternCrate;
+
+ let first_segment = match path.first_segment() {
+ Some(it) => it,
+ None => return default,
+ };
+
+ let kind = first_segment.kind().unwrap_or(PathSegmentKind::SelfKw);
+ match kind {
+ PathSegmentKind::SelfKw => ImportGroup::ThisModule,
+ PathSegmentKind::SuperKw => ImportGroup::SuperModule,
+ PathSegmentKind::CrateKw => ImportGroup::ThisCrate,
+ PathSegmentKind::Name(name) => match name.text().as_str() {
+ "std" => ImportGroup::Std,
+ "core" => ImportGroup::Std,
+ _ => ImportGroup::ExternCrate,
+ },
+ // these aren't valid use paths, so fall back to something random
+ PathSegmentKind::SelfTypeKw => ImportGroup::ExternCrate,
+ PathSegmentKind::Type { .. } => ImportGroup::ExternCrate,
+ }
+ }
+}
+
+#[derive(PartialEq, PartialOrd, Debug, Clone, Copy)]
+enum ImportGranularityGuess {
+ Unknown,
+ Item,
+ Module,
+ ModuleOrItem,
+ Crate,
+ CrateOrModule,
+}
+
+fn guess_granularity_from_scope(scope: &ImportScope) -> ImportGranularityGuess {
+ // The idea is simple, just check each import as well as the import and its precedent together for
+ // whether they fulfill a granularity criteria.
+ let use_stmt = |item| match item {
+ ast::Item::Use(use_) => {
+ let use_tree = use_.use_tree()?;
+ Some((use_tree, use_.visibility(), use_.attrs()))
+ }
+ _ => None,
+ };
+ let mut use_stmts = match scope {
+ ImportScope::File(f) => f.items(),
+ ImportScope::Module(m) => m.items(),
+ ImportScope::Block(b) => b.items(),
+ }
+ .filter_map(use_stmt);
+ let mut res = ImportGranularityGuess::Unknown;
+ let (mut prev, mut prev_vis, mut prev_attrs) = match use_stmts.next() {
+ Some(it) => it,
+ None => return res,
+ };
+ loop {
+ if let Some(use_tree_list) = prev.use_tree_list() {
+ if use_tree_list.use_trees().any(|tree| tree.use_tree_list().is_some()) {
+ // Nested tree lists can only occur in crate style, or with no proper style being enforced in the file.
+ break ImportGranularityGuess::Crate;
+ } else {
+ // Could still be crate-style so continue looking.
+ res = ImportGranularityGuess::CrateOrModule;
+ }
+ }
+
+ let (curr, curr_vis, curr_attrs) = match use_stmts.next() {
+ Some(it) => it,
+ None => break res,
+ };
+ if eq_visibility(prev_vis, curr_vis.clone()) && eq_attrs(prev_attrs, curr_attrs.clone()) {
+ if let Some((prev_path, curr_path)) = prev.path().zip(curr.path()) {
+ if let Some((prev_prefix, _)) = common_prefix(&prev_path, &curr_path) {
+ if prev.use_tree_list().is_none() && curr.use_tree_list().is_none() {
+ let prefix_c = prev_prefix.qualifiers().count();
+ let curr_c = curr_path.qualifiers().count() - prefix_c;
+ let prev_c = prev_path.qualifiers().count() - prefix_c;
+ if curr_c == 1 && prev_c == 1 {
+ // Same prefix, only differing in the last segment and no use tree lists so this has to be of item style.
+ break ImportGranularityGuess::Item;
+ } else {
+ // Same prefix and no use tree list but differs in more than one segment at the end. This might be module style still.
+ res = ImportGranularityGuess::ModuleOrItem;
+ }
+ } else {
+ // Same prefix with item tree lists, has to be module style as it
+ // can't be crate style since the trees wouldn't share a prefix then.
+ break ImportGranularityGuess::Module;
+ }
+ }
+ }
+ }
+ prev = curr;
+ prev_vis = curr_vis;
+ prev_attrs = curr_attrs;
+ }
+}
+
+fn insert_use_(
+ scope: &ImportScope,
+ insert_path: &ast::Path,
+ group_imports: bool,
+ use_item: ast::Use,
+) {
+ let scope_syntax = scope.as_syntax_node();
+ let group = ImportGroup::new(insert_path);
+ let path_node_iter = scope_syntax
+ .children()
+ .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
+ .flat_map(|(use_, node)| {
+ let tree = use_.use_tree()?;
+ let path = tree.path()?;
+ let has_tl = tree.use_tree_list().is_some();
+ Some((path, has_tl, node))
+ });
+
+ if group_imports {
+ // Iterator that discards anything thats not in the required grouping
+ // This implementation allows the user to rearrange their import groups as this only takes the first group that fits
+ let group_iter = path_node_iter
+ .clone()
+ .skip_while(|(path, ..)| ImportGroup::new(path) != group)
+ .take_while(|(path, ..)| ImportGroup::new(path) == group);
+
+ // track the last element we iterated over, if this is still None after the iteration then that means we never iterated in the first place
+ let mut last = None;
+ // find the element that would come directly after our new import
+ let post_insert: Option<(_, _, SyntaxNode)> = group_iter
+ .inspect(|(.., node)| last = Some(node.clone()))
+ .find(|&(ref path, has_tl, _)| {
+ use_tree_path_cmp(insert_path, false, path, has_tl) != Ordering::Greater
+ });
+
+ if let Some((.., node)) = post_insert {
+ cov_mark::hit!(insert_group);
+ // insert our import before that element
+ return ted::insert(ted::Position::before(node), use_item.syntax());
+ }
+ if let Some(node) = last {
+ cov_mark::hit!(insert_group_last);
+ // there is no element after our new import, so append it to the end of the group
+ return ted::insert(ted::Position::after(node), use_item.syntax());
+ }
+
+ // the group we were looking for actually doesn't exist, so insert
+
+ let mut last = None;
+ // find the group that comes after where we want to insert
+ let post_group = path_node_iter
+ .inspect(|(.., node)| last = Some(node.clone()))
+ .find(|(p, ..)| ImportGroup::new(p) > group);
+ if let Some((.., node)) = post_group {
+ cov_mark::hit!(insert_group_new_group);
+ ted::insert(ted::Position::before(&node), use_item.syntax());
+ if let Some(node) = algo::non_trivia_sibling(node.into(), Direction::Prev) {
+ ted::insert(ted::Position::after(node), make::tokens::single_newline());
+ }
+ return;
+ }
+ // there is no such group, so append after the last one
+ if let Some(node) = last {
+ cov_mark::hit!(insert_group_no_group);
+ ted::insert(ted::Position::after(&node), use_item.syntax());
+ ted::insert(ted::Position::after(node), make::tokens::single_newline());
+ return;
+ }
+ } else {
+ // There exists a group, so append to the end of it
+ if let Some((_, _, node)) = path_node_iter.last() {
+ cov_mark::hit!(insert_no_grouping_last);
+ ted::insert(ted::Position::after(node), use_item.syntax());
+ return;
+ }
+ }
+
+ let l_curly = match scope {
+ ImportScope::File(_) => None,
+ // don't insert the imports before the item list/block expr's opening curly brace
+ ImportScope::Module(item_list) => item_list.l_curly_token(),
+ // don't insert the imports before the item list's opening curly brace
+ ImportScope::Block(block) => block.l_curly_token(),
+ };
+ // there are no imports in this file at all
+ // so put the import after all inner module attributes and possible license header comments
+ if let Some(last_inner_element) = scope_syntax
+ .children_with_tokens()
+ // skip the curly brace
+ .skip(l_curly.is_some() as usize)
+ .take_while(|child| match child {
+ NodeOrToken::Node(node) => is_inner_attribute(node.clone()),
+ NodeOrToken::Token(token) => {
+ [SyntaxKind::WHITESPACE, SyntaxKind::COMMENT, SyntaxKind::SHEBANG]
+ .contains(&token.kind())
+ }
+ })
+ .filter(|child| child.as_token().map_or(true, |t| t.kind() != SyntaxKind::WHITESPACE))
+ .last()
+ {
+ cov_mark::hit!(insert_empty_inner_attr);
+ ted::insert(ted::Position::after(&last_inner_element), use_item.syntax());
+ ted::insert(ted::Position::after(last_inner_element), make::tokens::single_newline());
+ } else {
+ match l_curly {
+ Some(b) => {
+ cov_mark::hit!(insert_empty_module);
+ ted::insert(ted::Position::after(&b), make::tokens::single_newline());
+ ted::insert(ted::Position::after(&b), use_item.syntax());
+ }
+ None => {
+ cov_mark::hit!(insert_empty_file);
+ ted::insert(
+ ted::Position::first_child_of(scope_syntax),
+ make::tokens::blank_line(),
+ );
+ ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
+ }
+ }
+ }
+}
+
+fn is_inner_attribute(node: SyntaxNode) -> bool {
+ ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner)
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use/tests.rs b/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use/tests.rs
new file mode 100644
index 000000000..59673af32
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use/tests.rs
@@ -0,0 +1,1084 @@
+use base_db::fixture::WithFixture;
+use hir::PrefixKind;
+use stdx::trim_indent;
+use test_utils::{assert_eq_text, CURSOR_MARKER};
+
+use super::*;
+
+#[test]
+fn trailing_comment_in_empty_file() {
+ check(
+ "foo::bar",
+ r#"
+struct Struct;
+// 0 = 1
+"#,
+ r#"
+use foo::bar;
+
+struct Struct;
+// 0 = 1
+"#,
+ ImportGranularity::Crate,
+ );
+}
+
+#[test]
+fn respects_cfg_attr_fn() {
+ check(
+ r"bar::Bar",
+ r#"
+#[cfg(test)]
+fn foo() {$0}
+"#,
+ r#"
+#[cfg(test)]
+fn foo() {
+ use bar::Bar;
+}
+"#,
+ ImportGranularity::Crate,
+ );
+}
+
+#[test]
+fn respects_cfg_attr_const() {
+ check(
+ r"bar::Bar",
+ r#"
+#[cfg(test)]
+const FOO: Bar = {$0};
+"#,
+ r#"
+#[cfg(test)]
+const FOO: Bar = {
+ use bar::Bar;
+};
+"#,
+ ImportGranularity::Crate,
+ );
+}
+
+#[test]
+fn insert_skips_lone_glob_imports() {
+ check(
+ "use foo::baz::A",
+ r"
+use foo::bar::*;
+",
+ r"
+use foo::bar::*;
+use foo::baz::A;
+",
+ ImportGranularity::Crate,
+ );
+}
+
+#[test]
+fn insert_not_group() {
+ cov_mark::check!(insert_no_grouping_last);
+ check_with_config(
+ "use external_crate2::bar::A",
+ r"
+use std::bar::B;
+use external_crate::bar::A;
+use crate::bar::A;
+use self::bar::A;
+use super::bar::A;",
+ r"
+use std::bar::B;
+use external_crate::bar::A;
+use crate::bar::A;
+use self::bar::A;
+use super::bar::A;
+use external_crate2::bar::A;",
+ &InsertUseConfig {
+ granularity: ImportGranularity::Item,
+ enforce_granularity: true,
+ prefix_kind: PrefixKind::Plain,
+ group: false,
+ skip_glob_imports: true,
+ },
+ );
+}
+
+#[test]
+fn insert_existing() {
+ check_crate("std::fs", "use std::fs;", "use std::fs;")
+}
+
+#[test]
+fn insert_start() {
+ check_none(
+ "std::bar::AA",
+ r"
+use std::bar::B;
+use std::bar::D;
+use std::bar::F;
+use std::bar::G;",
+ r"
+use std::bar::AA;
+use std::bar::B;
+use std::bar::D;
+use std::bar::F;
+use std::bar::G;",
+ )
+}
+
+#[test]
+fn insert_start_indent() {
+ check_none(
+ "std::bar::AA",
+ r"
+ use std::bar::B;
+ use std::bar::C;",
+ r"
+ use std::bar::AA;
+ use std::bar::B;
+ use std::bar::C;",
+ );
+}
+
+#[test]
+fn insert_middle() {
+ cov_mark::check!(insert_group);
+ check_none(
+ "std::bar::EE",
+ r"
+use std::bar::A;
+use std::bar::D;
+use std::bar::F;
+use std::bar::G;",
+ r"
+use std::bar::A;
+use std::bar::D;
+use std::bar::EE;
+use std::bar::F;
+use std::bar::G;",
+ )
+}
+
+#[test]
+fn insert_middle_indent() {
+ check_none(
+ "std::bar::EE",
+ r"
+ use std::bar::A;
+ use std::bar::D;
+ use std::bar::F;
+ use std::bar::G;",
+ r"
+ use std::bar::A;
+ use std::bar::D;
+ use std::bar::EE;
+ use std::bar::F;
+ use std::bar::G;",
+ )
+}
+
+#[test]
+fn insert_end() {
+ cov_mark::check!(insert_group_last);
+ check_none(
+ "std::bar::ZZ",
+ r"
+use std::bar::A;
+use std::bar::D;
+use std::bar::F;
+use std::bar::G;",
+ r"
+use std::bar::A;
+use std::bar::D;
+use std::bar::F;
+use std::bar::G;
+use std::bar::ZZ;",
+ )
+}
+
+#[test]
+fn insert_end_indent() {
+ check_none(
+ "std::bar::ZZ",
+ r"
+ use std::bar::A;
+ use std::bar::D;
+ use std::bar::F;
+ use std::bar::G;",
+ r"
+ use std::bar::A;
+ use std::bar::D;
+ use std::bar::F;
+ use std::bar::G;
+ use std::bar::ZZ;",
+ )
+}
+
+#[test]
+fn insert_middle_nested() {
+ check_none(
+ "std::bar::EE",
+ r"
+use std::bar::A;
+use std::bar::{D, Z}; // example of weird imports due to user
+use std::bar::F;
+use std::bar::G;",
+ r"
+use std::bar::A;
+use std::bar::EE;
+use std::bar::{D, Z}; // example of weird imports due to user
+use std::bar::F;
+use std::bar::G;",
+ )
+}
+
+#[test]
+fn insert_middle_groups() {
+ check_none(
+ "foo::bar::GG",
+ r"
+ use std::bar::A;
+ use std::bar::D;
+
+ use foo::bar::F;
+ use foo::bar::H;",
+ r"
+ use std::bar::A;
+ use std::bar::D;
+
+ use foo::bar::F;
+ use foo::bar::GG;
+ use foo::bar::H;",
+ )
+}
+
+#[test]
+fn insert_first_matching_group() {
+ check_none(
+ "foo::bar::GG",
+ r"
+ use foo::bar::A;
+ use foo::bar::D;
+
+ use std;
+
+ use foo::bar::F;
+ use foo::bar::H;",
+ r"
+ use foo::bar::A;
+ use foo::bar::D;
+ use foo::bar::GG;
+
+ use std;
+
+ use foo::bar::F;
+ use foo::bar::H;",
+ )
+}
+
+#[test]
+fn insert_missing_group_std() {
+ cov_mark::check!(insert_group_new_group);
+ check_none(
+ "std::fmt",
+ r"
+ use foo::bar::A;
+ use foo::bar::D;",
+ r"
+ use std::fmt;
+
+ use foo::bar::A;
+ use foo::bar::D;",
+ )
+}
+
+#[test]
+fn insert_missing_group_self() {
+ cov_mark::check!(insert_group_no_group);
+ check_none(
+ "self::fmt",
+ r"
+use foo::bar::A;
+use foo::bar::D;",
+ r"
+use foo::bar::A;
+use foo::bar::D;
+
+use self::fmt;",
+ )
+}
+
+#[test]
+fn insert_no_imports() {
+ check_crate(
+ "foo::bar",
+ "fn main() {}",
+ r"use foo::bar;
+
+fn main() {}",
+ )
+}
+
+#[test]
+fn insert_empty_file() {
+ cov_mark::check_count!(insert_empty_file, 2);
+
+ // Default configuration
+ // empty files will get two trailing newlines
+ // this is due to the test case insert_no_imports above
+ check_crate(
+ "foo::bar",
+ "",
+ r"use foo::bar;
+
+",
+ );
+
+ // "not group" configuration
+ check_with_config(
+ "use external_crate2::bar::A",
+ r"",
+ r"use external_crate2::bar::A;
+
+",
+ &InsertUseConfig {
+ granularity: ImportGranularity::Item,
+ enforce_granularity: true,
+ prefix_kind: PrefixKind::Plain,
+ group: false,
+ skip_glob_imports: true,
+ },
+ );
+}
+
+#[test]
+fn insert_empty_module() {
+ cov_mark::check_count!(insert_empty_module, 2);
+
+ // Default configuration
+ check(
+ "foo::bar",
+ r"
+mod x {$0}
+",
+ r"
+mod x {
+ use foo::bar;
+}
+",
+ ImportGranularity::Item,
+ );
+
+ // "not group" configuration
+ check_with_config(
+ "foo::bar",
+ r"mod x {$0}",
+ r"mod x {
+ use foo::bar;
+}",
+ &InsertUseConfig {
+ granularity: ImportGranularity::Item,
+ enforce_granularity: true,
+ prefix_kind: PrefixKind::Plain,
+ group: false,
+ skip_glob_imports: true,
+ },
+ );
+}
+
+#[test]
+fn insert_after_inner_attr() {
+ cov_mark::check_count!(insert_empty_inner_attr, 2);
+
+ // Default configuration
+ check_crate(
+ "foo::bar",
+ r"#![allow(unused_imports)]",
+ r"#![allow(unused_imports)]
+
+use foo::bar;",
+ );
+
+ // "not group" configuration
+ check_with_config(
+ "foo::bar",
+ r"#![allow(unused_imports)]",
+ r"#![allow(unused_imports)]
+
+use foo::bar;",
+ &InsertUseConfig {
+ granularity: ImportGranularity::Item,
+ enforce_granularity: true,
+ prefix_kind: PrefixKind::Plain,
+ group: false,
+ skip_glob_imports: true,
+ },
+ );
+}
+
+#[test]
+fn insert_after_inner_attr2() {
+ check_crate(
+ "foo::bar",
+ r"#![allow(unused_imports)]
+
+#![no_std]
+fn main() {}",
+ r"#![allow(unused_imports)]
+
+#![no_std]
+
+use foo::bar;
+fn main() {}",
+ );
+}
+
+#[test]
+fn inserts_after_single_line_inner_comments() {
+ check_none(
+ "foo::bar::Baz",
+ "//! Single line inner comments do not allow any code before them.",
+ r#"//! Single line inner comments do not allow any code before them.
+
+use foo::bar::Baz;"#,
+ );
+ check_none(
+ "foo::bar::Baz",
+ r"mod foo {
+ //! Single line inner comments do not allow any code before them.
+$0
+}",
+ r"mod foo {
+ //! Single line inner comments do not allow any code before them.
+
+ use foo::bar::Baz;
+
+}",
+ );
+}
+
+#[test]
+fn inserts_after_single_line_comments() {
+ check_none(
+ "foo::bar::Baz",
+ "// Represents a possible license header and/or general module comments",
+ r#"// Represents a possible license header and/or general module comments
+
+use foo::bar::Baz;"#,
+ );
+}
+
+#[test]
+fn inserts_after_shebang() {
+ check_none(
+ "foo::bar::Baz",
+ "#!/usr/bin/env rust",
+ r#"#!/usr/bin/env rust
+
+use foo::bar::Baz;"#,
+ );
+}
+
+#[test]
+fn inserts_after_multiple_single_line_comments() {
+ check_none(
+ "foo::bar::Baz",
+ "// Represents a possible license header and/or general module comments
+// Second single-line comment
+// Third single-line comment",
+ r#"// Represents a possible license header and/or general module comments
+// Second single-line comment
+// Third single-line comment
+
+use foo::bar::Baz;"#,
+ );
+}
+
+#[test]
+fn inserts_before_single_line_item_comments() {
+ check_none(
+ "foo::bar::Baz",
+ r#"// Represents a comment about a function
+fn foo() {}"#,
+ r#"use foo::bar::Baz;
+
+// Represents a comment about a function
+fn foo() {}"#,
+ );
+}
+
+#[test]
+fn inserts_after_single_line_header_comments_and_before_item() {
+ check_none(
+ "foo::bar::Baz",
+ r#"// Represents a possible license header
+// Line two of possible license header
+
+fn foo() {}"#,
+ r#"// Represents a possible license header
+// Line two of possible license header
+
+use foo::bar::Baz;
+
+fn foo() {}"#,
+ );
+}
+
+#[test]
+fn inserts_after_multiline_inner_comments() {
+ check_none(
+ "foo::bar::Baz",
+ r#"/*! Multiline inner comments do not allow any code before them. */
+
+/*! Still an inner comment, cannot place any code before. */
+fn main() {}"#,
+ r#"/*! Multiline inner comments do not allow any code before them. */
+
+/*! Still an inner comment, cannot place any code before. */
+
+use foo::bar::Baz;
+fn main() {}"#,
+ )
+}
+
+#[test]
+fn inserts_after_all_inner_items() {
+ check_none(
+ "foo::bar::Baz",
+ r#"#![allow(unused_imports)]
+/*! Multiline line comment 2 */
+
+
+//! Single line comment 1
+#![no_std]
+//! Single line comment 2
+fn main() {}"#,
+ r#"#![allow(unused_imports)]
+/*! Multiline line comment 2 */
+
+
+//! Single line comment 1
+#![no_std]
+//! Single line comment 2
+
+use foo::bar::Baz;
+fn main() {}"#,
+ )
+}
+
+#[test]
+fn merge_groups() {
+ check_module("std::io", r"use std::fmt;", r"use std::{fmt, io};")
+}
+
+#[test]
+fn merge_groups_last() {
+ check_module(
+ "std::io",
+ r"use std::fmt::{Result, Display};",
+ r"use std::fmt::{Result, Display};
+use std::io;",
+ )
+}
+
+#[test]
+fn merge_last_into_self() {
+ check_module("foo::bar::baz", r"use foo::bar;", r"use foo::bar::{self, baz};");
+}
+
+#[test]
+fn merge_groups_full() {
+ check_crate(
+ "std::io",
+ r"use std::fmt::{Result, Display};",
+ r"use std::{fmt::{Result, Display}, io};",
+ )
+}
+
+#[test]
+fn merge_groups_long_full() {
+ check_crate("std::foo::bar::Baz", r"use std::foo::bar::Qux;", r"use std::foo::bar::{Qux, Baz};")
+}
+
+#[test]
+fn merge_groups_long_last() {
+ check_module(
+ "std::foo::bar::Baz",
+ r"use std::foo::bar::Qux;",
+ r"use std::foo::bar::{Qux, Baz};",
+ )
+}
+
+#[test]
+fn merge_groups_long_full_list() {
+ check_crate(
+ "std::foo::bar::Baz",
+ r"use std::foo::bar::{Qux, Quux};",
+ r"use std::foo::bar::{Qux, Quux, Baz};",
+ )
+}
+
+#[test]
+fn merge_groups_long_last_list() {
+ check_module(
+ "std::foo::bar::Baz",
+ r"use std::foo::bar::{Qux, Quux};",
+ r"use std::foo::bar::{Qux, Quux, Baz};",
+ )
+}
+
+#[test]
+fn merge_groups_long_full_nested() {
+ check_crate(
+ "std::foo::bar::Baz",
+ r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
+ r"use std::foo::bar::{Qux, quux::{Fez, Fizz}, Baz};",
+ )
+}
+
+#[test]
+fn merge_groups_long_last_nested() {
+ check_module(
+ "std::foo::bar::Baz",
+ r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
+ r"use std::foo::bar::Baz;
+use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
+ )
+}
+
+#[test]
+fn merge_groups_full_nested_deep() {
+ check_crate(
+ "std::foo::bar::quux::Baz",
+ r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
+ r"use std::foo::bar::{Qux, quux::{Fez, Fizz, Baz}};",
+ )
+}
+
+#[test]
+fn merge_groups_full_nested_long() {
+ check_crate(
+ "std::foo::bar::Baz",
+ r"use std::{foo::bar::Qux};",
+ r"use std::{foo::bar::{Qux, Baz}};",
+ );
+}
+
+#[test]
+fn merge_groups_last_nested_long() {
+ check_crate(
+ "std::foo::bar::Baz",
+ r"use std::{foo::bar::Qux};",
+ r"use std::{foo::bar::{Qux, Baz}};",
+ );
+}
+
+#[test]
+fn merge_groups_skip_pub() {
+ check_crate(
+ "std::io",
+ r"pub use std::fmt::{Result, Display};",
+ r"pub use std::fmt::{Result, Display};
+use std::io;",
+ )
+}
+
+#[test]
+fn merge_groups_skip_pub_crate() {
+ check_crate(
+ "std::io",
+ r"pub(crate) use std::fmt::{Result, Display};",
+ r"pub(crate) use std::fmt::{Result, Display};
+use std::io;",
+ )
+}
+
+#[test]
+fn merge_groups_skip_attributed() {
+ check_crate(
+ "std::io",
+ r#"
+#[cfg(feature = "gated")] use std::fmt::{Result, Display};
+"#,
+ r#"
+#[cfg(feature = "gated")] use std::fmt::{Result, Display};
+use std::io;
+"#,
+ )
+}
+
+#[test]
+fn split_out_merge() {
+ // FIXME: This is suboptimal, we want to get `use std::fmt::{self, Result}`
+ // instead.
+ check_module(
+ "std::fmt::Result",
+ r"use std::{fmt, io};",
+ r"use std::fmt::Result;
+use std::{fmt, io};",
+ )
+}
+
+#[test]
+fn merge_into_module_import() {
+ check_crate("std::fmt::Result", r"use std::{fmt, io};", r"use std::{fmt::{self, Result}, io};")
+}
+
+#[test]
+fn merge_groups_self() {
+ check_crate("std::fmt::Debug", r"use std::fmt;", r"use std::fmt::{self, Debug};")
+}
+
+#[test]
+fn merge_mod_into_glob() {
+ check_with_config(
+ "token::TokenKind",
+ r"use token::TokenKind::*;",
+ r"use token::TokenKind::{*, self};",
+ &InsertUseConfig {
+ granularity: ImportGranularity::Crate,
+ enforce_granularity: true,
+ prefix_kind: PrefixKind::Plain,
+ group: false,
+ skip_glob_imports: false,
+ },
+ )
+ // FIXME: have it emit `use token::TokenKind::{self, *}`?
+}
+
+#[test]
+fn merge_self_glob() {
+ check_with_config(
+ "self",
+ r"use self::*;",
+ r"use self::{*, self};",
+ &InsertUseConfig {
+ granularity: ImportGranularity::Crate,
+ enforce_granularity: true,
+ prefix_kind: PrefixKind::Plain,
+ group: false,
+ skip_glob_imports: false,
+ },
+ )
+ // FIXME: have it emit `use {self, *}`?
+}
+
+#[test]
+fn merge_glob() {
+ check_crate(
+ "syntax::SyntaxKind",
+ r"
+use syntax::{SyntaxKind::*};",
+ r"
+use syntax::{SyntaxKind::{*, self}};",
+ )
+}
+
+#[test]
+fn merge_glob_nested() {
+ check_crate(
+ "foo::bar::quux::Fez",
+ r"use foo::bar::{Baz, quux::*};",
+ r"use foo::bar::{Baz, quux::{*, Fez}};",
+ )
+}
+
+#[test]
+fn merge_nested_considers_first_segments() {
+ check_crate(
+ "hir_ty::display::write_bounds_like_dyn_trait",
+ r"use hir_ty::{autoderef, display::{HirDisplayError, HirFormatter}, method_resolution};",
+ r"use hir_ty::{autoderef, display::{HirDisplayError, HirFormatter, write_bounds_like_dyn_trait}, method_resolution};",
+ );
+}
+
+#[test]
+fn skip_merge_last_too_long() {
+ check_module(
+ "foo::bar",
+ r"use foo::bar::baz::Qux;",
+ r"use foo::bar;
+use foo::bar::baz::Qux;",
+ );
+}
+
+#[test]
+fn skip_merge_last_too_long2() {
+ check_module(
+ "foo::bar::baz::Qux",
+ r"use foo::bar;",
+ r"use foo::bar;
+use foo::bar::baz::Qux;",
+ );
+}
+
+#[test]
+fn insert_short_before_long() {
+ check_none(
+ "foo::bar",
+ r"use foo::bar::baz::Qux;",
+ r"use foo::bar;
+use foo::bar::baz::Qux;",
+ );
+}
+
+#[test]
+fn merge_last_fail() {
+ check_merge_only_fail(
+ r"use foo::bar::{baz::{Qux, Fez}};",
+ r"use foo::bar::{baaz::{Quux, Feez}};",
+ MergeBehavior::Module,
+ );
+}
+
+#[test]
+fn merge_last_fail1() {
+ check_merge_only_fail(
+ r"use foo::bar::{baz::{Qux, Fez}};",
+ r"use foo::bar::baaz::{Quux, Feez};",
+ MergeBehavior::Module,
+ );
+}
+
+#[test]
+fn merge_last_fail2() {
+ check_merge_only_fail(
+ r"use foo::bar::baz::{Qux, Fez};",
+ r"use foo::bar::{baaz::{Quux, Feez}};",
+ MergeBehavior::Module,
+ );
+}
+
+#[test]
+fn merge_last_fail3() {
+ check_merge_only_fail(
+ r"use foo::bar::baz::{Qux, Fez};",
+ r"use foo::bar::baaz::{Quux, Feez};",
+ MergeBehavior::Module,
+ );
+}
+
+#[test]
+fn guess_empty() {
+ check_guess("", ImportGranularityGuess::Unknown);
+}
+
+#[test]
+fn guess_single() {
+ check_guess(r"use foo::{baz::{qux, quux}, bar};", ImportGranularityGuess::Crate);
+ check_guess(r"use foo::bar;", ImportGranularityGuess::Unknown);
+ check_guess(r"use foo::bar::{baz, qux};", ImportGranularityGuess::CrateOrModule);
+}
+
+#[test]
+fn guess_unknown() {
+ check_guess(
+ r"
+use foo::bar::baz;
+use oof::rab::xuq;
+",
+ ImportGranularityGuess::Unknown,
+ );
+}
+
+#[test]
+fn guess_item() {
+ check_guess(
+ r"
+use foo::bar::baz;
+use foo::bar::qux;
+",
+ ImportGranularityGuess::Item,
+ );
+}
+
+#[test]
+fn guess_module_or_item() {
+ check_guess(
+ r"
+use foo::bar::Bar;
+use foo::qux;
+",
+ ImportGranularityGuess::ModuleOrItem,
+ );
+ check_guess(
+ r"
+use foo::bar::Bar;
+use foo::bar;
+",
+ ImportGranularityGuess::ModuleOrItem,
+ );
+}
+
+#[test]
+fn guess_module() {
+ check_guess(
+ r"
+use foo::bar::baz;
+use foo::bar::{qux, quux};
+",
+ ImportGranularityGuess::Module,
+ );
+ // this is a rather odd case, technically this file isn't following any style properly.
+ check_guess(
+ r"
+use foo::bar::baz;
+use foo::{baz::{qux, quux}, bar};
+",
+ ImportGranularityGuess::Module,
+ );
+ check_guess(
+ r"
+use foo::bar::Bar;
+use foo::baz::Baz;
+use foo::{Foo, Qux};
+",
+ ImportGranularityGuess::Module,
+ );
+}
+
+#[test]
+fn guess_crate_or_module() {
+ check_guess(
+ r"
+use foo::bar::baz;
+use oof::bar::{qux, quux};
+",
+ ImportGranularityGuess::CrateOrModule,
+ );
+}
+
+#[test]
+fn guess_crate() {
+ check_guess(
+ r"
+use frob::bar::baz;
+use foo::{baz::{qux, quux}, bar};
+",
+ ImportGranularityGuess::Crate,
+ );
+}
+
+#[test]
+fn guess_skips_differing_vis() {
+ check_guess(
+ r"
+use foo::bar::baz;
+pub use foo::bar::qux;
+",
+ ImportGranularityGuess::Unknown,
+ );
+}
+
+#[test]
+fn guess_skips_differing_attrs() {
+ check_guess(
+ r"
+pub use foo::bar::baz;
+#[doc(hidden)]
+pub use foo::bar::qux;
+",
+ ImportGranularityGuess::Unknown,
+ );
+}
+
+#[test]
+fn guess_grouping_matters() {
+ check_guess(
+ r"
+use foo::bar::baz;
+use oof::bar::baz;
+use foo::bar::qux;
+",
+ ImportGranularityGuess::Unknown,
+ );
+}
+
+fn check_with_config(
+ path: &str,
+ ra_fixture_before: &str,
+ ra_fixture_after: &str,
+ config: &InsertUseConfig,
+) {
+ let (db, file_id, pos) = if ra_fixture_before.contains(CURSOR_MARKER) {
+ let (db, file_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture_before);
+ (db, file_id, Some(range_or_offset))
+ } else {
+ let (db, file_id) = RootDatabase::with_single_file(ra_fixture_before);
+ (db, file_id, None)
+ };
+ let sema = &Semantics::new(&db);
+ let source_file = sema.parse(file_id);
+ let syntax = source_file.syntax().clone_for_update();
+ let file = pos
+ .and_then(|pos| syntax.token_at_offset(pos.expect_offset()).next()?.parent())
+ .and_then(|it| ImportScope::find_insert_use_container(&it, sema))
+ .or_else(|| ImportScope::from(syntax))
+ .unwrap();
+ let path = ast::SourceFile::parse(&format!("use {};", path))
+ .tree()
+ .syntax()
+ .descendants()
+ .find_map(ast::Path::cast)
+ .unwrap();
+
+ insert_use(&file, path, config);
+ let result = file.as_syntax_node().ancestors().last().unwrap().to_string();
+ assert_eq_text!(&trim_indent(ra_fixture_after), &result);
+}
+
+fn check(
+ path: &str,
+ ra_fixture_before: &str,
+ ra_fixture_after: &str,
+ granularity: ImportGranularity,
+) {
+ check_with_config(
+ path,
+ ra_fixture_before,
+ ra_fixture_after,
+ &InsertUseConfig {
+ granularity,
+ enforce_granularity: true,
+ prefix_kind: PrefixKind::Plain,
+ group: true,
+ skip_glob_imports: true,
+ },
+ )
+}
+
+fn check_crate(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
+ check(path, ra_fixture_before, ra_fixture_after, ImportGranularity::Crate)
+}
+
+fn check_module(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
+ check(path, ra_fixture_before, ra_fixture_after, ImportGranularity::Module)
+}
+
+fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
+ check(path, ra_fixture_before, ra_fixture_after, ImportGranularity::Item)
+}
+
+fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehavior) {
+ let use0 = ast::SourceFile::parse(ra_fixture0)
+ .tree()
+ .syntax()
+ .descendants()
+ .find_map(ast::Use::cast)
+ .unwrap();
+
+ let use1 = ast::SourceFile::parse(ra_fixture1)
+ .tree()
+ .syntax()
+ .descendants()
+ .find_map(ast::Use::cast)
+ .unwrap();
+
+ let result = try_merge_imports(&use0, &use1, mb);
+ assert_eq!(result.map(|u| u.to_string()), None);
+}
+
+fn check_guess(ra_fixture: &str, expected: ImportGranularityGuess) {
+ let syntax = ast::SourceFile::parse(ra_fixture).tree().syntax().clone();
+ let file = ImportScope::from(syntax).unwrap();
+ assert_eq!(super::guess_granularity_from_scope(&file), expected);
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs b/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs
new file mode 100644
index 000000000..7fb4b90e6
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs
@@ -0,0 +1,295 @@
+//! Handle syntactic aspects of merging UseTrees.
+use std::cmp::Ordering;
+
+use itertools::{EitherOrBoth, Itertools};
+use syntax::{
+ ast::{self, AstNode, HasAttrs, HasVisibility, PathSegmentKind},
+ ted,
+};
+
+use crate::syntax_helpers::node_ext::vis_eq;
+
+/// What type of merges are allowed.
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum MergeBehavior {
+ /// Merge imports from the same crate into a single use statement.
+ Crate,
+ /// Merge imports from the same module into a single use statement.
+ Module,
+}
+
+impl MergeBehavior {
+ fn is_tree_allowed(&self, tree: &ast::UseTree) -> bool {
+ match self {
+ MergeBehavior::Crate => true,
+ // only simple single segment paths are allowed
+ MergeBehavior::Module => {
+ tree.use_tree_list().is_none() && tree.path().map(path_len) <= Some(1)
+ }
+ }
+ }
+}
+
+/// Merge `rhs` into `lhs` keeping both intact.
+/// Returned AST is mutable.
+pub fn try_merge_imports(
+ lhs: &ast::Use,
+ rhs: &ast::Use,
+ merge_behavior: MergeBehavior,
+) -> Option<ast::Use> {
+ // don't merge imports with different visibilities
+ if !eq_visibility(lhs.visibility(), rhs.visibility()) {
+ return None;
+ }
+ if !eq_attrs(lhs.attrs(), rhs.attrs()) {
+ return None;
+ }
+
+ let lhs = lhs.clone_subtree().clone_for_update();
+ let rhs = rhs.clone_subtree().clone_for_update();
+ let lhs_tree = lhs.use_tree()?;
+ let rhs_tree = rhs.use_tree()?;
+ try_merge_trees_mut(&lhs_tree, &rhs_tree, merge_behavior)?;
+ Some(lhs)
+}
+
+/// Merge `rhs` into `lhs` keeping both intact.
+/// Returned AST is mutable.
+pub fn try_merge_trees(
+ lhs: &ast::UseTree,
+ rhs: &ast::UseTree,
+ merge: MergeBehavior,
+) -> Option<ast::UseTree> {
+ let lhs = lhs.clone_subtree().clone_for_update();
+ let rhs = rhs.clone_subtree().clone_for_update();
+ try_merge_trees_mut(&lhs, &rhs, merge)?;
+ Some(lhs)
+}
+
+fn try_merge_trees_mut(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehavior) -> Option<()> {
+ let lhs_path = lhs.path()?;
+ let rhs_path = rhs.path()?;
+
+ let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
+ if !(lhs.is_simple_path()
+ && rhs.is_simple_path()
+ && lhs_path == lhs_prefix
+ && rhs_path == rhs_prefix)
+ {
+ lhs.split_prefix(&lhs_prefix);
+ rhs.split_prefix(&rhs_prefix);
+ }
+ recursive_merge(lhs, rhs, merge)
+}
+
+/// Recursively merges rhs to lhs
+#[must_use]
+fn recursive_merge(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehavior) -> Option<()> {
+ let mut use_trees: Vec<ast::UseTree> = lhs
+ .use_tree_list()
+ .into_iter()
+ .flat_map(|list| list.use_trees())
+ // We use Option here to early return from this function(this is not the
+ // same as a `filter` op).
+ .map(|tree| merge.is_tree_allowed(&tree).then(|| tree))
+ .collect::<Option<_>>()?;
+ use_trees.sort_unstable_by(|a, b| path_cmp_for_sort(a.path(), b.path()));
+ for rhs_t in rhs.use_tree_list().into_iter().flat_map(|list| list.use_trees()) {
+ if !merge.is_tree_allowed(&rhs_t) {
+ return None;
+ }
+ let rhs_path = rhs_t.path();
+
+ match use_trees
+ .binary_search_by(|lhs_t| path_cmp_bin_search(lhs_t.path(), rhs_path.as_ref()))
+ {
+ Ok(idx) => {
+ let lhs_t = &mut use_trees[idx];
+ let lhs_path = lhs_t.path()?;
+ let rhs_path = rhs_path?;
+ let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
+ if lhs_prefix == lhs_path && rhs_prefix == rhs_path {
+ let tree_is_self = |tree: &ast::UseTree| {
+ tree.path().as_ref().map(path_is_self).unwrap_or(false)
+ };
+ // Check if only one of the two trees has a tree list, and
+ // whether that then contains `self` or not. If this is the
+ // case we can skip this iteration since the path without
+ // the list is already included in the other one via `self`.
+ let tree_contains_self = |tree: &ast::UseTree| {
+ tree.use_tree_list()
+ .map(|tree_list| tree_list.use_trees().any(|it| tree_is_self(&it)))
+ // Glob imports aren't part of the use-tree lists,
+ // so they need to be handled explicitly
+ .or_else(|| tree.star_token().map(|_| false))
+ };
+ match (tree_contains_self(lhs_t), tree_contains_self(&rhs_t)) {
+ (Some(true), None) => continue,
+ (None, Some(true)) => {
+ ted::replace(lhs_t.syntax(), rhs_t.syntax());
+ *lhs_t = rhs_t;
+ continue;
+ }
+ _ => (),
+ }
+
+ if lhs_t.is_simple_path() && rhs_t.is_simple_path() {
+ continue;
+ }
+ }
+ lhs_t.split_prefix(&lhs_prefix);
+ rhs_t.split_prefix(&rhs_prefix);
+ recursive_merge(lhs_t, &rhs_t, merge)?;
+ }
+ Err(_)
+ if merge == MergeBehavior::Module
+ && !use_trees.is_empty()
+ && rhs_t.use_tree_list().is_some() =>
+ {
+ return None
+ }
+ Err(idx) => {
+ use_trees.insert(idx, rhs_t.clone());
+ lhs.get_or_create_use_tree_list().add_use_tree(rhs_t);
+ }
+ }
+ }
+ Some(())
+}
+
+/// Traverses both paths until they differ, returning the common prefix of both.
+pub fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> {
+ let mut res = None;
+ let mut lhs_curr = lhs.first_qualifier_or_self();
+ let mut rhs_curr = rhs.first_qualifier_or_self();
+ loop {
+ match (lhs_curr.segment(), rhs_curr.segment()) {
+ (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
+ _ => break res,
+ }
+ res = Some((lhs_curr.clone(), rhs_curr.clone()));
+
+ match lhs_curr.parent_path().zip(rhs_curr.parent_path()) {
+ Some((lhs, rhs)) => {
+ lhs_curr = lhs;
+ rhs_curr = rhs;
+ }
+ _ => break res,
+ }
+ }
+}
+
+/// Orders paths in the following way:
+/// the sole self token comes first, after that come uppercase identifiers, then lowercase identifiers
+// FIXME: rustfmt sorts lowercase idents before uppercase, in general we want to have the same ordering rustfmt has
+// which is `self` and `super` first, then identifier imports with lowercase ones first, then glob imports and at last list imports.
+// Example foo::{self, foo, baz, Baz, Qux, *, {Bar}}
+fn path_cmp_for_sort(a: Option<ast::Path>, b: Option<ast::Path>) -> Ordering {
+ match (a, b) {
+ (None, None) => Ordering::Equal,
+ (None, Some(_)) => Ordering::Less,
+ (Some(_), None) => Ordering::Greater,
+ (Some(ref a), Some(ref b)) => match (path_is_self(a), path_is_self(b)) {
+ (true, true) => Ordering::Equal,
+ (true, false) => Ordering::Less,
+ (false, true) => Ordering::Greater,
+ (false, false) => path_cmp_short(a, b),
+ },
+ }
+}
+
+/// Path comparison func for binary searching for merging.
+fn path_cmp_bin_search(lhs: Option<ast::Path>, rhs: Option<&ast::Path>) -> Ordering {
+ match (lhs.as_ref().and_then(ast::Path::first_segment), rhs.and_then(ast::Path::first_segment))
+ {
+ (None, None) => Ordering::Equal,
+ (None, Some(_)) => Ordering::Less,
+ (Some(_), None) => Ordering::Greater,
+ (Some(ref a), Some(ref b)) => path_segment_cmp(a, b),
+ }
+}
+
+/// Short circuiting comparison, if both paths are equal until one of them ends they are considered
+/// equal
+fn path_cmp_short(a: &ast::Path, b: &ast::Path) -> Ordering {
+ let a = a.segments();
+ let b = b.segments();
+ // cmp_by would be useful for us here but that is currently unstable
+ // cmp doesn't work due the lifetimes on text's return type
+ a.zip(b)
+ .find_map(|(a, b)| match path_segment_cmp(&a, &b) {
+ Ordering::Equal => None,
+ ord => Some(ord),
+ })
+ .unwrap_or(Ordering::Equal)
+}
+
+/// Compares two paths, if one ends earlier than the other the has_tl parameters decide which is
+/// greater as a a path that has a tree list should be greater, while one that just ends without
+/// a tree list should be considered less.
+pub(super) fn use_tree_path_cmp(
+ a: &ast::Path,
+ a_has_tl: bool,
+ b: &ast::Path,
+ b_has_tl: bool,
+) -> Ordering {
+ let a_segments = a.segments();
+ let b_segments = b.segments();
+ // cmp_by would be useful for us here but that is currently unstable
+ // cmp doesn't work due the lifetimes on text's return type
+ a_segments
+ .zip_longest(b_segments)
+ .find_map(|zipped| match zipped {
+ EitherOrBoth::Both(ref a, ref b) => match path_segment_cmp(a, b) {
+ Ordering::Equal => None,
+ ord => Some(ord),
+ },
+ EitherOrBoth::Left(_) if !b_has_tl => Some(Ordering::Greater),
+ EitherOrBoth::Left(_) => Some(Ordering::Less),
+ EitherOrBoth::Right(_) if !a_has_tl => Some(Ordering::Less),
+ EitherOrBoth::Right(_) => Some(Ordering::Greater),
+ })
+ .unwrap_or(Ordering::Equal)
+}
+
+fn path_segment_cmp(a: &ast::PathSegment, b: &ast::PathSegment) -> Ordering {
+ let a = a.kind().and_then(|kind| match kind {
+ PathSegmentKind::Name(name_ref) => Some(name_ref),
+ _ => None,
+ });
+ let b = b.kind().and_then(|kind| match kind {
+ PathSegmentKind::Name(name_ref) => Some(name_ref),
+ _ => None,
+ });
+ a.as_ref().map(ast::NameRef::text).cmp(&b.as_ref().map(ast::NameRef::text))
+}
+
+pub fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool {
+ match (vis0, vis1) {
+ (None, None) => true,
+ (Some(vis0), Some(vis1)) => vis_eq(&vis0, &vis1),
+ _ => false,
+ }
+}
+
+pub fn eq_attrs(
+ attrs0: impl Iterator<Item = ast::Attr>,
+ attrs1: impl Iterator<Item = ast::Attr>,
+) -> bool {
+ // FIXME order of attributes should not matter
+ let attrs0 = attrs0
+ .flat_map(|attr| attr.syntax().descendants_with_tokens())
+ .flat_map(|it| it.into_token());
+ let attrs1 = attrs1
+ .flat_map(|attr| attr.syntax().descendants_with_tokens())
+ .flat_map(|it| it.into_token());
+ stdx::iter_eq_by(attrs0, attrs1, |tok, tok2| tok.text() == tok2.text())
+}
+
+fn path_is_self(path: &ast::Path) -> bool {
+ path.segment().and_then(|seg| seg.self_token()).is_some() && path.qualifier().is_none()
+}
+
+fn path_len(path: ast::Path) -> usize {
+ path.segments().count()
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/items_locator.rs b/src/tools/rust-analyzer/crates/ide-db/src/items_locator.rs
new file mode 100644
index 000000000..07a57c883
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/items_locator.rs
@@ -0,0 +1,151 @@
+//! This module has the functionality to search the project and its dependencies for a certain item,
+//! by its name and a few criteria.
+//! The main reason for this module to exist is the fact that project's items and dependencies' items
+//! are located in different caches, with different APIs.
+use either::Either;
+use hir::{
+ import_map::{self, ImportKind},
+ symbols::FileSymbol,
+ AsAssocItem, Crate, ItemInNs, Semantics,
+};
+use limit::Limit;
+use syntax::{ast, AstNode, SyntaxKind::NAME};
+
+use crate::{
+ defs::{Definition, NameClass},
+ imports::import_assets::NameToImport,
+ symbol_index, RootDatabase,
+};
+
+/// A value to use, when uncertain which limit to pick.
+pub static DEFAULT_QUERY_SEARCH_LIMIT: Limit = Limit::new(40);
+
+/// Three possible ways to search for the name in associated and/or other items.
+#[derive(Debug, Clone, Copy)]
+pub enum AssocItemSearch {
+ /// Search for the name in both associated and other items.
+ Include,
+ /// Search for the name in other items only.
+ Exclude,
+ /// Search for the name in the associated items only.
+ AssocItemsOnly,
+}
+
+/// Searches for importable items with the given name in the crate and its dependencies.
+pub fn items_with_name<'a>(
+ sema: &'a Semantics<'_, RootDatabase>,
+ krate: Crate,
+ name: NameToImport,
+ assoc_item_search: AssocItemSearch,
+ limit: Option<usize>,
+) -> impl Iterator<Item = ItemInNs> + 'a {
+ let _p = profile::span("items_with_name").detail(|| {
+ format!(
+ "Name: {}, crate: {:?}, assoc items: {:?}, limit: {:?}",
+ name.text(),
+ assoc_item_search,
+ krate.display_name(sema.db).map(|name| name.to_string()),
+ limit,
+ )
+ });
+
+ let (mut local_query, mut external_query) = match name {
+ NameToImport::Exact(exact_name, case_sensitive) => {
+ let mut local_query = symbol_index::Query::new(exact_name.clone());
+ local_query.exact();
+
+ let external_query = import_map::Query::new(exact_name)
+ .name_only()
+ .search_mode(import_map::SearchMode::Equals);
+
+ (
+ local_query,
+ if case_sensitive { external_query.case_sensitive() } else { external_query },
+ )
+ }
+ NameToImport::Fuzzy(fuzzy_search_string) => {
+ let mut local_query = symbol_index::Query::new(fuzzy_search_string.clone());
+
+ let mut external_query = import_map::Query::new(fuzzy_search_string.clone())
+ .search_mode(import_map::SearchMode::Fuzzy)
+ .name_only();
+ match assoc_item_search {
+ AssocItemSearch::Include => {}
+ AssocItemSearch::Exclude => {
+ external_query = external_query.exclude_import_kind(ImportKind::AssociatedItem);
+ }
+ AssocItemSearch::AssocItemsOnly => {
+ external_query = external_query.assoc_items_only();
+ }
+ }
+
+ if fuzzy_search_string.to_lowercase() != fuzzy_search_string {
+ local_query.case_sensitive();
+ external_query = external_query.case_sensitive();
+ }
+
+ (local_query, external_query)
+ }
+ };
+
+ if let Some(limit) = limit {
+ external_query = external_query.limit(limit);
+ local_query.limit(limit);
+ }
+
+ find_items(sema, krate, assoc_item_search, local_query, external_query)
+}
+
+fn find_items<'a>(
+ sema: &'a Semantics<'_, RootDatabase>,
+ krate: Crate,
+ assoc_item_search: AssocItemSearch,
+ local_query: symbol_index::Query,
+ external_query: import_map::Query,
+) -> impl Iterator<Item = ItemInNs> + 'a {
+ let _p = profile::span("find_items");
+ let db = sema.db;
+
+ let external_importables =
+ krate.query_external_importables(db, external_query).map(|external_importable| {
+ match external_importable {
+ Either::Left(module_def) => ItemInNs::from(module_def),
+ Either::Right(macro_def) => ItemInNs::from(macro_def),
+ }
+ });
+
+ // Query the local crate using the symbol index.
+ let local_results = symbol_index::crate_symbols(db, krate, local_query)
+ .into_iter()
+ .filter_map(move |local_candidate| get_name_definition(sema, &local_candidate))
+ .filter_map(|name_definition_to_import| match name_definition_to_import {
+ Definition::Macro(macro_def) => Some(ItemInNs::from(macro_def)),
+ def => <Option<_>>::from(def),
+ });
+
+ external_importables.chain(local_results).filter(move |&item| match assoc_item_search {
+ AssocItemSearch::Include => true,
+ AssocItemSearch::Exclude => !is_assoc_item(item, sema.db),
+ AssocItemSearch::AssocItemsOnly => is_assoc_item(item, sema.db),
+ })
+}
+
+fn get_name_definition(
+ sema: &Semantics<'_, RootDatabase>,
+ import_candidate: &FileSymbol,
+) -> Option<Definition> {
+ let _p = profile::span("get_name_definition");
+
+ let candidate_node = import_candidate.loc.syntax(sema)?;
+ let candidate_name_node = if candidate_node.kind() != NAME {
+ candidate_node.children().find(|it| it.kind() == NAME)?
+ } else {
+ candidate_node
+ };
+ let name = ast::Name::cast(candidate_name_node)?;
+ NameClass::classify(sema, &name)?.defined()
+}
+
+fn is_assoc_item(item: ItemInNs, db: &RootDatabase) -> bool {
+ item.as_module_def().and_then(|module_def| module_def.as_assoc_item(db)).is_some()
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/label.rs b/src/tools/rust-analyzer/crates/ide-db/src/label.rs
new file mode 100644
index 000000000..4b6d54b5e
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/label.rs
@@ -0,0 +1,48 @@
+//! See [`Label`]
+use std::fmt;
+
+/// A type to specify UI label, like an entry in the list of assists. Enforces
+/// proper casing:
+///
+/// Frobnicate bar
+///
+/// Note the upper-case first letter and the absence of `.` at the end.
+#[derive(Clone)]
+pub struct Label(String);
+
+impl PartialEq<str> for Label {
+ fn eq(&self, other: &str) -> bool {
+ self.0 == other
+ }
+}
+
+impl PartialEq<&'_ str> for Label {
+ fn eq(&self, other: &&str) -> bool {
+ self == *other
+ }
+}
+
+impl From<Label> for String {
+ fn from(label: Label) -> String {
+ label.0
+ }
+}
+
+impl Label {
+ pub fn new(label: String) -> Label {
+ assert!(label.starts_with(char::is_uppercase) && !label.ends_with('.'));
+ Label(label)
+ }
+}
+
+impl fmt::Display for Label {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Display::fmt(&self.0, f)
+ }
+}
+
+impl fmt::Debug for Label {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Debug::fmt(&self.0, f)
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/lib.rs b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs
new file mode 100644
index 000000000..966bba616
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs
@@ -0,0 +1,246 @@
+//! This crate defines the core datastructure representing IDE state -- `RootDatabase`.
+//!
+//! It is mainly a `HirDatabase` for semantic analysis, plus a `SymbolsDatabase`, for fuzzy search.
+
+#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
+
+mod apply_change;
+
+pub mod active_parameter;
+pub mod assists;
+pub mod defs;
+pub mod famous_defs;
+pub mod helpers;
+pub mod items_locator;
+pub mod label;
+pub mod line_index;
+pub mod path_transform;
+pub mod rename;
+pub mod rust_doc;
+pub mod search;
+pub mod source_change;
+pub mod symbol_index;
+pub mod traits;
+pub mod ty_filter;
+pub mod use_trivial_contructor;
+
+pub mod imports {
+ pub mod import_assets;
+ pub mod insert_use;
+ pub mod merge_imports;
+}
+
+pub mod generated {
+ pub mod lints;
+}
+
+pub mod syntax_helpers {
+ pub mod node_ext;
+ pub mod insert_whitespace_into_node;
+ pub mod format_string;
+
+ pub use parser::LexedStr;
+}
+
+use std::{fmt, mem::ManuallyDrop, sync::Arc};
+
+use base_db::{
+ salsa::{self, Durability},
+ AnchoredPath, CrateId, FileId, FileLoader, FileLoaderDelegate, SourceDatabase, Upcast,
+};
+use hir::{
+ db::{AstDatabase, DefDatabase, HirDatabase},
+ symbols::FileSymbolKind,
+};
+
+use crate::{line_index::LineIndex, symbol_index::SymbolsDatabase};
+pub use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
+
+/// `base_db` is normally also needed in places where `ide_db` is used, so this re-export is for convenience.
+pub use base_db;
+
+pub type FxIndexSet<T> = indexmap::IndexSet<T, std::hash::BuildHasherDefault<rustc_hash::FxHasher>>;
+pub type FxIndexMap<K, V> =
+ indexmap::IndexMap<K, V, std::hash::BuildHasherDefault<rustc_hash::FxHasher>>;
+
+#[salsa::database(
+ base_db::SourceDatabaseExtStorage,
+ base_db::SourceDatabaseStorage,
+ hir::db::AstDatabaseStorage,
+ hir::db::DefDatabaseStorage,
+ hir::db::HirDatabaseStorage,
+ hir::db::InternDatabaseStorage,
+ LineIndexDatabaseStorage,
+ symbol_index::SymbolsDatabaseStorage
+)]
+pub struct RootDatabase {
+ // We use `ManuallyDrop` here because every codegen unit that contains a
+ // `&RootDatabase -> &dyn OtherDatabase` cast will instantiate its drop glue in the vtable,
+ // which duplicates `Weak::drop` and `Arc::drop` tens of thousands of times, which makes
+ // compile times of all `ide_*` and downstream crates suffer greatly.
+ storage: ManuallyDrop<salsa::Storage<RootDatabase>>,
+}
+
+impl Drop for RootDatabase {
+ fn drop(&mut self) {
+ unsafe { ManuallyDrop::drop(&mut self.storage) };
+ }
+}
+
+impl fmt::Debug for RootDatabase {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("RootDatabase").finish()
+ }
+}
+
+impl Upcast<dyn AstDatabase> for RootDatabase {
+ fn upcast(&self) -> &(dyn AstDatabase + 'static) {
+ &*self
+ }
+}
+
+impl Upcast<dyn DefDatabase> for RootDatabase {
+ fn upcast(&self) -> &(dyn DefDatabase + 'static) {
+ &*self
+ }
+}
+
+impl Upcast<dyn HirDatabase> for RootDatabase {
+ fn upcast(&self) -> &(dyn HirDatabase + 'static) {
+ &*self
+ }
+}
+
+impl FileLoader for RootDatabase {
+ fn file_text(&self, file_id: FileId) -> Arc<String> {
+ FileLoaderDelegate(self).file_text(file_id)
+ }
+ fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId> {
+ FileLoaderDelegate(self).resolve_path(path)
+ }
+ fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>> {
+ FileLoaderDelegate(self).relevant_crates(file_id)
+ }
+}
+
+impl salsa::Database for RootDatabase {}
+
+impl Default for RootDatabase {
+ fn default() -> RootDatabase {
+ RootDatabase::new(None)
+ }
+}
+
+impl RootDatabase {
+ pub fn new(lru_capacity: Option<usize>) -> RootDatabase {
+ let mut db = RootDatabase { storage: ManuallyDrop::new(salsa::Storage::default()) };
+ db.set_crate_graph_with_durability(Default::default(), Durability::HIGH);
+ db.set_local_roots_with_durability(Default::default(), Durability::HIGH);
+ db.set_library_roots_with_durability(Default::default(), Durability::HIGH);
+ db.set_enable_proc_attr_macros(false);
+ db.update_lru_capacity(lru_capacity);
+ db
+ }
+
+ pub fn update_lru_capacity(&mut self, lru_capacity: Option<usize>) {
+ let lru_capacity = lru_capacity.unwrap_or(base_db::DEFAULT_LRU_CAP);
+ base_db::ParseQuery.in_db_mut(self).set_lru_capacity(lru_capacity);
+ hir::db::ParseMacroExpansionQuery.in_db_mut(self).set_lru_capacity(lru_capacity);
+ hir::db::MacroExpandQuery.in_db_mut(self).set_lru_capacity(lru_capacity);
+ }
+}
+
+impl salsa::ParallelDatabase for RootDatabase {
+ fn snapshot(&self) -> salsa::Snapshot<RootDatabase> {
+ salsa::Snapshot::new(RootDatabase { storage: ManuallyDrop::new(self.storage.snapshot()) })
+ }
+}
+
+#[salsa::query_group(LineIndexDatabaseStorage)]
+pub trait LineIndexDatabase: base_db::SourceDatabase {
+ fn line_index(&self, file_id: FileId) -> Arc<LineIndex>;
+}
+
+fn line_index(db: &dyn LineIndexDatabase, file_id: FileId) -> Arc<LineIndex> {
+ let text = db.file_text(file_id);
+ Arc::new(LineIndex::new(&*text))
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub enum SymbolKind {
+ Attribute,
+ BuiltinAttr,
+ Const,
+ ConstParam,
+ Derive,
+ DeriveHelper,
+ Enum,
+ Field,
+ Function,
+ Impl,
+ Label,
+ LifetimeParam,
+ Local,
+ Macro,
+ Module,
+ SelfParam,
+ SelfType,
+ Static,
+ Struct,
+ ToolModule,
+ Trait,
+ TypeAlias,
+ TypeParam,
+ Union,
+ ValueParam,
+ Variant,
+}
+
+impl From<hir::MacroKind> for SymbolKind {
+ fn from(it: hir::MacroKind) -> Self {
+ match it {
+ hir::MacroKind::Declarative | hir::MacroKind::BuiltIn | hir::MacroKind::ProcMacro => {
+ SymbolKind::Macro
+ }
+ hir::MacroKind::Derive => SymbolKind::Derive,
+ hir::MacroKind::Attr => SymbolKind::Attribute,
+ }
+ }
+}
+
+impl From<FileSymbolKind> for SymbolKind {
+ fn from(it: FileSymbolKind) -> Self {
+ match it {
+ FileSymbolKind::Const => SymbolKind::Const,
+ FileSymbolKind::Enum => SymbolKind::Enum,
+ FileSymbolKind::Function => SymbolKind::Function,
+ FileSymbolKind::Macro => SymbolKind::Macro,
+ FileSymbolKind::Module => SymbolKind::Module,
+ FileSymbolKind::Static => SymbolKind::Static,
+ FileSymbolKind::Struct => SymbolKind::Struct,
+ FileSymbolKind::Trait => SymbolKind::Trait,
+ FileSymbolKind::TypeAlias => SymbolKind::TypeAlias,
+ FileSymbolKind::Union => SymbolKind::Union,
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct SnippetCap {
+ _private: (),
+}
+
+impl SnippetCap {
+ pub const fn new(allow_snippets: bool) -> Option<SnippetCap> {
+ if allow_snippets {
+ Some(SnippetCap { _private: () })
+ } else {
+ None
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ mod sourcegen_lints;
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/line_index.rs b/src/tools/rust-analyzer/crates/ide-db/src/line_index.rs
new file mode 100644
index 000000000..68ad07ee8
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/line_index.rs
@@ -0,0 +1,300 @@
+//! `LineIndex` maps flat `TextSize` offsets into `(Line, Column)`
+//! representation.
+use std::{iter, mem};
+
+use rustc_hash::FxHashMap;
+use syntax::{TextRange, TextSize};
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct LineIndex {
+ /// Offset the the beginning of each line, zero-based
+ pub(crate) newlines: Vec<TextSize>,
+ /// List of non-ASCII characters on each line
+ pub(crate) utf16_lines: FxHashMap<u32, Vec<Utf16Char>>,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub struct LineColUtf16 {
+ /// Zero-based
+ pub line: u32,
+ /// Zero-based
+ pub col: u32,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub struct LineCol {
+ /// Zero-based
+ pub line: u32,
+ /// Zero-based utf8 offset
+ pub col: u32,
+}
+
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+pub(crate) struct Utf16Char {
+ /// Start offset of a character inside a line, zero-based
+ pub(crate) start: TextSize,
+ /// End offset of a character inside a line, zero-based
+ pub(crate) end: TextSize,
+}
+
+impl Utf16Char {
+ /// Returns the length in 8-bit UTF-8 code units.
+ fn len(&self) -> TextSize {
+ self.end - self.start
+ }
+
+ /// Returns the length in 16-bit UTF-16 code units.
+ fn len_utf16(&self) -> usize {
+ if self.len() == TextSize::from(4) {
+ 2
+ } else {
+ 1
+ }
+ }
+}
+
+impl LineIndex {
+ pub fn new(text: &str) -> LineIndex {
+ let mut utf16_lines = FxHashMap::default();
+ let mut utf16_chars = Vec::new();
+
+ let mut newlines = vec![0.into()];
+ let mut curr_row @ mut curr_col = 0.into();
+ let mut line = 0;
+ for c in text.chars() {
+ let c_len = TextSize::of(c);
+ curr_row += c_len;
+ if c == '\n' {
+ newlines.push(curr_row);
+
+ // Save any utf-16 characters seen in the previous line
+ if !utf16_chars.is_empty() {
+ utf16_lines.insert(line, mem::take(&mut utf16_chars));
+ }
+
+ // Prepare for processing the next line
+ curr_col = 0.into();
+ line += 1;
+ continue;
+ }
+
+ if !c.is_ascii() {
+ utf16_chars.push(Utf16Char { start: curr_col, end: curr_col + c_len });
+ }
+
+ curr_col += c_len;
+ }
+
+ // Save any utf-16 characters seen in the last line
+ if !utf16_chars.is_empty() {
+ utf16_lines.insert(line, utf16_chars);
+ }
+
+ LineIndex { newlines, utf16_lines }
+ }
+
+ pub fn line_col(&self, offset: TextSize) -> LineCol {
+ let line = self.newlines.partition_point(|&it| it <= offset) - 1;
+ let line_start_offset = self.newlines[line];
+ let col = offset - line_start_offset;
+ LineCol { line: line as u32, col: col.into() }
+ }
+
+ pub fn offset(&self, line_col: LineCol) -> Option<TextSize> {
+ self.newlines
+ .get(line_col.line as usize)
+ .map(|offset| offset + TextSize::from(line_col.col))
+ }
+
+ pub fn to_utf16(&self, line_col: LineCol) -> LineColUtf16 {
+ let col = self.utf8_to_utf16_col(line_col.line, line_col.col.into());
+ LineColUtf16 { line: line_col.line, col: col as u32 }
+ }
+
+ pub fn to_utf8(&self, line_col: LineColUtf16) -> LineCol {
+ let col = self.utf16_to_utf8_col(line_col.line, line_col.col);
+ LineCol { line: line_col.line, col: col.into() }
+ }
+
+ pub fn lines(&self, range: TextRange) -> impl Iterator<Item = TextRange> + '_ {
+ let lo = self.newlines.partition_point(|&it| it < range.start());
+ let hi = self.newlines.partition_point(|&it| it <= range.end());
+ let all = iter::once(range.start())
+ .chain(self.newlines[lo..hi].iter().copied())
+ .chain(iter::once(range.end()));
+
+ all.clone()
+ .zip(all.skip(1))
+ .map(|(lo, hi)| TextRange::new(lo, hi))
+ .filter(|it| !it.is_empty())
+ }
+
+ fn utf8_to_utf16_col(&self, line: u32, col: TextSize) -> usize {
+ let mut res: usize = col.into();
+ if let Some(utf16_chars) = self.utf16_lines.get(&line) {
+ for c in utf16_chars {
+ if c.end <= col {
+ res -= usize::from(c.len()) - c.len_utf16();
+ } else {
+ // From here on, all utf16 characters come *after* the character we are mapping,
+ // so we don't need to take them into account
+ break;
+ }
+ }
+ }
+ res
+ }
+
+ fn utf16_to_utf8_col(&self, line: u32, mut col: u32) -> TextSize {
+ if let Some(utf16_chars) = self.utf16_lines.get(&line) {
+ for c in utf16_chars {
+ if col > u32::from(c.start) {
+ col += u32::from(c.len()) - c.len_utf16() as u32;
+ } else {
+ // From here on, all utf16 characters come *after* the character we are mapping,
+ // so we don't need to take them into account
+ break;
+ }
+ }
+ }
+
+ col.into()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_line_index() {
+ let text = "hello\nworld";
+ let table = [
+ (00, 0, 0),
+ (01, 0, 1),
+ (05, 0, 5),
+ (06, 1, 0),
+ (07, 1, 1),
+ (08, 1, 2),
+ (10, 1, 4),
+ (11, 1, 5),
+ (12, 1, 6),
+ ];
+
+ let index = LineIndex::new(text);
+ for &(offset, line, col) in &table {
+ assert_eq!(index.line_col(offset.into()), LineCol { line, col });
+ }
+
+ let text = "\nhello\nworld";
+ let table = [(0, 0, 0), (1, 1, 0), (2, 1, 1), (6, 1, 5), (7, 2, 0)];
+ let index = LineIndex::new(text);
+ for &(offset, line, col) in &table {
+ assert_eq!(index.line_col(offset.into()), LineCol { line, col });
+ }
+ }
+
+ #[test]
+ fn test_char_len() {
+ assert_eq!('メ'.len_utf8(), 3);
+ assert_eq!('メ'.len_utf16(), 1);
+ }
+
+ #[test]
+ fn test_empty_index() {
+ let col_index = LineIndex::new(
+ "
+const C: char = 'x';
+",
+ );
+ assert_eq!(col_index.utf16_lines.len(), 0);
+ }
+
+ #[test]
+ fn test_single_char() {
+ let col_index = LineIndex::new(
+ "
+const C: char = 'メ';
+",
+ );
+
+ assert_eq!(col_index.utf16_lines.len(), 1);
+ assert_eq!(col_index.utf16_lines[&1].len(), 1);
+ assert_eq!(col_index.utf16_lines[&1][0], Utf16Char { start: 17.into(), end: 20.into() });
+
+ // UTF-8 to UTF-16, no changes
+ assert_eq!(col_index.utf8_to_utf16_col(1, 15.into()), 15);
+
+ // UTF-8 to UTF-16
+ assert_eq!(col_index.utf8_to_utf16_col(1, 22.into()), 20);
+
+ // UTF-16 to UTF-8, no changes
+ assert_eq!(col_index.utf16_to_utf8_col(1, 15), TextSize::from(15));
+
+ // UTF-16 to UTF-8
+ assert_eq!(col_index.utf16_to_utf8_col(1, 19), TextSize::from(21));
+
+ let col_index = LineIndex::new("a𐐏b");
+ assert_eq!(col_index.utf16_to_utf8_col(0, 3), TextSize::from(5));
+ }
+
+ #[test]
+ fn test_string() {
+ let col_index = LineIndex::new(
+ "
+const C: char = \"メ メ\";
+",
+ );
+
+ assert_eq!(col_index.utf16_lines.len(), 1);
+ assert_eq!(col_index.utf16_lines[&1].len(), 2);
+ assert_eq!(col_index.utf16_lines[&1][0], Utf16Char { start: 17.into(), end: 20.into() });
+ assert_eq!(col_index.utf16_lines[&1][1], Utf16Char { start: 21.into(), end: 24.into() });
+
+ // UTF-8 to UTF-16
+ assert_eq!(col_index.utf8_to_utf16_col(1, 15.into()), 15);
+
+ assert_eq!(col_index.utf8_to_utf16_col(1, 21.into()), 19);
+ assert_eq!(col_index.utf8_to_utf16_col(1, 25.into()), 21);
+
+ assert!(col_index.utf8_to_utf16_col(2, 15.into()) == 15);
+
+ // UTF-16 to UTF-8
+ assert_eq!(col_index.utf16_to_utf8_col(1, 15), TextSize::from(15));
+
+ // メ UTF-8: 0xE3 0x83 0xA1, UTF-16: 0x30E1
+ assert_eq!(col_index.utf16_to_utf8_col(1, 17), TextSize::from(17)); // first メ at 17..20
+ assert_eq!(col_index.utf16_to_utf8_col(1, 18), TextSize::from(20)); // space
+ assert_eq!(col_index.utf16_to_utf8_col(1, 19), TextSize::from(21)); // second メ at 21..24
+
+ assert_eq!(col_index.utf16_to_utf8_col(2, 15), TextSize::from(15));
+ }
+
+ #[test]
+ fn test_splitlines() {
+ fn r(lo: u32, hi: u32) -> TextRange {
+ TextRange::new(lo.into(), hi.into())
+ }
+
+ let text = "a\nbb\nccc\n";
+ let line_index = LineIndex::new(text);
+
+ let actual = line_index.lines(r(0, 9)).collect::<Vec<_>>();
+ let expected = vec![r(0, 2), r(2, 5), r(5, 9)];
+ assert_eq!(actual, expected);
+
+ let text = "";
+ let line_index = LineIndex::new(text);
+
+ let actual = line_index.lines(r(0, 0)).collect::<Vec<_>>();
+ let expected = vec![];
+ assert_eq!(actual, expected);
+
+ let text = "\n";
+ let line_index = LineIndex::new(text);
+
+ let actual = line_index.lines(r(0, 1)).collect::<Vec<_>>();
+ let expected = vec![r(0, 1)];
+ assert_eq!(actual, expected)
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs b/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs
new file mode 100644
index 000000000..40af9e6fe
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs
@@ -0,0 +1,287 @@
+//! See [`PathTransform`].
+
+use crate::helpers::mod_path_to_ast;
+use either::Either;
+use hir::{AsAssocItem, HirDisplay, SemanticsScope};
+use rustc_hash::FxHashMap;
+use syntax::{
+ ast::{self, AstNode},
+ ted, SyntaxNode,
+};
+
+/// `PathTransform` substitutes path in SyntaxNodes in bulk.
+///
+/// This is mostly useful for IDE code generation. If you paste some existing
+/// code into a new context (for example, to add method overrides to an `impl`
+/// block), you generally want to appropriately qualify the names, and sometimes
+/// you might want to substitute generic parameters as well:
+///
+/// ```
+/// mod x {
+/// pub struct A<V>;
+/// pub trait T<U> { fn foo(&self, _: U) -> A<U>; }
+/// }
+///
+/// mod y {
+/// use x::T;
+///
+/// impl T<()> for () {
+/// // If we invoke **Add Missing Members** here, we want to copy-paste `foo`.
+/// // But we want a slightly-modified version of it:
+/// fn foo(&self, _: ()) -> x::A<()> {}
+/// }
+/// }
+/// ```
+pub struct PathTransform<'a> {
+ generic_def: hir::GenericDef,
+ substs: Vec<ast::Type>,
+ target_scope: &'a SemanticsScope<'a>,
+ source_scope: &'a SemanticsScope<'a>,
+}
+
+impl<'a> PathTransform<'a> {
+ pub fn trait_impl(
+ target_scope: &'a SemanticsScope<'a>,
+ source_scope: &'a SemanticsScope<'a>,
+ trait_: hir::Trait,
+ impl_: ast::Impl,
+ ) -> PathTransform<'a> {
+ PathTransform {
+ source_scope,
+ target_scope,
+ generic_def: trait_.into(),
+ substs: get_syntactic_substs(impl_).unwrap_or_default(),
+ }
+ }
+
+ pub fn function_call(
+ target_scope: &'a SemanticsScope<'a>,
+ source_scope: &'a SemanticsScope<'a>,
+ function: hir::Function,
+ generic_arg_list: ast::GenericArgList,
+ ) -> PathTransform<'a> {
+ PathTransform {
+ source_scope,
+ target_scope,
+ generic_def: function.into(),
+ substs: get_type_args_from_arg_list(generic_arg_list).unwrap_or_default(),
+ }
+ }
+
+ pub fn apply(&self, syntax: &SyntaxNode) {
+ self.build_ctx().apply(syntax)
+ }
+
+ fn build_ctx(&self) -> Ctx<'a> {
+ let db = self.source_scope.db;
+ let target_module = self.target_scope.module();
+ let source_module = self.source_scope.module();
+ let skip = match self.generic_def {
+ // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky
+ hir::GenericDef::Trait(_) => 1,
+ _ => 0,
+ };
+ let substs_by_param: FxHashMap<_, _> = self
+ .generic_def
+ .type_params(db)
+ .into_iter()
+ .skip(skip)
+ // The actual list of trait type parameters may be longer than the one
+ // used in the `impl` block due to trailing default type parameters.
+ // For that case we extend the `substs` with an empty iterator so we
+ // can still hit those trailing values and check if they actually have
+ // a default type. If they do, go for that type from `hir` to `ast` so
+ // the resulting change can be applied correctly.
+ .zip(self.substs.iter().map(Some).chain(std::iter::repeat(None)))
+ .filter_map(|(k, v)| match k.split(db) {
+ Either::Left(_) => None,
+ Either::Right(t) => match v {
+ Some(v) => Some((k, v.clone())),
+ None => {
+ let default = t.default(db)?;
+ Some((
+ k,
+ ast::make::ty(
+ &default.display_source_code(db, source_module.into()).ok()?,
+ ),
+ ))
+ }
+ },
+ })
+ .collect();
+ Ctx { substs: substs_by_param, target_module, source_scope: self.source_scope }
+ }
+}
+
+struct Ctx<'a> {
+ substs: FxHashMap<hir::TypeOrConstParam, ast::Type>,
+ target_module: hir::Module,
+ source_scope: &'a SemanticsScope<'a>,
+}
+
+impl<'a> Ctx<'a> {
+ fn apply(&self, item: &SyntaxNode) {
+ // `transform_path` may update a node's parent and that would break the
+ // tree traversal. Thus all paths in the tree are collected into a vec
+ // so that such operation is safe.
+ let paths = item
+ .preorder()
+ .filter_map(|event| match event {
+ syntax::WalkEvent::Enter(_) => None,
+ syntax::WalkEvent::Leave(node) => Some(node),
+ })
+ .filter_map(ast::Path::cast)
+ .collect::<Vec<_>>();
+
+ for path in paths {
+ self.transform_path(path);
+ }
+ }
+ fn transform_path(&self, path: ast::Path) -> Option<()> {
+ if path.qualifier().is_some() {
+ return None;
+ }
+ if path.segment().map_or(false, |s| {
+ s.param_list().is_some() || (s.self_token().is_some() && path.parent_path().is_none())
+ }) {
+ // don't try to qualify `Fn(Foo) -> Bar` paths, they are in prelude anyway
+ // don't try to qualify sole `self` either, they are usually locals, but are returned as modules due to namespace clashing
+ return None;
+ }
+
+ let resolution = self.source_scope.speculative_resolve(&path)?;
+
+ match resolution {
+ hir::PathResolution::TypeParam(tp) => {
+ if let Some(subst) = self.substs.get(&tp.merge()) {
+ let parent = path.syntax().parent()?;
+ if let Some(parent) = ast::Path::cast(parent.clone()) {
+ // Path inside path means that there is an associated
+ // type/constant on the type parameter. It is necessary
+ // to fully qualify the type with `as Trait`. Even
+ // though it might be unnecessary if `subst` is generic
+ // type, always fully qualifying the path is safer
+ // because of potential clash of associated types from
+ // multiple traits
+
+ let trait_ref = find_trait_for_assoc_item(
+ self.source_scope,
+ tp,
+ parent.segment()?.name_ref()?,
+ )
+ .and_then(|trait_ref| {
+ let found_path = self.target_module.find_use_path(
+ self.source_scope.db.upcast(),
+ hir::ModuleDef::Trait(trait_ref),
+ )?;
+ match ast::make::ty_path(mod_path_to_ast(&found_path)) {
+ ast::Type::PathType(path_ty) => Some(path_ty),
+ _ => None,
+ }
+ });
+
+ let segment = ast::make::path_segment_ty(subst.clone(), trait_ref);
+ let qualified =
+ ast::make::path_from_segments(std::iter::once(segment), false);
+ ted::replace(path.syntax(), qualified.clone_for_update().syntax());
+ } else if let Some(path_ty) = ast::PathType::cast(parent) {
+ ted::replace(
+ path_ty.syntax(),
+ subst.clone_subtree().clone_for_update().syntax(),
+ );
+ } else {
+ ted::replace(
+ path.syntax(),
+ subst.clone_subtree().clone_for_update().syntax(),
+ );
+ }
+ }
+ }
+ hir::PathResolution::Def(def) if def.as_assoc_item(self.source_scope.db).is_none() => {
+ if let hir::ModuleDef::Trait(_) = def {
+ if matches!(path.segment()?.kind()?, ast::PathSegmentKind::Type { .. }) {
+ // `speculative_resolve` resolves segments like `<T as
+ // Trait>` into `Trait`, but just the trait name should
+ // not be used as the replacement of the original
+ // segment.
+ return None;
+ }
+ }
+
+ let found_path =
+ self.target_module.find_use_path(self.source_scope.db.upcast(), def)?;
+ let res = mod_path_to_ast(&found_path).clone_for_update();
+ if let Some(args) = path.segment().and_then(|it| it.generic_arg_list()) {
+ if let Some(segment) = res.segment() {
+ let old = segment.get_or_create_generic_arg_list();
+ ted::replace(old.syntax(), args.clone_subtree().syntax().clone_for_update())
+ }
+ }
+ ted::replace(path.syntax(), res.syntax())
+ }
+ hir::PathResolution::Local(_)
+ | hir::PathResolution::ConstParam(_)
+ | hir::PathResolution::SelfType(_)
+ | hir::PathResolution::Def(_)
+ | hir::PathResolution::BuiltinAttr(_)
+ | hir::PathResolution::ToolModule(_)
+ | hir::PathResolution::DeriveHelper(_) => (),
+ }
+ Some(())
+ }
+}
+
+// FIXME: It would probably be nicer if we could get this via HIR (i.e. get the
+// trait ref, and then go from the types in the substs back to the syntax).
+fn get_syntactic_substs(impl_def: ast::Impl) -> Option<Vec<ast::Type>> {
+ let target_trait = impl_def.trait_()?;
+ let path_type = match target_trait {
+ ast::Type::PathType(path) => path,
+ _ => return None,
+ };
+ let generic_arg_list = path_type.path()?.segment()?.generic_arg_list()?;
+
+ get_type_args_from_arg_list(generic_arg_list)
+}
+
+fn get_type_args_from_arg_list(generic_arg_list: ast::GenericArgList) -> Option<Vec<ast::Type>> {
+ let mut result = Vec::new();
+ for generic_arg in generic_arg_list.generic_args() {
+ if let ast::GenericArg::TypeArg(type_arg) = generic_arg {
+ result.push(type_arg.ty()?)
+ }
+ }
+
+ Some(result)
+}
+
+fn find_trait_for_assoc_item(
+ scope: &SemanticsScope<'_>,
+ type_param: hir::TypeParam,
+ assoc_item: ast::NameRef,
+) -> Option<hir::Trait> {
+ let db = scope.db;
+ let trait_bounds = type_param.trait_bounds(db);
+
+ let assoc_item_name = assoc_item.text();
+
+ for trait_ in trait_bounds {
+ let names = trait_.items(db).into_iter().filter_map(|item| match item {
+ hir::AssocItem::TypeAlias(ta) => Some(ta.name(db)),
+ hir::AssocItem::Const(cst) => cst.name(db),
+ _ => None,
+ });
+
+ for name in names {
+ if assoc_item_name.as_str() == name.as_text()?.as_str() {
+ // It is fine to return the first match because in case of
+ // multiple possibilities, the exact trait must be disambiguated
+ // in the definition of trait being implemented, so this search
+ // should not be needed.
+ return Some(trait_);
+ }
+ }
+ }
+
+ None
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/rename.rs b/src/tools/rust-analyzer/crates/ide-db/src/rename.rs
new file mode 100644
index 000000000..517fe3f24
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/rename.rs
@@ -0,0 +1,540 @@
+//! Rename infrastructure for rust-analyzer. It is used primarily for the
+//! literal "rename" in the ide (look for tests there), but it is also available
+//! as a general-purpose service. For example, it is used by the fix for the
+//! "incorrect case" diagnostic.
+//!
+//! It leverages the [`crate::search`] functionality to find what needs to be
+//! renamed. The actual renames are tricky -- field shorthands need special
+//! attention, and, when renaming modules, you also want to rename files on the
+//! file system.
+//!
+//! Another can of worms are macros:
+//!
+//! ```ignore
+//! macro_rules! m { () => { fn f() {} } }
+//! m!();
+//! fn main() {
+//! f() // <- rename me
+//! }
+//! ```
+//!
+//! The correct behavior in such cases is probably to show a dialog to the user.
+//! Our current behavior is ¯\_(ツ)_/¯.
+use std::fmt;
+
+use base_db::{AnchoredPathBuf, FileId, FileRange};
+use either::Either;
+use hir::{FieldSource, HasSource, InFile, ModuleSource, Semantics};
+use stdx::never;
+use syntax::{
+ ast::{self, HasName},
+ AstNode, SyntaxKind, TextRange, T,
+};
+use text_edit::{TextEdit, TextEditBuilder};
+
+use crate::{
+ defs::Definition,
+ search::FileReference,
+ source_change::{FileSystemEdit, SourceChange},
+ syntax_helpers::node_ext::expr_as_name_ref,
+ traits::convert_to_def_in_trait,
+ RootDatabase,
+};
+
+pub type Result<T, E = RenameError> = std::result::Result<T, E>;
+
+#[derive(Debug)]
+pub struct RenameError(pub String);
+
+impl fmt::Display for RenameError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Display::fmt(&self.0, f)
+ }
+}
+
+#[macro_export]
+macro_rules! _format_err {
+ ($fmt:expr) => { RenameError(format!($fmt)) };
+ ($fmt:expr, $($arg:tt)+) => { RenameError(format!($fmt, $($arg)+)) }
+}
+pub use _format_err as format_err;
+
+#[macro_export]
+macro_rules! _bail {
+ ($($tokens:tt)*) => { return Err(format_err!($($tokens)*)) }
+}
+pub use _bail as bail;
+
+impl Definition {
+ pub fn rename(
+ &self,
+ sema: &Semantics<'_, RootDatabase>,
+ new_name: &str,
+ ) -> Result<SourceChange> {
+ match *self {
+ Definition::Module(module) => rename_mod(sema, module, new_name),
+ Definition::BuiltinType(_) => {
+ bail!("Cannot rename builtin type")
+ }
+ Definition::SelfType(_) => bail!("Cannot rename `Self`"),
+ def => rename_reference(sema, def, new_name),
+ }
+ }
+
+ /// Textual range of the identifier which will change when renaming this
+ /// `Definition`. Note that some definitions, like buitin types, can't be
+ /// renamed.
+ pub fn range_for_rename(self, sema: &Semantics<'_, RootDatabase>) -> Option<FileRange> {
+ let res = match self {
+ Definition::Macro(mac) => {
+ let src = mac.source(sema.db)?;
+ let name = match &src.value {
+ Either::Left(it) => it.name()?,
+ Either::Right(it) => it.name()?,
+ };
+ src.with_value(name.syntax()).original_file_range_opt(sema.db)
+ }
+ Definition::Field(field) => {
+ let src = field.source(sema.db)?;
+ match &src.value {
+ FieldSource::Named(record_field) => {
+ let name = record_field.name()?;
+ src.with_value(name.syntax()).original_file_range_opt(sema.db)
+ }
+ FieldSource::Pos(_) => None,
+ }
+ }
+ Definition::Module(module) => {
+ let src = module.declaration_source(sema.db)?;
+ let name = src.value.name()?;
+ src.with_value(name.syntax()).original_file_range_opt(sema.db)
+ }
+ Definition::Function(it) => name_range(it, sema),
+ Definition::Adt(adt) => match adt {
+ hir::Adt::Struct(it) => name_range(it, sema),
+ hir::Adt::Union(it) => name_range(it, sema),
+ hir::Adt::Enum(it) => name_range(it, sema),
+ },
+ Definition::Variant(it) => name_range(it, sema),
+ Definition::Const(it) => name_range(it, sema),
+ Definition::Static(it) => name_range(it, sema),
+ Definition::Trait(it) => name_range(it, sema),
+ Definition::TypeAlias(it) => name_range(it, sema),
+ Definition::Local(local) => {
+ let src = local.source(sema.db);
+ let name = match &src.value {
+ Either::Left(bind_pat) => bind_pat.name()?,
+ Either::Right(_) => return None,
+ };
+ src.with_value(name.syntax()).original_file_range_opt(sema.db)
+ }
+ Definition::GenericParam(generic_param) => match generic_param {
+ hir::GenericParam::LifetimeParam(lifetime_param) => {
+ let src = lifetime_param.source(sema.db)?;
+ src.with_value(src.value.lifetime()?.syntax()).original_file_range_opt(sema.db)
+ }
+ _ => {
+ let x = match generic_param {
+ hir::GenericParam::TypeParam(it) => it.merge(),
+ hir::GenericParam::ConstParam(it) => it.merge(),
+ hir::GenericParam::LifetimeParam(_) => return None,
+ };
+ let src = x.source(sema.db)?;
+ let name = match &src.value {
+ Either::Left(x) => x.name()?,
+ Either::Right(_) => return None,
+ };
+ src.with_value(name.syntax()).original_file_range_opt(sema.db)
+ }
+ },
+ Definition::Label(label) => {
+ let src = label.source(sema.db);
+ let lifetime = src.value.lifetime()?;
+ src.with_value(lifetime.syntax()).original_file_range_opt(sema.db)
+ }
+ Definition::BuiltinType(_) => return None,
+ Definition::SelfType(_) => return None,
+ Definition::BuiltinAttr(_) => return None,
+ Definition::ToolModule(_) => return None,
+ // FIXME: This should be doable in theory
+ Definition::DeriveHelper(_) => return None,
+ };
+ return res;
+
+ fn name_range<D>(def: D, sema: &Semantics<'_, RootDatabase>) -> Option<FileRange>
+ where
+ D: HasSource,
+ D::Ast: ast::HasName,
+ {
+ let src = def.source(sema.db)?;
+ let name = src.value.name()?;
+ src.with_value(name.syntax()).original_file_range_opt(sema.db)
+ }
+ }
+}
+
+fn rename_mod(
+ sema: &Semantics<'_, RootDatabase>,
+ module: hir::Module,
+ new_name: &str,
+) -> Result<SourceChange> {
+ if IdentifierKind::classify(new_name)? != IdentifierKind::Ident {
+ bail!("Invalid name `{0}`: cannot rename module to {0}", new_name);
+ }
+
+ let mut source_change = SourceChange::default();
+
+ if module.is_crate_root(sema.db) {
+ return Ok(source_change);
+ }
+
+ let InFile { file_id, value: def_source } = module.definition_source(sema.db);
+ if let ModuleSource::SourceFile(..) = def_source {
+ let anchor = file_id.original_file(sema.db);
+
+ let is_mod_rs = module.is_mod_rs(sema.db);
+ let has_detached_child = module.children(sema.db).any(|child| !child.is_inline(sema.db));
+
+ // Module exists in a named file
+ if !is_mod_rs {
+ let path = format!("{}.rs", new_name);
+ let dst = AnchoredPathBuf { anchor, path };
+ source_change.push_file_system_edit(FileSystemEdit::MoveFile { src: anchor, dst })
+ }
+
+ // Rename the dir if:
+ // - Module source is in mod.rs
+ // - Module has submodules defined in separate files
+ let dir_paths = match (is_mod_rs, has_detached_child, module.name(sema.db)) {
+ // Go up one level since the anchor is inside the dir we're trying to rename
+ (true, _, Some(mod_name)) => {
+ Some((format!("../{}", mod_name), format!("../{}", new_name)))
+ }
+ // The anchor is on the same level as target dir
+ (false, true, Some(mod_name)) => Some((mod_name.to_string(), new_name.to_string())),
+ _ => None,
+ };
+
+ if let Some((src, dst)) = dir_paths {
+ let src = AnchoredPathBuf { anchor, path: src };
+ let dst = AnchoredPathBuf { anchor, path: dst };
+ source_change.push_file_system_edit(FileSystemEdit::MoveDir {
+ src,
+ src_id: anchor,
+ dst,
+ })
+ }
+ }
+
+ if let Some(src) = module.declaration_source(sema.db) {
+ let file_id = src.file_id.original_file(sema.db);
+ match src.value.name() {
+ Some(name) => {
+ if let Some(file_range) =
+ src.with_value(name.syntax()).original_file_range_opt(sema.db)
+ {
+ source_change.insert_source_edit(
+ file_id,
+ TextEdit::replace(file_range.range, new_name.to_string()),
+ )
+ };
+ }
+ _ => never!("Module source node is missing a name"),
+ }
+ }
+
+ let def = Definition::Module(module);
+ let usages = def.usages(sema).all();
+ let ref_edits = usages.iter().map(|(&file_id, references)| {
+ (file_id, source_edit_from_references(references, def, new_name))
+ });
+ source_change.extend(ref_edits);
+
+ Ok(source_change)
+}
+
+fn rename_reference(
+ sema: &Semantics<'_, RootDatabase>,
+ def: Definition,
+ new_name: &str,
+) -> Result<SourceChange> {
+ let ident_kind = IdentifierKind::classify(new_name)?;
+
+ if matches!(
+ def,
+ Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_)
+ ) {
+ match ident_kind {
+ IdentifierKind::Ident | IdentifierKind::Underscore => {
+ cov_mark::hit!(rename_not_a_lifetime_ident_ref);
+ bail!("Invalid name `{}`: not a lifetime identifier", new_name);
+ }
+ IdentifierKind::Lifetime => cov_mark::hit!(rename_lifetime),
+ }
+ } else {
+ match ident_kind {
+ IdentifierKind::Lifetime => {
+ cov_mark::hit!(rename_not_an_ident_ref);
+ bail!("Invalid name `{}`: not an identifier", new_name);
+ }
+ IdentifierKind::Ident => cov_mark::hit!(rename_non_local),
+ IdentifierKind::Underscore => (),
+ }
+ }
+
+ let def = convert_to_def_in_trait(sema.db, def);
+ let usages = def.usages(sema).all();
+
+ if !usages.is_empty() && ident_kind == IdentifierKind::Underscore {
+ cov_mark::hit!(rename_underscore_multiple);
+ bail!("Cannot rename reference to `_` as it is being referenced multiple times");
+ }
+ let mut source_change = SourceChange::default();
+ source_change.extend(usages.iter().map(|(&file_id, references)| {
+ (file_id, source_edit_from_references(references, def, new_name))
+ }));
+
+ let mut insert_def_edit = |def| {
+ let (file_id, edit) = source_edit_from_def(sema, def, new_name)?;
+ source_change.insert_source_edit(file_id, edit);
+ Ok(())
+ };
+ match def {
+ Definition::Local(l) => l
+ .associated_locals(sema.db)
+ .iter()
+ .try_for_each(|&local| insert_def_edit(Definition::Local(local))),
+ def => insert_def_edit(def),
+ }?;
+ Ok(source_change)
+}
+
+pub fn source_edit_from_references(
+ references: &[FileReference],
+ def: Definition,
+ new_name: &str,
+) -> TextEdit {
+ let mut edit = TextEdit::builder();
+ // macros can cause multiple refs to occur for the same text range, so keep track of what we have edited so far
+ let mut edited_ranges = Vec::new();
+ for &FileReference { range, ref name, .. } in references {
+ let name_range = name.syntax().text_range();
+ if name_range.len() != range.len() {
+ // This usage comes from a different token kind that was downmapped to a NameLike in a macro
+ // Renaming this will most likely break things syntax-wise
+ continue;
+ }
+ let has_emitted_edit = match name {
+ // if the ranges differ then the node is inside a macro call, we can't really attempt
+ // to make special rewrites like shorthand syntax and such, so just rename the node in
+ // the macro input
+ ast::NameLike::NameRef(name_ref) if name_range == range => {
+ source_edit_from_name_ref(&mut edit, name_ref, new_name, def)
+ }
+ ast::NameLike::Name(name) if name_range == range => {
+ source_edit_from_name(&mut edit, name, new_name)
+ }
+ _ => false,
+ };
+ if !has_emitted_edit {
+ if !edited_ranges.contains(&range.start()) {
+ edit.replace(range, new_name.to_string());
+ edited_ranges.push(range.start());
+ }
+ }
+ }
+
+ edit.finish()
+}
+
+fn source_edit_from_name(edit: &mut TextEditBuilder, name: &ast::Name, new_name: &str) -> bool {
+ if ast::RecordPatField::for_field_name(name).is_some() {
+ if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) {
+ cov_mark::hit!(rename_record_pat_field_name_split);
+ // Foo { ref mut field } -> Foo { new_name: ref mut field }
+ // ^ insert `new_name: `
+
+ // FIXME: instead of splitting the shorthand, recursively trigger a rename of the
+ // other name https://github.com/rust-lang/rust-analyzer/issues/6547
+ edit.insert(ident_pat.syntax().text_range().start(), format!("{}: ", new_name));
+ return true;
+ }
+ }
+
+ false
+}
+
+fn source_edit_from_name_ref(
+ edit: &mut TextEditBuilder,
+ name_ref: &ast::NameRef,
+ new_name: &str,
+ def: Definition,
+) -> bool {
+ if name_ref.super_token().is_some() {
+ return true;
+ }
+
+ if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) {
+ let rcf_name_ref = record_field.name_ref();
+ let rcf_expr = record_field.expr();
+ match &(rcf_name_ref, rcf_expr.and_then(|it| expr_as_name_ref(&it))) {
+ // field: init-expr, check if we can use a field init shorthand
+ (Some(field_name), Some(init)) => {
+ if field_name == name_ref {
+ if init.text() == new_name {
+ cov_mark::hit!(test_rename_field_put_init_shorthand);
+ // Foo { field: local } -> Foo { local }
+ // ^^^^^^^ delete this
+
+ // same names, we can use a shorthand here instead.
+ // we do not want to erase attributes hence this range start
+ let s = field_name.syntax().text_range().start();
+ let e = init.syntax().text_range().start();
+ edit.delete(TextRange::new(s, e));
+ return true;
+ }
+ } else if init == name_ref {
+ if field_name.text() == new_name {
+ cov_mark::hit!(test_rename_local_put_init_shorthand);
+ // Foo { field: local } -> Foo { field }
+ // ^^^^^^^ delete this
+
+ // same names, we can use a shorthand here instead.
+ // we do not want to erase attributes hence this range start
+ let s = field_name.syntax().text_range().end();
+ let e = init.syntax().text_range().end();
+ edit.delete(TextRange::new(s, e));
+ return true;
+ }
+ }
+ }
+ // init shorthand
+ (None, Some(_)) if matches!(def, Definition::Field(_)) => {
+ cov_mark::hit!(test_rename_field_in_field_shorthand);
+ // Foo { field } -> Foo { new_name: field }
+ // ^ insert `new_name: `
+ let offset = name_ref.syntax().text_range().start();
+ edit.insert(offset, format!("{}: ", new_name));
+ return true;
+ }
+ (None, Some(_)) if matches!(def, Definition::Local(_)) => {
+ cov_mark::hit!(test_rename_local_in_field_shorthand);
+ // Foo { field } -> Foo { field: new_name }
+ // ^ insert `: new_name`
+ let offset = name_ref.syntax().text_range().end();
+ edit.insert(offset, format!(": {}", new_name));
+ return true;
+ }
+ _ => (),
+ }
+ } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) {
+ let rcf_name_ref = record_field.name_ref();
+ let rcf_pat = record_field.pat();
+ match (rcf_name_ref, rcf_pat) {
+ // field: rename
+ (Some(field_name), Some(ast::Pat::IdentPat(pat)))
+ if field_name == *name_ref && pat.at_token().is_none() =>
+ {
+ // field name is being renamed
+ if let Some(name) = pat.name() {
+ if name.text() == new_name {
+ cov_mark::hit!(test_rename_field_put_init_shorthand_pat);
+ // Foo { field: ref mut local } -> Foo { ref mut field }
+ // ^^^^^^^ delete this
+ // ^^^^^ replace this with `field`
+
+ // same names, we can use a shorthand here instead/
+ // we do not want to erase attributes hence this range start
+ let s = field_name.syntax().text_range().start();
+ let e = pat.syntax().text_range().start();
+ edit.delete(TextRange::new(s, e));
+ edit.replace(name.syntax().text_range(), new_name.to_string());
+ return true;
+ }
+ }
+ }
+ _ => (),
+ }
+ }
+ false
+}
+
+fn source_edit_from_def(
+ sema: &Semantics<'_, RootDatabase>,
+ def: Definition,
+ new_name: &str,
+) -> Result<(FileId, TextEdit)> {
+ let FileRange { file_id, range } = def
+ .range_for_rename(sema)
+ .ok_or_else(|| format_err!("No identifier available to rename"))?;
+
+ let mut edit = TextEdit::builder();
+ if let Definition::Local(local) = def {
+ if let Either::Left(pat) = local.source(sema.db).value {
+ // special cases required for renaming fields/locals in Record patterns
+ if let Some(pat_field) = pat.syntax().parent().and_then(ast::RecordPatField::cast) {
+ let name_range = pat.name().unwrap().syntax().text_range();
+ if let Some(name_ref) = pat_field.name_ref() {
+ if new_name == name_ref.text() && pat.at_token().is_none() {
+ // Foo { field: ref mut local } -> Foo { ref mut field }
+ // ^^^^^^ delete this
+ // ^^^^^ replace this with `field`
+ cov_mark::hit!(test_rename_local_put_init_shorthand_pat);
+ edit.delete(
+ name_ref
+ .syntax()
+ .text_range()
+ .cover_offset(pat.syntax().text_range().start()),
+ );
+ edit.replace(name_range, name_ref.text().to_string());
+ } else {
+ // Foo { field: ref mut local @ local 2} -> Foo { field: ref mut new_name @ local2 }
+ // Foo { field: ref mut local } -> Foo { field: ref mut new_name }
+ // ^^^^^ replace this with `new_name`
+ edit.replace(name_range, new_name.to_string());
+ }
+ } else {
+ // Foo { ref mut field } -> Foo { field: ref mut new_name }
+ // ^ insert `field: `
+ // ^^^^^ replace this with `new_name`
+ edit.insert(
+ pat.syntax().text_range().start(),
+ format!("{}: ", pat_field.field_name().unwrap()),
+ );
+ edit.replace(name_range, new_name.to_string());
+ }
+ }
+ }
+ }
+ if edit.is_empty() {
+ edit.replace(range, new_name.to_string());
+ }
+ Ok((file_id, edit.finish()))
+}
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum IdentifierKind {
+ Ident,
+ Lifetime,
+ Underscore,
+}
+
+impl IdentifierKind {
+ pub fn classify(new_name: &str) -> Result<IdentifierKind> {
+ match parser::LexedStr::single_token(new_name) {
+ Some(res) => match res {
+ (SyntaxKind::IDENT, _) => Ok(IdentifierKind::Ident),
+ (T![_], _) => Ok(IdentifierKind::Underscore),
+ (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => {
+ Ok(IdentifierKind::Lifetime)
+ }
+ (SyntaxKind::LIFETIME_IDENT, _) => {
+ bail!("Invalid name `{}`: not a lifetime identifier", new_name)
+ }
+ (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error),
+ (_, None) => bail!("Invalid name `{}`: not an identifier", new_name),
+ },
+ None => bail!("Invalid name `{}`: not an identifier", new_name),
+ }
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/rust_doc.rs b/src/tools/rust-analyzer/crates/ide-db/src/rust_doc.rs
new file mode 100644
index 000000000..e27e23867
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/rust_doc.rs
@@ -0,0 +1,34 @@
+//! Rustdoc specific doc comment handling
+
+// stripped down version of https://github.com/rust-lang/rust/blob/392ba2ba1a7d6c542d2459fb8133bebf62a4a423/src/librustdoc/html/markdown.rs#L810-L933
+pub fn is_rust_fence(s: &str) -> bool {
+ let mut seen_rust_tags = false;
+ let mut seen_other_tags = false;
+
+ let tokens = s
+ .trim()
+ .split(|c| c == ',' || c == ' ' || c == '\t')
+ .map(str::trim)
+ .filter(|t| !t.is_empty());
+
+ for token in tokens {
+ match token {
+ "should_panic" | "no_run" | "ignore" | "allow_fail" => {
+ seen_rust_tags = !seen_other_tags
+ }
+ "rust" => seen_rust_tags = true,
+ "test_harness" | "compile_fail" => seen_rust_tags = !seen_other_tags || seen_rust_tags,
+ x if x.starts_with("edition") => {}
+ x if x.starts_with('E') && x.len() == 5 => {
+ if x[1..].parse::<u32>().is_ok() {
+ seen_rust_tags = !seen_other_tags || seen_rust_tags;
+ } else {
+ seen_other_tags = true;
+ }
+ }
+ _ => seen_other_tags = true,
+ }
+ }
+
+ !seen_other_tags || seen_rust_tags
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/search.rs b/src/tools/rust-analyzer/crates/ide-db/src/search.rs
new file mode 100644
index 000000000..bd038cdaa
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/search.rs
@@ -0,0 +1,785 @@
+//! Implementation of find-usages functionality.
+//!
+//! It is based on the standard ide trick: first, we run a fast text search to
+//! get a super-set of matches. Then, we we confirm each match using precise
+//! name resolution.
+
+use std::{convert::TryInto, mem, sync::Arc};
+
+use base_db::{FileId, FileRange, SourceDatabase, SourceDatabaseExt};
+use hir::{DefWithBody, HasAttrs, HasSource, InFile, ModuleSource, Semantics, Visibility};
+use once_cell::unsync::Lazy;
+use rustc_hash::FxHashMap;
+use syntax::{ast, match_ast, AstNode, TextRange, TextSize};
+
+use crate::{
+ defs::{Definition, NameClass, NameRefClass},
+ traits::{as_trait_assoc_def, convert_to_def_in_trait},
+ RootDatabase,
+};
+
+#[derive(Debug, Default, Clone)]
+pub struct UsageSearchResult {
+ pub references: FxHashMap<FileId, Vec<FileReference>>,
+}
+
+impl UsageSearchResult {
+ pub fn is_empty(&self) -> bool {
+ self.references.is_empty()
+ }
+
+ pub fn len(&self) -> usize {
+ self.references.len()
+ }
+
+ pub fn iter(&self) -> impl Iterator<Item = (&FileId, &[FileReference])> + '_ {
+ self.references.iter().map(|(file_id, refs)| (file_id, &**refs))
+ }
+
+ pub fn file_ranges(&self) -> impl Iterator<Item = FileRange> + '_ {
+ self.references.iter().flat_map(|(&file_id, refs)| {
+ refs.iter().map(move |&FileReference { range, .. }| FileRange { file_id, range })
+ })
+ }
+}
+
+impl IntoIterator for UsageSearchResult {
+ type Item = (FileId, Vec<FileReference>);
+ type IntoIter = <FxHashMap<FileId, Vec<FileReference>> as IntoIterator>::IntoIter;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.references.into_iter()
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct FileReference {
+ /// The range of the reference in the original file
+ pub range: TextRange,
+ /// The node of the reference in the (macro-)file
+ pub name: ast::NameLike,
+ pub category: Option<ReferenceCategory>,
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub enum ReferenceCategory {
+ // FIXME: Add this variant and delete the `retain_adt_literal_usages` function.
+ // Create
+ Write,
+ Read,
+ // FIXME: Some day should be able to search in doc comments. Would probably
+ // need to switch from enum to bitflags then?
+ // DocComment
+}
+
+/// Generally, `search_scope` returns files that might contain references for the element.
+/// For `pub(crate)` things it's a crate, for `pub` things it's a crate and dependant crates.
+/// In some cases, the location of the references is known to within a `TextRange`,
+/// e.g. for things like local variables.
+#[derive(Clone, Debug)]
+pub struct SearchScope {
+ entries: FxHashMap<FileId, Option<TextRange>>,
+}
+
+impl SearchScope {
+ fn new(entries: FxHashMap<FileId, Option<TextRange>>) -> SearchScope {
+ SearchScope { entries }
+ }
+
+ /// Build a search scope spanning the entire crate graph of files.
+ fn crate_graph(db: &RootDatabase) -> SearchScope {
+ let mut entries = FxHashMap::default();
+
+ let graph = db.crate_graph();
+ for krate in graph.iter() {
+ let root_file = graph[krate].root_file_id;
+ let source_root_id = db.file_source_root(root_file);
+ let source_root = db.source_root(source_root_id);
+ entries.extend(source_root.iter().map(|id| (id, None)));
+ }
+ SearchScope { entries }
+ }
+
+ /// Build a search scope spanning all the reverse dependencies of the given crate.
+ fn reverse_dependencies(db: &RootDatabase, of: hir::Crate) -> SearchScope {
+ let mut entries = FxHashMap::default();
+ for rev_dep in of.transitive_reverse_dependencies(db) {
+ let root_file = rev_dep.root_file(db);
+ let source_root_id = db.file_source_root(root_file);
+ let source_root = db.source_root(source_root_id);
+ entries.extend(source_root.iter().map(|id| (id, None)));
+ }
+ SearchScope { entries }
+ }
+
+ /// Build a search scope spanning the given crate.
+ fn krate(db: &RootDatabase, of: hir::Crate) -> SearchScope {
+ let root_file = of.root_file(db);
+ let source_root_id = db.file_source_root(root_file);
+ let source_root = db.source_root(source_root_id);
+ SearchScope {
+ entries: source_root.iter().map(|id| (id, None)).collect::<FxHashMap<_, _>>(),
+ }
+ }
+
+ /// Build a search scope spanning the given module and all its submodules.
+ fn module_and_children(db: &RootDatabase, module: hir::Module) -> SearchScope {
+ let mut entries = FxHashMap::default();
+
+ let (file_id, range) = {
+ let InFile { file_id, value } = module.definition_source(db);
+ if let Some((file_id, call_source)) = file_id.original_call_node(db) {
+ (file_id, Some(call_source.text_range()))
+ } else {
+ (
+ file_id.original_file(db),
+ match value {
+ ModuleSource::SourceFile(_) => None,
+ ModuleSource::Module(it) => Some(it.syntax().text_range()),
+ ModuleSource::BlockExpr(it) => Some(it.syntax().text_range()),
+ },
+ )
+ }
+ };
+ entries.insert(file_id, range);
+
+ let mut to_visit: Vec<_> = module.children(db).collect();
+ while let Some(module) = to_visit.pop() {
+ if let InFile { file_id, value: ModuleSource::SourceFile(_) } =
+ module.definition_source(db)
+ {
+ entries.insert(file_id.original_file(db), None);
+ }
+ to_visit.extend(module.children(db));
+ }
+ SearchScope { entries }
+ }
+
+ /// Build an empty search scope.
+ pub fn empty() -> SearchScope {
+ SearchScope::new(FxHashMap::default())
+ }
+
+ /// Build a empty search scope spanning the given file.
+ pub fn single_file(file: FileId) -> SearchScope {
+ SearchScope::new(std::iter::once((file, None)).collect())
+ }
+
+ /// Build a empty search scope spanning the text range of the given file.
+ pub fn file_range(range: FileRange) -> SearchScope {
+ SearchScope::new(std::iter::once((range.file_id, Some(range.range))).collect())
+ }
+
+ /// Build a empty search scope spanning the given files.
+ pub fn files(files: &[FileId]) -> SearchScope {
+ SearchScope::new(files.iter().map(|f| (*f, None)).collect())
+ }
+
+ pub fn intersection(&self, other: &SearchScope) -> SearchScope {
+ let (mut small, mut large) = (&self.entries, &other.entries);
+ if small.len() > large.len() {
+ mem::swap(&mut small, &mut large)
+ }
+
+ let intersect_ranges =
+ |r1: Option<TextRange>, r2: Option<TextRange>| -> Option<Option<TextRange>> {
+ match (r1, r2) {
+ (None, r) | (r, None) => Some(r),
+ (Some(r1), Some(r2)) => r1.intersect(r2).map(Some),
+ }
+ };
+ let res = small
+ .iter()
+ .filter_map(|(&file_id, &r1)| {
+ let &r2 = large.get(&file_id)?;
+ let r = intersect_ranges(r1, r2)?;
+ Some((file_id, r))
+ })
+ .collect();
+
+ SearchScope::new(res)
+ }
+}
+
+impl IntoIterator for SearchScope {
+ type Item = (FileId, Option<TextRange>);
+ type IntoIter = std::collections::hash_map::IntoIter<FileId, Option<TextRange>>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.entries.into_iter()
+ }
+}
+
+impl Definition {
+ fn search_scope(&self, db: &RootDatabase) -> SearchScope {
+ let _p = profile::span("search_scope");
+
+ if let Definition::BuiltinType(_) = self {
+ return SearchScope::crate_graph(db);
+ }
+
+ // def is crate root
+ // FIXME: We don't do searches for crates currently, as a crate does not actually have a single name
+ if let &Definition::Module(module) = self {
+ if module.is_crate_root(db) {
+ return SearchScope::reverse_dependencies(db, module.krate());
+ }
+ }
+
+ let module = match self.module(db) {
+ Some(it) => it,
+ None => return SearchScope::empty(),
+ };
+ let InFile { file_id, value: module_source } = module.definition_source(db);
+ let file_id = file_id.original_file(db);
+
+ if let Definition::Local(var) = self {
+ let def = match var.parent(db) {
+ DefWithBody::Function(f) => f.source(db).map(|src| src.syntax().cloned()),
+ DefWithBody::Const(c) => c.source(db).map(|src| src.syntax().cloned()),
+ DefWithBody::Static(s) => s.source(db).map(|src| src.syntax().cloned()),
+ };
+ return match def {
+ Some(def) => SearchScope::file_range(def.as_ref().original_file_range(db)),
+ None => SearchScope::single_file(file_id),
+ };
+ }
+
+ if let Definition::SelfType(impl_) = self {
+ return match impl_.source(db).map(|src| src.syntax().cloned()) {
+ Some(def) => SearchScope::file_range(def.as_ref().original_file_range(db)),
+ None => SearchScope::single_file(file_id),
+ };
+ }
+
+ if let Definition::GenericParam(hir::GenericParam::LifetimeParam(param)) = self {
+ let def = match param.parent(db) {
+ hir::GenericDef::Function(it) => it.source(db).map(|src| src.syntax().cloned()),
+ hir::GenericDef::Adt(it) => it.source(db).map(|src| src.syntax().cloned()),
+ hir::GenericDef::Trait(it) => it.source(db).map(|src| src.syntax().cloned()),
+ hir::GenericDef::TypeAlias(it) => it.source(db).map(|src| src.syntax().cloned()),
+ hir::GenericDef::Impl(it) => it.source(db).map(|src| src.syntax().cloned()),
+ hir::GenericDef::Variant(it) => it.source(db).map(|src| src.syntax().cloned()),
+ hir::GenericDef::Const(it) => it.source(db).map(|src| src.syntax().cloned()),
+ };
+ return match def {
+ Some(def) => SearchScope::file_range(def.as_ref().original_file_range(db)),
+ None => SearchScope::single_file(file_id),
+ };
+ }
+
+ if let Definition::Macro(macro_def) = self {
+ return match macro_def.kind(db) {
+ hir::MacroKind::Declarative => {
+ if macro_def.attrs(db).by_key("macro_export").exists() {
+ SearchScope::reverse_dependencies(db, module.krate())
+ } else {
+ SearchScope::krate(db, module.krate())
+ }
+ }
+ hir::MacroKind::BuiltIn => SearchScope::crate_graph(db),
+ hir::MacroKind::Derive | hir::MacroKind::Attr | hir::MacroKind::ProcMacro => {
+ SearchScope::reverse_dependencies(db, module.krate())
+ }
+ };
+ }
+
+ if let Definition::DeriveHelper(_) = self {
+ return SearchScope::reverse_dependencies(db, module.krate());
+ }
+
+ let vis = self.visibility(db);
+ if let Some(Visibility::Public) = vis {
+ return SearchScope::reverse_dependencies(db, module.krate());
+ }
+ if let Some(Visibility::Module(module)) = vis {
+ return SearchScope::module_and_children(db, module.into());
+ }
+
+ let range = match module_source {
+ ModuleSource::Module(m) => Some(m.syntax().text_range()),
+ ModuleSource::BlockExpr(b) => Some(b.syntax().text_range()),
+ ModuleSource::SourceFile(_) => None,
+ };
+ match range {
+ Some(range) => SearchScope::file_range(FileRange { file_id, range }),
+ None => SearchScope::single_file(file_id),
+ }
+ }
+
+ pub fn usages<'a>(self, sema: &'a Semantics<'_, RootDatabase>) -> FindUsages<'a> {
+ FindUsages {
+ local_repr: match self {
+ Definition::Local(local) => Some(local.representative(sema.db)),
+ _ => None,
+ },
+ def: self,
+ trait_assoc_def: as_trait_assoc_def(sema.db, self),
+ sema,
+ scope: None,
+ include_self_kw_refs: None,
+ search_self_mod: false,
+ }
+ }
+}
+
+#[derive(Clone)]
+pub struct FindUsages<'a> {
+ def: Definition,
+ /// If def is an assoc item from a trait or trait impl, this is the corresponding item of the trait definition
+ trait_assoc_def: Option<Definition>,
+ sema: &'a Semantics<'a, RootDatabase>,
+ scope: Option<SearchScope>,
+ include_self_kw_refs: Option<hir::Type>,
+ local_repr: Option<hir::Local>,
+ search_self_mod: bool,
+}
+
+impl<'a> FindUsages<'a> {
+ /// Enable searching for `Self` when the definition is a type or `self` for modules.
+ pub fn include_self_refs(mut self) -> FindUsages<'a> {
+ self.include_self_kw_refs = def_to_ty(self.sema, &self.def);
+ self.search_self_mod = true;
+ self
+ }
+
+ /// Limit the search to a given [`SearchScope`].
+ pub fn in_scope(self, scope: SearchScope) -> FindUsages<'a> {
+ self.set_scope(Some(scope))
+ }
+
+ /// Limit the search to a given [`SearchScope`].
+ pub fn set_scope(mut self, scope: Option<SearchScope>) -> FindUsages<'a> {
+ assert!(self.scope.is_none());
+ self.scope = scope;
+ self
+ }
+
+ pub fn at_least_one(&self) -> bool {
+ let mut found = false;
+ self.search(&mut |_, _| {
+ found = true;
+ true
+ });
+ found
+ }
+
+ pub fn all(self) -> UsageSearchResult {
+ let mut res = UsageSearchResult::default();
+ self.search(&mut |file_id, reference| {
+ res.references.entry(file_id).or_default().push(reference);
+ false
+ });
+ res
+ }
+
+ fn search(&self, sink: &mut dyn FnMut(FileId, FileReference) -> bool) {
+ let _p = profile::span("FindUsages:search");
+ let sema = self.sema;
+
+ let search_scope = {
+ let base = self.trait_assoc_def.unwrap_or(self.def).search_scope(sema.db);
+ match &self.scope {
+ None => base,
+ Some(scope) => base.intersection(scope),
+ }
+ };
+
+ let name = match self.def {
+ // special case crate modules as these do not have a proper name
+ Definition::Module(module) if module.is_crate_root(self.sema.db) => {
+ // FIXME: This assumes the crate name is always equal to its display name when it really isn't
+ module
+ .krate()
+ .display_name(self.sema.db)
+ .map(|crate_name| crate_name.crate_name().as_smol_str().clone())
+ }
+ _ => {
+ let self_kw_refs = || {
+ self.include_self_kw_refs.as_ref().and_then(|ty| {
+ ty.as_adt()
+ .map(|adt| adt.name(self.sema.db))
+ .or_else(|| ty.as_builtin().map(|builtin| builtin.name()))
+ })
+ };
+ self.def.name(sema.db).or_else(self_kw_refs).map(|it| it.to_smol_str())
+ }
+ };
+ let name = match &name {
+ Some(s) => s.as_str(),
+ None => return,
+ };
+
+ // these can't be closures because rust infers the lifetimes wrong ...
+ fn match_indices<'a>(
+ text: &'a str,
+ name: &'a str,
+ search_range: TextRange,
+ ) -> impl Iterator<Item = TextSize> + 'a {
+ text.match_indices(name).filter_map(move |(idx, _)| {
+ let offset: TextSize = idx.try_into().unwrap();
+ if !search_range.contains_inclusive(offset) {
+ return None;
+ }
+ Some(offset)
+ })
+ }
+
+ fn scope_files<'a>(
+ sema: &'a Semantics<'_, RootDatabase>,
+ scope: &'a SearchScope,
+ ) -> impl Iterator<Item = (Arc<String>, FileId, TextRange)> + 'a {
+ scope.entries.iter().map(|(&file_id, &search_range)| {
+ let text = sema.db.file_text(file_id);
+ let search_range =
+ search_range.unwrap_or_else(|| TextRange::up_to(TextSize::of(text.as_str())));
+
+ (text, file_id, search_range)
+ })
+ }
+
+ // FIXME: There should be optimization potential here
+ // Currently we try to descend everything we find which
+ // means we call `Semantics::descend_into_macros` on
+ // every textual hit. That function is notoriously
+ // expensive even for things that do not get down mapped
+ // into macros.
+ for (text, file_id, search_range) in scope_files(sema, &search_scope) {
+ let tree = Lazy::new(move || sema.parse(file_id).syntax().clone());
+
+ // Search for occurrences of the items name
+ for offset in match_indices(&text, name, search_range) {
+ for name in sema.find_nodes_at_offset_with_descend(&tree, offset) {
+ if match name {
+ ast::NameLike::NameRef(name_ref) => self.found_name_ref(&name_ref, sink),
+ ast::NameLike::Name(name) => self.found_name(&name, sink),
+ ast::NameLike::Lifetime(lifetime) => self.found_lifetime(&lifetime, sink),
+ } {
+ return;
+ }
+ }
+ }
+ // Search for occurrences of the `Self` referring to our type
+ if let Some(self_ty) = &self.include_self_kw_refs {
+ for offset in match_indices(&text, "Self", search_range) {
+ for name_ref in sema.find_nodes_at_offset_with_descend(&tree, offset) {
+ if self.found_self_ty_name_ref(self_ty, &name_ref, sink) {
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ // Search for `super` and `crate` resolving to our module
+ match self.def {
+ Definition::Module(module) => {
+ let scope = search_scope
+ .intersection(&SearchScope::module_and_children(self.sema.db, module));
+
+ let is_crate_root = module.is_crate_root(self.sema.db);
+
+ for (text, file_id, search_range) in scope_files(sema, &scope) {
+ let tree = Lazy::new(move || sema.parse(file_id).syntax().clone());
+
+ for offset in match_indices(&text, "super", search_range) {
+ for name_ref in sema.find_nodes_at_offset_with_descend(&tree, offset) {
+ if self.found_name_ref(&name_ref, sink) {
+ return;
+ }
+ }
+ }
+ if is_crate_root {
+ for offset in match_indices(&text, "crate", search_range) {
+ for name_ref in sema.find_nodes_at_offset_with_descend(&tree, offset) {
+ if self.found_name_ref(&name_ref, sink) {
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+ _ => (),
+ }
+
+ // search for module `self` references in our module's definition source
+ match self.def {
+ Definition::Module(module) if self.search_self_mod => {
+ let src = module.definition_source(sema.db);
+ let file_id = src.file_id.original_file(sema.db);
+ let (file_id, search_range) = match src.value {
+ ModuleSource::Module(m) => (file_id, Some(m.syntax().text_range())),
+ ModuleSource::BlockExpr(b) => (file_id, Some(b.syntax().text_range())),
+ ModuleSource::SourceFile(_) => (file_id, None),
+ };
+
+ let search_range = if let Some(&range) = search_scope.entries.get(&file_id) {
+ match (range, search_range) {
+ (None, range) | (range, None) => range,
+ (Some(range), Some(search_range)) => match range.intersect(search_range) {
+ Some(range) => Some(range),
+ None => return,
+ },
+ }
+ } else {
+ return;
+ };
+
+ let text = sema.db.file_text(file_id);
+ let search_range =
+ search_range.unwrap_or_else(|| TextRange::up_to(TextSize::of(text.as_str())));
+
+ let tree = Lazy::new(|| sema.parse(file_id).syntax().clone());
+
+ for offset in match_indices(&text, "self", search_range) {
+ for name_ref in sema.find_nodes_at_offset_with_descend(&tree, offset) {
+ if self.found_self_module_name_ref(&name_ref, sink) {
+ return;
+ }
+ }
+ }
+ }
+ _ => {}
+ }
+ }
+
+ fn found_self_ty_name_ref(
+ &self,
+ self_ty: &hir::Type,
+ name_ref: &ast::NameRef,
+ sink: &mut dyn FnMut(FileId, FileReference) -> bool,
+ ) -> bool {
+ match NameRefClass::classify(self.sema, name_ref) {
+ Some(NameRefClass::Definition(Definition::SelfType(impl_)))
+ if impl_.self_ty(self.sema.db) == *self_ty =>
+ {
+ let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
+ let reference = FileReference {
+ range,
+ name: ast::NameLike::NameRef(name_ref.clone()),
+ category: None,
+ };
+ sink(file_id, reference)
+ }
+ _ => false,
+ }
+ }
+
+ fn found_self_module_name_ref(
+ &self,
+ name_ref: &ast::NameRef,
+ sink: &mut dyn FnMut(FileId, FileReference) -> bool,
+ ) -> bool {
+ match NameRefClass::classify(self.sema, name_ref) {
+ Some(NameRefClass::Definition(def @ Definition::Module(_))) if def == self.def => {
+ let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
+ let reference = FileReference {
+ range,
+ name: ast::NameLike::NameRef(name_ref.clone()),
+ category: None,
+ };
+ sink(file_id, reference)
+ }
+ _ => false,
+ }
+ }
+
+ fn found_lifetime(
+ &self,
+ lifetime: &ast::Lifetime,
+ sink: &mut dyn FnMut(FileId, FileReference) -> bool,
+ ) -> bool {
+ match NameRefClass::classify_lifetime(self.sema, lifetime) {
+ Some(NameRefClass::Definition(def)) if def == self.def => {
+ let FileRange { file_id, range } = self.sema.original_range(lifetime.syntax());
+ let reference = FileReference {
+ range,
+ name: ast::NameLike::Lifetime(lifetime.clone()),
+ category: None,
+ };
+ sink(file_id, reference)
+ }
+ _ => false,
+ }
+ }
+
+ fn found_name_ref(
+ &self,
+ name_ref: &ast::NameRef,
+ sink: &mut dyn FnMut(FileId, FileReference) -> bool,
+ ) -> bool {
+ match NameRefClass::classify(self.sema, name_ref) {
+ Some(NameRefClass::Definition(def @ Definition::Local(local)))
+ if matches!(
+ self.local_repr, Some(repr) if repr == local.representative(self.sema.db)
+ ) =>
+ {
+ let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
+ let reference = FileReference {
+ range,
+ name: ast::NameLike::NameRef(name_ref.clone()),
+ category: ReferenceCategory::new(&def, name_ref),
+ };
+ sink(file_id, reference)
+ }
+ Some(NameRefClass::Definition(def))
+ if match self.trait_assoc_def {
+ Some(trait_assoc_def) => {
+ // we have a trait assoc item, so force resolve all assoc items to their trait version
+ convert_to_def_in_trait(self.sema.db, def) == trait_assoc_def
+ }
+ None => self.def == def,
+ } =>
+ {
+ let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
+ let reference = FileReference {
+ range,
+ name: ast::NameLike::NameRef(name_ref.clone()),
+ category: ReferenceCategory::new(&def, name_ref),
+ };
+ sink(file_id, reference)
+ }
+ Some(NameRefClass::Definition(def)) if self.include_self_kw_refs.is_some() => {
+ if self.include_self_kw_refs == def_to_ty(self.sema, &def) {
+ let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
+ let reference = FileReference {
+ range,
+ name: ast::NameLike::NameRef(name_ref.clone()),
+ category: ReferenceCategory::new(&def, name_ref),
+ };
+ sink(file_id, reference)
+ } else {
+ false
+ }
+ }
+ Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => {
+ let field = Definition::Field(field);
+ let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
+ let access = match self.def {
+ Definition::Field(_) if field == self.def => {
+ ReferenceCategory::new(&field, name_ref)
+ }
+ Definition::Local(_) if matches!(self.local_repr, Some(repr) if repr == local.representative(self.sema.db)) => {
+ ReferenceCategory::new(&Definition::Local(local), name_ref)
+ }
+ _ => return false,
+ };
+ let reference = FileReference {
+ range,
+ name: ast::NameLike::NameRef(name_ref.clone()),
+ category: access,
+ };
+ sink(file_id, reference)
+ }
+ _ => false,
+ }
+ }
+
+ fn found_name(
+ &self,
+ name: &ast::Name,
+ sink: &mut dyn FnMut(FileId, FileReference) -> bool,
+ ) -> bool {
+ match NameClass::classify(self.sema, name) {
+ Some(NameClass::PatFieldShorthand { local_def: _, field_ref })
+ if matches!(
+ self.def, Definition::Field(_) if Definition::Field(field_ref) == self.def
+ ) =>
+ {
+ let FileRange { file_id, range } = self.sema.original_range(name.syntax());
+ let reference = FileReference {
+ range,
+ name: ast::NameLike::Name(name.clone()),
+ // FIXME: mutable patterns should have `Write` access
+ category: Some(ReferenceCategory::Read),
+ };
+ sink(file_id, reference)
+ }
+ Some(NameClass::ConstReference(def)) if self.def == def => {
+ let FileRange { file_id, range } = self.sema.original_range(name.syntax());
+ let reference = FileReference {
+ range,
+ name: ast::NameLike::Name(name.clone()),
+ category: None,
+ };
+ sink(file_id, reference)
+ }
+ Some(NameClass::Definition(def @ Definition::Local(local))) if def != self.def => {
+ if matches!(
+ self.local_repr,
+ Some(repr) if local.representative(self.sema.db) == repr
+ ) {
+ let FileRange { file_id, range } = self.sema.original_range(name.syntax());
+ let reference = FileReference {
+ range,
+ name: ast::NameLike::Name(name.clone()),
+ category: None,
+ };
+ return sink(file_id, reference);
+ }
+ false
+ }
+ Some(NameClass::Definition(def)) if def != self.def => {
+ // if the def we are looking for is a trait (impl) assoc item, we'll have to resolve the items to trait definition assoc item
+ if !matches!(
+ self.trait_assoc_def,
+ Some(trait_assoc_def)
+ if convert_to_def_in_trait(self.sema.db, def) == trait_assoc_def
+ ) {
+ return false;
+ }
+ let FileRange { file_id, range } = self.sema.original_range(name.syntax());
+ let reference = FileReference {
+ range,
+ name: ast::NameLike::Name(name.clone()),
+ category: None,
+ };
+ sink(file_id, reference)
+ }
+ _ => false,
+ }
+ }
+}
+
+fn def_to_ty(sema: &Semantics<'_, RootDatabase>, def: &Definition) -> Option<hir::Type> {
+ match def {
+ Definition::Adt(adt) => Some(adt.ty(sema.db)),
+ Definition::TypeAlias(it) => Some(it.ty(sema.db)),
+ Definition::BuiltinType(it) => Some(it.ty(sema.db)),
+ Definition::SelfType(it) => Some(it.self_ty(sema.db)),
+ _ => None,
+ }
+}
+
+impl ReferenceCategory {
+ fn new(def: &Definition, r: &ast::NameRef) -> Option<ReferenceCategory> {
+ // Only Locals and Fields have accesses for now.
+ if !matches!(def, Definition::Local(_) | Definition::Field(_)) {
+ return None;
+ }
+
+ let mode = r.syntax().ancestors().find_map(|node| {
+ match_ast! {
+ match node {
+ ast::BinExpr(expr) => {
+ if matches!(expr.op_kind()?, ast::BinaryOp::Assignment { .. }) {
+ // If the variable or field ends on the LHS's end then it's a Write (covers fields and locals).
+ // FIXME: This is not terribly accurate.
+ if let Some(lhs) = expr.lhs() {
+ if lhs.syntax().text_range().end() == r.syntax().text_range().end() {
+ return Some(ReferenceCategory::Write);
+ }
+ }
+ }
+ Some(ReferenceCategory::Read)
+ },
+ _ => None
+ }
+ }
+ });
+
+ // Default Locals and Fields to read
+ mode.or(Some(ReferenceCategory::Read))
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/source_change.rs b/src/tools/rust-analyzer/crates/ide-db/src/source_change.rs
new file mode 100644
index 000000000..8132c73ef
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/source_change.rs
@@ -0,0 +1,99 @@
+//! This modules defines type to represent changes to the source code, that flow
+//! from the server to the client.
+//!
+//! It can be viewed as a dual for `Change`.
+
+use std::{collections::hash_map::Entry, iter};
+
+use base_db::{AnchoredPathBuf, FileId};
+use rustc_hash::FxHashMap;
+use stdx::never;
+use text_edit::TextEdit;
+
+#[derive(Default, Debug, Clone)]
+pub struct SourceChange {
+ pub source_file_edits: FxHashMap<FileId, TextEdit>,
+ pub file_system_edits: Vec<FileSystemEdit>,
+ pub is_snippet: bool,
+}
+
+impl SourceChange {
+ /// Creates a new SourceChange with the given label
+ /// from the edits.
+ pub fn from_edits(
+ source_file_edits: FxHashMap<FileId, TextEdit>,
+ file_system_edits: Vec<FileSystemEdit>,
+ ) -> Self {
+ SourceChange { source_file_edits, file_system_edits, is_snippet: false }
+ }
+
+ pub fn from_text_edit(file_id: FileId, edit: TextEdit) -> Self {
+ SourceChange {
+ source_file_edits: iter::once((file_id, edit)).collect(),
+ ..Default::default()
+ }
+ }
+
+ /// Inserts a [`TextEdit`] for the given [`FileId`]. This properly handles merging existing
+ /// edits for a file if some already exist.
+ pub fn insert_source_edit(&mut self, file_id: FileId, edit: TextEdit) {
+ match self.source_file_edits.entry(file_id) {
+ Entry::Occupied(mut entry) => {
+ never!(entry.get_mut().union(edit).is_err(), "overlapping edits for same file");
+ }
+ Entry::Vacant(entry) => {
+ entry.insert(edit);
+ }
+ }
+ }
+
+ pub fn push_file_system_edit(&mut self, edit: FileSystemEdit) {
+ self.file_system_edits.push(edit);
+ }
+
+ pub fn get_source_edit(&self, file_id: FileId) -> Option<&TextEdit> {
+ self.source_file_edits.get(&file_id)
+ }
+
+ pub fn merge(mut self, other: SourceChange) -> SourceChange {
+ self.extend(other.source_file_edits);
+ self.extend(other.file_system_edits);
+ self.is_snippet |= other.is_snippet;
+ self
+ }
+}
+
+impl Extend<(FileId, TextEdit)> for SourceChange {
+ fn extend<T: IntoIterator<Item = (FileId, TextEdit)>>(&mut self, iter: T) {
+ iter.into_iter().for_each(|(file_id, edit)| self.insert_source_edit(file_id, edit));
+ }
+}
+
+impl Extend<FileSystemEdit> for SourceChange {
+ fn extend<T: IntoIterator<Item = FileSystemEdit>>(&mut self, iter: T) {
+ iter.into_iter().for_each(|edit| self.push_file_system_edit(edit));
+ }
+}
+
+impl From<FxHashMap<FileId, TextEdit>> for SourceChange {
+ fn from(source_file_edits: FxHashMap<FileId, TextEdit>) -> SourceChange {
+ SourceChange { source_file_edits, file_system_edits: Vec::new(), is_snippet: false }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub enum FileSystemEdit {
+ CreateFile { dst: AnchoredPathBuf, initial_contents: String },
+ MoveFile { src: FileId, dst: AnchoredPathBuf },
+ MoveDir { src: AnchoredPathBuf, src_id: FileId, dst: AnchoredPathBuf },
+}
+
+impl From<FileSystemEdit> for SourceChange {
+ fn from(edit: FileSystemEdit) -> SourceChange {
+ SourceChange {
+ source_file_edits: Default::default(),
+ file_system_edits: vec![edit],
+ is_snippet: false,
+ }
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs b/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs
new file mode 100644
index 000000000..bfb003127
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs
@@ -0,0 +1,429 @@
+//! This module handles fuzzy-searching of functions, structs and other symbols
+//! by name across the whole workspace and dependencies.
+//!
+//! It works by building an incrementally-updated text-search index of all
+//! symbols. The backbone of the index is the **awesome** `fst` crate by
+//! @BurntSushi.
+//!
+//! In a nutshell, you give a set of strings to `fst`, and it builds a
+//! finite state machine describing this set of strings. The strings which
+//! could fuzzy-match a pattern can also be described by a finite state machine.
+//! What is freaking cool is that you can now traverse both state machines in
+//! lock-step to enumerate the strings which are both in the input set and
+//! fuzz-match the query. Or, more formally, given two languages described by
+//! FSTs, one can build a product FST which describes the intersection of the
+//! languages.
+//!
+//! `fst` does not support cheap updating of the index, but it supports unioning
+//! of state machines. So, to account for changing source code, we build an FST
+//! for each library (which is assumed to never change) and an FST for each Rust
+//! file in the current workspace, and run a query against the union of all
+//! those FSTs.
+
+use std::{
+ cmp::Ordering,
+ fmt,
+ hash::{Hash, Hasher},
+ mem,
+ sync::Arc,
+};
+
+use base_db::{
+ salsa::{self, ParallelDatabase},
+ SourceDatabaseExt, SourceRootId, Upcast,
+};
+use fst::{self, Streamer};
+use hir::{
+ db::HirDatabase,
+ symbols::{FileSymbol, SymbolCollector},
+ Crate, Module,
+};
+use rayon::prelude::*;
+use rustc_hash::FxHashSet;
+
+use crate::RootDatabase;
+
+#[derive(Debug)]
+pub struct Query {
+ query: String,
+ lowercased: String,
+ only_types: bool,
+ libs: bool,
+ exact: bool,
+ case_sensitive: bool,
+ limit: usize,
+}
+
+impl Query {
+ pub fn new(query: String) -> Query {
+ let lowercased = query.to_lowercase();
+ Query {
+ query,
+ lowercased,
+ only_types: false,
+ libs: false,
+ exact: false,
+ case_sensitive: false,
+ limit: usize::max_value(),
+ }
+ }
+
+ pub fn only_types(&mut self) {
+ self.only_types = true;
+ }
+
+ pub fn libs(&mut self) {
+ self.libs = true;
+ }
+
+ pub fn exact(&mut self) {
+ self.exact = true;
+ }
+
+ pub fn case_sensitive(&mut self) {
+ self.case_sensitive = true;
+ }
+
+ pub fn limit(&mut self, limit: usize) {
+ self.limit = limit
+ }
+}
+
+#[salsa::query_group(SymbolsDatabaseStorage)]
+pub trait SymbolsDatabase: HirDatabase + SourceDatabaseExt + Upcast<dyn HirDatabase> {
+ /// The symbol index for a given module. These modules should only be in source roots that
+ /// are inside local_roots.
+ fn module_symbols(&self, module: Module) -> Arc<SymbolIndex>;
+
+ /// The symbol index for a given source root within library_roots.
+ fn library_symbols(&self, source_root_id: SourceRootId) -> Arc<SymbolIndex>;
+
+ /// The set of "local" (that is, from the current workspace) roots.
+ /// Files in local roots are assumed to change frequently.
+ #[salsa::input]
+ fn local_roots(&self) -> Arc<FxHashSet<SourceRootId>>;
+
+ /// The set of roots for crates.io libraries.
+ /// Files in libraries are assumed to never change.
+ #[salsa::input]
+ fn library_roots(&self) -> Arc<FxHashSet<SourceRootId>>;
+}
+
+fn library_symbols(db: &dyn SymbolsDatabase, source_root_id: SourceRootId) -> Arc<SymbolIndex> {
+ let _p = profile::span("library_symbols");
+
+ // todo: this could be parallelized, once I figure out how to do that...
+ let symbols = db
+ .source_root_crates(source_root_id)
+ .iter()
+ .flat_map(|&krate| Crate::from(krate).modules(db.upcast()))
+ // we specifically avoid calling SymbolsDatabase::module_symbols here, even they do the same thing,
+ // as the index for a library is not going to really ever change, and we do not want to store each
+ // module's index in salsa.
+ .flat_map(|module| SymbolCollector::collect(db.upcast(), module))
+ .collect();
+
+ Arc::new(SymbolIndex::new(symbols))
+}
+
+fn module_symbols(db: &dyn SymbolsDatabase, module: Module) -> Arc<SymbolIndex> {
+ let _p = profile::span("module_symbols");
+ let symbols = SymbolCollector::collect(db.upcast(), module);
+ Arc::new(SymbolIndex::new(symbols))
+}
+
+/// Need to wrap Snapshot to provide `Clone` impl for `map_with`
+struct Snap<DB>(DB);
+impl<DB: ParallelDatabase> Snap<salsa::Snapshot<DB>> {
+ fn new(db: &DB) -> Self {
+ Self(db.snapshot())
+ }
+}
+impl<DB: ParallelDatabase> Clone for Snap<salsa::Snapshot<DB>> {
+ fn clone(&self) -> Snap<salsa::Snapshot<DB>> {
+ Snap(self.0.snapshot())
+ }
+}
+impl<DB> std::ops::Deref for Snap<DB> {
+ type Target = DB;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+// Feature: Workspace Symbol
+//
+// Uses fuzzy-search to find types, modules and functions by name across your
+// project and dependencies. This is **the** most useful feature, which improves code
+// navigation tremendously. It mostly works on top of the built-in LSP
+// functionality, however `#` and `*` symbols can be used to narrow down the
+// search. Specifically,
+//
+// - `Foo` searches for `Foo` type in the current workspace
+// - `foo#` searches for `foo` function in the current workspace
+// - `Foo*` searches for `Foo` type among dependencies, including `stdlib`
+// - `foo#*` searches for `foo` function among dependencies
+//
+// That is, `#` switches from "types" to all symbols, `*` switches from the current
+// workspace to dependencies.
+//
+// Note that filtering does not currently work in VSCode due to the editor never
+// sending the special symbols to the language server. Instead, you can configure
+// the filtering via the `rust-analyzer.workspace.symbol.search.scope` and
+// `rust-analyzer.workspace.symbol.search.kind` settings.
+//
+// |===
+// | Editor | Shortcut
+//
+// | VS Code | kbd:[Ctrl+T]
+// |===
+pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec<FileSymbol> {
+ let _p = profile::span("world_symbols").detail(|| query.query.clone());
+
+ let indices: Vec<_> = if query.libs {
+ db.library_roots()
+ .par_iter()
+ .map_with(Snap::new(db), |snap, &root| snap.library_symbols(root))
+ .collect()
+ } else {
+ let mut modules = Vec::new();
+
+ for &root in db.local_roots().iter() {
+ let crates = db.source_root_crates(root);
+ for &krate in crates.iter() {
+ modules.extend(Crate::from(krate).modules(db));
+ }
+ }
+
+ modules
+ .par_iter()
+ .map_with(Snap::new(db), |snap, &module| snap.module_symbols(module))
+ .collect()
+ };
+
+ query.search(&indices)
+}
+
+pub fn crate_symbols(db: &RootDatabase, krate: Crate, query: Query) -> Vec<FileSymbol> {
+ let _p = profile::span("crate_symbols").detail(|| format!("{:?}", query));
+
+ let modules = krate.modules(db);
+ let indices: Vec<_> = modules
+ .par_iter()
+ .map_with(Snap::new(db), |snap, &module| snap.module_symbols(module))
+ .collect();
+
+ query.search(&indices)
+}
+
+#[derive(Default)]
+pub struct SymbolIndex {
+ symbols: Vec<FileSymbol>,
+ map: fst::Map<Vec<u8>>,
+}
+
+impl fmt::Debug for SymbolIndex {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("SymbolIndex").field("n_symbols", &self.symbols.len()).finish()
+ }
+}
+
+impl PartialEq for SymbolIndex {
+ fn eq(&self, other: &SymbolIndex) -> bool {
+ self.symbols == other.symbols
+ }
+}
+
+impl Eq for SymbolIndex {}
+
+impl Hash for SymbolIndex {
+ fn hash<H: Hasher>(&self, hasher: &mut H) {
+ self.symbols.hash(hasher)
+ }
+}
+
+impl SymbolIndex {
+ fn new(mut symbols: Vec<FileSymbol>) -> SymbolIndex {
+ fn cmp(lhs: &FileSymbol, rhs: &FileSymbol) -> Ordering {
+ let lhs_chars = lhs.name.chars().map(|c| c.to_ascii_lowercase());
+ let rhs_chars = rhs.name.chars().map(|c| c.to_ascii_lowercase());
+ lhs_chars.cmp(rhs_chars)
+ }
+
+ symbols.par_sort_by(cmp);
+
+ let mut builder = fst::MapBuilder::memory();
+
+ let mut last_batch_start = 0;
+
+ for idx in 0..symbols.len() {
+ if let Some(next_symbol) = symbols.get(idx + 1) {
+ if cmp(&symbols[last_batch_start], next_symbol) == Ordering::Equal {
+ continue;
+ }
+ }
+
+ let start = last_batch_start;
+ let end = idx + 1;
+ last_batch_start = end;
+
+ let key = symbols[start].name.as_str().to_ascii_lowercase();
+ let value = SymbolIndex::range_to_map_value(start, end);
+
+ builder.insert(key, value).unwrap();
+ }
+
+ let map = fst::Map::new(builder.into_inner().unwrap()).unwrap();
+ SymbolIndex { symbols, map }
+ }
+
+ pub fn len(&self) -> usize {
+ self.symbols.len()
+ }
+
+ pub fn memory_size(&self) -> usize {
+ self.map.as_fst().size() + self.symbols.len() * mem::size_of::<FileSymbol>()
+ }
+
+ fn range_to_map_value(start: usize, end: usize) -> u64 {
+ debug_assert![start <= (std::u32::MAX as usize)];
+ debug_assert![end <= (std::u32::MAX as usize)];
+
+ ((start as u64) << 32) | end as u64
+ }
+
+ fn map_value_to_range(value: u64) -> (usize, usize) {
+ let end = value as u32 as usize;
+ let start = (value >> 32) as usize;
+ (start, end)
+ }
+}
+
+impl Query {
+ pub(crate) fn search(self, indices: &[Arc<SymbolIndex>]) -> Vec<FileSymbol> {
+ let _p = profile::span("symbol_index::Query::search");
+ let mut op = fst::map::OpBuilder::new();
+ for file_symbols in indices.iter() {
+ let automaton = fst::automaton::Subsequence::new(&self.lowercased);
+ op = op.add(file_symbols.map.search(automaton))
+ }
+ let mut stream = op.union();
+ let mut res = Vec::new();
+ while let Some((_, indexed_values)) = stream.next() {
+ for indexed_value in indexed_values {
+ let symbol_index = &indices[indexed_value.index];
+ let (start, end) = SymbolIndex::map_value_to_range(indexed_value.value);
+
+ for symbol in &symbol_index.symbols[start..end] {
+ if self.only_types && !symbol.kind.is_type() {
+ continue;
+ }
+ if self.exact {
+ if symbol.name != self.query {
+ continue;
+ }
+ } else if self.case_sensitive {
+ if self.query.chars().any(|c| !symbol.name.contains(c)) {
+ continue;
+ }
+ }
+
+ res.push(symbol.clone());
+ if res.len() >= self.limit {
+ return res;
+ }
+ }
+ }
+ }
+ res
+ }
+}
+
+#[cfg(test)]
+mod tests {
+
+ use base_db::fixture::WithFixture;
+ use expect_test::expect_file;
+ use hir::symbols::SymbolCollector;
+
+ use super::*;
+
+ #[test]
+ fn test_symbol_index_collection() {
+ let (db, _) = RootDatabase::with_many_files(
+ r#"
+//- /main.rs
+
+macro_rules! macro_rules_macro {
+ () => {}
+};
+
+macro_rules! define_struct {
+ () => {
+ struct StructFromMacro;
+ }
+};
+
+define_struct!();
+
+macro Macro { }
+
+struct Struct;
+enum Enum {
+ A, B
+}
+union Union {}
+
+impl Struct {
+ fn impl_fn() {}
+}
+
+trait Trait {
+ fn trait_fn(&self);
+}
+
+fn main() {
+ struct StructInFn;
+}
+
+const CONST: u32 = 1;
+static STATIC: &'static str = "2";
+type Alias = Struct;
+
+mod a_mod {
+ struct StructInModA;
+}
+
+const _: () = {
+ struct StructInUnnamedConst;
+
+ ()
+};
+
+const CONST_WITH_INNER: () = {
+ struct StructInNamedConst;
+
+ ()
+};
+
+mod b_mod;
+
+//- /b_mod.rs
+struct StructInModB;
+ "#,
+ );
+
+ let symbols: Vec<_> = Crate::from(db.test_crate())
+ .modules(&db)
+ .into_iter()
+ .map(|module_id| {
+ let mut symbols = SymbolCollector::collect(&db, module_id);
+ symbols.sort_by_key(|it| it.name.clone());
+ (module_id, symbols)
+ })
+ .collect();
+
+ expect_file!["./test_data/test_symbol_index_collection.txt"].assert_debug_eq(&symbols);
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/format_string.rs b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/format_string.rs
new file mode 100644
index 000000000..f48a57008
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/format_string.rs
@@ -0,0 +1,308 @@
+//! Tools to work with format string literals for the `format_args!` family of macros.
+use syntax::{
+ ast::{self, IsString},
+ AstNode, AstToken, TextRange, TextSize,
+};
+
+pub fn is_format_string(string: &ast::String) -> bool {
+ // Check if `string` is a format string argument of a macro invocation.
+ // `string` is a string literal, mapped down into the innermost macro expansion.
+ // Since `format_args!` etc. remove the format string when expanding, but place all arguments
+ // in the expanded output, we know that the string token is (part of) the format string if it
+ // appears in `format_args!` (otherwise it would have been mapped down further).
+ //
+ // This setup lets us correctly highlight the components of `concat!("{}", "bla")` format
+ // strings. It still fails for `concat!("{", "}")`, but that is rare.
+ (|| {
+ let macro_call = string.syntax().parent_ancestors().find_map(ast::MacroCall::cast)?;
+ let name = macro_call.path()?.segment()?.name_ref()?;
+
+ if !matches!(
+ name.text().as_str(),
+ "format_args" | "format_args_nl" | "const_format_args" | "panic_2015" | "panic_2021"
+ ) {
+ return None;
+ }
+
+ // NB: we match against `panic_2015`/`panic_2021` here because they have a special-cased arm for
+ // `"{}"`, which otherwise wouldn't get highlighted.
+
+ Some(())
+ })()
+ .is_some()
+}
+
+#[derive(Debug)]
+pub enum FormatSpecifier {
+ Open,
+ Close,
+ Integer,
+ Identifier,
+ Colon,
+ Fill,
+ Align,
+ Sign,
+ NumberSign,
+ Zero,
+ DollarSign,
+ Dot,
+ Asterisk,
+ QuestionMark,
+ Escape,
+}
+
+pub fn lex_format_specifiers(
+ string: &ast::String,
+ mut callback: &mut dyn FnMut(TextRange, FormatSpecifier),
+) {
+ let mut char_ranges = Vec::new();
+ string.escaped_char_ranges(&mut |range, res| char_ranges.push((range, res)));
+ let mut chars = char_ranges
+ .iter()
+ .filter_map(|(range, res)| Some((*range, *res.as_ref().ok()?)))
+ .peekable();
+
+ while let Some((range, first_char)) = chars.next() {
+ if let '{' = first_char {
+ // Format specifier, see syntax at https://doc.rust-lang.org/std/fmt/index.html#syntax
+ if let Some((_, '{')) = chars.peek() {
+ // Escaped format specifier, `{{`
+ read_escaped_format_specifier(&mut chars, &mut callback);
+ continue;
+ }
+
+ callback(range, FormatSpecifier::Open);
+
+ // check for integer/identifier
+ let (_, int_char) = chars.peek().copied().unwrap_or_default();
+ match int_char {
+ // integer
+ '0'..='9' => read_integer(&mut chars, &mut callback),
+ // identifier
+ c if c == '_' || c.is_alphabetic() => read_identifier(&mut chars, &mut callback),
+ _ => {}
+ }
+
+ if let Some((_, ':')) = chars.peek() {
+ skip_char_and_emit(&mut chars, FormatSpecifier::Colon, &mut callback);
+
+ // check for fill/align
+ let mut cloned = chars.clone().take(2);
+ let (_, first) = cloned.next().unwrap_or_default();
+ let (_, second) = cloned.next().unwrap_or_default();
+ match second {
+ '<' | '^' | '>' => {
+ // alignment specifier, first char specifies fillment
+ skip_char_and_emit(&mut chars, FormatSpecifier::Fill, &mut callback);
+ skip_char_and_emit(&mut chars, FormatSpecifier::Align, &mut callback);
+ }
+ _ => {
+ if let '<' | '^' | '>' = first {
+ skip_char_and_emit(&mut chars, FormatSpecifier::Align, &mut callback);
+ }
+ }
+ }
+
+ // check for sign
+ match chars.peek().copied().unwrap_or_default().1 {
+ '+' | '-' => {
+ skip_char_and_emit(&mut chars, FormatSpecifier::Sign, &mut callback);
+ }
+ _ => {}
+ }
+
+ // check for `#`
+ if let Some((_, '#')) = chars.peek() {
+ skip_char_and_emit(&mut chars, FormatSpecifier::NumberSign, &mut callback);
+ }
+
+ // check for `0`
+ let mut cloned = chars.clone().take(2);
+ let first = cloned.next().map(|next| next.1);
+ let second = cloned.next().map(|next| next.1);
+
+ if first == Some('0') && second != Some('$') {
+ skip_char_and_emit(&mut chars, FormatSpecifier::Zero, &mut callback);
+ }
+
+ // width
+ match chars.peek().copied().unwrap_or_default().1 {
+ '0'..='9' => {
+ read_integer(&mut chars, &mut callback);
+ if let Some((_, '$')) = chars.peek() {
+ skip_char_and_emit(
+ &mut chars,
+ FormatSpecifier::DollarSign,
+ &mut callback,
+ );
+ }
+ }
+ c if c == '_' || c.is_alphabetic() => {
+ read_identifier(&mut chars, &mut callback);
+
+ if chars.peek().map(|&(_, c)| c) == Some('?') {
+ skip_char_and_emit(
+ &mut chars,
+ FormatSpecifier::QuestionMark,
+ &mut callback,
+ );
+ }
+
+ // can be either width (indicated by dollar sign, or type in which case
+ // the next sign has to be `}`)
+ let next = chars.peek().map(|&(_, c)| c);
+
+ match next {
+ Some('$') => skip_char_and_emit(
+ &mut chars,
+ FormatSpecifier::DollarSign,
+ &mut callback,
+ ),
+ Some('}') => {
+ skip_char_and_emit(
+ &mut chars,
+ FormatSpecifier::Close,
+ &mut callback,
+ );
+ continue;
+ }
+ _ => continue,
+ };
+ }
+ _ => {}
+ }
+
+ // precision
+ if let Some((_, '.')) = chars.peek() {
+ skip_char_and_emit(&mut chars, FormatSpecifier::Dot, &mut callback);
+
+ match chars.peek().copied().unwrap_or_default().1 {
+ '*' => {
+ skip_char_and_emit(
+ &mut chars,
+ FormatSpecifier::Asterisk,
+ &mut callback,
+ );
+ }
+ '0'..='9' => {
+ read_integer(&mut chars, &mut callback);
+ if let Some((_, '$')) = chars.peek() {
+ skip_char_and_emit(
+ &mut chars,
+ FormatSpecifier::DollarSign,
+ &mut callback,
+ );
+ }
+ }
+ c if c == '_' || c.is_alphabetic() => {
+ read_identifier(&mut chars, &mut callback);
+ if chars.peek().map(|&(_, c)| c) != Some('$') {
+ continue;
+ }
+ skip_char_and_emit(
+ &mut chars,
+ FormatSpecifier::DollarSign,
+ &mut callback,
+ );
+ }
+ _ => {
+ continue;
+ }
+ }
+ }
+
+ // type
+ match chars.peek().copied().unwrap_or_default().1 {
+ '?' => {
+ skip_char_and_emit(
+ &mut chars,
+ FormatSpecifier::QuestionMark,
+ &mut callback,
+ );
+ }
+ c if c == '_' || c.is_alphabetic() => {
+ read_identifier(&mut chars, &mut callback);
+
+ if chars.peek().map(|&(_, c)| c) == Some('?') {
+ skip_char_and_emit(
+ &mut chars,
+ FormatSpecifier::QuestionMark,
+ &mut callback,
+ );
+ }
+ }
+ _ => {}
+ }
+ }
+
+ if let Some((_, '}')) = chars.peek() {
+ skip_char_and_emit(&mut chars, FormatSpecifier::Close, &mut callback);
+ }
+ continue;
+ } else if let '}' = first_char {
+ if let Some((_, '}')) = chars.peek() {
+ // Escaped format specifier, `}}`
+ read_escaped_format_specifier(&mut chars, &mut callback);
+ }
+ }
+ }
+
+ fn skip_char_and_emit<I, F>(
+ chars: &mut std::iter::Peekable<I>,
+ emit: FormatSpecifier,
+ callback: &mut F,
+ ) where
+ I: Iterator<Item = (TextRange, char)>,
+ F: FnMut(TextRange, FormatSpecifier),
+ {
+ let (range, _) = chars.next().unwrap();
+ callback(range, emit);
+ }
+
+ fn read_integer<I, F>(chars: &mut std::iter::Peekable<I>, callback: &mut F)
+ where
+ I: Iterator<Item = (TextRange, char)>,
+ F: FnMut(TextRange, FormatSpecifier),
+ {
+ let (mut range, c) = chars.next().unwrap();
+ assert!(c.is_ascii_digit());
+ while let Some(&(r, next_char)) = chars.peek() {
+ if next_char.is_ascii_digit() {
+ chars.next();
+ range = range.cover(r);
+ } else {
+ break;
+ }
+ }
+ callback(range, FormatSpecifier::Integer);
+ }
+
+ fn read_identifier<I, F>(chars: &mut std::iter::Peekable<I>, callback: &mut F)
+ where
+ I: Iterator<Item = (TextRange, char)>,
+ F: FnMut(TextRange, FormatSpecifier),
+ {
+ let (mut range, c) = chars.next().unwrap();
+ assert!(c.is_alphabetic() || c == '_');
+ while let Some(&(r, next_char)) = chars.peek() {
+ if next_char == '_' || next_char.is_ascii_digit() || next_char.is_alphabetic() {
+ chars.next();
+ range = range.cover(r);
+ } else {
+ break;
+ }
+ }
+ callback(range, FormatSpecifier::Identifier);
+ }
+
+ fn read_escaped_format_specifier<I, F>(chars: &mut std::iter::Peekable<I>, callback: &mut F)
+ where
+ I: Iterator<Item = (TextRange, char)>,
+ F: FnMut(TextRange, FormatSpecifier),
+ {
+ let (range, _) = chars.peek().unwrap();
+ let offset = TextSize::from(1);
+ callback(TextRange::new(range.start() - offset, range.end()), FormatSpecifier::Escape);
+ chars.next();
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/insert_whitespace_into_node.rs b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/insert_whitespace_into_node.rs
new file mode 100644
index 000000000..f54ae6c92
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/insert_whitespace_into_node.rs
@@ -0,0 +1,136 @@
+//! Utilities for formatting macro expanded nodes until we get a proper formatter.
+use syntax::{
+ ast::make,
+ ted::{self, Position},
+ NodeOrToken,
+ SyntaxKind::{self, *},
+ SyntaxNode, SyntaxToken, WalkEvent, T,
+};
+
+// FIXME: It would also be cool to share logic here and in the mbe tests,
+// which are pretty unreadable at the moment.
+/// Renders a [`SyntaxNode`] with whitespace inserted between tokens that require them.
+pub fn insert_ws_into(syn: SyntaxNode) -> SyntaxNode {
+ let mut indent = 0;
+ let mut last: Option<SyntaxKind> = None;
+ let mut mods = Vec::new();
+ let syn = syn.clone_subtree().clone_for_update();
+
+ let before = Position::before;
+ let after = Position::after;
+
+ let do_indent = |pos: fn(_) -> Position, token: &SyntaxToken, indent| {
+ (pos(token.clone()), make::tokens::whitespace(&" ".repeat(2 * indent)))
+ };
+ let do_ws = |pos: fn(_) -> Position, token: &SyntaxToken| {
+ (pos(token.clone()), make::tokens::single_space())
+ };
+ let do_nl = |pos: fn(_) -> Position, token: &SyntaxToken| {
+ (pos(token.clone()), make::tokens::single_newline())
+ };
+
+ for event in syn.preorder_with_tokens() {
+ let token = match event {
+ WalkEvent::Enter(NodeOrToken::Token(token)) => token,
+ WalkEvent::Leave(NodeOrToken::Node(node))
+ if matches!(
+ node.kind(),
+ ATTR | MATCH_ARM | STRUCT | ENUM | UNION | FN | IMPL | MACRO_RULES
+ ) =>
+ {
+ if indent > 0 {
+ mods.push((
+ Position::after(node.clone()),
+ make::tokens::whitespace(&" ".repeat(2 * indent)),
+ ));
+ }
+ if node.parent().is_some() {
+ mods.push((Position::after(node), make::tokens::single_newline()));
+ }
+ continue;
+ }
+ _ => continue,
+ };
+ let tok = &token;
+
+ let is_next = |f: fn(SyntaxKind) -> bool, default| -> bool {
+ tok.next_token().map(|it| f(it.kind())).unwrap_or(default)
+ };
+ let is_last =
+ |f: fn(SyntaxKind) -> bool, default| -> bool { last.map(f).unwrap_or(default) };
+
+ match tok.kind() {
+ k if is_text(k) && is_next(|it| !it.is_punct() || it == UNDERSCORE, false) => {
+ mods.push(do_ws(after, tok));
+ }
+ L_CURLY if is_next(|it| it != R_CURLY, true) => {
+ indent += 1;
+ if is_last(is_text, false) {
+ mods.push(do_ws(before, tok));
+ }
+
+ mods.push(do_indent(after, tok, indent));
+ mods.push(do_nl(after, tok));
+ }
+ R_CURLY if is_last(|it| it != L_CURLY, true) => {
+ indent = indent.saturating_sub(1);
+
+ if indent > 0 {
+ mods.push(do_indent(before, tok, indent));
+ }
+ mods.push(do_nl(before, tok));
+ }
+ R_CURLY => {
+ if indent > 0 {
+ mods.push(do_indent(after, tok, indent));
+ }
+ mods.push(do_nl(after, tok));
+ }
+ LIFETIME_IDENT if is_next(is_text, true) => {
+ mods.push(do_ws(after, tok));
+ }
+ MUT_KW if is_next(|it| it == SELF_KW, false) => {
+ mods.push(do_ws(after, tok));
+ }
+ AS_KW | DYN_KW | IMPL_KW | CONST_KW => {
+ mods.push(do_ws(after, tok));
+ }
+ T![;] => {
+ if indent > 0 {
+ mods.push(do_indent(after, tok, indent));
+ }
+ mods.push(do_nl(after, tok));
+ }
+ T![=] if is_next(|it| it == T![>], false) => {
+ // FIXME: this branch is for `=>` in macro_rules!, which is currently parsed as
+ // two separate symbols.
+ mods.push(do_ws(before, tok));
+ mods.push(do_ws(after, &tok.next_token().unwrap()));
+ }
+ T![->] | T![=] | T![=>] => {
+ mods.push(do_ws(before, tok));
+ mods.push(do_ws(after, tok));
+ }
+ T![!] if is_last(|it| it == MACRO_RULES_KW, false) && is_next(is_text, false) => {
+ mods.push(do_ws(after, tok));
+ }
+ _ => (),
+ }
+
+ last = Some(tok.kind());
+ }
+
+ for (pos, insert) in mods {
+ ted::insert(pos, insert);
+ }
+
+ if let Some(it) = syn.last_token().filter(|it| it.kind() == SyntaxKind::WHITESPACE) {
+ ted::remove(it);
+ }
+
+ syn
+}
+
+fn is_text(k: SyntaxKind) -> bool {
+ k.is_keyword() || k.is_literal() || k == IDENT || k == UNDERSCORE
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/node_ext.rs b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/node_ext.rs
new file mode 100644
index 000000000..84bde4d44
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/node_ext.rs
@@ -0,0 +1,460 @@
+//! Various helper functions to work with SyntaxNodes.
+use itertools::Itertools;
+use parser::T;
+use syntax::{
+ ast::{self, HasLoopBody, PathSegmentKind, VisibilityKind},
+ AstNode, Preorder, RustLanguage, WalkEvent,
+};
+
+pub fn expr_as_name_ref(expr: &ast::Expr) -> Option<ast::NameRef> {
+ if let ast::Expr::PathExpr(expr) = expr {
+ let path = expr.path()?;
+ path.as_single_name_ref()
+ } else {
+ None
+ }
+}
+
+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()
+}
+
+/// Preorder walk all the expression's child expressions.
+pub fn walk_expr(expr: &ast::Expr, cb: &mut dyn FnMut(ast::Expr)) {
+ preorder_expr(expr, &mut |ev| {
+ if let WalkEvent::Enter(expr) = ev {
+ cb(expr);
+ }
+ false
+ })
+}
+
+/// Preorder walk all the expression's child expressions preserving events.
+/// If the callback returns true on an [`WalkEvent::Enter`], the subtree of the expression will be skipped.
+/// Note that the subtree may already be skipped due to the context analysis this function does.
+pub fn preorder_expr(start: &ast::Expr, cb: &mut dyn FnMut(WalkEvent<ast::Expr>) -> bool) {
+ let mut preorder = start.syntax().preorder();
+ while let Some(event) = preorder.next() {
+ let node = match event {
+ WalkEvent::Enter(node) => node,
+ WalkEvent::Leave(node) => {
+ if let Some(expr) = ast::Expr::cast(node) {
+ cb(WalkEvent::Leave(expr));
+ }
+ continue;
+ }
+ };
+ if let Some(let_stmt) = node.parent().and_then(ast::LetStmt::cast) {
+ if Some(node.clone()) != let_stmt.initializer().map(|it| it.syntax().clone()) {
+ // skipping potential const pat expressions in let statements
+ preorder.skip_subtree();
+ continue;
+ }
+ }
+
+ match ast::Stmt::cast(node.clone()) {
+ // Don't skip subtree since we want to process the expression child next
+ Some(ast::Stmt::ExprStmt(_)) | Some(ast::Stmt::LetStmt(_)) => (),
+ // skip inner items which might have their own expressions
+ Some(ast::Stmt::Item(_)) => preorder.skip_subtree(),
+ None => {
+ // skip const args, those expressions are a different context
+ if ast::GenericArg::can_cast(node.kind()) {
+ preorder.skip_subtree();
+ } else if let Some(expr) = ast::Expr::cast(node) {
+ let is_different_context = match &expr {
+ ast::Expr::BlockExpr(block_expr) => {
+ matches!(
+ block_expr.modifier(),
+ Some(
+ ast::BlockModifier::Async(_)
+ | ast::BlockModifier::Try(_)
+ | ast::BlockModifier::Const(_)
+ )
+ )
+ }
+ ast::Expr::ClosureExpr(_) => true,
+ _ => false,
+ } && expr.syntax() != start.syntax();
+ let skip = cb(WalkEvent::Enter(expr));
+ if skip || is_different_context {
+ preorder.skip_subtree();
+ }
+ }
+ }
+ }
+ }
+}
+
+/// Preorder walk all the expression's child patterns.
+pub fn walk_patterns_in_expr(start: &ast::Expr, cb: &mut dyn FnMut(ast::Pat)) {
+ let mut preorder = start.syntax().preorder();
+ while let Some(event) = preorder.next() {
+ let node = match event {
+ WalkEvent::Enter(node) => node,
+ WalkEvent::Leave(_) => continue,
+ };
+ match ast::Stmt::cast(node.clone()) {
+ Some(ast::Stmt::LetStmt(l)) => {
+ if let Some(pat) = l.pat() {
+ walk_pat(&pat, cb);
+ }
+ if let Some(expr) = l.initializer() {
+ walk_patterns_in_expr(&expr, cb);
+ }
+ preorder.skip_subtree();
+ }
+ // Don't skip subtree since we want to process the expression child next
+ Some(ast::Stmt::ExprStmt(_)) => (),
+ // skip inner items which might have their own patterns
+ Some(ast::Stmt::Item(_)) => preorder.skip_subtree(),
+ None => {
+ // skip const args, those are a different context
+ if ast::GenericArg::can_cast(node.kind()) {
+ preorder.skip_subtree();
+ } else if let Some(expr) = ast::Expr::cast(node.clone()) {
+ let is_different_context = match &expr {
+ ast::Expr::BlockExpr(block_expr) => {
+ matches!(
+ block_expr.modifier(),
+ Some(
+ ast::BlockModifier::Async(_)
+ | ast::BlockModifier::Try(_)
+ | ast::BlockModifier::Const(_)
+ )
+ )
+ }
+ ast::Expr::ClosureExpr(_) => true,
+ _ => false,
+ } && expr.syntax() != start.syntax();
+ if is_different_context {
+ preorder.skip_subtree();
+ }
+ } else if let Some(pat) = ast::Pat::cast(node) {
+ preorder.skip_subtree();
+ walk_pat(&pat, cb);
+ }
+ }
+ }
+ }
+}
+
+/// Preorder walk all the pattern's sub patterns.
+pub fn walk_pat(pat: &ast::Pat, cb: &mut dyn FnMut(ast::Pat)) {
+ let mut preorder = pat.syntax().preorder();
+ while let Some(event) = preorder.next() {
+ let node = match event {
+ WalkEvent::Enter(node) => node,
+ WalkEvent::Leave(_) => continue,
+ };
+ let kind = node.kind();
+ match ast::Pat::cast(node) {
+ Some(pat @ ast::Pat::ConstBlockPat(_)) => {
+ preorder.skip_subtree();
+ cb(pat);
+ }
+ Some(pat) => {
+ cb(pat);
+ }
+ // skip const args
+ None if ast::GenericArg::can_cast(kind) => {
+ preorder.skip_subtree();
+ }
+ None => (),
+ }
+ }
+}
+
+/// Preorder walk all the type's sub types.
+pub fn walk_ty(ty: &ast::Type, cb: &mut dyn FnMut(ast::Type)) {
+ let mut preorder = ty.syntax().preorder();
+ while let Some(event) = preorder.next() {
+ let node = match event {
+ WalkEvent::Enter(node) => node,
+ WalkEvent::Leave(_) => continue,
+ };
+ let kind = node.kind();
+ match ast::Type::cast(node) {
+ Some(ty @ ast::Type::MacroType(_)) => {
+ preorder.skip_subtree();
+ cb(ty)
+ }
+ Some(ty) => {
+ cb(ty);
+ }
+ // skip const args
+ None if ast::ConstArg::can_cast(kind) => {
+ preorder.skip_subtree();
+ }
+ None => (),
+ }
+ }
+}
+
+pub fn vis_eq(this: &ast::Visibility, other: &ast::Visibility) -> bool {
+ match (this.kind(), other.kind()) {
+ (VisibilityKind::In(this), VisibilityKind::In(other)) => {
+ stdx::iter_eq_by(this.segments(), other.segments(), |lhs, rhs| {
+ lhs.kind().zip(rhs.kind()).map_or(false, |it| match it {
+ (PathSegmentKind::CrateKw, PathSegmentKind::CrateKw)
+ | (PathSegmentKind::SelfKw, PathSegmentKind::SelfKw)
+ | (PathSegmentKind::SuperKw, PathSegmentKind::SuperKw) => true,
+ (PathSegmentKind::Name(lhs), PathSegmentKind::Name(rhs)) => {
+ lhs.text() == rhs.text()
+ }
+ _ => false,
+ })
+ })
+ }
+ (VisibilityKind::PubSelf, VisibilityKind::PubSelf)
+ | (VisibilityKind::PubSuper, VisibilityKind::PubSuper)
+ | (VisibilityKind::PubCrate, VisibilityKind::PubCrate)
+ | (VisibilityKind::Pub, VisibilityKind::Pub) => true,
+ _ => false,
+ }
+}
+
+/// Returns the `let` only if there is exactly one (that is, `let pat = expr`
+/// or `((let pat = expr))`, but not `let pat = expr && expr` or `non_let_expr`).
+pub fn single_let(expr: ast::Expr) -> Option<ast::LetExpr> {
+ match expr {
+ ast::Expr::ParenExpr(expr) => expr.expr().and_then(single_let),
+ ast::Expr::LetExpr(expr) => Some(expr),
+ _ => None,
+ }
+}
+
+pub fn is_pattern_cond(expr: ast::Expr) -> bool {
+ match expr {
+ ast::Expr::BinExpr(expr)
+ if expr.op_kind() == Some(ast::BinaryOp::LogicOp(ast::LogicOp::And)) =>
+ {
+ expr.lhs()
+ .map(is_pattern_cond)
+ .or_else(|| expr.rhs().map(is_pattern_cond))
+ .unwrap_or(false)
+ }
+ ast::Expr::ParenExpr(expr) => expr.expr().map_or(false, is_pattern_cond),
+ ast::Expr::LetExpr(_) => true,
+ _ => false,
+ }
+}
+
+/// Calls `cb` on each expression inside `expr` that is at "tail position".
+/// Does not walk into `break` or `return` expressions.
+/// Note that modifying the tree while iterating it will cause undefined iteration which might
+/// potentially results in an out of bounds panic.
+pub fn for_each_tail_expr(expr: &ast::Expr, cb: &mut dyn FnMut(&ast::Expr)) {
+ match expr {
+ ast::Expr::BlockExpr(b) => {
+ match b.modifier() {
+ Some(
+ ast::BlockModifier::Async(_)
+ | ast::BlockModifier::Try(_)
+ | ast::BlockModifier::Const(_),
+ ) => return cb(expr),
+
+ Some(ast::BlockModifier::Label(label)) => {
+ for_each_break_expr(Some(label), b.stmt_list(), &mut |b| {
+ cb(&ast::Expr::BreakExpr(b))
+ });
+ }
+ Some(ast::BlockModifier::Unsafe(_)) => (),
+ None => (),
+ }
+ if let Some(stmt_list) = b.stmt_list() {
+ if let Some(e) = stmt_list.tail_expr() {
+ for_each_tail_expr(&e, cb);
+ }
+ }
+ }
+ ast::Expr::IfExpr(if_) => {
+ let mut if_ = if_.clone();
+ loop {
+ if let Some(block) = if_.then_branch() {
+ for_each_tail_expr(&ast::Expr::BlockExpr(block), cb);
+ }
+ match if_.else_branch() {
+ Some(ast::ElseBranch::IfExpr(it)) => if_ = it,
+ Some(ast::ElseBranch::Block(block)) => {
+ for_each_tail_expr(&ast::Expr::BlockExpr(block), cb);
+ break;
+ }
+ None => break,
+ }
+ }
+ }
+ ast::Expr::LoopExpr(l) => {
+ for_each_break_expr(l.label(), l.loop_body().and_then(|it| it.stmt_list()), &mut |b| {
+ cb(&ast::Expr::BreakExpr(b))
+ })
+ }
+ ast::Expr::MatchExpr(m) => {
+ if let Some(arms) = m.match_arm_list() {
+ arms.arms().filter_map(|arm| arm.expr()).for_each(|e| for_each_tail_expr(&e, cb));
+ }
+ }
+ ast::Expr::ArrayExpr(_)
+ | ast::Expr::AwaitExpr(_)
+ | ast::Expr::BinExpr(_)
+ | ast::Expr::BoxExpr(_)
+ | ast::Expr::BreakExpr(_)
+ | ast::Expr::CallExpr(_)
+ | ast::Expr::CastExpr(_)
+ | ast::Expr::ClosureExpr(_)
+ | ast::Expr::ContinueExpr(_)
+ | ast::Expr::FieldExpr(_)
+ | ast::Expr::ForExpr(_)
+ | ast::Expr::IndexExpr(_)
+ | ast::Expr::Literal(_)
+ | ast::Expr::MacroExpr(_)
+ | ast::Expr::MacroStmts(_)
+ | ast::Expr::MethodCallExpr(_)
+ | ast::Expr::ParenExpr(_)
+ | ast::Expr::PathExpr(_)
+ | ast::Expr::PrefixExpr(_)
+ | ast::Expr::RangeExpr(_)
+ | ast::Expr::RecordExpr(_)
+ | ast::Expr::RefExpr(_)
+ | ast::Expr::ReturnExpr(_)
+ | ast::Expr::TryExpr(_)
+ | ast::Expr::TupleExpr(_)
+ | ast::Expr::WhileExpr(_)
+ | ast::Expr::LetExpr(_)
+ | ast::Expr::UnderscoreExpr(_)
+ | ast::Expr::YieldExpr(_) => cb(expr),
+ }
+}
+
+pub fn for_each_break_and_continue_expr(
+ label: Option<ast::Label>,
+ body: Option<ast::StmtList>,
+ cb: &mut dyn FnMut(ast::Expr),
+) {
+ let label = label.and_then(|lbl| lbl.lifetime());
+ if let Some(b) = body {
+ let tree_depth_iterator = TreeWithDepthIterator::new(b);
+ for (expr, depth) in tree_depth_iterator {
+ match expr {
+ ast::Expr::BreakExpr(b)
+ if (depth == 0 && b.lifetime().is_none())
+ || eq_label_lt(&label, &b.lifetime()) =>
+ {
+ cb(ast::Expr::BreakExpr(b));
+ }
+ ast::Expr::ContinueExpr(c)
+ if (depth == 0 && c.lifetime().is_none())
+ || eq_label_lt(&label, &c.lifetime()) =>
+ {
+ cb(ast::Expr::ContinueExpr(c));
+ }
+ _ => (),
+ }
+ }
+ }
+}
+
+fn for_each_break_expr(
+ label: Option<ast::Label>,
+ body: Option<ast::StmtList>,
+ cb: &mut dyn FnMut(ast::BreakExpr),
+) {
+ let label = label.and_then(|lbl| lbl.lifetime());
+ if let Some(b) = body {
+ let tree_depth_iterator = TreeWithDepthIterator::new(b);
+ for (expr, depth) in tree_depth_iterator {
+ match expr {
+ ast::Expr::BreakExpr(b)
+ if (depth == 0 && b.lifetime().is_none())
+ || eq_label_lt(&label, &b.lifetime()) =>
+ {
+ cb(b);
+ }
+ _ => (),
+ }
+ }
+ }
+}
+
+fn eq_label_lt(lt1: &Option<ast::Lifetime>, lt2: &Option<ast::Lifetime>) -> bool {
+ lt1.as_ref().zip(lt2.as_ref()).map_or(false, |(lt, lbl)| lt.text() == lbl.text())
+}
+
+struct TreeWithDepthIterator {
+ preorder: Preorder<RustLanguage>,
+ depth: u32,
+}
+
+impl TreeWithDepthIterator {
+ fn new(body: ast::StmtList) -> Self {
+ let preorder = body.syntax().preorder();
+ Self { preorder, depth: 0 }
+ }
+}
+
+impl Iterator for TreeWithDepthIterator {
+ type Item = (ast::Expr, u32);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ while let Some(event) = self.preorder.find_map(|ev| match ev {
+ WalkEvent::Enter(it) => ast::Expr::cast(it).map(WalkEvent::Enter),
+ WalkEvent::Leave(it) => ast::Expr::cast(it).map(WalkEvent::Leave),
+ }) {
+ match event {
+ WalkEvent::Enter(
+ ast::Expr::LoopExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::ForExpr(_),
+ ) => {
+ self.depth += 1;
+ }
+ WalkEvent::Leave(
+ ast::Expr::LoopExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::ForExpr(_),
+ ) => {
+ self.depth -= 1;
+ }
+ WalkEvent::Enter(ast::Expr::BlockExpr(e)) if e.label().is_some() => {
+ self.depth += 1;
+ }
+ WalkEvent::Leave(ast::Expr::BlockExpr(e)) if e.label().is_some() => {
+ self.depth -= 1;
+ }
+ WalkEvent::Enter(expr) => return Some((expr, self.depth)),
+ _ => (),
+ }
+ }
+ None
+ }
+}
+
+/// Parses the input token tree as comma separated plain paths.
+pub fn parse_tt_as_comma_sep_paths(input: ast::TokenTree) -> Option<Vec<ast::Path>> {
+ let r_paren = input.r_paren_token();
+ let tokens =
+ input.syntax().children_with_tokens().skip(1).map_while(|it| match it.into_token() {
+ // seeing a keyword means the attribute is unclosed so stop parsing here
+ Some(tok) if tok.kind().is_keyword() => None,
+ // don't include the right token tree parenthesis if it exists
+ tok @ Some(_) if tok == r_paren => None,
+ // only nodes that we can find are other TokenTrees, those are unexpected in this parse though
+ None => None,
+ Some(tok) => Some(tok),
+ });
+ let input_expressions = tokens.group_by(|tok| tok.kind() == T![,]);
+ let paths = input_expressions
+ .into_iter()
+ .filter_map(|(is_sep, group)| (!is_sep).then(|| group))
+ .filter_map(|mut tokens| {
+ syntax::hacks::parse_expr_from_str(&tokens.join("")).and_then(|expr| match expr {
+ ast::Expr::PathExpr(it) => it.path(),
+ _ => None,
+ })
+ })
+ .collect();
+ Some(paths)
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt
new file mode 100644
index 000000000..2f531ca0c
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt
@@ -0,0 +1,533 @@
+[
+ (
+ Module {
+ id: ModuleId {
+ krate: CrateId(
+ 0,
+ ),
+ block: None,
+ local_id: Idx::<ModuleData>(0),
+ },
+ },
+ [
+ FileSymbol {
+ name: "Alias",
+ loc: DeclarationLocation {
+ hir_file_id: HirFileId(
+ FileId(
+ FileId(
+ 0,
+ ),
+ ),
+ ),
+ ptr: SyntaxNodePtr {
+ kind: TYPE_ALIAS,
+ range: 397..417,
+ },
+ name_ptr: SyntaxNodePtr {
+ kind: NAME,
+ range: 402..407,
+ },
+ },
+ kind: TypeAlias,
+ container_name: None,
+ },
+ FileSymbol {
+ name: "CONST",
+ loc: DeclarationLocation {
+ hir_file_id: HirFileId(
+ FileId(
+ FileId(
+ 0,
+ ),
+ ),
+ ),
+ ptr: SyntaxNodePtr {
+ kind: CONST,
+ range: 340..361,
+ },
+ name_ptr: SyntaxNodePtr {
+ kind: NAME,
+ range: 346..351,
+ },
+ },
+ kind: Const,
+ container_name: None,
+ },
+ FileSymbol {
+ name: "CONST_WITH_INNER",
+ loc: DeclarationLocation {
+ hir_file_id: HirFileId(
+ FileId(
+ FileId(
+ 0,
+ ),
+ ),
+ ),
+ ptr: SyntaxNodePtr {
+ kind: CONST,
+ range: 520..592,
+ },
+ name_ptr: SyntaxNodePtr {
+ kind: NAME,
+ range: 526..542,
+ },
+ },
+ kind: Const,
+ container_name: None,
+ },
+ FileSymbol {
+ name: "Enum",
+ loc: DeclarationLocation {
+ hir_file_id: HirFileId(
+ FileId(
+ FileId(
+ 0,
+ ),
+ ),
+ ),
+ ptr: SyntaxNodePtr {
+ kind: ENUM,
+ range: 185..207,
+ },
+ name_ptr: SyntaxNodePtr {
+ kind: NAME,
+ range: 190..194,
+ },
+ },
+ kind: Enum,
+ container_name: None,
+ },
+ FileSymbol {
+ name: "Macro",
+ loc: DeclarationLocation {
+ hir_file_id: HirFileId(
+ FileId(
+ FileId(
+ 0,
+ ),
+ ),
+ ),
+ ptr: SyntaxNodePtr {
+ kind: MACRO_DEF,
+ range: 153..168,
+ },
+ name_ptr: SyntaxNodePtr {
+ kind: NAME,
+ range: 159..164,
+ },
+ },
+ kind: Macro,
+ container_name: None,
+ },
+ FileSymbol {
+ name: "STATIC",
+ loc: DeclarationLocation {
+ hir_file_id: HirFileId(
+ FileId(
+ FileId(
+ 0,
+ ),
+ ),
+ ),
+ ptr: SyntaxNodePtr {
+ kind: STATIC,
+ range: 362..396,
+ },
+ name_ptr: SyntaxNodePtr {
+ kind: NAME,
+ range: 369..375,
+ },
+ },
+ kind: Static,
+ container_name: None,
+ },
+ FileSymbol {
+ name: "Struct",
+ loc: DeclarationLocation {
+ hir_file_id: HirFileId(
+ FileId(
+ FileId(
+ 0,
+ ),
+ ),
+ ),
+ ptr: SyntaxNodePtr {
+ kind: STRUCT,
+ range: 170..184,
+ },
+ name_ptr: SyntaxNodePtr {
+ kind: NAME,
+ range: 177..183,
+ },
+ },
+ kind: Struct,
+ container_name: None,
+ },
+ FileSymbol {
+ name: "StructFromMacro",
+ loc: DeclarationLocation {
+ hir_file_id: HirFileId(
+ MacroFile(
+ MacroFile {
+ macro_call_id: MacroCallId(
+ 0,
+ ),
+ },
+ ),
+ ),
+ ptr: SyntaxNodePtr {
+ kind: STRUCT,
+ range: 0..22,
+ },
+ name_ptr: SyntaxNodePtr {
+ kind: NAME,
+ range: 6..21,
+ },
+ },
+ kind: Struct,
+ container_name: None,
+ },
+ FileSymbol {
+ name: "StructInFn",
+ loc: DeclarationLocation {
+ hir_file_id: HirFileId(
+ FileId(
+ FileId(
+ 0,
+ ),
+ ),
+ ),
+ ptr: SyntaxNodePtr {
+ kind: STRUCT,
+ range: 318..336,
+ },
+ name_ptr: SyntaxNodePtr {
+ kind: NAME,
+ range: 325..335,
+ },
+ },
+ kind: Struct,
+ container_name: Some(
+ "main",
+ ),
+ },
+ FileSymbol {
+ name: "StructInNamedConst",
+ loc: DeclarationLocation {
+ hir_file_id: HirFileId(
+ FileId(
+ FileId(
+ 0,
+ ),
+ ),
+ ),
+ ptr: SyntaxNodePtr {
+ kind: STRUCT,
+ range: 555..581,
+ },
+ name_ptr: SyntaxNodePtr {
+ kind: NAME,
+ range: 562..580,
+ },
+ },
+ kind: Struct,
+ container_name: Some(
+ "CONST_WITH_INNER",
+ ),
+ },
+ FileSymbol {
+ name: "StructInUnnamedConst",
+ loc: DeclarationLocation {
+ hir_file_id: HirFileId(
+ FileId(
+ FileId(
+ 0,
+ ),
+ ),
+ ),
+ ptr: SyntaxNodePtr {
+ kind: STRUCT,
+ range: 479..507,
+ },
+ name_ptr: SyntaxNodePtr {
+ kind: NAME,
+ range: 486..506,
+ },
+ },
+ kind: Struct,
+ container_name: None,
+ },
+ FileSymbol {
+ name: "Trait",
+ loc: DeclarationLocation {
+ hir_file_id: HirFileId(
+ FileId(
+ FileId(
+ 0,
+ ),
+ ),
+ ),
+ ptr: SyntaxNodePtr {
+ kind: TRAIT,
+ range: 261..300,
+ },
+ name_ptr: SyntaxNodePtr {
+ kind: NAME,
+ range: 267..272,
+ },
+ },
+ kind: Trait,
+ container_name: None,
+ },
+ FileSymbol {
+ name: "Union",
+ loc: DeclarationLocation {
+ hir_file_id: HirFileId(
+ FileId(
+ FileId(
+ 0,
+ ),
+ ),
+ ),
+ ptr: SyntaxNodePtr {
+ kind: UNION,
+ range: 208..222,
+ },
+ name_ptr: SyntaxNodePtr {
+ kind: NAME,
+ range: 214..219,
+ },
+ },
+ kind: Union,
+ container_name: None,
+ },
+ FileSymbol {
+ name: "a_mod",
+ loc: DeclarationLocation {
+ hir_file_id: HirFileId(
+ FileId(
+ FileId(
+ 0,
+ ),
+ ),
+ ),
+ ptr: SyntaxNodePtr {
+ kind: MODULE,
+ range: 419..457,
+ },
+ name_ptr: SyntaxNodePtr {
+ kind: NAME,
+ range: 423..428,
+ },
+ },
+ kind: Module,
+ container_name: None,
+ },
+ FileSymbol {
+ name: "b_mod",
+ loc: DeclarationLocation {
+ hir_file_id: HirFileId(
+ FileId(
+ FileId(
+ 0,
+ ),
+ ),
+ ),
+ ptr: SyntaxNodePtr {
+ kind: MODULE,
+ range: 594..604,
+ },
+ name_ptr: SyntaxNodePtr {
+ kind: NAME,
+ range: 598..603,
+ },
+ },
+ kind: Module,
+ container_name: None,
+ },
+ FileSymbol {
+ name: "define_struct",
+ loc: DeclarationLocation {
+ hir_file_id: HirFileId(
+ FileId(
+ FileId(
+ 0,
+ ),
+ ),
+ ),
+ ptr: SyntaxNodePtr {
+ kind: MACRO_RULES,
+ range: 51..131,
+ },
+ name_ptr: SyntaxNodePtr {
+ kind: NAME,
+ range: 64..77,
+ },
+ },
+ kind: Macro,
+ container_name: None,
+ },
+ FileSymbol {
+ name: "impl_fn",
+ loc: DeclarationLocation {
+ hir_file_id: HirFileId(
+ FileId(
+ FileId(
+ 0,
+ ),
+ ),
+ ),
+ ptr: SyntaxNodePtr {
+ kind: FN,
+ range: 242..257,
+ },
+ name_ptr: SyntaxNodePtr {
+ kind: NAME,
+ range: 245..252,
+ },
+ },
+ kind: Function,
+ container_name: None,
+ },
+ FileSymbol {
+ name: "macro_rules_macro",
+ loc: DeclarationLocation {
+ hir_file_id: HirFileId(
+ FileId(
+ FileId(
+ 0,
+ ),
+ ),
+ ),
+ ptr: SyntaxNodePtr {
+ kind: MACRO_RULES,
+ range: 1..48,
+ },
+ name_ptr: SyntaxNodePtr {
+ kind: NAME,
+ range: 14..31,
+ },
+ },
+ kind: Macro,
+ container_name: None,
+ },
+ FileSymbol {
+ name: "main",
+ loc: DeclarationLocation {
+ hir_file_id: HirFileId(
+ FileId(
+ FileId(
+ 0,
+ ),
+ ),
+ ),
+ ptr: SyntaxNodePtr {
+ kind: FN,
+ range: 302..338,
+ },
+ name_ptr: SyntaxNodePtr {
+ kind: NAME,
+ range: 305..309,
+ },
+ },
+ kind: Function,
+ container_name: None,
+ },
+ FileSymbol {
+ name: "trait_fn",
+ loc: DeclarationLocation {
+ hir_file_id: HirFileId(
+ FileId(
+ FileId(
+ 0,
+ ),
+ ),
+ ),
+ ptr: SyntaxNodePtr {
+ kind: FN,
+ range: 279..298,
+ },
+ name_ptr: SyntaxNodePtr {
+ kind: NAME,
+ range: 282..290,
+ },
+ },
+ kind: Function,
+ container_name: Some(
+ "Trait",
+ ),
+ },
+ ],
+ ),
+ (
+ Module {
+ id: ModuleId {
+ krate: CrateId(
+ 0,
+ ),
+ block: None,
+ local_id: Idx::<ModuleData>(1),
+ },
+ },
+ [
+ FileSymbol {
+ name: "StructInModA",
+ loc: DeclarationLocation {
+ hir_file_id: HirFileId(
+ FileId(
+ FileId(
+ 0,
+ ),
+ ),
+ ),
+ ptr: SyntaxNodePtr {
+ kind: STRUCT,
+ range: 435..455,
+ },
+ name_ptr: SyntaxNodePtr {
+ kind: NAME,
+ range: 442..454,
+ },
+ },
+ kind: Struct,
+ container_name: None,
+ },
+ ],
+ ),
+ (
+ Module {
+ id: ModuleId {
+ krate: CrateId(
+ 0,
+ ),
+ block: None,
+ local_id: Idx::<ModuleData>(2),
+ },
+ },
+ [
+ FileSymbol {
+ name: "StructInModB",
+ loc: DeclarationLocation {
+ hir_file_id: HirFileId(
+ FileId(
+ FileId(
+ 1,
+ ),
+ ),
+ ),
+ ptr: SyntaxNodePtr {
+ kind: STRUCT,
+ range: 0..20,
+ },
+ name_ptr: SyntaxNodePtr {
+ kind: NAME,
+ range: 7..19,
+ },
+ },
+ kind: Struct,
+ container_name: None,
+ },
+ ],
+ ),
+]
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/tests/sourcegen_lints.rs b/src/tools/rust-analyzer/crates/ide-db/src/tests/sourcegen_lints.rs
new file mode 100644
index 000000000..5042f6d81
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/tests/sourcegen_lints.rs
@@ -0,0 +1,284 @@
+//! Generates descriptors structure for unstable feature from Unstable Book
+use std::{borrow::Cow, fs, path::Path};
+
+use itertools::Itertools;
+use stdx::format_to;
+use test_utils::project_root;
+use xshell::{cmd, Shell};
+
+/// This clones rustc repo, and so is not worth to keep up-to-date. We update
+/// manually by un-ignoring the test from time to time.
+#[test]
+#[ignore]
+fn sourcegen_lint_completions() {
+ let sh = &Shell::new().unwrap();
+
+ let rust_repo = project_root().join("./target/rust");
+ if !rust_repo.exists() {
+ cmd!(sh, "git clone --depth=1 https://github.com/rust-lang/rust {rust_repo}")
+ .run()
+ .unwrap();
+ }
+
+ let mut contents = String::from(
+ r"
+#[derive(Clone)]
+pub struct Lint {
+ pub label: &'static str,
+ pub description: &'static str,
+}
+pub struct LintGroup {
+ pub lint: Lint,
+ pub children: &'static [&'static str],
+}
+",
+ );
+
+ generate_lint_descriptor(sh, &mut contents);
+ contents.push('\n');
+
+ generate_feature_descriptor(&mut contents, &rust_repo.join("src/doc/unstable-book/src"));
+ contents.push('\n');
+
+ let lints_json = project_root().join("./target/clippy_lints.json");
+ cmd!(
+ sh,
+ "curl https://rust-lang.github.io/rust-clippy/master/lints.json --output {lints_json}"
+ )
+ .run()
+ .unwrap();
+ generate_descriptor_clippy(&mut contents, &lints_json);
+
+ let contents = sourcegen::add_preamble("sourcegen_lints", sourcegen::reformat(contents));
+
+ let destination = project_root().join("crates/ide_db/src/generated/lints.rs");
+ sourcegen::ensure_file_contents(destination.as_path(), &contents);
+}
+
+fn generate_lint_descriptor(sh: &Shell, buf: &mut String) {
+ // FIXME: rustdoc currently requires an input file for -Whelp cc https://github.com/rust-lang/rust/pull/88831
+ let file = project_root().join(file!());
+ let stdout = cmd!(sh, "rustdoc -W help {file}").read().unwrap();
+ let start_lints = stdout.find("---- ------- -------").unwrap();
+ let start_lint_groups = stdout.find("---- ---------").unwrap();
+ let start_lints_rustdoc =
+ stdout.find("Lint checks provided by plugins loaded by this crate:").unwrap();
+ let start_lint_groups_rustdoc =
+ stdout.find("Lint groups provided by plugins loaded by this crate:").unwrap();
+
+ buf.push_str(r#"pub const DEFAULT_LINTS: &[Lint] = &["#);
+ buf.push('\n');
+
+ let lints = stdout[start_lints..].lines().skip(1).take_while(|l| !l.is_empty()).map(|line| {
+ let (name, rest) = line.trim().split_once(char::is_whitespace).unwrap();
+ let (_default_level, description) = rest.trim().split_once(char::is_whitespace).unwrap();
+ (name.trim(), Cow::Borrowed(description.trim()), vec![])
+ });
+ let lint_groups =
+ stdout[start_lint_groups..].lines().skip(1).take_while(|l| !l.is_empty()).map(|line| {
+ let (name, lints) = line.trim().split_once(char::is_whitespace).unwrap();
+ (
+ name.trim(),
+ format!("lint group for: {}", lints.trim()).into(),
+ lints
+ .split_ascii_whitespace()
+ .map(|s| s.trim().trim_matches(',').replace('-', "_"))
+ .collect(),
+ )
+ });
+
+ let lints = lints
+ .chain(lint_groups)
+ .sorted_by(|(ident, ..), (ident2, ..)| ident.cmp(ident2))
+ .collect::<Vec<_>>();
+ for (name, description, ..) in &lints {
+ push_lint_completion(buf, &name.replace('-', "_"), description);
+ }
+ buf.push_str("];\n");
+ buf.push_str(r#"pub const DEFAULT_LINT_GROUPS: &[LintGroup] = &["#);
+ for (name, description, children) in &lints {
+ if !children.is_empty() {
+ // HACK: warnings is emitted with a general description, not with its members
+ if name == &"warnings" {
+ push_lint_group(buf, name, description, &Vec::new());
+ continue;
+ }
+ push_lint_group(buf, &name.replace('-', "_"), description, children);
+ }
+ }
+ buf.push('\n');
+ buf.push_str("];\n");
+
+ // rustdoc
+
+ buf.push('\n');
+ buf.push_str(r#"pub const RUSTDOC_LINTS: &[Lint] = &["#);
+ buf.push('\n');
+
+ let lints_rustdoc =
+ stdout[start_lints_rustdoc..].lines().skip(2).take_while(|l| !l.is_empty()).map(|line| {
+ let (name, rest) = line.trim().split_once(char::is_whitespace).unwrap();
+ let (_default_level, description) =
+ rest.trim().split_once(char::is_whitespace).unwrap();
+ (name.trim(), Cow::Borrowed(description.trim()), vec![])
+ });
+ let lint_groups_rustdoc =
+ stdout[start_lint_groups_rustdoc..].lines().skip(2).take_while(|l| !l.is_empty()).map(
+ |line| {
+ let (name, lints) = line.trim().split_once(char::is_whitespace).unwrap();
+ (
+ name.trim(),
+ format!("lint group for: {}", lints.trim()).into(),
+ lints
+ .split_ascii_whitespace()
+ .map(|s| s.trim().trim_matches(',').replace('-', "_"))
+ .collect(),
+ )
+ },
+ );
+
+ let lints_rustdoc = lints_rustdoc
+ .chain(lint_groups_rustdoc)
+ .sorted_by(|(ident, ..), (ident2, ..)| ident.cmp(ident2))
+ .collect::<Vec<_>>();
+
+ for (name, description, ..) in &lints_rustdoc {
+ push_lint_completion(buf, &name.replace('-', "_"), description)
+ }
+ buf.push_str("];\n");
+
+ buf.push_str(r#"pub const RUSTDOC_LINT_GROUPS: &[LintGroup] = &["#);
+ for (name, description, children) in &lints_rustdoc {
+ if !children.is_empty() {
+ push_lint_group(buf, &name.replace('-', "_"), description, children);
+ }
+ }
+ buf.push('\n');
+ buf.push_str("];\n");
+}
+
+fn generate_feature_descriptor(buf: &mut String, src_dir: &Path) {
+ let mut features = ["language-features", "library-features"]
+ .into_iter()
+ .flat_map(|it| sourcegen::list_files(&src_dir.join(it)))
+ .filter(|path| {
+ // Get all `.md ` files
+ path.extension().unwrap_or_default().to_str().unwrap_or_default() == "md"
+ })
+ .map(|path| {
+ let feature_ident = path.file_stem().unwrap().to_str().unwrap().replace('-', "_");
+ let doc = fs::read_to_string(path).unwrap();
+ (feature_ident, doc)
+ })
+ .collect::<Vec<_>>();
+ features.sort_by(|(feature_ident, _), (feature_ident2, _)| feature_ident.cmp(feature_ident2));
+
+ buf.push_str(r#"pub const FEATURES: &[Lint] = &["#);
+ for (feature_ident, doc) in features.into_iter() {
+ push_lint_completion(buf, &feature_ident, &doc)
+ }
+ buf.push('\n');
+ buf.push_str("];\n");
+}
+
+#[derive(Default)]
+struct ClippyLint {
+ help: String,
+ id: String,
+}
+
+fn unescape(s: &str) -> String {
+ s.replace(r#"\""#, "").replace(r#"\n"#, "\n").replace(r#"\r"#, "")
+}
+
+fn generate_descriptor_clippy(buf: &mut String, path: &Path) {
+ let file_content = std::fs::read_to_string(path).unwrap();
+ let mut clippy_lints: Vec<ClippyLint> = Vec::new();
+ let mut clippy_groups: std::collections::BTreeMap<String, Vec<String>> = Default::default();
+
+ for line in file_content.lines().map(|line| line.trim()) {
+ if let Some(line) = line.strip_prefix(r#""id": ""#) {
+ let clippy_lint = ClippyLint {
+ id: line.strip_suffix(r#"","#).expect("should be suffixed by comma").into(),
+ help: String::new(),
+ };
+ clippy_lints.push(clippy_lint)
+ } else if let Some(line) = line.strip_prefix(r#""group": ""#) {
+ if let Some(group) = line.strip_suffix("\",") {
+ clippy_groups
+ .entry(group.to_owned())
+ .or_default()
+ .push(clippy_lints.last().unwrap().id.clone());
+ }
+ } else if let Some(line) = line.strip_prefix(r#""docs": ""#) {
+ let prefix_to_strip = r#" ### What it does"#;
+ let line = match line.strip_prefix(prefix_to_strip) {
+ Some(line) => line,
+ None => {
+ eprintln!("unexpected clippy prefix for {}", clippy_lints.last().unwrap().id);
+ continue;
+ }
+ };
+ // Only take the description, any more than this is a lot of additional data we would embed into the exe
+ // which seems unnecessary
+ let up_to = line.find(r#"###"#).expect("no second section found?");
+ let line = &line[..up_to];
+
+ let clippy_lint = clippy_lints.last_mut().expect("clippy lint must already exist");
+ clippy_lint.help = unescape(line).trim().to_string();
+ }
+ }
+ clippy_lints.sort_by(|lint, lint2| lint.id.cmp(&lint2.id));
+
+ buf.push_str(r#"pub const CLIPPY_LINTS: &[Lint] = &["#);
+ buf.push('\n');
+ for clippy_lint in clippy_lints.into_iter() {
+ let lint_ident = format!("clippy::{}", clippy_lint.id);
+ let doc = clippy_lint.help;
+ push_lint_completion(buf, &lint_ident, &doc);
+ }
+ buf.push_str("];\n");
+
+ buf.push_str(r#"pub const CLIPPY_LINT_GROUPS: &[LintGroup] = &["#);
+ for (id, children) in clippy_groups {
+ let children = children.iter().map(|id| format!("clippy::{}", id)).collect::<Vec<_>>();
+ if !children.is_empty() {
+ let lint_ident = format!("clippy::{}", id);
+ let description = format!("lint group for: {}", children.iter().join(", "));
+ push_lint_group(buf, &lint_ident, &description, &children);
+ }
+ }
+ buf.push('\n');
+ buf.push_str("];\n");
+}
+
+fn push_lint_completion(buf: &mut String, label: &str, description: &str) {
+ format_to!(
+ buf,
+ r###" Lint {{
+ label: "{}",
+ description: r##"{}"##,
+ }},"###,
+ label,
+ description,
+ );
+}
+
+fn push_lint_group(buf: &mut String, label: &str, description: &str, children: &[String]) {
+ buf.push_str(
+ r###" LintGroup {
+ lint:
+ "###,
+ );
+
+ push_lint_completion(buf, label, description);
+
+ let children = format!("&[{}]", children.iter().map(|it| format!("\"{}\"", it)).join(", "));
+ format_to!(
+ buf,
+ r###"
+ children: {},
+ }},"###,
+ children,
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/traits.rs b/src/tools/rust-analyzer/crates/ide-db/src/traits.rs
new file mode 100644
index 000000000..6a7ea7c19
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/traits.rs
@@ -0,0 +1,273 @@
+//! Functionality for obtaining data related to traits from the DB.
+
+use crate::{defs::Definition, RootDatabase};
+use hir::{db::HirDatabase, AsAssocItem, Semantics};
+use rustc_hash::FxHashSet;
+use syntax::{ast, AstNode};
+
+/// Given the `impl` block, attempts to find the trait this `impl` corresponds to.
+pub fn resolve_target_trait(
+ sema: &Semantics<'_, RootDatabase>,
+ impl_def: &ast::Impl,
+) -> Option<hir::Trait> {
+ let ast_path =
+ impl_def.trait_().map(|it| it.syntax().clone()).and_then(ast::PathType::cast)?.path()?;
+
+ match sema.resolve_path(&ast_path) {
+ Some(hir::PathResolution::Def(hir::ModuleDef::Trait(def))) => Some(def),
+ _ => None,
+ }
+}
+
+/// Given the `impl` block, returns the list of associated items (e.g. functions or types) that are
+/// missing in this `impl` block.
+pub fn get_missing_assoc_items(
+ sema: &Semantics<'_, RootDatabase>,
+ impl_def: &ast::Impl,
+) -> Vec<hir::AssocItem> {
+ let imp = match sema.to_def(impl_def) {
+ Some(it) => it,
+ None => return vec![],
+ };
+
+ // Names must be unique between constants and functions. However, type aliases
+ // may share the same name as a function or constant.
+ let mut impl_fns_consts = FxHashSet::default();
+ let mut impl_type = FxHashSet::default();
+
+ for item in imp.items(sema.db) {
+ match item {
+ hir::AssocItem::Function(it) => {
+ impl_fns_consts.insert(it.name(sema.db).to_string());
+ }
+ hir::AssocItem::Const(it) => {
+ if let Some(name) = it.name(sema.db) {
+ impl_fns_consts.insert(name.to_string());
+ }
+ }
+ hir::AssocItem::TypeAlias(it) => {
+ impl_type.insert(it.name(sema.db).to_string());
+ }
+ }
+ }
+
+ resolve_target_trait(sema, impl_def).map_or(vec![], |target_trait| {
+ target_trait
+ .items(sema.db)
+ .into_iter()
+ .filter(|i| match i {
+ hir::AssocItem::Function(f) => {
+ !impl_fns_consts.contains(&f.name(sema.db).to_string())
+ }
+ hir::AssocItem::TypeAlias(t) => !impl_type.contains(&t.name(sema.db).to_string()),
+ hir::AssocItem::Const(c) => c
+ .name(sema.db)
+ .map(|n| !impl_fns_consts.contains(&n.to_string()))
+ .unwrap_or_default(),
+ })
+ .collect()
+ })
+}
+
+/// Converts associated trait impl items to their trait definition counterpart
+pub(crate) fn convert_to_def_in_trait(db: &dyn HirDatabase, def: Definition) -> Definition {
+ (|| {
+ let assoc = def.as_assoc_item(db)?;
+ let trait_ = assoc.containing_trait_impl(db)?;
+ assoc_item_of_trait(db, assoc, trait_)
+ })()
+ .unwrap_or(def)
+}
+
+/// If this is an trait (impl) assoc item, returns the assoc item of the corresponding trait definition.
+pub(crate) fn as_trait_assoc_def(db: &dyn HirDatabase, def: Definition) -> Option<Definition> {
+ let assoc = def.as_assoc_item(db)?;
+ let trait_ = match assoc.container(db) {
+ hir::AssocItemContainer::Trait(_) => return Some(def),
+ hir::AssocItemContainer::Impl(i) => i.trait_(db),
+ }?;
+ assoc_item_of_trait(db, assoc, trait_)
+}
+
+fn assoc_item_of_trait(
+ db: &dyn HirDatabase,
+ assoc: hir::AssocItem,
+ trait_: hir::Trait,
+) -> Option<Definition> {
+ use hir::AssocItem::*;
+ let name = match assoc {
+ Function(it) => it.name(db),
+ Const(it) => it.name(db)?,
+ TypeAlias(it) => it.name(db),
+ };
+ let item = trait_.items(db).into_iter().find(|it| match (it, assoc) {
+ (Function(trait_func), Function(_)) => trait_func.name(db) == name,
+ (Const(trait_konst), Const(_)) => trait_konst.name(db).map_or(false, |it| it == name),
+ (TypeAlias(trait_type_alias), TypeAlias(_)) => trait_type_alias.name(db) == name,
+ _ => false,
+ })?;
+ Some(Definition::from(item))
+}
+
+#[cfg(test)]
+mod tests {
+ use base_db::{fixture::ChangeFixture, FilePosition};
+ use expect_test::{expect, Expect};
+ use hir::Semantics;
+ use syntax::ast::{self, AstNode};
+
+ use crate::RootDatabase;
+
+ /// Creates analysis from a multi-file fixture, returns positions marked with $0.
+ pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
+ let change_fixture = ChangeFixture::parse(ra_fixture);
+ let mut database = RootDatabase::default();
+ database.apply_change(change_fixture.change);
+ let (file_id, range_or_offset) =
+ change_fixture.file_position.expect("expected a marker ($0)");
+ let offset = range_or_offset.expect_offset();
+ (database, FilePosition { file_id, offset })
+ }
+
+ fn check_trait(ra_fixture: &str, expect: Expect) {
+ let (db, position) = position(ra_fixture);
+ let sema = Semantics::new(&db);
+ let file = sema.parse(position.file_id);
+ let impl_block: ast::Impl =
+ sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap();
+ let trait_ = crate::traits::resolve_target_trait(&sema, &impl_block);
+ let actual = match trait_ {
+ Some(trait_) => trait_.name(&db).to_string(),
+ None => String::new(),
+ };
+ expect.assert_eq(&actual);
+ }
+
+ fn check_missing_assoc(ra_fixture: &str, expect: Expect) {
+ let (db, position) = position(ra_fixture);
+ let sema = Semantics::new(&db);
+ let file = sema.parse(position.file_id);
+ let impl_block: ast::Impl =
+ sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap();
+ let items = crate::traits::get_missing_assoc_items(&sema, &impl_block);
+ let actual = items
+ .into_iter()
+ .map(|item| item.name(&db).unwrap().to_string())
+ .collect::<Vec<_>>()
+ .join("\n");
+ expect.assert_eq(&actual);
+ }
+
+ #[test]
+ fn resolve_trait() {
+ check_trait(
+ r#"
+pub trait Foo {
+ fn bar();
+}
+impl Foo for u8 {
+ $0
+}
+ "#,
+ expect![["Foo"]],
+ );
+ check_trait(
+ r#"
+pub trait Foo {
+ fn bar();
+}
+impl Foo for u8 {
+ fn bar() {
+ fn baz() {
+ $0
+ }
+ baz();
+ }
+}
+ "#,
+ expect![["Foo"]],
+ );
+ check_trait(
+ r#"
+pub trait Foo {
+ fn bar();
+}
+pub struct Bar;
+impl Bar {
+ $0
+}
+ "#,
+ expect![[""]],
+ );
+ }
+
+ #[test]
+ fn missing_assoc_items() {
+ check_missing_assoc(
+ r#"
+pub trait Foo {
+ const FOO: u8;
+ fn bar();
+}
+impl Foo for u8 {
+ $0
+}"#,
+ expect![[r#"
+ FOO
+ bar"#]],
+ );
+
+ check_missing_assoc(
+ r#"
+pub trait Foo {
+ const FOO: u8;
+ fn bar();
+}
+impl Foo for u8 {
+ const FOO: u8 = 10;
+ $0
+}"#,
+ expect![[r#"
+ bar"#]],
+ );
+
+ check_missing_assoc(
+ r#"
+pub trait Foo {
+ const FOO: u8;
+ fn bar();
+}
+impl Foo for u8 {
+ const FOO: u8 = 10;
+ fn bar() {$0}
+}"#,
+ expect![[r#""#]],
+ );
+
+ check_missing_assoc(
+ r#"
+pub struct Foo;
+impl Foo {
+ fn bar() {$0}
+}"#,
+ expect![[r#""#]],
+ );
+
+ check_missing_assoc(
+ r#"
+trait Tr {
+ fn required();
+}
+macro_rules! m {
+ () => { fn required() {} };
+}
+impl Tr for () {
+ m!();
+ $0
+}
+
+ "#,
+ expect![[r#""#]],
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/ty_filter.rs b/src/tools/rust-analyzer/crates/ide-db/src/ty_filter.rs
new file mode 100644
index 000000000..46f47f258
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/ty_filter.rs
@@ -0,0 +1,86 @@
+//! This module contains structures for filtering the expected types.
+//! Use case for structures in this module is, for example, situation when you need to process
+//! only certain `Enum`s.
+
+use std::iter;
+
+use hir::Semantics;
+use syntax::ast::{self, make, Pat};
+
+use crate::RootDatabase;
+
+/// Enum types that implement `std::ops::Try` trait.
+#[derive(Clone, Copy)]
+pub enum TryEnum {
+ Result,
+ Option,
+}
+
+impl TryEnum {
+ const ALL: [TryEnum; 2] = [TryEnum::Option, TryEnum::Result];
+
+ /// Returns `Some(..)` if the provided type is an enum that implements `std::ops::Try`.
+ pub fn from_ty(sema: &Semantics<'_, RootDatabase>, ty: &hir::Type) -> Option<TryEnum> {
+ let enum_ = match ty.as_adt() {
+ Some(hir::Adt::Enum(it)) => it,
+ _ => return None,
+ };
+ TryEnum::ALL.iter().find_map(|&var| {
+ if enum_.name(sema.db).to_smol_str() == var.type_name() {
+ return Some(var);
+ }
+ None
+ })
+ }
+
+ pub fn happy_case(self) -> &'static str {
+ match self {
+ TryEnum::Result => "Ok",
+ TryEnum::Option => "Some",
+ }
+ }
+
+ pub fn sad_pattern(self) -> ast::Pat {
+ match self {
+ TryEnum::Result => make::tuple_struct_pat(
+ make::ext::ident_path("Err"),
+ iter::once(make::wildcard_pat().into()),
+ )
+ .into(),
+ TryEnum::Option => make::ext::simple_ident_pat(make::name("None")).into(),
+ }
+ }
+
+ pub fn happy_pattern(self, pat: Pat) -> ast::Pat {
+ match self {
+ TryEnum::Result => {
+ make::tuple_struct_pat(make::ext::ident_path("Ok"), iter::once(pat)).into()
+ }
+ TryEnum::Option => {
+ make::tuple_struct_pat(make::ext::ident_path("Some"), iter::once(pat)).into()
+ }
+ }
+ }
+
+ pub fn happy_pattern_wildcard(self) -> ast::Pat {
+ match self {
+ TryEnum::Result => make::tuple_struct_pat(
+ make::ext::ident_path("Ok"),
+ iter::once(make::wildcard_pat().into()),
+ )
+ .into(),
+ TryEnum::Option => make::tuple_struct_pat(
+ make::ext::ident_path("Some"),
+ iter::once(make::wildcard_pat().into()),
+ )
+ .into(),
+ }
+ }
+
+ fn type_name(self) -> &'static str {
+ match self {
+ TryEnum::Result => "Result",
+ TryEnum::Option => "Option",
+ }
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/use_trivial_contructor.rs b/src/tools/rust-analyzer/crates/ide-db/src/use_trivial_contructor.rs
new file mode 100644
index 000000000..39431bed3
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-db/src/use_trivial_contructor.rs
@@ -0,0 +1,34 @@
+//! Functionality for generating trivial contructors
+
+use hir::StructKind;
+use syntax::ast;
+
+/// given a type return the trivial contructor (if one exists)
+pub fn use_trivial_constructor(
+ db: &crate::RootDatabase,
+ path: ast::Path,
+ ty: &hir::Type,
+) -> Option<ast::Expr> {
+ match ty.as_adt() {
+ Some(hir::Adt::Enum(x)) => {
+ if let &[variant] = &*x.variants(db) {
+ if variant.kind(db) == hir::StructKind::Unit {
+ let path = ast::make::path_qualified(
+ path,
+ syntax::ast::make::path_segment(ast::make::name_ref(
+ &variant.name(db).to_smol_str(),
+ )),
+ );
+
+ return Some(syntax::ast::make::expr_path(path));
+ }
+ }
+ }
+ Some(hir::Adt::Struct(x)) if x.kind(db) == StructKind::Unit => {
+ return Some(syntax::ast::make::expr_path(path));
+ }
+ _ => {}
+ }
+
+ None
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/Cargo.toml b/src/tools/rust-analyzer/crates/ide-diagnostics/Cargo.toml
new file mode 100644
index 000000000..e221425ed
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/Cargo.toml
@@ -0,0 +1,34 @@
+[package]
+name = "ide-diagnostics"
+version = "0.0.0"
+description = "TBD"
+license = "MIT OR Apache-2.0"
+edition = "2021"
+rust-version = "1.57"
+
+[lib]
+doctest = false
+
+[dependencies]
+cov-mark = "2.0.0-pre.1"
+itertools = "0.10.3"
+
+
+either = "1.7.0"
+
+profile = { path = "../profile", version = "0.0.0" }
+stdx = { path = "../stdx", version = "0.0.0" }
+syntax = { path = "../syntax", version = "0.0.0" }
+text-edit = { path = "../text-edit", version = "0.0.0" }
+cfg = { path = "../cfg", version = "0.0.0" }
+hir = { path = "../hir", version = "0.0.0" }
+ide-db = { path = "../ide-db", version = "0.0.0" }
+
+[dev-dependencies]
+expect-test = "1.4.0"
+
+test-utils = { path = "../test-utils" }
+sourcegen = { path = "../sourcegen" }
+
+[features]
+in-rust-tree = []
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs
new file mode 100644
index 000000000..d12594a4c
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs
@@ -0,0 +1,30 @@
+use crate::{Diagnostic, DiagnosticsContext};
+
+// Diagnostic: break-outside-of-loop
+//
+// This diagnostic is triggered if the `break` keyword is used outside of a loop.
+pub(crate) fn break_outside_of_loop(
+ ctx: &DiagnosticsContext<'_>,
+ d: &hir::BreakOutsideOfLoop,
+) -> Diagnostic {
+ Diagnostic::new(
+ "break-outside-of-loop",
+ "break outside of loop",
+ ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::check_diagnostics;
+
+ #[test]
+ fn break_outside_of_loop() {
+ check_diagnostics(
+ r#"
+fn foo() { break; }
+ //^^^^^ error: break outside of loop
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/field_shorthand.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/field_shorthand.rs
new file mode 100644
index 000000000..2b7105362
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/field_shorthand.rs
@@ -0,0 +1,203 @@
+//! Suggests shortening `Foo { field: field }` to `Foo { field }` in both
+//! expressions and patterns.
+
+use ide_db::{base_db::FileId, source_change::SourceChange};
+use syntax::{ast, match_ast, AstNode, SyntaxNode};
+use text_edit::TextEdit;
+
+use crate::{fix, Diagnostic, Severity};
+
+pub(crate) fn field_shorthand(acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode) {
+ match_ast! {
+ match node {
+ ast::RecordExpr(it) => check_expr_field_shorthand(acc, file_id, it),
+ ast::RecordPat(it) => check_pat_field_shorthand(acc, file_id, it),
+ _ => ()
+ }
+ };
+}
+
+fn check_expr_field_shorthand(
+ acc: &mut Vec<Diagnostic>,
+ file_id: FileId,
+ record_expr: ast::RecordExpr,
+) {
+ let record_field_list = match record_expr.record_expr_field_list() {
+ Some(it) => it,
+ None => return,
+ };
+ for record_field in record_field_list.fields() {
+ let (name_ref, expr) = match record_field.name_ref().zip(record_field.expr()) {
+ Some(it) => it,
+ None => continue,
+ };
+
+ let field_name = name_ref.syntax().text().to_string();
+ let field_expr = expr.syntax().text().to_string();
+ let field_name_is_tup_index = name_ref.as_tuple_field().is_some();
+ if field_name != field_expr || field_name_is_tup_index {
+ continue;
+ }
+
+ let mut edit_builder = TextEdit::builder();
+ edit_builder.delete(record_field.syntax().text_range());
+ edit_builder.insert(record_field.syntax().text_range().start(), field_name);
+ let edit = edit_builder.finish();
+
+ let field_range = record_field.syntax().text_range();
+ acc.push(
+ Diagnostic::new("use-field-shorthand", "Shorthand struct initialization", field_range)
+ .severity(Severity::WeakWarning)
+ .with_fixes(Some(vec![fix(
+ "use_expr_field_shorthand",
+ "Use struct shorthand initialization",
+ SourceChange::from_text_edit(file_id, edit),
+ field_range,
+ )])),
+ );
+ }
+}
+
+fn check_pat_field_shorthand(
+ acc: &mut Vec<Diagnostic>,
+ file_id: FileId,
+ record_pat: ast::RecordPat,
+) {
+ let record_pat_field_list = match record_pat.record_pat_field_list() {
+ Some(it) => it,
+ None => return,
+ };
+ for record_pat_field in record_pat_field_list.fields() {
+ let (name_ref, pat) = match record_pat_field.name_ref().zip(record_pat_field.pat()) {
+ Some(it) => it,
+ None => continue,
+ };
+
+ let field_name = name_ref.syntax().text().to_string();
+ let field_pat = pat.syntax().text().to_string();
+ let field_name_is_tup_index = name_ref.as_tuple_field().is_some();
+ if field_name != field_pat || field_name_is_tup_index {
+ continue;
+ }
+
+ let mut edit_builder = TextEdit::builder();
+ edit_builder.delete(record_pat_field.syntax().text_range());
+ edit_builder.insert(record_pat_field.syntax().text_range().start(), field_name);
+ let edit = edit_builder.finish();
+
+ let field_range = record_pat_field.syntax().text_range();
+ acc.push(
+ Diagnostic::new("use-field-shorthand", "Shorthand struct pattern", field_range)
+ .severity(Severity::WeakWarning)
+ .with_fixes(Some(vec![fix(
+ "use_pat_field_shorthand",
+ "Use struct field shorthand",
+ SourceChange::from_text_edit(file_id, edit),
+ field_range,
+ )])),
+ );
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_diagnostics, check_fix};
+
+ #[test]
+ fn test_check_expr_field_shorthand() {
+ check_diagnostics(
+ r#"
+struct A { a: &'static str }
+fn main() { A { a: "hello" }; }
+"#,
+ );
+ check_diagnostics(
+ r#"
+struct A(usize);
+fn main() { A { 0: 0 }; }
+"#,
+ );
+
+ check_fix(
+ r#"
+struct A { a: &'static str }
+fn main() {
+ let a = "haha";
+ A { a$0: a };
+}
+"#,
+ r#"
+struct A { a: &'static str }
+fn main() {
+ let a = "haha";
+ A { a };
+}
+"#,
+ );
+
+ check_fix(
+ r#"
+struct A { a: &'static str, b: &'static str }
+fn main() {
+ let a = "haha";
+ let b = "bb";
+ A { a$0: a, b };
+}
+"#,
+ r#"
+struct A { a: &'static str, b: &'static str }
+fn main() {
+ let a = "haha";
+ let b = "bb";
+ A { a, b };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_check_pat_field_shorthand() {
+ check_diagnostics(
+ r#"
+struct A { a: &'static str }
+fn f(a: A) { let A { a: hello } = a; }
+"#,
+ );
+ check_diagnostics(
+ r#"
+struct A(usize);
+fn f(a: A) { let A { 0: 0 } = a; }
+"#,
+ );
+
+ check_fix(
+ r#"
+struct A { a: &'static str }
+fn f(a: A) {
+ let A { a$0: a } = a;
+}
+"#,
+ r#"
+struct A { a: &'static str }
+fn f(a: A) {
+ let A { a } = a;
+}
+"#,
+ );
+
+ check_fix(
+ r#"
+struct A { a: &'static str, b: &'static str }
+fn f(a: A) {
+ let A { a$0: a, b } = a;
+}
+"#,
+ r#"
+struct A { a: &'static str, b: &'static str }
+fn f(a: A) {
+ let A { a, b } = a;
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/inactive_code.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/inactive_code.rs
new file mode 100644
index 000000000..97ea5c456
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/inactive_code.rs
@@ -0,0 +1,144 @@
+use cfg::DnfExpr;
+use stdx::format_to;
+
+use crate::{Diagnostic, DiagnosticsContext, Severity};
+
+// Diagnostic: inactive-code
+//
+// This diagnostic is shown for code with inactive `#[cfg]` attributes.
+pub(crate) fn inactive_code(
+ ctx: &DiagnosticsContext<'_>,
+ d: &hir::InactiveCode,
+) -> Option<Diagnostic> {
+ // If there's inactive code somewhere in a macro, don't propagate to the call-site.
+ if d.node.file_id.is_macro() {
+ return None;
+ }
+
+ let inactive = DnfExpr::new(d.cfg.clone()).why_inactive(&d.opts);
+ let mut message = "code is inactive due to #[cfg] directives".to_string();
+
+ if let Some(inactive) = inactive {
+ let inactive_reasons = inactive.to_string();
+
+ if inactive_reasons.is_empty() {
+ format_to!(message);
+ } else {
+ format_to!(message, ": {}", inactive);
+ }
+ }
+
+ let res = Diagnostic::new(
+ "inactive-code",
+ message,
+ ctx.sema.diagnostics_display_range(d.node.clone()).range,
+ )
+ .severity(Severity::WeakWarning)
+ .with_unused(true);
+ Some(res)
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{tests::check_diagnostics_with_config, DiagnosticsConfig};
+
+ pub(crate) fn check(ra_fixture: &str) {
+ let config = DiagnosticsConfig::default();
+ check_diagnostics_with_config(config, ra_fixture)
+ }
+
+ #[test]
+ fn cfg_diagnostics() {
+ check(
+ r#"
+fn f() {
+ // The three g̶e̶n̶d̶e̶r̶s̶ statements:
+
+ #[cfg(a)] fn f() {} // Item statement
+ //^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
+ #[cfg(a)] {} // Expression statement
+ //^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
+ #[cfg(a)] let x = 0; // let statement
+ //^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
+
+ abc(#[cfg(a)] 0);
+ //^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
+ let x = Struct {
+ #[cfg(a)] f: 0,
+ //^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
+ };
+ match () {
+ () => (),
+ #[cfg(a)] () => (),
+ //^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
+ }
+
+ #[cfg(a)] 0 // Trailing expression of block
+ //^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn inactive_item() {
+ // Additional tests in `cfg` crate. This only tests disabled cfgs.
+
+ check(
+ r#"
+ #[cfg(no)] pub fn f() {}
+ //^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: no is disabled
+
+ #[cfg(no)] #[cfg(no2)] mod m;
+ //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: no and no2 are disabled
+
+ #[cfg(all(not(a), b))] enum E {}
+ //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: b is disabled
+
+ #[cfg(feature = "std")] use std;
+ //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: feature = "std" is disabled
+
+ #[cfg(any())] pub fn f() {}
+ //^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives
+"#,
+ );
+ }
+
+ #[test]
+ fn inactive_assoc_item() {
+ // FIXME these currently don't work, hence the *
+ check(
+ r#"
+struct Foo;
+impl Foo {
+ #[cfg(any())] pub fn f() {}
+ //*************************** weak: code is inactive due to #[cfg] directives
+}
+
+trait Bar {
+ #[cfg(any())] pub fn f() {}
+ //*************************** weak: code is inactive due to #[cfg] directives
+}
+"#,
+ );
+ }
+
+ /// Tests that `cfg` attributes behind `cfg_attr` is handled properly.
+ #[test]
+ fn inactive_via_cfg_attr() {
+ cov_mark::check!(cfg_attr_active);
+ check(
+ r#"
+ #[cfg_attr(not(never), cfg(no))] fn f() {}
+ //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: no is disabled
+
+ #[cfg_attr(not(never), cfg(not(no)))] fn f() {}
+
+ #[cfg_attr(never, cfg(no))] fn g() {}
+
+ #[cfg_attr(not(never), inline, cfg(no))] fn h() {}
+ //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: no is disabled
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs
new file mode 100644
index 000000000..6a78c08d4
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs
@@ -0,0 +1,486 @@
+use hir::{db::AstDatabase, InFile};
+use ide_db::{assists::Assist, defs::NameClass};
+use syntax::AstNode;
+
+use crate::{
+ // references::rename::rename_with_semantics,
+ unresolved_fix,
+ Diagnostic,
+ DiagnosticsContext,
+ Severity,
+};
+
+// Diagnostic: incorrect-ident-case
+//
+// This diagnostic is triggered if an item name doesn't follow https://doc.rust-lang.org/1.0.0/style/style/naming/README.html[Rust naming convention].
+pub(crate) fn incorrect_case(ctx: &DiagnosticsContext<'_>, d: &hir::IncorrectCase) -> Diagnostic {
+ Diagnostic::new(
+ "incorrect-ident-case",
+ format!(
+ "{} `{}` should have {} name, e.g. `{}`",
+ d.ident_type, d.ident_text, d.expected_case, d.suggested_text
+ ),
+ ctx.sema.diagnostics_display_range(InFile::new(d.file, d.ident.clone().into())).range,
+ )
+ .severity(Severity::WeakWarning)
+ .with_fixes(fixes(ctx, d))
+}
+
+fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::IncorrectCase) -> Option<Vec<Assist>> {
+ let root = ctx.sema.db.parse_or_expand(d.file)?;
+ let name_node = d.ident.to_node(&root);
+ let def = NameClass::classify(&ctx.sema, &name_node)?.defined()?;
+
+ let name_node = InFile::new(d.file, name_node.syntax());
+ let frange = name_node.original_file_range(ctx.sema.db);
+
+ let label = format!("Rename to {}", d.suggested_text);
+ let mut res = unresolved_fix("change_case", &label, frange.range);
+ if ctx.resolve.should_resolve(&res.id) {
+ let source_change = def.rename(&ctx.sema, &d.suggested_text);
+ res.source_change = Some(source_change.ok().unwrap_or_default());
+ }
+
+ Some(vec![res])
+}
+
+#[cfg(test)]
+mod change_case {
+ use crate::tests::{check_diagnostics, check_fix};
+
+ #[test]
+ fn test_rename_incorrect_case() {
+ check_fix(
+ r#"
+pub struct test_struct$0 { one: i32 }
+
+pub fn some_fn(val: test_struct) -> test_struct {
+ test_struct { one: val.one + 1 }
+}
+"#,
+ r#"
+pub struct TestStruct { one: i32 }
+
+pub fn some_fn(val: TestStruct) -> TestStruct {
+ TestStruct { one: val.one + 1 }
+}
+"#,
+ );
+
+ check_fix(
+ r#"
+pub fn some_fn(NonSnakeCase$0: u8) -> u8 {
+ NonSnakeCase
+}
+"#,
+ r#"
+pub fn some_fn(non_snake_case: u8) -> u8 {
+ non_snake_case
+}
+"#,
+ );
+
+ check_fix(
+ r#"
+pub fn SomeFn$0(val: u8) -> u8 {
+ if val != 0 { SomeFn(val - 1) } else { val }
+}
+"#,
+ r#"
+pub fn some_fn(val: u8) -> u8 {
+ if val != 0 { some_fn(val - 1) } else { val }
+}
+"#,
+ );
+
+ check_fix(
+ r#"
+fn some_fn() {
+ let whatAWeird_Formatting$0 = 10;
+ another_func(whatAWeird_Formatting);
+}
+"#,
+ r#"
+fn some_fn() {
+ let what_aweird_formatting = 10;
+ another_func(what_aweird_formatting);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_uppercase_const_no_diagnostics() {
+ check_diagnostics(
+ r#"
+fn foo() {
+ const ANOTHER_ITEM: &str = "some_item";
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_rename_incorrect_case_struct_method() {
+ check_fix(
+ r#"
+pub struct TestStruct;
+
+impl TestStruct {
+ pub fn SomeFn$0() -> TestStruct {
+ TestStruct
+ }
+}
+"#,
+ r#"
+pub struct TestStruct;
+
+impl TestStruct {
+ pub fn some_fn() -> TestStruct {
+ TestStruct
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_single_incorrect_case_diagnostic_in_function_name_issue_6970() {
+ check_diagnostics(
+ r#"
+fn FOO() {}
+// ^^^ 💡 weak: Function `FOO` should have snake_case name, e.g. `foo`
+"#,
+ );
+ check_fix(r#"fn FOO$0() {}"#, r#"fn foo() {}"#);
+ }
+
+ #[test]
+ fn incorrect_function_name() {
+ check_diagnostics(
+ r#"
+fn NonSnakeCaseName() {}
+// ^^^^^^^^^^^^^^^^ 💡 weak: Function `NonSnakeCaseName` should have snake_case name, e.g. `non_snake_case_name`
+"#,
+ );
+ }
+
+ #[test]
+ fn incorrect_function_params() {
+ check_diagnostics(
+ r#"
+fn foo(SomeParam: u8) {}
+ // ^^^^^^^^^ 💡 weak: Parameter `SomeParam` should have snake_case name, e.g. `some_param`
+
+fn foo2(ok_param: &str, CAPS_PARAM: u8) {}
+ // ^^^^^^^^^^ 💡 weak: Parameter `CAPS_PARAM` should have snake_case name, e.g. `caps_param`
+"#,
+ );
+ }
+
+ #[test]
+ fn incorrect_variable_names() {
+ check_diagnostics(
+ r#"
+fn foo() {
+ let SOME_VALUE = 10;
+ // ^^^^^^^^^^ 💡 weak: Variable `SOME_VALUE` should have snake_case name, e.g. `some_value`
+ let AnotherValue = 20;
+ // ^^^^^^^^^^^^ 💡 weak: Variable `AnotherValue` should have snake_case name, e.g. `another_value`
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn incorrect_struct_names() {
+ check_diagnostics(
+ r#"
+struct non_camel_case_name {}
+ // ^^^^^^^^^^^^^^^^^^^ 💡 weak: Structure `non_camel_case_name` should have CamelCase name, e.g. `NonCamelCaseName`
+
+struct SCREAMING_CASE {}
+ // ^^^^^^^^^^^^^^ 💡 weak: Structure `SCREAMING_CASE` should have CamelCase name, e.g. `ScreamingCase`
+"#,
+ );
+ }
+
+ #[test]
+ fn no_diagnostic_for_camel_cased_acronyms_in_struct_name() {
+ check_diagnostics(
+ r#"
+struct AABB {}
+"#,
+ );
+ }
+
+ #[test]
+ fn incorrect_struct_field() {
+ check_diagnostics(
+ r#"
+struct SomeStruct { SomeField: u8 }
+ // ^^^^^^^^^ 💡 weak: Field `SomeField` should have snake_case name, e.g. `some_field`
+"#,
+ );
+ }
+
+ #[test]
+ fn incorrect_enum_names() {
+ check_diagnostics(
+ r#"
+enum some_enum { Val(u8) }
+ // ^^^^^^^^^ 💡 weak: Enum `some_enum` should have CamelCase name, e.g. `SomeEnum`
+
+enum SOME_ENUM {}
+ // ^^^^^^^^^ 💡 weak: Enum `SOME_ENUM` should have CamelCase name, e.g. `SomeEnum`
+"#,
+ );
+ }
+
+ #[test]
+ fn no_diagnostic_for_camel_cased_acronyms_in_enum_name() {
+ check_diagnostics(
+ r#"
+enum AABB {}
+"#,
+ );
+ }
+
+ #[test]
+ fn incorrect_enum_variant_name() {
+ check_diagnostics(
+ r#"
+enum SomeEnum { SOME_VARIANT(u8) }
+ // ^^^^^^^^^^^^ 💡 weak: Variant `SOME_VARIANT` should have CamelCase name, e.g. `SomeVariant`
+"#,
+ );
+ }
+
+ #[test]
+ fn incorrect_const_name() {
+ check_diagnostics(
+ r#"
+const some_weird_const: u8 = 10;
+ // ^^^^^^^^^^^^^^^^ 💡 weak: Constant `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
+"#,
+ );
+ }
+
+ #[test]
+ fn incorrect_static_name() {
+ check_diagnostics(
+ r#"
+static some_weird_const: u8 = 10;
+ // ^^^^^^^^^^^^^^^^ 💡 weak: Static variable `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
+"#,
+ );
+ }
+
+ #[test]
+ fn fn_inside_impl_struct() {
+ check_diagnostics(
+ r#"
+struct someStruct;
+ // ^^^^^^^^^^ 💡 weak: Structure `someStruct` should have CamelCase name, e.g. `SomeStruct`
+
+impl someStruct {
+ fn SomeFunc(&self) {
+ // ^^^^^^^^ 💡 weak: Function `SomeFunc` should have snake_case name, e.g. `some_func`
+ let WHY_VAR_IS_CAPS = 10;
+ // ^^^^^^^^^^^^^^^ 💡 weak: Variable `WHY_VAR_IS_CAPS` should have snake_case name, e.g. `why_var_is_caps`
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_diagnostic_for_enum_varinats() {
+ check_diagnostics(
+ r#"
+enum Option { Some, None }
+
+fn main() {
+ match Option::None {
+ None => (),
+ Some => (),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn non_let_bind() {
+ check_diagnostics(
+ r#"
+enum Option { Some, None }
+
+fn main() {
+ match Option::None {
+ SOME_VAR @ None => (),
+ // ^^^^^^^^ 💡 weak: Variable `SOME_VAR` should have snake_case name, e.g. `some_var`
+ Some => (),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn allow_attributes_crate_attr() {
+ check_diagnostics(
+ r#"
+#![allow(non_snake_case)]
+#![allow(non_camel_case_types)]
+
+struct S {
+ fooBar: bool,
+}
+
+enum E {
+ fooBar,
+}
+
+mod F {
+ fn CheckItWorksWithCrateAttr(BAD_NAME_HI: u8) {}
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn complex_ignore() {
+ // FIXME: this should trigger errors for the second case.
+ check_diagnostics(
+ r#"
+trait T { fn a(); }
+struct U {}
+impl T for U {
+ fn a() {
+ #[allow(non_snake_case)]
+ trait __BitFlagsOk {
+ const HiImAlsoBad: u8 = 2;
+ fn Dirty(&self) -> bool { false }
+ }
+
+ trait __BitFlagsBad {
+ const HiImAlsoBad: u8 = 2;
+ fn Dirty(&self) -> bool { false }
+ }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn infinite_loop_inner_items() {
+ check_diagnostics(
+ r#"
+fn qualify() {
+ mod foo {
+ use super::*;
+ }
+}
+ "#,
+ )
+ }
+
+ #[test] // Issue #8809.
+ fn parenthesized_parameter() {
+ check_diagnostics(r#"fn f((O): _) {}"#)
+ }
+
+ #[test]
+ fn ignores_extern_items() {
+ cov_mark::check!(extern_func_incorrect_case_ignored);
+ cov_mark::check!(extern_static_incorrect_case_ignored);
+ check_diagnostics(
+ r#"
+extern {
+ fn NonSnakeCaseName(SOME_VAR: u8) -> u8;
+ pub static SomeStatic: u8 = 10;
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn ignores_extern_items_from_macro() {
+ check_diagnostics(
+ r#"
+macro_rules! m {
+ () => {
+ fn NonSnakeCaseName(SOME_VAR: u8) -> u8;
+ pub static SomeStatic: u8 = 10;
+ }
+}
+
+extern {
+ m!();
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn bug_traits_arent_checked() {
+ // FIXME: Traits and functions in traits aren't currently checked by
+ // r-a, even though rustc will complain about them.
+ check_diagnostics(
+ r#"
+trait BAD_TRAIT {
+ fn BAD_FUNCTION();
+ fn BadFunction();
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn allow_attributes() {
+ check_diagnostics(
+ r#"
+#[allow(non_snake_case)]
+fn NonSnakeCaseName(SOME_VAR: u8) -> u8{
+ // cov_flags generated output from elsewhere in this file
+ extern "C" {
+ #[no_mangle]
+ static lower_case: u8;
+ }
+
+ let OtherVar = SOME_VAR + 1;
+ OtherVar
+}
+
+#[allow(nonstandard_style)]
+mod CheckNonstandardStyle {
+ fn HiImABadFnName() {}
+}
+
+#[allow(bad_style)]
+mod CheckBadStyle {
+ fn HiImABadFnName() {}
+}
+
+mod F {
+ #![allow(non_snake_case)]
+ fn CheckItWorksWithModAttr(BAD_NAME_HI: u8) {}
+}
+
+#[allow(non_snake_case, non_camel_case_types)]
+pub struct some_type {
+ SOME_FIELD: u8,
+ SomeField: u16,
+}
+
+#[allow(non_upper_case_globals)]
+pub const some_const: u8 = 10;
+
+#[allow(non_upper_case_globals)]
+pub static SomeStatic: u8 = 10;
+ "#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/invalid_derive_target.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/invalid_derive_target.rs
new file mode 100644
index 000000000..c779266bc
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/invalid_derive_target.rs
@@ -0,0 +1,38 @@
+use crate::{Diagnostic, DiagnosticsContext, Severity};
+
+// Diagnostic: invalid-derive-target
+//
+// This diagnostic is shown when the derive attribute is used on an item other than a `struct`,
+// `enum` or `union`.
+pub(crate) fn invalid_derive_target(
+ ctx: &DiagnosticsContext<'_>,
+ d: &hir::InvalidDeriveTarget,
+) -> Diagnostic {
+ let display_range = ctx.sema.diagnostics_display_range(d.node.clone()).range;
+
+ Diagnostic::new(
+ "invalid-derive-target",
+ "`derive` may only be applied to `struct`s, `enum`s and `union`s",
+ display_range,
+ )
+ .severity(Severity::Error)
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::check_diagnostics;
+
+ #[test]
+ fn fails_on_function() {
+ check_diagnostics(
+ r#"
+//- minicore:derive
+mod __ {
+ #[derive()]
+ //^^^^^^^^^^^ error: `derive` may only be applied to `struct`s, `enum`s and `union`s
+ fn main() {}
+}
+ "#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/macro_error.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/macro_error.rs
new file mode 100644
index 000000000..d6a66dc15
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/macro_error.rs
@@ -0,0 +1,218 @@
+use crate::{Diagnostic, DiagnosticsContext};
+
+// Diagnostic: macro-error
+//
+// This diagnostic is shown for macro expansion errors.
+pub(crate) fn macro_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroError) -> Diagnostic {
+ // Use more accurate position if available.
+ let display_range = d
+ .precise_location
+ .unwrap_or_else(|| ctx.sema.diagnostics_display_range(d.node.clone()).range);
+
+ Diagnostic::new("macro-error", d.message.clone(), display_range).experimental()
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{
+ tests::{check_diagnostics, check_diagnostics_with_config},
+ DiagnosticsConfig,
+ };
+
+ #[test]
+ fn builtin_macro_fails_expansion() {
+ check_diagnostics(
+ r#"
+#[rustc_builtin_macro]
+macro_rules! include { () => {} }
+
+#[rustc_builtin_macro]
+macro_rules! compile_error { () => {} }
+
+ include!("doesntexist");
+//^^^^^^^ error: failed to load file `doesntexist`
+
+ compile_error!("compile_error macro works");
+//^^^^^^^^^^^^^ error: compile_error macro works
+ "#,
+ );
+ }
+
+ #[test]
+ fn eager_macro_concat() {
+ // FIXME: this is incorrectly handling `$crate`, resulting in a wrong diagnostic.
+ // See: https://github.com/rust-lang/rust-analyzer/issues/10300
+
+ check_diagnostics(
+ r#"
+//- /lib.rs crate:lib deps:core
+use core::{panic, concat};
+
+mod private {
+ pub use core::concat;
+}
+
+macro_rules! m {
+ () => {
+ panic!(concat!($crate::private::concat!("")));
+ };
+}
+
+fn f() {
+ m!();
+ //^^^^ error: unresolved macro `$crate::private::concat!`
+}
+
+//- /core.rs crate:core
+#[macro_export]
+#[rustc_builtin_macro]
+macro_rules! concat { () => {} }
+
+pub macro panic {
+ ($msg:expr) => (
+ $crate::panicking::panic_str($msg)
+ ),
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn include_macro_should_allow_empty_content() {
+ let mut config = DiagnosticsConfig::default();
+
+ // FIXME: This is a false-positive, the file is actually linked in via
+ // `include!` macro
+ config.disabled.insert("unlinked-file".to_string());
+
+ check_diagnostics_with_config(
+ config,
+ r#"
+//- /lib.rs
+#[rustc_builtin_macro]
+macro_rules! include { () => {} }
+
+include!("foo/bar.rs");
+//- /foo/bar.rs
+// empty
+"#,
+ );
+ }
+
+ #[test]
+ fn good_out_dir_diagnostic() {
+ check_diagnostics(
+ r#"
+#[rustc_builtin_macro]
+macro_rules! include { () => {} }
+#[rustc_builtin_macro]
+macro_rules! env { () => {} }
+#[rustc_builtin_macro]
+macro_rules! concat { () => {} }
+
+ include!(concat!(env!("OUT_DIR"), "/out.rs"));
+//^^^^^^^ error: `OUT_DIR` not set, enable "build scripts" to fix
+"#,
+ );
+ }
+
+ #[test]
+ fn register_attr_and_tool() {
+ cov_mark::check!(register_attr);
+ cov_mark::check!(register_tool);
+ check_diagnostics(
+ r#"
+#![register_tool(tool)]
+#![register_attr(attr)]
+
+#[tool::path]
+#[attr]
+struct S;
+"#,
+ );
+ // NB: we don't currently emit diagnostics here
+ }
+
+ #[test]
+ fn macro_diag_builtin() {
+ check_diagnostics(
+ r#"
+#[rustc_builtin_macro]
+macro_rules! env {}
+
+#[rustc_builtin_macro]
+macro_rules! include {}
+
+#[rustc_builtin_macro]
+macro_rules! compile_error {}
+
+#[rustc_builtin_macro]
+macro_rules! format_args { () => {} }
+
+fn main() {
+ // Test a handful of built-in (eager) macros:
+
+ include!(invalid);
+ //^^^^^^^ error: could not convert tokens
+ include!("does not exist");
+ //^^^^^^^ error: failed to load file `does not exist`
+
+ env!(invalid);
+ //^^^ error: could not convert tokens
+
+ env!("OUT_DIR");
+ //^^^ error: `OUT_DIR` not set, enable "build scripts" to fix
+
+ compile_error!("compile_error works");
+ //^^^^^^^^^^^^^ error: compile_error works
+
+ // Lazy:
+
+ format_args!();
+ //^^^^^^^^^^^ error: no rule matches input tokens
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn macro_rules_diag() {
+ check_diagnostics(
+ r#"
+macro_rules! m {
+ () => {};
+}
+fn f() {
+ m!();
+
+ m!(hi);
+ //^ error: leftover tokens
+}
+ "#,
+ );
+ }
+ #[test]
+ fn dollar_crate_in_builtin_macro() {
+ check_diagnostics(
+ r#"
+#[macro_export]
+#[rustc_builtin_macro]
+macro_rules! format_args {}
+
+#[macro_export]
+macro_rules! arg { () => {} }
+
+#[macro_export]
+macro_rules! outer {
+ () => {
+ $crate::format_args!( "", $crate::arg!(1) )
+ };
+}
+
+fn f() {
+ outer!();
+} //^^^^^^^^ error: leftover tokens
+"#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/malformed_derive.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/malformed_derive.rs
new file mode 100644
index 000000000..cd48bdba0
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/malformed_derive.rs
@@ -0,0 +1,37 @@
+use crate::{Diagnostic, DiagnosticsContext, Severity};
+
+// Diagnostic: malformed-derive
+//
+// This diagnostic is shown when the derive attribute has invalid input.
+pub(crate) fn malformed_derive(
+ ctx: &DiagnosticsContext<'_>,
+ d: &hir::MalformedDerive,
+) -> Diagnostic {
+ let display_range = ctx.sema.diagnostics_display_range(d.node.clone()).range;
+
+ Diagnostic::new(
+ "malformed-derive",
+ "malformed derive input, derive attributes are of the form `#[derive(Derive1, Derive2, ...)]`",
+ display_range,
+ )
+ .severity(Severity::Error)
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::check_diagnostics;
+
+ #[test]
+ fn invalid_input() {
+ check_diagnostics(
+ r#"
+//- minicore:derive
+mod __ {
+ #[derive = "aaaa"]
+ //^^^^^^^^^^^^^^^^^^ error: malformed derive input, derive attributes are of the form `#[derive(Derive1, Derive2, ...)]`
+ struct Foo;
+}
+ "#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs
new file mode 100644
index 000000000..5f8b3e543
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs
@@ -0,0 +1,334 @@
+use syntax::{
+ ast::{self, HasArgList},
+ AstNode, TextRange,
+};
+
+use crate::{adjusted_display_range, Diagnostic, DiagnosticsContext};
+
+// Diagnostic: mismatched-arg-count
+//
+// This diagnostic is triggered if a function is invoked with an incorrect amount of arguments.
+pub(crate) fn mismatched_arg_count(
+ ctx: &DiagnosticsContext<'_>,
+ d: &hir::MismatchedArgCount,
+) -> Diagnostic {
+ let s = if d.expected == 1 { "" } else { "s" };
+ let message = format!("expected {} argument{}, found {}", d.expected, s, d.found);
+ Diagnostic::new("mismatched-arg-count", message, invalid_args_range(ctx, d))
+}
+
+fn invalid_args_range(ctx: &DiagnosticsContext<'_>, d: &hir::MismatchedArgCount) -> TextRange {
+ adjusted_display_range::<ast::Expr>(ctx, d.call_expr.clone().map(|it| it.into()), &|expr| {
+ let arg_list = match expr {
+ ast::Expr::CallExpr(call) => call.arg_list()?,
+ ast::Expr::MethodCallExpr(call) => call.arg_list()?,
+ _ => return None,
+ };
+ if d.found < d.expected {
+ if d.found == 0 {
+ return Some(arg_list.syntax().text_range());
+ }
+ if let Some(r_paren) = arg_list.r_paren_token() {
+ return Some(r_paren.text_range());
+ }
+ }
+ if d.expected < d.found {
+ if d.expected == 0 {
+ return Some(arg_list.syntax().text_range());
+ }
+ let zip = arg_list.args().nth(d.expected).zip(arg_list.r_paren_token());
+ if let Some((arg, r_paren)) = zip {
+ return Some(arg.syntax().text_range().cover(r_paren.text_range()));
+ }
+ }
+
+ None
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::check_diagnostics;
+
+ #[test]
+ fn simple_free_fn_zero() {
+ check_diagnostics(
+ r#"
+fn zero() {}
+fn f() { zero(1); }
+ //^^^ error: expected 0 arguments, found 1
+"#,
+ );
+
+ check_diagnostics(
+ r#"
+fn zero() {}
+fn f() { zero(); }
+"#,
+ );
+ }
+
+ #[test]
+ fn simple_free_fn_one() {
+ check_diagnostics(
+ r#"
+fn one(arg: u8) {}
+fn f() { one(); }
+ //^^ error: expected 1 argument, found 0
+"#,
+ );
+
+ check_diagnostics(
+ r#"
+fn one(arg: u8) {}
+fn f() { one(1); }
+"#,
+ );
+ }
+
+ #[test]
+ fn method_as_fn() {
+ check_diagnostics(
+ r#"
+struct S;
+impl S { fn method(&self) {} }
+
+fn f() {
+ S::method();
+} //^^ error: expected 1 argument, found 0
+"#,
+ );
+
+ check_diagnostics(
+ r#"
+struct S;
+impl S { fn method(&self) {} }
+
+fn f() {
+ S::method(&S);
+ S.method();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn method_with_arg() {
+ check_diagnostics(
+ r#"
+struct S;
+impl S { fn method(&self, arg: u8) {} }
+
+ fn f() {
+ S.method();
+ } //^^ error: expected 1 argument, found 0
+ "#,
+ );
+
+ check_diagnostics(
+ r#"
+struct S;
+impl S { fn method(&self, arg: u8) {} }
+
+fn f() {
+ S::method(&S, 0);
+ S.method(1);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn method_unknown_receiver() {
+ // note: this is incorrect code, so there might be errors on this in the
+ // future, but we shouldn't emit an argument count diagnostic here
+ check_diagnostics(
+ r#"
+trait Foo { fn method(&self, arg: usize) {} }
+
+fn f() {
+ let x;
+ x.method();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn tuple_struct() {
+ check_diagnostics(
+ r#"
+struct Tup(u8, u16);
+fn f() {
+ Tup(0);
+} //^ error: expected 2 arguments, found 1
+"#,
+ )
+ }
+
+ #[test]
+ fn enum_variant() {
+ check_diagnostics(
+ r#"
+enum En { Variant(u8, u16), }
+fn f() {
+ En::Variant(0);
+} //^ error: expected 2 arguments, found 1
+"#,
+ )
+ }
+
+ #[test]
+ fn enum_variant_type_macro() {
+ check_diagnostics(
+ r#"
+macro_rules! Type {
+ () => { u32 };
+}
+enum Foo {
+ Bar(Type![])
+}
+impl Foo {
+ fn new() {
+ Foo::Bar(0);
+ Foo::Bar(0, 1);
+ //^^ error: expected 1 argument, found 2
+ Foo::Bar();
+ //^^ error: expected 1 argument, found 0
+ }
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn varargs() {
+ check_diagnostics(
+ r#"
+extern "C" {
+ fn fixed(fixed: u8);
+ fn varargs(fixed: u8, ...);
+ fn varargs2(...);
+}
+
+fn f() {
+ unsafe {
+ fixed(0);
+ fixed(0, 1);
+ //^^ error: expected 1 argument, found 2
+ varargs(0);
+ varargs(0, 1);
+ varargs2();
+ varargs2(0);
+ varargs2(0, 1);
+ }
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn arg_count_lambda() {
+ check_diagnostics(
+ r#"
+fn main() {
+ let f = |()| ();
+ f();
+ //^^ error: expected 1 argument, found 0
+ f(());
+ f((), ());
+ //^^^ error: expected 1 argument, found 2
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn cfgd_out_call_arguments() {
+ check_diagnostics(
+ r#"
+struct C(#[cfg(FALSE)] ());
+impl C {
+ fn new() -> Self {
+ Self(
+ #[cfg(FALSE)]
+ (),
+ )
+ }
+
+ fn method(&self) {}
+}
+
+fn main() {
+ C::new().method(#[cfg(FALSE)] 0);
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn cfgd_out_fn_params() {
+ check_diagnostics(
+ r#"
+fn foo(#[cfg(NEVER)] x: ()) {}
+
+struct S;
+
+impl S {
+ fn method(#[cfg(NEVER)] self) {}
+ fn method2(#[cfg(NEVER)] self, arg: u8) {}
+ fn method3(self, #[cfg(NEVER)] arg: u8) {}
+}
+
+extern "C" {
+ fn fixed(fixed: u8, #[cfg(NEVER)] ...);
+ fn varargs(#[cfg(not(NEVER))] ...);
+}
+
+fn main() {
+ foo();
+ S::method();
+ S::method2(0);
+ S::method3(S);
+ S.method3();
+ unsafe {
+ fixed(0);
+ varargs(1, 2, 3);
+ }
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn legacy_const_generics() {
+ check_diagnostics(
+ r#"
+#[rustc_legacy_const_generics(1, 3)]
+fn mixed<const N1: &'static str, const N2: bool>(
+ a: u8,
+ b: i8,
+) {}
+
+fn f() {
+ mixed(0, "", -1, true);
+ mixed::<"", true>(0, -1);
+}
+
+#[rustc_legacy_const_generics(1, 3)]
+fn b<const N1: u8, const N2: u8>(
+ a: u8,
+ b: u8,
+) {}
+
+fn g() {
+ b(0, 1, 2, 3);
+ b::<1, 3>(0, 2);
+
+ b(0, 1, 2);
+ //^ error: expected 4 arguments, found 3
+}
+ "#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs
new file mode 100644
index 000000000..edb1fc091
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs
@@ -0,0 +1,837 @@
+use either::Either;
+use hir::{
+ db::{AstDatabase, HirDatabase},
+ known, AssocItem, HirDisplay, InFile, Type,
+};
+use ide_db::{
+ assists::Assist, famous_defs::FamousDefs, imports::import_assets::item_for_path_search,
+ source_change::SourceChange, use_trivial_contructor::use_trivial_constructor, FxHashMap,
+};
+use stdx::format_to;
+use syntax::{
+ algo,
+ ast::{self, make},
+ AstNode, SyntaxNode, SyntaxNodePtr,
+};
+use text_edit::TextEdit;
+
+use crate::{fix, Diagnostic, DiagnosticsContext};
+
+// Diagnostic: missing-fields
+//
+// This diagnostic is triggered if record lacks some fields that exist in the corresponding structure.
+//
+// Example:
+//
+// ```rust
+// struct A { a: u8, b: u8 }
+//
+// let a = A { a: 10 };
+// ```
+pub(crate) fn missing_fields(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Diagnostic {
+ let mut message = String::from("missing structure fields:\n");
+ for field in &d.missed_fields {
+ format_to!(message, "- {}\n", field);
+ }
+
+ let ptr = InFile::new(
+ d.file,
+ d.field_list_parent_path
+ .clone()
+ .map(SyntaxNodePtr::from)
+ .unwrap_or_else(|| d.field_list_parent.clone().either(|it| it.into(), |it| it.into())),
+ );
+
+ Diagnostic::new("missing-fields", message, ctx.sema.diagnostics_display_range(ptr).range)
+ .with_fixes(fixes(ctx, d))
+}
+
+fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Assist>> {
+ // Note that although we could add a diagnostics to
+ // fill the missing tuple field, e.g :
+ // `struct A(usize);`
+ // `let a = A { 0: () }`
+ // but it is uncommon usage and it should not be encouraged.
+ if d.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
+ return None;
+ }
+
+ let root = ctx.sema.db.parse_or_expand(d.file)?;
+
+ let current_module = match &d.field_list_parent {
+ Either::Left(ptr) => ctx.sema.scope(ptr.to_node(&root).syntax()).map(|it| it.module()),
+ Either::Right(ptr) => ctx.sema.scope(ptr.to_node(&root).syntax()).map(|it| it.module()),
+ };
+
+ let build_text_edit = |parent_syntax, new_syntax: &SyntaxNode, old_syntax| {
+ let edit = {
+ let mut builder = TextEdit::builder();
+ if d.file.is_macro() {
+ // we can't map the diff up into the macro input unfortunately, as the macro loses all
+ // whitespace information so the diff wouldn't be applicable no matter what
+ // This has the downside that the cursor will be moved in macros by doing it without a diff
+ // but that is a trade off we can make.
+ // FIXME: this also currently discards a lot of whitespace in the input... we really need a formatter here
+ let range = ctx.sema.original_range_opt(old_syntax)?;
+ builder.replace(range.range, new_syntax.to_string());
+ } else {
+ algo::diff(old_syntax, new_syntax).into_text_edit(&mut builder);
+ }
+ builder.finish()
+ };
+ Some(vec![fix(
+ "fill_missing_fields",
+ "Fill struct fields",
+ SourceChange::from_text_edit(d.file.original_file(ctx.sema.db), edit),
+ ctx.sema.original_range(parent_syntax).range,
+ )])
+ };
+
+ match &d.field_list_parent {
+ Either::Left(record_expr) => {
+ let field_list_parent = record_expr.to_node(&root);
+ let missing_fields = ctx.sema.record_literal_missing_fields(&field_list_parent);
+
+ let mut locals = FxHashMap::default();
+ ctx.sema.scope(field_list_parent.syntax())?.process_all_names(&mut |name, def| {
+ if let hir::ScopeDef::Local(local) = def {
+ locals.insert(name, local);
+ }
+ });
+
+ let generate_fill_expr = |ty: &Type| match ctx.config.expr_fill_default {
+ crate::ExprFillDefaultMode::Todo => make::ext::expr_todo(),
+ crate::ExprFillDefaultMode::Default => {
+ get_default_constructor(ctx, d, ty).unwrap_or_else(|| make::ext::expr_todo())
+ }
+ };
+
+ let old_field_list = field_list_parent.record_expr_field_list()?;
+ let new_field_list = old_field_list.clone_for_update();
+ for (f, ty) in missing_fields.iter() {
+ let field_expr = if let Some(local_candidate) = locals.get(&f.name(ctx.sema.db)) {
+ cov_mark::hit!(field_shorthand);
+ let candidate_ty = local_candidate.ty(ctx.sema.db);
+ if ty.could_unify_with(ctx.sema.db, &candidate_ty) {
+ None
+ } else {
+ Some(generate_fill_expr(ty))
+ }
+ } else {
+ let expr = (|| -> Option<ast::Expr> {
+ let item_in_ns = hir::ItemInNs::from(hir::ModuleDef::from(ty.as_adt()?));
+
+ let type_path = current_module?.find_use_path(
+ ctx.sema.db,
+ item_for_path_search(ctx.sema.db, item_in_ns)?,
+ )?;
+
+ use_trivial_constructor(
+ &ctx.sema.db,
+ ide_db::helpers::mod_path_to_ast(&type_path),
+ &ty,
+ )
+ })();
+
+ if expr.is_some() {
+ expr
+ } else {
+ Some(generate_fill_expr(ty))
+ }
+ };
+ let field = make::record_expr_field(
+ make::name_ref(&f.name(ctx.sema.db).to_smol_str()),
+ field_expr,
+ );
+ new_field_list.add_field(field.clone_for_update());
+ }
+ build_text_edit(
+ field_list_parent.syntax(),
+ new_field_list.syntax(),
+ old_field_list.syntax(),
+ )
+ }
+ Either::Right(record_pat) => {
+ let field_list_parent = record_pat.to_node(&root);
+ let missing_fields = ctx.sema.record_pattern_missing_fields(&field_list_parent);
+
+ let old_field_list = field_list_parent.record_pat_field_list()?;
+ let new_field_list = old_field_list.clone_for_update();
+ for (f, _) in missing_fields.iter() {
+ let field = make::record_pat_field_shorthand(make::name_ref(
+ &f.name(ctx.sema.db).to_smol_str(),
+ ));
+ new_field_list.add_field(field.clone_for_update());
+ }
+ build_text_edit(
+ field_list_parent.syntax(),
+ new_field_list.syntax(),
+ old_field_list.syntax(),
+ )
+ }
+ }
+}
+
+fn make_ty(ty: &hir::Type, db: &dyn HirDatabase, module: hir::Module) -> ast::Type {
+ let ty_str = match ty.as_adt() {
+ Some(adt) => adt.name(db).to_string(),
+ None => ty.display_source_code(db, module.into()).ok().unwrap_or_else(|| "_".to_string()),
+ };
+
+ make::ty(&ty_str)
+}
+
+fn get_default_constructor(
+ ctx: &DiagnosticsContext<'_>,
+ d: &hir::MissingFields,
+ ty: &Type,
+) -> Option<ast::Expr> {
+ if let Some(builtin_ty) = ty.as_builtin() {
+ if builtin_ty.is_int() || builtin_ty.is_uint() {
+ return Some(make::ext::zero_number());
+ }
+ if builtin_ty.is_float() {
+ return Some(make::ext::zero_float());
+ }
+ if builtin_ty.is_char() {
+ return Some(make::ext::empty_char());
+ }
+ if builtin_ty.is_str() {
+ return Some(make::ext::empty_str());
+ }
+ if builtin_ty.is_bool() {
+ return Some(make::ext::default_bool());
+ }
+ }
+
+ let krate = ctx.sema.to_module_def(d.file.original_file(ctx.sema.db))?.krate();
+ let module = krate.root_module(ctx.sema.db);
+
+ // Look for a ::new() associated function
+ let has_new_func = ty
+ .iterate_assoc_items(ctx.sema.db, krate, |assoc_item| {
+ if let AssocItem::Function(func) = assoc_item {
+ if func.name(ctx.sema.db) == known::new
+ && func.assoc_fn_params(ctx.sema.db).is_empty()
+ {
+ return Some(());
+ }
+ }
+
+ None
+ })
+ .is_some();
+
+ let famous_defs = FamousDefs(&ctx.sema, krate);
+ if has_new_func {
+ Some(make::ext::expr_ty_new(&make_ty(ty, ctx.sema.db, module)))
+ } else if ty.as_adt() == famous_defs.core_option_Option()?.ty(ctx.sema.db).as_adt() {
+ Some(make::ext::option_none())
+ } else if !ty.is_array()
+ && ty.impls_trait(ctx.sema.db, famous_defs.core_default_Default()?, &[])
+ {
+ Some(make::ext::expr_ty_default(&make_ty(ty, ctx.sema.db, module)))
+ } else {
+ None
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_diagnostics, check_fix};
+
+ #[test]
+ fn missing_record_pat_field_diagnostic() {
+ check_diagnostics(
+ r#"
+struct S { foo: i32, bar: () }
+fn baz(s: S) {
+ let S { foo: _ } = s;
+ //^ 💡 error: missing structure fields:
+ //| - bar
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() {
+ check_diagnostics(
+ r"
+struct S { foo: i32, bar: () }
+fn baz(s: S) -> i32 {
+ match s {
+ S { foo, .. } => foo,
+ }
+}
+",
+ )
+ }
+
+ #[test]
+ fn missing_record_pat_field_box() {
+ check_diagnostics(
+ r"
+struct S { s: Box<u32> }
+fn x(a: S) {
+ let S { box s } = a;
+}
+",
+ )
+ }
+
+ #[test]
+ fn missing_record_pat_field_ref() {
+ check_diagnostics(
+ r"
+struct S { s: u32 }
+fn x(a: S) {
+ let S { ref s } = a;
+}
+",
+ )
+ }
+
+ #[test]
+ fn missing_record_expr_in_assignee_expr() {
+ check_diagnostics(
+ r"
+struct S { s: usize, t: usize }
+struct S2 { s: S, t: () }
+struct T(S);
+fn regular(a: S) {
+ let s;
+ S { s, .. } = a;
+}
+fn nested(a: S2) {
+ let s;
+ S2 { s: S { s, .. }, .. } = a;
+}
+fn in_tuple(a: (S,)) {
+ let s;
+ (S { s, .. },) = a;
+}
+fn in_array(a: [S;1]) {
+ let s;
+ [S { s, .. },] = a;
+}
+fn in_tuple_struct(a: T) {
+ let s;
+ T(S { s, .. }) = a;
+}
+ ",
+ );
+ }
+
+ #[test]
+ fn range_mapping_out_of_macros() {
+ check_fix(
+ r#"
+fn some() {}
+fn items() {}
+fn here() {}
+
+macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
+
+fn main() {
+ let _x = id![Foo { a: $042 }];
+}
+
+pub struct Foo { pub a: i32, pub b: i32 }
+"#,
+ r#"
+fn some() {}
+fn items() {}
+fn here() {}
+
+macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
+
+fn main() {
+ let _x = id![Foo {a:42, b: 0 }];
+}
+
+pub struct Foo { pub a: i32, pub b: i32 }
+"#,
+ );
+ }
+
+ #[test]
+ fn test_fill_struct_fields_empty() {
+ check_fix(
+ r#"
+//- minicore: option
+struct TestStruct { one: i32, two: i64, three: Option<i32>, four: bool }
+
+fn test_fn() {
+ let s = TestStruct {$0};
+}
+"#,
+ r#"
+struct TestStruct { one: i32, two: i64, three: Option<i32>, four: bool }
+
+fn test_fn() {
+ let s = TestStruct { one: 0, two: 0, three: None, four: false };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_fill_struct_zst_fields() {
+ check_fix(
+ r#"
+struct Empty;
+
+struct TestStruct { one: i32, two: Empty }
+
+fn test_fn() {
+ let s = TestStruct {$0};
+}
+"#,
+ r#"
+struct Empty;
+
+struct TestStruct { one: i32, two: Empty }
+
+fn test_fn() {
+ let s = TestStruct { one: 0, two: Empty };
+}
+"#,
+ );
+ check_fix(
+ r#"
+enum Empty { Foo };
+
+struct TestStruct { one: i32, two: Empty }
+
+fn test_fn() {
+ let s = TestStruct {$0};
+}
+"#,
+ r#"
+enum Empty { Foo };
+
+struct TestStruct { one: i32, two: Empty }
+
+fn test_fn() {
+ let s = TestStruct { one: 0, two: Empty::Foo };
+}
+"#,
+ );
+
+ // make sure the assist doesn't fill non Unit variants
+ check_fix(
+ r#"
+struct Empty {};
+
+struct TestStruct { one: i32, two: Empty }
+
+fn test_fn() {
+ let s = TestStruct {$0};
+}
+"#,
+ r#"
+struct Empty {};
+
+struct TestStruct { one: i32, two: Empty }
+
+fn test_fn() {
+ let s = TestStruct { one: 0, two: todo!() };
+}
+"#,
+ );
+ check_fix(
+ r#"
+enum Empty { Foo {} };
+
+struct TestStruct { one: i32, two: Empty }
+
+fn test_fn() {
+ let s = TestStruct {$0};
+}
+"#,
+ r#"
+enum Empty { Foo {} };
+
+struct TestStruct { one: i32, two: Empty }
+
+fn test_fn() {
+ let s = TestStruct { one: 0, two: todo!() };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_fill_struct_fields_self() {
+ check_fix(
+ r#"
+struct TestStruct { one: i32 }
+
+impl TestStruct {
+ fn test_fn() { let s = Self {$0}; }
+}
+"#,
+ r#"
+struct TestStruct { one: i32 }
+
+impl TestStruct {
+ fn test_fn() { let s = Self { one: 0 }; }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_fill_struct_fields_enum() {
+ check_fix(
+ r#"
+enum Expr {
+ Bin { lhs: Box<Expr>, rhs: Box<Expr> }
+}
+
+impl Expr {
+ fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
+ Expr::Bin {$0 }
+ }
+}
+"#,
+ r#"
+enum Expr {
+ Bin { lhs: Box<Expr>, rhs: Box<Expr> }
+}
+
+impl Expr {
+ fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
+ Expr::Bin { lhs, rhs }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_fill_struct_fields_partial() {
+ check_fix(
+ r#"
+struct TestStruct { one: i32, two: i64 }
+
+fn test_fn() {
+ let s = TestStruct{ two: 2$0 };
+}
+"#,
+ r"
+struct TestStruct { one: i32, two: i64 }
+
+fn test_fn() {
+ let s = TestStruct{ two: 2, one: 0 };
+}
+",
+ );
+ }
+
+ #[test]
+ fn test_fill_struct_fields_new() {
+ check_fix(
+ r#"
+struct TestWithNew(usize);
+impl TestWithNew {
+ pub fn new() -> Self {
+ Self(0)
+ }
+}
+struct TestStruct { one: i32, two: TestWithNew }
+
+fn test_fn() {
+ let s = TestStruct{ $0 };
+}
+"#,
+ r"
+struct TestWithNew(usize);
+impl TestWithNew {
+ pub fn new() -> Self {
+ Self(0)
+ }
+}
+struct TestStruct { one: i32, two: TestWithNew }
+
+fn test_fn() {
+ let s = TestStruct{ one: 0, two: TestWithNew::new() };
+}
+",
+ );
+ }
+
+ #[test]
+ fn test_fill_struct_fields_default() {
+ check_fix(
+ r#"
+//- minicore: default, option
+struct TestWithDefault(usize);
+impl Default for TestWithDefault {
+ pub fn default() -> Self {
+ Self(0)
+ }
+}
+struct TestStruct { one: i32, two: TestWithDefault }
+
+fn test_fn() {
+ let s = TestStruct{ $0 };
+}
+"#,
+ r"
+struct TestWithDefault(usize);
+impl Default for TestWithDefault {
+ pub fn default() -> Self {
+ Self(0)
+ }
+}
+struct TestStruct { one: i32, two: TestWithDefault }
+
+fn test_fn() {
+ let s = TestStruct{ one: 0, two: TestWithDefault::default() };
+}
+",
+ );
+ }
+
+ #[test]
+ fn test_fill_struct_fields_raw_ident() {
+ check_fix(
+ r#"
+struct TestStruct { r#type: u8 }
+
+fn test_fn() {
+ TestStruct { $0 };
+}
+"#,
+ r"
+struct TestStruct { r#type: u8 }
+
+fn test_fn() {
+ TestStruct { r#type: 0 };
+}
+",
+ );
+ }
+
+ #[test]
+ fn test_fill_struct_fields_no_diagnostic() {
+ check_diagnostics(
+ r#"
+struct TestStruct { one: i32, two: i64 }
+
+fn test_fn() {
+ let one = 1;
+ let s = TestStruct{ one, two: 2 };
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn test_fill_struct_fields_no_diagnostic_on_spread() {
+ check_diagnostics(
+ r#"
+struct TestStruct { one: i32, two: i64 }
+
+fn test_fn() {
+ let one = 1;
+ let s = TestStruct{ ..a };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_fill_struct_fields_blank_line() {
+ check_fix(
+ r#"
+struct S { a: (), b: () }
+
+fn f() {
+ S {
+ $0
+ };
+}
+"#,
+ r#"
+struct S { a: (), b: () }
+
+fn f() {
+ S {
+ a: todo!(),
+ b: todo!(),
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_fill_struct_fields_shorthand() {
+ cov_mark::check!(field_shorthand);
+ check_fix(
+ r#"
+struct S { a: &'static str, b: i32 }
+
+fn f() {
+ let a = "hello";
+ let b = 1i32;
+ S {
+ $0
+ };
+}
+"#,
+ r#"
+struct S { a: &'static str, b: i32 }
+
+fn f() {
+ let a = "hello";
+ let b = 1i32;
+ S {
+ a,
+ b,
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_fill_struct_fields_shorthand_ty_mismatch() {
+ check_fix(
+ r#"
+struct S { a: &'static str, b: i32 }
+
+fn f() {
+ let a = "hello";
+ let b = 1usize;
+ S {
+ $0
+ };
+}
+"#,
+ r#"
+struct S { a: &'static str, b: i32 }
+
+fn f() {
+ let a = "hello";
+ let b = 1usize;
+ S {
+ a,
+ b: 0,
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_fill_struct_fields_shorthand_unifies() {
+ check_fix(
+ r#"
+struct S<T> { a: &'static str, b: T }
+
+fn f() {
+ let a = "hello";
+ let b = 1i32;
+ S {
+ $0
+ };
+}
+"#,
+ r#"
+struct S<T> { a: &'static str, b: T }
+
+fn f() {
+ let a = "hello";
+ let b = 1i32;
+ S {
+ a,
+ b,
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_fill_struct_pat_fields() {
+ check_fix(
+ r#"
+struct S { a: &'static str, b: i32 }
+
+fn f() {
+ let S {
+ $0
+ };
+}
+"#,
+ r#"
+struct S { a: &'static str, b: i32 }
+
+fn f() {
+ let S {
+ a,
+ b,
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_fill_struct_pat_fields_partial() {
+ check_fix(
+ r#"
+struct S { a: &'static str, b: i32 }
+
+fn f() {
+ let S {
+ a,$0
+ };
+}
+"#,
+ r#"
+struct S { a: &'static str, b: i32 }
+
+fn f() {
+ let S {
+ a,
+ b,
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn import_extern_crate_clash_with_inner_item() {
+ // This is more of a resolver test, but doesn't really work with the hir_def testsuite.
+
+ check_diagnostics(
+ r#"
+//- /lib.rs crate:lib deps:jwt
+mod permissions;
+
+use permissions::jwt;
+
+fn f() {
+ fn inner() {}
+ jwt::Claims {}; // should resolve to the local one with 0 fields, and not get a diagnostic
+}
+
+//- /permissions.rs
+pub mod jwt {
+ pub struct Claims {}
+}
+
+//- /jwt/lib.rs crate:jwt
+pub struct Claims {
+ field: u8,
+}
+ "#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_match_arms.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
new file mode 100644
index 000000000..9e66fbfb7
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
@@ -0,0 +1,1012 @@
+use hir::InFile;
+
+use crate::{Diagnostic, DiagnosticsContext};
+
+// Diagnostic: missing-match-arm
+//
+// This diagnostic is triggered if `match` block is missing one or more match arms.
+pub(crate) fn missing_match_arms(
+ ctx: &DiagnosticsContext<'_>,
+ d: &hir::MissingMatchArms,
+) -> Diagnostic {
+ Diagnostic::new(
+ "missing-match-arm",
+ format!("missing match arm: {}", d.uncovered_patterns),
+ ctx.sema.diagnostics_display_range(InFile::new(d.file, d.match_expr.clone().into())).range,
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::check_diagnostics;
+
+ fn check_diagnostics_no_bails(ra_fixture: &str) {
+ cov_mark::check_count!(validate_match_bailed_out, 0);
+ crate::tests::check_diagnostics(ra_fixture)
+ }
+
+ #[test]
+ fn empty_tuple() {
+ check_diagnostics_no_bails(
+ r#"
+fn main() {
+ match () { }
+ //^^ error: missing match arm: type `()` is non-empty
+ match (()) { }
+ //^^^^ error: missing match arm: type `()` is non-empty
+
+ match () { _ => (), }
+ match () { () => (), }
+ match (()) { (()) => (), }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn tuple_of_two_empty_tuple() {
+ check_diagnostics_no_bails(
+ r#"
+fn main() {
+ match ((), ()) { }
+ //^^^^^^^^ error: missing match arm: type `((), ())` is non-empty
+
+ match ((), ()) { ((), ()) => (), }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn boolean() {
+ check_diagnostics_no_bails(
+ r#"
+fn test_main() {
+ match false { }
+ //^^^^^ error: missing match arm: type `bool` is non-empty
+ match false { true => (), }
+ //^^^^^ error: missing match arm: `false` not covered
+ match (false, true) {}
+ //^^^^^^^^^^^^^ error: missing match arm: type `(bool, bool)` is non-empty
+ match (false, true) { (true, true) => (), }
+ //^^^^^^^^^^^^^ error: missing match arm: `(false, _)` not covered
+ match (false, true) {
+ //^^^^^^^^^^^^^ error: missing match arm: `(true, true)` not covered
+ (false, true) => (),
+ (false, false) => (),
+ (true, false) => (),
+ }
+ match (false, true) { (true, _x) => (), }
+ //^^^^^^^^^^^^^ error: missing match arm: `(false, _)` not covered
+
+ match false { true => (), false => (), }
+ match (false, true) {
+ (false, _) => (),
+ (true, false) => (),
+ (_, true) => (),
+ }
+ match (false, true) {
+ (true, true) => (),
+ (true, false) => (),
+ (false, true) => (),
+ (false, false) => (),
+ }
+ match (false, true) {
+ (true, _x) => (),
+ (false, true) => (),
+ (false, false) => (),
+ }
+ match (false, true, false) {
+ (false, ..) => (),
+ (true, ..) => (),
+ }
+ match (false, true, false) {
+ (.., false) => (),
+ (.., true) => (),
+ }
+ match (false, true, false) { (..) => (), }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn tuple_of_tuple_and_bools() {
+ check_diagnostics_no_bails(
+ r#"
+fn main() {
+ match (false, ((), false)) {}
+ //^^^^^^^^^^^^^^^^^^^^ error: missing match arm: type `(bool, ((), bool))` is non-empty
+ match (false, ((), false)) { (true, ((), true)) => (), }
+ //^^^^^^^^^^^^^^^^^^^^ error: missing match arm: `(false, _)` not covered
+ match (false, ((), false)) { (true, _) => (), }
+ //^^^^^^^^^^^^^^^^^^^^ error: missing match arm: `(false, _)` not covered
+
+ match (false, ((), false)) {
+ (true, ((), true)) => (),
+ (true, ((), false)) => (),
+ (false, ((), true)) => (),
+ (false, ((), false)) => (),
+ }
+ match (false, ((), false)) {
+ (true, ((), true)) => (),
+ (true, ((), false)) => (),
+ (false, _) => (),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn enums() {
+ check_diagnostics_no_bails(
+ r#"
+enum Either { A, B, }
+
+fn main() {
+ match Either::A { }
+ //^^^^^^^^^ error: missing match arm: `A` and `B` not covered
+ match Either::B { Either::A => (), }
+ //^^^^^^^^^ error: missing match arm: `B` not covered
+
+ match &Either::B {
+ //^^^^^^^^^^ error: missing match arm: `&B` not covered
+ Either::A => (),
+ }
+
+ match Either::B {
+ Either::A => (), Either::B => (),
+ }
+ match &Either::B {
+ Either::A => (), Either::B => (),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn enum_containing_bool() {
+ check_diagnostics_no_bails(
+ r#"
+enum Either { A(bool), B }
+
+fn main() {
+ match Either::B { }
+ //^^^^^^^^^ error: missing match arm: `A(_)` and `B` not covered
+ match Either::B {
+ //^^^^^^^^^ error: missing match arm: `A(false)` not covered
+ Either::A(true) => (), Either::B => ()
+ }
+
+ match Either::B {
+ Either::A(true) => (),
+ Either::A(false) => (),
+ Either::B => (),
+ }
+ match Either::B {
+ Either::B => (),
+ _ => (),
+ }
+ match Either::B {
+ Either::A(_) => (),
+ Either::B => (),
+ }
+
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn enum_different_sizes() {
+ check_diagnostics_no_bails(
+ r#"
+enum Either { A(bool), B(bool, bool) }
+
+fn main() {
+ match Either::A(false) {
+ //^^^^^^^^^^^^^^^^ error: missing match arm: `B(true, _)` not covered
+ Either::A(_) => (),
+ Either::B(false, _) => (),
+ }
+
+ match Either::A(false) {
+ Either::A(_) => (),
+ Either::B(true, _) => (),
+ Either::B(false, _) => (),
+ }
+ match Either::A(false) {
+ Either::A(true) | Either::A(false) => (),
+ Either::B(true, _) => (),
+ Either::B(false, _) => (),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn tuple_of_enum_no_diagnostic() {
+ check_diagnostics_no_bails(
+ r#"
+enum Either { A(bool), B(bool, bool) }
+enum Either2 { C, D }
+
+fn main() {
+ match (Either::A(false), Either2::C) {
+ (Either::A(true), _) | (Either::A(false), _) => (),
+ (Either::B(true, _), Either2::C) => (),
+ (Either::B(false, _), Either2::C) => (),
+ (Either::B(_, _), Either2::D) => (),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn or_pattern_no_diagnostic() {
+ check_diagnostics_no_bails(
+ r#"
+enum Either {A, B}
+
+fn main() {
+ match (Either::A, Either::B) {
+ (Either::A | Either::B, _) => (),
+ }
+}"#,
+ )
+ }
+
+ #[test]
+ fn mismatched_types() {
+ cov_mark::check_count!(validate_match_bailed_out, 4);
+ // Match statements with arms that don't match the
+ // expression pattern do not fire this diagnostic.
+ check_diagnostics(
+ r#"
+enum Either { A, B }
+enum Either2 { C, D }
+
+fn main() {
+ match Either::A {
+ Either2::C => (),
+ Either2::D => (),
+ }
+ match (true, false) {
+ (true, false, true) => (),
+ (true) => (),
+ // ^^^^ error: expected (bool, bool), found bool
+ }
+ match (true, false) { (true,) => {} }
+ match (0) { () => () }
+ match Unresolved::Bar { Unresolved::Baz => () }
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn mismatched_types_in_or_patterns() {
+ cov_mark::check_count!(validate_match_bailed_out, 2);
+ check_diagnostics(
+ r#"
+fn main() {
+ match false { true | () => {} }
+ match (false,) { (true | (),) => {} }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn malformed_match_arm_tuple_enum_missing_pattern() {
+ // We are testing to be sure we don't panic here when the match
+ // arm `Either::B` is missing its pattern.
+ check_diagnostics_no_bails(
+ r#"
+enum Either { A, B(u32) }
+
+fn main() {
+ match Either::A {
+ Either::A => (),
+ Either::B() => (),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn malformed_match_arm_extra_fields() {
+ cov_mark::check_count!(validate_match_bailed_out, 2);
+ check_diagnostics(
+ r#"
+enum A { B(isize, isize), C }
+fn main() {
+ match A::B(1, 2) {
+ A::B(_, _, _) => (),
+ }
+ match A::B(1, 2) {
+ A::C(_) => (),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn expr_diverges() {
+ cov_mark::check_count!(validate_match_bailed_out, 2);
+ check_diagnostics(
+ r#"
+enum Either { A, B }
+
+fn main() {
+ match loop {} {
+ Either::A => (),
+ Either::B => (),
+ }
+ match loop {} {
+ Either::A => (),
+ }
+ match loop { break Foo::A } {
+ //^^^^^^^^^^^^^^^^^^^^^ error: missing match arm: `B` not covered
+ Either::A => (),
+ }
+ match loop { break Foo::A } {
+ Either::A => (),
+ Either::B => (),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn expr_partially_diverges() {
+ check_diagnostics_no_bails(
+ r#"
+enum Either<T> { A(T), B }
+
+fn foo() -> Either<!> { Either::B }
+fn main() -> u32 {
+ match foo() {
+ Either::A(val) => val,
+ Either::B => 0,
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn enum_record() {
+ check_diagnostics_no_bails(
+ r#"
+enum Either { A { foo: bool }, B }
+
+fn main() {
+ let a = Either::A { foo: true };
+ match a { }
+ //^ error: missing match arm: `A { .. }` and `B` not covered
+ match a { Either::A { foo: true } => () }
+ //^ error: missing match arm: `B` not covered
+ match a {
+ Either::A { } => (),
+ //^^^^^^^^^ 💡 error: missing structure fields:
+ // | - foo
+ Either::B => (),
+ }
+ match a {
+ //^ error: missing match arm: `B` not covered
+ Either::A { } => (),
+ } //^^^^^^^^^ 💡 error: missing structure fields:
+ // | - foo
+
+ match a {
+ Either::A { foo: true } => (),
+ Either::A { foo: false } => (),
+ Either::B => (),
+ }
+ match a {
+ Either::A { foo: _ } => (),
+ Either::B => (),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn enum_record_fields_out_of_order() {
+ check_diagnostics_no_bails(
+ r#"
+enum Either {
+ A { foo: bool, bar: () },
+ B,
+}
+
+fn main() {
+ let a = Either::A { foo: true, bar: () };
+ match a {
+ //^ error: missing match arm: `B` not covered
+ Either::A { bar: (), foo: false } => (),
+ Either::A { foo: true, bar: () } => (),
+ }
+
+ match a {
+ Either::A { bar: (), foo: false } => (),
+ Either::A { foo: true, bar: () } => (),
+ Either::B => (),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn enum_record_ellipsis() {
+ check_diagnostics_no_bails(
+ r#"
+enum Either {
+ A { foo: bool, bar: bool },
+ B,
+}
+
+fn main() {
+ let a = Either::B;
+ match a {
+ //^ error: missing match arm: `A { foo: false, .. }` not covered
+ Either::A { foo: true, .. } => (),
+ Either::B => (),
+ }
+ match a {
+ //^ error: missing match arm: `B` not covered
+ Either::A { .. } => (),
+ }
+
+ match a {
+ Either::A { foo: true, .. } => (),
+ Either::A { foo: false, .. } => (),
+ Either::B => (),
+ }
+
+ match a {
+ Either::A { .. } => (),
+ Either::B => (),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn enum_tuple_partial_ellipsis() {
+ check_diagnostics_no_bails(
+ r#"
+enum Either {
+ A(bool, bool, bool, bool),
+ B,
+}
+
+fn main() {
+ match Either::B {
+ //^^^^^^^^^ error: missing match arm: `A(false, _, _, true)` not covered
+ Either::A(true, .., true) => (),
+ Either::A(true, .., false) => (),
+ Either::A(false, .., false) => (),
+ Either::B => (),
+ }
+ match Either::B {
+ //^^^^^^^^^ error: missing match arm: `A(false, _, _, false)` not covered
+ Either::A(true, .., true) => (),
+ Either::A(true, .., false) => (),
+ Either::A(.., true) => (),
+ Either::B => (),
+ }
+
+ match Either::B {
+ Either::A(true, .., true) => (),
+ Either::A(true, .., false) => (),
+ Either::A(false, .., true) => (),
+ Either::A(false, .., false) => (),
+ Either::B => (),
+ }
+ match Either::B {
+ Either::A(true, .., true) => (),
+ Either::A(true, .., false) => (),
+ Either::A(.., true) => (),
+ Either::A(.., false) => (),
+ Either::B => (),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn never() {
+ check_diagnostics_no_bails(
+ r#"
+enum Never {}
+
+fn enum_(never: Never) {
+ match never {}
+}
+fn enum_ref(never: &Never) {
+ match never {}
+ //^^^^^ error: missing match arm: type `&Never` is non-empty
+}
+fn bang(never: !) {
+ match never {}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unknown_type() {
+ cov_mark::check_count!(validate_match_bailed_out, 1);
+
+ check_diagnostics(
+ r#"
+enum Option<T> { Some(T), None }
+
+fn main() {
+ // `Never` is deliberately not defined so that it's an uninferred type.
+ match Option::<Never>::None {
+ None => (),
+ Some(never) => match never {},
+ }
+ match Option::<Never>::None {
+ //^^^^^^^^^^^^^^^^^^^^^ error: missing match arm: `None` not covered
+ Option::Some(_never) => {},
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn tuple_of_bools_with_ellipsis_at_end_missing_arm() {
+ check_diagnostics_no_bails(
+ r#"
+fn main() {
+ match (false, true, false) {
+ //^^^^^^^^^^^^^^^^^^^^ error: missing match arm: `(true, _, _)` not covered
+ (false, ..) => (),
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn tuple_of_bools_with_ellipsis_at_beginning_missing_arm() {
+ check_diagnostics_no_bails(
+ r#"
+fn main() {
+ match (false, true, false) {
+ //^^^^^^^^^^^^^^^^^^^^ error: missing match arm: `(_, _, true)` not covered
+ (.., false) => (),
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn tuple_of_bools_with_ellipsis_in_middle_missing_arm() {
+ check_diagnostics_no_bails(
+ r#"
+fn main() {
+ match (false, true, false) {
+ //^^^^^^^^^^^^^^^^^^^^ error: missing match arm: `(false, _, _)` not covered
+ (true, .., false) => (),
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn record_struct() {
+ check_diagnostics_no_bails(
+ r#"struct Foo { a: bool }
+fn main(f: Foo) {
+ match f {}
+ //^ error: missing match arm: type `Foo` is non-empty
+ match f { Foo { a: true } => () }
+ //^ error: missing match arm: `Foo { a: false }` not covered
+ match &f { Foo { a: true } => () }
+ //^^ error: missing match arm: `&Foo { a: false }` not covered
+ match f { Foo { a: _ } => () }
+ match f {
+ Foo { a: true } => (),
+ Foo { a: false } => (),
+ }
+ match &f {
+ Foo { a: true } => (),
+ Foo { a: false } => (),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn tuple_struct() {
+ check_diagnostics_no_bails(
+ r#"struct Foo(bool);
+fn main(f: Foo) {
+ match f {}
+ //^ error: missing match arm: type `Foo` is non-empty
+ match f { Foo(true) => () }
+ //^ error: missing match arm: `Foo(false)` not covered
+ match f {
+ Foo(true) => (),
+ Foo(false) => (),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unit_struct() {
+ check_diagnostics_no_bails(
+ r#"struct Foo;
+fn main(f: Foo) {
+ match f {}
+ //^ error: missing match arm: type `Foo` is non-empty
+ match f { Foo => () }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn record_struct_ellipsis() {
+ check_diagnostics_no_bails(
+ r#"struct Foo { foo: bool, bar: bool }
+fn main(f: Foo) {
+ match f { Foo { foo: true, .. } => () }
+ //^ error: missing match arm: `Foo { foo: false, .. }` not covered
+ match f {
+ //^ error: missing match arm: `Foo { foo: false, bar: true }` not covered
+ Foo { foo: true, .. } => (),
+ Foo { bar: false, .. } => ()
+ }
+ match f { Foo { .. } => () }
+ match f {
+ Foo { foo: true, .. } => (),
+ Foo { foo: false, .. } => ()
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn internal_or() {
+ check_diagnostics_no_bails(
+ r#"
+fn main() {
+ enum Either { A(bool), B }
+ match Either::B {
+ //^^^^^^^^^ error: missing match arm: `B` not covered
+ Either::A(true | false) => (),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_panic_at_unimplemented_subpattern_type() {
+ cov_mark::check_count!(validate_match_bailed_out, 1);
+
+ check_diagnostics(
+ r#"
+struct S { a: char}
+fn main(v: S) {
+ match v { S{ a } => {} }
+ match v { S{ a: _x } => {} }
+ match v { S{ a: 'a' } => {} }
+ match v { S{..} => {} }
+ match v { _ => {} }
+ match v { }
+ //^ error: missing match arm: type `S` is non-empty
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn binding() {
+ check_diagnostics_no_bails(
+ r#"
+fn main() {
+ match true {
+ _x @ true => {}
+ false => {}
+ }
+ match true { _x @ true => {} }
+ //^^^^ error: missing match arm: `false` not covered
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn binding_ref_has_correct_type() {
+ cov_mark::check_count!(validate_match_bailed_out, 1);
+
+ // Asserts `PatKind::Binding(ref _x): bool`, not &bool.
+ // If that's not true match checking will panic with "incompatible constructors"
+ // FIXME: make facilities to test this directly like `tests::check_infer(..)`
+ check_diagnostics(
+ r#"
+enum Foo { A }
+fn main() {
+ // FIXME: this should not bail out but current behavior is such as the old algorithm.
+ // ExprValidator::validate_match(..) checks types of top level patterns incorrecly.
+ match Foo::A {
+ ref _x => {}
+ Foo::A => {}
+ }
+ match (true,) {
+ (ref _x,) => {}
+ (true,) => {}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn enum_non_exhaustive() {
+ check_diagnostics_no_bails(
+ r#"
+//- /lib.rs crate:lib
+#[non_exhaustive]
+pub enum E { A, B }
+fn _local() {
+ match E::A { _ => {} }
+ match E::A {
+ E::A => {}
+ E::B => {}
+ }
+ match E::A {
+ E::A | E::B => {}
+ }
+}
+
+//- /main.rs crate:main deps:lib
+use lib::E;
+fn main() {
+ match E::A { _ => {} }
+ match E::A {
+ //^^^^ error: missing match arm: `_` not covered
+ E::A => {}
+ E::B => {}
+ }
+ match E::A {
+ //^^^^ error: missing match arm: `_` not covered
+ E::A | E::B => {}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn match_guard() {
+ check_diagnostics_no_bails(
+ r#"
+fn main() {
+ match true {
+ true if false => {}
+ true => {}
+ false => {}
+ }
+ match true {
+ //^^^^ error: missing match arm: `true` not covered
+ true if false => {}
+ false => {}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn pattern_type_is_of_substitution() {
+ check_diagnostics_no_bails(
+ r#"
+struct Foo<T>(T);
+struct Bar;
+fn main() {
+ match Foo(Bar) {
+ _ | Foo(Bar) => {}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn record_struct_no_such_field() {
+ cov_mark::check_count!(validate_match_bailed_out, 1);
+
+ check_diagnostics(
+ r#"
+struct Foo { }
+fn main(f: Foo) {
+ match f { Foo { bar } => () }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn match_ergonomics_issue_9095() {
+ check_diagnostics_no_bails(
+ r#"
+enum Foo<T> { A(T) }
+fn main() {
+ match &Foo::A(true) {
+ _ => {}
+ Foo::A(_) => {}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn normalize_field_ty() {
+ check_diagnostics_no_bails(
+ r"
+trait Trait { type Projection; }
+enum E {Foo, Bar}
+struct A;
+impl Trait for A { type Projection = E; }
+struct Next<T: Trait>(T::Projection);
+static __: () = {
+ let n: Next<A> = Next(E::Foo);
+ match n { Next(E::Foo) => {} }
+ // ^ error: missing match arm: `Next(Bar)` not covered
+ match n { Next(E::Foo | E::Bar) => {} }
+ match n { Next(E::Foo | _ ) => {} }
+ match n { Next(_ | E::Bar) => {} }
+ match n { _ | Next(E::Bar) => {} }
+ match &n { Next(E::Foo | E::Bar) => {} }
+ match &n { _ | Next(E::Bar) => {} }
+};",
+ );
+ }
+
+ #[test]
+ fn binding_mode_by_ref() {
+ check_diagnostics_no_bails(
+ r"
+enum E{ A, B }
+fn foo() {
+ match &E::A {
+ E::A => {}
+ x => {}
+ }
+}",
+ );
+ }
+
+ #[test]
+ fn macro_or_pat() {
+ check_diagnostics_no_bails(
+ r#"
+macro_rules! m {
+ () => {
+ Enum::Type1 | Enum::Type2
+ };
+}
+
+enum Enum {
+ Type1,
+ Type2,
+ Type3,
+}
+
+fn f(ty: Enum) {
+ match ty {
+ //^^ error: missing match arm: `Type3` not covered
+ m!() => (),
+ }
+
+ match ty {
+ m!() | Enum::Type3 => ()
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unexpected_ty_fndef() {
+ cov_mark::check!(validate_match_bailed_out);
+ check_diagnostics(
+ r"
+enum Exp {
+ Tuple(()),
+}
+fn f() {
+ match __unknown {
+ Exp::Tuple => {}
+ }
+}",
+ );
+ }
+
+ mod false_negatives {
+ //! The implementation of match checking here is a work in progress. As we roll this out, we
+ //! prefer false negatives to false positives (ideally there would be no false positives). This
+ //! test module should document known false negatives. Eventually we will have a complete
+ //! implementation of match checking and this module will be empty.
+ //!
+ //! The reasons for documenting known false negatives:
+ //!
+ //! 1. It acts as a backlog of work that can be done to improve the behavior of the system.
+ //! 2. It ensures the code doesn't panic when handling these cases.
+ use super::*;
+
+ #[test]
+ fn integers() {
+ cov_mark::check_count!(validate_match_bailed_out, 1);
+
+ // We don't currently check integer exhaustiveness.
+ check_diagnostics(
+ r#"
+fn main() {
+ match 5 {
+ 10 => (),
+ 11..20 => (),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn reference_patterns_at_top_level() {
+ cov_mark::check_count!(validate_match_bailed_out, 1);
+
+ check_diagnostics(
+ r#"
+fn main() {
+ match &false {
+ &true => {}
+ }
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn reference_patterns_in_fields() {
+ cov_mark::check_count!(validate_match_bailed_out, 2);
+
+ check_diagnostics(
+ r#"
+fn main() {
+ match (&false,) {
+ (true,) => {}
+ }
+ match (&false,) {
+ (&true,) => {}
+ }
+}
+ "#,
+ );
+ }
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs
new file mode 100644
index 000000000..7acd9228a
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs
@@ -0,0 +1,101 @@
+use crate::{Diagnostic, DiagnosticsContext};
+
+// Diagnostic: missing-unsafe
+//
+// This diagnostic is triggered if an operation marked as `unsafe` is used outside of an `unsafe` function or block.
+pub(crate) fn missing_unsafe(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsafe) -> Diagnostic {
+ Diagnostic::new(
+ "missing-unsafe",
+ "this operation is unsafe and requires an unsafe function or block",
+ ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::check_diagnostics;
+
+ #[test]
+ fn missing_unsafe_diagnostic_with_raw_ptr() {
+ check_diagnostics(
+ r#"
+fn main() {
+ let x = &5 as *const usize;
+ unsafe { let y = *x; }
+ let z = *x;
+} //^^ error: this operation is unsafe and requires an unsafe function or block
+"#,
+ )
+ }
+
+ #[test]
+ fn missing_unsafe_diagnostic_with_unsafe_call() {
+ check_diagnostics(
+ r#"
+struct HasUnsafe;
+
+impl HasUnsafe {
+ unsafe fn unsafe_fn(&self) {
+ let x = &5 as *const usize;
+ let y = *x;
+ }
+}
+
+unsafe fn unsafe_fn() {
+ let x = &5 as *const usize;
+ let y = *x;
+}
+
+fn main() {
+ unsafe_fn();
+ //^^^^^^^^^^^ error: this operation is unsafe and requires an unsafe function or block
+ HasUnsafe.unsafe_fn();
+ //^^^^^^^^^^^^^^^^^^^^^ error: this operation is unsafe and requires an unsafe function or block
+ unsafe {
+ unsafe_fn();
+ HasUnsafe.unsafe_fn();
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn missing_unsafe_diagnostic_with_static_mut() {
+ check_diagnostics(
+ r#"
+struct Ty {
+ a: u8,
+}
+
+static mut STATIC_MUT: Ty = Ty { a: 0 };
+
+fn main() {
+ let x = STATIC_MUT.a;
+ //^^^^^^^^^^ error: this operation is unsafe and requires an unsafe function or block
+ unsafe {
+ let x = STATIC_MUT.a;
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_missing_unsafe_diagnostic_with_safe_intrinsic() {
+ check_diagnostics(
+ r#"
+extern "rust-intrinsic" {
+ pub fn bitreverse(x: u32) -> u32; // Safe intrinsic
+ pub fn floorf32(x: f32) -> f32; // Unsafe intrinsic
+}
+
+fn main() {
+ let _ = bitreverse(12);
+ let _ = floorf32(12.0);
+ //^^^^^^^^^^^^^^ error: this operation is unsafe and requires an unsafe function or block
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs
new file mode 100644
index 000000000..e032c578f
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs
@@ -0,0 +1,283 @@
+use hir::{db::AstDatabase, HasSource, HirDisplay, Semantics};
+use ide_db::{base_db::FileId, source_change::SourceChange, RootDatabase};
+use syntax::{
+ ast::{self, edit::IndentLevel, make},
+ AstNode,
+};
+use text_edit::TextEdit;
+
+use crate::{fix, Assist, Diagnostic, DiagnosticsContext};
+
+// Diagnostic: no-such-field
+//
+// This diagnostic is triggered if created structure does not have field provided in record.
+pub(crate) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Diagnostic {
+ Diagnostic::new(
+ "no-such-field",
+ "no such field",
+ ctx.sema.diagnostics_display_range(d.field.clone().map(|it| it.into())).range,
+ )
+ .with_fixes(fixes(ctx, d))
+}
+
+fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Option<Vec<Assist>> {
+ let root = ctx.sema.db.parse_or_expand(d.field.file_id)?;
+ missing_record_expr_field_fixes(
+ &ctx.sema,
+ d.field.file_id.original_file(ctx.sema.db),
+ &d.field.value.to_node(&root),
+ )
+}
+
+fn missing_record_expr_field_fixes(
+ sema: &Semantics<'_, RootDatabase>,
+ usage_file_id: FileId,
+ record_expr_field: &ast::RecordExprField,
+) -> Option<Vec<Assist>> {
+ let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
+ let def_id = sema.resolve_variant(record_lit)?;
+ let module;
+ let def_file_id;
+ let record_fields = match def_id {
+ hir::VariantDef::Struct(s) => {
+ module = s.module(sema.db);
+ let source = s.source(sema.db)?;
+ def_file_id = source.file_id;
+ let fields = source.value.field_list()?;
+ record_field_list(fields)?
+ }
+ hir::VariantDef::Union(u) => {
+ module = u.module(sema.db);
+ let source = u.source(sema.db)?;
+ def_file_id = source.file_id;
+ source.value.record_field_list()?
+ }
+ hir::VariantDef::Variant(e) => {
+ module = e.module(sema.db);
+ let source = e.source(sema.db)?;
+ def_file_id = source.file_id;
+ let fields = source.value.field_list()?;
+ record_field_list(fields)?
+ }
+ };
+ let def_file_id = def_file_id.original_file(sema.db);
+
+ let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?.adjusted();
+ if new_field_type.is_unknown() {
+ return None;
+ }
+ let new_field = make::record_field(
+ None,
+ make::name(&record_expr_field.field_name()?.text()),
+ make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
+ );
+
+ let last_field = record_fields.fields().last()?;
+ let last_field_syntax = last_field.syntax();
+ let indent = IndentLevel::from_node(last_field_syntax);
+
+ let mut new_field = new_field.to_string();
+ if usage_file_id != def_file_id {
+ new_field = format!("pub(crate) {}", new_field);
+ }
+ new_field = format!("\n{}{}", indent, new_field);
+
+ let needs_comma = !last_field_syntax.to_string().ends_with(',');
+ if needs_comma {
+ new_field = format!(",{}", new_field);
+ }
+
+ let source_change = SourceChange::from_text_edit(
+ def_file_id,
+ TextEdit::insert(last_field_syntax.text_range().end(), new_field),
+ );
+
+ return Some(vec![fix(
+ "create_field",
+ "Create field",
+ source_change,
+ record_expr_field.syntax().text_range(),
+ )]);
+
+ fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
+ match field_def_list {
+ ast::FieldList::RecordFieldList(it) => Some(it),
+ ast::FieldList::TupleFieldList(_) => None,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_diagnostics, check_fix};
+
+ #[test]
+ fn no_such_field_diagnostics() {
+ check_diagnostics(
+ r#"
+struct S { foo: i32, bar: () }
+impl S {
+ fn new() -> S {
+ S {
+ //^ 💡 error: missing structure fields:
+ //| - bar
+ foo: 92,
+ baz: 62,
+ //^^^^^^^ 💡 error: no such field
+ }
+ }
+}
+"#,
+ );
+ }
+ #[test]
+ fn no_such_field_with_feature_flag_diagnostics() {
+ check_diagnostics(
+ r#"
+//- /lib.rs crate:foo cfg:feature=foo
+struct MyStruct {
+ my_val: usize,
+ #[cfg(feature = "foo")]
+ bar: bool,
+}
+
+impl MyStruct {
+ #[cfg(feature = "foo")]
+ pub(crate) fn new(my_val: usize, bar: bool) -> Self {
+ Self { my_val, bar }
+ }
+ #[cfg(not(feature = "foo"))]
+ pub(crate) fn new(my_val: usize, _bar: bool) -> Self {
+ Self { my_val }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_such_field_enum_with_feature_flag_diagnostics() {
+ check_diagnostics(
+ r#"
+//- /lib.rs crate:foo cfg:feature=foo
+enum Foo {
+ #[cfg(not(feature = "foo"))]
+ Buz,
+ #[cfg(feature = "foo")]
+ Bar,
+ Baz
+}
+
+fn test_fn(f: Foo) {
+ match f {
+ Foo::Bar => {},
+ Foo::Baz => {},
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_such_field_with_feature_flag_diagnostics_on_struct_lit() {
+ check_diagnostics(
+ r#"
+//- /lib.rs crate:foo cfg:feature=foo
+struct S {
+ #[cfg(feature = "foo")]
+ foo: u32,
+ #[cfg(not(feature = "foo"))]
+ bar: u32,
+}
+
+impl S {
+ #[cfg(feature = "foo")]
+ fn new(foo: u32) -> Self {
+ Self { foo }
+ }
+ #[cfg(not(feature = "foo"))]
+ fn new(bar: u32) -> Self {
+ Self { bar }
+ }
+ fn new2(bar: u32) -> Self {
+ #[cfg(feature = "foo")]
+ { Self { foo: bar } }
+ #[cfg(not(feature = "foo"))]
+ { Self { bar } }
+ }
+ fn new2(val: u32) -> Self {
+ Self {
+ #[cfg(feature = "foo")]
+ foo: val,
+ #[cfg(not(feature = "foo"))]
+ bar: val,
+ }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_such_field_with_type_macro() {
+ check_diagnostics(
+ r#"
+macro_rules! Type { () => { u32 }; }
+struct Foo { bar: Type![] }
+
+impl Foo {
+ fn new() -> Self {
+ Foo { bar: 0 }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_add_field_from_usage() {
+ check_fix(
+ r"
+fn main() {
+ Foo { bar: 3, baz$0: false};
+}
+struct Foo {
+ bar: i32
+}
+",
+ r"
+fn main() {
+ Foo { bar: 3, baz: false};
+}
+struct Foo {
+ bar: i32,
+ baz: bool
+}
+",
+ )
+ }
+
+ #[test]
+ fn test_add_field_in_other_file_from_usage() {
+ check_fix(
+ r#"
+//- /main.rs
+mod foo;
+
+fn main() {
+ foo::Foo { bar: 3, $0baz: false};
+}
+//- /foo.rs
+struct Foo {
+ bar: i32
+}
+"#,
+ r#"
+struct Foo {
+ bar: i32,
+ pub(crate) baz: bool
+}
+"#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs
new file mode 100644
index 000000000..9826e1c70
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs
@@ -0,0 +1,131 @@
+use hir::{db::AstDatabase, InFile};
+use ide_db::source_change::SourceChange;
+use syntax::{
+ ast::{self, HasArgList},
+ AstNode, TextRange,
+};
+use text_edit::TextEdit;
+
+use crate::{fix, Assist, Diagnostic, DiagnosticsContext, Severity};
+
+// Diagnostic: replace-filter-map-next-with-find-map
+//
+// This diagnostic is triggered when `.filter_map(..).next()` is used, rather than the more concise `.find_map(..)`.
+pub(crate) fn replace_filter_map_next_with_find_map(
+ ctx: &DiagnosticsContext<'_>,
+ d: &hir::ReplaceFilterMapNextWithFindMap,
+) -> Diagnostic {
+ Diagnostic::new(
+ "replace-filter-map-next-with-find-map",
+ "replace filter_map(..).next() with find_map(..)",
+ ctx.sema.diagnostics_display_range(InFile::new(d.file, d.next_expr.clone().into())).range,
+ )
+ .severity(Severity::WeakWarning)
+ .with_fixes(fixes(ctx, d))
+}
+
+fn fixes(
+ ctx: &DiagnosticsContext<'_>,
+ d: &hir::ReplaceFilterMapNextWithFindMap,
+) -> Option<Vec<Assist>> {
+ let root = ctx.sema.db.parse_or_expand(d.file)?;
+ let next_expr = d.next_expr.to_node(&root);
+ let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?;
+
+ let filter_map_call = ast::MethodCallExpr::cast(next_call.receiver()?.syntax().clone())?;
+ let filter_map_name_range = filter_map_call.name_ref()?.ident_token()?.text_range();
+ let filter_map_args = filter_map_call.arg_list()?;
+
+ let range_to_replace =
+ TextRange::new(filter_map_name_range.start(), next_expr.syntax().text_range().end());
+ let replacement = format!("find_map{}", filter_map_args.syntax().text());
+ let trigger_range = next_expr.syntax().text_range();
+
+ let edit = TextEdit::replace(range_to_replace, replacement);
+
+ let source_change = SourceChange::from_text_edit(d.file.original_file(ctx.sema.db), edit);
+
+ Some(vec![fix(
+ "replace_with_find_map",
+ "Replace filter_map(..).next() with find_map()",
+ source_change,
+ trigger_range,
+ )])
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_diagnostics, check_fix};
+
+ #[test]
+ fn replace_filter_map_next_with_find_map2() {
+ check_diagnostics(
+ r#"
+//- minicore: iterators
+fn foo() {
+ let m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
+} //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 weak: replace filter_map(..).next() with find_map(..)
+"#,
+ );
+ }
+
+ #[test]
+ fn replace_filter_map_next_with_find_map_no_diagnostic_without_next() {
+ check_diagnostics(
+ r#"
+//- minicore: iterators
+fn foo() {
+ let m = core::iter::repeat(())
+ .filter_map(|()| Some(92))
+ .count();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn replace_filter_map_next_with_find_map_no_diagnostic_with_intervening_methods() {
+ check_diagnostics(
+ r#"
+//- minicore: iterators
+fn foo() {
+ let m = core::iter::repeat(())
+ .filter_map(|()| Some(92))
+ .map(|x| x + 2)
+ .next();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn replace_filter_map_next_with_find_map_no_diagnostic_if_not_in_chain() {
+ check_diagnostics(
+ r#"
+//- minicore: iterators
+fn foo() {
+ let m = core::iter::repeat(())
+ .filter_map(|()| Some(92));
+ let n = m.next();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn replace_with_find_map() {
+ check_fix(
+ r#"
+//- minicore: iterators
+fn foo() {
+ let m = core::iter::repeat(()).$0filter_map(|()| Some(92)).next();
+}
+"#,
+ r#"
+fn foo() {
+ let m = core::iter::repeat(()).find_map(|()| Some(92));
+}
+"#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs
new file mode 100644
index 000000000..6bf90e645
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs
@@ -0,0 +1,573 @@
+use hir::{db::AstDatabase, HirDisplay, Type};
+use ide_db::{famous_defs::FamousDefs, source_change::SourceChange};
+use syntax::{
+ ast::{self, BlockExpr, ExprStmt},
+ AstNode,
+};
+use text_edit::TextEdit;
+
+use crate::{adjusted_display_range, fix, Assist, Diagnostic, DiagnosticsContext};
+
+// Diagnostic: type-mismatch
+//
+// This diagnostic is triggered when the type of an expression does not match
+// the expected type.
+pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Diagnostic {
+ let display_range = adjusted_display_range::<ast::BlockExpr>(
+ ctx,
+ d.expr.clone().map(|it| it.into()),
+ &|block| {
+ let r_curly_range = block.stmt_list()?.r_curly_token()?.text_range();
+ cov_mark::hit!(type_mismatch_on_block);
+ Some(r_curly_range)
+ },
+ );
+
+ let mut diag = Diagnostic::new(
+ "type-mismatch",
+ format!(
+ "expected {}, found {}",
+ d.expected.display(ctx.sema.db),
+ d.actual.display(ctx.sema.db)
+ ),
+ display_range,
+ )
+ .with_fixes(fixes(ctx, d));
+ if diag.fixes.is_none() {
+ diag.experimental = true;
+ }
+ diag
+}
+
+fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Option<Vec<Assist>> {
+ let mut fixes = Vec::new();
+
+ add_reference(ctx, d, &mut fixes);
+ add_missing_ok_or_some(ctx, d, &mut fixes);
+ remove_semicolon(ctx, d, &mut fixes);
+ str_ref_to_owned(ctx, d, &mut fixes);
+
+ if fixes.is_empty() {
+ None
+ } else {
+ Some(fixes)
+ }
+}
+
+fn add_reference(
+ ctx: &DiagnosticsContext<'_>,
+ d: &hir::TypeMismatch,
+ acc: &mut Vec<Assist>,
+) -> Option<()> {
+ let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?;
+ let expr_node = d.expr.value.to_node(&root);
+
+ let range = ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range;
+
+ let (_, mutability) = d.expected.as_reference()?;
+ let actual_with_ref = Type::reference(&d.actual, mutability);
+ if !actual_with_ref.could_coerce_to(ctx.sema.db, &d.expected) {
+ return None;
+ }
+
+ let ampersands = format!("&{}", mutability.as_keyword_for_ref());
+
+ let edit = TextEdit::insert(expr_node.syntax().text_range().start(), ampersands);
+ let source_change =
+ SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit);
+ acc.push(fix("add_reference_here", "Add reference here", source_change, range));
+ Some(())
+}
+
+fn add_missing_ok_or_some(
+ ctx: &DiagnosticsContext<'_>,
+ d: &hir::TypeMismatch,
+ acc: &mut Vec<Assist>,
+) -> Option<()> {
+ let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?;
+ let expr = d.expr.value.to_node(&root);
+ let expr_range = expr.syntax().text_range();
+ let scope = ctx.sema.scope(expr.syntax())?;
+
+ let expected_adt = d.expected.as_adt()?;
+ let expected_enum = expected_adt.as_enum()?;
+
+ let famous_defs = FamousDefs(&ctx.sema, scope.krate());
+ let core_result = famous_defs.core_result_Result();
+ let core_option = famous_defs.core_option_Option();
+
+ if Some(expected_enum) != core_result && Some(expected_enum) != core_option {
+ return None;
+ }
+
+ let variant_name = if Some(expected_enum) == core_result { "Ok" } else { "Some" };
+
+ let wrapped_actual_ty = expected_adt.ty_with_args(ctx.sema.db, &[d.actual.clone()]);
+
+ if !d.expected.could_unify_with(ctx.sema.db, &wrapped_actual_ty) {
+ return None;
+ }
+
+ let mut builder = TextEdit::builder();
+ builder.insert(expr.syntax().text_range().start(), format!("{}(", variant_name));
+ builder.insert(expr.syntax().text_range().end(), ")".to_string());
+ let source_change =
+ SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), builder.finish());
+ let name = format!("Wrap in {}", variant_name);
+ acc.push(fix("wrap_in_constructor", &name, source_change, expr_range));
+ Some(())
+}
+
+fn remove_semicolon(
+ ctx: &DiagnosticsContext<'_>,
+ d: &hir::TypeMismatch,
+ acc: &mut Vec<Assist>,
+) -> Option<()> {
+ let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?;
+ let expr = d.expr.value.to_node(&root);
+ if !d.actual.is_unit() {
+ return None;
+ }
+ let block = BlockExpr::cast(expr.syntax().clone())?;
+ let expr_before_semi =
+ block.statements().last().and_then(|s| ExprStmt::cast(s.syntax().clone()))?;
+ let type_before_semi = ctx.sema.type_of_expr(&expr_before_semi.expr()?)?.original();
+ if !type_before_semi.could_coerce_to(ctx.sema.db, &d.expected) {
+ return None;
+ }
+ let semicolon_range = expr_before_semi.semicolon_token()?.text_range();
+
+ let edit = TextEdit::delete(semicolon_range);
+ let source_change =
+ SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit);
+
+ acc.push(fix("remove_semicolon", "Remove this semicolon", source_change, semicolon_range));
+ Some(())
+}
+
+fn str_ref_to_owned(
+ ctx: &DiagnosticsContext<'_>,
+ d: &hir::TypeMismatch,
+ acc: &mut Vec<Assist>,
+) -> Option<()> {
+ let expected = d.expected.display(ctx.sema.db);
+ let actual = d.actual.display(ctx.sema.db);
+
+ if expected.to_string() != "String" || actual.to_string() != "&str" {
+ return None;
+ }
+
+ let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?;
+ let expr = d.expr.value.to_node(&root);
+ let expr_range = expr.syntax().text_range();
+
+ let to_owned = format!(".to_owned()");
+
+ let edit = TextEdit::insert(expr.syntax().text_range().end(), to_owned);
+ let source_change =
+ SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit);
+ acc.push(fix("str_ref_to_owned", "Add .to_owned() here", source_change, expr_range));
+
+ Some(())
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_diagnostics, check_fix, check_no_fix};
+
+ #[test]
+ fn missing_reference() {
+ check_diagnostics(
+ r#"
+fn main() {
+ test(123);
+ //^^^ 💡 error: expected &i32, found i32
+}
+fn test(arg: &i32) {}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_add_reference_to_int() {
+ check_fix(
+ r#"
+fn main() {
+ test(123$0);
+}
+fn test(arg: &i32) {}
+ "#,
+ r#"
+fn main() {
+ test(&123);
+}
+fn test(arg: &i32) {}
+ "#,
+ );
+ }
+
+ #[test]
+ fn test_add_mutable_reference_to_int() {
+ check_fix(
+ r#"
+fn main() {
+ test($0123);
+}
+fn test(arg: &mut i32) {}
+ "#,
+ r#"
+fn main() {
+ test(&mut 123);
+}
+fn test(arg: &mut i32) {}
+ "#,
+ );
+ }
+
+ #[test]
+ fn test_add_reference_to_array() {
+ check_fix(
+ r#"
+//- minicore: coerce_unsized
+fn main() {
+ test($0[1, 2, 3]);
+}
+fn test(arg: &[i32]) {}
+ "#,
+ r#"
+fn main() {
+ test(&[1, 2, 3]);
+}
+fn test(arg: &[i32]) {}
+ "#,
+ );
+ }
+
+ #[test]
+ fn test_add_reference_with_autoderef() {
+ check_fix(
+ r#"
+//- minicore: coerce_unsized, deref
+struct Foo;
+struct Bar;
+impl core::ops::Deref for Foo {
+ type Target = Bar;
+}
+
+fn main() {
+ test($0Foo);
+}
+fn test(arg: &Bar) {}
+ "#,
+ r#"
+struct Foo;
+struct Bar;
+impl core::ops::Deref for Foo {
+ type Target = Bar;
+}
+
+fn main() {
+ test(&Foo);
+}
+fn test(arg: &Bar) {}
+ "#,
+ );
+ }
+
+ #[test]
+ fn test_add_reference_to_method_call() {
+ check_fix(
+ r#"
+fn main() {
+ Test.call_by_ref($0123);
+}
+struct Test;
+impl Test {
+ fn call_by_ref(&self, arg: &i32) {}
+}
+ "#,
+ r#"
+fn main() {
+ Test.call_by_ref(&123);
+}
+struct Test;
+impl Test {
+ fn call_by_ref(&self, arg: &i32) {}
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn test_add_reference_to_let_stmt() {
+ check_fix(
+ r#"
+fn main() {
+ let test: &i32 = $0123;
+}
+ "#,
+ r#"
+fn main() {
+ let test: &i32 = &123;
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn test_add_mutable_reference_to_let_stmt() {
+ check_fix(
+ r#"
+fn main() {
+ let test: &mut i32 = $0123;
+}
+ "#,
+ r#"
+fn main() {
+ let test: &mut i32 = &mut 123;
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn test_wrap_return_type_option() {
+ check_fix(
+ r#"
+//- minicore: option, result
+fn div(x: i32, y: i32) -> Option<i32> {
+ if y == 0 {
+ return None;
+ }
+ x / y$0
+}
+"#,
+ r#"
+fn div(x: i32, y: i32) -> Option<i32> {
+ if y == 0 {
+ return None;
+ }
+ Some(x / y)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn const_generic_type_mismatch() {
+ check_diagnostics(
+ r#"
+ pub struct Rate<const N: u32>;
+ fn f<const N: u64>() -> Rate<N> { // FIXME: add some error
+ loop {}
+ }
+ fn run(t: Rate<5>) {
+ }
+ fn main() {
+ run(f()) // FIXME: remove this error
+ //^^^ error: expected Rate<5>, found Rate<_>
+ }
+"#,
+ );
+ }
+
+ #[test]
+ fn const_generic_unknown() {
+ check_diagnostics(
+ r#"
+ pub struct Rate<T, const NOM: u32, const DENOM: u32>(T);
+ fn run(t: Rate<u32, 1, 1>) {
+ }
+ fn main() {
+ run(Rate::<_, _, _>(5));
+ }
+"#,
+ );
+ }
+
+ #[test]
+ fn test_wrap_return_type_option_tails() {
+ check_fix(
+ r#"
+//- minicore: option, result
+fn div(x: i32, y: i32) -> Option<i32> {
+ if y == 0 {
+ Some(0)
+ } else if true {
+ 100$0
+ } else {
+ None
+ }
+}
+"#,
+ r#"
+fn div(x: i32, y: i32) -> Option<i32> {
+ if y == 0 {
+ Some(0)
+ } else if true {
+ Some(100)
+ } else {
+ None
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_wrap_return_type() {
+ check_fix(
+ r#"
+//- minicore: option, result
+fn div(x: i32, y: i32) -> Result<i32, ()> {
+ if y == 0 {
+ return Err(());
+ }
+ x / y$0
+}
+"#,
+ r#"
+fn div(x: i32, y: i32) -> Result<i32, ()> {
+ if y == 0 {
+ return Err(());
+ }
+ Ok(x / y)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_wrap_return_type_handles_generic_functions() {
+ check_fix(
+ r#"
+//- minicore: option, result
+fn div<T>(x: T) -> Result<T, i32> {
+ if x == 0 {
+ return Err(7);
+ }
+ $0x
+}
+"#,
+ r#"
+fn div<T>(x: T) -> Result<T, i32> {
+ if x == 0 {
+ return Err(7);
+ }
+ Ok(x)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_wrap_return_type_handles_type_aliases() {
+ check_fix(
+ r#"
+//- minicore: option, result
+type MyResult<T> = Result<T, ()>;
+
+fn div(x: i32, y: i32) -> MyResult<i32> {
+ if y == 0 {
+ return Err(());
+ }
+ x $0/ y
+}
+"#,
+ r#"
+type MyResult<T> = Result<T, ()>;
+
+fn div(x: i32, y: i32) -> MyResult<i32> {
+ if y == 0 {
+ return Err(());
+ }
+ Ok(x / y)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_in_const_and_static() {
+ check_fix(
+ r#"
+//- minicore: option, result
+static A: Option<()> = {($0)};
+ "#,
+ r#"
+static A: Option<()> = {Some(())};
+ "#,
+ );
+ check_fix(
+ r#"
+//- minicore: option, result
+const _: Option<()> = {($0)};
+ "#,
+ r#"
+const _: Option<()> = {Some(())};
+ "#,
+ );
+ }
+
+ #[test]
+ fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() {
+ check_no_fix(
+ r#"
+//- minicore: option, result
+fn foo() -> Result<(), i32> { 0$0 }
+"#,
+ );
+ }
+
+ #[test]
+ fn test_wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() {
+ check_no_fix(
+ r#"
+//- minicore: option, result
+enum SomeOtherEnum { Ok(i32), Err(String) }
+
+fn foo() -> SomeOtherEnum { 0$0 }
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_semicolon() {
+ check_fix(r#"fn f() -> i32 { 92$0; }"#, r#"fn f() -> i32 { 92 }"#);
+ }
+
+ #[test]
+ fn str_ref_to_owned() {
+ check_fix(
+ r#"
+struct String;
+
+fn test() -> String {
+ "a"$0
+}
+ "#,
+ r#"
+struct String;
+
+fn test() -> String {
+ "a".to_owned()
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn type_mismatch_on_block() {
+ cov_mark::check!(type_mismatch_on_block);
+ check_diagnostics(
+ r#"
+fn f() -> i32 {
+ let x = 1;
+ let y = 2;
+ let _ = x + y;
+ }
+//^ error: expected i32, found ()
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unimplemented_builtin_macro.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unimplemented_builtin_macro.rs
new file mode 100644
index 000000000..e879de75c
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unimplemented_builtin_macro.rs
@@ -0,0 +1,16 @@
+use crate::{Diagnostic, DiagnosticsContext, Severity};
+
+// Diagnostic: unimplemented-builtin-macro
+//
+// This diagnostic is shown for builtin macros which are not yet implemented by rust-analyzer
+pub(crate) fn unimplemented_builtin_macro(
+ ctx: &DiagnosticsContext<'_>,
+ d: &hir::UnimplementedBuiltinMacro,
+) -> Diagnostic {
+ Diagnostic::new(
+ "unimplemented-builtin-macro",
+ "unimplemented built-in macro".to_string(),
+ ctx.sema.diagnostics_display_range(d.node.clone()).range,
+ )
+ .severity(Severity::WeakWarning)
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs
new file mode 100644
index 000000000..c626932f1
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs
@@ -0,0 +1,336 @@
+//! Diagnostic emitted for files that aren't part of any crate.
+
+use hir::db::DefDatabase;
+use ide_db::{
+ base_db::{FileId, FileLoader, SourceDatabase, SourceDatabaseExt},
+ source_change::SourceChange,
+ RootDatabase,
+};
+use syntax::{
+ ast::{self, HasModuleItem, HasName},
+ AstNode, TextRange, TextSize,
+};
+use text_edit::TextEdit;
+
+use crate::{fix, Assist, Diagnostic, DiagnosticsContext, Severity};
+
+// Diagnostic: unlinked-file
+//
+// This diagnostic is shown for files that are not included in any crate, or files that are part of
+// crates rust-analyzer failed to discover. The file will not have IDE features available.
+pub(crate) fn unlinked_file(
+ ctx: &DiagnosticsContext<'_>,
+ acc: &mut Vec<Diagnostic>,
+ file_id: FileId,
+) {
+ // Limit diagnostic to the first few characters in the file. This matches how VS Code
+ // renders it with the full span, but on other editors, and is less invasive.
+ let range = ctx.sema.db.parse(file_id).syntax_node().text_range();
+ // FIXME: This is wrong if one of the first three characters is not ascii: `//Ы`.
+ let range = range.intersect(TextRange::up_to(TextSize::of("..."))).unwrap_or(range);
+
+ acc.push(
+ Diagnostic::new("unlinked-file", "file not included in module tree", range)
+ .severity(Severity::WeakWarning)
+ .with_fixes(fixes(ctx, file_id)),
+ );
+}
+
+fn fixes(ctx: &DiagnosticsContext<'_>, file_id: FileId) -> Option<Vec<Assist>> {
+ // If there's an existing module that could add `mod` or `pub mod` items to include the unlinked file,
+ // suggest that as a fix.
+
+ let source_root = ctx.sema.db.source_root(ctx.sema.db.file_source_root(file_id));
+ let our_path = source_root.path_for_file(&file_id)?;
+ let (mut module_name, _) = our_path.name_and_extension()?;
+
+ // Candidates to look for:
+ // - `mod.rs`, `main.rs` and `lib.rs` in the same folder
+ // - `$dir.rs` in the parent folder, where `$dir` is the directory containing `self.file_id`
+ let parent = our_path.parent()?;
+ let paths = {
+ let parent = if module_name == "mod" {
+ // for mod.rs we need to actually look up one higher
+ // and take the parent as our to be module name
+ let (name, _) = parent.name_and_extension()?;
+ module_name = name;
+ parent.parent()?
+ } else {
+ parent
+ };
+ let mut paths =
+ vec![parent.join("mod.rs")?, parent.join("lib.rs")?, parent.join("main.rs")?];
+
+ // `submod/bla.rs` -> `submod.rs`
+ let parent_mod = (|| {
+ let (name, _) = parent.name_and_extension()?;
+ parent.parent()?.join(&format!("{}.rs", name))
+ })();
+ paths.extend(parent_mod);
+ paths
+ };
+
+ for &parent_id in paths.iter().filter_map(|path| source_root.file_for_path(path)) {
+ for &krate in ctx.sema.db.relevant_crates(parent_id).iter() {
+ let crate_def_map = ctx.sema.db.crate_def_map(krate);
+ for (_, module) in crate_def_map.modules() {
+ if module.origin.is_inline() {
+ // We don't handle inline `mod parent {}`s, they use different paths.
+ continue;
+ }
+
+ if module.origin.file_id() == Some(parent_id) {
+ return make_fixes(ctx.sema.db, parent_id, module_name, file_id);
+ }
+ }
+ }
+ }
+
+ None
+}
+
+fn make_fixes(
+ db: &RootDatabase,
+ parent_file_id: FileId,
+ new_mod_name: &str,
+ added_file_id: FileId,
+) -> Option<Vec<Assist>> {
+ fn is_outline_mod(item: &ast::Item) -> bool {
+ matches!(item, ast::Item::Module(m) if m.item_list().is_none())
+ }
+
+ let mod_decl = format!("mod {};", new_mod_name);
+ let pub_mod_decl = format!("pub mod {};", new_mod_name);
+
+ let ast: ast::SourceFile = db.parse(parent_file_id).tree();
+
+ let mut mod_decl_builder = TextEdit::builder();
+ let mut pub_mod_decl_builder = TextEdit::builder();
+
+ // If there's an existing `mod m;` statement matching the new one, don't emit a fix (it's
+ // probably `#[cfg]`d out).
+ for item in ast.items() {
+ if let ast::Item::Module(m) = item {
+ if let Some(name) = m.name() {
+ if m.item_list().is_none() && name.to_string() == new_mod_name {
+ cov_mark::hit!(unlinked_file_skip_fix_when_mod_already_exists);
+ return None;
+ }
+ }
+ }
+ }
+
+ // If there are existing `mod m;` items, append after them (after the first group of them, rather).
+ match ast.items().skip_while(|item| !is_outline_mod(item)).take_while(is_outline_mod).last() {
+ Some(last) => {
+ cov_mark::hit!(unlinked_file_append_to_existing_mods);
+ let offset = last.syntax().text_range().end();
+ mod_decl_builder.insert(offset, format!("\n{}", mod_decl));
+ pub_mod_decl_builder.insert(offset, format!("\n{}", pub_mod_decl));
+ }
+ None => {
+ // Prepend before the first item in the file.
+ match ast.items().next() {
+ Some(item) => {
+ cov_mark::hit!(unlinked_file_prepend_before_first_item);
+ let offset = item.syntax().text_range().start();
+ mod_decl_builder.insert(offset, format!("{}\n\n", mod_decl));
+ pub_mod_decl_builder.insert(offset, format!("{}\n\n", pub_mod_decl));
+ }
+ None => {
+ // No items in the file, so just append at the end.
+ cov_mark::hit!(unlinked_file_empty_file);
+ let offset = ast.syntax().text_range().end();
+ mod_decl_builder.insert(offset, format!("{}\n", mod_decl));
+ pub_mod_decl_builder.insert(offset, format!("{}\n", pub_mod_decl));
+ }
+ }
+ }
+ }
+
+ let trigger_range = db.parse(added_file_id).tree().syntax().text_range();
+ Some(vec![
+ fix(
+ "add_mod_declaration",
+ &format!("Insert `{}`", mod_decl),
+ SourceChange::from_text_edit(parent_file_id, mod_decl_builder.finish()),
+ trigger_range,
+ ),
+ fix(
+ "add_pub_mod_declaration",
+ &format!("Insert `{}`", pub_mod_decl),
+ SourceChange::from_text_edit(parent_file_id, pub_mod_decl_builder.finish()),
+ trigger_range,
+ ),
+ ])
+}
+
+#[cfg(test)]
+mod tests {
+
+ use crate::tests::{check_diagnostics, check_fix, check_fixes, check_no_fix};
+
+ #[test]
+ fn unlinked_file_prepend_first_item() {
+ cov_mark::check!(unlinked_file_prepend_before_first_item);
+ // Only tests the first one for `pub mod` since the rest are the same
+ check_fixes(
+ r#"
+//- /main.rs
+fn f() {}
+//- /foo.rs
+$0
+"#,
+ vec![
+ r#"
+mod foo;
+
+fn f() {}
+"#,
+ r#"
+pub mod foo;
+
+fn f() {}
+"#,
+ ],
+ );
+ }
+
+ #[test]
+ fn unlinked_file_append_mod() {
+ cov_mark::check!(unlinked_file_append_to_existing_mods);
+ check_fix(
+ r#"
+//- /main.rs
+//! Comment on top
+
+mod preexisting;
+
+mod preexisting2;
+
+struct S;
+
+mod preexisting_bottom;)
+//- /foo.rs
+$0
+"#,
+ r#"
+//! Comment on top
+
+mod preexisting;
+
+mod preexisting2;
+mod foo;
+
+struct S;
+
+mod preexisting_bottom;)
+"#,
+ );
+ }
+
+ #[test]
+ fn unlinked_file_insert_in_empty_file() {
+ cov_mark::check!(unlinked_file_empty_file);
+ check_fix(
+ r#"
+//- /main.rs
+//- /foo.rs
+$0
+"#,
+ r#"
+mod foo;
+"#,
+ );
+ }
+
+ #[test]
+ fn unlinked_file_insert_in_empty_file_mod_file() {
+ check_fix(
+ r#"
+//- /main.rs
+//- /foo/mod.rs
+$0
+"#,
+ r#"
+mod foo;
+"#,
+ );
+ check_fix(
+ r#"
+//- /main.rs
+mod bar;
+//- /bar.rs
+// bar module
+//- /bar/foo/mod.rs
+$0
+"#,
+ r#"
+// bar module
+mod foo;
+"#,
+ );
+ }
+
+ #[test]
+ fn unlinked_file_old_style_modrs() {
+ check_fix(
+ r#"
+//- /main.rs
+mod submod;
+//- /submod/mod.rs
+// in mod.rs
+//- /submod/foo.rs
+$0
+"#,
+ r#"
+// in mod.rs
+mod foo;
+"#,
+ );
+ }
+
+ #[test]
+ fn unlinked_file_new_style_mod() {
+ check_fix(
+ r#"
+//- /main.rs
+mod submod;
+//- /submod.rs
+//- /submod/foo.rs
+$0
+"#,
+ r#"
+mod foo;
+"#,
+ );
+ }
+
+ #[test]
+ fn unlinked_file_with_cfg_off() {
+ cov_mark::check!(unlinked_file_skip_fix_when_mod_already_exists);
+ check_no_fix(
+ r#"
+//- /main.rs
+#[cfg(never)]
+mod foo;
+
+//- /foo.rs
+$0
+"#,
+ );
+ }
+
+ #[test]
+ fn unlinked_file_with_cfg_on() {
+ check_diagnostics(
+ r#"
+//- /main.rs
+#[cfg(not(never))]
+mod foo;
+
+//- /foo.rs
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_extern_crate.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_extern_crate.rs
new file mode 100644
index 000000000..74e4a69c6
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_extern_crate.rs
@@ -0,0 +1,49 @@
+use crate::{Diagnostic, DiagnosticsContext};
+
+// Diagnostic: unresolved-extern-crate
+//
+// This diagnostic is triggered if rust-analyzer is unable to discover referred extern crate.
+pub(crate) fn unresolved_extern_crate(
+ ctx: &DiagnosticsContext<'_>,
+ d: &hir::UnresolvedExternCrate,
+) -> Diagnostic {
+ Diagnostic::new(
+ "unresolved-extern-crate",
+ "unresolved extern crate",
+ ctx.sema.diagnostics_display_range(d.decl.clone().map(|it| it.into())).range,
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::check_diagnostics;
+
+ #[test]
+ fn unresolved_extern_crate() {
+ check_diagnostics(
+ r#"
+//- /main.rs crate:main deps:core
+extern crate core;
+ extern crate doesnotexist;
+//^^^^^^^^^^^^^^^^^^^^^^^^^^ error: unresolved extern crate
+//- /lib.rs crate:core
+"#,
+ );
+ }
+
+ #[test]
+ fn extern_crate_self_as() {
+ cov_mark::check!(extern_crate_self_as);
+ check_diagnostics(
+ r#"
+//- /lib.rs
+ extern crate doesnotexist;
+//^^^^^^^^^^^^^^^^^^^^^^^^^^ error: unresolved extern crate
+// Should not error.
+extern crate self as foo;
+struct Foo;
+use foo::Foo as Bar;
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_import.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_import.rs
new file mode 100644
index 000000000..e52a88459
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_import.rs
@@ -0,0 +1,90 @@
+use crate::{Diagnostic, DiagnosticsContext};
+
+// Diagnostic: unresolved-import
+//
+// This diagnostic is triggered if rust-analyzer is unable to resolve a path in
+// a `use` declaration.
+pub(crate) fn unresolved_import(
+ ctx: &DiagnosticsContext<'_>,
+ d: &hir::UnresolvedImport,
+) -> Diagnostic {
+ Diagnostic::new(
+ "unresolved-import",
+ "unresolved import",
+ ctx.sema.diagnostics_display_range(d.decl.clone().map(|it| it.into())).range,
+ )
+ // This currently results in false positives in the following cases:
+ // - `cfg_if!`-generated code in libstd (we don't load the sysroot correctly)
+ // - `core::arch` (we don't handle `#[path = "../<path>"]` correctly)
+ // - proc macros and/or proc macro generated code
+ .experimental()
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::check_diagnostics;
+
+ #[test]
+ fn unresolved_import() {
+ check_diagnostics(
+ r#"
+use does_exist;
+use does_not_exist;
+ //^^^^^^^^^^^^^^ error: unresolved import
+
+mod does_exist {}
+"#,
+ );
+ }
+
+ #[test]
+ fn unresolved_import_in_use_tree() {
+ // Only the relevant part of a nested `use` item should be highlighted.
+ check_diagnostics(
+ r#"
+use does_exist::{Exists, DoesntExist};
+ //^^^^^^^^^^^ error: unresolved import
+
+use {does_not_exist::*, does_exist};
+ //^^^^^^^^^^^^^^^^^ error: unresolved import
+
+use does_not_exist::{
+ a,
+ //^ error: unresolved import
+ b,
+ //^ error: unresolved import
+ c,
+ //^ error: unresolved import
+};
+
+mod does_exist {
+ pub struct Exists;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn dedup_unresolved_import_from_unresolved_crate() {
+ check_diagnostics(
+ r#"
+//- /main.rs crate:main
+mod a {
+ extern crate doesnotexist;
+ //^^^^^^^^^^^^^^^^^^^^^^^^^^ error: unresolved extern crate
+
+ // Should not error, since we already errored for the missing crate.
+ use doesnotexist::{self, bla, *};
+
+ use crate::doesnotexist;
+ //^^^^^^^^^^^^^^^^^^^ error: unresolved import
+}
+
+mod m {
+ use super::doesnotexist;
+ //^^^^^^^^^^^^^^^^^^^ error: unresolved import
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs
new file mode 100644
index 000000000..4b4312475
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs
@@ -0,0 +1,76 @@
+use crate::{Diagnostic, DiagnosticsContext};
+
+// Diagnostic: unresolved-macro-call
+//
+// This diagnostic is triggered if rust-analyzer is unable to resolve the path
+// to a macro in a macro invocation.
+pub(crate) fn unresolved_macro_call(
+ ctx: &DiagnosticsContext<'_>,
+ d: &hir::UnresolvedMacroCall,
+) -> Diagnostic {
+ // Use more accurate position if available.
+ let display_range = d
+ .precise_location
+ .unwrap_or_else(|| ctx.sema.diagnostics_display_range(d.macro_call.clone()).range);
+
+ let bang = if d.is_bang { "!" } else { "" };
+ Diagnostic::new(
+ "unresolved-macro-call",
+ format!("unresolved macro `{}{}`", d.path, bang),
+ display_range,
+ )
+ .experimental()
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::check_diagnostics;
+
+ #[test]
+ fn unresolved_macro_diag() {
+ check_diagnostics(
+ r#"
+fn f() {
+ m!();
+} //^ error: unresolved macro `m!`
+
+"#,
+ );
+ }
+
+ #[test]
+ fn test_unresolved_macro_range() {
+ check_diagnostics(
+ r#"
+foo::bar!(92);
+ //^^^ error: unresolved macro `foo::bar!`
+"#,
+ );
+ }
+
+ #[test]
+ fn unresolved_legacy_scope_macro() {
+ check_diagnostics(
+ r#"
+macro_rules! m { () => {} }
+
+m!(); m2!();
+ //^^ error: unresolved macro `m2!`
+"#,
+ );
+ }
+
+ #[test]
+ fn unresolved_module_scope_macro() {
+ check_diagnostics(
+ r#"
+mod mac {
+#[macro_export]
+macro_rules! m { () => {} } }
+
+self::m!(); self::m2!();
+ //^^ error: unresolved macro `self::m2!`
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_module.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_module.rs
new file mode 100644
index 000000000..b8f2a9e94
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_module.rs
@@ -0,0 +1,156 @@
+use hir::db::AstDatabase;
+use ide_db::{assists::Assist, base_db::AnchoredPathBuf, source_change::FileSystemEdit};
+use itertools::Itertools;
+use syntax::AstNode;
+
+use crate::{fix, Diagnostic, DiagnosticsContext};
+
+// Diagnostic: unresolved-module
+//
+// This diagnostic is triggered if rust-analyzer is unable to discover referred module.
+pub(crate) fn unresolved_module(
+ ctx: &DiagnosticsContext<'_>,
+ d: &hir::UnresolvedModule,
+) -> Diagnostic {
+ Diagnostic::new(
+ "unresolved-module",
+ match &*d.candidates {
+ [] => "unresolved module".to_string(),
+ [candidate] => format!("unresolved module, can't find module file: {}", candidate),
+ [candidates @ .., last] => {
+ format!(
+ "unresolved module, can't find module file: {}, or {}",
+ candidates.iter().format(", "),
+ last
+ )
+ }
+ },
+ ctx.sema.diagnostics_display_range(d.decl.clone().map(|it| it.into())).range,
+ )
+ .with_fixes(fixes(ctx, d))
+}
+
+fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedModule) -> Option<Vec<Assist>> {
+ let root = ctx.sema.db.parse_or_expand(d.decl.file_id)?;
+ let unresolved_module = d.decl.value.to_node(&root);
+ Some(
+ d.candidates
+ .iter()
+ .map(|candidate| {
+ fix(
+ "create_module",
+ &format!("Create module at `{candidate}`"),
+ FileSystemEdit::CreateFile {
+ dst: AnchoredPathBuf {
+ anchor: d.decl.file_id.original_file(ctx.sema.db),
+ path: candidate.clone(),
+ },
+ initial_contents: "".to_string(),
+ }
+ .into(),
+ unresolved_module.syntax().text_range(),
+ )
+ })
+ .collect(),
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::expect;
+
+ use crate::tests::{check_diagnostics, check_expect};
+
+ #[test]
+ fn unresolved_module() {
+ check_diagnostics(
+ r#"
+//- /lib.rs
+mod foo;
+ mod bar;
+//^^^^^^^^ 💡 error: unresolved module, can't find module file: bar.rs, or bar/mod.rs
+mod baz {}
+//- /foo.rs
+"#,
+ );
+ }
+
+ #[test]
+ fn test_unresolved_module_diagnostic() {
+ check_expect(
+ r#"mod foo;"#,
+ expect![[r#"
+ [
+ Diagnostic {
+ code: DiagnosticCode(
+ "unresolved-module",
+ ),
+ message: "unresolved module, can't find module file: foo.rs, or foo/mod.rs",
+ range: 0..8,
+ severity: Error,
+ unused: false,
+ experimental: false,
+ fixes: Some(
+ [
+ Assist {
+ id: AssistId(
+ "create_module",
+ QuickFix,
+ ),
+ label: "Create module at `foo.rs`",
+ group: None,
+ target: 0..8,
+ source_change: Some(
+ SourceChange {
+ source_file_edits: {},
+ file_system_edits: [
+ CreateFile {
+ dst: AnchoredPathBuf {
+ anchor: FileId(
+ 0,
+ ),
+ path: "foo.rs",
+ },
+ initial_contents: "",
+ },
+ ],
+ is_snippet: false,
+ },
+ ),
+ trigger_signature_help: false,
+ },
+ Assist {
+ id: AssistId(
+ "create_module",
+ QuickFix,
+ ),
+ label: "Create module at `foo/mod.rs`",
+ group: None,
+ target: 0..8,
+ source_change: Some(
+ SourceChange {
+ source_file_edits: {},
+ file_system_edits: [
+ CreateFile {
+ dst: AnchoredPathBuf {
+ anchor: FileId(
+ 0,
+ ),
+ path: "foo/mod.rs",
+ },
+ initial_contents: "",
+ },
+ ],
+ is_snippet: false,
+ },
+ ),
+ trigger_signature_help: false,
+ },
+ ],
+ ),
+ },
+ ]
+ "#]],
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs
new file mode 100644
index 000000000..760f51f90
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs
@@ -0,0 +1,62 @@
+use hir::db::DefDatabase;
+use syntax::NodeOrToken;
+
+use crate::{Diagnostic, DiagnosticsContext, Severity};
+
+// Diagnostic: unresolved-proc-macro
+//
+// This diagnostic is shown when a procedural macro can not be found. This usually means that
+// procedural macro support is simply disabled (and hence is only a weak hint instead of an error),
+// but can also indicate project setup problems.
+//
+// If you are seeing a lot of "proc macro not expanded" warnings, you can add this option to the
+// `rust-analyzer.diagnostics.disabled` list to prevent them from showing. Alternatively you can
+// enable support for procedural macros (see `rust-analyzer.procMacro.attributes.enable`).
+pub(crate) fn unresolved_proc_macro(
+ ctx: &DiagnosticsContext<'_>,
+ d: &hir::UnresolvedProcMacro,
+ proc_macros_enabled: bool,
+ proc_attr_macros_enabled: bool,
+) -> Diagnostic {
+ // Use more accurate position if available.
+ let display_range = (|| {
+ let precise_location = d.precise_location?;
+ let root = ctx.sema.parse_or_expand(d.node.file_id)?;
+ match root.covering_element(precise_location) {
+ NodeOrToken::Node(it) => Some(ctx.sema.original_range(&it)),
+ NodeOrToken::Token(it) => d.node.with_value(it).original_file_range_opt(ctx.sema.db),
+ }
+ })()
+ .unwrap_or_else(|| ctx.sema.diagnostics_display_range(d.node.clone()))
+ .range;
+
+ let config_enabled = match d.kind {
+ hir::MacroKind::Attr => proc_macros_enabled && proc_attr_macros_enabled,
+ _ => proc_macros_enabled,
+ };
+
+ let message = match &d.macro_name {
+ Some(name) => format!("proc macro `{}` not expanded", name),
+ None => "proc macro not expanded".to_string(),
+ };
+ let severity = if config_enabled { Severity::Error } else { Severity::WeakWarning };
+ let def_map = ctx.sema.db.crate_def_map(d.krate);
+ let message = format!(
+ "{message}: {}",
+ if config_enabled {
+ match def_map.proc_macro_loading_error() {
+ Some(e) => e,
+ None => "proc macro not found in the built dylib",
+ }
+ } else {
+ match d.kind {
+ hir::MacroKind::Attr if proc_macros_enabled => {
+ "attribute macro expansion is disabled"
+ }
+ _ => "proc-macro expansion is disabled",
+ }
+ },
+ );
+
+ Diagnostic::new("unresolved-proc-macro", message, display_range).severity(severity)
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/useless_braces.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/useless_braces.rs
new file mode 100644
index 000000000..8b9330e04
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/useless_braces.rs
@@ -0,0 +1,148 @@
+use ide_db::{base_db::FileId, source_change::SourceChange};
+use itertools::Itertools;
+use syntax::{ast, AstNode, SyntaxNode, TextRange};
+use text_edit::TextEdit;
+
+use crate::{fix, Diagnostic, Severity};
+
+// Diagnostic: unnecessary-braces
+//
+// Diagnostic for unnecessary braces in `use` items.
+pub(crate) fn useless_braces(
+ acc: &mut Vec<Diagnostic>,
+ file_id: FileId,
+ node: &SyntaxNode,
+) -> Option<()> {
+ let use_tree_list = ast::UseTreeList::cast(node.clone())?;
+ if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() {
+ // If there is a comment inside the bracketed `use`,
+ // assume it is a commented out module path and don't show diagnostic.
+ if use_tree_list.has_inner_comment() {
+ return Some(());
+ }
+
+ let use_range = use_tree_list.syntax().text_range();
+ let edit = remove_braces(&single_use_tree).unwrap_or_else(|| {
+ let to_replace = single_use_tree.syntax().text().to_string();
+ let mut edit_builder = TextEdit::builder();
+ edit_builder.delete(use_range);
+ edit_builder.insert(use_range.start(), to_replace);
+ edit_builder.finish()
+ });
+
+ acc.push(
+ Diagnostic::new(
+ "unnecessary-braces",
+ "Unnecessary braces in use statement".to_string(),
+ use_range,
+ )
+ .severity(Severity::WeakWarning)
+ .with_fixes(Some(vec![fix(
+ "remove_braces",
+ "Remove unnecessary braces",
+ SourceChange::from_text_edit(file_id, edit),
+ use_range,
+ )])),
+ );
+ }
+
+ Some(())
+}
+
+fn remove_braces(single_use_tree: &ast::UseTree) -> Option<TextEdit> {
+ let use_tree_list_node = single_use_tree.syntax().parent()?;
+ if single_use_tree.path()?.segment()?.self_token().is_some() {
+ let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start();
+ let end = use_tree_list_node.text_range().end();
+ return Some(TextEdit::delete(TextRange::new(start, end)));
+ }
+ None
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_diagnostics, check_fix};
+
+ #[test]
+ fn test_check_unnecessary_braces_in_use_statement() {
+ check_diagnostics(
+ r#"
+use a;
+use a::{c, d::e};
+
+mod a {
+ mod c {}
+ mod d {
+ mod e {}
+ }
+}
+"#,
+ );
+ check_diagnostics(
+ r#"
+use a;
+use a::{
+ c,
+ // d::e
+};
+
+mod a {
+ mod c {}
+ mod d {
+ mod e {}
+ }
+}
+"#,
+ );
+ check_fix(
+ r#"
+mod b {}
+use {$0b};
+"#,
+ r#"
+mod b {}
+use b;
+"#,
+ );
+ check_fix(
+ r#"
+mod b {}
+use {b$0};
+"#,
+ r#"
+mod b {}
+use b;
+"#,
+ );
+ check_fix(
+ r#"
+mod a { mod c {} }
+use a::{c$0};
+"#,
+ r#"
+mod a { mod c {} }
+use a::c;
+"#,
+ );
+ check_fix(
+ r#"
+mod a {}
+use a::{self$0};
+"#,
+ r#"
+mod a {}
+use a;
+"#,
+ );
+ check_fix(
+ r#"
+mod a { mod c {} mod d { mod e {} } }
+use a::{c, d::{e$0}};
+"#,
+ r#"
+mod a { mod c {} mod d { mod e {} } }
+use a::{c, d::e};
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs
new file mode 100644
index 000000000..41abaa836
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs
@@ -0,0 +1,260 @@
+//! Diagnostics rendering and fixits.
+//!
+//! Most of the diagnostics originate from the dark depth of the compiler, and
+//! are originally expressed in term of IR. When we emit the diagnostic, we are
+//! usually not in the position to decide how to best "render" it in terms of
+//! user-authored source code. We are especially not in the position to offer
+//! fixits, as the compiler completely lacks the infrastructure to edit the
+//! source code.
+//!
+//! Instead, we "bubble up" raw, structured diagnostics until the `hir` crate,
+//! where we "cook" them so that each diagnostic is formulated in terms of `hir`
+//! types. Well, at least that's the aspiration, the "cooking" is somewhat
+//! ad-hoc at the moment. Anyways, we get a bunch of ide-friendly diagnostic
+//! structs from hir, and we want to render them to unified serializable
+//! representation (span, level, message) here. If we can, we also provide
+//! fixits. By the way, that's why we want to keep diagnostics structured
+//! internally -- so that we have all the info to make fixes.
+//!
+//! We have one "handler" module per diagnostic code. Such a module contains
+//! rendering, optional fixes and tests. It's OK if some low-level compiler
+//! functionality ends up being tested via a diagnostic.
+//!
+//! There are also a couple of ad-hoc diagnostics implemented directly here, we
+//! don't yet have a great pattern for how to do them properly.
+
+#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
+
+mod handlers {
+ pub(crate) mod break_outside_of_loop;
+ pub(crate) mod inactive_code;
+ pub(crate) mod incorrect_case;
+ pub(crate) mod invalid_derive_target;
+ pub(crate) mod macro_error;
+ pub(crate) mod malformed_derive;
+ pub(crate) mod mismatched_arg_count;
+ pub(crate) mod missing_fields;
+ pub(crate) mod missing_match_arms;
+ pub(crate) mod missing_unsafe;
+ pub(crate) mod no_such_field;
+ pub(crate) mod replace_filter_map_next_with_find_map;
+ pub(crate) mod type_mismatch;
+ pub(crate) mod unimplemented_builtin_macro;
+ pub(crate) mod unresolved_extern_crate;
+ pub(crate) mod unresolved_import;
+ pub(crate) mod unresolved_macro_call;
+ pub(crate) mod unresolved_module;
+ pub(crate) mod unresolved_proc_macro;
+
+ // The handlers below are unusual, the implement the diagnostics as well.
+ pub(crate) mod field_shorthand;
+ pub(crate) mod useless_braces;
+ pub(crate) mod unlinked_file;
+}
+
+#[cfg(test)]
+mod tests;
+
+use hir::{diagnostics::AnyDiagnostic, InFile, Semantics};
+use ide_db::{
+ assists::{Assist, AssistId, AssistKind, AssistResolveStrategy},
+ base_db::{FileId, FileRange, SourceDatabase},
+ label::Label,
+ source_change::SourceChange,
+ FxHashSet, RootDatabase,
+};
+use syntax::{algo::find_node_at_range, ast::AstNode, SyntaxNodePtr, TextRange};
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub struct DiagnosticCode(pub &'static str);
+
+impl DiagnosticCode {
+ pub fn as_str(&self) -> &str {
+ self.0
+ }
+}
+
+#[derive(Debug)]
+pub struct Diagnostic {
+ pub code: DiagnosticCode,
+ pub message: String,
+ pub range: TextRange,
+ pub severity: Severity,
+ pub unused: bool,
+ pub experimental: bool,
+ pub fixes: Option<Vec<Assist>>,
+}
+
+impl Diagnostic {
+ fn new(code: &'static str, message: impl Into<String>, range: TextRange) -> Diagnostic {
+ let message = message.into();
+ Diagnostic {
+ code: DiagnosticCode(code),
+ message,
+ range,
+ severity: Severity::Error,
+ unused: false,
+ experimental: false,
+ fixes: None,
+ }
+ }
+
+ fn experimental(mut self) -> Diagnostic {
+ self.experimental = true;
+ self
+ }
+
+ fn severity(mut self, severity: Severity) -> Diagnostic {
+ self.severity = severity;
+ self
+ }
+
+ fn with_fixes(mut self, fixes: Option<Vec<Assist>>) -> Diagnostic {
+ self.fixes = fixes;
+ self
+ }
+
+ fn with_unused(mut self, unused: bool) -> Diagnostic {
+ self.unused = unused;
+ self
+ }
+}
+
+#[derive(Debug, Copy, Clone)]
+pub enum Severity {
+ Error,
+ // We don't actually emit this one yet, but we should at some point.
+ // Warning,
+ WeakWarning,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum ExprFillDefaultMode {
+ Todo,
+ Default,
+}
+impl Default for ExprFillDefaultMode {
+ fn default() -> Self {
+ Self::Todo
+ }
+}
+
+#[derive(Default, Debug, Clone)]
+pub struct DiagnosticsConfig {
+ pub proc_macros_enabled: bool,
+ pub proc_attr_macros_enabled: bool,
+ pub disable_experimental: bool,
+ pub disabled: FxHashSet<String>,
+ pub expr_fill_default: ExprFillDefaultMode,
+}
+
+struct DiagnosticsContext<'a> {
+ config: &'a DiagnosticsConfig,
+ sema: Semantics<'a, RootDatabase>,
+ resolve: &'a AssistResolveStrategy,
+}
+
+pub fn diagnostics(
+ db: &RootDatabase,
+ config: &DiagnosticsConfig,
+ resolve: &AssistResolveStrategy,
+ file_id: FileId,
+) -> Vec<Diagnostic> {
+ let _p = profile::span("diagnostics");
+ let sema = Semantics::new(db);
+ let parse = db.parse(file_id);
+ let mut res = Vec::new();
+
+ // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily.
+ res.extend(
+ parse.errors().iter().take(128).map(|err| {
+ Diagnostic::new("syntax-error", format!("Syntax Error: {}", err), err.range())
+ }),
+ );
+
+ for node in parse.tree().syntax().descendants() {
+ handlers::useless_braces::useless_braces(&mut res, file_id, &node);
+ handlers::field_shorthand::field_shorthand(&mut res, file_id, &node);
+ }
+
+ let module = sema.to_module_def(file_id);
+
+ let ctx = DiagnosticsContext { config, sema, resolve };
+ if module.is_none() {
+ handlers::unlinked_file::unlinked_file(&ctx, &mut res, file_id);
+ }
+
+ let mut diags = Vec::new();
+ if let Some(m) = module {
+ m.diagnostics(db, &mut diags)
+ }
+
+ for diag in diags {
+ #[rustfmt::skip]
+ let d = match diag {
+ AnyDiagnostic::BreakOutsideOfLoop(d) => handlers::break_outside_of_loop::break_outside_of_loop(&ctx, &d),
+ AnyDiagnostic::IncorrectCase(d) => handlers::incorrect_case::incorrect_case(&ctx, &d),
+ AnyDiagnostic::MacroError(d) => handlers::macro_error::macro_error(&ctx, &d),
+ AnyDiagnostic::MalformedDerive(d) => handlers::malformed_derive::malformed_derive(&ctx, &d),
+ AnyDiagnostic::MismatchedArgCount(d) => handlers::mismatched_arg_count::mismatched_arg_count(&ctx, &d),
+ AnyDiagnostic::MissingFields(d) => handlers::missing_fields::missing_fields(&ctx, &d),
+ AnyDiagnostic::MissingMatchArms(d) => handlers::missing_match_arms::missing_match_arms(&ctx, &d),
+ AnyDiagnostic::MissingUnsafe(d) => handlers::missing_unsafe::missing_unsafe(&ctx, &d),
+ AnyDiagnostic::NoSuchField(d) => handlers::no_such_field::no_such_field(&ctx, &d),
+ AnyDiagnostic::ReplaceFilterMapNextWithFindMap(d) => handlers::replace_filter_map_next_with_find_map::replace_filter_map_next_with_find_map(&ctx, &d),
+ AnyDiagnostic::TypeMismatch(d) => handlers::type_mismatch::type_mismatch(&ctx, &d),
+ AnyDiagnostic::UnimplementedBuiltinMacro(d) => handlers::unimplemented_builtin_macro::unimplemented_builtin_macro(&ctx, &d),
+ AnyDiagnostic::UnresolvedExternCrate(d) => handlers::unresolved_extern_crate::unresolved_extern_crate(&ctx, &d),
+ AnyDiagnostic::UnresolvedImport(d) => handlers::unresolved_import::unresolved_import(&ctx, &d),
+ AnyDiagnostic::UnresolvedMacroCall(d) => handlers::unresolved_macro_call::unresolved_macro_call(&ctx, &d),
+ AnyDiagnostic::UnresolvedModule(d) => handlers::unresolved_module::unresolved_module(&ctx, &d),
+ AnyDiagnostic::UnresolvedProcMacro(d) => handlers::unresolved_proc_macro::unresolved_proc_macro(&ctx, &d, config.proc_macros_enabled, config.proc_attr_macros_enabled),
+ AnyDiagnostic::InvalidDeriveTarget(d) => handlers::invalid_derive_target::invalid_derive_target(&ctx, &d),
+
+ AnyDiagnostic::InactiveCode(d) => match handlers::inactive_code::inactive_code(&ctx, &d) {
+ Some(it) => it,
+ None => continue,
+ }
+ };
+ res.push(d)
+ }
+
+ res.retain(|d| {
+ !ctx.config.disabled.contains(d.code.as_str())
+ && !(ctx.config.disable_experimental && d.experimental)
+ });
+
+ res
+}
+
+fn fix(id: &'static str, label: &str, source_change: SourceChange, target: TextRange) -> Assist {
+ let mut res = unresolved_fix(id, label, target);
+ res.source_change = Some(source_change);
+ res
+}
+
+fn unresolved_fix(id: &'static str, label: &str, target: TextRange) -> Assist {
+ assert!(!id.contains(' '));
+ Assist {
+ id: AssistId(id, AssistKind::QuickFix),
+ label: Label::new(label.to_string()),
+ group: None,
+ target,
+ source_change: None,
+ trigger_signature_help: false,
+ }
+}
+
+fn adjusted_display_range<N: AstNode>(
+ ctx: &DiagnosticsContext<'_>,
+ diag_ptr: InFile<SyntaxNodePtr>,
+ adj: &dyn Fn(N) -> Option<TextRange>,
+) -> TextRange {
+ let FileRange { file_id, range } = ctx.sema.diagnostics_display_range(diag_ptr);
+
+ let source_file = ctx.sema.db.parse(file_id);
+ find_node_at_range::<N>(&source_file.syntax_node(), range)
+ .filter(|it| it.syntax().text_range() == range)
+ .and_then(adj)
+ .unwrap_or(range)
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests.rs
new file mode 100644
index 000000000..7312bca32
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests.rs
@@ -0,0 +1,145 @@
+#[cfg(not(feature = "in-rust-tree"))]
+mod sourcegen;
+
+use expect_test::Expect;
+use ide_db::{
+ assists::AssistResolveStrategy,
+ base_db::{fixture::WithFixture, SourceDatabaseExt},
+ RootDatabase,
+};
+use stdx::trim_indent;
+use test_utils::{assert_eq_text, extract_annotations};
+
+use crate::{DiagnosticsConfig, ExprFillDefaultMode, Severity};
+
+/// Takes a multi-file input fixture with annotated cursor positions,
+/// and checks that:
+/// * a diagnostic is produced
+/// * the first diagnostic fix trigger range touches the input cursor position
+/// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied
+#[track_caller]
+pub(crate) fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) {
+ check_nth_fix(0, ra_fixture_before, ra_fixture_after);
+}
+/// Takes a multi-file input fixture with annotated cursor positions,
+/// and checks that:
+/// * a diagnostic is produced
+/// * every diagnostic fixes trigger range touches the input cursor position
+/// * that the contents of the file containing the cursor match `after` after each diagnostic fix is applied
+pub(crate) fn check_fixes(ra_fixture_before: &str, ra_fixtures_after: Vec<&str>) {
+ for (i, ra_fixture_after) in ra_fixtures_after.iter().enumerate() {
+ check_nth_fix(i, ra_fixture_before, ra_fixture_after)
+ }
+}
+
+#[track_caller]
+fn check_nth_fix(nth: usize, ra_fixture_before: &str, ra_fixture_after: &str) {
+ let after = trim_indent(ra_fixture_after);
+
+ let (db, file_position) = RootDatabase::with_position(ra_fixture_before);
+ let mut conf = DiagnosticsConfig::default();
+ conf.expr_fill_default = ExprFillDefaultMode::Default;
+ let diagnostic =
+ super::diagnostics(&db, &conf, &AssistResolveStrategy::All, file_position.file_id)
+ .pop()
+ .expect("no diagnostics");
+ let fix = &diagnostic.fixes.expect("diagnostic misses fixes")[nth];
+ let actual = {
+ let source_change = fix.source_change.as_ref().unwrap();
+ let file_id = *source_change.source_file_edits.keys().next().unwrap();
+ let mut actual = db.file_text(file_id).to_string();
+
+ for edit in source_change.source_file_edits.values() {
+ edit.apply(&mut actual);
+ }
+ actual
+ };
+
+ assert!(
+ fix.target.contains_inclusive(file_position.offset),
+ "diagnostic fix range {:?} does not touch cursor position {:?}",
+ fix.target,
+ file_position.offset
+ );
+ assert_eq_text!(&after, &actual);
+}
+
+/// Checks that there's a diagnostic *without* fix at `$0`.
+pub(crate) fn check_no_fix(ra_fixture: &str) {
+ let (db, file_position) = RootDatabase::with_position(ra_fixture);
+ let diagnostic = super::diagnostics(
+ &db,
+ &DiagnosticsConfig::default(),
+ &AssistResolveStrategy::All,
+ file_position.file_id,
+ )
+ .pop()
+ .unwrap();
+ assert!(diagnostic.fixes.is_none(), "got a fix when none was expected: {:?}", diagnostic);
+}
+
+pub(crate) fn check_expect(ra_fixture: &str, expect: Expect) {
+ let (db, file_id) = RootDatabase::with_single_file(ra_fixture);
+ let diagnostics = super::diagnostics(
+ &db,
+ &DiagnosticsConfig::default(),
+ &AssistResolveStrategy::All,
+ file_id,
+ );
+ expect.assert_debug_eq(&diagnostics)
+}
+
+#[track_caller]
+pub(crate) fn check_diagnostics(ra_fixture: &str) {
+ let mut config = DiagnosticsConfig::default();
+ config.disabled.insert("inactive-code".to_string());
+ check_diagnostics_with_config(config, ra_fixture)
+}
+
+#[track_caller]
+pub(crate) fn check_diagnostics_with_config(config: DiagnosticsConfig, ra_fixture: &str) {
+ let (db, files) = RootDatabase::with_many_files(ra_fixture);
+ for file_id in files {
+ let diagnostics = super::diagnostics(&db, &config, &AssistResolveStrategy::All, file_id);
+
+ let expected = extract_annotations(&*db.file_text(file_id));
+ let mut actual = diagnostics
+ .into_iter()
+ .map(|d| {
+ let mut annotation = String::new();
+ if let Some(fixes) = &d.fixes {
+ assert!(!fixes.is_empty());
+ annotation.push_str("💡 ")
+ }
+ annotation.push_str(match d.severity {
+ Severity::Error => "error",
+ Severity::WeakWarning => "weak",
+ });
+ annotation.push_str(": ");
+ annotation.push_str(&d.message);
+ (d.range, annotation)
+ })
+ .collect::<Vec<_>>();
+ actual.sort_by_key(|(range, _)| range.start());
+ assert_eq!(expected, actual);
+ }
+}
+
+#[test]
+fn test_disabled_diagnostics() {
+ let mut config = DiagnosticsConfig::default();
+ config.disabled.insert("unresolved-module".into());
+
+ let (db, file_id) = RootDatabase::with_single_file(r#"mod foo;"#);
+
+ let diagnostics = super::diagnostics(&db, &config, &AssistResolveStrategy::All, file_id);
+ assert!(diagnostics.is_empty());
+
+ let diagnostics = super::diagnostics(
+ &db,
+ &DiagnosticsConfig::default(),
+ &AssistResolveStrategy::All,
+ file_id,
+ );
+ assert!(!diagnostics.is_empty());
+}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests/sourcegen.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests/sourcegen.rs
new file mode 100644
index 000000000..ec6558a46
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests/sourcegen.rs
@@ -0,0 +1,73 @@
+//! Generates `assists.md` documentation.
+
+use std::{fmt, fs, io, path::PathBuf};
+
+use sourcegen::project_root;
+
+#[test]
+fn sourcegen_diagnostic_docs() {
+ let diagnostics = Diagnostic::collect().unwrap();
+ let contents =
+ diagnostics.into_iter().map(|it| it.to_string()).collect::<Vec<_>>().join("\n\n");
+ let contents = sourcegen::add_preamble("sourcegen_diagnostic_docs", contents);
+ let dst = project_root().join("docs/user/generated_diagnostic.adoc");
+ fs::write(&dst, &contents).unwrap();
+}
+
+#[derive(Debug)]
+struct Diagnostic {
+ id: String,
+ location: sourcegen::Location,
+ doc: String,
+}
+
+impl Diagnostic {
+ fn collect() -> io::Result<Vec<Diagnostic>> {
+ let handlers_dir = project_root().join("crates/ide-diagnostics/src/handlers");
+
+ let mut res = Vec::new();
+ for path in sourcegen::list_rust_files(&handlers_dir) {
+ collect_file(&mut res, path)?;
+ }
+ res.sort_by(|lhs, rhs| lhs.id.cmp(&rhs.id));
+ return Ok(res);
+
+ fn collect_file(acc: &mut Vec<Diagnostic>, path: PathBuf) -> io::Result<()> {
+ let text = fs::read_to_string(&path)?;
+ let comment_blocks = sourcegen::CommentBlock::extract("Diagnostic", &text);
+
+ for block in comment_blocks {
+ let id = block.id;
+ if let Err(msg) = is_valid_diagnostic_name(&id) {
+ panic!("invalid diagnostic name: {:?}:\n {}", id, msg)
+ }
+ let doc = block.contents.join("\n");
+ let location = sourcegen::Location { file: path.clone(), line: block.line };
+ acc.push(Diagnostic { id, location, doc })
+ }
+
+ Ok(())
+ }
+ }
+}
+
+fn is_valid_diagnostic_name(diagnostic: &str) -> Result<(), String> {
+ let diagnostic = diagnostic.trim();
+ if diagnostic.find(char::is_whitespace).is_some() {
+ return Err("Diagnostic names can't contain whitespace symbols".into());
+ }
+ if diagnostic.chars().any(|c| c.is_ascii_uppercase()) {
+ return Err("Diagnostic names can't contain uppercase symbols".into());
+ }
+ if diagnostic.chars().any(|c| !c.is_ascii()) {
+ return Err("Diagnostic can't contain non-ASCII symbols".into());
+ }
+
+ Ok(())
+}
+
+impl fmt::Display for Diagnostic {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ writeln!(f, "=== {}\n**Source:** {}\n{}", self.id, self.location, self.doc)
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-ssr/Cargo.toml b/src/tools/rust-analyzer/crates/ide-ssr/Cargo.toml
new file mode 100644
index 000000000..d36dd02d4
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-ssr/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+name = "ide-ssr"
+version = "0.0.0"
+description = "Structural search and replace of Rust code"
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/rust-lang/rust-analyzer"
+edition = "2021"
+rust-version = "1.57"
+
+[lib]
+doctest = false
+
+[dependencies]
+cov-mark = "2.0.0-pre.1"
+
+itertools = "0.10.3"
+
+text-edit = { path = "../text-edit", version = "0.0.0" }
+parser = { path = "../parser", version = "0.0.0" }
+syntax = { path = "../syntax", version = "0.0.0" }
+ide-db = { path = "../ide-db", version = "0.0.0" }
+hir = { path = "../hir", version = "0.0.0" }
+
+[dev-dependencies]
+test-utils = { path = "../test-utils" }
+expect-test = "1.4.0"
diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/errors.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/errors.rs
new file mode 100644
index 000000000..c02bacae6
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-ssr/src/errors.rs
@@ -0,0 +1,29 @@
+//! Code relating to errors produced by SSR.
+
+/// Constructs an SsrError taking arguments like the format macro.
+macro_rules! _error {
+ ($fmt:expr) => {$crate::SsrError::new(format!($fmt))};
+ ($fmt:expr, $($arg:tt)+) => {$crate::SsrError::new(format!($fmt, $($arg)+))}
+}
+pub(crate) use _error as error;
+
+/// Returns from the current function with an error, supplied by arguments as for format!
+macro_rules! _bail {
+ ($($tokens:tt)*) => {return Err(crate::errors::error!($($tokens)*))}
+}
+pub(crate) use _bail as bail;
+
+#[derive(Debug, PartialEq)]
+pub struct SsrError(pub(crate) String);
+
+impl std::fmt::Display for SsrError {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ write!(f, "Parse error: {}", self.0)
+ }
+}
+
+impl SsrError {
+ pub(crate) fn new(message: impl Into<String>) -> SsrError {
+ SsrError(message.into())
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/fragments.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/fragments.rs
new file mode 100644
index 000000000..503754afe
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-ssr/src/fragments.rs
@@ -0,0 +1,58 @@
+//! When specifying SSR rule, you generally want to map one *kind* of thing to
+//! the same kind of thing: path to path, expression to expression, type to
+//! type.
+//!
+//! The problem is, while this *kind* is generally obvious to the human, the ide
+//! needs to determine it somehow. We do this in a stupid way -- by pasting SSR
+//! rule into different contexts and checking what works.
+
+use syntax::{ast, AstNode, SyntaxNode};
+
+pub(crate) fn ty(s: &str) -> Result<SyntaxNode, ()> {
+ fragment::<ast::Type>("type T = {};", s)
+}
+
+pub(crate) fn item(s: &str) -> Result<SyntaxNode, ()> {
+ fragment::<ast::Item>("{}", s)
+}
+
+pub(crate) fn pat(s: &str) -> Result<SyntaxNode, ()> {
+ fragment::<ast::Pat>("const _: () = {let {} = ();};", s)
+}
+
+pub(crate) fn expr(s: &str) -> Result<SyntaxNode, ()> {
+ fragment::<ast::Expr>("const _: () = {};", s)
+}
+
+pub(crate) fn stmt(s: &str) -> Result<SyntaxNode, ()> {
+ let template = "const _: () = { {}; };";
+ let input = template.replace("{}", s);
+ let parse = syntax::SourceFile::parse(&input);
+ if !parse.errors().is_empty() {
+ return Err(());
+ }
+ let mut node =
+ parse.tree().syntax().descendants().skip(2).find_map(ast::Stmt::cast).ok_or(())?;
+ if !s.ends_with(';') && node.to_string().ends_with(';') {
+ node = node.clone_for_update();
+ node.syntax().last_token().map(|it| it.detach());
+ }
+ if node.to_string() != s {
+ return Err(());
+ }
+ Ok(node.syntax().clone_subtree())
+}
+
+fn fragment<T: AstNode>(template: &str, s: &str) -> Result<SyntaxNode, ()> {
+ let s = s.trim();
+ let input = template.replace("{}", s);
+ let parse = syntax::SourceFile::parse(&input);
+ if !parse.errors().is_empty() {
+ return Err(());
+ }
+ let node = parse.tree().syntax().descendants().find_map(T::cast).ok_or(())?;
+ if node.syntax().text() != s {
+ return Err(());
+ }
+ Ok(node.syntax().clone_subtree())
+}
diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/from_comment.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/from_comment.rs
new file mode 100644
index 000000000..5b6e01625
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-ssr/src/from_comment.rs
@@ -0,0 +1,35 @@
+//! This module allows building an SSR MatchFinder by parsing the SSR rule
+//! from a comment.
+
+use ide_db::{
+ base_db::{FilePosition, FileRange, SourceDatabase},
+ RootDatabase,
+};
+use syntax::{
+ ast::{self, AstNode, AstToken},
+ TextRange,
+};
+
+use crate::MatchFinder;
+
+/// Attempts to build an SSR MatchFinder from a comment at the given file
+/// range. If successful, returns the MatchFinder and a TextRange covering
+/// comment.
+pub fn ssr_from_comment(
+ db: &RootDatabase,
+ frange: FileRange,
+) -> Option<(MatchFinder<'_>, TextRange)> {
+ let comment = {
+ let file = db.parse(frange.file_id);
+ file.tree().syntax().token_at_offset(frange.range.start()).find_map(ast::Comment::cast)
+ }?;
+ let comment_text_without_prefix = comment.text().strip_prefix(comment.prefix()).unwrap();
+ let ssr_rule = comment_text_without_prefix.parse().ok()?;
+
+ let lookup_context = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
+
+ let mut match_finder = MatchFinder::in_context(db, lookup_context, vec![]).ok()?;
+ match_finder.add_rule(ssr_rule).ok()?;
+
+ Some((match_finder, comment.syntax().text_range()))
+}
diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/lib.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/lib.rs
new file mode 100644
index 000000000..a5e24daa9
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-ssr/src/lib.rs
@@ -0,0 +1,358 @@
+//! Structural Search Replace
+//!
+//! Allows searching the AST for code that matches one or more patterns and then replacing that code
+//! based on a template.
+
+#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
+
+// Feature: Structural Search and Replace
+//
+// Search and replace with named wildcards that will match any expression, type, path, pattern or item.
+// The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`.
+// A `$<name>` placeholder in the search pattern will match any AST node and `$<name>` will reference it in the replacement.
+// Within a macro call, a placeholder will match up until whatever token follows the placeholder.
+//
+// All paths in both the search pattern and the replacement template must resolve in the context
+// in which this command is invoked. Paths in the search pattern will then match the code if they
+// resolve to the same item, even if they're written differently. For example if we invoke the
+// command in the module `foo` with a pattern of `Bar`, then code in the parent module that refers
+// to `foo::Bar` will match.
+//
+// Paths in the replacement template will be rendered appropriately for the context in which the
+// replacement occurs. For example if our replacement template is `foo::Bar` and we match some
+// code in the `foo` module, we'll insert just `Bar`.
+//
+// Inherent method calls should generally be written in UFCS form. e.g. `foo::Bar::baz($s, $a)` will
+// match `$s.baz($a)`, provided the method call `baz` resolves to the method `foo::Bar::baz`. When a
+// placeholder is the receiver of a method call in the search pattern (e.g. `$s.foo()`), but not in
+// the replacement template (e.g. `bar($s)`), then *, & and &mut will be added as needed to mirror
+// whatever autoderef and autoref was happening implicitly in the matched code.
+//
+// The scope of the search / replace will be restricted to the current selection if any, otherwise
+// it will apply to the whole workspace.
+//
+// Placeholders may be given constraints by writing them as `${<name>:<constraint1>:<constraint2>...}`.
+//
+// Supported constraints:
+//
+// |===
+// | Constraint | Restricts placeholder
+//
+// | kind(literal) | Is a literal (e.g. `42` or `"forty two"`)
+// | not(a) | Negates the constraint `a`
+// |===
+//
+// Available via the command `rust-analyzer.ssr`.
+//
+// ```rust
+// // Using structural search replace command [foo($a, $b) ==>> ($a).foo($b)]
+//
+// // BEFORE
+// String::from(foo(y + 5, z))
+//
+// // AFTER
+// String::from((y + 5).foo(z))
+// ```
+//
+// |===
+// | Editor | Action Name
+//
+// | VS Code | **Rust Analyzer: Structural Search Replace**
+// |===
+//
+// Also available as an assist, by writing a comment containing the structural
+// search and replace rule. You will only see the assist if the comment can
+// be parsed as a valid structural search and replace rule.
+//
+// ```rust
+// // Place the cursor on the line below to see the assist 💡.
+// // foo($a, $b) ==>> ($a).foo($b)
+// ```
+
+mod from_comment;
+mod matching;
+mod nester;
+mod parsing;
+mod fragments;
+mod replacing;
+mod resolving;
+mod search;
+#[macro_use]
+mod errors;
+#[cfg(test)]
+mod tests;
+
+pub use crate::{errors::SsrError, from_comment::ssr_from_comment, matching::Match};
+
+use crate::{errors::bail, matching::MatchFailureReason};
+use hir::Semantics;
+use ide_db::{
+ base_db::{FileId, FilePosition, FileRange},
+ FxHashMap,
+};
+use resolving::ResolvedRule;
+use syntax::{ast, AstNode, SyntaxNode, TextRange};
+use text_edit::TextEdit;
+
+// A structured search replace rule. Create by calling `parse` on a str.
+#[derive(Debug)]
+pub struct SsrRule {
+ /// A structured pattern that we're searching for.
+ pattern: parsing::RawPattern,
+ /// What we'll replace it with.
+ template: parsing::RawPattern,
+ parsed_rules: Vec<parsing::ParsedRule>,
+}
+
+#[derive(Debug)]
+pub struct SsrPattern {
+ parsed_rules: Vec<parsing::ParsedRule>,
+}
+
+#[derive(Debug, Default)]
+pub struct SsrMatches {
+ pub matches: Vec<Match>,
+}
+
+/// Searches a crate for pattern matches and possibly replaces them with something else.
+pub struct MatchFinder<'db> {
+ /// Our source of information about the user's code.
+ sema: Semantics<'db, ide_db::RootDatabase>,
+ rules: Vec<ResolvedRule>,
+ resolution_scope: resolving::ResolutionScope<'db>,
+ restrict_ranges: Vec<FileRange>,
+}
+
+impl<'db> MatchFinder<'db> {
+ /// Constructs a new instance where names will be looked up as if they appeared at
+ /// `lookup_context`.
+ pub fn in_context(
+ db: &'db ide_db::RootDatabase,
+ lookup_context: FilePosition,
+ mut restrict_ranges: Vec<FileRange>,
+ ) -> Result<MatchFinder<'db>, SsrError> {
+ restrict_ranges.retain(|range| !range.range.is_empty());
+ let sema = Semantics::new(db);
+ let resolution_scope = resolving::ResolutionScope::new(&sema, lookup_context)
+ .ok_or_else(|| SsrError("no resolution scope for file".into()))?;
+ Ok(MatchFinder { sema, rules: Vec::new(), resolution_scope, restrict_ranges })
+ }
+
+ /// Constructs an instance using the start of the first file in `db` as the lookup context.
+ pub fn at_first_file(db: &'db ide_db::RootDatabase) -> Result<MatchFinder<'db>, SsrError> {
+ use ide_db::base_db::SourceDatabaseExt;
+ use ide_db::symbol_index::SymbolsDatabase;
+ if let Some(first_file_id) =
+ db.local_roots().iter().next().and_then(|root| db.source_root(*root).iter().next())
+ {
+ MatchFinder::in_context(
+ db,
+ FilePosition { file_id: first_file_id, offset: 0.into() },
+ vec![],
+ )
+ } else {
+ bail!("No files to search");
+ }
+ }
+
+ /// Adds a rule to be applied. The order in which rules are added matters. Earlier rules take
+ /// precedence. If a node is matched by an earlier rule, then later rules won't be permitted to
+ /// match to it.
+ pub fn add_rule(&mut self, rule: SsrRule) -> Result<(), SsrError> {
+ for parsed_rule in rule.parsed_rules {
+ self.rules.push(ResolvedRule::new(
+ parsed_rule,
+ &self.resolution_scope,
+ self.rules.len(),
+ )?);
+ }
+ Ok(())
+ }
+
+ /// Finds matches for all added rules and returns edits for all found matches.
+ pub fn edits(&self) -> FxHashMap<FileId, TextEdit> {
+ use ide_db::base_db::SourceDatabaseExt;
+ let mut matches_by_file = FxHashMap::default();
+ for m in self.matches().matches {
+ matches_by_file
+ .entry(m.range.file_id)
+ .or_insert_with(SsrMatches::default)
+ .matches
+ .push(m);
+ }
+ matches_by_file
+ .into_iter()
+ .map(|(file_id, matches)| {
+ (
+ file_id,
+ replacing::matches_to_edit(
+ &matches,
+ &self.sema.db.file_text(file_id),
+ &self.rules,
+ ),
+ )
+ })
+ .collect()
+ }
+
+ /// Adds a search pattern. For use if you intend to only call `find_matches_in_file`. If you
+ /// intend to do replacement, use `add_rule` instead.
+ pub fn add_search_pattern(&mut self, pattern: SsrPattern) -> Result<(), SsrError> {
+ for parsed_rule in pattern.parsed_rules {
+ self.rules.push(ResolvedRule::new(
+ parsed_rule,
+ &self.resolution_scope,
+ self.rules.len(),
+ )?);
+ }
+ Ok(())
+ }
+
+ /// Returns matches for all added rules.
+ pub fn matches(&self) -> SsrMatches {
+ let mut matches = Vec::new();
+ let mut usage_cache = search::UsageCache::default();
+ for rule in &self.rules {
+ self.find_matches_for_rule(rule, &mut usage_cache, &mut matches);
+ }
+ nester::nest_and_remove_collisions(matches, &self.sema)
+ }
+
+ /// Finds all nodes in `file_id` whose text is exactly equal to `snippet` and attempts to match
+ /// them, while recording reasons why they don't match. This API is useful for command
+ /// line-based debugging where providing a range is difficult.
+ pub fn debug_where_text_equal(&self, file_id: FileId, snippet: &str) -> Vec<MatchDebugInfo> {
+ use ide_db::base_db::SourceDatabaseExt;
+ let file = self.sema.parse(file_id);
+ let mut res = Vec::new();
+ let file_text = self.sema.db.file_text(file_id);
+ let mut remaining_text = file_text.as_str();
+ let mut base = 0;
+ let len = snippet.len() as u32;
+ while let Some(offset) = remaining_text.find(snippet) {
+ let start = base + offset as u32;
+ let end = start + len;
+ self.output_debug_for_nodes_at_range(
+ file.syntax(),
+ FileRange { file_id, range: TextRange::new(start.into(), end.into()) },
+ &None,
+ &mut res,
+ );
+ remaining_text = &remaining_text[offset + snippet.len()..];
+ base = end;
+ }
+ res
+ }
+
+ fn output_debug_for_nodes_at_range(
+ &self,
+ node: &SyntaxNode,
+ range: FileRange,
+ restrict_range: &Option<FileRange>,
+ out: &mut Vec<MatchDebugInfo>,
+ ) {
+ for node in node.children() {
+ let node_range = self.sema.original_range(&node);
+ if node_range.file_id != range.file_id || !node_range.range.contains_range(range.range)
+ {
+ continue;
+ }
+ if node_range.range == range.range {
+ for rule in &self.rules {
+ // For now we ignore rules that have a different kind than our node, otherwise
+ // we get lots of noise. If at some point we add support for restricting rules
+ // to a particular kind of thing (e.g. only match type references), then we can
+ // relax this. We special-case expressions, since function calls can match
+ // method calls.
+ if rule.pattern.node.kind() != node.kind()
+ && !(ast::Expr::can_cast(rule.pattern.node.kind())
+ && ast::Expr::can_cast(node.kind()))
+ {
+ continue;
+ }
+ out.push(MatchDebugInfo {
+ matched: matching::get_match(true, rule, &node, restrict_range, &self.sema)
+ .map_err(|e| MatchFailureReason {
+ reason: e.reason.unwrap_or_else(|| {
+ "Match failed, but no reason was given".to_owned()
+ }),
+ }),
+ pattern: rule.pattern.node.clone(),
+ node: node.clone(),
+ });
+ }
+ } else if let Some(macro_call) = ast::MacroCall::cast(node.clone()) {
+ if let Some(expanded) = self.sema.expand(&macro_call) {
+ if let Some(tt) = macro_call.token_tree() {
+ self.output_debug_for_nodes_at_range(
+ &expanded,
+ range,
+ &Some(self.sema.original_range(tt.syntax())),
+ out,
+ );
+ }
+ }
+ }
+ self.output_debug_for_nodes_at_range(&node, range, restrict_range, out);
+ }
+ }
+}
+
+pub struct MatchDebugInfo {
+ node: SyntaxNode,
+ /// Our search pattern parsed as an expression or item, etc
+ pattern: SyntaxNode,
+ matched: Result<Match, MatchFailureReason>,
+}
+
+impl std::fmt::Debug for MatchDebugInfo {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match &self.matched {
+ Ok(_) => writeln!(f, "Node matched")?,
+ Err(reason) => writeln!(f, "Node failed to match because: {}", reason.reason)?,
+ }
+ writeln!(
+ f,
+ "============ AST ===========\n\
+ {:#?}",
+ self.node
+ )?;
+ writeln!(f, "========= PATTERN ==========")?;
+ writeln!(f, "{:#?}", self.pattern)?;
+ writeln!(f, "============================")?;
+ Ok(())
+ }
+}
+
+impl SsrMatches {
+ /// Returns `self` with any nested matches removed and made into top-level matches.
+ pub fn flattened(self) -> SsrMatches {
+ let mut out = SsrMatches::default();
+ self.flatten_into(&mut out);
+ out
+ }
+
+ fn flatten_into(self, out: &mut SsrMatches) {
+ for mut m in self.matches {
+ for p in m.placeholder_values.values_mut() {
+ std::mem::take(&mut p.inner_matches).flatten_into(out);
+ }
+ out.matches.push(m);
+ }
+ }
+}
+
+impl Match {
+ pub fn matched_text(&self) -> String {
+ self.matched_node.text().to_string()
+ }
+}
+
+impl std::error::Error for SsrError {}
+
+#[cfg(test)]
+impl MatchDebugInfo {
+ pub(crate) fn match_failure_reason(&self) -> Option<&str> {
+ self.matched.as_ref().err().map(|r| r.reason.as_str())
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/matching.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/matching.rs
new file mode 100644
index 000000000..e3a837ddc
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-ssr/src/matching.rs
@@ -0,0 +1,803 @@
+//! This module is responsible for matching a search pattern against a node in the AST. In the
+//! process of matching, placeholder values are recorded.
+
+use crate::{
+ parsing::{Constraint, NodeKind, Placeholder, Var},
+ resolving::{ResolvedPattern, ResolvedRule, UfcsCallInfo},
+ SsrMatches,
+};
+use hir::Semantics;
+use ide_db::{base_db::FileRange, FxHashMap};
+use std::{cell::Cell, iter::Peekable};
+use syntax::{
+ ast::{self, AstNode, AstToken},
+ SmolStr, SyntaxElement, SyntaxElementChildren, SyntaxKind, SyntaxNode, SyntaxToken,
+};
+
+// Creates a match error. If we're currently attempting to match some code that we thought we were
+// going to match, as indicated by the --debug-snippet flag, then populate the reason field.
+macro_rules! match_error {
+ ($e:expr) => {{
+ MatchFailed {
+ reason: if recording_match_fail_reasons() {
+ Some(format!("{}", $e))
+ } else {
+ None
+ }
+ }
+ }};
+ ($fmt:expr, $($arg:tt)+) => {{
+ MatchFailed {
+ reason: if recording_match_fail_reasons() {
+ Some(format!($fmt, $($arg)+))
+ } else {
+ None
+ }
+ }
+ }};
+}
+
+// Fails the current match attempt, recording the supplied reason if we're recording match fail reasons.
+macro_rules! fail_match {
+ ($($args:tt)*) => {return Err(match_error!($($args)*))};
+}
+
+/// Information about a match that was found.
+#[derive(Debug)]
+pub struct Match {
+ pub(crate) range: FileRange,
+ pub(crate) matched_node: SyntaxNode,
+ pub(crate) placeholder_values: FxHashMap<Var, PlaceholderMatch>,
+ pub(crate) ignored_comments: Vec<ast::Comment>,
+ pub(crate) rule_index: usize,
+ /// The depth of matched_node.
+ pub(crate) depth: usize,
+ // Each path in the template rendered for the module in which the match was found.
+ pub(crate) rendered_template_paths: FxHashMap<SyntaxNode, hir::ModPath>,
+}
+
+/// Information about a placeholder bound in a match.
+#[derive(Debug)]
+pub(crate) struct PlaceholderMatch {
+ pub(crate) range: FileRange,
+ /// More matches, found within `node`.
+ pub(crate) inner_matches: SsrMatches,
+ /// How many times the code that the placeholder matched needed to be dereferenced. Will only be
+ /// non-zero if the placeholder matched to the receiver of a method call.
+ pub(crate) autoderef_count: usize,
+ pub(crate) autoref_kind: ast::SelfParamKind,
+}
+
+#[derive(Debug)]
+pub(crate) struct MatchFailureReason {
+ pub(crate) reason: String,
+}
+
+/// An "error" indicating that matching failed. Use the fail_match! macro to create and return this.
+#[derive(Clone)]
+pub(crate) struct MatchFailed {
+ /// The reason why we failed to match. Only present when debug_active true in call to
+ /// `get_match`.
+ pub(crate) reason: Option<String>,
+}
+
+/// Checks if `code` matches the search pattern found in `search_scope`, returning information about
+/// the match, if it does. Since we only do matching in this module and searching is done by the
+/// parent module, we don't populate nested matches.
+pub(crate) fn get_match(
+ debug_active: bool,
+ rule: &ResolvedRule,
+ code: &SyntaxNode,
+ restrict_range: &Option<FileRange>,
+ sema: &Semantics<'_, ide_db::RootDatabase>,
+) -> Result<Match, MatchFailed> {
+ record_match_fails_reasons_scope(debug_active, || {
+ Matcher::try_match(rule, code, restrict_range, sema)
+ })
+}
+
+/// Checks if our search pattern matches a particular node of the AST.
+struct Matcher<'db, 'sema> {
+ sema: &'sema Semantics<'db, ide_db::RootDatabase>,
+ /// If any placeholders come from anywhere outside of this range, then the match will be
+ /// rejected.
+ restrict_range: Option<FileRange>,
+ rule: &'sema ResolvedRule,
+}
+
+/// Which phase of matching we're currently performing. We do two phases because most attempted
+/// matches will fail and it means we can defer more expensive checks to the second phase.
+enum Phase<'a> {
+ /// On the first phase, we perform cheap checks. No state is mutated and nothing is recorded.
+ First,
+ /// On the second phase, we construct the `Match`. Things like what placeholders bind to is
+ /// recorded.
+ Second(&'a mut Match),
+}
+
+impl<'db, 'sema> Matcher<'db, 'sema> {
+ fn try_match(
+ rule: &ResolvedRule,
+ code: &SyntaxNode,
+ restrict_range: &Option<FileRange>,
+ sema: &'sema Semantics<'db, ide_db::RootDatabase>,
+ ) -> Result<Match, MatchFailed> {
+ let match_state = Matcher { sema, restrict_range: *restrict_range, rule };
+ // First pass at matching, where we check that node types and idents match.
+ match_state.attempt_match_node(&mut Phase::First, &rule.pattern.node, code)?;
+ match_state.validate_range(&sema.original_range(code))?;
+ let mut the_match = Match {
+ range: sema.original_range(code),
+ matched_node: code.clone(),
+ placeholder_values: FxHashMap::default(),
+ ignored_comments: Vec::new(),
+ rule_index: rule.index,
+ depth: 0,
+ rendered_template_paths: FxHashMap::default(),
+ };
+ // Second matching pass, where we record placeholder matches, ignored comments and maybe do
+ // any other more expensive checks that we didn't want to do on the first pass.
+ match_state.attempt_match_node(
+ &mut Phase::Second(&mut the_match),
+ &rule.pattern.node,
+ code,
+ )?;
+ the_match.depth = sema.ancestors_with_macros(the_match.matched_node.clone()).count();
+ if let Some(template) = &rule.template {
+ the_match.render_template_paths(template, sema)?;
+ }
+ Ok(the_match)
+ }
+
+ /// Checks that `range` is within the permitted range if any. This is applicable when we're
+ /// processing a macro expansion and we want to fail the match if we're working with a node that
+ /// didn't originate from the token tree of the macro call.
+ fn validate_range(&self, range: &FileRange) -> Result<(), MatchFailed> {
+ if let Some(restrict_range) = &self.restrict_range {
+ if restrict_range.file_id != range.file_id
+ || !restrict_range.range.contains_range(range.range)
+ {
+ fail_match!("Node originated from a macro");
+ }
+ }
+ Ok(())
+ }
+
+ fn attempt_match_node(
+ &self,
+ phase: &mut Phase<'_>,
+ pattern: &SyntaxNode,
+ code: &SyntaxNode,
+ ) -> Result<(), MatchFailed> {
+ // Handle placeholders.
+ if let Some(placeholder) = self.get_placeholder_for_node(pattern) {
+ for constraint in &placeholder.constraints {
+ self.check_constraint(constraint, code)?;
+ }
+ if let Phase::Second(matches_out) = phase {
+ let original_range = self.sema.original_range(code);
+ // We validated the range for the node when we started the match, so the placeholder
+ // probably can't fail range validation, but just to be safe...
+ self.validate_range(&original_range)?;
+ matches_out.placeholder_values.insert(
+ placeholder.ident.clone(),
+ PlaceholderMatch::from_range(original_range),
+ );
+ }
+ return Ok(());
+ }
+ // We allow a UFCS call to match a method call, provided they resolve to the same function.
+ if let Some(pattern_ufcs) = self.rule.pattern.ufcs_function_calls.get(pattern) {
+ if let Some(code) = ast::MethodCallExpr::cast(code.clone()) {
+ return self.attempt_match_ufcs_to_method_call(phase, pattern_ufcs, &code);
+ }
+ if let Some(code) = ast::CallExpr::cast(code.clone()) {
+ return self.attempt_match_ufcs_to_ufcs(phase, pattern_ufcs, &code);
+ }
+ }
+ if pattern.kind() != code.kind() {
+ fail_match!(
+ "Pattern had `{}` ({:?}), code had `{}` ({:?})",
+ pattern.text(),
+ pattern.kind(),
+ code.text(),
+ code.kind()
+ );
+ }
+ // Some kinds of nodes have special handling. For everything else, we fall back to default
+ // matching.
+ match code.kind() {
+ SyntaxKind::RECORD_EXPR_FIELD_LIST => {
+ self.attempt_match_record_field_list(phase, pattern, code)
+ }
+ SyntaxKind::TOKEN_TREE => self.attempt_match_token_tree(phase, pattern, code),
+ SyntaxKind::PATH => self.attempt_match_path(phase, pattern, code),
+ _ => self.attempt_match_node_children(phase, pattern, code),
+ }
+ }
+
+ fn attempt_match_node_children(
+ &self,
+ phase: &mut Phase<'_>,
+ pattern: &SyntaxNode,
+ code: &SyntaxNode,
+ ) -> Result<(), MatchFailed> {
+ self.attempt_match_sequences(
+ phase,
+ PatternIterator::new(pattern),
+ code.children_with_tokens(),
+ )
+ }
+
+ fn attempt_match_sequences(
+ &self,
+ phase: &mut Phase<'_>,
+ pattern_it: PatternIterator,
+ mut code_it: SyntaxElementChildren,
+ ) -> Result<(), MatchFailed> {
+ let mut pattern_it = pattern_it.peekable();
+ loop {
+ match phase.next_non_trivial(&mut code_it) {
+ None => {
+ if let Some(p) = pattern_it.next() {
+ fail_match!("Part of the pattern was unmatched: {:?}", p);
+ }
+ return Ok(());
+ }
+ Some(SyntaxElement::Token(c)) => {
+ self.attempt_match_token(phase, &mut pattern_it, &c)?;
+ }
+ Some(SyntaxElement::Node(c)) => match pattern_it.next() {
+ Some(SyntaxElement::Node(p)) => {
+ self.attempt_match_node(phase, &p, &c)?;
+ }
+ Some(p) => fail_match!("Pattern wanted '{}', code has {}", p, c.text()),
+ None => fail_match!("Pattern reached end, code has {}", c.text()),
+ },
+ }
+ }
+ }
+
+ fn attempt_match_token(
+ &self,
+ phase: &mut Phase<'_>,
+ pattern: &mut Peekable<PatternIterator>,
+ code: &syntax::SyntaxToken,
+ ) -> Result<(), MatchFailed> {
+ phase.record_ignored_comments(code);
+ // Ignore whitespace and comments.
+ if code.kind().is_trivia() {
+ return Ok(());
+ }
+ if let Some(SyntaxElement::Token(p)) = pattern.peek() {
+ // If the code has a comma and the pattern is about to close something, then accept the
+ // comma without advancing the pattern. i.e. ignore trailing commas.
+ if code.kind() == SyntaxKind::COMMA && is_closing_token(p.kind()) {
+ return Ok(());
+ }
+ // Conversely, if the pattern has a comma and the code doesn't, skip that part of the
+ // pattern and continue to match the code.
+ if p.kind() == SyntaxKind::COMMA && is_closing_token(code.kind()) {
+ pattern.next();
+ }
+ }
+ // Consume an element from the pattern and make sure it matches.
+ match pattern.next() {
+ Some(SyntaxElement::Token(p)) => {
+ if p.kind() != code.kind() || p.text() != code.text() {
+ fail_match!(
+ "Pattern wanted token '{}' ({:?}), but code had token '{}' ({:?})",
+ p.text(),
+ p.kind(),
+ code.text(),
+ code.kind()
+ )
+ }
+ }
+ Some(SyntaxElement::Node(p)) => {
+ // Not sure if this is actually reachable.
+ fail_match!(
+ "Pattern wanted {:?}, but code had token '{}' ({:?})",
+ p,
+ code.text(),
+ code.kind()
+ );
+ }
+ None => {
+ fail_match!("Pattern exhausted, while code remains: `{}`", code.text());
+ }
+ }
+ Ok(())
+ }
+
+ fn check_constraint(
+ &self,
+ constraint: &Constraint,
+ code: &SyntaxNode,
+ ) -> Result<(), MatchFailed> {
+ match constraint {
+ Constraint::Kind(kind) => {
+ kind.matches(code)?;
+ }
+ Constraint::Not(sub) => {
+ if self.check_constraint(&*sub, code).is_ok() {
+ fail_match!("Constraint {:?} failed for '{}'", constraint, code.text());
+ }
+ }
+ }
+ Ok(())
+ }
+
+ /// Paths are matched based on whether they refer to the same thing, even if they're written
+ /// differently.
+ fn attempt_match_path(
+ &self,
+ phase: &mut Phase<'_>,
+ pattern: &SyntaxNode,
+ code: &SyntaxNode,
+ ) -> Result<(), MatchFailed> {
+ if let Some(pattern_resolved) = self.rule.pattern.resolved_paths.get(pattern) {
+ let pattern_path = ast::Path::cast(pattern.clone()).unwrap();
+ let code_path = ast::Path::cast(code.clone()).unwrap();
+ if let (Some(pattern_segment), Some(code_segment)) =
+ (pattern_path.segment(), code_path.segment())
+ {
+ // Match everything within the segment except for the name-ref, which is handled
+ // separately via comparing what the path resolves to below.
+ self.attempt_match_opt(
+ phase,
+ pattern_segment.generic_arg_list(),
+ code_segment.generic_arg_list(),
+ )?;
+ self.attempt_match_opt(
+ phase,
+ pattern_segment.param_list(),
+ code_segment.param_list(),
+ )?;
+ }
+ if matches!(phase, Phase::Second(_)) {
+ let resolution = self
+ .sema
+ .resolve_path(&code_path)
+ .ok_or_else(|| match_error!("Failed to resolve path `{}`", code.text()))?;
+ if pattern_resolved.resolution != resolution {
+ fail_match!("Pattern had path `{}` code had `{}`", pattern.text(), code.text());
+ }
+ }
+ } else {
+ return self.attempt_match_node_children(phase, pattern, code);
+ }
+ Ok(())
+ }
+
+ fn attempt_match_opt<T: AstNode>(
+ &self,
+ phase: &mut Phase<'_>,
+ pattern: Option<T>,
+ code: Option<T>,
+ ) -> Result<(), MatchFailed> {
+ match (pattern, code) {
+ (Some(p), Some(c)) => self.attempt_match_node(phase, p.syntax(), c.syntax()),
+ (None, None) => Ok(()),
+ (Some(p), None) => fail_match!("Pattern `{}` had nothing to match", p.syntax().text()),
+ (None, Some(c)) => {
+ fail_match!("Nothing in pattern to match code `{}`", c.syntax().text())
+ }
+ }
+ }
+
+ /// We want to allow the records to match in any order, so we have special matching logic for
+ /// them.
+ fn attempt_match_record_field_list(
+ &self,
+ phase: &mut Phase<'_>,
+ pattern: &SyntaxNode,
+ code: &SyntaxNode,
+ ) -> Result<(), MatchFailed> {
+ // Build a map keyed by field name.
+ let mut fields_by_name: FxHashMap<SmolStr, SyntaxNode> = FxHashMap::default();
+ for child in code.children() {
+ if let Some(record) = ast::RecordExprField::cast(child.clone()) {
+ if let Some(name) = record.field_name() {
+ fields_by_name.insert(name.text().into(), child.clone());
+ }
+ }
+ }
+ for p in pattern.children_with_tokens() {
+ if let SyntaxElement::Node(p) = p {
+ if let Some(name_element) = p.first_child_or_token() {
+ if self.get_placeholder(&name_element).is_some() {
+ // If the pattern is using placeholders for field names then order
+ // independence doesn't make sense. Fall back to regular ordered
+ // matching.
+ return self.attempt_match_node_children(phase, pattern, code);
+ }
+ if let Some(ident) = only_ident(name_element) {
+ let code_record = fields_by_name.remove(ident.text()).ok_or_else(|| {
+ match_error!(
+ "Placeholder has record field '{}', but code doesn't",
+ ident
+ )
+ })?;
+ self.attempt_match_node(phase, &p, &code_record)?;
+ }
+ }
+ }
+ }
+ if let Some(unmatched_fields) = fields_by_name.keys().next() {
+ fail_match!(
+ "{} field(s) of a record literal failed to match, starting with {}",
+ fields_by_name.len(),
+ unmatched_fields
+ );
+ }
+ Ok(())
+ }
+
+ /// Outside of token trees, a placeholder can only match a single AST node, whereas in a token
+ /// tree it can match a sequence of tokens. Note, that this code will only be used when the
+ /// pattern matches the macro invocation. For matches within the macro call, we'll already have
+ /// expanded the macro.
+ fn attempt_match_token_tree(
+ &self,
+ phase: &mut Phase<'_>,
+ pattern: &SyntaxNode,
+ code: &syntax::SyntaxNode,
+ ) -> Result<(), MatchFailed> {
+ let mut pattern = PatternIterator::new(pattern).peekable();
+ let mut children = code.children_with_tokens();
+ while let Some(child) = children.next() {
+ if let Some(placeholder) = pattern.peek().and_then(|p| self.get_placeholder(p)) {
+ pattern.next();
+ let next_pattern_token = pattern
+ .peek()
+ .and_then(|p| match p {
+ SyntaxElement::Token(t) => Some(t.clone()),
+ SyntaxElement::Node(n) => n.first_token(),
+ })
+ .map(|p| p.text().to_string());
+ let first_matched_token = child.clone();
+ let mut last_matched_token = child;
+ // Read code tokens util we reach one equal to the next token from our pattern
+ // or we reach the end of the token tree.
+ for next in &mut children {
+ match &next {
+ SyntaxElement::Token(t) => {
+ if Some(t.to_string()) == next_pattern_token {
+ pattern.next();
+ break;
+ }
+ }
+ SyntaxElement::Node(n) => {
+ if let Some(first_token) = n.first_token() {
+ if Some(first_token.text()) == next_pattern_token.as_deref() {
+ if let Some(SyntaxElement::Node(p)) = pattern.next() {
+ // We have a subtree that starts with the next token in our pattern.
+ self.attempt_match_token_tree(phase, &p, n)?;
+ break;
+ }
+ }
+ }
+ }
+ };
+ last_matched_token = next;
+ }
+ if let Phase::Second(match_out) = phase {
+ match_out.placeholder_values.insert(
+ placeholder.ident.clone(),
+ PlaceholderMatch::from_range(FileRange {
+ file_id: self.sema.original_range(code).file_id,
+ range: first_matched_token
+ .text_range()
+ .cover(last_matched_token.text_range()),
+ }),
+ );
+ }
+ continue;
+ }
+ // Match literal (non-placeholder) tokens.
+ match child {
+ SyntaxElement::Token(token) => {
+ self.attempt_match_token(phase, &mut pattern, &token)?;
+ }
+ SyntaxElement::Node(node) => match pattern.next() {
+ Some(SyntaxElement::Node(p)) => {
+ self.attempt_match_token_tree(phase, &p, &node)?;
+ }
+ Some(SyntaxElement::Token(p)) => fail_match!(
+ "Pattern has token '{}', code has subtree '{}'",
+ p.text(),
+ node.text()
+ ),
+ None => fail_match!("Pattern has nothing, code has '{}'", node.text()),
+ },
+ }
+ }
+ if let Some(p) = pattern.next() {
+ fail_match!("Reached end of token tree in code, but pattern still has {:?}", p);
+ }
+ Ok(())
+ }
+
+ fn attempt_match_ufcs_to_method_call(
+ &self,
+ phase: &mut Phase<'_>,
+ pattern_ufcs: &UfcsCallInfo,
+ code: &ast::MethodCallExpr,
+ ) -> Result<(), MatchFailed> {
+ use ast::HasArgList;
+ let code_resolved_function = self
+ .sema
+ .resolve_method_call(code)
+ .ok_or_else(|| match_error!("Failed to resolve method call"))?;
+ if pattern_ufcs.function != code_resolved_function {
+ fail_match!("Method call resolved to a different function");
+ }
+ // Check arguments.
+ let mut pattern_args = pattern_ufcs
+ .call_expr
+ .arg_list()
+ .ok_or_else(|| match_error!("Pattern function call has no args"))?
+ .args();
+ // If the function we're calling takes a self parameter, then we store additional
+ // information on the placeholder match about autoderef and autoref. This allows us to use
+ // the placeholder in a context where autoderef and autoref don't apply.
+ if code_resolved_function.self_param(self.sema.db).is_some() {
+ if let (Some(pattern_type), Some(expr)) =
+ (&pattern_ufcs.qualifier_type, &code.receiver())
+ {
+ let deref_count = self.check_expr_type(pattern_type, expr)?;
+ let pattern_receiver = pattern_args.next();
+ self.attempt_match_opt(phase, pattern_receiver.clone(), code.receiver())?;
+ if let Phase::Second(match_out) = phase {
+ if let Some(placeholder_value) = pattern_receiver
+ .and_then(|n| self.get_placeholder_for_node(n.syntax()))
+ .and_then(|placeholder| {
+ match_out.placeholder_values.get_mut(&placeholder.ident)
+ })
+ {
+ placeholder_value.autoderef_count = deref_count;
+ placeholder_value.autoref_kind = self
+ .sema
+ .resolve_method_call_as_callable(code)
+ .and_then(|callable| callable.receiver_param(self.sema.db))
+ .map(|self_param| self_param.kind())
+ .unwrap_or(ast::SelfParamKind::Owned);
+ }
+ }
+ }
+ } else {
+ self.attempt_match_opt(phase, pattern_args.next(), code.receiver())?;
+ }
+ let mut code_args =
+ code.arg_list().ok_or_else(|| match_error!("Code method call has no args"))?.args();
+ loop {
+ match (pattern_args.next(), code_args.next()) {
+ (None, None) => return Ok(()),
+ (p, c) => self.attempt_match_opt(phase, p, c)?,
+ }
+ }
+ }
+
+ fn attempt_match_ufcs_to_ufcs(
+ &self,
+ phase: &mut Phase<'_>,
+ pattern_ufcs: &UfcsCallInfo,
+ code: &ast::CallExpr,
+ ) -> Result<(), MatchFailed> {
+ use ast::HasArgList;
+ // Check that the first argument is the expected type.
+ if let (Some(pattern_type), Some(expr)) = (
+ &pattern_ufcs.qualifier_type,
+ &code.arg_list().and_then(|code_args| code_args.args().next()),
+ ) {
+ self.check_expr_type(pattern_type, expr)?;
+ }
+ self.attempt_match_node_children(phase, pattern_ufcs.call_expr.syntax(), code.syntax())
+ }
+
+ /// Verifies that `expr` matches `pattern_type`, possibly after dereferencing some number of
+ /// times. Returns the number of times it needed to be dereferenced.
+ fn check_expr_type(
+ &self,
+ pattern_type: &hir::Type,
+ expr: &ast::Expr,
+ ) -> Result<usize, MatchFailed> {
+ use hir::HirDisplay;
+ let code_type = self
+ .sema
+ .type_of_expr(expr)
+ .ok_or_else(|| {
+ match_error!("Failed to get receiver type for `{}`", expr.syntax().text())
+ })?
+ .original;
+ // Temporary needed to make the borrow checker happy.
+ let res = code_type
+ .autoderef(self.sema.db)
+ .enumerate()
+ .find(|(_, deref_code_type)| pattern_type == deref_code_type)
+ .map(|(count, _)| count)
+ .ok_or_else(|| {
+ match_error!(
+ "Pattern type `{}` didn't match code type `{}`",
+ pattern_type.display(self.sema.db),
+ code_type.display(self.sema.db)
+ )
+ });
+ res
+ }
+
+ fn get_placeholder_for_node(&self, node: &SyntaxNode) -> Option<&Placeholder> {
+ self.get_placeholder(&SyntaxElement::Node(node.clone()))
+ }
+
+ fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> {
+ only_ident(element.clone()).and_then(|ident| self.rule.get_placeholder(&ident))
+ }
+}
+
+impl Match {
+ fn render_template_paths(
+ &mut self,
+ template: &ResolvedPattern,
+ sema: &Semantics<'_, ide_db::RootDatabase>,
+ ) -> Result<(), MatchFailed> {
+ let module = sema
+ .scope(&self.matched_node)
+ .ok_or_else(|| match_error!("Matched node isn't in a module"))?
+ .module();
+ for (path, resolved_path) in &template.resolved_paths {
+ if let hir::PathResolution::Def(module_def) = resolved_path.resolution {
+ let mod_path = module.find_use_path(sema.db, module_def).ok_or_else(|| {
+ match_error!("Failed to render template path `{}` at match location")
+ })?;
+ self.rendered_template_paths.insert(path.clone(), mod_path);
+ }
+ }
+ Ok(())
+ }
+}
+
+impl Phase<'_> {
+ fn next_non_trivial(&mut self, code_it: &mut SyntaxElementChildren) -> Option<SyntaxElement> {
+ loop {
+ let c = code_it.next();
+ if let Some(SyntaxElement::Token(t)) = &c {
+ self.record_ignored_comments(t);
+ if t.kind().is_trivia() {
+ continue;
+ }
+ }
+ return c;
+ }
+ }
+
+ fn record_ignored_comments(&mut self, token: &SyntaxToken) {
+ if token.kind() == SyntaxKind::COMMENT {
+ if let Phase::Second(match_out) = self {
+ if let Some(comment) = ast::Comment::cast(token.clone()) {
+ match_out.ignored_comments.push(comment);
+ }
+ }
+ }
+ }
+}
+
+fn is_closing_token(kind: SyntaxKind) -> bool {
+ kind == SyntaxKind::R_PAREN || kind == SyntaxKind::R_CURLY || kind == SyntaxKind::R_BRACK
+}
+
+pub(crate) fn record_match_fails_reasons_scope<F, T>(debug_active: bool, f: F) -> T
+where
+ F: Fn() -> T,
+{
+ RECORDING_MATCH_FAIL_REASONS.with(|c| c.set(debug_active));
+ let res = f();
+ RECORDING_MATCH_FAIL_REASONS.with(|c| c.set(false));
+ res
+}
+
+// For performance reasons, we don't want to record the reason why every match fails, only the bit
+// of code that the user indicated they thought would match. We use a thread local to indicate when
+// we are trying to match that bit of code. This saves us having to pass a boolean into all the bits
+// of code that can make the decision to not match.
+thread_local! {
+ pub static RECORDING_MATCH_FAIL_REASONS: Cell<bool> = Cell::new(false);
+}
+
+fn recording_match_fail_reasons() -> bool {
+ RECORDING_MATCH_FAIL_REASONS.with(|c| c.get())
+}
+
+impl PlaceholderMatch {
+ fn from_range(range: FileRange) -> Self {
+ Self {
+ range,
+ inner_matches: SsrMatches::default(),
+ autoderef_count: 0,
+ autoref_kind: ast::SelfParamKind::Owned,
+ }
+ }
+}
+
+impl NodeKind {
+ fn matches(&self, node: &SyntaxNode) -> Result<(), MatchFailed> {
+ let ok = match self {
+ Self::Literal => {
+ cov_mark::hit!(literal_constraint);
+ ast::Literal::can_cast(node.kind())
+ }
+ };
+ if !ok {
+ fail_match!("Code '{}' isn't of kind {:?}", node.text(), self);
+ }
+ Ok(())
+ }
+}
+
+// If `node` contains nothing but an ident then return it, otherwise return None.
+fn only_ident(element: SyntaxElement) -> Option<SyntaxToken> {
+ match element {
+ SyntaxElement::Token(t) => {
+ if t.kind() == SyntaxKind::IDENT {
+ return Some(t);
+ }
+ }
+ SyntaxElement::Node(n) => {
+ let mut children = n.children_with_tokens();
+ if let (Some(only_child), None) = (children.next(), children.next()) {
+ return only_ident(only_child);
+ }
+ }
+ }
+ None
+}
+
+struct PatternIterator {
+ iter: SyntaxElementChildren,
+}
+
+impl Iterator for PatternIterator {
+ type Item = SyntaxElement;
+
+ fn next(&mut self) -> Option<SyntaxElement> {
+ for element in &mut self.iter {
+ if !element.kind().is_trivia() {
+ return Some(element);
+ }
+ }
+ None
+ }
+}
+
+impl PatternIterator {
+ fn new(parent: &SyntaxNode) -> Self {
+ Self { iter: parent.children_with_tokens() }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{MatchFinder, SsrRule};
+
+ #[test]
+ fn parse_match_replace() {
+ let rule: SsrRule = "foo($x) ==>> bar($x)".parse().unwrap();
+ let input = "fn foo() {} fn bar() {} fn main() { foo(1+2); }";
+
+ let (db, position, selections) = crate::tests::single_file(input);
+ let mut match_finder = MatchFinder::in_context(&db, position, selections).unwrap();
+ match_finder.add_rule(rule).unwrap();
+ let matches = match_finder.matches();
+ assert_eq!(matches.matches.len(), 1);
+ assert_eq!(matches.matches[0].matched_node.text(), "foo(1+2)");
+ assert_eq!(matches.matches[0].placeholder_values.len(), 1);
+
+ let edits = match_finder.edits();
+ assert_eq!(edits.len(), 1);
+ let edit = &edits[&position.file_id];
+ let mut after = input.to_string();
+ edit.apply(&mut after);
+ assert_eq!(after, "fn foo() {} fn bar() {} fn main() { bar(1+2); }");
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/nester.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/nester.rs
new file mode 100644
index 000000000..afaaafd1f
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-ssr/src/nester.rs
@@ -0,0 +1,99 @@
+//! Converts a flat collection of matches into a nested form suitable for replacement. When there
+//! are multiple matches for a node, or that overlap, priority is given to the earlier rule. Nested
+//! matches are only permitted if the inner match is contained entirely within a placeholder of an
+//! outer match.
+//!
+//! For example, if our search pattern is `foo(foo($a))` and the code had `foo(foo(foo(foo(42))))`,
+//! then we'll get 3 matches, however only the outermost and innermost matches can be accepted. The
+//! middle match would take the second `foo` from the outer match.
+
+use ide_db::FxHashMap;
+use syntax::SyntaxNode;
+
+use crate::{Match, SsrMatches};
+
+pub(crate) fn nest_and_remove_collisions(
+ mut matches: Vec<Match>,
+ sema: &hir::Semantics<'_, ide_db::RootDatabase>,
+) -> SsrMatches {
+ // We sort the matches by depth then by rule index. Sorting by depth means that by the time we
+ // see a match, any parent matches or conflicting matches will have already been seen. Sorting
+ // by rule_index means that if there are two matches for the same node, the rule added first
+ // will take precedence.
+ matches.sort_by(|a, b| a.depth.cmp(&b.depth).then_with(|| a.rule_index.cmp(&b.rule_index)));
+ let mut collector = MatchCollector::default();
+ for m in matches {
+ collector.add_match(m, sema);
+ }
+ collector.into()
+}
+
+#[derive(Default)]
+struct MatchCollector {
+ matches_by_node: FxHashMap<SyntaxNode, Match>,
+}
+
+impl MatchCollector {
+ /// Attempts to add `m` to matches. If it conflicts with an existing match, it is discarded. If
+ /// it is entirely within the a placeholder of an existing match, then it is added as a child
+ /// match of the existing match.
+ fn add_match(&mut self, m: Match, sema: &hir::Semantics<'_, ide_db::RootDatabase>) {
+ let matched_node = m.matched_node.clone();
+ if let Some(existing) = self.matches_by_node.get_mut(&matched_node) {
+ try_add_sub_match(m, existing, sema);
+ return;
+ }
+ for ancestor in sema.ancestors_with_macros(m.matched_node.clone()) {
+ if let Some(existing) = self.matches_by_node.get_mut(&ancestor) {
+ try_add_sub_match(m, existing, sema);
+ return;
+ }
+ }
+ self.matches_by_node.insert(matched_node, m);
+ }
+}
+
+/// Attempts to add `m` as a sub-match of `existing`.
+fn try_add_sub_match(
+ m: Match,
+ existing: &mut Match,
+ sema: &hir::Semantics<'_, ide_db::RootDatabase>,
+) {
+ for p in existing.placeholder_values.values_mut() {
+ // Note, no need to check if p.range.file is equal to m.range.file, since we
+ // already know we're within `existing`.
+ if p.range.range.contains_range(m.range.range) {
+ // Convert the inner matches in `p` into a temporary MatchCollector. When
+ // we're done, we then convert it back into an SsrMatches. If we expected
+ // lots of inner matches, it might be worthwhile keeping a MatchCollector
+ // around for each placeholder match. However we expect most placeholder
+ // will have 0 and a few will have 1. More than that should hopefully be
+ // exceptional.
+ let mut collector = MatchCollector::default();
+ for m in std::mem::take(&mut p.inner_matches.matches) {
+ collector.matches_by_node.insert(m.matched_node.clone(), m);
+ }
+ collector.add_match(m, sema);
+ p.inner_matches = collector.into();
+ break;
+ }
+ }
+}
+
+impl From<MatchCollector> for SsrMatches {
+ fn from(mut match_collector: MatchCollector) -> Self {
+ let mut matches = SsrMatches::default();
+ for (_, m) in match_collector.matches_by_node.drain() {
+ matches.matches.push(m);
+ }
+ matches.matches.sort_by(|a, b| {
+ // Order matches by file_id then by start range. This should be sufficient since ranges
+ // shouldn't be overlapping.
+ a.range
+ .file_id
+ .cmp(&b.range.file_id)
+ .then_with(|| a.range.range.start().cmp(&b.range.range.start()))
+ });
+ matches
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/parsing.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/parsing.rs
new file mode 100644
index 000000000..f6220b928
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-ssr/src/parsing.rs
@@ -0,0 +1,406 @@
+//! This file contains code for parsing SSR rules, which look something like `foo($a) ==>> bar($b)`.
+//! We first split everything before and after the separator `==>>`. Next, both the search pattern
+//! and the replacement template get tokenized by the Rust tokenizer. Tokens are then searched for
+//! placeholders, which start with `$`. For replacement templates, this is the final form. For
+//! search patterns, we go further and parse the pattern as each kind of thing that we can match.
+//! e.g. expressions, type references etc.
+use ide_db::{FxHashMap, FxHashSet};
+use std::{fmt::Display, str::FromStr};
+use syntax::{SmolStr, SyntaxKind, SyntaxNode, T};
+
+use crate::errors::bail;
+use crate::{fragments, SsrError, SsrPattern, SsrRule};
+
+#[derive(Debug)]
+pub(crate) struct ParsedRule {
+ pub(crate) placeholders_by_stand_in: FxHashMap<SmolStr, Placeholder>,
+ pub(crate) pattern: SyntaxNode,
+ pub(crate) template: Option<SyntaxNode>,
+}
+
+#[derive(Debug)]
+pub(crate) struct RawPattern {
+ tokens: Vec<PatternElement>,
+}
+
+// Part of a search or replace pattern.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub(crate) enum PatternElement {
+ Token(Token),
+ Placeholder(Placeholder),
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub(crate) struct Placeholder {
+ /// The name of this placeholder. e.g. for "$a", this would be "a"
+ pub(crate) ident: Var,
+ /// A unique name used in place of this placeholder when we parse the pattern as Rust code.
+ stand_in_name: String,
+ pub(crate) constraints: Vec<Constraint>,
+}
+
+/// Represents a `$var` in an SSR query.
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub(crate) struct Var(pub(crate) String);
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub(crate) enum Constraint {
+ Kind(NodeKind),
+ Not(Box<Constraint>),
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub(crate) enum NodeKind {
+ Literal,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(crate) struct Token {
+ kind: SyntaxKind,
+ pub(crate) text: SmolStr,
+}
+
+impl ParsedRule {
+ fn new(
+ pattern: &RawPattern,
+ template: Option<&RawPattern>,
+ ) -> Result<Vec<ParsedRule>, SsrError> {
+ let raw_pattern = pattern.as_rust_code();
+ let raw_template = template.map(|t| t.as_rust_code());
+ let raw_template = raw_template.as_deref();
+ let mut builder = RuleBuilder {
+ placeholders_by_stand_in: pattern.placeholders_by_stand_in(),
+ rules: Vec::new(),
+ };
+
+ let raw_template_stmt = raw_template.map(fragments::stmt);
+ if let raw_template_expr @ Some(Ok(_)) = raw_template.map(fragments::expr) {
+ builder.try_add(fragments::expr(&raw_pattern), raw_template_expr);
+ } else {
+ builder.try_add(fragments::expr(&raw_pattern), raw_template_stmt.clone());
+ }
+ builder.try_add(fragments::ty(&raw_pattern), raw_template.map(fragments::ty));
+ builder.try_add(fragments::item(&raw_pattern), raw_template.map(fragments::item));
+ builder.try_add(fragments::pat(&raw_pattern), raw_template.map(fragments::pat));
+ builder.try_add(fragments::stmt(&raw_pattern), raw_template_stmt);
+ builder.build()
+ }
+}
+
+struct RuleBuilder {
+ placeholders_by_stand_in: FxHashMap<SmolStr, Placeholder>,
+ rules: Vec<ParsedRule>,
+}
+
+impl RuleBuilder {
+ fn try_add(
+ &mut self,
+ pattern: Result<SyntaxNode, ()>,
+ template: Option<Result<SyntaxNode, ()>>,
+ ) {
+ match (pattern, template) {
+ (Ok(pattern), Some(Ok(template))) => self.rules.push(ParsedRule {
+ placeholders_by_stand_in: self.placeholders_by_stand_in.clone(),
+ pattern,
+ template: Some(template),
+ }),
+ (Ok(pattern), None) => self.rules.push(ParsedRule {
+ placeholders_by_stand_in: self.placeholders_by_stand_in.clone(),
+ pattern,
+ template: None,
+ }),
+ _ => {}
+ }
+ }
+
+ fn build(mut self) -> Result<Vec<ParsedRule>, SsrError> {
+ if self.rules.is_empty() {
+ bail!("Not a valid Rust expression, type, item, path or pattern");
+ }
+ // If any rules contain paths, then we reject any rules that don't contain paths. Allowing a
+ // mix leads to strange semantics, since the path-based rules only match things where the
+ // path refers to semantically the same thing, whereas the non-path-based rules could match
+ // anything. Specifically, if we have a rule like `foo ==>> bar` we only want to match the
+ // `foo` that is in the current scope, not any `foo`. However "foo" can be parsed as a
+ // pattern (IDENT_PAT -> NAME -> IDENT). Allowing such a rule through would result in
+ // renaming everything called `foo` to `bar`. It'd also be slow, since without a path, we'd
+ // have to use the slow-scan search mechanism.
+ if self.rules.iter().any(|rule| contains_path(&rule.pattern)) {
+ let old_len = self.rules.len();
+ self.rules.retain(|rule| contains_path(&rule.pattern));
+ if self.rules.len() < old_len {
+ cov_mark::hit!(pattern_is_a_single_segment_path);
+ }
+ }
+ Ok(self.rules)
+ }
+}
+
+/// Returns whether there are any paths in `node`.
+fn contains_path(node: &SyntaxNode) -> bool {
+ node.kind() == SyntaxKind::PATH
+ || node.descendants().any(|node| node.kind() == SyntaxKind::PATH)
+}
+
+impl FromStr for SsrRule {
+ type Err = SsrError;
+
+ fn from_str(query: &str) -> Result<SsrRule, SsrError> {
+ let mut it = query.split("==>>");
+ let pattern = it.next().expect("at least empty string").trim();
+ let template = it
+ .next()
+ .ok_or_else(|| SsrError("Cannot find delimiter `==>>`".into()))?
+ .trim()
+ .to_string();
+ if it.next().is_some() {
+ return Err(SsrError("More than one delimiter found".into()));
+ }
+ let raw_pattern = pattern.parse()?;
+ let raw_template = template.parse()?;
+ let parsed_rules = ParsedRule::new(&raw_pattern, Some(&raw_template))?;
+ let rule = SsrRule { pattern: raw_pattern, template: raw_template, parsed_rules };
+ validate_rule(&rule)?;
+ Ok(rule)
+ }
+}
+
+impl FromStr for RawPattern {
+ type Err = SsrError;
+
+ fn from_str(pattern_str: &str) -> Result<RawPattern, SsrError> {
+ Ok(RawPattern { tokens: parse_pattern(pattern_str)? })
+ }
+}
+
+impl RawPattern {
+ /// Returns this search pattern as Rust source code that we can feed to the Rust parser.
+ fn as_rust_code(&self) -> String {
+ let mut res = String::new();
+ for t in &self.tokens {
+ res.push_str(match t {
+ PatternElement::Token(token) => token.text.as_str(),
+ PatternElement::Placeholder(placeholder) => placeholder.stand_in_name.as_str(),
+ });
+ }
+ res
+ }
+
+ pub(crate) fn placeholders_by_stand_in(&self) -> FxHashMap<SmolStr, Placeholder> {
+ let mut res = FxHashMap::default();
+ for t in &self.tokens {
+ if let PatternElement::Placeholder(placeholder) = t {
+ res.insert(SmolStr::new(placeholder.stand_in_name.clone()), placeholder.clone());
+ }
+ }
+ res
+ }
+}
+
+impl FromStr for SsrPattern {
+ type Err = SsrError;
+
+ fn from_str(pattern_str: &str) -> Result<SsrPattern, SsrError> {
+ let raw_pattern = pattern_str.parse()?;
+ let parsed_rules = ParsedRule::new(&raw_pattern, None)?;
+ Ok(SsrPattern { parsed_rules })
+ }
+}
+
+/// Returns `pattern_str`, parsed as a search or replace pattern. If `remove_whitespace` is true,
+/// then any whitespace tokens will be removed, which we do for the search pattern, but not for the
+/// replace pattern.
+fn parse_pattern(pattern_str: &str) -> Result<Vec<PatternElement>, SsrError> {
+ let mut res = Vec::new();
+ let mut placeholder_names = FxHashSet::default();
+ let mut tokens = tokenize(pattern_str)?.into_iter();
+ while let Some(token) = tokens.next() {
+ if token.kind == T![$] {
+ let placeholder = parse_placeholder(&mut tokens)?;
+ if !placeholder_names.insert(placeholder.ident.clone()) {
+ bail!("Placeholder `{}` repeats more than once", placeholder.ident);
+ }
+ res.push(PatternElement::Placeholder(placeholder));
+ } else {
+ res.push(PatternElement::Token(token));
+ }
+ }
+ Ok(res)
+}
+
+/// Checks for errors in a rule. e.g. the replace pattern referencing placeholders that the search
+/// pattern didn't define.
+fn validate_rule(rule: &SsrRule) -> Result<(), SsrError> {
+ let mut defined_placeholders = FxHashSet::default();
+ for p in &rule.pattern.tokens {
+ if let PatternElement::Placeholder(placeholder) = p {
+ defined_placeholders.insert(&placeholder.ident);
+ }
+ }
+ let mut undefined = Vec::new();
+ for p in &rule.template.tokens {
+ if let PatternElement::Placeholder(placeholder) = p {
+ if !defined_placeholders.contains(&placeholder.ident) {
+ undefined.push(placeholder.ident.to_string());
+ }
+ if !placeholder.constraints.is_empty() {
+ bail!("Replacement placeholders cannot have constraints");
+ }
+ }
+ }
+ if !undefined.is_empty() {
+ bail!("Replacement contains undefined placeholders: {}", undefined.join(", "));
+ }
+ Ok(())
+}
+
+fn tokenize(source: &str) -> Result<Vec<Token>, SsrError> {
+ let lexed = parser::LexedStr::new(source);
+ if let Some((_, first_error)) = lexed.errors().next() {
+ bail!("Failed to parse pattern: {}", first_error);
+ }
+ let mut tokens: Vec<Token> = Vec::new();
+ for i in 0..lexed.len() {
+ tokens.push(Token { kind: lexed.kind(i), text: lexed.text(i).into() });
+ }
+ Ok(tokens)
+}
+
+fn parse_placeholder(tokens: &mut std::vec::IntoIter<Token>) -> Result<Placeholder, SsrError> {
+ let mut name = None;
+ let mut constraints = Vec::new();
+ if let Some(token) = tokens.next() {
+ match token.kind {
+ SyntaxKind::IDENT => {
+ name = Some(token.text);
+ }
+ T!['{'] => {
+ let token =
+ tokens.next().ok_or_else(|| SsrError::new("Unexpected end of placeholder"))?;
+ if token.kind == SyntaxKind::IDENT {
+ name = Some(token.text);
+ }
+ loop {
+ let token = tokens
+ .next()
+ .ok_or_else(|| SsrError::new("Placeholder is missing closing brace '}'"))?;
+ match token.kind {
+ T![:] => {
+ constraints.push(parse_constraint(tokens)?);
+ }
+ T!['}'] => break,
+ _ => bail!("Unexpected token while parsing placeholder: '{}'", token.text),
+ }
+ }
+ }
+ _ => {
+ bail!("Placeholders should either be $name or ${{name:constraints}}");
+ }
+ }
+ }
+ let name = name.ok_or_else(|| SsrError::new("Placeholder ($) with no name"))?;
+ Ok(Placeholder::new(name, constraints))
+}
+
+fn parse_constraint(tokens: &mut std::vec::IntoIter<Token>) -> Result<Constraint, SsrError> {
+ let constraint_type = tokens
+ .next()
+ .ok_or_else(|| SsrError::new("Found end of placeholder while looking for a constraint"))?
+ .text
+ .to_string();
+ match constraint_type.as_str() {
+ "kind" => {
+ expect_token(tokens, "(")?;
+ let t = tokens.next().ok_or_else(|| {
+ SsrError::new("Unexpected end of constraint while looking for kind")
+ })?;
+ if t.kind != SyntaxKind::IDENT {
+ bail!("Expected ident, found {:?} while parsing kind constraint", t.kind);
+ }
+ expect_token(tokens, ")")?;
+ Ok(Constraint::Kind(NodeKind::from(&t.text)?))
+ }
+ "not" => {
+ expect_token(tokens, "(")?;
+ let sub = parse_constraint(tokens)?;
+ expect_token(tokens, ")")?;
+ Ok(Constraint::Not(Box::new(sub)))
+ }
+ x => bail!("Unsupported constraint type '{}'", x),
+ }
+}
+
+fn expect_token(tokens: &mut std::vec::IntoIter<Token>, expected: &str) -> Result<(), SsrError> {
+ if let Some(t) = tokens.next() {
+ if t.text == expected {
+ return Ok(());
+ }
+ bail!("Expected {} found {}", expected, t.text);
+ }
+ bail!("Expected {} found end of stream", expected);
+}
+
+impl NodeKind {
+ fn from(name: &SmolStr) -> Result<NodeKind, SsrError> {
+ Ok(match name.as_str() {
+ "literal" => NodeKind::Literal,
+ _ => bail!("Unknown node kind '{}'", name),
+ })
+ }
+}
+
+impl Placeholder {
+ fn new(name: SmolStr, constraints: Vec<Constraint>) -> Self {
+ Self {
+ stand_in_name: format!("__placeholder_{}", name),
+ constraints,
+ ident: Var(name.to_string()),
+ }
+ }
+}
+
+impl Display for Var {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "${}", self.0)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn parser_happy_case() {
+ fn token(kind: SyntaxKind, text: &str) -> PatternElement {
+ PatternElement::Token(Token { kind, text: SmolStr::new(text) })
+ }
+ fn placeholder(name: &str) -> PatternElement {
+ PatternElement::Placeholder(Placeholder::new(SmolStr::new(name), Vec::new()))
+ }
+ let result: SsrRule = "foo($a, $b) ==>> bar($b, $a)".parse().unwrap();
+ assert_eq!(
+ result.pattern.tokens,
+ vec![
+ token(SyntaxKind::IDENT, "foo"),
+ token(T!['('], "("),
+ placeholder("a"),
+ token(T![,], ","),
+ token(SyntaxKind::WHITESPACE, " "),
+ placeholder("b"),
+ token(T![')'], ")"),
+ ]
+ );
+ assert_eq!(
+ result.template.tokens,
+ vec![
+ token(SyntaxKind::IDENT, "bar"),
+ token(T!['('], "("),
+ placeholder("b"),
+ token(T![,], ","),
+ token(SyntaxKind::WHITESPACE, " "),
+ placeholder("a"),
+ token(T![')'], ")"),
+ ]
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/replacing.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/replacing.rs
new file mode 100644
index 000000000..e27ef6e35
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-ssr/src/replacing.rs
@@ -0,0 +1,242 @@
+//! Code for applying replacement templates for matches that have previously been found.
+
+use ide_db::{FxHashMap, FxHashSet};
+use itertools::Itertools;
+use syntax::{
+ ast::{self, AstNode, AstToken},
+ SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize,
+};
+use text_edit::TextEdit;
+
+use crate::{fragments, resolving::ResolvedRule, Match, SsrMatches};
+
+/// Returns a text edit that will replace each match in `matches` with its corresponding replacement
+/// template. Placeholders in the template will have been substituted with whatever they matched to
+/// in the original code.
+pub(crate) fn matches_to_edit(
+ matches: &SsrMatches,
+ file_src: &str,
+ rules: &[ResolvedRule],
+) -> TextEdit {
+ matches_to_edit_at_offset(matches, file_src, 0.into(), rules)
+}
+
+fn matches_to_edit_at_offset(
+ matches: &SsrMatches,
+ file_src: &str,
+ relative_start: TextSize,
+ rules: &[ResolvedRule],
+) -> TextEdit {
+ let mut edit_builder = TextEdit::builder();
+ for m in &matches.matches {
+ edit_builder.replace(
+ m.range.range.checked_sub(relative_start).unwrap(),
+ render_replace(m, file_src, rules),
+ );
+ }
+ edit_builder.finish()
+}
+
+struct ReplacementRenderer<'a> {
+ match_info: &'a Match,
+ file_src: &'a str,
+ rules: &'a [ResolvedRule],
+ rule: &'a ResolvedRule,
+ out: String,
+ // Map from a range within `out` to a token in `template` that represents a placeholder. This is
+ // used to validate that the generated source code doesn't split any placeholder expansions (see
+ // below).
+ placeholder_tokens_by_range: FxHashMap<TextRange, SyntaxToken>,
+ // Which placeholder tokens need to be wrapped in parenthesis in order to ensure that when `out`
+ // is parsed, placeholders don't get split. e.g. if a template of `$a.to_string()` results in `1
+ // + 2.to_string()` then the placeholder value `1 + 2` was split and needs parenthesis.
+ placeholder_tokens_requiring_parenthesis: FxHashSet<SyntaxToken>,
+}
+
+fn render_replace(match_info: &Match, file_src: &str, rules: &[ResolvedRule]) -> String {
+ let rule = &rules[match_info.rule_index];
+ let template = rule
+ .template
+ .as_ref()
+ .expect("You called MatchFinder::edits after calling MatchFinder::add_search_pattern");
+ let mut renderer = ReplacementRenderer {
+ match_info,
+ file_src,
+ rules,
+ rule,
+ out: String::new(),
+ placeholder_tokens_requiring_parenthesis: FxHashSet::default(),
+ placeholder_tokens_by_range: FxHashMap::default(),
+ };
+ renderer.render_node(&template.node);
+ renderer.maybe_rerender_with_extra_parenthesis(&template.node);
+ for comment in &match_info.ignored_comments {
+ renderer.out.push_str(&comment.syntax().to_string());
+ }
+ renderer.out
+}
+
+impl ReplacementRenderer<'_> {
+ fn render_node_children(&mut self, node: &SyntaxNode) {
+ for node_or_token in node.children_with_tokens() {
+ self.render_node_or_token(&node_or_token);
+ }
+ }
+
+ fn render_node_or_token(&mut self, node_or_token: &SyntaxElement) {
+ match node_or_token {
+ SyntaxElement::Token(token) => {
+ self.render_token(token);
+ }
+ SyntaxElement::Node(child_node) => {
+ self.render_node(child_node);
+ }
+ }
+ }
+
+ fn render_node(&mut self, node: &SyntaxNode) {
+ if let Some(mod_path) = self.match_info.rendered_template_paths.get(node) {
+ self.out.push_str(&mod_path.to_string());
+ // Emit everything except for the segment's name-ref, since we already effectively
+ // emitted that as part of `mod_path`.
+ if let Some(path) = ast::Path::cast(node.clone()) {
+ if let Some(segment) = path.segment() {
+ for node_or_token in segment.syntax().children_with_tokens() {
+ if node_or_token.kind() != SyntaxKind::NAME_REF {
+ self.render_node_or_token(&node_or_token);
+ }
+ }
+ }
+ }
+ } else {
+ self.render_node_children(node);
+ }
+ }
+
+ fn render_token(&mut self, token: &SyntaxToken) {
+ if let Some(placeholder) = self.rule.get_placeholder(token) {
+ if let Some(placeholder_value) =
+ self.match_info.placeholder_values.get(&placeholder.ident)
+ {
+ let range = &placeholder_value.range.range;
+ let mut matched_text =
+ self.file_src[usize::from(range.start())..usize::from(range.end())].to_owned();
+ // If a method call is performed directly on the placeholder, then autoderef and
+ // autoref will apply, so we can just substitute whatever the placeholder matched to
+ // directly. If we're not applying a method call, then we need to add explicitly
+ // deref and ref in order to match whatever was being done implicitly at the match
+ // site.
+ if !token_is_method_call_receiver(token)
+ && (placeholder_value.autoderef_count > 0
+ || placeholder_value.autoref_kind != ast::SelfParamKind::Owned)
+ {
+ cov_mark::hit!(replace_autoref_autoderef_capture);
+ let ref_kind = match placeholder_value.autoref_kind {
+ ast::SelfParamKind::Owned => "",
+ ast::SelfParamKind::Ref => "&",
+ ast::SelfParamKind::MutRef => "&mut ",
+ };
+ matched_text = format!(
+ "{}{}{}",
+ ref_kind,
+ "*".repeat(placeholder_value.autoderef_count),
+ matched_text
+ );
+ }
+ let edit = matches_to_edit_at_offset(
+ &placeholder_value.inner_matches,
+ self.file_src,
+ range.start(),
+ self.rules,
+ );
+ let needs_parenthesis =
+ self.placeholder_tokens_requiring_parenthesis.contains(token);
+ edit.apply(&mut matched_text);
+ if needs_parenthesis {
+ self.out.push('(');
+ }
+ self.placeholder_tokens_by_range.insert(
+ TextRange::new(
+ TextSize::of(&self.out),
+ TextSize::of(&self.out) + TextSize::of(&matched_text),
+ ),
+ token.clone(),
+ );
+ self.out.push_str(&matched_text);
+ if needs_parenthesis {
+ self.out.push(')');
+ }
+ } else {
+ // We validated that all placeholder references were valid before we
+ // started, so this shouldn't happen.
+ panic!(
+ "Internal error: replacement referenced unknown placeholder {}",
+ placeholder.ident
+ );
+ }
+ } else {
+ self.out.push_str(token.text());
+ }
+ }
+
+ // Checks if the resulting code, when parsed doesn't split any placeholders due to different
+ // order of operations between the search pattern and the replacement template. If any do, then
+ // we rerender the template and wrap the problematic placeholders with parenthesis.
+ fn maybe_rerender_with_extra_parenthesis(&mut self, template: &SyntaxNode) {
+ if let Some(node) = parse_as_kind(&self.out, template.kind()) {
+ self.remove_node_ranges(node);
+ if self.placeholder_tokens_by_range.is_empty() {
+ return;
+ }
+ self.placeholder_tokens_requiring_parenthesis =
+ self.placeholder_tokens_by_range.values().cloned().collect();
+ self.out.clear();
+ self.render_node(template);
+ }
+ }
+
+ fn remove_node_ranges(&mut self, node: SyntaxNode) {
+ self.placeholder_tokens_by_range.remove(&node.text_range());
+ for child in node.children() {
+ self.remove_node_ranges(child);
+ }
+ }
+}
+
+/// Returns whether token is the receiver of a method call. Note, being within the receiver of a
+/// method call doesn't count. e.g. if the token is `$a`, then `$a.foo()` will return true, while
+/// `($a + $b).foo()` or `x.foo($a)` will return false.
+fn token_is_method_call_receiver(token: &SyntaxToken) -> bool {
+ // Find the first method call among the ancestors of `token`, then check if the only token
+ // within the receiver is `token`.
+ if let Some(receiver) = token
+ .parent_ancestors()
+ .find_map(ast::MethodCallExpr::cast)
+ .and_then(|call| call.receiver())
+ {
+ let tokens = receiver.syntax().descendants_with_tokens().filter_map(|node_or_token| {
+ match node_or_token {
+ SyntaxElement::Token(t) => Some(t),
+ _ => None,
+ }
+ });
+ if let Some((only_token,)) = tokens.collect_tuple() {
+ return only_token == *token;
+ }
+ }
+ false
+}
+
+fn parse_as_kind(code: &str, kind: SyntaxKind) -> Option<SyntaxNode> {
+ if ast::Expr::can_cast(kind) {
+ if let Ok(expr) = fragments::expr(code) {
+ return Some(expr);
+ }
+ }
+ if ast::Item::can_cast(kind) {
+ if let Ok(item) = fragments::item(code) {
+ return Some(item);
+ }
+ }
+ None
+}
diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/resolving.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/resolving.rs
new file mode 100644
index 000000000..4731f14f4
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-ssr/src/resolving.rs
@@ -0,0 +1,308 @@
+//! This module is responsible for resolving paths within rules.
+
+use hir::AsAssocItem;
+use ide_db::{base_db::FilePosition, FxHashMap};
+use parsing::Placeholder;
+use syntax::{ast, SmolStr, SyntaxKind, SyntaxNode, SyntaxToken};
+
+use crate::{errors::error, parsing, SsrError};
+
+pub(crate) struct ResolutionScope<'db> {
+ scope: hir::SemanticsScope<'db>,
+ node: SyntaxNode,
+}
+
+pub(crate) struct ResolvedRule {
+ pub(crate) pattern: ResolvedPattern,
+ pub(crate) template: Option<ResolvedPattern>,
+ pub(crate) index: usize,
+}
+
+pub(crate) struct ResolvedPattern {
+ pub(crate) placeholders_by_stand_in: FxHashMap<SmolStr, parsing::Placeholder>,
+ pub(crate) node: SyntaxNode,
+ // Paths in `node` that we've resolved.
+ pub(crate) resolved_paths: FxHashMap<SyntaxNode, ResolvedPath>,
+ pub(crate) ufcs_function_calls: FxHashMap<SyntaxNode, UfcsCallInfo>,
+ pub(crate) contains_self: bool,
+}
+
+pub(crate) struct ResolvedPath {
+ pub(crate) resolution: hir::PathResolution,
+ /// The depth of the ast::Path that was resolved within the pattern.
+ pub(crate) depth: u32,
+}
+
+pub(crate) struct UfcsCallInfo {
+ pub(crate) call_expr: ast::CallExpr,
+ pub(crate) function: hir::Function,
+ pub(crate) qualifier_type: Option<hir::Type>,
+}
+
+impl ResolvedRule {
+ pub(crate) fn new(
+ rule: parsing::ParsedRule,
+ resolution_scope: &ResolutionScope<'_>,
+ index: usize,
+ ) -> Result<ResolvedRule, SsrError> {
+ let resolver =
+ Resolver { resolution_scope, placeholders_by_stand_in: rule.placeholders_by_stand_in };
+ let resolved_template = match rule.template {
+ Some(template) => Some(resolver.resolve_pattern_tree(template)?),
+ None => None,
+ };
+ Ok(ResolvedRule {
+ pattern: resolver.resolve_pattern_tree(rule.pattern)?,
+ template: resolved_template,
+ index,
+ })
+ }
+
+ pub(crate) fn get_placeholder(&self, token: &SyntaxToken) -> Option<&Placeholder> {
+ if token.kind() != SyntaxKind::IDENT {
+ return None;
+ }
+ self.pattern.placeholders_by_stand_in.get(token.text())
+ }
+}
+
+struct Resolver<'a, 'db> {
+ resolution_scope: &'a ResolutionScope<'db>,
+ placeholders_by_stand_in: FxHashMap<SmolStr, parsing::Placeholder>,
+}
+
+impl Resolver<'_, '_> {
+ fn resolve_pattern_tree(&self, pattern: SyntaxNode) -> Result<ResolvedPattern, SsrError> {
+ use syntax::ast::AstNode;
+ use syntax::{SyntaxElement, T};
+ let mut resolved_paths = FxHashMap::default();
+ self.resolve(pattern.clone(), 0, &mut resolved_paths)?;
+ let ufcs_function_calls = resolved_paths
+ .iter()
+ .filter_map(|(path_node, resolved)| {
+ if let Some(grandparent) = path_node.parent().and_then(|parent| parent.parent()) {
+ if let Some(call_expr) = ast::CallExpr::cast(grandparent.clone()) {
+ if let hir::PathResolution::Def(hir::ModuleDef::Function(function)) =
+ resolved.resolution
+ {
+ if function.as_assoc_item(self.resolution_scope.scope.db).is_some() {
+ let qualifier_type =
+ self.resolution_scope.qualifier_type(path_node);
+ return Some((
+ grandparent,
+ UfcsCallInfo { call_expr, function, qualifier_type },
+ ));
+ }
+ }
+ }
+ }
+ None
+ })
+ .collect();
+ let contains_self =
+ pattern.descendants_with_tokens().any(|node_or_token| match node_or_token {
+ SyntaxElement::Token(t) => t.kind() == T![self],
+ _ => false,
+ });
+ Ok(ResolvedPattern {
+ node: pattern,
+ resolved_paths,
+ placeholders_by_stand_in: self.placeholders_by_stand_in.clone(),
+ ufcs_function_calls,
+ contains_self,
+ })
+ }
+
+ fn resolve(
+ &self,
+ node: SyntaxNode,
+ depth: u32,
+ resolved_paths: &mut FxHashMap<SyntaxNode, ResolvedPath>,
+ ) -> Result<(), SsrError> {
+ use syntax::ast::AstNode;
+ if let Some(path) = ast::Path::cast(node.clone()) {
+ if is_self(&path) {
+ // Self cannot be resolved like other paths.
+ return Ok(());
+ }
+ // Check if this is an appropriate place in the path to resolve. If the path is
+ // something like `a::B::<i32>::c` then we want to resolve `a::B`. If the path contains
+ // a placeholder. e.g. `a::$b::c` then we want to resolve `a`.
+ if !path_contains_type_arguments(path.qualifier())
+ && !self.path_contains_placeholder(&path)
+ {
+ let resolution = self
+ .resolution_scope
+ .resolve_path(&path)
+ .ok_or_else(|| error!("Failed to resolve path `{}`", node.text()))?;
+ if self.ok_to_use_path_resolution(&resolution) {
+ resolved_paths.insert(node, ResolvedPath { resolution, depth });
+ return Ok(());
+ }
+ }
+ }
+ for node in node.children() {
+ self.resolve(node, depth + 1, resolved_paths)?;
+ }
+ Ok(())
+ }
+
+ /// Returns whether `path` contains a placeholder, but ignores any placeholders within type
+ /// arguments.
+ fn path_contains_placeholder(&self, path: &ast::Path) -> bool {
+ if let Some(segment) = path.segment() {
+ if let Some(name_ref) = segment.name_ref() {
+ if self.placeholders_by_stand_in.contains_key(name_ref.text().as_str()) {
+ return true;
+ }
+ }
+ }
+ if let Some(qualifier) = path.qualifier() {
+ return self.path_contains_placeholder(&qualifier);
+ }
+ false
+ }
+
+ fn ok_to_use_path_resolution(&self, resolution: &hir::PathResolution) -> bool {
+ match resolution {
+ hir::PathResolution::Def(hir::ModuleDef::Function(function))
+ if function.as_assoc_item(self.resolution_scope.scope.db).is_some() =>
+ {
+ if function.self_param(self.resolution_scope.scope.db).is_some() {
+ // If we don't use this path resolution, then we won't be able to match method
+ // calls. e.g. `Foo::bar($s)` should match `x.bar()`.
+ true
+ } else {
+ cov_mark::hit!(replace_associated_trait_default_function_call);
+ false
+ }
+ }
+ hir::PathResolution::Def(
+ def @ (hir::ModuleDef::Const(_) | hir::ModuleDef::TypeAlias(_)),
+ ) if def.as_assoc_item(self.resolution_scope.scope.db).is_some() => {
+ // Not a function. Could be a constant or an associated type.
+ cov_mark::hit!(replace_associated_trait_constant);
+ false
+ }
+ _ => true,
+ }
+ }
+}
+
+impl<'db> ResolutionScope<'db> {
+ pub(crate) fn new(
+ sema: &hir::Semantics<'db, ide_db::RootDatabase>,
+ resolve_context: FilePosition,
+ ) -> Option<ResolutionScope<'db>> {
+ use syntax::ast::AstNode;
+ let file = sema.parse(resolve_context.file_id);
+ // Find a node at the requested position, falling back to the whole file.
+ let node = file
+ .syntax()
+ .token_at_offset(resolve_context.offset)
+ .left_biased()
+ .and_then(|token| token.parent())
+ .unwrap_or_else(|| file.syntax().clone());
+ let node = pick_node_for_resolution(node);
+ let scope = sema.scope(&node)?;
+ Some(ResolutionScope { scope, node })
+ }
+
+ /// Returns the function in which SSR was invoked, if any.
+ pub(crate) fn current_function(&self) -> Option<SyntaxNode> {
+ self.node.ancestors().find(|node| node.kind() == SyntaxKind::FN)
+ }
+
+ fn resolve_path(&self, path: &ast::Path) -> Option<hir::PathResolution> {
+ // First try resolving the whole path. This will work for things like
+ // `std::collections::HashMap`, but will fail for things like
+ // `std::collections::HashMap::new`.
+ if let Some(resolution) = self.scope.speculative_resolve(path) {
+ return Some(resolution);
+ }
+ // Resolution failed, try resolving the qualifier (e.g. `std::collections::HashMap` and if
+ // that succeeds, then iterate through the candidates on the resolved type with the provided
+ // name.
+ let resolved_qualifier = self.scope.speculative_resolve(&path.qualifier()?)?;
+ if let hir::PathResolution::Def(hir::ModuleDef::Adt(adt)) = resolved_qualifier {
+ let name = path.segment()?.name_ref()?;
+ let module = self.scope.module();
+ adt.ty(self.scope.db).iterate_path_candidates(
+ self.scope.db,
+ &self.scope,
+ &self.scope.visible_traits().0,
+ Some(module),
+ None,
+ |assoc_item| {
+ let item_name = assoc_item.name(self.scope.db)?;
+ if item_name.to_smol_str().as_str() == name.text() {
+ Some(hir::PathResolution::Def(assoc_item.into()))
+ } else {
+ None
+ }
+ },
+ )
+ } else {
+ None
+ }
+ }
+
+ fn qualifier_type(&self, path: &SyntaxNode) -> Option<hir::Type> {
+ use syntax::ast::AstNode;
+ if let Some(path) = ast::Path::cast(path.clone()) {
+ if let Some(qualifier) = path.qualifier() {
+ if let Some(hir::PathResolution::Def(hir::ModuleDef::Adt(adt))) =
+ self.resolve_path(&qualifier)
+ {
+ return Some(adt.ty(self.scope.db));
+ }
+ }
+ }
+ None
+ }
+}
+
+fn is_self(path: &ast::Path) -> bool {
+ path.segment().map(|segment| segment.self_token().is_some()).unwrap_or(false)
+}
+
+/// Returns a suitable node for resolving paths in the current scope. If we create a scope based on
+/// a statement node, then we can't resolve local variables that were defined in the current scope
+/// (only in parent scopes). So we find another node, ideally a child of the statement where local
+/// variable resolution is permitted.
+fn pick_node_for_resolution(node: SyntaxNode) -> SyntaxNode {
+ match node.kind() {
+ SyntaxKind::EXPR_STMT => {
+ if let Some(n) = node.first_child() {
+ cov_mark::hit!(cursor_after_semicolon);
+ return n;
+ }
+ }
+ SyntaxKind::LET_STMT | SyntaxKind::IDENT_PAT => {
+ if let Some(next) = node.next_sibling() {
+ return pick_node_for_resolution(next);
+ }
+ }
+ SyntaxKind::NAME => {
+ if let Some(parent) = node.parent() {
+ return pick_node_for_resolution(parent);
+ }
+ }
+ _ => {}
+ }
+ node
+}
+
+/// Returns whether `path` or any of its qualifiers contains type arguments.
+fn path_contains_type_arguments(path: Option<ast::Path>) -> bool {
+ if let Some(path) = path {
+ if let Some(segment) = path.segment() {
+ if segment.generic_arg_list().is_some() {
+ cov_mark::hit!(type_arguments_within_path);
+ return true;
+ }
+ }
+ return path_contains_type_arguments(path.qualifier());
+ }
+ false
+}
diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/search.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/search.rs
new file mode 100644
index 000000000..0a85569b6
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-ssr/src/search.rs
@@ -0,0 +1,289 @@
+//! Searching for matches.
+
+use crate::{
+ matching,
+ resolving::{ResolvedPath, ResolvedPattern, ResolvedRule},
+ Match, MatchFinder,
+};
+use ide_db::{
+ base_db::{FileId, FileRange},
+ defs::Definition,
+ search::{SearchScope, UsageSearchResult},
+ FxHashSet,
+};
+use syntax::{ast, AstNode, SyntaxKind, SyntaxNode};
+
+/// A cache for the results of find_usages. This is for when we have multiple patterns that have the
+/// same path. e.g. if the pattern was `foo::Bar` that can parse as a path, an expression, a type
+/// and as a pattern. In each, the usages of `foo::Bar` are the same and we'd like to avoid finding
+/// them more than once.
+#[derive(Default)]
+pub(crate) struct UsageCache {
+ usages: Vec<(Definition, UsageSearchResult)>,
+}
+
+impl<'db> MatchFinder<'db> {
+ /// Adds all matches for `rule` to `matches_out`. Matches may overlap in ways that make
+ /// replacement impossible, so further processing is required in order to properly nest matches
+ /// and remove overlapping matches. This is done in the `nesting` module.
+ pub(crate) fn find_matches_for_rule(
+ &self,
+ rule: &ResolvedRule,
+ usage_cache: &mut UsageCache,
+ matches_out: &mut Vec<Match>,
+ ) {
+ if rule.pattern.contains_self {
+ // If the pattern contains `self` we restrict the scope of the search to just the
+ // current method. No other method can reference the same `self`. This makes the
+ // behavior of `self` consistent with other variables.
+ if let Some(current_function) = self.resolution_scope.current_function() {
+ self.slow_scan_node(&current_function, rule, &None, matches_out);
+ }
+ return;
+ }
+ if pick_path_for_usages(&rule.pattern).is_none() {
+ self.slow_scan(rule, matches_out);
+ return;
+ }
+ self.find_matches_for_pattern_tree(rule, &rule.pattern, usage_cache, matches_out);
+ }
+
+ fn find_matches_for_pattern_tree(
+ &self,
+ rule: &ResolvedRule,
+ pattern: &ResolvedPattern,
+ usage_cache: &mut UsageCache,
+ matches_out: &mut Vec<Match>,
+ ) {
+ if let Some(resolved_path) = pick_path_for_usages(pattern) {
+ let definition: Definition = resolved_path.resolution.clone().into();
+ for file_range in self.find_usages(usage_cache, definition).file_ranges() {
+ for node_to_match in self.find_nodes_to_match(resolved_path, file_range) {
+ if !is_search_permitted_ancestors(&node_to_match) {
+ cov_mark::hit!(use_declaration_with_braces);
+ continue;
+ }
+ self.try_add_match(rule, &node_to_match, &None, matches_out);
+ }
+ }
+ }
+ }
+
+ fn find_nodes_to_match(
+ &self,
+ resolved_path: &ResolvedPath,
+ file_range: FileRange,
+ ) -> Vec<SyntaxNode> {
+ let file = self.sema.parse(file_range.file_id);
+ let depth = resolved_path.depth as usize;
+ let offset = file_range.range.start();
+
+ let mut paths = self
+ .sema
+ .find_nodes_at_offset_with_descend::<ast::Path>(file.syntax(), offset)
+ .peekable();
+
+ if paths.peek().is_some() {
+ paths
+ .filter_map(|path| {
+ self.sema.ancestors_with_macros(path.syntax().clone()).nth(depth)
+ })
+ .collect::<Vec<_>>()
+ } else {
+ self.sema
+ .find_nodes_at_offset_with_descend::<ast::MethodCallExpr>(file.syntax(), offset)
+ .filter_map(|path| {
+ // If the pattern contained a path and we found a reference to that path that wasn't
+ // itself a path, but was a method call, then we need to adjust how far up to try
+ // matching by how deep the path was within a CallExpr. The structure would have been
+ // CallExpr, PathExpr, Path - i.e. a depth offset of 2. We don't need to check if the
+ // path was part of a CallExpr because if it wasn't then all that will happen is we'll
+ // fail to match, which is the desired behavior.
+ const PATH_DEPTH_IN_CALL_EXPR: usize = 2;
+ if depth < PATH_DEPTH_IN_CALL_EXPR {
+ return None;
+ }
+ self.sema
+ .ancestors_with_macros(path.syntax().clone())
+ .nth(depth - PATH_DEPTH_IN_CALL_EXPR)
+ })
+ .collect::<Vec<_>>()
+ }
+ }
+
+ fn find_usages<'a>(
+ &self,
+ usage_cache: &'a mut UsageCache,
+ definition: Definition,
+ ) -> &'a UsageSearchResult {
+ // Logically if a lookup succeeds we should just return it. Unfortunately returning it would
+ // extend the lifetime of the borrow, then we wouldn't be able to do the insertion on a
+ // cache miss. This is a limitation of NLL and is fixed with Polonius. For now we do two
+ // lookups in the case of a cache hit.
+ if usage_cache.find(&definition).is_none() {
+ let usages = definition.usages(&self.sema).in_scope(self.search_scope()).all();
+ usage_cache.usages.push((definition, usages));
+ return &usage_cache.usages.last().unwrap().1;
+ }
+ usage_cache.find(&definition).unwrap()
+ }
+
+ /// Returns the scope within which we want to search. We don't want un unrestricted search
+ /// scope, since we don't want to find references in external dependencies.
+ fn search_scope(&self) -> SearchScope {
+ // FIXME: We should ideally have a test that checks that we edit local roots and not library
+ // roots. This probably would require some changes to fixtures, since currently everything
+ // seems to get put into a single source root.
+ let mut files = Vec::new();
+ self.search_files_do(|file_id| {
+ files.push(file_id);
+ });
+ SearchScope::files(&files)
+ }
+
+ fn slow_scan(&self, rule: &ResolvedRule, matches_out: &mut Vec<Match>) {
+ self.search_files_do(|file_id| {
+ let file = self.sema.parse(file_id);
+ let code = file.syntax();
+ self.slow_scan_node(code, rule, &None, matches_out);
+ })
+ }
+
+ fn search_files_do(&self, mut callback: impl FnMut(FileId)) {
+ if self.restrict_ranges.is_empty() {
+ // Unrestricted search.
+ use ide_db::base_db::SourceDatabaseExt;
+ use ide_db::symbol_index::SymbolsDatabase;
+ for &root in self.sema.db.local_roots().iter() {
+ let sr = self.sema.db.source_root(root);
+ for file_id in sr.iter() {
+ callback(file_id);
+ }
+ }
+ } else {
+ // Search is restricted, deduplicate file IDs (generally only one).
+ let mut files = FxHashSet::default();
+ for range in &self.restrict_ranges {
+ if files.insert(range.file_id) {
+ callback(range.file_id);
+ }
+ }
+ }
+ }
+
+ fn slow_scan_node(
+ &self,
+ code: &SyntaxNode,
+ rule: &ResolvedRule,
+ restrict_range: &Option<FileRange>,
+ matches_out: &mut Vec<Match>,
+ ) {
+ if !is_search_permitted(code) {
+ return;
+ }
+ self.try_add_match(rule, code, restrict_range, matches_out);
+ // If we've got a macro call, we already tried matching it pre-expansion, which is the only
+ // way to match the whole macro, now try expanding it and matching the expansion.
+ if let Some(macro_call) = ast::MacroCall::cast(code.clone()) {
+ if let Some(expanded) = self.sema.expand(&macro_call) {
+ if let Some(tt) = macro_call.token_tree() {
+ // When matching within a macro expansion, we only want to allow matches of
+ // nodes that originated entirely from within the token tree of the macro call.
+ // i.e. we don't want to match something that came from the macro itself.
+ self.slow_scan_node(
+ &expanded,
+ rule,
+ &Some(self.sema.original_range(tt.syntax())),
+ matches_out,
+ );
+ }
+ }
+ }
+ for child in code.children() {
+ self.slow_scan_node(&child, rule, restrict_range, matches_out);
+ }
+ }
+
+ fn try_add_match(
+ &self,
+ rule: &ResolvedRule,
+ code: &SyntaxNode,
+ restrict_range: &Option<FileRange>,
+ matches_out: &mut Vec<Match>,
+ ) {
+ if !self.within_range_restrictions(code) {
+ cov_mark::hit!(replace_nonpath_within_selection);
+ return;
+ }
+ if let Ok(m) = matching::get_match(false, rule, code, restrict_range, &self.sema) {
+ matches_out.push(m);
+ }
+ }
+
+ /// Returns whether `code` is within one of our range restrictions if we have any. No range
+ /// restrictions is considered unrestricted and always returns true.
+ fn within_range_restrictions(&self, code: &SyntaxNode) -> bool {
+ if self.restrict_ranges.is_empty() {
+ // There is no range restriction.
+ return true;
+ }
+ let node_range = self.sema.original_range(code);
+ for range in &self.restrict_ranges {
+ if range.file_id == node_range.file_id && range.range.contains_range(node_range.range) {
+ return true;
+ }
+ }
+ false
+ }
+}
+
+/// Returns whether we support matching within `node` and all of its ancestors.
+fn is_search_permitted_ancestors(node: &SyntaxNode) -> bool {
+ if let Some(parent) = node.parent() {
+ if !is_search_permitted_ancestors(&parent) {
+ return false;
+ }
+ }
+ is_search_permitted(node)
+}
+
+/// Returns whether we support matching within this kind of node.
+fn is_search_permitted(node: &SyntaxNode) -> bool {
+ // FIXME: Properly handle use declarations. At the moment, if our search pattern is `foo::bar`
+ // and the code is `use foo::{baz, bar}`, we'll match `bar`, since it resolves to `foo::bar`.
+ // However we'll then replace just the part we matched `bar`. We probably need to instead remove
+ // `bar` and insert a new use declaration.
+ node.kind() != SyntaxKind::USE
+}
+
+impl UsageCache {
+ fn find(&mut self, definition: &Definition) -> Option<&UsageSearchResult> {
+ // We expect a very small number of cache entries (generally 1), so a linear scan should be
+ // fast enough and avoids the need to implement Hash for Definition.
+ for (d, refs) in &self.usages {
+ if d == definition {
+ return Some(refs);
+ }
+ }
+ None
+ }
+}
+
+/// Returns a path that's suitable for path resolution. We exclude builtin types, since they aren't
+/// something that we can find references to. We then somewhat arbitrarily pick the path that is the
+/// longest as this is hopefully more likely to be less common, making it faster to find.
+fn pick_path_for_usages(pattern: &ResolvedPattern) -> Option<&ResolvedPath> {
+ // FIXME: Take the scope of the resolved path into account. e.g. if there are any paths that are
+ // private to the current module, then we definitely would want to pick them over say a path
+ // from std. Possibly we should go further than this and intersect the search scopes for all
+ // resolved paths then search only in that scope.
+ pattern
+ .resolved_paths
+ .iter()
+ .filter(|(_, p)| {
+ !matches!(p.resolution, hir::PathResolution::Def(hir::ModuleDef::BuiltinType(_)))
+ })
+ .map(|(node, resolved)| (node.text().len(), resolved))
+ .max_by(|(a, _), (b, _)| a.cmp(b))
+ .map(|(_, resolved)| resolved)
+}
diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/tests.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/tests.rs
new file mode 100644
index 000000000..1ecb7aa9a
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-ssr/src/tests.rs
@@ -0,0 +1,1397 @@
+use expect_test::{expect, Expect};
+use ide_db::{
+ base_db::{salsa::Durability, FileId, FilePosition, FileRange, SourceDatabaseExt},
+ FxHashSet,
+};
+use std::sync::Arc;
+use test_utils::RangeOrOffset;
+
+use crate::{MatchFinder, SsrRule};
+
+fn parse_error_text(query: &str) -> String {
+ format!("{}", query.parse::<SsrRule>().unwrap_err())
+}
+
+#[test]
+fn parser_empty_query() {
+ assert_eq!(parse_error_text(""), "Parse error: Cannot find delimiter `==>>`");
+}
+
+#[test]
+fn parser_no_delimiter() {
+ assert_eq!(parse_error_text("foo()"), "Parse error: Cannot find delimiter `==>>`");
+}
+
+#[test]
+fn parser_two_delimiters() {
+ assert_eq!(
+ parse_error_text("foo() ==>> a ==>> b "),
+ "Parse error: More than one delimiter found"
+ );
+}
+
+#[test]
+fn parser_repeated_name() {
+ assert_eq!(
+ parse_error_text("foo($a, $a) ==>>"),
+ "Parse error: Placeholder `$a` repeats more than once"
+ );
+}
+
+#[test]
+fn parser_invalid_pattern() {
+ assert_eq!(
+ parse_error_text(" ==>> ()"),
+ "Parse error: Not a valid Rust expression, type, item, path or pattern"
+ );
+}
+
+#[test]
+fn parser_invalid_template() {
+ assert_eq!(
+ parse_error_text("() ==>> )"),
+ "Parse error: Not a valid Rust expression, type, item, path or pattern"
+ );
+}
+
+#[test]
+fn parser_undefined_placeholder_in_replacement() {
+ assert_eq!(
+ parse_error_text("42 ==>> $a"),
+ "Parse error: Replacement contains undefined placeholders: $a"
+ );
+}
+
+/// `code` may optionally contain a cursor marker `$0`. If it doesn't, then the position will be
+/// the start of the file. If there's a second cursor marker, then we'll return a single range.
+pub(crate) fn single_file(code: &str) -> (ide_db::RootDatabase, FilePosition, Vec<FileRange>) {
+ use ide_db::base_db::fixture::WithFixture;
+ use ide_db::symbol_index::SymbolsDatabase;
+ let (mut db, file_id, range_or_offset) = if code.contains(test_utils::CURSOR_MARKER) {
+ ide_db::RootDatabase::with_range_or_offset(code)
+ } else {
+ let (db, file_id) = ide_db::RootDatabase::with_single_file(code);
+ (db, file_id, RangeOrOffset::Offset(0.into()))
+ };
+ let selections;
+ let position;
+ match range_or_offset {
+ RangeOrOffset::Range(range) => {
+ position = FilePosition { file_id, offset: range.start() };
+ selections = vec![FileRange { file_id, range }];
+ }
+ RangeOrOffset::Offset(offset) => {
+ position = FilePosition { file_id, offset };
+ selections = vec![];
+ }
+ }
+ let mut local_roots = FxHashSet::default();
+ local_roots.insert(ide_db::base_db::fixture::WORKSPACE);
+ db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH);
+ (db, position, selections)
+}
+
+fn assert_ssr_transform(rule: &str, input: &str, expected: Expect) {
+ assert_ssr_transforms(&[rule], input, expected);
+}
+
+fn assert_ssr_transforms(rules: &[&str], input: &str, expected: Expect) {
+ let (db, position, selections) = single_file(input);
+ let mut match_finder = MatchFinder::in_context(&db, position, selections).unwrap();
+ for rule in rules {
+ let rule: SsrRule = rule.parse().unwrap();
+ match_finder.add_rule(rule).unwrap();
+ }
+ let edits = match_finder.edits();
+ if edits.is_empty() {
+ panic!("No edits were made");
+ }
+ // Note, db.file_text is not necessarily the same as `input`, since fixture parsing alters
+ // stuff.
+ let mut actual = db.file_text(position.file_id).to_string();
+ edits[&position.file_id].apply(&mut actual);
+ expected.assert_eq(&actual);
+}
+
+fn print_match_debug_info(match_finder: &MatchFinder<'_>, file_id: FileId, snippet: &str) {
+ let debug_info = match_finder.debug_where_text_equal(file_id, snippet);
+ println!(
+ "Match debug info: {} nodes had text exactly equal to '{}'",
+ debug_info.len(),
+ snippet
+ );
+ for (index, d) in debug_info.iter().enumerate() {
+ println!("Node #{}\n{:#?}\n", index, d);
+ }
+}
+
+fn assert_matches(pattern: &str, code: &str, expected: &[&str]) {
+ let (db, position, selections) = single_file(code);
+ let mut match_finder = MatchFinder::in_context(&db, position, selections).unwrap();
+ match_finder.add_search_pattern(pattern.parse().unwrap()).unwrap();
+ let matched_strings: Vec<String> =
+ match_finder.matches().flattened().matches.iter().map(|m| m.matched_text()).collect();
+ if matched_strings != expected && !expected.is_empty() {
+ print_match_debug_info(&match_finder, position.file_id, expected[0]);
+ }
+ assert_eq!(matched_strings, expected);
+}
+
+fn assert_no_match(pattern: &str, code: &str) {
+ let (db, position, selections) = single_file(code);
+ let mut match_finder = MatchFinder::in_context(&db, position, selections).unwrap();
+ match_finder.add_search_pattern(pattern.parse().unwrap()).unwrap();
+ let matches = match_finder.matches().flattened().matches;
+ if !matches.is_empty() {
+ print_match_debug_info(&match_finder, position.file_id, &matches[0].matched_text());
+ panic!("Got {} matches when we expected none: {:#?}", matches.len(), matches);
+ }
+}
+
+fn assert_match_failure_reason(pattern: &str, code: &str, snippet: &str, expected_reason: &str) {
+ let (db, position, selections) = single_file(code);
+ let mut match_finder = MatchFinder::in_context(&db, position, selections).unwrap();
+ match_finder.add_search_pattern(pattern.parse().unwrap()).unwrap();
+ let mut reasons = Vec::new();
+ for d in match_finder.debug_where_text_equal(position.file_id, snippet) {
+ if let Some(reason) = d.match_failure_reason() {
+ reasons.push(reason.to_owned());
+ }
+ }
+ assert_eq!(reasons, vec![expected_reason]);
+}
+
+#[test]
+fn ssr_let_stmt_in_macro_match() {
+ assert_matches(
+ "let a = 0",
+ r#"
+ macro_rules! m1 { ($a:stmt) => {$a}; }
+ fn f() {m1!{ let a = 0 };}"#,
+ // FIXME: Whitespace is not part of the matched block
+ &["leta=0"],
+ );
+}
+
+#[test]
+fn ssr_let_stmt_in_fn_match() {
+ assert_matches("let $a = 10;", "fn main() { let x = 10; x }", &["let x = 10;"]);
+ assert_matches("let $a = $b;", "fn main() { let x = 10; x }", &["let x = 10;"]);
+}
+
+#[test]
+fn ssr_block_expr_match() {
+ assert_matches("{ let $a = $b; }", "fn main() { let x = 10; }", &["{ let x = 10; }"]);
+ assert_matches("{ let $a = $b; $c }", "fn main() { let x = 10; x }", &["{ let x = 10; x }"]);
+}
+
+#[test]
+fn ssr_let_stmt_replace() {
+ // Pattern and template with trailing semicolon
+ assert_ssr_transform(
+ "let $a = $b; ==>> let $a = 11;",
+ "fn main() { let x = 10; x }",
+ expect![["fn main() { let x = 11; x }"]],
+ );
+}
+
+#[test]
+fn ssr_let_stmt_replace_expr() {
+ // Trailing semicolon should be dropped from the new expression
+ assert_ssr_transform(
+ "let $a = $b; ==>> $b",
+ "fn main() { let x = 10; }",
+ expect![["fn main() { 10 }"]],
+ );
+}
+
+#[test]
+fn ssr_blockexpr_replace_stmt_with_stmt() {
+ assert_ssr_transform(
+ "if $a() {$b;} ==>> $b;",
+ "{
+ if foo() {
+ bar();
+ }
+ Ok(())
+}",
+ expect![[r#"{
+ bar();
+ Ok(())
+}"#]],
+ );
+}
+
+#[test]
+fn ssr_blockexpr_match_trailing_expr() {
+ assert_matches(
+ "if $a() {$b;}",
+ "{
+ if foo() {
+ bar();
+ }
+}",
+ &["if foo() {
+ bar();
+ }"],
+ );
+}
+
+#[test]
+fn ssr_blockexpr_replace_trailing_expr_with_stmt() {
+ assert_ssr_transform(
+ "if $a() {$b;} ==>> $b;",
+ "{
+ if foo() {
+ bar();
+ }
+}",
+ expect![["{
+ bar();
+}"]],
+ );
+}
+
+#[test]
+fn ssr_function_to_method() {
+ assert_ssr_transform(
+ "my_function($a, $b) ==>> ($a).my_method($b)",
+ "fn my_function() {} fn main() { loop { my_function( other_func(x, y), z + w) } }",
+ expect![["fn my_function() {} fn main() { loop { (other_func(x, y)).my_method(z + w) } }"]],
+ )
+}
+
+#[test]
+fn ssr_nested_function() {
+ assert_ssr_transform(
+ "foo($a, $b, $c) ==>> bar($c, baz($a, $b))",
+ r#"
+ //- /lib.rs crate:foo
+ fn foo() {}
+ fn bar() {}
+ fn baz() {}
+ fn main { foo (x + value.method(b), x+y-z, true && false) }
+ "#,
+ expect![[r#"
+ fn foo() {}
+ fn bar() {}
+ fn baz() {}
+ fn main { bar(true && false, baz(x + value.method(b), x+y-z)) }
+ "#]],
+ )
+}
+
+#[test]
+fn ssr_expected_spacing() {
+ assert_ssr_transform(
+ "foo($x) + bar() ==>> bar($x)",
+ "fn foo() {} fn bar() {} fn main() { foo(5) + bar() }",
+ expect![["fn foo() {} fn bar() {} fn main() { bar(5) }"]],
+ );
+}
+
+#[test]
+fn ssr_with_extra_space() {
+ assert_ssr_transform(
+ "foo($x ) + bar() ==>> bar($x)",
+ "fn foo() {} fn bar() {} fn main() { foo( 5 ) +bar( ) }",
+ expect![["fn foo() {} fn bar() {} fn main() { bar(5) }"]],
+ );
+}
+
+#[test]
+fn ssr_keeps_nested_comment() {
+ assert_ssr_transform(
+ "foo($x) ==>> bar($x)",
+ "fn foo() {} fn bar() {} fn main() { foo(other(5 /* using 5 */)) }",
+ expect![["fn foo() {} fn bar() {} fn main() { bar(other(5 /* using 5 */)) }"]],
+ )
+}
+
+#[test]
+fn ssr_keeps_comment() {
+ assert_ssr_transform(
+ "foo($x) ==>> bar($x)",
+ "fn foo() {} fn bar() {} fn main() { foo(5 /* using 5 */) }",
+ expect![["fn foo() {} fn bar() {} fn main() { bar(5)/* using 5 */ }"]],
+ )
+}
+
+#[test]
+fn ssr_struct_lit() {
+ assert_ssr_transform(
+ "Foo{a: $a, b: $b} ==>> Foo::new($a, $b)",
+ r#"
+ struct Foo() {}
+ impl Foo { fn new() {} }
+ fn main() { Foo{b:2, a:1} }
+ "#,
+ expect![[r#"
+ struct Foo() {}
+ impl Foo { fn new() {} }
+ fn main() { Foo::new(1, 2) }
+ "#]],
+ )
+}
+
+#[test]
+fn ssr_struct_def() {
+ assert_ssr_transform(
+ "struct Foo { $f: $t } ==>> struct Foo($t);",
+ r#"struct Foo { field: i32 }"#,
+ expect![[r#"struct Foo(i32);"#]],
+ )
+}
+
+#[test]
+fn ignores_whitespace() {
+ assert_matches("1+2", "fn f() -> i32 {1 + 2}", &["1 + 2"]);
+ assert_matches("1 + 2", "fn f() -> i32 {1+2}", &["1+2"]);
+}
+
+#[test]
+fn no_match() {
+ assert_no_match("1 + 3", "fn f() -> i32 {1 + 2}");
+}
+
+#[test]
+fn match_fn_definition() {
+ assert_matches("fn $a($b: $t) {$c}", "fn f(a: i32) {bar()}", &["fn f(a: i32) {bar()}"]);
+}
+
+#[test]
+fn match_struct_definition() {
+ let code = r#"
+ struct Option<T> {}
+ struct Bar {}
+ struct Foo {name: Option<String>}"#;
+ assert_matches("struct $n {$f: Option<String>}", code, &["struct Foo {name: Option<String>}"]);
+}
+
+#[test]
+fn match_expr() {
+ let code = r#"
+ fn foo() {}
+ fn f() -> i32 {foo(40 + 2, 42)}"#;
+ assert_matches("foo($a, $b)", code, &["foo(40 + 2, 42)"]);
+ assert_no_match("foo($a, $b, $c)", code);
+ assert_no_match("foo($a)", code);
+}
+
+#[test]
+fn match_nested_method_calls() {
+ assert_matches(
+ "$a.z().z().z()",
+ "fn f() {h().i().j().z().z().z().d().e()}",
+ &["h().i().j().z().z().z()"],
+ );
+}
+
+// Make sure that our node matching semantics don't differ within macro calls.
+#[test]
+fn match_nested_method_calls_with_macro_call() {
+ assert_matches(
+ "$a.z().z().z()",
+ r#"
+ macro_rules! m1 { ($a:expr) => {$a}; }
+ fn f() {m1!(h().i().j().z().z().z().d().e())}"#,
+ &["h().i().j().z().z().z()"],
+ );
+}
+
+#[test]
+fn match_complex_expr() {
+ let code = r#"
+ fn foo() {} fn bar() {}
+ fn f() -> i32 {foo(bar(40, 2), 42)}"#;
+ assert_matches("foo($a, $b)", code, &["foo(bar(40, 2), 42)"]);
+ assert_no_match("foo($a, $b, $c)", code);
+ assert_no_match("foo($a)", code);
+ assert_matches("bar($a, $b)", code, &["bar(40, 2)"]);
+}
+
+// Trailing commas in the code should be ignored.
+#[test]
+fn match_with_trailing_commas() {
+ // Code has comma, pattern doesn't.
+ assert_matches("foo($a, $b)", "fn foo() {} fn f() {foo(1, 2,);}", &["foo(1, 2,)"]);
+ assert_matches("Foo{$a, $b}", "struct Foo {} fn f() {Foo{1, 2,};}", &["Foo{1, 2,}"]);
+
+ // Pattern has comma, code doesn't.
+ assert_matches("foo($a, $b,)", "fn foo() {} fn f() {foo(1, 2);}", &["foo(1, 2)"]);
+ assert_matches("Foo{$a, $b,}", "struct Foo {} fn f() {Foo{1, 2};}", &["Foo{1, 2}"]);
+}
+
+#[test]
+fn match_type() {
+ assert_matches("i32", "fn f() -> i32 {1 + 2}", &["i32"]);
+ assert_matches(
+ "Option<$a>",
+ "struct Option<T> {} fn f() -> Option<i32> {42}",
+ &["Option<i32>"],
+ );
+ assert_no_match(
+ "Option<$a>",
+ "struct Option<T> {} struct Result<T, E> {} fn f() -> Result<i32, ()> {42}",
+ );
+}
+
+#[test]
+fn match_struct_instantiation() {
+ let code = r#"
+ struct Foo {bar: i32, baz: i32}
+ fn f() {Foo {bar: 1, baz: 2}}"#;
+ assert_matches("Foo {bar: 1, baz: 2}", code, &["Foo {bar: 1, baz: 2}"]);
+ // Now with placeholders for all parts of the struct.
+ assert_matches("Foo {$a: $b, $c: $d}", code, &["Foo {bar: 1, baz: 2}"]);
+ assert_matches("Foo {}", "struct Foo {} fn f() {Foo {}}", &["Foo {}"]);
+}
+
+#[test]
+fn match_path() {
+ let code = r#"
+ mod foo {
+ pub fn bar() {}
+ }
+ fn f() {foo::bar(42)}"#;
+ assert_matches("foo::bar", code, &["foo::bar"]);
+ assert_matches("$a::bar", code, &["foo::bar"]);
+ assert_matches("foo::$b", code, &["foo::bar"]);
+}
+
+#[test]
+fn match_pattern() {
+ assert_matches("Some($a)", "struct Some(); fn f() {if let Some(x) = foo() {}}", &["Some(x)"]);
+}
+
+// If our pattern has a full path, e.g. a::b::c() and the code has c(), but c resolves to
+// a::b::c, then we should match.
+#[test]
+fn match_fully_qualified_fn_path() {
+ let code = r#"
+ mod a {
+ pub mod b {
+ pub fn c(_: i32) {}
+ }
+ }
+ use a::b::c;
+ fn f1() {
+ c(42);
+ }
+ "#;
+ assert_matches("a::b::c($a)", code, &["c(42)"]);
+}
+
+#[test]
+fn match_resolved_type_name() {
+ let code = r#"
+ mod m1 {
+ pub mod m2 {
+ pub trait Foo<T> {}
+ }
+ }
+ mod m3 {
+ trait Foo<T> {}
+ fn f1(f: Option<&dyn Foo<bool>>) {}
+ }
+ mod m4 {
+ use crate::m1::m2::Foo;
+ fn f1(f: Option<&dyn Foo<i32>>) {}
+ }
+ "#;
+ assert_matches("m1::m2::Foo<$t>", code, &["Foo<i32>"]);
+}
+
+#[test]
+fn type_arguments_within_path() {
+ cov_mark::check!(type_arguments_within_path);
+ let code = r#"
+ mod foo {
+ pub struct Bar<T> {t: T}
+ impl<T> Bar<T> {
+ pub fn baz() {}
+ }
+ }
+ fn f1() {foo::Bar::<i32>::baz();}
+ "#;
+ assert_no_match("foo::Bar::<i64>::baz()", code);
+ assert_matches("foo::Bar::<i32>::baz()", code, &["foo::Bar::<i32>::baz()"]);
+}
+
+#[test]
+fn literal_constraint() {
+ cov_mark::check!(literal_constraint);
+ let code = r#"
+ enum Option<T> { Some(T), None }
+ use Option::Some;
+ fn f1() {
+ let x1 = Some(42);
+ let x2 = Some("foo");
+ let x3 = Some(x1);
+ let x4 = Some(40 + 2);
+ let x5 = Some(true);
+ }
+ "#;
+ assert_matches("Some(${a:kind(literal)})", code, &["Some(42)", "Some(\"foo\")", "Some(true)"]);
+ assert_matches("Some(${a:not(kind(literal))})", code, &["Some(x1)", "Some(40 + 2)"]);
+}
+
+#[test]
+fn match_reordered_struct_instantiation() {
+ assert_matches(
+ "Foo {aa: 1, b: 2, ccc: 3}",
+ "struct Foo {} fn f() {Foo {b: 2, ccc: 3, aa: 1}}",
+ &["Foo {b: 2, ccc: 3, aa: 1}"],
+ );
+ assert_no_match("Foo {a: 1}", "struct Foo {} fn f() {Foo {b: 1}}");
+ assert_no_match("Foo {a: 1}", "struct Foo {} fn f() {Foo {a: 2}}");
+ assert_no_match("Foo {a: 1, b: 2}", "struct Foo {} fn f() {Foo {a: 1}}");
+ assert_no_match("Foo {a: 1, b: 2}", "struct Foo {} fn f() {Foo {b: 2}}");
+ assert_no_match("Foo {a: 1, }", "struct Foo {} fn f() {Foo {a: 1, b: 2}}");
+ assert_no_match("Foo {a: 1, z: 9}", "struct Foo {} fn f() {Foo {a: 1}}");
+}
+
+#[test]
+fn match_macro_invocation() {
+ assert_matches(
+ "foo!($a)",
+ "macro_rules! foo {() => {}} fn() {foo(foo!(foo()))}",
+ &["foo!(foo())"],
+ );
+ assert_matches(
+ "foo!(41, $a, 43)",
+ "macro_rules! foo {() => {}} fn() {foo!(41, 42, 43)}",
+ &["foo!(41, 42, 43)"],
+ );
+ assert_no_match("foo!(50, $a, 43)", "macro_rules! foo {() => {}} fn() {foo!(41, 42, 43}");
+ assert_no_match("foo!(41, $a, 50)", "macro_rules! foo {() => {}} fn() {foo!(41, 42, 43}");
+ assert_matches(
+ "foo!($a())",
+ "macro_rules! foo {() => {}} fn() {foo!(bar())}",
+ &["foo!(bar())"],
+ );
+}
+
+// When matching within a macro expansion, we only allow matches of nodes that originated from
+// the macro call, not from the macro definition.
+#[test]
+fn no_match_expression_from_macro() {
+ assert_no_match(
+ "$a.clone()",
+ r#"
+ macro_rules! m1 {
+ () => {42.clone()}
+ }
+ fn f1() {m1!()}
+ "#,
+ );
+}
+
+// We definitely don't want to allow matching of an expression that part originates from the
+// macro call `42` and part from the macro definition `.clone()`.
+#[test]
+fn no_match_split_expression() {
+ assert_no_match(
+ "$a.clone()",
+ r#"
+ macro_rules! m1 {
+ ($x:expr) => {$x.clone()}
+ }
+ fn f1() {m1!(42)}
+ "#,
+ );
+}
+
+#[test]
+fn replace_function_call() {
+ // This test also makes sure that we ignore empty-ranges.
+ assert_ssr_transform(
+ "foo() ==>> bar()",
+ "fn foo() {$0$0} fn bar() {} fn f1() {foo(); foo();}",
+ expect![["fn foo() {} fn bar() {} fn f1() {bar(); bar();}"]],
+ );
+}
+
+#[test]
+fn replace_function_call_with_placeholders() {
+ assert_ssr_transform(
+ "foo($a, $b) ==>> bar($b, $a)",
+ "fn foo() {} fn bar() {} fn f1() {foo(5, 42)}",
+ expect![["fn foo() {} fn bar() {} fn f1() {bar(42, 5)}"]],
+ );
+}
+
+#[test]
+fn replace_nested_function_calls() {
+ assert_ssr_transform(
+ "foo($a) ==>> bar($a)",
+ "fn foo() {} fn bar() {} fn f1() {foo(foo(42))}",
+ expect![["fn foo() {} fn bar() {} fn f1() {bar(bar(42))}"]],
+ );
+}
+
+#[test]
+fn replace_associated_function_call() {
+ assert_ssr_transform(
+ "Foo::new() ==>> Bar::new()",
+ r#"
+ struct Foo {}
+ impl Foo { fn new() {} }
+ struct Bar {}
+ impl Bar { fn new() {} }
+ fn f1() {Foo::new();}
+ "#,
+ expect![[r#"
+ struct Foo {}
+ impl Foo { fn new() {} }
+ struct Bar {}
+ impl Bar { fn new() {} }
+ fn f1() {Bar::new();}
+ "#]],
+ );
+}
+
+#[test]
+fn replace_associated_trait_default_function_call() {
+ cov_mark::check!(replace_associated_trait_default_function_call);
+ assert_ssr_transform(
+ "Bar2::foo() ==>> Bar2::foo2()",
+ r#"
+ trait Foo { fn foo() {} }
+ pub struct Bar {}
+ impl Foo for Bar {}
+ pub struct Bar2 {}
+ impl Foo for Bar2 {}
+ impl Bar2 { fn foo2() {} }
+ fn main() {
+ Bar::foo();
+ Bar2::foo();
+ }
+ "#,
+ expect![[r#"
+ trait Foo { fn foo() {} }
+ pub struct Bar {}
+ impl Foo for Bar {}
+ pub struct Bar2 {}
+ impl Foo for Bar2 {}
+ impl Bar2 { fn foo2() {} }
+ fn main() {
+ Bar::foo();
+ Bar2::foo2();
+ }
+ "#]],
+ );
+}
+
+#[test]
+fn replace_associated_trait_constant() {
+ cov_mark::check!(replace_associated_trait_constant);
+ assert_ssr_transform(
+ "Bar2::VALUE ==>> Bar2::VALUE_2222",
+ r#"
+ trait Foo { const VALUE: i32; const VALUE_2222: i32; }
+ pub struct Bar {}
+ impl Foo for Bar { const VALUE: i32 = 1; const VALUE_2222: i32 = 2; }
+ pub struct Bar2 {}
+ impl Foo for Bar2 { const VALUE: i32 = 1; const VALUE_2222: i32 = 2; }
+ impl Bar2 { fn foo2() {} }
+ fn main() {
+ Bar::VALUE;
+ Bar2::VALUE;
+ }
+ "#,
+ expect![[r#"
+ trait Foo { const VALUE: i32; const VALUE_2222: i32; }
+ pub struct Bar {}
+ impl Foo for Bar { const VALUE: i32 = 1; const VALUE_2222: i32 = 2; }
+ pub struct Bar2 {}
+ impl Foo for Bar2 { const VALUE: i32 = 1; const VALUE_2222: i32 = 2; }
+ impl Bar2 { fn foo2() {} }
+ fn main() {
+ Bar::VALUE;
+ Bar2::VALUE_2222;
+ }
+ "#]],
+ );
+}
+
+#[test]
+fn replace_path_in_different_contexts() {
+ // Note the $0 inside module a::b which marks the point where the rule is interpreted. We
+ // replace foo with bar, but both need different path qualifiers in different contexts. In f4,
+ // foo is unqualified because of a use statement, however the replacement needs to be fully
+ // qualified.
+ assert_ssr_transform(
+ "c::foo() ==>> c::bar()",
+ r#"
+ mod a {
+ pub mod b {$0
+ pub mod c {
+ pub fn foo() {}
+ pub fn bar() {}
+ fn f1() { foo() }
+ }
+ fn f2() { c::foo() }
+ }
+ fn f3() { b::c::foo() }
+ }
+ use a::b::c::foo;
+ fn f4() { foo() }
+ "#,
+ expect![[r#"
+ mod a {
+ pub mod b {
+ pub mod c {
+ pub fn foo() {}
+ pub fn bar() {}
+ fn f1() { bar() }
+ }
+ fn f2() { c::bar() }
+ }
+ fn f3() { b::c::bar() }
+ }
+ use a::b::c::foo;
+ fn f4() { a::b::c::bar() }
+ "#]],
+ );
+}
+
+#[test]
+fn replace_associated_function_with_generics() {
+ assert_ssr_transform(
+ "c::Foo::<$a>::new() ==>> d::Bar::<$a>::default()",
+ r#"
+ mod c {
+ pub struct Foo<T> {v: T}
+ impl<T> Foo<T> { pub fn new() {} }
+ fn f1() {
+ Foo::<i32>::new();
+ }
+ }
+ mod d {
+ pub struct Bar<T> {v: T}
+ impl<T> Bar<T> { pub fn default() {} }
+ fn f1() {
+ super::c::Foo::<i32>::new();
+ }
+ }
+ "#,
+ expect![[r#"
+ mod c {
+ pub struct Foo<T> {v: T}
+ impl<T> Foo<T> { pub fn new() {} }
+ fn f1() {
+ crate::d::Bar::<i32>::default();
+ }
+ }
+ mod d {
+ pub struct Bar<T> {v: T}
+ impl<T> Bar<T> { pub fn default() {} }
+ fn f1() {
+ Bar::<i32>::default();
+ }
+ }
+ "#]],
+ );
+}
+
+#[test]
+fn replace_type() {
+ assert_ssr_transform(
+ "Result<(), $a> ==>> Option<$a>",
+ "struct Result<T, E> {} struct Option<T> {} fn f1() -> Result<(), Vec<Error>> {foo()}",
+ expect![[
+ "struct Result<T, E> {} struct Option<T> {} fn f1() -> Option<Vec<Error>> {foo()}"
+ ]],
+ );
+ assert_ssr_transform(
+ "dyn Trait<$a> ==>> DynTrait<$a>",
+ r#"
+trait Trait<T> {}
+struct DynTrait<T> {}
+fn f1() -> dyn Trait<Vec<Error>> {foo()}
+"#,
+ expect![[r#"
+trait Trait<T> {}
+struct DynTrait<T> {}
+fn f1() -> DynTrait<Vec<Error>> {foo()}
+"#]],
+ );
+}
+
+#[test]
+fn replace_macro_invocations() {
+ assert_ssr_transform(
+ "try!($a) ==>> $a?",
+ "macro_rules! try {() => {}} fn f1() -> Result<(), E> {bar(try!(foo()));}",
+ expect![["macro_rules! try {() => {}} fn f1() -> Result<(), E> {bar(foo()?);}"]],
+ );
+ // FIXME: Figure out why this doesn't work anymore
+ // assert_ssr_transform(
+ // "foo!($a($b)) ==>> foo($b, $a)",
+ // "macro_rules! foo {() => {}} fn f1() {foo!(abc(def() + 2));}",
+ // expect![["macro_rules! foo {() => {}} fn f1() {foo(def() + 2, abc);}"]],
+ // );
+}
+
+#[test]
+fn replace_binary_op() {
+ assert_ssr_transform(
+ "$a + $b ==>> $b + $a",
+ "fn f() {2 * 3 + 4 * 5}",
+ expect![["fn f() {4 * 5 + 2 * 3}"]],
+ );
+ assert_ssr_transform(
+ "$a + $b ==>> $b + $a",
+ "fn f() {1 + 2 + 3 + 4}",
+ expect![[r#"fn f() {4 + (3 + (2 + 1))}"#]],
+ );
+}
+
+#[test]
+fn match_binary_op() {
+ assert_matches("$a + $b", "fn f() {1 + 2 + 3 + 4}", &["1 + 2", "1 + 2 + 3", "1 + 2 + 3 + 4"]);
+}
+
+#[test]
+fn multiple_rules() {
+ assert_ssr_transforms(
+ &["$a + 1 ==>> add_one($a)", "$a + $b ==>> add($a, $b)"],
+ "fn add() {} fn add_one() {} fn f() -> i32 {3 + 2 + 1}",
+ expect![["fn add() {} fn add_one() {} fn f() -> i32 {add_one(add(3, 2))}"]],
+ )
+}
+
+#[test]
+fn multiple_rules_with_nested_matches() {
+ assert_ssr_transforms(
+ &["foo1($a) ==>> bar1($a)", "foo2($a) ==>> bar2($a)"],
+ r#"
+ fn foo1() {} fn foo2() {} fn bar1() {} fn bar2() {}
+ fn f() {foo1(foo2(foo1(foo2(foo1(42)))))}
+ "#,
+ expect![[r#"
+ fn foo1() {} fn foo2() {} fn bar1() {} fn bar2() {}
+ fn f() {bar1(bar2(bar1(bar2(bar1(42)))))}
+ "#]],
+ )
+}
+
+#[test]
+fn match_within_macro_invocation() {
+ let code = r#"
+ macro_rules! foo {
+ ($a:stmt; $b:expr) => {
+ $b
+ };
+ }
+ struct A {}
+ impl A {
+ fn bar() {}
+ }
+ fn f1() {
+ let aaa = A {};
+ foo!(macro_ignores_this(); aaa.bar());
+ }
+ "#;
+ assert_matches("$a.bar()", code, &["aaa.bar()"]);
+}
+
+#[test]
+fn replace_within_macro_expansion() {
+ assert_ssr_transform(
+ "$a.foo() ==>> bar($a)",
+ r#"
+ macro_rules! macro1 {
+ ($a:expr) => {$a}
+ }
+ fn bar() {}
+ fn f() {macro1!(5.x().foo().o2())}
+ "#,
+ expect![[r#"
+ macro_rules! macro1 {
+ ($a:expr) => {$a}
+ }
+ fn bar() {}
+ fn f() {macro1!(bar(5.x()).o2())}
+ "#]],
+ )
+}
+
+#[test]
+fn replace_outside_and_within_macro_expansion() {
+ assert_ssr_transform(
+ "foo($a) ==>> bar($a)",
+ r#"
+ fn foo() {} fn bar() {}
+ macro_rules! macro1 {
+ ($a:expr) => {$a}
+ }
+ fn f() {foo(foo(macro1!(foo(foo(42)))))}
+ "#,
+ expect![[r#"
+ fn foo() {} fn bar() {}
+ macro_rules! macro1 {
+ ($a:expr) => {$a}
+ }
+ fn f() {bar(bar(macro1!(bar(bar(42)))))}
+ "#]],
+ )
+}
+
+#[test]
+fn preserves_whitespace_within_macro_expansion() {
+ assert_ssr_transform(
+ "$a + $b ==>> $b - $a",
+ r#"
+ macro_rules! macro1 {
+ ($a:expr) => {$a}
+ }
+ fn f() {macro1!(1 * 2 + 3 + 4)}
+ "#,
+ expect![[r#"
+ macro_rules! macro1 {
+ ($a:expr) => {$a}
+ }
+ fn f() {macro1!(4 - (3 - 1 * 2))}
+ "#]],
+ )
+}
+
+#[test]
+fn add_parenthesis_when_necessary() {
+ assert_ssr_transform(
+ "foo($a) ==>> $a.to_string()",
+ r#"
+ fn foo(_: i32) {}
+ fn bar3(v: i32) {
+ foo(1 + 2);
+ foo(-v);
+ }
+ "#,
+ expect![[r#"
+ fn foo(_: i32) {}
+ fn bar3(v: i32) {
+ (1 + 2).to_string();
+ (-v).to_string();
+ }
+ "#]],
+ )
+}
+
+#[test]
+fn match_failure_reasons() {
+ let code = r#"
+ fn bar() {}
+ macro_rules! foo {
+ ($a:expr) => {
+ 1 + $a + 2
+ };
+ }
+ fn f1() {
+ bar(1, 2);
+ foo!(5 + 43.to_string() + 5);
+ }
+ "#;
+ assert_match_failure_reason(
+ "bar($a, 3)",
+ code,
+ "bar(1, 2)",
+ r#"Pattern wanted token '3' (INT_NUMBER), but code had token '2' (INT_NUMBER)"#,
+ );
+ assert_match_failure_reason(
+ "42.to_string()",
+ code,
+ "43.to_string()",
+ r#"Pattern wanted token '42' (INT_NUMBER), but code had token '43' (INT_NUMBER)"#,
+ );
+}
+
+#[test]
+fn overlapping_possible_matches() {
+ // There are three possible matches here, however the middle one, `foo(foo(foo(42)))` shouldn't
+ // match because it overlaps with the outer match. The inner match is permitted since it's is
+ // contained entirely within the placeholder of the outer match.
+ assert_matches(
+ "foo(foo($a))",
+ "fn foo() {} fn main() {foo(foo(foo(foo(42))))}",
+ &["foo(foo(42))", "foo(foo(foo(foo(42))))"],
+ );
+}
+
+#[test]
+fn use_declaration_with_braces() {
+ // It would be OK for a path rule to match and alter a use declaration. We shouldn't mess it up
+ // though. In particular, we must not change `use foo::{baz, bar}` to `use foo::{baz,
+ // foo2::bar2}`.
+ cov_mark::check!(use_declaration_with_braces);
+ assert_ssr_transform(
+ "foo::bar ==>> foo2::bar2",
+ r#"
+ mod foo { pub fn bar() {} pub fn baz() {} }
+ mod foo2 { pub fn bar2() {} }
+ use foo::{baz, bar};
+ fn main() { bar() }
+ "#,
+ expect![["
+ mod foo { pub fn bar() {} pub fn baz() {} }
+ mod foo2 { pub fn bar2() {} }
+ use foo::{baz, bar};
+ fn main() { foo2::bar2() }
+ "]],
+ )
+}
+
+#[test]
+fn ufcs_matches_method_call() {
+ let code = r#"
+ struct Foo {}
+ impl Foo {
+ fn new(_: i32) -> Foo { Foo {} }
+ fn do_stuff(&self, _: i32) {}
+ }
+ struct Bar {}
+ impl Bar {
+ fn new(_: i32) -> Bar { Bar {} }
+ fn do_stuff(&self, v: i32) {}
+ }
+ fn main() {
+ let b = Bar {};
+ let f = Foo {};
+ b.do_stuff(1);
+ f.do_stuff(2);
+ Foo::new(4).do_stuff(3);
+ // Too many / too few args - should never match
+ f.do_stuff(2, 10);
+ f.do_stuff();
+ }
+ "#;
+ assert_matches("Foo::do_stuff($a, $b)", code, &["f.do_stuff(2)", "Foo::new(4).do_stuff(3)"]);
+ // The arguments needs special handling in the case of a function call matching a method call
+ // and the first argument is different.
+ assert_matches("Foo::do_stuff($a, 2)", code, &["f.do_stuff(2)"]);
+ assert_matches("Foo::do_stuff(Foo::new(4), $b)", code, &["Foo::new(4).do_stuff(3)"]);
+
+ assert_ssr_transform(
+ "Foo::do_stuff(Foo::new($a), $b) ==>> Bar::new($b).do_stuff($a)",
+ code,
+ expect![[r#"
+ struct Foo {}
+ impl Foo {
+ fn new(_: i32) -> Foo { Foo {} }
+ fn do_stuff(&self, _: i32) {}
+ }
+ struct Bar {}
+ impl Bar {
+ fn new(_: i32) -> Bar { Bar {} }
+ fn do_stuff(&self, v: i32) {}
+ }
+ fn main() {
+ let b = Bar {};
+ let f = Foo {};
+ b.do_stuff(1);
+ f.do_stuff(2);
+ Bar::new(3).do_stuff(4);
+ // Too many / too few args - should never match
+ f.do_stuff(2, 10);
+ f.do_stuff();
+ }
+ "#]],
+ );
+}
+
+#[test]
+fn pattern_is_a_single_segment_path() {
+ cov_mark::check!(pattern_is_a_single_segment_path);
+ // The first function should not be altered because the `foo` in scope at the cursor position is
+ // a different `foo`. This case is special because "foo" can be parsed as a pattern (IDENT_PAT ->
+ // NAME -> IDENT), which contains no path. If we're not careful we'll end up matching the `foo`
+ // in `let foo` from the first function. Whether we should match the `let foo` in the second
+ // function is less clear. At the moment, we don't. Doing so sounds like a rename operation,
+ // which isn't really what SSR is for, especially since the replacement `bar` must be able to be
+ // resolved, which means if we rename `foo` we'll get a name collision.
+ assert_ssr_transform(
+ "foo ==>> bar",
+ r#"
+ fn f1() -> i32 {
+ let foo = 1;
+ let bar = 2;
+ foo
+ }
+ fn f1() -> i32 {
+ let foo = 1;
+ let bar = 2;
+ foo$0
+ }
+ "#,
+ expect![[r#"
+ fn f1() -> i32 {
+ let foo = 1;
+ let bar = 2;
+ foo
+ }
+ fn f1() -> i32 {
+ let foo = 1;
+ let bar = 2;
+ bar
+ }
+ "#]],
+ );
+}
+
+#[test]
+fn replace_local_variable_reference() {
+ // The pattern references a local variable `foo` in the block containing the cursor. We should
+ // only replace references to this variable `foo`, not other variables that just happen to have
+ // the same name.
+ cov_mark::check!(cursor_after_semicolon);
+ assert_ssr_transform(
+ "foo + $a ==>> $a - foo",
+ r#"
+ fn bar1() -> i32 {
+ let mut res = 0;
+ let foo = 5;
+ res += foo + 1;
+ let foo = 10;
+ res += foo + 2;$0
+ res += foo + 3;
+ let foo = 15;
+ res += foo + 4;
+ res
+ }
+ "#,
+ expect![[r#"
+ fn bar1() -> i32 {
+ let mut res = 0;
+ let foo = 5;
+ res += foo + 1;
+ let foo = 10;
+ res += 2 - foo;
+ res += 3 - foo;
+ let foo = 15;
+ res += foo + 4;
+ res
+ }
+ "#]],
+ )
+}
+
+#[test]
+fn replace_path_within_selection() {
+ assert_ssr_transform(
+ "foo ==>> bar",
+ r#"
+ fn main() {
+ let foo = 41;
+ let bar = 42;
+ do_stuff(foo);
+ do_stuff(foo);$0
+ do_stuff(foo);
+ do_stuff(foo);$0
+ do_stuff(foo);
+ }"#,
+ expect![[r#"
+ fn main() {
+ let foo = 41;
+ let bar = 42;
+ do_stuff(foo);
+ do_stuff(foo);
+ do_stuff(bar);
+ do_stuff(bar);
+ do_stuff(foo);
+ }"#]],
+ );
+}
+
+#[test]
+fn replace_nonpath_within_selection() {
+ cov_mark::check!(replace_nonpath_within_selection);
+ assert_ssr_transform(
+ "$a + $b ==>> $b * $a",
+ r#"
+ fn main() {
+ let v = 1 + 2;$0
+ let v2 = 3 + 3;
+ let v3 = 4 + 5;$0
+ let v4 = 6 + 7;
+ }"#,
+ expect![[r#"
+ fn main() {
+ let v = 1 + 2;
+ let v2 = 3 * 3;
+ let v3 = 5 * 4;
+ let v4 = 6 + 7;
+ }"#]],
+ );
+}
+
+#[test]
+fn replace_self() {
+ // `foo(self)` occurs twice in the code, however only the first occurrence is the `self` that's
+ // in scope where the rule is invoked.
+ assert_ssr_transform(
+ "foo(self) ==>> bar(self)",
+ r#"
+ struct S1 {}
+ fn foo(_: &S1) {}
+ fn bar(_: &S1) {}
+ impl S1 {
+ fn f1(&self) {
+ foo(self)$0
+ }
+ fn f2(&self) {
+ foo(self)
+ }
+ }
+ "#,
+ expect![[r#"
+ struct S1 {}
+ fn foo(_: &S1) {}
+ fn bar(_: &S1) {}
+ impl S1 {
+ fn f1(&self) {
+ bar(self)
+ }
+ fn f2(&self) {
+ foo(self)
+ }
+ }
+ "#]],
+ );
+}
+
+#[test]
+fn match_trait_method_call() {
+ // `Bar::foo` and `Bar2::foo` resolve to the same function. Make sure we only match if the type
+ // matches what's in the pattern. Also checks that we handle autoderef.
+ let code = r#"
+ pub struct Bar {}
+ pub struct Bar2 {}
+ pub trait Foo {
+ fn foo(&self, _: i32) {}
+ }
+ impl Foo for Bar {}
+ impl Foo for Bar2 {}
+ fn main() {
+ let v1 = Bar {};
+ let v2 = Bar2 {};
+ let v1_ref = &v1;
+ let v2_ref = &v2;
+ v1.foo(1);
+ v2.foo(2);
+ Bar::foo(&v1, 3);
+ Bar2::foo(&v2, 4);
+ v1_ref.foo(5);
+ v2_ref.foo(6);
+ }
+ "#;
+ assert_matches("Bar::foo($a, $b)", code, &["v1.foo(1)", "Bar::foo(&v1, 3)", "v1_ref.foo(5)"]);
+ assert_matches("Bar2::foo($a, $b)", code, &["v2.foo(2)", "Bar2::foo(&v2, 4)", "v2_ref.foo(6)"]);
+}
+
+#[test]
+fn replace_autoref_autoderef_capture() {
+ // Here we have several calls to `$a.foo()`. In the first case autoref is applied, in the
+ // second, we already have a reference, so it isn't. When $a is used in a context where autoref
+ // doesn't apply, we need to prefix it with `&`. Finally, we have some cases where autoderef
+ // needs to be applied.
+ cov_mark::check!(replace_autoref_autoderef_capture);
+ let code = r#"
+ struct Foo {}
+ impl Foo {
+ fn foo(&self) {}
+ fn foo2(&self) {}
+ }
+ fn bar(_: &Foo) {}
+ fn main() {
+ let f = Foo {};
+ let fr = &f;
+ let fr2 = &fr;
+ let fr3 = &fr2;
+ f.foo();
+ fr.foo();
+ fr2.foo();
+ fr3.foo();
+ }
+ "#;
+ assert_ssr_transform(
+ "Foo::foo($a) ==>> bar($a)",
+ code,
+ expect![[r#"
+ struct Foo {}
+ impl Foo {
+ fn foo(&self) {}
+ fn foo2(&self) {}
+ }
+ fn bar(_: &Foo) {}
+ fn main() {
+ let f = Foo {};
+ let fr = &f;
+ let fr2 = &fr;
+ let fr3 = &fr2;
+ bar(&f);
+ bar(&*fr);
+ bar(&**fr2);
+ bar(&***fr3);
+ }
+ "#]],
+ );
+ // If the placeholder is used as the receiver of another method call, then we don't need to
+ // explicitly autoderef or autoref.
+ assert_ssr_transform(
+ "Foo::foo($a) ==>> $a.foo2()",
+ code,
+ expect![[r#"
+ struct Foo {}
+ impl Foo {
+ fn foo(&self) {}
+ fn foo2(&self) {}
+ }
+ fn bar(_: &Foo) {}
+ fn main() {
+ let f = Foo {};
+ let fr = &f;
+ let fr2 = &fr;
+ let fr3 = &fr2;
+ f.foo2();
+ fr.foo2();
+ fr2.foo2();
+ fr3.foo2();
+ }
+ "#]],
+ );
+}
+
+#[test]
+fn replace_autoref_mut() {
+ let code = r#"
+ struct Foo {}
+ impl Foo {
+ fn foo(&mut self) {}
+ }
+ fn bar(_: &mut Foo) {}
+ fn main() {
+ let mut f = Foo {};
+ f.foo();
+ let fr = &mut f;
+ fr.foo();
+ }
+ "#;
+ assert_ssr_transform(
+ "Foo::foo($a) ==>> bar($a)",
+ code,
+ expect![[r#"
+ struct Foo {}
+ impl Foo {
+ fn foo(&mut self) {}
+ }
+ fn bar(_: &mut Foo) {}
+ fn main() {
+ let mut f = Foo {};
+ bar(&mut f);
+ let fr = &mut f;
+ bar(&mut *fr);
+ }
+ "#]],
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide/Cargo.toml b/src/tools/rust-analyzer/crates/ide/Cargo.toml
new file mode 100644
index 000000000..0e9771cd2
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/Cargo.toml
@@ -0,0 +1,47 @@
+[package]
+name = "ide"
+version = "0.0.0"
+description = "TBD"
+license = "MIT OR Apache-2.0"
+edition = "2021"
+rust-version = "1.57"
+
+[lib]
+doctest = false
+
+[dependencies]
+cov-mark = "2.0.0-pre.1"
+crossbeam-channel = "0.5.5"
+either = "1.7.0"
+itertools = "0.10.3"
+tracing = "0.1.35"
+oorandom = "11.1.3"
+pulldown-cmark-to-cmark = "10.0.1"
+pulldown-cmark = { version = "0.9.1", default-features = false }
+url = "2.2.2"
+dot = "0.1.4"
+
+stdx = { path = "../stdx", version = "0.0.0" }
+syntax = { path = "../syntax", version = "0.0.0" }
+text-edit = { path = "../text-edit", version = "0.0.0" }
+ide-db = { path = "../ide-db", version = "0.0.0" }
+cfg = { path = "../cfg", version = "0.0.0" }
+profile = { path = "../profile", version = "0.0.0" }
+ide-assists = { path = "../ide-assists", version = "0.0.0" }
+ide-diagnostics = { path = "../ide-diagnostics", version = "0.0.0" }
+ide-ssr = { path = "../ide-ssr", version = "0.0.0" }
+ide-completion = { path = "../ide-completion", version = "0.0.0" }
+
+# ide should depend only on the top-level `hir` package. if you need
+# something from some `hir-xxx` subpackage, reexport the API via `hir`.
+hir = { path = "../hir", version = "0.0.0" }
+
+[target.'cfg(not(any(target_arch = "wasm32", target_os = "emscripten")))'.dependencies]
+toolchain = { path = "../toolchain", version = "0.0.0" }
+
+[dev-dependencies]
+test-utils = { path = "../test-utils" }
+expect-test = "1.4.0"
+
+[features]
+in-rust-tree = ["ide-assists/in-rust-tree", "ide-diagnostics/in-rust-tree"]
diff --git a/src/tools/rust-analyzer/crates/ide/src/annotations.rs b/src/tools/rust-analyzer/crates/ide/src/annotations.rs
new file mode 100644
index 000000000..210c5c7fa
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/annotations.rs
@@ -0,0 +1,789 @@
+use hir::{HasSource, InFile, Semantics};
+use ide_db::{
+ base_db::{FileId, FilePosition, FileRange},
+ defs::Definition,
+ helpers::visit_file_defs,
+ RootDatabase,
+};
+use syntax::{ast::HasName, AstNode, TextRange};
+
+use crate::{
+ fn_references::find_all_methods,
+ goto_implementation::goto_implementation,
+ references::find_all_refs,
+ runnables::{runnables, Runnable},
+ NavigationTarget, RunnableKind,
+};
+
+// Feature: Annotations
+//
+// Provides user with annotations above items for looking up references or impl blocks
+// and running/debugging binaries.
+//
+// image::https://user-images.githubusercontent.com/48062697/113020672-b7c34f00-917a-11eb-8f6e-858735660a0e.png[]
+#[derive(Debug)]
+pub struct Annotation {
+ pub range: TextRange,
+ pub kind: AnnotationKind,
+}
+
+#[derive(Debug)]
+pub enum AnnotationKind {
+ Runnable(Runnable),
+ HasImpls { file_id: FileId, data: Option<Vec<NavigationTarget>> },
+ HasReferences { file_id: FileId, data: Option<Vec<FileRange>> },
+}
+
+pub struct AnnotationConfig {
+ pub binary_target: bool,
+ pub annotate_runnables: bool,
+ pub annotate_impls: bool,
+ pub annotate_references: bool,
+ pub annotate_method_references: bool,
+ pub annotate_enum_variant_references: bool,
+}
+
+pub(crate) fn annotations(
+ db: &RootDatabase,
+ config: &AnnotationConfig,
+ file_id: FileId,
+) -> Vec<Annotation> {
+ let mut annotations = Vec::default();
+
+ if config.annotate_runnables {
+ for runnable in runnables(db, file_id) {
+ if should_skip_runnable(&runnable.kind, config.binary_target) {
+ continue;
+ }
+
+ let range = runnable.nav.focus_or_full_range();
+
+ annotations.push(Annotation { range, kind: AnnotationKind::Runnable(runnable) });
+ }
+ }
+
+ visit_file_defs(&Semantics::new(db), file_id, &mut |def| {
+ let range = match def {
+ Definition::Const(konst) if config.annotate_references => {
+ konst.source(db).and_then(|node| name_range(db, node, file_id))
+ }
+ Definition::Trait(trait_) if config.annotate_references || config.annotate_impls => {
+ trait_.source(db).and_then(|node| name_range(db, node, file_id))
+ }
+ Definition::Adt(adt) => match adt {
+ hir::Adt::Enum(enum_) => {
+ if config.annotate_enum_variant_references {
+ enum_
+ .variants(db)
+ .into_iter()
+ .map(|variant| {
+ variant.source(db).and_then(|node| name_range(db, node, file_id))
+ })
+ .flatten()
+ .for_each(|range| {
+ annotations.push(Annotation {
+ range,
+ kind: AnnotationKind::HasReferences { file_id, data: None },
+ })
+ })
+ }
+ if config.annotate_references || config.annotate_impls {
+ enum_.source(db).and_then(|node| name_range(db, node, file_id))
+ } else {
+ None
+ }
+ }
+ _ => {
+ if config.annotate_references || config.annotate_impls {
+ adt.source(db).and_then(|node| name_range(db, node, file_id))
+ } else {
+ None
+ }
+ }
+ },
+ _ => None,
+ };
+
+ let range = match range {
+ Some(range) => range,
+ None => return,
+ };
+
+ if config.annotate_impls && !matches!(def, Definition::Const(_)) {
+ annotations
+ .push(Annotation { range, kind: AnnotationKind::HasImpls { file_id, data: None } });
+ }
+ if config.annotate_references {
+ annotations.push(Annotation {
+ range,
+ kind: AnnotationKind::HasReferences { file_id, data: None },
+ });
+ }
+
+ fn name_range<T: HasName>(
+ db: &RootDatabase,
+ node: InFile<T>,
+ source_file_id: FileId,
+ ) -> Option<TextRange> {
+ if let Some(InFile { file_id, value }) = node.original_ast_node(db) {
+ if file_id == source_file_id.into() {
+ return value.name().map(|it| it.syntax().text_range());
+ }
+ }
+ None
+ }
+ });
+
+ if config.annotate_method_references {
+ annotations.extend(find_all_methods(db, file_id).into_iter().map(
+ |FileRange { file_id, range }| Annotation {
+ range,
+ kind: AnnotationKind::HasReferences { file_id, data: None },
+ },
+ ));
+ }
+
+ annotations
+}
+
+pub(crate) fn resolve_annotation(db: &RootDatabase, mut annotation: Annotation) -> Annotation {
+ match annotation.kind {
+ AnnotationKind::HasImpls { file_id, ref mut data } => {
+ *data =
+ goto_implementation(db, FilePosition { file_id, offset: annotation.range.start() })
+ .map(|range| range.info);
+ }
+ AnnotationKind::HasReferences { file_id, ref mut data } => {
+ *data = find_all_refs(
+ &Semantics::new(db),
+ FilePosition { file_id, offset: annotation.range.start() },
+ None,
+ )
+ .map(|result| {
+ result
+ .into_iter()
+ .flat_map(|res| res.references)
+ .flat_map(|(file_id, access)| {
+ access.into_iter().map(move |(range, _)| FileRange { file_id, range })
+ })
+ .collect()
+ });
+ }
+ _ => {}
+ };
+
+ annotation
+}
+
+fn should_skip_runnable(kind: &RunnableKind, binary_target: bool) -> bool {
+ match kind {
+ RunnableKind::Bin => !binary_target,
+ _ => false,
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+
+ use crate::{fixture, Annotation, AnnotationConfig};
+
+ fn check(ra_fixture: &str, expect: Expect) {
+ let (analysis, file_id) = fixture::file(ra_fixture);
+
+ let annotations: Vec<Annotation> = analysis
+ .annotations(
+ &AnnotationConfig {
+ binary_target: true,
+ annotate_runnables: true,
+ annotate_impls: true,
+ annotate_references: true,
+ annotate_method_references: true,
+ annotate_enum_variant_references: true,
+ },
+ file_id,
+ )
+ .unwrap()
+ .into_iter()
+ .map(|annotation| analysis.resolve_annotation(annotation).unwrap())
+ .collect();
+
+ expect.assert_debug_eq(&annotations);
+ }
+
+ #[test]
+ fn const_annotations() {
+ check(
+ r#"
+const DEMO: i32 = 123;
+
+const UNUSED: i32 = 123;
+
+fn main() {
+ let hello = DEMO;
+}
+ "#,
+ expect![[r#"
+ [
+ Annotation {
+ range: 53..57,
+ kind: Runnable(
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 50..85,
+ focus_range: 53..57,
+ name: "main",
+ kind: Function,
+ },
+ kind: Bin,
+ cfg: None,
+ },
+ ),
+ },
+ Annotation {
+ range: 6..10,
+ kind: HasReferences {
+ file_id: FileId(
+ 0,
+ ),
+ data: Some(
+ [
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 78..82,
+ },
+ ],
+ ),
+ },
+ },
+ Annotation {
+ range: 30..36,
+ kind: HasReferences {
+ file_id: FileId(
+ 0,
+ ),
+ data: Some(
+ [],
+ ),
+ },
+ },
+ Annotation {
+ range: 53..57,
+ kind: HasReferences {
+ file_id: FileId(
+ 0,
+ ),
+ data: Some(
+ [],
+ ),
+ },
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn struct_references_annotations() {
+ check(
+ r#"
+struct Test;
+
+fn main() {
+ let test = Test;
+}
+ "#,
+ expect![[r#"
+ [
+ Annotation {
+ range: 17..21,
+ kind: Runnable(
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 14..48,
+ focus_range: 17..21,
+ name: "main",
+ kind: Function,
+ },
+ kind: Bin,
+ cfg: None,
+ },
+ ),
+ },
+ Annotation {
+ range: 7..11,
+ kind: HasImpls {
+ file_id: FileId(
+ 0,
+ ),
+ data: Some(
+ [],
+ ),
+ },
+ },
+ Annotation {
+ range: 7..11,
+ kind: HasReferences {
+ file_id: FileId(
+ 0,
+ ),
+ data: Some(
+ [
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 41..45,
+ },
+ ],
+ ),
+ },
+ },
+ Annotation {
+ range: 17..21,
+ kind: HasReferences {
+ file_id: FileId(
+ 0,
+ ),
+ data: Some(
+ [],
+ ),
+ },
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn struct_and_trait_impls_annotations() {
+ check(
+ r#"
+struct Test;
+
+trait MyCoolTrait {}
+
+impl MyCoolTrait for Test {}
+
+fn main() {
+ let test = Test;
+}
+ "#,
+ expect![[r#"
+ [
+ Annotation {
+ range: 69..73,
+ kind: Runnable(
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 66..100,
+ focus_range: 69..73,
+ name: "main",
+ kind: Function,
+ },
+ kind: Bin,
+ cfg: None,
+ },
+ ),
+ },
+ Annotation {
+ range: 7..11,
+ kind: HasImpls {
+ file_id: FileId(
+ 0,
+ ),
+ data: Some(
+ [
+ NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 36..64,
+ focus_range: 57..61,
+ name: "impl",
+ kind: Impl,
+ },
+ ],
+ ),
+ },
+ },
+ Annotation {
+ range: 7..11,
+ kind: HasReferences {
+ file_id: FileId(
+ 0,
+ ),
+ data: Some(
+ [
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 57..61,
+ },
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 93..97,
+ },
+ ],
+ ),
+ },
+ },
+ Annotation {
+ range: 20..31,
+ kind: HasImpls {
+ file_id: FileId(
+ 0,
+ ),
+ data: Some(
+ [
+ NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 36..64,
+ focus_range: 57..61,
+ name: "impl",
+ kind: Impl,
+ },
+ ],
+ ),
+ },
+ },
+ Annotation {
+ range: 20..31,
+ kind: HasReferences {
+ file_id: FileId(
+ 0,
+ ),
+ data: Some(
+ [
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 41..52,
+ },
+ ],
+ ),
+ },
+ },
+ Annotation {
+ range: 69..73,
+ kind: HasReferences {
+ file_id: FileId(
+ 0,
+ ),
+ data: Some(
+ [],
+ ),
+ },
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn runnable_annotation() {
+ check(
+ r#"
+fn main() {}
+ "#,
+ expect![[r#"
+ [
+ Annotation {
+ range: 3..7,
+ kind: Runnable(
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..12,
+ focus_range: 3..7,
+ name: "main",
+ kind: Function,
+ },
+ kind: Bin,
+ cfg: None,
+ },
+ ),
+ },
+ Annotation {
+ range: 3..7,
+ kind: HasReferences {
+ file_id: FileId(
+ 0,
+ ),
+ data: Some(
+ [],
+ ),
+ },
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn method_annotations() {
+ check(
+ r#"
+struct Test;
+
+impl Test {
+ fn self_by_ref(&self) {}
+}
+
+fn main() {
+ Test.self_by_ref();
+}
+ "#,
+ expect![[r#"
+ [
+ Annotation {
+ range: 61..65,
+ kind: Runnable(
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 58..95,
+ focus_range: 61..65,
+ name: "main",
+ kind: Function,
+ },
+ kind: Bin,
+ cfg: None,
+ },
+ ),
+ },
+ Annotation {
+ range: 7..11,
+ kind: HasImpls {
+ file_id: FileId(
+ 0,
+ ),
+ data: Some(
+ [
+ NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 14..56,
+ focus_range: 19..23,
+ name: "impl",
+ kind: Impl,
+ },
+ ],
+ ),
+ },
+ },
+ Annotation {
+ range: 7..11,
+ kind: HasReferences {
+ file_id: FileId(
+ 0,
+ ),
+ data: Some(
+ [
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 19..23,
+ },
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 74..78,
+ },
+ ],
+ ),
+ },
+ },
+ Annotation {
+ range: 33..44,
+ kind: HasReferences {
+ file_id: FileId(
+ 0,
+ ),
+ data: Some(
+ [
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 79..90,
+ },
+ ],
+ ),
+ },
+ },
+ Annotation {
+ range: 61..65,
+ kind: HasReferences {
+ file_id: FileId(
+ 0,
+ ),
+ data: Some(
+ [],
+ ),
+ },
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_annotations() {
+ check(
+ r#"
+fn main() {}
+
+mod tests {
+ #[test]
+ fn my_cool_test() {}
+}
+ "#,
+ expect![[r#"
+ [
+ Annotation {
+ range: 3..7,
+ kind: Runnable(
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..12,
+ focus_range: 3..7,
+ name: "main",
+ kind: Function,
+ },
+ kind: Bin,
+ cfg: None,
+ },
+ ),
+ },
+ Annotation {
+ range: 18..23,
+ kind: Runnable(
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 14..64,
+ focus_range: 18..23,
+ name: "tests",
+ kind: Module,
+ description: "mod tests",
+ },
+ kind: TestMod {
+ path: "tests",
+ },
+ cfg: None,
+ },
+ ),
+ },
+ Annotation {
+ range: 45..57,
+ kind: Runnable(
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 30..62,
+ focus_range: 45..57,
+ name: "my_cool_test",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "tests::my_cool_test",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ ),
+ },
+ Annotation {
+ range: 3..7,
+ kind: HasReferences {
+ file_id: FileId(
+ 0,
+ ),
+ data: Some(
+ [],
+ ),
+ },
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_no_annotations_outside_module_tree() {
+ check(
+ r#"
+//- /foo.rs
+struct Foo;
+//- /lib.rs
+// this file comes last since `check` checks the first file only
+"#,
+ expect![[r#"
+ []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_no_annotations_macro_struct_def() {
+ check(
+ r#"
+//- /lib.rs
+macro_rules! m {
+ () => {
+ struct A {}
+ };
+}
+
+m!();
+"#,
+ expect![[r#"
+ []
+ "#]],
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs b/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs
new file mode 100644
index 000000000..a18a6bea9
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs
@@ -0,0 +1,460 @@
+//! Entry point for call-hierarchy
+
+use hir::Semantics;
+use ide_db::{
+ defs::{Definition, NameClass, NameRefClass},
+ helpers::pick_best_token,
+ search::FileReference,
+ FxIndexMap, RootDatabase,
+};
+use syntax::{ast, AstNode, SyntaxKind::NAME, TextRange};
+
+use crate::{goto_definition, FilePosition, NavigationTarget, RangeInfo, TryToNav};
+
+#[derive(Debug, Clone)]
+pub struct CallItem {
+ pub target: NavigationTarget,
+ pub ranges: Vec<TextRange>,
+}
+
+impl CallItem {
+ #[cfg(test)]
+ pub(crate) fn debug_render(&self) -> String {
+ format!("{} : {:?}", self.target.debug_render(), self.ranges)
+ }
+}
+
+pub(crate) fn call_hierarchy(
+ db: &RootDatabase,
+ position: FilePosition,
+) -> Option<RangeInfo<Vec<NavigationTarget>>> {
+ goto_definition::goto_definition(db, position)
+}
+
+pub(crate) fn incoming_calls(
+ db: &RootDatabase,
+ FilePosition { file_id, offset }: FilePosition,
+) -> Option<Vec<CallItem>> {
+ let sema = &Semantics::new(db);
+
+ let file = sema.parse(file_id);
+ let file = file.syntax();
+ let mut calls = CallLocations::default();
+
+ let references = sema
+ .find_nodes_at_offset_with_descend(file, offset)
+ .filter_map(move |node| match node {
+ ast::NameLike::NameRef(name_ref) => match NameRefClass::classify(sema, &name_ref)? {
+ NameRefClass::Definition(def @ Definition::Function(_)) => Some(def),
+ _ => None,
+ },
+ ast::NameLike::Name(name) => match NameClass::classify(sema, &name)? {
+ NameClass::Definition(def @ Definition::Function(_)) => Some(def),
+ _ => None,
+ },
+ ast::NameLike::Lifetime(_) => None,
+ })
+ .flat_map(|func| func.usages(sema).all());
+
+ for (_, references) in references {
+ let references = references.into_iter().map(|FileReference { name, .. }| name);
+ for name in references {
+ // This target is the containing function
+ let nav = sema.ancestors_with_macros(name.syntax().clone()).find_map(|node| {
+ let def = ast::Fn::cast(node).and_then(|fn_| sema.to_def(&fn_))?;
+ def.try_to_nav(sema.db)
+ });
+ if let Some(nav) = nav {
+ calls.add(nav, sema.original_range(name.syntax()).range);
+ }
+ }
+ }
+
+ Some(calls.into_items())
+}
+
+pub(crate) fn outgoing_calls(db: &RootDatabase, position: FilePosition) -> Option<Vec<CallItem>> {
+ let sema = Semantics::new(db);
+ let file_id = position.file_id;
+ let file = sema.parse(file_id);
+ let file = file.syntax();
+ let token = pick_best_token(file.token_at_offset(position.offset), |kind| match kind {
+ NAME => 1,
+ _ => 0,
+ })?;
+ let mut calls = CallLocations::default();
+
+ sema.descend_into_macros(token)
+ .into_iter()
+ .filter_map(|it| it.parent_ancestors().nth(1).and_then(ast::Item::cast))
+ .filter_map(|item| match item {
+ ast::Item::Const(c) => c.body().map(|it| it.syntax().descendants()),
+ ast::Item::Fn(f) => f.body().map(|it| it.syntax().descendants()),
+ ast::Item::Static(s) => s.body().map(|it| it.syntax().descendants()),
+ _ => None,
+ })
+ .flatten()
+ .filter_map(ast::CallableExpr::cast)
+ .filter_map(|call_node| {
+ let (nav_target, range) = match call_node {
+ ast::CallableExpr::Call(call) => {
+ let expr = call.expr()?;
+ let callable = sema.type_of_expr(&expr)?.original.as_callable(db)?;
+ match callable.kind() {
+ hir::CallableKind::Function(it) => {
+ let range = expr.syntax().text_range();
+ it.try_to_nav(db).zip(Some(range))
+ }
+ _ => None,
+ }
+ }
+ ast::CallableExpr::MethodCall(expr) => {
+ let range = expr.name_ref()?.syntax().text_range();
+ let function = sema.resolve_method_call(&expr)?;
+ function.try_to_nav(db).zip(Some(range))
+ }
+ }?;
+ Some((nav_target, range))
+ })
+ .for_each(|(nav, range)| calls.add(nav, range));
+
+ Some(calls.into_items())
+}
+
+#[derive(Default)]
+struct CallLocations {
+ funcs: FxIndexMap<NavigationTarget, Vec<TextRange>>,
+}
+
+impl CallLocations {
+ fn add(&mut self, target: NavigationTarget, range: TextRange) {
+ self.funcs.entry(target).or_default().push(range);
+ }
+
+ fn into_items(self) -> Vec<CallItem> {
+ self.funcs.into_iter().map(|(target, ranges)| CallItem { target, ranges }).collect()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+ use ide_db::base_db::FilePosition;
+ use itertools::Itertools;
+
+ use crate::fixture;
+
+ fn check_hierarchy(
+ ra_fixture: &str,
+ expected: Expect,
+ expected_incoming: Expect,
+ expected_outgoing: Expect,
+ ) {
+ let (analysis, pos) = fixture::position(ra_fixture);
+
+ let mut navs = analysis.call_hierarchy(pos).unwrap().unwrap().info;
+ assert_eq!(navs.len(), 1);
+ let nav = navs.pop().unwrap();
+ expected.assert_eq(&nav.debug_render());
+
+ let item_pos =
+ FilePosition { file_id: nav.file_id, offset: nav.focus_or_full_range().start() };
+ let incoming_calls = analysis.incoming_calls(item_pos).unwrap().unwrap();
+ expected_incoming
+ .assert_eq(&incoming_calls.into_iter().map(|call| call.debug_render()).join("\n"));
+
+ let outgoing_calls = analysis.outgoing_calls(item_pos).unwrap().unwrap();
+ expected_outgoing
+ .assert_eq(&outgoing_calls.into_iter().map(|call| call.debug_render()).join("\n"));
+ }
+
+ #[test]
+ fn test_call_hierarchy_on_ref() {
+ check_hierarchy(
+ r#"
+//- /lib.rs
+fn callee() {}
+fn caller() {
+ call$0ee();
+}
+"#,
+ expect![["callee Function FileId(0) 0..14 3..9"]],
+ expect![["caller Function FileId(0) 15..44 18..24 : [33..39]"]],
+ expect![[]],
+ );
+ }
+
+ #[test]
+ fn test_call_hierarchy_on_def() {
+ check_hierarchy(
+ r#"
+//- /lib.rs
+fn call$0ee() {}
+fn caller() {
+ callee();
+}
+"#,
+ expect![["callee Function FileId(0) 0..14 3..9"]],
+ expect![["caller Function FileId(0) 15..44 18..24 : [33..39]"]],
+ expect![[]],
+ );
+ }
+
+ #[test]
+ fn test_call_hierarchy_in_same_fn() {
+ check_hierarchy(
+ r#"
+//- /lib.rs
+fn callee() {}
+fn caller() {
+ call$0ee();
+ callee();
+}
+"#,
+ expect![["callee Function FileId(0) 0..14 3..9"]],
+ expect![["caller Function FileId(0) 15..58 18..24 : [33..39, 47..53]"]],
+ expect![[]],
+ );
+ }
+
+ #[test]
+ fn test_call_hierarchy_in_different_fn() {
+ check_hierarchy(
+ r#"
+//- /lib.rs
+fn callee() {}
+fn caller1() {
+ call$0ee();
+}
+
+fn caller2() {
+ callee();
+}
+"#,
+ expect![["callee Function FileId(0) 0..14 3..9"]],
+ expect![["
+ caller1 Function FileId(0) 15..45 18..25 : [34..40]
+ caller2 Function FileId(0) 47..77 50..57 : [66..72]"]],
+ expect![[]],
+ );
+ }
+
+ #[test]
+ fn test_call_hierarchy_in_tests_mod() {
+ check_hierarchy(
+ r#"
+//- /lib.rs cfg:test
+fn callee() {}
+fn caller1() {
+ call$0ee();
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_caller() {
+ callee();
+ }
+}
+"#,
+ expect![["callee Function FileId(0) 0..14 3..9"]],
+ expect![[r#"
+ caller1 Function FileId(0) 15..45 18..25 : [34..40]
+ test_caller Function FileId(0) 95..149 110..121 : [134..140]"#]],
+ expect![[]],
+ );
+ }
+
+ #[test]
+ fn test_call_hierarchy_in_different_files() {
+ check_hierarchy(
+ r#"
+//- /lib.rs
+mod foo;
+use foo::callee;
+
+fn caller() {
+ call$0ee();
+}
+
+//- /foo/mod.rs
+pub fn callee() {}
+"#,
+ expect![["callee Function FileId(1) 0..18 7..13"]],
+ expect![["caller Function FileId(0) 27..56 30..36 : [45..51]"]],
+ expect![[]],
+ );
+ }
+
+ #[test]
+ fn test_call_hierarchy_outgoing() {
+ check_hierarchy(
+ r#"
+//- /lib.rs
+fn callee() {}
+fn call$0er() {
+ callee();
+ callee();
+}
+"#,
+ expect![["caller Function FileId(0) 15..58 18..24"]],
+ expect![[]],
+ expect![["callee Function FileId(0) 0..14 3..9 : [33..39, 47..53]"]],
+ );
+ }
+
+ #[test]
+ fn test_call_hierarchy_outgoing_in_different_files() {
+ check_hierarchy(
+ r#"
+//- /lib.rs
+mod foo;
+use foo::callee;
+
+fn call$0er() {
+ callee();
+}
+
+//- /foo/mod.rs
+pub fn callee() {}
+"#,
+ expect![["caller Function FileId(0) 27..56 30..36"]],
+ expect![[]],
+ expect![["callee Function FileId(1) 0..18 7..13 : [45..51]"]],
+ );
+ }
+
+ #[test]
+ fn test_call_hierarchy_incoming_outgoing() {
+ check_hierarchy(
+ r#"
+//- /lib.rs
+fn caller1() {
+ call$0er2();
+}
+
+fn caller2() {
+ caller3();
+}
+
+fn caller3() {
+
+}
+"#,
+ expect![["caller2 Function FileId(0) 33..64 36..43"]],
+ expect![["caller1 Function FileId(0) 0..31 3..10 : [19..26]"]],
+ expect![["caller3 Function FileId(0) 66..83 69..76 : [52..59]"]],
+ );
+ }
+
+ #[test]
+ fn test_call_hierarchy_issue_5103() {
+ check_hierarchy(
+ r#"
+fn a() {
+ b()
+}
+
+fn b() {}
+
+fn main() {
+ a$0()
+}
+"#,
+ expect![["a Function FileId(0) 0..18 3..4"]],
+ expect![["main Function FileId(0) 31..52 34..38 : [47..48]"]],
+ expect![["b Function FileId(0) 20..29 23..24 : [13..14]"]],
+ );
+
+ check_hierarchy(
+ r#"
+fn a() {
+ b$0()
+}
+
+fn b() {}
+
+fn main() {
+ a()
+}
+"#,
+ expect![["b Function FileId(0) 20..29 23..24"]],
+ expect![["a Function FileId(0) 0..18 3..4 : [13..14]"]],
+ expect![[]],
+ );
+ }
+
+ #[test]
+ fn test_call_hierarchy_in_macros_incoming() {
+ check_hierarchy(
+ r#"
+macro_rules! define {
+ ($ident:ident) => {
+ fn $ident {}
+ }
+}
+macro_rules! call {
+ ($ident:ident) => {
+ $ident()
+ }
+}
+define!(callee)
+fn caller() {
+ call!(call$0ee);
+}
+"#,
+ expect![[r#"callee Function FileId(0) 144..159 152..158"#]],
+ expect![[r#"caller Function FileId(0) 160..194 163..169 : [184..190]"#]],
+ expect![[]],
+ );
+ check_hierarchy(
+ r#"
+macro_rules! define {
+ ($ident:ident) => {
+ fn $ident {}
+ }
+}
+macro_rules! call {
+ ($ident:ident) => {
+ $ident()
+ }
+}
+define!(cal$0lee)
+fn caller() {
+ call!(callee);
+}
+"#,
+ expect![[r#"callee Function FileId(0) 144..159 152..158"#]],
+ expect![[r#"caller Function FileId(0) 160..194 163..169 : [184..190]"#]],
+ expect![[]],
+ );
+ }
+
+ #[test]
+ fn test_call_hierarchy_in_macros_outgoing() {
+ check_hierarchy(
+ r#"
+macro_rules! define {
+ ($ident:ident) => {
+ fn $ident {}
+ }
+}
+macro_rules! call {
+ ($ident:ident) => {
+ $ident()
+ }
+}
+define!(callee)
+fn caller$0() {
+ call!(callee);
+}
+"#,
+ expect![[r#"caller Function FileId(0) 160..194 163..169"#]],
+ expect![[]],
+ // FIXME
+ expect![[]],
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs
new file mode 100644
index 000000000..582e9fe7e
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs
@@ -0,0 +1,549 @@
+//! Extracts, resolves and rewrites links and intra-doc links in markdown documentation.
+
+#[cfg(test)]
+mod tests;
+
+mod intra_doc_links;
+
+use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag};
+use pulldown_cmark_to_cmark::{cmark_resume_with_options, Options as CMarkOptions};
+use stdx::format_to;
+use url::Url;
+
+use hir::{db::HirDatabase, Adt, AsAssocItem, AssocItem, AssocItemContainer, HasAttrs};
+use ide_db::{
+ base_db::{CrateOrigin, LangCrateOrigin, SourceDatabase},
+ defs::{Definition, NameClass, NameRefClass},
+ helpers::pick_best_token,
+ RootDatabase,
+};
+use syntax::{
+ ast::{self, IsString},
+ match_ast, AstNode, AstToken,
+ SyntaxKind::*,
+ SyntaxNode, SyntaxToken, TextRange, TextSize, T,
+};
+
+use crate::{
+ doc_links::intra_doc_links::{parse_intra_doc_link, strip_prefixes_suffixes},
+ FilePosition, Semantics,
+};
+
+/// Weblink to an item's documentation.
+pub(crate) type DocumentationLink = String;
+
+const MARKDOWN_OPTIONS: Options =
+ Options::ENABLE_FOOTNOTES.union(Options::ENABLE_TABLES).union(Options::ENABLE_TASKLISTS);
+
+/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs)
+pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: Definition) -> String {
+ let mut cb = broken_link_clone_cb;
+ let doc = Parser::new_with_broken_link_callback(markdown, MARKDOWN_OPTIONS, Some(&mut cb));
+
+ let doc = map_links(doc, |target, title| {
+ // This check is imperfect, there's some overlap between valid intra-doc links
+ // and valid URLs so we choose to be too eager to try to resolve what might be
+ // a URL.
+ if target.contains("://") {
+ (Some(LinkType::Inline), target.to_string(), title.to_string())
+ } else {
+ // Two possibilities:
+ // * path-based links: `../../module/struct.MyStruct.html`
+ // * module-based links (AKA intra-doc links): `super::super::module::MyStruct`
+ if let Some((target, title)) = rewrite_intra_doc_link(db, definition, target, title) {
+ return (None, target, title);
+ }
+ if let Some(target) = rewrite_url_link(db, definition, target) {
+ return (Some(LinkType::Inline), target, title.to_string());
+ }
+
+ (None, target.to_string(), title.to_string())
+ }
+ });
+ let mut out = String::new();
+ cmark_resume_with_options(
+ doc,
+ &mut out,
+ None,
+ CMarkOptions { code_block_token_count: 3, ..Default::default() },
+ )
+ .ok();
+ out
+}
+
+/// Remove all links in markdown documentation.
+pub(crate) fn remove_links(markdown: &str) -> String {
+ let mut drop_link = false;
+
+ let mut cb = |_: BrokenLink<'_>| {
+ let empty = InlineStr::try_from("").unwrap();
+ Some((CowStr::Inlined(empty), CowStr::Inlined(empty)))
+ };
+ let doc = Parser::new_with_broken_link_callback(markdown, MARKDOWN_OPTIONS, Some(&mut cb));
+ let doc = doc.filter_map(move |evt| match evt {
+ Event::Start(Tag::Link(link_type, target, title)) => {
+ if link_type == LinkType::Inline && target.contains("://") {
+ Some(Event::Start(Tag::Link(link_type, target, title)))
+ } else {
+ drop_link = true;
+ None
+ }
+ }
+ Event::End(_) if drop_link => {
+ drop_link = false;
+ None
+ }
+ _ => Some(evt),
+ });
+
+ let mut out = String::new();
+ cmark_resume_with_options(
+ doc,
+ &mut out,
+ None,
+ CMarkOptions { code_block_token_count: 3, ..Default::default() },
+ )
+ .ok();
+ out
+}
+
+/// Retrieve a link to documentation for the given symbol.
+pub(crate) fn external_docs(
+ db: &RootDatabase,
+ position: &FilePosition,
+) -> Option<DocumentationLink> {
+ let sema = &Semantics::new(db);
+ let file = sema.parse(position.file_id).syntax().clone();
+ let token = pick_best_token(file.token_at_offset(position.offset), |kind| match kind {
+ IDENT | INT_NUMBER | T![self] => 3,
+ T!['('] | T![')'] => 2,
+ kind if kind.is_trivia() => 0,
+ _ => 1,
+ })?;
+ let token = sema.descend_into_macros_single(token);
+
+ let node = token.parent()?;
+ let definition = match_ast! {
+ match node {
+ ast::NameRef(name_ref) => match NameRefClass::classify(sema, &name_ref)? {
+ NameRefClass::Definition(def) => def,
+ NameRefClass::FieldShorthand { local_ref: _, field_ref } => {
+ Definition::Field(field_ref)
+ }
+ },
+ ast::Name(name) => match NameClass::classify(sema, &name)? {
+ NameClass::Definition(it) | NameClass::ConstReference(it) => it,
+ NameClass::PatFieldShorthand { local_def: _, field_ref } => Definition::Field(field_ref),
+ },
+ _ => return None,
+ }
+ };
+
+ get_doc_link(db, definition)
+}
+
+/// Extracts all links from a given markdown text returning the definition text range, link-text
+/// and the namespace if known.
+pub(crate) fn extract_definitions_from_docs(
+ docs: &hir::Documentation,
+) -> Vec<(TextRange, String, Option<hir::Namespace>)> {
+ Parser::new_with_broken_link_callback(
+ docs.as_str(),
+ MARKDOWN_OPTIONS,
+ Some(&mut broken_link_clone_cb),
+ )
+ .into_offset_iter()
+ .filter_map(|(event, range)| match event {
+ Event::Start(Tag::Link(_, target, _)) => {
+ let (link, ns) = parse_intra_doc_link(&target);
+ Some((
+ TextRange::new(range.start.try_into().ok()?, range.end.try_into().ok()?),
+ link.to_string(),
+ ns,
+ ))
+ }
+ _ => None,
+ })
+ .collect()
+}
+
+pub(crate) fn resolve_doc_path_for_def(
+ db: &dyn HirDatabase,
+ def: Definition,
+ link: &str,
+ ns: Option<hir::Namespace>,
+) -> Option<Definition> {
+ match def {
+ Definition::Module(it) => it.resolve_doc_path(db, link, ns),
+ Definition::Function(it) => it.resolve_doc_path(db, link, ns),
+ Definition::Adt(it) => it.resolve_doc_path(db, link, ns),
+ Definition::Variant(it) => it.resolve_doc_path(db, link, ns),
+ Definition::Const(it) => it.resolve_doc_path(db, link, ns),
+ Definition::Static(it) => it.resolve_doc_path(db, link, ns),
+ Definition::Trait(it) => it.resolve_doc_path(db, link, ns),
+ Definition::TypeAlias(it) => it.resolve_doc_path(db, link, ns),
+ Definition::Macro(it) => it.resolve_doc_path(db, link, ns),
+ Definition::Field(it) => it.resolve_doc_path(db, link, ns),
+ Definition::BuiltinAttr(_)
+ | Definition::ToolModule(_)
+ | Definition::BuiltinType(_)
+ | Definition::SelfType(_)
+ | Definition::Local(_)
+ | Definition::GenericParam(_)
+ | Definition::Label(_)
+ | Definition::DeriveHelper(_) => None,
+ }
+ .map(Definition::from)
+}
+
+pub(crate) fn doc_attributes(
+ sema: &Semantics<'_, RootDatabase>,
+ node: &SyntaxNode,
+) -> Option<(hir::AttrsWithOwner, Definition)> {
+ match_ast! {
+ match node {
+ ast::SourceFile(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Module(def))),
+ ast::Module(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Module(def))),
+ ast::Fn(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Function(def))),
+ ast::Struct(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Adt(hir::Adt::Struct(def)))),
+ ast::Union(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Adt(hir::Adt::Union(def)))),
+ ast::Enum(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Adt(hir::Adt::Enum(def)))),
+ ast::Variant(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Variant(def))),
+ ast::Trait(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Trait(def))),
+ ast::Static(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Static(def))),
+ ast::Const(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Const(def))),
+ ast::TypeAlias(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::TypeAlias(def))),
+ ast::Impl(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::SelfType(def))),
+ ast::RecordField(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Field(def))),
+ ast::TupleField(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Field(def))),
+ ast::Macro(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Macro(def))),
+ // ast::Use(it) => sema.to_def(&it).map(|def| (Box::new(it) as _, def.attrs(sema.db))),
+ _ => None
+ }
+ }
+}
+
+pub(crate) struct DocCommentToken {
+ doc_token: SyntaxToken,
+ prefix_len: TextSize,
+}
+
+pub(crate) fn token_as_doc_comment(doc_token: &SyntaxToken) -> Option<DocCommentToken> {
+ (match_ast! {
+ match doc_token {
+ ast::Comment(comment) => TextSize::try_from(comment.prefix().len()).ok(),
+ ast::String(string) => doc_token.parent_ancestors().find_map(ast::Attr::cast)
+ .filter(|attr| attr.simple_name().as_deref() == Some("doc")).and_then(|_| string.open_quote_text_range().map(|it| it.len())),
+ _ => None,
+ }
+ }).map(|prefix_len| DocCommentToken { prefix_len, doc_token: doc_token.clone() })
+}
+
+impl DocCommentToken {
+ pub(crate) fn get_definition_with_descend_at<T>(
+ self,
+ sema: &Semantics<'_, RootDatabase>,
+ offset: TextSize,
+ // Definition, CommentOwner, range of intra doc link in original file
+ mut cb: impl FnMut(Definition, SyntaxNode, TextRange) -> Option<T>,
+ ) -> Option<T> {
+ let DocCommentToken { prefix_len, doc_token } = self;
+ // offset relative to the comments contents
+ let original_start = doc_token.text_range().start();
+ let relative_comment_offset = offset - original_start - prefix_len;
+
+ sema.descend_into_macros(doc_token).into_iter().find_map(|t| {
+ let (node, descended_prefix_len) = match_ast! {
+ match t {
+ ast::Comment(comment) => (t.parent()?, TextSize::try_from(comment.prefix().len()).ok()?),
+ ast::String(string) => (t.parent_ancestors().skip_while(|n| n.kind() != ATTR).nth(1)?, string.open_quote_text_range()?.len()),
+ _ => return None,
+ }
+ };
+ let token_start = t.text_range().start();
+ let abs_in_expansion_offset = token_start + relative_comment_offset + descended_prefix_len;
+
+ let (attributes, def) = doc_attributes(sema, &node)?;
+ let (docs, doc_mapping) = attributes.docs_with_rangemap(sema.db)?;
+ let (in_expansion_range, link, ns) =
+ extract_definitions_from_docs(&docs).into_iter().find_map(|(range, link, ns)| {
+ let mapped = doc_mapping.map(range)?;
+ (mapped.value.contains(abs_in_expansion_offset)).then(|| (mapped.value, link, ns))
+ })?;
+ // get the relative range to the doc/attribute in the expansion
+ let in_expansion_relative_range = in_expansion_range - descended_prefix_len - token_start;
+ // Apply relative range to the original input comment
+ let absolute_range = in_expansion_relative_range + original_start + prefix_len;
+ let def = resolve_doc_path_for_def(sema.db, def, &link, ns)?;
+ cb(def, node, absolute_range)
+ })
+ }
+}
+
+fn broken_link_clone_cb<'a>(link: BrokenLink<'a>) -> Option<(CowStr<'a>, CowStr<'a>)> {
+ Some((/*url*/ link.reference.clone(), /*title*/ link.reference))
+}
+
+// FIXME:
+// BUG: For Option::Some
+// Returns https://doc.rust-lang.org/nightly/core/prelude/v1/enum.Option.html#variant.Some
+// Instead of https://doc.rust-lang.org/nightly/core/option/enum.Option.html
+//
+// This should cease to be a problem if RFC2988 (Stable Rustdoc URLs) is implemented
+// https://github.com/rust-lang/rfcs/pull/2988
+fn get_doc_link(db: &RootDatabase, def: Definition) -> Option<String> {
+ let (target, file, frag) = filename_and_frag_for_def(db, def)?;
+
+ let mut url = get_doc_base_url(db, target)?;
+
+ if let Some(path) = mod_path_of_def(db, target) {
+ url = url.join(&path).ok()?;
+ }
+
+ url = url.join(&file).ok()?;
+ url.set_fragment(frag.as_deref());
+
+ Some(url.into())
+}
+
+fn rewrite_intra_doc_link(
+ db: &RootDatabase,
+ def: Definition,
+ target: &str,
+ title: &str,
+) -> Option<(String, String)> {
+ let (link, ns) = parse_intra_doc_link(target);
+
+ let resolved = resolve_doc_path_for_def(db, def, link, ns)?;
+ let mut url = get_doc_base_url(db, resolved)?;
+
+ let (_, file, frag) = filename_and_frag_for_def(db, resolved)?;
+ if let Some(path) = mod_path_of_def(db, resolved) {
+ url = url.join(&path).ok()?;
+ }
+
+ url = url.join(&file).ok()?;
+ url.set_fragment(frag.as_deref());
+
+ Some((url.into(), strip_prefixes_suffixes(title).to_string()))
+}
+
+/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`).
+fn rewrite_url_link(db: &RootDatabase, def: Definition, target: &str) -> Option<String> {
+ if !(target.contains('#') || target.contains(".html")) {
+ return None;
+ }
+
+ let mut url = get_doc_base_url(db, def)?;
+ let (def, file, frag) = filename_and_frag_for_def(db, def)?;
+
+ if let Some(path) = mod_path_of_def(db, def) {
+ url = url.join(&path).ok()?;
+ }
+
+ url = url.join(&file).ok()?;
+ url.set_fragment(frag.as_deref());
+ url.join(target).ok().map(Into::into)
+}
+
+fn mod_path_of_def(db: &RootDatabase, def: Definition) -> Option<String> {
+ def.canonical_module_path(db).map(|it| {
+ let mut path = String::new();
+ it.flat_map(|it| it.name(db)).for_each(|name| format_to!(path, "{}/", name));
+ path
+ })
+}
+
+/// Rewrites a markdown document, applying 'callback' to each link.
+fn map_links<'e>(
+ events: impl Iterator<Item = Event<'e>>,
+ callback: impl Fn(&str, &str) -> (Option<LinkType>, String, String),
+) -> impl Iterator<Item = Event<'e>> {
+ let mut in_link = false;
+ // holds the origin link target on start event and the rewritten one on end event
+ let mut end_link_target: Option<CowStr<'_>> = None;
+ // normally link's type is determined by the type of link tag in the end event,
+ // however in some cases we want to change the link type, for example,
+ // `Shortcut` type parsed from Start/End tags doesn't make sense for url links
+ let mut end_link_type: Option<LinkType> = None;
+
+ events.map(move |evt| match evt {
+ Event::Start(Tag::Link(link_type, ref target, _)) => {
+ in_link = true;
+ end_link_target = Some(target.clone());
+ end_link_type = Some(link_type);
+ evt
+ }
+ Event::End(Tag::Link(link_type, target, _)) => {
+ in_link = false;
+ Event::End(Tag::Link(
+ end_link_type.unwrap_or(link_type),
+ end_link_target.take().unwrap_or(target),
+ CowStr::Borrowed(""),
+ ))
+ }
+ Event::Text(s) if in_link => {
+ let (link_type, link_target_s, link_name) =
+ callback(&end_link_target.take().unwrap(), &s);
+ end_link_target = Some(CowStr::Boxed(link_target_s.into()));
+ if !matches!(end_link_type, Some(LinkType::Autolink)) {
+ end_link_type = link_type;
+ }
+ Event::Text(CowStr::Boxed(link_name.into()))
+ }
+ Event::Code(s) if in_link => {
+ let (link_type, link_target_s, link_name) =
+ callback(&end_link_target.take().unwrap(), &s);
+ end_link_target = Some(CowStr::Boxed(link_target_s.into()));
+ if !matches!(end_link_type, Some(LinkType::Autolink)) {
+ end_link_type = link_type;
+ }
+ Event::Code(CowStr::Boxed(link_name.into()))
+ }
+ _ => evt,
+ })
+}
+
+/// Get the root URL for the documentation of a definition.
+///
+/// ```ignore
+/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
+/// ^^^^^^^^^^^^^^^^^^^^^^^^^^
+/// ```
+fn get_doc_base_url(db: &RootDatabase, def: Definition) -> Option<Url> {
+ // special case base url of `BuiltinType` to core
+ // https://github.com/rust-lang/rust-analyzer/issues/12250
+ if let Definition::BuiltinType(..) = def {
+ return Url::parse("https://doc.rust-lang.org/nightly/core/").ok();
+ };
+
+ let krate = def.krate(db)?;
+ let display_name = krate.display_name(db)?;
+
+ let base = match db.crate_graph()[krate.into()].origin {
+ // std and co do not specify `html_root_url` any longer so we gotta handwrite this ourself.
+ // FIXME: Use the toolchains channel instead of nightly
+ CrateOrigin::Lang(
+ origin @ (LangCrateOrigin::Alloc
+ | LangCrateOrigin::Core
+ | LangCrateOrigin::ProcMacro
+ | LangCrateOrigin::Std
+ | LangCrateOrigin::Test),
+ ) => {
+ format!("https://doc.rust-lang.org/nightly/{origin}")
+ }
+ _ => {
+ krate.get_html_root_url(db).or_else(|| {
+ let version = krate.version(db);
+ // Fallback to docs.rs. This uses `display_name` and can never be
+ // correct, but that's what fallbacks are about.
+ //
+ // FIXME: clicking on the link should just open the file in the editor,
+ // instead of falling back to external urls.
+ Some(format!(
+ "https://docs.rs/{krate}/{version}/",
+ krate = display_name,
+ version = version.as_deref().unwrap_or("*")
+ ))
+ })?
+ }
+ };
+ Url::parse(&base).ok()?.join(&format!("{}/", display_name)).ok()
+}
+
+/// Get the filename and extension generated for a symbol by rustdoc.
+///
+/// ```ignore
+/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
+/// ^^^^^^^^^^^^^^^^^^^
+/// ```
+fn filename_and_frag_for_def(
+ db: &dyn HirDatabase,
+ def: Definition,
+) -> Option<(Definition, String, Option<String>)> {
+ if let Some(assoc_item) = def.as_assoc_item(db) {
+ let def = match assoc_item.container(db) {
+ AssocItemContainer::Trait(t) => t.into(),
+ AssocItemContainer::Impl(i) => i.self_ty(db).as_adt()?.into(),
+ };
+ let (_, file, _) = filename_and_frag_for_def(db, def)?;
+ let frag = get_assoc_item_fragment(db, assoc_item)?;
+ return Some((def, file, Some(frag)));
+ }
+
+ let res = match def {
+ Definition::Adt(adt) => match adt {
+ Adt::Struct(s) => format!("struct.{}.html", s.name(db)),
+ Adt::Enum(e) => format!("enum.{}.html", e.name(db)),
+ Adt::Union(u) => format!("union.{}.html", u.name(db)),
+ },
+ Definition::Module(m) => match m.name(db) {
+ // `#[doc(keyword = "...")]` is internal used only by rust compiler
+ Some(name) => match m.attrs(db).by_key("doc").find_string_value_in_tt("keyword") {
+ Some(kw) => {
+ format!("keyword.{}.html", kw.trim_matches('"'))
+ }
+ None => format!("{}/index.html", name),
+ },
+ None => String::from("index.html"),
+ },
+ Definition::Trait(t) => format!("trait.{}.html", t.name(db)),
+ Definition::TypeAlias(t) => format!("type.{}.html", t.name(db)),
+ Definition::BuiltinType(t) => format!("primitive.{}.html", t.name()),
+ Definition::Function(f) => format!("fn.{}.html", f.name(db)),
+ Definition::Variant(ev) => {
+ format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db))
+ }
+ Definition::Const(c) => format!("const.{}.html", c.name(db)?),
+ Definition::Static(s) => format!("static.{}.html", s.name(db)),
+ Definition::Macro(mac) => format!("macro.{}.html", mac.name(db)),
+ Definition::Field(field) => {
+ let def = match field.parent_def(db) {
+ hir::VariantDef::Struct(it) => Definition::Adt(it.into()),
+ hir::VariantDef::Union(it) => Definition::Adt(it.into()),
+ hir::VariantDef::Variant(it) => Definition::Variant(it),
+ };
+ let (_, file, _) = filename_and_frag_for_def(db, def)?;
+ return Some((def, file, Some(format!("structfield.{}", field.name(db)))));
+ }
+ Definition::SelfType(impl_) => {
+ let adt = impl_.self_ty(db).as_adt()?.into();
+ let (_, file, _) = filename_and_frag_for_def(db, adt)?;
+ // FIXME fragment numbering
+ return Some((adt, file, Some(String::from("impl"))));
+ }
+ Definition::Local(_)
+ | Definition::GenericParam(_)
+ | Definition::Label(_)
+ | Definition::BuiltinAttr(_)
+ | Definition::ToolModule(_)
+ | Definition::DeriveHelper(_) => return None,
+ };
+
+ Some((def, res, None))
+}
+
+/// Get the fragment required to link to a specific field, method, associated type, or associated constant.
+///
+/// ```ignore
+/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
+/// ^^^^^^^^^^^^^^
+/// ```
+fn get_assoc_item_fragment(db: &dyn HirDatabase, assoc_item: hir::AssocItem) -> Option<String> {
+ Some(match assoc_item {
+ AssocItem::Function(function) => {
+ let is_trait_method =
+ function.as_assoc_item(db).and_then(|assoc| assoc.containing_trait(db)).is_some();
+ // This distinction may get more complicated when specialization is available.
+ // Rustdoc makes this decision based on whether a method 'has defaultness'.
+ // Currently this is only the case for provided trait methods.
+ if is_trait_method && !function.has_body(db) {
+ format!("tymethod.{}", function.name(db))
+ } else {
+ format!("method.{}", function.name(db))
+ }
+ }
+ AssocItem::Const(constant) => format!("associatedconstant.{}", constant.name(db)?),
+ AssocItem::TypeAlias(ty) => format!("associatedtype.{}", ty.name(db)),
+ })
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/doc_links/intra_doc_links.rs b/src/tools/rust-analyzer/crates/ide/src/doc_links/intra_doc_links.rs
new file mode 100644
index 000000000..1df9aaae2
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/doc_links/intra_doc_links.rs
@@ -0,0 +1,77 @@
+//! Helper tools for intra doc links.
+
+const TYPES: ([&str; 9], [&str; 0]) =
+ (["type", "struct", "enum", "mod", "trait", "union", "module", "prim", "primitive"], []);
+const VALUES: ([&str; 8], [&str; 1]) =
+ (["value", "function", "fn", "method", "const", "static", "mod", "module"], ["()"]);
+const MACROS: ([&str; 2], [&str; 1]) = (["macro", "derive"], ["!"]);
+
+/// Extract the specified namespace from an intra-doc-link if one exists.
+///
+/// # Examples
+///
+/// * `struct MyStruct` -> ("MyStruct", `Namespace::Types`)
+/// * `panic!` -> ("panic", `Namespace::Macros`)
+/// * `fn@from_intra_spec` -> ("from_intra_spec", `Namespace::Values`)
+pub(super) fn parse_intra_doc_link(s: &str) -> (&str, Option<hir::Namespace>) {
+ let s = s.trim_matches('`');
+
+ [
+ (hir::Namespace::Types, (TYPES.0.iter(), TYPES.1.iter())),
+ (hir::Namespace::Values, (VALUES.0.iter(), VALUES.1.iter())),
+ (hir::Namespace::Macros, (MACROS.0.iter(), MACROS.1.iter())),
+ ]
+ .into_iter()
+ .find_map(|(ns, (mut prefixes, mut suffixes))| {
+ if let Some(prefix) = prefixes.find(|&&prefix| {
+ s.starts_with(prefix)
+ && s.chars().nth(prefix.len()).map_or(false, |c| c == '@' || c == ' ')
+ }) {
+ Some((&s[prefix.len() + 1..], ns))
+ } else {
+ suffixes.find_map(|&suffix| s.strip_suffix(suffix).zip(Some(ns)))
+ }
+ })
+ .map_or((s, None), |(s, ns)| (s, Some(ns)))
+}
+
+pub(super) fn strip_prefixes_suffixes(s: &str) -> &str {
+ [
+ (TYPES.0.iter(), TYPES.1.iter()),
+ (VALUES.0.iter(), VALUES.1.iter()),
+ (MACROS.0.iter(), MACROS.1.iter()),
+ ]
+ .into_iter()
+ .find_map(|(mut prefixes, mut suffixes)| {
+ if let Some(prefix) = prefixes.find(|&&prefix| {
+ s.starts_with(prefix)
+ && s.chars().nth(prefix.len()).map_or(false, |c| c == '@' || c == ' ')
+ }) {
+ Some(&s[prefix.len() + 1..])
+ } else {
+ suffixes.find_map(|&suffix| s.strip_suffix(suffix))
+ }
+ })
+ .unwrap_or(s)
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+
+ use super::*;
+
+ fn check(link: &str, expected: Expect) {
+ let (l, a) = parse_intra_doc_link(link);
+ let a = a.map_or_else(String::new, |a| format!(" ({:?})", a));
+ expected.assert_eq(&format!("{}{}", l, a));
+ }
+
+ #[test]
+ fn test_name() {
+ check("foo", expect![[r#"foo"#]]);
+ check("struct Struct", expect![[r#"Struct (Types)"#]]);
+ check("makro!", expect![[r#"makro (Macros)"#]]);
+ check("fn@function", expect![[r#"function (Values)"#]]);
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/doc_links/tests.rs b/src/tools/rust-analyzer/crates/ide/src/doc_links/tests.rs
new file mode 100644
index 000000000..c6bfb6b9d
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/doc_links/tests.rs
@@ -0,0 +1,491 @@
+use expect_test::{expect, Expect};
+use hir::{HasAttrs, Semantics};
+use ide_db::{
+ base_db::{FilePosition, FileRange},
+ defs::Definition,
+ RootDatabase,
+};
+use itertools::Itertools;
+use syntax::{ast, match_ast, AstNode, SyntaxNode};
+
+use crate::{
+ doc_links::{extract_definitions_from_docs, resolve_doc_path_for_def, rewrite_links},
+ fixture, TryToNav,
+};
+
+fn check_external_docs(ra_fixture: &str, expect: Expect) {
+ let (analysis, position) = fixture::position(ra_fixture);
+ let url = analysis.external_docs(position).unwrap().expect("could not find url for symbol");
+
+ expect.assert_eq(&url)
+}
+
+fn check_rewrite(ra_fixture: &str, expect: Expect) {
+ let (analysis, position) = fixture::position(ra_fixture);
+ let sema = &Semantics::new(&*analysis.db);
+ let (cursor_def, docs) = def_under_cursor(sema, &position);
+ let res = rewrite_links(sema.db, docs.as_str(), cursor_def);
+ expect.assert_eq(&res)
+}
+
+fn check_doc_links(ra_fixture: &str) {
+ let key_fn = |&(FileRange { file_id, range }, _): &_| (file_id, range.start());
+
+ let (analysis, position, mut expected) = fixture::annotations(ra_fixture);
+ expected.sort_by_key(key_fn);
+ let sema = &Semantics::new(&*analysis.db);
+ let (cursor_def, docs) = def_under_cursor(sema, &position);
+ let defs = extract_definitions_from_docs(&docs);
+ let actual: Vec<_> = defs
+ .into_iter()
+ .map(|(_, link, ns)| {
+ let def = resolve_doc_path_for_def(sema.db, cursor_def, &link, ns)
+ .unwrap_or_else(|| panic!("Failed to resolve {}", link));
+ let nav_target = def.try_to_nav(sema.db).unwrap();
+ let range =
+ FileRange { file_id: nav_target.file_id, range: nav_target.focus_or_full_range() };
+ (range, link)
+ })
+ .sorted_by_key(key_fn)
+ .collect();
+ assert_eq!(expected, actual);
+}
+
+fn def_under_cursor(
+ sema: &Semantics<'_, RootDatabase>,
+ position: &FilePosition,
+) -> (Definition, hir::Documentation) {
+ let (docs, def) = sema
+ .parse(position.file_id)
+ .syntax()
+ .token_at_offset(position.offset)
+ .left_biased()
+ .unwrap()
+ .parent_ancestors()
+ .find_map(|it| node_to_def(sema, &it))
+ .expect("no def found")
+ .unwrap();
+ let docs = docs.expect("no docs found for cursor def");
+ (def, docs)
+}
+
+fn node_to_def(
+ sema: &Semantics<'_, RootDatabase>,
+ node: &SyntaxNode,
+) -> Option<Option<(Option<hir::Documentation>, Definition)>> {
+ Some(match_ast! {
+ match node {
+ ast::SourceFile(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Module(def))),
+ ast::Module(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Module(def))),
+ ast::Fn(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Function(def))),
+ ast::Struct(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Adt(hir::Adt::Struct(def)))),
+ ast::Union(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Adt(hir::Adt::Union(def)))),
+ ast::Enum(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Adt(hir::Adt::Enum(def)))),
+ ast::Variant(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Variant(def))),
+ ast::Trait(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Trait(def))),
+ ast::Static(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Static(def))),
+ ast::Const(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Const(def))),
+ ast::TypeAlias(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::TypeAlias(def))),
+ ast::Impl(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::SelfType(def))),
+ ast::RecordField(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Field(def))),
+ ast::TupleField(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Field(def))),
+ ast::Macro(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Macro(def))),
+ // ast::Use(it) => sema.to_def(&it).map(|def| (Box::new(it) as _, def.attrs(sema.db))),
+ _ => return None,
+ }
+ })
+}
+
+#[test]
+fn external_docs_doc_url_crate() {
+ check_external_docs(
+ r#"
+//- /main.rs crate:main deps:foo
+use foo$0::Foo;
+//- /lib.rs crate:foo
+pub struct Foo;
+"#,
+ expect![[r#"https://docs.rs/foo/*/foo/index.html"#]],
+ );
+}
+
+#[test]
+fn external_docs_doc_url_std_crate() {
+ check_external_docs(
+ r#"
+//- /main.rs crate:std
+use self$0;
+"#,
+ expect![[r#"https://doc.rust-lang.org/nightly/std/index.html"#]],
+ );
+}
+
+#[test]
+fn external_docs_doc_url_struct() {
+ check_external_docs(
+ r#"
+//- /main.rs crate:foo
+pub struct Fo$0o;
+"#,
+ expect![[r#"https://docs.rs/foo/*/foo/struct.Foo.html"#]],
+ );
+}
+
+#[test]
+fn external_docs_doc_url_struct_field() {
+ check_external_docs(
+ r#"
+//- /main.rs crate:foo
+pub struct Foo {
+ field$0: ()
+}
+"#,
+ expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#structfield.field"##]],
+ );
+}
+
+#[test]
+fn external_docs_doc_url_fn() {
+ check_external_docs(
+ r#"
+//- /main.rs crate:foo
+pub fn fo$0o() {}
+"#,
+ expect![[r#"https://docs.rs/foo/*/foo/fn.foo.html"#]],
+ );
+}
+
+#[test]
+fn external_docs_doc_url_impl_assoc() {
+ check_external_docs(
+ r#"
+//- /main.rs crate:foo
+pub struct Foo;
+impl Foo {
+ pub fn method$0() {}
+}
+"#,
+ expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#method.method"##]],
+ );
+ check_external_docs(
+ r#"
+//- /main.rs crate:foo
+pub struct Foo;
+impl Foo {
+ const CONST$0: () = ();
+}
+"#,
+ expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#associatedconstant.CONST"##]],
+ );
+}
+
+#[test]
+fn external_docs_doc_url_impl_trait_assoc() {
+ check_external_docs(
+ r#"
+//- /main.rs crate:foo
+pub struct Foo;
+pub trait Trait {
+ fn method() {}
+}
+impl Trait for Foo {
+ pub fn method$0() {}
+}
+"#,
+ expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#method.method"##]],
+ );
+ check_external_docs(
+ r#"
+//- /main.rs crate:foo
+pub struct Foo;
+pub trait Trait {
+ const CONST: () = ();
+}
+impl Trait for Foo {
+ const CONST$0: () = ();
+}
+"#,
+ expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#associatedconstant.CONST"##]],
+ );
+ check_external_docs(
+ r#"
+//- /main.rs crate:foo
+pub struct Foo;
+pub trait Trait {
+ type Type;
+}
+impl Trait for Foo {
+ type Type$0 = ();
+}
+"#,
+ expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#associatedtype.Type"##]],
+ );
+}
+
+#[test]
+fn external_docs_doc_url_trait_assoc() {
+ check_external_docs(
+ r#"
+//- /main.rs crate:foo
+pub trait Foo {
+ fn method$0();
+}
+"#,
+ expect![[r##"https://docs.rs/foo/*/foo/trait.Foo.html#tymethod.method"##]],
+ );
+ check_external_docs(
+ r#"
+//- /main.rs crate:foo
+pub trait Foo {
+ const CONST$0: ();
+}
+"#,
+ expect![[r##"https://docs.rs/foo/*/foo/trait.Foo.html#associatedconstant.CONST"##]],
+ );
+ check_external_docs(
+ r#"
+//- /main.rs crate:foo
+pub trait Foo {
+ type Type$0;
+}
+"#,
+ expect![[r##"https://docs.rs/foo/*/foo/trait.Foo.html#associatedtype.Type"##]],
+ );
+}
+
+#[test]
+fn external_docs_trait() {
+ check_external_docs(
+ r#"
+//- /main.rs crate:foo
+trait Trait$0 {}
+"#,
+ expect![[r#"https://docs.rs/foo/*/foo/trait.Trait.html"#]],
+ )
+}
+
+#[test]
+fn external_docs_module() {
+ check_external_docs(
+ r#"
+//- /main.rs crate:foo
+pub mod foo {
+ pub mod ba$0r {}
+}
+"#,
+ expect![[r#"https://docs.rs/foo/*/foo/foo/bar/index.html"#]],
+ )
+}
+
+#[test]
+fn external_docs_reexport_order() {
+ check_external_docs(
+ r#"
+//- /main.rs crate:foo
+pub mod wrapper {
+ pub use module::Item;
+
+ pub mod module {
+ pub struct Item;
+ }
+}
+
+fn foo() {
+ let bar: wrapper::It$0em;
+}
+ "#,
+ expect![[r#"https://docs.rs/foo/*/foo/wrapper/module/struct.Item.html"#]],
+ )
+}
+
+#[test]
+fn doc_links_items_simple() {
+ check_doc_links(
+ r#"
+//- /main.rs crate:main deps:krate
+/// [`krate`]
+//! [`Trait`]
+//! [`function`]
+//! [`CONST`]
+//! [`STATIC`]
+//! [`Struct`]
+//! [`Enum`]
+//! [`Union`]
+//! [`Type`]
+//! [`module`]
+use self$0;
+
+const CONST: () = ();
+ // ^^^^^ CONST
+static STATIC: () = ();
+ // ^^^^^^ STATIC
+trait Trait {
+ // ^^^^^ Trait
+}
+fn function() {}
+// ^^^^^^^^ function
+struct Struct;
+ // ^^^^^^ Struct
+enum Enum {}
+ // ^^^^ Enum
+union Union {__: ()}
+ // ^^^^^ Union
+type Type = ();
+ // ^^^^ Type
+mod module {}
+ // ^^^^^^ module
+//- /krate.rs crate:krate
+// empty
+//^file krate
+"#,
+ )
+}
+
+#[test]
+fn doc_links_inherent_impl_items() {
+ check_doc_links(
+ r#"
+// /// [`Struct::CONST`]
+// /// [`Struct::function`]
+/// FIXME #9694
+struct Struct$0;
+
+impl Struct {
+ const CONST: () = ();
+ fn function() {}
+}
+"#,
+ )
+}
+
+#[test]
+fn doc_links_trait_impl_items() {
+ check_doc_links(
+ r#"
+trait Trait {
+ type Type;
+ const CONST: usize;
+ fn function();
+}
+// /// [`Struct::Type`]
+// /// [`Struct::CONST`]
+// /// [`Struct::function`]
+/// FIXME #9694
+struct Struct$0;
+
+impl Trait for Struct {
+ type Type = ();
+ const CONST: () = ();
+ fn function() {}
+}
+"#,
+ )
+}
+
+#[test]
+fn doc_links_trait_items() {
+ check_doc_links(
+ r#"
+/// [`Trait`]
+/// [`Trait::Type`]
+/// [`Trait::CONST`]
+/// [`Trait::function`]
+trait Trait$0 {
+ // ^^^^^ Trait
+type Type;
+ // ^^^^ Trait::Type
+const CONST: usize;
+ // ^^^^^ Trait::CONST
+fn function();
+// ^^^^^^^^ Trait::function
+}
+ "#,
+ )
+}
+
+#[test]
+fn rewrite_html_root_url() {
+ check_rewrite(
+ r#"
+//- /main.rs crate:foo
+#![doc(arbitrary_attribute = "test", html_root_url = "https:/example.com", arbitrary_attribute2)]
+
+pub mod foo {
+ pub struct Foo;
+}
+/// [Foo](foo::Foo)
+pub struct B$0ar
+"#,
+ expect![[r#"[Foo](https://example.com/foo/foo/struct.Foo.html)"#]],
+ );
+}
+
+#[test]
+fn rewrite_on_field() {
+ check_rewrite(
+ r#"
+//- /main.rs crate:foo
+pub struct Foo {
+ /// [Foo](struct.Foo.html)
+ fie$0ld: ()
+}
+"#,
+ expect![[r#"[Foo](https://docs.rs/foo/*/foo/struct.Foo.html)"#]],
+ );
+}
+
+#[test]
+fn rewrite_struct() {
+ check_rewrite(
+ r#"
+//- /main.rs crate:foo
+/// [Foo]
+pub struct $0Foo;
+"#,
+ expect![[r#"[Foo](https://docs.rs/foo/*/foo/struct.Foo.html)"#]],
+ );
+ check_rewrite(
+ r#"
+//- /main.rs crate:foo
+/// [`Foo`]
+pub struct $0Foo;
+"#,
+ expect![[r#"[`Foo`](https://docs.rs/foo/*/foo/struct.Foo.html)"#]],
+ );
+ check_rewrite(
+ r#"
+//- /main.rs crate:foo
+/// [Foo](struct.Foo.html)
+pub struct $0Foo;
+"#,
+ expect![[r#"[Foo](https://docs.rs/foo/*/foo/struct.Foo.html)"#]],
+ );
+ check_rewrite(
+ r#"
+//- /main.rs crate:foo
+/// [struct Foo](struct.Foo.html)
+pub struct $0Foo;
+"#,
+ expect![[r#"[struct Foo](https://docs.rs/foo/*/foo/struct.Foo.html)"#]],
+ );
+ check_rewrite(
+ r#"
+//- /main.rs crate:foo
+/// [my Foo][foo]
+///
+/// [foo]: Foo
+pub struct $0Foo;
+"#,
+ expect![[r#"[my Foo](https://docs.rs/foo/*/foo/struct.Foo.html)"#]],
+ );
+ check_rewrite(
+ r#"
+//- /main.rs crate:foo
+/// [`foo`]
+///
+/// [`foo`]: Foo
+pub struct $0Foo;
+"#,
+ expect![["[`foo`]"]],
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs b/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs
new file mode 100644
index 000000000..efa8551a0
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs
@@ -0,0 +1,521 @@
+use hir::Semantics;
+use ide_db::{
+ base_db::FileId, helpers::pick_best_token,
+ syntax_helpers::insert_whitespace_into_node::insert_ws_into, RootDatabase,
+};
+use syntax::{ast, ted, AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T};
+
+use crate::FilePosition;
+
+pub struct ExpandedMacro {
+ pub name: String,
+ pub expansion: String,
+}
+
+// Feature: Expand Macro Recursively
+//
+// Shows the full macro expansion of the macro at current cursor.
+//
+// |===
+// | Editor | Action Name
+//
+// | VS Code | **Rust Analyzer: Expand macro recursively**
+// |===
+//
+// image::https://user-images.githubusercontent.com/48062697/113020648-b3973180-917a-11eb-84a9-ecb921293dc5.gif[]
+pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> {
+ let sema = Semantics::new(db);
+ let file = sema.parse(position.file_id);
+
+ let tok = pick_best_token(file.syntax().token_at_offset(position.offset), |kind| match kind {
+ SyntaxKind::IDENT => 1,
+ _ => 0,
+ })?;
+
+ // due to how Rust Analyzer works internally, we need to special case derive attributes,
+ // otherwise they might not get found, e.g. here with the cursor at $0 `#[attr]` would expand:
+ // ```
+ // #[attr]
+ // #[derive($0Foo)]
+ // struct Bar;
+ // ```
+
+ let derive = sema.descend_into_macros(tok.clone()).into_iter().find_map(|descended| {
+ let hir_file = sema.hir_file_for(&descended.parent()?);
+ if !hir_file.is_derive_attr_pseudo_expansion(db) {
+ return None;
+ }
+
+ let name = descended.parent_ancestors().filter_map(ast::Path::cast).last()?.to_string();
+ // up map out of the #[derive] expansion
+ let token = hir::InFile::new(hir_file, descended).upmap(db)?.value;
+ let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
+ let expansions = sema.expand_derive_macro(&attr)?;
+ let idx = attr
+ .token_tree()?
+ .token_trees_and_tokens()
+ .filter_map(NodeOrToken::into_token)
+ .take_while(|it| it != &token)
+ .filter(|it| it.kind() == T![,])
+ .count();
+ let expansion =
+ format(db, SyntaxKind::MACRO_ITEMS, position.file_id, expansions.get(idx).cloned()?);
+ Some(ExpandedMacro { name, expansion })
+ });
+
+ if derive.is_some() {
+ return derive;
+ }
+
+ // FIXME: Intermix attribute and bang! expansions
+ // currently we only recursively expand one of the two types
+ let mut anc = tok.parent_ancestors();
+ let (name, expanded, kind) = loop {
+ let node = anc.next()?;
+
+ if let Some(item) = ast::Item::cast(node.clone()) {
+ if let Some(def) = sema.resolve_attr_macro_call(&item) {
+ break (
+ def.name(db).to_string(),
+ expand_attr_macro_recur(&sema, &item)?,
+ SyntaxKind::MACRO_ITEMS,
+ );
+ }
+ }
+ if let Some(mac) = ast::MacroCall::cast(node) {
+ break (
+ mac.path()?.segment()?.name_ref()?.to_string(),
+ expand_macro_recur(&sema, &mac)?,
+ mac.syntax().parent().map(|it| it.kind()).unwrap_or(SyntaxKind::MACRO_ITEMS),
+ );
+ }
+ };
+
+ // FIXME:
+ // macro expansion may lose all white space information
+ // But we hope someday we can use ra_fmt for that
+ let expansion = format(db, kind, position.file_id, expanded);
+
+ Some(ExpandedMacro { name, expansion })
+}
+
+fn expand_macro_recur(
+ sema: &Semantics<'_, RootDatabase>,
+ macro_call: &ast::MacroCall,
+) -> Option<SyntaxNode> {
+ let expanded = sema.expand(macro_call)?.clone_for_update();
+ expand(sema, expanded, ast::MacroCall::cast, expand_macro_recur)
+}
+
+fn expand_attr_macro_recur(
+ sema: &Semantics<'_, RootDatabase>,
+ item: &ast::Item,
+) -> Option<SyntaxNode> {
+ let expanded = sema.expand_attr_macro(item)?.clone_for_update();
+ expand(sema, expanded, ast::Item::cast, expand_attr_macro_recur)
+}
+
+fn expand<T: AstNode>(
+ sema: &Semantics<'_, RootDatabase>,
+ expanded: SyntaxNode,
+ f: impl FnMut(SyntaxNode) -> Option<T>,
+ exp: impl Fn(&Semantics<'_, RootDatabase>, &T) -> Option<SyntaxNode>,
+) -> Option<SyntaxNode> {
+ let children = expanded.descendants().filter_map(f);
+ let mut replacements = Vec::new();
+
+ for child in children {
+ if let Some(new_node) = exp(sema, &child) {
+ // check if the whole original syntax is replaced
+ if expanded == *child.syntax() {
+ return Some(new_node);
+ }
+ replacements.push((child, new_node));
+ }
+ }
+
+ replacements.into_iter().rev().for_each(|(old, new)| ted::replace(old.syntax(), new));
+ Some(expanded)
+}
+
+fn format(db: &RootDatabase, kind: SyntaxKind, file_id: FileId, expanded: SyntaxNode) -> String {
+ let expansion = insert_ws_into(expanded).to_string();
+
+ _format(db, kind, file_id, &expansion).unwrap_or(expansion)
+}
+
+#[cfg(any(test, target_arch = "wasm32", target_os = "emscripten"))]
+fn _format(
+ _db: &RootDatabase,
+ _kind: SyntaxKind,
+ _file_id: FileId,
+ _expansion: &str,
+) -> Option<String> {
+ None
+}
+
+#[cfg(not(any(test, target_arch = "wasm32", target_os = "emscripten")))]
+fn _format(
+ db: &RootDatabase,
+ kind: SyntaxKind,
+ file_id: FileId,
+ expansion: &str,
+) -> Option<String> {
+ use ide_db::base_db::{FileLoader, SourceDatabase};
+ // hack until we get hygiene working (same character amount to preserve formatting as much as possible)
+ const DOLLAR_CRATE_REPLACE: &str = &"__r_a_";
+ let expansion = expansion.replace("$crate", DOLLAR_CRATE_REPLACE);
+ let (prefix, suffix) = match kind {
+ SyntaxKind::MACRO_PAT => ("fn __(", ": u32);"),
+ SyntaxKind::MACRO_EXPR | SyntaxKind::MACRO_STMTS => ("fn __() {", "}"),
+ SyntaxKind::MACRO_TYPE => ("type __ =", ";"),
+ _ => ("", ""),
+ };
+ let expansion = format!("{prefix}{expansion}{suffix}");
+
+ let &crate_id = db.relevant_crates(file_id).iter().next()?;
+ let edition = db.crate_graph()[crate_id].edition;
+
+ let mut cmd = std::process::Command::new(toolchain::rustfmt());
+ cmd.arg("--edition");
+ cmd.arg(edition.to_string());
+
+ let mut rustfmt = cmd
+ .stdin(std::process::Stdio::piped())
+ .stdout(std::process::Stdio::piped())
+ .stderr(std::process::Stdio::piped())
+ .spawn()
+ .ok()?;
+
+ std::io::Write::write_all(&mut rustfmt.stdin.as_mut()?, expansion.as_bytes()).ok()?;
+
+ let output = rustfmt.wait_with_output().ok()?;
+ let captured_stdout = String::from_utf8(output.stdout).ok()?;
+
+ if output.status.success() && !captured_stdout.trim().is_empty() {
+ let output = captured_stdout.replace(DOLLAR_CRATE_REPLACE, "$crate");
+ let output = output.trim().strip_prefix(prefix)?;
+ let output = match kind {
+ SyntaxKind::MACRO_PAT => {
+ output.strip_suffix(suffix).or_else(|| output.strip_suffix(": u32,\n);"))?
+ }
+ _ => output.strip_suffix(suffix)?,
+ };
+ let trim_indent = stdx::trim_indent(output);
+ tracing::debug!("expand_macro: formatting succeeded");
+ Some(trim_indent)
+ } else {
+ None
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+
+ use crate::fixture;
+
+ #[track_caller]
+ fn check(ra_fixture: &str, expect: Expect) {
+ let (analysis, pos) = fixture::position(ra_fixture);
+ let expansion = analysis.expand_macro(pos).unwrap().unwrap();
+ let actual = format!("{}\n{}", expansion.name, expansion.expansion);
+ expect.assert_eq(&actual);
+ }
+
+ #[test]
+ fn macro_expand_as_keyword() {
+ check(
+ r#"
+macro_rules! bar {
+ ($i:tt) => { $i as _ }
+}
+fn main() {
+ let x: u64 = ba$0r!(5i64);
+}
+"#,
+ expect![[r#"
+ bar
+ 5i64 as _"#]],
+ );
+ }
+
+ #[test]
+ fn macro_expand_underscore() {
+ check(
+ r#"
+macro_rules! bar {
+ ($i:tt) => { for _ in 0..$i {} }
+}
+fn main() {
+ ba$0r!(42);
+}
+"#,
+ expect![[r#"
+ bar
+ for _ in 0..42{}"#]],
+ );
+ }
+
+ #[test]
+ fn macro_expand_recursive_expansion() {
+ check(
+ r#"
+macro_rules! bar {
+ () => { fn b() {} }
+}
+macro_rules! foo {
+ () => { bar!(); }
+}
+macro_rules! baz {
+ () => { foo!(); }
+}
+f$0oo!();
+"#,
+ expect![[r#"
+ foo
+ fn b(){}
+ "#]],
+ );
+ }
+
+ #[test]
+ fn macro_expand_multiple_lines() {
+ check(
+ r#"
+macro_rules! foo {
+ () => {
+ fn some_thing() -> u32 {
+ let a = 0;
+ a + 10
+ }
+ }
+}
+f$0oo!();
+ "#,
+ expect![[r#"
+ foo
+ fn some_thing() -> u32 {
+ let a = 0;
+ a+10
+ }"#]],
+ );
+ }
+
+ #[test]
+ fn macro_expand_match_ast() {
+ check(
+ r#"
+macro_rules! match_ast {
+ (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
+ (match ($node:expr) {
+ $( ast::$ast:ident($it:ident) => $res:block, )*
+ _ => $catch_all:expr $(,)?
+ }) => {{
+ $( if let Some($it) = ast::$ast::cast($node.clone()) $res else )*
+ { $catch_all }
+ }};
+}
+
+fn main() {
+ mat$0ch_ast! {
+ match container {
+ ast::TraitDef(it) => {},
+ ast::ImplDef(it) => {},
+ _ => { continue },
+ }
+ }
+}
+"#,
+ expect![[r#"
+ match_ast
+ {
+ if let Some(it) = ast::TraitDef::cast(container.clone()){}
+ else if let Some(it) = ast::ImplDef::cast(container.clone()){}
+ else {
+ {
+ continue
+ }
+ }
+ }"#]],
+ );
+ }
+
+ #[test]
+ fn macro_expand_match_ast_inside_let_statement() {
+ check(
+ r#"
+macro_rules! match_ast {
+ (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
+ (match ($node:expr) {}) => {{}};
+}
+
+fn main() {
+ let p = f(|it| {
+ let res = mat$0ch_ast! { match c {}};
+ Some(res)
+ })?;
+}
+"#,
+ expect![[r#"
+ match_ast
+ {}"#]],
+ );
+ }
+
+ #[test]
+ fn macro_expand_inner_macro_rules() {
+ check(
+ r#"
+macro_rules! foo {
+ ($t:tt) => {{
+ macro_rules! bar {
+ () => {
+ $t
+ }
+ }
+ bar!()
+ }};
+}
+
+fn main() {
+ foo$0!(42);
+}
+ "#,
+ expect![[r#"
+ foo
+ {
+ macro_rules! bar {
+ () => {
+ 42
+ }
+ }
+ 42
+ }"#]],
+ );
+ }
+
+ #[test]
+ fn macro_expand_inner_macro_fail_to_expand() {
+ check(
+ r#"
+macro_rules! bar {
+ (BAD) => {};
+}
+macro_rules! foo {
+ () => {bar!()};
+}
+
+fn main() {
+ let res = fo$0o!();
+}
+"#,
+ expect![[r#"
+ foo
+ "#]],
+ );
+ }
+
+ #[test]
+ fn macro_expand_with_dollar_crate() {
+ check(
+ r#"
+#[macro_export]
+macro_rules! bar {
+ () => {0};
+}
+macro_rules! foo {
+ () => {$crate::bar!()};
+}
+
+fn main() {
+ let res = fo$0o!();
+}
+"#,
+ expect![[r#"
+ foo
+ 0"#]],
+ );
+ }
+
+ #[test]
+ fn macro_expand_with_dyn_absolute_path() {
+ check(
+ r#"
+macro_rules! foo {
+ () => {fn f<T>(_: &dyn ::std::marker::Copy) {}};
+}
+
+fn main() {
+ let res = fo$0o!();
+}
+"#,
+ expect![[r#"
+ foo
+ fn f<T>(_: &dyn ::std::marker::Copy){}"#]],
+ );
+ }
+
+ #[test]
+ fn macro_expand_derive() {
+ check(
+ r#"
+//- proc_macros: identity
+//- minicore: clone, derive
+
+#[proc_macros::identity]
+#[derive(C$0lone)]
+struct Foo {}
+"#,
+ expect![[r#"
+ Clone
+ impl < >core::clone::Clone for Foo< >{}
+ "#]],
+ );
+ }
+
+ #[test]
+ fn macro_expand_derive2() {
+ check(
+ r#"
+//- minicore: copy, clone, derive
+
+#[derive(Cop$0y)]
+#[derive(Clone)]
+struct Foo {}
+"#,
+ expect![[r#"
+ Copy
+ impl < >core::marker::Copy for Foo< >{}
+ "#]],
+ );
+ }
+
+ #[test]
+ fn macro_expand_derive_multi() {
+ check(
+ r#"
+//- minicore: copy, clone, derive
+
+#[derive(Cop$0y, Clone)]
+struct Foo {}
+"#,
+ expect![[r#"
+ Copy
+ impl < >core::marker::Copy for Foo< >{}
+ "#]],
+ );
+ check(
+ r#"
+//- minicore: copy, clone, derive
+
+#[derive(Copy, Cl$0one)]
+struct Foo {}
+"#,
+ expect![[r#"
+ Clone
+ impl < >core::clone::Clone for Foo< >{}
+ "#]],
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/extend_selection.rs b/src/tools/rust-analyzer/crates/ide/src/extend_selection.rs
new file mode 100644
index 000000000..45f1fd748
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/extend_selection.rs
@@ -0,0 +1,662 @@
+use std::iter::successors;
+
+use hir::Semantics;
+use ide_db::RootDatabase;
+use syntax::{
+ algo::{self, skip_trivia_token},
+ ast::{self, AstNode, AstToken},
+ Direction, NodeOrToken,
+ SyntaxKind::{self, *},
+ SyntaxNode, SyntaxToken, TextRange, TextSize, TokenAtOffset, T,
+};
+
+use crate::FileRange;
+
+// Feature: Expand and Shrink Selection
+//
+// Extends or shrinks the current selection to the encompassing syntactic construct
+// (expression, statement, item, module, etc). It works with multiple cursors.
+//
+// This is a standard LSP feature and not a protocol extension.
+//
+// |===
+// | Editor | Shortcut
+//
+// | VS Code | kbd:[Alt+Shift+→], kbd:[Alt+Shift+←]
+// |===
+//
+// image::https://user-images.githubusercontent.com/48062697/113020651-b42fc800-917a-11eb-8a4f-cf1a07859fac.gif[]
+pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange {
+ let sema = Semantics::new(db);
+ let src = sema.parse(frange.file_id);
+ try_extend_selection(&sema, src.syntax(), frange).unwrap_or(frange.range)
+}
+
+fn try_extend_selection(
+ sema: &Semantics<'_, RootDatabase>,
+ root: &SyntaxNode,
+ frange: FileRange,
+) -> Option<TextRange> {
+ let range = frange.range;
+
+ let string_kinds = [COMMENT, STRING, BYTE_STRING];
+ let list_kinds = [
+ RECORD_PAT_FIELD_LIST,
+ MATCH_ARM_LIST,
+ RECORD_FIELD_LIST,
+ TUPLE_FIELD_LIST,
+ RECORD_EXPR_FIELD_LIST,
+ VARIANT_LIST,
+ USE_TREE_LIST,
+ GENERIC_PARAM_LIST,
+ GENERIC_ARG_LIST,
+ TYPE_BOUND_LIST,
+ PARAM_LIST,
+ ARG_LIST,
+ ARRAY_EXPR,
+ TUPLE_EXPR,
+ TUPLE_TYPE,
+ TUPLE_PAT,
+ WHERE_CLAUSE,
+ ];
+
+ if range.is_empty() {
+ let offset = range.start();
+ let mut leaves = root.token_at_offset(offset);
+ if leaves.clone().all(|it| it.kind() == WHITESPACE) {
+ return Some(extend_ws(root, leaves.next()?, offset));
+ }
+ let leaf_range = match leaves {
+ TokenAtOffset::None => return None,
+ TokenAtOffset::Single(l) => {
+ if string_kinds.contains(&l.kind()) {
+ extend_single_word_in_comment_or_string(&l, offset)
+ .unwrap_or_else(|| l.text_range())
+ } else {
+ l.text_range()
+ }
+ }
+ TokenAtOffset::Between(l, r) => pick_best(l, r).text_range(),
+ };
+ return Some(leaf_range);
+ };
+ let node = match root.covering_element(range) {
+ NodeOrToken::Token(token) => {
+ if token.text_range() != range {
+ return Some(token.text_range());
+ }
+ if let Some(comment) = ast::Comment::cast(token.clone()) {
+ if let Some(range) = extend_comments(comment) {
+ return Some(range);
+ }
+ }
+ token.parent()?
+ }
+ NodeOrToken::Node(node) => node,
+ };
+
+ // if we are in single token_tree, we maybe live in macro or attr
+ if node.kind() == TOKEN_TREE {
+ if let Some(macro_call) = node.ancestors().find_map(ast::MacroCall::cast) {
+ if let Some(range) = extend_tokens_from_range(sema, macro_call, range) {
+ return Some(range);
+ }
+ }
+ }
+
+ if node.text_range() != range {
+ return Some(node.text_range());
+ }
+
+ let node = shallowest_node(&node);
+
+ if node.parent().map(|n| list_kinds.contains(&n.kind())) == Some(true) {
+ if let Some(range) = extend_list_item(&node) {
+ return Some(range);
+ }
+ }
+
+ node.parent().map(|it| it.text_range())
+}
+
+fn extend_tokens_from_range(
+ sema: &Semantics<'_, RootDatabase>,
+ macro_call: ast::MacroCall,
+ original_range: TextRange,
+) -> Option<TextRange> {
+ let src = macro_call.syntax().covering_element(original_range);
+ let (first_token, last_token) = match src {
+ NodeOrToken::Node(it) => (it.first_token()?, it.last_token()?),
+ NodeOrToken::Token(it) => (it.clone(), it),
+ };
+
+ let mut first_token = skip_trivia_token(first_token, Direction::Next)?;
+ let mut last_token = skip_trivia_token(last_token, Direction::Prev)?;
+
+ while !original_range.contains_range(first_token.text_range()) {
+ first_token = skip_trivia_token(first_token.next_token()?, Direction::Next)?;
+ }
+ while !original_range.contains_range(last_token.text_range()) {
+ last_token = skip_trivia_token(last_token.prev_token()?, Direction::Prev)?;
+ }
+
+ // compute original mapped token range
+ let extended = {
+ let fst_expanded = sema.descend_into_macros_single(first_token.clone());
+ let lst_expanded = sema.descend_into_macros_single(last_token.clone());
+ let mut lca =
+ algo::least_common_ancestor(&fst_expanded.parent()?, &lst_expanded.parent()?)?;
+ lca = shallowest_node(&lca);
+ if lca.first_token() == Some(fst_expanded) && lca.last_token() == Some(lst_expanded) {
+ lca = lca.parent()?;
+ }
+ lca
+ };
+
+ // Compute parent node range
+ let validate = |token: &SyntaxToken| -> bool {
+ let expanded = sema.descend_into_macros_single(token.clone());
+ let parent = match expanded.parent() {
+ Some(it) => it,
+ None => return false,
+ };
+ algo::least_common_ancestor(&extended, &parent).as_ref() == Some(&extended)
+ };
+
+ // Find the first and last text range under expanded parent
+ let first = successors(Some(first_token), |token| {
+ let token = token.prev_token()?;
+ skip_trivia_token(token, Direction::Prev)
+ })
+ .take_while(validate)
+ .last()?;
+
+ let last = successors(Some(last_token), |token| {
+ let token = token.next_token()?;
+ skip_trivia_token(token, Direction::Next)
+ })
+ .take_while(validate)
+ .last()?;
+
+ let range = first.text_range().cover(last.text_range());
+ if range.contains_range(original_range) && original_range != range {
+ Some(range)
+ } else {
+ None
+ }
+}
+
+/// Find the shallowest node with same range, which allows us to traverse siblings.
+fn shallowest_node(node: &SyntaxNode) -> SyntaxNode {
+ node.ancestors().take_while(|n| n.text_range() == node.text_range()).last().unwrap()
+}
+
+fn extend_single_word_in_comment_or_string(
+ leaf: &SyntaxToken,
+ offset: TextSize,
+) -> Option<TextRange> {
+ let text: &str = leaf.text();
+ let cursor_position: u32 = (offset - leaf.text_range().start()).into();
+
+ let (before, after) = text.split_at(cursor_position as usize);
+
+ fn non_word_char(c: char) -> bool {
+ !(c.is_alphanumeric() || c == '_')
+ }
+
+ let start_idx = before.rfind(non_word_char)? as u32;
+ let end_idx = after.find(non_word_char).unwrap_or_else(|| after.len()) as u32;
+
+ let from: TextSize = (start_idx + 1).into();
+ let to: TextSize = (cursor_position + end_idx).into();
+
+ let range = TextRange::new(from, to);
+ if range.is_empty() {
+ None
+ } else {
+ Some(range + leaf.text_range().start())
+ }
+}
+
+fn extend_ws(root: &SyntaxNode, ws: SyntaxToken, offset: TextSize) -> TextRange {
+ let ws_text = ws.text();
+ let suffix = TextRange::new(offset, ws.text_range().end()) - ws.text_range().start();
+ let prefix = TextRange::new(ws.text_range().start(), offset) - ws.text_range().start();
+ let ws_suffix = &ws_text[suffix];
+ let ws_prefix = &ws_text[prefix];
+ if ws_text.contains('\n') && !ws_suffix.contains('\n') {
+ if let Some(node) = ws.next_sibling_or_token() {
+ let start = match ws_prefix.rfind('\n') {
+ Some(idx) => ws.text_range().start() + TextSize::from((idx + 1) as u32),
+ None => node.text_range().start(),
+ };
+ let end = if root.text().char_at(node.text_range().end()) == Some('\n') {
+ node.text_range().end() + TextSize::of('\n')
+ } else {
+ node.text_range().end()
+ };
+ return TextRange::new(start, end);
+ }
+ }
+ ws.text_range()
+}
+
+fn pick_best(l: SyntaxToken, r: SyntaxToken) -> SyntaxToken {
+ return if priority(&r) > priority(&l) { r } else { l };
+ fn priority(n: &SyntaxToken) -> usize {
+ match n.kind() {
+ WHITESPACE => 0,
+ IDENT | T![self] | T![super] | T![crate] | T![Self] | LIFETIME_IDENT => 2,
+ _ => 1,
+ }
+ }
+}
+
+/// Extend list item selection to include nearby delimiter and whitespace.
+fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> {
+ fn is_single_line_ws(node: &SyntaxToken) -> bool {
+ node.kind() == WHITESPACE && !node.text().contains('\n')
+ }
+
+ fn nearby_delimiter(
+ delimiter_kind: SyntaxKind,
+ node: &SyntaxNode,
+ dir: Direction,
+ ) -> Option<SyntaxToken> {
+ node.siblings_with_tokens(dir)
+ .skip(1)
+ .find(|node| match node {
+ NodeOrToken::Node(_) => true,
+ NodeOrToken::Token(it) => !is_single_line_ws(it),
+ })
+ .and_then(|it| it.into_token())
+ .filter(|node| node.kind() == delimiter_kind)
+ }
+
+ let delimiter = match node.kind() {
+ TYPE_BOUND => T![+],
+ _ => T![,],
+ };
+
+ if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Next) {
+ // Include any following whitespace when delimiter is after list item.
+ let final_node = delimiter_node
+ .next_sibling_or_token()
+ .and_then(|it| it.into_token())
+ .filter(is_single_line_ws)
+ .unwrap_or(delimiter_node);
+
+ return Some(TextRange::new(node.text_range().start(), final_node.text_range().end()));
+ }
+ if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Prev) {
+ return Some(TextRange::new(delimiter_node.text_range().start(), node.text_range().end()));
+ }
+
+ None
+}
+
+fn extend_comments(comment: ast::Comment) -> Option<TextRange> {
+ let prev = adj_comments(&comment, Direction::Prev);
+ let next = adj_comments(&comment, Direction::Next);
+ if prev != next {
+ Some(TextRange::new(prev.syntax().text_range().start(), next.syntax().text_range().end()))
+ } else {
+ None
+ }
+}
+
+fn adj_comments(comment: &ast::Comment, dir: Direction) -> ast::Comment {
+ let mut res = comment.clone();
+ for element in comment.syntax().siblings_with_tokens(dir) {
+ let token = match element.as_token() {
+ None => break,
+ Some(token) => token,
+ };
+ if let Some(c) = ast::Comment::cast(token.clone()) {
+ res = c
+ } else if token.kind() != WHITESPACE || token.text().contains("\n\n") {
+ break;
+ }
+ }
+ res
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::fixture;
+
+ use super::*;
+
+ fn do_check(before: &str, afters: &[&str]) {
+ let (analysis, position) = fixture::position(before);
+ let before = analysis.file_text(position.file_id).unwrap();
+ let range = TextRange::empty(position.offset);
+ let mut frange = FileRange { file_id: position.file_id, range };
+
+ for &after in afters {
+ frange.range = analysis.extend_selection(frange).unwrap();
+ let actual = &before[frange.range];
+ assert_eq!(after, actual);
+ }
+ }
+
+ #[test]
+ fn test_extend_selection_arith() {
+ do_check(r#"fn foo() { $01 + 1 }"#, &["1", "1 + 1", "{ 1 + 1 }"]);
+ }
+
+ #[test]
+ fn test_extend_selection_list() {
+ do_check(r#"fn foo($0x: i32) {}"#, &["x", "x: i32"]);
+ do_check(r#"fn foo($0x: i32, y: i32) {}"#, &["x", "x: i32", "x: i32, "]);
+ do_check(r#"fn foo($0x: i32,y: i32) {}"#, &["x", "x: i32", "x: i32,", "(x: i32,y: i32)"]);
+ do_check(r#"fn foo(x: i32, $0y: i32) {}"#, &["y", "y: i32", ", y: i32"]);
+ do_check(r#"fn foo(x: i32, $0y: i32, ) {}"#, &["y", "y: i32", "y: i32, "]);
+ do_check(r#"fn foo(x: i32,$0y: i32) {}"#, &["y", "y: i32", ",y: i32"]);
+
+ do_check(r#"const FOO: [usize; 2] = [ 22$0 , 33];"#, &["22", "22 , "]);
+ do_check(r#"const FOO: [usize; 2] = [ 22 , 33$0];"#, &["33", ", 33"]);
+ do_check(r#"const FOO: [usize; 2] = [ 22 , 33$0 ,];"#, &["33", "33 ,", "[ 22 , 33 ,]"]);
+
+ do_check(r#"fn main() { (1, 2$0) }"#, &["2", ", 2", "(1, 2)"]);
+
+ do_check(
+ r#"
+const FOO: [usize; 2] = [
+ 22,
+ $033,
+]"#,
+ &["33", "33,"],
+ );
+
+ do_check(
+ r#"
+const FOO: [usize; 2] = [
+ 22
+ , 33$0,
+]"#,
+ &["33", "33,"],
+ );
+ }
+
+ #[test]
+ fn test_extend_selection_start_of_the_line() {
+ do_check(
+ r#"
+impl S {
+$0 fn foo() {
+
+ }
+}"#,
+ &[" fn foo() {\n\n }\n"],
+ );
+ }
+
+ #[test]
+ fn test_extend_selection_doc_comments() {
+ do_check(
+ r#"
+struct A;
+
+/// bla
+/// bla
+struct B {
+ $0
+}
+ "#,
+ &["\n \n", "{\n \n}", "/// bla\n/// bla\nstruct B {\n \n}"],
+ )
+ }
+
+ #[test]
+ fn test_extend_selection_comments() {
+ do_check(
+ r#"
+fn bar(){}
+
+// fn foo() {
+// 1 + $01
+// }
+
+// fn foo(){}
+ "#,
+ &["1", "// 1 + 1", "// fn foo() {\n// 1 + 1\n// }"],
+ );
+
+ do_check(
+ r#"
+// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+// pub enum Direction {
+// $0 Next,
+// Prev
+// }
+"#,
+ &[
+ "// Next,",
+ "// #[derive(Debug, Clone, Copy, PartialEq, Eq)]\n// pub enum Direction {\n// Next,\n// Prev\n// }",
+ ],
+ );
+
+ do_check(
+ r#"
+/*
+foo
+_bar1$0*/
+"#,
+ &["_bar1", "/*\nfoo\n_bar1*/"],
+ );
+
+ do_check(r#"//!$0foo_2 bar"#, &["foo_2", "//!foo_2 bar"]);
+
+ do_check(r#"/$0/foo bar"#, &["//foo bar"]);
+ }
+
+ #[test]
+ fn test_extend_selection_prefer_idents() {
+ do_check(
+ r#"
+fn main() { foo$0+bar;}
+"#,
+ &["foo", "foo+bar"],
+ );
+ do_check(
+ r#"
+fn main() { foo+$0bar;}
+"#,
+ &["bar", "foo+bar"],
+ );
+ }
+
+ #[test]
+ fn test_extend_selection_prefer_lifetimes() {
+ do_check(r#"fn foo<$0'a>() {}"#, &["'a", "<'a>"]);
+ do_check(r#"fn foo<'a$0>() {}"#, &["'a", "<'a>"]);
+ }
+
+ #[test]
+ fn test_extend_selection_select_first_word() {
+ do_check(r#"// foo bar b$0az quxx"#, &["baz", "// foo bar baz quxx"]);
+ do_check(
+ r#"
+impl S {
+fn foo() {
+// hel$0lo world
+}
+}
+"#,
+ &["hello", "// hello world"],
+ );
+ }
+
+ #[test]
+ fn test_extend_selection_string() {
+ do_check(
+ r#"
+fn bar(){}
+
+" fn f$0oo() {"
+"#,
+ &["foo", "\" fn foo() {\""],
+ );
+ }
+
+ #[test]
+ fn test_extend_trait_bounds_list_in_where_clause() {
+ do_check(
+ r#"
+fn foo<R>()
+ where
+ R: req::Request + 'static,
+ R::Params: DeserializeOwned$0 + panic::UnwindSafe + 'static,
+ R::Result: Serialize + 'static,
+"#,
+ &[
+ "DeserializeOwned",
+ "DeserializeOwned + ",
+ "DeserializeOwned + panic::UnwindSafe + 'static",
+ "R::Params: DeserializeOwned + panic::UnwindSafe + 'static",
+ "R::Params: DeserializeOwned + panic::UnwindSafe + 'static,",
+ ],
+ );
+ do_check(r#"fn foo<T>() where T: $0Copy"#, &["Copy"]);
+ do_check(r#"fn foo<T>() where T: $0Copy + Display"#, &["Copy", "Copy + "]);
+ do_check(r#"fn foo<T>() where T: $0Copy +Display"#, &["Copy", "Copy +"]);
+ do_check(r#"fn foo<T>() where T: $0Copy+Display"#, &["Copy", "Copy+"]);
+ do_check(r#"fn foo<T>() where T: Copy + $0Display"#, &["Display", "+ Display"]);
+ do_check(r#"fn foo<T>() where T: Copy + $0Display + Sync"#, &["Display", "Display + "]);
+ do_check(r#"fn foo<T>() where T: Copy +$0Display"#, &["Display", "+Display"]);
+ }
+
+ #[test]
+ fn test_extend_trait_bounds_list_inline() {
+ do_check(r#"fn foo<T: $0Copy>() {}"#, &["Copy"]);
+ do_check(r#"fn foo<T: $0Copy + Display>() {}"#, &["Copy", "Copy + "]);
+ do_check(r#"fn foo<T: $0Copy +Display>() {}"#, &["Copy", "Copy +"]);
+ do_check(r#"fn foo<T: $0Copy+Display>() {}"#, &["Copy", "Copy+"]);
+ do_check(r#"fn foo<T: Copy + $0Display>() {}"#, &["Display", "+ Display"]);
+ do_check(r#"fn foo<T: Copy + $0Display + Sync>() {}"#, &["Display", "Display + "]);
+ do_check(r#"fn foo<T: Copy +$0Display>() {}"#, &["Display", "+Display"]);
+ do_check(
+ r#"fn foo<T: Copy$0 + Display, U: Copy>() {}"#,
+ &[
+ "Copy",
+ "Copy + ",
+ "Copy + Display",
+ "T: Copy + Display",
+ "T: Copy + Display, ",
+ "<T: Copy + Display, U: Copy>",
+ ],
+ );
+ }
+
+ #[test]
+ fn test_extend_selection_on_tuple_in_type() {
+ do_check(
+ r#"fn main() { let _: (krate, $0_crate_def_map, module_id) = (); }"#,
+ &["_crate_def_map", "_crate_def_map, ", "(krate, _crate_def_map, module_id)"],
+ );
+ // white space variations
+ do_check(
+ r#"fn main() { let _: (krate,$0_crate_def_map,module_id) = (); }"#,
+ &["_crate_def_map", "_crate_def_map,", "(krate,_crate_def_map,module_id)"],
+ );
+ do_check(
+ r#"
+fn main() { let _: (
+ krate,
+ _crate$0_def_map,
+ module_id
+) = (); }"#,
+ &[
+ "_crate_def_map",
+ "_crate_def_map,",
+ "(\n krate,\n _crate_def_map,\n module_id\n)",
+ ],
+ );
+ }
+
+ #[test]
+ fn test_extend_selection_on_tuple_in_rvalue() {
+ do_check(
+ r#"fn main() { let var = (krate, _crate_def_map$0, module_id); }"#,
+ &["_crate_def_map", "_crate_def_map, ", "(krate, _crate_def_map, module_id)"],
+ );
+ // white space variations
+ do_check(
+ r#"fn main() { let var = (krate,_crate$0_def_map,module_id); }"#,
+ &["_crate_def_map", "_crate_def_map,", "(krate,_crate_def_map,module_id)"],
+ );
+ do_check(
+ r#"
+fn main() { let var = (
+ krate,
+ _crate_def_map$0,
+ module_id
+); }"#,
+ &[
+ "_crate_def_map",
+ "_crate_def_map,",
+ "(\n krate,\n _crate_def_map,\n module_id\n)",
+ ],
+ );
+ }
+
+ #[test]
+ fn test_extend_selection_on_tuple_pat() {
+ do_check(
+ r#"fn main() { let (krate, _crate_def_map$0, module_id) = var; }"#,
+ &["_crate_def_map", "_crate_def_map, ", "(krate, _crate_def_map, module_id)"],
+ );
+ // white space variations
+ do_check(
+ r#"fn main() { let (krate,_crate$0_def_map,module_id) = var; }"#,
+ &["_crate_def_map", "_crate_def_map,", "(krate,_crate_def_map,module_id)"],
+ );
+ do_check(
+ r#"
+fn main() { let (
+ krate,
+ _crate_def_map$0,
+ module_id
+) = var; }"#,
+ &[
+ "_crate_def_map",
+ "_crate_def_map,",
+ "(\n krate,\n _crate_def_map,\n module_id\n)",
+ ],
+ );
+ }
+
+ #[test]
+ fn extend_selection_inside_macros() {
+ do_check(
+ r#"macro_rules! foo { ($item:item) => {$item} }
+ foo!{fn hello(na$0me:usize){}}"#,
+ &[
+ "name",
+ "name:usize",
+ "(name:usize)",
+ "fn hello(name:usize){}",
+ "{fn hello(name:usize){}}",
+ "foo!{fn hello(name:usize){}}",
+ ],
+ );
+ }
+
+ #[test]
+ fn extend_selection_inside_recur_macros() {
+ do_check(
+ r#" macro_rules! foo2 { ($item:item) => {$item} }
+ macro_rules! foo { ($item:item) => {foo2!($item);} }
+ foo!{fn hello(na$0me:usize){}}"#,
+ &[
+ "name",
+ "name:usize",
+ "(name:usize)",
+ "fn hello(name:usize){}",
+ "{fn hello(name:usize){}}",
+ "foo!{fn hello(name:usize){}}",
+ ],
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/file_structure.rs b/src/tools/rust-analyzer/crates/ide/src/file_structure.rs
new file mode 100644
index 000000000..68fd0952b
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/file_structure.rs
@@ -0,0 +1,579 @@
+use ide_db::SymbolKind;
+use syntax::{
+ ast::{self, HasAttrs, HasGenericParams, HasName},
+ match_ast, AstNode, AstToken, NodeOrToken, SourceFile, SyntaxNode, SyntaxToken, TextRange,
+ WalkEvent,
+};
+
+#[derive(Debug, Clone)]
+pub struct StructureNode {
+ pub parent: Option<usize>,
+ pub label: String,
+ pub navigation_range: TextRange,
+ pub node_range: TextRange,
+ pub kind: StructureNodeKind,
+ pub detail: Option<String>,
+ pub deprecated: bool,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub enum StructureNodeKind {
+ SymbolKind(SymbolKind),
+ Region,
+}
+
+// Feature: File Structure
+//
+// Provides a tree of the symbols defined in the file. Can be used to
+//
+// * fuzzy search symbol in a file (super useful)
+// * draw breadcrumbs to describe the context around the cursor
+// * draw outline of the file
+//
+// |===
+// | Editor | Shortcut
+//
+// | VS Code | kbd:[Ctrl+Shift+O]
+// |===
+//
+// image::https://user-images.githubusercontent.com/48062697/113020654-b42fc800-917a-11eb-8388-e7dc4d92b02e.gif[]
+
+pub(crate) fn file_structure(file: &SourceFile) -> Vec<StructureNode> {
+ let mut res = Vec::new();
+ let mut stack = Vec::new();
+
+ for event in file.syntax().preorder_with_tokens() {
+ match event {
+ WalkEvent::Enter(NodeOrToken::Node(node)) => {
+ if let Some(mut symbol) = structure_node(&node) {
+ symbol.parent = stack.last().copied();
+ stack.push(res.len());
+ res.push(symbol);
+ }
+ }
+ WalkEvent::Leave(NodeOrToken::Node(node)) => {
+ if structure_node(&node).is_some() {
+ stack.pop().unwrap();
+ }
+ }
+ WalkEvent::Enter(NodeOrToken::Token(token)) => {
+ if let Some(mut symbol) = structure_token(token) {
+ symbol.parent = stack.last().copied();
+ stack.push(res.len());
+ res.push(symbol);
+ }
+ }
+ WalkEvent::Leave(NodeOrToken::Token(token)) => {
+ if structure_token(token).is_some() {
+ stack.pop().unwrap();
+ }
+ }
+ }
+ }
+ res
+}
+
+fn structure_node(node: &SyntaxNode) -> Option<StructureNode> {
+ fn decl<N: HasName + HasAttrs>(node: N, kind: StructureNodeKind) -> Option<StructureNode> {
+ decl_with_detail(&node, None, kind)
+ }
+
+ fn decl_with_type_ref<N: HasName + HasAttrs>(
+ node: &N,
+ type_ref: Option<ast::Type>,
+ kind: StructureNodeKind,
+ ) -> Option<StructureNode> {
+ let detail = type_ref.map(|type_ref| {
+ let mut detail = String::new();
+ collapse_ws(type_ref.syntax(), &mut detail);
+ detail
+ });
+ decl_with_detail(node, detail, kind)
+ }
+
+ fn decl_with_detail<N: HasName + HasAttrs>(
+ node: &N,
+ detail: Option<String>,
+ kind: StructureNodeKind,
+ ) -> Option<StructureNode> {
+ let name = node.name()?;
+
+ Some(StructureNode {
+ parent: None,
+ label: name.text().to_string(),
+ navigation_range: name.syntax().text_range(),
+ node_range: node.syntax().text_range(),
+ kind,
+ detail,
+ deprecated: node.attrs().filter_map(|x| x.simple_name()).any(|x| x == "deprecated"),
+ })
+ }
+
+ fn collapse_ws(node: &SyntaxNode, output: &mut String) {
+ let mut can_insert_ws = false;
+ node.text().for_each_chunk(|chunk| {
+ for line in chunk.lines() {
+ let line = line.trim();
+ if line.is_empty() {
+ if can_insert_ws {
+ output.push(' ');
+ can_insert_ws = false;
+ }
+ } else {
+ output.push_str(line);
+ can_insert_ws = true;
+ }
+ }
+ })
+ }
+
+ match_ast! {
+ match node {
+ ast::Fn(it) => {
+ let mut detail = String::from("fn");
+ if let Some(type_param_list) = it.generic_param_list() {
+ collapse_ws(type_param_list.syntax(), &mut detail);
+ }
+ if let Some(param_list) = it.param_list() {
+ collapse_ws(param_list.syntax(), &mut detail);
+ }
+ if let Some(ret_type) = it.ret_type() {
+ detail.push(' ');
+ collapse_ws(ret_type.syntax(), &mut detail);
+ }
+
+ decl_with_detail(&it, Some(detail), StructureNodeKind::SymbolKind(SymbolKind::Function))
+ },
+ ast::Struct(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Struct)),
+ ast::Union(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Union)),
+ ast::Enum(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Enum)),
+ ast::Variant(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Variant)),
+ ast::Trait(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Trait)),
+ ast::Module(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Module)),
+ ast::TypeAlias(it) => decl_with_type_ref(&it, it.ty(), StructureNodeKind::SymbolKind(SymbolKind::TypeAlias)),
+ ast::RecordField(it) => decl_with_type_ref(&it, it.ty(), StructureNodeKind::SymbolKind(SymbolKind::Field)),
+ ast::Const(it) => decl_with_type_ref(&it, it.ty(), StructureNodeKind::SymbolKind(SymbolKind::Const)),
+ ast::Static(it) => decl_with_type_ref(&it, it.ty(), StructureNodeKind::SymbolKind(SymbolKind::Static)),
+ ast::Impl(it) => {
+ let target_type = it.self_ty()?;
+ let target_trait = it.trait_();
+ let label = match target_trait {
+ None => format!("impl {}", target_type.syntax().text()),
+ Some(t) => {
+ format!("impl {} for {}", t.syntax().text(), target_type.syntax().text(),)
+ }
+ };
+
+ let node = StructureNode {
+ parent: None,
+ label,
+ navigation_range: target_type.syntax().text_range(),
+ node_range: it.syntax().text_range(),
+ kind: StructureNodeKind::SymbolKind(SymbolKind::Impl),
+ detail: None,
+ deprecated: false,
+ };
+ Some(node)
+ },
+ ast::Macro(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Macro)),
+ _ => None,
+ }
+ }
+}
+
+fn structure_token(token: SyntaxToken) -> Option<StructureNode> {
+ if let Some(comment) = ast::Comment::cast(token) {
+ let text = comment.text().trim();
+
+ if let Some(region_name) = text.strip_prefix("// region:").map(str::trim) {
+ return Some(StructureNode {
+ parent: None,
+ label: region_name.to_string(),
+ navigation_range: comment.syntax().text_range(),
+ node_range: comment.syntax().text_range(),
+ kind: StructureNodeKind::Region,
+ detail: None,
+ deprecated: false,
+ });
+ }
+ }
+
+ None
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+
+ use super::*;
+
+ fn check(ra_fixture: &str, expect: Expect) {
+ let file = SourceFile::parse(ra_fixture).ok().unwrap();
+ let structure = file_structure(&file);
+ expect.assert_debug_eq(&structure)
+ }
+
+ #[test]
+ fn test_file_structure() {
+ check(
+ r#"
+struct Foo {
+ x: i32
+}
+
+mod m {
+ fn bar1() {}
+ fn bar2<T>(t: T) -> T {}
+ fn bar3<A,
+ B>(a: A,
+ b: B) -> Vec<
+ u32
+ > {}
+}
+
+enum E { X, Y(i32) }
+type T = ();
+static S: i32 = 92;
+const C: i32 = 92;
+
+impl E {}
+
+impl fmt::Debug for E {}
+
+macro_rules! mc {
+ () => {}
+}
+
+#[macro_export]
+macro_rules! mcexp {
+ () => {}
+}
+
+/// Doc comment
+macro_rules! mcexp {
+ () => {}
+}
+
+#[deprecated]
+fn obsolete() {}
+
+#[deprecated(note = "for awhile")]
+fn very_obsolete() {}
+
+// region: Some region name
+// endregion
+
+// region: dontpanic
+mod m {
+fn f() {}
+// endregion
+fn g() {}
+}
+"#,
+ expect![[r#"
+ [
+ StructureNode {
+ parent: None,
+ label: "Foo",
+ navigation_range: 8..11,
+ node_range: 1..26,
+ kind: SymbolKind(
+ Struct,
+ ),
+ detail: None,
+ deprecated: false,
+ },
+ StructureNode {
+ parent: Some(
+ 0,
+ ),
+ label: "x",
+ navigation_range: 18..19,
+ node_range: 18..24,
+ kind: SymbolKind(
+ Field,
+ ),
+ detail: Some(
+ "i32",
+ ),
+ deprecated: false,
+ },
+ StructureNode {
+ parent: None,
+ label: "m",
+ navigation_range: 32..33,
+ node_range: 28..158,
+ kind: SymbolKind(
+ Module,
+ ),
+ detail: None,
+ deprecated: false,
+ },
+ StructureNode {
+ parent: Some(
+ 2,
+ ),
+ label: "bar1",
+ navigation_range: 43..47,
+ node_range: 40..52,
+ kind: SymbolKind(
+ Function,
+ ),
+ detail: Some(
+ "fn()",
+ ),
+ deprecated: false,
+ },
+ StructureNode {
+ parent: Some(
+ 2,
+ ),
+ label: "bar2",
+ navigation_range: 60..64,
+ node_range: 57..81,
+ kind: SymbolKind(
+ Function,
+ ),
+ detail: Some(
+ "fn<T>(t: T) -> T",
+ ),
+ deprecated: false,
+ },
+ StructureNode {
+ parent: Some(
+ 2,
+ ),
+ label: "bar3",
+ navigation_range: 89..93,
+ node_range: 86..156,
+ kind: SymbolKind(
+ Function,
+ ),
+ detail: Some(
+ "fn<A, B>(a: A, b: B) -> Vec< u32 >",
+ ),
+ deprecated: false,
+ },
+ StructureNode {
+ parent: None,
+ label: "E",
+ navigation_range: 165..166,
+ node_range: 160..180,
+ kind: SymbolKind(
+ Enum,
+ ),
+ detail: None,
+ deprecated: false,
+ },
+ StructureNode {
+ parent: Some(
+ 6,
+ ),
+ label: "X",
+ navigation_range: 169..170,
+ node_range: 169..170,
+ kind: SymbolKind(
+ Variant,
+ ),
+ detail: None,
+ deprecated: false,
+ },
+ StructureNode {
+ parent: Some(
+ 6,
+ ),
+ label: "Y",
+ navigation_range: 172..173,
+ node_range: 172..178,
+ kind: SymbolKind(
+ Variant,
+ ),
+ detail: None,
+ deprecated: false,
+ },
+ StructureNode {
+ parent: None,
+ label: "T",
+ navigation_range: 186..187,
+ node_range: 181..193,
+ kind: SymbolKind(
+ TypeAlias,
+ ),
+ detail: Some(
+ "()",
+ ),
+ deprecated: false,
+ },
+ StructureNode {
+ parent: None,
+ label: "S",
+ navigation_range: 201..202,
+ node_range: 194..213,
+ kind: SymbolKind(
+ Static,
+ ),
+ detail: Some(
+ "i32",
+ ),
+ deprecated: false,
+ },
+ StructureNode {
+ parent: None,
+ label: "C",
+ navigation_range: 220..221,
+ node_range: 214..232,
+ kind: SymbolKind(
+ Const,
+ ),
+ detail: Some(
+ "i32",
+ ),
+ deprecated: false,
+ },
+ StructureNode {
+ parent: None,
+ label: "impl E",
+ navigation_range: 239..240,
+ node_range: 234..243,
+ kind: SymbolKind(
+ Impl,
+ ),
+ detail: None,
+ deprecated: false,
+ },
+ StructureNode {
+ parent: None,
+ label: "impl fmt::Debug for E",
+ navigation_range: 265..266,
+ node_range: 245..269,
+ kind: SymbolKind(
+ Impl,
+ ),
+ detail: None,
+ deprecated: false,
+ },
+ StructureNode {
+ parent: None,
+ label: "mc",
+ navigation_range: 284..286,
+ node_range: 271..303,
+ kind: SymbolKind(
+ Macro,
+ ),
+ detail: None,
+ deprecated: false,
+ },
+ StructureNode {
+ parent: None,
+ label: "mcexp",
+ navigation_range: 334..339,
+ node_range: 305..356,
+ kind: SymbolKind(
+ Macro,
+ ),
+ detail: None,
+ deprecated: false,
+ },
+ StructureNode {
+ parent: None,
+ label: "mcexp",
+ navigation_range: 387..392,
+ node_range: 358..409,
+ kind: SymbolKind(
+ Macro,
+ ),
+ detail: None,
+ deprecated: false,
+ },
+ StructureNode {
+ parent: None,
+ label: "obsolete",
+ navigation_range: 428..436,
+ node_range: 411..441,
+ kind: SymbolKind(
+ Function,
+ ),
+ detail: Some(
+ "fn()",
+ ),
+ deprecated: true,
+ },
+ StructureNode {
+ parent: None,
+ label: "very_obsolete",
+ navigation_range: 481..494,
+ node_range: 443..499,
+ kind: SymbolKind(
+ Function,
+ ),
+ detail: Some(
+ "fn()",
+ ),
+ deprecated: true,
+ },
+ StructureNode {
+ parent: None,
+ label: "Some region name",
+ navigation_range: 501..528,
+ node_range: 501..528,
+ kind: Region,
+ detail: None,
+ deprecated: false,
+ },
+ StructureNode {
+ parent: None,
+ label: "m",
+ navigation_range: 568..569,
+ node_range: 543..606,
+ kind: SymbolKind(
+ Module,
+ ),
+ detail: None,
+ deprecated: false,
+ },
+ StructureNode {
+ parent: Some(
+ 20,
+ ),
+ label: "dontpanic",
+ navigation_range: 543..563,
+ node_range: 543..563,
+ kind: Region,
+ detail: None,
+ deprecated: false,
+ },
+ StructureNode {
+ parent: Some(
+ 20,
+ ),
+ label: "f",
+ navigation_range: 575..576,
+ node_range: 572..581,
+ kind: SymbolKind(
+ Function,
+ ),
+ detail: Some(
+ "fn()",
+ ),
+ deprecated: false,
+ },
+ StructureNode {
+ parent: Some(
+ 20,
+ ),
+ label: "g",
+ navigation_range: 598..599,
+ node_range: 582..604,
+ kind: SymbolKind(
+ Function,
+ ),
+ detail: Some(
+ "fn()",
+ ),
+ deprecated: false,
+ },
+ ]
+ "#]],
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/fixture.rs b/src/tools/rust-analyzer/crates/ide/src/fixture.rs
new file mode 100644
index 000000000..2ea6f6a9a
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/fixture.rs
@@ -0,0 +1,87 @@
+//! Utilities for creating `Analysis` instances for tests.
+use hir::db::DefDatabase;
+use ide_db::base_db::fixture::ChangeFixture;
+use test_utils::{extract_annotations, RangeOrOffset};
+
+use crate::{Analysis, AnalysisHost, FileId, FilePosition, FileRange};
+
+/// Creates analysis for a single file.
+pub(crate) fn file(ra_fixture: &str) -> (Analysis, FileId) {
+ let mut host = AnalysisHost::default();
+ let change_fixture = ChangeFixture::parse(ra_fixture);
+ host.db.set_enable_proc_attr_macros(true);
+ host.db.apply_change(change_fixture.change);
+ (host.analysis(), change_fixture.files[0])
+}
+
+/// Creates analysis from a multi-file fixture, returns positions marked with $0.
+pub(crate) fn position(ra_fixture: &str) -> (Analysis, FilePosition) {
+ let mut host = AnalysisHost::default();
+ let change_fixture = ChangeFixture::parse(ra_fixture);
+ host.db.set_enable_proc_attr_macros(true);
+ host.db.apply_change(change_fixture.change);
+ let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
+ let offset = range_or_offset.expect_offset();
+ (host.analysis(), FilePosition { file_id, offset })
+}
+
+/// Creates analysis for a single file, returns range marked with a pair of $0.
+pub(crate) fn range(ra_fixture: &str) -> (Analysis, FileRange) {
+ let mut host = AnalysisHost::default();
+ let change_fixture = ChangeFixture::parse(ra_fixture);
+ host.db.set_enable_proc_attr_macros(true);
+ host.db.apply_change(change_fixture.change);
+ let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
+ let range = range_or_offset.expect_range();
+ (host.analysis(), FileRange { file_id, range })
+}
+
+/// Creates analysis for a single file, returns range marked with a pair of $0 or a position marked with $0.
+pub(crate) fn range_or_position(ra_fixture: &str) -> (Analysis, FileId, RangeOrOffset) {
+ let mut host = AnalysisHost::default();
+ let change_fixture = ChangeFixture::parse(ra_fixture);
+ host.db.set_enable_proc_attr_macros(true);
+ host.db.apply_change(change_fixture.change);
+ let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
+ (host.analysis(), file_id, range_or_offset)
+}
+
+/// Creates analysis from a multi-file fixture, returns positions marked with $0.
+pub(crate) fn annotations(ra_fixture: &str) -> (Analysis, FilePosition, Vec<(FileRange, String)>) {
+ let mut host = AnalysisHost::default();
+ let change_fixture = ChangeFixture::parse(ra_fixture);
+ host.db.set_enable_proc_attr_macros(true);
+ host.db.apply_change(change_fixture.change);
+ let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
+ let offset = range_or_offset.expect_offset();
+
+ let annotations = change_fixture
+ .files
+ .iter()
+ .flat_map(|&file_id| {
+ let file_text = host.analysis().file_text(file_id).unwrap();
+ let annotations = extract_annotations(&file_text);
+ annotations.into_iter().map(move |(range, data)| (FileRange { file_id, range }, data))
+ })
+ .collect();
+ (host.analysis(), FilePosition { file_id, offset }, annotations)
+}
+
+/// Creates analysis from a multi-file fixture with annonations without $0
+pub(crate) fn annotations_without_marker(ra_fixture: &str) -> (Analysis, Vec<(FileRange, String)>) {
+ let mut host = AnalysisHost::default();
+ let change_fixture = ChangeFixture::parse(ra_fixture);
+ host.db.set_enable_proc_attr_macros(true);
+ host.db.apply_change(change_fixture.change);
+
+ let annotations = change_fixture
+ .files
+ .iter()
+ .flat_map(|&file_id| {
+ let file_text = host.analysis().file_text(file_id).unwrap();
+ let annotations = extract_annotations(&file_text);
+ annotations.into_iter().map(move |(range, data)| (FileRange { file_id, range }, data))
+ })
+ .collect();
+ (host.analysis(), annotations)
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/fn_references.rs b/src/tools/rust-analyzer/crates/ide/src/fn_references.rs
new file mode 100644
index 000000000..63fb322ce
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/fn_references.rs
@@ -0,0 +1,94 @@
+//! This module implements a methods and free functions search in the specified file.
+//! We have to skip tests, so cannot reuse file_structure module.
+
+use hir::Semantics;
+use ide_assists::utils::test_related_attribute;
+use ide_db::RootDatabase;
+use syntax::{ast, ast::HasName, AstNode, SyntaxNode};
+
+use crate::{FileId, FileRange};
+
+pub(crate) fn find_all_methods(db: &RootDatabase, file_id: FileId) -> Vec<FileRange> {
+ let sema = Semantics::new(db);
+ let source_file = sema.parse(file_id);
+ source_file.syntax().descendants().filter_map(|it| method_range(it, file_id)).collect()
+}
+
+fn method_range(item: SyntaxNode, file_id: FileId) -> Option<FileRange> {
+ ast::Fn::cast(item).and_then(|fn_def| {
+ if test_related_attribute(&fn_def).is_some() {
+ None
+ } else {
+ fn_def.name().map(|name| FileRange { file_id, range: name.syntax().text_range() })
+ }
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::fixture;
+ use crate::{FileRange, TextSize};
+ use std::ops::RangeInclusive;
+
+ #[test]
+ fn test_find_all_methods() {
+ let (analysis, pos) = fixture::position(
+ r#"
+ fn private_fn() {$0}
+
+ pub fn pub_fn() {}
+
+ pub fn generic_fn<T>(arg: T) {}
+ "#,
+ );
+
+ let refs = analysis.find_all_methods(pos.file_id).unwrap();
+ check_result(&refs, &[3..=13, 27..=33, 47..=57]);
+ }
+
+ #[test]
+ fn test_find_trait_methods() {
+ let (analysis, pos) = fixture::position(
+ r#"
+ trait Foo {
+ fn bar() {$0}
+ fn baz() {}
+ }
+ "#,
+ );
+
+ let refs = analysis.find_all_methods(pos.file_id).unwrap();
+ check_result(&refs, &[19..=22, 35..=38]);
+ }
+
+ #[test]
+ fn test_skip_tests() {
+ let (analysis, pos) = fixture::position(
+ r#"
+ //- /lib.rs
+ #[test]
+ fn foo() {$0}
+
+ pub fn pub_fn() {}
+
+ mod tests {
+ #[test]
+ fn bar() {}
+ }
+ "#,
+ );
+
+ let refs = analysis.find_all_methods(pos.file_id).unwrap();
+ check_result(&refs, &[28..=34]);
+ }
+
+ fn check_result(refs: &[FileRange], expected: &[RangeInclusive<u32>]) {
+ assert_eq!(refs.len(), expected.len());
+
+ for (i, item) in refs.iter().enumerate() {
+ let range = &expected[i];
+ assert_eq!(TextSize::from(*range.start()), item.range.start());
+ assert_eq!(TextSize::from(*range.end()), item.range.end());
+ }
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/folding_ranges.rs b/src/tools/rust-analyzer/crates/ide/src/folding_ranges.rs
new file mode 100755
index 000000000..c694d95d5
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/folding_ranges.rs
@@ -0,0 +1,626 @@
+use ide_db::{syntax_helpers::node_ext::vis_eq, FxHashSet};
+use syntax::{
+ ast::{self, AstNode, AstToken},
+ match_ast, Direction, NodeOrToken, SourceFile,
+ SyntaxKind::{self, *},
+ TextRange, TextSize,
+};
+
+use std::hash::Hash;
+
+const REGION_START: &str = "// region:";
+const REGION_END: &str = "// endregion";
+
+#[derive(Debug, PartialEq, Eq)]
+pub enum FoldKind {
+ Comment,
+ Imports,
+ Mods,
+ Block,
+ ArgList,
+ Region,
+ Consts,
+ Statics,
+ Array,
+ WhereClause,
+ ReturnType,
+ MatchArm,
+}
+
+#[derive(Debug)]
+pub struct Fold {
+ pub range: TextRange,
+ pub kind: FoldKind,
+}
+
+// Feature: Folding
+//
+// Defines folding regions for curly braced blocks, runs of consecutive use, mod, const or static
+// items, and `region` / `endregion` comment markers.
+pub(crate) fn folding_ranges(file: &SourceFile) -> Vec<Fold> {
+ let mut res = vec![];
+ let mut visited_comments = FxHashSet::default();
+ let mut visited_imports = FxHashSet::default();
+ let mut visited_mods = FxHashSet::default();
+ let mut visited_consts = FxHashSet::default();
+ let mut visited_statics = FxHashSet::default();
+
+ // regions can be nested, here is a LIFO buffer
+ let mut region_starts: Vec<TextSize> = vec![];
+
+ for element in file.syntax().descendants_with_tokens() {
+ // Fold items that span multiple lines
+ if let Some(kind) = fold_kind(element.kind()) {
+ let is_multiline = match &element {
+ NodeOrToken::Node(node) => node.text().contains_char('\n'),
+ NodeOrToken::Token(token) => token.text().contains('\n'),
+ };
+ if is_multiline {
+ res.push(Fold { range: element.text_range(), kind });
+ continue;
+ }
+ }
+
+ match element {
+ NodeOrToken::Token(token) => {
+ // Fold groups of comments
+ if let Some(comment) = ast::Comment::cast(token) {
+ if visited_comments.contains(&comment) {
+ continue;
+ }
+ let text = comment.text().trim_start();
+ if text.starts_with(REGION_START) {
+ region_starts.push(comment.syntax().text_range().start());
+ } else if text.starts_with(REGION_END) {
+ if let Some(region) = region_starts.pop() {
+ res.push(Fold {
+ range: TextRange::new(region, comment.syntax().text_range().end()),
+ kind: FoldKind::Region,
+ })
+ }
+ } else if let Some(range) =
+ contiguous_range_for_comment(comment, &mut visited_comments)
+ {
+ res.push(Fold { range, kind: FoldKind::Comment })
+ }
+ }
+ }
+ NodeOrToken::Node(node) => {
+ match_ast! {
+ match node {
+ ast::Module(module) => {
+ if module.item_list().is_none() {
+ if let Some(range) = contiguous_range_for_item_group(
+ module,
+ &mut visited_mods,
+ ) {
+ res.push(Fold { range, kind: FoldKind::Mods })
+ }
+ }
+ },
+ ast::Use(use_) => {
+ if let Some(range) = contiguous_range_for_item_group(use_, &mut visited_imports) {
+ res.push(Fold { range, kind: FoldKind::Imports })
+ }
+ },
+ ast::Const(konst) => {
+ if let Some(range) = contiguous_range_for_item_group(konst, &mut visited_consts) {
+ res.push(Fold { range, kind: FoldKind::Consts })
+ }
+ },
+ ast::Static(statik) => {
+ if let Some(range) = contiguous_range_for_item_group(statik, &mut visited_statics) {
+ res.push(Fold { range, kind: FoldKind::Statics })
+ }
+ },
+ ast::WhereClause(where_clause) => {
+ if let Some(range) = fold_range_for_where_clause(where_clause) {
+ res.push(Fold { range, kind: FoldKind::WhereClause })
+ }
+ },
+ ast::MatchArm(match_arm) => {
+ if let Some(range) = fold_range_for_multiline_match_arm(match_arm) {
+ res.push(Fold {range, kind: FoldKind::MatchArm})
+ }
+ },
+ _ => (),
+ }
+ }
+ }
+ }
+ }
+
+ res
+}
+
+fn fold_kind(kind: SyntaxKind) -> Option<FoldKind> {
+ match kind {
+ COMMENT => Some(FoldKind::Comment),
+ ARG_LIST | PARAM_LIST => Some(FoldKind::ArgList),
+ ARRAY_EXPR => Some(FoldKind::Array),
+ RET_TYPE => Some(FoldKind::ReturnType),
+ ASSOC_ITEM_LIST
+ | RECORD_FIELD_LIST
+ | RECORD_PAT_FIELD_LIST
+ | RECORD_EXPR_FIELD_LIST
+ | ITEM_LIST
+ | EXTERN_ITEM_LIST
+ | USE_TREE_LIST
+ | BLOCK_EXPR
+ | MATCH_ARM_LIST
+ | VARIANT_LIST
+ | TOKEN_TREE => Some(FoldKind::Block),
+ _ => None,
+ }
+}
+
+fn contiguous_range_for_item_group<N>(first: N, visited: &mut FxHashSet<N>) -> Option<TextRange>
+where
+ N: ast::HasVisibility + Clone + Hash + Eq,
+{
+ if !visited.insert(first.clone()) {
+ return None;
+ }
+
+ let (mut last, mut last_vis) = (first.clone(), first.visibility());
+ for element in first.syntax().siblings_with_tokens(Direction::Next) {
+ let node = match element {
+ NodeOrToken::Token(token) => {
+ if let Some(ws) = ast::Whitespace::cast(token) {
+ if !ws.spans_multiple_lines() {
+ // Ignore whitespace without blank lines
+ continue;
+ }
+ }
+ // There is a blank line or another token, which means that the
+ // group ends here
+ break;
+ }
+ NodeOrToken::Node(node) => node,
+ };
+
+ if let Some(next) = N::cast(node) {
+ let next_vis = next.visibility();
+ if eq_visibility(next_vis.clone(), last_vis) {
+ visited.insert(next.clone());
+ last_vis = next_vis;
+ last = next;
+ continue;
+ }
+ }
+ // Stop if we find an item of a different kind or with a different visibility.
+ break;
+ }
+
+ if first != last {
+ Some(TextRange::new(first.syntax().text_range().start(), last.syntax().text_range().end()))
+ } else {
+ // The group consists of only one element, therefore it cannot be folded
+ None
+ }
+}
+
+fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool {
+ match (vis0, vis1) {
+ (None, None) => true,
+ (Some(vis0), Some(vis1)) => vis_eq(&vis0, &vis1),
+ _ => false,
+ }
+}
+
+fn contiguous_range_for_comment(
+ first: ast::Comment,
+ visited: &mut FxHashSet<ast::Comment>,
+) -> Option<TextRange> {
+ visited.insert(first.clone());
+
+ // Only fold comments of the same flavor
+ let group_kind = first.kind();
+ if !group_kind.shape.is_line() {
+ return None;
+ }
+
+ let mut last = first.clone();
+ for element in first.syntax().siblings_with_tokens(Direction::Next) {
+ match element {
+ NodeOrToken::Token(token) => {
+ if let Some(ws) = ast::Whitespace::cast(token.clone()) {
+ if !ws.spans_multiple_lines() {
+ // Ignore whitespace without blank lines
+ continue;
+ }
+ }
+ if let Some(c) = ast::Comment::cast(token) {
+ if c.kind() == group_kind {
+ let text = c.text().trim_start();
+ // regions are not real comments
+ if !(text.starts_with(REGION_START) || text.starts_with(REGION_END)) {
+ visited.insert(c.clone());
+ last = c;
+ continue;
+ }
+ }
+ }
+ // The comment group ends because either:
+ // * An element of a different kind was reached
+ // * A comment of a different flavor was reached
+ break;
+ }
+ NodeOrToken::Node(_) => break,
+ };
+ }
+
+ if first != last {
+ Some(TextRange::new(first.syntax().text_range().start(), last.syntax().text_range().end()))
+ } else {
+ // The group consists of only one element, therefore it cannot be folded
+ None
+ }
+}
+
+fn fold_range_for_where_clause(where_clause: ast::WhereClause) -> Option<TextRange> {
+ let first_where_pred = where_clause.predicates().next();
+ let last_where_pred = where_clause.predicates().last();
+
+ if first_where_pred != last_where_pred {
+ let start = where_clause.where_token()?.text_range().end();
+ let end = where_clause.syntax().text_range().end();
+ return Some(TextRange::new(start, end));
+ }
+ None
+}
+
+fn fold_range_for_multiline_match_arm(match_arm: ast::MatchArm) -> Option<TextRange> {
+ if let Some(_) = fold_kind(match_arm.expr()?.syntax().kind()) {
+ return None;
+ }
+ if match_arm.expr()?.syntax().text().contains_char('\n') {
+ return Some(match_arm.expr()?.syntax().text_range());
+ }
+ None
+}
+
+#[cfg(test)]
+mod tests {
+ use test_utils::extract_tags;
+
+ use super::*;
+
+ fn check(ra_fixture: &str) {
+ let (ranges, text) = extract_tags(ra_fixture, "fold");
+
+ let parse = SourceFile::parse(&text);
+ let mut folds = folding_ranges(&parse.tree());
+ folds.sort_by_key(|fold| (fold.range.start(), fold.range.end()));
+
+ assert_eq!(
+ folds.len(),
+ ranges.len(),
+ "The amount of folds is different than the expected amount"
+ );
+
+ for (fold, (range, attr)) in folds.iter().zip(ranges.into_iter()) {
+ assert_eq!(fold.range.start(), range.start(), "mismatched start of folding ranges");
+ assert_eq!(fold.range.end(), range.end(), "mismatched end of folding ranges");
+
+ let kind = match fold.kind {
+ FoldKind::Comment => "comment",
+ FoldKind::Imports => "imports",
+ FoldKind::Mods => "mods",
+ FoldKind::Block => "block",
+ FoldKind::ArgList => "arglist",
+ FoldKind::Region => "region",
+ FoldKind::Consts => "consts",
+ FoldKind::Statics => "statics",
+ FoldKind::Array => "array",
+ FoldKind::WhereClause => "whereclause",
+ FoldKind::ReturnType => "returntype",
+ FoldKind::MatchArm => "matcharm",
+ };
+ assert_eq!(kind, &attr.unwrap());
+ }
+ }
+
+ #[test]
+ fn test_fold_comments() {
+ check(
+ r#"
+<fold comment>// Hello
+// this is a multiline
+// comment
+//</fold>
+
+// But this is not
+
+fn main() <fold block>{
+ <fold comment>// We should
+ // also
+ // fold
+ // this one.</fold>
+ <fold comment>//! But this one is different
+ //! because it has another flavor</fold>
+ <fold comment>/* As does this
+ multiline comment */</fold>
+}</fold>
+"#,
+ );
+ }
+
+ #[test]
+ fn test_fold_imports() {
+ check(
+ r#"
+use std::<fold block>{
+ str,
+ vec,
+ io as iop
+}</fold>;
+"#,
+ );
+ }
+
+ #[test]
+ fn test_fold_mods() {
+ check(
+ r#"
+
+pub mod foo;
+<fold mods>mod after_pub;
+mod after_pub_next;</fold>
+
+<fold mods>mod before_pub;
+mod before_pub_next;</fold>
+pub mod bar;
+
+mod not_folding_single;
+pub mod foobar;
+pub not_folding_single_next;
+
+<fold mods>#[cfg(test)]
+mod with_attribute;
+mod with_attribute_next;</fold>
+
+mod inline0 {}
+mod inline1 {}
+
+mod inline2 <fold block>{
+
+}</fold>
+"#,
+ );
+ }
+
+ #[test]
+ fn test_fold_import_groups() {
+ check(
+ r#"
+<fold imports>use std::str;
+use std::vec;
+use std::io as iop;</fold>
+
+<fold imports>use std::mem;
+use std::f64;</fold>
+
+<fold imports>use std::collections::HashMap;
+// Some random comment
+use std::collections::VecDeque;</fold>
+"#,
+ );
+ }
+
+ #[test]
+ fn test_fold_import_and_groups() {
+ check(
+ r#"
+<fold imports>use std::str;
+use std::vec;
+use std::io as iop;</fold>
+
+<fold imports>use std::mem;
+use std::f64;</fold>
+
+use std::collections::<fold block>{
+ HashMap,
+ VecDeque,
+}</fold>;
+// Some random comment
+"#,
+ );
+ }
+
+ #[test]
+ fn test_folds_structs() {
+ check(
+ r#"
+struct Foo <fold block>{
+}</fold>
+"#,
+ );
+ }
+
+ #[test]
+ fn test_folds_traits() {
+ check(
+ r#"
+trait Foo <fold block>{
+}</fold>
+"#,
+ );
+ }
+
+ #[test]
+ fn test_folds_macros() {
+ check(
+ r#"
+macro_rules! foo <fold block>{
+ ($($tt:tt)*) => { $($tt)* }
+}</fold>
+"#,
+ );
+ }
+
+ #[test]
+ fn test_fold_match_arms() {
+ check(
+ r#"
+fn main() <fold block>{
+ match 0 <fold block>{
+ 0 => 0,
+ _ => 1,
+ }</fold>
+}</fold>
+"#,
+ );
+ }
+
+ #[test]
+ fn test_fold_multiline_non_block_match_arm() {
+ check(
+ r#"
+ fn main() <fold block>{
+ match foo <fold block>{
+ block => <fold block>{
+ }</fold>,
+ matcharm => <fold matcharm>some.
+ call().
+ chain()</fold>,
+ matcharm2
+ => 0,
+ match_expr => <fold matcharm>match foo2 <fold block>{
+ bar => (),
+ }</fold></fold>,
+ array_list => <fold array>[
+ 1,
+ 2,
+ 3,
+ ]</fold>,
+ strustS => <fold matcharm>StructS <fold block>{
+ a: 31,
+ }</fold></fold>,
+ }</fold>
+ }</fold>
+ "#,
+ )
+ }
+
+ #[test]
+ fn fold_big_calls() {
+ check(
+ r#"
+fn main() <fold block>{
+ frobnicate<fold arglist>(
+ 1,
+ 2,
+ 3,
+ )</fold>
+}</fold>
+"#,
+ )
+ }
+
+ #[test]
+ fn fold_record_literals() {
+ check(
+ r#"
+const _: S = S <fold block>{
+
+}</fold>;
+"#,
+ )
+ }
+
+ #[test]
+ fn fold_multiline_params() {
+ check(
+ r#"
+fn foo<fold arglist>(
+ x: i32,
+ y: String,
+)</fold> {}
+"#,
+ )
+ }
+
+ #[test]
+ fn fold_multiline_array() {
+ check(
+ r#"
+const FOO: [usize; 4] = <fold array>[
+ 1,
+ 2,
+ 3,
+ 4,
+]</fold>;
+"#,
+ )
+ }
+
+ #[test]
+ fn fold_region() {
+ check(
+ r#"
+// 1. some normal comment
+<fold region>// region: test
+// 2. some normal comment
+<fold region>// region: inner
+fn f() {}
+// endregion</fold>
+fn f2() {}
+// endregion: test</fold>
+"#,
+ )
+ }
+
+ #[test]
+ fn fold_consecutive_const() {
+ check(
+ r#"
+<fold consts>const FIRST_CONST: &str = "first";
+const SECOND_CONST: &str = "second";</fold>
+"#,
+ )
+ }
+
+ #[test]
+ fn fold_consecutive_static() {
+ check(
+ r#"
+<fold statics>static FIRST_STATIC: &str = "first";
+static SECOND_STATIC: &str = "second";</fold>
+"#,
+ )
+ }
+
+ #[test]
+ fn fold_where_clause() {
+ // fold multi-line and don't fold single line.
+ check(
+ r#"
+fn foo()
+where<fold whereclause>
+ A: Foo,
+ B: Foo,
+ C: Foo,
+ D: Foo,</fold> {}
+
+fn bar()
+where
+ A: Bar, {}
+"#,
+ )
+ }
+
+ #[test]
+ fn fold_return_type() {
+ check(
+ r#"
+fn foo()<fold returntype>-> (
+ bool,
+ bool,
+)</fold> { (true, true) }
+
+fn bar() -> (bool, bool) { (true, true) }
+"#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs b/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs
new file mode 100644
index 000000000..926292c9b
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs
@@ -0,0 +1,112 @@
+use hir::Semantics;
+use ide_db::{
+ defs::{Definition, NameClass, NameRefClass},
+ RootDatabase,
+};
+use syntax::{ast, match_ast, AstNode, SyntaxKind::*, T};
+
+use crate::{FilePosition, NavigationTarget, RangeInfo};
+
+// Feature: Go to Declaration
+//
+// Navigates to the declaration of an identifier.
+//
+// This is currently the same as `Go to Definition` with the exception of outline modules where it
+// will navigate to the `mod name;` item declaration.
+pub(crate) fn goto_declaration(
+ db: &RootDatabase,
+ position: FilePosition,
+) -> Option<RangeInfo<Vec<NavigationTarget>>> {
+ let sema = Semantics::new(db);
+ let file = sema.parse(position.file_id).syntax().clone();
+ let original_token = file
+ .token_at_offset(position.offset)
+ .find(|it| matches!(it.kind(), IDENT | T![self] | T![super] | T![crate] | T![Self]))?;
+ let range = original_token.text_range();
+ let info: Vec<NavigationTarget> = sema
+ .descend_into_macros(original_token)
+ .iter()
+ .filter_map(|token| {
+ let parent = token.parent()?;
+ let def = match_ast! {
+ match parent {
+ ast::NameRef(name_ref) => match NameRefClass::classify(&sema, &name_ref)? {
+ NameRefClass::Definition(it) => Some(it),
+ _ => None
+ },
+ ast::Name(name) => match NameClass::classify(&sema, &name)? {
+ NameClass::Definition(it) => Some(it),
+ _ => None
+ },
+ _ => None
+ }
+ };
+ match def? {
+ Definition::Module(module) => {
+ Some(NavigationTarget::from_module_to_decl(db, module))
+ }
+ _ => None,
+ }
+ })
+ .collect();
+
+ Some(RangeInfo::new(range, info))
+}
+
+#[cfg(test)]
+mod tests {
+ use ide_db::base_db::FileRange;
+ use itertools::Itertools;
+
+ use crate::fixture;
+
+ fn check(ra_fixture: &str) {
+ let (analysis, position, expected) = fixture::annotations(ra_fixture);
+ let navs = analysis
+ .goto_declaration(position)
+ .unwrap()
+ .expect("no declaration or definition found")
+ .info;
+ if navs.is_empty() {
+ panic!("unresolved reference")
+ }
+
+ let cmp = |&FileRange { file_id, range }: &_| (file_id, range.start());
+ let navs = navs
+ .into_iter()
+ .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
+ .sorted_by_key(cmp)
+ .collect::<Vec<_>>();
+ let expected = expected
+ .into_iter()
+ .map(|(FileRange { file_id, range }, _)| FileRange { file_id, range })
+ .sorted_by_key(cmp)
+ .collect::<Vec<_>>();
+ assert_eq!(expected, navs);
+ }
+
+ #[test]
+ fn goto_decl_module_outline() {
+ check(
+ r#"
+//- /main.rs
+mod foo;
+ // ^^^
+//- /foo.rs
+use self$0;
+"#,
+ )
+ }
+
+ #[test]
+ fn goto_decl_module_inline() {
+ check(
+ r#"
+mod foo {
+ // ^^^
+ use self$0;
+}
+"#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs
new file mode 100644
index 000000000..d9c97751c
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs
@@ -0,0 +1,1634 @@
+use std::{convert::TryInto, mem::discriminant};
+
+use crate::{doc_links::token_as_doc_comment, FilePosition, NavigationTarget, RangeInfo, TryToNav};
+use hir::{AsAssocItem, AssocItem, Semantics};
+use ide_db::{
+ base_db::{AnchoredPath, FileId, FileLoader},
+ defs::{Definition, IdentClass},
+ helpers::pick_best_token,
+ RootDatabase,
+};
+use itertools::Itertools;
+use syntax::{ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextRange, T};
+
+// Feature: Go to Definition
+//
+// Navigates to the definition of an identifier.
+//
+// For outline modules, this will navigate to the source file of the module.
+//
+// |===
+// | Editor | Shortcut
+//
+// | VS Code | kbd:[F12]
+// |===
+//
+// image::https://user-images.githubusercontent.com/48062697/113065563-025fbe00-91b1-11eb-83e4-a5a703610b23.gif[]
+pub(crate) fn goto_definition(
+ db: &RootDatabase,
+ position: FilePosition,
+) -> Option<RangeInfo<Vec<NavigationTarget>>> {
+ let sema = &Semantics::new(db);
+ let file = sema.parse(position.file_id).syntax().clone();
+ let original_token =
+ pick_best_token(file.token_at_offset(position.offset), |kind| match kind {
+ IDENT
+ | INT_NUMBER
+ | LIFETIME_IDENT
+ | T![self]
+ | T![super]
+ | T![crate]
+ | T![Self]
+ | COMMENT => 2,
+ kind if kind.is_trivia() => 0,
+ _ => 1,
+ })?;
+ if let Some(doc_comment) = token_as_doc_comment(&original_token) {
+ return doc_comment.get_definition_with_descend_at(sema, position.offset, |def, _, _| {
+ let nav = def.try_to_nav(db)?;
+ Some(RangeInfo::new(original_token.text_range(), vec![nav]))
+ });
+ }
+ let navs = sema
+ .descend_into_macros(original_token.clone())
+ .into_iter()
+ .filter_map(|token| {
+ let parent = token.parent()?;
+ if let Some(tt) = ast::TokenTree::cast(parent) {
+ if let Some(x) = try_lookup_include_path(sema, tt, token.clone(), position.file_id)
+ {
+ return Some(vec![x]);
+ }
+ }
+ Some(
+ IdentClass::classify_token(sema, &token)?
+ .definitions()
+ .into_iter()
+ .flat_map(|def| {
+ try_filter_trait_item_definition(sema, &def)
+ .unwrap_or_else(|| def_to_nav(sema.db, def))
+ })
+ .collect(),
+ )
+ })
+ .flatten()
+ .unique()
+ .collect::<Vec<NavigationTarget>>();
+
+ Some(RangeInfo::new(original_token.text_range(), navs))
+}
+
+fn try_lookup_include_path(
+ sema: &Semantics<'_, RootDatabase>,
+ tt: ast::TokenTree,
+ token: SyntaxToken,
+ file_id: FileId,
+) -> Option<NavigationTarget> {
+ let token = ast::String::cast(token)?;
+ let path = token.value()?.into_owned();
+ let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?;
+ let name = macro_call.path()?.segment()?.name_ref()?;
+ if !matches!(&*name.text(), "include" | "include_str" | "include_bytes") {
+ return None;
+ }
+ let file_id = sema.db.resolve_path(AnchoredPath { anchor: file_id, path: &path })?;
+ let size = sema.db.file_text(file_id).len().try_into().ok()?;
+ Some(NavigationTarget {
+ file_id,
+ full_range: TextRange::new(0.into(), size),
+ name: path.into(),
+ focus_range: None,
+ kind: None,
+ container_name: None,
+ description: None,
+ docs: None,
+ })
+}
+/// finds the trait definition of an impl'd item, except function
+/// e.g.
+/// ```rust
+/// trait A { type a; }
+/// struct S;
+/// impl A for S { type a = i32; } // <-- on this associate type, will get the location of a in the trait
+/// ```
+fn try_filter_trait_item_definition(
+ sema: &Semantics<'_, RootDatabase>,
+ def: &Definition,
+) -> Option<Vec<NavigationTarget>> {
+ let db = sema.db;
+ let assoc = def.as_assoc_item(db)?;
+ match assoc {
+ AssocItem::Function(..) => None,
+ AssocItem::Const(..) | AssocItem::TypeAlias(..) => {
+ let imp = match assoc.container(db) {
+ hir::AssocItemContainer::Impl(imp) => imp,
+ _ => return None,
+ };
+ let trait_ = imp.trait_(db)?;
+ let name = def.name(db)?;
+ let discri_value = discriminant(&assoc);
+ trait_
+ .items(db)
+ .iter()
+ .filter(|itm| discriminant(*itm) == discri_value)
+ .find_map(|itm| (itm.name(db)? == name).then(|| itm.try_to_nav(db)).flatten())
+ .map(|it| vec![it])
+ }
+ }
+}
+
+fn def_to_nav(db: &RootDatabase, def: Definition) -> Vec<NavigationTarget> {
+ def.try_to_nav(db).map(|it| vec![it]).unwrap_or_default()
+}
+
+#[cfg(test)]
+mod tests {
+ use ide_db::base_db::FileRange;
+ use itertools::Itertools;
+
+ use crate::fixture;
+
+ #[track_caller]
+ fn check(ra_fixture: &str) {
+ let (analysis, position, expected) = fixture::annotations(ra_fixture);
+ let navs = analysis.goto_definition(position).unwrap().expect("no definition found").info;
+ if navs.is_empty() {
+ panic!("unresolved reference")
+ }
+
+ let cmp = |&FileRange { file_id, range }: &_| (file_id, range.start());
+ let navs = navs
+ .into_iter()
+ .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
+ .sorted_by_key(cmp)
+ .collect::<Vec<_>>();
+ let expected = expected
+ .into_iter()
+ .map(|(FileRange { file_id, range }, _)| FileRange { file_id, range })
+ .sorted_by_key(cmp)
+ .collect::<Vec<_>>();
+ assert_eq!(expected, navs);
+ }
+
+ fn check_unresolved(ra_fixture: &str) {
+ let (analysis, position) = fixture::position(ra_fixture);
+ let navs = analysis.goto_definition(position).unwrap().expect("no definition found").info;
+
+ assert!(navs.is_empty(), "didn't expect this to resolve anywhere: {:?}", navs)
+ }
+
+ #[test]
+ fn goto_def_if_items_same_name() {
+ check(
+ r#"
+trait Trait {
+ type A;
+ const A: i32;
+ //^
+}
+
+struct T;
+impl Trait for T {
+ type A = i32;
+ const A$0: i32 = -9;
+}"#,
+ );
+ }
+ #[test]
+ fn goto_def_in_mac_call_in_attr_invoc() {
+ check(
+ r#"
+//- proc_macros: identity
+pub struct Struct {
+ // ^^^^^^
+ field: i32,
+}
+
+macro_rules! identity {
+ ($($tt:tt)*) => {$($tt)*};
+}
+
+#[proc_macros::identity]
+fn function() {
+ identity!(Struct$0 { field: 0 });
+}
+
+"#,
+ )
+ }
+
+ #[test]
+ fn goto_def_for_extern_crate() {
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+extern crate std$0;
+//- /std/lib.rs crate:std
+// empty
+//^file
+"#,
+ )
+ }
+
+ #[test]
+ fn goto_def_for_renamed_extern_crate() {
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+extern crate std as abc$0;
+//- /std/lib.rs crate:std
+// empty
+//^file
+"#,
+ )
+ }
+
+ #[test]
+ fn goto_def_in_items() {
+ check(
+ r#"
+struct Foo;
+ //^^^
+enum E { X(Foo$0) }
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_at_start_of_item() {
+ check(
+ r#"
+struct Foo;
+ //^^^
+enum E { X($0Foo) }
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_definition_resolves_correct_name() {
+ check(
+ r#"
+//- /lib.rs
+use a::Foo;
+mod a;
+mod b;
+enum E { X(Foo$0) }
+
+//- /a.rs
+struct Foo;
+ //^^^
+//- /b.rs
+struct Foo;
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_module_declaration() {
+ check(
+ r#"
+//- /lib.rs
+mod $0foo;
+
+//- /foo.rs
+// empty
+//^file
+"#,
+ );
+
+ check(
+ r#"
+//- /lib.rs
+mod $0foo;
+
+//- /foo/mod.rs
+// empty
+//^file
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_macros() {
+ check(
+ r#"
+macro_rules! foo { () => { () } }
+ //^^^
+fn bar() {
+ $0foo!();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_macros_from_other_crates() {
+ check(
+ r#"
+//- /lib.rs crate:main deps:foo
+use foo::foo;
+fn bar() {
+ $0foo!();
+}
+
+//- /foo/lib.rs crate:foo
+#[macro_export]
+macro_rules! foo { () => { () } }
+ //^^^
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_macros_in_use_tree() {
+ check(
+ r#"
+//- /lib.rs crate:main deps:foo
+use foo::foo$0;
+
+//- /foo/lib.rs crate:foo
+#[macro_export]
+macro_rules! foo { () => { () } }
+ //^^^
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_macro_defined_fn_with_arg() {
+ check(
+ r#"
+//- /lib.rs
+macro_rules! define_fn {
+ ($name:ident) => (fn $name() {})
+}
+
+define_fn!(foo);
+ //^^^
+
+fn bar() {
+ $0foo();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_macro_defined_fn_no_arg() {
+ check(
+ r#"
+//- /lib.rs
+macro_rules! define_fn {
+ () => (fn foo() {})
+}
+
+ define_fn!();
+//^^^^^^^^^^^^^
+
+fn bar() {
+ $0foo();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_definition_works_for_macro_inside_pattern() {
+ check(
+ r#"
+//- /lib.rs
+macro_rules! foo {() => {0}}
+ //^^^
+
+fn bar() {
+ match (0,1) {
+ ($0foo!(), _) => {}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_definition_works_for_macro_inside_match_arm_lhs() {
+ check(
+ r#"
+//- /lib.rs
+macro_rules! foo {() => {0}}
+ //^^^
+fn bar() {
+ match 0 {
+ $0foo!() => {}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_use_alias() {
+ check(
+ r#"
+//- /lib.rs crate:main deps:foo
+use foo as bar$0;
+
+//- /foo/lib.rs crate:foo
+// empty
+//^file
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_use_alias_foo_macro() {
+ check(
+ r#"
+//- /lib.rs crate:main deps:foo
+use foo::foo as bar$0;
+
+//- /foo/lib.rs crate:foo
+#[macro_export]
+macro_rules! foo { () => { () } }
+ //^^^
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_methods() {
+ check(
+ r#"
+struct Foo;
+impl Foo {
+ fn frobnicate(&self) { }
+ //^^^^^^^^^^
+}
+
+fn bar(foo: &Foo) {
+ foo.frobnicate$0();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_fields() {
+ check(
+ r#"
+struct Foo {
+ spam: u32,
+} //^^^^
+
+fn bar(foo: &Foo) {
+ foo.spam$0;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_record_fields() {
+ check(
+ r#"
+//- /lib.rs
+struct Foo {
+ spam: u32,
+} //^^^^
+
+fn bar() -> Foo {
+ Foo {
+ spam$0: 0,
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_record_pat_fields() {
+ check(
+ r#"
+//- /lib.rs
+struct Foo {
+ spam: u32,
+} //^^^^
+
+fn bar(foo: Foo) -> Foo {
+ let Foo { spam$0: _, } = foo
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_record_fields_macros() {
+ check(
+ r"
+macro_rules! m { () => { 92 };}
+struct Foo { spam: u32 }
+ //^^^^
+
+fn bar() -> Foo {
+ Foo { spam$0: m!() }
+}
+",
+ );
+ }
+
+ #[test]
+ fn goto_for_tuple_fields() {
+ check(
+ r#"
+struct Foo(u32);
+ //^^^
+
+fn bar() {
+ let foo = Foo(0);
+ foo.$00;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_ufcs_inherent_methods() {
+ check(
+ r#"
+struct Foo;
+impl Foo {
+ fn frobnicate() { }
+} //^^^^^^^^^^
+
+fn bar(foo: &Foo) {
+ Foo::frobnicate$0();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_ufcs_trait_methods_through_traits() {
+ check(
+ r#"
+trait Foo {
+ fn frobnicate();
+} //^^^^^^^^^^
+
+fn bar() {
+ Foo::frobnicate$0();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_ufcs_trait_methods_through_self() {
+ check(
+ r#"
+struct Foo;
+trait Trait {
+ fn frobnicate();
+} //^^^^^^^^^^
+impl Trait for Foo {}
+
+fn bar() {
+ Foo::frobnicate$0();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_definition_on_self() {
+ check(
+ r#"
+struct Foo;
+impl Foo {
+ //^^^
+ pub fn new() -> Self {
+ Self$0 {}
+ }
+}
+"#,
+ );
+ check(
+ r#"
+struct Foo;
+impl Foo {
+ //^^^
+ pub fn new() -> Self$0 {
+ Self {}
+ }
+}
+"#,
+ );
+
+ check(
+ r#"
+enum Foo { A }
+impl Foo {
+ //^^^
+ pub fn new() -> Self$0 {
+ Foo::A
+ }
+}
+"#,
+ );
+
+ check(
+ r#"
+enum Foo { A }
+impl Foo {
+ //^^^
+ pub fn thing(a: &Self$0) {
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_definition_on_self_in_trait_impl() {
+ check(
+ r#"
+struct Foo;
+trait Make {
+ fn new() -> Self;
+}
+impl Make for Foo {
+ //^^^
+ fn new() -> Self {
+ Self$0 {}
+ }
+}
+"#,
+ );
+
+ check(
+ r#"
+struct Foo;
+trait Make {
+ fn new() -> Self;
+}
+impl Make for Foo {
+ //^^^
+ fn new() -> Self$0 {
+ Self {}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_when_used_on_definition_name_itself() {
+ check(
+ r#"
+struct Foo$0 { value: u32 }
+ //^^^
+ "#,
+ );
+
+ check(
+ r#"
+struct Foo {
+ field$0: string,
+} //^^^^^
+"#,
+ );
+
+ check(
+ r#"
+fn foo_test$0() { }
+ //^^^^^^^^
+"#,
+ );
+
+ check(
+ r#"
+enum Foo$0 { Variant }
+ //^^^
+"#,
+ );
+
+ check(
+ r#"
+enum Foo {
+ Variant1,
+ Variant2$0,
+ //^^^^^^^^
+ Variant3,
+}
+"#,
+ );
+
+ check(
+ r#"
+static INNER$0: &str = "";
+ //^^^^^
+"#,
+ );
+
+ check(
+ r#"
+const INNER$0: &str = "";
+ //^^^^^
+"#,
+ );
+
+ check(
+ r#"
+type Thing$0 = Option<()>;
+ //^^^^^
+"#,
+ );
+
+ check(
+ r#"
+trait Foo$0 { }
+ //^^^
+"#,
+ );
+
+ check(
+ r#"
+mod bar$0 { }
+ //^^^
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_from_macro() {
+ check(
+ r#"
+macro_rules! id {
+ ($($tt:tt)*) => { $($tt)* }
+}
+fn foo() {}
+ //^^^
+id! {
+ fn bar() {
+ fo$0o();
+ }
+}
+mod confuse_index { fn foo(); }
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_through_format() {
+ check(
+ r#"
+#[macro_export]
+macro_rules! format {
+ ($($arg:tt)*) => ($crate::fmt::format($crate::__export::format_args!($($arg)*)))
+}
+#[rustc_builtin_macro]
+#[macro_export]
+macro_rules! format_args {
+ ($fmt:expr) => ({ /* compiler built-in */ });
+ ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
+}
+pub mod __export {
+ pub use crate::format_args;
+ fn foo() {} // for index confusion
+}
+fn foo() -> i8 {}
+ //^^^
+fn test() {
+ format!("{}", fo$0o())
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_through_included_file() {
+ check(
+ r#"
+//- /main.rs
+#[rustc_builtin_macro]
+macro_rules! include {}
+
+ include!("foo.rs");
+//^^^^^^^^^^^^^^^^^^^
+
+fn f() {
+ foo$0();
+}
+
+mod confuse_index {
+ pub fn foo() {}
+}
+
+//- /foo.rs
+fn foo() {}
+ "#,
+ );
+ }
+
+ #[test]
+ fn goto_for_type_param() {
+ check(
+ r#"
+struct Foo<T: Clone> { t: $0T }
+ //^
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_within_macro() {
+ check(
+ r#"
+macro_rules! id {
+ ($($tt:tt)*) => ($($tt)*)
+}
+
+fn foo() {
+ let x = 1;
+ //^
+ id!({
+ let y = $0x;
+ let z = y;
+ });
+}
+"#,
+ );
+
+ check(
+ r#"
+macro_rules! id {
+ ($($tt:tt)*) => ($($tt)*)
+}
+
+fn foo() {
+ let x = 1;
+ id!({
+ let y = x;
+ //^
+ let z = $0y;
+ });
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_in_local_fn() {
+ check(
+ r#"
+fn main() {
+ fn foo() {
+ let x = 92;
+ //^
+ $0x;
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_in_local_macro() {
+ check(
+ r#"
+fn bar() {
+ macro_rules! foo { () => { () } }
+ //^^^
+ $0foo!();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_field_init_shorthand() {
+ check(
+ r#"
+struct Foo { x: i32 }
+ //^
+fn main() {
+ let x = 92;
+ //^
+ Foo { x$0 };
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn goto_def_for_enum_variant_field() {
+ check(
+ r#"
+enum Foo {
+ Bar { x: i32 }
+ //^
+}
+fn baz(foo: Foo) {
+ match foo {
+ Foo::Bar { x$0 } => x
+ //^
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_enum_variant_self_pattern_const() {
+ check(
+ r#"
+enum Foo { Bar }
+ //^^^
+impl Foo {
+ fn baz(self) {
+ match self { Self::Bar$0 => {} }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_enum_variant_self_pattern_record() {
+ check(
+ r#"
+enum Foo { Bar { val: i32 } }
+ //^^^
+impl Foo {
+ fn baz(self) -> i32 {
+ match self { Self::Bar$0 { val } => {} }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_enum_variant_self_expr_const() {
+ check(
+ r#"
+enum Foo { Bar }
+ //^^^
+impl Foo {
+ fn baz(self) { Self::Bar$0; }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_enum_variant_self_expr_record() {
+ check(
+ r#"
+enum Foo { Bar { val: i32 } }
+ //^^^
+impl Foo {
+ fn baz(self) { Self::Bar$0 {val: 4}; }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_type_alias_generic_parameter() {
+ check(
+ r#"
+type Alias<T> = T$0;
+ //^
+"#,
+ )
+ }
+
+ #[test]
+ fn goto_def_for_macro_container() {
+ check(
+ r#"
+//- /lib.rs crate:main deps:foo
+foo::module$0::mac!();
+
+//- /foo/lib.rs crate:foo
+pub mod module {
+ //^^^^^^
+ #[macro_export]
+ macro_rules! _mac { () => { () } }
+ pub use crate::_mac as mac;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_assoc_ty_in_path() {
+ check(
+ r#"
+trait Iterator {
+ type Item;
+ //^^^^
+}
+
+fn f() -> impl Iterator<Item$0 = u8> {}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_super_assoc_ty_in_path() {
+ check(
+ r#"
+trait Super {
+ type Item;
+ //^^^^
+}
+
+trait Sub: Super {}
+
+fn f() -> impl Sub<Item$0 = u8> {}
+"#,
+ );
+ }
+
+ #[test]
+ fn unknown_assoc_ty() {
+ check_unresolved(
+ r#"
+trait Iterator { type Item; }
+fn f() -> impl Iterator<Invalid$0 = u8> {}
+"#,
+ )
+ }
+
+ #[test]
+ fn goto_def_for_assoc_ty_in_path_multiple() {
+ check(
+ r#"
+trait Iterator {
+ type A;
+ //^
+ type B;
+}
+
+fn f() -> impl Iterator<A$0 = u8, B = ()> {}
+"#,
+ );
+ check(
+ r#"
+trait Iterator {
+ type A;
+ type B;
+ //^
+}
+
+fn f() -> impl Iterator<A = u8, B$0 = ()> {}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_assoc_ty_ufcs() {
+ check(
+ r#"
+trait Iterator {
+ type Item;
+ //^^^^
+}
+
+fn g() -> <() as Iterator<Item$0 = ()>>::Item {}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_assoc_ty_ufcs_multiple() {
+ check(
+ r#"
+trait Iterator {
+ type A;
+ //^
+ type B;
+}
+
+fn g() -> <() as Iterator<A$0 = (), B = u8>>::B {}
+"#,
+ );
+ check(
+ r#"
+trait Iterator {
+ type A;
+ type B;
+ //^
+}
+
+fn g() -> <() as Iterator<A = (), B$0 = u8>>::A {}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_self_param_ty_specified() {
+ check(
+ r#"
+struct Foo {}
+
+impl Foo {
+ fn bar(self: &Foo) {
+ //^^^^
+ let foo = sel$0f;
+ }
+}"#,
+ )
+ }
+
+ #[test]
+ fn goto_self_param_on_decl() {
+ check(
+ r#"
+struct Foo {}
+
+impl Foo {
+ fn bar(&self$0) {
+ //^^^^
+ }
+}"#,
+ )
+ }
+
+ #[test]
+ fn goto_lifetime_param_on_decl() {
+ check(
+ r#"
+fn foo<'foobar$0>(_: &'foobar ()) {
+ //^^^^^^^
+}"#,
+ )
+ }
+
+ #[test]
+ fn goto_lifetime_param_decl() {
+ check(
+ r#"
+fn foo<'foobar>(_: &'foobar$0 ()) {
+ //^^^^^^^
+}"#,
+ )
+ }
+
+ #[test]
+ fn goto_lifetime_param_decl_nested() {
+ check(
+ r#"
+fn foo<'foobar>(_: &'foobar ()) {
+ fn foo<'foobar>(_: &'foobar$0 ()) {}
+ //^^^^^^^
+}"#,
+ )
+ }
+
+ #[test]
+ fn goto_lifetime_hrtb() {
+ // FIXME: requires the HIR to somehow track these hrtb lifetimes
+ check_unresolved(
+ r#"
+trait Foo<T> {}
+fn foo<T>() where for<'a> T: Foo<&'a$0 (u8, u16)>, {}
+ //^^
+"#,
+ );
+ check_unresolved(
+ r#"
+trait Foo<T> {}
+fn foo<T>() where for<'a$0> T: Foo<&'a (u8, u16)>, {}
+ //^^
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_lifetime_hrtb_for_type() {
+ // FIXME: requires ForTypes to be implemented
+ check_unresolved(
+ r#"trait Foo<T> {}
+fn foo<T>() where T: for<'a> Foo<&'a$0 (u8, u16)>, {}
+ //^^
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_label() {
+ check(
+ r#"
+fn foo<'foo>(_: &'foo ()) {
+ 'foo: {
+ //^^^^
+ 'bar: loop {
+ break 'foo$0;
+ }
+ }
+}"#,
+ )
+ }
+
+ #[test]
+ fn goto_def_for_intra_doc_link_same_file() {
+ check(
+ r#"
+/// Blah, [`bar`](bar) .. [`foo`](foo$0) has [`bar`](bar)
+pub fn bar() { }
+
+/// You might want to see [`std::fs::read()`] too.
+pub fn foo() { }
+ //^^^
+
+}"#,
+ )
+ }
+
+ #[test]
+ fn goto_def_for_intra_doc_link_inner() {
+ check(
+ r#"
+//- /main.rs
+mod m;
+struct S;
+ //^
+
+//- /m.rs
+//! [`super::S$0`]
+"#,
+ )
+ }
+
+ #[test]
+ fn goto_incomplete_field() {
+ check(
+ r#"
+struct A { a: u32 }
+ //^
+fn foo() { A { a$0: }; }
+"#,
+ )
+ }
+
+ #[test]
+ fn goto_proc_macro() {
+ check(
+ r#"
+//- /main.rs crate:main deps:mac
+use mac::fn_macro;
+
+fn_macro$0!();
+
+//- /mac.rs crate:mac
+#![crate_type="proc-macro"]
+#[proc_macro]
+fn fn_macro() {}
+ //^^^^^^^^
+ "#,
+ )
+ }
+
+ #[test]
+ fn goto_intra_doc_links() {
+ check(
+ r#"
+
+pub mod theitem {
+ /// This is the item. Cool!
+ pub struct TheItem;
+ //^^^^^^^
+}
+
+/// Gives you a [`TheItem$0`].
+///
+/// [`TheItem`]: theitem::TheItem
+pub fn gimme() -> theitem::TheItem {
+ theitem::TheItem
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_ident_from_pat_macro() {
+ check(
+ r#"
+macro_rules! pat {
+ ($name:ident) => { Enum::Variant1($name) }
+}
+
+enum Enum {
+ Variant1(u8),
+ Variant2,
+}
+
+fn f(e: Enum) {
+ match e {
+ pat!(bind) => {
+ //^^^^
+ bind$0
+ }
+ Enum::Variant2 => {}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_include() {
+ check(
+ r#"
+//- /main.rs
+fn main() {
+ let str = include_str!("foo.txt$0");
+}
+//- /foo.txt
+// empty
+//^file
+"#,
+ );
+ }
+ #[cfg(test)]
+ mod goto_impl_of_trait_fn {
+ use super::check;
+ #[test]
+ fn cursor_on_impl() {
+ check(
+ r#"
+trait Twait {
+ fn a();
+}
+
+struct Stwuct;
+
+impl Twait for Stwuct {
+ fn a$0();
+ //^
+}
+ "#,
+ );
+ }
+ #[test]
+ fn method_call() {
+ check(
+ r#"
+trait Twait {
+ fn a(&self);
+}
+
+struct Stwuct;
+
+impl Twait for Stwuct {
+ fn a(&self){};
+ //^
+}
+fn f() {
+ let s = Stwuct;
+ s.a$0();
+}
+ "#,
+ );
+ }
+ #[test]
+ fn path_call() {
+ check(
+ r#"
+trait Twait {
+ fn a(&self);
+}
+
+struct Stwuct;
+
+impl Twait for Stwuct {
+ fn a(&self){};
+ //^
+}
+fn f() {
+ let s = Stwuct;
+ Stwuct::a$0(&s);
+}
+ "#,
+ );
+ }
+ #[test]
+ fn where_clause_can_work() {
+ check(
+ r#"
+trait G {
+ fn g(&self);
+}
+trait Bound{}
+trait EA{}
+struct Gen<T>(T);
+impl <T:EA> G for Gen<T> {
+ fn g(&self) {
+ }
+}
+impl <T> G for Gen<T>
+where T : Bound
+{
+ fn g(&self){
+ //^
+ }
+}
+struct A;
+impl Bound for A{}
+fn f() {
+ let gen = Gen::<A>(A);
+ gen.g$0();
+}
+ "#,
+ );
+ }
+ #[test]
+ fn wc_case_is_ok() {
+ check(
+ r#"
+trait G {
+ fn g(&self);
+}
+trait BParent{}
+trait Bound: BParent{}
+struct Gen<T>(T);
+impl <T> G for Gen<T>
+where T : Bound
+{
+ fn g(&self){
+ //^
+ }
+}
+struct A;
+impl Bound for A{}
+fn f() {
+ let gen = Gen::<A>(A);
+ gen.g$0();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn method_call_defaulted() {
+ check(
+ r#"
+trait Twait {
+ fn a(&self) {}
+ //^
+}
+
+struct Stwuct;
+
+impl Twait for Stwuct {
+}
+fn f() {
+ let s = Stwuct;
+ s.a$0();
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn method_call_on_generic() {
+ check(
+ r#"
+trait Twait {
+ fn a(&self) {}
+ //^
+}
+
+fn f<T: Twait>(s: T) {
+ s.a$0();
+}
+ "#,
+ );
+ }
+ }
+
+ #[test]
+ fn goto_def_of_trait_impl_const() {
+ check(
+ r#"
+trait Twait {
+ const NOMS: bool;
+ // ^^^^
+}
+
+struct Stwuct;
+
+impl Twait for Stwuct {
+ const NOMS$0: bool = true;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_of_trait_impl_type_alias() {
+ check(
+ r#"
+trait Twait {
+ type IsBad;
+ // ^^^^^
+}
+
+struct Stwuct;
+
+impl Twait for Stwuct {
+ type IsBad$0 = !;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_derive_input() {
+ check(
+ r#"
+ //- minicore:derive
+ #[rustc_builtin_macro]
+ pub macro Copy {}
+ // ^^^^
+ #[derive(Copy$0)]
+ struct Foo;
+ "#,
+ );
+ check(
+ r#"
+//- minicore:derive
+#[rustc_builtin_macro]
+pub macro Copy {}
+ // ^^^^
+#[cfg_attr(feature = "false", derive)]
+#[derive(Copy$0)]
+struct Foo;
+ "#,
+ );
+ check(
+ r#"
+//- minicore:derive
+mod foo {
+ #[rustc_builtin_macro]
+ pub macro Copy {}
+ // ^^^^
+}
+#[derive(foo::Copy$0)]
+struct Foo;
+ "#,
+ );
+ check(
+ r#"
+//- minicore:derive
+mod foo {
+ // ^^^
+ #[rustc_builtin_macro]
+ pub macro Copy {}
+}
+#[derive(foo$0::Copy)]
+struct Foo;
+ "#,
+ );
+ }
+
+ #[test]
+ fn goto_def_in_macro_multi() {
+ check(
+ r#"
+struct Foo {
+ foo: ()
+ //^^^
+}
+macro_rules! foo {
+ ($ident:ident) => {
+ fn $ident(Foo { $ident }: Foo) {}
+ }
+}
+foo!(foo$0);
+ //^^^
+ //^^^
+"#,
+ );
+ check(
+ r#"
+fn bar() {}
+ //^^^
+struct bar;
+ //^^^
+macro_rules! foo {
+ ($ident:ident) => {
+ fn foo() {
+ let _: $ident = $ident;
+ }
+ }
+}
+
+foo!(bar$0);
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs b/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs
new file mode 100644
index 000000000..04b51c839
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs
@@ -0,0 +1,344 @@
+use hir::{AsAssocItem, Impl, Semantics};
+use ide_db::{
+ defs::{Definition, NameClass, NameRefClass},
+ helpers::pick_best_token,
+ RootDatabase,
+};
+use itertools::Itertools;
+use syntax::{ast, AstNode, SyntaxKind::*, T};
+
+use crate::{FilePosition, NavigationTarget, RangeInfo, TryToNav};
+
+// Feature: Go to Implementation
+//
+// Navigates to the impl blocks of types.
+//
+// |===
+// | Editor | Shortcut
+//
+// | VS Code | kbd:[Ctrl+F12]
+// |===
+//
+// image::https://user-images.githubusercontent.com/48062697/113065566-02f85480-91b1-11eb-9288-aaad8abd8841.gif[]
+pub(crate) fn goto_implementation(
+ db: &RootDatabase,
+ position: FilePosition,
+) -> Option<RangeInfo<Vec<NavigationTarget>>> {
+ let sema = Semantics::new(db);
+ let source_file = sema.parse(position.file_id);
+ let syntax = source_file.syntax().clone();
+
+ let original_token =
+ pick_best_token(syntax.token_at_offset(position.offset), |kind| match kind {
+ IDENT | T![self] => 1,
+ _ => 0,
+ })?;
+ let range = original_token.text_range();
+ let navs = sema
+ .descend_into_macros(original_token)
+ .into_iter()
+ .filter_map(|token| token.parent().and_then(ast::NameLike::cast))
+ .filter_map(|node| match &node {
+ ast::NameLike::Name(name) => {
+ NameClass::classify(&sema, name).map(|class| match class {
+ NameClass::Definition(it) | NameClass::ConstReference(it) => it,
+ NameClass::PatFieldShorthand { local_def, field_ref: _ } => {
+ Definition::Local(local_def)
+ }
+ })
+ }
+ ast::NameLike::NameRef(name_ref) => {
+ NameRefClass::classify(&sema, name_ref).map(|class| match class {
+ NameRefClass::Definition(def) => def,
+ NameRefClass::FieldShorthand { local_ref, field_ref: _ } => {
+ Definition::Local(local_ref)
+ }
+ })
+ }
+ ast::NameLike::Lifetime(_) => None,
+ })
+ .unique()
+ .filter_map(|def| {
+ let navs = match def {
+ Definition::Trait(trait_) => impls_for_trait(&sema, trait_),
+ Definition::Adt(adt) => impls_for_ty(&sema, adt.ty(sema.db)),
+ Definition::TypeAlias(alias) => impls_for_ty(&sema, alias.ty(sema.db)),
+ Definition::BuiltinType(builtin) => impls_for_ty(&sema, builtin.ty(sema.db)),
+ Definition::Function(f) => {
+ let assoc = f.as_assoc_item(sema.db)?;
+ let name = assoc.name(sema.db)?;
+ let trait_ = assoc.containing_trait_or_trait_impl(sema.db)?;
+ impls_for_trait_item(&sema, trait_, name)
+ }
+ Definition::Const(c) => {
+ let assoc = c.as_assoc_item(sema.db)?;
+ let name = assoc.name(sema.db)?;
+ let trait_ = assoc.containing_trait_or_trait_impl(sema.db)?;
+ impls_for_trait_item(&sema, trait_, name)
+ }
+ _ => return None,
+ };
+ Some(navs)
+ })
+ .flatten()
+ .collect();
+
+ Some(RangeInfo { range, info: navs })
+}
+
+fn impls_for_ty(sema: &Semantics<'_, RootDatabase>, ty: hir::Type) -> Vec<NavigationTarget> {
+ Impl::all_for_type(sema.db, ty).into_iter().filter_map(|imp| imp.try_to_nav(sema.db)).collect()
+}
+
+fn impls_for_trait(
+ sema: &Semantics<'_, RootDatabase>,
+ trait_: hir::Trait,
+) -> Vec<NavigationTarget> {
+ Impl::all_for_trait(sema.db, trait_)
+ .into_iter()
+ .filter_map(|imp| imp.try_to_nav(sema.db))
+ .collect()
+}
+
+fn impls_for_trait_item(
+ sema: &Semantics<'_, RootDatabase>,
+ trait_: hir::Trait,
+ fun_name: hir::Name,
+) -> Vec<NavigationTarget> {
+ Impl::all_for_trait(sema.db, trait_)
+ .into_iter()
+ .filter_map(|imp| {
+ let item = imp.items(sema.db).iter().find_map(|itm| {
+ let itm_name = itm.name(sema.db)?;
+ (itm_name == fun_name).then(|| *itm)
+ })?;
+ item.try_to_nav(sema.db)
+ })
+ .collect()
+}
+
+#[cfg(test)]
+mod tests {
+ use ide_db::base_db::FileRange;
+ use itertools::Itertools;
+
+ use crate::fixture;
+
+ fn check(ra_fixture: &str) {
+ let (analysis, position, expected) = fixture::annotations(ra_fixture);
+
+ let navs = analysis.goto_implementation(position).unwrap().unwrap().info;
+
+ let cmp = |frange: &FileRange| (frange.file_id, frange.range.start());
+
+ let actual = navs
+ .into_iter()
+ .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
+ .sorted_by_key(cmp)
+ .collect::<Vec<_>>();
+ let expected =
+ expected.into_iter().map(|(range, _)| range).sorted_by_key(cmp).collect::<Vec<_>>();
+ assert_eq!(expected, actual);
+ }
+
+ #[test]
+ fn goto_implementation_works() {
+ check(
+ r#"
+struct Foo$0;
+impl Foo {}
+ //^^^
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_implementation_works_multiple_blocks() {
+ check(
+ r#"
+struct Foo$0;
+impl Foo {}
+ //^^^
+impl Foo {}
+ //^^^
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_implementation_works_multiple_mods() {
+ check(
+ r#"
+struct Foo$0;
+mod a {
+ impl super::Foo {}
+ //^^^^^^^^^^
+}
+mod b {
+ impl super::Foo {}
+ //^^^^^^^^^^
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_implementation_works_multiple_files() {
+ check(
+ r#"
+//- /lib.rs
+struct Foo$0;
+mod a;
+mod b;
+//- /a.rs
+impl crate::Foo {}
+ //^^^^^^^^^^
+//- /b.rs
+impl crate::Foo {}
+ //^^^^^^^^^^
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_implementation_for_trait() {
+ check(
+ r#"
+trait T$0 {}
+struct Foo;
+impl T for Foo {}
+ //^^^
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_implementation_for_trait_multiple_files() {
+ check(
+ r#"
+//- /lib.rs
+trait T$0 {};
+struct Foo;
+mod a;
+mod b;
+//- /a.rs
+impl crate::T for crate::Foo {}
+ //^^^^^^^^^^
+//- /b.rs
+impl crate::T for crate::Foo {}
+ //^^^^^^^^^^
+ "#,
+ );
+ }
+
+ #[test]
+ fn goto_implementation_all_impls() {
+ check(
+ r#"
+//- /lib.rs
+trait T {}
+struct Foo$0;
+impl Foo {}
+ //^^^
+impl T for Foo {}
+ //^^^
+impl T for &Foo {}
+ //^^^^
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_implementation_to_builtin_derive() {
+ check(
+ r#"
+//- minicore: copy, derive
+ #[derive(Copy)]
+//^^^^^^^^^^^^^^^
+struct Foo$0;
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_implementation_type_alias() {
+ check(
+ r#"
+struct Foo;
+
+type Bar$0 = Foo;
+
+impl Foo {}
+ //^^^
+impl Bar {}
+ //^^^
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_implementation_adt_generic() {
+ check(
+ r#"
+struct Foo$0<T>;
+
+impl<T> Foo<T> {}
+ //^^^^^^
+impl Foo<str> {}
+ //^^^^^^^^
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_implementation_builtin() {
+ check(
+ r#"
+//- /lib.rs crate:main deps:core
+fn foo(_: bool$0) {{}}
+//- /libcore.rs crate:core
+#[lang = "bool"]
+impl bool {}
+ //^^^^
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_implementation_trait_functions() {
+ check(
+ r#"
+trait Tr {
+ fn f$0();
+}
+
+struct S;
+
+impl Tr for S {
+ fn f() {
+ //^
+ println!("Hello, world!");
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_implementation_trait_assoc_const() {
+ check(
+ r#"
+trait Tr {
+ const C$0: usize;
+}
+
+struct S;
+
+impl Tr for S {
+ const C: usize = 4;
+ //^
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs b/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs
new file mode 100644
index 000000000..55cdb3200
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs
@@ -0,0 +1,296 @@
+use ide_db::{base_db::Upcast, defs::Definition, helpers::pick_best_token, RootDatabase};
+use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, T};
+
+use crate::{FilePosition, NavigationTarget, RangeInfo, TryToNav};
+
+// Feature: Go to Type Definition
+//
+// Navigates to the type of an identifier.
+//
+// |===
+// | Editor | Action Name
+//
+// | VS Code | **Go to Type Definition*
+// |===
+//
+// image::https://user-images.githubusercontent.com/48062697/113020657-b560f500-917a-11eb-9007-0f809733a338.gif[]
+pub(crate) fn goto_type_definition(
+ db: &RootDatabase,
+ position: FilePosition,
+) -> Option<RangeInfo<Vec<NavigationTarget>>> {
+ let sema = hir::Semantics::new(db);
+
+ let file: ast::SourceFile = sema.parse(position.file_id);
+ let token: SyntaxToken =
+ pick_best_token(file.syntax().token_at_offset(position.offset), |kind| match kind {
+ IDENT | INT_NUMBER | T![self] => 2,
+ kind if kind.is_trivia() => 0,
+ _ => 1,
+ })?;
+
+ let mut res = Vec::new();
+ let mut push = |def: Definition| {
+ if let Some(nav) = def.try_to_nav(db) {
+ if !res.contains(&nav) {
+ res.push(nav);
+ }
+ }
+ };
+ let range = token.text_range();
+ sema.descend_into_macros(token)
+ .iter()
+ .filter_map(|token| {
+ let ty = sema.token_ancestors_with_macros(token.clone()).find_map(|node| {
+ let ty = match_ast! {
+ match node {
+ ast::Expr(it) => sema.type_of_expr(&it)?.original,
+ ast::Pat(it) => sema.type_of_pat(&it)?.original,
+ ast::SelfParam(it) => sema.type_of_self(&it)?,
+ ast::Type(it) => sema.resolve_type(&it)?,
+ ast::RecordField(it) => sema.to_def(&it).map(|d| d.ty(db.upcast()))?,
+ // can't match on RecordExprField directly as `ast::Expr` will match an iteration too early otherwise
+ ast::NameRef(it) => {
+ if let Some(record_field) = ast::RecordExprField::for_name_ref(&it) {
+ let (_, _, ty) = sema.resolve_record_field(&record_field)?;
+ ty
+ } else {
+ let record_field = ast::RecordPatField::for_field_name_ref(&it)?;
+ sema.resolve_record_pat_field(&record_field)?.ty(db)
+ }
+ },
+ _ => return None,
+ }
+ };
+
+ Some(ty)
+ });
+ ty
+ })
+ .for_each(|ty| {
+ // collect from each `ty` into the `res` result vec
+ let ty = ty.strip_references();
+ ty.walk(db, |t| {
+ if let Some(adt) = t.as_adt() {
+ push(adt.into());
+ } else if let Some(trait_) = t.as_dyn_trait() {
+ push(trait_.into());
+ } else if let Some(traits) = t.as_impl_traits(db) {
+ traits.for_each(|it| push(it.into()));
+ } else if let Some(trait_) = t.as_associated_type_parent_trait(db) {
+ push(trait_.into());
+ }
+ });
+ });
+ Some(RangeInfo::new(range, res))
+}
+
+#[cfg(test)]
+mod tests {
+ use ide_db::base_db::FileRange;
+ use itertools::Itertools;
+
+ use crate::fixture;
+
+ fn check(ra_fixture: &str) {
+ let (analysis, position, expected) = fixture::annotations(ra_fixture);
+ let navs = analysis.goto_type_definition(position).unwrap().unwrap().info;
+ assert_ne!(navs.len(), 0);
+
+ let cmp = |&FileRange { file_id, range }: &_| (file_id, range.start());
+ let navs = navs
+ .into_iter()
+ .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
+ .sorted_by_key(cmp)
+ .collect::<Vec<_>>();
+ let expected = expected
+ .into_iter()
+ .map(|(FileRange { file_id, range }, _)| FileRange { file_id, range })
+ .sorted_by_key(cmp)
+ .collect::<Vec<_>>();
+ assert_eq!(expected, navs);
+ }
+
+ #[test]
+ fn goto_type_definition_works_simple() {
+ check(
+ r#"
+struct Foo;
+ //^^^
+fn foo() {
+ let f: Foo; f$0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_type_definition_record_expr_field() {
+ check(
+ r#"
+struct Bar;
+ // ^^^
+struct Foo { foo: Bar }
+fn foo() {
+ Foo { foo$0 }
+}
+"#,
+ );
+ check(
+ r#"
+struct Bar;
+ // ^^^
+struct Foo { foo: Bar }
+fn foo() {
+ Foo { foo$0: Bar }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_type_definition_record_pat_field() {
+ check(
+ r#"
+struct Bar;
+ // ^^^
+struct Foo { foo: Bar }
+fn foo() {
+ let Foo { foo$0 };
+}
+"#,
+ );
+ check(
+ r#"
+struct Bar;
+ // ^^^
+struct Foo { foo: Bar }
+fn foo() {
+ let Foo { foo$0: bar };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_type_definition_works_simple_ref() {
+ check(
+ r#"
+struct Foo;
+ //^^^
+fn foo() {
+ let f: &Foo; f$0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_type_definition_works_through_macro() {
+ check(
+ r#"
+macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
+struct Foo {}
+ //^^^
+id! {
+ fn bar() { let f$0 = Foo {}; }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_type_definition_for_param() {
+ check(
+ r#"
+struct Foo;
+ //^^^
+fn foo($0f: Foo) {}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_type_definition_for_tuple_field() {
+ check(
+ r#"
+struct Foo;
+ //^^^
+struct Bar(Foo);
+fn foo() {
+ let bar = Bar(Foo);
+ bar.$00;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_self_param() {
+ check(
+ r#"
+struct Foo;
+ //^^^
+impl Foo {
+ fn f(&self$0) {}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn goto_def_for_type_fallback() {
+ check(
+ r#"
+struct Foo;
+ //^^^
+impl Foo$0 {}
+"#,
+ )
+ }
+
+ #[test]
+ fn goto_def_for_struct_field() {
+ check(
+ r#"
+struct Bar;
+ //^^^
+
+struct Foo {
+ bar$0: Bar,
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_enum_struct_field() {
+ check(
+ r#"
+struct Bar;
+ //^^^
+
+enum Foo {
+ Bar {
+ bar$0: Bar
+ },
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_considers_generics() {
+ check(
+ r#"
+struct Foo;
+ //^^^
+struct Bar<T, U>(T, U);
+ //^^^
+struct Baz<T>(T);
+ //^^^
+
+fn foo(x$0: Bar<Baz<Foo>, Baz<usize>) {}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs b/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs
new file mode 100644
index 000000000..f2d7029ea
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs
@@ -0,0 +1,1377 @@
+use hir::Semantics;
+use ide_db::{
+ base_db::{FileId, FilePosition},
+ defs::{Definition, IdentClass},
+ helpers::pick_best_token,
+ search::{FileReference, ReferenceCategory, SearchScope},
+ syntax_helpers::node_ext::{for_each_break_and_continue_expr, for_each_tail_expr, walk_expr},
+ FxHashSet, RootDatabase,
+};
+use syntax::{
+ ast::{self, HasLoopBody},
+ match_ast, AstNode,
+ SyntaxKind::{self, IDENT, INT_NUMBER},
+ SyntaxNode, SyntaxToken, TextRange, T,
+};
+
+use crate::{references, NavigationTarget, TryToNav};
+
+#[derive(PartialEq, Eq, Hash)]
+pub struct HighlightedRange {
+ pub range: TextRange,
+ // FIXME: This needs to be more precise. Reference category makes sense only
+ // for references, but we also have defs. And things like exit points are
+ // neither.
+ pub category: Option<ReferenceCategory>,
+}
+
+#[derive(Default, Clone)]
+pub struct HighlightRelatedConfig {
+ pub references: bool,
+ pub exit_points: bool,
+ pub break_points: bool,
+ pub yield_points: bool,
+}
+
+// Feature: Highlight Related
+//
+// Highlights constructs related to the thing under the cursor:
+//
+// . if on an identifier, highlights all references to that identifier in the current file
+// . if on an `async` or `await token, highlights all yield points for that async context
+// . if on a `return` or `fn` keyword, `?` character or `->` return type arrow, highlights all exit points for that context
+// . if on a `break`, `loop`, `while` or `for` token, highlights all break points for that loop or block context
+//
+// Note: `?` and `->` do not currently trigger this behavior in the VSCode editor.
+pub(crate) fn highlight_related(
+ sema: &Semantics<'_, RootDatabase>,
+ config: HighlightRelatedConfig,
+ FilePosition { offset, file_id }: FilePosition,
+) -> Option<Vec<HighlightedRange>> {
+ let _p = profile::span("highlight_related");
+ let syntax = sema.parse(file_id).syntax().clone();
+
+ let token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind {
+ T![?] => 4, // prefer `?` when the cursor is sandwiched like in `await$0?`
+ T![->] => 3,
+ kind if kind.is_keyword() => 2,
+ IDENT | INT_NUMBER => 1,
+ _ => 0,
+ })?;
+ match token.kind() {
+ T![?] if config.exit_points && token.parent().and_then(ast::TryExpr::cast).is_some() => {
+ highlight_exit_points(sema, token)
+ }
+ T![fn] | T![return] | T![->] if config.exit_points => highlight_exit_points(sema, token),
+ T![await] | T![async] if config.yield_points => highlight_yield_points(token),
+ T![for] if config.break_points && token.parent().and_then(ast::ForExpr::cast).is_some() => {
+ highlight_break_points(token)
+ }
+ T![break] | T![loop] | T![while] | T![continue] if config.break_points => {
+ highlight_break_points(token)
+ }
+ _ if config.references => highlight_references(sema, &syntax, token, file_id),
+ _ => None,
+ }
+}
+
+fn highlight_references(
+ sema: &Semantics<'_, RootDatabase>,
+ node: &SyntaxNode,
+ token: SyntaxToken,
+ file_id: FileId,
+) -> Option<Vec<HighlightedRange>> {
+ let defs = find_defs(sema, token);
+ let usages = defs
+ .iter()
+ .filter_map(|&d| {
+ d.usages(sema)
+ .set_scope(Some(SearchScope::single_file(file_id)))
+ .include_self_refs()
+ .all()
+ .references
+ .remove(&file_id)
+ })
+ .flatten()
+ .map(|FileReference { category: access, range, .. }| HighlightedRange {
+ range,
+ category: access,
+ });
+ let mut res = FxHashSet::default();
+
+ let mut def_to_hl_range = |def| {
+ let hl_range = match def {
+ Definition::Module(module) => {
+ Some(NavigationTarget::from_module_to_decl(sema.db, module))
+ }
+ def => def.try_to_nav(sema.db),
+ }
+ .filter(|decl| decl.file_id == file_id)
+ .and_then(|decl| decl.focus_range)
+ .map(|range| {
+ let category =
+ references::decl_mutability(&def, node, range).then(|| ReferenceCategory::Write);
+ HighlightedRange { range, category }
+ });
+ if let Some(hl_range) = hl_range {
+ res.insert(hl_range);
+ }
+ };
+ for &def in &defs {
+ match def {
+ Definition::Local(local) => local
+ .associated_locals(sema.db)
+ .iter()
+ .for_each(|&local| def_to_hl_range(Definition::Local(local))),
+ def => def_to_hl_range(def),
+ }
+ }
+
+ res.extend(usages);
+ if res.is_empty() {
+ None
+ } else {
+ Some(res.into_iter().collect())
+ }
+}
+
+fn highlight_exit_points(
+ sema: &Semantics<'_, RootDatabase>,
+ token: SyntaxToken,
+) -> Option<Vec<HighlightedRange>> {
+ fn hl(
+ sema: &Semantics<'_, RootDatabase>,
+ body: Option<ast::Expr>,
+ ) -> Option<Vec<HighlightedRange>> {
+ let mut highlights = Vec::new();
+ let body = body?;
+ walk_expr(&body, &mut |expr| match expr {
+ ast::Expr::ReturnExpr(expr) => {
+ if let Some(token) = expr.return_token() {
+ highlights.push(HighlightedRange { category: None, range: token.text_range() });
+ }
+ }
+ ast::Expr::TryExpr(try_) => {
+ if let Some(token) = try_.question_mark_token() {
+ highlights.push(HighlightedRange { category: None, range: token.text_range() });
+ }
+ }
+ ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroExpr(_) => {
+ if sema.type_of_expr(&expr).map_or(false, |ty| ty.original.is_never()) {
+ highlights.push(HighlightedRange {
+ category: None,
+ range: expr.syntax().text_range(),
+ });
+ }
+ }
+ _ => (),
+ });
+ let tail = match body {
+ ast::Expr::BlockExpr(b) => b.tail_expr(),
+ e => Some(e),
+ };
+
+ if let Some(tail) = tail {
+ for_each_tail_expr(&tail, &mut |tail| {
+ let range = match tail {
+ ast::Expr::BreakExpr(b) => b
+ .break_token()
+ .map_or_else(|| tail.syntax().text_range(), |tok| tok.text_range()),
+ _ => tail.syntax().text_range(),
+ };
+ highlights.push(HighlightedRange { category: None, range })
+ });
+ }
+ Some(highlights)
+ }
+ for anc in token.parent_ancestors() {
+ return match_ast! {
+ match anc {
+ ast::Fn(fn_) => hl(sema, fn_.body().map(ast::Expr::BlockExpr)),
+ ast::ClosureExpr(closure) => hl(sema, closure.body()),
+ ast::BlockExpr(block_expr) => if matches!(block_expr.modifier(), Some(ast::BlockModifier::Async(_) | ast::BlockModifier::Try(_)| ast::BlockModifier::Const(_))) {
+ hl(sema, Some(block_expr.into()))
+ } else {
+ continue;
+ },
+ _ => continue,
+ }
+ };
+ }
+ None
+}
+
+fn highlight_break_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
+ fn hl(
+ cursor_token_kind: SyntaxKind,
+ token: Option<SyntaxToken>,
+ label: Option<ast::Label>,
+ body: Option<ast::StmtList>,
+ ) -> Option<Vec<HighlightedRange>> {
+ let mut highlights = Vec::new();
+ let range = cover_range(
+ token.map(|tok| tok.text_range()),
+ label.as_ref().map(|it| it.syntax().text_range()),
+ );
+ highlights.extend(range.map(|range| HighlightedRange { category: None, range }));
+ for_each_break_and_continue_expr(label, body, &mut |expr| {
+ let range: Option<TextRange> = match (cursor_token_kind, expr) {
+ (T![for] | T![while] | T![loop] | T![break], ast::Expr::BreakExpr(break_)) => {
+ cover_range(
+ break_.break_token().map(|it| it.text_range()),
+ break_.lifetime().map(|it| it.syntax().text_range()),
+ )
+ }
+ (
+ T![for] | T![while] | T![loop] | T![continue],
+ ast::Expr::ContinueExpr(continue_),
+ ) => cover_range(
+ continue_.continue_token().map(|it| it.text_range()),
+ continue_.lifetime().map(|it| it.syntax().text_range()),
+ ),
+ _ => None,
+ };
+ highlights.extend(range.map(|range| HighlightedRange { category: None, range }));
+ });
+ Some(highlights)
+ }
+ let parent = token.parent()?;
+ let lbl = match_ast! {
+ match parent {
+ ast::BreakExpr(b) => b.lifetime(),
+ ast::ContinueExpr(c) => c.lifetime(),
+ ast::LoopExpr(l) => l.label().and_then(|it| it.lifetime()),
+ ast::ForExpr(f) => f.label().and_then(|it| it.lifetime()),
+ ast::WhileExpr(w) => w.label().and_then(|it| it.lifetime()),
+ ast::BlockExpr(b) => Some(b.label().and_then(|it| it.lifetime())?),
+ _ => return None,
+ }
+ };
+ let lbl = lbl.as_ref();
+ let label_matches = |def_lbl: Option<ast::Label>| match lbl {
+ Some(lbl) => {
+ Some(lbl.text()) == def_lbl.and_then(|it| it.lifetime()).as_ref().map(|it| it.text())
+ }
+ None => true,
+ };
+ let token_kind = token.kind();
+ for anc in token.parent_ancestors().flat_map(ast::Expr::cast) {
+ return match anc {
+ ast::Expr::LoopExpr(l) if label_matches(l.label()) => hl(
+ token_kind,
+ l.loop_token(),
+ l.label(),
+ l.loop_body().and_then(|it| it.stmt_list()),
+ ),
+ ast::Expr::ForExpr(f) if label_matches(f.label()) => hl(
+ token_kind,
+ f.for_token(),
+ f.label(),
+ f.loop_body().and_then(|it| it.stmt_list()),
+ ),
+ ast::Expr::WhileExpr(w) if label_matches(w.label()) => hl(
+ token_kind,
+ w.while_token(),
+ w.label(),
+ w.loop_body().and_then(|it| it.stmt_list()),
+ ),
+ ast::Expr::BlockExpr(e) if e.label().is_some() && label_matches(e.label()) => {
+ hl(token_kind, None, e.label(), e.stmt_list())
+ }
+ _ => continue,
+ };
+ }
+ None
+}
+
+fn highlight_yield_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
+ fn hl(
+ async_token: Option<SyntaxToken>,
+ body: Option<ast::Expr>,
+ ) -> Option<Vec<HighlightedRange>> {
+ let mut highlights =
+ vec![HighlightedRange { category: None, range: async_token?.text_range() }];
+ if let Some(body) = body {
+ walk_expr(&body, &mut |expr| {
+ if let ast::Expr::AwaitExpr(expr) = expr {
+ if let Some(token) = expr.await_token() {
+ highlights
+ .push(HighlightedRange { category: None, range: token.text_range() });
+ }
+ }
+ });
+ }
+ Some(highlights)
+ }
+ for anc in token.parent_ancestors() {
+ return match_ast! {
+ match anc {
+ ast::Fn(fn_) => hl(fn_.async_token(), fn_.body().map(ast::Expr::BlockExpr)),
+ ast::BlockExpr(block_expr) => {
+ if block_expr.async_token().is_none() {
+ continue;
+ }
+ hl(block_expr.async_token(), Some(block_expr.into()))
+ },
+ ast::ClosureExpr(closure) => hl(closure.async_token(), closure.body()),
+ _ => continue,
+ }
+ };
+ }
+ None
+}
+
+fn cover_range(r0: Option<TextRange>, r1: Option<TextRange>) -> Option<TextRange> {
+ match (r0, r1) {
+ (Some(r0), Some(r1)) => Some(r0.cover(r1)),
+ (Some(range), None) => Some(range),
+ (None, Some(range)) => Some(range),
+ (None, None) => None,
+ }
+}
+
+fn find_defs(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> FxHashSet<Definition> {
+ sema.descend_into_macros(token)
+ .into_iter()
+ .filter_map(|token| IdentClass::classify_token(sema, &token).map(IdentClass::definitions))
+ .flatten()
+ .collect()
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::fixture;
+
+ use super::*;
+
+ #[track_caller]
+ fn check(ra_fixture: &str) {
+ let config = HighlightRelatedConfig {
+ break_points: true,
+ exit_points: true,
+ references: true,
+ yield_points: true,
+ };
+
+ check_with_config(ra_fixture, config);
+ }
+
+ #[track_caller]
+ fn check_with_config(ra_fixture: &str, config: HighlightRelatedConfig) {
+ let (analysis, pos, annotations) = fixture::annotations(ra_fixture);
+
+ let hls = analysis.highlight_related(config, pos).unwrap().unwrap_or_default();
+
+ let mut expected = annotations
+ .into_iter()
+ .map(|(r, access)| (r.range, (!access.is_empty()).then(|| access)))
+ .collect::<Vec<_>>();
+
+ let mut actual = hls
+ .into_iter()
+ .map(|hl| {
+ (
+ hl.range,
+ hl.category.map(|it| {
+ match it {
+ ReferenceCategory::Read => "read",
+ ReferenceCategory::Write => "write",
+ }
+ .to_string()
+ }),
+ )
+ })
+ .collect::<Vec<_>>();
+ actual.sort_by_key(|(range, _)| range.start());
+ expected.sort_by_key(|(range, _)| range.start());
+
+ assert_eq!(expected, actual);
+ }
+
+ #[test]
+ fn test_hl_tuple_fields() {
+ check(
+ r#"
+struct Tuple(u32, u32);
+
+fn foo(t: Tuple) {
+ t.0$0;
+ // ^ read
+ t.0;
+ // ^ read
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_module() {
+ check(
+ r#"
+//- /lib.rs
+mod foo$0;
+ // ^^^
+//- /foo.rs
+struct Foo;
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_self_in_crate_root() {
+ check(
+ r#"
+use crate$0;
+ //^^^^^
+use self;
+ //^^^^
+mod __ {
+ use super;
+ //^^^^^
+}
+"#,
+ );
+ check(
+ r#"
+//- /main.rs crate:main deps:lib
+use lib$0;
+ //^^^
+//- /lib.rs crate:lib
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_self_in_module() {
+ check(
+ r#"
+//- /lib.rs
+mod foo;
+//- /foo.rs
+use self$0;
+ // ^^^^
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_local() {
+ check(
+ r#"
+fn foo() {
+ let mut bar = 3;
+ // ^^^ write
+ bar$0;
+ // ^^^ read
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_local_in_attr() {
+ check(
+ r#"
+//- proc_macros: identity
+#[proc_macros::identity]
+fn foo() {
+ let mut bar = 3;
+ // ^^^ write
+ bar$0;
+ // ^^^ read
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_multi_macro_usage() {
+ check(
+ r#"
+macro_rules! foo {
+ ($ident:ident) => {
+ fn $ident() -> $ident { loop {} }
+ struct $ident;
+ }
+}
+
+foo!(bar$0);
+ // ^^^
+fn foo() {
+ let bar: bar = bar();
+ // ^^^
+ // ^^^
+}
+"#,
+ );
+ check(
+ r#"
+macro_rules! foo {
+ ($ident:ident) => {
+ fn $ident() -> $ident { loop {} }
+ struct $ident;
+ }
+}
+
+foo!(bar);
+ // ^^^
+fn foo() {
+ let bar: bar$0 = bar();
+ // ^^^
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_yield_points() {
+ check(
+ r#"
+pub async fn foo() {
+ // ^^^^^
+ let x = foo()
+ .await$0
+ // ^^^^^
+ .await;
+ // ^^^^^
+ || { 0.await };
+ (async { 0.await }).await
+ // ^^^^^
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_yield_points2() {
+ check(
+ r#"
+pub async$0 fn foo() {
+ // ^^^^^
+ let x = foo()
+ .await
+ // ^^^^^
+ .await;
+ // ^^^^^
+ || { 0.await };
+ (async { 0.await }).await
+ // ^^^^^
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_yield_nested_fn() {
+ check(
+ r#"
+async fn foo() {
+ async fn foo2() {
+ // ^^^^^
+ async fn foo3() {
+ 0.await
+ }
+ 0.await$0
+ // ^^^^^
+ }
+ 0.await
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_yield_nested_async_blocks() {
+ check(
+ r#"
+async fn foo() {
+ (async {
+ // ^^^^^
+ (async {
+ 0.await
+ }).await$0 }
+ // ^^^^^
+ ).await;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_exit_points() {
+ check(
+ r#"
+fn foo() -> u32 {
+ if true {
+ return$0 0;
+ // ^^^^^^
+ }
+
+ 0?;
+ // ^
+ 0xDEAD_BEEF
+ // ^^^^^^^^^^^
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_exit_points2() {
+ check(
+ r#"
+fn foo() ->$0 u32 {
+ if true {
+ return 0;
+ // ^^^^^^
+ }
+
+ 0?;
+ // ^
+ 0xDEAD_BEEF
+ // ^^^^^^^^^^^
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_exit_points3() {
+ check(
+ r#"
+fn$0 foo() -> u32 {
+ if true {
+ return 0;
+ // ^^^^^^
+ }
+
+ 0?;
+ // ^
+ 0xDEAD_BEEF
+ // ^^^^^^^^^^^
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_prefer_ref_over_tail_exit() {
+ check(
+ r#"
+fn foo() -> u32 {
+// ^^^
+ if true {
+ return 0;
+ }
+
+ 0?;
+
+ foo$0()
+ // ^^^
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_never_call_is_exit_point() {
+ check(
+ r#"
+struct Never;
+impl Never {
+ fn never(self) -> ! { loop {} }
+}
+macro_rules! never {
+ () => { never() }
+}
+fn never() -> ! { loop {} }
+fn foo() ->$0 u32 {
+ never();
+ // ^^^^^^^
+ never!();
+ // ^^^^^^^^
+
+ Never.never();
+ // ^^^^^^^^^^^^^
+
+ 0
+ // ^
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_inner_tail_exit_points() {
+ check(
+ r#"
+fn foo() ->$0 u32 {
+ if true {
+ unsafe {
+ return 5;
+ // ^^^^^^
+ 5
+ // ^
+ }
+ } else if false {
+ 0
+ // ^
+ } else {
+ match 5 {
+ 6 => 100,
+ // ^^^
+ 7 => loop {
+ break 5;
+ // ^^^^^
+ }
+ 8 => 'a: loop {
+ 'b: loop {
+ break 'a 5;
+ // ^^^^^
+ break 'b 5;
+ break 5;
+ };
+ }
+ //
+ _ => 500,
+ // ^^^
+ }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_inner_tail_exit_points_labeled_block() {
+ check(
+ r#"
+fn foo() ->$0 u32 {
+ 'foo: {
+ break 'foo 0;
+ // ^^^^^
+ loop {
+ break;
+ break 'foo 0;
+ // ^^^^^
+ }
+ 0
+ // ^
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_break_loop() {
+ check(
+ r#"
+fn foo() {
+ 'outer: loop {
+ // ^^^^^^^^^^^^
+ break;
+ // ^^^^^
+ 'inner: loop {
+ break;
+ 'innermost: loop {
+ break 'outer;
+ // ^^^^^^^^^^^^
+ break 'inner;
+ }
+ break$0 'outer;
+ // ^^^^^^^^^^^^
+ break;
+ }
+ break;
+ // ^^^^^
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_break_loop2() {
+ check(
+ r#"
+fn foo() {
+ 'outer: loop {
+ break;
+ 'inner: loop {
+ // ^^^^^^^^^^^^
+ break;
+ // ^^^^^
+ 'innermost: loop {
+ break 'outer;
+ break 'inner;
+ // ^^^^^^^^^^^^
+ }
+ break 'outer;
+ break$0;
+ // ^^^^^
+ }
+ break;
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_break_for() {
+ check(
+ r#"
+fn foo() {
+ 'outer: for _ in () {
+ // ^^^^^^^^^^^
+ break;
+ // ^^^^^
+ 'inner: for _ in () {
+ break;
+ 'innermost: for _ in () {
+ break 'outer;
+ // ^^^^^^^^^^^^
+ break 'inner;
+ }
+ break$0 'outer;
+ // ^^^^^^^^^^^^
+ break;
+ }
+ break;
+ // ^^^^^
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_break_for_but_not_continue() {
+ check(
+ r#"
+fn foo() {
+ 'outer: for _ in () {
+ // ^^^^^^^^^^^
+ break;
+ // ^^^^^
+ continue;
+ 'inner: for _ in () {
+ break;
+ continue;
+ 'innermost: for _ in () {
+ continue 'outer;
+ break 'outer;
+ // ^^^^^^^^^^^^
+ continue 'inner;
+ break 'inner;
+ }
+ break$0 'outer;
+ // ^^^^^^^^^^^^
+ continue 'outer;
+ break;
+ continue;
+ }
+ break;
+ // ^^^^^
+ continue;
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_continue_for_but_not_break() {
+ check(
+ r#"
+fn foo() {
+ 'outer: for _ in () {
+ // ^^^^^^^^^^^
+ break;
+ continue;
+ // ^^^^^^^^
+ 'inner: for _ in () {
+ break;
+ continue;
+ 'innermost: for _ in () {
+ continue 'outer;
+ // ^^^^^^^^^^^^^^^
+ break 'outer;
+ continue 'inner;
+ break 'inner;
+ }
+ break 'outer;
+ continue$0 'outer;
+ // ^^^^^^^^^^^^^^^
+ break;
+ continue;
+ }
+ break;
+ continue;
+ // ^^^^^^^^
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_break_and_continue() {
+ check(
+ r#"
+fn foo() {
+ 'outer: fo$0r _ in () {
+ // ^^^^^^^^^^^
+ break;
+ // ^^^^^
+ continue;
+ // ^^^^^^^^
+ 'inner: for _ in () {
+ break;
+ continue;
+ 'innermost: for _ in () {
+ continue 'outer;
+ // ^^^^^^^^^^^^^^^
+ break 'outer;
+ // ^^^^^^^^^^^^
+ continue 'inner;
+ break 'inner;
+ }
+ break 'outer;
+ // ^^^^^^^^^^^^
+ continue 'outer;
+ // ^^^^^^^^^^^^^^^
+ break;
+ continue;
+ }
+ break;
+ // ^^^^^
+ continue;
+ // ^^^^^^^^
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_break_while() {
+ check(
+ r#"
+fn foo() {
+ 'outer: while true {
+ // ^^^^^^^^^^^^^
+ break;
+ // ^^^^^
+ 'inner: while true {
+ break;
+ 'innermost: while true {
+ break 'outer;
+ // ^^^^^^^^^^^^
+ break 'inner;
+ }
+ break$0 'outer;
+ // ^^^^^^^^^^^^
+ break;
+ }
+ break;
+ // ^^^^^
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_break_labeled_block() {
+ check(
+ r#"
+fn foo() {
+ 'outer: {
+ // ^^^^^^^
+ break;
+ // ^^^^^
+ 'inner: {
+ break;
+ 'innermost: {
+ break 'outer;
+ // ^^^^^^^^^^^^
+ break 'inner;
+ }
+ break$0 'outer;
+ // ^^^^^^^^^^^^
+ break;
+ }
+ break;
+ // ^^^^^
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_break_unlabeled_loop() {
+ check(
+ r#"
+fn foo() {
+ loop {
+ // ^^^^
+ break$0;
+ // ^^^^^
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_break_unlabeled_block_in_loop() {
+ check(
+ r#"
+fn foo() {
+ loop {
+ // ^^^^
+ {
+ break$0;
+ // ^^^^^
+ }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_field_shorthand() {
+ check(
+ r#"
+struct Struct { field: u32 }
+ //^^^^^
+fn function(field: u32) {
+ //^^^^^
+ Struct { field$0 }
+ //^^^^^ read
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_disabled_ref_local() {
+ let config = HighlightRelatedConfig {
+ references: false,
+ break_points: true,
+ exit_points: true,
+ yield_points: true,
+ };
+
+ check_with_config(
+ r#"
+fn foo() {
+ let x$0 = 5;
+ let y = x * 2;
+}
+"#,
+ config,
+ );
+ }
+
+ #[test]
+ fn test_hl_disabled_ref_local_preserved_break() {
+ let config = HighlightRelatedConfig {
+ references: false,
+ break_points: true,
+ exit_points: true,
+ yield_points: true,
+ };
+
+ check_with_config(
+ r#"
+fn foo() {
+ let x$0 = 5;
+ let y = x * 2;
+
+ loop {
+ break;
+ }
+}
+"#,
+ config.clone(),
+ );
+
+ check_with_config(
+ r#"
+fn foo() {
+ let x = 5;
+ let y = x * 2;
+
+ loop$0 {
+// ^^^^
+ break;
+// ^^^^^
+ }
+}
+"#,
+ config,
+ );
+ }
+
+ #[test]
+ fn test_hl_disabled_ref_local_preserved_yield() {
+ let config = HighlightRelatedConfig {
+ references: false,
+ break_points: true,
+ exit_points: true,
+ yield_points: true,
+ };
+
+ check_with_config(
+ r#"
+async fn foo() {
+ let x$0 = 5;
+ let y = x * 2;
+
+ 0.await;
+}
+"#,
+ config.clone(),
+ );
+
+ check_with_config(
+ r#"
+ async fn foo() {
+// ^^^^^
+ let x = 5;
+ let y = x * 2;
+
+ 0.await$0;
+// ^^^^^
+}
+"#,
+ config,
+ );
+ }
+
+ #[test]
+ fn test_hl_disabled_ref_local_preserved_exit() {
+ let config = HighlightRelatedConfig {
+ references: false,
+ break_points: true,
+ exit_points: true,
+ yield_points: true,
+ };
+
+ check_with_config(
+ r#"
+fn foo() -> i32 {
+ let x$0 = 5;
+ let y = x * 2;
+
+ if true {
+ return y;
+ }
+
+ 0?
+}
+"#,
+ config.clone(),
+ );
+
+ check_with_config(
+ r#"
+fn foo() ->$0 i32 {
+ let x = 5;
+ let y = x * 2;
+
+ if true {
+ return y;
+// ^^^^^^
+ }
+
+ 0?
+// ^
+"#,
+ config,
+ );
+ }
+
+ #[test]
+ fn test_hl_disabled_break() {
+ let config = HighlightRelatedConfig {
+ references: true,
+ break_points: false,
+ exit_points: true,
+ yield_points: true,
+ };
+
+ check_with_config(
+ r#"
+fn foo() {
+ loop {
+ break$0;
+ }
+}
+"#,
+ config,
+ );
+ }
+
+ #[test]
+ fn test_hl_disabled_yield() {
+ let config = HighlightRelatedConfig {
+ references: true,
+ break_points: true,
+ exit_points: true,
+ yield_points: false,
+ };
+
+ check_with_config(
+ r#"
+async$0 fn foo() {
+ 0.await;
+}
+"#,
+ config,
+ );
+ }
+
+ #[test]
+ fn test_hl_disabled_exit() {
+ let config = HighlightRelatedConfig {
+ references: true,
+ break_points: true,
+ exit_points: false,
+ yield_points: true,
+ };
+
+ check_with_config(
+ r#"
+fn foo() ->$0 i32 {
+ if true {
+ return -1;
+ }
+
+ 42
+}"#,
+ config,
+ );
+ }
+
+ #[test]
+ fn test_hl_multi_local() {
+ check(
+ r#"
+fn foo((
+ foo$0
+ //^^^
+ | foo
+ //^^^
+ | foo
+ //^^^
+): ()) {
+ foo;
+ //^^^read
+ let foo;
+}
+"#,
+ );
+ check(
+ r#"
+fn foo((
+ foo
+ //^^^
+ | foo$0
+ //^^^
+ | foo
+ //^^^
+): ()) {
+ foo;
+ //^^^read
+ let foo;
+}
+"#,
+ );
+ check(
+ r#"
+fn foo((
+ foo
+ //^^^
+ | foo
+ //^^^
+ | foo
+ //^^^
+): ()) {
+ foo$0;
+ //^^^read
+ let foo;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_trait_impl_methods() {
+ check(
+ r#"
+trait Trait {
+ fn func$0(self) {}
+ //^^^^
+}
+
+impl Trait for () {
+ fn func(self) {}
+ //^^^^
+}
+
+fn main() {
+ <()>::func(());
+ //^^^^
+ ().func();
+ //^^^^
+}
+"#,
+ );
+ check(
+ r#"
+trait Trait {
+ fn func(self) {}
+ //^^^^
+}
+
+impl Trait for () {
+ fn func$0(self) {}
+ //^^^^
+}
+
+fn main() {
+ <()>::func(());
+ //^^^^
+ ().func();
+ //^^^^
+}
+"#,
+ );
+ check(
+ r#"
+trait Trait {
+ fn func(self) {}
+ //^^^^
+}
+
+impl Trait for () {
+ fn func(self) {}
+ //^^^^
+}
+
+fn main() {
+ <()>::func(());
+ //^^^^
+ ().func$0();
+ //^^^^
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/hover.rs b/src/tools/rust-analyzer/crates/ide/src/hover.rs
new file mode 100644
index 000000000..59c97f2dc
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/hover.rs
@@ -0,0 +1,390 @@
+mod render;
+
+#[cfg(test)]
+mod tests;
+
+use std::iter;
+
+use either::Either;
+use hir::{HasSource, Semantics};
+use ide_db::{
+ base_db::FileRange,
+ defs::{Definition, IdentClass},
+ famous_defs::FamousDefs,
+ helpers::pick_best_token,
+ FxIndexSet, RootDatabase,
+};
+use itertools::Itertools;
+use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, T};
+
+use crate::{
+ doc_links::token_as_doc_comment,
+ markup::Markup,
+ runnables::{runnable_fn, runnable_mod},
+ FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, TryToNav,
+};
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct HoverConfig {
+ pub links_in_hover: bool,
+ pub documentation: Option<HoverDocFormat>,
+}
+
+impl HoverConfig {
+ fn markdown(&self) -> bool {
+ matches!(self.documentation, Some(HoverDocFormat::Markdown))
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum HoverDocFormat {
+ Markdown,
+ PlainText,
+}
+
+#[derive(Debug, Clone)]
+pub enum HoverAction {
+ Runnable(Runnable),
+ Implementation(FilePosition),
+ Reference(FilePosition),
+ GoToType(Vec<HoverGotoTypeData>),
+}
+
+impl HoverAction {
+ fn goto_type_from_targets(db: &RootDatabase, targets: Vec<hir::ModuleDef>) -> Self {
+ let targets = targets
+ .into_iter()
+ .filter_map(|it| {
+ Some(HoverGotoTypeData {
+ mod_path: render::path(
+ db,
+ it.module(db)?,
+ it.name(db).map(|name| name.to_string()),
+ ),
+ nav: it.try_to_nav(db)?,
+ })
+ })
+ .collect();
+ HoverAction::GoToType(targets)
+ }
+}
+
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub struct HoverGotoTypeData {
+ pub mod_path: String,
+ pub nav: NavigationTarget,
+}
+
+/// Contains the results when hovering over an item
+#[derive(Debug, Default)]
+pub struct HoverResult {
+ pub markup: Markup,
+ pub actions: Vec<HoverAction>,
+}
+
+// Feature: Hover
+//
+// Shows additional information, like the type of an expression or the documentation for a definition when "focusing" code.
+// Focusing is usually hovering with a mouse, but can also be triggered with a shortcut.
+//
+// image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif[]
+pub(crate) fn hover(
+ db: &RootDatabase,
+ FileRange { file_id, range }: FileRange,
+ config: &HoverConfig,
+) -> Option<RangeInfo<HoverResult>> {
+ let sema = &hir::Semantics::new(db);
+ let file = sema.parse(file_id).syntax().clone();
+
+ if !range.is_empty() {
+ return hover_ranged(&file, range, sema, config);
+ }
+ let offset = range.start();
+
+ let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
+ IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] | T![Self] => 3,
+ T!['('] | T![')'] => 2,
+ kind if kind.is_trivia() => 0,
+ _ => 1,
+ })?;
+
+ if let Some(doc_comment) = token_as_doc_comment(&original_token) {
+ cov_mark::hit!(no_highlight_on_comment_hover);
+ return doc_comment.get_definition_with_descend_at(sema, offset, |def, node, range| {
+ let res = hover_for_definition(sema, file_id, def, &node, config)?;
+ Some(RangeInfo::new(range, res))
+ });
+ }
+
+ let in_attr = matches!(original_token.parent().and_then(ast::TokenTree::cast), Some(tt) if tt.syntax().ancestors().any(|it| ast::Meta::can_cast(it.kind())));
+ let descended = if in_attr {
+ [sema.descend_into_macros_with_kind_preference(original_token.clone())].into()
+ } else {
+ sema.descend_into_macros_with_same_text(original_token.clone())
+ };
+
+ // FIXME: Definition should include known lints and the like instead of having this special case here
+ let hovered_lint = descended.iter().find_map(|token| {
+ let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
+ render::try_for_lint(&attr, token)
+ });
+ if let Some(res) = hovered_lint {
+ return Some(RangeInfo::new(original_token.text_range(), res));
+ }
+
+ let result = descended
+ .iter()
+ .filter_map(|token| {
+ let node = token.parent()?;
+ let class = IdentClass::classify_token(sema, token)?;
+ Some(class.definitions().into_iter().zip(iter::once(node).cycle()))
+ })
+ .flatten()
+ .unique_by(|&(def, _)| def)
+ .filter_map(|(def, node)| hover_for_definition(sema, file_id, def, &node, config))
+ .reduce(|mut acc: HoverResult, HoverResult { markup, actions }| {
+ acc.actions.extend(actions);
+ acc.markup = Markup::from(format!("{}\n---\n{}", acc.markup, markup));
+ acc
+ });
+
+ if result.is_none() {
+ // fallbacks, show keywords or types
+
+ let res = descended.iter().find_map(|token| render::keyword(sema, config, token));
+ if let Some(res) = res {
+ return Some(RangeInfo::new(original_token.text_range(), res));
+ }
+ let res = descended
+ .iter()
+ .find_map(|token| hover_type_fallback(sema, config, token, &original_token));
+ if let Some(_) = res {
+ return res;
+ }
+ }
+ result.map(|mut res: HoverResult| {
+ res.actions = dedupe_or_merge_hover_actions(res.actions);
+ RangeInfo::new(original_token.text_range(), res)
+ })
+}
+
+pub(crate) fn hover_for_definition(
+ sema: &Semantics<'_, RootDatabase>,
+ file_id: FileId,
+ definition: Definition,
+ node: &SyntaxNode,
+ config: &HoverConfig,
+) -> Option<HoverResult> {
+ let famous_defs = match &definition {
+ Definition::BuiltinType(_) => Some(FamousDefs(sema, sema.scope(node)?.krate())),
+ _ => None,
+ };
+ render::definition(sema.db, definition, famous_defs.as_ref(), config).map(|markup| {
+ HoverResult {
+ markup: render::process_markup(sema.db, definition, &markup, config),
+ actions: show_implementations_action(sema.db, definition)
+ .into_iter()
+ .chain(show_fn_references_action(sema.db, definition))
+ .chain(runnable_action(sema, definition, file_id))
+ .chain(goto_type_action_for_def(sema.db, definition))
+ .collect(),
+ }
+ })
+}
+
+fn hover_ranged(
+ file: &SyntaxNode,
+ range: syntax::TextRange,
+ sema: &Semantics<'_, RootDatabase>,
+ config: &HoverConfig,
+) -> Option<RangeInfo<HoverResult>> {
+ // FIXME: make this work in attributes
+ let expr_or_pat = file.covering_element(range).ancestors().find_map(|it| {
+ match_ast! {
+ match it {
+ ast::Expr(expr) => Some(Either::Left(expr)),
+ ast::Pat(pat) => Some(Either::Right(pat)),
+ _ => None,
+ }
+ }
+ })?;
+ let res = match &expr_or_pat {
+ Either::Left(ast::Expr::TryExpr(try_expr)) => render::try_expr(sema, config, try_expr),
+ Either::Left(ast::Expr::PrefixExpr(prefix_expr))
+ if prefix_expr.op_kind() == Some(ast::UnaryOp::Deref) =>
+ {
+ render::deref_expr(sema, config, prefix_expr)
+ }
+ _ => None,
+ };
+ let res = res.or_else(|| render::type_info(sema, config, &expr_or_pat));
+ res.map(|it| {
+ let range = match expr_or_pat {
+ Either::Left(it) => it.syntax().text_range(),
+ Either::Right(it) => it.syntax().text_range(),
+ };
+ RangeInfo::new(range, it)
+ })
+}
+
+fn hover_type_fallback(
+ sema: &Semantics<'_, RootDatabase>,
+ config: &HoverConfig,
+ token: &SyntaxToken,
+ original_token: &SyntaxToken,
+) -> Option<RangeInfo<HoverResult>> {
+ let node = token
+ .parent_ancestors()
+ .take_while(|it| !ast::Item::can_cast(it.kind()))
+ .find(|n| ast::Expr::can_cast(n.kind()) || ast::Pat::can_cast(n.kind()))?;
+
+ let expr_or_pat = match_ast! {
+ match node {
+ ast::Expr(it) => Either::Left(it),
+ ast::Pat(it) => Either::Right(it),
+ // If this node is a MACRO_CALL, it means that `descend_into_macros_many` failed to resolve.
+ // (e.g expanding a builtin macro). So we give up here.
+ ast::MacroCall(_it) => return None,
+ _ => return None,
+ }
+ };
+
+ let res = render::type_info(sema, config, &expr_or_pat)?;
+ let range = sema
+ .original_range_opt(&node)
+ .map(|frange| frange.range)
+ .unwrap_or_else(|| original_token.text_range());
+ Some(RangeInfo::new(range, res))
+}
+
+fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
+ fn to_action(nav_target: NavigationTarget) -> HoverAction {
+ HoverAction::Implementation(FilePosition {
+ file_id: nav_target.file_id,
+ offset: nav_target.focus_or_full_range().start(),
+ })
+ }
+
+ let adt = match def {
+ Definition::Trait(it) => return it.try_to_nav(db).map(to_action),
+ Definition::Adt(it) => Some(it),
+ Definition::SelfType(it) => it.self_ty(db).as_adt(),
+ _ => None,
+ }?;
+ adt.try_to_nav(db).map(to_action)
+}
+
+fn show_fn_references_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
+ match def {
+ Definition::Function(it) => it.try_to_nav(db).map(|nav_target| {
+ HoverAction::Reference(FilePosition {
+ file_id: nav_target.file_id,
+ offset: nav_target.focus_or_full_range().start(),
+ })
+ }),
+ _ => None,
+ }
+}
+
+fn runnable_action(
+ sema: &hir::Semantics<'_, RootDatabase>,
+ def: Definition,
+ file_id: FileId,
+) -> Option<HoverAction> {
+ match def {
+ Definition::Module(it) => runnable_mod(sema, it).map(HoverAction::Runnable),
+ Definition::Function(func) => {
+ let src = func.source(sema.db)?;
+ if src.file_id != file_id.into() {
+ cov_mark::hit!(hover_macro_generated_struct_fn_doc_comment);
+ cov_mark::hit!(hover_macro_generated_struct_fn_doc_attr);
+ return None;
+ }
+
+ runnable_fn(sema, func).map(HoverAction::Runnable)
+ }
+ _ => None,
+ }
+}
+
+fn goto_type_action_for_def(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
+ let mut targets: Vec<hir::ModuleDef> = Vec::new();
+ let mut push_new_def = |item: hir::ModuleDef| {
+ if !targets.contains(&item) {
+ targets.push(item);
+ }
+ };
+
+ if let Definition::GenericParam(hir::GenericParam::TypeParam(it)) = def {
+ it.trait_bounds(db).into_iter().for_each(|it| push_new_def(it.into()));
+ } else {
+ let ty = match def {
+ Definition::Local(it) => it.ty(db),
+ Definition::GenericParam(hir::GenericParam::ConstParam(it)) => it.ty(db),
+ Definition::Field(field) => field.ty(db),
+ Definition::Function(function) => function.ret_type(db),
+ _ => return None,
+ };
+
+ walk_and_push_ty(db, &ty, &mut push_new_def);
+ }
+
+ Some(HoverAction::goto_type_from_targets(db, targets))
+}
+
+fn walk_and_push_ty(
+ db: &RootDatabase,
+ ty: &hir::Type,
+ push_new_def: &mut dyn FnMut(hir::ModuleDef),
+) {
+ ty.walk(db, |t| {
+ if let Some(adt) = t.as_adt() {
+ push_new_def(adt.into());
+ } else if let Some(trait_) = t.as_dyn_trait() {
+ push_new_def(trait_.into());
+ } else if let Some(traits) = t.as_impl_traits(db) {
+ traits.for_each(|it| push_new_def(it.into()));
+ } else if let Some(trait_) = t.as_associated_type_parent_trait(db) {
+ push_new_def(trait_.into());
+ }
+ });
+}
+
+fn dedupe_or_merge_hover_actions(actions: Vec<HoverAction>) -> Vec<HoverAction> {
+ let mut deduped_actions = Vec::with_capacity(actions.len());
+ let mut go_to_type_targets = FxIndexSet::default();
+
+ let mut seen_implementation = false;
+ let mut seen_reference = false;
+ let mut seen_runnable = false;
+ for action in actions {
+ match action {
+ HoverAction::GoToType(targets) => {
+ go_to_type_targets.extend(targets);
+ }
+ HoverAction::Implementation(..) => {
+ if !seen_implementation {
+ seen_implementation = true;
+ deduped_actions.push(action);
+ }
+ }
+ HoverAction::Reference(..) => {
+ if !seen_reference {
+ seen_reference = true;
+ deduped_actions.push(action);
+ }
+ }
+ HoverAction::Runnable(..) => {
+ if !seen_runnable {
+ seen_runnable = true;
+ deduped_actions.push(action);
+ }
+ }
+ };
+ }
+
+ if !go_to_type_targets.is_empty() {
+ deduped_actions.push(HoverAction::GoToType(go_to_type_targets.into_iter().collect()));
+ }
+
+ deduped_actions
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs
new file mode 100644
index 000000000..6c50a4e6a
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs
@@ -0,0 +1,563 @@
+//! Logic for rendering the different hover messages
+use std::fmt::Display;
+
+use either::Either;
+use hir::{AsAssocItem, AttributeTemplate, HasAttrs, HirDisplay, Semantics, TypeInfo};
+use ide_db::{
+ base_db::SourceDatabase,
+ defs::Definition,
+ famous_defs::FamousDefs,
+ generated::lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES},
+ RootDatabase,
+};
+use itertools::Itertools;
+use stdx::format_to;
+use syntax::{
+ algo, ast, match_ast, AstNode, Direction,
+ SyntaxKind::{LET_EXPR, LET_STMT},
+ SyntaxToken, T,
+};
+
+use crate::{
+ doc_links::{remove_links, rewrite_links},
+ hover::walk_and_push_ty,
+ markdown_remove::remove_markdown,
+ HoverAction, HoverConfig, HoverResult, Markup,
+};
+
+pub(super) fn type_info(
+ sema: &Semantics<'_, RootDatabase>,
+ config: &HoverConfig,
+ expr_or_pat: &Either<ast::Expr, ast::Pat>,
+) -> Option<HoverResult> {
+ let TypeInfo { original, adjusted } = match expr_or_pat {
+ Either::Left(expr) => sema.type_of_expr(expr)?,
+ Either::Right(pat) => sema.type_of_pat(pat)?,
+ };
+
+ let mut res = HoverResult::default();
+ let mut targets: Vec<hir::ModuleDef> = Vec::new();
+ let mut push_new_def = |item: hir::ModuleDef| {
+ if !targets.contains(&item) {
+ targets.push(item);
+ }
+ };
+ walk_and_push_ty(sema.db, &original, &mut push_new_def);
+
+ res.markup = if let Some(adjusted_ty) = adjusted {
+ walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
+ let original = original.display(sema.db).to_string();
+ let adjusted = adjusted_ty.display(sema.db).to_string();
+ let static_text_diff_len = "Coerced to: ".len() - "Type: ".len();
+ format!(
+ "{bt_start}Type: {:>apad$}\nCoerced to: {:>opad$}\n{bt_end}",
+ original,
+ adjusted,
+ apad = static_text_diff_len + adjusted.len().max(original.len()),
+ opad = original.len(),
+ bt_start = if config.markdown() { "```text\n" } else { "" },
+ bt_end = if config.markdown() { "```\n" } else { "" }
+ )
+ .into()
+ } else {
+ if config.markdown() {
+ Markup::fenced_block(&original.display(sema.db))
+ } else {
+ original.display(sema.db).to_string().into()
+ }
+ };
+ res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
+ Some(res)
+}
+
+pub(super) fn try_expr(
+ sema: &Semantics<'_, RootDatabase>,
+ config: &HoverConfig,
+ try_expr: &ast::TryExpr,
+) -> Option<HoverResult> {
+ let inner_ty = sema.type_of_expr(&try_expr.expr()?)?.original;
+ let mut ancestors = try_expr.syntax().ancestors();
+ let mut body_ty = loop {
+ let next = ancestors.next()?;
+ break match_ast! {
+ match next {
+ ast::Fn(fn_) => sema.to_def(&fn_)?.ret_type(sema.db),
+ ast::Item(__) => return None,
+ ast::ClosureExpr(closure) => sema.type_of_expr(&closure.body()?)?.original,
+ ast::BlockExpr(block_expr) => if matches!(block_expr.modifier(), Some(ast::BlockModifier::Async(_) | ast::BlockModifier::Try(_)| ast::BlockModifier::Const(_))) {
+ sema.type_of_expr(&block_expr.into())?.original
+ } else {
+ continue;
+ },
+ _ => continue,
+ }
+ };
+ };
+
+ if inner_ty == body_ty {
+ return None;
+ }
+
+ let mut inner_ty = inner_ty;
+ let mut s = "Try Target".to_owned();
+
+ let adts = inner_ty.as_adt().zip(body_ty.as_adt());
+ if let Some((hir::Adt::Enum(inner), hir::Adt::Enum(body))) = adts {
+ let famous_defs = FamousDefs(sema, sema.scope(try_expr.syntax())?.krate());
+ // special case for two options, there is no value in showing them
+ if let Some(option_enum) = famous_defs.core_option_Option() {
+ if inner == option_enum && body == option_enum {
+ cov_mark::hit!(hover_try_expr_opt_opt);
+ return None;
+ }
+ }
+
+ // special case two results to show the error variants only
+ if let Some(result_enum) = famous_defs.core_result_Result() {
+ if inner == result_enum && body == result_enum {
+ let error_type_args =
+ inner_ty.type_arguments().nth(1).zip(body_ty.type_arguments().nth(1));
+ if let Some((inner, body)) = error_type_args {
+ inner_ty = inner;
+ body_ty = body;
+ s = "Try Error".to_owned();
+ }
+ }
+ }
+ }
+
+ let mut res = HoverResult::default();
+
+ let mut targets: Vec<hir::ModuleDef> = Vec::new();
+ let mut push_new_def = |item: hir::ModuleDef| {
+ if !targets.contains(&item) {
+ targets.push(item);
+ }
+ };
+ walk_and_push_ty(sema.db, &inner_ty, &mut push_new_def);
+ walk_and_push_ty(sema.db, &body_ty, &mut push_new_def);
+ res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
+
+ let inner_ty = inner_ty.display(sema.db).to_string();
+ let body_ty = body_ty.display(sema.db).to_string();
+ let ty_len_max = inner_ty.len().max(body_ty.len());
+
+ let l = "Propagated as: ".len() - " Type: ".len();
+ let static_text_len_diff = l as isize - s.len() as isize;
+ let tpad = static_text_len_diff.max(0) as usize;
+ let ppad = static_text_len_diff.min(0).abs() as usize;
+
+ res.markup = format!(
+ "{bt_start}{} Type: {:>pad0$}\nPropagated as: {:>pad1$}\n{bt_end}",
+ s,
+ inner_ty,
+ body_ty,
+ pad0 = ty_len_max + tpad,
+ pad1 = ty_len_max + ppad,
+ bt_start = if config.markdown() { "```text\n" } else { "" },
+ bt_end = if config.markdown() { "```\n" } else { "" }
+ )
+ .into();
+ Some(res)
+}
+
+pub(super) fn deref_expr(
+ sema: &Semantics<'_, RootDatabase>,
+ config: &HoverConfig,
+ deref_expr: &ast::PrefixExpr,
+) -> Option<HoverResult> {
+ let inner_ty = sema.type_of_expr(&deref_expr.expr()?)?.original;
+ let TypeInfo { original, adjusted } =
+ sema.type_of_expr(&ast::Expr::from(deref_expr.clone()))?;
+
+ let mut res = HoverResult::default();
+ let mut targets: Vec<hir::ModuleDef> = Vec::new();
+ let mut push_new_def = |item: hir::ModuleDef| {
+ if !targets.contains(&item) {
+ targets.push(item);
+ }
+ };
+ walk_and_push_ty(sema.db, &inner_ty, &mut push_new_def);
+ walk_and_push_ty(sema.db, &original, &mut push_new_def);
+
+ res.markup = if let Some(adjusted_ty) = adjusted {
+ walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
+ let original = original.display(sema.db).to_string();
+ let adjusted = adjusted_ty.display(sema.db).to_string();
+ let inner = inner_ty.display(sema.db).to_string();
+ let type_len = "To type: ".len();
+ let coerced_len = "Coerced to: ".len();
+ let deref_len = "Dereferenced from: ".len();
+ let max_len = (original.len() + type_len)
+ .max(adjusted.len() + coerced_len)
+ .max(inner.len() + deref_len);
+ format!(
+ "{bt_start}Dereferenced from: {:>ipad$}\nTo type: {:>apad$}\nCoerced to: {:>opad$}\n{bt_end}",
+ inner,
+ original,
+ adjusted,
+ ipad = max_len - deref_len,
+ apad = max_len - type_len,
+ opad = max_len - coerced_len,
+ bt_start = if config.markdown() { "```text\n" } else { "" },
+ bt_end = if config.markdown() { "```\n" } else { "" }
+ )
+ .into()
+ } else {
+ let original = original.display(sema.db).to_string();
+ let inner = inner_ty.display(sema.db).to_string();
+ let type_len = "To type: ".len();
+ let deref_len = "Dereferenced from: ".len();
+ let max_len = (original.len() + type_len).max(inner.len() + deref_len);
+ format!(
+ "{bt_start}Dereferenced from: {:>ipad$}\nTo type: {:>apad$}\n{bt_end}",
+ inner,
+ original,
+ ipad = max_len - deref_len,
+ apad = max_len - type_len,
+ bt_start = if config.markdown() { "```text\n" } else { "" },
+ bt_end = if config.markdown() { "```\n" } else { "" }
+ )
+ .into()
+ };
+ res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
+
+ Some(res)
+}
+
+pub(super) fn keyword(
+ sema: &Semantics<'_, RootDatabase>,
+ config: &HoverConfig,
+ token: &SyntaxToken,
+) -> Option<HoverResult> {
+ if !token.kind().is_keyword() || !config.documentation.is_some() {
+ return None;
+ }
+ let parent = token.parent()?;
+ let famous_defs = FamousDefs(sema, sema.scope(&parent)?.krate());
+
+ let KeywordHint { description, keyword_mod, actions } = keyword_hints(sema, token, parent);
+
+ let doc_owner = find_std_module(&famous_defs, &keyword_mod)?;
+ let docs = doc_owner.attrs(sema.db).docs()?;
+ let markup = process_markup(
+ sema.db,
+ Definition::Module(doc_owner),
+ &markup(Some(docs.into()), description, None)?,
+ config,
+ );
+ Some(HoverResult { markup, actions })
+}
+
+pub(super) fn try_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option<HoverResult> {
+ let (path, tt) = attr.as_simple_call()?;
+ if !tt.syntax().text_range().contains(token.text_range().start()) {
+ return None;
+ }
+ let (is_clippy, lints) = match &*path {
+ "feature" => (false, FEATURES),
+ "allow" | "deny" | "forbid" | "warn" => {
+ let is_clippy = algo::non_trivia_sibling(token.clone().into(), Direction::Prev)
+ .filter(|t| t.kind() == T![:])
+ .and_then(|t| algo::non_trivia_sibling(t, Direction::Prev))
+ .filter(|t| t.kind() == T![:])
+ .and_then(|t| algo::non_trivia_sibling(t, Direction::Prev))
+ .map_or(false, |t| {
+ t.kind() == T![ident] && t.into_token().map_or(false, |t| t.text() == "clippy")
+ });
+ if is_clippy {
+ (true, CLIPPY_LINTS)
+ } else {
+ (false, DEFAULT_LINTS)
+ }
+ }
+ _ => return None,
+ };
+
+ let tmp;
+ let needle = if is_clippy {
+ tmp = format!("clippy::{}", token.text());
+ &tmp
+ } else {
+ &*token.text()
+ };
+
+ let lint =
+ lints.binary_search_by_key(&needle, |lint| lint.label).ok().map(|idx| &lints[idx])?;
+ Some(HoverResult {
+ markup: Markup::from(format!("```\n{}\n```\n___\n\n{}", lint.label, lint.description)),
+ ..Default::default()
+ })
+}
+
+pub(super) fn process_markup(
+ db: &RootDatabase,
+ def: Definition,
+ markup: &Markup,
+ config: &HoverConfig,
+) -> Markup {
+ let markup = markup.as_str();
+ let markup = if !config.markdown() {
+ remove_markdown(markup)
+ } else if config.links_in_hover {
+ rewrite_links(db, markup, def)
+ } else {
+ remove_links(markup)
+ };
+ Markup::from(markup)
+}
+
+fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String> {
+ match def {
+ Definition::Field(f) => Some(f.parent_def(db).name(db)),
+ Definition::Local(l) => l.parent(db).name(db),
+ Definition::Function(f) => match f.as_assoc_item(db)?.container(db) {
+ hir::AssocItemContainer::Trait(t) => Some(t.name(db)),
+ hir::AssocItemContainer::Impl(i) => i.self_ty(db).as_adt().map(|adt| adt.name(db)),
+ },
+ Definition::Variant(e) => Some(e.parent_enum(db).name(db)),
+ _ => None,
+ }
+ .map(|name| name.to_string())
+}
+
+pub(super) fn path(db: &RootDatabase, module: hir::Module, item_name: Option<String>) -> String {
+ let crate_name =
+ db.crate_graph()[module.krate().into()].display_name.as_ref().map(|it| it.to_string());
+ let module_path = module
+ .path_to_root(db)
+ .into_iter()
+ .rev()
+ .flat_map(|it| it.name(db).map(|name| name.to_string()));
+ crate_name.into_iter().chain(module_path).chain(item_name).join("::")
+}
+
+pub(super) fn definition(
+ db: &RootDatabase,
+ def: Definition,
+ famous_defs: Option<&FamousDefs<'_, '_>>,
+ config: &HoverConfig,
+) -> Option<Markup> {
+ let mod_path = definition_mod_path(db, &def);
+ let (label, docs) = match def {
+ Definition::Macro(it) => label_and_docs(db, it),
+ Definition::Field(it) => label_and_docs(db, it),
+ Definition::Module(it) => label_and_docs(db, it),
+ Definition::Function(it) => label_and_docs(db, it),
+ Definition::Adt(it) => label_and_docs(db, it),
+ Definition::Variant(it) => label_and_docs(db, it),
+ Definition::Const(it) => label_value_and_docs(db, it, |it| {
+ let body = it.eval(db);
+ match body {
+ Ok(x) => Some(format!("{}", x)),
+ Err(_) => it.value(db).map(|x| format!("{}", x)),
+ }
+ }),
+ Definition::Static(it) => label_value_and_docs(db, it, |it| it.value(db)),
+ Definition::Trait(it) => label_and_docs(db, it),
+ Definition::TypeAlias(it) => label_and_docs(db, it),
+ Definition::BuiltinType(it) => {
+ return famous_defs
+ .and_then(|fd| builtin(fd, it))
+ .or_else(|| Some(Markup::fenced_block(&it.name())))
+ }
+ Definition::Local(it) => return local(db, it),
+ Definition::SelfType(impl_def) => {
+ impl_def.self_ty(db).as_adt().map(|adt| label_and_docs(db, adt))?
+ }
+ Definition::GenericParam(it) => label_and_docs(db, it),
+ Definition::Label(it) => return Some(Markup::fenced_block(&it.name(db))),
+ // FIXME: We should be able to show more info about these
+ Definition::BuiltinAttr(it) => return render_builtin_attr(db, it),
+ Definition::ToolModule(it) => return Some(Markup::fenced_block(&it.name(db))),
+ Definition::DeriveHelper(it) => (format!("derive_helper {}", it.name(db)), None),
+ };
+
+ let docs = match config.documentation {
+ Some(_) => docs.or_else(|| {
+ // docs are missing, for assoc items of trait impls try to fall back to the docs of the
+ // original item of the trait
+ let assoc = def.as_assoc_item(db)?;
+ let trait_ = assoc.containing_trait_impl(db)?;
+ let name = Some(assoc.name(db)?);
+ let item = trait_.items(db).into_iter().find(|it| it.name(db) == name)?;
+ item.docs(db)
+ }),
+ None => None,
+ };
+ let docs = docs.filter(|_| config.documentation.is_some()).map(Into::into);
+ markup(docs, label, mod_path)
+}
+
+fn render_builtin_attr(db: &RootDatabase, attr: hir::BuiltinAttr) -> Option<Markup> {
+ let name = attr.name(db);
+ let desc = format!("#[{}]", name);
+
+ let AttributeTemplate { word, list, name_value_str } = match attr.template(db) {
+ Some(template) => template,
+ None => return Some(Markup::fenced_block(&attr.name(db))),
+ };
+ let mut docs = "Valid forms are:".to_owned();
+ if word {
+ format_to!(docs, "\n - #\\[{}]", name);
+ }
+ if let Some(list) = list {
+ format_to!(docs, "\n - #\\[{}({})]", name, list);
+ }
+ if let Some(name_value_str) = name_value_str {
+ format_to!(docs, "\n - #\\[{} = {}]", name, name_value_str);
+ }
+ markup(Some(docs.replace('*', "\\*")), desc, None)
+}
+
+fn label_and_docs<D>(db: &RootDatabase, def: D) -> (String, Option<hir::Documentation>)
+where
+ D: HasAttrs + HirDisplay,
+{
+ let label = def.display(db).to_string();
+ let docs = def.attrs(db).docs();
+ (label, docs)
+}
+
+fn label_value_and_docs<D, E, V>(
+ db: &RootDatabase,
+ def: D,
+ value_extractor: E,
+) -> (String, Option<hir::Documentation>)
+where
+ D: HasAttrs + HirDisplay,
+ E: Fn(&D) -> Option<V>,
+ V: Display,
+{
+ let label = if let Some(value) = value_extractor(&def) {
+ format!("{} = {}", def.display(db), value)
+ } else {
+ def.display(db).to_string()
+ };
+ let docs = def.attrs(db).docs();
+ (label, docs)
+}
+
+fn definition_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> {
+ if let Definition::GenericParam(_) = def {
+ return None;
+ }
+ def.module(db).map(|module| path(db, module, definition_owner_name(db, def)))
+}
+
+fn markup(docs: Option<String>, desc: String, mod_path: Option<String>) -> Option<Markup> {
+ let mut buf = String::new();
+
+ if let Some(mod_path) = mod_path {
+ if !mod_path.is_empty() {
+ format_to!(buf, "```rust\n{}\n```\n\n", mod_path);
+ }
+ }
+ format_to!(buf, "```rust\n{}\n```", desc);
+
+ if let Some(doc) = docs {
+ format_to!(buf, "\n___\n\n{}", doc);
+ }
+ Some(buf.into())
+}
+
+fn builtin(famous_defs: &FamousDefs<'_, '_>, builtin: hir::BuiltinType) -> Option<Markup> {
+ // std exposes prim_{} modules with docstrings on the root to document the builtins
+ let primitive_mod = format!("prim_{}", builtin.name());
+ let doc_owner = find_std_module(famous_defs, &primitive_mod)?;
+ let docs = doc_owner.attrs(famous_defs.0.db).docs()?;
+ markup(Some(docs.into()), builtin.name().to_string(), None)
+}
+
+fn find_std_module(famous_defs: &FamousDefs<'_, '_>, name: &str) -> Option<hir::Module> {
+ let db = famous_defs.0.db;
+ let std_crate = famous_defs.std()?;
+ let std_root_module = std_crate.root_module(db);
+ std_root_module
+ .children(db)
+ .find(|module| module.name(db).map_or(false, |module| module.to_string() == name))
+}
+
+fn local(db: &RootDatabase, it: hir::Local) -> Option<Markup> {
+ let ty = it.ty(db);
+ let ty = ty.display_truncated(db, None);
+ let is_mut = if it.is_mut(db) { "mut " } else { "" };
+ let desc = match it.source(db).value {
+ Either::Left(ident) => {
+ let name = it.name(db);
+ let let_kw = if ident
+ .syntax()
+ .parent()
+ .map_or(false, |p| p.kind() == LET_STMT || p.kind() == LET_EXPR)
+ {
+ "let "
+ } else {
+ ""
+ };
+ format!("{}{}{}: {}", let_kw, is_mut, name, ty)
+ }
+ Either::Right(_) => format!("{}self: {}", is_mut, ty),
+ };
+ markup(None, desc, None)
+}
+
+struct KeywordHint {
+ description: String,
+ keyword_mod: String,
+ actions: Vec<HoverAction>,
+}
+
+impl KeywordHint {
+ fn new(description: String, keyword_mod: String) -> Self {
+ Self { description, keyword_mod, actions: Vec::default() }
+ }
+}
+
+fn keyword_hints(
+ sema: &Semantics<'_, RootDatabase>,
+ token: &SyntaxToken,
+ parent: syntax::SyntaxNode,
+) -> KeywordHint {
+ match token.kind() {
+ T![await] | T![loop] | T![match] | T![unsafe] | T![as] | T![try] | T![if] | T![else] => {
+ let keyword_mod = format!("{}_keyword", token.text());
+
+ match ast::Expr::cast(parent).and_then(|site| sema.type_of_expr(&site)) {
+ // ignore the unit type ()
+ Some(ty) if !ty.adjusted.as_ref().unwrap_or(&ty.original).is_unit() => {
+ let mut targets: Vec<hir::ModuleDef> = Vec::new();
+ let mut push_new_def = |item: hir::ModuleDef| {
+ if !targets.contains(&item) {
+ targets.push(item);
+ }
+ };
+ walk_and_push_ty(sema.db, &ty.original, &mut push_new_def);
+
+ let ty = ty.adjusted();
+ let description = format!("{}: {}", token.text(), ty.display(sema.db));
+
+ KeywordHint {
+ description,
+ keyword_mod,
+ actions: vec![HoverAction::goto_type_from_targets(sema.db, targets)],
+ }
+ }
+ _ => KeywordHint {
+ description: token.text().to_string(),
+ keyword_mod,
+ actions: Vec::new(),
+ },
+ }
+ }
+ T![fn] => {
+ let module = match ast::FnPtrType::cast(parent) {
+ // treat fn keyword inside function pointer type as primitive
+ Some(_) => format!("prim_{}", token.text()),
+ None => format!("{}_keyword", token.text()),
+ };
+ KeywordHint::new(token.text().to_string(), module)
+ }
+ T![Self] => KeywordHint::new(token.text().to_string(), "self_upper_keyword".into()),
+ _ => KeywordHint::new(token.text().to_string(), format!("{}_keyword", token.text())),
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs
new file mode 100644
index 000000000..867d1f54d
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs
@@ -0,0 +1,5053 @@
+use expect_test::{expect, Expect};
+use ide_db::base_db::{FileLoader, FileRange};
+use syntax::TextRange;
+
+use crate::{fixture, hover::HoverDocFormat, HoverConfig};
+
+fn check_hover_no_result(ra_fixture: &str) {
+ let (analysis, position) = fixture::position(ra_fixture);
+ let hover = analysis
+ .hover(
+ &HoverConfig { links_in_hover: true, documentation: Some(HoverDocFormat::Markdown) },
+ FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
+ )
+ .unwrap();
+ assert!(hover.is_none(), "hover not expected but found: {:?}", hover.unwrap());
+}
+
+#[track_caller]
+fn check(ra_fixture: &str, expect: Expect) {
+ let (analysis, position) = fixture::position(ra_fixture);
+ let hover = analysis
+ .hover(
+ &HoverConfig { links_in_hover: true, documentation: Some(HoverDocFormat::Markdown) },
+ FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
+ )
+ .unwrap()
+ .unwrap();
+
+ let content = analysis.db.file_text(position.file_id);
+ let hovered_element = &content[hover.range];
+
+ let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup);
+ expect.assert_eq(&actual)
+}
+
+fn check_hover_no_links(ra_fixture: &str, expect: Expect) {
+ let (analysis, position) = fixture::position(ra_fixture);
+ let hover = analysis
+ .hover(
+ &HoverConfig { links_in_hover: false, documentation: Some(HoverDocFormat::Markdown) },
+ FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
+ )
+ .unwrap()
+ .unwrap();
+
+ let content = analysis.db.file_text(position.file_id);
+ let hovered_element = &content[hover.range];
+
+ let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup);
+ expect.assert_eq(&actual)
+}
+
+fn check_hover_no_markdown(ra_fixture: &str, expect: Expect) {
+ let (analysis, position) = fixture::position(ra_fixture);
+ let hover = analysis
+ .hover(
+ &HoverConfig { links_in_hover: true, documentation: Some(HoverDocFormat::PlainText) },
+ FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
+ )
+ .unwrap()
+ .unwrap();
+
+ let content = analysis.db.file_text(position.file_id);
+ let hovered_element = &content[hover.range];
+
+ let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup);
+ expect.assert_eq(&actual)
+}
+
+fn check_actions(ra_fixture: &str, expect: Expect) {
+ let (analysis, file_id, position) = fixture::range_or_position(ra_fixture);
+ let hover = analysis
+ .hover(
+ &HoverConfig { links_in_hover: true, documentation: Some(HoverDocFormat::Markdown) },
+ FileRange { file_id, range: position.range_or_empty() },
+ )
+ .unwrap()
+ .unwrap();
+ expect.assert_debug_eq(&hover.info.actions)
+}
+
+fn check_hover_range(ra_fixture: &str, expect: Expect) {
+ let (analysis, range) = fixture::range(ra_fixture);
+ let hover = analysis
+ .hover(
+ &HoverConfig { links_in_hover: false, documentation: Some(HoverDocFormat::Markdown) },
+ range,
+ )
+ .unwrap()
+ .unwrap();
+ expect.assert_eq(hover.info.markup.as_str())
+}
+
+fn check_hover_range_no_results(ra_fixture: &str) {
+ let (analysis, range) = fixture::range(ra_fixture);
+ let hover = analysis
+ .hover(
+ &HoverConfig { links_in_hover: false, documentation: Some(HoverDocFormat::Markdown) },
+ range,
+ )
+ .unwrap();
+ assert!(hover.is_none());
+}
+
+#[test]
+fn hover_descend_macros_avoids_duplicates() {
+ check(
+ r#"
+macro_rules! dupe_use {
+ ($local:ident) => {
+ {
+ $local;
+ $local;
+ }
+ }
+}
+fn foo() {
+ let local = 0;
+ dupe_use!(local$0);
+}
+"#,
+ expect![[r#"
+ *local*
+
+ ```rust
+ let local: i32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_shows_all_macro_descends() {
+ check(
+ r#"
+macro_rules! m {
+ ($name:ident) => {
+ /// Outer
+ fn $name() {}
+
+ mod module {
+ /// Inner
+ fn $name() {}
+ }
+ };
+}
+
+m!(ab$0c);
+ "#,
+ expect![[r#"
+ *abc*
+
+ ```rust
+ test::module
+ ```
+
+ ```rust
+ fn abc()
+ ```
+
+ ---
+
+ Inner
+ ---
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn abc()
+ ```
+
+ ---
+
+ Outer
+ "#]],
+ );
+}
+
+#[test]
+fn hover_shows_type_of_an_expression() {
+ check(
+ r#"
+pub fn foo() -> u32 { 1 }
+
+fn main() {
+ let foo_test = foo()$0;
+}
+"#,
+ expect![[r#"
+ *foo()*
+ ```rust
+ u32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_remove_markdown_if_configured() {
+ check_hover_no_markdown(
+ r#"
+pub fn foo() -> u32 { 1 }
+
+fn main() {
+ let foo_test = foo()$0;
+}
+"#,
+ expect![[r#"
+ *foo()*
+ u32
+ "#]],
+ );
+}
+
+#[test]
+fn hover_shows_long_type_of_an_expression() {
+ check(
+ r#"
+struct Scan<A, B, C> { a: A, b: B, c: C }
+struct Iter<I> { inner: I }
+enum Option<T> { Some(T), None }
+
+struct OtherStruct<T> { i: T }
+
+fn scan<A, B, C>(a: A, b: B, c: C) -> Iter<Scan<OtherStruct<A>, B, C>> {
+ Iter { inner: Scan { a, b, c } }
+}
+
+fn main() {
+ let num: i32 = 55;
+ let closure = |memo: &mut u32, value: &u32, _another: &mut u32| -> Option<u32> {
+ Option::Some(*memo + value)
+ };
+ let number = 5u32;
+ let mut iter$0 = scan(OtherStruct { i: num }, closure, number);
+}
+"#,
+ expect![[r#"
+ *iter*
+
+ ```rust
+ let mut iter: Iter<Scan<OtherStruct<OtherStruct<i32>>, |&mut u32, &u32, &mut u32| -> Option<u32>, u32>>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_shows_fn_signature() {
+ // Single file with result
+ check(
+ r#"
+pub fn foo() -> u32 { 1 }
+
+fn main() { let foo_test = fo$0o(); }
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub fn foo() -> u32
+ ```
+ "#]],
+ );
+
+ // Multiple candidates but results are ambiguous.
+ check(
+ r#"
+//- /a.rs
+pub fn foo() -> u32 { 1 }
+
+//- /b.rs
+pub fn foo() -> &str { "" }
+
+//- /c.rs
+pub fn foo(a: u32, b: u32) {}
+
+//- /main.rs
+mod a;
+mod b;
+mod c;
+
+fn main() { let foo_test = fo$0o(); }
+ "#,
+ expect![[r#"
+ *foo*
+ ```rust
+ {unknown}
+ ```
+ "#]],
+ );
+
+ // Use literal `crate` in path
+ check(
+ r#"
+pub struct X;
+
+fn foo() -> crate::X { X }
+
+fn main() { f$0oo(); }
+ "#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn foo() -> crate::X
+ ```
+ "#]],
+ );
+
+ // Check `super` in path
+ check(
+ r#"
+pub struct X;
+
+mod m { pub fn foo() -> super::X { super::X } }
+
+fn main() { m::f$0oo(); }
+ "#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test::m
+ ```
+
+ ```rust
+ pub fn foo() -> super::X
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_omits_unnamed_where_preds() {
+ check(
+ r#"
+pub fn foo(bar: impl T) { }
+
+fn main() { fo$0o(); }
+ "#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub fn foo(bar: impl T)
+ ```
+ "#]],
+ );
+ check(
+ r#"
+pub fn foo<V: AsRef<str>>(bar: impl T, baz: V) { }
+
+fn main() { fo$0o(); }
+ "#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub fn foo<V>(bar: impl T, baz: V)
+ where
+ V: AsRef<str>,
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_shows_fn_signature_with_type_params() {
+ check(
+ r#"
+pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str { }
+
+fn main() { let foo_test = fo$0o(); }
+ "#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub fn foo<'a, T>(b: &'a T) -> &'a str
+ where
+ T: AsRef<str>,
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_shows_fn_signature_on_fn_name() {
+ check(
+ r#"
+pub fn foo$0(a: u32, b: u32) -> u32 {}
+
+fn main() { }
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub fn foo(a: u32, b: u32) -> u32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_shows_fn_doc() {
+ check(
+ r#"
+/// # Example
+/// ```
+/// # use std::path::Path;
+/// #
+/// foo(Path::new("hello, world!"))
+/// ```
+pub fn foo$0(_: &Path) {}
+
+fn main() { }
+"#,
+ expect![[r##"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub fn foo(_: &Path)
+ ```
+
+ ---
+
+ # Example
+
+ ```
+ # use std::path::Path;
+ #
+ foo(Path::new("hello, world!"))
+ ```
+ "##]],
+ );
+}
+
+#[test]
+fn hover_shows_fn_doc_attr_raw_string() {
+ check(
+ r##"
+#[doc = r#"Raw string doc attr"#]
+pub fn foo$0(_: &Path) {}
+
+fn main() { }
+"##,
+ expect![[r##"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub fn foo(_: &Path)
+ ```
+
+ ---
+
+ Raw string doc attr
+ "##]],
+ );
+}
+
+#[test]
+fn hover_shows_struct_field_info() {
+ // Hovering over the field when instantiating
+ check(
+ r#"
+struct Foo { field_a: u32 }
+
+fn main() {
+ let foo = Foo { field_a$0: 0, };
+}
+"#,
+ expect![[r#"
+ *field_a*
+
+ ```rust
+ test::Foo
+ ```
+
+ ```rust
+ field_a: u32
+ ```
+ "#]],
+ );
+
+ // Hovering over the field in the definition
+ check(
+ r#"
+struct Foo { field_a$0: u32 }
+
+fn main() {
+ let foo = Foo { field_a: 0 };
+}
+"#,
+ expect![[r#"
+ *field_a*
+
+ ```rust
+ test::Foo
+ ```
+
+ ```rust
+ field_a: u32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_const_static() {
+ check(
+ r#"const foo$0: u32 = 123;"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const foo: u32 = 123 (0x7B)
+ ```
+ "#]],
+ );
+ check(
+ r#"
+const foo$0: u32 = {
+ let x = foo();
+ x + 100
+};"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const foo: u32 = {
+ let x = foo();
+ x + 100
+ }
+ ```
+ "#]],
+ );
+
+ check(
+ r#"static foo$0: u32 = 456;"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ static foo: u32 = 456
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_default_generic_types() {
+ check(
+ r#"
+struct Test<K, T = u8> { k: K, t: T }
+
+fn main() {
+ let zz$0 = Test { t: 23u8, k: 33 };
+}"#,
+ expect![[r#"
+ *zz*
+
+ ```rust
+ let zz: Test<i32>
+ ```
+ "#]],
+ );
+ check_hover_range(
+ r#"
+struct Test<K, T = u8> { k: K, t: T }
+
+fn main() {
+ let $0zz$0 = Test { t: 23u8, k: 33 };
+}"#,
+ expect![[r#"
+ ```rust
+ Test<i32, u8>
+ ```"#]],
+ );
+}
+
+#[test]
+fn hover_some() {
+ check(
+ r#"
+enum Option<T> { Some(T) }
+use Option::Some;
+
+fn main() { So$0me(12); }
+"#,
+ expect![[r#"
+ *Some*
+
+ ```rust
+ test::Option
+ ```
+
+ ```rust
+ Some(T)
+ ```
+ "#]],
+ );
+
+ check(
+ r#"
+enum Option<T> { Some(T) }
+use Option::Some;
+
+fn main() { let b$0ar = Some(12); }
+"#,
+ expect![[r#"
+ *bar*
+
+ ```rust
+ let bar: Option<i32>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_enum_variant() {
+ check(
+ r#"
+enum Option<T> {
+ /// The None variant
+ Non$0e
+}
+"#,
+ expect![[r#"
+ *None*
+
+ ```rust
+ test::Option
+ ```
+
+ ```rust
+ None
+ ```
+
+ ---
+
+ The None variant
+ "#]],
+ );
+
+ check(
+ r#"
+enum Option<T> {
+ /// The Some variant
+ Some(T)
+}
+fn main() {
+ let s = Option::Som$0e(12);
+}
+"#,
+ expect![[r#"
+ *Some*
+
+ ```rust
+ test::Option
+ ```
+
+ ```rust
+ Some(T)
+ ```
+
+ ---
+
+ The Some variant
+ "#]],
+ );
+}
+
+#[test]
+fn hover_for_local_variable() {
+ check(
+ r#"fn func(foo: i32) { fo$0o; }"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ foo: i32
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn hover_for_local_variable_pat() {
+ check(
+ r#"fn func(fo$0o: i32) {}"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ foo: i32
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn hover_local_var_edge() {
+ check(
+ r#"fn func(foo: i32) { if true { $0foo; }; }"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ foo: i32
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn hover_for_param_edge() {
+ check(
+ r#"fn func($0foo: i32) {}"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ foo: i32
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn hover_for_param_with_multiple_traits() {
+ check(
+ r#"
+ //- minicore: sized
+ trait Deref {
+ type Target: ?Sized;
+ }
+ trait DerefMut {
+ type Target: ?Sized;
+ }
+ fn f(_x$0: impl Deref<Target=u8> + DerefMut<Target=u8>) {}"#,
+ expect![[r#"
+ *_x*
+
+ ```rust
+ _x: impl Deref<Target = u8> + DerefMut<Target = u8>
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_infer_associated_method_result() {
+ check(
+ r#"
+struct Thing { x: u32 }
+
+impl Thing {
+ fn new() -> Thing { Thing { x: 0 } }
+}
+
+fn main() { let foo_$0test = Thing::new(); }
+"#,
+ expect![[r#"
+ *foo_test*
+
+ ```rust
+ let foo_test: Thing
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_infer_associated_method_exact() {
+ check(
+ r#"
+mod wrapper {
+ pub struct Thing { x: u32 }
+
+ impl Thing {
+ pub fn new() -> Thing { Thing { x: 0 } }
+ }
+}
+
+fn main() { let foo_test = wrapper::Thing::new$0(); }
+"#,
+ expect![[r#"
+ *new*
+
+ ```rust
+ test::wrapper::Thing
+ ```
+
+ ```rust
+ pub fn new() -> Thing
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_infer_associated_const_in_pattern() {
+ check(
+ r#"
+struct X;
+impl X {
+ const C: u32 = 1;
+}
+
+fn main() {
+ match 1 {
+ X::C$0 => {},
+ 2 => {},
+ _ => {}
+ };
+}
+"#,
+ expect![[r#"
+ *C*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const C: u32 = 1
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_self() {
+ check(
+ r#"
+struct Thing { x: u32 }
+impl Thing {
+ fn new() -> Self { Self$0 { x: 0 } }
+}
+"#,
+ expect![[r#"
+ *Self*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ struct Thing
+ ```
+ "#]],
+ );
+ check(
+ r#"
+struct Thing { x: u32 }
+impl Thing {
+ fn new() -> Self$0 { Self { x: 0 } }
+}
+"#,
+ expect![[r#"
+ *Self*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ struct Thing
+ ```
+ "#]],
+ );
+ check(
+ r#"
+enum Thing { A }
+impl Thing {
+ pub fn new() -> Self$0 { Thing::A }
+}
+"#,
+ expect![[r#"
+ *Self*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ enum Thing
+ ```
+ "#]],
+ );
+ check(
+ r#"
+ enum Thing { A }
+ impl Thing {
+ pub fn thing(a: Self$0) {}
+ }
+ "#,
+ expect![[r#"
+ *Self*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ enum Thing
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_shadowing_pat() {
+ check(
+ r#"
+fn x() {}
+
+fn y() {
+ let x = 0i32;
+ x$0;
+}
+"#,
+ expect![[r#"
+ *x*
+
+ ```rust
+ let x: i32
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_macro_invocation() {
+ check(
+ r#"
+macro_rules! foo { () => {} }
+
+fn f() { fo$0o!(); }
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ macro_rules! foo
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_macro2_invocation() {
+ check(
+ r#"
+/// foo bar
+///
+/// foo bar baz
+macro foo() {}
+
+fn f() { fo$0o!(); }
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ macro foo
+ ```
+
+ ---
+
+ foo bar
+
+ foo bar baz
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_tuple_field() {
+ check(
+ r#"struct TS(String, i32$0);"#,
+ expect![[r#"
+ *i32*
+
+ ```rust
+ i32
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_through_macro() {
+ check(
+ r#"
+macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
+fn foo() {}
+id! {
+ fn bar() { fo$0o(); }
+}
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn foo()
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_through_attr() {
+ check(
+ r#"
+//- proc_macros: identity
+#[proc_macros::identity]
+fn foo$0() {}
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn foo()
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_through_expr_in_macro() {
+ check(
+ r#"
+macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
+fn foo(bar:u32) { let a = id!(ba$0r); }
+"#,
+ expect![[r#"
+ *bar*
+
+ ```rust
+ bar: u32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_through_expr_in_macro_recursive() {
+ check(
+ r#"
+macro_rules! id_deep { ($($tt:tt)*) => { $($tt)* } }
+macro_rules! id { ($($tt:tt)*) => { id_deep!($($tt)*) } }
+fn foo(bar:u32) { let a = id!(ba$0r); }
+"#,
+ expect![[r#"
+ *bar*
+
+ ```rust
+ bar: u32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_through_func_in_macro_recursive() {
+ check(
+ r#"
+macro_rules! id_deep { ($($tt:tt)*) => { $($tt)* } }
+macro_rules! id { ($($tt:tt)*) => { id_deep!($($tt)*) } }
+fn bar() -> u32 { 0 }
+fn foo() { let a = id!([0u32, bar($0)] ); }
+"#,
+ expect![[r#"
+ *bar()*
+ ```rust
+ u32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_through_literal_string_in_macro() {
+ check(
+ r#"
+macro_rules! arr { ($($tt:tt)*) => { [$($tt)*] } }
+fn foo() {
+ let mastered_for_itunes = "";
+ let _ = arr!("Tr$0acks", &mastered_for_itunes);
+}
+"#,
+ expect![[r#"
+ *"Tracks"*
+ ```rust
+ &str
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_through_assert_macro() {
+ check(
+ r#"
+#[rustc_builtin_macro]
+macro_rules! assert {}
+
+fn bar() -> bool { true }
+fn foo() {
+ assert!(ba$0r());
+}
+"#,
+ expect![[r#"
+ *bar*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn bar() -> bool
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_multiple_actions() {
+ check_actions(
+ r#"
+struct Bar;
+struct Foo { bar: Bar }
+
+fn foo(Foo { b$0ar }: &Foo) {}
+ "#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Bar",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..11,
+ focus_range: 7..10,
+ name: "Bar",
+ kind: Struct,
+ description: "struct Bar",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_through_literal_string_in_builtin_macro() {
+ check_hover_no_result(
+ r#"
+ #[rustc_builtin_macro]
+ macro_rules! format {}
+
+ fn foo() {
+ format!("hel$0lo {}", 0);
+ }
+"#,
+ );
+}
+
+#[test]
+fn test_hover_non_ascii_space_doc() {
+ check(
+ "
+/// <- `\u{3000}` here
+fn foo() { }
+
+fn bar() { fo$0o(); }
+",
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn foo()
+ ```
+
+ ---
+
+ \<- ` ` here
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_function_show_qualifiers() {
+ check(
+ r#"async fn foo$0() {}"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ async fn foo()
+ ```
+ "#]],
+ );
+ check(
+ r#"pub const unsafe fn foo$0() {}"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub const unsafe fn foo()
+ ```
+ "#]],
+ );
+ // Top level `pub(crate)` will be displayed as no visibility.
+ check(
+ r#"mod m { pub(crate) async unsafe extern "C" fn foo$0() {} }"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test::m
+ ```
+
+ ```rust
+ pub(crate) async unsafe extern "C" fn foo()
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_function_show_types() {
+ check(
+ r#"fn foo$0(a: i32, b:i32) -> i32 { 0 }"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn foo(a: i32, b: i32) -> i32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_function_pointer_show_identifiers() {
+ check(
+ r#"type foo$0 = fn(a: i32, b: i32) -> i32;"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ type foo = fn(a: i32, b: i32) -> i32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_function_pointer_no_identifier() {
+ check(
+ r#"type foo$0 = fn(i32, _: i32) -> i32;"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ type foo = fn(i32, i32) -> i32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_trait_show_qualifiers() {
+ check_actions(
+ r"unsafe trait foo$0() {}",
+ expect![[r#"
+ [
+ Implementation(
+ FilePosition {
+ file_id: FileId(
+ 0,
+ ),
+ offset: 13,
+ },
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_extern_crate() {
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+extern crate st$0d;
+//- /std/lib.rs crate:std
+//! Standard library for this test
+//!
+//! Printed?
+//! abc123
+"#,
+ expect![[r#"
+ *std*
+
+ ```rust
+ extern crate std
+ ```
+
+ ---
+
+ Standard library for this test
+
+ Printed?
+ abc123
+ "#]],
+ );
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+extern crate std as ab$0c;
+//- /std/lib.rs crate:std
+//! Standard library for this test
+//!
+//! Printed?
+//! abc123
+"#,
+ expect![[r#"
+ *abc*
+
+ ```rust
+ extern crate std
+ ```
+
+ ---
+
+ Standard library for this test
+
+ Printed?
+ abc123
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_mod_with_same_name_as_function() {
+ check(
+ r#"
+use self::m$0y::Bar;
+mod my { pub struct Bar; }
+
+fn my() {}
+"#,
+ expect![[r#"
+ *my*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ mod my
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_struct_doc_comment() {
+ check(
+ r#"
+/// This is an example
+/// multiline doc
+///
+/// # Example
+///
+/// ```
+/// let five = 5;
+///
+/// assert_eq!(6, my_crate::add_one(5));
+/// ```
+struct Bar;
+
+fn foo() { let bar = Ba$0r; }
+"#,
+ expect![[r##"
+ *Bar*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ struct Bar
+ ```
+
+ ---
+
+ This is an example
+ multiline doc
+
+ # Example
+
+ ```
+ let five = 5;
+
+ assert_eq!(6, my_crate::add_one(5));
+ ```
+ "##]],
+ );
+}
+
+#[test]
+fn test_hover_struct_doc_attr() {
+ check(
+ r#"
+#[doc = "bar docs"]
+struct Bar;
+
+fn foo() { let bar = Ba$0r; }
+"#,
+ expect![[r#"
+ *Bar*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ struct Bar
+ ```
+
+ ---
+
+ bar docs
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_struct_doc_attr_multiple_and_mixed() {
+ check(
+ r#"
+/// bar docs 0
+#[doc = "bar docs 1"]
+#[doc = "bar docs 2"]
+struct Bar;
+
+fn foo() { let bar = Ba$0r; }
+"#,
+ expect![[r#"
+ *Bar*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ struct Bar
+ ```
+
+ ---
+
+ bar docs 0
+ bar docs 1
+ bar docs 2
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_external_url() {
+ check(
+ r#"
+pub struct Foo;
+/// [external](https://www.google.com)
+pub struct B$0ar
+"#,
+ expect![[r#"
+ *Bar*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub struct Bar
+ ```
+
+ ---
+
+ [external](https://www.google.com)
+ "#]],
+ );
+}
+
+// Check that we don't rewrite links which we can't identify
+#[test]
+fn test_hover_unknown_target() {
+ check(
+ r#"
+pub struct Foo;
+/// [baz](Baz)
+pub struct B$0ar
+"#,
+ expect![[r#"
+ *Bar*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub struct Bar
+ ```
+
+ ---
+
+ [baz](Baz)
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_no_links() {
+ check_hover_no_links(
+ r#"
+/// Test cases:
+/// case 1. bare URL: https://www.example.com/
+/// case 2. inline URL with title: [example](https://www.example.com/)
+/// case 3. code reference: [`Result`]
+/// case 4. code reference but miss footnote: [`String`]
+/// case 5. autolink: <http://www.example.com/>
+/// case 6. email address: <test@example.com>
+/// case 7. reference: [example][example]
+/// case 8. collapsed link: [example][]
+/// case 9. shortcut link: [example]
+/// case 10. inline without URL: [example]()
+/// case 11. reference: [foo][foo]
+/// case 12. reference: [foo][bar]
+/// case 13. collapsed link: [foo][]
+/// case 14. shortcut link: [foo]
+/// case 15. inline without URL: [foo]()
+/// case 16. just escaped text: \[foo]
+/// case 17. inline link: [Foo](foo::Foo)
+///
+/// [`Result`]: ../../std/result/enum.Result.html
+/// [^example]: https://www.example.com/
+pub fn fo$0o() {}
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub fn foo()
+ ```
+
+ ---
+
+ Test cases:
+ case 1. bare URL: https://www.example.com/
+ case 2. inline URL with title: [example](https://www.example.com/)
+ case 3. code reference: `Result`
+ case 4. code reference but miss footnote: `String`
+ case 5. autolink: http://www.example.com/
+ case 6. email address: test@example.com
+ case 7. reference: example
+ case 8. collapsed link: example
+ case 9. shortcut link: example
+ case 10. inline without URL: example
+ case 11. reference: foo
+ case 12. reference: foo
+ case 13. collapsed link: foo
+ case 14. shortcut link: foo
+ case 15. inline without URL: foo
+ case 16. just escaped text: \[foo\]
+ case 17. inline link: Foo
+
+ [^example]: https://www.example.com/
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_macro_generated_struct_fn_doc_comment() {
+ cov_mark::check!(hover_macro_generated_struct_fn_doc_comment);
+
+ check(
+ r#"
+macro_rules! bar {
+ () => {
+ struct Bar;
+ impl Bar {
+ /// Do the foo
+ fn foo(&self) {}
+ }
+ }
+}
+
+bar!();
+
+fn foo() { let bar = Bar; bar.fo$0o(); }
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test::Bar
+ ```
+
+ ```rust
+ fn foo(&self)
+ ```
+
+ ---
+
+ Do the foo
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_macro_generated_struct_fn_doc_attr() {
+ cov_mark::check!(hover_macro_generated_struct_fn_doc_attr);
+
+ check(
+ r#"
+macro_rules! bar {
+ () => {
+ struct Bar;
+ impl Bar {
+ #[doc = "Do the foo"]
+ fn foo(&self) {}
+ }
+ }
+}
+
+bar!();
+
+fn foo() { let bar = Bar; bar.fo$0o(); }
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test::Bar
+ ```
+
+ ```rust
+ fn foo(&self)
+ ```
+
+ ---
+
+ Do the foo
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_variadic_function() {
+ check(
+ r#"
+extern "C" {
+ pub fn foo(bar: i32, ...) -> i32;
+}
+
+fn main() { let foo_test = unsafe { fo$0o(1, 2, 3); } }
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub unsafe fn foo(bar: i32, ...) -> i32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_trait_has_impl_action() {
+ check_actions(
+ r#"trait foo$0() {}"#,
+ expect![[r#"
+ [
+ Implementation(
+ FilePosition {
+ file_id: FileId(
+ 0,
+ ),
+ offset: 6,
+ },
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_struct_has_impl_action() {
+ check_actions(
+ r"struct foo$0() {}",
+ expect![[r#"
+ [
+ Implementation(
+ FilePosition {
+ file_id: FileId(
+ 0,
+ ),
+ offset: 7,
+ },
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_union_has_impl_action() {
+ check_actions(
+ r#"union foo$0() {}"#,
+ expect![[r#"
+ [
+ Implementation(
+ FilePosition {
+ file_id: FileId(
+ 0,
+ ),
+ offset: 6,
+ },
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_enum_has_impl_action() {
+ check_actions(
+ r"enum foo$0() { A, B }",
+ expect![[r#"
+ [
+ Implementation(
+ FilePosition {
+ file_id: FileId(
+ 0,
+ ),
+ offset: 5,
+ },
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_self_has_impl_action() {
+ check_actions(
+ r#"struct foo where Self$0:;"#,
+ expect![[r#"
+ [
+ Implementation(
+ FilePosition {
+ file_id: FileId(
+ 0,
+ ),
+ offset: 7,
+ },
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_test_has_action() {
+ check_actions(
+ r#"
+#[test]
+fn foo_$0test() {}
+"#,
+ expect![[r#"
+ [
+ Reference(
+ FilePosition {
+ file_id: FileId(
+ 0,
+ ),
+ offset: 11,
+ },
+ ),
+ Runnable(
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..24,
+ focus_range: 11..19,
+ name: "foo_test",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "foo_test",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_test_mod_has_action() {
+ check_actions(
+ r#"
+mod tests$0 {
+ #[test]
+ fn foo_test() {}
+}
+"#,
+ expect![[r#"
+ [
+ Runnable(
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..46,
+ focus_range: 4..9,
+ name: "tests",
+ kind: Module,
+ description: "mod tests",
+ },
+ kind: TestMod {
+ path: "tests",
+ },
+ cfg: None,
+ },
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_struct_has_goto_type_action() {
+ check_actions(
+ r#"
+struct S{ f1: u32 }
+
+fn main() { let s$0t = S{ f1:0 }; }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..19,
+ focus_range: 7..8,
+ name: "S",
+ kind: Struct,
+ description: "struct S",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_generic_struct_has_goto_type_actions() {
+ check_actions(
+ r#"
+struct Arg(u32);
+struct S<T>{ f1: T }
+
+fn main() { let s$0t = S{ f1:Arg(0) }; }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 17..37,
+ focus_range: 24..25,
+ name: "S",
+ kind: Struct,
+ description: "struct S<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::Arg",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..16,
+ focus_range: 7..10,
+ name: "Arg",
+ kind: Struct,
+ description: "struct Arg",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_generic_struct_has_flattened_goto_type_actions() {
+ check_actions(
+ r#"
+struct Arg(u32);
+struct S<T>{ f1: T }
+
+fn main() { let s$0t = S{ f1: S{ f1: Arg(0) } }; }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 17..37,
+ focus_range: 24..25,
+ name: "S",
+ kind: Struct,
+ description: "struct S<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::Arg",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..16,
+ focus_range: 7..10,
+ name: "Arg",
+ kind: Struct,
+ description: "struct Arg",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_tuple_has_goto_type_actions() {
+ check_actions(
+ r#"
+struct A(u32);
+struct B(u32);
+mod M {
+ pub struct C(u32);
+}
+
+fn main() { let s$0t = (A(1), B(2), M::C(3) ); }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::A",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..14,
+ focus_range: 7..8,
+ name: "A",
+ kind: Struct,
+ description: "struct A",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::B",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 15..29,
+ focus_range: 22..23,
+ name: "B",
+ kind: Struct,
+ description: "struct B",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::M::C",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 42..60,
+ focus_range: 53..54,
+ name: "C",
+ kind: Struct,
+ description: "pub struct C",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_return_impl_trait_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo {}
+fn foo() -> impl Foo {}
+
+fn main() { let s$0t = foo(); }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..12,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_generic_return_impl_trait_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo<T> {}
+struct S;
+fn foo() -> impl Foo<S> {}
+
+fn main() { let s$0t = foo(); }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..15,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 16..25,
+ focus_range: 23..24,
+ name: "S",
+ kind: Struct,
+ description: "struct S",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_return_impl_traits_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo {}
+trait Bar {}
+fn foo() -> impl Foo + Bar {}
+
+fn main() { let s$0t = foo(); }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..12,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::Bar",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 13..25,
+ focus_range: 19..22,
+ name: "Bar",
+ kind: Trait,
+ description: "trait Bar",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_generic_return_impl_traits_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo<T> {}
+trait Bar<T> {}
+struct S1 {}
+struct S2 {}
+
+fn foo() -> impl Foo<S1> + Bar<S2> {}
+
+fn main() { let s$0t = foo(); }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..15,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::Bar",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 16..31,
+ focus_range: 22..25,
+ name: "Bar",
+ kind: Trait,
+ description: "trait Bar<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::S1",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 32..44,
+ focus_range: 39..41,
+ name: "S1",
+ kind: Struct,
+ description: "struct S1",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::S2",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 45..57,
+ focus_range: 52..54,
+ name: "S2",
+ kind: Struct,
+ description: "struct S2",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_arg_impl_trait_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo {}
+fn foo(ar$0g: &impl Foo) {}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..12,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_arg_impl_traits_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo {}
+trait Bar<T> {}
+struct S{}
+
+fn foo(ar$0g: &impl Foo + Bar<S>) {}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..12,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::Bar",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 13..28,
+ focus_range: 19..22,
+ name: "Bar",
+ kind: Trait,
+ description: "trait Bar<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 29..39,
+ focus_range: 36..37,
+ name: "S",
+ kind: Struct,
+ description: "struct S",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_async_block_impl_trait_has_goto_type_action() {
+ check_actions(
+ r#"
+//- /main.rs crate:main deps:core
+// we don't use minicore here so that this test doesn't randomly fail
+// when someone edits minicore
+struct S;
+fn foo() {
+ let fo$0o = async { S };
+}
+//- /core.rs crate:core
+pub mod future {
+ #[lang = "future_trait"]
+ pub trait Future {}
+}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "core::future::Future",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 1,
+ ),
+ full_range: 21..69,
+ focus_range: 60..66,
+ name: "Future",
+ kind: Trait,
+ description: "pub trait Future",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "main::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..110,
+ focus_range: 108..109,
+ name: "S",
+ kind: Struct,
+ description: "struct S",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_arg_generic_impl_trait_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo<T> {}
+struct S {}
+fn foo(ar$0g: &impl Foo<S>) {}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..15,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 16..27,
+ focus_range: 23..24,
+ name: "S",
+ kind: Struct,
+ description: "struct S",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_dyn_return_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo {}
+struct S;
+impl Foo for S {}
+
+struct B<T>{}
+fn foo() -> B<dyn Foo> {}
+
+fn main() { let s$0t = foo(); }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::B",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 42..55,
+ focus_range: 49..50,
+ name: "B",
+ kind: Struct,
+ description: "struct B<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..12,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_dyn_arg_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo {}
+fn foo(ar$0g: &dyn Foo) {}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..12,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_generic_dyn_arg_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo<T> {}
+struct S {}
+fn foo(ar$0g: &dyn Foo<S>) {}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..15,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 16..27,
+ focus_range: 23..24,
+ name: "S",
+ kind: Struct,
+ description: "struct S",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_goto_type_action_links_order() {
+ check_actions(
+ r#"
+trait ImplTrait<T> {}
+trait DynTrait<T> {}
+struct B<T> {}
+struct S {}
+
+fn foo(a$0rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::ImplTrait",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..21,
+ focus_range: 6..15,
+ name: "ImplTrait",
+ kind: Trait,
+ description: "trait ImplTrait<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::B",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 43..57,
+ focus_range: 50..51,
+ name: "B",
+ kind: Struct,
+ description: "struct B<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::DynTrait",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 22..42,
+ focus_range: 28..36,
+ name: "DynTrait",
+ kind: Trait,
+ description: "trait DynTrait<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 58..69,
+ focus_range: 65..66,
+ name: "S",
+ kind: Struct,
+ description: "struct S",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_associated_type_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo {
+ type Item;
+ fn get(self) -> Self::Item {}
+}
+
+struct Bar{}
+struct S{}
+
+impl Foo for S { type Item = Bar; }
+
+fn test() -> impl Foo { S {} }
+
+fn main() { let s$0t = test().get(); }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..62,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_const_param_has_goto_type_action() {
+ check_actions(
+ r#"
+struct Bar;
+struct Foo<const BAR: Bar>;
+
+impl<const BAR: Bar> Foo<BAR$0> {}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Bar",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..11,
+ focus_range: 7..10,
+ name: "Bar",
+ kind: Struct,
+ description: "struct Bar",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_type_param_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo {}
+
+fn foo<T: Foo>(t: T$0){}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..12,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_self_has_go_to_type() {
+ check_actions(
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(&self$0) {}
+}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..11,
+ focus_range: 7..10,
+ name: "Foo",
+ kind: Struct,
+ description: "struct Foo",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn hover_displays_normalized_crate_names() {
+ check(
+ r#"
+//- /lib.rs crate:name-with-dashes
+pub mod wrapper {
+ pub struct Thing { x: u32 }
+
+ impl Thing {
+ pub fn new() -> Thing { Thing { x: 0 } }
+ }
+}
+
+//- /main.rs crate:main deps:name-with-dashes
+fn main() { let foo_test = name_with_dashes::wrapper::Thing::new$0(); }
+"#,
+ expect![[r#"
+ *new*
+
+ ```rust
+ name_with_dashes::wrapper::Thing
+ ```
+
+ ```rust
+ pub fn new() -> Thing
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn hover_field_pat_shorthand_ref_match_ergonomics() {
+ check(
+ r#"
+struct S {
+ f: i32,
+}
+
+fn main() {
+ let s = S { f: 0 };
+ let S { f$0 } = &s;
+}
+"#,
+ expect![[r#"
+ *f*
+
+ ```rust
+ f: &i32
+ ```
+ ---
+
+ ```rust
+ test::S
+ ```
+
+ ```rust
+ f: i32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn const_generic_order() {
+ check(
+ r#"
+struct Foo;
+struct S$0T<const C: usize = 1, T = Foo>(T);
+"#,
+ expect![[r#"
+ *ST*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ struct ST<const C: usize, T = Foo>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn const_generic_positive_i8_literal() {
+ check(
+ r#"
+struct Const<const N: i8>;
+
+fn main() {
+ let v$0alue = Const::<1>;
+}
+"#,
+ expect![[r#"
+ *value*
+
+ ```rust
+ let value: Const<1>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn const_generic_zero_i8_literal() {
+ check(
+ r#"
+struct Const<const N: i8>;
+
+fn main() {
+ let v$0alue = Const::<0>;
+}
+"#,
+ expect![[r#"
+ *value*
+
+ ```rust
+ let value: Const<0>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn const_generic_negative_i8_literal() {
+ check(
+ r#"
+struct Const<const N: i8>;
+
+fn main() {
+ let v$0alue = Const::<-1>;
+}
+"#,
+ expect![[r#"
+ *value*
+
+ ```rust
+ let value: Const<-1>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn const_generic_bool_literal() {
+ check(
+ r#"
+struct Const<const F: bool>;
+
+fn main() {
+ let v$0alue = Const::<true>;
+}
+"#,
+ expect![[r#"
+ *value*
+
+ ```rust
+ let value: Const<true>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn const_generic_char_literal() {
+ check(
+ r#"
+struct Const<const C: char>;
+
+fn main() {
+ let v$0alue = Const::<'🦀'>;
+}
+"#,
+ expect![[r#"
+ *value*
+
+ ```rust
+ let value: Const<'🦀'>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_self_param_shows_type() {
+ check(
+ r#"
+struct Foo {}
+impl Foo {
+ fn bar(&sel$0f) {}
+}
+"#,
+ expect![[r#"
+ *self*
+
+ ```rust
+ self: &Foo
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_self_param_shows_type_for_arbitrary_self_type() {
+ check(
+ r#"
+struct Arc<T>(T);
+struct Foo {}
+impl Foo {
+ fn bar(sel$0f: Arc<Foo>) {}
+}
+"#,
+ expect![[r#"
+ *self*
+
+ ```rust
+ self: Arc<Foo>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_doc_outer_inner() {
+ check(
+ r#"
+/// Be quick;
+mod Foo$0 {
+ //! time is mana
+
+ /// This comment belongs to the function
+ fn foo() {}
+}
+"#,
+ expect![[r#"
+ *Foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ mod Foo
+ ```
+
+ ---
+
+ Be quick;
+ time is mana
+ "#]],
+ );
+}
+
+#[test]
+fn hover_doc_outer_inner_attribue() {
+ check(
+ r#"
+#[doc = "Be quick;"]
+mod Foo$0 {
+ #![doc = "time is mana"]
+
+ #[doc = "This comment belongs to the function"]
+ fn foo() {}
+}
+"#,
+ expect![[r#"
+ *Foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ mod Foo
+ ```
+
+ ---
+
+ Be quick;
+ time is mana
+ "#]],
+ );
+}
+
+#[test]
+fn hover_doc_block_style_indentend() {
+ check(
+ r#"
+/**
+ foo
+ ```rust
+ let x = 3;
+ ```
+*/
+fn foo$0() {}
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn foo()
+ ```
+
+ ---
+
+ foo
+
+ ```rust
+ let x = 3;
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_comments_dont_highlight_parent() {
+ cov_mark::check!(no_highlight_on_comment_hover);
+ check_hover_no_result(
+ r#"
+fn no_hover() {
+ // no$0hover
+}
+"#,
+ );
+}
+
+#[test]
+fn hover_label() {
+ check(
+ r#"
+fn foo() {
+ 'label$0: loop {}
+}
+"#,
+ expect![[r#"
+ *'label*
+
+ ```rust
+ 'label
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_lifetime() {
+ check(
+ r#"fn foo<'lifetime>(_: &'lifetime$0 ()) {}"#,
+ expect![[r#"
+ *'lifetime*
+
+ ```rust
+ 'lifetime
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_type_param() {
+ check(
+ r#"
+//- minicore: sized
+struct Foo<T>(T);
+trait TraitA {}
+trait TraitB {}
+impl<T: TraitA + TraitB> Foo<T$0> where T: Sized {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: TraitA + TraitB
+ ```
+ "#]],
+ );
+ check(
+ r#"
+//- minicore: sized
+struct Foo<T>(T);
+impl<T> Foo<T$0> {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T
+ ```
+ "#]],
+ );
+ // lifetimes bounds arent being tracked yet
+ check(
+ r#"
+//- minicore: sized
+struct Foo<T>(T);
+impl<T: 'static> Foo<T$0> {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_type_param_sized_bounds() {
+ // implicit `: Sized` bound
+ check(
+ r#"
+//- minicore: sized
+trait Trait {}
+struct Foo<T>(T);
+impl<T: Trait> Foo<T$0> {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: Trait
+ ```
+ "#]],
+ );
+ check(
+ r#"
+//- minicore: sized
+trait Trait {}
+struct Foo<T>(T);
+impl<T: Trait + ?Sized> Foo<T$0> {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: Trait + ?Sized
+ ```
+ "#]],
+ );
+}
+
+mod type_param_sized_bounds {
+ use super::*;
+
+ #[test]
+ fn single_implicit() {
+ check(
+ r#"
+//- minicore: sized
+fn foo<T$0>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn single_explicit() {
+ check(
+ r#"
+//- minicore: sized
+fn foo<T$0: Sized>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn single_relaxed() {
+ check(
+ r#"
+//- minicore: sized
+fn foo<T$0: ?Sized>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: ?Sized
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn multiple_implicit() {
+ check(
+ r#"
+//- minicore: sized
+trait Trait {}
+fn foo<T$0: Trait>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: Trait
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn multiple_explicit() {
+ check(
+ r#"
+//- minicore: sized
+trait Trait {}
+fn foo<T$0: Trait + Sized>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: Trait
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn multiple_relaxed() {
+ check(
+ r#"
+//- minicore: sized
+trait Trait {}
+fn foo<T$0: Trait + ?Sized>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: Trait + ?Sized
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn mixed() {
+ check(
+ r#"
+//- minicore: sized
+fn foo<T$0: ?Sized + Sized + Sized>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T
+ ```
+ "#]],
+ );
+ check(
+ r#"
+//- minicore: sized
+trait Trait {}
+fn foo<T$0: Sized + ?Sized + Sized + Trait>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: Trait
+ ```
+ "#]],
+ );
+ }
+}
+
+#[test]
+fn hover_const_generic_type_alias() {
+ check(
+ r#"
+struct Foo<const LEN: usize>;
+type Fo$0o2 = Foo<2>;
+"#,
+ expect![[r#"
+ *Foo2*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ type Foo2 = Foo<2>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_const_param() {
+ check(
+ r#"
+struct Foo<const LEN: usize>;
+impl<const LEN: usize> Foo<LEN$0> {}
+"#,
+ expect![[r#"
+ *LEN*
+
+ ```rust
+ const LEN: usize
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_const_eval() {
+ // show hex for <10
+ check(
+ r#"
+/// This is a doc
+const FOO$0: usize = 1 << 3;
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: usize = 8
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ // show hex for >10
+ check(
+ r#"
+/// This is a doc
+const FOO$0: usize = (1 << 3) + (1 << 2);
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: usize = 12 (0xC)
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ // show original body when const eval fails
+ check(
+ r#"
+/// This is a doc
+const FOO$0: usize = 2 - 3;
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: usize = 2 - 3
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ // don't show hex for negatives
+ check(
+ r#"
+/// This is a doc
+const FOO$0: i32 = 2 - 3;
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: i32 = -1
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ check(
+ r#"
+/// This is a doc
+const FOO$0: &str = "bar";
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: &str = "bar"
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ // show char literal
+ check(
+ r#"
+/// This is a doc
+const FOO$0: char = 'a';
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: char = 'a'
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ // show escaped char literal
+ check(
+ r#"
+/// This is a doc
+const FOO$0: char = '\x61';
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: char = 'a'
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ // show byte literal
+ check(
+ r#"
+/// This is a doc
+const FOO$0: u8 = b'a';
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: u8 = 97 (0x61)
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ // show escaped byte literal
+ check(
+ r#"
+/// This is a doc
+const FOO$0: u8 = b'\x61';
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: u8 = 97 (0x61)
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ // show float literal
+ check(
+ r#"
+ /// This is a doc
+ const FOO$0: f64 = 1.0234;
+ "#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: f64 = 1.0234
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ //show float typecasted from int
+ check(
+ r#"
+/// This is a doc
+const FOO$0: f32 = 1f32;
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: f32 = 1.0
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ //show f64 typecasted from float
+ check(
+ r#"
+/// This is a doc
+const FOO$0: f64 = 1.0f64;
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: f64 = 1.0
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+}
+
+#[test]
+fn hover_const_pat() {
+ check(
+ r#"
+/// This is a doc
+const FOO: usize = 3;
+fn foo() {
+ match 5 {
+ FOO$0 => (),
+ _ => ()
+ }
+}
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: usize = 3
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+}
+
+#[test]
+fn array_repeat_exp() {
+ check(
+ r#"
+fn main() {
+ let til$0e4 = [0_u32; (4 * 8 * 8) / 32];
+}
+ "#,
+ expect![[r#"
+ *tile4*
+
+ ```rust
+ let tile4: [u32; 8]
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_mod_def() {
+ check(
+ r#"
+//- /main.rs
+mod foo$0;
+//- /foo.rs
+//! For the horde!
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ mod foo
+ ```
+
+ ---
+
+ For the horde!
+ "#]],
+ );
+}
+
+#[test]
+fn hover_self_in_use() {
+ check(
+ r#"
+//! This should not appear
+mod foo {
+ /// But this should appear
+ pub mod bar {}
+}
+use foo::bar::{self$0};
+"#,
+ expect![[r#"
+ *self*
+
+ ```rust
+ test::foo
+ ```
+
+ ```rust
+ mod bar
+ ```
+
+ ---
+
+ But this should appear
+ "#]],
+ )
+}
+
+#[test]
+fn hover_keyword() {
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+fn f() { retur$0n; }
+//- /libstd.rs crate:std
+/// Docs for return_keyword
+mod return_keyword {}
+"#,
+ expect![[r#"
+ *return*
+
+ ```rust
+ return
+ ```
+
+ ---
+
+ Docs for return_keyword
+ "#]],
+ );
+}
+
+#[test]
+fn hover_keyword_doc() {
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+fn foo() {
+ let bar = mov$0e || {};
+}
+//- /libstd.rs crate:std
+#[doc(keyword = "move")]
+/// [closure]
+/// [closures][closure]
+/// [threads]
+/// <https://doc.rust-lang.org/nightly/book/ch13-01-closures.html>
+///
+/// [closure]: ../book/ch13-01-closures.html
+/// [threads]: ../book/ch16-01-threads.html#using-move-closures-with-threads
+mod move_keyword {}
+"#,
+ expect![[r##"
+ *move*
+
+ ```rust
+ move
+ ```
+
+ ---
+
+ [closure](https://doc.rust-lang.org/nightly/book/ch13-01-closures.html)
+ [closures](https://doc.rust-lang.org/nightly/book/ch13-01-closures.html)
+ [threads](https://doc.rust-lang.org/nightly/book/ch16-01-threads.html#using-move-closures-with-threads)
+ <https://doc.rust-lang.org/nightly/book/ch13-01-closures.html>
+ "##]],
+ );
+}
+
+#[test]
+fn hover_keyword_as_primitive() {
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+type F = f$0n(i32) -> i32;
+//- /libstd.rs crate:std
+/// Docs for prim_fn
+mod prim_fn {}
+"#,
+ expect![[r#"
+ *fn*
+
+ ```rust
+ fn
+ ```
+
+ ---
+
+ Docs for prim_fn
+ "#]],
+ );
+}
+
+#[test]
+fn hover_builtin() {
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+cosnt _: &str$0 = ""; }
+
+//- /libstd.rs crate:std
+/// Docs for prim_str
+/// [`foo`](../std/keyword.foo.html)
+mod prim_str {}
+"#,
+ expect![[r#"
+ *str*
+
+ ```rust
+ str
+ ```
+
+ ---
+
+ Docs for prim_str
+ [`foo`](https://doc.rust-lang.org/nightly/std/keyword.foo.html)
+ "#]],
+ );
+}
+
+#[test]
+fn hover_macro_expanded_function() {
+ check(
+ r#"
+struct S<'a, T>(&'a T);
+trait Clone {}
+macro_rules! foo {
+ () => {
+ fn bar<'t, T: Clone + 't>(s: &mut S<'t, T>, t: u32) -> *mut u32 where
+ 't: 't + 't,
+ for<'a> T: Clone + 'a
+ { 0 as _ }
+ };
+}
+
+foo!();
+
+fn main() {
+ bar$0;
+}
+"#,
+ expect![[r#"
+ *bar*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn bar<'t, T>(s: &mut S<'t, T>, t: u32) -> *mut u32
+ where
+ T: Clone + 't,
+ 't: 't + 't,
+ for<'a> T: Clone + 'a,
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn hover_intra_doc_links() {
+ check(
+ r#"
+
+pub mod theitem {
+ /// This is the item. Cool!
+ pub struct TheItem;
+}
+
+/// Gives you a [`TheItem$0`].
+///
+/// [`TheItem`]: theitem::TheItem
+pub fn gimme() -> theitem::TheItem {
+ theitem::TheItem
+}
+"#,
+ expect![[r#"
+ *[`TheItem`]*
+
+ ```rust
+ test::theitem
+ ```
+
+ ```rust
+ pub struct TheItem
+ ```
+
+ ---
+
+ This is the item. Cool!
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_trait_assoc_typealias() {
+ check(
+ r#"
+ fn main() {}
+
+trait T1 {
+ type Bar;
+ type Baz;
+}
+
+struct Foo;
+
+mod t2 {
+ pub trait T2 {
+ type Bar;
+ }
+}
+
+use t2::T2;
+
+impl T2 for Foo {
+ type Bar = String;
+}
+
+impl T1 for Foo {
+ type Bar = <Foo as t2::T2>::Ba$0r;
+ // ^^^ unresolvedReference
+}
+ "#,
+ expect![[r#"
+*Bar*
+
+```rust
+test::t2
+```
+
+```rust
+pub type Bar
+```
+"#]],
+ );
+}
+#[test]
+fn hover_generic_assoc() {
+ check(
+ r#"
+fn foo<T: A>() where T::Assoc$0: {}
+
+trait A {
+ type Assoc;
+}"#,
+ expect![[r#"
+ *Assoc*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ type Assoc
+ ```
+ "#]],
+ );
+ check(
+ r#"
+fn foo<T: A>() {
+ let _: <T>::Assoc$0;
+}
+
+trait A {
+ type Assoc;
+}"#,
+ expect![[r#"
+ *Assoc*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ type Assoc
+ ```
+ "#]],
+ );
+ check(
+ r#"
+trait A where
+ Self::Assoc$0: ,
+{
+ type Assoc;
+}"#,
+ expect![[r#"
+ *Assoc*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ type Assoc
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn string_shadowed_with_inner_items() {
+ check(
+ r#"
+//- /main.rs crate:main deps:alloc
+
+/// Custom `String` type.
+struct String;
+
+fn f() {
+ let _: String$0;
+
+ fn inner() {}
+}
+
+//- /alloc.rs crate:alloc
+#[prelude_import]
+pub use string::*;
+
+mod string {
+ /// This is `alloc::String`.
+ pub struct String;
+}
+"#,
+ expect![[r#"
+ *String*
+
+ ```rust
+ main
+ ```
+
+ ```rust
+ struct String
+ ```
+
+ ---
+
+ Custom `String` type.
+ "#]],
+ )
+}
+
+#[test]
+fn function_doesnt_shadow_crate_in_use_tree() {
+ check(
+ r#"
+//- /main.rs crate:main deps:foo
+use foo$0::{foo};
+
+//- /foo.rs crate:foo
+pub fn foo() {}
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ extern crate foo
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn hover_feature() {
+ check(
+ r#"#![feature(box_syntax$0)]"#,
+ expect![[r##"
+ *box_syntax*
+ ```
+ box_syntax
+ ```
+ ___
+
+ # `box_syntax`
+
+ The tracking issue for this feature is: [#49733]
+
+ [#49733]: https://github.com/rust-lang/rust/issues/49733
+
+ See also [`box_patterns`](box-patterns.md)
+
+ ------------------------
+
+ Currently the only stable way to create a `Box` is via the `Box::new` method.
+ Also it is not possible in stable Rust to destructure a `Box` in a match
+ pattern. The unstable `box` keyword can be used to create a `Box`. An example
+ usage would be:
+
+ ```rust
+ #![feature(box_syntax)]
+
+ fn main() {
+ let b = box 5;
+ }
+ ```
+
+ "##]],
+ )
+}
+
+#[test]
+fn hover_lint() {
+ check(
+ r#"#![allow(arithmetic_overflow$0)]"#,
+ expect![[r#"
+ *arithmetic_overflow*
+ ```
+ arithmetic_overflow
+ ```
+ ___
+
+ arithmetic operation overflows
+ "#]],
+ )
+}
+
+#[test]
+fn hover_clippy_lint() {
+ check(
+ r#"#![allow(clippy::almost_swapped$0)]"#,
+ expect![[r#"
+ *almost_swapped*
+ ```
+ clippy::almost_swapped
+ ```
+ ___
+
+ Checks for `foo = bar; bar = foo` sequences.
+ "#]],
+ )
+}
+
+#[test]
+fn hover_attr_path_qualifier() {
+ check(
+ r#"
+//- /foo.rs crate:foo
+
+//- /lib.rs crate:main.rs deps:foo
+#[fo$0o::bar()]
+struct Foo;
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ extern crate foo
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn hover_rename() {
+ check(
+ r#"
+use self as foo$0;
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ extern crate test
+ ```
+ "#]],
+ );
+ check(
+ r#"
+mod bar {}
+use bar::{self as foo$0};
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ mod bar
+ ```
+ "#]],
+ );
+ check(
+ r#"
+mod bar {
+ use super as foo$0;
+}
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ extern crate test
+ ```
+ "#]],
+ );
+ check(
+ r#"
+use crate as foo$0;
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ extern crate test
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_attribute_in_macro() {
+ check(
+ r#"
+//- minicore:derive
+macro_rules! identity {
+ ($struct:item) => {
+ $struct
+ };
+}
+#[rustc_builtin_macro]
+pub macro Copy {}
+identity!{
+ #[derive(Copy$0)]
+ struct Foo;
+}
+"#,
+ expect![[r#"
+ *Copy*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ macro Copy
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_derive_input() {
+ check(
+ r#"
+//- minicore:derive
+#[rustc_builtin_macro]
+pub macro Copy {}
+#[derive(Copy$0)]
+struct Foo;
+"#,
+ expect![[r#"
+ *Copy*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ macro Copy
+ ```
+ "#]],
+ );
+ check(
+ r#"
+//- minicore:derive
+mod foo {
+ #[rustc_builtin_macro]
+ pub macro Copy {}
+}
+#[derive(foo::Copy$0)]
+struct Foo;
+"#,
+ expect![[r#"
+ *Copy*
+
+ ```rust
+ test::foo
+ ```
+
+ ```rust
+ macro Copy
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_range_math() {
+ check_hover_range(
+ r#"
+fn f() { let expr = $01 + 2 * 3$0 }
+"#,
+ expect![[r#"
+ ```rust
+ i32
+ ```"#]],
+ );
+
+ check_hover_range(
+ r#"
+fn f() { let expr = 1 $0+ 2 * $03 }
+"#,
+ expect![[r#"
+ ```rust
+ i32
+ ```"#]],
+ );
+
+ check_hover_range(
+ r#"
+fn f() { let expr = 1 + $02 * 3$0 }
+"#,
+ expect![[r#"
+ ```rust
+ i32
+ ```"#]],
+ );
+}
+
+#[test]
+fn hover_range_arrays() {
+ check_hover_range(
+ r#"
+fn f() { let expr = $0[1, 2, 3, 4]$0 }
+"#,
+ expect![[r#"
+ ```rust
+ [i32; 4]
+ ```"#]],
+ );
+
+ check_hover_range(
+ r#"
+fn f() { let expr = [1, 2, $03, 4]$0 }
+"#,
+ expect![[r#"
+ ```rust
+ [i32; 4]
+ ```"#]],
+ );
+
+ check_hover_range(
+ r#"
+fn f() { let expr = [1, 2, $03$0, 4] }
+"#,
+ expect![[r#"
+ ```rust
+ i32
+ ```"#]],
+ );
+}
+
+#[test]
+fn hover_range_functions() {
+ check_hover_range(
+ r#"
+fn f<T>(a: &[T]) { }
+fn b() { $0f$0(&[1, 2, 3, 4, 5]); }
+"#,
+ expect![[r#"
+ ```rust
+ fn f<i32>(&[i32])
+ ```"#]],
+ );
+
+ check_hover_range(
+ r#"
+fn f<T>(a: &[T]) { }
+fn b() { f($0&[1, 2, 3, 4, 5]$0); }
+"#,
+ expect![[r#"
+ ```rust
+ &[i32; 5]
+ ```"#]],
+ );
+}
+
+#[test]
+fn hover_range_shows_nothing_when_invalid() {
+ check_hover_range_no_results(
+ r#"
+fn f<T>(a: &[T]) { }
+fn b()$0 { f(&[1, 2, 3, 4, 5]); }$0
+"#,
+ );
+
+ check_hover_range_no_results(
+ r#"
+fn f<T>$0(a: &[T]) { }
+fn b() { f(&[1, 2, 3,$0 4, 5]); }
+"#,
+ );
+
+ check_hover_range_no_results(
+ r#"
+fn $0f() { let expr = [1, 2, 3, 4]$0 }
+"#,
+ );
+}
+
+#[test]
+fn hover_range_shows_unit_for_statements() {
+ check_hover_range(
+ r#"
+fn f<T>(a: &[T]) { }
+fn b() { $0f(&[1, 2, 3, 4, 5]); }$0
+"#,
+ expect![[r#"
+ ```rust
+ ()
+ ```"#]],
+ );
+
+ check_hover_range(
+ r#"
+fn f() { let expr$0 = $0[1, 2, 3, 4] }
+"#,
+ expect![[r#"
+ ```rust
+ ()
+ ```"#]],
+ );
+}
+
+#[test]
+fn hover_range_for_pat() {
+ check_hover_range(
+ r#"
+fn foo() {
+ let $0x$0 = 0;
+}
+"#,
+ expect![[r#"
+ ```rust
+ i32
+ ```"#]],
+ );
+
+ check_hover_range(
+ r#"
+fn foo() {
+ let $0x$0 = "";
+}
+"#,
+ expect![[r#"
+ ```rust
+ &str
+ ```"#]],
+ );
+}
+
+#[test]
+fn hover_range_shows_coercions_if_applicable_expr() {
+ check_hover_range(
+ r#"
+fn foo() {
+ let x: &u32 = $0&&&&&0$0;
+}
+"#,
+ expect![[r#"
+ ```text
+ Type: &&&&&u32
+ Coerced to: &u32
+ ```
+ "#]],
+ );
+ check_hover_range(
+ r#"
+fn foo() {
+ let x: *const u32 = $0&0$0;
+}
+"#,
+ expect![[r#"
+ ```text
+ Type: &u32
+ Coerced to: *const u32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_range_shows_type_actions() {
+ check_actions(
+ r#"
+struct Foo;
+fn foo() {
+ let x: &Foo = $0&&&&&Foo$0;
+}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..11,
+ focus_range: 7..10,
+ name: "Foo",
+ kind: Struct,
+ description: "struct Foo",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn hover_try_expr_res() {
+ check_hover_range(
+ r#"
+//- minicore:result
+struct FooError;
+
+fn foo() -> Result<(), FooError> {
+ Ok($0Result::<(), FooError>::Ok(())?$0)
+}
+"#,
+ expect![[r#"
+ ```rust
+ ()
+ ```"#]],
+ );
+ check_hover_range(
+ r#"
+//- minicore:result
+struct FooError;
+struct BarError;
+
+fn foo() -> Result<(), FooError> {
+ Ok($0Result::<(), BarError>::Ok(())?$0)
+}
+"#,
+ expect![[r#"
+ ```text
+ Try Error Type: BarError
+ Propagated as: FooError
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_try_expr() {
+ check_hover_range(
+ r#"
+struct NotResult<T, U>(T, U);
+struct Short;
+struct Looooong;
+
+fn foo() -> NotResult<(), Looooong> {
+ $0NotResult((), Short)?$0;
+}
+"#,
+ expect![[r#"
+ ```text
+ Try Target Type: NotResult<(), Short>
+ Propagated as: NotResult<(), Looooong>
+ ```
+ "#]],
+ );
+ check_hover_range(
+ r#"
+struct NotResult<T, U>(T, U);
+struct Short;
+struct Looooong;
+
+fn foo() -> NotResult<(), Short> {
+ $0NotResult((), Looooong)?$0;
+}
+"#,
+ expect![[r#"
+ ```text
+ Try Target Type: NotResult<(), Looooong>
+ Propagated as: NotResult<(), Short>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_try_expr_option() {
+ cov_mark::check!(hover_try_expr_opt_opt);
+ check_hover_range(
+ r#"
+//- minicore: option, try
+
+fn foo() -> Option<()> {
+ $0Some(0)?$0;
+ None
+}
+"#,
+ expect![[r#"
+ ```rust
+ <Option<i32> as Try>::Output
+ ```"#]],
+ );
+}
+
+#[test]
+fn hover_deref_expr() {
+ check_hover_range(
+ r#"
+//- minicore: deref
+use core::ops::Deref;
+
+struct DerefExample<T> {
+ value: T
+}
+
+impl<T> Deref for DerefExample<T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.value
+ }
+}
+
+fn foo() {
+ let x = DerefExample { value: 0 };
+ let y: i32 = $0*x$0;
+}
+"#,
+ expect![[r#"
+ ```text
+ Dereferenced from: DerefExample<i32>
+ To type: i32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_deref_expr_with_coercion() {
+ check_hover_range(
+ r#"
+//- minicore: deref
+use core::ops::Deref;
+
+struct DerefExample<T> {
+ value: T
+}
+
+impl<T> Deref for DerefExample<T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.value
+ }
+}
+
+fn foo() {
+ let x = DerefExample { value: &&&&&0 };
+ let y: &i32 = $0*x$0;
+}
+"#,
+ expect![[r#"
+ ```text
+ Dereferenced from: DerefExample<&&&&&i32>
+ To type: &&&&&i32
+ Coerced to: &i32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_intra_in_macro() {
+ check(
+ r#"
+macro_rules! foo_macro {
+ ($(#[$attr:meta])* $name:ident) => {
+ $(#[$attr])*
+ pub struct $name;
+ }
+}
+
+foo_macro!(
+ /// Doc comment for [`Foo$0`]
+ Foo
+);
+"#,
+ expect![[r#"
+ *[`Foo`]*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub struct Foo
+ ```
+
+ ---
+
+ Doc comment for [`Foo`](https://docs.rs/test/*/test/struct.Foo.html)
+ "#]],
+ );
+}
+
+#[test]
+fn hover_intra_in_attr() {
+ check(
+ r#"
+#[doc = "Doc comment for [`Foo$0`]"]
+pub struct Foo;
+"#,
+ expect![[r#"
+ *[`Foo`]*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub struct Foo
+ ```
+
+ ---
+
+ Doc comment for [`Foo`](https://docs.rs/test/*/test/struct.Foo.html)
+ "#]],
+ );
+}
+
+#[test]
+fn hover_inert_attr() {
+ check(
+ r#"
+#[doc$0 = ""]
+pub struct Foo;
+"#,
+ expect![[r##"
+ *doc*
+
+ ```rust
+ #[doc]
+ ```
+
+ ---
+
+ Valid forms are:
+
+ * \#\[doc(hidden|inline|...)\]
+ * \#\[doc = string\]
+ "##]],
+ );
+ check(
+ r#"
+#[allow$0()]
+pub struct Foo;
+"#,
+ expect![[r##"
+ *allow*
+
+ ```rust
+ #[allow]
+ ```
+
+ ---
+
+ Valid forms are:
+
+ * \#\[allow(lint1, lint2, ..., /\*opt\*/ reason = "...")\]
+ "##]],
+ );
+}
+
+#[test]
+fn hover_dollar_crate() {
+ // $crate should be resolved to the right crate name.
+
+ check(
+ r#"
+//- /main.rs crate:main deps:dep
+dep::m!(KONST$0);
+//- /dep.rs crate:dep
+#[macro_export]
+macro_rules! m {
+ ( $name:ident ) => { const $name: $crate::Type = $crate::Type; };
+}
+
+pub struct Type;
+"#,
+ expect![[r#"
+ *KONST*
+
+ ```rust
+ main
+ ```
+
+ ```rust
+ const KONST: dep::Type = $crate::Type
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_record_variant() {
+ check(
+ r#"
+enum Enum {
+ RecordV$0 { field: u32 }
+}
+"#,
+ expect![[r#"
+ *RecordV*
+
+ ```rust
+ test::Enum
+ ```
+
+ ```rust
+ RecordV { field: u32 }
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_trait_impl_assoc_item_def_doc_forwarding() {
+ check(
+ r#"
+trait T {
+ /// Trait docs
+ fn func() {}
+}
+impl T for () {
+ fn func$0() {}
+}
+"#,
+ expect![[r#"
+ *func*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn func()
+ ```
+
+ ---
+
+ Trait docs
+ "#]],
+ );
+}
+
+#[test]
+fn hover_ranged_macro_call() {
+ check_hover_range(
+ r#"
+macro_rules! __rust_force_expr {
+ ($e:expr) => {
+ $e
+ };
+}
+macro_rules! vec {
+ ($elem:expr) => {
+ __rust_force_expr!($elem)
+ };
+}
+
+struct Struct;
+impl Struct {
+ fn foo(self) {}
+}
+
+fn f() {
+ $0vec![Struct]$0;
+}
+"#,
+ expect![[r#"
+ ```rust
+ Struct
+ ```"#]],
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs
new file mode 100644
index 000000000..5aae669aa
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs
@@ -0,0 +1,2818 @@
+use either::Either;
+use hir::{known, Callable, HasVisibility, HirDisplay, Mutability, Semantics, TypeInfo};
+use ide_db::{
+ base_db::FileRange, famous_defs::FamousDefs, syntax_helpers::node_ext::walk_ty, FxHashMap,
+ RootDatabase,
+};
+use itertools::Itertools;
+use stdx::to_lower_snake_case;
+use syntax::{
+ ast::{self, AstNode, HasArgList, HasGenericParams, HasName, UnaryOp},
+ match_ast, Direction, NodeOrToken, SmolStr, SyntaxKind, SyntaxNode, SyntaxToken, TextRange,
+ TextSize, T,
+};
+
+use crate::FileId;
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct InlayHintsConfig {
+ pub render_colons: bool,
+ pub type_hints: bool,
+ pub parameter_hints: bool,
+ pub chaining_hints: bool,
+ pub reborrow_hints: ReborrowHints,
+ pub closure_return_type_hints: ClosureReturnTypeHints,
+ pub binding_mode_hints: bool,
+ pub lifetime_elision_hints: LifetimeElisionHints,
+ pub param_names_for_lifetime_elision_hints: bool,
+ pub hide_named_constructor_hints: bool,
+ pub hide_closure_initialization_hints: bool,
+ pub max_length: Option<usize>,
+ pub closing_brace_hints_min_lines: Option<usize>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum ClosureReturnTypeHints {
+ Always,
+ WithBlock,
+ Never,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum LifetimeElisionHints {
+ Always,
+ SkipTrivial,
+ Never,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum ReborrowHints {
+ Always,
+ MutableOnly,
+ Never,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum InlayKind {
+ BindingModeHint,
+ ChainingHint,
+ ClosingBraceHint,
+ ClosureReturnTypeHint,
+ GenericParamListHint,
+ ImplicitReborrowHint,
+ LifetimeHint,
+ ParameterHint,
+ TypeHint,
+}
+
+#[derive(Debug)]
+pub struct InlayHint {
+ pub range: TextRange,
+ pub kind: InlayKind,
+ pub label: String,
+ pub tooltip: Option<InlayTooltip>,
+}
+
+#[derive(Debug)]
+pub enum InlayTooltip {
+ String(String),
+ HoverRanged(FileId, TextRange),
+ HoverOffset(FileId, TextSize),
+}
+
+// Feature: Inlay Hints
+//
+// rust-analyzer shows additional information inline with the source code.
+// Editors usually render this using read-only virtual text snippets interspersed with code.
+//
+// rust-analyzer by default shows hints for
+//
+// * types of local variables
+// * names of function arguments
+// * types of chained expressions
+//
+// Optionally, one can enable additional hints for
+//
+// * return types of closure expressions
+// * elided lifetimes
+// * compiler inserted reborrows
+//
+// |===
+// | Editor | Action Name
+//
+// | VS Code | **Rust Analyzer: Toggle inlay hints*
+// |===
+//
+// image::https://user-images.githubusercontent.com/48062697/113020660-b5f98b80-917a-11eb-8d70-3be3fd558cdd.png[]
+pub(crate) fn inlay_hints(
+ db: &RootDatabase,
+ file_id: FileId,
+ range_limit: Option<FileRange>,
+ config: &InlayHintsConfig,
+) -> Vec<InlayHint> {
+ let _p = profile::span("inlay_hints");
+ let sema = Semantics::new(db);
+ let file = sema.parse(file_id);
+ let file = file.syntax();
+
+ let mut acc = Vec::new();
+
+ if let Some(scope) = sema.scope(&file) {
+ let famous_defs = FamousDefs(&sema, scope.krate());
+
+ let hints = |node| hints(&mut acc, &famous_defs, config, file_id, node);
+ match range_limit {
+ Some(FileRange { range, .. }) => match file.covering_element(range) {
+ NodeOrToken::Token(_) => return acc,
+ NodeOrToken::Node(n) => n
+ .descendants()
+ .filter(|descendant| range.intersect(descendant.text_range()).is_some())
+ .for_each(hints),
+ },
+ None => file.descendants().for_each(hints),
+ };
+ }
+
+ acc
+}
+
+fn hints(
+ hints: &mut Vec<InlayHint>,
+ famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
+ config: &InlayHintsConfig,
+ file_id: FileId,
+ node: SyntaxNode,
+) {
+ closing_brace_hints(hints, sema, config, file_id, node.clone());
+ match_ast! {
+ match node {
+ ast::Expr(expr) => {
+ chaining_hints(hints, sema, &famous_defs, config, file_id, &expr);
+ match expr {
+ ast::Expr::CallExpr(it) => param_name_hints(hints, sema, config, ast::Expr::from(it)),
+ ast::Expr::MethodCallExpr(it) => {
+ param_name_hints(hints, sema, config, ast::Expr::from(it))
+ }
+ ast::Expr::ClosureExpr(it) => closure_ret_hints(hints, sema, &famous_defs, config, file_id, it),
+ // We could show reborrows for all expressions, but usually that is just noise to the user
+ // and the main point here is to show why "moving" a mutable reference doesn't necessarily move it
+ ast::Expr::PathExpr(_) => reborrow_hints(hints, sema, config, &expr),
+ _ => None,
+ }
+ },
+ ast::Pat(it) => {
+ binding_mode_hints(hints, sema, config, &it);
+ if let ast::Pat::IdentPat(it) = it {
+ bind_pat_hints(hints, sema, config, file_id, &it);
+ }
+ Some(())
+ },
+ ast::Item(it) => match it {
+ // FIXME: record impl lifetimes so they aren't being reused in assoc item lifetime inlay hints
+ ast::Item::Impl(_) => None,
+ ast::Item::Fn(it) => fn_lifetime_fn_hints(hints, config, it),
+ // static type elisions
+ ast::Item::Static(it) => implicit_static_hints(hints, config, Either::Left(it)),
+ ast::Item::Const(it) => implicit_static_hints(hints, config, Either::Right(it)),
+ _ => None,
+ },
+ // FIXME: fn-ptr type, dyn fn type, and trait object type elisions
+ ast::Type(_) => None,
+ _ => None,
+ }
+ };
+}
+
+fn closing_brace_hints(
+ acc: &mut Vec<InlayHint>,
+ sema: &Semantics<'_, RootDatabase>,
+ config: &InlayHintsConfig,
+ file_id: FileId,
+ node: SyntaxNode,
+) -> Option<()> {
+ let min_lines = config.closing_brace_hints_min_lines?;
+
+ let name = |it: ast::Name| it.syntax().text_range().start();
+
+ let mut closing_token;
+ let (label, name_offset) = if let Some(item_list) = ast::AssocItemList::cast(node.clone()) {
+ closing_token = item_list.r_curly_token()?;
+
+ let parent = item_list.syntax().parent()?;
+ match_ast! {
+ match parent {
+ ast::Impl(imp) => {
+ let imp = sema.to_def(&imp)?;
+ let ty = imp.self_ty(sema.db);
+ let trait_ = imp.trait_(sema.db);
+
+ (match trait_ {
+ Some(tr) => format!("impl {} for {}", tr.name(sema.db), ty.display_truncated(sema.db, config.max_length)),
+ None => format!("impl {}", ty.display_truncated(sema.db, config.max_length)),
+ }, None)
+ },
+ ast::Trait(tr) => {
+ (format!("trait {}", tr.name()?), tr.name().map(name))
+ },
+ _ => return None,
+ }
+ }
+ } else if let Some(list) = ast::ItemList::cast(node.clone()) {
+ closing_token = list.r_curly_token()?;
+
+ let module = ast::Module::cast(list.syntax().parent()?)?;
+ (format!("mod {}", module.name()?), module.name().map(name))
+ } else if let Some(block) = ast::BlockExpr::cast(node.clone()) {
+ closing_token = block.stmt_list()?.r_curly_token()?;
+
+ let parent = block.syntax().parent()?;
+ match_ast! {
+ match parent {
+ ast::Fn(it) => {
+ // FIXME: this could include parameters, but `HirDisplay` prints too much info
+ // and doesn't respect the max length either, so the hints end up way too long
+ (format!("fn {}", it.name()?), it.name().map(name))
+ },
+ ast::Static(it) => (format!("static {}", it.name()?), it.name().map(name)),
+ ast::Const(it) => {
+ if it.underscore_token().is_some() {
+ ("const _".into(), None)
+ } else {
+ (format!("const {}", it.name()?), it.name().map(name))
+ }
+ },
+ _ => return None,
+ }
+ }
+ } else if let Some(mac) = ast::MacroCall::cast(node.clone()) {
+ let last_token = mac.syntax().last_token()?;
+ if last_token.kind() != T![;] && last_token.kind() != SyntaxKind::R_CURLY {
+ return None;
+ }
+ closing_token = last_token;
+
+ (
+ format!("{}!", mac.path()?),
+ mac.path().and_then(|it| it.segment()).map(|it| it.syntax().text_range().start()),
+ )
+ } else {
+ return None;
+ };
+
+ if let Some(mut next) = closing_token.next_token() {
+ if next.kind() == T![;] {
+ if let Some(tok) = next.next_token() {
+ closing_token = next;
+ next = tok;
+ }
+ }
+ if !(next.kind() == SyntaxKind::WHITESPACE && next.text().contains('\n')) {
+ // Only display the hint if the `}` is the last token on the line
+ return None;
+ }
+ }
+
+ let mut lines = 1;
+ node.text().for_each_chunk(|s| lines += s.matches('\n').count());
+ if lines < min_lines {
+ return None;
+ }
+
+ acc.push(InlayHint {
+ range: closing_token.text_range(),
+ kind: InlayKind::ClosingBraceHint,
+ label,
+ tooltip: name_offset.map(|it| InlayTooltip::HoverOffset(file_id, it)),
+ });
+
+ None
+}
+
+fn implicit_static_hints(
+ acc: &mut Vec<InlayHint>,
+ config: &InlayHintsConfig,
+ statik_or_const: Either<ast::Static, ast::Const>,
+) -> Option<()> {
+ if config.lifetime_elision_hints != LifetimeElisionHints::Always {
+ return None;
+ }
+
+ if let Either::Right(it) = &statik_or_const {
+ if ast::AssocItemList::can_cast(
+ it.syntax().parent().map_or(SyntaxKind::EOF, |it| it.kind()),
+ ) {
+ return None;
+ }
+ }
+
+ if let Some(ast::Type::RefType(ty)) = statik_or_const.either(|it| it.ty(), |it| it.ty()) {
+ if ty.lifetime().is_none() {
+ let t = ty.amp_token()?;
+ acc.push(InlayHint {
+ range: t.text_range(),
+ kind: InlayKind::LifetimeHint,
+ label: "'static".to_owned(),
+ tooltip: Some(InlayTooltip::String("Elided static lifetime".into())),
+ });
+ }
+ }
+
+ Some(())
+}
+
+fn fn_lifetime_fn_hints(
+ acc: &mut Vec<InlayHint>,
+ config: &InlayHintsConfig,
+ func: ast::Fn,
+) -> Option<()> {
+ if config.lifetime_elision_hints == LifetimeElisionHints::Never {
+ return None;
+ }
+
+ let mk_lt_hint = |t: SyntaxToken, label| InlayHint {
+ range: t.text_range(),
+ kind: InlayKind::LifetimeHint,
+ label,
+ tooltip: Some(InlayTooltip::String("Elided lifetime".into())),
+ };
+
+ let param_list = func.param_list()?;
+ let generic_param_list = func.generic_param_list();
+ let ret_type = func.ret_type();
+ let self_param = param_list.self_param().filter(|it| it.amp_token().is_some());
+
+ let is_elided = |lt: &Option<ast::Lifetime>| match lt {
+ Some(lt) => matches!(lt.text().as_str(), "'_"),
+ None => true,
+ };
+
+ let potential_lt_refs = {
+ let mut acc: Vec<_> = vec![];
+ if let Some(self_param) = &self_param {
+ let lifetime = self_param.lifetime();
+ let is_elided = is_elided(&lifetime);
+ acc.push((None, self_param.amp_token(), lifetime, is_elided));
+ }
+ param_list.params().filter_map(|it| Some((it.pat(), it.ty()?))).for_each(|(pat, ty)| {
+ // FIXME: check path types
+ walk_ty(&ty, &mut |ty| match ty {
+ ast::Type::RefType(r) => {
+ let lifetime = r.lifetime();
+ let is_elided = is_elided(&lifetime);
+ acc.push((
+ pat.as_ref().and_then(|it| match it {
+ ast::Pat::IdentPat(p) => p.name(),
+ _ => None,
+ }),
+ r.amp_token(),
+ lifetime,
+ is_elided,
+ ))
+ }
+ _ => (),
+ })
+ });
+ acc
+ };
+
+ // allocate names
+ let mut gen_idx_name = {
+ let mut gen = (0u8..).map(|idx| match idx {
+ idx if idx < 10 => SmolStr::from_iter(['\'', (idx + 48) as char]),
+ idx => format!("'{idx}").into(),
+ });
+ move || gen.next().unwrap_or_default()
+ };
+ let mut allocated_lifetimes = vec![];
+
+ let mut used_names: FxHashMap<SmolStr, usize> =
+ match config.param_names_for_lifetime_elision_hints {
+ true => generic_param_list
+ .iter()
+ .flat_map(|gpl| gpl.lifetime_params())
+ .filter_map(|param| param.lifetime())
+ .filter_map(|lt| Some((SmolStr::from(lt.text().as_str().get(1..)?), 0)))
+ .collect(),
+ false => Default::default(),
+ };
+ {
+ let mut potential_lt_refs = potential_lt_refs.iter().filter(|&&(.., is_elided)| is_elided);
+ if let Some(_) = &self_param {
+ if let Some(_) = potential_lt_refs.next() {
+ allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints {
+ // self can't be used as a lifetime, so no need to check for collisions
+ "'self".into()
+ } else {
+ gen_idx_name()
+ });
+ }
+ }
+ potential_lt_refs.for_each(|(name, ..)| {
+ let name = match name {
+ Some(it) if config.param_names_for_lifetime_elision_hints => {
+ if let Some(c) = used_names.get_mut(it.text().as_str()) {
+ *c += 1;
+ SmolStr::from(format!("'{text}{c}", text = it.text().as_str()))
+ } else {
+ used_names.insert(it.text().as_str().into(), 0);
+ SmolStr::from_iter(["\'", it.text().as_str()])
+ }
+ }
+ _ => gen_idx_name(),
+ };
+ allocated_lifetimes.push(name);
+ });
+ }
+
+ // fetch output lifetime if elision rule applies
+ let output = match potential_lt_refs.as_slice() {
+ [(_, _, lifetime, _), ..] if self_param.is_some() || potential_lt_refs.len() == 1 => {
+ match lifetime {
+ Some(lt) => match lt.text().as_str() {
+ "'_" => allocated_lifetimes.get(0).cloned(),
+ "'static" => None,
+ name => Some(name.into()),
+ },
+ None => allocated_lifetimes.get(0).cloned(),
+ }
+ }
+ [..] => None,
+ };
+
+ if allocated_lifetimes.is_empty() && output.is_none() {
+ return None;
+ }
+
+ // apply hints
+ // apply output if required
+ let mut is_trivial = true;
+ if let (Some(output_lt), Some(r)) = (&output, ret_type) {
+ if let Some(ty) = r.ty() {
+ walk_ty(&ty, &mut |ty| match ty {
+ ast::Type::RefType(ty) if ty.lifetime().is_none() => {
+ if let Some(amp) = ty.amp_token() {
+ is_trivial = false;
+ acc.push(mk_lt_hint(amp, output_lt.to_string()));
+ }
+ }
+ _ => (),
+ })
+ }
+ }
+
+ if config.lifetime_elision_hints == LifetimeElisionHints::SkipTrivial && is_trivial {
+ return None;
+ }
+
+ let mut a = allocated_lifetimes.iter();
+ for (_, amp_token, _, is_elided) in potential_lt_refs {
+ if is_elided {
+ let t = amp_token?;
+ let lt = a.next()?;
+ acc.push(mk_lt_hint(t, lt.to_string()));
+ }
+ }
+
+ // generate generic param list things
+ match (generic_param_list, allocated_lifetimes.as_slice()) {
+ (_, []) => (),
+ (Some(gpl), allocated_lifetimes) => {
+ let angle_tok = gpl.l_angle_token()?;
+ let is_empty = gpl.generic_params().next().is_none();
+ acc.push(InlayHint {
+ range: angle_tok.text_range(),
+ kind: InlayKind::LifetimeHint,
+ label: format!(
+ "{}{}",
+ allocated_lifetimes.iter().format(", "),
+ if is_empty { "" } else { ", " }
+ ),
+ tooltip: Some(InlayTooltip::String("Elided lifetimes".into())),
+ });
+ }
+ (None, allocated_lifetimes) => acc.push(InlayHint {
+ range: func.name()?.syntax().text_range(),
+ kind: InlayKind::GenericParamListHint,
+ label: format!("<{}>", allocated_lifetimes.iter().format(", "),).into(),
+ tooltip: Some(InlayTooltip::String("Elided lifetimes".into())),
+ }),
+ }
+ Some(())
+}
+
+fn closure_ret_hints(
+ acc: &mut Vec<InlayHint>,
+ sema: &Semantics<'_, RootDatabase>,
+ famous_defs: &FamousDefs<'_, '_>,
+ config: &InlayHintsConfig,
+ file_id: FileId,
+ closure: ast::ClosureExpr,
+) -> Option<()> {
+ if config.closure_return_type_hints == ClosureReturnTypeHints::Never {
+ return None;
+ }
+
+ if closure.ret_type().is_some() {
+ return None;
+ }
+
+ if !closure_has_block_body(&closure)
+ && config.closure_return_type_hints == ClosureReturnTypeHints::WithBlock
+ {
+ return None;
+ }
+
+ let param_list = closure.param_list()?;
+
+ let closure = sema.descend_node_into_attributes(closure.clone()).pop()?;
+ let ty = sema.type_of_expr(&ast::Expr::ClosureExpr(closure))?.adjusted();
+ let callable = ty.as_callable(sema.db)?;
+ let ty = callable.return_type();
+ if ty.is_unit() {
+ return None;
+ }
+ acc.push(InlayHint {
+ range: param_list.syntax().text_range(),
+ kind: InlayKind::ClosureReturnTypeHint,
+ label: hint_iterator(sema, &famous_defs, config, &ty)
+ .unwrap_or_else(|| ty.display_truncated(sema.db, config.max_length).to_string()),
+ tooltip: Some(InlayTooltip::HoverRanged(file_id, param_list.syntax().text_range())),
+ });
+ Some(())
+}
+
+fn reborrow_hints(
+ acc: &mut Vec<InlayHint>,
+ sema: &Semantics<'_, RootDatabase>,
+ config: &InlayHintsConfig,
+ expr: &ast::Expr,
+) -> Option<()> {
+ if config.reborrow_hints == ReborrowHints::Never {
+ return None;
+ }
+
+ let descended = sema.descend_node_into_attributes(expr.clone()).pop();
+ let desc_expr = descended.as_ref().unwrap_or(expr);
+ let mutability = sema.is_implicit_reborrow(desc_expr)?;
+ let label = match mutability {
+ hir::Mutability::Shared if config.reborrow_hints != ReborrowHints::MutableOnly => "&*",
+ hir::Mutability::Mut => "&mut *",
+ _ => return None,
+ };
+ acc.push(InlayHint {
+ range: expr.syntax().text_range(),
+ kind: InlayKind::ImplicitReborrowHint,
+ label: label.to_string(),
+ tooltip: Some(InlayTooltip::String("Compiler inserted reborrow".into())),
+ });
+ Some(())
+}
+
+fn chaining_hints(
+ acc: &mut Vec<InlayHint>,
+ sema: &Semantics<'_, RootDatabase>,
+ famous_defs: &FamousDefs<'_, '_>,
+ config: &InlayHintsConfig,
+ file_id: FileId,
+ expr: &ast::Expr,
+) -> Option<()> {
+ if !config.chaining_hints {
+ return None;
+ }
+
+ if matches!(expr, ast::Expr::RecordExpr(_)) {
+ return None;
+ }
+
+ let descended = sema.descend_node_into_attributes(expr.clone()).pop();
+ let desc_expr = descended.as_ref().unwrap_or(expr);
+
+ let mut tokens = expr
+ .syntax()
+ .siblings_with_tokens(Direction::Next)
+ .filter_map(NodeOrToken::into_token)
+ .filter(|t| match t.kind() {
+ SyntaxKind::WHITESPACE if !t.text().contains('\n') => false,
+ SyntaxKind::COMMENT => false,
+ _ => true,
+ });
+
+ // Chaining can be defined as an expression whose next sibling tokens are newline and dot
+ // Ignoring extra whitespace and comments
+ let next = tokens.next()?.kind();
+ if next == SyntaxKind::WHITESPACE {
+ let mut next_next = tokens.next()?.kind();
+ while next_next == SyntaxKind::WHITESPACE {
+ next_next = tokens.next()?.kind();
+ }
+ if next_next == T![.] {
+ let ty = sema.type_of_expr(desc_expr)?.original;
+ if ty.is_unknown() {
+ return None;
+ }
+ if matches!(expr, ast::Expr::PathExpr(_)) {
+ if let Some(hir::Adt::Struct(st)) = ty.as_adt() {
+ if st.fields(sema.db).is_empty() {
+ return None;
+ }
+ }
+ }
+ acc.push(InlayHint {
+ range: expr.syntax().text_range(),
+ kind: InlayKind::ChainingHint,
+ label: hint_iterator(sema, &famous_defs, config, &ty).unwrap_or_else(|| {
+ ty.display_truncated(sema.db, config.max_length).to_string()
+ }),
+ tooltip: Some(InlayTooltip::HoverRanged(file_id, expr.syntax().text_range())),
+ });
+ }
+ }
+ Some(())
+}
+
+fn param_name_hints(
+ acc: &mut Vec<InlayHint>,
+ sema: &Semantics<'_, RootDatabase>,
+ config: &InlayHintsConfig,
+ expr: ast::Expr,
+) -> Option<()> {
+ if !config.parameter_hints {
+ return None;
+ }
+
+ let (callable, arg_list) = get_callable(sema, &expr)?;
+ let hints = callable
+ .params(sema.db)
+ .into_iter()
+ .zip(arg_list.args())
+ .filter_map(|((param, _ty), arg)| {
+ // Only annotate hints for expressions that exist in the original file
+ let range = sema.original_range_opt(arg.syntax())?;
+ let (param_name, name_syntax) = match param.as_ref()? {
+ Either::Left(pat) => ("self".to_string(), pat.name()),
+ Either::Right(pat) => match pat {
+ ast::Pat::IdentPat(it) => (it.name()?.to_string(), it.name()),
+ _ => return None,
+ },
+ };
+ Some((name_syntax, param_name, arg, range))
+ })
+ .filter(|(_, param_name, arg, _)| {
+ !should_hide_param_name_hint(sema, &callable, param_name, arg)
+ })
+ .map(|(param, param_name, _, FileRange { range, .. })| {
+ let mut tooltip = None;
+ if let Some(name) = param {
+ if let hir::CallableKind::Function(f) = callable.kind() {
+ // assert the file is cached so we can map out of macros
+ if let Some(_) = sema.source(f) {
+ tooltip = sema.original_range_opt(name.syntax());
+ }
+ }
+ }
+
+ InlayHint {
+ range,
+ kind: InlayKind::ParameterHint,
+ label: param_name,
+ tooltip: tooltip.map(|it| InlayTooltip::HoverOffset(it.file_id, it.range.start())),
+ }
+ });
+
+ acc.extend(hints);
+ Some(())
+}
+
+fn binding_mode_hints(
+ acc: &mut Vec<InlayHint>,
+ sema: &Semantics<'_, RootDatabase>,
+ config: &InlayHintsConfig,
+ pat: &ast::Pat,
+) -> Option<()> {
+ if !config.binding_mode_hints {
+ return None;
+ }
+
+ let range = pat.syntax().text_range();
+ sema.pattern_adjustments(&pat).iter().for_each(|ty| {
+ let reference = ty.is_reference();
+ let mut_reference = ty.is_mutable_reference();
+ let r = match (reference, mut_reference) {
+ (true, true) => "&mut",
+ (true, false) => "&",
+ _ => return,
+ };
+ acc.push(InlayHint {
+ range,
+ kind: InlayKind::BindingModeHint,
+ label: r.to_string(),
+ tooltip: Some(InlayTooltip::String("Inferred binding mode".into())),
+ });
+ });
+ match pat {
+ ast::Pat::IdentPat(pat) if pat.ref_token().is_none() && pat.mut_token().is_none() => {
+ let bm = sema.binding_mode_of_pat(pat)?;
+ let bm = match bm {
+ hir::BindingMode::Move => return None,
+ hir::BindingMode::Ref(Mutability::Mut) => "ref mut",
+ hir::BindingMode::Ref(Mutability::Shared) => "ref",
+ };
+ acc.push(InlayHint {
+ range,
+ kind: InlayKind::BindingModeHint,
+ label: bm.to_string(),
+ tooltip: Some(InlayTooltip::String("Inferred binding mode".into())),
+ });
+ }
+ _ => (),
+ }
+
+ Some(())
+}
+
+fn bind_pat_hints(
+ acc: &mut Vec<InlayHint>,
+ sema: &Semantics<'_, RootDatabase>,
+ config: &InlayHintsConfig,
+ file_id: FileId,
+ pat: &ast::IdentPat,
+) -> Option<()> {
+ if !config.type_hints {
+ return None;
+ }
+
+ let descended = sema.descend_node_into_attributes(pat.clone()).pop();
+ let desc_pat = descended.as_ref().unwrap_or(pat);
+ let ty = sema.type_of_pat(&desc_pat.clone().into())?.original;
+
+ if should_not_display_type_hint(sema, config, pat, &ty) {
+ return None;
+ }
+
+ let krate = sema.scope(desc_pat.syntax())?.krate();
+ let famous_defs = FamousDefs(sema, krate);
+ let label = hint_iterator(sema, &famous_defs, config, &ty);
+
+ let label = match label {
+ Some(label) => label,
+ None => {
+ let ty_name = ty.display_truncated(sema.db, config.max_length).to_string();
+ if config.hide_named_constructor_hints
+ && is_named_constructor(sema, pat, &ty_name).is_some()
+ {
+ return None;
+ }
+ ty_name
+ }
+ };
+
+ acc.push(InlayHint {
+ range: match pat.name() {
+ Some(name) => name.syntax().text_range(),
+ None => pat.syntax().text_range(),
+ },
+ kind: InlayKind::TypeHint,
+ label,
+ tooltip: pat
+ .name()
+ .map(|it| it.syntax().text_range())
+ .map(|it| InlayTooltip::HoverRanged(file_id, it)),
+ });
+
+ Some(())
+}
+
+fn is_named_constructor(
+ sema: &Semantics<'_, RootDatabase>,
+ pat: &ast::IdentPat,
+ ty_name: &str,
+) -> Option<()> {
+ let let_node = pat.syntax().parent()?;
+ let expr = match_ast! {
+ match let_node {
+ ast::LetStmt(it) => it.initializer(),
+ ast::LetExpr(it) => it.expr(),
+ _ => None,
+ }
+ }?;
+
+ let expr = sema.descend_node_into_attributes(expr.clone()).pop().unwrap_or(expr);
+ // unwrap postfix expressions
+ let expr = match expr {
+ ast::Expr::TryExpr(it) => it.expr(),
+ ast::Expr::AwaitExpr(it) => it.expr(),
+ expr => Some(expr),
+ }?;
+ let expr = match expr {
+ ast::Expr::CallExpr(call) => match call.expr()? {
+ ast::Expr::PathExpr(path) => path,
+ _ => return None,
+ },
+ ast::Expr::PathExpr(path) => path,
+ _ => return None,
+ };
+ let path = expr.path()?;
+
+ let callable = sema.type_of_expr(&ast::Expr::PathExpr(expr))?.original.as_callable(sema.db);
+ let callable_kind = callable.map(|it| it.kind());
+ let qual_seg = match callable_kind {
+ Some(hir::CallableKind::Function(_) | hir::CallableKind::TupleEnumVariant(_)) => {
+ path.qualifier()?.segment()
+ }
+ _ => path.segment(),
+ }?;
+
+ let ctor_name = match qual_seg.kind()? {
+ ast::PathSegmentKind::Name(name_ref) => {
+ match qual_seg.generic_arg_list().map(|it| it.generic_args()) {
+ Some(generics) => format!("{}<{}>", name_ref, generics.format(", ")),
+ None => name_ref.to_string(),
+ }
+ }
+ ast::PathSegmentKind::Type { type_ref: Some(ty), trait_ref: None } => ty.to_string(),
+ _ => return None,
+ };
+ (ctor_name == ty_name).then(|| ())
+}
+
+/// Checks if the type is an Iterator from std::iter and replaces its hint with an `impl Iterator<Item = Ty>`.
+fn hint_iterator(
+ sema: &Semantics<'_, RootDatabase>,
+ famous_defs: &FamousDefs<'_, '_>,
+ config: &InlayHintsConfig,
+ ty: &hir::Type,
+) -> Option<String> {
+ let db = sema.db;
+ let strukt = ty.strip_references().as_adt()?;
+ let krate = strukt.module(db).krate();
+ if krate != famous_defs.core()? {
+ return None;
+ }
+ let iter_trait = famous_defs.core_iter_Iterator()?;
+ let iter_mod = famous_defs.core_iter()?;
+
+ // Assert that this struct comes from `core::iter`.
+ if !(strukt.visibility(db) == hir::Visibility::Public
+ && strukt.module(db).path_to_root(db).contains(&iter_mod))
+ {
+ return None;
+ }
+
+ if ty.impls_trait(db, iter_trait, &[]) {
+ let assoc_type_item = iter_trait.items(db).into_iter().find_map(|item| match item {
+ hir::AssocItem::TypeAlias(alias) if alias.name(db) == known::Item => Some(alias),
+ _ => None,
+ })?;
+ if let Some(ty) = ty.normalize_trait_assoc_type(db, &[], assoc_type_item) {
+ const LABEL_START: &str = "impl Iterator<Item = ";
+ const LABEL_END: &str = ">";
+
+ let ty_display = hint_iterator(sema, famous_defs, config, &ty)
+ .map(|assoc_type_impl| assoc_type_impl.to_string())
+ .unwrap_or_else(|| {
+ ty.display_truncated(
+ db,
+ config
+ .max_length
+ .map(|len| len.saturating_sub(LABEL_START.len() + LABEL_END.len())),
+ )
+ .to_string()
+ });
+ return Some(format!("{}{}{}", LABEL_START, ty_display, LABEL_END));
+ }
+ }
+
+ None
+}
+
+fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::IdentPat, pat_ty: &hir::Type) -> bool {
+ if let Some(hir::Adt::Enum(enum_data)) = pat_ty.as_adt() {
+ let pat_text = bind_pat.to_string();
+ enum_data
+ .variants(db)
+ .into_iter()
+ .map(|variant| variant.name(db).to_smol_str())
+ .any(|enum_name| enum_name == pat_text)
+ } else {
+ false
+ }
+}
+
+fn should_not_display_type_hint(
+ sema: &Semantics<'_, RootDatabase>,
+ config: &InlayHintsConfig,
+ bind_pat: &ast::IdentPat,
+ pat_ty: &hir::Type,
+) -> bool {
+ let db = sema.db;
+
+ if pat_ty.is_unknown() {
+ return true;
+ }
+
+ if let Some(hir::Adt::Struct(s)) = pat_ty.as_adt() {
+ if s.fields(db).is_empty() && s.name(db).to_smol_str() == bind_pat.to_string() {
+ return true;
+ }
+ }
+
+ if config.hide_closure_initialization_hints {
+ if let Some(parent) = bind_pat.syntax().parent() {
+ if let Some(it) = ast::LetStmt::cast(parent.clone()) {
+ if let Some(ast::Expr::ClosureExpr(closure)) = it.initializer() {
+ if closure_has_block_body(&closure) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ for node in bind_pat.syntax().ancestors() {
+ match_ast! {
+ match node {
+ ast::LetStmt(it) => return it.ty().is_some(),
+ // FIXME: We might wanna show type hints in parameters for non-top level patterns as well
+ ast::Param(it) => return it.ty().is_some(),
+ ast::MatchArm(_) => return pat_is_enum_variant(db, bind_pat, pat_ty),
+ ast::LetExpr(_) => return pat_is_enum_variant(db, bind_pat, pat_ty),
+ ast::IfExpr(_) => return false,
+ ast::WhileExpr(_) => return false,
+ ast::ForExpr(it) => {
+ // We *should* display hint only if user provided "in {expr}" and we know the type of expr (and it's not unit).
+ // Type of expr should be iterable.
+ return it.in_token().is_none() ||
+ it.iterable()
+ .and_then(|iterable_expr| sema.type_of_expr(&iterable_expr))
+ .map(TypeInfo::original)
+ .map_or(true, |iterable_ty| iterable_ty.is_unknown() || iterable_ty.is_unit())
+ },
+ _ => (),
+ }
+ }
+ }
+ false
+}
+
+fn closure_has_block_body(closure: &ast::ClosureExpr) -> bool {
+ matches!(closure.body(), Some(ast::Expr::BlockExpr(_)))
+}
+
+fn should_hide_param_name_hint(
+ sema: &Semantics<'_, RootDatabase>,
+ callable: &hir::Callable,
+ param_name: &str,
+ argument: &ast::Expr,
+) -> bool {
+ // These are to be tested in the `parameter_hint_heuristics` test
+ // hide when:
+ // - the parameter name is a suffix of the function's name
+ // - the argument is a qualified constructing or call expression where the qualifier is an ADT
+ // - exact argument<->parameter match(ignoring leading underscore) or parameter is a prefix/suffix
+ // of argument with _ splitting it off
+ // - param starts with `ra_fixture`
+ // - param is a well known name in a unary function
+
+ let param_name = param_name.trim_start_matches('_');
+ if param_name.is_empty() {
+ return true;
+ }
+
+ if matches!(argument, ast::Expr::PrefixExpr(prefix) if prefix.op_kind() == Some(UnaryOp::Not)) {
+ return false;
+ }
+
+ let fn_name = match callable.kind() {
+ hir::CallableKind::Function(it) => Some(it.name(sema.db).to_smol_str()),
+ _ => None,
+ };
+ let fn_name = fn_name.as_deref();
+ is_param_name_suffix_of_fn_name(param_name, callable, fn_name)
+ || is_argument_similar_to_param_name(argument, param_name)
+ || param_name.starts_with("ra_fixture")
+ || (callable.n_params() == 1 && is_obvious_param(param_name))
+ || is_adt_constructor_similar_to_param_name(sema, argument, param_name)
+}
+
+fn is_argument_similar_to_param_name(argument: &ast::Expr, param_name: &str) -> bool {
+ // check whether param_name and argument are the same or
+ // whether param_name is a prefix/suffix of argument(split at `_`)
+ let argument = match get_string_representation(argument) {
+ Some(argument) => argument,
+ None => return false,
+ };
+
+ // std is honestly too panic happy...
+ let str_split_at = |str: &str, at| str.is_char_boundary(at).then(|| argument.split_at(at));
+
+ let param_name = param_name.trim_start_matches('_');
+ let argument = argument.trim_start_matches('_');
+
+ match str_split_at(argument, param_name.len()) {
+ Some((prefix, rest)) if prefix.eq_ignore_ascii_case(param_name) => {
+ return rest.is_empty() || rest.starts_with('_');
+ }
+ _ => (),
+ }
+ match argument.len().checked_sub(param_name.len()).and_then(|at| str_split_at(argument, at)) {
+ Some((rest, suffix)) if param_name.eq_ignore_ascii_case(suffix) => {
+ return rest.is_empty() || rest.ends_with('_');
+ }
+ _ => (),
+ }
+ false
+}
+
+/// Hide the parameter name of a unary function if it is a `_` - prefixed suffix of the function's name, or equal.
+///
+/// `fn strip_suffix(suffix)` will be hidden.
+/// `fn stripsuffix(suffix)` will not be hidden.
+fn is_param_name_suffix_of_fn_name(
+ param_name: &str,
+ callable: &Callable,
+ fn_name: Option<&str>,
+) -> bool {
+ match (callable.n_params(), fn_name) {
+ (1, Some(function)) => {
+ function == param_name
+ || function
+ .len()
+ .checked_sub(param_name.len())
+ .and_then(|at| function.is_char_boundary(at).then(|| function.split_at(at)))
+ .map_or(false, |(prefix, suffix)| {
+ suffix.eq_ignore_ascii_case(param_name) && prefix.ends_with('_')
+ })
+ }
+ _ => false,
+ }
+}
+
+fn is_adt_constructor_similar_to_param_name(
+ sema: &Semantics<'_, RootDatabase>,
+ argument: &ast::Expr,
+ param_name: &str,
+) -> bool {
+ let path = match argument {
+ ast::Expr::CallExpr(c) => c.expr().and_then(|e| match e {
+ ast::Expr::PathExpr(p) => p.path(),
+ _ => None,
+ }),
+ ast::Expr::PathExpr(p) => p.path(),
+ ast::Expr::RecordExpr(r) => r.path(),
+ _ => return false,
+ };
+ let path = match path {
+ Some(it) => it,
+ None => return false,
+ };
+ (|| match sema.resolve_path(&path)? {
+ hir::PathResolution::Def(hir::ModuleDef::Adt(_)) => {
+ Some(to_lower_snake_case(&path.segment()?.name_ref()?.text()) == param_name)
+ }
+ hir::PathResolution::Def(hir::ModuleDef::Function(_) | hir::ModuleDef::Variant(_)) => {
+ if to_lower_snake_case(&path.segment()?.name_ref()?.text()) == param_name {
+ return Some(true);
+ }
+ let qual = path.qualifier()?;
+ match sema.resolve_path(&qual)? {
+ hir::PathResolution::Def(hir::ModuleDef::Adt(_)) => {
+ Some(to_lower_snake_case(&qual.segment()?.name_ref()?.text()) == param_name)
+ }
+ _ => None,
+ }
+ }
+ _ => None,
+ })()
+ .unwrap_or(false)
+}
+
+fn get_string_representation(expr: &ast::Expr) -> Option<String> {
+ match expr {
+ ast::Expr::MethodCallExpr(method_call_expr) => {
+ let name_ref = method_call_expr.name_ref()?;
+ match name_ref.text().as_str() {
+ "clone" | "as_ref" => method_call_expr.receiver().map(|rec| rec.to_string()),
+ name_ref => Some(name_ref.to_owned()),
+ }
+ }
+ ast::Expr::MacroExpr(macro_expr) => {
+ Some(macro_expr.macro_call()?.path()?.segment()?.to_string())
+ }
+ ast::Expr::FieldExpr(field_expr) => Some(field_expr.name_ref()?.to_string()),
+ ast::Expr::PathExpr(path_expr) => Some(path_expr.path()?.segment()?.to_string()),
+ ast::Expr::PrefixExpr(prefix_expr) => get_string_representation(&prefix_expr.expr()?),
+ ast::Expr::RefExpr(ref_expr) => get_string_representation(&ref_expr.expr()?),
+ ast::Expr::CastExpr(cast_expr) => get_string_representation(&cast_expr.expr()?),
+ _ => None,
+ }
+}
+
+fn is_obvious_param(param_name: &str) -> bool {
+ // avoid displaying hints for common functions like map, filter, etc.
+ // or other obvious words used in std
+ let is_obvious_param_name =
+ matches!(param_name, "predicate" | "value" | "pat" | "rhs" | "other");
+ param_name.len() == 1 || is_obvious_param_name
+}
+
+fn get_callable(
+ sema: &Semantics<'_, RootDatabase>,
+ expr: &ast::Expr,
+) -> Option<(hir::Callable, ast::ArgList)> {
+ match expr {
+ ast::Expr::CallExpr(expr) => {
+ let descended = sema.descend_node_into_attributes(expr.clone()).pop();
+ let expr = descended.as_ref().unwrap_or(expr);
+ sema.type_of_expr(&expr.expr()?)?.original.as_callable(sema.db).zip(expr.arg_list())
+ }
+ ast::Expr::MethodCallExpr(expr) => {
+ let descended = sema.descend_node_into_attributes(expr.clone()).pop();
+ let expr = descended.as_ref().unwrap_or(expr);
+ sema.resolve_method_call_as_callable(expr).zip(expr.arg_list())
+ }
+ _ => None,
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+ use ide_db::base_db::FileRange;
+ use itertools::Itertools;
+ use syntax::{TextRange, TextSize};
+ use test_utils::extract_annotations;
+
+ use crate::inlay_hints::ReborrowHints;
+ use crate::{fixture, inlay_hints::InlayHintsConfig, LifetimeElisionHints};
+
+ use super::ClosureReturnTypeHints;
+
+ const DISABLED_CONFIG: InlayHintsConfig = InlayHintsConfig {
+ render_colons: false,
+ type_hints: false,
+ parameter_hints: false,
+ chaining_hints: false,
+ lifetime_elision_hints: LifetimeElisionHints::Never,
+ closure_return_type_hints: ClosureReturnTypeHints::Never,
+ reborrow_hints: ReborrowHints::Always,
+ binding_mode_hints: false,
+ hide_named_constructor_hints: false,
+ hide_closure_initialization_hints: false,
+ param_names_for_lifetime_elision_hints: false,
+ max_length: None,
+ closing_brace_hints_min_lines: None,
+ };
+ const TEST_CONFIG: InlayHintsConfig = InlayHintsConfig {
+ type_hints: true,
+ parameter_hints: true,
+ chaining_hints: true,
+ reborrow_hints: ReborrowHints::Always,
+ closure_return_type_hints: ClosureReturnTypeHints::WithBlock,
+ binding_mode_hints: true,
+ lifetime_elision_hints: LifetimeElisionHints::Always,
+ ..DISABLED_CONFIG
+ };
+
+ #[track_caller]
+ fn check(ra_fixture: &str) {
+ check_with_config(TEST_CONFIG, ra_fixture);
+ }
+
+ #[track_caller]
+ fn check_params(ra_fixture: &str) {
+ check_with_config(
+ InlayHintsConfig { parameter_hints: true, ..DISABLED_CONFIG },
+ ra_fixture,
+ );
+ }
+
+ #[track_caller]
+ fn check_types(ra_fixture: &str) {
+ check_with_config(InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG }, ra_fixture);
+ }
+
+ #[track_caller]
+ fn check_chains(ra_fixture: &str) {
+ check_with_config(InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG }, ra_fixture);
+ }
+
+ #[track_caller]
+ fn check_with_config(config: InlayHintsConfig, ra_fixture: &str) {
+ let (analysis, file_id) = fixture::file(ra_fixture);
+ let mut expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
+ let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
+ let actual = inlay_hints
+ .into_iter()
+ .map(|it| (it.range, it.label.to_string()))
+ .sorted_by_key(|(range, _)| range.start())
+ .collect::<Vec<_>>();
+ expected.sort_by_key(|(range, _)| range.start());
+
+ assert_eq!(expected, actual, "\nExpected:\n{:#?}\n\nActual:\n{:#?}", expected, actual);
+ }
+
+ #[track_caller]
+ fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) {
+ let (analysis, file_id) = fixture::file(ra_fixture);
+ let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
+ expect.assert_debug_eq(&inlay_hints)
+ }
+
+ #[test]
+ fn hints_disabled() {
+ check_with_config(
+ InlayHintsConfig { render_colons: true, ..DISABLED_CONFIG },
+ r#"
+fn foo(a: i32, b: i32) -> i32 { a + b }
+fn main() {
+ let _x = foo(4, 4);
+}"#,
+ );
+ }
+
+ // Parameter hint tests
+
+ #[test]
+ fn param_hints_only() {
+ check_params(
+ r#"
+fn foo(a: i32, b: i32) -> i32 { a + b }
+fn main() {
+ let _x = foo(
+ 4,
+ //^ a
+ 4,
+ //^ b
+ );
+}"#,
+ );
+ }
+
+ #[test]
+ fn param_hints_on_closure() {
+ check_params(
+ r#"
+fn main() {
+ let clo = |a: u8, b: u8| a + b;
+ clo(
+ 1,
+ //^ a
+ 2,
+ //^ b
+ );
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn param_name_similar_to_fn_name_still_hints() {
+ check_params(
+ r#"
+fn max(x: i32, y: i32) -> i32 { x + y }
+fn main() {
+ let _x = max(
+ 4,
+ //^ x
+ 4,
+ //^ y
+ );
+}"#,
+ );
+ }
+
+ #[test]
+ fn param_name_similar_to_fn_name() {
+ check_params(
+ r#"
+fn param_with_underscore(with_underscore: i32) -> i32 { with_underscore }
+fn main() {
+ let _x = param_with_underscore(
+ 4,
+ );
+}"#,
+ );
+ check_params(
+ r#"
+fn param_with_underscore(underscore: i32) -> i32 { underscore }
+fn main() {
+ let _x = param_with_underscore(
+ 4,
+ );
+}"#,
+ );
+ }
+
+ #[test]
+ fn param_name_same_as_fn_name() {
+ check_params(
+ r#"
+fn foo(foo: i32) -> i32 { foo }
+fn main() {
+ let _x = foo(
+ 4,
+ );
+}"#,
+ );
+ }
+
+ #[test]
+ fn never_hide_param_when_multiple_params() {
+ check_params(
+ r#"
+fn foo(foo: i32, bar: i32) -> i32 { bar + baz }
+fn main() {
+ let _x = foo(
+ 4,
+ //^ foo
+ 8,
+ //^ bar
+ );
+}"#,
+ );
+ }
+
+ #[test]
+ fn param_hints_look_through_as_ref_and_clone() {
+ check_params(
+ r#"
+fn foo(bar: i32, baz: f32) {}
+
+fn main() {
+ let bar = 3;
+ let baz = &"baz";
+ let fez = 1.0;
+ foo(bar.clone(), bar.clone());
+ //^^^^^^^^^^^ baz
+ foo(bar.as_ref(), bar.as_ref());
+ //^^^^^^^^^^^^ baz
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn self_param_hints() {
+ check_params(
+ r#"
+struct Foo;
+
+impl Foo {
+ fn foo(self: Self) {}
+ fn bar(self: &Self) {}
+}
+
+fn main() {
+ Foo::foo(Foo);
+ //^^^ self
+ Foo::bar(&Foo);
+ //^^^^ self
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn param_name_hints_show_for_literals() {
+ check_params(
+ r#"pub fn test(a: i32, b: i32) -> [i32; 2] { [a, b] }
+fn main() {
+ test(
+ 0xa_b,
+ //^^^^^ a
+ 0xa_b,
+ //^^^^^ b
+ );
+}"#,
+ )
+ }
+
+ #[test]
+ fn function_call_parameter_hint() {
+ check_params(
+ r#"
+//- minicore: option
+struct FileId {}
+struct SmolStr {}
+
+struct TextRange {}
+struct SyntaxKind {}
+struct NavigationTarget {}
+
+struct Test {}
+
+impl Test {
+ fn method(&self, mut param: i32) -> i32 { param * 2 }
+
+ fn from_syntax(
+ file_id: FileId,
+ name: SmolStr,
+ focus_range: Option<TextRange>,
+ full_range: TextRange,
+ kind: SyntaxKind,
+ docs: Option<String>,
+ ) -> NavigationTarget {
+ NavigationTarget {}
+ }
+}
+
+fn test_func(mut foo: i32, bar: i32, msg: &str, _: i32, last: i32) -> i32 {
+ foo + bar
+}
+
+fn main() {
+ let not_literal = 1;
+ let _: i32 = test_func(1, 2, "hello", 3, not_literal);
+ //^ foo ^ bar ^^^^^^^ msg ^^^^^^^^^^^ last
+ let t: Test = Test {};
+ t.method(123);
+ //^^^ param
+ Test::method(&t, 3456);
+ //^^ self ^^^^ param
+ Test::from_syntax(
+ FileId {},
+ "impl".into(),
+ //^^^^^^^^^^^^^ name
+ None,
+ //^^^^ focus_range
+ TextRange {},
+ //^^^^^^^^^^^^ full_range
+ SyntaxKind {},
+ //^^^^^^^^^^^^^ kind
+ None,
+ //^^^^ docs
+ );
+}"#,
+ );
+ }
+
+ #[test]
+ fn parameter_hint_heuristics() {
+ check_params(
+ r#"
+fn check(ra_fixture_thing: &str) {}
+
+fn map(f: i32) {}
+fn filter(predicate: i32) {}
+
+fn strip_suffix(suffix: &str) {}
+fn stripsuffix(suffix: &str) {}
+fn same(same: u32) {}
+fn same2(_same2: u32) {}
+
+fn enum_matches_param_name(completion_kind: CompletionKind) {}
+
+fn foo(param: u32) {}
+fn bar(param_eter: u32) {}
+
+enum CompletionKind {
+ Keyword,
+}
+
+fn non_ident_pat((a, b): (u32, u32)) {}
+
+fn main() {
+ const PARAM: u32 = 0;
+ foo(PARAM);
+ foo(!PARAM);
+ // ^^^^^^ param
+ check("");
+
+ map(0);
+ filter(0);
+
+ strip_suffix("");
+ stripsuffix("");
+ //^^ suffix
+ same(0);
+ same2(0);
+
+ enum_matches_param_name(CompletionKind::Keyword);
+
+ let param = 0;
+ foo(param);
+ foo(param as _);
+ let param_end = 0;
+ foo(param_end);
+ let start_param = 0;
+ foo(start_param);
+ let param2 = 0;
+ foo(param2);
+ //^^^^^^ param
+
+ macro_rules! param {
+ () => {};
+ };
+ foo(param!());
+
+ let param_eter = 0;
+ bar(param_eter);
+ let param_eter_end = 0;
+ bar(param_eter_end);
+ let start_param_eter = 0;
+ bar(start_param_eter);
+ let param_eter2 = 0;
+ bar(param_eter2);
+ //^^^^^^^^^^^ param_eter
+
+ non_ident_pat((0, 0));
+}"#,
+ );
+ }
+
+ // Type-Hint tests
+
+ #[test]
+ fn type_hints_only() {
+ check_types(
+ r#"
+fn foo(a: i32, b: i32) -> i32 { a + b }
+fn main() {
+ let _x = foo(4, 4);
+ //^^ i32
+}"#,
+ );
+ }
+
+ #[test]
+ fn type_hints_bindings_after_at() {
+ check_types(
+ r#"
+//- minicore: option
+fn main() {
+ let ref foo @ bar @ ref mut baz = 0;
+ //^^^ &i32
+ //^^^ i32
+ //^^^ &mut i32
+ let [x @ ..] = [0];
+ //^ [i32; 1]
+ if let x @ Some(_) = Some(0) {}
+ //^ Option<i32>
+ let foo @ (bar, baz) = (3, 3);
+ //^^^ (i32, i32)
+ //^^^ i32
+ //^^^ i32
+}"#,
+ );
+ }
+
+ #[test]
+ fn default_generic_types_should_not_be_displayed() {
+ check(
+ r#"
+struct Test<K, T = u8> { k: K, t: T }
+
+fn main() {
+ let zz = Test { t: 23u8, k: 33 };
+ //^^ Test<i32>
+ let zz_ref = &zz;
+ //^^^^^^ &Test<i32>
+ let test = || zz;
+ //^^^^ || -> Test<i32>
+}"#,
+ );
+ }
+
+ #[test]
+ fn shorten_iterators_in_associated_params() {
+ check_types(
+ r#"
+//- minicore: iterators
+use core::iter;
+
+pub struct SomeIter<T> {}
+
+impl<T> SomeIter<T> {
+ pub fn new() -> Self { SomeIter {} }
+ pub fn push(&mut self, t: T) {}
+}
+
+impl<T> Iterator for SomeIter<T> {
+ type Item = T;
+ fn next(&mut self) -> Option<Self::Item> {
+ None
+ }
+}
+
+fn main() {
+ let mut some_iter = SomeIter::new();
+ //^^^^^^^^^ SomeIter<Take<Repeat<i32>>>
+ some_iter.push(iter::repeat(2).take(2));
+ let iter_of_iters = some_iter.take(2);
+ //^^^^^^^^^^^^^ impl Iterator<Item = impl Iterator<Item = i32>>
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn infer_call_method_return_associated_types_with_generic() {
+ check_types(
+ r#"
+ pub trait Default {
+ fn default() -> Self;
+ }
+ pub trait Foo {
+ type Bar: Default;
+ }
+
+ pub fn quux<T: Foo>() -> T::Bar {
+ let y = Default::default();
+ //^ <T as Foo>::Bar
+
+ y
+ }
+ "#,
+ );
+ }
+
+ #[test]
+ fn fn_hints() {
+ check_types(
+ r#"
+//- minicore: fn, sized
+fn foo() -> impl Fn() { loop {} }
+fn foo1() -> impl Fn(f64) { loop {} }
+fn foo2() -> impl Fn(f64, f64) { loop {} }
+fn foo3() -> impl Fn(f64, f64) -> u32 { loop {} }
+fn foo4() -> &'static dyn Fn(f64, f64) -> u32 { loop {} }
+fn foo5() -> &'static dyn Fn(&'static dyn Fn(f64, f64) -> u32, f64) -> u32 { loop {} }
+fn foo6() -> impl Fn(f64, f64) -> u32 + Sized { loop {} }
+fn foo7() -> *const (impl Fn(f64, f64) -> u32 + Sized) { loop {} }
+
+fn main() {
+ let foo = foo();
+ // ^^^ impl Fn()
+ let foo = foo1();
+ // ^^^ impl Fn(f64)
+ let foo = foo2();
+ // ^^^ impl Fn(f64, f64)
+ let foo = foo3();
+ // ^^^ impl Fn(f64, f64) -> u32
+ let foo = foo4();
+ // ^^^ &dyn Fn(f64, f64) -> u32
+ let foo = foo5();
+ // ^^^ &dyn Fn(&dyn Fn(f64, f64) -> u32, f64) -> u32
+ let foo = foo6();
+ // ^^^ impl Fn(f64, f64) -> u32
+ let foo = foo7();
+ // ^^^ *const impl Fn(f64, f64) -> u32
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn check_hint_range_limit() {
+ let fixture = r#"
+ //- minicore: fn, sized
+ fn foo() -> impl Fn() { loop {} }
+ fn foo1() -> impl Fn(f64) { loop {} }
+ fn foo2() -> impl Fn(f64, f64) { loop {} }
+ fn foo3() -> impl Fn(f64, f64) -> u32 { loop {} }
+ fn foo4() -> &'static dyn Fn(f64, f64) -> u32 { loop {} }
+ fn foo5() -> &'static dyn Fn(&'static dyn Fn(f64, f64) -> u32, f64) -> u32 { loop {} }
+ fn foo6() -> impl Fn(f64, f64) -> u32 + Sized { loop {} }
+ fn foo7() -> *const (impl Fn(f64, f64) -> u32 + Sized) { loop {} }
+
+ fn main() {
+ let foo = foo();
+ let foo = foo1();
+ let foo = foo2();
+ // ^^^ impl Fn(f64, f64)
+ let foo = foo3();
+ // ^^^ impl Fn(f64, f64) -> u32
+ let foo = foo4();
+ let foo = foo5();
+ let foo = foo6();
+ let foo = foo7();
+ }
+ "#;
+ let (analysis, file_id) = fixture::file(fixture);
+ let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
+ let inlay_hints = analysis
+ .inlay_hints(
+ &InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG },
+ file_id,
+ Some(FileRange {
+ file_id,
+ range: TextRange::new(TextSize::from(500), TextSize::from(600)),
+ }),
+ )
+ .unwrap();
+ let actual =
+ inlay_hints.into_iter().map(|it| (it.range, it.label.to_string())).collect::<Vec<_>>();
+ assert_eq!(expected, actual, "\nExpected:\n{:#?}\n\nActual:\n{:#?}", expected, actual);
+ }
+
+ #[test]
+ fn fn_hints_ptr_rpit_fn_parentheses() {
+ check_types(
+ r#"
+//- minicore: fn, sized
+trait Trait {}
+
+fn foo1() -> *const impl Fn() { loop {} }
+fn foo2() -> *const (impl Fn() + Sized) { loop {} }
+fn foo3() -> *const (impl Fn() + ?Sized) { loop {} }
+fn foo4() -> *const (impl Sized + Fn()) { loop {} }
+fn foo5() -> *const (impl ?Sized + Fn()) { loop {} }
+fn foo6() -> *const (impl Fn() + Trait) { loop {} }
+fn foo7() -> *const (impl Fn() + Sized + Trait) { loop {} }
+fn foo8() -> *const (impl Fn() + ?Sized + Trait) { loop {} }
+fn foo9() -> *const (impl Fn() -> u8 + ?Sized) { loop {} }
+fn foo10() -> *const (impl Fn() + Sized + ?Sized) { loop {} }
+
+fn main() {
+ let foo = foo1();
+ // ^^^ *const impl Fn()
+ let foo = foo2();
+ // ^^^ *const impl Fn()
+ let foo = foo3();
+ // ^^^ *const (impl Fn() + ?Sized)
+ let foo = foo4();
+ // ^^^ *const impl Fn()
+ let foo = foo5();
+ // ^^^ *const (impl Fn() + ?Sized)
+ let foo = foo6();
+ // ^^^ *const (impl Fn() + Trait)
+ let foo = foo7();
+ // ^^^ *const (impl Fn() + Trait)
+ let foo = foo8();
+ // ^^^ *const (impl Fn() + Trait + ?Sized)
+ let foo = foo9();
+ // ^^^ *const (impl Fn() -> u8 + ?Sized)
+ let foo = foo10();
+ // ^^^ *const impl Fn()
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn unit_structs_have_no_type_hints() {
+ check_types(
+ r#"
+//- minicore: result
+struct SyntheticSyntax;
+
+fn main() {
+ match Ok(()) {
+ Ok(_) => (),
+ Err(SyntheticSyntax) => (),
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn let_statement() {
+ check_types(
+ r#"
+#[derive(PartialEq)]
+enum Option<T> { None, Some(T) }
+
+#[derive(PartialEq)]
+struct Test { a: Option<u32>, b: u8 }
+
+fn main() {
+ struct InnerStruct {}
+
+ let test = 54;
+ //^^^^ i32
+ let test: i32 = 33;
+ let mut test = 33;
+ //^^^^ i32
+ let _ = 22;
+ let test = "test";
+ //^^^^ &str
+ let test = InnerStruct {};
+ //^^^^ InnerStruct
+
+ let test = unresolved();
+
+ let test = (42, 'a');
+ //^^^^ (i32, char)
+ let (a, (b, (c,)) = (2, (3, (9.2,));
+ //^ i32 ^ i32 ^ f64
+ let &x = &92;
+ //^ i32
+}"#,
+ );
+ }
+
+ #[test]
+ fn if_expr() {
+ check_types(
+ r#"
+//- minicore: option
+struct Test { a: Option<u32>, b: u8 }
+
+fn main() {
+ let test = Some(Test { a: Some(3), b: 1 });
+ //^^^^ Option<Test>
+ if let None = &test {};
+ if let test = &test {};
+ //^^^^ &Option<Test>
+ if let Some(test) = &test {};
+ //^^^^ &Test
+ if let Some(Test { a, b }) = &test {};
+ //^ &Option<u32> ^ &u8
+ if let Some(Test { a: x, b: y }) = &test {};
+ //^ &Option<u32> ^ &u8
+ if let Some(Test { a: Some(x), b: y }) = &test {};
+ //^ &u32 ^ &u8
+ if let Some(Test { a: None, b: y }) = &test {};
+ //^ &u8
+ if let Some(Test { b: y, .. }) = &test {};
+ //^ &u8
+ if test == None {}
+}"#,
+ );
+ }
+
+ #[test]
+ fn while_expr() {
+ check_types(
+ r#"
+//- minicore: option
+struct Test { a: Option<u32>, b: u8 }
+
+fn main() {
+ let test = Some(Test { a: Some(3), b: 1 });
+ //^^^^ Option<Test>
+ while let Some(Test { a: Some(x), b: y }) = &test {};
+ //^ &u32 ^ &u8
+}"#,
+ );
+ }
+
+ #[test]
+ fn match_arm_list() {
+ check_types(
+ r#"
+//- minicore: option
+struct Test { a: Option<u32>, b: u8 }
+
+fn main() {
+ match Some(Test { a: Some(3), b: 1 }) {
+ None => (),
+ test => (),
+ //^^^^ Option<Test>
+ Some(Test { a: Some(x), b: y }) => (),
+ //^ u32 ^ u8
+ _ => {}
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn complete_for_hint() {
+ check_types(
+ r#"
+//- minicore: iterator
+pub struct Vec<T> {}
+
+impl<T> Vec<T> {
+ pub fn new() -> Self { Vec {} }
+ pub fn push(&mut self, t: T) {}
+}
+
+impl<T> IntoIterator for Vec<T> {
+ type Item=T;
+}
+
+fn main() {
+ let mut data = Vec::new();
+ //^^^^ Vec<&str>
+ data.push("foo");
+ for i in data {
+ //^ &str
+ let z = i;
+ //^ &str
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn multi_dyn_trait_bounds() {
+ check_types(
+ r#"
+pub struct Vec<T> {}
+
+impl<T> Vec<T> {
+ pub fn new() -> Self { Vec {} }
+}
+
+pub struct Box<T> {}
+
+trait Display {}
+trait Sync {}
+
+fn main() {
+ // The block expression wrapping disables the constructor hint hiding logic
+ let _v = { Vec::<Box<&(dyn Display + Sync)>>::new() };
+ //^^ Vec<Box<&(dyn Display + Sync)>>
+ let _v = { Vec::<Box<*const (dyn Display + Sync)>>::new() };
+ //^^ Vec<Box<*const (dyn Display + Sync)>>
+ let _v = { Vec::<Box<dyn Display + Sync>>::new() };
+ //^^ Vec<Box<dyn Display + Sync>>
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn shorten_iterator_hints() {
+ check_types(
+ r#"
+//- minicore: iterators
+use core::iter;
+
+struct MyIter;
+
+impl Iterator for MyIter {
+ type Item = ();
+ fn next(&mut self) -> Option<Self::Item> {
+ None
+ }
+}
+
+fn main() {
+ let _x = MyIter;
+ //^^ MyIter
+ let _x = iter::repeat(0);
+ //^^ impl Iterator<Item = i32>
+ fn generic<T: Clone>(t: T) {
+ let _x = iter::repeat(t);
+ //^^ impl Iterator<Item = T>
+ let _chained = iter::repeat(t).take(10);
+ //^^^^^^^^ impl Iterator<Item = T>
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn skip_constructor_and_enum_type_hints() {
+ check_with_config(
+ InlayHintsConfig {
+ type_hints: true,
+ hide_named_constructor_hints: true,
+ ..DISABLED_CONFIG
+ },
+ r#"
+//- minicore: try, option
+use core::ops::ControlFlow;
+
+mod x {
+ pub mod y { pub struct Foo; }
+ pub struct Foo;
+ pub enum AnotherEnum {
+ Variant()
+ };
+}
+struct Struct;
+struct TupleStruct();
+
+impl Struct {
+ fn new() -> Self {
+ Struct
+ }
+ fn try_new() -> ControlFlow<(), Self> {
+ ControlFlow::Continue(Struct)
+ }
+}
+
+struct Generic<T>(T);
+impl Generic<i32> {
+ fn new() -> Self {
+ Generic(0)
+ }
+}
+
+enum Enum {
+ Variant(u32)
+}
+
+fn times2(value: i32) -> i32 {
+ 2 * value
+}
+
+fn main() {
+ let enumb = Enum::Variant(0);
+
+ let strukt = x::Foo;
+ let strukt = x::y::Foo;
+ let strukt = Struct;
+ let strukt = Struct::new();
+
+ let tuple_struct = TupleStruct();
+
+ let generic0 = Generic::new();
+ // ^^^^^^^^ Generic<i32>
+ let generic1 = Generic(0);
+ // ^^^^^^^^ Generic<i32>
+ let generic2 = Generic::<i32>::new();
+ let generic3 = <Generic<i32>>::new();
+ let generic4 = Generic::<i32>(0);
+
+
+ let option = Some(0);
+ // ^^^^^^ Option<i32>
+ let func = times2;
+ // ^^^^ fn times2(i32) -> i32
+ let closure = |x: i32| x * 2;
+ // ^^^^^^^ |i32| -> i32
+}
+
+fn fallible() -> ControlFlow<()> {
+ let strukt = Struct::try_new()?;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn shows_constructor_type_hints_when_enabled() {
+ check_types(
+ r#"
+//- minicore: try
+use core::ops::ControlFlow;
+
+struct Struct;
+struct TupleStruct();
+
+impl Struct {
+ fn new() -> Self {
+ Struct
+ }
+ fn try_new() -> ControlFlow<(), Self> {
+ ControlFlow::Continue(Struct)
+ }
+}
+
+struct Generic<T>(T);
+impl Generic<i32> {
+ fn new() -> Self {
+ Generic(0)
+ }
+}
+
+fn main() {
+ let strukt = Struct::new();
+ // ^^^^^^ Struct
+ let tuple_struct = TupleStruct();
+ // ^^^^^^^^^^^^ TupleStruct
+ let generic0 = Generic::new();
+ // ^^^^^^^^ Generic<i32>
+ let generic1 = Generic::<i32>::new();
+ // ^^^^^^^^ Generic<i32>
+ let generic2 = <Generic<i32>>::new();
+ // ^^^^^^^^ Generic<i32>
+}
+
+fn fallible() -> ControlFlow<()> {
+ let strukt = Struct::try_new()?;
+ // ^^^^^^ Struct
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn closures() {
+ check(
+ r#"
+fn main() {
+ let mut start = 0;
+ //^^^^^ i32
+ (0..2).for_each(|increment | { start += increment; });
+ //^^^^^^^^^ i32
+
+ let multiply =
+ //^^^^^^^^ |i32, i32| -> i32
+ | a, b| a * b
+ //^ i32 ^ i32
+
+ ;
+
+ let _: i32 = multiply(1, 2);
+ //^ a ^ b
+ let multiply_ref = &multiply;
+ //^^^^^^^^^^^^ &|i32, i32| -> i32
+
+ let return_42 = || 42;
+ //^^^^^^^^^ || -> i32
+ || { 42 };
+ //^^ i32
+}"#,
+ );
+ }
+
+ #[test]
+ fn return_type_hints_for_closure_without_block() {
+ check_with_config(
+ InlayHintsConfig {
+ closure_return_type_hints: ClosureReturnTypeHints::Always,
+ ..DISABLED_CONFIG
+ },
+ r#"
+fn main() {
+ let a = || { 0 };
+ //^^ i32
+ let b = || 0;
+ //^^ i32
+}"#,
+ );
+ }
+
+ #[test]
+ fn skip_closure_type_hints() {
+ check_with_config(
+ InlayHintsConfig {
+ type_hints: true,
+ hide_closure_initialization_hints: true,
+ ..DISABLED_CONFIG
+ },
+ r#"
+//- minicore: fn
+fn main() {
+ let multiple_2 = |x: i32| { x * 2 };
+
+ let multiple_2 = |x: i32| x * 2;
+ // ^^^^^^^^^^ |i32| -> i32
+
+ let (not) = (|x: bool| { !x });
+ // ^^^ |bool| -> bool
+
+ let (is_zero, _b) = (|x: usize| { x == 0 }, false);
+ // ^^^^^^^ |usize| -> bool
+ // ^^ bool
+
+ let plus_one = |x| { x + 1 };
+ // ^ u8
+ foo(plus_one);
+
+ let add_mul = bar(|x: u8| { x + 1 });
+ // ^^^^^^^ impl FnOnce(u8) -> u8 + ?Sized
+
+ let closure = if let Some(6) = add_mul(2).checked_sub(1) {
+ // ^^^^^^^ fn(i32) -> i32
+ |x: i32| { x * 2 }
+ } else {
+ |x: i32| { x * 3 }
+ };
+}
+
+fn foo(f: impl FnOnce(u8) -> u8) {}
+
+fn bar(f: impl FnOnce(u8) -> u8) -> impl FnOnce(u8) -> u8 {
+ move |x: u8| f(x) * 2
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn hint_truncation() {
+ check_with_config(
+ InlayHintsConfig { max_length: Some(8), ..TEST_CONFIG },
+ r#"
+struct Smol<T>(T);
+
+struct VeryLongOuterName<T>(T);
+
+fn main() {
+ let a = Smol(0u32);
+ //^ Smol<u32>
+ let b = VeryLongOuterName(0usize);
+ //^ VeryLongOuterName<…>
+ let c = Smol(Smol(0u32))
+ //^ Smol<Smol<…>>
+}"#,
+ );
+ }
+
+ // Chaining hint tests
+
+ #[test]
+ fn chaining_hints_ignore_comments() {
+ check_expect(
+ InlayHintsConfig { type_hints: false, chaining_hints: true, ..DISABLED_CONFIG },
+ r#"
+struct A(B);
+impl A { fn into_b(self) -> B { self.0 } }
+struct B(C);
+impl B { fn into_c(self) -> C { self.0 } }
+struct C;
+
+fn main() {
+ let c = A(B(C))
+ .into_b() // This is a comment
+ // This is another comment
+ .into_c();
+}
+"#,
+ expect![[r#"
+ [
+ InlayHint {
+ range: 147..172,
+ kind: ChainingHint,
+ label: "B",
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 147..172,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 147..154,
+ kind: ChainingHint,
+ label: "A",
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 147..154,
+ ),
+ ),
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn chaining_hints_without_newlines() {
+ check_chains(
+ r#"
+struct A(B);
+impl A { fn into_b(self) -> B { self.0 } }
+struct B(C);
+impl B { fn into_c(self) -> C { self.0 } }
+struct C;
+
+fn main() {
+ let c = A(B(C)).into_b().into_c();
+}"#,
+ );
+ }
+
+ #[test]
+ fn struct_access_chaining_hints() {
+ check_expect(
+ InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
+ r#"
+struct A { pub b: B }
+struct B { pub c: C }
+struct C(pub bool);
+struct D;
+
+impl D {
+ fn foo(&self) -> i32 { 42 }
+}
+
+fn main() {
+ let x = A { b: B { c: C(true) } }
+ .b
+ .c
+ .0;
+ let x = D
+ .foo();
+}"#,
+ expect![[r#"
+ [
+ InlayHint {
+ range: 143..190,
+ kind: ChainingHint,
+ label: "C",
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 143..190,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 143..179,
+ kind: ChainingHint,
+ label: "B",
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 143..179,
+ ),
+ ),
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn generic_chaining_hints() {
+ check_expect(
+ InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
+ r#"
+struct A<T>(T);
+struct B<T>(T);
+struct C<T>(T);
+struct X<T,R>(T, R);
+
+impl<T> A<T> {
+ fn new(t: T) -> Self { A(t) }
+ fn into_b(self) -> B<T> { B(self.0) }
+}
+impl<T> B<T> {
+ fn into_c(self) -> C<T> { C(self.0) }
+}
+fn main() {
+ let c = A::new(X(42, true))
+ .into_b()
+ .into_c();
+}
+"#,
+ expect![[r#"
+ [
+ InlayHint {
+ range: 246..283,
+ kind: ChainingHint,
+ label: "B<X<i32, bool>>",
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 246..283,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 246..265,
+ kind: ChainingHint,
+ label: "A<X<i32, bool>>",
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 246..265,
+ ),
+ ),
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn shorten_iterator_chaining_hints() {
+ check_expect(
+ InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
+ r#"
+//- minicore: iterators
+use core::iter;
+
+struct MyIter;
+
+impl Iterator for MyIter {
+ type Item = ();
+ fn next(&mut self) -> Option<Self::Item> {
+ None
+ }
+}
+
+fn main() {
+ let _x = MyIter.by_ref()
+ .take(5)
+ .by_ref()
+ .take(5)
+ .by_ref();
+}
+"#,
+ expect![[r#"
+ [
+ InlayHint {
+ range: 174..241,
+ kind: ChainingHint,
+ label: "impl Iterator<Item = ()>",
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 174..241,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 174..224,
+ kind: ChainingHint,
+ label: "impl Iterator<Item = ()>",
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 174..224,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 174..206,
+ kind: ChainingHint,
+ label: "impl Iterator<Item = ()>",
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 174..206,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 174..189,
+ kind: ChainingHint,
+ label: "&mut MyIter",
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 174..189,
+ ),
+ ),
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn hints_in_attr_call() {
+ check_expect(
+ TEST_CONFIG,
+ r#"
+//- proc_macros: identity, input_replace
+struct Struct;
+impl Struct {
+ fn chain(self) -> Self {
+ self
+ }
+}
+#[proc_macros::identity]
+fn main() {
+ let strukt = Struct;
+ strukt
+ .chain()
+ .chain()
+ .chain();
+ Struct::chain(strukt);
+}
+"#,
+ expect![[r#"
+ [
+ InlayHint {
+ range: 124..130,
+ kind: TypeHint,
+ label: "Struct",
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 124..130,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 145..185,
+ kind: ChainingHint,
+ label: "Struct",
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 145..185,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 145..168,
+ kind: ChainingHint,
+ label: "Struct",
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 145..168,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 222..228,
+ kind: ParameterHint,
+ label: "self",
+ tooltip: Some(
+ HoverOffset(
+ FileId(
+ 0,
+ ),
+ 42,
+ ),
+ ),
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn hints_lifetimes() {
+ check(
+ r#"
+fn empty() {}
+
+fn no_gpl(a: &()) {}
+ //^^^^^^<'0>
+ // ^'0
+fn empty_gpl<>(a: &()) {}
+ // ^'0 ^'0
+fn partial<'b>(a: &(), b: &'b ()) {}
+// ^'0, $ ^'0
+fn partial<'a>(a: &'a (), b: &()) {}
+// ^'0, $ ^'0
+
+fn single_ret(a: &()) -> &() {}
+// ^^^^^^^^^^<'0>
+ // ^'0 ^'0
+fn full_mul(a: &(), b: &()) {}
+// ^^^^^^^^<'0, '1>
+ // ^'0 ^'1
+
+fn foo<'c>(a: &'c ()) -> &() {}
+ // ^'c
+
+fn nested_in(a: & &X< &()>) {}
+// ^^^^^^^^^<'0, '1, '2>
+ //^'0 ^'1 ^'2
+fn nested_out(a: &()) -> & &X< &()>{}
+// ^^^^^^^^^^<'0>
+ //^'0 ^'0 ^'0 ^'0
+
+impl () {
+ fn foo(&self) {}
+ // ^^^<'0>
+ // ^'0
+ fn foo(&self) -> &() {}
+ // ^^^<'0>
+ // ^'0 ^'0
+ fn foo(&self, a: &()) -> &() {}
+ // ^^^<'0, '1>
+ // ^'0 ^'1 ^'0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn hints_lifetimes_named() {
+ check_with_config(
+ InlayHintsConfig { param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG },
+ r#"
+fn nested_in<'named>(named: & &X< &()>) {}
+// ^'named1, 'named2, 'named3, $
+ //^'named1 ^'named2 ^'named3
+"#,
+ );
+ }
+
+ #[test]
+ fn hints_lifetimes_trivial_skip() {
+ check_with_config(
+ InlayHintsConfig {
+ lifetime_elision_hints: LifetimeElisionHints::SkipTrivial,
+ ..TEST_CONFIG
+ },
+ r#"
+fn no_gpl(a: &()) {}
+fn empty_gpl<>(a: &()) {}
+fn partial<'b>(a: &(), b: &'b ()) {}
+fn partial<'a>(a: &'a (), b: &()) {}
+
+fn single_ret(a: &()) -> &() {}
+// ^^^^^^^^^^<'0>
+ // ^'0 ^'0
+fn full_mul(a: &(), b: &()) {}
+
+fn foo<'c>(a: &'c ()) -> &() {}
+ // ^'c
+
+fn nested_in(a: & &X< &()>) {}
+fn nested_out(a: &()) -> & &X< &()>{}
+// ^^^^^^^^^^<'0>
+ //^'0 ^'0 ^'0 ^'0
+
+impl () {
+ fn foo(&self) {}
+ fn foo(&self) -> &() {}
+ // ^^^<'0>
+ // ^'0 ^'0
+ fn foo(&self, a: &()) -> &() {}
+ // ^^^<'0, '1>
+ // ^'0 ^'1 ^'0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn hints_lifetimes_static() {
+ check_with_config(
+ InlayHintsConfig {
+ lifetime_elision_hints: LifetimeElisionHints::Always,
+ ..TEST_CONFIG
+ },
+ r#"
+trait Trait {}
+static S: &str = "";
+// ^'static
+const C: &str = "";
+// ^'static
+const C: &dyn Trait = panic!();
+// ^'static
+
+impl () {
+ const C: &str = "";
+ const C: &dyn Trait = panic!();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn hints_implicit_reborrow() {
+ check_with_config(
+ InlayHintsConfig {
+ reborrow_hints: ReborrowHints::Always,
+ parameter_hints: true,
+ ..DISABLED_CONFIG
+ },
+ r#"
+fn __() {
+ let unique = &mut ();
+ let r_mov = unique;
+ let foo: &mut _ = unique;
+ //^^^^^^ &mut *
+ ref_mut_id(unique);
+ //^^^^^^ mut_ref
+ //^^^^^^ &mut *
+ let shared = ref_id(unique);
+ //^^^^^^ shared_ref
+ //^^^^^^ &*
+ let mov = shared;
+ let r_mov: &_ = shared;
+ ref_id(shared);
+ //^^^^^^ shared_ref
+
+ identity(unique);
+ identity(shared);
+}
+fn identity<T>(t: T) -> T {
+ t
+}
+fn ref_mut_id(mut_ref: &mut ()) -> &mut () {
+ mut_ref
+ //^^^^^^^ &mut *
+}
+fn ref_id(shared_ref: &()) -> &() {
+ shared_ref
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn hints_binding_modes() {
+ check_with_config(
+ InlayHintsConfig { binding_mode_hints: true, ..DISABLED_CONFIG },
+ r#"
+fn __(
+ (x,): (u32,),
+ (x,): &(u32,),
+ //^^^^&
+ //^ ref
+ (x,): &mut (u32,)
+ //^^^^&mut
+ //^ ref mut
+) {
+ let (x,) = (0,);
+ let (x,) = &(0,);
+ //^^^^ &
+ //^ ref
+ let (x,) = &mut (0,);
+ //^^^^ &mut
+ //^ ref mut
+ let &mut (x,) = &mut (0,);
+ let (ref mut x,) = &mut (0,);
+ //^^^^^^^^^^^^ &mut
+ let &mut (ref mut x,) = &mut (0,);
+ let (mut x,) = &mut (0,);
+ //^^^^^^^^ &mut
+ match (0,) {
+ (x,) => ()
+ }
+ match &(0,) {
+ (x,) => ()
+ //^^^^ &
+ //^ ref
+ }
+ match &mut (0,) {
+ (x,) => ()
+ //^^^^ &mut
+ //^ ref mut
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn hints_closing_brace() {
+ check_with_config(
+ InlayHintsConfig { closing_brace_hints_min_lines: Some(2), ..DISABLED_CONFIG },
+ r#"
+fn a() {}
+
+fn f() {
+} // no hint unless `}` is the last token on the line
+
+fn g() {
+ }
+//^ fn g
+
+fn h<T>(with: T, arguments: u8, ...) {
+ }
+//^ fn h
+
+trait Tr {
+ fn f();
+ fn g() {
+ }
+ //^ fn g
+ }
+//^ trait Tr
+impl Tr for () {
+ }
+//^ impl Tr for ()
+impl dyn Tr {
+ }
+//^ impl dyn Tr
+
+static S0: () = 0;
+static S1: () = {};
+static S2: () = {
+ };
+//^ static S2
+const _: () = {
+ };
+//^ const _
+
+mod m {
+ }
+//^ mod m
+
+m! {}
+m!();
+m!(
+ );
+//^ m!
+
+m! {
+ }
+//^ m!
+
+fn f() {
+ let v = vec![
+ ];
+ }
+//^ fn f
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/join_lines.rs b/src/tools/rust-analyzer/crates/ide/src/join_lines.rs
new file mode 100644
index 000000000..08621adde
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/join_lines.rs
@@ -0,0 +1,1087 @@
+use ide_assists::utils::extract_trivial_expression;
+use ide_db::syntax_helpers::node_ext::expr_as_name_ref;
+use itertools::Itertools;
+use syntax::{
+ ast::{self, AstNode, AstToken, IsString},
+ NodeOrToken, SourceFile, SyntaxElement,
+ SyntaxKind::{self, USE_TREE, WHITESPACE},
+ SyntaxToken, TextRange, TextSize, T,
+};
+
+use text_edit::{TextEdit, TextEditBuilder};
+
+pub struct JoinLinesConfig {
+ pub join_else_if: bool,
+ pub remove_trailing_comma: bool,
+ pub unwrap_trivial_blocks: bool,
+ pub join_assignments: bool,
+}
+
+// Feature: Join Lines
+//
+// Join selected lines into one, smartly fixing up whitespace, trailing commas, and braces.
+//
+// See
+// https://user-images.githubusercontent.com/1711539/124515923-4504e800-dde9-11eb-8d58-d97945a1a785.gif[this gif]
+// for the cases handled specially by joined lines.
+//
+// |===
+// | Editor | Action Name
+//
+// | VS Code | **Rust Analyzer: Join lines**
+// |===
+//
+// image::https://user-images.githubusercontent.com/48062697/113020661-b6922200-917a-11eb-87c4-b75acc028f11.gif[]
+pub(crate) fn join_lines(
+ config: &JoinLinesConfig,
+ file: &SourceFile,
+ range: TextRange,
+) -> TextEdit {
+ let range = if range.is_empty() {
+ let syntax = file.syntax();
+ let text = syntax.text().slice(range.start()..);
+ let pos = match text.find_char('\n') {
+ None => return TextEdit::builder().finish(),
+ Some(pos) => pos,
+ };
+ TextRange::at(range.start() + pos, TextSize::of('\n'))
+ } else {
+ range
+ };
+
+ let mut edit = TextEdit::builder();
+ match file.syntax().covering_element(range) {
+ NodeOrToken::Node(node) => {
+ for token in node.descendants_with_tokens().filter_map(|it| it.into_token()) {
+ remove_newlines(config, &mut edit, &token, range)
+ }
+ }
+ NodeOrToken::Token(token) => remove_newlines(config, &mut edit, &token, range),
+ };
+ edit.finish()
+}
+
+fn remove_newlines(
+ config: &JoinLinesConfig,
+ edit: &mut TextEditBuilder,
+ token: &SyntaxToken,
+ range: TextRange,
+) {
+ let intersection = match range.intersect(token.text_range()) {
+ Some(range) => range,
+ None => return,
+ };
+
+ let range = intersection - token.text_range().start();
+ let text = token.text();
+ for (pos, _) in text[range].bytes().enumerate().filter(|&(_, b)| b == b'\n') {
+ let pos: TextSize = (pos as u32).into();
+ let offset = token.text_range().start() + range.start() + pos;
+ if !edit.invalidates_offset(offset) {
+ remove_newline(config, edit, token, offset);
+ }
+ }
+}
+
+fn remove_newline(
+ config: &JoinLinesConfig,
+ edit: &mut TextEditBuilder,
+ token: &SyntaxToken,
+ offset: TextSize,
+) {
+ if token.kind() != WHITESPACE || token.text().bytes().filter(|&b| b == b'\n').count() != 1 {
+ let n_spaces_after_line_break = {
+ let suff = &token.text()[TextRange::new(
+ offset - token.text_range().start() + TextSize::of('\n'),
+ TextSize::of(token.text()),
+ )];
+ suff.bytes().take_while(|&b| b == b' ').count()
+ };
+
+ let mut no_space = false;
+ if let Some(string) = ast::String::cast(token.clone()) {
+ if let Some(range) = string.open_quote_text_range() {
+ cov_mark::hit!(join_string_literal_open_quote);
+ no_space |= range.end() == offset;
+ }
+ if let Some(range) = string.close_quote_text_range() {
+ cov_mark::hit!(join_string_literal_close_quote);
+ no_space |= range.start()
+ == offset
+ + TextSize::of('\n')
+ + TextSize::try_from(n_spaces_after_line_break).unwrap();
+ }
+ }
+
+ let range = TextRange::at(offset, ((n_spaces_after_line_break + 1) as u32).into());
+ let replace_with = if no_space { "" } else { " " };
+ edit.replace(range, replace_with.to_string());
+ return;
+ }
+
+ // The node is between two other nodes
+ let (prev, next) = match (token.prev_sibling_or_token(), token.next_sibling_or_token()) {
+ (Some(prev), Some(next)) => (prev, next),
+ _ => return,
+ };
+
+ if config.remove_trailing_comma && prev.kind() == T![,] {
+ match next.kind() {
+ T![')'] | T![']'] => {
+ // Removes: trailing comma, newline (incl. surrounding whitespace)
+ edit.delete(TextRange::new(prev.text_range().start(), token.text_range().end()));
+ return;
+ }
+ T!['}'] => {
+ // Removes: comma, newline (incl. surrounding whitespace)
+ let space = match prev.prev_sibling_or_token() {
+ Some(left) => compute_ws(left.kind(), next.kind()),
+ None => " ",
+ };
+ edit.replace(
+ TextRange::new(prev.text_range().start(), token.text_range().end()),
+ space.to_string(),
+ );
+ return;
+ }
+ _ => (),
+ }
+ }
+
+ if config.join_else_if {
+ if let (Some(prev), Some(_next)) = (as_if_expr(&prev), as_if_expr(&next)) {
+ match prev.else_token() {
+ Some(_) => cov_mark::hit!(join_two_ifs_with_existing_else),
+ None => {
+ cov_mark::hit!(join_two_ifs);
+ edit.replace(token.text_range(), " else ".to_string());
+ return;
+ }
+ }
+ }
+ }
+
+ if config.join_assignments {
+ if join_assignments(edit, &prev, &next).is_some() {
+ return;
+ }
+ }
+
+ if config.unwrap_trivial_blocks {
+ // Special case that turns something like:
+ //
+ // ```
+ // my_function({$0
+ // <some-expr>
+ // })
+ // ```
+ //
+ // into `my_function(<some-expr>)`
+ if join_single_expr_block(edit, token).is_some() {
+ return;
+ }
+ // ditto for
+ //
+ // ```
+ // use foo::{$0
+ // bar
+ // };
+ // ```
+ if join_single_use_tree(edit, token).is_some() {
+ return;
+ }
+ }
+
+ if let (Some(_), Some(next)) = (
+ prev.as_token().cloned().and_then(ast::Comment::cast),
+ next.as_token().cloned().and_then(ast::Comment::cast),
+ ) {
+ // Removes: newline (incl. surrounding whitespace), start of the next comment
+ edit.delete(TextRange::new(
+ token.text_range().start(),
+ next.syntax().text_range().start() + TextSize::of(next.prefix()),
+ ));
+ return;
+ }
+
+ // Remove newline but add a computed amount of whitespace characters
+ edit.replace(token.text_range(), compute_ws(prev.kind(), next.kind()).to_string());
+}
+
+fn join_single_expr_block(edit: &mut TextEditBuilder, token: &SyntaxToken) -> Option<()> {
+ let block_expr = ast::BlockExpr::cast(token.parent_ancestors().nth(1)?)?;
+ if !block_expr.is_standalone() {
+ return None;
+ }
+ let expr = extract_trivial_expression(&block_expr)?;
+
+ let block_range = block_expr.syntax().text_range();
+ let mut buf = expr.syntax().text().to_string();
+
+ // Match block needs to have a comma after the block
+ if let Some(match_arm) = block_expr.syntax().parent().and_then(ast::MatchArm::cast) {
+ if match_arm.comma_token().is_none() {
+ buf.push(',');
+ }
+ }
+
+ edit.replace(block_range, buf);
+
+ Some(())
+}
+
+fn join_single_use_tree(edit: &mut TextEditBuilder, token: &SyntaxToken) -> Option<()> {
+ let use_tree_list = ast::UseTreeList::cast(token.parent()?)?;
+ let (tree,) = use_tree_list.use_trees().collect_tuple()?;
+ edit.replace(use_tree_list.syntax().text_range(), tree.syntax().text().to_string());
+ Some(())
+}
+
+fn join_assignments(
+ edit: &mut TextEditBuilder,
+ prev: &SyntaxElement,
+ next: &SyntaxElement,
+) -> Option<()> {
+ let let_stmt = ast::LetStmt::cast(prev.as_node()?.clone())?;
+ if let_stmt.eq_token().is_some() {
+ cov_mark::hit!(join_assignments_already_initialized);
+ return None;
+ }
+ let let_ident_pat = match let_stmt.pat()? {
+ ast::Pat::IdentPat(it) => it,
+ _ => return None,
+ };
+
+ let expr_stmt = ast::ExprStmt::cast(next.as_node()?.clone())?;
+ let bin_expr = match expr_stmt.expr()? {
+ ast::Expr::BinExpr(it) => it,
+ _ => return None,
+ };
+ if !matches!(bin_expr.op_kind()?, ast::BinaryOp::Assignment { op: None }) {
+ return None;
+ }
+ let lhs = bin_expr.lhs()?;
+ let name_ref = expr_as_name_ref(&lhs)?;
+
+ if name_ref.to_string() != let_ident_pat.syntax().to_string() {
+ cov_mark::hit!(join_assignments_mismatch);
+ return None;
+ }
+
+ edit.delete(let_stmt.semicolon_token()?.text_range().cover(lhs.syntax().text_range()));
+ Some(())
+}
+
+fn as_if_expr(element: &SyntaxElement) -> Option<ast::IfExpr> {
+ let mut node = element.as_node()?.clone();
+ if let Some(stmt) = ast::ExprStmt::cast(node.clone()) {
+ node = stmt.expr()?.syntax().clone();
+ }
+ ast::IfExpr::cast(node)
+}
+
+fn compute_ws(left: SyntaxKind, right: SyntaxKind) -> &'static str {
+ match left {
+ T!['('] | T!['['] => return "",
+ T!['{'] => {
+ if let USE_TREE = right {
+ return "";
+ }
+ }
+ _ => (),
+ }
+ match right {
+ T![')'] | T![']'] => return "",
+ T!['}'] => {
+ if let USE_TREE = left {
+ return "";
+ }
+ }
+ T![.] => return "",
+ _ => (),
+ }
+ " "
+}
+
+#[cfg(test)]
+mod tests {
+ use syntax::SourceFile;
+ use test_utils::{add_cursor, assert_eq_text, extract_offset, extract_range};
+
+ use super::*;
+
+ fn check_join_lines(ra_fixture_before: &str, ra_fixture_after: &str) {
+ let config = JoinLinesConfig {
+ join_else_if: true,
+ remove_trailing_comma: true,
+ unwrap_trivial_blocks: true,
+ join_assignments: true,
+ };
+
+ let (before_cursor_pos, before) = extract_offset(ra_fixture_before);
+ let file = SourceFile::parse(&before).ok().unwrap();
+
+ let range = TextRange::empty(before_cursor_pos);
+ let result = join_lines(&config, &file, range);
+
+ let actual = {
+ let mut actual = before;
+ result.apply(&mut actual);
+ actual
+ };
+ let actual_cursor_pos = result
+ .apply_to_offset(before_cursor_pos)
+ .expect("cursor position is affected by the edit");
+ let actual = add_cursor(&actual, actual_cursor_pos);
+ assert_eq_text!(ra_fixture_after, &actual);
+ }
+
+ fn check_join_lines_sel(ra_fixture_before: &str, ra_fixture_after: &str) {
+ let config = JoinLinesConfig {
+ join_else_if: true,
+ remove_trailing_comma: true,
+ unwrap_trivial_blocks: true,
+ join_assignments: true,
+ };
+
+ let (sel, before) = extract_range(ra_fixture_before);
+ let parse = SourceFile::parse(&before);
+ let result = join_lines(&config, &parse.tree(), sel);
+ let actual = {
+ let mut actual = before;
+ result.apply(&mut actual);
+ actual
+ };
+ assert_eq_text!(ra_fixture_after, &actual);
+ }
+
+ #[test]
+ fn test_join_lines_comma() {
+ check_join_lines(
+ r"
+fn foo() {
+ $0foo(1,
+ )
+}
+",
+ r"
+fn foo() {
+ $0foo(1)
+}
+",
+ );
+ }
+
+ #[test]
+ fn test_join_lines_lambda_block() {
+ check_join_lines(
+ r"
+pub fn reparse(&self, edit: &AtomTextEdit) -> File {
+ $0self.incremental_reparse(edit).unwrap_or_else(|| {
+ self.full_reparse(edit)
+ })
+}
+",
+ r"
+pub fn reparse(&self, edit: &AtomTextEdit) -> File {
+ $0self.incremental_reparse(edit).unwrap_or_else(|| self.full_reparse(edit))
+}
+",
+ );
+ }
+
+ #[test]
+ fn test_join_lines_block() {
+ check_join_lines(
+ r"
+fn foo() {
+ foo($0{
+ 92
+ })
+}",
+ r"
+fn foo() {
+ foo($092)
+}",
+ );
+ }
+
+ #[test]
+ fn test_join_lines_diverging_block() {
+ check_join_lines(
+ r"
+fn foo() {
+ loop {
+ match x {
+ 92 => $0{
+ continue;
+ }
+ }
+ }
+}
+ ",
+ r"
+fn foo() {
+ loop {
+ match x {
+ 92 => $0continue,
+ }
+ }
+}
+ ",
+ );
+ }
+
+ #[test]
+ fn join_lines_adds_comma_for_block_in_match_arm() {
+ check_join_lines(
+ r"
+fn foo(e: Result<U, V>) {
+ match e {
+ Ok(u) => $0{
+ u.foo()
+ }
+ Err(v) => v,
+ }
+}",
+ r"
+fn foo(e: Result<U, V>) {
+ match e {
+ Ok(u) => $0u.foo(),
+ Err(v) => v,
+ }
+}",
+ );
+ }
+
+ #[test]
+ fn join_lines_multiline_in_block() {
+ check_join_lines(
+ r"
+fn foo() {
+ match ty {
+ $0 Some(ty) => {
+ match ty {
+ _ => false,
+ }
+ }
+ _ => true,
+ }
+}
+",
+ r"
+fn foo() {
+ match ty {
+ $0 Some(ty) => match ty {
+ _ => false,
+ },
+ _ => true,
+ }
+}
+",
+ );
+ }
+
+ #[test]
+ fn join_lines_keeps_comma_for_block_in_match_arm() {
+ // We already have a comma
+ check_join_lines(
+ r"
+fn foo(e: Result<U, V>) {
+ match e {
+ Ok(u) => $0{
+ u.foo()
+ },
+ Err(v) => v,
+ }
+}",
+ r"
+fn foo(e: Result<U, V>) {
+ match e {
+ Ok(u) => $0u.foo(),
+ Err(v) => v,
+ }
+}",
+ );
+
+ // comma with whitespace between brace and ,
+ check_join_lines(
+ r"
+fn foo(e: Result<U, V>) {
+ match e {
+ Ok(u) => $0{
+ u.foo()
+ } ,
+ Err(v) => v,
+ }
+}",
+ r"
+fn foo(e: Result<U, V>) {
+ match e {
+ Ok(u) => $0u.foo() ,
+ Err(v) => v,
+ }
+}",
+ );
+
+ // comma with newline between brace and ,
+ check_join_lines(
+ r"
+fn foo(e: Result<U, V>) {
+ match e {
+ Ok(u) => $0{
+ u.foo()
+ }
+ ,
+ Err(v) => v,
+ }
+}",
+ r"
+fn foo(e: Result<U, V>) {
+ match e {
+ Ok(u) => $0u.foo()
+ ,
+ Err(v) => v,
+ }
+}",
+ );
+ }
+
+ #[test]
+ fn join_lines_keeps_comma_with_single_arg_tuple() {
+ // A single arg tuple
+ check_join_lines(
+ r"
+fn foo() {
+ let x = ($0{
+ 4
+ },);
+}",
+ r"
+fn foo() {
+ let x = ($04,);
+}",
+ );
+
+ // single arg tuple with whitespace between brace and comma
+ check_join_lines(
+ r"
+fn foo() {
+ let x = ($0{
+ 4
+ } ,);
+}",
+ r"
+fn foo() {
+ let x = ($04 ,);
+}",
+ );
+
+ // single arg tuple with newline between brace and comma
+ check_join_lines(
+ r"
+fn foo() {
+ let x = ($0{
+ 4
+ }
+ ,);
+}",
+ r"
+fn foo() {
+ let x = ($04
+ ,);
+}",
+ );
+ }
+
+ #[test]
+ fn test_join_lines_use_items_left() {
+ // No space after the '{'
+ check_join_lines(
+ r"
+$0use syntax::{
+ TextSize, TextRange,
+};",
+ r"
+$0use syntax::{TextSize, TextRange,
+};",
+ );
+ }
+
+ #[test]
+ fn test_join_lines_use_items_right() {
+ // No space after the '}'
+ check_join_lines(
+ r"
+use syntax::{
+$0 TextSize, TextRange
+};",
+ r"
+use syntax::{
+$0 TextSize, TextRange};",
+ );
+ }
+
+ #[test]
+ fn test_join_lines_use_items_right_comma() {
+ // No space after the '}'
+ check_join_lines(
+ r"
+use syntax::{
+$0 TextSize, TextRange,
+};",
+ r"
+use syntax::{
+$0 TextSize, TextRange};",
+ );
+ }
+
+ #[test]
+ fn test_join_lines_use_tree() {
+ check_join_lines(
+ r"
+use syntax::{
+ algo::$0{
+ find_token_at_offset,
+ },
+ ast,
+};",
+ r"
+use syntax::{
+ algo::$0find_token_at_offset,
+ ast,
+};",
+ );
+ }
+
+ #[test]
+ fn test_join_lines_normal_comments() {
+ check_join_lines(
+ r"
+fn foo() {
+ // Hello$0
+ // world!
+}
+",
+ r"
+fn foo() {
+ // Hello$0 world!
+}
+",
+ );
+ }
+
+ #[test]
+ fn test_join_lines_doc_comments() {
+ check_join_lines(
+ r"
+fn foo() {
+ /// Hello$0
+ /// world!
+}
+",
+ r"
+fn foo() {
+ /// Hello$0 world!
+}
+",
+ );
+ }
+
+ #[test]
+ fn test_join_lines_mod_comments() {
+ check_join_lines(
+ r"
+fn foo() {
+ //! Hello$0
+ //! world!
+}
+",
+ r"
+fn foo() {
+ //! Hello$0 world!
+}
+",
+ );
+ }
+
+ #[test]
+ fn test_join_lines_multiline_comments_1() {
+ check_join_lines(
+ r"
+fn foo() {
+ // Hello$0
+ /* world! */
+}
+",
+ r"
+fn foo() {
+ // Hello$0 world! */
+}
+",
+ );
+ }
+
+ #[test]
+ fn test_join_lines_multiline_comments_2() {
+ check_join_lines(
+ r"
+fn foo() {
+ // The$0
+ /* quick
+ brown
+ fox! */
+}
+",
+ r"
+fn foo() {
+ // The$0 quick
+ brown
+ fox! */
+}
+",
+ );
+ }
+
+ #[test]
+ fn test_join_lines_selection_fn_args() {
+ check_join_lines_sel(
+ r"
+fn foo() {
+ $0foo(1,
+ 2,
+ 3,
+ $0)
+}
+ ",
+ r"
+fn foo() {
+ foo(1, 2, 3)
+}
+ ",
+ );
+ }
+
+ #[test]
+ fn test_join_lines_selection_struct() {
+ check_join_lines_sel(
+ r"
+struct Foo $0{
+ f: u32,
+}$0
+ ",
+ r"
+struct Foo { f: u32 }
+ ",
+ );
+ }
+
+ #[test]
+ fn test_join_lines_selection_dot_chain() {
+ check_join_lines_sel(
+ r"
+fn foo() {
+ join($0type_params.type_params()
+ .filter_map(|it| it.name())
+ .map(|it| it.text())$0)
+}",
+ r"
+fn foo() {
+ join(type_params.type_params().filter_map(|it| it.name()).map(|it| it.text()))
+}",
+ );
+ }
+
+ #[test]
+ fn test_join_lines_selection_lambda_block_body() {
+ check_join_lines_sel(
+ r"
+pub fn handle_find_matching_brace() {
+ params.offsets
+ .map(|offset| $0{
+ world.analysis().matching_brace(&file, offset).unwrap_or(offset)
+ }$0)
+ .collect();
+}",
+ r"
+pub fn handle_find_matching_brace() {
+ params.offsets
+ .map(|offset| world.analysis().matching_brace(&file, offset).unwrap_or(offset))
+ .collect();
+}",
+ );
+ }
+
+ #[test]
+ fn test_join_lines_commented_block() {
+ check_join_lines(
+ r"
+fn main() {
+ let _ = {
+ // $0foo
+ // bar
+ 92
+ };
+}
+ ",
+ r"
+fn main() {
+ let _ = {
+ // $0foo bar
+ 92
+ };
+}
+ ",
+ )
+ }
+
+ #[test]
+ fn join_lines_mandatory_blocks_block() {
+ check_join_lines(
+ r"
+$0fn foo() {
+ 92
+}
+ ",
+ r"
+$0fn foo() { 92
+}
+ ",
+ );
+
+ check_join_lines(
+ r"
+fn foo() {
+ $0if true {
+ 92
+ }
+}
+ ",
+ r"
+fn foo() {
+ $0if true { 92
+ }
+}
+ ",
+ );
+
+ check_join_lines(
+ r"
+fn foo() {
+ $0loop {
+ 92
+ }
+}
+ ",
+ r"
+fn foo() {
+ $0loop { 92
+ }
+}
+ ",
+ );
+
+ check_join_lines(
+ r"
+fn foo() {
+ $0unsafe {
+ 92
+ }
+}
+ ",
+ r"
+fn foo() {
+ $0unsafe { 92
+ }
+}
+ ",
+ );
+ }
+
+ #[test]
+ fn join_string_literal() {
+ {
+ cov_mark::check!(join_string_literal_open_quote);
+ check_join_lines(
+ r#"
+fn main() {
+ $0"
+hello
+";
+}
+"#,
+ r#"
+fn main() {
+ $0"hello
+";
+}
+"#,
+ );
+ }
+
+ {
+ cov_mark::check!(join_string_literal_close_quote);
+ check_join_lines(
+ r#"
+fn main() {
+ $0"hello
+";
+}
+"#,
+ r#"
+fn main() {
+ $0"hello";
+}
+"#,
+ );
+ check_join_lines(
+ r#"
+fn main() {
+ $0r"hello
+ ";
+}
+"#,
+ r#"
+fn main() {
+ $0r"hello";
+}
+"#,
+ );
+ }
+
+ check_join_lines(
+ r#"
+fn main() {
+ "
+$0hello
+world
+";
+}
+"#,
+ r#"
+fn main() {
+ "
+$0hello world
+";
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn join_last_line_empty() {
+ check_join_lines(
+ r#"
+fn main() {$0}
+"#,
+ r#"
+fn main() {$0}
+"#,
+ );
+ }
+
+ #[test]
+ fn join_two_ifs() {
+ cov_mark::check!(join_two_ifs);
+ check_join_lines(
+ r#"
+fn main() {
+ if foo {
+
+ }$0
+ if bar {
+
+ }
+}
+"#,
+ r#"
+fn main() {
+ if foo {
+
+ }$0 else if bar {
+
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn join_two_ifs_with_existing_else() {
+ cov_mark::check!(join_two_ifs_with_existing_else);
+ check_join_lines(
+ r#"
+fn main() {
+ if foo {
+
+ } else {
+
+ }$0
+ if bar {
+
+ }
+}
+"#,
+ r#"
+fn main() {
+ if foo {
+
+ } else {
+
+ }$0 if bar {
+
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn join_assignments() {
+ check_join_lines(
+ r#"
+fn foo() {
+ $0let foo;
+ foo = "bar";
+}
+"#,
+ r#"
+fn foo() {
+ $0let foo = "bar";
+}
+"#,
+ );
+
+ cov_mark::check!(join_assignments_mismatch);
+ check_join_lines(
+ r#"
+fn foo() {
+ let foo;
+ let qux;$0
+ foo = "bar";
+}
+"#,
+ r#"
+fn foo() {
+ let foo;
+ let qux;$0 foo = "bar";
+}
+"#,
+ );
+
+ cov_mark::check!(join_assignments_already_initialized);
+ check_join_lines(
+ r#"
+fn foo() {
+ let foo = "bar";$0
+ foo = "bar";
+}
+"#,
+ r#"
+fn foo() {
+ let foo = "bar";$0 foo = "bar";
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs
new file mode 100644
index 000000000..dd108fa79
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs
@@ -0,0 +1,702 @@
+//! ide crate provides "ide-centric" APIs for the rust-analyzer. That is,
+//! it generally operates with files and text ranges, and returns results as
+//! Strings, suitable for displaying to the human.
+//!
+//! What powers this API are the `RootDatabase` struct, which defines a `salsa`
+//! database, and the `hir` crate, where majority of the analysis happens.
+//! However, IDE specific bits of the analysis (most notably completion) happen
+//! in this crate.
+
+// For proving that RootDatabase is RefUnwindSafe.
+#![recursion_limit = "128"]
+#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
+
+#[allow(unused)]
+macro_rules! eprintln {
+ ($($tt:tt)*) => { stdx::eprintln!($($tt)*) };
+}
+
+#[cfg(test)]
+mod fixture;
+
+mod markup;
+mod prime_caches;
+mod navigation_target;
+
+mod annotations;
+mod call_hierarchy;
+mod signature_help;
+mod doc_links;
+mod highlight_related;
+mod expand_macro;
+mod extend_selection;
+mod file_structure;
+mod fn_references;
+mod folding_ranges;
+mod goto_declaration;
+mod goto_definition;
+mod goto_implementation;
+mod goto_type_definition;
+mod hover;
+mod inlay_hints;
+mod join_lines;
+mod markdown_remove;
+mod matching_brace;
+mod moniker;
+mod move_item;
+mod parent_module;
+mod references;
+mod rename;
+mod runnables;
+mod ssr;
+mod static_index;
+mod status;
+mod syntax_highlighting;
+mod syntax_tree;
+mod typing;
+mod view_crate_graph;
+mod view_hir;
+mod view_item_tree;
+mod shuffle_crate_graph;
+
+use std::sync::Arc;
+
+use cfg::CfgOptions;
+use ide_db::{
+ base_db::{
+ salsa::{self, ParallelDatabase},
+ CrateOrigin, Env, FileLoader, FileSet, SourceDatabase, VfsPath,
+ },
+ symbol_index, LineIndexDatabase,
+};
+use syntax::SourceFile;
+
+use crate::navigation_target::{ToNav, TryToNav};
+
+pub use crate::{
+ annotations::{Annotation, AnnotationConfig, AnnotationKind},
+ call_hierarchy::CallItem,
+ expand_macro::ExpandedMacro,
+ file_structure::{StructureNode, StructureNodeKind},
+ folding_ranges::{Fold, FoldKind},
+ highlight_related::{HighlightRelatedConfig, HighlightedRange},
+ hover::{HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult},
+ inlay_hints::{
+ ClosureReturnTypeHints, InlayHint, InlayHintsConfig, InlayKind, InlayTooltip,
+ LifetimeElisionHints, ReborrowHints,
+ },
+ join_lines::JoinLinesConfig,
+ markup::Markup,
+ moniker::{MonikerKind, MonikerResult, PackageInformation},
+ move_item::Direction,
+ navigation_target::NavigationTarget,
+ prime_caches::ParallelPrimeCachesProgress,
+ references::ReferenceSearchResult,
+ rename::RenameError,
+ runnables::{Runnable, RunnableKind, TestId},
+ signature_help::SignatureHelp,
+ static_index::{StaticIndex, StaticIndexedFile, TokenId, TokenStaticData},
+ syntax_highlighting::{
+ tags::{Highlight, HlMod, HlMods, HlOperator, HlPunct, HlTag},
+ HlRange,
+ },
+};
+pub use hir::{Documentation, Semantics};
+pub use ide_assists::{
+ Assist, AssistConfig, AssistId, AssistKind, AssistResolveStrategy, SingleResolve,
+};
+pub use ide_completion::{
+ CallableSnippets, CompletionConfig, CompletionItem, CompletionItemKind, CompletionRelevance,
+ Snippet, SnippetScope,
+};
+pub use ide_db::{
+ base_db::{
+ Cancelled, Change, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange,
+ SourceRoot, SourceRootId,
+ },
+ label::Label,
+ line_index::{LineCol, LineColUtf16, LineIndex},
+ search::{ReferenceCategory, SearchScope},
+ source_change::{FileSystemEdit, SourceChange},
+ symbol_index::Query,
+ RootDatabase, SymbolKind,
+};
+pub use ide_diagnostics::{Diagnostic, DiagnosticsConfig, ExprFillDefaultMode, Severity};
+pub use ide_ssr::SsrError;
+pub use syntax::{TextRange, TextSize};
+pub use text_edit::{Indel, TextEdit};
+
+pub type Cancellable<T> = Result<T, Cancelled>;
+
+/// Info associated with a text range.
+#[derive(Debug)]
+pub struct RangeInfo<T> {
+ pub range: TextRange,
+ pub info: T,
+}
+
+impl<T> RangeInfo<T> {
+ pub fn new(range: TextRange, info: T) -> RangeInfo<T> {
+ RangeInfo { range, info }
+ }
+}
+
+/// `AnalysisHost` stores the current state of the world.
+#[derive(Debug)]
+pub struct AnalysisHost {
+ db: RootDatabase,
+}
+
+impl AnalysisHost {
+ pub fn new(lru_capacity: Option<usize>) -> AnalysisHost {
+ AnalysisHost { db: RootDatabase::new(lru_capacity) }
+ }
+
+ pub fn update_lru_capacity(&mut self, lru_capacity: Option<usize>) {
+ self.db.update_lru_capacity(lru_capacity);
+ }
+
+ /// Returns a snapshot of the current state, which you can query for
+ /// semantic information.
+ pub fn analysis(&self) -> Analysis {
+ Analysis { db: self.db.snapshot() }
+ }
+
+ /// Applies changes to the current state of the world. If there are
+ /// outstanding snapshots, they will be canceled.
+ pub fn apply_change(&mut self, change: Change) {
+ self.db.apply_change(change)
+ }
+
+ /// NB: this clears the database
+ pub fn per_query_memory_usage(&mut self) -> Vec<(String, profile::Bytes)> {
+ self.db.per_query_memory_usage()
+ }
+ pub fn request_cancellation(&mut self) {
+ self.db.request_cancellation();
+ }
+ pub fn raw_database(&self) -> &RootDatabase {
+ &self.db
+ }
+ pub fn raw_database_mut(&mut self) -> &mut RootDatabase {
+ &mut self.db
+ }
+
+ pub fn shuffle_crate_graph(&mut self) {
+ shuffle_crate_graph::shuffle_crate_graph(&mut self.db);
+ }
+}
+
+impl Default for AnalysisHost {
+ fn default() -> AnalysisHost {
+ AnalysisHost::new(None)
+ }
+}
+
+/// Analysis is a snapshot of a world state at a moment in time. It is the main
+/// entry point for asking semantic information about the world. When the world
+/// state is advanced using `AnalysisHost::apply_change` method, all existing
+/// `Analysis` are canceled (most method return `Err(Canceled)`).
+#[derive(Debug)]
+pub struct Analysis {
+ db: salsa::Snapshot<RootDatabase>,
+}
+
+// As a general design guideline, `Analysis` API are intended to be independent
+// from the language server protocol. That is, when exposing some functionality
+// we should think in terms of "what API makes most sense" and not in terms of
+// "what types LSP uses". Although currently LSP is the only consumer of the
+// API, the API should in theory be usable as a library, or via a different
+// protocol.
+impl Analysis {
+ // Creates an analysis instance for a single file, without any external
+ // dependencies, stdlib support or ability to apply changes. See
+ // `AnalysisHost` for creating a fully-featured analysis.
+ pub fn from_single_file(text: String) -> (Analysis, FileId) {
+ let mut host = AnalysisHost::default();
+ let file_id = FileId(0);
+ let mut file_set = FileSet::default();
+ file_set.insert(file_id, VfsPath::new_virtual_path("/main.rs".to_string()));
+ let source_root = SourceRoot::new_local(file_set);
+
+ let mut change = Change::new();
+ change.set_roots(vec![source_root]);
+ let mut crate_graph = CrateGraph::default();
+ // FIXME: cfg options
+ // Default to enable test for single file.
+ let mut cfg_options = CfgOptions::default();
+ cfg_options.insert_atom("test".into());
+ crate_graph.add_crate_root(
+ file_id,
+ Edition::CURRENT,
+ None,
+ None,
+ cfg_options.clone(),
+ cfg_options,
+ Env::default(),
+ Ok(Vec::new()),
+ false,
+ CrateOrigin::CratesIo { repo: None },
+ );
+ change.change_file(file_id, Some(Arc::new(text)));
+ change.set_crate_graph(crate_graph);
+ host.apply_change(change);
+ (host.analysis(), file_id)
+ }
+
+ /// Debug info about the current state of the analysis.
+ pub fn status(&self, file_id: Option<FileId>) -> Cancellable<String> {
+ self.with_db(|db| status::status(&*db, file_id))
+ }
+
+ pub fn parallel_prime_caches<F>(&self, num_worker_threads: u8, cb: F) -> Cancellable<()>
+ where
+ F: Fn(ParallelPrimeCachesProgress) + Sync + std::panic::UnwindSafe,
+ {
+ self.with_db(move |db| prime_caches::parallel_prime_caches(db, num_worker_threads, &cb))
+ }
+
+ /// Gets the text of the source file.
+ pub fn file_text(&self, file_id: FileId) -> Cancellable<Arc<String>> {
+ self.with_db(|db| db.file_text(file_id))
+ }
+
+ /// Gets the syntax tree of the file.
+ pub fn parse(&self, file_id: FileId) -> Cancellable<SourceFile> {
+ self.with_db(|db| db.parse(file_id).tree())
+ }
+
+ /// Returns true if this file belongs to an immutable library.
+ pub fn is_library_file(&self, file_id: FileId) -> Cancellable<bool> {
+ use ide_db::base_db::SourceDatabaseExt;
+ self.with_db(|db| db.source_root(db.file_source_root(file_id)).is_library)
+ }
+
+ /// Gets the file's `LineIndex`: data structure to convert between absolute
+ /// offsets and line/column representation.
+ pub fn file_line_index(&self, file_id: FileId) -> Cancellable<Arc<LineIndex>> {
+ self.with_db(|db| db.line_index(file_id))
+ }
+
+ /// Selects the next syntactic nodes encompassing the range.
+ pub fn extend_selection(&self, frange: FileRange) -> Cancellable<TextRange> {
+ self.with_db(|db| extend_selection::extend_selection(db, frange))
+ }
+
+ /// Returns position of the matching brace (all types of braces are
+ /// supported).
+ pub fn matching_brace(&self, position: FilePosition) -> Cancellable<Option<TextSize>> {
+ self.with_db(|db| {
+ let parse = db.parse(position.file_id);
+ let file = parse.tree();
+ matching_brace::matching_brace(&file, position.offset)
+ })
+ }
+
+ /// Returns a syntax tree represented as `String`, for debug purposes.
+ // FIXME: use a better name here.
+ pub fn syntax_tree(
+ &self,
+ file_id: FileId,
+ text_range: Option<TextRange>,
+ ) -> Cancellable<String> {
+ self.with_db(|db| syntax_tree::syntax_tree(db, file_id, text_range))
+ }
+
+ pub fn view_hir(&self, position: FilePosition) -> Cancellable<String> {
+ self.with_db(|db| view_hir::view_hir(db, position))
+ }
+
+ pub fn view_item_tree(&self, file_id: FileId) -> Cancellable<String> {
+ self.with_db(|db| view_item_tree::view_item_tree(db, file_id))
+ }
+
+ /// Renders the crate graph to GraphViz "dot" syntax.
+ pub fn view_crate_graph(&self, full: bool) -> Cancellable<Result<String, String>> {
+ self.with_db(|db| view_crate_graph::view_crate_graph(db, full))
+ }
+
+ pub fn expand_macro(&self, position: FilePosition) -> Cancellable<Option<ExpandedMacro>> {
+ self.with_db(|db| expand_macro::expand_macro(db, position))
+ }
+
+ /// Returns an edit to remove all newlines in the range, cleaning up minor
+ /// stuff like trailing commas.
+ pub fn join_lines(&self, config: &JoinLinesConfig, frange: FileRange) -> Cancellable<TextEdit> {
+ self.with_db(|db| {
+ let parse = db.parse(frange.file_id);
+ join_lines::join_lines(config, &parse.tree(), frange.range)
+ })
+ }
+
+ /// Returns an edit which should be applied when opening a new line, fixing
+ /// up minor stuff like continuing the comment.
+ /// The edit will be a snippet (with `$0`).
+ pub fn on_enter(&self, position: FilePosition) -> Cancellable<Option<TextEdit>> {
+ self.with_db(|db| typing::on_enter(db, position))
+ }
+
+ /// Returns an edit which should be applied after a character was typed.
+ ///
+ /// This is useful for some on-the-fly fixups, like adding `;` to `let =`
+ /// automatically.
+ pub fn on_char_typed(
+ &self,
+ position: FilePosition,
+ char_typed: char,
+ autoclose: bool,
+ ) -> Cancellable<Option<SourceChange>> {
+ // Fast path to not even parse the file.
+ if !typing::TRIGGER_CHARS.contains(char_typed) {
+ return Ok(None);
+ }
+ if char_typed == '<' && !autoclose {
+ return Ok(None);
+ }
+
+ self.with_db(|db| typing::on_char_typed(db, position, char_typed))
+ }
+
+ /// Returns a tree representation of symbols in the file. Useful to draw a
+ /// file outline.
+ pub fn file_structure(&self, file_id: FileId) -> Cancellable<Vec<StructureNode>> {
+ self.with_db(|db| file_structure::file_structure(&db.parse(file_id).tree()))
+ }
+
+ /// Returns a list of the places in the file where type hints can be displayed.
+ pub fn inlay_hints(
+ &self,
+ config: &InlayHintsConfig,
+ file_id: FileId,
+ range: Option<FileRange>,
+ ) -> Cancellable<Vec<InlayHint>> {
+ self.with_db(|db| inlay_hints::inlay_hints(db, file_id, range, config))
+ }
+
+ /// Returns the set of folding ranges.
+ pub fn folding_ranges(&self, file_id: FileId) -> Cancellable<Vec<Fold>> {
+ self.with_db(|db| folding_ranges::folding_ranges(&db.parse(file_id).tree()))
+ }
+
+ /// Fuzzy searches for a symbol.
+ pub fn symbol_search(&self, query: Query) -> Cancellable<Vec<NavigationTarget>> {
+ self.with_db(|db| {
+ symbol_index::world_symbols(db, query)
+ .into_iter() // xx: should we make this a par iter?
+ .filter_map(|s| s.try_to_nav(db))
+ .collect::<Vec<_>>()
+ })
+ }
+
+ /// Returns the definitions from the symbol at `position`.
+ pub fn goto_definition(
+ &self,
+ position: FilePosition,
+ ) -> Cancellable<Option<RangeInfo<Vec<NavigationTarget>>>> {
+ self.with_db(|db| goto_definition::goto_definition(db, position))
+ }
+
+ /// Returns the declaration from the symbol at `position`.
+ pub fn goto_declaration(
+ &self,
+ position: FilePosition,
+ ) -> Cancellable<Option<RangeInfo<Vec<NavigationTarget>>>> {
+ self.with_db(|db| goto_declaration::goto_declaration(db, position))
+ }
+
+ /// Returns the impls from the symbol at `position`.
+ pub fn goto_implementation(
+ &self,
+ position: FilePosition,
+ ) -> Cancellable<Option<RangeInfo<Vec<NavigationTarget>>>> {
+ self.with_db(|db| goto_implementation::goto_implementation(db, position))
+ }
+
+ /// Returns the type definitions for the symbol at `position`.
+ pub fn goto_type_definition(
+ &self,
+ position: FilePosition,
+ ) -> Cancellable<Option<RangeInfo<Vec<NavigationTarget>>>> {
+ self.with_db(|db| goto_type_definition::goto_type_definition(db, position))
+ }
+
+ /// Finds all usages of the reference at point.
+ pub fn find_all_refs(
+ &self,
+ position: FilePosition,
+ search_scope: Option<SearchScope>,
+ ) -> Cancellable<Option<Vec<ReferenceSearchResult>>> {
+ self.with_db(|db| references::find_all_refs(&Semantics::new(db), position, search_scope))
+ }
+
+ /// Finds all methods and free functions for the file. Does not return tests!
+ pub fn find_all_methods(&self, file_id: FileId) -> Cancellable<Vec<FileRange>> {
+ self.with_db(|db| fn_references::find_all_methods(db, file_id))
+ }
+
+ /// Returns a short text describing element at position.
+ pub fn hover(
+ &self,
+ config: &HoverConfig,
+ range: FileRange,
+ ) -> Cancellable<Option<RangeInfo<HoverResult>>> {
+ self.with_db(|db| hover::hover(db, range, config))
+ }
+
+ /// Returns moniker of symbol at position.
+ pub fn moniker(
+ &self,
+ position: FilePosition,
+ ) -> Cancellable<Option<RangeInfo<Vec<moniker::MonikerResult>>>> {
+ self.with_db(|db| moniker::moniker(db, position))
+ }
+
+ /// Return URL(s) for the documentation of the symbol under the cursor.
+ pub fn external_docs(
+ &self,
+ position: FilePosition,
+ ) -> Cancellable<Option<doc_links::DocumentationLink>> {
+ self.with_db(|db| doc_links::external_docs(db, &position))
+ }
+
+ /// Computes parameter information at the given position.
+ pub fn signature_help(&self, position: FilePosition) -> Cancellable<Option<SignatureHelp>> {
+ self.with_db(|db| signature_help::signature_help(db, position))
+ }
+
+ /// Computes call hierarchy candidates for the given file position.
+ pub fn call_hierarchy(
+ &self,
+ position: FilePosition,
+ ) -> Cancellable<Option<RangeInfo<Vec<NavigationTarget>>>> {
+ self.with_db(|db| call_hierarchy::call_hierarchy(db, position))
+ }
+
+ /// Computes incoming calls for the given file position.
+ pub fn incoming_calls(&self, position: FilePosition) -> Cancellable<Option<Vec<CallItem>>> {
+ self.with_db(|db| call_hierarchy::incoming_calls(db, position))
+ }
+
+ /// Computes outgoing calls for the given file position.
+ pub fn outgoing_calls(&self, position: FilePosition) -> Cancellable<Option<Vec<CallItem>>> {
+ self.with_db(|db| call_hierarchy::outgoing_calls(db, position))
+ }
+
+ /// Returns a `mod name;` declaration which created the current module.
+ pub fn parent_module(&self, position: FilePosition) -> Cancellable<Vec<NavigationTarget>> {
+ self.with_db(|db| parent_module::parent_module(db, position))
+ }
+
+ /// Returns crates this file belongs too.
+ pub fn crate_for(&self, file_id: FileId) -> Cancellable<Vec<CrateId>> {
+ self.with_db(|db| parent_module::crate_for(db, file_id))
+ }
+
+ /// Returns the edition of the given crate.
+ pub fn crate_edition(&self, crate_id: CrateId) -> Cancellable<Edition> {
+ self.with_db(|db| db.crate_graph()[crate_id].edition)
+ }
+
+ /// Returns the root file of the given crate.
+ pub fn crate_root(&self, crate_id: CrateId) -> Cancellable<FileId> {
+ self.with_db(|db| db.crate_graph()[crate_id].root_file_id)
+ }
+
+ /// Returns the set of possible targets to run for the current file.
+ pub fn runnables(&self, file_id: FileId) -> Cancellable<Vec<Runnable>> {
+ self.with_db(|db| runnables::runnables(db, file_id))
+ }
+
+ /// Returns the set of tests for the given file position.
+ pub fn related_tests(
+ &self,
+ position: FilePosition,
+ search_scope: Option<SearchScope>,
+ ) -> Cancellable<Vec<Runnable>> {
+ self.with_db(|db| runnables::related_tests(db, position, search_scope))
+ }
+
+ /// Computes syntax highlighting for the given file
+ pub fn highlight(&self, file_id: FileId) -> Cancellable<Vec<HlRange>> {
+ self.with_db(|db| syntax_highlighting::highlight(db, file_id, None, false))
+ }
+
+ /// Computes all ranges to highlight for a given item in a file.
+ pub fn highlight_related(
+ &self,
+ config: HighlightRelatedConfig,
+ position: FilePosition,
+ ) -> Cancellable<Option<Vec<HighlightedRange>>> {
+ self.with_db(|db| {
+ highlight_related::highlight_related(&Semantics::new(db), config, position)
+ })
+ }
+
+ /// Computes syntax highlighting for the given file range.
+ pub fn highlight_range(&self, frange: FileRange) -> Cancellable<Vec<HlRange>> {
+ self.with_db(|db| {
+ syntax_highlighting::highlight(db, frange.file_id, Some(frange.range), false)
+ })
+ }
+
+ /// Computes syntax highlighting for the given file.
+ pub fn highlight_as_html(&self, file_id: FileId, rainbow: bool) -> Cancellable<String> {
+ self.with_db(|db| syntax_highlighting::highlight_as_html(db, file_id, rainbow))
+ }
+
+ /// Computes completions at the given position.
+ pub fn completions(
+ &self,
+ config: &CompletionConfig,
+ position: FilePosition,
+ trigger_character: Option<char>,
+ ) -> Cancellable<Option<Vec<CompletionItem>>> {
+ self.with_db(|db| {
+ ide_completion::completions(db, config, position, trigger_character).map(Into::into)
+ })
+ }
+
+ /// Resolves additional completion data at the position given.
+ pub fn resolve_completion_edits(
+ &self,
+ config: &CompletionConfig,
+ position: FilePosition,
+ imports: impl IntoIterator<Item = (String, String)> + std::panic::UnwindSafe,
+ ) -> Cancellable<Vec<TextEdit>> {
+ Ok(self
+ .with_db(|db| ide_completion::resolve_completion_edits(db, config, position, imports))?
+ .unwrap_or_default())
+ }
+
+ /// Computes the set of diagnostics for the given file.
+ pub fn diagnostics(
+ &self,
+ config: &DiagnosticsConfig,
+ resolve: AssistResolveStrategy,
+ file_id: FileId,
+ ) -> Cancellable<Vec<Diagnostic>> {
+ self.with_db(|db| ide_diagnostics::diagnostics(db, config, &resolve, file_id))
+ }
+
+ /// Convenience function to return assists + quick fixes for diagnostics
+ pub fn assists_with_fixes(
+ &self,
+ assist_config: &AssistConfig,
+ diagnostics_config: &DiagnosticsConfig,
+ resolve: AssistResolveStrategy,
+ frange: FileRange,
+ ) -> Cancellable<Vec<Assist>> {
+ let include_fixes = match &assist_config.allowed {
+ Some(it) => it.iter().any(|&it| it == AssistKind::None || it == AssistKind::QuickFix),
+ None => true,
+ };
+
+ self.with_db(|db| {
+ let diagnostic_assists = if include_fixes {
+ ide_diagnostics::diagnostics(db, diagnostics_config, &resolve, frange.file_id)
+ .into_iter()
+ .flat_map(|it| it.fixes.unwrap_or_default())
+ .filter(|it| it.target.intersect(frange.range).is_some())
+ .collect()
+ } else {
+ Vec::new()
+ };
+ let ssr_assists = ssr::ssr_assists(db, &resolve, frange);
+ let assists = ide_assists::assists(db, assist_config, resolve, frange);
+
+ let mut res = diagnostic_assists;
+ res.extend(ssr_assists.into_iter());
+ res.extend(assists.into_iter());
+
+ res
+ })
+ }
+
+ /// Returns the edit required to rename reference at the position to the new
+ /// name.
+ pub fn rename(
+ &self,
+ position: FilePosition,
+ new_name: &str,
+ ) -> Cancellable<Result<SourceChange, RenameError>> {
+ self.with_db(|db| rename::rename(db, position, new_name))
+ }
+
+ pub fn prepare_rename(
+ &self,
+ position: FilePosition,
+ ) -> Cancellable<Result<RangeInfo<()>, RenameError>> {
+ self.with_db(|db| rename::prepare_rename(db, position))
+ }
+
+ pub fn will_rename_file(
+ &self,
+ file_id: FileId,
+ new_name_stem: &str,
+ ) -> Cancellable<Option<SourceChange>> {
+ self.with_db(|db| rename::will_rename_file(db, file_id, new_name_stem))
+ }
+
+ pub fn structural_search_replace(
+ &self,
+ query: &str,
+ parse_only: bool,
+ resolve_context: FilePosition,
+ selections: Vec<FileRange>,
+ ) -> Cancellable<Result<SourceChange, SsrError>> {
+ self.with_db(|db| {
+ let rule: ide_ssr::SsrRule = query.parse()?;
+ let mut match_finder =
+ ide_ssr::MatchFinder::in_context(db, resolve_context, selections)?;
+ match_finder.add_rule(rule)?;
+ let edits = if parse_only { Default::default() } else { match_finder.edits() };
+ Ok(SourceChange::from(edits))
+ })
+ }
+
+ pub fn annotations(
+ &self,
+ config: &AnnotationConfig,
+ file_id: FileId,
+ ) -> Cancellable<Vec<Annotation>> {
+ self.with_db(|db| annotations::annotations(db, config, file_id))
+ }
+
+ pub fn resolve_annotation(&self, annotation: Annotation) -> Cancellable<Annotation> {
+ self.with_db(|db| annotations::resolve_annotation(db, annotation))
+ }
+
+ pub fn move_item(
+ &self,
+ range: FileRange,
+ direction: Direction,
+ ) -> Cancellable<Option<TextEdit>> {
+ self.with_db(|db| move_item::move_item(db, range, direction))
+ }
+
+ /// Performs an operation on the database that may be canceled.
+ ///
+ /// rust-analyzer needs to be able to answer semantic questions about the
+ /// code while the code is being modified. A common problem is that a
+ /// long-running query is being calculated when a new change arrives.
+ ///
+ /// We can't just apply the change immediately: this will cause the pending
+ /// query to see inconsistent state (it will observe an absence of
+ /// repeatable read). So what we do is we **cancel** all pending queries
+ /// before applying the change.
+ ///
+ /// Salsa implements cancellation by unwinding with a special value and
+ /// catching it on the API boundary.
+ fn with_db<F, T>(&self, f: F) -> Cancellable<T>
+ where
+ F: FnOnce(&RootDatabase) -> T + std::panic::UnwindSafe,
+ {
+ Cancelled::catch(|| f(&self.db))
+ }
+}
+
+#[test]
+fn analysis_is_send() {
+ fn is_send<T: Send>() {}
+ is_send::<Analysis>();
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/markdown_remove.rs b/src/tools/rust-analyzer/crates/ide/src/markdown_remove.rs
new file mode 100644
index 000000000..3ec5c629e
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/markdown_remove.rs
@@ -0,0 +1,22 @@
+//! Removes markdown from strings.
+use pulldown_cmark::{Event, Parser, Tag};
+
+/// Removes all markdown, keeping the text and code blocks
+///
+/// Currently limited in styling, i.e. no ascii tables or lists
+pub(crate) fn remove_markdown(markdown: &str) -> String {
+ let mut out = String::new();
+ let parser = Parser::new(markdown);
+
+ for event in parser {
+ match event {
+ Event::Text(text) | Event::Code(text) => out.push_str(&text),
+ Event::SoftBreak | Event::HardBreak | Event::Rule | Event::End(Tag::CodeBlock(_)) => {
+ out.push('\n')
+ }
+ _ => {}
+ }
+ }
+
+ out
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/markup.rs b/src/tools/rust-analyzer/crates/ide/src/markup.rs
new file mode 100644
index 000000000..60c193c40
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/markup.rs
@@ -0,0 +1,38 @@
+//! Markdown formatting.
+//!
+//! Sometimes, we want to display a "rich text" in the UI. At the moment, we use
+//! markdown for this purpose. It doesn't feel like a right option, but that's
+//! what is used by LSP, so let's keep it simple.
+use std::fmt;
+
+#[derive(Default, Debug)]
+pub struct Markup {
+ text: String,
+}
+
+impl From<Markup> for String {
+ fn from(markup: Markup) -> Self {
+ markup.text
+ }
+}
+
+impl From<String> for Markup {
+ fn from(text: String) -> Self {
+ Markup { text }
+ }
+}
+
+impl fmt::Display for Markup {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Display::fmt(&self.text, f)
+ }
+}
+
+impl Markup {
+ pub fn as_str(&self) -> &str {
+ self.text.as_str()
+ }
+ pub fn fenced_block(contents: &impl fmt::Display) -> Markup {
+ format!("```rust\n{}\n```", contents).into()
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/matching_brace.rs b/src/tools/rust-analyzer/crates/ide/src/matching_brace.rs
new file mode 100644
index 000000000..da70cecdd
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/matching_brace.rs
@@ -0,0 +1,78 @@
+use syntax::{
+ ast::{self, AstNode},
+ SourceFile, SyntaxKind, TextSize, T,
+};
+
+// Feature: Matching Brace
+//
+// If the cursor is on any brace (`<>(){}[]||`) which is a part of a brace-pair,
+// moves cursor to the matching brace. It uses the actual parser to determine
+// braces, so it won't confuse generics with comparisons.
+//
+// |===
+// | Editor | Action Name
+//
+// | VS Code | **Rust Analyzer: Find matching brace**
+// |===
+//
+// image::https://user-images.githubusercontent.com/48062697/113065573-04298180-91b1-11eb-8dec-d4e2a202f304.gif[]
+pub(crate) fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<TextSize> {
+ const BRACES: &[SyntaxKind] =
+ &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>], T![|], T![|]];
+ let (brace_token, brace_idx) = file
+ .syntax()
+ .token_at_offset(offset)
+ .filter_map(|node| {
+ let idx = BRACES.iter().position(|&brace| brace == node.kind())?;
+ Some((node, idx))
+ })
+ .last()?;
+ let parent = brace_token.parent()?;
+ if brace_token.kind() == T![|] && !ast::ParamList::can_cast(parent.kind()) {
+ cov_mark::hit!(pipes_not_braces);
+ return None;
+ }
+ let matching_kind = BRACES[brace_idx ^ 1];
+ let matching_node = parent
+ .children_with_tokens()
+ .filter_map(|it| it.into_token())
+ .find(|node| node.kind() == matching_kind && node != &brace_token)?;
+ Some(matching_node.text_range().start())
+}
+
+#[cfg(test)]
+mod tests {
+ use test_utils::{add_cursor, assert_eq_text, extract_offset};
+
+ use super::*;
+
+ #[test]
+ fn test_matching_brace() {
+ fn do_check(before: &str, after: &str) {
+ let (pos, before) = extract_offset(before);
+ let parse = SourceFile::parse(&before);
+ let new_pos = match matching_brace(&parse.tree(), pos) {
+ None => pos,
+ Some(pos) => pos,
+ };
+ let actual = add_cursor(&before, new_pos);
+ assert_eq_text!(after, &actual);
+ }
+
+ do_check("struct Foo { a: i32, }$0", "struct Foo $0{ a: i32, }");
+ do_check("fn main() { |x: i32|$0 x * 2;}", "fn main() { $0|x: i32| x * 2;}");
+ do_check("fn main() { $0|x: i32| x * 2;}", "fn main() { |x: i32$0| x * 2;}");
+ do_check(
+ "fn func(x) { return (2 * (x + 3)$0) + 5;}",
+ "fn func(x) { return $0(2 * (x + 3)) + 5;}",
+ );
+
+ {
+ cov_mark::check!(pipes_not_braces);
+ do_check(
+ "fn main() { match 92 { 1 | 2 |$0 3 => 92 } }",
+ "fn main() { match 92 { 1 | 2 |$0 3 => 92 } }",
+ );
+ }
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/moniker.rs b/src/tools/rust-analyzer/crates/ide/src/moniker.rs
new file mode 100644
index 000000000..6bab9fa1e
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/moniker.rs
@@ -0,0 +1,342 @@
+//! This module generates [moniker](https://microsoft.github.io/language-server-protocol/specifications/lsif/0.6.0/specification/#exportsImports)
+//! for LSIF and LSP.
+
+use hir::{db::DefDatabase, AsAssocItem, AssocItemContainer, Crate, Name, Semantics};
+use ide_db::{
+ base_db::{CrateOrigin, FileId, FileLoader, FilePosition, LangCrateOrigin},
+ defs::{Definition, IdentClass},
+ helpers::pick_best_token,
+ RootDatabase,
+};
+use itertools::Itertools;
+use syntax::{AstNode, SyntaxKind::*, T};
+
+use crate::{doc_links::token_as_doc_comment, RangeInfo};
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct MonikerIdentifier {
+ crate_name: String,
+ path: Vec<Name>,
+}
+
+impl ToString for MonikerIdentifier {
+ fn to_string(&self) -> String {
+ match self {
+ MonikerIdentifier { path, crate_name } => {
+ format!("{}::{}", crate_name, path.iter().map(|x| x.to_string()).join("::"))
+ }
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum MonikerKind {
+ Import,
+ Export,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct MonikerResult {
+ pub identifier: MonikerIdentifier,
+ pub kind: MonikerKind,
+ pub package_information: PackageInformation,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct PackageInformation {
+ pub name: String,
+ pub repo: String,
+ pub version: String,
+}
+
+pub(crate) fn crate_for_file(db: &RootDatabase, file_id: FileId) -> Option<Crate> {
+ for &krate in db.relevant_crates(file_id).iter() {
+ let crate_def_map = db.crate_def_map(krate);
+ for (_, data) in crate_def_map.modules() {
+ if data.origin.file_id() == Some(file_id) {
+ return Some(krate.into());
+ }
+ }
+ }
+ None
+}
+
+pub(crate) fn moniker(
+ db: &RootDatabase,
+ FilePosition { file_id, offset }: FilePosition,
+) -> Option<RangeInfo<Vec<MonikerResult>>> {
+ let sema = &Semantics::new(db);
+ let file = sema.parse(file_id).syntax().clone();
+ let current_crate = crate_for_file(db, file_id)?;
+ let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
+ IDENT
+ | INT_NUMBER
+ | LIFETIME_IDENT
+ | T![self]
+ | T![super]
+ | T![crate]
+ | T![Self]
+ | COMMENT => 2,
+ kind if kind.is_trivia() => 0,
+ _ => 1,
+ })?;
+ if let Some(doc_comment) = token_as_doc_comment(&original_token) {
+ return doc_comment.get_definition_with_descend_at(sema, offset, |def, _, _| {
+ let m = def_to_moniker(db, def, current_crate)?;
+ Some(RangeInfo::new(original_token.text_range(), vec![m]))
+ });
+ }
+ let navs = sema
+ .descend_into_macros(original_token.clone())
+ .into_iter()
+ .filter_map(|token| {
+ IdentClass::classify_token(sema, &token).map(IdentClass::definitions).map(|it| {
+ it.into_iter().flat_map(|def| def_to_moniker(sema.db, def, current_crate))
+ })
+ })
+ .flatten()
+ .unique()
+ .collect::<Vec<_>>();
+ Some(RangeInfo::new(original_token.text_range(), navs))
+}
+
+pub(crate) fn def_to_moniker(
+ db: &RootDatabase,
+ def: Definition,
+ from_crate: Crate,
+) -> Option<MonikerResult> {
+ if matches!(def, Definition::GenericParam(_) | Definition::SelfType(_) | Definition::Local(_)) {
+ return None;
+ }
+ let module = def.module(db)?;
+ let krate = module.krate();
+ let mut path = vec![];
+ path.extend(module.path_to_root(db).into_iter().filter_map(|x| x.name(db)));
+
+ // Handle associated items within a trait
+ if let Some(assoc) = def.as_assoc_item(db) {
+ let container = assoc.container(db);
+ match container {
+ AssocItemContainer::Trait(trait_) => {
+ // Because different traits can have functions with the same name,
+ // we have to include the trait name as part of the moniker for uniqueness.
+ path.push(trait_.name(db));
+ }
+ AssocItemContainer::Impl(impl_) => {
+ // Because a struct can implement multiple traits, for implementations
+ // we add both the struct name and the trait name to the path
+ if let Some(adt) = impl_.self_ty(db).as_adt() {
+ path.push(adt.name(db));
+ }
+
+ if let Some(trait_) = impl_.trait_(db) {
+ path.push(trait_.name(db));
+ }
+ }
+ }
+ }
+
+ if let Definition::Field(it) = def {
+ path.push(it.parent_def(db).name(db));
+ }
+
+ path.push(def.name(db)?);
+ Some(MonikerResult {
+ identifier: MonikerIdentifier {
+ crate_name: krate.display_name(db)?.crate_name().to_string(),
+ path,
+ },
+ kind: if krate == from_crate { MonikerKind::Export } else { MonikerKind::Import },
+ package_information: {
+ let name = krate.display_name(db)?.to_string();
+ let (repo, version) = match krate.origin(db) {
+ CrateOrigin::CratesIo { repo } => (repo?, krate.version(db)?),
+ CrateOrigin::Lang(lang) => (
+ "https://github.com/rust-lang/rust/".to_string(),
+ match lang {
+ LangCrateOrigin::Other => {
+ "https://github.com/rust-lang/rust/library/".into()
+ }
+ lang => format!("https://github.com/rust-lang/rust/library/{lang}",),
+ },
+ ),
+ };
+ PackageInformation { name, repo, version }
+ },
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::fixture;
+
+ use super::MonikerKind;
+
+ #[track_caller]
+ fn no_moniker(ra_fixture: &str) {
+ let (analysis, position) = fixture::position(ra_fixture);
+ if let Some(x) = analysis.moniker(position).unwrap() {
+ assert_eq!(x.info.len(), 0, "Moniker founded but no moniker expected: {:?}", x);
+ }
+ }
+
+ #[track_caller]
+ fn check_moniker(ra_fixture: &str, identifier: &str, package: &str, kind: MonikerKind) {
+ let (analysis, position) = fixture::position(ra_fixture);
+ let x = analysis.moniker(position).unwrap().expect("no moniker found").info;
+ assert_eq!(x.len(), 1);
+ let x = x.into_iter().next().unwrap();
+ assert_eq!(identifier, x.identifier.to_string());
+ assert_eq!(package, format!("{:?}", x.package_information));
+ assert_eq!(kind, x.kind);
+ }
+
+ #[test]
+ fn basic() {
+ check_moniker(
+ r#"
+//- /lib.rs crate:main deps:foo
+use foo::module::func;
+fn main() {
+ func$0();
+}
+//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+pub mod module {
+ pub fn func() {}
+}
+"#,
+ "foo::module::func",
+ r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
+ MonikerKind::Import,
+ );
+ check_moniker(
+ r#"
+//- /lib.rs crate:main deps:foo
+use foo::module::func;
+fn main() {
+ func();
+}
+//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+pub mod module {
+ pub fn func$0() {}
+}
+"#,
+ "foo::module::func",
+ r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
+ MonikerKind::Export,
+ );
+ }
+
+ #[test]
+ fn moniker_for_trait() {
+ check_moniker(
+ r#"
+//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+pub mod module {
+ pub trait MyTrait {
+ pub fn func$0() {}
+ }
+}
+"#,
+ "foo::module::MyTrait::func",
+ r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
+ MonikerKind::Export,
+ );
+ }
+
+ #[test]
+ fn moniker_for_trait_constant() {
+ check_moniker(
+ r#"
+//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+pub mod module {
+ pub trait MyTrait {
+ const MY_CONST$0: u8;
+ }
+}
+"#,
+ "foo::module::MyTrait::MY_CONST",
+ r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
+ MonikerKind::Export,
+ );
+ }
+
+ #[test]
+ fn moniker_for_trait_type() {
+ check_moniker(
+ r#"
+//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+pub mod module {
+ pub trait MyTrait {
+ type MyType$0;
+ }
+}
+"#,
+ "foo::module::MyTrait::MyType",
+ r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
+ MonikerKind::Export,
+ );
+ }
+
+ #[test]
+ fn moniker_for_trait_impl_function() {
+ check_moniker(
+ r#"
+//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+pub mod module {
+ pub trait MyTrait {
+ pub fn func() {}
+ }
+
+ struct MyStruct {}
+
+ impl MyTrait for MyStruct {
+ pub fn func$0() {}
+ }
+}
+"#,
+ "foo::module::MyStruct::MyTrait::func",
+ r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
+ MonikerKind::Export,
+ );
+ }
+
+ #[test]
+ fn moniker_for_field() {
+ check_moniker(
+ r#"
+//- /lib.rs crate:main deps:foo
+use foo::St;
+fn main() {
+ let x = St { a$0: 2 };
+}
+//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+pub struct St {
+ pub a: i32,
+}
+"#,
+ "foo::St::a",
+ r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
+ MonikerKind::Import,
+ );
+ }
+
+ #[test]
+ fn no_moniker_for_local() {
+ no_moniker(
+ r#"
+//- /lib.rs crate:main deps:foo
+use foo::module::func;
+fn main() {
+ func();
+}
+//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+pub mod module {
+ pub fn func() {
+ let x$0 = 2;
+ }
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/move_item.rs b/src/tools/rust-analyzer/crates/ide/src/move_item.rs
new file mode 100644
index 000000000..02e9fb8b5
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/move_item.rs
@@ -0,0 +1,890 @@
+use std::{iter::once, mem};
+
+use hir::Semantics;
+use ide_db::{base_db::FileRange, helpers::pick_best_token, RootDatabase};
+use itertools::Itertools;
+use syntax::{algo, ast, match_ast, AstNode, SyntaxElement, SyntaxKind, SyntaxNode, TextRange};
+use text_edit::{TextEdit, TextEditBuilder};
+
+#[derive(Copy, Clone, Debug)]
+pub enum Direction {
+ Up,
+ Down,
+}
+
+// Feature: Move Item
+//
+// Move item under cursor or selection up and down.
+//
+// |===
+// | Editor | Action Name
+//
+// | VS Code | **Rust Analyzer: Move item up**
+// | VS Code | **Rust Analyzer: Move item down**
+// |===
+//
+// image::https://user-images.githubusercontent.com/48062697/113065576-04298180-91b1-11eb-91ce-4505e99ed598.gif[]
+pub(crate) fn move_item(
+ db: &RootDatabase,
+ range: FileRange,
+ direction: Direction,
+) -> Option<TextEdit> {
+ let sema = Semantics::new(db);
+ let file = sema.parse(range.file_id);
+
+ let item = if range.range.is_empty() {
+ SyntaxElement::Token(pick_best_token(
+ file.syntax().token_at_offset(range.range.start()),
+ |kind| match kind {
+ SyntaxKind::IDENT | SyntaxKind::LIFETIME_IDENT => 2,
+ kind if kind.is_trivia() => 0,
+ _ => 1,
+ },
+ )?)
+ } else {
+ file.syntax().covering_element(range.range)
+ };
+
+ find_ancestors(item, direction, range.range)
+}
+
+fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) -> Option<TextEdit> {
+ let root = match item {
+ SyntaxElement::Node(node) => node,
+ SyntaxElement::Token(token) => token.parent()?,
+ };
+
+ let movable = [
+ SyntaxKind::ARG_LIST,
+ SyntaxKind::GENERIC_PARAM_LIST,
+ SyntaxKind::GENERIC_ARG_LIST,
+ SyntaxKind::VARIANT_LIST,
+ SyntaxKind::TYPE_BOUND_LIST,
+ SyntaxKind::MATCH_ARM,
+ SyntaxKind::PARAM,
+ SyntaxKind::LET_STMT,
+ SyntaxKind::EXPR_STMT,
+ SyntaxKind::IF_EXPR,
+ SyntaxKind::FOR_EXPR,
+ SyntaxKind::LOOP_EXPR,
+ SyntaxKind::WHILE_EXPR,
+ SyntaxKind::RETURN_EXPR,
+ SyntaxKind::MATCH_EXPR,
+ SyntaxKind::MACRO_CALL,
+ SyntaxKind::TYPE_ALIAS,
+ SyntaxKind::TRAIT,
+ SyntaxKind::IMPL,
+ SyntaxKind::MACRO_DEF,
+ SyntaxKind::STRUCT,
+ SyntaxKind::UNION,
+ SyntaxKind::ENUM,
+ SyntaxKind::FN,
+ SyntaxKind::MODULE,
+ SyntaxKind::USE,
+ SyntaxKind::STATIC,
+ SyntaxKind::CONST,
+ SyntaxKind::MACRO_RULES,
+ SyntaxKind::MACRO_DEF,
+ ];
+
+ let ancestor = once(root.clone())
+ .chain(root.ancestors())
+ .find(|ancestor| movable.contains(&ancestor.kind()))?;
+
+ move_in_direction(&ancestor, direction, range)
+}
+
+fn move_in_direction(
+ node: &SyntaxNode,
+ direction: Direction,
+ range: TextRange,
+) -> Option<TextEdit> {
+ match_ast! {
+ match node {
+ ast::ArgList(it) => swap_sibling_in_list(node, it.args(), range, direction),
+ ast::GenericParamList(it) => swap_sibling_in_list(node, it.generic_params(), range, direction),
+ ast::GenericArgList(it) => swap_sibling_in_list(node, it.generic_args(), range, direction),
+ ast::VariantList(it) => swap_sibling_in_list(node, it.variants(), range, direction),
+ ast::TypeBoundList(it) => swap_sibling_in_list(node, it.bounds(), range, direction),
+ _ => Some(replace_nodes(range, node, &match direction {
+ Direction::Up => node.prev_sibling(),
+ Direction::Down => node.next_sibling(),
+ }?))
+ }
+ }
+}
+
+fn swap_sibling_in_list<A: AstNode + Clone, I: Iterator<Item = A>>(
+ node: &SyntaxNode,
+ list: I,
+ range: TextRange,
+ direction: Direction,
+) -> Option<TextEdit> {
+ let list_lookup = list.tuple_windows().find(|(l, r)| match direction {
+ Direction::Up => r.syntax().text_range().contains_range(range),
+ Direction::Down => l.syntax().text_range().contains_range(range),
+ });
+
+ if let Some((l, r)) = list_lookup {
+ Some(replace_nodes(range, l.syntax(), r.syntax()))
+ } else {
+ // Cursor is beyond any movable list item (for example, on curly brace in enum).
+ // It's not necessary, that parent of list is movable (arg list's parent is not, for example),
+ // and we have to continue tree traversal to find suitable node.
+ find_ancestors(SyntaxElement::Node(node.parent()?), direction, range)
+ }
+}
+
+fn replace_nodes<'a>(
+ range: TextRange,
+ mut first: &'a SyntaxNode,
+ mut second: &'a SyntaxNode,
+) -> TextEdit {
+ let cursor_offset = if range.is_empty() {
+ // FIXME: `applySnippetTextEdits` does not support non-empty selection ranges
+ if first.text_range().contains_range(range) {
+ Some(range.start() - first.text_range().start())
+ } else if second.text_range().contains_range(range) {
+ mem::swap(&mut first, &mut second);
+ Some(range.start() - first.text_range().start())
+ } else {
+ None
+ }
+ } else {
+ None
+ };
+
+ let first_with_cursor = match cursor_offset {
+ Some(offset) => {
+ let mut item_text = first.text().to_string();
+ item_text.insert_str(offset.into(), "$0");
+ item_text
+ }
+ None => first.text().to_string(),
+ };
+
+ let mut edit = TextEditBuilder::default();
+
+ algo::diff(first, second).into_text_edit(&mut edit);
+ edit.replace(second.text_range(), first_with_cursor);
+
+ edit.finish()
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::fixture;
+ use expect_test::{expect, Expect};
+
+ use crate::Direction;
+
+ fn check(ra_fixture: &str, expect: Expect, direction: Direction) {
+ let (analysis, range) = fixture::range(ra_fixture);
+ let edit = analysis.move_item(range, direction).unwrap().unwrap_or_default();
+ let mut file = analysis.file_text(range.file_id).unwrap().to_string();
+ edit.apply(&mut file);
+ expect.assert_eq(&file);
+ }
+
+ #[test]
+ fn test_moves_match_arm_up() {
+ check(
+ r#"
+fn main() {
+ match true {
+ true => {
+ println!("Hello, world");
+ },
+ false =>$0$0 {
+ println!("Test");
+ }
+ };
+}
+"#,
+ expect![[r#"
+ fn main() {
+ match true {
+ false =>$0 {
+ println!("Test");
+ }
+ true => {
+ println!("Hello, world");
+ },
+ };
+ }
+ "#]],
+ Direction::Up,
+ );
+ }
+
+ #[test]
+ fn test_moves_match_arm_down() {
+ check(
+ r#"
+fn main() {
+ match true {
+ true =>$0$0 {
+ println!("Hello, world");
+ },
+ false => {
+ println!("Test");
+ }
+ };
+}
+"#,
+ expect![[r#"
+ fn main() {
+ match true {
+ false => {
+ println!("Test");
+ }
+ true =>$0 {
+ println!("Hello, world");
+ },
+ };
+ }
+ "#]],
+ Direction::Down,
+ );
+ }
+
+ #[test]
+ fn test_nowhere_to_move() {
+ check(
+ r#"
+fn main() {
+ match true {
+ true =>$0$0 {
+ println!("Hello, world");
+ },
+ false => {
+ println!("Test");
+ }
+ };
+}
+"#,
+ expect![[r#"
+ fn main() {
+ match true {
+ true => {
+ println!("Hello, world");
+ },
+ false => {
+ println!("Test");
+ }
+ };
+ }
+ "#]],
+ Direction::Up,
+ );
+ }
+
+ #[test]
+ fn test_moves_let_stmt_up() {
+ check(
+ r#"
+fn main() {
+ let test = 123;
+ let test2$0$0 = 456;
+}
+"#,
+ expect![[r#"
+ fn main() {
+ let test2$0 = 456;
+ let test = 123;
+ }
+ "#]],
+ Direction::Up,
+ );
+ }
+
+ #[test]
+ fn test_moves_expr_up() {
+ check(
+ r#"
+fn main() {
+ println!("Hello, world");
+ println!("All I want to say is...");$0$0
+}
+"#,
+ expect![[r#"
+ fn main() {
+ println!("All I want to say is...");$0
+ println!("Hello, world");
+ }
+ "#]],
+ Direction::Up,
+ );
+ check(
+ r#"
+fn main() {
+ println!("Hello, world");
+
+ if true {
+ println!("Test");
+ }$0$0
+}
+"#,
+ expect![[r#"
+ fn main() {
+ if true {
+ println!("Test");
+ }$0
+
+ println!("Hello, world");
+ }
+ "#]],
+ Direction::Up,
+ );
+ check(
+ r#"
+fn main() {
+ println!("Hello, world");
+
+ for i in 0..10 {
+ println!("Test");
+ }$0$0
+}
+"#,
+ expect![[r#"
+ fn main() {
+ for i in 0..10 {
+ println!("Test");
+ }$0
+
+ println!("Hello, world");
+ }
+ "#]],
+ Direction::Up,
+ );
+ check(
+ r#"
+fn main() {
+ println!("Hello, world");
+
+ loop {
+ println!("Test");
+ }$0$0
+}
+"#,
+ expect![[r#"
+ fn main() {
+ loop {
+ println!("Test");
+ }$0
+
+ println!("Hello, world");
+ }
+ "#]],
+ Direction::Up,
+ );
+ check(
+ r#"
+fn main() {
+ println!("Hello, world");
+
+ while true {
+ println!("Test");
+ }$0$0
+}
+"#,
+ expect![[r#"
+ fn main() {
+ while true {
+ println!("Test");
+ }$0
+
+ println!("Hello, world");
+ }
+ "#]],
+ Direction::Up,
+ );
+ check(
+ r#"
+fn main() {
+ println!("Hello, world");
+
+ return 123;$0$0
+}
+"#,
+ expect![[r#"
+ fn main() {
+ return 123;$0
+
+ println!("Hello, world");
+ }
+ "#]],
+ Direction::Up,
+ );
+ }
+
+ #[test]
+ fn test_nowhere_to_move_stmt() {
+ check(
+ r#"
+fn main() {
+ println!("All I want to say is...");$0$0
+ println!("Hello, world");
+}
+"#,
+ expect![[r#"
+ fn main() {
+ println!("All I want to say is...");
+ println!("Hello, world");
+ }
+ "#]],
+ Direction::Up,
+ );
+ }
+
+ #[test]
+ fn test_move_item() {
+ check(
+ r#"
+fn main() {}
+
+fn foo() {}$0$0
+"#,
+ expect![[r#"
+ fn foo() {}$0
+
+ fn main() {}
+ "#]],
+ Direction::Up,
+ );
+ }
+
+ #[test]
+ fn test_move_impl_up() {
+ check(
+ r#"
+struct Yay;
+
+trait Wow {}
+
+impl Wow for Yay $0$0{}
+"#,
+ expect![[r#"
+ struct Yay;
+
+ impl Wow for Yay $0{}
+
+ trait Wow {}
+ "#]],
+ Direction::Up,
+ );
+ }
+
+ #[test]
+ fn test_move_use_up() {
+ check(
+ r#"
+use std::vec::Vec;
+use std::collections::HashMap$0$0;
+"#,
+ expect![[r#"
+ use std::collections::HashMap$0;
+ use std::vec::Vec;
+ "#]],
+ Direction::Up,
+ );
+ }
+
+ #[test]
+ fn test_moves_match_expr_up() {
+ check(
+ r#"
+fn main() {
+ let test = 123;
+
+ $0match test {
+ 456 => {},
+ _ => {}
+ };$0
+}
+"#,
+ expect![[r#"
+ fn main() {
+ match test {
+ 456 => {},
+ _ => {}
+ };
+
+ let test = 123;
+ }
+ "#]],
+ Direction::Up,
+ );
+ }
+
+ #[test]
+ fn test_moves_param() {
+ check(
+ r#"
+fn test(one: i32, two$0$0: u32) {}
+
+fn main() {
+ test(123, 456);
+}
+"#,
+ expect![[r#"
+ fn test(two$0: u32, one: i32) {}
+
+ fn main() {
+ test(123, 456);
+ }
+ "#]],
+ Direction::Up,
+ );
+ check(
+ r#"
+fn f($0$0arg: u8, arg2: u16) {}
+"#,
+ expect![[r#"
+ fn f(arg2: u16, $0arg: u8) {}
+ "#]],
+ Direction::Down,
+ );
+ }
+
+ #[test]
+ fn test_moves_arg_up() {
+ check(
+ r#"
+fn test(one: i32, two: u32) {}
+
+fn main() {
+ test(123, 456$0$0);
+}
+"#,
+ expect![[r#"
+ fn test(one: i32, two: u32) {}
+
+ fn main() {
+ test(456$0, 123);
+ }
+ "#]],
+ Direction::Up,
+ );
+ }
+
+ #[test]
+ fn test_moves_arg_down() {
+ check(
+ r#"
+fn test(one: i32, two: u32) {}
+
+fn main() {
+ test(123$0$0, 456);
+}
+"#,
+ expect![[r#"
+ fn test(one: i32, two: u32) {}
+
+ fn main() {
+ test(456, 123$0);
+ }
+ "#]],
+ Direction::Down,
+ );
+ }
+
+ #[test]
+ fn test_nowhere_to_move_arg() {
+ check(
+ r#"
+fn test(one: i32, two: u32) {}
+
+fn main() {
+ test(123$0$0, 456);
+}
+"#,
+ expect![[r#"
+ fn test(one: i32, two: u32) {}
+
+ fn main() {
+ test(123, 456);
+ }
+ "#]],
+ Direction::Up,
+ );
+ }
+
+ #[test]
+ fn test_moves_generic_param_up() {
+ check(
+ r#"
+struct Test<A, B$0$0>(A, B);
+
+fn main() {}
+"#,
+ expect![[r#"
+ struct Test<B$0, A>(A, B);
+
+ fn main() {}
+ "#]],
+ Direction::Up,
+ );
+ }
+
+ #[test]
+ fn test_moves_generic_arg_up() {
+ check(
+ r#"
+struct Test<A, B>(A, B);
+
+fn main() {
+ let t = Test::<i32, &str$0$0>(123, "yay");
+}
+"#,
+ expect![[r#"
+ struct Test<A, B>(A, B);
+
+ fn main() {
+ let t = Test::<&str$0, i32>(123, "yay");
+ }
+ "#]],
+ Direction::Up,
+ );
+ }
+
+ #[test]
+ fn test_moves_variant_up() {
+ check(
+ r#"
+enum Hello {
+ One,
+ Two$0$0
+}
+
+fn main() {}
+"#,
+ expect![[r#"
+ enum Hello {
+ Two$0,
+ One
+ }
+
+ fn main() {}
+ "#]],
+ Direction::Up,
+ );
+ }
+
+ #[test]
+ fn test_moves_type_bound_up() {
+ check(
+ r#"
+trait One {}
+
+trait Two {}
+
+fn test<T: One + Two$0$0>(t: T) {}
+
+fn main() {}
+"#,
+ expect![[r#"
+ trait One {}
+
+ trait Two {}
+
+ fn test<T: Two$0 + One>(t: T) {}
+
+ fn main() {}
+ "#]],
+ Direction::Up,
+ );
+ }
+
+ #[test]
+ fn test_prioritizes_trait_items() {
+ check(
+ r#"
+struct Test;
+
+trait Yay {
+ type One;
+
+ type Two;
+
+ fn inner();
+}
+
+impl Yay for Test {
+ type One = i32;
+
+ type Two = u32;
+
+ fn inner() {$0$0
+ println!("Mmmm");
+ }
+}
+"#,
+ expect![[r#"
+ struct Test;
+
+ trait Yay {
+ type One;
+
+ type Two;
+
+ fn inner();
+ }
+
+ impl Yay for Test {
+ type One = i32;
+
+ fn inner() {$0
+ println!("Mmmm");
+ }
+
+ type Two = u32;
+ }
+ "#]],
+ Direction::Up,
+ );
+ }
+
+ #[test]
+ fn test_weird_nesting() {
+ check(
+ r#"
+fn test() {
+ mod hello {
+ fn inner() {}
+ }
+
+ mod hi {$0$0
+ fn inner() {}
+ }
+}
+"#,
+ expect![[r#"
+ fn test() {
+ mod hi {$0
+ fn inner() {}
+ }
+
+ mod hello {
+ fn inner() {}
+ }
+ }
+ "#]],
+ Direction::Up,
+ );
+ }
+
+ #[test]
+ fn test_cursor_at_item_start() {
+ check(
+ r#"
+$0$0#[derive(Debug)]
+enum FooBar {
+ Foo,
+ Bar,
+}
+
+fn main() {}
+"#,
+ expect![[r##"
+ fn main() {}
+
+ $0#[derive(Debug)]
+ enum FooBar {
+ Foo,
+ Bar,
+ }
+ "##]],
+ Direction::Down,
+ );
+ check(
+ r#"
+$0$0enum FooBar {
+ Foo,
+ Bar,
+}
+
+fn main() {}
+"#,
+ expect![[r#"
+ fn main() {}
+
+ $0enum FooBar {
+ Foo,
+ Bar,
+ }
+ "#]],
+ Direction::Down,
+ );
+ check(
+ r#"
+struct Test;
+
+trait SomeTrait {}
+
+$0$0impl SomeTrait for Test {}
+
+fn main() {}
+"#,
+ expect![[r#"
+ struct Test;
+
+ $0impl SomeTrait for Test {}
+
+ trait SomeTrait {}
+
+ fn main() {}
+ "#]],
+ Direction::Up,
+ );
+ }
+
+ #[test]
+ fn test_cursor_at_item_end() {
+ check(
+ r#"
+enum FooBar {
+ Foo,
+ Bar,
+}$0$0
+
+fn main() {}
+"#,
+ expect![[r#"
+ fn main() {}
+
+ enum FooBar {
+ Foo,
+ Bar,
+ }$0
+ "#]],
+ Direction::Down,
+ );
+ check(
+ r#"
+struct Test;
+
+trait SomeTrait {}
+
+impl SomeTrait for Test {}$0$0
+
+fn main() {}
+"#,
+ expect![[r#"
+ struct Test;
+
+ impl SomeTrait for Test {}$0
+
+ trait SomeTrait {}
+
+ fn main() {}
+ "#]],
+ Direction::Up,
+ );
+ }
+
+ #[test]
+ fn handles_empty_file() {
+ check(r#"$0$0"#, expect![[r#""#]], Direction::Up);
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs b/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs
new file mode 100644
index 000000000..9f049e298
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs
@@ -0,0 +1,623 @@
+//! See [`NavigationTarget`].
+
+use std::fmt;
+
+use either::Either;
+use hir::{
+ symbols::FileSymbol, AssocItem, Documentation, FieldSource, HasAttrs, HasSource, HirDisplay,
+ InFile, ModuleSource, Semantics,
+};
+use ide_db::{
+ base_db::{FileId, FileRange},
+ SymbolKind,
+};
+use ide_db::{defs::Definition, RootDatabase};
+use stdx::never;
+use syntax::{
+ ast::{self, HasName},
+ match_ast, AstNode, SmolStr, SyntaxNode, TextRange,
+};
+
+/// `NavigationTarget` represents an element in the editor's UI which you can
+/// click on to navigate to a particular piece of code.
+///
+/// Typically, a `NavigationTarget` corresponds to some element in the source
+/// code, like a function or a struct, but this is not strictly required.
+#[derive(Clone, PartialEq, Eq, Hash)]
+pub struct NavigationTarget {
+ pub file_id: FileId,
+ /// Range which encompasses the whole element.
+ ///
+ /// Should include body, doc comments, attributes, etc.
+ ///
+ /// Clients should use this range to answer "is the cursor inside the
+ /// element?" question.
+ pub full_range: TextRange,
+ /// A "most interesting" range within the `full_range`.
+ ///
+ /// Typically, `full_range` is the whole syntax node, including doc
+ /// comments, and `focus_range` is the range of the identifier.
+ ///
+ /// Clients should place the cursor on this range when navigating to this target.
+ pub focus_range: Option<TextRange>,
+ pub name: SmolStr,
+ pub kind: Option<SymbolKind>,
+ pub container_name: Option<SmolStr>,
+ pub description: Option<String>,
+ pub docs: Option<Documentation>,
+}
+
+impl fmt::Debug for NavigationTarget {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let mut f = f.debug_struct("NavigationTarget");
+ macro_rules! opt {
+ ($($name:ident)*) => {$(
+ if let Some(it) = &self.$name {
+ f.field(stringify!($name), it);
+ }
+ )*}
+ }
+ f.field("file_id", &self.file_id).field("full_range", &self.full_range);
+ opt!(focus_range);
+ f.field("name", &self.name);
+ opt!(kind container_name description docs);
+ f.finish()
+ }
+}
+
+pub(crate) trait ToNav {
+ fn to_nav(&self, db: &RootDatabase) -> NavigationTarget;
+}
+
+pub(crate) trait TryToNav {
+ fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget>;
+}
+
+impl<T: TryToNav, U: TryToNav> TryToNav for Either<T, U> {
+ fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
+ match self {
+ Either::Left(it) => it.try_to_nav(db),
+ Either::Right(it) => it.try_to_nav(db),
+ }
+ }
+}
+
+impl NavigationTarget {
+ pub fn focus_or_full_range(&self) -> TextRange {
+ self.focus_range.unwrap_or(self.full_range)
+ }
+
+ pub(crate) fn from_module_to_decl(db: &RootDatabase, module: hir::Module) -> NavigationTarget {
+ let name = module.name(db).map(|it| it.to_smol_str()).unwrap_or_default();
+ if let Some(src @ InFile { value, .. }) = &module.declaration_source(db) {
+ let FileRange { file_id, range: full_range } = src.syntax().original_file_range(db);
+ let focus_range =
+ value.name().and_then(|it| orig_focus_range(db, src.file_id, it.syntax()));
+ let mut res = NavigationTarget::from_syntax(
+ file_id,
+ name,
+ focus_range,
+ full_range,
+ SymbolKind::Module,
+ );
+ res.docs = module.attrs(db).docs();
+ res.description = Some(module.display(db).to_string());
+ return res;
+ }
+ module.to_nav(db)
+ }
+
+ #[cfg(test)]
+ pub(crate) fn debug_render(&self) -> String {
+ let mut buf = format!(
+ "{} {:?} {:?} {:?}",
+ self.name,
+ self.kind.unwrap(),
+ self.file_id,
+ self.full_range
+ );
+ if let Some(focus_range) = self.focus_range {
+ buf.push_str(&format!(" {:?}", focus_range))
+ }
+ if let Some(container_name) = &self.container_name {
+ buf.push_str(&format!(" {}", container_name))
+ }
+ buf
+ }
+
+ /// Allows `NavigationTarget` to be created from a `NameOwner`
+ pub(crate) fn from_named(
+ db: &RootDatabase,
+ node @ InFile { file_id, value }: InFile<&dyn ast::HasName>,
+ kind: SymbolKind,
+ ) -> NavigationTarget {
+ let name = value.name().map(|it| it.text().into()).unwrap_or_else(|| "_".into());
+ let focus_range = value.name().and_then(|it| orig_focus_range(db, file_id, it.syntax()));
+ let FileRange { file_id, range } = node.map(|it| it.syntax()).original_file_range(db);
+
+ NavigationTarget::from_syntax(file_id, name, focus_range, range, kind)
+ }
+
+ fn from_syntax(
+ file_id: FileId,
+ name: SmolStr,
+ focus_range: Option<TextRange>,
+ full_range: TextRange,
+ kind: SymbolKind,
+ ) -> NavigationTarget {
+ NavigationTarget {
+ file_id,
+ name,
+ kind: Some(kind),
+ full_range,
+ focus_range,
+ container_name: None,
+ description: None,
+ docs: None,
+ }
+ }
+}
+
+impl TryToNav for FileSymbol {
+ fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
+ let full_range = self.loc.original_range(db)?;
+ let name_range = self.loc.original_name_range(db)?;
+
+ Some(NavigationTarget {
+ file_id: full_range.file_id,
+ name: self.name.clone(),
+ kind: Some(self.kind.into()),
+ full_range: full_range.range,
+ focus_range: Some(name_range.range),
+ container_name: self.container_name.clone(),
+ description: description_from_symbol(db, self),
+ docs: None,
+ })
+ }
+}
+
+impl TryToNav for Definition {
+ fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
+ match self {
+ Definition::Local(it) => Some(it.to_nav(db)),
+ Definition::Label(it) => Some(it.to_nav(db)),
+ Definition::Module(it) => Some(it.to_nav(db)),
+ Definition::Macro(it) => it.try_to_nav(db),
+ Definition::Field(it) => it.try_to_nav(db),
+ Definition::SelfType(it) => it.try_to_nav(db),
+ Definition::GenericParam(it) => it.try_to_nav(db),
+ Definition::Function(it) => it.try_to_nav(db),
+ Definition::Adt(it) => it.try_to_nav(db),
+ Definition::Variant(it) => it.try_to_nav(db),
+ Definition::Const(it) => it.try_to_nav(db),
+ Definition::Static(it) => it.try_to_nav(db),
+ Definition::Trait(it) => it.try_to_nav(db),
+ Definition::TypeAlias(it) => it.try_to_nav(db),
+ Definition::BuiltinType(_) => None,
+ Definition::ToolModule(_) => None,
+ Definition::BuiltinAttr(_) => None,
+ // FIXME: The focus range should be set to the helper declaration
+ Definition::DeriveHelper(it) => it.derive().try_to_nav(db),
+ }
+ }
+}
+
+impl TryToNav for hir::ModuleDef {
+ fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
+ match self {
+ hir::ModuleDef::Module(it) => Some(it.to_nav(db)),
+ hir::ModuleDef::Function(it) => it.try_to_nav(db),
+ hir::ModuleDef::Adt(it) => it.try_to_nav(db),
+ hir::ModuleDef::Variant(it) => it.try_to_nav(db),
+ hir::ModuleDef::Const(it) => it.try_to_nav(db),
+ hir::ModuleDef::Static(it) => it.try_to_nav(db),
+ hir::ModuleDef::Trait(it) => it.try_to_nav(db),
+ hir::ModuleDef::TypeAlias(it) => it.try_to_nav(db),
+ hir::ModuleDef::Macro(it) => it.try_to_nav(db),
+ hir::ModuleDef::BuiltinType(_) => None,
+ }
+ }
+}
+
+pub(crate) trait ToNavFromAst {
+ const KIND: SymbolKind;
+}
+impl ToNavFromAst for hir::Function {
+ const KIND: SymbolKind = SymbolKind::Function;
+}
+impl ToNavFromAst for hir::Const {
+ const KIND: SymbolKind = SymbolKind::Const;
+}
+impl ToNavFromAst for hir::Static {
+ const KIND: SymbolKind = SymbolKind::Static;
+}
+impl ToNavFromAst for hir::Struct {
+ const KIND: SymbolKind = SymbolKind::Struct;
+}
+impl ToNavFromAst for hir::Enum {
+ const KIND: SymbolKind = SymbolKind::Enum;
+}
+impl ToNavFromAst for hir::Variant {
+ const KIND: SymbolKind = SymbolKind::Variant;
+}
+impl ToNavFromAst for hir::Union {
+ const KIND: SymbolKind = SymbolKind::Union;
+}
+impl ToNavFromAst for hir::TypeAlias {
+ const KIND: SymbolKind = SymbolKind::TypeAlias;
+}
+impl ToNavFromAst for hir::Trait {
+ const KIND: SymbolKind = SymbolKind::Trait;
+}
+
+impl<D> TryToNav for D
+where
+ D: HasSource + ToNavFromAst + Copy + HasAttrs + HirDisplay,
+ D::Ast: ast::HasName,
+{
+ fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
+ let src = self.source(db)?;
+ let mut res = NavigationTarget::from_named(
+ db,
+ src.as_ref().map(|it| it as &dyn ast::HasName),
+ D::KIND,
+ );
+ res.docs = self.docs(db);
+ res.description = Some(self.display(db).to_string());
+ Some(res)
+ }
+}
+
+impl ToNav for hir::Module {
+ fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
+ let InFile { file_id, value } = self.definition_source(db);
+
+ let name = self.name(db).map(|it| it.to_smol_str()).unwrap_or_default();
+ let (syntax, focus) = match &value {
+ ModuleSource::SourceFile(node) => (node.syntax(), None),
+ ModuleSource::Module(node) => (
+ node.syntax(),
+ node.name().and_then(|it| orig_focus_range(db, file_id, it.syntax())),
+ ),
+ ModuleSource::BlockExpr(node) => (node.syntax(), None),
+ };
+ let FileRange { file_id, range: full_range } =
+ InFile::new(file_id, syntax).original_file_range(db);
+ NavigationTarget::from_syntax(file_id, name, focus, full_range, SymbolKind::Module)
+ }
+}
+
+impl TryToNav for hir::Impl {
+ fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
+ let InFile { file_id, value } = self.source(db)?;
+ let derive_attr = self.is_builtin_derive(db);
+
+ let focus_range = if derive_attr.is_some() {
+ None
+ } else {
+ value.self_ty().and_then(|ty| orig_focus_range(db, file_id, ty.syntax()))
+ };
+
+ let FileRange { file_id, range: full_range } = match &derive_attr {
+ Some(attr) => attr.syntax().original_file_range(db),
+ None => InFile::new(file_id, value.syntax()).original_file_range(db),
+ };
+
+ Some(NavigationTarget::from_syntax(
+ file_id,
+ "impl".into(),
+ focus_range,
+ full_range,
+ SymbolKind::Impl,
+ ))
+ }
+}
+
+impl TryToNav for hir::Field {
+ fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
+ let src = self.source(db)?;
+
+ let field_source = match &src.value {
+ FieldSource::Named(it) => {
+ let mut res =
+ NavigationTarget::from_named(db, src.with_value(it), SymbolKind::Field);
+ res.docs = self.docs(db);
+ res.description = Some(self.display(db).to_string());
+ res
+ }
+ FieldSource::Pos(it) => {
+ let FileRange { file_id, range } =
+ src.with_value(it.syntax()).original_file_range(db);
+ NavigationTarget::from_syntax(file_id, "".into(), None, range, SymbolKind::Field)
+ }
+ };
+ Some(field_source)
+ }
+}
+
+impl TryToNav for hir::Macro {
+ fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
+ let src = self.source(db)?;
+ let name_owner: &dyn ast::HasName = match &src.value {
+ Either::Left(it) => it,
+ Either::Right(it) => it,
+ };
+ let mut res = NavigationTarget::from_named(
+ db,
+ src.as_ref().with_value(name_owner),
+ self.kind(db).into(),
+ );
+ res.docs = self.docs(db);
+ Some(res)
+ }
+}
+
+impl TryToNav for hir::Adt {
+ fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
+ match self {
+ hir::Adt::Struct(it) => it.try_to_nav(db),
+ hir::Adt::Union(it) => it.try_to_nav(db),
+ hir::Adt::Enum(it) => it.try_to_nav(db),
+ }
+ }
+}
+
+impl TryToNav for hir::AssocItem {
+ fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
+ match self {
+ AssocItem::Function(it) => it.try_to_nav(db),
+ AssocItem::Const(it) => it.try_to_nav(db),
+ AssocItem::TypeAlias(it) => it.try_to_nav(db),
+ }
+ }
+}
+
+impl TryToNav for hir::GenericParam {
+ fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
+ match self {
+ hir::GenericParam::TypeParam(it) => it.try_to_nav(db),
+ hir::GenericParam::ConstParam(it) => it.try_to_nav(db),
+ hir::GenericParam::LifetimeParam(it) => it.try_to_nav(db),
+ }
+ }
+}
+
+impl ToNav for hir::Local {
+ fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
+ let InFile { file_id, value } = self.source(db);
+ let (node, name) = match &value {
+ Either::Left(bind_pat) => (bind_pat.syntax(), bind_pat.name()),
+ Either::Right(it) => (it.syntax(), it.name()),
+ };
+ let focus_range = name.and_then(|it| orig_focus_range(db, file_id, it.syntax()));
+ let FileRange { file_id, range: full_range } =
+ InFile::new(file_id, node).original_file_range(db);
+
+ let name = self.name(db).to_smol_str();
+ let kind = if self.is_self(db) {
+ SymbolKind::SelfParam
+ } else if self.is_param(db) {
+ SymbolKind::ValueParam
+ } else {
+ SymbolKind::Local
+ };
+ NavigationTarget {
+ file_id,
+ name,
+ kind: Some(kind),
+ full_range,
+ focus_range,
+ container_name: None,
+ description: None,
+ docs: None,
+ }
+ }
+}
+
+impl ToNav for hir::Label {
+ fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
+ let InFile { file_id, value } = self.source(db);
+ let name = self.name(db).to_smol_str();
+
+ let range = |syntax: &_| InFile::new(file_id, syntax).original_file_range(db);
+ let FileRange { file_id, range: full_range } = range(value.syntax());
+ let focus_range = value.lifetime().map(|lt| range(lt.syntax()).range);
+
+ NavigationTarget {
+ file_id,
+ name,
+ kind: Some(SymbolKind::Label),
+ full_range,
+ focus_range,
+ container_name: None,
+ description: None,
+ docs: None,
+ }
+ }
+}
+
+impl TryToNav for hir::TypeParam {
+ fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
+ let InFile { file_id, value } = self.merge().source(db)?;
+ let name = self.name(db).to_smol_str();
+
+ let value = match value {
+ Either::Left(ast::TypeOrConstParam::Type(x)) => Either::Left(x),
+ Either::Left(ast::TypeOrConstParam::Const(_)) => {
+ never!();
+ return None;
+ }
+ Either::Right(x) => Either::Right(x),
+ };
+
+ let range = |syntax: &_| InFile::new(file_id, syntax).original_file_range(db);
+ let focus_range = |syntax: &_| InFile::new(file_id, syntax).original_file_range_opt(db);
+ let FileRange { file_id, range: full_range } = match &value {
+ Either::Left(type_param) => range(type_param.syntax()),
+ Either::Right(trait_) => trait_
+ .name()
+ .and_then(|name| focus_range(name.syntax()))
+ .unwrap_or_else(|| range(trait_.syntax())),
+ };
+ let focus_range = value
+ .either(|it| it.name(), |it| it.name())
+ .and_then(|it| focus_range(it.syntax()))
+ .map(|it| it.range);
+ Some(NavigationTarget {
+ file_id,
+ name,
+ kind: Some(SymbolKind::TypeParam),
+ full_range,
+ focus_range,
+ container_name: None,
+ description: None,
+ docs: None,
+ })
+ }
+}
+
+impl TryToNav for hir::TypeOrConstParam {
+ fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
+ self.split(db).try_to_nav(db)
+ }
+}
+
+impl TryToNav for hir::LifetimeParam {
+ fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
+ let InFile { file_id, value } = self.source(db)?;
+ let name = self.name(db).to_smol_str();
+
+ let FileRange { file_id, range: full_range } =
+ InFile::new(file_id, value.syntax()).original_file_range(db);
+ Some(NavigationTarget {
+ file_id,
+ name,
+ kind: Some(SymbolKind::LifetimeParam),
+ full_range,
+ focus_range: Some(full_range),
+ container_name: None,
+ description: None,
+ docs: None,
+ })
+ }
+}
+
+impl TryToNav for hir::ConstParam {
+ fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
+ let InFile { file_id, value } = self.merge().source(db)?;
+ let name = self.name(db).to_smol_str();
+
+ let value = match value {
+ Either::Left(ast::TypeOrConstParam::Const(x)) => x,
+ _ => {
+ never!();
+ return None;
+ }
+ };
+
+ let focus_range = value.name().and_then(|it| orig_focus_range(db, file_id, it.syntax()));
+ let FileRange { file_id, range: full_range } =
+ InFile::new(file_id, value.syntax()).original_file_range(db);
+ Some(NavigationTarget {
+ file_id,
+ name,
+ kind: Some(SymbolKind::ConstParam),
+ full_range,
+ focus_range,
+ container_name: None,
+ description: None,
+ docs: None,
+ })
+ }
+}
+
+/// Get a description of a symbol.
+///
+/// e.g. `struct Name`, `enum Name`, `fn Name`
+pub(crate) fn description_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option<String> {
+ let sema = Semantics::new(db);
+ let node = symbol.loc.syntax(&sema)?;
+
+ match_ast! {
+ match node {
+ ast::Fn(it) => sema.to_def(&it).map(|it| it.display(db).to_string()),
+ ast::Struct(it) => sema.to_def(&it).map(|it| it.display(db).to_string()),
+ ast::Enum(it) => sema.to_def(&it).map(|it| it.display(db).to_string()),
+ ast::Trait(it) => sema.to_def(&it).map(|it| it.display(db).to_string()),
+ ast::Module(it) => sema.to_def(&it).map(|it| it.display(db).to_string()),
+ ast::TypeAlias(it) => sema.to_def(&it).map(|it| it.display(db).to_string()),
+ ast::Const(it) => sema.to_def(&it).map(|it| it.display(db).to_string()),
+ ast::Static(it) => sema.to_def(&it).map(|it| it.display(db).to_string()),
+ ast::RecordField(it) => sema.to_def(&it).map(|it| it.display(db).to_string()),
+ ast::Variant(it) => sema.to_def(&it).map(|it| it.display(db).to_string()),
+ ast::Union(it) => sema.to_def(&it).map(|it| it.display(db).to_string()),
+ _ => None,
+ }
+ }
+}
+
+fn orig_focus_range(
+ db: &RootDatabase,
+ file_id: hir::HirFileId,
+ syntax: &SyntaxNode,
+) -> Option<TextRange> {
+ InFile::new(file_id, syntax).original_file_range_opt(db).map(|it| it.range)
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::expect;
+
+ use crate::{fixture, Query};
+
+ #[test]
+ fn test_nav_for_symbol() {
+ let (analysis, _) = fixture::file(
+ r#"
+enum FooInner { }
+fn foo() { enum FooInner { } }
+"#,
+ );
+
+ let navs = analysis.symbol_search(Query::new("FooInner".to_string())).unwrap();
+ expect![[r#"
+ [
+ NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..17,
+ focus_range: 5..13,
+ name: "FooInner",
+ kind: Enum,
+ description: "enum FooInner",
+ },
+ NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 29..46,
+ focus_range: 34..42,
+ name: "FooInner",
+ kind: Enum,
+ container_name: "foo",
+ description: "enum FooInner",
+ },
+ ]
+ "#]]
+ .assert_debug_eq(&navs);
+ }
+
+ #[test]
+ fn test_world_symbols_are_case_sensitive() {
+ let (analysis, _) = fixture::file(
+ r#"
+fn foo() {}
+struct Foo;
+"#,
+ );
+
+ let navs = analysis.symbol_search(Query::new("foo".to_string())).unwrap();
+ assert_eq!(navs.len(), 2)
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/parent_module.rs b/src/tools/rust-analyzer/crates/ide/src/parent_module.rs
new file mode 100644
index 000000000..9b1f48044
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/parent_module.rs
@@ -0,0 +1,167 @@
+use hir::Semantics;
+use ide_db::{
+ base_db::{CrateId, FileId, FilePosition},
+ RootDatabase,
+};
+use itertools::Itertools;
+use syntax::{
+ algo::find_node_at_offset,
+ ast::{self, AstNode},
+};
+
+use crate::NavigationTarget;
+
+// Feature: Parent Module
+//
+// Navigates to the parent module of the current module.
+//
+// |===
+// | Editor | Action Name
+//
+// | VS Code | **Rust Analyzer: Locate parent module**
+// |===
+//
+// image::https://user-images.githubusercontent.com/48062697/113065580-04c21800-91b1-11eb-9a32-00086161c0bd.gif[]
+
+/// This returns `Vec` because a module may be included from several places.
+pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> {
+ let sema = Semantics::new(db);
+ let source_file = sema.parse(position.file_id);
+
+ let mut module = find_node_at_offset::<ast::Module>(source_file.syntax(), position.offset);
+
+ // If cursor is literally on `mod foo`, go to the grandpa.
+ if let Some(m) = &module {
+ if !m
+ .item_list()
+ .map_or(false, |it| it.syntax().text_range().contains_inclusive(position.offset))
+ {
+ cov_mark::hit!(test_resolve_parent_module_on_module_decl);
+ module = m.syntax().ancestors().skip(1).find_map(ast::Module::cast);
+ }
+ }
+
+ match module {
+ Some(module) => sema
+ .to_def(&module)
+ .into_iter()
+ .map(|module| NavigationTarget::from_module_to_decl(db, module))
+ .collect(),
+ None => sema
+ .to_module_defs(position.file_id)
+ .map(|module| NavigationTarget::from_module_to_decl(db, module))
+ .collect(),
+ }
+}
+
+/// Returns `Vec` for the same reason as `parent_module`
+pub(crate) fn crate_for(db: &RootDatabase, file_id: FileId) -> Vec<CrateId> {
+ let sema = Semantics::new(db);
+ sema.to_module_defs(file_id).map(|module| module.krate().into()).unique().collect()
+}
+
+#[cfg(test)]
+mod tests {
+ use ide_db::base_db::FileRange;
+
+ use crate::fixture;
+
+ fn check(ra_fixture: &str) {
+ let (analysis, position, expected) = fixture::annotations(ra_fixture);
+ let navs = analysis.parent_module(position).unwrap();
+ let navs = navs
+ .iter()
+ .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
+ .collect::<Vec<_>>();
+ assert_eq!(expected.into_iter().map(|(fr, _)| fr).collect::<Vec<_>>(), navs);
+ }
+
+ #[test]
+ fn test_resolve_parent_module() {
+ check(
+ r#"
+//- /lib.rs
+mod foo;
+ //^^^
+
+//- /foo.rs
+$0// empty
+"#,
+ );
+ }
+
+ #[test]
+ fn test_resolve_parent_module_on_module_decl() {
+ cov_mark::check!(test_resolve_parent_module_on_module_decl);
+ check(
+ r#"
+//- /lib.rs
+mod foo;
+ //^^^
+//- /foo.rs
+mod $0bar;
+
+//- /foo/bar.rs
+// empty
+"#,
+ );
+ }
+
+ #[test]
+ fn test_resolve_parent_module_for_inline() {
+ check(
+ r#"
+//- /lib.rs
+mod foo {
+ mod bar {
+ mod baz { $0 }
+ } //^^^
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_resolve_multi_parent_module() {
+ check(
+ r#"
+//- /main.rs
+mod foo;
+ //^^^
+#[path = "foo.rs"]
+mod bar;
+ //^^^
+//- /foo.rs
+$0
+"#,
+ );
+ }
+
+ #[test]
+ fn test_resolve_crate_root() {
+ let (analysis, file_id) = fixture::file(
+ r#"
+//- /foo.rs
+$0
+//- /main.rs
+mod foo;
+"#,
+ );
+ assert_eq!(analysis.crate_for(file_id).unwrap().len(), 1);
+ }
+
+ #[test]
+ fn test_resolve_multi_parent_crate() {
+ let (analysis, file_id) = fixture::file(
+ r#"
+//- /baz.rs
+$0
+//- /foo.rs crate:foo
+mod baz;
+//- /bar.rs crate:bar
+mod baz;
+"#,
+ );
+ assert_eq!(analysis.crate_for(file_id).unwrap().len(), 2);
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/prime_caches.rs b/src/tools/rust-analyzer/crates/ide/src/prime_caches.rs
new file mode 100644
index 000000000..296270036
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/prime_caches.rs
@@ -0,0 +1,158 @@
+//! rust-analyzer is lazy and doesn't compute anything unless asked. This
+//! sometimes is counter productive when, for example, the first goto definition
+//! request takes longer to compute. This modules implemented prepopulation of
+//! various caches, it's not really advanced at the moment.
+mod topologic_sort;
+
+use std::time::Duration;
+
+use hir::db::DefDatabase;
+use ide_db::{
+ base_db::{
+ salsa::{Database, ParallelDatabase, Snapshot},
+ Cancelled, CrateGraph, CrateId, SourceDatabase, SourceDatabaseExt,
+ },
+ FxHashSet, FxIndexMap,
+};
+
+use crate::RootDatabase;
+
+/// We're indexing many crates.
+#[derive(Debug)]
+pub struct ParallelPrimeCachesProgress {
+ /// the crates that we are currently priming.
+ pub crates_currently_indexing: Vec<String>,
+ /// the total number of crates we want to prime.
+ pub crates_total: usize,
+ /// the total number of crates that have finished priming
+ pub crates_done: usize,
+}
+
+pub(crate) fn parallel_prime_caches(
+ db: &RootDatabase,
+ num_worker_threads: u8,
+ cb: &(dyn Fn(ParallelPrimeCachesProgress) + Sync),
+) {
+ let _p = profile::span("prime_caches");
+
+ let graph = db.crate_graph();
+ let mut crates_to_prime = {
+ let crate_ids = compute_crates_to_prime(db, &graph);
+
+ let mut builder = topologic_sort::TopologicalSortIter::builder();
+
+ for &crate_id in &crate_ids {
+ let crate_data = &graph[crate_id];
+ let dependencies = crate_data
+ .dependencies
+ .iter()
+ .map(|d| d.crate_id)
+ .filter(|i| crate_ids.contains(i));
+
+ builder.add(crate_id, dependencies);
+ }
+
+ builder.build()
+ };
+
+ enum ParallelPrimeCacheWorkerProgress {
+ BeginCrate { crate_id: CrateId, crate_name: String },
+ EndCrate { crate_id: CrateId },
+ }
+
+ let (work_sender, progress_receiver) = {
+ let (progress_sender, progress_receiver) = crossbeam_channel::unbounded();
+ let (work_sender, work_receiver) = crossbeam_channel::unbounded();
+ let prime_caches_worker = move |db: Snapshot<RootDatabase>| {
+ while let Ok((crate_id, crate_name)) = work_receiver.recv() {
+ progress_sender
+ .send(ParallelPrimeCacheWorkerProgress::BeginCrate { crate_id, crate_name })?;
+
+ // This also computes the DefMap
+ db.import_map(crate_id);
+
+ progress_sender.send(ParallelPrimeCacheWorkerProgress::EndCrate { crate_id })?;
+ }
+
+ Ok::<_, crossbeam_channel::SendError<_>>(())
+ };
+
+ for _ in 0..num_worker_threads {
+ let worker = prime_caches_worker.clone();
+ let db = db.snapshot();
+ std::thread::spawn(move || Cancelled::catch(|| worker(db)));
+ }
+
+ (work_sender, progress_receiver)
+ };
+
+ let crates_total = crates_to_prime.pending();
+ let mut crates_done = 0;
+
+ // an index map is used to preserve ordering so we can sort the progress report in order of
+ // "longest crate to index" first
+ let mut crates_currently_indexing =
+ FxIndexMap::with_capacity_and_hasher(num_worker_threads as _, Default::default());
+
+ while crates_done < crates_total {
+ db.unwind_if_cancelled();
+
+ for crate_id in &mut crates_to_prime {
+ work_sender
+ .send((
+ crate_id,
+ graph[crate_id].display_name.as_deref().unwrap_or_default().to_string(),
+ ))
+ .ok();
+ }
+
+ // recv_timeout is somewhat a hack, we need a way to from this thread check to see if the current salsa revision
+ // is cancelled on a regular basis. workers will only exit if they are processing a task that is cancelled, or
+ // if this thread exits, and closes the work channel.
+ let worker_progress = match progress_receiver.recv_timeout(Duration::from_millis(10)) {
+ Ok(p) => p,
+ Err(crossbeam_channel::RecvTimeoutError::Timeout) => {
+ continue;
+ }
+ Err(crossbeam_channel::RecvTimeoutError::Disconnected) => {
+ // our workers may have died from a cancelled task, so we'll check and re-raise here.
+ db.unwind_if_cancelled();
+ break;
+ }
+ };
+ match worker_progress {
+ ParallelPrimeCacheWorkerProgress::BeginCrate { crate_id, crate_name } => {
+ crates_currently_indexing.insert(crate_id, crate_name);
+ }
+ ParallelPrimeCacheWorkerProgress::EndCrate { crate_id } => {
+ crates_currently_indexing.remove(&crate_id);
+ crates_to_prime.mark_done(crate_id);
+ crates_done += 1;
+ }
+ };
+
+ let progress = ParallelPrimeCachesProgress {
+ crates_currently_indexing: crates_currently_indexing.values().cloned().collect(),
+ crates_done,
+ crates_total,
+ };
+
+ cb(progress);
+ }
+}
+
+fn compute_crates_to_prime(db: &RootDatabase, graph: &CrateGraph) -> FxHashSet<CrateId> {
+ // We're only interested in the workspace crates and the `ImportMap`s of their direct
+ // dependencies, though in practice the latter also compute the `DefMap`s.
+ // We don't prime transitive dependencies because they're generally not visible in
+ // the current workspace.
+ graph
+ .iter()
+ .filter(|&id| {
+ let file_id = graph[id].root_file_id;
+ let root_id = db.file_source_root(file_id);
+ !db.source_root(root_id).is_library
+ })
+ .flat_map(|id| graph[id].dependencies.iter().map(|krate| krate.crate_id))
+ .collect()
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/prime_caches/topologic_sort.rs b/src/tools/rust-analyzer/crates/ide/src/prime_caches/topologic_sort.rs
new file mode 100644
index 000000000..9c3ceedbb
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/prime_caches/topologic_sort.rs
@@ -0,0 +1,98 @@
+//! helper data structure to schedule work for parallel prime caches.
+use std::{collections::VecDeque, hash::Hash};
+
+use ide_db::FxHashMap;
+
+pub(crate) struct TopologicSortIterBuilder<T> {
+ nodes: FxHashMap<T, Entry<T>>,
+}
+
+impl<T> TopologicSortIterBuilder<T>
+where
+ T: Copy + Eq + PartialEq + Hash,
+{
+ fn new() -> Self {
+ Self { nodes: Default::default() }
+ }
+
+ fn get_or_create_entry(&mut self, item: T) -> &mut Entry<T> {
+ self.nodes.entry(item).or_default()
+ }
+
+ pub(crate) fn add(&mut self, item: T, predecessors: impl IntoIterator<Item = T>) {
+ let mut num_predecessors = 0;
+
+ for predecessor in predecessors.into_iter() {
+ self.get_or_create_entry(predecessor).successors.push(item);
+ num_predecessors += 1;
+ }
+
+ let entry = self.get_or_create_entry(item);
+ entry.num_predecessors += num_predecessors;
+ }
+
+ pub(crate) fn build(self) -> TopologicalSortIter<T> {
+ let ready = self
+ .nodes
+ .iter()
+ .filter_map(
+ |(item, entry)| if entry.num_predecessors == 0 { Some(*item) } else { None },
+ )
+ .collect();
+
+ TopologicalSortIter { nodes: self.nodes, ready }
+ }
+}
+
+pub(crate) struct TopologicalSortIter<T> {
+ ready: VecDeque<T>,
+ nodes: FxHashMap<T, Entry<T>>,
+}
+
+impl<T> TopologicalSortIter<T>
+where
+ T: Copy + Eq + PartialEq + Hash,
+{
+ pub(crate) fn builder() -> TopologicSortIterBuilder<T> {
+ TopologicSortIterBuilder::new()
+ }
+
+ pub(crate) fn pending(&self) -> usize {
+ self.nodes.len()
+ }
+
+ pub(crate) fn mark_done(&mut self, item: T) {
+ let entry = self.nodes.remove(&item).expect("invariant: unknown item marked as done");
+
+ for successor in entry.successors {
+ let succ_entry = self
+ .nodes
+ .get_mut(&successor)
+ .expect("invariant: unknown successor referenced by entry");
+
+ succ_entry.num_predecessors -= 1;
+ if succ_entry.num_predecessors == 0 {
+ self.ready.push_back(successor);
+ }
+ }
+ }
+}
+
+impl<T> Iterator for TopologicalSortIter<T> {
+ type Item = T;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.ready.pop_front()
+ }
+}
+
+struct Entry<T> {
+ successors: Vec<T>,
+ num_predecessors: usize,
+}
+
+impl<T> Default for Entry<T> {
+ fn default() -> Self {
+ Self { successors: Default::default(), num_predecessors: 0 }
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/references.rs b/src/tools/rust-analyzer/crates/ide/src/references.rs
new file mode 100644
index 000000000..1a6beec18
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/references.rs
@@ -0,0 +1,1636 @@
+//! This module implements a reference search.
+//! First, the element at the cursor position must be either an `ast::Name`
+//! or `ast::NameRef`. If it's an `ast::NameRef`, at the classification step we
+//! try to resolve the direct tree parent of this element, otherwise we
+//! already have a definition and just need to get its HIR together with
+//! some information that is needed for further steps of searching.
+//! After that, we collect files that might contain references and look
+//! for text occurrences of the identifier. If there's an `ast::NameRef`
+//! at the index that the match starts at and its tree parent is
+//! resolved to the search element definition, we get a reference.
+
+use hir::{PathResolution, Semantics};
+use ide_db::{
+ base_db::FileId,
+ defs::{Definition, NameClass, NameRefClass},
+ search::{ReferenceCategory, SearchScope, UsageSearchResult},
+ FxHashMap, RootDatabase,
+};
+use syntax::{
+ algo::find_node_at_offset,
+ ast::{self, HasName},
+ match_ast, AstNode,
+ SyntaxKind::*,
+ SyntaxNode, TextRange, TextSize, T,
+};
+
+use crate::{FilePosition, NavigationTarget, TryToNav};
+
+#[derive(Debug, Clone)]
+pub struct ReferenceSearchResult {
+ pub declaration: Option<Declaration>,
+ pub references: FxHashMap<FileId, Vec<(TextRange, Option<ReferenceCategory>)>>,
+}
+
+#[derive(Debug, Clone)]
+pub struct Declaration {
+ pub nav: NavigationTarget,
+ pub is_mut: bool,
+}
+
+// Feature: Find All References
+//
+// Shows all references of the item at the cursor location
+//
+// |===
+// | Editor | Shortcut
+//
+// | VS Code | kbd:[Shift+Alt+F12]
+// |===
+//
+// image::https://user-images.githubusercontent.com/48062697/113020670-b7c34f00-917a-11eb-8003-370ac5f2b3cb.gif[]
+pub(crate) fn find_all_refs(
+ sema: &Semantics<'_, RootDatabase>,
+ position: FilePosition,
+ search_scope: Option<SearchScope>,
+) -> Option<Vec<ReferenceSearchResult>> {
+ let _p = profile::span("find_all_refs");
+ let syntax = sema.parse(position.file_id).syntax().clone();
+ let make_searcher = |literal_search: bool| {
+ move |def: Definition| {
+ let declaration = match def {
+ Definition::Module(module) => {
+ Some(NavigationTarget::from_module_to_decl(sema.db, module))
+ }
+ def => def.try_to_nav(sema.db),
+ }
+ .map(|nav| {
+ let decl_range = nav.focus_or_full_range();
+ Declaration {
+ is_mut: decl_mutability(&def, sema.parse(nav.file_id).syntax(), decl_range),
+ nav,
+ }
+ });
+ let mut usages =
+ def.usages(sema).set_scope(search_scope.clone()).include_self_refs().all();
+
+ if literal_search {
+ retain_adt_literal_usages(&mut usages, def, sema);
+ }
+
+ let references = usages
+ .into_iter()
+ .map(|(file_id, refs)| {
+ (
+ file_id,
+ refs.into_iter()
+ .map(|file_ref| (file_ref.range, file_ref.category))
+ .collect(),
+ )
+ })
+ .collect();
+
+ ReferenceSearchResult { declaration, references }
+ }
+ };
+
+ match name_for_constructor_search(&syntax, position) {
+ Some(name) => {
+ let def = match NameClass::classify(sema, &name)? {
+ NameClass::Definition(it) | NameClass::ConstReference(it) => it,
+ NameClass::PatFieldShorthand { local_def: _, field_ref } => {
+ Definition::Field(field_ref)
+ }
+ };
+ Some(vec![make_searcher(true)(def)])
+ }
+ None => {
+ let search = make_searcher(false);
+ Some(find_defs(sema, &syntax, position.offset)?.map(search).collect())
+ }
+ }
+}
+
+pub(crate) fn find_defs<'a>(
+ sema: &'a Semantics<'_, RootDatabase>,
+ syntax: &SyntaxNode,
+ offset: TextSize,
+) -> Option<impl Iterator<Item = Definition> + 'a> {
+ let token = syntax.token_at_offset(offset).find(|t| {
+ matches!(
+ t.kind(),
+ IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] | T![Self]
+ )
+ });
+ token.map(|token| {
+ sema.descend_into_macros_with_same_text(token)
+ .into_iter()
+ .filter_map(|it| ast::NameLike::cast(it.parent()?))
+ .filter_map(move |name_like| {
+ let def = match name_like {
+ ast::NameLike::NameRef(name_ref) => {
+ match NameRefClass::classify(sema, &name_ref)? {
+ NameRefClass::Definition(def) => def,
+ NameRefClass::FieldShorthand { local_ref, field_ref: _ } => {
+ Definition::Local(local_ref)
+ }
+ }
+ }
+ ast::NameLike::Name(name) => match NameClass::classify(sema, &name)? {
+ NameClass::Definition(it) | NameClass::ConstReference(it) => it,
+ NameClass::PatFieldShorthand { local_def, field_ref: _ } => {
+ Definition::Local(local_def)
+ }
+ },
+ ast::NameLike::Lifetime(lifetime) => {
+ NameRefClass::classify_lifetime(sema, &lifetime)
+ .and_then(|class| match class {
+ NameRefClass::Definition(it) => Some(it),
+ _ => None,
+ })
+ .or_else(|| {
+ NameClass::classify_lifetime(sema, &lifetime)
+ .and_then(NameClass::defined)
+ })?
+ }
+ };
+ Some(def)
+ })
+ })
+}
+
+pub(crate) fn decl_mutability(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> bool {
+ match def {
+ Definition::Local(_) | Definition::Field(_) => {}
+ _ => return false,
+ };
+
+ match find_node_at_offset::<ast::LetStmt>(syntax, range.start()) {
+ Some(stmt) if stmt.initializer().is_some() => match stmt.pat() {
+ Some(ast::Pat::IdentPat(it)) => it.mut_token().is_some(),
+ _ => false,
+ },
+ _ => false,
+ }
+}
+
+/// Filter out all non-literal usages for adt-defs
+fn retain_adt_literal_usages(
+ usages: &mut UsageSearchResult,
+ def: Definition,
+ sema: &Semantics<'_, RootDatabase>,
+) {
+ let refs = usages.references.values_mut();
+ match def {
+ Definition::Adt(hir::Adt::Enum(enum_)) => {
+ refs.for_each(|it| {
+ it.retain(|reference| {
+ reference
+ .name
+ .as_name_ref()
+ .map_or(false, |name_ref| is_enum_lit_name_ref(sema, enum_, name_ref))
+ })
+ });
+ usages.references.retain(|_, it| !it.is_empty());
+ }
+ Definition::Adt(_) | Definition::Variant(_) => {
+ refs.for_each(|it| {
+ it.retain(|reference| reference.name.as_name_ref().map_or(false, is_lit_name_ref))
+ });
+ usages.references.retain(|_, it| !it.is_empty());
+ }
+ _ => {}
+ }
+}
+
+/// Returns `Some` if the cursor is at a position for an item to search for all its constructor/literal usages
+fn name_for_constructor_search(syntax: &SyntaxNode, position: FilePosition) -> Option<ast::Name> {
+ let token = syntax.token_at_offset(position.offset).right_biased()?;
+ let token_parent = token.parent()?;
+ let kind = token.kind();
+ if kind == T![;] {
+ ast::Struct::cast(token_parent)
+ .filter(|struct_| struct_.field_list().is_none())
+ .and_then(|struct_| struct_.name())
+ } else if kind == T!['{'] {
+ match_ast! {
+ match token_parent {
+ ast::RecordFieldList(rfl) => match_ast! {
+ match (rfl.syntax().parent()?) {
+ ast::Variant(it) => it.name(),
+ ast::Struct(it) => it.name(),
+ ast::Union(it) => it.name(),
+ _ => None,
+ }
+ },
+ ast::VariantList(vl) => ast::Enum::cast(vl.syntax().parent()?)?.name(),
+ _ => None,
+ }
+ }
+ } else if kind == T!['('] {
+ let tfl = ast::TupleFieldList::cast(token_parent)?;
+ match_ast! {
+ match (tfl.syntax().parent()?) {
+ ast::Variant(it) => it.name(),
+ ast::Struct(it) => it.name(),
+ _ => None,
+ }
+ }
+ } else {
+ None
+ }
+}
+
+fn is_enum_lit_name_ref(
+ sema: &Semantics<'_, RootDatabase>,
+ enum_: hir::Enum,
+ name_ref: &ast::NameRef,
+) -> bool {
+ let path_is_variant_of_enum = |path: ast::Path| {
+ matches!(
+ sema.resolve_path(&path),
+ Some(PathResolution::Def(hir::ModuleDef::Variant(variant)))
+ if variant.parent_enum(sema.db) == enum_
+ )
+ };
+ name_ref
+ .syntax()
+ .ancestors()
+ .find_map(|ancestor| {
+ match_ast! {
+ match ancestor {
+ ast::PathExpr(path_expr) => path_expr.path().map(path_is_variant_of_enum),
+ ast::RecordExpr(record_expr) => record_expr.path().map(path_is_variant_of_enum),
+ _ => None,
+ }
+ }
+ })
+ .unwrap_or(false)
+}
+
+fn path_ends_with(path: Option<ast::Path>, name_ref: &ast::NameRef) -> bool {
+ path.and_then(|path| path.segment())
+ .and_then(|segment| segment.name_ref())
+ .map_or(false, |segment| segment == *name_ref)
+}
+
+fn is_lit_name_ref(name_ref: &ast::NameRef) -> bool {
+ name_ref.syntax().ancestors().find_map(|ancestor| {
+ match_ast! {
+ match ancestor {
+ ast::PathExpr(path_expr) => Some(path_ends_with(path_expr.path(), name_ref)),
+ ast::RecordExpr(record_expr) => Some(path_ends_with(record_expr.path(), name_ref)),
+ _ => None,
+ }
+ }
+ }).unwrap_or(false)
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+ use ide_db::{base_db::FileId, search::ReferenceCategory};
+ use stdx::format_to;
+
+ use crate::{fixture, SearchScope};
+
+ #[test]
+ fn test_struct_literal_after_space() {
+ check(
+ r#"
+struct Foo $0{
+ a: i32,
+}
+impl Foo {
+ fn f() -> i32 { 42 }
+}
+fn main() {
+ let f: Foo;
+ f = Foo {a: Foo::f()};
+}
+"#,
+ expect![[r#"
+ Foo Struct FileId(0) 0..26 7..10
+
+ FileId(0) 101..104
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_struct_literal_before_space() {
+ check(
+ r#"
+struct Foo$0 {}
+ fn main() {
+ let f: Foo;
+ f = Foo {};
+}
+"#,
+ expect![[r#"
+ Foo Struct FileId(0) 0..13 7..10
+
+ FileId(0) 41..44
+ FileId(0) 54..57
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_struct_literal_with_generic_type() {
+ check(
+ r#"
+struct Foo<T> $0{}
+ fn main() {
+ let f: Foo::<i32>;
+ f = Foo {};
+}
+"#,
+ expect![[r#"
+ Foo Struct FileId(0) 0..16 7..10
+
+ FileId(0) 64..67
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_struct_literal_for_tuple() {
+ check(
+ r#"
+struct Foo$0(i32);
+
+fn main() {
+ let f: Foo;
+ f = Foo(1);
+}
+"#,
+ expect![[r#"
+ Foo Struct FileId(0) 0..16 7..10
+
+ FileId(0) 54..57
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_struct_literal_for_union() {
+ check(
+ r#"
+union Foo $0{
+ x: u32
+}
+
+fn main() {
+ let f: Foo;
+ f = Foo { x: 1 };
+}
+"#,
+ expect![[r#"
+ Foo Union FileId(0) 0..24 6..9
+
+ FileId(0) 62..65
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_enum_after_space() {
+ check(
+ r#"
+enum Foo $0{
+ A,
+ B(),
+ C{},
+}
+fn main() {
+ let f: Foo;
+ f = Foo::A;
+ f = Foo::B();
+ f = Foo::C{};
+}
+"#,
+ expect![[r#"
+ Foo Enum FileId(0) 0..37 5..8
+
+ FileId(0) 74..77
+ FileId(0) 90..93
+ FileId(0) 108..111
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_variant_record_after_space() {
+ check(
+ r#"
+enum Foo {
+ A $0{ n: i32 },
+ B,
+}
+fn main() {
+ let f: Foo;
+ f = Foo::B;
+ f = Foo::A { n: 92 };
+}
+"#,
+ expect![[r#"
+ A Variant FileId(0) 15..27 15..16
+
+ FileId(0) 95..96
+ "#]],
+ );
+ }
+ #[test]
+ fn test_variant_tuple_before_paren() {
+ check(
+ r#"
+enum Foo {
+ A$0(i32),
+ B,
+}
+fn main() {
+ let f: Foo;
+ f = Foo::B;
+ f = Foo::A(92);
+}
+"#,
+ expect![[r#"
+ A Variant FileId(0) 15..21 15..16
+
+ FileId(0) 89..90
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_enum_before_space() {
+ check(
+ r#"
+enum Foo$0 {
+ A,
+ B,
+}
+fn main() {
+ let f: Foo;
+ f = Foo::A;
+}
+"#,
+ expect![[r#"
+ Foo Enum FileId(0) 0..26 5..8
+
+ FileId(0) 50..53
+ FileId(0) 63..66
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_enum_with_generic_type() {
+ check(
+ r#"
+enum Foo<T> $0{
+ A(T),
+ B,
+}
+fn main() {
+ let f: Foo<i8>;
+ f = Foo::A(1);
+}
+"#,
+ expect![[r#"
+ Foo Enum FileId(0) 0..32 5..8
+
+ FileId(0) 73..76
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_enum_for_tuple() {
+ check(
+ r#"
+enum Foo$0{
+ A(i8),
+ B(i8),
+}
+fn main() {
+ let f: Foo;
+ f = Foo::A(1);
+}
+"#,
+ expect![[r#"
+ Foo Enum FileId(0) 0..33 5..8
+
+ FileId(0) 70..73
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_find_all_refs_for_local() {
+ check(
+ r#"
+fn main() {
+ let mut i = 1;
+ let j = 1;
+ i = i$0 + j;
+
+ {
+ i = 0;
+ }
+
+ i = 5;
+}"#,
+ expect![[r#"
+ i Local FileId(0) 20..25 24..25 Write
+
+ FileId(0) 50..51 Write
+ FileId(0) 54..55 Read
+ FileId(0) 76..77 Write
+ FileId(0) 94..95 Write
+ "#]],
+ );
+ }
+
+ #[test]
+ fn search_filters_by_range() {
+ check(
+ r#"
+fn foo() {
+ let spam$0 = 92;
+ spam + spam
+}
+fn bar() {
+ let spam = 92;
+ spam + spam
+}
+"#,
+ expect![[r#"
+ spam Local FileId(0) 19..23 19..23
+
+ FileId(0) 34..38 Read
+ FileId(0) 41..45 Read
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_find_all_refs_for_param_inside() {
+ check(
+ r#"
+fn foo(i : u32) -> u32 { i$0 }
+"#,
+ expect![[r#"
+ i ValueParam FileId(0) 7..8 7..8
+
+ FileId(0) 25..26 Read
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_find_all_refs_for_fn_param() {
+ check(
+ r#"
+fn foo(i$0 : u32) -> u32 { i }
+"#,
+ expect![[r#"
+ i ValueParam FileId(0) 7..8 7..8
+
+ FileId(0) 25..26 Read
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_find_all_refs_field_name() {
+ check(
+ r#"
+//- /lib.rs
+struct Foo {
+ pub spam$0: u32,
+}
+
+fn main(s: Foo) {
+ let f = s.spam;
+}
+"#,
+ expect![[r#"
+ spam Field FileId(0) 17..30 21..25
+
+ FileId(0) 67..71 Read
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_find_all_refs_impl_item_name() {
+ check(
+ r#"
+struct Foo;
+impl Foo {
+ fn f$0(&self) { }
+}
+"#,
+ expect![[r#"
+ f Function FileId(0) 27..43 30..31
+
+ (no references)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_find_all_refs_enum_var_name() {
+ check(
+ r#"
+enum Foo {
+ A,
+ B$0,
+ C,
+}
+"#,
+ expect![[r#"
+ B Variant FileId(0) 22..23 22..23
+
+ (no references)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_find_all_refs_enum_var_field() {
+ check(
+ r#"
+enum Foo {
+ A,
+ B { field$0: u8 },
+ C,
+}
+"#,
+ expect![[r#"
+ field Field FileId(0) 26..35 26..31
+
+ (no references)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_find_all_refs_two_modules() {
+ check(
+ r#"
+//- /lib.rs
+pub mod foo;
+pub mod bar;
+
+fn f() {
+ let i = foo::Foo { n: 5 };
+}
+
+//- /foo.rs
+use crate::bar;
+
+pub struct Foo {
+ pub n: u32,
+}
+
+fn f() {
+ let i = bar::Bar { n: 5 };
+}
+
+//- /bar.rs
+use crate::foo;
+
+pub struct Bar {
+ pub n: u32,
+}
+
+fn f() {
+ let i = foo::Foo$0 { n: 5 };
+}
+"#,
+ expect![[r#"
+ Foo Struct FileId(1) 17..51 28..31
+
+ FileId(0) 53..56
+ FileId(2) 79..82
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_find_all_refs_decl_module() {
+ check(
+ r#"
+//- /lib.rs
+mod foo$0;
+
+use foo::Foo;
+
+fn f() {
+ let i = Foo { n: 5 };
+}
+
+//- /foo.rs
+pub struct Foo {
+ pub n: u32,
+}
+"#,
+ expect![[r#"
+ foo Module FileId(0) 0..8 4..7
+
+ FileId(0) 14..17
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_find_all_refs_decl_module_on_self() {
+ check(
+ r#"
+//- /lib.rs
+mod foo;
+
+//- /foo.rs
+use self$0;
+"#,
+ expect![[r#"
+ foo Module FileId(0) 0..8 4..7
+
+ FileId(1) 4..8
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_find_all_refs_decl_module_on_self_crate_root() {
+ check(
+ r#"
+//- /lib.rs
+use self$0;
+"#,
+ expect![[r#"
+ Module FileId(0) 0..10
+
+ FileId(0) 4..8
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_find_all_refs_super_mod_vis() {
+ check(
+ r#"
+//- /lib.rs
+mod foo;
+
+//- /foo.rs
+mod some;
+use some::Foo;
+
+fn f() {
+ let i = Foo { n: 5 };
+}
+
+//- /foo/some.rs
+pub(super) struct Foo$0 {
+ pub n: u32,
+}
+"#,
+ expect![[r#"
+ Foo Struct FileId(2) 0..41 18..21
+
+ FileId(1) 20..23
+ FileId(1) 47..50
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_find_all_refs_with_scope() {
+ let code = r#"
+ //- /lib.rs
+ mod foo;
+ mod bar;
+
+ pub fn quux$0() {}
+
+ //- /foo.rs
+ fn f() { super::quux(); }
+
+ //- /bar.rs
+ fn f() { super::quux(); }
+ "#;
+
+ check_with_scope(
+ code,
+ None,
+ expect![[r#"
+ quux Function FileId(0) 19..35 26..30
+
+ FileId(1) 16..20
+ FileId(2) 16..20
+ "#]],
+ );
+
+ check_with_scope(
+ code,
+ Some(SearchScope::single_file(FileId(2))),
+ expect![[r#"
+ quux Function FileId(0) 19..35 26..30
+
+ FileId(2) 16..20
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_find_all_refs_macro_def() {
+ check(
+ r#"
+#[macro_export]
+macro_rules! m1$0 { () => (()) }
+
+fn foo() {
+ m1();
+ m1();
+}
+"#,
+ expect![[r#"
+ m1 Macro FileId(0) 0..46 29..31
+
+ FileId(0) 63..65
+ FileId(0) 73..75
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_basic_highlight_read_write() {
+ check(
+ r#"
+fn foo() {
+ let mut i$0 = 0;
+ i = i + 1;
+}
+"#,
+ expect![[r#"
+ i Local FileId(0) 19..24 23..24 Write
+
+ FileId(0) 34..35 Write
+ FileId(0) 38..39 Read
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_basic_highlight_field_read_write() {
+ check(
+ r#"
+struct S {
+ f: u32,
+}
+
+fn foo() {
+ let mut s = S{f: 0};
+ s.f$0 = 0;
+}
+"#,
+ expect![[r#"
+ f Field FileId(0) 15..21 15..16
+
+ FileId(0) 55..56 Read
+ FileId(0) 68..69 Write
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_basic_highlight_decl_no_write() {
+ check(
+ r#"
+fn foo() {
+ let i$0;
+ i = 1;
+}
+"#,
+ expect![[r#"
+ i Local FileId(0) 19..20 19..20
+
+ FileId(0) 26..27 Write
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_find_struct_function_refs_outside_module() {
+ check(
+ r#"
+mod foo {
+ pub struct Foo;
+
+ impl Foo {
+ pub fn new$0() -> Foo { Foo }
+ }
+}
+
+fn main() {
+ let _f = foo::Foo::new();
+}
+"#,
+ expect![[r#"
+ new Function FileId(0) 54..81 61..64
+
+ FileId(0) 126..129
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_find_all_refs_nested_module() {
+ check(
+ r#"
+//- /lib.rs
+mod foo { mod bar; }
+
+fn f$0() {}
+
+//- /foo/bar.rs
+use crate::f;
+
+fn g() { f(); }
+"#,
+ expect![[r#"
+ f Function FileId(0) 22..31 25..26
+
+ FileId(1) 11..12
+ FileId(1) 24..25
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_find_all_refs_struct_pat() {
+ check(
+ r#"
+struct S {
+ field$0: u8,
+}
+
+fn f(s: S) {
+ match s {
+ S { field } => {}
+ }
+}
+"#,
+ expect![[r#"
+ field Field FileId(0) 15..24 15..20
+
+ FileId(0) 68..73 Read
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_find_all_refs_enum_var_pat() {
+ check(
+ r#"
+enum En {
+ Variant {
+ field$0: u8,
+ }
+}
+
+fn f(e: En) {
+ match e {
+ En::Variant { field } => {}
+ }
+}
+"#,
+ expect![[r#"
+ field Field FileId(0) 32..41 32..37
+
+ FileId(0) 102..107 Read
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_find_all_refs_enum_var_privacy() {
+ check(
+ r#"
+mod m {
+ pub enum En {
+ Variant {
+ field$0: u8,
+ }
+ }
+}
+
+fn f() -> m::En {
+ m::En::Variant { field: 0 }
+}
+"#,
+ expect![[r#"
+ field Field FileId(0) 56..65 56..61
+
+ FileId(0) 125..130 Read
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_find_self_refs() {
+ check(
+ r#"
+struct Foo { bar: i32 }
+
+impl Foo {
+ fn foo(self) {
+ let x = self$0.bar;
+ if true {
+ let _ = match () {
+ () => self,
+ };
+ }
+ }
+}
+"#,
+ expect![[r#"
+ self SelfParam FileId(0) 47..51 47..51
+
+ FileId(0) 71..75 Read
+ FileId(0) 152..156 Read
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_find_self_refs_decl() {
+ check(
+ r#"
+struct Foo { bar: i32 }
+
+impl Foo {
+ fn foo(self$0) {
+ self;
+ }
+}
+"#,
+ expect![[r#"
+ self SelfParam FileId(0) 47..51 47..51
+
+ FileId(0) 63..67 Read
+ "#]],
+ );
+ }
+
+ fn check(ra_fixture: &str, expect: Expect) {
+ check_with_scope(ra_fixture, None, expect)
+ }
+
+ fn check_with_scope(ra_fixture: &str, search_scope: Option<SearchScope>, expect: Expect) {
+ let (analysis, pos) = fixture::position(ra_fixture);
+ let refs = analysis.find_all_refs(pos, search_scope).unwrap().unwrap();
+
+ let mut actual = String::new();
+ for refs in refs {
+ actual += "\n\n";
+
+ if let Some(decl) = refs.declaration {
+ format_to!(actual, "{}", decl.nav.debug_render());
+ if decl.is_mut {
+ format_to!(actual, " {:?}", ReferenceCategory::Write)
+ }
+ actual += "\n\n";
+ }
+
+ for (file_id, references) in &refs.references {
+ for (range, access) in references {
+ format_to!(actual, "{:?} {:?}", file_id, range);
+ if let Some(access) = access {
+ format_to!(actual, " {:?}", access);
+ }
+ actual += "\n";
+ }
+ }
+
+ if refs.references.is_empty() {
+ actual += "(no references)\n";
+ }
+ }
+ expect.assert_eq(actual.trim_start())
+ }
+
+ #[test]
+ fn test_find_lifetimes_function() {
+ check(
+ r#"
+trait Foo<'a> {}
+impl<'a> Foo<'a> for &'a () {}
+fn foo<'a, 'b: 'a>(x: &'a$0 ()) -> &'a () where &'a (): Foo<'a> {
+ fn bar<'a>(_: &'a ()) {}
+ x
+}
+"#,
+ expect![[r#"
+ 'a LifetimeParam FileId(0) 55..57 55..57
+
+ FileId(0) 63..65
+ FileId(0) 71..73
+ FileId(0) 82..84
+ FileId(0) 95..97
+ FileId(0) 106..108
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_find_lifetimes_type_alias() {
+ check(
+ r#"
+type Foo<'a, T> where T: 'a$0 = &'a T;
+"#,
+ expect![[r#"
+ 'a LifetimeParam FileId(0) 9..11 9..11
+
+ FileId(0) 25..27
+ FileId(0) 31..33
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_find_lifetimes_trait_impl() {
+ check(
+ r#"
+trait Foo<'a> {
+ fn foo() -> &'a ();
+}
+impl<'a> Foo<'a> for &'a () {
+ fn foo() -> &'a$0 () {
+ unimplemented!()
+ }
+}
+"#,
+ expect![[r#"
+ 'a LifetimeParam FileId(0) 47..49 47..49
+
+ FileId(0) 55..57
+ FileId(0) 64..66
+ FileId(0) 89..91
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_map_range_to_original() {
+ check(
+ r#"
+macro_rules! foo {($i:ident) => {$i} }
+fn main() {
+ let a$0 = "test";
+ foo!(a);
+}
+"#,
+ expect![[r#"
+ a Local FileId(0) 59..60 59..60
+
+ FileId(0) 80..81 Read
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_map_range_to_original_ref() {
+ check(
+ r#"
+macro_rules! foo {($i:ident) => {$i} }
+fn main() {
+ let a = "test";
+ foo!(a$0);
+}
+"#,
+ expect![[r#"
+ a Local FileId(0) 59..60 59..60
+
+ FileId(0) 80..81 Read
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_find_labels() {
+ check(
+ r#"
+fn foo<'a>() -> &'a () {
+ 'a: loop {
+ 'b: loop {
+ continue 'a$0;
+ }
+ break 'a;
+ }
+}
+"#,
+ expect![[r#"
+ 'a Label FileId(0) 29..32 29..31
+
+ FileId(0) 80..82
+ FileId(0) 108..110
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_find_const_param() {
+ check(
+ r#"
+fn foo<const FOO$0: usize>() -> usize {
+ FOO
+}
+"#,
+ expect![[r#"
+ FOO ConstParam FileId(0) 7..23 13..16
+
+ FileId(0) 42..45
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_trait() {
+ check(
+ r#"
+trait Foo$0 where Self: {}
+
+impl Foo for () {}
+"#,
+ expect![[r#"
+ Foo Trait FileId(0) 0..24 6..9
+
+ FileId(0) 31..34
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_trait_self() {
+ check(
+ r#"
+trait Foo where Self$0 {
+ fn f() -> Self;
+}
+
+impl Foo for () {}
+"#,
+ expect![[r#"
+ Self TypeParam FileId(0) 6..9 6..9
+
+ FileId(0) 16..20
+ FileId(0) 37..41
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_self_ty() {
+ check(
+ r#"
+ struct $0Foo;
+
+ impl Foo where Self: {
+ fn f() -> Self;
+ }
+ "#,
+ expect![[r#"
+ Foo Struct FileId(0) 0..11 7..10
+
+ FileId(0) 18..21
+ FileId(0) 28..32
+ FileId(0) 50..54
+ "#]],
+ );
+ check(
+ r#"
+struct Foo;
+
+impl Foo where Self: {
+ fn f() -> Self$0;
+}
+"#,
+ expect![[r#"
+ impl Impl FileId(0) 13..57 18..21
+
+ FileId(0) 18..21
+ FileId(0) 28..32
+ FileId(0) 50..54
+ "#]],
+ );
+ }
+ #[test]
+ fn test_self_variant_with_payload() {
+ check(
+ r#"
+enum Foo { Bar() }
+
+impl Foo {
+ fn foo(self) {
+ match self {
+ Self::Bar$0() => (),
+ }
+ }
+}
+
+"#,
+ expect![[r#"
+ Bar Variant FileId(0) 11..16 11..14
+
+ FileId(0) 89..92
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_attr_differs_from_fn_with_same_name() {
+ check(
+ r#"
+#[test]
+fn test$0() {
+ test();
+}
+"#,
+ expect![[r#"
+ test Function FileId(0) 0..33 11..15
+
+ FileId(0) 24..28
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_const_in_pattern() {
+ check(
+ r#"
+const A$0: i32 = 42;
+
+fn main() {
+ match A {
+ A => (),
+ _ => (),
+ }
+ if let A = A {}
+}
+"#,
+ expect![[r#"
+ A Const FileId(0) 0..18 6..7
+
+ FileId(0) 42..43
+ FileId(0) 54..55
+ FileId(0) 97..98
+ FileId(0) 101..102
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_primitives() {
+ check(
+ r#"
+fn foo(_: bool) -> bo$0ol { true }
+"#,
+ expect![[r#"
+ FileId(0) 10..14
+ FileId(0) 19..23
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_transitive() {
+ check(
+ r#"
+//- /level3.rs new_source_root:local crate:level3
+pub struct Fo$0o;
+//- /level2.rs new_source_root:local crate:level2 deps:level3
+pub use level3::Foo;
+//- /level1.rs new_source_root:local crate:level1 deps:level2
+pub use level2::Foo;
+//- /level0.rs new_source_root:local crate:level0 deps:level1
+pub use level1::Foo;
+"#,
+ expect![[r#"
+ Foo Struct FileId(0) 0..15 11..14
+
+ FileId(1) 16..19
+ FileId(2) 16..19
+ FileId(3) 16..19
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_decl_macro_references() {
+ check(
+ r#"
+//- /lib.rs crate:lib
+#[macro_use]
+mod qux;
+mod bar;
+
+pub use self::foo;
+//- /qux.rs
+#[macro_export]
+macro_rules! foo$0 {
+ () => {struct Foo;};
+}
+//- /bar.rs
+foo!();
+//- /other.rs crate:other deps:lib new_source_root:local
+lib::foo!();
+"#,
+ expect![[r#"
+ foo Macro FileId(1) 0..61 29..32
+
+ FileId(0) 46..49
+ FileId(2) 0..3
+ FileId(3) 5..8
+ "#]],
+ );
+ }
+
+ #[test]
+ fn macro_doesnt_reference_attribute_on_call() {
+ check(
+ r#"
+macro_rules! m {
+ () => {};
+}
+
+#[proc_macro_test::attr_noop]
+m$0!();
+
+"#,
+ expect![[r#"
+ m Macro FileId(0) 0..32 13..14
+
+ FileId(0) 64..65
+ "#]],
+ );
+ }
+
+ #[test]
+ fn multi_def() {
+ check(
+ r#"
+macro_rules! m {
+ ($name:ident) => {
+ mod module {
+ pub fn $name() {}
+ }
+
+ pub fn $name() {}
+ }
+}
+
+m!(func$0);
+
+fn f() {
+ func();
+ module::func();
+}
+ "#,
+ expect![[r#"
+ func Function FileId(0) 137..146 140..144
+
+ FileId(0) 161..165
+
+
+ func Function FileId(0) 137..146 140..144
+
+ FileId(0) 181..185
+ "#]],
+ )
+ }
+
+ #[test]
+ fn attr_expanded() {
+ check(
+ r#"
+//- proc_macros: identity
+#[proc_macros::identity]
+fn func$0() {
+ func();
+}
+"#,
+ expect![[r#"
+ func Function FileId(0) 25..50 28..32
+
+ FileId(0) 41..45
+ "#]],
+ )
+ }
+
+ #[test]
+ fn attr_assoc_item() {
+ check(
+ r#"
+//- proc_macros: identity
+
+trait Trait {
+ #[proc_macros::identity]
+ fn func() {
+ Self::func$0();
+ }
+}
+"#,
+ expect![[r#"
+ func Function FileId(0) 48..87 51..55
+
+ FileId(0) 74..78
+ "#]],
+ )
+ }
+
+ // FIXME: import is classified as function
+ #[test]
+ fn attr() {
+ check(
+ r#"
+//- proc_macros: identity
+use proc_macros::identity;
+
+#[proc_macros::$0identity]
+fn func() {}
+"#,
+ expect![[r#"
+ identity Attribute FileId(1) 1..107 32..40
+
+ FileId(0) 43..51
+ "#]],
+ );
+ check(
+ r#"
+#![crate_type="proc-macro"]
+#[proc_macro_attribute]
+fn func$0() {}
+"#,
+ expect![[r#"
+ func Attribute FileId(0) 28..64 55..59
+
+ (no references)
+ "#]],
+ );
+ }
+
+ // FIXME: import is classified as function
+ #[test]
+ fn proc_macro() {
+ check(
+ r#"
+//- proc_macros: mirror
+use proc_macros::mirror;
+
+mirror$0! {}
+"#,
+ expect![[r#"
+ mirror Macro FileId(1) 1..77 22..28
+
+ FileId(0) 26..32
+ "#]],
+ )
+ }
+
+ #[test]
+ fn derive() {
+ check(
+ r#"
+//- proc_macros: derive_identity
+//- minicore: derive
+use proc_macros::DeriveIdentity;
+
+#[derive(proc_macros::DeriveIdentity$0)]
+struct Foo;
+"#,
+ expect![[r#"
+ derive_identity Derive FileId(2) 1..107 45..60
+
+ FileId(0) 17..31
+ FileId(0) 56..70
+ "#]],
+ );
+ check(
+ r#"
+#![crate_type="proc-macro"]
+#[proc_macro_derive(Derive, attributes(x))]
+pub fn deri$0ve(_stream: TokenStream) -> TokenStream {}
+"#,
+ expect![[r#"
+ derive Derive FileId(0) 28..125 79..85
+
+ (no references)
+ "#]],
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/rename.rs b/src/tools/rust-analyzer/crates/ide/src/rename.rs
new file mode 100644
index 000000000..fe44856dc
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/rename.rs
@@ -0,0 +1,2252 @@
+//! Renaming functionality.
+//!
+//! This is mostly front-end for [`ide_db::rename`], but it also includes the
+//! tests. This module also implements a couple of magic tricks, like renaming
+//! `self` and to `self` (to switch between associated function and method).
+
+use hir::{AsAssocItem, InFile, Semantics};
+use ide_db::{
+ base_db::FileId,
+ defs::{Definition, NameClass, NameRefClass},
+ rename::{bail, format_err, source_edit_from_references, IdentifierKind},
+ RootDatabase,
+};
+use itertools::Itertools;
+use stdx::{always, never};
+use syntax::{ast, AstNode, SyntaxNode};
+
+use text_edit::TextEdit;
+
+use crate::{FilePosition, RangeInfo, SourceChange};
+
+pub use ide_db::rename::RenameError;
+
+type RenameResult<T> = Result<T, RenameError>;
+
+/// Prepares a rename. The sole job of this function is to return the TextRange of the thing that is
+/// being targeted for a rename.
+pub(crate) fn prepare_rename(
+ db: &RootDatabase,
+ position: FilePosition,
+) -> RenameResult<RangeInfo<()>> {
+ let sema = Semantics::new(db);
+ let source_file = sema.parse(position.file_id);
+ let syntax = source_file.syntax();
+
+ let res = find_definitions(&sema, syntax, position)?
+ .map(|(name_like, def)| {
+ // ensure all ranges are valid
+
+ if def.range_for_rename(&sema).is_none() {
+ bail!("No references found at position")
+ }
+ let frange = sema.original_range(name_like.syntax());
+
+ always!(
+ frange.range.contains_inclusive(position.offset)
+ && frange.file_id == position.file_id
+ );
+ Ok(frange.range)
+ })
+ .reduce(|acc, cur| match (acc, cur) {
+ // ensure all ranges are the same
+ (Ok(acc_inner), Ok(cur_inner)) if acc_inner == cur_inner => Ok(acc_inner),
+ (Err(e), _) => Err(e),
+ _ => bail!("inconsistent text range"),
+ });
+
+ match res {
+ // ensure at least one definition was found
+ Some(res) => res.map(|range| RangeInfo::new(range, ())),
+ None => bail!("No references found at position"),
+ }
+}
+
+// Feature: Rename
+//
+// Renames the item below the cursor and all of its references
+//
+// |===
+// | Editor | Shortcut
+//
+// | VS Code | kbd:[F2]
+// |===
+//
+// image::https://user-images.githubusercontent.com/48062697/113065582-055aae80-91b1-11eb-8ade-2b58e6d81883.gif[]
+pub(crate) fn rename(
+ db: &RootDatabase,
+ position: FilePosition,
+ new_name: &str,
+) -> RenameResult<SourceChange> {
+ let sema = Semantics::new(db);
+ let source_file = sema.parse(position.file_id);
+ let syntax = source_file.syntax();
+
+ let defs = find_definitions(&sema, syntax, position)?;
+
+ let ops: RenameResult<Vec<SourceChange>> = defs
+ .map(|(_namelike, def)| {
+ if let Definition::Local(local) = def {
+ if let Some(self_param) = local.as_self_param(sema.db) {
+ cov_mark::hit!(rename_self_to_param);
+ return rename_self_to_param(&sema, local, self_param, new_name);
+ }
+ if new_name == "self" {
+ cov_mark::hit!(rename_to_self);
+ return rename_to_self(&sema, local);
+ }
+ }
+ def.rename(&sema, new_name)
+ })
+ .collect();
+
+ ops?.into_iter()
+ .reduce(|acc, elem| acc.merge(elem))
+ .ok_or_else(|| format_err!("No references found at position"))
+}
+
+/// Called by the client when it is about to rename a file.
+pub(crate) fn will_rename_file(
+ db: &RootDatabase,
+ file_id: FileId,
+ new_name_stem: &str,
+) -> Option<SourceChange> {
+ let sema = Semantics::new(db);
+ let module = sema.to_module_def(file_id)?;
+ let def = Definition::Module(module);
+ let mut change = def.rename(&sema, new_name_stem).ok()?;
+ change.file_system_edits.clear();
+ Some(change)
+}
+
+fn find_definitions(
+ sema: &Semantics<'_, RootDatabase>,
+ syntax: &SyntaxNode,
+ position: FilePosition,
+) -> RenameResult<impl Iterator<Item = (ast::NameLike, Definition)>> {
+ let symbols = sema
+ .find_nodes_at_offset_with_descend::<ast::NameLike>(syntax, position.offset)
+ .map(|name_like| {
+ let res = match &name_like {
+ // renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet
+ ast::NameLike::Name(name)
+ if name
+ .syntax()
+ .parent()
+ .map_or(false, |it| ast::Rename::can_cast(it.kind())) =>
+ {
+ bail!("Renaming aliases is currently unsupported")
+ }
+ ast::NameLike::Name(name) => NameClass::classify(sema, name)
+ .map(|class| match class {
+ NameClass::Definition(it) | NameClass::ConstReference(it) => it,
+ NameClass::PatFieldShorthand { local_def, field_ref: _ } => {
+ Definition::Local(local_def)
+ }
+ })
+ .map(|def| (name_like.clone(), def))
+ .ok_or_else(|| format_err!("No references found at position")),
+ ast::NameLike::NameRef(name_ref) => {
+ NameRefClass::classify(sema, name_ref)
+ .map(|class| match class {
+ NameRefClass::Definition(def) => def,
+ NameRefClass::FieldShorthand { local_ref, field_ref: _ } => {
+ Definition::Local(local_ref)
+ }
+ })
+ .ok_or_else(|| format_err!("No references found at position"))
+ .and_then(|def| {
+ // if the name differs from the definitions name it has to be an alias
+ if def
+ .name(sema.db)
+ .map_or(false, |it| it.to_smol_str() != name_ref.text().as_str())
+ {
+ Err(format_err!("Renaming aliases is currently unsupported"))
+ } else {
+ Ok((name_like.clone(), def))
+ }
+ })
+ }
+ ast::NameLike::Lifetime(lifetime) => {
+ NameRefClass::classify_lifetime(sema, lifetime)
+ .and_then(|class| match class {
+ NameRefClass::Definition(def) => Some(def),
+ _ => None,
+ })
+ .or_else(|| {
+ NameClass::classify_lifetime(sema, lifetime).and_then(|it| match it {
+ NameClass::Definition(it) => Some(it),
+ _ => None,
+ })
+ })
+ .map(|def| (name_like, def))
+ .ok_or_else(|| format_err!("No references found at position"))
+ }
+ };
+ res
+ });
+
+ let res: RenameResult<Vec<_>> = symbols.collect();
+ match res {
+ Ok(v) => {
+ if v.is_empty() {
+ // FIXME: some semantic duplication between "empty vec" and "Err()"
+ Err(format_err!("No references found at position"))
+ } else {
+ // remove duplicates, comparing `Definition`s
+ Ok(v.into_iter().unique_by(|t| t.1))
+ }
+ }
+ Err(e) => Err(e),
+ }
+}
+
+fn rename_to_self(
+ sema: &Semantics<'_, RootDatabase>,
+ local: hir::Local,
+) -> RenameResult<SourceChange> {
+ if never!(local.is_self(sema.db)) {
+ bail!("rename_to_self invoked on self");
+ }
+
+ let fn_def = match local.parent(sema.db) {
+ hir::DefWithBody::Function(func) => func,
+ _ => bail!("Cannot rename local to self outside of function"),
+ };
+
+ if fn_def.self_param(sema.db).is_some() {
+ bail!("Method already has a self parameter");
+ }
+
+ let params = fn_def.assoc_fn_params(sema.db);
+ let first_param = params
+ .first()
+ .ok_or_else(|| format_err!("Cannot rename local to self unless it is a parameter"))?;
+ match first_param.as_local(sema.db) {
+ Some(plocal) => {
+ if plocal != local {
+ bail!("Only the first parameter may be renamed to self");
+ }
+ }
+ None => bail!("rename_to_self invoked on destructuring parameter"),
+ }
+
+ let assoc_item = fn_def
+ .as_assoc_item(sema.db)
+ .ok_or_else(|| format_err!("Cannot rename parameter to self for free function"))?;
+ let impl_ = match assoc_item.container(sema.db) {
+ hir::AssocItemContainer::Trait(_) => {
+ bail!("Cannot rename parameter to self for trait functions");
+ }
+ hir::AssocItemContainer::Impl(impl_) => impl_,
+ };
+ let first_param_ty = first_param.ty();
+ let impl_ty = impl_.self_ty(sema.db);
+ let (ty, self_param) = if impl_ty.remove_ref().is_some() {
+ // if the impl is a ref to the type we can just match the `&T` with self directly
+ (first_param_ty.clone(), "self")
+ } else {
+ first_param_ty.remove_ref().map_or((first_param_ty.clone(), "self"), |ty| {
+ (ty, if first_param_ty.is_mutable_reference() { "&mut self" } else { "&self" })
+ })
+ };
+
+ if ty != impl_ty {
+ bail!("Parameter type differs from impl block type");
+ }
+
+ let InFile { file_id, value: param_source } =
+ first_param.source(sema.db).ok_or_else(|| format_err!("No source for parameter found"))?;
+
+ let def = Definition::Local(local);
+ let usages = def.usages(sema).all();
+ let mut source_change = SourceChange::default();
+ source_change.extend(usages.iter().map(|(&file_id, references)| {
+ (file_id, source_edit_from_references(references, def, "self"))
+ }));
+ source_change.insert_source_edit(
+ file_id.original_file(sema.db),
+ TextEdit::replace(param_source.syntax().text_range(), String::from(self_param)),
+ );
+ Ok(source_change)
+}
+
+fn rename_self_to_param(
+ sema: &Semantics<'_, RootDatabase>,
+ local: hir::Local,
+ self_param: hir::SelfParam,
+ new_name: &str,
+) -> RenameResult<SourceChange> {
+ if new_name == "self" {
+ // Let's do nothing rather than complain.
+ cov_mark::hit!(rename_self_to_self);
+ return Ok(SourceChange::default());
+ }
+
+ let identifier_kind = IdentifierKind::classify(new_name)?;
+
+ let InFile { file_id, value: self_param } =
+ self_param.source(sema.db).ok_or_else(|| format_err!("cannot find function source"))?;
+
+ let def = Definition::Local(local);
+ let usages = def.usages(sema).all();
+ let edit = text_edit_from_self_param(&self_param, new_name)
+ .ok_or_else(|| format_err!("No target type found"))?;
+ if usages.len() > 1 && identifier_kind == IdentifierKind::Underscore {
+ bail!("Cannot rename reference to `_` as it is being referenced multiple times");
+ }
+ let mut source_change = SourceChange::default();
+ source_change.insert_source_edit(file_id.original_file(sema.db), edit);
+ source_change.extend(usages.iter().map(|(&file_id, references)| {
+ (file_id, source_edit_from_references(references, def, new_name))
+ }));
+ Ok(source_change)
+}
+
+fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Option<TextEdit> {
+ fn target_type_name(impl_def: &ast::Impl) -> Option<String> {
+ if let Some(ast::Type::PathType(p)) = impl_def.self_ty() {
+ return Some(p.path()?.segment()?.name_ref()?.text().to_string());
+ }
+ None
+ }
+
+ let impl_def = self_param.syntax().ancestors().find_map(ast::Impl::cast)?;
+ let type_name = target_type_name(&impl_def)?;
+
+ let mut replacement_text = String::from(new_name);
+ replacement_text.push_str(": ");
+ match (self_param.amp_token(), self_param.mut_token()) {
+ (Some(_), None) => replacement_text.push('&'),
+ (Some(_), Some(_)) => replacement_text.push_str("&mut "),
+ (_, _) => (),
+ };
+ replacement_text.push_str(type_name.as_str());
+
+ Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+ use stdx::trim_indent;
+ use test_utils::assert_eq_text;
+ use text_edit::TextEdit;
+
+ use crate::{fixture, FileId};
+
+ use super::{RangeInfo, RenameError};
+
+ #[track_caller]
+ fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
+ let ra_fixture_after = &trim_indent(ra_fixture_after);
+ let (analysis, position) = fixture::position(ra_fixture_before);
+ let rename_result = analysis
+ .rename(position, new_name)
+ .unwrap_or_else(|err| panic!("Rename to '{}' was cancelled: {}", new_name, err));
+ match rename_result {
+ Ok(source_change) => {
+ let mut text_edit_builder = TextEdit::builder();
+ let mut file_id: Option<FileId> = None;
+ for edit in source_change.source_file_edits {
+ file_id = Some(edit.0);
+ for indel in edit.1.into_iter() {
+ text_edit_builder.replace(indel.delete, indel.insert);
+ }
+ }
+ if let Some(file_id) = file_id {
+ let mut result = analysis.file_text(file_id).unwrap().to_string();
+ text_edit_builder.finish().apply(&mut result);
+ assert_eq_text!(ra_fixture_after, &*result);
+ }
+ }
+ Err(err) => {
+ if ra_fixture_after.starts_with("error:") {
+ let error_message = ra_fixture_after
+ .chars()
+ .into_iter()
+ .skip("error:".len())
+ .collect::<String>();
+ assert_eq!(error_message.trim(), err.to_string());
+ } else {
+ panic!("Rename to '{}' failed unexpectedly: {}", new_name, err)
+ }
+ }
+ };
+ }
+
+ fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) {
+ let (analysis, position) = fixture::position(ra_fixture);
+ let source_change =
+ analysis.rename(position, new_name).unwrap().expect("Expect returned a RenameError");
+ expect.assert_debug_eq(&source_change)
+ }
+
+ fn check_expect_will_rename_file(new_name: &str, ra_fixture: &str, expect: Expect) {
+ let (analysis, position) = fixture::position(ra_fixture);
+ let source_change = analysis
+ .will_rename_file(position.file_id, new_name)
+ .unwrap()
+ .expect("Expect returned a RenameError");
+ expect.assert_debug_eq(&source_change)
+ }
+
+ fn check_prepare(ra_fixture: &str, expect: Expect) {
+ let (analysis, position) = fixture::position(ra_fixture);
+ let result = analysis
+ .prepare_rename(position)
+ .unwrap_or_else(|err| panic!("PrepareRename was cancelled: {}", err));
+ match result {
+ Ok(RangeInfo { range, info: () }) => {
+ let source = analysis.file_text(position.file_id).unwrap();
+ expect.assert_eq(&format!("{:?}: {}", range, &source[range]))
+ }
+ Err(RenameError(err)) => expect.assert_eq(&err),
+ };
+ }
+
+ #[test]
+ fn test_prepare_rename_namelikes() {
+ check_prepare(r"fn name$0<'lifetime>() {}", expect![[r#"3..7: name"#]]);
+ check_prepare(r"fn name<'lifetime$0>() {}", expect![[r#"8..17: 'lifetime"#]]);
+ check_prepare(r"fn name<'lifetime>() { name$0(); }", expect![[r#"23..27: name"#]]);
+ }
+
+ #[test]
+ fn test_prepare_rename_in_macro() {
+ check_prepare(
+ r"macro_rules! foo {
+ ($ident:ident) => {
+ pub struct $ident;
+ }
+}
+foo!(Foo$0);",
+ expect![[r#"83..86: Foo"#]],
+ );
+ }
+
+ #[test]
+ fn test_prepare_rename_keyword() {
+ check_prepare(r"struct$0 Foo;", expect![[r#"No references found at position"#]]);
+ }
+
+ #[test]
+ fn test_prepare_rename_tuple_field() {
+ check_prepare(
+ r#"
+struct Foo(i32);
+
+fn baz() {
+ let mut x = Foo(4);
+ x.0$0 = 5;
+}
+"#,
+ expect![[r#"No references found at position"#]],
+ );
+ }
+
+ #[test]
+ fn test_prepare_rename_builtin() {
+ check_prepare(
+ r#"
+fn foo() {
+ let x: i32$0 = 0;
+}
+"#,
+ expect![[r#"No references found at position"#]],
+ );
+ }
+
+ #[test]
+ fn test_prepare_rename_self() {
+ check_prepare(
+ r#"
+struct Foo {}
+
+impl Foo {
+ fn foo(self) -> Self$0 {
+ self
+ }
+}
+"#,
+ expect![[r#"No references found at position"#]],
+ );
+ }
+
+ #[test]
+ fn test_rename_to_underscore() {
+ check("_", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let _ = 1; }"#);
+ }
+
+ #[test]
+ fn test_rename_to_raw_identifier() {
+ check("r#fn", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let r#fn = 1; }"#);
+ }
+
+ #[test]
+ fn test_rename_to_invalid_identifier1() {
+ check(
+ "invalid!",
+ r#"fn main() { let i$0 = 1; }"#,
+ "error: Invalid name `invalid!`: not an identifier",
+ );
+ }
+
+ #[test]
+ fn test_rename_to_invalid_identifier2() {
+ check(
+ "multiple tokens",
+ r#"fn main() { let i$0 = 1; }"#,
+ "error: Invalid name `multiple tokens`: not an identifier",
+ );
+ }
+
+ #[test]
+ fn test_rename_to_invalid_identifier3() {
+ check(
+ "let",
+ r#"fn main() { let i$0 = 1; }"#,
+ "error: Invalid name `let`: not an identifier",
+ );
+ }
+
+ #[test]
+ fn test_rename_to_invalid_identifier_lifetime() {
+ cov_mark::check!(rename_not_an_ident_ref);
+ check(
+ "'foo",
+ r#"fn main() { let i$0 = 1; }"#,
+ "error: Invalid name `'foo`: not an identifier",
+ );
+ }
+
+ #[test]
+ fn test_rename_to_invalid_identifier_lifetime2() {
+ cov_mark::check!(rename_not_a_lifetime_ident_ref);
+ check(
+ "foo",
+ r#"fn main<'a>(_: &'a$0 ()) {}"#,
+ "error: Invalid name `foo`: not a lifetime identifier",
+ );
+ }
+
+ #[test]
+ fn test_rename_to_underscore_invalid() {
+ cov_mark::check!(rename_underscore_multiple);
+ check(
+ "_",
+ r#"fn main(foo$0: ()) {foo;}"#,
+ "error: Cannot rename reference to `_` as it is being referenced multiple times",
+ );
+ }
+
+ #[test]
+ fn test_rename_mod_invalid() {
+ check(
+ "'foo",
+ r#"mod foo$0 {}"#,
+ "error: Invalid name `'foo`: cannot rename module to 'foo",
+ );
+ }
+
+ #[test]
+ fn test_rename_for_local() {
+ check(
+ "k",
+ r#"
+fn main() {
+ let mut i = 1;
+ let j = 1;
+ i = i$0 + j;
+
+ { i = 0; }
+
+ i = 5;
+}
+"#,
+ r#"
+fn main() {
+ let mut k = 1;
+ let j = 1;
+ k = k + j;
+
+ { k = 0; }
+
+ k = 5;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_rename_unresolved_reference() {
+ check(
+ "new_name",
+ r#"fn main() { let _ = unresolved_ref$0; }"#,
+ "error: No references found at position",
+ );
+ }
+
+ #[test]
+ fn test_rename_macro_multiple_occurrences() {
+ check(
+ "Baaah",
+ r#"macro_rules! foo {
+ ($ident:ident) => {
+ const $ident: () = ();
+ struct $ident {}
+ };
+}
+
+foo!($0Foo);
+const _: () = Foo;
+const _: Foo = Foo {};
+ "#,
+ r#"
+macro_rules! foo {
+ ($ident:ident) => {
+ const $ident: () = ();
+ struct $ident {}
+ };
+}
+
+foo!(Baaah);
+const _: () = Baaah;
+const _: Baaah = Baaah {};
+ "#,
+ )
+ }
+
+ #[test]
+ fn test_rename_for_macro_args() {
+ check(
+ "b",
+ r#"
+macro_rules! foo {($i:ident) => {$i} }
+fn main() {
+ let a$0 = "test";
+ foo!(a);
+}
+"#,
+ r#"
+macro_rules! foo {($i:ident) => {$i} }
+fn main() {
+ let b = "test";
+ foo!(b);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_rename_for_macro_args_rev() {
+ check(
+ "b",
+ r#"
+macro_rules! foo {($i:ident) => {$i} }
+fn main() {
+ let a = "test";
+ foo!(a$0);
+}
+"#,
+ r#"
+macro_rules! foo {($i:ident) => {$i} }
+fn main() {
+ let b = "test";
+ foo!(b);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_rename_for_macro_define_fn() {
+ check(
+ "bar",
+ r#"
+macro_rules! define_fn {($id:ident) => { fn $id{} }}
+define_fn!(foo);
+fn main() {
+ fo$0o();
+}
+"#,
+ r#"
+macro_rules! define_fn {($id:ident) => { fn $id{} }}
+define_fn!(bar);
+fn main() {
+ bar();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_rename_for_macro_define_fn_rev() {
+ check(
+ "bar",
+ r#"
+macro_rules! define_fn {($id:ident) => { fn $id{} }}
+define_fn!(fo$0o);
+fn main() {
+ foo();
+}
+"#,
+ r#"
+macro_rules! define_fn {($id:ident) => { fn $id{} }}
+define_fn!(bar);
+fn main() {
+ bar();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_rename_for_param_inside() {
+ check("j", r#"fn foo(i : u32) -> u32 { i$0 }"#, r#"fn foo(j : u32) -> u32 { j }"#);
+ }
+
+ #[test]
+ fn test_rename_refs_for_fn_param() {
+ check("j", r#"fn foo(i$0 : u32) -> u32 { i }"#, r#"fn foo(j : u32) -> u32 { j }"#);
+ }
+
+ #[test]
+ fn test_rename_for_mut_param() {
+ check("j", r#"fn foo(mut i$0 : u32) -> u32 { i }"#, r#"fn foo(mut j : u32) -> u32 { j }"#);
+ }
+
+ #[test]
+ fn test_rename_struct_field() {
+ check(
+ "foo",
+ r#"
+struct Foo { field$0: i32 }
+
+impl Foo {
+ fn new(i: i32) -> Self {
+ Self { field: i }
+ }
+}
+"#,
+ r#"
+struct Foo { foo: i32 }
+
+impl Foo {
+ fn new(i: i32) -> Self {
+ Self { foo: i }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_rename_field_in_field_shorthand() {
+ cov_mark::check!(test_rename_field_in_field_shorthand);
+ check(
+ "field",
+ r#"
+struct Foo { foo$0: i32 }
+
+impl Foo {
+ fn new(foo: i32) -> Self {
+ Self { foo }
+ }
+}
+"#,
+ r#"
+struct Foo { field: i32 }
+
+impl Foo {
+ fn new(foo: i32) -> Self {
+ Self { field: foo }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_rename_local_in_field_shorthand() {
+ cov_mark::check!(test_rename_local_in_field_shorthand);
+ check(
+ "j",
+ r#"
+struct Foo { i: i32 }
+
+impl Foo {
+ fn new(i$0: i32) -> Self {
+ Self { i }
+ }
+}
+"#,
+ r#"
+struct Foo { i: i32 }
+
+impl Foo {
+ fn new(j: i32) -> Self {
+ Self { i: j }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_field_shorthand_correct_struct() {
+ check(
+ "j",
+ r#"
+struct Foo { i$0: i32 }
+struct Bar { i: i32 }
+
+impl Bar {
+ fn new(i: i32) -> Self {
+ Self { i }
+ }
+}
+"#,
+ r#"
+struct Foo { j: i32 }
+struct Bar { i: i32 }
+
+impl Bar {
+ fn new(i: i32) -> Self {
+ Self { i }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_shadow_local_for_struct_shorthand() {
+ check(
+ "j",
+ r#"
+struct Foo { i: i32 }
+
+fn baz(i$0: i32) -> Self {
+ let x = Foo { i };
+ {
+ let i = 0;
+ Foo { i }
+ }
+}
+"#,
+ r#"
+struct Foo { i: i32 }
+
+fn baz(j: i32) -> Self {
+ let x = Foo { i: j };
+ {
+ let i = 0;
+ Foo { i }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_rename_mod() {
+ check_expect(
+ "foo2",
+ r#"
+//- /lib.rs
+mod bar;
+
+//- /bar.rs
+mod foo$0;
+
+//- /bar/foo.rs
+// empty
+"#,
+ expect![[r#"
+ SourceChange {
+ source_file_edits: {
+ FileId(
+ 1,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "foo2",
+ delete: 4..7,
+ },
+ ],
+ },
+ },
+ file_system_edits: [
+ MoveFile {
+ src: FileId(
+ 2,
+ ),
+ dst: AnchoredPathBuf {
+ anchor: FileId(
+ 2,
+ ),
+ path: "foo2.rs",
+ },
+ },
+ ],
+ is_snippet: false,
+ }
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_rename_mod_in_use_tree() {
+ check_expect(
+ "quux",
+ r#"
+//- /main.rs
+pub mod foo;
+pub mod bar;
+fn main() {}
+
+//- /foo.rs
+pub struct FooContent;
+
+//- /bar.rs
+use crate::foo$0::FooContent;
+"#,
+ expect![[r#"
+ SourceChange {
+ source_file_edits: {
+ FileId(
+ 0,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "quux",
+ delete: 8..11,
+ },
+ ],
+ },
+ FileId(
+ 2,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "quux",
+ delete: 11..14,
+ },
+ ],
+ },
+ },
+ file_system_edits: [
+ MoveFile {
+ src: FileId(
+ 1,
+ ),
+ dst: AnchoredPathBuf {
+ anchor: FileId(
+ 1,
+ ),
+ path: "quux.rs",
+ },
+ },
+ ],
+ is_snippet: false,
+ }
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_rename_mod_in_dir() {
+ check_expect(
+ "foo2",
+ r#"
+//- /lib.rs
+mod fo$0o;
+//- /foo/mod.rs
+// empty
+"#,
+ expect![[r#"
+ SourceChange {
+ source_file_edits: {
+ FileId(
+ 0,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "foo2",
+ delete: 4..7,
+ },
+ ],
+ },
+ },
+ file_system_edits: [
+ MoveDir {
+ src: AnchoredPathBuf {
+ anchor: FileId(
+ 1,
+ ),
+ path: "../foo",
+ },
+ src_id: FileId(
+ 1,
+ ),
+ dst: AnchoredPathBuf {
+ anchor: FileId(
+ 1,
+ ),
+ path: "../foo2",
+ },
+ },
+ ],
+ is_snippet: false,
+ }
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_rename_unusually_nested_mod() {
+ check_expect(
+ "bar",
+ r#"
+//- /lib.rs
+mod outer { mod fo$0o; }
+
+//- /outer/foo.rs
+// empty
+"#,
+ expect![[r#"
+ SourceChange {
+ source_file_edits: {
+ FileId(
+ 0,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "bar",
+ delete: 16..19,
+ },
+ ],
+ },
+ },
+ file_system_edits: [
+ MoveFile {
+ src: FileId(
+ 1,
+ ),
+ dst: AnchoredPathBuf {
+ anchor: FileId(
+ 1,
+ ),
+ path: "bar.rs",
+ },
+ },
+ ],
+ is_snippet: false,
+ }
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_module_rename_in_path() {
+ check(
+ "baz",
+ r#"
+mod $0foo {
+ pub use self::bar as qux;
+ pub fn bar() {}
+}
+
+fn main() { foo::bar(); }
+"#,
+ r#"
+mod baz {
+ pub use self::bar as qux;
+ pub fn bar() {}
+}
+
+fn main() { baz::bar(); }
+"#,
+ );
+ }
+
+ #[test]
+ fn test_rename_mod_filename_and_path() {
+ check_expect(
+ "foo2",
+ r#"
+//- /lib.rs
+mod bar;
+fn f() {
+ bar::foo::fun()
+}
+
+//- /bar.rs
+pub mod foo$0;
+
+//- /bar/foo.rs
+// pub fn fun() {}
+"#,
+ expect![[r#"
+ SourceChange {
+ source_file_edits: {
+ FileId(
+ 0,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "foo2",
+ delete: 27..30,
+ },
+ ],
+ },
+ FileId(
+ 1,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "foo2",
+ delete: 8..11,
+ },
+ ],
+ },
+ },
+ file_system_edits: [
+ MoveFile {
+ src: FileId(
+ 2,
+ ),
+ dst: AnchoredPathBuf {
+ anchor: FileId(
+ 2,
+ ),
+ path: "foo2.rs",
+ },
+ },
+ ],
+ is_snippet: false,
+ }
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_rename_mod_recursive() {
+ check_expect(
+ "foo2",
+ r#"
+//- /lib.rs
+mod foo$0;
+
+//- /foo.rs
+mod bar;
+mod corge;
+
+//- /foo/bar.rs
+mod qux;
+
+//- /foo/bar/qux.rs
+mod quux;
+
+//- /foo/bar/qux/quux/mod.rs
+// empty
+
+//- /foo/corge.rs
+// empty
+"#,
+ expect![[r#"
+ SourceChange {
+ source_file_edits: {
+ FileId(
+ 0,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "foo2",
+ delete: 4..7,
+ },
+ ],
+ },
+ },
+ file_system_edits: [
+ MoveFile {
+ src: FileId(
+ 1,
+ ),
+ dst: AnchoredPathBuf {
+ anchor: FileId(
+ 1,
+ ),
+ path: "foo2.rs",
+ },
+ },
+ MoveDir {
+ src: AnchoredPathBuf {
+ anchor: FileId(
+ 1,
+ ),
+ path: "foo",
+ },
+ src_id: FileId(
+ 1,
+ ),
+ dst: AnchoredPathBuf {
+ anchor: FileId(
+ 1,
+ ),
+ path: "foo2",
+ },
+ },
+ ],
+ is_snippet: false,
+ }
+ "#]],
+ )
+ }
+ #[test]
+ fn test_rename_mod_ref_by_super() {
+ check(
+ "baz",
+ r#"
+ mod $0foo {
+ struct X;
+
+ mod bar {
+ use super::X;
+ }
+ }
+ "#,
+ r#"
+ mod baz {
+ struct X;
+
+ mod bar {
+ use super::X;
+ }
+ }
+ "#,
+ )
+ }
+
+ #[test]
+ fn test_rename_mod_in_macro() {
+ check(
+ "bar",
+ r#"
+//- /foo.rs
+
+//- /lib.rs
+macro_rules! submodule {
+ ($name:ident) => {
+ mod $name;
+ };
+}
+
+submodule!($0foo);
+"#,
+ r#"
+macro_rules! submodule {
+ ($name:ident) => {
+ mod $name;
+ };
+}
+
+submodule!(bar);
+"#,
+ )
+ }
+
+ #[test]
+ fn test_rename_mod_for_crate_root() {
+ check_expect_will_rename_file(
+ "main",
+ r#"
+//- /lib.rs
+use crate::foo as bar;
+fn foo() {}
+mod bar$0;
+"#,
+ expect![[r#"
+ SourceChange {
+ source_file_edits: {},
+ file_system_edits: [],
+ is_snippet: false,
+ }
+ "#]],
+ )
+ }
+
+ #[test]
+ fn test_enum_variant_from_module_1() {
+ cov_mark::check!(rename_non_local);
+ check(
+ "Baz",
+ r#"
+mod foo {
+ pub enum Foo { Bar$0 }
+}
+
+fn func(f: foo::Foo) {
+ match f {
+ foo::Foo::Bar => {}
+ }
+}
+"#,
+ r#"
+mod foo {
+ pub enum Foo { Baz }
+}
+
+fn func(f: foo::Foo) {
+ match f {
+ foo::Foo::Baz => {}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_enum_variant_from_module_2() {
+ check(
+ "baz",
+ r#"
+mod foo {
+ pub struct Foo { pub bar$0: uint }
+}
+
+fn foo(f: foo::Foo) {
+ let _ = f.bar;
+}
+"#,
+ r#"
+mod foo {
+ pub struct Foo { pub baz: uint }
+}
+
+fn foo(f: foo::Foo) {
+ let _ = f.baz;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_parameter_to_self() {
+ cov_mark::check!(rename_to_self);
+ check(
+ "self",
+ r#"
+struct Foo { i: i32 }
+
+impl Foo {
+ fn f(foo$0: &mut Foo) -> i32 {
+ foo.i
+ }
+}
+"#,
+ r#"
+struct Foo { i: i32 }
+
+impl Foo {
+ fn f(&mut self) -> i32 {
+ self.i
+ }
+}
+"#,
+ );
+ check(
+ "self",
+ r#"
+struct Foo { i: i32 }
+
+impl Foo {
+ fn f(foo$0: Foo) -> i32 {
+ foo.i
+ }
+}
+"#,
+ r#"
+struct Foo { i: i32 }
+
+impl Foo {
+ fn f(self) -> i32 {
+ self.i
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_parameter_to_self_error_no_impl() {
+ check(
+ "self",
+ r#"
+struct Foo { i: i32 }
+
+fn f(foo$0: &mut Foo) -> i32 {
+ foo.i
+}
+"#,
+ "error: Cannot rename parameter to self for free function",
+ );
+ check(
+ "self",
+ r#"
+struct Foo { i: i32 }
+struct Bar;
+
+impl Bar {
+ fn f(foo$0: &mut Foo) -> i32 {
+ foo.i
+ }
+}
+"#,
+ "error: Parameter type differs from impl block type",
+ );
+ }
+
+ #[test]
+ fn test_parameter_to_self_error_not_first() {
+ check(
+ "self",
+ r#"
+struct Foo { i: i32 }
+impl Foo {
+ fn f(x: (), foo$0: &mut Foo) -> i32 {
+ foo.i
+ }
+}
+"#,
+ "error: Only the first parameter may be renamed to self",
+ );
+ }
+
+ #[test]
+ fn test_parameter_to_self_impl_ref() {
+ check(
+ "self",
+ r#"
+struct Foo { i: i32 }
+impl &Foo {
+ fn f(foo$0: &Foo) -> i32 {
+ foo.i
+ }
+}
+"#,
+ r#"
+struct Foo { i: i32 }
+impl &Foo {
+ fn f(self) -> i32 {
+ self.i
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_self_to_parameter() {
+ check(
+ "foo",
+ r#"
+struct Foo { i: i32 }
+
+impl Foo {
+ fn f(&mut $0self) -> i32 {
+ self.i
+ }
+}
+"#,
+ r#"
+struct Foo { i: i32 }
+
+impl Foo {
+ fn f(foo: &mut Foo) -> i32 {
+ foo.i
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_owned_self_to_parameter() {
+ cov_mark::check!(rename_self_to_param);
+ check(
+ "foo",
+ r#"
+struct Foo { i: i32 }
+
+impl Foo {
+ fn f($0self) -> i32 {
+ self.i
+ }
+}
+"#,
+ r#"
+struct Foo { i: i32 }
+
+impl Foo {
+ fn f(foo: Foo) -> i32 {
+ foo.i
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_self_in_path_to_parameter() {
+ check(
+ "foo",
+ r#"
+struct Foo { i: i32 }
+
+impl Foo {
+ fn f(&self) -> i32 {
+ let self_var = 1;
+ self$0.i
+ }
+}
+"#,
+ r#"
+struct Foo { i: i32 }
+
+impl Foo {
+ fn f(foo: &Foo) -> i32 {
+ let self_var = 1;
+ foo.i
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_rename_field_put_init_shorthand() {
+ cov_mark::check!(test_rename_field_put_init_shorthand);
+ check(
+ "bar",
+ r#"
+struct Foo { i$0: i32 }
+
+fn foo(bar: i32) -> Foo {
+ Foo { i: bar }
+}
+"#,
+ r#"
+struct Foo { bar: i32 }
+
+fn foo(bar: i32) -> Foo {
+ Foo { bar }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_rename_local_put_init_shorthand() {
+ cov_mark::check!(test_rename_local_put_init_shorthand);
+ check(
+ "i",
+ r#"
+struct Foo { i: i32 }
+
+fn foo(bar$0: i32) -> Foo {
+ Foo { i: bar }
+}
+"#,
+ r#"
+struct Foo { i: i32 }
+
+fn foo(i: i32) -> Foo {
+ Foo { i }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_struct_field_pat_into_shorthand() {
+ cov_mark::check!(test_rename_field_put_init_shorthand_pat);
+ check(
+ "baz",
+ r#"
+struct Foo { i$0: i32 }
+
+fn foo(foo: Foo) {
+ let Foo { i: ref baz @ qux } = foo;
+ let _ = qux;
+}
+"#,
+ r#"
+struct Foo { baz: i32 }
+
+fn foo(foo: Foo) {
+ let Foo { baz: ref baz @ qux } = foo;
+ let _ = qux;
+}
+"#,
+ );
+ check(
+ "baz",
+ r#"
+struct Foo { i$0: i32 }
+
+fn foo(foo: Foo) {
+ let Foo { i: ref baz } = foo;
+ let _ = qux;
+}
+"#,
+ r#"
+struct Foo { baz: i32 }
+
+fn foo(foo: Foo) {
+ let Foo { ref baz } = foo;
+ let _ = qux;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_struct_local_pat_into_shorthand() {
+ cov_mark::check!(test_rename_local_put_init_shorthand_pat);
+ check(
+ "field",
+ r#"
+struct Foo { field: i32 }
+
+fn foo(foo: Foo) {
+ let Foo { field: qux$0 } = foo;
+ let _ = qux;
+}
+"#,
+ r#"
+struct Foo { field: i32 }
+
+fn foo(foo: Foo) {
+ let Foo { field } = foo;
+ let _ = field;
+}
+"#,
+ );
+ check(
+ "field",
+ r#"
+struct Foo { field: i32 }
+
+fn foo(foo: Foo) {
+ let Foo { field: x @ qux$0 } = foo;
+ let _ = qux;
+}
+"#,
+ r#"
+struct Foo { field: i32 }
+
+fn foo(foo: Foo) {
+ let Foo { field: x @ field } = foo;
+ let _ = field;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_rename_binding_in_destructure_pat() {
+ let expected_fixture = r#"
+struct Foo {
+ i: i32,
+}
+
+fn foo(foo: Foo) {
+ let Foo { i: bar } = foo;
+ let _ = bar;
+}
+"#;
+ check(
+ "bar",
+ r#"
+struct Foo {
+ i: i32,
+}
+
+fn foo(foo: Foo) {
+ let Foo { i: b } = foo;
+ let _ = b$0;
+}
+"#,
+ expected_fixture,
+ );
+ check(
+ "bar",
+ r#"
+struct Foo {
+ i: i32,
+}
+
+fn foo(foo: Foo) {
+ let Foo { i } = foo;
+ let _ = i$0;
+}
+"#,
+ expected_fixture,
+ );
+ }
+
+ #[test]
+ fn test_rename_binding_in_destructure_param_pat() {
+ check(
+ "bar",
+ r#"
+struct Foo {
+ i: i32
+}
+
+fn foo(Foo { i }: Foo) -> i32 {
+ i$0
+}
+"#,
+ r#"
+struct Foo {
+ i: i32
+}
+
+fn foo(Foo { i: bar }: Foo) -> i32 {
+ bar
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_struct_field_complex_ident_pat() {
+ cov_mark::check!(rename_record_pat_field_name_split);
+ check(
+ "baz",
+ r#"
+struct Foo { i$0: i32 }
+
+fn foo(foo: Foo) {
+ let Foo { ref i } = foo;
+}
+"#,
+ r#"
+struct Foo { baz: i32 }
+
+fn foo(foo: Foo) {
+ let Foo { baz: ref i } = foo;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_rename_lifetimes() {
+ cov_mark::check!(rename_lifetime);
+ check(
+ "'yeeee",
+ r#"
+trait Foo<'a> {
+ fn foo() -> &'a ();
+}
+impl<'a> Foo<'a> for &'a () {
+ fn foo() -> &'a$0 () {
+ unimplemented!()
+ }
+}
+"#,
+ r#"
+trait Foo<'a> {
+ fn foo() -> &'a ();
+}
+impl<'yeeee> Foo<'yeeee> for &'yeeee () {
+ fn foo() -> &'yeeee () {
+ unimplemented!()
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_rename_bind_pat() {
+ check(
+ "new_name",
+ r#"
+fn main() {
+ enum CustomOption<T> {
+ None,
+ Some(T),
+ }
+
+ let test_variable = CustomOption::Some(22);
+
+ match test_variable {
+ CustomOption::Some(foo$0) if foo == 11 => {}
+ _ => (),
+ }
+}"#,
+ r#"
+fn main() {
+ enum CustomOption<T> {
+ None,
+ Some(T),
+ }
+
+ let test_variable = CustomOption::Some(22);
+
+ match test_variable {
+ CustomOption::Some(new_name) if new_name == 11 => {}
+ _ => (),
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_rename_label() {
+ check(
+ "'foo",
+ r#"
+fn foo<'a>() -> &'a () {
+ 'a: {
+ 'b: loop {
+ break 'a$0;
+ }
+ }
+}
+"#,
+ r#"
+fn foo<'a>() -> &'a () {
+ 'foo: {
+ 'b: loop {
+ break 'foo;
+ }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_self_to_self() {
+ cov_mark::check!(rename_self_to_self);
+ check(
+ "self",
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(self$0) {}
+}
+"#,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(self) {}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_rename_field_in_pat_in_macro_doesnt_shorthand() {
+ // ideally we would be able to make this emit a short hand, but I doubt this is easily possible
+ check(
+ "baz",
+ r#"
+macro_rules! foo {
+ ($pattern:pat) => {
+ let $pattern = loop {};
+ };
+}
+struct Foo {
+ bar$0: u32,
+}
+fn foo() {
+ foo!(Foo { bar: baz });
+}
+"#,
+ r#"
+macro_rules! foo {
+ ($pattern:pat) => {
+ let $pattern = loop {};
+ };
+}
+struct Foo {
+ baz: u32,
+}
+fn foo() {
+ foo!(Foo { baz: baz });
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_rename_tuple_field() {
+ check(
+ "foo",
+ r#"
+struct Foo(i32);
+
+fn baz() {
+ let mut x = Foo(4);
+ x.0$0 = 5;
+}
+"#,
+ "error: No identifier available to rename",
+ );
+ }
+
+ #[test]
+ fn test_rename_builtin() {
+ check(
+ "foo",
+ r#"
+fn foo() {
+ let x: i32$0 = 0;
+}
+"#,
+ "error: Cannot rename builtin type",
+ );
+ }
+
+ #[test]
+ fn test_rename_self() {
+ check(
+ "foo",
+ r#"
+struct Foo {}
+
+impl Foo {
+ fn foo(self) -> Self$0 {
+ self
+ }
+}
+"#,
+ "error: Cannot rename `Self`",
+ );
+ }
+
+ #[test]
+ fn test_rename_ignores_self_ty() {
+ check(
+ "Fo0",
+ r#"
+struct $0Foo;
+
+impl Foo where Self: {}
+"#,
+ r#"
+struct Fo0;
+
+impl Fo0 where Self: {}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_rename_fails_on_aliases() {
+ check(
+ "Baz",
+ r#"
+struct Foo;
+use Foo as Bar$0;
+"#,
+ "error: Renaming aliases is currently unsupported",
+ );
+ check(
+ "Baz",
+ r#"
+struct Foo;
+use Foo as Bar;
+use Bar$0;
+"#,
+ "error: Renaming aliases is currently unsupported",
+ );
+ }
+
+ #[test]
+ fn test_rename_trait_method() {
+ let res = r"
+trait Foo {
+ fn foo(&self) {
+ self.foo();
+ }
+}
+
+impl Foo for () {
+ fn foo(&self) {
+ self.foo();
+ }
+}";
+ check(
+ "foo",
+ r#"
+trait Foo {
+ fn bar$0(&self) {
+ self.bar();
+ }
+}
+
+impl Foo for () {
+ fn bar(&self) {
+ self.bar();
+ }
+}"#,
+ res,
+ );
+ check(
+ "foo",
+ r#"
+trait Foo {
+ fn bar(&self) {
+ self.bar$0();
+ }
+}
+
+impl Foo for () {
+ fn bar(&self) {
+ self.bar();
+ }
+}"#,
+ res,
+ );
+ check(
+ "foo",
+ r#"
+trait Foo {
+ fn bar(&self) {
+ self.bar();
+ }
+}
+
+impl Foo for () {
+ fn bar$0(&self) {
+ self.bar();
+ }
+}"#,
+ res,
+ );
+ check(
+ "foo",
+ r#"
+trait Foo {
+ fn bar(&self) {
+ self.bar();
+ }
+}
+
+impl Foo for () {
+ fn bar(&self) {
+ self.bar$0();
+ }
+}"#,
+ res,
+ );
+ }
+
+ #[test]
+ fn test_rename_trait_method_prefix_of_second() {
+ check(
+ "qux",
+ r#"
+trait Foo {
+ fn foo$0() {}
+ fn foobar() {}
+}
+"#,
+ r#"
+trait Foo {
+ fn qux() {}
+ fn foobar() {}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_rename_trait_const() {
+ let res = r"
+trait Foo {
+ const FOO: ();
+}
+
+impl Foo for () {
+ const FOO: ();
+}
+fn f() { <()>::FOO; }";
+ check(
+ "FOO",
+ r#"
+trait Foo {
+ const BAR$0: ();
+}
+
+impl Foo for () {
+ const BAR: ();
+}
+fn f() { <()>::BAR; }"#,
+ res,
+ );
+ check(
+ "FOO",
+ r#"
+trait Foo {
+ const BAR: ();
+}
+
+impl Foo for () {
+ const BAR$0: ();
+}
+fn f() { <()>::BAR; }"#,
+ res,
+ );
+ check(
+ "FOO",
+ r#"
+trait Foo {
+ const BAR: ();
+}
+
+impl Foo for () {
+ const BAR: ();
+}
+fn f() { <()>::BAR$0; }"#,
+ res,
+ );
+ }
+
+ #[test]
+ fn defs_from_macros_arent_renamed() {
+ check(
+ "lol",
+ r#"
+macro_rules! m { () => { fn f() {} } }
+m!();
+fn main() { f$0() }
+"#,
+ "error: No identifier available to rename",
+ )
+ }
+
+ #[test]
+ fn attributed_item() {
+ check(
+ "function",
+ r#"
+//- proc_macros: identity
+
+#[proc_macros::identity]
+fn func$0() {
+ func();
+}
+"#,
+ r#"
+
+#[proc_macros::identity]
+fn function() {
+ function();
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn in_macro_multi_mapping() {
+ check(
+ "a",
+ r#"
+fn foo() {
+ macro_rules! match_ast2 {
+ ($node:ident {
+ $( $res:expr, )*
+ }) => {{
+ $( if $node { $res } else )*
+ { loop {} }
+ }};
+ }
+ let $0d = 3;
+ match_ast2! {
+ d {
+ d,
+ d,
+ }
+ };
+}
+"#,
+ r#"
+fn foo() {
+ macro_rules! match_ast2 {
+ ($node:ident {
+ $( $res:expr, )*
+ }) => {{
+ $( if $node { $res } else )*
+ { loop {} }
+ }};
+ }
+ let a = 3;
+ match_ast2! {
+ a {
+ a,
+ a,
+ }
+ };
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn rename_multi_local() {
+ check(
+ "bar",
+ r#"
+fn foo((foo$0 | foo | foo): ()) {
+ foo;
+ let foo;
+}
+"#,
+ r#"
+fn foo((bar | bar | bar): ()) {
+ bar;
+ let foo;
+}
+"#,
+ );
+ check(
+ "bar",
+ r#"
+fn foo((foo | foo$0 | foo): ()) {
+ foo;
+ let foo;
+}
+"#,
+ r#"
+fn foo((bar | bar | bar): ()) {
+ bar;
+ let foo;
+}
+"#,
+ );
+ check(
+ "bar",
+ r#"
+fn foo((foo | foo | foo): ()) {
+ foo$0;
+ let foo;
+}
+"#,
+ r#"
+fn foo((bar | bar | bar): ()) {
+ bar;
+ let foo;
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/runnables.rs b/src/tools/rust-analyzer/crates/ide/src/runnables.rs
new file mode 100644
index 000000000..bec770ed9
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/runnables.rs
@@ -0,0 +1,2163 @@
+use std::fmt;
+
+use ast::HasName;
+use cfg::CfgExpr;
+use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics};
+use ide_assists::utils::test_related_attribute;
+use ide_db::{
+ base_db::{FilePosition, FileRange},
+ defs::Definition,
+ helpers::visit_file_defs,
+ search::SearchScope,
+ FxHashMap, FxHashSet, RootDatabase, SymbolKind,
+};
+use itertools::Itertools;
+use stdx::{always, format_to};
+use syntax::{
+ ast::{self, AstNode, HasAttrs as _},
+ SmolStr, SyntaxNode,
+};
+
+use crate::{references, FileId, NavigationTarget, ToNav, TryToNav};
+
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub struct Runnable {
+ pub use_name_in_title: bool,
+ pub nav: NavigationTarget,
+ pub kind: RunnableKind,
+ pub cfg: Option<CfgExpr>,
+}
+
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub enum TestId {
+ Name(SmolStr),
+ Path(String),
+}
+
+impl fmt::Display for TestId {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ TestId::Name(name) => name.fmt(f),
+ TestId::Path(path) => path.fmt(f),
+ }
+ }
+}
+
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub enum RunnableKind {
+ Test { test_id: TestId, attr: TestAttr },
+ TestMod { path: String },
+ Bench { test_id: TestId },
+ DocTest { test_id: TestId },
+ Bin,
+}
+
+#[cfg(test)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+enum RunnableTestKind {
+ Test,
+ TestMod,
+ DocTest,
+ Bench,
+ Bin,
+}
+
+impl Runnable {
+ // test package::module::testname
+ pub fn label(&self, target: Option<String>) -> String {
+ match &self.kind {
+ RunnableKind::Test { test_id, .. } => format!("test {}", test_id),
+ RunnableKind::TestMod { path } => format!("test-mod {}", path),
+ RunnableKind::Bench { test_id } => format!("bench {}", test_id),
+ RunnableKind::DocTest { test_id, .. } => format!("doctest {}", test_id),
+ RunnableKind::Bin => {
+ target.map_or_else(|| "run binary".to_string(), |t| format!("run {}", t))
+ }
+ }
+ }
+
+ pub fn title(&self) -> String {
+ let mut s = String::from("▶\u{fe0e} Run ");
+ if self.use_name_in_title {
+ format_to!(s, "{}", self.nav.name);
+ if !matches!(self.kind, RunnableKind::Bin) {
+ s.push(' ');
+ }
+ }
+ let suffix = match &self.kind {
+ RunnableKind::TestMod { .. } => "Tests",
+ RunnableKind::Test { .. } => "Test",
+ RunnableKind::DocTest { .. } => "Doctest",
+ RunnableKind::Bench { .. } => "Bench",
+ RunnableKind::Bin => return s,
+ };
+ s.push_str(suffix);
+ s
+ }
+
+ #[cfg(test)]
+ fn test_kind(&self) -> RunnableTestKind {
+ match &self.kind {
+ RunnableKind::TestMod { .. } => RunnableTestKind::TestMod,
+ RunnableKind::Test { .. } => RunnableTestKind::Test,
+ RunnableKind::DocTest { .. } => RunnableTestKind::DocTest,
+ RunnableKind::Bench { .. } => RunnableTestKind::Bench,
+ RunnableKind::Bin => RunnableTestKind::Bin,
+ }
+ }
+}
+
+// Feature: Run
+//
+// Shows a popup suggesting to run a test/benchmark/binary **at the current cursor
+// location**. Super useful for repeatedly running just a single test. Do bind this
+// to a shortcut!
+//
+// |===
+// | Editor | Action Name
+//
+// | VS Code | **Rust Analyzer: Run**
+// |===
+// image::https://user-images.githubusercontent.com/48062697/113065583-055aae80-91b1-11eb-958f-d67efcaf6a2f.gif[]
+pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
+ let sema = Semantics::new(db);
+
+ let mut res = Vec::new();
+ // Record all runnables that come from macro expansions here instead.
+ // In case an expansion creates multiple runnables we want to name them to avoid emitting a bunch of equally named runnables.
+ let mut in_macro_expansion = FxHashMap::<hir::HirFileId, Vec<Runnable>>::default();
+ let mut add_opt = |runnable: Option<Runnable>, def| {
+ if let Some(runnable) = runnable.filter(|runnable| {
+ always!(
+ runnable.nav.file_id == file_id,
+ "tried adding a runnable pointing to a different file: {:?} for {:?}",
+ runnable.kind,
+ file_id
+ )
+ }) {
+ if let Some(def) = def {
+ let file_id = match def {
+ Definition::Module(it) => it.declaration_source(db).map(|src| src.file_id),
+ Definition::Function(it) => it.source(db).map(|src| src.file_id),
+ _ => None,
+ };
+ if let Some(file_id) = file_id.filter(|file| file.call_node(db).is_some()) {
+ in_macro_expansion.entry(file_id).or_default().push(runnable);
+ return;
+ }
+ }
+ res.push(runnable);
+ }
+ };
+ visit_file_defs(&sema, file_id, &mut |def| {
+ let runnable = match def {
+ Definition::Module(it) => runnable_mod(&sema, it),
+ Definition::Function(it) => runnable_fn(&sema, it),
+ Definition::SelfType(impl_) => runnable_impl(&sema, &impl_),
+ _ => None,
+ };
+ add_opt(
+ runnable
+ .or_else(|| module_def_doctest(sema.db, def))
+ // #[macro_export] mbe macros are declared in the root, while their definition may reside in a different module
+ .filter(|it| it.nav.file_id == file_id),
+ Some(def),
+ );
+ if let Definition::SelfType(impl_) = def {
+ impl_.items(db).into_iter().for_each(|assoc| {
+ let runnable = match assoc {
+ hir::AssocItem::Function(it) => {
+ runnable_fn(&sema, it).or_else(|| module_def_doctest(sema.db, it.into()))
+ }
+ hir::AssocItem::Const(it) => module_def_doctest(sema.db, it.into()),
+ hir::AssocItem::TypeAlias(it) => module_def_doctest(sema.db, it.into()),
+ };
+ add_opt(runnable, Some(assoc.into()))
+ });
+ }
+ });
+
+ sema.to_module_defs(file_id)
+ .map(|it| runnable_mod_outline_definition(&sema, it))
+ .for_each(|it| add_opt(it, None));
+
+ res.extend(in_macro_expansion.into_iter().flat_map(|(_, runnables)| {
+ let use_name_in_title = runnables.len() != 1;
+ runnables.into_iter().map(move |mut r| {
+ r.use_name_in_title = use_name_in_title;
+ r
+ })
+ }));
+ res
+}
+
+// Feature: Related Tests
+//
+// Provides a sneak peek of all tests where the current item is used.
+//
+// The simplest way to use this feature is via the context menu:
+// - Right-click on the selected item. The context menu opens.
+// - Select **Peek related tests**
+//
+// |===
+// | Editor | Action Name
+//
+// | VS Code | **Rust Analyzer: Peek related tests**
+// |===
+pub(crate) fn related_tests(
+ db: &RootDatabase,
+ position: FilePosition,
+ search_scope: Option<SearchScope>,
+) -> Vec<Runnable> {
+ let sema = Semantics::new(db);
+ let mut res: FxHashSet<Runnable> = FxHashSet::default();
+ let syntax = sema.parse(position.file_id).syntax().clone();
+
+ find_related_tests(&sema, &syntax, position, search_scope, &mut res);
+
+ res.into_iter().collect()
+}
+
+fn find_related_tests(
+ sema: &Semantics<'_, RootDatabase>,
+ syntax: &SyntaxNode,
+ position: FilePosition,
+ search_scope: Option<SearchScope>,
+ tests: &mut FxHashSet<Runnable>,
+) {
+ // FIXME: why is this using references::find_defs, this should use ide_db::search
+ let defs = match references::find_defs(sema, syntax, position.offset) {
+ Some(defs) => defs,
+ None => return,
+ };
+ for def in defs {
+ let defs = def
+ .usages(sema)
+ .set_scope(search_scope.clone())
+ .all()
+ .references
+ .into_values()
+ .flatten();
+ for ref_ in defs {
+ let name_ref = match ref_.name {
+ ast::NameLike::NameRef(name_ref) => name_ref,
+ _ => continue,
+ };
+ if let Some(fn_def) =
+ sema.ancestors_with_macros(name_ref.syntax().clone()).find_map(ast::Fn::cast)
+ {
+ if let Some(runnable) = as_test_runnable(sema, &fn_def) {
+ // direct test
+ tests.insert(runnable);
+ } else if let Some(module) = parent_test_module(sema, &fn_def) {
+ // indirect test
+ find_related_tests_in_module(sema, syntax, &fn_def, &module, tests);
+ }
+ }
+ }
+ }
+}
+
+fn find_related_tests_in_module(
+ sema: &Semantics<'_, RootDatabase>,
+ syntax: &SyntaxNode,
+ fn_def: &ast::Fn,
+ parent_module: &hir::Module,
+ tests: &mut FxHashSet<Runnable>,
+) {
+ let fn_name = match fn_def.name() {
+ Some(it) => it,
+ _ => return,
+ };
+ let mod_source = parent_module.definition_source(sema.db);
+ let range = match &mod_source.value {
+ hir::ModuleSource::Module(m) => m.syntax().text_range(),
+ hir::ModuleSource::BlockExpr(b) => b.syntax().text_range(),
+ hir::ModuleSource::SourceFile(f) => f.syntax().text_range(),
+ };
+
+ let file_id = mod_source.file_id.original_file(sema.db);
+ let mod_scope = SearchScope::file_range(FileRange { file_id, range });
+ let fn_pos = FilePosition { file_id, offset: fn_name.syntax().text_range().start() };
+ find_related_tests(sema, syntax, fn_pos, Some(mod_scope), tests)
+}
+
+fn as_test_runnable(sema: &Semantics<'_, RootDatabase>, fn_def: &ast::Fn) -> Option<Runnable> {
+ if test_related_attribute(fn_def).is_some() {
+ let function = sema.to_def(fn_def)?;
+ runnable_fn(sema, function)
+ } else {
+ None
+ }
+}
+
+fn parent_test_module(sema: &Semantics<'_, RootDatabase>, fn_def: &ast::Fn) -> Option<hir::Module> {
+ fn_def.syntax().ancestors().find_map(|node| {
+ let module = ast::Module::cast(node)?;
+ let module = sema.to_def(&module)?;
+
+ if has_test_function_or_multiple_test_submodules(sema, &module) {
+ Some(module)
+ } else {
+ None
+ }
+ })
+}
+
+pub(crate) fn runnable_fn(
+ sema: &Semantics<'_, RootDatabase>,
+ def: hir::Function,
+) -> Option<Runnable> {
+ let func = def.source(sema.db)?;
+ let name = def.name(sema.db).to_smol_str();
+
+ let root = def.module(sema.db).krate().root_module(sema.db);
+
+ let kind = if name == "main" && def.module(sema.db) == root {
+ RunnableKind::Bin
+ } else {
+ let test_id = || {
+ let canonical_path = {
+ let def: hir::ModuleDef = def.into();
+ def.canonical_path(sema.db)
+ };
+ canonical_path.map(TestId::Path).unwrap_or(TestId::Name(name))
+ };
+
+ if test_related_attribute(&func.value).is_some() {
+ let attr = TestAttr::from_fn(&func.value);
+ RunnableKind::Test { test_id: test_id(), attr }
+ } else if func.value.has_atom_attr("bench") {
+ RunnableKind::Bench { test_id: test_id() }
+ } else {
+ return None;
+ }
+ };
+
+ let nav = NavigationTarget::from_named(
+ sema.db,
+ func.as_ref().map(|it| it as &dyn ast::HasName),
+ SymbolKind::Function,
+ );
+ let cfg = def.attrs(sema.db).cfg();
+ Some(Runnable { use_name_in_title: false, nav, kind, cfg })
+}
+
+pub(crate) fn runnable_mod(
+ sema: &Semantics<'_, RootDatabase>,
+ def: hir::Module,
+) -> Option<Runnable> {
+ if !has_test_function_or_multiple_test_submodules(sema, &def) {
+ return None;
+ }
+ let path =
+ def.path_to_root(sema.db).into_iter().rev().filter_map(|it| it.name(sema.db)).join("::");
+
+ let attrs = def.attrs(sema.db);
+ let cfg = attrs.cfg();
+ let nav = NavigationTarget::from_module_to_decl(sema.db, def);
+ Some(Runnable { use_name_in_title: false, nav, kind: RunnableKind::TestMod { path }, cfg })
+}
+
+pub(crate) fn runnable_impl(
+ sema: &Semantics<'_, RootDatabase>,
+ def: &hir::Impl,
+) -> Option<Runnable> {
+ let attrs = def.attrs(sema.db);
+ if !has_runnable_doc_test(&attrs) {
+ return None;
+ }
+ let cfg = attrs.cfg();
+ let nav = def.try_to_nav(sema.db)?;
+ let ty = def.self_ty(sema.db);
+ let adt_name = ty.as_adt()?.name(sema.db);
+ let mut ty_args = ty.type_arguments().peekable();
+ let params = if ty_args.peek().is_some() {
+ format!("<{}>", ty_args.format_with(", ", |ty, cb| cb(&ty.display(sema.db))))
+ } else {
+ String::new()
+ };
+ let test_id = TestId::Path(format!("{}{}", adt_name, params));
+
+ Some(Runnable { use_name_in_title: false, nav, kind: RunnableKind::DocTest { test_id }, cfg })
+}
+
+/// Creates a test mod runnable for outline modules at the top of their definition.
+fn runnable_mod_outline_definition(
+ sema: &Semantics<'_, RootDatabase>,
+ def: hir::Module,
+) -> Option<Runnable> {
+ if !has_test_function_or_multiple_test_submodules(sema, &def) {
+ return None;
+ }
+ let path =
+ def.path_to_root(sema.db).into_iter().rev().filter_map(|it| it.name(sema.db)).join("::");
+
+ let attrs = def.attrs(sema.db);
+ let cfg = attrs.cfg();
+ match def.definition_source(sema.db).value {
+ hir::ModuleSource::SourceFile(_) => Some(Runnable {
+ use_name_in_title: false,
+ nav: def.to_nav(sema.db),
+ kind: RunnableKind::TestMod { path },
+ cfg,
+ }),
+ _ => None,
+ }
+}
+
+fn module_def_doctest(db: &RootDatabase, def: Definition) -> Option<Runnable> {
+ let attrs = match def {
+ Definition::Module(it) => it.attrs(db),
+ Definition::Function(it) => it.attrs(db),
+ Definition::Adt(it) => it.attrs(db),
+ Definition::Variant(it) => it.attrs(db),
+ Definition::Const(it) => it.attrs(db),
+ Definition::Static(it) => it.attrs(db),
+ Definition::Trait(it) => it.attrs(db),
+ Definition::TypeAlias(it) => it.attrs(db),
+ Definition::Macro(it) => it.attrs(db),
+ Definition::SelfType(it) => it.attrs(db),
+ _ => return None,
+ };
+ if !has_runnable_doc_test(&attrs) {
+ return None;
+ }
+ let def_name = def.name(db)?;
+ let path = (|| {
+ let mut path = String::new();
+ def.canonical_module_path(db)?
+ .flat_map(|it| it.name(db))
+ .for_each(|name| format_to!(path, "{}::", name));
+ // This probably belongs to canonical_path?
+ if let Some(assoc_item) = def.as_assoc_item(db) {
+ if let hir::AssocItemContainer::Impl(imp) = assoc_item.container(db) {
+ let ty = imp.self_ty(db);
+ if let Some(adt) = ty.as_adt() {
+ let name = adt.name(db);
+ let mut ty_args = ty.type_arguments().peekable();
+ format_to!(path, "{}", name);
+ if ty_args.peek().is_some() {
+ format_to!(
+ path,
+ "<{}>",
+ ty_args.format_with(", ", |ty, cb| cb(&ty.display(db)))
+ );
+ }
+ format_to!(path, "::{}", def_name);
+ return Some(path);
+ }
+ }
+ }
+ format_to!(path, "{}", def_name);
+ Some(path)
+ })();
+
+ let test_id = path.map_or_else(|| TestId::Name(def_name.to_smol_str()), TestId::Path);
+
+ let mut nav = match def {
+ Definition::Module(def) => NavigationTarget::from_module_to_decl(db, def),
+ def => def.try_to_nav(db)?,
+ };
+ nav.focus_range = None;
+ nav.description = None;
+ nav.docs = None;
+ nav.kind = None;
+ let res = Runnable {
+ use_name_in_title: false,
+ nav,
+ kind: RunnableKind::DocTest { test_id },
+ cfg: attrs.cfg(),
+ };
+ Some(res)
+}
+
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub struct TestAttr {
+ pub ignore: bool,
+}
+
+impl TestAttr {
+ fn from_fn(fn_def: &ast::Fn) -> TestAttr {
+ let ignore = fn_def
+ .attrs()
+ .filter_map(|attr| attr.simple_name())
+ .any(|attribute_text| attribute_text == "ignore");
+ TestAttr { ignore }
+ }
+}
+
+const RUSTDOC_FENCES: [&str; 2] = ["```", "~~~"];
+const RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE: &[&str] =
+ &["", "rust", "should_panic", "edition2015", "edition2018", "edition2021"];
+
+fn has_runnable_doc_test(attrs: &hir::Attrs) -> bool {
+ attrs.docs().map_or(false, |doc| {
+ let mut in_code_block = false;
+
+ for line in String::from(doc).lines() {
+ if let Some(header) =
+ RUSTDOC_FENCES.into_iter().find_map(|fence| line.strip_prefix(fence))
+ {
+ in_code_block = !in_code_block;
+
+ if in_code_block
+ && header
+ .split(',')
+ .all(|sub| RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE.contains(&sub.trim()))
+ {
+ return true;
+ }
+ }
+ }
+
+ false
+ })
+}
+
+// We could create runnables for modules with number_of_test_submodules > 0,
+// but that bloats the runnables for no real benefit, since all tests can be run by the submodule already
+fn has_test_function_or_multiple_test_submodules(
+ sema: &Semantics<'_, RootDatabase>,
+ module: &hir::Module,
+) -> bool {
+ let mut number_of_test_submodules = 0;
+
+ for item in module.declarations(sema.db) {
+ match item {
+ hir::ModuleDef::Function(f) => {
+ if let Some(it) = f.source(sema.db) {
+ if test_related_attribute(&it.value).is_some() {
+ return true;
+ }
+ }
+ }
+ hir::ModuleDef::Module(submodule) => {
+ if has_test_function_or_multiple_test_submodules(sema, &submodule) {
+ number_of_test_submodules += 1;
+ }
+ }
+ _ => (),
+ }
+ }
+
+ number_of_test_submodules > 1
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+
+ use crate::fixture;
+
+ use super::{RunnableTestKind::*, *};
+
+ fn check(
+ ra_fixture: &str,
+ // FIXME: fold this into `expect` as well
+ actions: &[RunnableTestKind],
+ expect: Expect,
+ ) {
+ let (analysis, position) = fixture::position(ra_fixture);
+ let mut runnables = analysis.runnables(position.file_id).unwrap();
+ runnables.sort_by_key(|it| (it.nav.full_range.start(), it.nav.name.clone()));
+ expect.assert_debug_eq(&runnables);
+ assert_eq!(
+ actions,
+ runnables.into_iter().map(|it| it.test_kind()).collect::<Vec<_>>().as_slice()
+ );
+ }
+
+ fn check_tests(ra_fixture: &str, expect: Expect) {
+ let (analysis, position) = fixture::position(ra_fixture);
+ let tests = analysis.related_tests(position, None).unwrap();
+ expect.assert_debug_eq(&tests);
+ }
+
+ #[test]
+ fn test_runnables() {
+ check(
+ r#"
+//- /lib.rs
+$0
+fn main() {}
+
+#[test]
+fn test_foo() {}
+
+#[test]
+#[ignore]
+fn test_foo() {}
+
+#[bench]
+fn bench() {}
+
+mod not_a_root {
+ fn main() {}
+}
+"#,
+ &[TestMod, Bin, Test, Test, Bench],
+ expect![[r#"
+ [
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..137,
+ name: "",
+ kind: Module,
+ },
+ kind: TestMod {
+ path: "",
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 1..13,
+ focus_range: 4..8,
+ name: "main",
+ kind: Function,
+ },
+ kind: Bin,
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 15..39,
+ focus_range: 26..34,
+ name: "test_foo",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "test_foo",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 41..75,
+ focus_range: 62..70,
+ name: "test_foo",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "test_foo",
+ ),
+ attr: TestAttr {
+ ignore: true,
+ },
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 77..99,
+ focus_range: 89..94,
+ name: "bench",
+ kind: Function,
+ },
+ kind: Bench {
+ test_id: Path(
+ "bench",
+ ),
+ },
+ cfg: None,
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_runnables_doc_test() {
+ check(
+ r#"
+//- /lib.rs
+$0
+fn main() {}
+
+/// ```
+/// let x = 5;
+/// ```
+fn should_have_runnable() {}
+
+/// ```edition2018
+/// let x = 5;
+/// ```
+fn should_have_runnable_1() {}
+
+/// ```
+/// let z = 55;
+/// ```
+///
+/// ```ignore
+/// let z = 56;
+/// ```
+fn should_have_runnable_2() {}
+
+/**
+```rust
+let z = 55;
+```
+*/
+fn should_have_no_runnable_3() {}
+
+/**
+ ```rust
+ let z = 55;
+ ```
+*/
+fn should_have_no_runnable_4() {}
+
+/// ```no_run
+/// let z = 55;
+/// ```
+fn should_have_no_runnable() {}
+
+/// ```ignore
+/// let z = 55;
+/// ```
+fn should_have_no_runnable_2() {}
+
+/// ```compile_fail
+/// let z = 55;
+/// ```
+fn should_have_no_runnable_3() {}
+
+/// ```text
+/// arbitrary plain text
+/// ```
+fn should_have_no_runnable_4() {}
+
+/// ```text
+/// arbitrary plain text
+/// ```
+///
+/// ```sh
+/// $ shell code
+/// ```
+fn should_have_no_runnable_5() {}
+
+/// ```rust,no_run
+/// let z = 55;
+/// ```
+fn should_have_no_runnable_6() {}
+
+/// ```
+/// let x = 5;
+/// ```
+struct StructWithRunnable(String);
+
+/// ```
+/// let x = 5;
+/// ```
+impl StructWithRunnable {}
+
+trait Test {
+ fn test() -> usize {
+ 5usize
+ }
+}
+
+/// ```
+/// let x = 5;
+/// ```
+impl Test for StructWithRunnable {}
+"#,
+ &[Bin, DocTest, DocTest, DocTest, DocTest, DocTest, DocTest, DocTest, DocTest],
+ expect![[r#"
+ [
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 1..13,
+ focus_range: 4..8,
+ name: "main",
+ kind: Function,
+ },
+ kind: Bin,
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 15..74,
+ name: "should_have_runnable",
+ },
+ kind: DocTest {
+ test_id: Path(
+ "should_have_runnable",
+ ),
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 76..148,
+ name: "should_have_runnable_1",
+ },
+ kind: DocTest {
+ test_id: Path(
+ "should_have_runnable_1",
+ ),
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 150..254,
+ name: "should_have_runnable_2",
+ },
+ kind: DocTest {
+ test_id: Path(
+ "should_have_runnable_2",
+ ),
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 256..320,
+ name: "should_have_no_runnable_3",
+ },
+ kind: DocTest {
+ test_id: Path(
+ "should_have_no_runnable_3",
+ ),
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 322..398,
+ name: "should_have_no_runnable_4",
+ },
+ kind: DocTest {
+ test_id: Path(
+ "should_have_no_runnable_4",
+ ),
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 900..965,
+ name: "StructWithRunnable",
+ },
+ kind: DocTest {
+ test_id: Path(
+ "StructWithRunnable",
+ ),
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 967..1024,
+ focus_range: 1003..1021,
+ name: "impl",
+ kind: Impl,
+ },
+ kind: DocTest {
+ test_id: Path(
+ "StructWithRunnable",
+ ),
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 1088..1154,
+ focus_range: 1133..1151,
+ name: "impl",
+ kind: Impl,
+ },
+ kind: DocTest {
+ test_id: Path(
+ "StructWithRunnable",
+ ),
+ },
+ cfg: None,
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_runnables_doc_test_in_impl() {
+ check(
+ r#"
+//- /lib.rs
+$0
+fn main() {}
+
+struct Data;
+impl Data {
+ /// ```
+ /// let x = 5;
+ /// ```
+ fn foo() {}
+}
+"#,
+ &[Bin, DocTest],
+ expect![[r#"
+ [
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 1..13,
+ focus_range: 4..8,
+ name: "main",
+ kind: Function,
+ },
+ kind: Bin,
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 44..98,
+ name: "foo",
+ },
+ kind: DocTest {
+ test_id: Path(
+ "Data::foo",
+ ),
+ },
+ cfg: None,
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_runnables_module() {
+ check(
+ r#"
+//- /lib.rs
+$0
+mod test_mod {
+ #[test]
+ fn test_foo1() {}
+}
+"#,
+ &[TestMod, Test],
+ expect![[r#"
+ [
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 1..51,
+ focus_range: 5..13,
+ name: "test_mod",
+ kind: Module,
+ description: "mod test_mod",
+ },
+ kind: TestMod {
+ path: "test_mod",
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 20..49,
+ focus_range: 35..44,
+ name: "test_foo1",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "test_mod::test_foo1",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn only_modules_with_test_functions_or_more_than_one_test_submodule_have_runners() {
+ check(
+ r#"
+//- /lib.rs
+$0
+mod root_tests {
+ mod nested_tests_0 {
+ mod nested_tests_1 {
+ #[test]
+ fn nested_test_11() {}
+
+ #[test]
+ fn nested_test_12() {}
+ }
+
+ mod nested_tests_2 {
+ #[test]
+ fn nested_test_2() {}
+ }
+
+ mod nested_tests_3 {}
+ }
+
+ mod nested_tests_4 {}
+}
+"#,
+ &[TestMod, TestMod, Test, Test, TestMod, Test],
+ expect![[r#"
+ [
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 22..323,
+ focus_range: 26..40,
+ name: "nested_tests_0",
+ kind: Module,
+ description: "mod nested_tests_0",
+ },
+ kind: TestMod {
+ path: "root_tests::nested_tests_0",
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 51..192,
+ focus_range: 55..69,
+ name: "nested_tests_1",
+ kind: Module,
+ description: "mod nested_tests_1",
+ },
+ kind: TestMod {
+ path: "root_tests::nested_tests_0::nested_tests_1",
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 84..126,
+ focus_range: 107..121,
+ name: "nested_test_11",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "root_tests::nested_tests_0::nested_tests_1::nested_test_11",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 140..182,
+ focus_range: 163..177,
+ name: "nested_test_12",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "root_tests::nested_tests_0::nested_tests_1::nested_test_12",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 202..286,
+ focus_range: 206..220,
+ name: "nested_tests_2",
+ kind: Module,
+ description: "mod nested_tests_2",
+ },
+ kind: TestMod {
+ path: "root_tests::nested_tests_0::nested_tests_2",
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 235..276,
+ focus_range: 258..271,
+ name: "nested_test_2",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "root_tests::nested_tests_0::nested_tests_2::nested_test_2",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_runnables_with_feature() {
+ check(
+ r#"
+//- /lib.rs crate:foo cfg:feature=foo
+$0
+#[test]
+#[cfg(feature = "foo")]
+fn test_foo1() {}
+"#,
+ &[TestMod, Test],
+ expect![[r#"
+ [
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..51,
+ name: "",
+ kind: Module,
+ },
+ kind: TestMod {
+ path: "",
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 1..50,
+ focus_range: 36..45,
+ name: "test_foo1",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "test_foo1",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: Some(
+ Atom(
+ KeyValue {
+ key: "feature",
+ value: "foo",
+ },
+ ),
+ ),
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_runnables_with_features() {
+ check(
+ r#"
+//- /lib.rs crate:foo cfg:feature=foo,feature=bar
+$0
+#[test]
+#[cfg(all(feature = "foo", feature = "bar"))]
+fn test_foo1() {}
+"#,
+ &[TestMod, Test],
+ expect![[r#"
+ [
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..73,
+ name: "",
+ kind: Module,
+ },
+ kind: TestMod {
+ path: "",
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 1..72,
+ focus_range: 58..67,
+ name: "test_foo1",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "test_foo1",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: Some(
+ All(
+ [
+ Atom(
+ KeyValue {
+ key: "feature",
+ value: "foo",
+ },
+ ),
+ Atom(
+ KeyValue {
+ key: "feature",
+ value: "bar",
+ },
+ ),
+ ],
+ ),
+ ),
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_runnables_no_test_function_in_module() {
+ check(
+ r#"
+//- /lib.rs
+$0
+mod test_mod {
+ fn foo1() {}
+}
+"#,
+ &[],
+ expect![[r#"
+ []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_doc_runnables_impl_mod() {
+ check(
+ r#"
+//- /lib.rs
+mod foo;
+//- /foo.rs
+struct Foo;$0
+impl Foo {
+ /// ```
+ /// let x = 5;
+ /// ```
+ fn foo() {}
+}
+ "#,
+ &[DocTest],
+ expect![[r#"
+ [
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 1,
+ ),
+ full_range: 27..81,
+ name: "foo",
+ },
+ kind: DocTest {
+ test_id: Path(
+ "foo::Foo::foo",
+ ),
+ },
+ cfg: None,
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_runnables_in_macro() {
+ check(
+ r#"
+//- /lib.rs
+$0
+macro_rules! gen {
+ () => {
+ #[test]
+ fn foo_test() {}
+ }
+}
+macro_rules! gen2 {
+ () => {
+ mod tests2 {
+ #[test]
+ fn foo_test2() {}
+ }
+ }
+}
+mod tests {
+ gen!();
+}
+gen2!();
+"#,
+ &[TestMod, TestMod, Test, Test, TestMod],
+ expect![[r#"
+ [
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..237,
+ name: "",
+ kind: Module,
+ },
+ kind: TestMod {
+ path: "",
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 202..227,
+ focus_range: 206..211,
+ name: "tests",
+ kind: Module,
+ description: "mod tests",
+ },
+ kind: TestMod {
+ path: "tests",
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 218..225,
+ name: "foo_test",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "tests::foo_test",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: true,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 228..236,
+ name: "foo_test2",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "tests2::foo_test2",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: true,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 228..236,
+ name: "tests2",
+ kind: Module,
+ description: "mod tests2",
+ },
+ kind: TestMod {
+ path: "tests2",
+ },
+ cfg: None,
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn big_mac() {
+ check(
+ r#"
+//- /lib.rs
+$0
+macro_rules! foo {
+ () => {
+ mod foo_tests {
+ #[test]
+ fn foo0() {}
+ #[test]
+ fn foo1() {}
+ #[test]
+ fn foo2() {}
+ }
+ };
+}
+foo!();
+"#,
+ &[Test, Test, Test, TestMod],
+ expect![[r#"
+ [
+ Runnable {
+ use_name_in_title: true,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 210..217,
+ name: "foo0",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "foo_tests::foo0",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: true,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 210..217,
+ name: "foo1",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "foo_tests::foo1",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: true,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 210..217,
+ name: "foo2",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "foo_tests::foo2",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: true,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 210..217,
+ name: "foo_tests",
+ kind: Module,
+ description: "mod foo_tests",
+ },
+ kind: TestMod {
+ path: "foo_tests",
+ },
+ cfg: None,
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn dont_recurse_in_outline_submodules() {
+ check(
+ r#"
+//- /lib.rs
+$0
+mod m;
+//- /m.rs
+mod tests {
+ #[test]
+ fn t() {}
+}
+"#,
+ &[],
+ expect![[r#"
+ []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn outline_submodule1() {
+ check(
+ r#"
+//- /lib.rs
+$0
+mod m;
+//- /m.rs
+#[test]
+fn t0() {}
+#[test]
+fn t1() {}
+"#,
+ &[TestMod],
+ expect![[r#"
+ [
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 1..7,
+ focus_range: 5..6,
+ name: "m",
+ kind: Module,
+ description: "mod m",
+ },
+ kind: TestMod {
+ path: "m",
+ },
+ cfg: None,
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn outline_submodule2() {
+ check(
+ r#"
+//- /lib.rs
+mod m;
+//- /m.rs
+$0
+#[test]
+fn t0() {}
+#[test]
+fn t1() {}
+"#,
+ &[TestMod, Test, Test],
+ expect![[r#"
+ [
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 1,
+ ),
+ full_range: 0..39,
+ name: "m",
+ kind: Module,
+ },
+ kind: TestMod {
+ path: "m",
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 1,
+ ),
+ full_range: 1..19,
+ focus_range: 12..14,
+ name: "t0",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "m::t0",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 1,
+ ),
+ full_range: 20..38,
+ focus_range: 31..33,
+ name: "t1",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "m::t1",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn attributed_module() {
+ check(
+ r#"
+//- proc_macros: identity
+//- /lib.rs
+$0
+#[proc_macros::identity]
+mod module {
+ #[test]
+ fn t0() {}
+ #[test]
+ fn t1() {}
+}
+"#,
+ &[TestMod, Test, Test],
+ expect![[r#"
+ [
+ Runnable {
+ use_name_in_title: true,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 26..94,
+ focus_range: 30..36,
+ name: "module",
+ kind: Module,
+ description: "mod module",
+ },
+ kind: TestMod {
+ path: "module",
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: true,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 43..65,
+ focus_range: 58..60,
+ name: "t0",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "module::t0",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: true,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 70..92,
+ focus_range: 85..87,
+ name: "t1",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "module::t1",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn find_no_tests() {
+ check_tests(
+ r#"
+//- /lib.rs
+fn foo$0() { };
+"#,
+ expect![[r#"
+ []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn find_direct_fn_test() {
+ check_tests(
+ r#"
+//- /lib.rs
+fn foo$0() { };
+
+mod tests {
+ #[test]
+ fn foo_test() {
+ super::foo()
+ }
+}
+"#,
+ expect![[r#"
+ [
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 31..85,
+ focus_range: 46..54,
+ name: "foo_test",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "tests::foo_test",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn find_direct_struct_test() {
+ check_tests(
+ r#"
+//- /lib.rs
+struct Fo$0o;
+fn foo(arg: &Foo) { };
+
+mod tests {
+ use super::*;
+
+ #[test]
+ fn foo_test() {
+ foo(Foo);
+ }
+}
+"#,
+ expect![[r#"
+ [
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 71..122,
+ focus_range: 86..94,
+ name: "foo_test",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "tests::foo_test",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn find_indirect_fn_test() {
+ check_tests(
+ r#"
+//- /lib.rs
+fn foo$0() { };
+
+mod tests {
+ use super::foo;
+
+ fn check1() {
+ check2()
+ }
+
+ fn check2() {
+ foo()
+ }
+
+ #[test]
+ fn foo_test() {
+ check1()
+ }
+}
+"#,
+ expect![[r#"
+ [
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 133..183,
+ focus_range: 148..156,
+ name: "foo_test",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "tests::foo_test",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn tests_are_unique() {
+ check_tests(
+ r#"
+//- /lib.rs
+fn foo$0() { };
+
+mod tests {
+ use super::foo;
+
+ #[test]
+ fn foo_test() {
+ foo();
+ foo();
+ }
+
+ #[test]
+ fn foo2_test() {
+ foo();
+ foo();
+ }
+
+}
+"#,
+ expect![[r#"
+ [
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 52..115,
+ focus_range: 67..75,
+ name: "foo_test",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "tests::foo_test",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 121..185,
+ focus_range: 136..145,
+ name: "foo2_test",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "tests::foo2_test",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn doc_test_type_params() {
+ check(
+ r#"
+//- /lib.rs
+$0
+struct Foo<T, U>;
+
+impl<T, U> Foo<T, U> {
+ /// ```rust
+ /// ````
+ fn t() {}
+}
+"#,
+ &[DocTest],
+ expect![[r#"
+ [
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 47..85,
+ name: "t",
+ },
+ kind: DocTest {
+ test_id: Path(
+ "Foo<T, U>::t",
+ ),
+ },
+ cfg: None,
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn doc_test_macro_export_mbe() {
+ check(
+ r#"
+//- /lib.rs
+$0
+mod foo;
+
+//- /foo.rs
+/// ```
+/// fn foo() {
+/// }
+/// ```
+#[macro_export]
+macro_rules! foo {
+ () => {
+
+ };
+}
+"#,
+ &[],
+ expect![[r#"
+ []
+ "#]],
+ );
+ check(
+ r#"
+//- /lib.rs
+$0
+/// ```
+/// fn foo() {
+/// }
+/// ```
+#[macro_export]
+macro_rules! foo {
+ () => {
+
+ };
+}
+"#,
+ &[DocTest],
+ expect![[r#"
+ [
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 1..94,
+ name: "foo",
+ },
+ kind: DocTest {
+ test_id: Path(
+ "foo",
+ ),
+ },
+ cfg: None,
+ },
+ ]
+ "#]],
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/shuffle_crate_graph.rs b/src/tools/rust-analyzer/crates/ide/src/shuffle_crate_graph.rs
new file mode 100644
index 000000000..15cb89dcc
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/shuffle_crate_graph.rs
@@ -0,0 +1,71 @@
+use std::sync::Arc;
+
+use ide_db::{
+ base_db::{salsa::Durability, CrateGraph, SourceDatabase},
+ FxHashMap, RootDatabase,
+};
+
+// Feature: Shuffle Crate Graph
+//
+// Randomizes all crate IDs in the crate graph, for debugging.
+//
+// |===
+// | Editor | Action Name
+//
+// | VS Code | **Rust Analyzer: Shuffle Crate Graph**
+// |===
+pub(crate) fn shuffle_crate_graph(db: &mut RootDatabase) {
+ let crate_graph = db.crate_graph();
+
+ let mut shuffled_ids = crate_graph.iter().collect::<Vec<_>>();
+ shuffle(&mut shuffled_ids);
+
+ let mut new_graph = CrateGraph::default();
+
+ let mut map = FxHashMap::default();
+ for old_id in shuffled_ids.iter().copied() {
+ let data = &crate_graph[old_id];
+ let new_id = new_graph.add_crate_root(
+ data.root_file_id,
+ data.edition,
+ data.display_name.clone(),
+ data.version.clone(),
+ data.cfg_options.clone(),
+ data.potential_cfg_options.clone(),
+ data.env.clone(),
+ data.proc_macro.clone(),
+ data.is_proc_macro,
+ data.origin.clone(),
+ );
+ map.insert(old_id, new_id);
+ }
+
+ for old_id in shuffled_ids.iter().copied() {
+ let data = &crate_graph[old_id];
+ for dep in &data.dependencies {
+ let mut new_dep = dep.clone();
+ new_dep.crate_id = map[&dep.crate_id];
+ new_graph.add_dep(map[&old_id], new_dep).unwrap();
+ }
+ }
+
+ db.set_crate_graph_with_durability(Arc::new(new_graph), Durability::HIGH);
+}
+
+fn shuffle<T>(slice: &mut [T]) {
+ let mut rng = oorandom::Rand32::new(seed());
+
+ let mut remaining = slice.len() - 1;
+ while remaining > 0 {
+ let index = rng.rand_range(0..remaining as u32);
+ slice.swap(remaining, index as usize);
+ remaining -= 1;
+ }
+}
+
+fn seed() -> u64 {
+ use std::collections::hash_map::RandomState;
+ use std::hash::{BuildHasher, Hasher};
+
+ RandomState::new().build_hasher().finish()
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/signature_help.rs b/src/tools/rust-analyzer/crates/ide/src/signature_help.rs
new file mode 100644
index 000000000..fedc1a435
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/signature_help.rs
@@ -0,0 +1,1334 @@
+//! This module provides primitives for showing type and function parameter information when editing
+//! a call or use-site.
+
+use std::collections::BTreeSet;
+
+use either::Either;
+use hir::{AssocItem, GenericParam, HasAttrs, HirDisplay, Semantics, Trait};
+use ide_db::{active_parameter::callable_for_node, base_db::FilePosition};
+use stdx::format_to;
+use syntax::{
+ algo,
+ ast::{self, HasArgList},
+ match_ast, AstNode, Direction, SyntaxToken, TextRange, TextSize,
+};
+
+use crate::RootDatabase;
+
+/// Contains information about an item signature as seen from a use site.
+///
+/// This includes the "active parameter", which is the parameter whose value is currently being
+/// edited.
+#[derive(Debug)]
+pub struct SignatureHelp {
+ pub doc: Option<String>,
+ pub signature: String,
+ pub active_parameter: Option<usize>,
+ parameters: Vec<TextRange>,
+}
+
+impl SignatureHelp {
+ pub fn parameter_labels(&self) -> impl Iterator<Item = &str> + '_ {
+ self.parameters.iter().map(move |&it| &self.signature[it])
+ }
+
+ pub fn parameter_ranges(&self) -> &[TextRange] {
+ &self.parameters
+ }
+
+ fn push_call_param(&mut self, param: &str) {
+ self.push_param('(', param);
+ }
+
+ fn push_generic_param(&mut self, param: &str) {
+ self.push_param('<', param);
+ }
+
+ fn push_param(&mut self, opening_delim: char, param: &str) {
+ if !self.signature.ends_with(opening_delim) {
+ self.signature.push_str(", ");
+ }
+ let start = TextSize::of(&self.signature);
+ self.signature.push_str(param);
+ let end = TextSize::of(&self.signature);
+ self.parameters.push(TextRange::new(start, end))
+ }
+}
+
+/// Computes parameter information for the given position.
+pub(crate) fn signature_help(db: &RootDatabase, position: FilePosition) -> Option<SignatureHelp> {
+ let sema = Semantics::new(db);
+ let file = sema.parse(position.file_id);
+ let file = file.syntax();
+ let token = file
+ .token_at_offset(position.offset)
+ .left_biased()
+ // if the cursor is sandwiched between two space tokens and the call is unclosed
+ // this prevents us from leaving the CallExpression
+ .and_then(|tok| algo::skip_trivia_token(tok, Direction::Prev))?;
+ let token = sema.descend_into_macros_single(token);
+
+ for node in token.parent_ancestors() {
+ match_ast! {
+ match node {
+ ast::ArgList(arg_list) => {
+ let cursor_outside = arg_list.r_paren_token().as_ref() == Some(&token);
+ if cursor_outside {
+ return None;
+ }
+ return signature_help_for_call(&sema, token);
+ },
+ ast::GenericArgList(garg_list) => {
+ let cursor_outside = garg_list.r_angle_token().as_ref() == Some(&token);
+ if cursor_outside {
+ return None;
+ }
+ return signature_help_for_generics(&sema, token);
+ },
+ _ => (),
+ }
+ }
+ }
+
+ None
+}
+
+fn signature_help_for_call(
+ sema: &Semantics<'_, RootDatabase>,
+ token: SyntaxToken,
+) -> Option<SignatureHelp> {
+ // Find the calling expression and its NameRef
+ let mut node = token.parent()?;
+ let calling_node = loop {
+ if let Some(callable) = ast::CallableExpr::cast(node.clone()) {
+ if callable
+ .arg_list()
+ .map_or(false, |it| it.syntax().text_range().contains(token.text_range().start()))
+ {
+ break callable;
+ }
+ }
+
+ // Stop at multi-line expressions, since the signature of the outer call is not very
+ // helpful inside them.
+ if let Some(expr) = ast::Expr::cast(node.clone()) {
+ if expr.syntax().text().contains_char('\n') {
+ return None;
+ }
+ }
+
+ node = node.parent()?;
+ };
+
+ let (callable, active_parameter) = callable_for_node(sema, &calling_node, &token)?;
+
+ let mut res =
+ SignatureHelp { doc: None, signature: String::new(), parameters: vec![], active_parameter };
+
+ let db = sema.db;
+ let mut fn_params = None;
+ match callable.kind() {
+ hir::CallableKind::Function(func) => {
+ res.doc = func.docs(db).map(|it| it.into());
+ format_to!(res.signature, "fn {}", func.name(db));
+ fn_params = Some(match callable.receiver_param(db) {
+ Some(_self) => func.params_without_self(db),
+ None => func.assoc_fn_params(db),
+ });
+ }
+ hir::CallableKind::TupleStruct(strukt) => {
+ res.doc = strukt.docs(db).map(|it| it.into());
+ format_to!(res.signature, "struct {}", strukt.name(db));
+ }
+ hir::CallableKind::TupleEnumVariant(variant) => {
+ res.doc = variant.docs(db).map(|it| it.into());
+ format_to!(
+ res.signature,
+ "enum {}::{}",
+ variant.parent_enum(db).name(db),
+ variant.name(db)
+ );
+ }
+ hir::CallableKind::Closure | hir::CallableKind::FnPtr => (),
+ }
+
+ res.signature.push('(');
+ {
+ if let Some(self_param) = callable.receiver_param(db) {
+ format_to!(res.signature, "{}", self_param)
+ }
+ let mut buf = String::new();
+ for (idx, (pat, ty)) in callable.params(db).into_iter().enumerate() {
+ buf.clear();
+ if let Some(pat) = pat {
+ match pat {
+ Either::Left(_self) => format_to!(buf, "self: "),
+ Either::Right(pat) => format_to!(buf, "{}: ", pat),
+ }
+ }
+ // APITs (argument position `impl Trait`s) are inferred as {unknown} as the user is
+ // in the middle of entering call arguments.
+ // In that case, fall back to render definitions of the respective parameters.
+ // This is overly conservative: we do not substitute known type vars
+ // (see FIXME in tests::impl_trait) and falling back on any unknowns.
+ match (ty.contains_unknown(), fn_params.as_deref()) {
+ (true, Some(fn_params)) => format_to!(buf, "{}", fn_params[idx].ty().display(db)),
+ _ => format_to!(buf, "{}", ty.display(db)),
+ }
+ res.push_call_param(&buf);
+ }
+ }
+ res.signature.push(')');
+
+ let mut render = |ret_type: hir::Type| {
+ if !ret_type.is_unit() {
+ format_to!(res.signature, " -> {}", ret_type.display(db));
+ }
+ };
+ match callable.kind() {
+ hir::CallableKind::Function(func) if callable.return_type().contains_unknown() => {
+ render(func.ret_type(db))
+ }
+ hir::CallableKind::Function(_) | hir::CallableKind::Closure | hir::CallableKind::FnPtr => {
+ render(callable.return_type())
+ }
+ hir::CallableKind::TupleStruct(_) | hir::CallableKind::TupleEnumVariant(_) => {}
+ }
+ Some(res)
+}
+
+fn signature_help_for_generics(
+ sema: &Semantics<'_, RootDatabase>,
+ token: SyntaxToken,
+) -> Option<SignatureHelp> {
+ let parent = token.parent()?;
+ let arg_list = parent
+ .ancestors()
+ .filter_map(ast::GenericArgList::cast)
+ .find(|list| list.syntax().text_range().contains(token.text_range().start()))?;
+
+ let mut active_parameter = arg_list
+ .generic_args()
+ .take_while(|arg| arg.syntax().text_range().end() <= token.text_range().start())
+ .count();
+
+ let first_arg_is_non_lifetime = arg_list
+ .generic_args()
+ .next()
+ .map_or(false, |arg| !matches!(arg, ast::GenericArg::LifetimeArg(_)));
+
+ let mut generics_def = if let Some(path) =
+ arg_list.syntax().ancestors().find_map(ast::Path::cast)
+ {
+ let res = sema.resolve_path(&path)?;
+ let generic_def: hir::GenericDef = match res {
+ hir::PathResolution::Def(hir::ModuleDef::Adt(it)) => it.into(),
+ hir::PathResolution::Def(hir::ModuleDef::Function(it)) => it.into(),
+ hir::PathResolution::Def(hir::ModuleDef::Trait(it)) => it.into(),
+ hir::PathResolution::Def(hir::ModuleDef::TypeAlias(it)) => it.into(),
+ hir::PathResolution::Def(hir::ModuleDef::Variant(it)) => it.into(),
+ hir::PathResolution::Def(hir::ModuleDef::BuiltinType(_))
+ | hir::PathResolution::Def(hir::ModuleDef::Const(_))
+ | hir::PathResolution::Def(hir::ModuleDef::Macro(_))
+ | hir::PathResolution::Def(hir::ModuleDef::Module(_))
+ | hir::PathResolution::Def(hir::ModuleDef::Static(_)) => return None,
+ hir::PathResolution::BuiltinAttr(_)
+ | hir::PathResolution::ToolModule(_)
+ | hir::PathResolution::Local(_)
+ | hir::PathResolution::TypeParam(_)
+ | hir::PathResolution::ConstParam(_)
+ | hir::PathResolution::SelfType(_)
+ | hir::PathResolution::DeriveHelper(_) => return None,
+ };
+
+ generic_def
+ } else if let Some(method_call) = arg_list.syntax().parent().and_then(ast::MethodCallExpr::cast)
+ {
+ // recv.method::<$0>()
+ let method = sema.resolve_method_call(&method_call)?;
+ method.into()
+ } else {
+ return None;
+ };
+
+ let mut res = SignatureHelp {
+ doc: None,
+ signature: String::new(),
+ parameters: vec![],
+ active_parameter: None,
+ };
+
+ let db = sema.db;
+ match generics_def {
+ hir::GenericDef::Function(it) => {
+ res.doc = it.docs(db).map(|it| it.into());
+ format_to!(res.signature, "fn {}", it.name(db));
+ }
+ hir::GenericDef::Adt(hir::Adt::Enum(it)) => {
+ res.doc = it.docs(db).map(|it| it.into());
+ format_to!(res.signature, "enum {}", it.name(db));
+ }
+ hir::GenericDef::Adt(hir::Adt::Struct(it)) => {
+ res.doc = it.docs(db).map(|it| it.into());
+ format_to!(res.signature, "struct {}", it.name(db));
+ }
+ hir::GenericDef::Adt(hir::Adt::Union(it)) => {
+ res.doc = it.docs(db).map(|it| it.into());
+ format_to!(res.signature, "union {}", it.name(db));
+ }
+ hir::GenericDef::Trait(it) => {
+ res.doc = it.docs(db).map(|it| it.into());
+ format_to!(res.signature, "trait {}", it.name(db));
+ }
+ hir::GenericDef::TypeAlias(it) => {
+ res.doc = it.docs(db).map(|it| it.into());
+ format_to!(res.signature, "type {}", it.name(db));
+ }
+ hir::GenericDef::Variant(it) => {
+ // In paths, generics of an enum can be specified *after* one of its variants.
+ // eg. `None::<u8>`
+ // We'll use the signature of the enum, but include the docs of the variant.
+ res.doc = it.docs(db).map(|it| it.into());
+ let it = it.parent_enum(db);
+ format_to!(res.signature, "enum {}", it.name(db));
+ generics_def = it.into();
+ }
+ // These don't have generic args that can be specified
+ hir::GenericDef::Impl(_) | hir::GenericDef::Const(_) => return None,
+ }
+
+ let params = generics_def.params(sema.db);
+ let num_lifetime_params =
+ params.iter().take_while(|param| matches!(param, GenericParam::LifetimeParam(_))).count();
+ if first_arg_is_non_lifetime {
+ // Lifetime parameters were omitted.
+ active_parameter += num_lifetime_params;
+ }
+ res.active_parameter = Some(active_parameter);
+
+ res.signature.push('<');
+ let mut buf = String::new();
+ for param in params {
+ if let hir::GenericParam::TypeParam(ty) = param {
+ if ty.is_implicit(db) {
+ continue;
+ }
+ }
+
+ buf.clear();
+ format_to!(buf, "{}", param.display(db));
+ res.push_generic_param(&buf);
+ }
+ if let hir::GenericDef::Trait(tr) = generics_def {
+ add_assoc_type_bindings(db, &mut res, tr, arg_list);
+ }
+ res.signature.push('>');
+
+ Some(res)
+}
+
+fn add_assoc_type_bindings(
+ db: &RootDatabase,
+ res: &mut SignatureHelp,
+ tr: Trait,
+ args: ast::GenericArgList,
+) {
+ if args.syntax().ancestors().find_map(ast::TypeBound::cast).is_none() {
+ // Assoc type bindings are only valid in type bound position.
+ return;
+ }
+
+ let present_bindings = args
+ .generic_args()
+ .filter_map(|arg| match arg {
+ ast::GenericArg::AssocTypeArg(arg) => arg.name_ref().map(|n| n.to_string()),
+ _ => None,
+ })
+ .collect::<BTreeSet<_>>();
+
+ let mut buf = String::new();
+ for binding in &present_bindings {
+ buf.clear();
+ format_to!(buf, "{} = …", binding);
+ res.push_generic_param(&buf);
+ }
+
+ for item in tr.items_with_supertraits(db) {
+ if let AssocItem::TypeAlias(ty) = item {
+ let name = ty.name(db).to_smol_str();
+ if !present_bindings.contains(&*name) {
+ buf.clear();
+ format_to!(buf, "{} = …", name);
+ res.push_generic_param(&buf);
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::iter;
+
+ use expect_test::{expect, Expect};
+ use ide_db::base_db::{fixture::ChangeFixture, FilePosition};
+ use stdx::format_to;
+
+ use crate::RootDatabase;
+
+ /// Creates analysis from a multi-file fixture, returns positions marked with $0.
+ pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
+ let change_fixture = ChangeFixture::parse(ra_fixture);
+ let mut database = RootDatabase::default();
+ database.apply_change(change_fixture.change);
+ let (file_id, range_or_offset) =
+ change_fixture.file_position.expect("expected a marker ($0)");
+ let offset = range_or_offset.expect_offset();
+ (database, FilePosition { file_id, offset })
+ }
+
+ fn check(ra_fixture: &str, expect: Expect) {
+ // Implicitly add `Sized` to avoid noisy `T: ?Sized` in the results.
+ let fixture = format!(
+ r#"
+#[lang = "sized"] trait Sized {{}}
+{ra_fixture}
+ "#
+ );
+ let (db, position) = position(&fixture);
+ let sig_help = crate::signature_help::signature_help(&db, position);
+ let actual = match sig_help {
+ Some(sig_help) => {
+ let mut rendered = String::new();
+ if let Some(docs) = &sig_help.doc {
+ format_to!(rendered, "{}\n------\n", docs.as_str());
+ }
+ format_to!(rendered, "{}\n", sig_help.signature);
+ let mut offset = 0;
+ for (i, range) in sig_help.parameter_ranges().iter().enumerate() {
+ let is_active = sig_help.active_parameter == Some(i);
+
+ let start = u32::from(range.start());
+ let gap = start.checked_sub(offset).unwrap_or_else(|| {
+ panic!("parameter ranges out of order: {:?}", sig_help.parameter_ranges())
+ });
+ rendered.extend(iter::repeat(' ').take(gap as usize));
+ let param_text = &sig_help.signature[*range];
+ let width = param_text.chars().count(); // …
+ let marker = if is_active { '^' } else { '-' };
+ rendered.extend(iter::repeat(marker).take(width));
+ offset += gap + u32::from(range.len());
+ }
+ if !sig_help.parameter_ranges().is_empty() {
+ format_to!(rendered, "\n");
+ }
+ rendered
+ }
+ None => String::new(),
+ };
+ expect.assert_eq(&actual);
+ }
+
+ #[test]
+ fn test_fn_signature_two_args() {
+ check(
+ r#"
+fn foo(x: u32, y: u32) -> u32 {x + y}
+fn bar() { foo($03, ); }
+"#,
+ expect![[r#"
+ fn foo(x: u32, y: u32) -> u32
+ ^^^^^^ ------
+ "#]],
+ );
+ check(
+ r#"
+fn foo(x: u32, y: u32) -> u32 {x + y}
+fn bar() { foo(3$0, ); }
+"#,
+ expect![[r#"
+ fn foo(x: u32, y: u32) -> u32
+ ^^^^^^ ------
+ "#]],
+ );
+ check(
+ r#"
+fn foo(x: u32, y: u32) -> u32 {x + y}
+fn bar() { foo(3,$0 ); }
+"#,
+ expect![[r#"
+ fn foo(x: u32, y: u32) -> u32
+ ------ ^^^^^^
+ "#]],
+ );
+ check(
+ r#"
+fn foo(x: u32, y: u32) -> u32 {x + y}
+fn bar() { foo(3, $0); }
+"#,
+ expect![[r#"
+ fn foo(x: u32, y: u32) -> u32
+ ------ ^^^^^^
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_fn_signature_two_args_empty() {
+ check(
+ r#"
+fn foo(x: u32, y: u32) -> u32 {x + y}
+fn bar() { foo($0); }
+"#,
+ expect![[r#"
+ fn foo(x: u32, y: u32) -> u32
+ ^^^^^^ ------
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_fn_signature_two_args_first_generics() {
+ check(
+ r#"
+fn foo<T, U: Copy + Display>(x: T, y: U) -> u32
+ where T: Copy + Display, U: Debug
+{ x + y }
+
+fn bar() { foo($03, ); }
+"#,
+ expect![[r#"
+ fn foo(x: i32, y: U) -> u32
+ ^^^^^^ ----
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_fn_signature_no_params() {
+ check(
+ r#"
+fn foo<T>() -> T where T: Copy + Display {}
+fn bar() { foo($0); }
+"#,
+ expect![[r#"
+ fn foo() -> T
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_fn_signature_for_impl() {
+ check(
+ r#"
+struct F;
+impl F { pub fn new() { } }
+fn bar() {
+ let _ : F = F::new($0);
+}
+"#,
+ expect![[r#"
+ fn new()
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_fn_signature_for_method_self() {
+ check(
+ r#"
+struct S;
+impl S { pub fn do_it(&self) {} }
+
+fn bar() {
+ let s: S = S;
+ s.do_it($0);
+}
+"#,
+ expect![[r#"
+ fn do_it(&self)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_fn_signature_for_method_with_arg() {
+ check(
+ r#"
+struct S;
+impl S {
+ fn foo(&self, x: i32) {}
+}
+
+fn main() { S.foo($0); }
+"#,
+ expect![[r#"
+ fn foo(&self, x: i32)
+ ^^^^^^
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_fn_signature_for_generic_method() {
+ check(
+ r#"
+struct S<T>(T);
+impl<T> S<T> {
+ fn foo(&self, x: T) {}
+}
+
+fn main() { S(1u32).foo($0); }
+"#,
+ expect![[r#"
+ fn foo(&self, x: u32)
+ ^^^^^^
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_fn_signature_for_method_with_arg_as_assoc_fn() {
+ check(
+ r#"
+struct S;
+impl S {
+ fn foo(&self, x: i32) {}
+}
+
+fn main() { S::foo($0); }
+"#,
+ expect![[r#"
+ fn foo(self: &S, x: i32)
+ ^^^^^^^^ ------
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_fn_signature_with_docs_simple() {
+ check(
+ r#"
+/// test
+// non-doc-comment
+fn foo(j: u32) -> u32 {
+ j
+}
+
+fn bar() {
+ let _ = foo($0);
+}
+"#,
+ expect![[r#"
+ test
+ ------
+ fn foo(j: u32) -> u32
+ ^^^^^^
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_fn_signature_with_docs() {
+ check(
+ r#"
+/// Adds one to the number given.
+///
+/// # Examples
+///
+/// ```
+/// let five = 5;
+///
+/// assert_eq!(6, my_crate::add_one(5));
+/// ```
+pub fn add_one(x: i32) -> i32 {
+ x + 1
+}
+
+pub fn do() {
+ add_one($0
+}"#,
+ expect![[r##"
+ Adds one to the number given.
+
+ # Examples
+
+ ```
+ let five = 5;
+
+ assert_eq!(6, my_crate::add_one(5));
+ ```
+ ------
+ fn add_one(x: i32) -> i32
+ ^^^^^^
+ "##]],
+ );
+ }
+
+ #[test]
+ fn test_fn_signature_with_docs_impl() {
+ check(
+ r#"
+struct addr;
+impl addr {
+ /// Adds one to the number given.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// let five = 5;
+ ///
+ /// assert_eq!(6, my_crate::add_one(5));
+ /// ```
+ pub fn add_one(x: i32) -> i32 {
+ x + 1
+ }
+}
+
+pub fn do_it() {
+ addr {};
+ addr::add_one($0);
+}
+"#,
+ expect![[r##"
+ Adds one to the number given.
+
+ # Examples
+
+ ```
+ let five = 5;
+
+ assert_eq!(6, my_crate::add_one(5));
+ ```
+ ------
+ fn add_one(x: i32) -> i32
+ ^^^^^^
+ "##]],
+ );
+ }
+
+ #[test]
+ fn test_fn_signature_with_docs_from_actix() {
+ check(
+ r#"
+trait Actor {
+ /// Actor execution context type
+ type Context;
+}
+trait WriteHandler<E>
+where
+ Self: Actor
+{
+ /// Method is called when writer finishes.
+ ///
+ /// By default this method stops actor's `Context`.
+ fn finished(&mut self, ctx: &mut Self::Context) {}
+}
+
+fn foo(mut r: impl WriteHandler<()>) {
+ r.finished($0);
+}
+"#,
+ expect![[r#"
+ Method is called when writer finishes.
+
+ By default this method stops actor's `Context`.
+ ------
+ fn finished(&mut self, ctx: &mut <impl WriteHandler<()> as Actor>::Context)
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ "#]],
+ );
+ }
+
+ #[test]
+ fn call_info_bad_offset() {
+ check(
+ r#"
+fn foo(x: u32, y: u32) -> u32 {x + y}
+fn bar() { foo $0 (3, ); }
+"#,
+ expect![[""]],
+ );
+ }
+
+ #[test]
+ fn outside_of_arg_list() {
+ check(
+ r#"
+fn foo(a: u8) {}
+fn f() {
+ foo(123)$0
+}
+"#,
+ expect![[]],
+ );
+ check(
+ r#"
+fn foo<T>(a: u8) {}
+fn f() {
+ foo::<u32>$0()
+}
+"#,
+ expect![[]],
+ );
+ }
+
+ #[test]
+ fn test_nested_method_in_lambda() {
+ check(
+ r#"
+struct Foo;
+impl Foo { fn bar(&self, _: u32) { } }
+
+fn bar(_: u32) { }
+
+fn main() {
+ let foo = Foo;
+ std::thread::spawn(move || foo.bar($0));
+}
+"#,
+ expect![[r#"
+ fn bar(&self, _: u32)
+ ^^^^^^
+ "#]],
+ );
+ }
+
+ #[test]
+ fn works_for_tuple_structs() {
+ check(
+ r#"
+/// A cool tuple struct
+struct S(u32, i32);
+fn main() {
+ let s = S(0, $0);
+}
+"#,
+ expect![[r#"
+ A cool tuple struct
+ ------
+ struct S(u32, i32)
+ --- ^^^
+ "#]],
+ );
+ }
+
+ #[test]
+ fn generic_struct() {
+ check(
+ r#"
+struct S<T>(T);
+fn main() {
+ let s = S($0);
+}
+"#,
+ expect![[r#"
+ struct S({unknown})
+ ^^^^^^^^^
+ "#]],
+ );
+ }
+
+ #[test]
+ fn works_for_enum_variants() {
+ check(
+ r#"
+enum E {
+ /// A Variant
+ A(i32),
+ /// Another
+ B,
+ /// And C
+ C { a: i32, b: i32 }
+}
+
+fn main() {
+ let a = E::A($0);
+}
+"#,
+ expect![[r#"
+ A Variant
+ ------
+ enum E::A(i32)
+ ^^^
+ "#]],
+ );
+ }
+
+ #[test]
+ fn cant_call_struct_record() {
+ check(
+ r#"
+struct S { x: u32, y: i32 }
+fn main() {
+ let s = S($0);
+}
+"#,
+ expect![[""]],
+ );
+ }
+
+ #[test]
+ fn cant_call_enum_record() {
+ check(
+ r#"
+enum E {
+ /// A Variant
+ A(i32),
+ /// Another
+ B,
+ /// And C
+ C { a: i32, b: i32 }
+}
+
+fn main() {
+ let a = E::C($0);
+}
+"#,
+ expect![[""]],
+ );
+ }
+
+ #[test]
+ fn fn_signature_for_call_in_macro() {
+ check(
+ r#"
+macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
+fn foo() { }
+id! {
+ fn bar() { foo($0); }
+}
+"#,
+ expect![[r#"
+ fn foo()
+ "#]],
+ );
+ }
+
+ #[test]
+ fn call_info_for_lambdas() {
+ check(
+ r#"
+struct S;
+fn foo(s: S) -> i32 { 92 }
+fn main() {
+ (|s| foo(s))($0)
+}
+ "#,
+ expect![[r#"
+ (s: S) -> i32
+ ^^^^
+ "#]],
+ )
+ }
+
+ #[test]
+ fn call_info_for_fn_ptr() {
+ check(
+ r#"
+fn main(f: fn(i32, f64) -> char) {
+ f(0, $0)
+}
+ "#,
+ expect![[r#"
+ (i32, f64) -> char
+ --- ^^^
+ "#]],
+ )
+ }
+
+ #[test]
+ fn call_info_for_unclosed_call() {
+ check(
+ r#"
+fn foo(foo: u32, bar: u32) {}
+fn main() {
+ foo($0
+}"#,
+ expect![[r#"
+ fn foo(foo: u32, bar: u32)
+ ^^^^^^^^ --------
+ "#]],
+ );
+ // check with surrounding space
+ check(
+ r#"
+fn foo(foo: u32, bar: u32) {}
+fn main() {
+ foo( $0
+}"#,
+ expect![[r#"
+ fn foo(foo: u32, bar: u32)
+ ^^^^^^^^ --------
+ "#]],
+ )
+ }
+
+ #[test]
+ fn test_multiline_argument() {
+ check(
+ r#"
+fn callee(a: u8, b: u8) {}
+fn main() {
+ callee(match 0 {
+ 0 => 1,$0
+ })
+}"#,
+ expect![[r#""#]],
+ );
+ check(
+ r#"
+fn callee(a: u8, b: u8) {}
+fn main() {
+ callee(match 0 {
+ 0 => 1,
+ },$0)
+}"#,
+ expect![[r#"
+ fn callee(a: u8, b: u8)
+ ----- ^^^^^
+ "#]],
+ );
+ check(
+ r#"
+fn callee(a: u8, b: u8) {}
+fn main() {
+ callee($0match 0 {
+ 0 => 1,
+ })
+}"#,
+ expect![[r#"
+ fn callee(a: u8, b: u8)
+ ^^^^^ -----
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_generics_simple() {
+ check(
+ r#"
+/// Option docs.
+enum Option<T> {
+ Some(T),
+ None,
+}
+
+fn f() {
+ let opt: Option<$0
+}
+ "#,
+ expect![[r#"
+ Option docs.
+ ------
+ enum Option<T>
+ ^
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_generics_on_variant() {
+ check(
+ r#"
+/// Option docs.
+enum Option<T> {
+ /// Some docs.
+ Some(T),
+ /// None docs.
+ None,
+}
+
+use Option::*;
+
+fn f() {
+ None::<$0
+}
+ "#,
+ expect![[r#"
+ None docs.
+ ------
+ enum Option<T>
+ ^
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_lots_of_generics() {
+ check(
+ r#"
+trait Tr<T> {}
+
+struct S<T>(T);
+
+impl<T> S<T> {
+ fn f<G, H>(g: G, h: impl Tr<G>) where G: Tr<()> {}
+}
+
+fn f() {
+ S::<u8>::f::<(), $0
+}
+ "#,
+ expect![[r#"
+ fn f<G: Tr<()>, H>
+ --------- ^
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_generics_in_trait_ufcs() {
+ check(
+ r#"
+trait Tr {
+ fn f<T: Tr, U>() {}
+}
+
+struct S;
+
+impl Tr for S {}
+
+fn f() {
+ <S as Tr>::f::<$0
+}
+ "#,
+ expect![[r#"
+ fn f<T: Tr, U>
+ ^^^^^ -
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_generics_in_method_call() {
+ check(
+ r#"
+struct S;
+
+impl S {
+ fn f<T>(&self) {}
+}
+
+fn f() {
+ S.f::<$0
+}
+ "#,
+ expect![[r#"
+ fn f<T>
+ ^
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_generic_param_in_method_call() {
+ check(
+ r#"
+struct Foo;
+impl Foo {
+ fn test<V>(&mut self, val: V) {}
+}
+fn sup() {
+ Foo.test($0)
+}
+"#,
+ expect![[r#"
+ fn test(&mut self, val: V)
+ ^^^^^^
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_generic_kinds() {
+ check(
+ r#"
+fn callee<'a, const A: u8, T, const C: u8>() {}
+
+fn f() {
+ callee::<'static, $0
+}
+ "#,
+ expect![[r#"
+ fn callee<'a, const A: u8, T, const C: u8>
+ -- ^^^^^^^^^^^ - -----------
+ "#]],
+ );
+ check(
+ r#"
+fn callee<'a, const A: u8, T, const C: u8>() {}
+
+fn f() {
+ callee::<NON_LIFETIME$0
+}
+ "#,
+ expect![[r#"
+ fn callee<'a, const A: u8, T, const C: u8>
+ -- ^^^^^^^^^^^ - -----------
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_trait_assoc_types() {
+ check(
+ r#"
+trait Trait<'a, T> {
+ type Assoc;
+}
+fn f() -> impl Trait<(), $0
+ "#,
+ expect![[r#"
+ trait Trait<'a, T, Assoc = …>
+ -- - ^^^^^^^^^
+ "#]],
+ );
+ check(
+ r#"
+trait Iterator {
+ type Item;
+}
+fn f() -> impl Iterator<$0
+ "#,
+ expect![[r#"
+ trait Iterator<Item = …>
+ ^^^^^^^^
+ "#]],
+ );
+ check(
+ r#"
+trait Iterator {
+ type Item;
+}
+fn f() -> impl Iterator<Item = $0
+ "#,
+ expect![[r#"
+ trait Iterator<Item = …>
+ ^^^^^^^^
+ "#]],
+ );
+ check(
+ r#"
+trait Tr {
+ type A;
+ type B;
+}
+fn f() -> impl Tr<$0
+ "#,
+ expect![[r#"
+ trait Tr<A = …, B = …>
+ ^^^^^ -----
+ "#]],
+ );
+ check(
+ r#"
+trait Tr {
+ type A;
+ type B;
+}
+fn f() -> impl Tr<B$0
+ "#,
+ expect![[r#"
+ trait Tr<A = …, B = …>
+ ^^^^^ -----
+ "#]],
+ );
+ check(
+ r#"
+trait Tr {
+ type A;
+ type B;
+}
+fn f() -> impl Tr<B = $0
+ "#,
+ expect![[r#"
+ trait Tr<B = …, A = …>
+ ^^^^^ -----
+ "#]],
+ );
+ check(
+ r#"
+trait Tr {
+ type A;
+ type B;
+}
+fn f() -> impl Tr<B = (), $0
+ "#,
+ expect![[r#"
+ trait Tr<B = …, A = …>
+ ----- ^^^^^
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_supertrait_assoc() {
+ check(
+ r#"
+trait Super {
+ type SuperTy;
+}
+trait Sub: Super + Super {
+ type SubTy;
+}
+fn f() -> impl Sub<$0
+ "#,
+ expect![[r#"
+ trait Sub<SubTy = …, SuperTy = …>
+ ^^^^^^^^^ -----------
+ "#]],
+ );
+ }
+
+ #[test]
+ fn no_assoc_types_outside_type_bounds() {
+ check(
+ r#"
+trait Tr<T> {
+ type Assoc;
+}
+
+impl Tr<$0
+ "#,
+ expect![[r#"
+ trait Tr<T>
+ ^
+ "#]],
+ );
+ }
+
+ #[test]
+ fn impl_trait() {
+ // FIXME: Substitute type vars in impl trait (`U` -> `i8`)
+ check(
+ r#"
+trait Trait<T> {}
+struct Wrap<T>(T);
+fn foo<U>(x: Wrap<impl Trait<U>>) {}
+fn f() {
+ foo::<i8>($0)
+}
+"#,
+ expect![[r#"
+ fn foo(x: Wrap<impl Trait<U>>)
+ ^^^^^^^^^^^^^^^^^^^^^^
+ "#]],
+ );
+ }
+
+ #[test]
+ fn fully_qualified_syntax() {
+ check(
+ r#"
+fn f() {
+ trait A { fn foo(&self, other: Self); }
+ A::foo(&self$0, other);
+}
+"#,
+ expect![[r#"
+ fn foo(self: &Self, other: Self)
+ ^^^^^^^^^^^ -----------
+ "#]],
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/ssr.rs b/src/tools/rust-analyzer/crates/ide/src/ssr.rs
new file mode 100644
index 000000000..497eb1cc1
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/ssr.rs
@@ -0,0 +1,255 @@
+//! This module provides an SSR assist. It is not desirable to include this
+//! assist in ide_assists because that would require the ide_assists crate
+//! depend on the ide_ssr crate.
+
+use ide_assists::{Assist, AssistId, AssistKind, AssistResolveStrategy, GroupLabel};
+use ide_db::{base_db::FileRange, label::Label, source_change::SourceChange, RootDatabase};
+
+pub(crate) fn ssr_assists(
+ db: &RootDatabase,
+ resolve: &AssistResolveStrategy,
+ frange: FileRange,
+) -> Vec<Assist> {
+ let mut ssr_assists = Vec::with_capacity(2);
+
+ let (match_finder, comment_range) = match ide_ssr::ssr_from_comment(db, frange) {
+ Some(ssr_data) => ssr_data,
+ None => return ssr_assists,
+ };
+ let id = AssistId("ssr", AssistKind::RefactorRewrite);
+
+ let (source_change_for_file, source_change_for_workspace) = if resolve.should_resolve(&id) {
+ let edits = match_finder.edits();
+
+ let source_change_for_file = {
+ let text_edit_for_file = edits.get(&frange.file_id).cloned().unwrap_or_default();
+ SourceChange::from_text_edit(frange.file_id, text_edit_for_file)
+ };
+
+ let source_change_for_workspace = SourceChange::from(match_finder.edits());
+
+ (Some(source_change_for_file), Some(source_change_for_workspace))
+ } else {
+ (None, None)
+ };
+
+ let assists = vec![
+ ("Apply SSR in file", source_change_for_file),
+ ("Apply SSR in workspace", source_change_for_workspace),
+ ];
+
+ for (label, source_change) in assists.into_iter() {
+ let assist = Assist {
+ id,
+ label: Label::new(label.to_string()),
+ group: Some(GroupLabel("Apply SSR".into())),
+ target: comment_range,
+ source_change,
+ trigger_signature_help: false,
+ };
+
+ ssr_assists.push(assist);
+ }
+
+ ssr_assists
+}
+
+#[cfg(test)]
+mod tests {
+ use std::sync::Arc;
+
+ use expect_test::expect;
+ use ide_assists::{Assist, AssistResolveStrategy};
+ use ide_db::{
+ base_db::{fixture::WithFixture, salsa::Durability, FileRange},
+ symbol_index::SymbolsDatabase,
+ FxHashSet, RootDatabase,
+ };
+
+ use super::ssr_assists;
+
+ fn get_assists(ra_fixture: &str, resolve: AssistResolveStrategy) -> Vec<Assist> {
+ let (mut db, file_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture);
+ let mut local_roots = FxHashSet::default();
+ local_roots.insert(ide_db::base_db::fixture::WORKSPACE);
+ db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH);
+ ssr_assists(&db, &resolve, FileRange { file_id, range: range_or_offset.into() })
+ }
+
+ #[test]
+ fn not_applicable_comment_not_ssr() {
+ let ra_fixture = r#"
+ //- /lib.rs
+
+ // This is foo $0
+ fn foo() {}
+ "#;
+ let assists = get_assists(ra_fixture, AssistResolveStrategy::All);
+
+ assert_eq!(0, assists.len());
+ }
+
+ #[test]
+ fn resolve_edits_true() {
+ let assists = get_assists(
+ r#"
+ //- /lib.rs
+ mod bar;
+
+ // 2 ==>> 3$0
+ fn foo() { 2 }
+
+ //- /bar.rs
+ fn bar() { 2 }
+ "#,
+ AssistResolveStrategy::All,
+ );
+
+ assert_eq!(2, assists.len());
+ let mut assists = assists.into_iter();
+
+ let apply_in_file_assist = assists.next().unwrap();
+ expect![[r#"
+ Assist {
+ id: AssistId(
+ "ssr",
+ RefactorRewrite,
+ ),
+ label: "Apply SSR in file",
+ group: Some(
+ GroupLabel(
+ "Apply SSR",
+ ),
+ ),
+ target: 10..21,
+ source_change: Some(
+ SourceChange {
+ source_file_edits: {
+ FileId(
+ 0,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "3",
+ delete: 33..34,
+ },
+ ],
+ },
+ },
+ file_system_edits: [],
+ is_snippet: false,
+ },
+ ),
+ trigger_signature_help: false,
+ }
+ "#]]
+ .assert_debug_eq(&apply_in_file_assist);
+
+ let apply_in_workspace_assist = assists.next().unwrap();
+ expect![[r#"
+ Assist {
+ id: AssistId(
+ "ssr",
+ RefactorRewrite,
+ ),
+ label: "Apply SSR in workspace",
+ group: Some(
+ GroupLabel(
+ "Apply SSR",
+ ),
+ ),
+ target: 10..21,
+ source_change: Some(
+ SourceChange {
+ source_file_edits: {
+ FileId(
+ 0,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "3",
+ delete: 33..34,
+ },
+ ],
+ },
+ FileId(
+ 1,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "3",
+ delete: 11..12,
+ },
+ ],
+ },
+ },
+ file_system_edits: [],
+ is_snippet: false,
+ },
+ ),
+ trigger_signature_help: false,
+ }
+ "#]]
+ .assert_debug_eq(&apply_in_workspace_assist);
+ }
+
+ #[test]
+ fn resolve_edits_false() {
+ let assists = get_assists(
+ r#"
+ //- /lib.rs
+ mod bar;
+
+ // 2 ==>> 3$0
+ fn foo() { 2 }
+
+ //- /bar.rs
+ fn bar() { 2 }
+ "#,
+ AssistResolveStrategy::None,
+ );
+
+ assert_eq!(2, assists.len());
+ let mut assists = assists.into_iter();
+
+ let apply_in_file_assist = assists.next().unwrap();
+ expect![[r#"
+ Assist {
+ id: AssistId(
+ "ssr",
+ RefactorRewrite,
+ ),
+ label: "Apply SSR in file",
+ group: Some(
+ GroupLabel(
+ "Apply SSR",
+ ),
+ ),
+ target: 10..21,
+ source_change: None,
+ trigger_signature_help: false,
+ }
+ "#]]
+ .assert_debug_eq(&apply_in_file_assist);
+
+ let apply_in_workspace_assist = assists.next().unwrap();
+ expect![[r#"
+ Assist {
+ id: AssistId(
+ "ssr",
+ RefactorRewrite,
+ ),
+ label: "Apply SSR in workspace",
+ group: Some(
+ GroupLabel(
+ "Apply SSR",
+ ),
+ ),
+ target: 10..21,
+ source_change: None,
+ trigger_signature_help: false,
+ }
+ "#]]
+ .assert_debug_eq(&apply_in_workspace_assist);
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/static_index.rs b/src/tools/rust-analyzer/crates/ide/src/static_index.rs
new file mode 100644
index 000000000..d74b64041
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/static_index.rs
@@ -0,0 +1,321 @@
+//! This module provides `StaticIndex` which is used for powering
+//! read-only code browsers and emitting LSIF
+
+use std::collections::HashMap;
+
+use hir::{db::HirDatabase, Crate, Module, Semantics};
+use ide_db::{
+ base_db::{FileId, FileRange, SourceDatabaseExt},
+ defs::{Definition, IdentClass},
+ FxHashSet, RootDatabase,
+};
+use syntax::{AstNode, SyntaxKind::*, SyntaxToken, TextRange, T};
+
+use crate::{
+ hover::hover_for_definition,
+ moniker::{crate_for_file, def_to_moniker, MonikerResult},
+ Analysis, Fold, HoverConfig, HoverDocFormat, HoverResult, InlayHint, InlayHintsConfig,
+ TryToNav,
+};
+
+/// A static representation of fully analyzed source code.
+///
+/// The intended use-case is powering read-only code browsers and emitting LSIF
+#[derive(Debug)]
+pub struct StaticIndex<'a> {
+ pub files: Vec<StaticIndexedFile>,
+ pub tokens: TokenStore,
+ analysis: &'a Analysis,
+ db: &'a RootDatabase,
+ def_map: HashMap<Definition, TokenId>,
+}
+
+#[derive(Debug)]
+pub struct ReferenceData {
+ pub range: FileRange,
+ pub is_definition: bool,
+}
+
+#[derive(Debug)]
+pub struct TokenStaticData {
+ pub hover: Option<HoverResult>,
+ pub definition: Option<FileRange>,
+ pub references: Vec<ReferenceData>,
+ pub moniker: Option<MonikerResult>,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct TokenId(usize);
+
+impl TokenId {
+ pub fn raw(self) -> usize {
+ self.0
+ }
+}
+
+#[derive(Default, Debug)]
+pub struct TokenStore(Vec<TokenStaticData>);
+
+impl TokenStore {
+ pub fn insert(&mut self, data: TokenStaticData) -> TokenId {
+ let id = TokenId(self.0.len());
+ self.0.push(data);
+ id
+ }
+
+ pub fn get_mut(&mut self, id: TokenId) -> Option<&mut TokenStaticData> {
+ self.0.get_mut(id.0)
+ }
+
+ pub fn get(&self, id: TokenId) -> Option<&TokenStaticData> {
+ self.0.get(id.0)
+ }
+
+ pub fn iter(self) -> impl Iterator<Item = (TokenId, TokenStaticData)> {
+ self.0.into_iter().enumerate().map(|(i, x)| (TokenId(i), x))
+ }
+}
+
+#[derive(Debug)]
+pub struct StaticIndexedFile {
+ pub file_id: FileId,
+ pub folds: Vec<Fold>,
+ pub inlay_hints: Vec<InlayHint>,
+ pub tokens: Vec<(TextRange, TokenId)>,
+}
+
+fn all_modules(db: &dyn HirDatabase) -> Vec<Module> {
+ let mut worklist: Vec<_> =
+ Crate::all(db).into_iter().map(|krate| krate.root_module(db)).collect();
+ let mut modules = Vec::new();
+
+ while let Some(module) = worklist.pop() {
+ modules.push(module);
+ worklist.extend(module.children(db));
+ }
+
+ modules
+}
+
+impl StaticIndex<'_> {
+ fn add_file(&mut self, file_id: FileId) {
+ let current_crate = crate_for_file(self.db, file_id);
+ let folds = self.analysis.folding_ranges(file_id).unwrap();
+ let inlay_hints = self
+ .analysis
+ .inlay_hints(
+ &InlayHintsConfig {
+ render_colons: true,
+ type_hints: true,
+ parameter_hints: true,
+ chaining_hints: true,
+ closure_return_type_hints: crate::ClosureReturnTypeHints::WithBlock,
+ lifetime_elision_hints: crate::LifetimeElisionHints::Never,
+ reborrow_hints: crate::ReborrowHints::Never,
+ hide_named_constructor_hints: false,
+ hide_closure_initialization_hints: false,
+ param_names_for_lifetime_elision_hints: false,
+ binding_mode_hints: false,
+ max_length: Some(25),
+ closing_brace_hints_min_lines: Some(25),
+ },
+ file_id,
+ None,
+ )
+ .unwrap();
+ // hovers
+ let sema = hir::Semantics::new(self.db);
+ let tokens_or_nodes = sema.parse(file_id).syntax().clone();
+ let tokens = tokens_or_nodes.descendants_with_tokens().filter_map(|x| match x {
+ syntax::NodeOrToken::Node(_) => None,
+ syntax::NodeOrToken::Token(x) => Some(x),
+ });
+ let hover_config =
+ HoverConfig { links_in_hover: true, documentation: Some(HoverDocFormat::Markdown) };
+ let tokens = tokens.filter(|token| {
+ matches!(
+ token.kind(),
+ IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] | T![Self]
+ )
+ });
+ let mut result = StaticIndexedFile { file_id, inlay_hints, folds, tokens: vec![] };
+ for token in tokens {
+ let range = token.text_range();
+ let node = token.parent().unwrap();
+ let def = match get_definition(&sema, token.clone()) {
+ Some(x) => x,
+ None => continue,
+ };
+ let id = if let Some(x) = self.def_map.get(&def) {
+ *x
+ } else {
+ let x = self.tokens.insert(TokenStaticData {
+ hover: hover_for_definition(&sema, file_id, def, &node, &hover_config),
+ definition: def
+ .try_to_nav(self.db)
+ .map(|x| FileRange { file_id: x.file_id, range: x.focus_or_full_range() }),
+ references: vec![],
+ moniker: current_crate.and_then(|cc| def_to_moniker(self.db, def, cc)),
+ });
+ self.def_map.insert(def, x);
+ x
+ };
+ let token = self.tokens.get_mut(id).unwrap();
+ token.references.push(ReferenceData {
+ range: FileRange { range, file_id },
+ is_definition: match def.try_to_nav(self.db) {
+ Some(x) => x.file_id == file_id && x.focus_or_full_range() == range,
+ None => false,
+ },
+ });
+ result.tokens.push((range, id));
+ }
+ self.files.push(result);
+ }
+
+ pub fn compute(analysis: &Analysis) -> StaticIndex<'_> {
+ let db = &*analysis.db;
+ let work = all_modules(db).into_iter().filter(|module| {
+ let file_id = module.definition_source(db).file_id.original_file(db);
+ let source_root = db.file_source_root(file_id);
+ let source_root = db.source_root(source_root);
+ !source_root.is_library
+ });
+ let mut this = StaticIndex {
+ files: vec![],
+ tokens: Default::default(),
+ analysis,
+ db,
+ def_map: Default::default(),
+ };
+ let mut visited_files = FxHashSet::default();
+ for module in work {
+ let file_id = module.definition_source(db).file_id.original_file(db);
+ if visited_files.contains(&file_id) {
+ continue;
+ }
+ this.add_file(file_id);
+ // mark the file
+ visited_files.insert(file_id);
+ }
+ this
+ }
+}
+
+fn get_definition(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> Option<Definition> {
+ for token in sema.descend_into_macros(token) {
+ let def = IdentClass::classify_token(sema, &token).map(IdentClass::definitions);
+ if let Some(&[x]) = def.as_deref() {
+ return Some(x);
+ } else {
+ continue;
+ };
+ }
+ None
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{fixture, StaticIndex};
+ use ide_db::base_db::FileRange;
+ use std::collections::HashSet;
+ use syntax::TextSize;
+
+ fn check_all_ranges(ra_fixture: &str) {
+ let (analysis, ranges) = fixture::annotations_without_marker(ra_fixture);
+ let s = StaticIndex::compute(&analysis);
+ let mut range_set: HashSet<_> = ranges.iter().map(|x| x.0).collect();
+ for f in s.files {
+ for (range, _) in f.tokens {
+ let x = FileRange { file_id: f.file_id, range };
+ if !range_set.contains(&x) {
+ panic!("additional range {:?}", x);
+ }
+ range_set.remove(&x);
+ }
+ }
+ if !range_set.is_empty() {
+ panic!("unfound ranges {:?}", range_set);
+ }
+ }
+
+ fn check_definitions(ra_fixture: &str) {
+ let (analysis, ranges) = fixture::annotations_without_marker(ra_fixture);
+ let s = StaticIndex::compute(&analysis);
+ let mut range_set: HashSet<_> = ranges.iter().map(|x| x.0).collect();
+ for (_, t) in s.tokens.iter() {
+ if let Some(x) = t.definition {
+ if x.range.start() == TextSize::from(0) {
+ // ignore definitions that are whole of file
+ continue;
+ }
+ if !range_set.contains(&x) {
+ panic!("additional definition {:?}", x);
+ }
+ range_set.remove(&x);
+ }
+ }
+ if !range_set.is_empty() {
+ panic!("unfound definitions {:?}", range_set);
+ }
+ }
+
+ #[test]
+ fn struct_and_enum() {
+ check_all_ranges(
+ r#"
+struct Foo;
+ //^^^
+enum E { X(Foo) }
+ //^ ^ ^^^
+"#,
+ );
+ check_definitions(
+ r#"
+struct Foo;
+ //^^^
+enum E { X(Foo) }
+ //^ ^
+"#,
+ );
+ }
+
+ #[test]
+ fn multi_crate() {
+ check_definitions(
+ r#"
+//- /main.rs crate:main deps:foo
+
+
+use foo::func;
+
+fn main() {
+ //^^^^
+ func();
+}
+//- /foo/lib.rs crate:foo
+
+pub func() {
+
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn derives() {
+ check_all_ranges(
+ r#"
+//- minicore:derive
+#[rustc_builtin_macro]
+//^^^^^^^^^^^^^^^^^^^
+pub macro Copy {}
+ //^^^^
+#[derive(Copy)]
+//^^^^^^ ^^^^
+struct Hello(i32);
+ //^^^^^ ^^^
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/status.rs b/src/tools/rust-analyzer/crates/ide/src/status.rs
new file mode 100644
index 000000000..3191870eb
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/status.rs
@@ -0,0 +1,164 @@
+use std::{fmt, iter::FromIterator, sync::Arc};
+
+use hir::{ExpandResult, MacroFile};
+use ide_db::base_db::{
+ salsa::debug::{DebugQueryTable, TableEntry},
+ CrateId, FileId, FileTextQuery, SourceDatabase, SourceRootId,
+};
+use ide_db::{
+ symbol_index::{LibrarySymbolsQuery, SymbolIndex},
+ RootDatabase,
+};
+use itertools::Itertools;
+use profile::{memory_usage, Bytes};
+use std::env;
+use stdx::format_to;
+use syntax::{ast, Parse, SyntaxNode};
+
+fn syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats {
+ ide_db::base_db::ParseQuery.in_db(db).entries::<SyntaxTreeStats>()
+}
+fn macro_syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats {
+ hir::db::ParseMacroExpansionQuery.in_db(db).entries::<SyntaxTreeStats>()
+}
+
+// Feature: Status
+//
+// Shows internal statistic about memory usage of rust-analyzer.
+//
+// |===
+// | Editor | Action Name
+//
+// | VS Code | **Rust Analyzer: Status**
+// |===
+// image::https://user-images.githubusercontent.com/48062697/113065584-05f34500-91b1-11eb-98cc-5c196f76be7f.gif[]
+pub(crate) fn status(db: &RootDatabase, file_id: Option<FileId>) -> String {
+ let mut buf = String::new();
+ format_to!(buf, "{}\n", FileTextQuery.in_db(db).entries::<FilesStats>());
+ format_to!(buf, "{}\n", LibrarySymbolsQuery.in_db(db).entries::<LibrarySymbolsStats>());
+ format_to!(buf, "{}\n", syntax_tree_stats(db));
+ format_to!(buf, "{} (Macros)\n", macro_syntax_tree_stats(db));
+ format_to!(buf, "{} in total\n", memory_usage());
+ if env::var("RA_COUNT").is_ok() {
+ format_to!(buf, "\nCounts:\n{}", profile::countme::get_all());
+ }
+
+ if let Some(file_id) = file_id {
+ format_to!(buf, "\nFile info:\n");
+ let crates = crate::parent_module::crate_for(db, file_id);
+ if crates.is_empty() {
+ format_to!(buf, "Does not belong to any crate");
+ }
+ let crate_graph = db.crate_graph();
+ for krate in crates {
+ let display_crate = |krate: CrateId| match &crate_graph[krate].display_name {
+ Some(it) => format!("{}({:?})", it, krate),
+ None => format!("{:?}", krate),
+ };
+ format_to!(buf, "Crate: {}\n", display_crate(krate));
+ let deps = crate_graph[krate]
+ .dependencies
+ .iter()
+ .map(|dep| format!("{}={:?}", dep.name, dep.crate_id))
+ .format(", ");
+ format_to!(buf, "Dependencies: {}\n", deps);
+ }
+ }
+
+ buf.trim().to_string()
+}
+
+#[derive(Default)]
+struct FilesStats {
+ total: usize,
+ size: Bytes,
+}
+
+impl fmt::Display for FilesStats {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(fmt, "{} of files", self.size)
+ }
+}
+
+impl FromIterator<TableEntry<FileId, Arc<String>>> for FilesStats {
+ fn from_iter<T>(iter: T) -> FilesStats
+ where
+ T: IntoIterator<Item = TableEntry<FileId, Arc<String>>>,
+ {
+ let mut res = FilesStats::default();
+ for entry in iter {
+ res.total += 1;
+ res.size += entry.value.unwrap().len();
+ }
+ res
+ }
+}
+
+#[derive(Default)]
+pub(crate) struct SyntaxTreeStats {
+ total: usize,
+ pub(crate) retained: usize,
+}
+
+impl fmt::Display for SyntaxTreeStats {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(fmt, "{} trees, {} preserved", self.total, self.retained)
+ }
+}
+
+impl FromIterator<TableEntry<FileId, Parse<ast::SourceFile>>> for SyntaxTreeStats {
+ fn from_iter<T>(iter: T) -> SyntaxTreeStats
+ where
+ T: IntoIterator<Item = TableEntry<FileId, Parse<ast::SourceFile>>>,
+ {
+ let mut res = SyntaxTreeStats::default();
+ for entry in iter {
+ res.total += 1;
+ res.retained += entry.value.is_some() as usize;
+ }
+ res
+ }
+}
+
+impl<M> FromIterator<TableEntry<MacroFile, ExpandResult<Option<(Parse<SyntaxNode>, M)>>>>
+ for SyntaxTreeStats
+{
+ fn from_iter<T>(iter: T) -> SyntaxTreeStats
+ where
+ T: IntoIterator<Item = TableEntry<MacroFile, ExpandResult<Option<(Parse<SyntaxNode>, M)>>>>,
+ {
+ let mut res = SyntaxTreeStats::default();
+ for entry in iter {
+ res.total += 1;
+ res.retained += entry.value.is_some() as usize;
+ }
+ res
+ }
+}
+
+#[derive(Default)]
+struct LibrarySymbolsStats {
+ total: usize,
+ size: Bytes,
+}
+
+impl fmt::Display for LibrarySymbolsStats {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(fmt, "{} of index symbols ({})", self.size, self.total)
+ }
+}
+
+impl FromIterator<TableEntry<SourceRootId, Arc<SymbolIndex>>> for LibrarySymbolsStats {
+ fn from_iter<T>(iter: T) -> LibrarySymbolsStats
+ where
+ T: IntoIterator<Item = TableEntry<SourceRootId, Arc<SymbolIndex>>>,
+ {
+ let mut res = LibrarySymbolsStats::default();
+ for entry in iter {
+ let symbols = entry.value.unwrap();
+ res.total += symbols.len();
+ res.size += symbols.memory_size();
+ }
+ res
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs
new file mode 100644
index 000000000..3fb49b45d
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs
@@ -0,0 +1,449 @@
+pub(crate) mod tags;
+
+mod highlights;
+mod injector;
+
+mod highlight;
+mod format;
+mod macro_;
+mod inject;
+mod escape;
+
+mod html;
+#[cfg(test)]
+mod tests;
+
+use hir::{Name, Semantics};
+use ide_db::{FxHashMap, RootDatabase};
+use syntax::{
+ ast, AstNode, AstToken, NodeOrToken, SyntaxKind::*, SyntaxNode, TextRange, WalkEvent, T,
+};
+
+use crate::{
+ syntax_highlighting::{
+ escape::highlight_escape_string, format::highlight_format_string, highlights::Highlights,
+ macro_::MacroHighlighter, tags::Highlight,
+ },
+ FileId, HlMod, HlTag,
+};
+
+pub(crate) use html::highlight_as_html;
+
+#[derive(Debug, Clone, Copy)]
+pub struct HlRange {
+ pub range: TextRange,
+ pub highlight: Highlight,
+ pub binding_hash: Option<u64>,
+}
+
+// Feature: Semantic Syntax Highlighting
+//
+// rust-analyzer highlights the code semantically.
+// For example, `Bar` in `foo::Bar` might be colored differently depending on whether `Bar` is an enum or a trait.
+// rust-analyzer does not specify colors directly, instead it assigns a tag (like `struct`) and a set of modifiers (like `declaration`) to each token.
+// It's up to the client to map those to specific colors.
+//
+// The general rule is that a reference to an entity gets colored the same way as the entity itself.
+// We also give special modifier for `mut` and `&mut` local variables.
+//
+//
+// .Token Tags
+//
+// Rust-analyzer currently emits the following token tags:
+//
+// - For items:
+// +
+// [horizontal]
+// attribute:: Emitted for attribute macros.
+// enum:: Emitted for enums.
+// function:: Emitted for free-standing functions.
+// derive:: Emitted for derive macros.
+// macro:: Emitted for function-like macros.
+// method:: Emitted for associated functions, also knowns as methods.
+// namespace:: Emitted for modules.
+// struct:: Emitted for structs.
+// trait:: Emitted for traits.
+// typeAlias:: Emitted for type aliases and `Self` in `impl`s.
+// union:: Emitted for unions.
+//
+// - For literals:
+// +
+// [horizontal]
+// boolean:: Emitted for the boolean literals `true` and `false`.
+// character:: Emitted for character literals.
+// number:: Emitted for numeric literals.
+// string:: Emitted for string literals.
+// escapeSequence:: Emitted for escaped sequences inside strings like `\n`.
+// formatSpecifier:: Emitted for format specifiers `{:?}` in `format!`-like macros.
+//
+// - For operators:
+// +
+// [horizontal]
+// operator:: Emitted for general operators.
+// arithmetic:: Emitted for the arithmetic operators `+`, `-`, `*`, `/`, `+=`, `-=`, `*=`, `/=`.
+// bitwise:: Emitted for the bitwise operators `|`, `&`, `!`, `^`, `|=`, `&=`, `^=`.
+// comparison:: Emitted for the comparison operators `>`, `<`, `==`, `>=`, `<=`, `!=`.
+// logical:: Emitted for the logical operators `||`, `&&`, `!`.
+//
+// - For punctuation:
+// +
+// [horizontal]
+// punctuation:: Emitted for general punctuation.
+// attributeBracket:: Emitted for attribute invocation brackets, that is the `#[` and `]` tokens.
+// angle:: Emitted for `<>` angle brackets.
+// brace:: Emitted for `{}` braces.
+// bracket:: Emitted for `[]` brackets.
+// parenthesis:: Emitted for `()` parentheses.
+// colon:: Emitted for the `:` token.
+// comma:: Emitted for the `,` token.
+// dot:: Emitted for the `.` token.
+// semi:: Emitted for the `;` token.
+// macroBang:: Emitted for the `!` token in macro calls.
+//
+// //-
+//
+// [horizontal]
+// builtinAttribute:: Emitted for names to builtin attributes in attribute path, the `repr` in `#[repr(u8)]` for example.
+// builtinType:: Emitted for builtin types like `u32`, `str` and `f32`.
+// comment:: Emitted for comments.
+// constParameter:: Emitted for const parameters.
+// deriveHelper:: Emitted for derive helper attributes.
+// enumMember:: Emitted for enum variants.
+// generic:: Emitted for generic tokens that have no mapping.
+// keyword:: Emitted for keywords.
+// label:: Emitted for labels.
+// lifetime:: Emitted for lifetimes.
+// parameter:: Emitted for non-self function parameters.
+// property:: Emitted for struct and union fields.
+// selfKeyword:: Emitted for the self function parameter and self path-specifier.
+// selfTypeKeyword:: Emitted for the Self type parameter.
+// toolModule:: Emitted for tool modules.
+// typeParameter:: Emitted for type parameters.
+// unresolvedReference:: Emitted for unresolved references, names that rust-analyzer can't find the definition of.
+// variable:: Emitted for locals, constants and statics.
+//
+//
+// .Token Modifiers
+//
+// Token modifiers allow to style some elements in the source code more precisely.
+//
+// Rust-analyzer currently emits the following token modifiers:
+//
+// [horizontal]
+// async:: Emitted for async functions and the `async` and `await` keywords.
+// attribute:: Emitted for tokens inside attributes.
+// callable:: Emitted for locals whose types implements one of the `Fn*` traits.
+// constant:: Emitted for consts.
+// consuming:: Emitted for locals that are being consumed when use in a function call.
+// controlFlow:: Emitted for control-flow related tokens, this includes the `?` operator.
+// crateRoot:: Emitted for crate names, like `serde` and `crate`.
+// declaration:: Emitted for names of definitions, like `foo` in `fn foo() {}`.
+// defaultLibrary:: Emitted for items from built-in crates (std, core, alloc, test and proc_macro).
+// documentation:: Emitted for documentation comments.
+// injected:: Emitted for doc-string injected highlighting like rust source blocks in documentation.
+// intraDocLink:: Emitted for intra doc links in doc-strings.
+// library:: Emitted for items that are defined outside of the current crate.
+// mutable:: Emitted for mutable locals and statics as well as functions taking `&mut self`.
+// public:: Emitted for items that are from the current crate and are `pub`.
+// reference:: Emitted for locals behind a reference and functions taking `self` by reference.
+// static:: Emitted for "static" functions, also known as functions that do not take a `self` param, as well as statics and consts.
+// trait:: Emitted for associated trait items.
+// unsafe:: Emitted for unsafe operations, like unsafe function calls, as well as the `unsafe` token.
+//
+//
+// image::https://user-images.githubusercontent.com/48062697/113164457-06cfb980-9239-11eb-819b-0f93e646acf8.png[]
+// image::https://user-images.githubusercontent.com/48062697/113187625-f7f50100-9250-11eb-825e-91c58f236071.png[]
+pub(crate) fn highlight(
+ db: &RootDatabase,
+ file_id: FileId,
+ range_to_highlight: Option<TextRange>,
+ syntactic_name_ref_highlighting: bool,
+) -> Vec<HlRange> {
+ let _p = profile::span("highlight");
+ let sema = Semantics::new(db);
+
+ // Determine the root based on the given range.
+ let (root, range_to_highlight) = {
+ let source_file = sema.parse(file_id);
+ let source_file = source_file.syntax();
+ match range_to_highlight {
+ Some(range) => {
+ let node = match source_file.covering_element(range) {
+ NodeOrToken::Node(it) => it,
+ NodeOrToken::Token(it) => it.parent().unwrap_or_else(|| source_file.clone()),
+ };
+ (node, range)
+ }
+ None => (source_file.clone(), source_file.text_range()),
+ }
+ };
+
+ let mut hl = highlights::Highlights::new(root.text_range());
+ let krate = match sema.scope(&root) {
+ Some(it) => it.krate(),
+ None => return hl.to_vec(),
+ };
+ traverse(
+ &mut hl,
+ &sema,
+ file_id,
+ &root,
+ krate,
+ range_to_highlight,
+ syntactic_name_ref_highlighting,
+ );
+ hl.to_vec()
+}
+
+fn traverse(
+ hl: &mut Highlights,
+ sema: &Semantics<'_, RootDatabase>,
+ file_id: FileId,
+ root: &SyntaxNode,
+ krate: hir::Crate,
+ range_to_highlight: TextRange,
+ syntactic_name_ref_highlighting: bool,
+) {
+ let is_unlinked = sema.to_module_def(file_id).is_none();
+ let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
+
+ enum AttrOrDerive {
+ Attr(ast::Item),
+ Derive(ast::Item),
+ }
+
+ impl AttrOrDerive {
+ fn item(&self) -> &ast::Item {
+ match self {
+ AttrOrDerive::Attr(item) | AttrOrDerive::Derive(item) => item,
+ }
+ }
+ }
+
+ let mut tt_level = 0;
+ let mut attr_or_derive_item = None;
+ let mut current_macro: Option<ast::Macro> = None;
+ let mut macro_highlighter = MacroHighlighter::default();
+ let mut inside_attribute = false;
+
+ // Walk all nodes, keeping track of whether we are inside a macro or not.
+ // If in macro, expand it first and highlight the expanded code.
+ for event in root.preorder_with_tokens() {
+ use WalkEvent::{Enter, Leave};
+
+ let range = match &event {
+ Enter(it) | Leave(it) => it.text_range(),
+ };
+
+ // Element outside of the viewport, no need to highlight
+ if range_to_highlight.intersect(range).is_none() {
+ continue;
+ }
+
+ // set macro and attribute highlighting states
+ match event.clone() {
+ Enter(NodeOrToken::Node(node)) if ast::TokenTree::can_cast(node.kind()) => {
+ tt_level += 1;
+ }
+ Leave(NodeOrToken::Node(node)) if ast::TokenTree::can_cast(node.kind()) => {
+ tt_level -= 1;
+ }
+ Enter(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => {
+ inside_attribute = true
+ }
+ Leave(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => {
+ inside_attribute = false
+ }
+
+ Enter(NodeOrToken::Node(node)) if ast::Item::can_cast(node.kind()) => {
+ match ast::Item::cast(node.clone()) {
+ Some(ast::Item::MacroRules(mac)) => {
+ macro_highlighter.init();
+ current_macro = Some(mac.into());
+ continue;
+ }
+ Some(ast::Item::MacroDef(mac)) => {
+ macro_highlighter.init();
+ current_macro = Some(mac.into());
+ continue;
+ }
+ Some(item) => {
+ if matches!(node.kind(), FN | CONST | STATIC) {
+ bindings_shadow_count.clear();
+ }
+
+ if attr_or_derive_item.is_none() {
+ if sema.is_attr_macro_call(&item) {
+ attr_or_derive_item = Some(AttrOrDerive::Attr(item));
+ } else {
+ let adt = match item {
+ ast::Item::Enum(it) => Some(ast::Adt::Enum(it)),
+ ast::Item::Struct(it) => Some(ast::Adt::Struct(it)),
+ ast::Item::Union(it) => Some(ast::Adt::Union(it)),
+ _ => None,
+ };
+ match adt {
+ Some(adt) if sema.is_derive_annotated(&adt) => {
+ attr_or_derive_item =
+ Some(AttrOrDerive::Derive(ast::Item::from(adt)));
+ }
+ _ => (),
+ }
+ }
+ }
+ }
+ _ => (),
+ }
+ }
+ Leave(NodeOrToken::Node(node)) if ast::Item::can_cast(node.kind()) => {
+ match ast::Item::cast(node.clone()) {
+ Some(ast::Item::MacroRules(mac)) => {
+ assert_eq!(current_macro, Some(mac.into()));
+ current_macro = None;
+ macro_highlighter = MacroHighlighter::default();
+ }
+ Some(ast::Item::MacroDef(mac)) => {
+ assert_eq!(current_macro, Some(mac.into()));
+ current_macro = None;
+ macro_highlighter = MacroHighlighter::default();
+ }
+ Some(item)
+ if attr_or_derive_item.as_ref().map_or(false, |it| *it.item() == item) =>
+ {
+ attr_or_derive_item = None;
+ }
+ _ => (),
+ }
+ }
+ _ => (),
+ }
+
+ let element = match event {
+ Enter(NodeOrToken::Token(tok)) if tok.kind() == WHITESPACE => continue,
+ Enter(it) => it,
+ Leave(NodeOrToken::Token(_)) => continue,
+ Leave(NodeOrToken::Node(node)) => {
+ // Doc comment highlighting injection, we do this when leaving the node
+ // so that we overwrite the highlighting of the doc comment itself.
+ inject::doc_comment(hl, sema, file_id, &node);
+ continue;
+ }
+ };
+
+ if current_macro.is_some() {
+ if let Some(tok) = element.as_token() {
+ macro_highlighter.advance(tok);
+ }
+ }
+
+ let element = match element.clone() {
+ NodeOrToken::Node(n) => match ast::NameLike::cast(n) {
+ Some(n) => NodeOrToken::Node(n),
+ None => continue,
+ },
+ NodeOrToken::Token(t) => NodeOrToken::Token(t),
+ };
+ let token = element.as_token().cloned();
+
+ // Descending tokens into macros is expensive even if no descending occurs, so make sure
+ // that we actually are in a position where descending is possible.
+ let in_macro = tt_level > 0
+ || match attr_or_derive_item {
+ Some(AttrOrDerive::Attr(_)) => true,
+ Some(AttrOrDerive::Derive(_)) => inside_attribute,
+ None => false,
+ };
+ let descended_element = if in_macro {
+ // Attempt to descend tokens into macro-calls.
+ match element {
+ NodeOrToken::Token(token) if token.kind() != COMMENT => {
+ let token = match attr_or_derive_item {
+ Some(AttrOrDerive::Attr(_)) => {
+ sema.descend_into_macros_with_kind_preference(token)
+ }
+ Some(AttrOrDerive::Derive(_)) | None => {
+ sema.descend_into_macros_single(token)
+ }
+ };
+ match token.parent().and_then(ast::NameLike::cast) {
+ // Remap the token into the wrapping single token nodes
+ Some(parent) => match (token.kind(), parent.syntax().kind()) {
+ (T![self] | T![ident], NAME | NAME_REF) => NodeOrToken::Node(parent),
+ (T![self] | T![super] | T![crate] | T![Self], NAME_REF) => {
+ NodeOrToken::Node(parent)
+ }
+ (INT_NUMBER, NAME_REF) => NodeOrToken::Node(parent),
+ (LIFETIME_IDENT, LIFETIME) => NodeOrToken::Node(parent),
+ _ => NodeOrToken::Token(token),
+ },
+ None => NodeOrToken::Token(token),
+ }
+ }
+ e => e,
+ }
+ } else {
+ element
+ };
+
+ // FIXME: do proper macro def highlighting https://github.com/rust-lang/rust-analyzer/issues/6232
+ // Skip metavariables from being highlighted to prevent keyword highlighting in them
+ if descended_element.as_token().and_then(|t| macro_highlighter.highlight(t)).is_some() {
+ continue;
+ }
+
+ // string highlight injections, note this does not use the descended element as proc-macros
+ // can rewrite string literals which invalidates our indices
+ if let (Some(token), Some(descended_token)) = (token, descended_element.as_token()) {
+ if ast::String::can_cast(token.kind()) && ast::String::can_cast(descended_token.kind())
+ {
+ let string = ast::String::cast(token);
+ let string_to_highlight = ast::String::cast(descended_token.clone());
+ if let Some((string, expanded_string)) = string.zip(string_to_highlight) {
+ if string.is_raw() {
+ if inject::ra_fixture(hl, sema, &string, &expanded_string).is_some() {
+ continue;
+ }
+ }
+ highlight_format_string(hl, &string, &expanded_string, range);
+ highlight_escape_string(hl, &string, range.start());
+ }
+ } else if ast::ByteString::can_cast(token.kind())
+ && ast::ByteString::can_cast(descended_token.kind())
+ {
+ if let Some(byte_string) = ast::ByteString::cast(token) {
+ highlight_escape_string(hl, &byte_string, range.start());
+ }
+ }
+ }
+
+ let element = match descended_element {
+ NodeOrToken::Node(name_like) => highlight::name_like(
+ sema,
+ krate,
+ &mut bindings_shadow_count,
+ syntactic_name_ref_highlighting,
+ name_like,
+ ),
+ NodeOrToken::Token(token) => highlight::token(sema, token).zip(Some(None)),
+ };
+ if let Some((mut highlight, binding_hash)) = element {
+ if is_unlinked && highlight.tag == HlTag::UnresolvedReference {
+ // do not emit unresolved references if the file is unlinked
+ // let the editor do its highlighting for these tokens instead
+ continue;
+ }
+ if highlight.tag == HlTag::UnresolvedReference
+ && matches!(attr_or_derive_item, Some(AttrOrDerive::Derive(_)) if inside_attribute)
+ {
+ // do not emit unresolved references in derive helpers if the token mapping maps to
+ // something unresolvable. FIXME: There should be a way to prevent that
+ continue;
+ }
+ if inside_attribute {
+ highlight |= HlMod::Attribute
+ }
+
+ hl.add(HlRange { range, highlight, binding_hash });
+ }
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/escape.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/escape.rs
new file mode 100644
index 000000000..6a1236c79
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/escape.rs
@@ -0,0 +1,25 @@
+//! Syntax highlighting for escape sequences
+use crate::syntax_highlighting::highlights::Highlights;
+use crate::{HlRange, HlTag};
+use syntax::ast::IsString;
+use syntax::TextSize;
+
+pub(super) fn highlight_escape_string<T: IsString>(
+ stack: &mut Highlights,
+ string: &T,
+ start: TextSize,
+) {
+ string.escaped_char_ranges(&mut |piece_range, char| {
+ if char.is_err() {
+ return;
+ }
+
+ if string.text()[piece_range.start().into()..].starts_with('\\') {
+ stack.add(HlRange {
+ range: piece_range + start,
+ highlight: HlTag::EscapeSequence.into(),
+ binding_hash: None,
+ });
+ }
+ });
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/format.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/format.rs
new file mode 100644
index 000000000..2ed57e201
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/format.rs
@@ -0,0 +1,50 @@
+//! Syntax highlighting for format macro strings.
+use ide_db::{
+ syntax_helpers::format_string::{is_format_string, lex_format_specifiers, FormatSpecifier},
+ SymbolKind,
+};
+use syntax::{ast, TextRange};
+
+use crate::{syntax_highlighting::highlights::Highlights, HlRange, HlTag};
+
+pub(super) fn highlight_format_string(
+ stack: &mut Highlights,
+ string: &ast::String,
+ expanded_string: &ast::String,
+ range: TextRange,
+) {
+ if !is_format_string(expanded_string) {
+ return;
+ }
+
+ lex_format_specifiers(string, &mut |piece_range, kind| {
+ if let Some(highlight) = highlight_format_specifier(kind) {
+ stack.add(HlRange {
+ range: piece_range + range.start(),
+ highlight: highlight.into(),
+ binding_hash: None,
+ });
+ }
+ });
+}
+
+fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HlTag> {
+ Some(match kind {
+ FormatSpecifier::Open
+ | FormatSpecifier::Close
+ | FormatSpecifier::Colon
+ | FormatSpecifier::Fill
+ | FormatSpecifier::Align
+ | FormatSpecifier::Sign
+ | FormatSpecifier::NumberSign
+ | FormatSpecifier::DollarSign
+ | FormatSpecifier::Dot
+ | FormatSpecifier::Asterisk
+ | FormatSpecifier::QuestionMark => HlTag::FormatSpecifier,
+
+ FormatSpecifier::Integer | FormatSpecifier::Zero => HlTag::NumericLiteral,
+
+ FormatSpecifier::Identifier => HlTag::Symbol(SymbolKind::Local),
+ FormatSpecifier::Escape => HlTag::EscapeSequence,
+ })
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs
new file mode 100644
index 000000000..9395e914c
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs
@@ -0,0 +1,690 @@
+//! Computes color for a single element.
+
+use hir::{AsAssocItem, HasVisibility, Semantics};
+use ide_db::{
+ defs::{Definition, IdentClass, NameClass, NameRefClass},
+ FxHashMap, RootDatabase, SymbolKind,
+};
+use syntax::{
+ ast, match_ast, AstNode, AstToken, NodeOrToken,
+ SyntaxKind::{self, *},
+ SyntaxNode, SyntaxToken, T,
+};
+
+use crate::{
+ syntax_highlighting::tags::{HlOperator, HlPunct},
+ Highlight, HlMod, HlTag,
+};
+
+pub(super) fn token(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> Option<Highlight> {
+ if let Some(comment) = ast::Comment::cast(token.clone()) {
+ let h = HlTag::Comment;
+ return Some(match comment.kind().doc {
+ Some(_) => h | HlMod::Documentation,
+ None => h.into(),
+ });
+ }
+
+ let highlight: Highlight = match token.kind() {
+ STRING | BYTE_STRING => HlTag::StringLiteral.into(),
+ INT_NUMBER if token.parent_ancestors().nth(1).map(|it| it.kind()) == Some(FIELD_EXPR) => {
+ SymbolKind::Field.into()
+ }
+ INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(),
+ BYTE => HlTag::ByteLiteral.into(),
+ CHAR => HlTag::CharLiteral.into(),
+ IDENT if token.parent().and_then(ast::TokenTree::cast).is_some() => {
+ // from this point on we are inside a token tree, this only happens for identifiers
+ // that were not mapped down into macro invocations
+ HlTag::None.into()
+ }
+ p if p.is_punct() => punctuation(sema, token, p),
+ k if k.is_keyword() => keyword(sema, token, k)?,
+ _ => return None,
+ };
+ Some(highlight)
+}
+
+pub(super) fn name_like(
+ sema: &Semantics<'_, RootDatabase>,
+ krate: hir::Crate,
+ bindings_shadow_count: &mut FxHashMap<hir::Name, u32>,
+ syntactic_name_ref_highlighting: bool,
+ name_like: ast::NameLike,
+) -> Option<(Highlight, Option<u64>)> {
+ let mut binding_hash = None;
+ let highlight = match name_like {
+ ast::NameLike::NameRef(name_ref) => highlight_name_ref(
+ sema,
+ krate,
+ bindings_shadow_count,
+ &mut binding_hash,
+ syntactic_name_ref_highlighting,
+ name_ref,
+ ),
+ ast::NameLike::Name(name) => {
+ highlight_name(sema, bindings_shadow_count, &mut binding_hash, krate, name)
+ }
+ ast::NameLike::Lifetime(lifetime) => match IdentClass::classify_lifetime(sema, &lifetime) {
+ Some(IdentClass::NameClass(NameClass::Definition(def))) => {
+ highlight_def(sema, krate, def) | HlMod::Definition
+ }
+ Some(IdentClass::NameRefClass(NameRefClass::Definition(def))) => {
+ highlight_def(sema, krate, def)
+ }
+ // FIXME: Fallback for 'static and '_, as we do not resolve these yet
+ _ => SymbolKind::LifetimeParam.into(),
+ },
+ };
+ Some((highlight, binding_hash))
+}
+
+fn punctuation(
+ sema: &Semantics<'_, RootDatabase>,
+ token: SyntaxToken,
+ kind: SyntaxKind,
+) -> Highlight {
+ let parent = token.parent();
+ let parent_kind = parent.as_ref().map_or(EOF, SyntaxNode::kind);
+ match (kind, parent_kind) {
+ (T![?], _) => HlTag::Operator(HlOperator::Other) | HlMod::ControlFlow,
+ (T![&], BIN_EXPR) => HlOperator::Bitwise.into(),
+ (T![&], _) => {
+ let h = HlTag::Operator(HlOperator::Other).into();
+ let is_unsafe = parent
+ .and_then(ast::RefExpr::cast)
+ .map(|ref_expr| sema.is_unsafe_ref_expr(&ref_expr));
+ if let Some(true) = is_unsafe {
+ h | HlMod::Unsafe
+ } else {
+ h
+ }
+ }
+ (T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] | T![.], _) => HlOperator::Other.into(),
+ (T![!], MACRO_CALL | MACRO_RULES) => HlPunct::MacroBang.into(),
+ (T![!], NEVER_TYPE) => HlTag::BuiltinType.into(),
+ (T![!], PREFIX_EXPR) => HlOperator::Logical.into(),
+ (T![*], PTR_TYPE) => HlTag::Keyword.into(),
+ (T![*], PREFIX_EXPR) => {
+ let is_raw_ptr = (|| {
+ let prefix_expr = parent.and_then(ast::PrefixExpr::cast)?;
+ let expr = prefix_expr.expr()?;
+ sema.type_of_expr(&expr)?.original.is_raw_ptr().then(|| ())
+ })();
+ if let Some(()) = is_raw_ptr {
+ HlTag::Operator(HlOperator::Other) | HlMod::Unsafe
+ } else {
+ HlOperator::Other.into()
+ }
+ }
+ (T![-], PREFIX_EXPR) => {
+ let prefix_expr = parent.and_then(ast::PrefixExpr::cast).and_then(|e| e.expr());
+ match prefix_expr {
+ Some(ast::Expr::Literal(_)) => HlTag::NumericLiteral,
+ _ => HlTag::Operator(HlOperator::Other),
+ }
+ .into()
+ }
+ (T![+] | T![-] | T![*] | T![/] | T![%], BIN_EXPR) => HlOperator::Arithmetic.into(),
+ (T![+=] | T![-=] | T![*=] | T![/=] | T![%=], BIN_EXPR) => {
+ Highlight::from(HlOperator::Arithmetic) | HlMod::Mutable
+ }
+ (T![|] | T![&] | T![!] | T![^] | T![>>] | T![<<], BIN_EXPR) => HlOperator::Bitwise.into(),
+ (T![|=] | T![&=] | T![^=] | T![>>=] | T![<<=], BIN_EXPR) => {
+ Highlight::from(HlOperator::Bitwise) | HlMod::Mutable
+ }
+ (T![&&] | T![||], BIN_EXPR) => HlOperator::Logical.into(),
+ (T![>] | T![<] | T![==] | T![>=] | T![<=] | T![!=], BIN_EXPR) => {
+ HlOperator::Comparison.into()
+ }
+ (_, PREFIX_EXPR | BIN_EXPR | RANGE_EXPR | RANGE_PAT | REST_PAT) => HlOperator::Other.into(),
+ (_, ATTR) => HlTag::AttributeBracket.into(),
+ (kind, _) => match kind {
+ T!['['] | T![']'] => HlPunct::Bracket,
+ T!['{'] | T!['}'] => HlPunct::Brace,
+ T!['('] | T![')'] => HlPunct::Parenthesis,
+ T![<] | T![>] => HlPunct::Angle,
+ T![,] => HlPunct::Comma,
+ T![:] => HlPunct::Colon,
+ T![;] => HlPunct::Semi,
+ T![.] => HlPunct::Dot,
+ _ => HlPunct::Other,
+ }
+ .into(),
+ }
+}
+
+fn keyword(
+ sema: &Semantics<'_, RootDatabase>,
+ token: SyntaxToken,
+ kind: SyntaxKind,
+) -> Option<Highlight> {
+ let h = Highlight::new(HlTag::Keyword);
+ let h = match kind {
+ T![await] => h | HlMod::Async | HlMod::ControlFlow,
+ T![async] => h | HlMod::Async,
+ T![break]
+ | T![continue]
+ | T![else]
+ | T![if]
+ | T![in]
+ | T![loop]
+ | T![match]
+ | T![return]
+ | T![while]
+ | T![yield] => h | HlMod::ControlFlow,
+ T![for] if parent_matches::<ast::ForExpr>(&token) => h | HlMod::ControlFlow,
+ T![unsafe] => h | HlMod::Unsafe,
+ T![true] | T![false] => HlTag::BoolLiteral.into(),
+ // crate is handled just as a token if it's in an `extern crate`
+ T![crate] if parent_matches::<ast::ExternCrate>(&token) => h,
+ // self, crate, super and `Self` are handled as either a Name or NameRef already, unless they
+ // are inside unmapped token trees
+ T![self] | T![crate] | T![super] | T![Self] if parent_matches::<ast::NameRef>(&token) => {
+ return None
+ }
+ T![self] if parent_matches::<ast::Name>(&token) => return None,
+ T![ref] => match token.parent().and_then(ast::IdentPat::cast) {
+ Some(ident) if sema.is_unsafe_ident_pat(&ident) => h | HlMod::Unsafe,
+ _ => h,
+ },
+ _ => h,
+ };
+ Some(h)
+}
+
+fn highlight_name_ref(
+ sema: &Semantics<'_, RootDatabase>,
+ krate: hir::Crate,
+ bindings_shadow_count: &mut FxHashMap<hir::Name, u32>,
+ binding_hash: &mut Option<u64>,
+ syntactic_name_ref_highlighting: bool,
+ name_ref: ast::NameRef,
+) -> Highlight {
+ let db = sema.db;
+ if let Some(res) = highlight_method_call_by_name_ref(sema, krate, &name_ref) {
+ return res;
+ }
+
+ let name_class = match NameRefClass::classify(sema, &name_ref) {
+ Some(name_kind) => name_kind,
+ None if syntactic_name_ref_highlighting => {
+ return highlight_name_ref_by_syntax(name_ref, sema, krate)
+ }
+ // FIXME: This is required for helper attributes used by proc-macros, as those do not map down
+ // to anything when used.
+ // We can fix this for derive attributes since derive helpers are recorded, but not for
+ // general attributes.
+ None if name_ref.syntax().ancestors().any(|it| it.kind() == ATTR) => {
+ return HlTag::Symbol(SymbolKind::Attribute).into();
+ }
+ None => return HlTag::UnresolvedReference.into(),
+ };
+ let mut h = match name_class {
+ NameRefClass::Definition(def) => {
+ if let Definition::Local(local) = &def {
+ let name = local.name(db);
+ let shadow_count = bindings_shadow_count.entry(name.clone()).or_default();
+ *binding_hash = Some(calc_binding_hash(&name, *shadow_count))
+ };
+
+ let mut h = highlight_def(sema, krate, def);
+
+ match def {
+ Definition::Local(local) if is_consumed_lvalue(name_ref.syntax(), &local, db) => {
+ h |= HlMod::Consuming;
+ }
+ Definition::Trait(trait_) if trait_.is_unsafe(db) => {
+ if ast::Impl::for_trait_name_ref(&name_ref)
+ .map_or(false, |impl_| impl_.unsafe_token().is_some())
+ {
+ h |= HlMod::Unsafe;
+ }
+ }
+ Definition::Field(field) => {
+ if let Some(parent) = name_ref.syntax().parent() {
+ if matches!(parent.kind(), FIELD_EXPR | RECORD_PAT_FIELD) {
+ if let hir::VariantDef::Union(_) = field.parent_def(db) {
+ h |= HlMod::Unsafe;
+ }
+ }
+ }
+ }
+ 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;
+ }
+ }
+ }
+ _ => (),
+ }
+
+ h
+ }
+ NameRefClass::FieldShorthand { .. } => SymbolKind::Field.into(),
+ };
+
+ h.tag = match name_ref.token_kind() {
+ T![Self] => HlTag::Symbol(SymbolKind::SelfType),
+ T![self] => HlTag::Symbol(SymbolKind::SelfParam),
+ T![super] | T![crate] => HlTag::Keyword,
+ _ => h.tag,
+ };
+ h
+}
+
+fn highlight_name(
+ sema: &Semantics<'_, RootDatabase>,
+ bindings_shadow_count: &mut FxHashMap<hir::Name, u32>,
+ binding_hash: &mut Option<u64>,
+ krate: hir::Crate,
+ name: ast::Name,
+) -> Highlight {
+ let name_kind = NameClass::classify(sema, &name);
+ if let Some(NameClass::Definition(Definition::Local(local))) = &name_kind {
+ let name = local.name(sema.db);
+ let shadow_count = bindings_shadow_count.entry(name.clone()).or_default();
+ *shadow_count += 1;
+ *binding_hash = Some(calc_binding_hash(&name, *shadow_count))
+ };
+ match name_kind {
+ Some(NameClass::Definition(def)) => {
+ let mut h = highlight_def(sema, krate, def) | HlMod::Definition;
+ if let Definition::Trait(trait_) = &def {
+ if trait_.is_unsafe(sema.db) {
+ h |= HlMod::Unsafe;
+ }
+ }
+ h
+ }
+ Some(NameClass::ConstReference(def)) => highlight_def(sema, krate, def),
+ Some(NameClass::PatFieldShorthand { field_ref, .. }) => {
+ let mut h = HlTag::Symbol(SymbolKind::Field).into();
+ if let hir::VariantDef::Union(_) = field_ref.parent_def(sema.db) {
+ h |= HlMod::Unsafe;
+ }
+ h
+ }
+ None => highlight_name_by_syntax(name) | HlMod::Definition,
+ }
+}
+
+fn calc_binding_hash(name: &hir::Name, shadow_count: u32) -> u64 {
+ fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 {
+ use std::{collections::hash_map::DefaultHasher, hash::Hasher};
+
+ let mut hasher = DefaultHasher::new();
+ x.hash(&mut hasher);
+ hasher.finish()
+ }
+
+ hash((name, shadow_count))
+}
+
+fn highlight_def(
+ sema: &Semantics<'_, RootDatabase>,
+ krate: hir::Crate,
+ def: Definition,
+) -> Highlight {
+ let db = sema.db;
+ let mut h = match def {
+ Definition::Macro(m) => Highlight::new(HlTag::Symbol(m.kind(sema.db).into())),
+ Definition::Field(_) => Highlight::new(HlTag::Symbol(SymbolKind::Field)),
+ Definition::Module(module) => {
+ let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Module));
+ if module.is_crate_root(db) {
+ h |= HlMod::CrateRoot;
+ }
+ h
+ }
+ Definition::Function(func) => {
+ let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Function));
+ if let Some(item) = func.as_assoc_item(db) {
+ h |= HlMod::Associated;
+ match func.self_param(db) {
+ Some(sp) => match sp.access(db) {
+ hir::Access::Exclusive => {
+ h |= HlMod::Mutable;
+ h |= HlMod::Reference;
+ }
+ hir::Access::Shared => h |= HlMod::Reference,
+ hir::Access::Owned => h |= HlMod::Consuming,
+ },
+ None => h |= HlMod::Static,
+ }
+
+ match item.container(db) {
+ hir::AssocItemContainer::Impl(i) => {
+ if i.trait_(db).is_some() {
+ h |= HlMod::Trait;
+ }
+ }
+ hir::AssocItemContainer::Trait(_t) => {
+ h |= HlMod::Trait;
+ }
+ }
+ }
+
+ if func.is_unsafe_to_call(db) {
+ h |= HlMod::Unsafe;
+ }
+ if func.is_async(db) {
+ h |= HlMod::Async;
+ }
+
+ h
+ }
+ Definition::Adt(adt) => {
+ let h = match adt {
+ hir::Adt::Struct(_) => HlTag::Symbol(SymbolKind::Struct),
+ hir::Adt::Enum(_) => HlTag::Symbol(SymbolKind::Enum),
+ hir::Adt::Union(_) => HlTag::Symbol(SymbolKind::Union),
+ };
+
+ Highlight::new(h)
+ }
+ Definition::Variant(_) => Highlight::new(HlTag::Symbol(SymbolKind::Variant)),
+ Definition::Const(konst) => {
+ let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Const));
+
+ if let Some(item) = konst.as_assoc_item(db) {
+ h |= HlMod::Associated;
+ match item.container(db) {
+ hir::AssocItemContainer::Impl(i) => {
+ if i.trait_(db).is_some() {
+ h |= HlMod::Trait;
+ }
+ }
+ hir::AssocItemContainer::Trait(_t) => {
+ h |= HlMod::Trait;
+ }
+ }
+ }
+
+ h
+ }
+ Definition::Trait(_) => Highlight::new(HlTag::Symbol(SymbolKind::Trait)),
+ Definition::TypeAlias(type_) => {
+ let mut h = Highlight::new(HlTag::Symbol(SymbolKind::TypeAlias));
+
+ if let Some(item) = type_.as_assoc_item(db) {
+ h |= HlMod::Associated;
+ match item.container(db) {
+ hir::AssocItemContainer::Impl(i) => {
+ if i.trait_(db).is_some() {
+ h |= HlMod::Trait;
+ }
+ }
+ hir::AssocItemContainer::Trait(_t) => {
+ h |= HlMod::Trait;
+ }
+ }
+ }
+
+ h
+ }
+ Definition::BuiltinType(_) => Highlight::new(HlTag::BuiltinType),
+ Definition::Static(s) => {
+ let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Static));
+
+ if s.is_mut(db) {
+ h |= HlMod::Mutable;
+ h |= HlMod::Unsafe;
+ }
+
+ h
+ }
+ Definition::SelfType(_) => Highlight::new(HlTag::Symbol(SymbolKind::Impl)),
+ Definition::GenericParam(it) => match it {
+ hir::GenericParam::TypeParam(_) => Highlight::new(HlTag::Symbol(SymbolKind::TypeParam)),
+ hir::GenericParam::ConstParam(_) => {
+ Highlight::new(HlTag::Symbol(SymbolKind::ConstParam))
+ }
+ hir::GenericParam::LifetimeParam(_) => {
+ Highlight::new(HlTag::Symbol(SymbolKind::LifetimeParam))
+ }
+ },
+ Definition::Local(local) => {
+ let tag = if local.is_self(db) {
+ HlTag::Symbol(SymbolKind::SelfParam)
+ } else if local.is_param(db) {
+ HlTag::Symbol(SymbolKind::ValueParam)
+ } else {
+ HlTag::Symbol(SymbolKind::Local)
+ };
+ let mut h = Highlight::new(tag);
+ let ty = local.ty(db);
+ if local.is_mut(db) || ty.is_mutable_reference() {
+ h |= HlMod::Mutable;
+ }
+ if local.is_ref(db) || ty.is_reference() {
+ h |= HlMod::Reference;
+ }
+ if ty.as_callable(db).is_some() || ty.impls_fnonce(db) {
+ h |= HlMod::Callable;
+ }
+ h
+ }
+ Definition::Label(_) => Highlight::new(HlTag::Symbol(SymbolKind::Label)),
+ Definition::BuiltinAttr(_) => Highlight::new(HlTag::Symbol(SymbolKind::BuiltinAttr)),
+ Definition::ToolModule(_) => Highlight::new(HlTag::Symbol(SymbolKind::ToolModule)),
+ Definition::DeriveHelper(_) => Highlight::new(HlTag::Symbol(SymbolKind::DeriveHelper)),
+ };
+
+ let def_crate = def.krate(db);
+ let is_from_other_crate = def_crate != Some(krate);
+ let is_from_builtin_crate = def_crate.map_or(false, |def_crate| def_crate.is_builtin(db));
+ let is_builtin_type = matches!(def, Definition::BuiltinType(_));
+ let is_public = def.visibility(db) == Some(hir::Visibility::Public);
+
+ match (is_from_other_crate, is_builtin_type, is_public) {
+ (true, false, _) => h |= HlMod::Library,
+ (false, _, true) => h |= HlMod::Public,
+ _ => {}
+ }
+
+ if is_from_builtin_crate {
+ h |= HlMod::DefaultLibrary;
+ }
+
+ h
+}
+
+fn highlight_method_call_by_name_ref(
+ sema: &Semantics<'_, RootDatabase>,
+ krate: hir::Crate,
+ name_ref: &ast::NameRef,
+) -> Option<Highlight> {
+ let mc = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast)?;
+ highlight_method_call(sema, krate, &mc)
+}
+
+fn highlight_method_call(
+ sema: &Semantics<'_, RootDatabase>,
+ krate: hir::Crate,
+ method_call: &ast::MethodCallExpr,
+) -> Option<Highlight> {
+ let func = sema.resolve_method_call(method_call)?;
+
+ let mut h = SymbolKind::Function.into();
+ h |= HlMod::Associated;
+
+ if func.is_unsafe_to_call(sema.db) || sema.is_unsafe_method_call(method_call) {
+ h |= HlMod::Unsafe;
+ }
+ if func.is_async(sema.db) {
+ h |= HlMod::Async;
+ }
+ if func
+ .as_assoc_item(sema.db)
+ .and_then(|it| it.containing_trait_or_trait_impl(sema.db))
+ .is_some()
+ {
+ h |= HlMod::Trait;
+ }
+
+ let def_crate = func.module(sema.db).krate();
+ let is_from_other_crate = def_crate != krate;
+ let is_from_builtin_crate = def_crate.is_builtin(sema.db);
+ let is_public = func.visibility(sema.db) == hir::Visibility::Public;
+
+ if is_from_other_crate {
+ h |= HlMod::Library;
+ } else if is_public {
+ h |= HlMod::Public;
+ }
+
+ if is_from_builtin_crate {
+ h |= HlMod::DefaultLibrary;
+ }
+
+ if let Some(self_param) = func.self_param(sema.db) {
+ match self_param.access(sema.db) {
+ hir::Access::Shared => h |= HlMod::Reference,
+ hir::Access::Exclusive => {
+ h |= HlMod::Mutable;
+ h |= HlMod::Reference;
+ }
+ hir::Access::Owned => {
+ if let Some(receiver_ty) =
+ method_call.receiver().and_then(|it| sema.type_of_expr(&it))
+ {
+ if !receiver_ty.adjusted().is_copy(sema.db) {
+ h |= HlMod::Consuming
+ }
+ }
+ }
+ }
+ }
+ Some(h)
+}
+
+fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
+ let default = HlTag::UnresolvedReference;
+
+ let parent = match name.syntax().parent() {
+ Some(it) => it,
+ _ => return default.into(),
+ };
+
+ let tag = match parent.kind() {
+ STRUCT => SymbolKind::Struct,
+ ENUM => SymbolKind::Enum,
+ VARIANT => SymbolKind::Variant,
+ UNION => SymbolKind::Union,
+ TRAIT => SymbolKind::Trait,
+ TYPE_ALIAS => SymbolKind::TypeAlias,
+ TYPE_PARAM => SymbolKind::TypeParam,
+ RECORD_FIELD => SymbolKind::Field,
+ MODULE => SymbolKind::Module,
+ FN => SymbolKind::Function,
+ CONST => SymbolKind::Const,
+ STATIC => SymbolKind::Static,
+ IDENT_PAT => SymbolKind::Local,
+ _ => return default.into(),
+ };
+
+ tag.into()
+}
+
+fn highlight_name_ref_by_syntax(
+ name: ast::NameRef,
+ sema: &Semantics<'_, RootDatabase>,
+ krate: hir::Crate,
+) -> Highlight {
+ let default = HlTag::UnresolvedReference;
+
+ let parent = match name.syntax().parent() {
+ Some(it) => it,
+ _ => return default.into(),
+ };
+
+ match parent.kind() {
+ METHOD_CALL_EXPR => ast::MethodCallExpr::cast(parent)
+ .and_then(|it| highlight_method_call(sema, krate, &it))
+ .unwrap_or_else(|| SymbolKind::Function.into()),
+ FIELD_EXPR => {
+ let h = HlTag::Symbol(SymbolKind::Field);
+ let is_union = ast::FieldExpr::cast(parent)
+ .and_then(|field_expr| sema.resolve_field(&field_expr))
+ .map_or(false, |field| {
+ matches!(field.parent_def(sema.db), hir::VariantDef::Union(_))
+ });
+ if is_union {
+ h | HlMod::Unsafe
+ } else {
+ h.into()
+ }
+ }
+ PATH_SEGMENT => {
+ let name_based_fallback = || {
+ if name.text().chars().next().unwrap_or_default().is_uppercase() {
+ SymbolKind::Struct.into()
+ } else {
+ SymbolKind::Module.into()
+ }
+ };
+ let path = match parent.parent().and_then(ast::Path::cast) {
+ Some(it) => it,
+ _ => return name_based_fallback(),
+ };
+ let expr = match path.syntax().parent() {
+ Some(parent) => match_ast! {
+ match parent {
+ ast::PathExpr(path) => path,
+ ast::MacroCall(_) => return SymbolKind::Macro.into(),
+ _ => return name_based_fallback(),
+ }
+ },
+ // within path, decide whether it is module or adt by checking for uppercase name
+ None => return name_based_fallback(),
+ };
+ let parent = match expr.syntax().parent() {
+ Some(it) => it,
+ None => return default.into(),
+ };
+
+ match parent.kind() {
+ CALL_EXPR => SymbolKind::Function.into(),
+ _ => if name.text().chars().next().unwrap_or_default().is_uppercase() {
+ SymbolKind::Struct
+ } else {
+ SymbolKind::Const
+ }
+ .into(),
+ }
+ }
+ _ => default.into(),
+ }
+}
+
+fn is_consumed_lvalue(node: &SyntaxNode, local: &hir::Local, db: &RootDatabase) -> bool {
+ // When lvalues are passed as arguments and they're not Copy, then mark them as Consuming.
+ parents_match(node.clone().into(), &[PATH_SEGMENT, PATH, PATH_EXPR, ARG_LIST])
+ && !local.ty(db).is_copy(db)
+}
+
+/// Returns true if the parent nodes of `node` all match the `SyntaxKind`s in `kinds` exactly.
+fn parents_match(mut node: NodeOrToken<SyntaxNode, SyntaxToken>, mut kinds: &[SyntaxKind]) -> bool {
+ while let (Some(parent), [kind, rest @ ..]) = (&node.parent(), kinds) {
+ if parent.kind() != *kind {
+ return false;
+ }
+
+ // FIXME: Would be nice to get parent out of the match, but binding by-move and by-value
+ // in the same pattern is unstable: rust-lang/rust#68354.
+ node = node.parent().unwrap().into();
+ kinds = rest;
+ }
+
+ // Only true if we matched all expected kinds
+ kinds.is_empty()
+}
+
+fn parent_matches<N: AstNode>(token: &SyntaxToken) -> bool {
+ token.parent().map_or(false, |it| N::can_cast(it.kind()))
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlights.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlights.rs
new file mode 100644
index 000000000..340290eaf
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlights.rs
@@ -0,0 +1,92 @@
+//! Collects a tree of highlighted ranges and flattens it.
+use std::iter;
+
+use stdx::equal_range_by;
+use syntax::TextRange;
+
+use crate::{HlRange, HlTag};
+
+pub(super) struct Highlights {
+ root: Node,
+}
+
+struct Node {
+ hl_range: HlRange,
+ nested: Vec<Node>,
+}
+
+impl Highlights {
+ pub(super) fn new(range: TextRange) -> Highlights {
+ Highlights {
+ root: Node::new(HlRange { range, highlight: HlTag::None.into(), binding_hash: None }),
+ }
+ }
+
+ pub(super) fn add(&mut self, hl_range: HlRange) {
+ self.root.add(hl_range);
+ }
+
+ pub(super) fn to_vec(&self) -> Vec<HlRange> {
+ let mut res = Vec::new();
+ self.root.flatten(&mut res);
+ res
+ }
+}
+
+impl Node {
+ fn new(hl_range: HlRange) -> Node {
+ Node { hl_range, nested: Vec::new() }
+ }
+
+ fn add(&mut self, hl_range: HlRange) {
+ assert!(self.hl_range.range.contains_range(hl_range.range));
+
+ // Fast path
+ if let Some(last) = self.nested.last_mut() {
+ if last.hl_range.range.contains_range(hl_range.range) {
+ return last.add(hl_range);
+ }
+ if last.hl_range.range.end() <= hl_range.range.start() {
+ return self.nested.push(Node::new(hl_range));
+ }
+ }
+
+ let overlapping =
+ equal_range_by(&self.nested, |n| TextRange::ordering(n.hl_range.range, hl_range.range));
+
+ if overlapping.len() == 1
+ && self.nested[overlapping.start].hl_range.range.contains_range(hl_range.range)
+ {
+ return self.nested[overlapping.start].add(hl_range);
+ }
+
+ let nested = self
+ .nested
+ .splice(overlapping.clone(), iter::once(Node::new(hl_range)))
+ .collect::<Vec<_>>();
+ self.nested[overlapping.start].nested = nested;
+ }
+
+ fn flatten(&self, acc: &mut Vec<HlRange>) {
+ let mut start = self.hl_range.range.start();
+ let mut nested = self.nested.iter();
+ loop {
+ let next = nested.next();
+ let end = next.map_or(self.hl_range.range.end(), |it| it.hl_range.range.start());
+ if start < end {
+ acc.push(HlRange {
+ range: TextRange::new(start, end),
+ highlight: self.hl_range.highlight,
+ binding_hash: self.hl_range.binding_hash,
+ });
+ }
+ start = match next {
+ Some(child) => {
+ child.flatten(acc);
+ child.hl_range.range.end()
+ }
+ None => break,
+ }
+ }
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/html.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/html.rs
new file mode 100644
index 000000000..9777c014c
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/html.rs
@@ -0,0 +1,97 @@
+//! Renders a bit of code as HTML.
+
+use ide_db::base_db::SourceDatabase;
+use oorandom::Rand32;
+use stdx::format_to;
+use syntax::AstNode;
+
+use crate::{syntax_highlighting::highlight, FileId, RootDatabase};
+
+pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: bool) -> String {
+ let parse = db.parse(file_id);
+
+ fn rainbowify(seed: u64) -> String {
+ let mut rng = Rand32::new(seed);
+ format!(
+ "hsl({h},{s}%,{l}%)",
+ h = rng.rand_range(0..361),
+ s = rng.rand_range(42..99),
+ l = rng.rand_range(40..91),
+ )
+ }
+
+ let hl_ranges = highlight(db, file_id, None, false);
+ let text = parse.tree().syntax().to_string();
+ let mut buf = String::new();
+ buf.push_str(STYLE);
+ buf.push_str("<pre><code>");
+ for r in &hl_ranges {
+ let chunk = html_escape(&text[r.range]);
+ if r.highlight.is_empty() {
+ format_to!(buf, "{}", chunk);
+ continue;
+ }
+
+ let class = r.highlight.to_string().replace('.', " ");
+ let color = match (rainbow, r.binding_hash) {
+ (true, Some(hash)) => {
+ format!(" data-binding-hash=\"{}\" style=\"color: {};\"", hash, rainbowify(hash))
+ }
+ _ => "".into(),
+ };
+ format_to!(buf, "<span class=\"{}\"{}>{}</span>", class, color, chunk);
+ }
+ buf.push_str("</code></pre>");
+ buf
+}
+
+//FIXME: like, real html escaping
+fn html_escape(text: &str) -> String {
+ text.replace('<', "&lt;").replace('>', "&gt;")
+}
+
+const STYLE: &str = "
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.function.unsafe { color: #BC8383; }
+.trait.unsafe { color: #BC8383; }
+.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; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+";
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs
new file mode 100644
index 000000000..f376f9fda
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs
@@ -0,0 +1,279 @@
+//! "Recursive" Syntax highlighting for code in doctests and fixtures.
+
+use std::mem;
+
+use either::Either;
+use hir::{InFile, Semantics};
+use ide_db::{
+ active_parameter::ActiveParameter, base_db::FileId, defs::Definition, rust_doc::is_rust_fence,
+ SymbolKind,
+};
+use syntax::{
+ ast::{self, AstNode, IsString, QuoteOffsets},
+ AstToken, NodeOrToken, SyntaxNode, TextRange, TextSize,
+};
+
+use crate::{
+ doc_links::{doc_attributes, extract_definitions_from_docs, resolve_doc_path_for_def},
+ syntax_highlighting::{highlights::Highlights, injector::Injector},
+ Analysis, HlMod, HlRange, HlTag, RootDatabase,
+};
+
+pub(super) fn ra_fixture(
+ hl: &mut Highlights,
+ sema: &Semantics<'_, RootDatabase>,
+ literal: &ast::String,
+ expanded: &ast::String,
+) -> Option<()> {
+ let active_parameter = ActiveParameter::at_token(sema, expanded.syntax().clone())?;
+ if !active_parameter.ident().map_or(false, |name| name.text().starts_with("ra_fixture")) {
+ return None;
+ }
+ let value = literal.value()?;
+
+ if let Some(range) = literal.open_quote_text_range() {
+ hl.add(HlRange { range, highlight: HlTag::StringLiteral.into(), binding_hash: None })
+ }
+
+ let mut inj = Injector::default();
+
+ let mut text = &*value;
+ let mut offset: TextSize = 0.into();
+
+ while !text.is_empty() {
+ let marker = "$0";
+ let idx = text.find(marker).unwrap_or(text.len());
+ let (chunk, next) = text.split_at(idx);
+ inj.add(chunk, TextRange::at(offset, TextSize::of(chunk)));
+
+ text = next;
+ offset += TextSize::of(chunk);
+
+ if let Some(next) = text.strip_prefix(marker) {
+ if let Some(range) = literal.map_range_up(TextRange::at(offset, TextSize::of(marker))) {
+ hl.add(HlRange { range, highlight: HlTag::Keyword.into(), binding_hash: None });
+ }
+
+ text = next;
+
+ let marker_len = TextSize::of(marker);
+ offset += marker_len;
+ }
+ }
+
+ let (analysis, tmp_file_id) = Analysis::from_single_file(inj.take_text());
+
+ for mut hl_range in analysis.highlight(tmp_file_id).unwrap() {
+ for range in inj.map_range_up(hl_range.range) {
+ if let Some(range) = literal.map_range_up(range) {
+ hl_range.range = range;
+ hl.add(hl_range);
+ }
+ }
+ }
+
+ if let Some(range) = literal.close_quote_text_range() {
+ hl.add(HlRange { range, highlight: HlTag::StringLiteral.into(), binding_hash: None })
+ }
+
+ Some(())
+}
+
+const RUSTDOC_FENCE_LENGTH: usize = 3;
+const RUSTDOC_FENCES: [&str; 2] = ["```", "~~~"];
+
+/// Injection of syntax highlighting of doctests and intra doc links.
+pub(super) fn doc_comment(
+ hl: &mut Highlights,
+ sema: &Semantics<'_, RootDatabase>,
+ src_file_id: FileId,
+ node: &SyntaxNode,
+) {
+ let (attributes, def) = match doc_attributes(sema, node) {
+ Some(it) => it,
+ None => return,
+ };
+ let src_file_id = src_file_id.into();
+
+ // Extract intra-doc links and emit highlights for them.
+ if let Some((docs, doc_mapping)) = attributes.docs_with_rangemap(sema.db) {
+ extract_definitions_from_docs(&docs)
+ .into_iter()
+ .filter_map(|(range, link, ns)| {
+ doc_mapping.map(range).filter(|mapping| mapping.file_id == src_file_id).and_then(
+ |InFile { value: mapped_range, .. }| {
+ Some(mapped_range).zip(resolve_doc_path_for_def(sema.db, def, &link, ns))
+ },
+ )
+ })
+ .for_each(|(range, def)| {
+ hl.add(HlRange {
+ range,
+ highlight: module_def_to_hl_tag(def)
+ | HlMod::Documentation
+ | HlMod::Injected
+ | HlMod::IntraDocLink,
+ binding_hash: None,
+ })
+ });
+ }
+
+ // Extract doc-test sources from the docs and calculate highlighting for them.
+
+ let mut inj = Injector::default();
+ inj.add_unmapped("fn doctest() {\n");
+
+ let attrs_source_map = attributes.source_map(sema.db);
+
+ let mut is_codeblock = false;
+ let mut is_doctest = false;
+
+ let mut new_comments = Vec::new();
+ let mut string;
+
+ for attr in attributes.by_key("doc").attrs() {
+ let InFile { file_id, value: src } = attrs_source_map.source_of(attr);
+ if file_id != src_file_id {
+ continue;
+ }
+ let (line, range) = match &src {
+ Either::Left(it) => {
+ string = match find_doc_string_in_attr(attr, it) {
+ Some(it) => it,
+ None => continue,
+ };
+ let text = string.text();
+ let text_range = string.syntax().text_range();
+ match string.quote_offsets() {
+ Some(QuoteOffsets { contents, .. }) => {
+ (&text[contents - text_range.start()], contents)
+ }
+ None => (text, text_range),
+ }
+ }
+ Either::Right(comment) => {
+ let value = comment.prefix().len();
+ let range = comment.syntax().text_range();
+ (
+ &comment.text()[value..],
+ TextRange::new(range.start() + TextSize::try_from(value).unwrap(), range.end()),
+ )
+ }
+ };
+
+ let mut range_start = range.start();
+ for line in line.split('\n') {
+ let line_len = TextSize::from(line.len() as u32);
+ let prev_range_start = {
+ let next_range_start = range_start + line_len + TextSize::from(1);
+ mem::replace(&mut range_start, next_range_start)
+ };
+ let mut pos = TextSize::from(0);
+
+ match RUSTDOC_FENCES.into_iter().find_map(|fence| line.find(fence)) {
+ Some(idx) => {
+ is_codeblock = !is_codeblock;
+ // Check whether code is rust by inspecting fence guards
+ let guards = &line[idx + RUSTDOC_FENCE_LENGTH..];
+ let is_rust = is_rust_fence(guards);
+ is_doctest = is_codeblock && is_rust;
+ continue;
+ }
+ None if !is_doctest => continue,
+ None => (),
+ }
+
+ // whitespace after comment is ignored
+ if let Some(ws) = line[pos.into()..].chars().next().filter(|c| c.is_whitespace()) {
+ pos += TextSize::of(ws);
+ }
+ // lines marked with `#` should be ignored in output, we skip the `#` char
+ if line[pos.into()..].starts_with('#') {
+ pos += TextSize::of('#');
+ }
+
+ new_comments.push(TextRange::at(prev_range_start, pos));
+ inj.add(&line[pos.into()..], TextRange::new(pos, line_len) + prev_range_start);
+ inj.add_unmapped("\n");
+ }
+ }
+
+ if new_comments.is_empty() {
+ return; // no need to run an analysis on an empty file
+ }
+
+ inj.add_unmapped("\n}");
+
+ let (analysis, tmp_file_id) = Analysis::from_single_file(inj.take_text());
+
+ if let Ok(ranges) = analysis.with_db(|db| super::highlight(db, tmp_file_id, None, true)) {
+ for HlRange { range, highlight, binding_hash } in ranges {
+ for range in inj.map_range_up(range) {
+ hl.add(HlRange { range, highlight: highlight | HlMod::Injected, binding_hash });
+ }
+ }
+ }
+
+ for range in new_comments {
+ hl.add(HlRange {
+ range,
+ highlight: HlTag::Comment | HlMod::Documentation,
+ binding_hash: None,
+ });
+ }
+}
+
+fn find_doc_string_in_attr(attr: &hir::Attr, it: &ast::Attr) -> Option<ast::String> {
+ match it.expr() {
+ // #[doc = lit]
+ Some(ast::Expr::Literal(lit)) => match lit.kind() {
+ ast::LiteralKind::String(it) => Some(it),
+ _ => None,
+ },
+ // #[cfg_attr(..., doc = "", ...)]
+ None => {
+ // We gotta hunt the string token manually here
+ let text = attr.string_value()?;
+ // FIXME: We just pick the first string literal that has the same text as the doc attribute
+ // This means technically we might highlight the wrong one
+ it.syntax()
+ .descendants_with_tokens()
+ .filter_map(NodeOrToken::into_token)
+ .filter_map(ast::String::cast)
+ .find(|string| {
+ string.text().get(1..string.text().len() - 1).map_or(false, |it| it == text)
+ })
+ }
+ _ => None,
+ }
+}
+
+fn module_def_to_hl_tag(def: Definition) -> HlTag {
+ let symbol = match def {
+ Definition::Module(_) => SymbolKind::Module,
+ Definition::Function(_) => SymbolKind::Function,
+ Definition::Adt(hir::Adt::Struct(_)) => SymbolKind::Struct,
+ Definition::Adt(hir::Adt::Enum(_)) => SymbolKind::Enum,
+ Definition::Adt(hir::Adt::Union(_)) => SymbolKind::Union,
+ Definition::Variant(_) => SymbolKind::Variant,
+ Definition::Const(_) => SymbolKind::Const,
+ Definition::Static(_) => SymbolKind::Static,
+ Definition::Trait(_) => SymbolKind::Trait,
+ Definition::TypeAlias(_) => SymbolKind::TypeAlias,
+ Definition::BuiltinType(_) => return HlTag::BuiltinType,
+ Definition::Macro(_) => SymbolKind::Macro,
+ Definition::Field(_) => SymbolKind::Field,
+ Definition::SelfType(_) => SymbolKind::Impl,
+ Definition::Local(_) => SymbolKind::Local,
+ Definition::GenericParam(gp) => match gp {
+ hir::GenericParam::TypeParam(_) => SymbolKind::TypeParam,
+ hir::GenericParam::ConstParam(_) => SymbolKind::ConstParam,
+ hir::GenericParam::LifetimeParam(_) => SymbolKind::LifetimeParam,
+ },
+ Definition::Label(_) => SymbolKind::Label,
+ Definition::BuiltinAttr(_) => SymbolKind::BuiltinAttr,
+ Definition::ToolModule(_) => SymbolKind::ToolModule,
+ Definition::DeriveHelper(_) => SymbolKind::DeriveHelper,
+ };
+ HlTag::Symbol(symbol)
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/injector.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/injector.rs
new file mode 100644
index 000000000..a902fd717
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/injector.rs
@@ -0,0 +1,81 @@
+//! Extracts a subsequence of a text document, remembering the mapping of ranges
+//! between original and extracted texts.
+use std::ops::{self, Sub};
+
+use stdx::equal_range_by;
+use syntax::{TextRange, TextSize};
+
+#[derive(Default)]
+pub(super) struct Injector {
+ buf: String,
+ ranges: Vec<(TextRange, Option<Delta<TextSize>>)>,
+}
+
+impl Injector {
+ pub(super) fn add(&mut self, text: &str, source_range: TextRange) {
+ let len = TextSize::of(text);
+ assert_eq!(len, source_range.len());
+ self.add_impl(text, Some(source_range.start()));
+ }
+
+ pub(super) fn add_unmapped(&mut self, text: &str) {
+ self.add_impl(text, None);
+ }
+
+ fn add_impl(&mut self, text: &str, source: Option<TextSize>) {
+ let len = TextSize::of(text);
+ let target_range = TextRange::at(TextSize::of(&self.buf), len);
+ self.ranges.push((target_range, source.map(|it| Delta::new(target_range.start(), it))));
+ self.buf.push_str(text);
+ }
+
+ pub(super) fn take_text(&mut self) -> String {
+ std::mem::take(&mut self.buf)
+ }
+
+ pub(super) fn map_range_up(&self, range: TextRange) -> impl Iterator<Item = TextRange> + '_ {
+ equal_range_by(&self.ranges, |&(r, _)| TextRange::ordering(r, range)).filter_map(move |i| {
+ let (target_range, delta) = self.ranges[i];
+ let intersection = target_range.intersect(range).unwrap();
+ Some(intersection + delta?)
+ })
+ }
+}
+
+#[derive(Clone, Copy)]
+enum Delta<T> {
+ Add(T),
+ Sub(T),
+}
+
+impl<T> Delta<T> {
+ fn new(from: T, to: T) -> Delta<T>
+ where
+ T: Ord + Sub<Output = T>,
+ {
+ if to >= from {
+ Delta::Add(to - from)
+ } else {
+ Delta::Sub(from - to)
+ }
+ }
+}
+
+impl ops::Add<Delta<TextSize>> for TextSize {
+ type Output = TextSize;
+
+ fn add(self, rhs: Delta<TextSize>) -> TextSize {
+ match rhs {
+ Delta::Add(it) => self + it,
+ Delta::Sub(it) => self - it,
+ }
+ }
+}
+
+impl ops::Add<Delta<TextSize>> for TextRange {
+ type Output = TextRange;
+
+ fn add(self, rhs: Delta<TextSize>) -> TextRange {
+ TextRange::at(self.start() + rhs, self.len())
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/macro_.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/macro_.rs
new file mode 100644
index 000000000..1099d9c23
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/macro_.rs
@@ -0,0 +1,128 @@
+//! Syntax highlighting for macro_rules!.
+use syntax::{SyntaxKind, SyntaxToken, TextRange, T};
+
+use crate::{HlRange, HlTag};
+
+#[derive(Default)]
+pub(super) struct MacroHighlighter {
+ state: Option<MacroMatcherParseState>,
+}
+
+impl MacroHighlighter {
+ pub(super) fn init(&mut self) {
+ self.state = Some(MacroMatcherParseState::default());
+ }
+
+ pub(super) fn advance(&mut self, token: &SyntaxToken) {
+ if let Some(state) = self.state.as_mut() {
+ update_macro_state(state, token);
+ }
+ }
+
+ pub(super) fn highlight(&self, token: &SyntaxToken) -> Option<HlRange> {
+ if let Some(state) = self.state.as_ref() {
+ if matches!(state.rule_state, RuleState::Matcher | RuleState::Expander) {
+ if let Some(range) = is_metavariable(token) {
+ return Some(HlRange {
+ range,
+ highlight: HlTag::UnresolvedReference.into(),
+ binding_hash: None,
+ });
+ }
+ }
+ }
+ None
+ }
+}
+
+struct MacroMatcherParseState {
+ /// Opening and corresponding closing bracket of the matcher or expander of the current rule
+ paren_ty: Option<(SyntaxKind, SyntaxKind)>,
+ paren_level: usize,
+ rule_state: RuleState,
+ /// Whether we are inside the outer `{` `}` macro block that holds the rules
+ in_invoc_body: bool,
+}
+
+impl Default for MacroMatcherParseState {
+ fn default() -> Self {
+ MacroMatcherParseState {
+ paren_ty: None,
+ paren_level: 0,
+ in_invoc_body: false,
+ rule_state: RuleState::None,
+ }
+ }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+enum RuleState {
+ Matcher,
+ Expander,
+ Between,
+ None,
+}
+
+impl RuleState {
+ fn transition(&mut self) {
+ *self = match self {
+ RuleState::Matcher => RuleState::Between,
+ RuleState::Expander => RuleState::None,
+ RuleState::Between => RuleState::Expander,
+ RuleState::None => RuleState::Matcher,
+ };
+ }
+}
+
+fn update_macro_state(state: &mut MacroMatcherParseState, tok: &SyntaxToken) {
+ if !state.in_invoc_body {
+ if tok.kind() == T!['{'] || tok.kind() == T!['('] {
+ state.in_invoc_body = true;
+ }
+ return;
+ }
+
+ match state.paren_ty {
+ Some((open, close)) => {
+ if tok.kind() == open {
+ state.paren_level += 1;
+ } else if tok.kind() == close {
+ state.paren_level -= 1;
+ if state.paren_level == 0 {
+ state.rule_state.transition();
+ state.paren_ty = None;
+ }
+ }
+ }
+ None => {
+ match tok.kind() {
+ T!['('] => {
+ state.paren_ty = Some((T!['('], T![')']));
+ }
+ T!['{'] => {
+ state.paren_ty = Some((T!['{'], T!['}']));
+ }
+ T!['['] => {
+ state.paren_ty = Some((T!['['], T![']']));
+ }
+ _ => (),
+ }
+ if state.paren_ty.is_some() {
+ state.paren_level = 1;
+ state.rule_state.transition();
+ }
+ }
+ }
+}
+
+fn is_metavariable(token: &SyntaxToken) -> Option<TextRange> {
+ match token.kind() {
+ kind if kind == SyntaxKind::IDENT || kind.is_keyword() => {
+ if let Some(_dollar) = token.prev_token().filter(|t| t.kind() == T![$]) {
+ return Some(token.text_range());
+ }
+ }
+ _ => (),
+ };
+ None
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tags.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tags.rs
new file mode 100644
index 000000000..5262770f3
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tags.rs
@@ -0,0 +1,340 @@
+//! Defines token tags we use for syntax highlighting.
+//! A tag is not unlike a CSS class.
+
+use std::{
+ fmt::{self, Write},
+ ops,
+};
+
+use ide_db::SymbolKind;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct Highlight {
+ pub tag: HlTag,
+ pub mods: HlMods,
+}
+
+#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct HlMods(u32);
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub enum HlTag {
+ Symbol(SymbolKind),
+
+ AttributeBracket,
+ BoolLiteral,
+ BuiltinType,
+ ByteLiteral,
+ CharLiteral,
+ Comment,
+ EscapeSequence,
+ FormatSpecifier,
+ Keyword,
+ NumericLiteral,
+ Operator(HlOperator),
+ Punctuation(HlPunct),
+ StringLiteral,
+ UnresolvedReference,
+
+ // For things which don't have a specific highlight.
+ None,
+}
+
+// Don't forget to adjust the feature description in crates/ide/src/syntax_highlighting.rs.
+// And make sure to use the lsp strings used when converting to the protocol in crates\rust-analyzer\src\semantic_tokens.rs, not the names of the variants here.
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+#[repr(u8)]
+pub enum HlMod {
+ /// Used for items in traits and impls.
+ Associated = 0,
+ /// Used with keywords like `async` and `await`.
+ Async,
+ /// Used to differentiate individual elements within attributes.
+ Attribute,
+ /// Callable item or value.
+ Callable,
+ /// Value that is being consumed in a function call
+ Consuming,
+ /// Used with keywords like `if` and `break`.
+ ControlFlow,
+ /// Used for crate names, like `serde`.
+ CrateRoot,
+ /// Used for items from built-in crates (std, core, alloc, test and proc_macro).
+ DefaultLibrary,
+ /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is
+ /// not.
+ Definition,
+ /// Doc-strings like this one.
+ Documentation,
+ /// Highlighting injection like rust code in doc strings or ra_fixture.
+ Injected,
+ /// Used for intra doc links in doc injection.
+ IntraDocLink,
+ /// Used for items from other crates.
+ Library,
+ /// Mutable binding.
+ Mutable,
+ /// Used for public items.
+ Public,
+ /// Immutable reference.
+ Reference,
+ /// Used for associated functions.
+ Static,
+ /// Used for items in traits and trait impls.
+ Trait,
+ // Keep this last!
+ /// Used for unsafe functions, unsafe traits, mutable statics, union accesses and unsafe operations.
+ Unsafe,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub enum HlPunct {
+ /// []
+ Bracket,
+ /// {}
+ Brace,
+ /// ()
+ Parenthesis,
+ /// <>
+ Angle,
+ /// ,
+ Comma,
+ /// .
+ Dot,
+ /// :
+ Colon,
+ /// ;
+ Semi,
+ /// ! (only for macro calls)
+ MacroBang,
+ ///
+ Other,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub enum HlOperator {
+ /// |, &, !, ^, |=, &=, ^=
+ Bitwise,
+ /// +, -, *, /, +=, -=, *=, /=
+ Arithmetic,
+ /// &&, ||, !
+ Logical,
+ /// >, <, ==, >=, <=, !=
+ Comparison,
+ ///
+ Other,
+}
+
+impl HlTag {
+ fn as_str(self) -> &'static str {
+ match self {
+ HlTag::Symbol(symbol) => match symbol {
+ SymbolKind::Attribute => "attribute",
+ SymbolKind::BuiltinAttr => "builtin_attr",
+ SymbolKind::Const => "constant",
+ SymbolKind::ConstParam => "const_param",
+ SymbolKind::Derive => "derive",
+ SymbolKind::DeriveHelper => "derive_helper",
+ SymbolKind::Enum => "enum",
+ SymbolKind::Field => "field",
+ SymbolKind::Function => "function",
+ SymbolKind::Impl => "self_type",
+ SymbolKind::Label => "label",
+ SymbolKind::LifetimeParam => "lifetime",
+ SymbolKind::Local => "variable",
+ SymbolKind::Macro => "macro",
+ SymbolKind::Module => "module",
+ SymbolKind::SelfParam => "self_keyword",
+ SymbolKind::SelfType => "self_type_keyword",
+ SymbolKind::Static => "static",
+ SymbolKind::Struct => "struct",
+ SymbolKind::ToolModule => "tool_module",
+ SymbolKind::Trait => "trait",
+ SymbolKind::TypeAlias => "type_alias",
+ SymbolKind::TypeParam => "type_param",
+ SymbolKind::Union => "union",
+ SymbolKind::ValueParam => "value_param",
+ SymbolKind::Variant => "enum_variant",
+ },
+ HlTag::AttributeBracket => "attribute_bracket",
+ HlTag::BoolLiteral => "bool_literal",
+ HlTag::BuiltinType => "builtin_type",
+ HlTag::ByteLiteral => "byte_literal",
+ HlTag::CharLiteral => "char_literal",
+ HlTag::Comment => "comment",
+ HlTag::EscapeSequence => "escape_sequence",
+ HlTag::FormatSpecifier => "format_specifier",
+ HlTag::Keyword => "keyword",
+ HlTag::Punctuation(punct) => match punct {
+ HlPunct::Bracket => "bracket",
+ HlPunct::Brace => "brace",
+ HlPunct::Parenthesis => "parenthesis",
+ HlPunct::Angle => "angle",
+ HlPunct::Comma => "comma",
+ HlPunct::Dot => "dot",
+ HlPunct::Colon => "colon",
+ HlPunct::Semi => "semicolon",
+ HlPunct::MacroBang => "macro_bang",
+ HlPunct::Other => "punctuation",
+ },
+ HlTag::NumericLiteral => "numeric_literal",
+ HlTag::Operator(op) => match op {
+ HlOperator::Bitwise => "bitwise",
+ HlOperator::Arithmetic => "arithmetic",
+ HlOperator::Logical => "logical",
+ HlOperator::Comparison => "comparison",
+ HlOperator::Other => "operator",
+ },
+ HlTag::StringLiteral => "string_literal",
+ HlTag::UnresolvedReference => "unresolved_reference",
+ HlTag::None => "none",
+ }
+ }
+}
+
+impl fmt::Display for HlTag {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Display::fmt(self.as_str(), f)
+ }
+}
+
+impl HlMod {
+ const ALL: &'static [HlMod; HlMod::Unsafe as u8 as usize + 1] = &[
+ HlMod::Associated,
+ HlMod::Async,
+ HlMod::Attribute,
+ HlMod::Callable,
+ HlMod::Consuming,
+ HlMod::ControlFlow,
+ HlMod::CrateRoot,
+ HlMod::DefaultLibrary,
+ HlMod::Definition,
+ HlMod::Documentation,
+ HlMod::Injected,
+ HlMod::IntraDocLink,
+ HlMod::Library,
+ HlMod::Mutable,
+ HlMod::Public,
+ HlMod::Reference,
+ HlMod::Static,
+ HlMod::Trait,
+ HlMod::Unsafe,
+ ];
+
+ fn as_str(self) -> &'static str {
+ match self {
+ HlMod::Associated => "associated",
+ HlMod::Async => "async",
+ HlMod::Attribute => "attribute",
+ HlMod::Callable => "callable",
+ HlMod::Consuming => "consuming",
+ HlMod::ControlFlow => "control",
+ HlMod::CrateRoot => "crate_root",
+ HlMod::DefaultLibrary => "default_library",
+ HlMod::Definition => "declaration",
+ HlMod::Documentation => "documentation",
+ HlMod::Injected => "injected",
+ HlMod::IntraDocLink => "intra_doc_link",
+ HlMod::Library => "library",
+ HlMod::Mutable => "mutable",
+ HlMod::Public => "public",
+ HlMod::Reference => "reference",
+ HlMod::Static => "static",
+ HlMod::Trait => "trait",
+ HlMod::Unsafe => "unsafe",
+ }
+ }
+
+ fn mask(self) -> u32 {
+ 1 << (self as u32)
+ }
+}
+
+impl fmt::Display for HlMod {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Display::fmt(self.as_str(), f)
+ }
+}
+
+impl fmt::Display for Highlight {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.tag.fmt(f)?;
+ for modifier in self.mods.iter() {
+ f.write_char('.')?;
+ modifier.fmt(f)?;
+ }
+ Ok(())
+ }
+}
+
+impl From<HlTag> for Highlight {
+ fn from(tag: HlTag) -> Highlight {
+ Highlight::new(tag)
+ }
+}
+
+impl From<HlOperator> for Highlight {
+ fn from(op: HlOperator) -> Highlight {
+ Highlight::new(HlTag::Operator(op))
+ }
+}
+
+impl From<HlPunct> for Highlight {
+ fn from(punct: HlPunct) -> Highlight {
+ Highlight::new(HlTag::Punctuation(punct))
+ }
+}
+
+impl From<SymbolKind> for Highlight {
+ fn from(sym: SymbolKind) -> Highlight {
+ Highlight::new(HlTag::Symbol(sym))
+ }
+}
+
+impl Highlight {
+ pub(crate) fn new(tag: HlTag) -> Highlight {
+ Highlight { tag, mods: HlMods::default() }
+ }
+ pub fn is_empty(&self) -> bool {
+ self.tag == HlTag::None && self.mods == HlMods::default()
+ }
+}
+
+impl ops::BitOr<HlMod> for HlTag {
+ type Output = Highlight;
+
+ fn bitor(self, rhs: HlMod) -> Highlight {
+ Highlight::new(self) | rhs
+ }
+}
+
+impl ops::BitOrAssign<HlMod> for HlMods {
+ fn bitor_assign(&mut self, rhs: HlMod) {
+ self.0 |= rhs.mask();
+ }
+}
+
+impl ops::BitOrAssign<HlMod> for Highlight {
+ fn bitor_assign(&mut self, rhs: HlMod) {
+ self.mods |= rhs;
+ }
+}
+
+impl ops::BitOr<HlMod> for Highlight {
+ type Output = Highlight;
+
+ fn bitor(mut self, rhs: HlMod) -> Highlight {
+ self |= rhs;
+ self
+ }
+}
+
+impl HlMods {
+ pub fn contains(self, m: HlMod) -> bool {
+ self.0 & m.mask() == m.mask()
+ }
+
+ pub fn iter(self) -> impl Iterator<Item = HlMod> {
+ HlMod::ALL.iter().copied().filter(move |it| self.0 & it.mask() == it.mask())
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html
new file mode 100644
index 000000000..e07fd3925
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html
@@ -0,0 +1,62 @@
+
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.function.unsafe { color: #BC8383; }
+.trait.unsafe { color: #BC8383; }
+.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; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code><span class="keyword">fn</span> <span class="function declaration">not_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="keyword">struct</span> <span class="struct declaration">foo</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="keyword">impl</span> <span class="struct">foo</span> <span class="brace">{</span>
+ <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function associated declaration public static">is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+ <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function associated declaration public reference">is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="keyword">trait</span> <span class="trait declaration">t</span> <span class="brace">{</span>
+ <span class="keyword">fn</span> <span class="function associated declaration static trait">t_is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+ <span class="keyword">fn</span> <span class="function associated declaration reference trait">t_is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="keyword">impl</span> <span class="trait">t</span> <span class="keyword">for</span> <span class="struct">foo</span> <span class="brace">{</span>
+ <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function associated declaration public static trait">is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+ <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function associated declaration public reference trait">is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="brace">}</span></code></pre> \ No newline at end of file
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html
new file mode 100644
index 000000000..1a4398814
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html
@@ -0,0 +1,58 @@
+
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.function.unsafe { color: #BC8383; }
+.trait.unsafe { color: #BC8383; }
+.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; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code><span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">allow</span><span class="parenthesis attribute">(</span><span class="none attribute">dead_code</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="tool_module attribute library">rustfmt</span><span class="operator attribute">::</span><span class="tool_module attribute library">skip</span><span class="attribute_bracket attribute">]</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="module attribute crate_root library">proc_macros</span><span class="operator attribute">::</span><span class="attribute attribute library">identity</span><span class="attribute_bracket attribute">]</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="attribute attribute default_library library">derive</span><span class="parenthesis attribute">(</span><span class="derive attribute default_library library">Copy</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span>
+<span class="comment documentation">/// This is a doc comment</span>
+<span class="comment">// This is a normal comment</span>
+<span class="comment documentation">/// This is a doc comment</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="attribute attribute default_library library">derive</span><span class="parenthesis attribute">(</span><span class="derive attribute default_library library">Copy</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span>
+<span class="comment">// This is another normal comment</span>
+<span class="comment documentation">/// This is another doc comment</span>
+<span class="comment">// This is another normal comment</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="attribute attribute default_library library">derive</span><span class="parenthesis attribute">(</span><span class="derive attribute default_library library">Copy</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span>
+<span class="comment">// The reason for these being here is to test AttrIds</span>
+<span class="keyword">struct</span> <span class="struct declaration">Foo</span><span class="semicolon">;</span></code></pre> \ No newline at end of file
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html
new file mode 100644
index 000000000..1e4c06df7
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html
@@ -0,0 +1,66 @@
+
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.function.unsafe { color: #BC8383; }
+.trait.unsafe { color: #BC8383; }
+.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; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code><span class="keyword">extern</span> <span class="keyword">crate</span> <span class="module crate_root library">foo</span><span class="semicolon">;</span>
+<span class="keyword">use</span> <span class="module crate_root default_library library">core</span><span class="operator">::</span><span class="module default_library library">iter</span><span class="semicolon">;</span>
+
+<span class="keyword">pub</span> <span class="keyword">const</span> <span class="constant declaration public">NINETY_TWO</span><span class="colon">:</span> <span class="builtin_type">u8</span> <span class="operator">=</span> <span class="numeric_literal">92</span><span class="semicolon">;</span>
+
+<span class="keyword">use</span> <span class="module crate_root library">foo</span> <span class="keyword">as</span> <span class="module crate_root declaration library">foooo</span><span class="semicolon">;</span>
+
+<span class="keyword">pub</span><span class="parenthesis">(</span><span class="keyword crate_root public">crate</span><span class="parenthesis">)</span> <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">baz</span> <span class="operator">=</span> <span class="module default_library library">iter</span><span class="operator">::</span><span class="function default_library library">repeat</span><span class="parenthesis">(</span><span class="numeric_literal">92</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+
+<span class="keyword">mod</span> <span class="module declaration">bar</span> <span class="brace">{</span>
+ <span class="keyword">pub</span><span class="parenthesis">(</span><span class="keyword control">in</span> <span class="keyword crate_root public">super</span><span class="parenthesis">)</span> <span class="keyword">const</span> <span class="constant declaration">FORTY_TWO</span><span class="colon">:</span> <span class="builtin_type">u8</span> <span class="operator">=</span> <span class="numeric_literal">42</span><span class="semicolon">;</span>
+
+ <span class="keyword">mod</span> <span class="module declaration">baz</span> <span class="brace">{</span>
+ <span class="keyword">use</span> <span class="keyword">super</span><span class="operator">::</span><span class="keyword crate_root public">super</span><span class="operator">::</span><span class="constant public">NINETY_TWO</span><span class="semicolon">;</span>
+ <span class="keyword">use</span> <span class="keyword crate_root public">crate</span><span class="operator">::</span><span class="module crate_root library">foooo</span><span class="operator">::</span><span class="struct library">Point</span><span class="semicolon">;</span>
+
+ <span class="keyword">pub</span><span class="parenthesis">(</span><span class="keyword control">in</span> <span class="keyword">super</span><span class="operator">::</span><span class="keyword crate_root public">super</span><span class="parenthesis">)</span> <span class="keyword">const</span> <span class="constant declaration">TWENTY_NINE</span><span class="colon">:</span> <span class="builtin_type">u8</span> <span class="operator">=</span> <span class="numeric_literal">29</span><span class="semicolon">;</span>
+ <span class="brace">}</span>
+<span class="brace">}</span>
+</code></pre> \ No newline at end of file
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html
new file mode 100644
index 000000000..5d66f832d
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html
@@ -0,0 +1,50 @@
+
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.function.unsafe { color: #BC8383; }
+.trait.unsafe { color: #BC8383; }
+.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; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code><span class="keyword">use</span> <span class="module crate_root default_library library">core</span><span class="operator">::</span><span class="module default_library library">iter</span><span class="semicolon">;</span>
+
+<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">foo</span> <span class="operator">=</span> <span class="enum_variant default_library library">Some</span><span class="parenthesis">(</span><span class="numeric_literal">92</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="variable declaration">nums</span> <span class="operator">=</span> <span class="module default_library library">iter</span><span class="operator">::</span><span class="function default_library library">repeat</span><span class="parenthesis">(</span><span class="variable">foo</span><span class="operator">.</span><span class="function associated consuming default_library library">unwrap</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+<span class="brace">}</span></code></pre> \ No newline at end of file
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
new file mode 100644
index 000000000..a747b4bc1
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
@@ -0,0 +1,190 @@
+
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.function.unsafe { color: #BC8383; }
+.trait.unsafe { color: #BC8383; }
+.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; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code><span class="comment documentation">//! This is a module to test doc injection.</span>
+<span class="comment documentation">//! ```</span>
+<span class="comment documentation">//!</span><span class="comment documentation"> </span><span class="keyword injected">fn</span><span class="none injected"> </span><span class="function declaration injected">test</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="brace injected">{</span><span class="brace injected">}</span>
+<span class="comment documentation">//! ```</span>
+
+<span class="keyword">mod</span> <span class="module declaration">outline_module</span><span class="semicolon">;</span>
+
+<span class="comment documentation">/// ```</span>
+<span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="string_literal injected">"early doctests should not go boom"</span><span class="semicolon injected">;</span>
+<span class="comment documentation">/// ```</span>
+<span class="keyword">struct</span> <span class="struct declaration">Foo</span> <span class="brace">{</span>
+ <span class="field declaration">bar</span><span class="colon">:</span> <span class="builtin_type">bool</span><span class="comma">,</span>
+<span class="brace">}</span>
+
+<span class="comment documentation">/// This is an impl with a code block.</span>
+<span class="comment documentation">///</span>
+<span class="comment documentation">/// ```</span>
+<span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">fn</span><span class="none injected"> </span><span class="function declaration injected">foo</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="brace injected">{</span>
+<span class="comment documentation">///</span>
+<span class="comment documentation">///</span><span class="comment documentation"> </span><span class="brace injected">}</span>
+<span class="comment documentation">/// ```</span>
+<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="brace">{</span>
+ <span class="comment documentation">/// ```</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="string_literal injected">"Call me</span>
+ <span class="comment">// KILLER WHALE</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="string_literal injected"> Ishmael."</span><span class="semicolon injected">;</span>
+ <span class="comment documentation">/// ```</span>
+ <span class="keyword">pub</span> <span class="keyword">const</span> <span class="constant associated declaration public">bar</span><span class="colon">:</span> <span class="builtin_type">bool</span> <span class="operator">=</span> <span class="bool_literal">true</span><span class="semicolon">;</span>
+
+ <span class="comment documentation">/// Constructs a new `Foo`.</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">/// # Examples</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">/// ```</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> #</span><span class="none injected"> </span><span class="attribute_bracket attribute injected">#</span><span class="attribute_bracket attribute injected">!</span><span class="attribute_bracket attribute injected">[</span><span class="builtin_attr attribute injected library">allow</span><span class="parenthesis attribute injected">(</span><span class="none attribute injected">unused_mut</span><span class="parenthesis attribute injected">)</span><span class="attribute_bracket attribute injected">]</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="keyword injected">mut</span><span class="none injected"> </span><span class="variable declaration injected mutable">foo</span><span class="colon injected">:</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
+ <span class="comment documentation">/// ```</span>
+ <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function associated declaration public static">new</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="struct">Foo</span> <span class="brace">{</span>
+ <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">bar</span><span class="colon">:</span> <span class="bool_literal">true</span> <span class="brace">}</span>
+ <span class="brace">}</span>
+
+ <span class="comment documentation">/// `bar` method on `Foo`.</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">/// # Examples</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">/// ```</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">use</span><span class="none injected"> </span><span class="module injected">x</span><span class="operator injected">::</span><span class="module injected">y</span><span class="semicolon injected">;</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected">foo</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="comment injected">// calls bar on foo</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="macro injected">assert</span><span class="macro_bang injected">!</span><span class="parenthesis injected">(</span><span class="none injected">foo</span><span class="operator injected">.</span><span class="none injected">bar</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected">bar</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="variable injected">foo</span><span class="operator injected">.</span><span class="field injected">bar</span><span class="none injected"> </span><span class="logical injected">||</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="constant injected">bar</span><span class="semicolon injected">;</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="comment injected">/* multi-line</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="comment injected"> comment */</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected reference">multi_line_string</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="string_literal injected">"Foo</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="string_literal injected"> bar</span><span class="escape_sequence injected">\n</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="string_literal injected"> "</span><span class="semicolon injected">;</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">/// ```</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">/// ```rust,no_run</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected">foobar</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="operator injected">.</span><span class="function injected">bar</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
+ <span class="comment documentation">/// ```</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">/// ~~~rust,no_run</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="comment injected">// code block with tilde.</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected">foobar</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="operator injected">.</span><span class="function injected">bar</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
+ <span class="comment documentation">/// ~~~</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">/// ```</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="comment injected">// functions</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">fn</span><span class="none injected"> </span><span class="function declaration injected">foo</span><span class="angle injected">&lt;</span><span class="type_param declaration injected">T</span><span class="comma injected">,</span><span class="none injected"> </span><span class="keyword injected">const</span><span class="none injected"> </span><span class="const_param declaration injected">X</span><span class="colon injected">:</span><span class="none injected"> </span><span class="builtin_type injected">usize</span><span class="angle injected">&gt;</span><span class="parenthesis injected">(</span><span class="value_param declaration injected">arg</span><span class="colon injected">:</span><span class="none injected"> </span><span class="builtin_type injected">i32</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="brace injected">{</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="none injected"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected">x</span><span class="colon injected">:</span><span class="none injected"> </span><span class="type_param injected">T</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="const_param injected">X</span><span class="semicolon injected">;</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="brace injected">}</span>
+ <span class="comment documentation">/// ```</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">/// ```sh</span>
+ <span class="comment documentation">/// echo 1</span>
+ <span class="comment documentation">/// ```</span>
+ <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function associated declaration public reference">foo</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">bool</span> <span class="brace">{</span>
+ <span class="bool_literal">true</span>
+ <span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="comment documentation">/// </span><span class="struct documentation injected intra_doc_link">[`Foo`](Foo)</span><span class="comment documentation"> is a struct</span>
+<span class="comment documentation">/// This function is &gt; </span><span class="function documentation injected intra_doc_link">[`all_the_links`](all_the_links)</span><span class="comment documentation"> &lt;</span>
+<span class="comment documentation">/// </span><span class="macro documentation injected intra_doc_link">[`noop`](noop)</span><span class="comment documentation"> is a macro below</span>
+<span class="comment documentation">/// </span><span class="struct documentation injected intra_doc_link">[`Item`]</span><span class="comment documentation"> is a struct in the module </span><span class="module documentation injected intra_doc_link">[`module`]</span>
+<span class="comment documentation">///</span>
+<span class="comment documentation">/// [`Item`]: module::Item</span>
+<span class="comment documentation">/// [mix_and_match]: ThisShouldntResolve</span>
+<span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration public">all_the_links</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="keyword">pub</span> <span class="keyword">mod</span> <span class="module declaration public">module</span> <span class="brace">{</span>
+ <span class="keyword">pub</span> <span class="keyword">struct</span> <span class="struct declaration public">Item</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+
+<span class="comment documentation">/// ```</span>
+<span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">macro_rules</span><span class="macro_bang injected">!</span><span class="none injected"> </span><span class="macro declaration injected">noop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected"> </span><span class="parenthesis injected">(</span><span class="punctuation injected">$</span><span class="none injected">expr</span><span class="colon injected">:</span><span class="none injected">expr</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="operator injected">=</span><span class="angle injected">&gt;</span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected"> </span><span class="punctuation injected">$</span><span class="none injected">expr </span><span class="brace injected">}</span><span class="brace injected">}</span>
+<span class="comment documentation">///</span><span class="comment documentation"> </span><span class="macro injected">noop</span><span class="macro_bang injected">!</span><span class="parenthesis injected">(</span><span class="numeric_literal injected">1</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
+<span class="comment documentation">/// ```</span>
+<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">noop</span> <span class="brace">{</span>
+ <span class="parenthesis">(</span><span class="punctuation">$</span>expr<span class="colon">:</span>expr<span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">&gt;</span> <span class="brace">{</span>
+ <span class="punctuation">$</span>expr
+ <span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="comment documentation">/// ```rust</span>
+<span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="function injected">example</span><span class="parenthesis injected">(</span><span class="operator injected">&</span><span class="bracket injected">[</span><span class="numeric_literal injected">1</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">2</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">3</span><span class="bracket injected">]</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
+<span class="comment documentation">/// ```</span>
+<span class="comment documentation">///</span>
+<span class="comment documentation">/// ```</span>
+<span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword control injected">loop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="brace injected">}</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">cfg_attr</span><span class="parenthesis attribute">(</span><span class="none attribute">not</span><span class="parenthesis attribute">(</span><span class="none attribute">feature</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"false"</span><span class="parenthesis attribute">)</span><span class="comma attribute">,</span> <span class="none attribute">doc</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"</span><span class="keyword control injected">loop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="brace injected">}</span><span class="string_literal attribute">"</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">doc</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"</span><span class="keyword control injected">loop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="brace injected">}</span><span class="string_literal attribute">"</span><span class="attribute_bracket attribute">]</span>
+<span class="comment documentation">/// ```</span>
+<span class="comment documentation">///</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">cfg_attr</span><span class="parenthesis attribute">(</span><span class="none attribute">feature</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"alloc"</span><span class="comma attribute">,</span> <span class="none attribute">doc</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"```rust"</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">cfg_attr</span><span class="parenthesis attribute">(</span><span class="none attribute">not</span><span class="parenthesis attribute">(</span><span class="none attribute">feature</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"alloc"</span><span class="parenthesis attribute">)</span><span class="comma attribute">,</span> <span class="none attribute">doc</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"```ignore"</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span>
+<span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="function injected">example</span><span class="parenthesis injected">(</span><span class="operator injected">&</span><span class="module injected">alloc</span><span class="operator injected">::</span><span class="macro injected">vec</span><span class="macro_bang injected">!</span><span class="bracket injected">[</span><span class="numeric_literal injected">1</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">2</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">3</span><span class="bracket injected">]</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
+<span class="comment documentation">/// ```</span>
+<span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration public">mix_and_match</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="comment documentation">/**
+It is beyond me why you'd use these when you got ///
+```rust
+</span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="function injected">example</span><span class="parenthesis injected">(</span><span class="operator injected">&</span><span class="bracket injected">[</span><span class="numeric_literal injected">1</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">2</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">3</span><span class="bracket injected">]</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span><span class="comment documentation">
+```
+</span><span class="function documentation injected intra_doc_link">[`block_comments2`]</span><span class="comment documentation"> tests these with indentation
+ */</span>
+<span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration public">block_comments</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="comment documentation">/**
+ Really, I don't get it
+ ```rust
+</span><span class="comment documentation"> </span><span class="none injected"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="function injected">example</span><span class="parenthesis injected">(</span><span class="operator injected">&</span><span class="bracket injected">[</span><span class="numeric_literal injected">1</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">2</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">3</span><span class="bracket injected">]</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span><span class="comment documentation">
+ ```
+ </span><span class="function documentation injected intra_doc_link">[`block_comments`]</span><span class="comment documentation"> tests these without indentation
+*/</span>
+<span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration public">block_comments2</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+
+</code></pre> \ No newline at end of file
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html
new file mode 100644
index 000000000..af41796e2
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html
@@ -0,0 +1,47 @@
+
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.function.unsafe { color: #BC8383; }
+.trait.unsafe { color: #BC8383; }
+.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; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code><span class="keyword">extern</span> <span class="keyword">crate</span> <span class="module crate_root default_library library">std</span><span class="semicolon">;</span>
+<span class="keyword">extern</span> <span class="keyword">crate</span> <span class="module crate_root library">alloc</span> <span class="keyword">as</span> <span class="module crate_root declaration library">abc</span><span class="semicolon">;</span>
+</code></pre> \ No newline at end of file
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_general.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_general.html
new file mode 100644
index 000000000..a97802cbb
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_general.html
@@ -0,0 +1,233 @@
+
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.function.unsafe { color: #BC8383; }
+.trait.unsafe { color: #BC8383; }
+.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; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code><span class="keyword">use</span> <span class="module">inner</span><span class="operator">::</span><span class="brace">{</span><span class="self_keyword">self</span> <span class="keyword">as</span> <span class="module declaration">inner_mod</span><span class="brace">}</span><span class="semicolon">;</span>
+<span class="keyword">mod</span> <span class="module declaration">inner</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="keyword">pub</span> <span class="keyword">mod</span> <span class="module declaration public">ops</span> <span class="brace">{</span>
+ <span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">lang</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"fn_once"</span><span class="attribute_bracket attribute">]</span>
+ <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration public">FnOnce</span><span class="angle">&lt;</span><span class="type_param declaration">Args</span><span class="angle">&gt;</span> <span class="brace">{</span><span class="brace">}</span>
+
+ <span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">lang</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"fn_mut"</span><span class="attribute_bracket attribute">]</span>
+ <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration public">FnMut</span><span class="angle">&lt;</span><span class="type_param declaration">Args</span><span class="angle">&gt;</span><span class="colon">:</span> <span class="trait public">FnOnce</span><span class="angle">&lt;</span><span class="type_param">Args</span><span class="angle">&gt;</span> <span class="brace">{</span><span class="brace">}</span>
+
+ <span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">lang</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"fn"</span><span class="attribute_bracket attribute">]</span>
+ <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration public">Fn</span><span class="angle">&lt;</span><span class="type_param declaration">Args</span><span class="angle">&gt;</span><span class="colon">:</span> <span class="trait public">FnMut</span><span class="angle">&lt;</span><span class="type_param">Args</span><span class="angle">&gt;</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="keyword">struct</span> <span class="struct declaration">Foo</span> <span class="brace">{</span>
+ <span class="field declaration">x</span><span class="colon">:</span> <span class="builtin_type">u32</span><span class="comma">,</span>
+<span class="brace">}</span>
+
+<span class="keyword">trait</span> <span class="trait declaration">Bar</span> <span class="brace">{</span>
+ <span class="keyword">fn</span> <span class="function associated declaration reference trait">bar</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+
+<span class="keyword">impl</span> <span class="trait">Bar</span> <span class="keyword">for</span> <span class="struct">Foo</span> <span class="brace">{</span>
+ <span class="keyword">fn</span> <span class="function associated declaration reference trait">bar</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="brace">{</span>
+ <span class="self_keyword reference">self</span><span class="operator">.</span><span class="field">x</span>
+ <span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="brace">{</span>
+ <span class="keyword">fn</span> <span class="function associated consuming declaration">baz</span><span class="parenthesis">(</span><span class="keyword">mut</span> <span class="self_keyword declaration mutable">self</span><span class="comma">,</span> <span class="value_param declaration">f</span><span class="colon">:</span> <span class="struct">Foo</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="brace">{</span>
+ <span class="value_param">f</span><span class="operator">.</span><span class="function associated consuming">baz</span><span class="parenthesis">(</span><span class="self_keyword consuming mutable">self</span><span class="parenthesis">)</span>
+ <span class="brace">}</span>
+
+ <span class="keyword">fn</span> <span class="function associated declaration mutable reference">qux</span><span class="parenthesis">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword declaration mutable reference">self</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="self_keyword mutable reference">self</span><span class="operator">.</span><span class="field">x</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="semicolon">;</span>
+ <span class="brace">}</span>
+
+ <span class="keyword">fn</span> <span class="function associated declaration reference">quop</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="brace">{</span>
+ <span class="self_keyword reference">self</span><span class="operator">.</span><span class="field">x</span>
+ <span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="keyword">use</span> <span class="self_keyword crate_root public">self</span><span class="operator">::</span><span class="struct">FooCopy</span><span class="operator">::</span><span class="brace">{</span><span class="self_keyword">self</span> <span class="keyword">as</span> <span class="struct declaration">BarCopy</span><span class="brace">}</span><span class="semicolon">;</span>
+
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="attribute attribute default_library library">derive</span><span class="parenthesis attribute">(</span><span class="derive attribute default_library library">Copy</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span>
+<span class="keyword">struct</span> <span class="struct declaration">FooCopy</span> <span class="brace">{</span>
+ <span class="field declaration">x</span><span class="colon">:</span> <span class="builtin_type">u32</span><span class="comma">,</span>
+<span class="brace">}</span>
+
+<span class="keyword">impl</span> <span class="struct">FooCopy</span> <span class="brace">{</span>
+ <span class="keyword">fn</span> <span class="function associated consuming declaration">baz</span><span class="parenthesis">(</span><span class="self_keyword declaration">self</span><span class="comma">,</span> <span class="value_param declaration">f</span><span class="colon">:</span> <span class="struct">FooCopy</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">u32</span> <span class="brace">{</span>
+ <span class="value_param">f</span><span class="operator">.</span><span class="function associated">baz</span><span class="parenthesis">(</span><span class="self_keyword">self</span><span class="parenthesis">)</span>
+ <span class="brace">}</span>
+
+ <span class="keyword">fn</span> <span class="function associated declaration mutable reference">qux</span><span class="parenthesis">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword declaration mutable reference">self</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="self_keyword mutable reference">self</span><span class="operator">.</span><span class="field">x</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="semicolon">;</span>
+ <span class="brace">}</span>
+
+ <span class="keyword">fn</span> <span class="function associated declaration reference">quop</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">u32</span> <span class="brace">{</span>
+ <span class="self_keyword reference">self</span><span class="operator">.</span><span class="field">x</span>
+ <span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="keyword">fn</span> <span class="function declaration">str</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="function">str</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+
+<span class="keyword">fn</span> <span class="function declaration">foo</span><span class="angle">&lt;</span><span class="lifetime declaration">'a</span><span class="comma">,</span> <span class="type_param declaration">T</span><span class="angle">&gt;</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="type_param">T</span> <span class="brace">{</span>
+ <span class="function">foo</span><span class="operator">::</span><span class="angle">&lt;</span><span class="lifetime">'a</span><span class="comma">,</span> <span class="builtin_type">i32</span><span class="angle">&gt;</span><span class="parenthesis">(</span><span class="parenthesis">)</span>
+<span class="brace">}</span>
+
+<span class="keyword">fn</span> <span class="function declaration">never</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">!</span> <span class="brace">{</span>
+ <span class="keyword control">loop</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="keyword">fn</span> <span class="function declaration">const_param</span><span class="angle">&lt;</span><span class="keyword">const</span> <span class="const_param declaration">FOO</span><span class="colon">:</span> <span class="builtin_type">usize</span><span class="angle">&gt;</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">usize</span> <span class="brace">{</span>
+ <span class="function">const_param</span><span class="operator">::</span><span class="angle">&lt;</span><span class="brace">{</span> <span class="const_param">FOO</span> <span class="brace">}</span><span class="angle">&gt;</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="const_param">FOO</span>
+<span class="brace">}</span>
+
+<span class="keyword">use</span> <span class="module public">ops</span><span class="operator">::</span><span class="trait public">Fn</span><span class="semicolon">;</span>
+<span class="keyword">fn</span> <span class="function declaration">baz</span><span class="angle">&lt;</span><span class="type_param declaration">F</span><span class="colon">:</span> <span class="trait public">Fn</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="angle">&gt;</span><span class="parenthesis">(</span><span class="value_param callable declaration">f</span><span class="colon">:</span> <span class="type_param">F</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="value_param callable">f</span><span class="parenthesis">(</span><span class="parenthesis">)</span>
+<span class="brace">}</span>
+
+<span class="keyword">fn</span> <span class="function declaration">foobar</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="keyword">impl</span> <span class="trait default_library library">Copy</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="keyword">fn</span> <span class="function declaration">foo</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="keyword">let</span> <span class="variable declaration">bar</span> <span class="operator">=</span> <span class="function">foobar</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+
+<span class="comment">// comment</span>
+<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="keyword">mut</span> <span class="variable declaration mutable">x</span> <span class="operator">=</span> <span class="numeric_literal">42</span><span class="semicolon">;</span>
+ <span class="variable mutable">x</span> <span class="arithmetic mutable">+=</span> <span class="numeric_literal">1</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="variable declaration mutable reference">y</span> <span class="operator">=</span> <span class="operator">&</span><span class="keyword">mut</span> <span class="variable mutable">x</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="variable declaration reference">z</span> <span class="operator">=</span> <span class="operator">&</span><span class="variable mutable reference">y</span><span class="semicolon">;</span>
+
+ <span class="keyword">let</span> <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">x</span><span class="colon">:</span> <span class="variable declaration">z</span><span class="comma">,</span> <span class="variable declaration">y</span> <span class="brace">}</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">x</span><span class="colon">:</span> <span class="variable reference">z</span><span class="comma">,</span> <span class="variable mutable reference">y</span> <span class="brace">}</span><span class="semicolon">;</span>
+
+ <span class="variable">y</span><span class="semicolon">;</span>
+
+ <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">x</span><span class="comma">,</span> <span class="unresolved_reference">y</span><span class="colon">:</span> <span class="variable mutable">x</span> <span class="brace">}</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="variable declaration">foo2</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">x</span><span class="comma">,</span> <span class="unresolved_reference">y</span><span class="colon">:</span> <span class="variable mutable">x</span> <span class="brace">}</span><span class="semicolon">;</span>
+ <span class="variable mutable">foo</span><span class="operator">.</span><span class="function associated reference">quop</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="variable mutable">foo</span><span class="operator">.</span><span class="function associated mutable reference">qux</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="variable mutable">foo</span><span class="operator">.</span><span class="function associated consuming">baz</span><span class="parenthesis">(</span><span class="variable consuming">foo2</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+
+ <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">copy</span> <span class="operator">=</span> <span class="struct">FooCopy</span> <span class="brace">{</span> <span class="field">x</span> <span class="brace">}</span><span class="semicolon">;</span>
+ <span class="variable mutable">copy</span><span class="operator">.</span><span class="function associated reference">quop</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="variable mutable">copy</span><span class="operator">.</span><span class="function associated mutable reference">qux</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="variable mutable">copy</span><span class="operator">.</span><span class="function associated">baz</span><span class="parenthesis">(</span><span class="variable mutable">copy</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+
+ <span class="keyword">let</span> <span class="variable callable declaration">a</span> <span class="operator">=</span> <span class="punctuation">|</span><span class="value_param declaration">x</span><span class="punctuation">|</span> <span class="value_param">x</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="variable callable declaration">bar</span> <span class="operator">=</span> <span class="struct">Foo</span><span class="operator">::</span><span class="function associated consuming">baz</span><span class="semicolon">;</span>
+
+ <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="parenthesis">(</span><span class="numeric_literal">-</span><span class="numeric_literal">42</span><span class="comma">,</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="operator">-</span><span class="variable">baz</span><span class="operator">.</span><span class="field">0</span><span class="semicolon">;</span>
+
+ <span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="logical">!</span><span class="bool_literal">true</span><span class="semicolon">;</span>
+
+ <span class="label declaration">'foo</span><span class="colon">:</span> <span class="keyword control">loop</span> <span class="brace">{</span>
+ <span class="keyword control">break</span> <span class="label">'foo</span><span class="semicolon">;</span>
+ <span class="keyword control">continue</span> <span class="label">'foo</span><span class="semicolon">;</span>
+ <span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="keyword">enum</span> <span class="enum declaration">Option</span><span class="angle">&lt;</span><span class="type_param declaration">T</span><span class="angle">&gt;</span> <span class="brace">{</span>
+ <span class="enum_variant declaration">Some</span><span class="parenthesis">(</span><span class="type_param">T</span><span class="parenthesis">)</span><span class="comma">,</span>
+ <span class="enum_variant declaration">None</span><span class="comma">,</span>
+<span class="brace">}</span>
+<span class="keyword">use</span> <span class="enum">Option</span><span class="operator">::</span><span class="punctuation">*</span><span class="semicolon">;</span>
+
+<span class="keyword">impl</span><span class="angle">&lt;</span><span class="type_param declaration">T</span><span class="angle">&gt;</span> <span class="enum">Option</span><span class="angle">&lt;</span><span class="type_param">T</span><span class="angle">&gt;</span> <span class="brace">{</span>
+ <span class="keyword">fn</span> <span class="function associated consuming declaration">and</span><span class="angle">&lt;</span><span class="type_param declaration">U</span><span class="angle">&gt;</span><span class="parenthesis">(</span><span class="self_keyword declaration">self</span><span class="comma">,</span> <span class="value_param declaration">other</span><span class="colon">:</span> <span class="enum">Option</span><span class="angle">&lt;</span><span class="type_param">U</span><span class="angle">&gt;</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="enum">Option</span><span class="angle">&lt;</span><span class="parenthesis">(</span><span class="type_param">T</span><span class="comma">,</span> <span class="type_param">U</span><span class="parenthesis">)</span><span class="angle">&gt;</span> <span class="brace">{</span>
+ <span class="keyword control">match</span> <span class="value_param">other</span> <span class="brace">{</span>
+ <span class="enum_variant">None</span> <span class="operator">=&gt;</span> <span class="unresolved_reference">unimplemented</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="comma">,</span>
+ <span class="variable declaration">Nope</span> <span class="operator">=&gt;</span> <span class="variable">Nope</span><span class="comma">,</span>
+ <span class="brace">}</span>
+ <span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="keyword async">async</span> <span class="keyword">fn</span> <span class="function async declaration">learn_and_sing</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="keyword">let</span> <span class="variable declaration">song</span> <span class="operator">=</span> <span class="unresolved_reference">learn_song</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="operator">.</span><span class="keyword async control">await</span><span class="semicolon">;</span>
+ <span class="unresolved_reference">sing_song</span><span class="parenthesis">(</span><span class="variable consuming">song</span><span class="parenthesis">)</span><span class="operator">.</span><span class="keyword async control">await</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+
+<span class="keyword async">async</span> <span class="keyword">fn</span> <span class="function async declaration">async_main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="keyword">let</span> <span class="variable declaration">f1</span> <span class="operator">=</span> <span class="function async">learn_and_sing</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="variable declaration">f2</span> <span class="operator">=</span> <span class="unresolved_reference">dance</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="unresolved_reference">futures</span><span class="operator">::</span><span class="unresolved_reference">join</span><span class="macro_bang">!</span><span class="parenthesis">(</span>f1<span class="comma">,</span> f2<span class="parenthesis">)</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+
+<span class="keyword">fn</span> <span class="function declaration">use_foo_items</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="keyword">let</span> <span class="variable declaration">bob</span> <span class="operator">=</span> <span class="module crate_root library">foo</span><span class="operator">::</span><span class="struct library">Person</span> <span class="brace">{</span>
+ <span class="field library">name</span><span class="colon">:</span> <span class="string_literal">"Bob"</span><span class="comma">,</span>
+ <span class="field library">age</span><span class="colon">:</span> <span class="module crate_root library">foo</span><span class="operator">::</span><span class="module library">consts</span><span class="operator">::</span><span class="constant library">NUMBER</span><span class="comma">,</span>
+ <span class="brace">}</span><span class="semicolon">;</span>
+
+ <span class="keyword">let</span> <span class="variable declaration">control_flow</span> <span class="operator">=</span> <span class="module crate_root library">foo</span><span class="operator">::</span><span class="function library">identity</span><span class="parenthesis">(</span><span class="module crate_root library">foo</span><span class="operator">::</span><span class="enum library">ControlFlow</span><span class="operator">::</span><span class="enum_variant library">Continue</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+
+ <span class="keyword control">if</span> <span class="variable">control_flow</span><span class="operator">.</span><span class="function associated consuming library">should_die</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="module crate_root library">foo</span><span class="operator">::</span><span class="unresolved_reference">die</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="keyword">pub</span> <span class="keyword">enum</span> <span class="enum declaration public">Bool</span> <span class="brace">{</span> <span class="enum_variant declaration public">True</span><span class="comma">,</span> <span class="enum_variant declaration public">False</span> <span class="brace">}</span>
+
+<span class="keyword">impl</span> <span class="enum public">Bool</span> <span class="brace">{</span>
+ <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function associated consuming declaration public">to_primitive</span><span class="parenthesis">(</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">bool</span> <span class="brace">{</span>
+ <span class="bool_literal">true</span>
+ <span class="brace">}</span>
+<span class="brace">}</span>
+<span class="keyword">const</span> <span class="constant declaration">USAGE_OF_BOOL</span><span class="colon">:</span><span class="builtin_type">bool</span> <span class="operator">=</span> <span class="enum public">Bool</span><span class="operator">::</span><span class="enum_variant public">True</span><span class="operator">.</span><span class="function associated consuming public">to_primitive</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+
+<span class="keyword">trait</span> <span class="trait declaration">Baz</span> <span class="brace">{</span>
+ <span class="keyword">type</span> <span class="type_alias associated declaration trait">Qux</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+
+<span class="keyword">fn</span> <span class="function declaration">baz</span><span class="angle">&lt;</span><span class="type_param declaration">T</span><span class="angle">&gt;</span><span class="parenthesis">(</span><span class="value_param declaration">t</span><span class="colon">:</span> <span class="type_param">T</span><span class="parenthesis">)</span>
+<span class="keyword">where</span>
+ <span class="type_param">T</span><span class="colon">:</span> <span class="trait">Baz</span><span class="comma">,</span>
+ <span class="angle">&lt;</span><span class="type_param">T</span> <span class="keyword">as</span> <span class="trait">Baz</span><span class="angle">&gt;</span><span class="operator">::</span><span class="type_alias associated trait">Qux</span><span class="colon">:</span> <span class="trait">Bar</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="keyword">fn</span> <span class="function declaration">gp_shadows_trait</span><span class="angle">&lt;</span><span class="type_param declaration">Baz</span><span class="colon">:</span> <span class="trait">Bar</span><span class="angle">&gt;</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="type_param">Baz</span><span class="operator">::</span><span class="function associated reference trait">bar</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+
+</code></pre> \ No newline at end of file
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
new file mode 100644
index 000000000..ced7d22f0
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
@@ -0,0 +1,62 @@
+
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.function.unsafe { color: #BC8383; }
+.trait.unsafe { color: #BC8383; }
+.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; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code><span class="keyword">fn</span> <span class="function declaration">fixture</span><span class="parenthesis">(</span><span class="value_param declaration reference">ra_fixture</span><span class="colon">:</span> <span class="operator">&</span><span class="builtin_type">str</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+
+<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="function">fixture</span><span class="parenthesis">(</span><span class="string_literal">r#"</span>
+<span class="keyword">trait</span> <span class="trait declaration">Foo</span> <span class="brace">{</span>
+ <span class="keyword">fn</span> <span class="function associated declaration static trait">foo</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="unresolved_reference">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"2 + 2 = {}"</span><span class="comma">,</span> <span class="numeric_literal">4</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="brace">}</span>
+<span class="brace">}</span><span class="string_literal">"#</span>
+ <span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="function">fixture</span><span class="parenthesis">(</span><span class="string_literal">r"</span>
+<span class="keyword">fn</span> <span class="function declaration">foo</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="function">foo</span><span class="parenthesis">(</span><span class="keyword">$0</span><span class="brace">{</span>
+ <span class="numeric_literal">92</span>
+ <span class="brace">}</span><span class="keyword">$0</span><span class="parenthesis">)</span>
+<span class="brace">}</span><span class="string_literal">"</span>
+ <span class="parenthesis">)</span><span class="semicolon">;</span>
+<span class="brace">}</span></code></pre> \ No newline at end of file
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html
new file mode 100644
index 000000000..66f9ede96
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html
@@ -0,0 +1,58 @@
+
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.function.unsafe { color: #BC8383; }
+.trait.unsafe { color: #BC8383; }
+.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; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code><span class="keyword">extern</span> <span class="keyword">crate</span> <span class="self_keyword crate_root public">self</span><span class="semicolon">;</span>
+
+<span class="keyword">use</span> <span class="keyword crate_root public">crate</span><span class="semicolon">;</span>
+<span class="keyword">use</span> <span class="self_keyword crate_root public">self</span><span class="semicolon">;</span>
+<span class="keyword">mod</span> <span class="module declaration">__</span> <span class="brace">{</span>
+ <span class="keyword">use</span> <span class="keyword crate_root public">super</span><span class="operator">::</span><span class="punctuation">*</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+
+<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">void</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="brace">}</span>
+<span class="brace">}</span>
+<span class="macro">void</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="keyword">Self</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+<span class="keyword">struct</span> <span class="struct declaration">__</span> <span class="keyword">where</span> <span class="self_type_keyword">Self</span><span class="colon">:</span><span class="semicolon">;</span>
+<span class="keyword">fn</span> <span class="function declaration">__</span><span class="parenthesis">(</span><span class="punctuation">_</span><span class="colon">:</span> <span class="unresolved_reference">Self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span></code></pre> \ No newline at end of file
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_lifetimes.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_lifetimes.html
new file mode 100644
index 000000000..2d85fc8c9
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_lifetimes.html
@@ -0,0 +1,55 @@
+
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.function.unsafe { color: #BC8383; }
+.trait.unsafe { color: #BC8383; }
+.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; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="attribute attribute default_library library">derive</span><span class="parenthesis attribute">(</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span>
+<span class="keyword">struct</span> <span class="struct declaration">Foo</span><span class="angle">&lt;</span><span class="lifetime declaration">'a</span><span class="comma">,</span> <span class="lifetime declaration">'b</span><span class="comma">,</span> <span class="lifetime declaration">'c</span><span class="angle">&gt;</span> <span class="keyword">where</span> <span class="lifetime">'a</span><span class="colon">:</span> <span class="lifetime">'a</span><span class="comma">,</span> <span class="lifetime">'static</span><span class="colon">:</span> <span class="lifetime">'static</span> <span class="brace">{</span>
+ <span class="field declaration">field</span><span class="colon">:</span> <span class="operator">&</span><span class="lifetime">'a</span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="comma">,</span>
+ <span class="field declaration">field2</span><span class="colon">:</span> <span class="operator">&</span><span class="lifetime">'static</span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="comma">,</span>
+<span class="brace">}</span>
+<span class="keyword">impl</span><span class="angle">&lt;</span><span class="lifetime declaration">'a</span><span class="angle">&gt;</span> <span class="struct">Foo</span><span class="angle">&lt;</span><span class="lifetime">'_</span><span class="comma">,</span> <span class="lifetime">'a</span><span class="comma">,</span> <span class="lifetime">'static</span><span class="angle">&gt;</span>
+<span class="keyword">where</span>
+ <span class="lifetime">'a</span><span class="colon">:</span> <span class="lifetime">'a</span><span class="comma">,</span>
+ <span class="lifetime">'static</span><span class="colon">:</span> <span class="lifetime">'static</span>
+<span class="brace">{</span><span class="brace">}</span></code></pre> \ No newline at end of file
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html
new file mode 100644
index 000000000..54d427952
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html
@@ -0,0 +1,96 @@
+
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.function.unsafe { color: #BC8383; }
+.trait.unsafe { color: #BC8383; }
+.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; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code><span class="module crate_root library">proc_macros</span><span class="operator">::</span><span class="macro library">mirror</span><span class="macro_bang">!</span> <span class="brace">{</span>
+ <span class="brace">{</span>
+ <span class="comma">,</span><span class="builtin_type">i32</span> <span class="colon">:</span><span class="field declaration public">x</span> <span class="keyword">pub</span>
+ <span class="comma">,</span><span class="builtin_type">i32</span> <span class="colon">:</span><span class="field declaration public">y</span> <span class="keyword">pub</span>
+ <span class="brace">}</span> <span class="struct declaration">Foo</span> <span class="keyword">struct</span>
+<span class="brace">}</span>
+<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">def_fn</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="brace">}</span>
+
+<span class="macro">def_fn</span><span class="macro_bang">!</span> <span class="brace">{</span>
+ <span class="keyword">fn</span> <span class="function declaration">bar</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-</span><span class="operator">&gt;</span> <span class="builtin_type">u32</span> <span class="brace">{</span>
+ <span class="numeric_literal">100</span>
+ <span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">dont_color_me_braces</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="numeric_literal">0</span><span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">noop</span> <span class="brace">{</span>
+ <span class="parenthesis">(</span><span class="punctuation">$</span>expr<span class="colon">:</span>expr<span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">&gt;</span> <span class="brace">{</span>
+ <span class="punctuation">$</span>expr
+ <span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="comment documentation">/// textually shadow previous definition</span>
+<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">noop</span> <span class="brace">{</span>
+ <span class="parenthesis">(</span><span class="punctuation">$</span>expr<span class="colon">:</span>expr<span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">&gt;</span> <span class="brace">{</span>
+ <span class="punctuation">$</span>expr
+ <span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">keyword_frag</span> <span class="brace">{</span>
+ <span class="parenthesis">(</span><span class="punctuation">$</span>type<span class="colon">:</span>ty<span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">&gt;</span> <span class="parenthesis">(</span><span class="punctuation">$</span>type<span class="parenthesis">)</span>
+<span class="brace">}</span>
+
+<span class="keyword">macro</span> <span class="macro declaration">with_args</span><span class="parenthesis">(</span><span class="punctuation">$</span>i<span class="colon">:</span>ident<span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="punctuation">$</span>i
+<span class="brace">}</span>
+
+<span class="keyword">macro</span> <span class="macro declaration">without_args</span> <span class="brace">{</span>
+ <span class="parenthesis">(</span><span class="punctuation">$</span>i<span class="colon">:</span>ident<span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">&gt;</span> <span class="brace">{</span>
+ <span class="punctuation">$</span>i
+ <span class="brace">}</span>
+<span class="brace">}</span>
+
+<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="unresolved_reference">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello, {}!"</span><span class="comma">,</span> <span class="numeric_literal">92</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">dont_color_me_braces</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">noop</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="macro">noop</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="numeric_literal">1</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+<span class="brace">}</span></code></pre> \ No newline at end of file
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_inline.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_inline.html
new file mode 100644
index 000000000..8a1d69816
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_inline.html
@@ -0,0 +1,51 @@
+
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.function.unsafe { color: #BC8383; }
+.trait.unsafe { color: #BC8383; }
+.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; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code><span class="comment documentation">//! </span><span class="struct documentation injected intra_doc_link">[Struct]</span>
+<span class="comment documentation">//! This is an intra doc injection test for modules</span>
+<span class="comment documentation">//! </span><span class="struct documentation injected intra_doc_link">[Struct]</span>
+<span class="comment documentation">//! This is an intra doc injection test for modules</span>
+
+<span class="keyword">pub</span> <span class="keyword">struct</span> <span class="struct declaration public">Struct</span><span class="semicolon">;</span>
+</code></pre> \ No newline at end of file
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_outline.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_outline.html
new file mode 100644
index 000000000..c4c3e3dc2
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_outline.html
@@ -0,0 +1,50 @@
+
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.function.unsafe { color: #BC8383; }
+.trait.unsafe { color: #BC8383; }
+.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; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code><span class="comment documentation">/// </span><span class="struct documentation injected intra_doc_link">[crate::foo::Struct]</span>
+<span class="comment documentation">/// This is an intra doc injection test for modules</span>
+<span class="comment documentation">/// </span><span class="struct documentation injected intra_doc_link">[crate::foo::Struct]</span>
+<span class="comment documentation">/// This is an intra doc injection test for modules</span>
+<span class="keyword">mod</span> <span class="module declaration">foo</span><span class="semicolon">;</span>
+</code></pre> \ No newline at end of file
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_operators.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_operators.html
new file mode 100644
index 000000000..2369071ae
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_operators.html
@@ -0,0 +1,58 @@
+
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.function.unsafe { color: #BC8383; }
+.trait.unsafe { color: #BC8383; }
+.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; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code><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="numeric_literal">1</span> <span class="arithmetic">+</span> <span class="numeric_literal">1</span> <span class="arithmetic">-</span> <span class="numeric_literal">1</span> <span class="arithmetic">*</span> <span class="numeric_literal">1</span> <span class="arithmetic">/</span> <span class="numeric_literal">1</span> <span class="arithmetic">%</span> <span class="numeric_literal">1</span> <span class="bitwise">|</span> <span class="numeric_literal">1</span> <span class="bitwise">&</span> <span class="numeric_literal">1</span> <span class="logical">!</span> <span class="numeric_literal">1</span> <span class="bitwise">^</span> <span class="numeric_literal">1</span> <span class="bitwise">&gt;&gt;</span> <span class="numeric_literal">1</span> <span class="bitwise">&lt;&lt;</span> <span class="numeric_literal">1</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">a</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="semicolon">;</span>
+ <span class="variable mutable">a</span> <span class="arithmetic mutable">+=</span> <span class="numeric_literal">1</span><span class="semicolon">;</span>
+ <span class="variable mutable">a</span> <span class="arithmetic mutable">-=</span> <span class="numeric_literal">1</span><span class="semicolon">;</span>
+ <span class="variable mutable">a</span> <span class="arithmetic mutable">*=</span> <span class="numeric_literal">1</span><span class="semicolon">;</span>
+ <span class="variable mutable">a</span> <span class="arithmetic mutable">/=</span> <span class="numeric_literal">1</span><span class="semicolon">;</span>
+ <span class="variable mutable">a</span> <span class="arithmetic mutable">%=</span> <span class="numeric_literal">1</span><span class="semicolon">;</span>
+ <span class="variable mutable">a</span> <span class="bitwise mutable">|=</span> <span class="numeric_literal">1</span><span class="semicolon">;</span>
+ <span class="variable mutable">a</span> <span class="bitwise mutable">&=</span> <span class="numeric_literal">1</span><span class="semicolon">;</span>
+ <span class="variable mutable">a</span> <span class="bitwise mutable">^=</span> <span class="numeric_literal">1</span><span class="semicolon">;</span>
+ <span class="variable mutable">a</span> <span class="bitwise mutable">&gt;&gt;=</span> <span class="numeric_literal">1</span><span class="semicolon">;</span>
+ <span class="variable mutable">a</span> <span class="bitwise mutable">&lt;&lt;=</span> <span class="numeric_literal">1</span><span class="semicolon">;</span>
+<span class="brace">}</span></code></pre> \ No newline at end of file
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_rainbow.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_rainbow.html
new file mode 100644
index 000000000..bff35c897
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_rainbow.html
@@ -0,0 +1,56 @@
+
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.function.unsafe { color: #BC8383; }
+.trait.unsafe { color: #BC8383; }
+.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; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code><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 reference" data-binding-hash="8121853618659664005" style="color: hsl(273,88%,88%);">hello</span> <span class="operator">=</span> <span class="string_literal">"hello"</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="2705725358298919760" style="color: hsl(76,47%,83%);">x</span> <span class="operator">=</span> <span class="variable reference" data-binding-hash="8121853618659664005" style="color: hsl(273,88%,88%);">hello</span><span class="operator">.</span><span class="unresolved_reference">to_string</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="3365759661443752373" style="color: hsl(15,86%,51%);">y</span> <span class="operator">=</span> <span class="variable reference" data-binding-hash="8121853618659664005" style="color: hsl(273,88%,88%);">hello</span><span class="operator">.</span><span class="unresolved_reference">to_string</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+
+ <span class="keyword">let</span> <span class="variable declaration reference" data-binding-hash="794745962933817518" style="color: hsl(127,71%,87%);">x</span> <span class="operator">=</span> <span class="string_literal">"other color please!"</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="6717528807933952652" style="color: hsl(90,74%,79%);">y</span> <span class="operator">=</span> <span class="variable reference" data-binding-hash="794745962933817518" style="color: hsl(127,71%,87%);">x</span><span class="operator">.</span><span class="unresolved_reference">to_string</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+
+<span class="keyword">fn</span> <span class="function declaration">bar</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable reference" data-binding-hash="8121853618659664005" style="color: hsl(273,88%,88%);">hello</span> <span class="operator">=</span> <span class="string_literal">"hello"</span><span class="semicolon">;</span>
+<span class="brace">}</span></code></pre> \ No newline at end of file
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
new file mode 100644
index 000000000..c627bc9b0
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
@@ -0,0 +1,164 @@
+
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.function.unsafe { color: #BC8383; }
+.trait.unsafe { color: #BC8383; }
+.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; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code><span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">println</span> <span class="brace">{</span>
+ <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<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="parenthesis">(</span><span class="brace">{</span>
+ <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>io<span class="colon">:</span><span class="colon">:</span>_print<span class="parenthesis">(</span><span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>format_args_nl<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="brace">}</span><span class="parenthesis">)</span>
+<span class="brace">}</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute_bracket attribute">]</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">macro_export</span><span class="attribute_bracket attribute">]</span>
+<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">format_args</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute_bracket attribute">]</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">macro_export</span><span class="attribute_bracket attribute">]</span>
+<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">const_format_args</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute_bracket attribute">]</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">macro_export</span><span class="attribute_bracket attribute">]</span>
+<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">format_args_nl</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="keyword">mod</span> <span class="module declaration">panic</span> <span class="brace">{</span>
+ <span class="keyword">pub</span> <span class="keyword">macro</span> <span class="macro declaration">panic_2015</span> <span class="brace">{</span>
+ <span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">&gt;</span> <span class="parenthesis">(</span>
+ <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panicking<span class="colon">:</span><span class="colon">:</span>panic<span class="parenthesis">(</span><span class="string_literal">"explicit panic"</span><span class="parenthesis">)</span>
+ <span class="parenthesis">)</span><span class="comma">,</span>
+ <span class="parenthesis">(</span><span class="punctuation">$</span>msg<span class="colon">:</span>literal <span class="punctuation">$</span><span class="parenthesis">(</span><span class="comma">,</span><span class="parenthesis">)</span><span class="operator control">?</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">&gt;</span> <span class="parenthesis">(</span>
+ <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panicking<span class="colon">:</span><span class="colon">:</span>panic<span class="parenthesis">(</span><span class="punctuation">$</span>msg<span class="parenthesis">)</span>
+ <span class="parenthesis">)</span><span class="comma">,</span>
+ <span class="comment">// Use `panic_str` instead of `panic_display::&lt;&str&gt;` for non_fmt_panic lint.</span>
+ <span class="parenthesis">(</span><span class="punctuation">$</span>msg<span class="colon">:</span>expr <span class="punctuation">$</span><span class="parenthesis">(</span><span class="comma">,</span><span class="parenthesis">)</span><span class="operator control">?</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">&gt;</span> <span class="parenthesis">(</span>
+ <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panicking<span class="colon">:</span><span class="colon">:</span>panic_str<span class="parenthesis">(</span><span class="punctuation">$</span>msg<span class="parenthesis">)</span>
+ <span class="parenthesis">)</span><span class="comma">,</span>
+ <span class="comment">// Special-case the single-argument case for const_panic.</span>
+ <span class="parenthesis">(</span><span class="string_literal">"{}"</span><span class="comma">,</span> <span class="punctuation">$</span>arg<span class="colon">:</span>expr <span class="punctuation">$</span><span class="parenthesis">(</span><span class="comma">,</span><span class="parenthesis">)</span><span class="operator control">?</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">&gt;</span> <span class="parenthesis">(</span>
+ <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panicking<span class="colon">:</span><span class="colon">:</span>panic_display<span class="parenthesis">(</span><span class="operator">&</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span>
+ <span class="parenthesis">)</span><span class="comma">,</span>
+ <span class="parenthesis">(</span><span class="punctuation">$</span>fmt<span class="colon">:</span>expr<span class="comma">,</span> <span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<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="parenthesis">(</span>
+ <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panicking<span class="colon">:</span><span class="colon">:</span>panic_fmt<span class="parenthesis">(</span><span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>const_format_args<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span>fmt<span class="comma">,</span> <span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span><span class="punctuation">+</span><span class="parenthesis">)</span><span class="parenthesis">)</span>
+ <span class="parenthesis">)</span><span class="comma">,</span>
+ <span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="parenthesis attribute">(</span><span class="none attribute">std_panic</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">macro_export</span><span class="attribute_bracket attribute">]</span>
+<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">panic</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute_bracket attribute">]</span>
+<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">assert</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute_bracket attribute">]</span>
+<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">asm</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">toho</span> <span class="brace">{</span>
+ <span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">&gt;</span> <span class="parenthesis">(</span><span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panic<span class="punctuation">!</span><span class="parenthesis">(</span><span class="string_literal">"not yet implemented"</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<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="parenthesis">(</span><span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panic<span class="punctuation">!</span><span class="parenthesis">(</span><span class="string_literal">"not yet implemented: {}"</span><span class="comma">,</span> <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>format_args<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span><span class="punctuation">+</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+
+<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="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="escape_sequence">{{</span><span class="string_literal">Hello</span><span class="escape_sequence">}}</span><span class="string_literal">"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="comment">// from https://doc.rust-lang.org/std/fmt/index.html</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello"</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="comment">// =&gt; "Hello"</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello, </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="string_literal">"world"</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="comment">// =&gt; "Hello, world!"</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"The number is </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="numeric_literal">1</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="comment">// =&gt; "The number is 1"</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">?</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="parenthesis">(</span><span class="numeric_literal">3</span><span class="comma">,</span> <span class="numeric_literal">4</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="comment">// =&gt; "(3, 4)"</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">value</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> value<span class="operator">=</span><span class="numeric_literal">4</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="comment">// =&gt; "4"</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="numeric_literal">1</span><span class="comma">,</span> <span class="numeric_literal">2</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="comment">// =&gt; "1 2"</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="numeric_literal">4</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="numeric_literal">42</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="comment">// =&gt; "0042" with leading zerosV</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="numeric_literal">0</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="numeric_literal">1</span><span class="comma">,</span> <span class="numeric_literal">2</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="comment">// =&gt; "2 1 1 2"</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">argument</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> argument <span class="operator">=</span> <span class="string_literal">"test"</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="comment">// =&gt; "test"</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="numeric_literal">1</span><span class="comma">,</span> name <span class="operator">=</span> <span class="numeric_literal">2</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="comment">// =&gt; "2 1"</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">a</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">c</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">b</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> a<span class="operator">=</span><span class="string_literal">"a"</span><span class="comma">,</span> b<span class="operator">=</span><span class="char_literal">'b'</span><span class="comma">,</span> c<span class="operator">=</span><span class="numeric_literal">3</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="comment">// =&gt; "a 3 b"</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="escape_sequence">{{</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="escape_sequence">}}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="numeric_literal">2</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="comment">// =&gt; "{2}"</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">1</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="comma">,</span> <span class="numeric_literal">5</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="numeric_literal">5</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="variable">width</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="comma">,</span> width <span class="operator">=</span> <span class="numeric_literal">5</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">&lt;</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">-</span><span class="format_specifier">&lt;</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">^</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">&gt;</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">+</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="numeric_literal">5</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">#</span><span class="variable">x</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="numeric_literal">27</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="numeric_literal">5</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="numeric_literal">-</span><span class="numeric_literal">5</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">#</span><span class="numeric_literal">0</span><span class="numeric_literal">10</span><span class="variable">x</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="numeric_literal">27</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="numeric_literal">0</span><span class="format_specifier">}</span><span class="string_literal"> is </span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="comma">,</span> <span class="numeric_literal">0.01</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">}</span><span class="string_literal"> is </span><span class="format_specifier">{</span><span class="numeric_literal">2</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="numeric_literal">0</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="numeric_literal">5</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="comma">,</span> <span class="numeric_literal">0.01</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="numeric_literal">0</span><span class="format_specifier">}</span><span class="string_literal"> is </span><span class="format_specifier">{</span><span class="numeric_literal">2</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="numeric_literal">1</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="comma">,</span> <span class="numeric_literal">5</span><span class="comma">,</span> <span class="numeric_literal">0.01</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal"> is </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="format_specifier">*</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="comma">,</span> <span class="numeric_literal">5</span><span class="comma">,</span> <span class="numeric_literal">0.01</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal"> is </span><span class="format_specifier">{</span><span class="numeric_literal">2</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="format_specifier">*</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="comma">,</span> <span class="numeric_literal">5</span><span class="comma">,</span> <span class="numeric_literal">0.01</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal"> is </span><span class="format_specifier">{</span><span class="variable">number</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="variable">prec</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="comma">,</span> prec <span class="operator">=</span> <span class="numeric_literal">5</span><span class="comma">,</span> number <span class="operator">=</span> <span class="numeric_literal">0.01</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">, `</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="format_specifier">*</span><span class="format_specifier">}</span><span class="string_literal">` has 3 fractional digits"</span><span class="comma">,</span> <span class="string_literal">"Hello"</span><span class="comma">,</span> <span class="numeric_literal">3</span><span class="comma">,</span> name<span class="operator">=</span><span class="numeric_literal">1234.56</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">, `</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="format_specifier">*</span><span class="format_specifier">}</span><span class="string_literal">` has 3 characters"</span><span class="comma">,</span> <span class="string_literal">"Hello"</span><span class="comma">,</span> <span class="numeric_literal">3</span><span class="comma">,</span> name<span class="operator">=</span><span class="string_literal">"1234.56"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">, `</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">:</span><span class="format_specifier">&gt;</span><span class="numeric_literal">8</span><span class="format_specifier">.</span><span class="format_specifier">*</span><span class="format_specifier">}</span><span class="string_literal">` has 3 right-aligned characters"</span><span class="comma">,</span> <span class="string_literal">"Hello"</span><span class="comma">,</span> <span class="numeric_literal">3</span><span class="comma">,</span> name<span class="operator">=</span><span class="string_literal">"1234.56"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+
+ <span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="string_literal">"{}"</span>
+ <span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="string_literal">"{{}}"</span><span class="semicolon">;</span>
+
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="escape_sequence">{{</span><span class="escape_sequence">}}</span><span class="string_literal">"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="escape_sequence">{{</span><span class="string_literal"> Hello"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="escape_sequence">}}</span><span class="string_literal">"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="escape_sequence">{{</span><span class="string_literal">Hello</span><span class="escape_sequence">}}</span><span class="string_literal">"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="escape_sequence">{{</span><span class="string_literal"> Hello </span><span class="escape_sequence">}}</span><span class="string_literal">"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="escape_sequence">{{</span><span class="string_literal">Hello </span><span class="escape_sequence">}}</span><span class="string_literal">"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="escape_sequence">{{</span><span class="string_literal"> Hello</span><span class="escape_sequence">}}</span><span class="string_literal">"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">r"Hello, </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="string_literal">"world"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+
+ <span class="comment">// escape sequences</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello</span><span class="escape_sequence">\n</span><span class="string_literal">World"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="escape_sequence">\u{48}</span><span class="escape_sequence">\x65</span><span class="escape_sequence">\x6C</span><span class="escape_sequence">\x6C</span><span class="escape_sequence">\x6F</span><span class="string_literal"> World"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+
+ <span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="string_literal">"</span><span class="escape_sequence">\x28</span><span class="escape_sequence">\x28</span><span class="escape_sequence">\x00</span><span class="escape_sequence">\x63</span><span class="escape_sequence">\n</span><span class="string_literal">"</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="string_literal">b"</span><span class="escape_sequence">\x28</span><span class="escape_sequence">\x28</span><span class="escape_sequence">\x00</span><span class="escape_sequence">\x63</span><span class="escape_sequence">\n</span><span class="string_literal">"</span><span class="semicolon">;</span>
+
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="escape_sequence">\x41</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> A <span class="operator">=</span> <span class="numeric_literal">92</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">ничоси</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> ничоси <span class="operator">=</span> <span class="numeric_literal">92</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="variable">x</span><span class="format_specifier">?</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal"> "</span><span class="comma">,</span> <span class="unresolved_reference">thingy</span><span class="comma">,</span> <span class="unresolved_reference">n2</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">panic</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="numeric_literal">0</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">panic</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"more </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="numeric_literal">1</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">assert</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="bool_literal">true</span><span class="comma">,</span> <span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="numeric_literal">1</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">assert</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="bool_literal">true</span><span class="comma">,</span> <span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal"> asdasd"</span><span class="comma">,</span> <span class="numeric_literal">1</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">toho</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">fmt"</span><span class="comma">,</span> <span class="numeric_literal">0</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro unsafe">asm</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"mov eax, </span><span class="format_specifier">{</span><span class="numeric_literal">0</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">format_args</span><span class="macro_bang">!</span><span class="parenthesis">(</span>concat<span class="punctuation">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="parenthesis">)</span><span class="comma">,</span> <span class="string_literal">"{}"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+<span class="brace">}</span></code></pre> \ No newline at end of file
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
new file mode 100644
index 000000000..0716bae75
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
@@ -0,0 +1,126 @@
+
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.function.unsafe { color: #BC8383; }
+.trait.unsafe { color: #BC8383; }
+.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; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code><span class="keyword">macro_rules</span><span class="macro_bang">!</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="macro_bang">!</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>
+
+<span class="keyword">union</span> <span class="union declaration">Union</span> <span class="brace">{</span>
+ <span class="field declaration">a</span><span class="colon">:</span> <span class="builtin_type">u32</span><span class="comma">,</span>
+ <span class="field declaration">b</span><span class="colon">:</span> <span class="builtin_type">f32</span><span class="comma">,</span>
+<span class="brace">}</span>
+
+<span class="keyword">struct</span> <span class="struct declaration">Struct</span> <span class="brace">{</span> <span class="field declaration">field</span><span class="colon">:</span> <span class="builtin_type">i32</span> <span class="brace">}</span>
+<span class="keyword">impl</span> <span class="struct">Struct</span> <span class="brace">{</span>
+ <span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function associated declaration reference unsafe">unsafe_method</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">repr</span><span class="parenthesis attribute">(</span><span class="none attribute">packed</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span>
+<span class="keyword">struct</span> <span class="struct declaration">Packed</span> <span class="brace">{</span>
+ <span class="field declaration">a</span><span class="colon">:</span> <span class="builtin_type">u16</span><span class="comma">,</span>
+<span class="brace">}</span>
+
+<span class="keyword unsafe">unsafe</span> <span class="keyword">trait</span> <span class="trait declaration unsafe">UnsafeTrait</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="keyword unsafe">unsafe</span> <span class="keyword">impl</span> <span class="trait unsafe">UnsafeTrait</span> <span class="keyword">for</span> <span class="struct">Packed</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="keyword">impl</span> <span class="punctuation">!</span><span class="trait">UnsafeTrait</span> <span class="keyword">for</span> <span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="keyword">fn</span> <span class="function declaration">unsafe_trait_bound</span><span class="angle">&lt;</span><span class="type_param declaration">T</span><span class="colon">:</span> <span class="trait">UnsafeTrait</span><span class="angle">&gt;</span><span class="parenthesis">(</span><span class="punctuation">_</span><span class="colon">:</span> <span class="type_param">T</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="keyword">trait</span> <span class="trait declaration">DoTheAutoref</span> <span class="brace">{</span>
+ <span class="keyword">fn</span> <span class="function associated declaration reference trait">calls_autoref</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+
+<span class="keyword">impl</span> <span class="trait">DoTheAutoref</span> <span class="keyword">for</span> <span class="builtin_type">u16</span> <span class="brace">{</span>
+ <span class="keyword">fn</span> <span class="function associated declaration reference trait">calls_autoref</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="brace">}</span>
+
+<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>
+ <span class="keyword control">match</span> <span class="variable">u</span> <span class="brace">{</span>
+ <span class="union">Union</span> <span class="brace">{</span> <span class="field unsafe">b</span><span class="colon">:</span> <span class="numeric_literal">0</span> <span class="brace">}</span> <span class="operator">=&gt;</span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="comma">,</span>
+ <span class="union">Union</span> <span class="brace">{</span> <span class="field unsafe">a</span> <span class="brace">}</span> <span class="operator">=&gt;</span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="comma">,</span>
+ <span class="brace">}</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="operator">.</span><span class="function associated reference unsafe">unsafe_method</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+
+ <span class="comment">// unsafe deref</span>
+ <span class="operator unsafe">*</span><span class="variable">x</span><span class="semicolon">;</span>
+
+ <span class="comment">// unsafe access to a static mut</span>
+ <span class="static mutable unsafe">MUT_GLOBAL</span><span class="operator">.</span><span class="field">field</span><span class="semicolon">;</span>
+ <span class="static">GLOBAL</span><span class="operator">.</span><span class="field">field</span><span class="semicolon">;</span>
+
+ <span class="comment">// unsafe ref of packed fields</span>
+ <span class="keyword">let</span> <span class="variable declaration">packed</span> <span class="operator">=</span> <span class="struct">Packed</span> <span class="brace">{</span> <span class="field">a</span><span class="colon">:</span> <span class="numeric_literal">0</span> <span class="brace">}</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="variable declaration reference">a</span> <span class="operator">=</span> <span class="operator unsafe">&</span><span class="variable">packed</span><span class="operator">.</span><span class="field">a</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="keyword unsafe">ref</span> <span class="variable declaration reference">a</span> <span class="operator">=</span> <span class="variable">packed</span><span class="operator">.</span><span class="field">a</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="struct">Packed</span> <span class="brace">{</span> <span class="keyword unsafe">ref</span> <span class="field">a</span> <span class="brace">}</span> <span class="operator">=</span> <span class="variable">packed</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="struct">Packed</span> <span class="brace">{</span> <span class="field">a</span><span class="colon">:</span> <span class="keyword unsafe">ref</span> <span class="variable declaration reference">_a</span> <span class="brace">}</span> <span class="operator">=</span> <span class="variable">packed</span><span class="semicolon">;</span>
+
+ <span class="comment">// unsafe auto ref of packed field</span>
+ <span class="variable">packed</span><span class="operator">.</span><span class="field">a</span><span class="operator">.</span><span class="function associated reference trait unsafe">calls_autoref</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="brace">}</span>
+<span class="brace">}</span></code></pre> \ No newline at end of file
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs
new file mode 100644
index 000000000..99be7c664
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs
@@ -0,0 +1,1096 @@
+use std::time::Instant;
+
+use expect_test::{expect_file, ExpectFile};
+use ide_db::SymbolKind;
+use test_utils::{bench, bench_fixture, skip_slow_tests, AssertLinear};
+
+use crate::{fixture, FileRange, HlTag, TextRange};
+
+#[test]
+fn attributes() {
+ check_highlighting(
+ r#"
+//- proc_macros: identity
+//- minicore: derive, copy
+#[allow(dead_code)]
+#[rustfmt::skip]
+#[proc_macros::identity]
+#[derive(Copy)]
+/// This is a doc comment
+// This is a normal comment
+/// This is a doc comment
+#[derive(Copy)]
+// This is another normal comment
+/// This is another doc comment
+// This is another normal comment
+#[derive(Copy)]
+// The reason for these being here is to test AttrIds
+struct Foo;
+"#,
+ expect_file!["./test_data/highlight_attributes.html"],
+ false,
+ );
+}
+
+#[test]
+fn macros() {
+ check_highlighting(
+ r#"
+//- proc_macros: mirror
+proc_macros::mirror! {
+ {
+ ,i32 :x pub
+ ,i32 :y pub
+ } Foo struct
+}
+macro_rules! def_fn {
+ ($($tt:tt)*) => {$($tt)*}
+}
+
+def_fn! {
+ fn bar() -> u32 {
+ 100
+ }
+}
+
+macro_rules! dont_color_me_braces {
+ () => {0}
+}
+
+macro_rules! noop {
+ ($expr:expr) => {
+ $expr
+ }
+}
+
+/// textually shadow previous definition
+macro_rules! noop {
+ ($expr:expr) => {
+ $expr
+ }
+}
+
+macro_rules! keyword_frag {
+ ($type:ty) => ($type)
+}
+
+macro with_args($i:ident) {
+ $i
+}
+
+macro without_args {
+ ($i:ident) => {
+ $i
+ }
+}
+
+fn main() {
+ println!("Hello, {}!", 92);
+ dont_color_me_braces!();
+ noop!(noop!(1));
+}
+"#,
+ expect_file!["./test_data/highlight_macros.html"],
+ false,
+ );
+}
+
+/// If what you want to test feels like a specific entity consider making a new test instead,
+/// this test fixture here in fact should shrink instead of grow ideally.
+#[test]
+fn test_highlighting() {
+ check_highlighting(
+ r#"
+//- minicore: derive, copy
+//- /main.rs crate:main deps:foo
+use inner::{self as inner_mod};
+mod inner {}
+
+pub mod ops {
+ #[lang = "fn_once"]
+ pub trait FnOnce<Args> {}
+
+ #[lang = "fn_mut"]
+ pub trait FnMut<Args>: FnOnce<Args> {}
+
+ #[lang = "fn"]
+ pub trait Fn<Args>: FnMut<Args> {}
+}
+
+struct Foo {
+ x: u32,
+}
+
+trait Bar {
+ fn bar(&self) -> i32;
+}
+
+impl Bar for Foo {
+ fn bar(&self) -> i32 {
+ self.x
+ }
+}
+
+impl Foo {
+ fn baz(mut self, f: Foo) -> i32 {
+ f.baz(self)
+ }
+
+ fn qux(&mut self) {
+ self.x = 0;
+ }
+
+ fn quop(&self) -> i32 {
+ self.x
+ }
+}
+
+use self::FooCopy::{self as BarCopy};
+
+#[derive(Copy)]
+struct FooCopy {
+ x: u32,
+}
+
+impl FooCopy {
+ fn baz(self, f: FooCopy) -> u32 {
+ f.baz(self)
+ }
+
+ fn qux(&mut self) {
+ self.x = 0;
+ }
+
+ fn quop(&self) -> u32 {
+ self.x
+ }
+}
+
+fn str() {
+ str();
+}
+
+fn foo<'a, T>() -> T {
+ foo::<'a, i32>()
+}
+
+fn never() -> ! {
+ loop {}
+}
+
+fn const_param<const FOO: usize>() -> usize {
+ const_param::<{ FOO }>();
+ FOO
+}
+
+use ops::Fn;
+fn baz<F: Fn() -> ()>(f: F) {
+ f()
+}
+
+fn foobar() -> impl Copy {}
+
+fn foo() {
+ let bar = foobar();
+}
+
+// comment
+fn main() {
+ let mut x = 42;
+ x += 1;
+ let y = &mut x;
+ let z = &y;
+
+ let Foo { x: z, y } = Foo { x: z, y };
+
+ y;
+
+ let mut foo = Foo { x, y: x };
+ let foo2 = Foo { x, y: x };
+ foo.quop();
+ foo.qux();
+ foo.baz(foo2);
+
+ let mut copy = FooCopy { x };
+ copy.quop();
+ copy.qux();
+ copy.baz(copy);
+
+ let a = |x| x;
+ let bar = Foo::baz;
+
+ let baz = (-42,);
+ let baz = -baz.0;
+
+ let _ = !true;
+
+ 'foo: loop {
+ break 'foo;
+ continue 'foo;
+ }
+}
+
+enum Option<T> {
+ Some(T),
+ None,
+}
+use Option::*;
+
+impl<T> Option<T> {
+ fn and<U>(self, other: Option<U>) -> Option<(T, U)> {
+ match other {
+ None => unimplemented!(),
+ Nope => Nope,
+ }
+ }
+}
+
+async fn learn_and_sing() {
+ let song = learn_song().await;
+ sing_song(song).await;
+}
+
+async fn async_main() {
+ let f1 = learn_and_sing();
+ let f2 = dance();
+ futures::join!(f1, f2);
+}
+
+fn use_foo_items() {
+ let bob = foo::Person {
+ name: "Bob",
+ age: foo::consts::NUMBER,
+ };
+
+ let control_flow = foo::identity(foo::ControlFlow::Continue);
+
+ if control_flow.should_die() {
+ foo::die!();
+ }
+}
+
+pub enum Bool { True, False }
+
+impl Bool {
+ pub const fn to_primitive(self) -> bool {
+ true
+ }
+}
+const USAGE_OF_BOOL:bool = Bool::True.to_primitive();
+
+trait Baz {
+ type Qux;
+}
+
+fn baz<T>(t: T)
+where
+ T: Baz,
+ <T as Baz>::Qux: Bar {}
+
+fn gp_shadows_trait<Baz: Bar>() {
+ Baz::bar;
+}
+
+//- /foo.rs crate:foo
+pub struct Person {
+ pub name: &'static str,
+ pub age: u8,
+}
+
+pub enum ControlFlow {
+ Continue,
+ Die,
+}
+
+impl ControlFlow {
+ pub fn should_die(self) -> bool {
+ matches!(self, ControlFlow::Die)
+ }
+}
+
+pub fn identity<T>(x: T) -> T { x }
+
+pub mod consts {
+ pub const NUMBER: i64 = 92;
+}
+
+macro_rules! die {
+ () => {
+ panic!();
+ };
+}
+"#,
+ expect_file!["./test_data/highlight_general.html"],
+ false,
+ );
+}
+
+#[test]
+fn test_lifetime_highlighting() {
+ check_highlighting(
+ r#"
+//- minicore: derive
+
+#[derive()]
+struct Foo<'a, 'b, 'c> where 'a: 'a, 'static: 'static {
+ field: &'a (),
+ field2: &'static (),
+}
+impl<'a> Foo<'_, 'a, 'static>
+where
+ 'a: 'a,
+ 'static: 'static
+{}
+"#,
+ expect_file!["./test_data/highlight_lifetimes.html"],
+ false,
+ );
+}
+
+#[test]
+fn test_keyword_highlighting() {
+ check_highlighting(
+ r#"
+extern crate self;
+
+use crate;
+use self;
+mod __ {
+ use super::*;
+}
+
+macro_rules! void {
+ ($($tt:tt)*) => {}
+}
+void!(Self);
+struct __ where Self:;
+fn __(_: Self) {}
+"#,
+ expect_file!["./test_data/highlight_keywords.html"],
+ false,
+ );
+}
+
+#[test]
+fn test_string_highlighting() {
+ // The format string detection is based on macro-expansion,
+ // thus, we have to copy the macro definition from `std`
+ check_highlighting(
+ r#"
+macro_rules! println {
+ ($($arg:tt)*) => ({
+ $crate::io::_print($crate::format_args_nl!($($arg)*));
+ })
+}
+#[rustc_builtin_macro]
+#[macro_export]
+macro_rules! format_args {}
+#[rustc_builtin_macro]
+#[macro_export]
+macro_rules! const_format_args {}
+#[rustc_builtin_macro]
+#[macro_export]
+macro_rules! format_args_nl {}
+
+mod panic {
+ pub macro panic_2015 {
+ () => (
+ $crate::panicking::panic("explicit panic")
+ ),
+ ($msg:literal $(,)?) => (
+ $crate::panicking::panic($msg)
+ ),
+ // Use `panic_str` instead of `panic_display::<&str>` for non_fmt_panic lint.
+ ($msg:expr $(,)?) => (
+ $crate::panicking::panic_str($msg)
+ ),
+ // Special-case the single-argument case for const_panic.
+ ("{}", $arg:expr $(,)?) => (
+ $crate::panicking::panic_display(&$arg)
+ ),
+ ($fmt:expr, $($arg:tt)+) => (
+ $crate::panicking::panic_fmt($crate::const_format_args!($fmt, $($arg)+))
+ ),
+ }
+}
+
+#[rustc_builtin_macro(std_panic)]
+#[macro_export]
+macro_rules! panic {}
+#[rustc_builtin_macro]
+macro_rules! assert {}
+#[rustc_builtin_macro]
+macro_rules! asm {}
+
+macro_rules! toho {
+ () => ($crate::panic!("not yet implemented"));
+ ($($arg:tt)+) => ($crate::panic!("not yet implemented: {}", $crate::format_args!($($arg)+)));
+}
+
+fn main() {
+ println!("Hello {{Hello}}");
+ // from https://doc.rust-lang.org/std/fmt/index.html
+ println!("Hello"); // => "Hello"
+ println!("Hello, {}!", "world"); // => "Hello, world!"
+ println!("The number is {}", 1); // => "The number is 1"
+ println!("{:?}", (3, 4)); // => "(3, 4)"
+ println!("{value}", value=4); // => "4"
+ println!("{} {}", 1, 2); // => "1 2"
+ println!("{:04}", 42); // => "0042" with leading zerosV
+ println!("{1} {} {0} {}", 1, 2); // => "2 1 1 2"
+ println!("{argument}", argument = "test"); // => "test"
+ println!("{name} {}", 1, name = 2); // => "2 1"
+ println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b"
+ println!("{{{}}}", 2); // => "{2}"
+ println!("Hello {:5}!", "x");
+ println!("Hello {:1$}!", "x", 5);
+ println!("Hello {1:0$}!", 5, "x");
+ println!("Hello {:width$}!", "x", width = 5);
+ println!("Hello {:<5}!", "x");
+ println!("Hello {:-<5}!", "x");
+ println!("Hello {:^5}!", "x");
+ println!("Hello {:>5}!", "x");
+ println!("Hello {:+}!", 5);
+ println!("{:#x}!", 27);
+ println!("Hello {:05}!", 5);
+ println!("Hello {:05}!", -5);
+ println!("{:#010x}!", 27);
+ println!("Hello {0} is {1:.5}", "x", 0.01);
+ println!("Hello {1} is {2:.0$}", 5, "x", 0.01);
+ println!("Hello {0} is {2:.1$}", "x", 5, 0.01);
+ println!("Hello {} is {:.*}", "x", 5, 0.01);
+ println!("Hello {} is {2:.*}", "x", 5, 0.01);
+ println!("Hello {} is {number:.prec$}", "x", prec = 5, number = 0.01);
+ println!("{}, `{name:.*}` has 3 fractional digits", "Hello", 3, name=1234.56);
+ println!("{}, `{name:.*}` has 3 characters", "Hello", 3, name="1234.56");
+ println!("{}, `{name:>8.*}` has 3 right-aligned characters", "Hello", 3, name="1234.56");
+
+ let _ = "{}"
+ let _ = "{{}}";
+
+ println!("Hello {{}}");
+ println!("{{ Hello");
+ println!("Hello }}");
+ println!("{{Hello}}");
+ println!("{{ Hello }}");
+ println!("{{Hello }}");
+ println!("{{ Hello}}");
+
+ println!(r"Hello, {}!", "world");
+
+ // escape sequences
+ println!("Hello\nWorld");
+ println!("\u{48}\x65\x6C\x6C\x6F World");
+
+ let _ = "\x28\x28\x00\x63\n";
+ let _ = b"\x28\x28\x00\x63\n";
+
+ println!("{\x41}", A = 92);
+ println!("{ничоси}", ничоси = 92);
+
+ println!("{:x?} {} ", thingy, n2);
+ panic!("{}", 0);
+ panic!("more {}", 1);
+ assert!(true, "{}", 1);
+ assert!(true, "{} asdasd", 1);
+ toho!("{}fmt", 0);
+ asm!("mov eax, {0}");
+ format_args!(concat!("{}"), "{}");
+}"#,
+ expect_file!["./test_data/highlight_strings.html"],
+ false,
+ );
+}
+
+#[test]
+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() {}
+
+union Union {
+ a: u32,
+ b: f32,
+}
+
+struct Struct { field: i32 }
+impl Struct {
+ unsafe fn unsafe_method(&self) {}
+}
+
+#[repr(packed)]
+struct Packed {
+ a: u16,
+}
+
+unsafe trait UnsafeTrait {}
+unsafe impl UnsafeTrait for Packed {}
+impl !UnsafeTrait for () {}
+
+fn unsafe_trait_bound<T: UnsafeTrait>(_: T) {}
+
+trait DoTheAutoref {
+ fn calls_autoref(&self);
+}
+
+impl DoTheAutoref for u16 {
+ fn calls_autoref(&self) {}
+}
+
+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;
+ match u {
+ Union { b: 0 } => (),
+ Union { a } => (),
+ }
+ Struct { field: 0 }.unsafe_method();
+
+ // unsafe deref
+ *x;
+
+ // unsafe access to a static mut
+ MUT_GLOBAL.field;
+ GLOBAL.field;
+
+ // unsafe ref of packed fields
+ let packed = Packed { a: 0 };
+ let a = &packed.a;
+ let ref a = packed.a;
+ let Packed { ref a } = packed;
+ let Packed { a: ref _a } = packed;
+
+ // unsafe auto ref of packed field
+ packed.a.calls_autoref();
+ }
+}
+"#,
+ expect_file!["./test_data/highlight_unsafe.html"],
+ false,
+ );
+}
+
+#[test]
+fn test_highlight_doc_comment() {
+ check_highlighting(
+ r#"
+//- /main.rs
+//! This is a module to test doc injection.
+//! ```
+//! fn test() {}
+//! ```
+
+mod outline_module;
+
+/// ```
+/// let _ = "early doctests should not go boom";
+/// ```
+struct Foo {
+ bar: bool,
+}
+
+/// This is an impl with a code block.
+///
+/// ```
+/// fn foo() {
+///
+/// }
+/// ```
+impl Foo {
+ /// ```
+ /// let _ = "Call me
+ // KILLER WHALE
+ /// Ishmael.";
+ /// ```
+ pub const bar: bool = true;
+
+ /// Constructs a new `Foo`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # #![allow(unused_mut)]
+ /// let mut foo: Foo = Foo::new();
+ /// ```
+ pub const fn new() -> Foo {
+ Foo { bar: true }
+ }
+
+ /// `bar` method on `Foo`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use x::y;
+ ///
+ /// let foo = Foo::new();
+ ///
+ /// // calls bar on foo
+ /// assert!(foo.bar());
+ ///
+ /// let bar = foo.bar || Foo::bar;
+ ///
+ /// /* multi-line
+ /// comment */
+ ///
+ /// let multi_line_string = "Foo
+ /// bar\n
+ /// ";
+ ///
+ /// ```
+ ///
+ /// ```rust,no_run
+ /// let foobar = Foo::new().bar();
+ /// ```
+ ///
+ /// ~~~rust,no_run
+ /// // code block with tilde.
+ /// let foobar = Foo::new().bar();
+ /// ~~~
+ ///
+ /// ```
+ /// // functions
+ /// fn foo<T, const X: usize>(arg: i32) {
+ /// let x: T = X;
+ /// }
+ /// ```
+ ///
+ /// ```sh
+ /// echo 1
+ /// ```
+ pub fn foo(&self) -> bool {
+ true
+ }
+}
+
+/// [`Foo`](Foo) is a struct
+/// This function is > [`all_the_links`](all_the_links) <
+/// [`noop`](noop) is a macro below
+/// [`Item`] is a struct in the module [`module`]
+///
+/// [`Item`]: module::Item
+/// [mix_and_match]: ThisShouldntResolve
+pub fn all_the_links() {}
+
+pub mod module {
+ pub struct Item;
+}
+
+/// ```
+/// macro_rules! noop { ($expr:expr) => { $expr }}
+/// noop!(1);
+/// ```
+macro_rules! noop {
+ ($expr:expr) => {
+ $expr
+ }
+}
+
+/// ```rust
+/// let _ = example(&[1, 2, 3]);
+/// ```
+///
+/// ```
+/// loop {}
+#[cfg_attr(not(feature = "false"), doc = "loop {}")]
+#[doc = "loop {}"]
+/// ```
+///
+#[cfg_attr(feature = "alloc", doc = "```rust")]
+#[cfg_attr(not(feature = "alloc"), doc = "```ignore")]
+/// let _ = example(&alloc::vec![1, 2, 3]);
+/// ```
+pub fn mix_and_match() {}
+
+/**
+It is beyond me why you'd use these when you got ///
+```rust
+let _ = example(&[1, 2, 3]);
+```
+[`block_comments2`] tests these with indentation
+ */
+pub fn block_comments() {}
+
+/**
+ Really, I don't get it
+ ```rust
+ let _ = example(&[1, 2, 3]);
+ ```
+ [`block_comments`] tests these without indentation
+*/
+pub fn block_comments2() {}
+
+//- /outline_module.rs
+//! This is an outline module whose purpose is to test that its inline attribute injection does not
+//! spill into its parent.
+//! ```
+//! fn test() {}
+//! ```
+"#,
+ expect_file!["./test_data/highlight_doctest.html"],
+ false,
+ );
+}
+
+#[test]
+fn test_extern_crate() {
+ check_highlighting(
+ r#"
+//- /main.rs crate:main deps:std,alloc
+extern crate std;
+extern crate alloc as abc;
+//- /std/lib.rs crate:std
+pub struct S;
+//- /alloc/lib.rs crate:alloc
+pub struct A
+"#,
+ expect_file!["./test_data/highlight_extern_crate.html"],
+ false,
+ );
+}
+
+#[test]
+fn test_crate_root() {
+ check_highlighting(
+ r#"
+//- minicore: iterators
+//- /main.rs crate:main deps:foo
+extern crate foo;
+use core::iter;
+
+pub const NINETY_TWO: u8 = 92;
+
+use foo as foooo;
+
+pub(crate) fn main() {
+ let baz = iter::repeat(92);
+}
+
+mod bar {
+ pub(in super) const FORTY_TWO: u8 = 42;
+
+ mod baz {
+ use super::super::NINETY_TWO;
+ use crate::foooo::Point;
+
+ pub(in super::super) const TWENTY_NINE: u8 = 29;
+ }
+}
+//- /foo.rs crate:foo
+struct Point {
+ x: u8,
+ y: u8,
+}
+
+mod inner {
+ pub(super) fn swap(p: crate::Point) -> crate::Point {
+ crate::Point { x: p.y, y: p.x }
+ }
+}
+"#,
+ expect_file!["./test_data/highlight_crate_root.html"],
+ false,
+ );
+}
+
+#[test]
+fn test_default_library() {
+ check_highlighting(
+ r#"
+//- minicore: option, iterators
+use core::iter;
+
+fn main() {
+ let foo = Some(92);
+ let nums = iter::repeat(foo.unwrap());
+}
+"#,
+ expect_file!["./test_data/highlight_default_library.html"],
+ false,
+ );
+}
+
+#[test]
+fn test_associated_function() {
+ check_highlighting(
+ r#"
+fn not_static() {}
+
+struct foo {}
+
+impl foo {
+ pub fn is_static() {}
+ pub fn is_not_static(&self) {}
+}
+
+trait t {
+ fn t_is_static() {}
+ fn t_is_not_static(&self) {}
+}
+
+impl t for foo {
+ pub fn is_static() {}
+ pub fn is_not_static(&self) {}
+}
+"#,
+ expect_file!["./test_data/highlight_assoc_functions.html"],
+ false,
+ )
+}
+
+#[test]
+fn test_injection() {
+ check_highlighting(
+ r##"
+fn fixture(ra_fixture: &str) {}
+
+fn main() {
+ fixture(r#"
+trait Foo {
+ fn foo() {
+ println!("2 + 2 = {}", 4);
+ }
+}"#
+ );
+ fixture(r"
+fn foo() {
+ foo(\$0{
+ 92
+ }\$0)
+}"
+ );
+}
+"##,
+ expect_file!["./test_data/highlight_injection.html"],
+ false,
+ );
+}
+
+#[test]
+fn test_operators() {
+ check_highlighting(
+ r##"
+fn main() {
+ 1 + 1 - 1 * 1 / 1 % 1 | 1 & 1 ! 1 ^ 1 >> 1 << 1;
+ let mut a = 0;
+ a += 1;
+ a -= 1;
+ a *= 1;
+ a /= 1;
+ a %= 1;
+ a |= 1;
+ a &= 1;
+ a ^= 1;
+ a >>= 1;
+ a <<= 1;
+}
+"##,
+ expect_file!["./test_data/highlight_operators.html"],
+ false,
+ );
+}
+
+#[test]
+fn test_mod_hl_injection() {
+ check_highlighting(
+ r##"
+//- /foo.rs
+//! [Struct]
+//! This is an intra doc injection test for modules
+//! [Struct]
+//! This is an intra doc injection test for modules
+
+pub struct Struct;
+//- /lib.rs crate:foo
+/// [crate::foo::Struct]
+/// This is an intra doc injection test for modules
+/// [crate::foo::Struct]
+/// This is an intra doc injection test for modules
+mod foo;
+"##,
+ expect_file!["./test_data/highlight_module_docs_inline.html"],
+ false,
+ );
+ check_highlighting(
+ r##"
+//- /lib.rs crate:foo
+/// [crate::foo::Struct]
+/// This is an intra doc injection test for modules
+/// [crate::foo::Struct]
+/// This is an intra doc injection test for modules
+mod foo;
+//- /foo.rs
+//! [Struct]
+//! This is an intra doc injection test for modules
+//! [Struct]
+//! This is an intra doc injection test for modules
+
+pub struct Struct;
+"##,
+ expect_file!["./test_data/highlight_module_docs_outline.html"],
+ false,
+ );
+}
+
+#[test]
+#[cfg_attr(
+ all(unix, not(target_pointer_width = "64")),
+ ignore = "depends on `DefaultHasher` outputs"
+)]
+fn test_rainbow_highlighting() {
+ check_highlighting(
+ r#"
+fn main() {
+ let hello = "hello";
+ let x = hello.to_string();
+ let y = hello.to_string();
+
+ let x = "other color please!";
+ let y = x.to_string();
+}
+
+fn bar() {
+ let mut hello = "hello";
+}
+"#,
+ expect_file!["./test_data/highlight_rainbow.html"],
+ true,
+ );
+}
+
+#[test]
+fn test_ranges() {
+ let (analysis, file_id) = fixture::file(
+ r#"
+#[derive(Clone, Debug)]
+struct Foo {
+ pub x: i32,
+ pub y: i32,
+}
+"#,
+ );
+
+ // The "x"
+ let highlights = &analysis
+ .highlight_range(FileRange { file_id, range: TextRange::at(45.into(), 1.into()) })
+ .unwrap();
+
+ assert_eq!(&highlights[0].highlight.to_string(), "field.declaration.public");
+}
+
+#[test]
+fn ranges_sorted() {
+ let (analysis, file_id) = fixture::file(
+ r#"
+#[foo(bar = "bar")]
+macro_rules! test {}
+}"#
+ .trim(),
+ );
+ let _ = analysis.highlight(file_id).unwrap();
+}
+
+/// Highlights the code given by the `ra_fixture` argument, renders the
+/// result as HTML, and compares it with the HTML file given as `snapshot`.
+/// Note that the `snapshot` file is overwritten by the rendered HTML.
+fn check_highlighting(ra_fixture: &str, expect: ExpectFile, rainbow: bool) {
+ let (analysis, file_id) = fixture::file(ra_fixture.trim());
+ let actual_html = &analysis.highlight_as_html(file_id, rainbow).unwrap();
+ expect.assert_eq(actual_html)
+}
+
+#[test]
+fn benchmark_syntax_highlighting_long_struct() {
+ if skip_slow_tests() {
+ return;
+ }
+
+ let fixture = bench_fixture::big_struct();
+ let (analysis, file_id) = fixture::file(&fixture);
+
+ let hash = {
+ let _pt = bench("syntax highlighting long struct");
+ analysis
+ .highlight(file_id)
+ .unwrap()
+ .iter()
+ .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Struct))
+ .count()
+ };
+ assert_eq!(hash, 2001);
+}
+
+#[test]
+fn syntax_highlighting_not_quadratic() {
+ if skip_slow_tests() {
+ return;
+ }
+
+ let mut al = AssertLinear::default();
+ while al.next_round() {
+ for i in 6..=10 {
+ let n = 1 << i;
+
+ let fixture = bench_fixture::big_struct_n(n);
+ let (analysis, file_id) = fixture::file(&fixture);
+
+ let time = Instant::now();
+
+ let hash = analysis
+ .highlight(file_id)
+ .unwrap()
+ .iter()
+ .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Struct))
+ .count();
+ assert!(hash > n as usize);
+
+ let elapsed = time.elapsed();
+ al.sample(n as f64, elapsed.as_millis() as f64);
+ }
+ }
+}
+
+#[test]
+fn benchmark_syntax_highlighting_parser() {
+ if skip_slow_tests() {
+ return;
+ }
+
+ let fixture = bench_fixture::glorious_old_parser();
+ let (analysis, file_id) = fixture::file(&fixture);
+
+ let hash = {
+ let _pt = bench("syntax highlighting parser");
+ analysis
+ .highlight(file_id)
+ .unwrap()
+ .iter()
+ .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Function))
+ .count()
+ };
+ assert_eq!(hash, 1609);
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_tree.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_tree.rs
new file mode 100644
index 000000000..9003e7cd3
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_tree.rs
@@ -0,0 +1,339 @@
+use ide_db::base_db::{FileId, SourceDatabase};
+use ide_db::RootDatabase;
+use syntax::{
+ AstNode, NodeOrToken, SourceFile, SyntaxKind::STRING, SyntaxToken, TextRange, TextSize,
+};
+
+// Feature: Show Syntax Tree
+//
+// Shows the parse tree of the current file. It exists mostly for debugging
+// rust-analyzer itself.
+//
+// |===
+// | Editor | Action Name
+//
+// | VS Code | **Rust Analyzer: Show Syntax Tree**
+// |===
+// image::https://user-images.githubusercontent.com/48062697/113065586-068bdb80-91b1-11eb-9507-fee67f9f45a0.gif[]
+pub(crate) fn syntax_tree(
+ db: &RootDatabase,
+ file_id: FileId,
+ text_range: Option<TextRange>,
+) -> String {
+ let parse = db.parse(file_id);
+ if let Some(text_range) = text_range {
+ let node = match parse.tree().syntax().covering_element(text_range) {
+ NodeOrToken::Node(node) => node,
+ NodeOrToken::Token(token) => {
+ if let Some(tree) = syntax_tree_for_string(&token, text_range) {
+ return tree;
+ }
+ token.parent().unwrap()
+ }
+ };
+
+ format!("{:#?}", node)
+ } else {
+ format!("{:#?}", parse.tree().syntax())
+ }
+}
+
+/// Attempts parsing the selected contents of a string literal
+/// as rust syntax and returns its syntax tree
+fn syntax_tree_for_string(token: &SyntaxToken, text_range: TextRange) -> Option<String> {
+ // When the range is inside a string
+ // we'll attempt parsing it as rust syntax
+ // to provide the syntax tree of the contents of the string
+ match token.kind() {
+ STRING => syntax_tree_for_token(token, text_range),
+ _ => None,
+ }
+}
+
+fn syntax_tree_for_token(node: &SyntaxToken, text_range: TextRange) -> Option<String> {
+ // Range of the full node
+ let node_range = node.text_range();
+ let text = node.text().to_string();
+
+ // We start at some point inside the node
+ // Either we have selected the whole string
+ // or our selection is inside it
+ let start = text_range.start() - node_range.start();
+
+ // how many characters we have selected
+ let len = text_range.len();
+
+ let node_len = node_range.len();
+
+ let start = start;
+
+ // We want to cap our length
+ let len = len.min(node_len);
+
+ // Ensure our slice is inside the actual string
+ let end =
+ if start + len < TextSize::of(&text) { start + len } else { TextSize::of(&text) - start };
+
+ let text = &text[TextRange::new(start, end)];
+
+ // Remove possible extra string quotes from the start
+ // and the end of the string
+ let text = text
+ .trim_start_matches('r')
+ .trim_start_matches('#')
+ .trim_start_matches('"')
+ .trim_end_matches('#')
+ .trim_end_matches('"')
+ .trim()
+ // Remove custom markers
+ .replace("$0", "");
+
+ let parsed = SourceFile::parse(&text);
+
+ // If the "file" parsed without errors,
+ // return its syntax
+ if parsed.errors().is_empty() {
+ return Some(format!("{:#?}", parsed.tree().syntax()));
+ }
+
+ None
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::expect;
+
+ use crate::fixture;
+
+ fn check(ra_fixture: &str, expect: expect_test::Expect) {
+ let (analysis, file_id) = fixture::file(ra_fixture);
+ let syn = analysis.syntax_tree(file_id, None).unwrap();
+ expect.assert_eq(&syn)
+ }
+ fn check_range(ra_fixture: &str, expect: expect_test::Expect) {
+ let (analysis, frange) = fixture::range(ra_fixture);
+ let syn = analysis.syntax_tree(frange.file_id, Some(frange.range)).unwrap();
+ expect.assert_eq(&syn)
+ }
+
+ #[test]
+ fn test_syntax_tree_without_range() {
+ // Basic syntax
+ check(
+ r#"fn foo() {}"#,
+ expect![[r#"
+ SOURCE_FILE@0..11
+ FN@0..11
+ FN_KW@0..2 "fn"
+ WHITESPACE@2..3 " "
+ NAME@3..6
+ IDENT@3..6 "foo"
+ PARAM_LIST@6..8
+ L_PAREN@6..7 "("
+ R_PAREN@7..8 ")"
+ WHITESPACE@8..9 " "
+ BLOCK_EXPR@9..11
+ STMT_LIST@9..11
+ L_CURLY@9..10 "{"
+ R_CURLY@10..11 "}"
+ "#]],
+ );
+
+ check(
+ r#"
+fn test() {
+ assert!("
+ fn foo() {
+ }
+ ", "");
+}"#,
+ expect![[r#"
+ SOURCE_FILE@0..60
+ FN@0..60
+ FN_KW@0..2 "fn"
+ WHITESPACE@2..3 " "
+ NAME@3..7
+ IDENT@3..7 "test"
+ PARAM_LIST@7..9
+ L_PAREN@7..8 "("
+ R_PAREN@8..9 ")"
+ WHITESPACE@9..10 " "
+ BLOCK_EXPR@10..60
+ STMT_LIST@10..60
+ L_CURLY@10..11 "{"
+ WHITESPACE@11..16 "\n "
+ EXPR_STMT@16..58
+ MACRO_EXPR@16..57
+ MACRO_CALL@16..57
+ PATH@16..22
+ PATH_SEGMENT@16..22
+ NAME_REF@16..22
+ IDENT@16..22 "assert"
+ BANG@22..23 "!"
+ TOKEN_TREE@23..57
+ L_PAREN@23..24 "("
+ STRING@24..52 "\"\n fn foo() {\n ..."
+ COMMA@52..53 ","
+ WHITESPACE@53..54 " "
+ STRING@54..56 "\"\""
+ R_PAREN@56..57 ")"
+ SEMICOLON@57..58 ";"
+ WHITESPACE@58..59 "\n"
+ R_CURLY@59..60 "}"
+ "#]],
+ )
+ }
+
+ #[test]
+ fn test_syntax_tree_with_range() {
+ check_range(
+ r#"$0fn foo() {}$0"#,
+ expect![[r#"
+ FN@0..11
+ FN_KW@0..2 "fn"
+ WHITESPACE@2..3 " "
+ NAME@3..6
+ IDENT@3..6 "foo"
+ PARAM_LIST@6..8
+ L_PAREN@6..7 "("
+ R_PAREN@7..8 ")"
+ WHITESPACE@8..9 " "
+ BLOCK_EXPR@9..11
+ STMT_LIST@9..11
+ L_CURLY@9..10 "{"
+ R_CURLY@10..11 "}"
+ "#]],
+ );
+
+ check_range(
+ r#"
+fn test() {
+ $0assert!("
+ fn foo() {
+ }
+ ", "");$0
+}"#,
+ expect![[r#"
+ EXPR_STMT@16..58
+ MACRO_EXPR@16..57
+ MACRO_CALL@16..57
+ PATH@16..22
+ PATH_SEGMENT@16..22
+ NAME_REF@16..22
+ IDENT@16..22 "assert"
+ BANG@22..23 "!"
+ TOKEN_TREE@23..57
+ L_PAREN@23..24 "("
+ STRING@24..52 "\"\n fn foo() {\n ..."
+ COMMA@52..53 ","
+ WHITESPACE@53..54 " "
+ STRING@54..56 "\"\""
+ R_PAREN@56..57 ")"
+ SEMICOLON@57..58 ";"
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_syntax_tree_inside_string() {
+ check_range(
+ r#"fn test() {
+ assert!("
+$0fn foo() {
+}$0
+fn bar() {
+}
+ ", "");
+}"#,
+ expect![[r#"
+ SOURCE_FILE@0..12
+ FN@0..12
+ FN_KW@0..2 "fn"
+ WHITESPACE@2..3 " "
+ NAME@3..6
+ IDENT@3..6 "foo"
+ PARAM_LIST@6..8
+ L_PAREN@6..7 "("
+ R_PAREN@7..8 ")"
+ WHITESPACE@8..9 " "
+ BLOCK_EXPR@9..12
+ STMT_LIST@9..12
+ L_CURLY@9..10 "{"
+ WHITESPACE@10..11 "\n"
+ R_CURLY@11..12 "}"
+ "#]],
+ );
+
+ // With a raw string
+ check_range(
+ r###"fn test() {
+ assert!(r#"
+$0fn foo() {
+}$0
+fn bar() {
+}
+ "#, "");
+}"###,
+ expect![[r#"
+ SOURCE_FILE@0..12
+ FN@0..12
+ FN_KW@0..2 "fn"
+ WHITESPACE@2..3 " "
+ NAME@3..6
+ IDENT@3..6 "foo"
+ PARAM_LIST@6..8
+ L_PAREN@6..7 "("
+ R_PAREN@7..8 ")"
+ WHITESPACE@8..9 " "
+ BLOCK_EXPR@9..12
+ STMT_LIST@9..12
+ L_CURLY@9..10 "{"
+ WHITESPACE@10..11 "\n"
+ R_CURLY@11..12 "}"
+ "#]],
+ );
+
+ // With a raw string
+ check_range(
+ r###"fn test() {
+ assert!(r$0#"
+fn foo() {
+}
+fn bar() {
+}"$0#, "");
+}"###,
+ expect![[r#"
+ SOURCE_FILE@0..25
+ FN@0..12
+ FN_KW@0..2 "fn"
+ WHITESPACE@2..3 " "
+ NAME@3..6
+ IDENT@3..6 "foo"
+ PARAM_LIST@6..8
+ L_PAREN@6..7 "("
+ R_PAREN@7..8 ")"
+ WHITESPACE@8..9 " "
+ BLOCK_EXPR@9..12
+ STMT_LIST@9..12
+ L_CURLY@9..10 "{"
+ WHITESPACE@10..11 "\n"
+ R_CURLY@11..12 "}"
+ WHITESPACE@12..13 "\n"
+ FN@13..25
+ FN_KW@13..15 "fn"
+ WHITESPACE@15..16 " "
+ NAME@16..19
+ IDENT@16..19 "bar"
+ PARAM_LIST@19..21
+ L_PAREN@19..20 "("
+ R_PAREN@20..21 ")"
+ WHITESPACE@21..22 " "
+ BLOCK_EXPR@22..25
+ STMT_LIST@22..25
+ L_CURLY@22..23 "{"
+ WHITESPACE@23..24 "\n"
+ R_CURLY@24..25 "}"
+ "#]],
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/typing.rs b/src/tools/rust-analyzer/crates/ide/src/typing.rs
new file mode 100644
index 000000000..9118f3c69
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/typing.rs
@@ -0,0 +1,1210 @@
+//! This module handles auto-magic editing actions applied together with users
+//! edits. For example, if the user typed
+//!
+//! ```text
+//! foo
+//! .bar()
+//! .baz()
+//! | // <- cursor is here
+//! ```
+//!
+//! and types `.` next, we want to indent the dot.
+//!
+//! Language server executes such typing assists synchronously. That is, they
+//! block user's typing and should be pretty fast for this reason!
+
+mod on_enter;
+
+use ide_db::{
+ base_db::{FilePosition, SourceDatabase},
+ RootDatabase,
+};
+use syntax::{
+ algo::{ancestors_at_offset, find_node_at_offset},
+ ast::{self, edit::IndentLevel, AstToken},
+ AstNode, Parse, SourceFile, SyntaxKind, TextRange, TextSize, T,
+};
+
+use text_edit::{Indel, TextEdit};
+
+use crate::SourceChange;
+
+pub(crate) use on_enter::on_enter;
+
+// Don't forget to add new trigger characters to `server_capabilities` in `caps.rs`.
+pub(crate) const TRIGGER_CHARS: &str = ".=<>{";
+
+struct ExtendedTextEdit {
+ edit: TextEdit,
+ is_snippet: bool,
+}
+
+// Feature: On Typing Assists
+//
+// Some features trigger on typing certain characters:
+//
+// - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression
+// - typing `=` between two expressions adds `;` when in statement position
+// - typing `=` to turn an assignment into an equality comparison removes `;` when in expression position
+// - typing `.` in a chain method call auto-indents
+// - typing `{` in front of an expression inserts a closing `}` after the expression
+// - typing `{` in a use item adds a closing `}` in the right place
+//
+// VS Code::
+//
+// Add the following to `settings.json`:
+// [source,json]
+// ----
+// "editor.formatOnType": true,
+// ----
+//
+// image::https://user-images.githubusercontent.com/48062697/113166163-69758500-923a-11eb-81ee-eb33ec380399.gif[]
+// image::https://user-images.githubusercontent.com/48062697/113171066-105c2000-923f-11eb-87ab-f4a263346567.gif[]
+pub(crate) fn on_char_typed(
+ db: &RootDatabase,
+ position: FilePosition,
+ char_typed: char,
+) -> Option<SourceChange> {
+ if !stdx::always!(TRIGGER_CHARS.contains(char_typed)) {
+ return None;
+ }
+ let file = &db.parse(position.file_id);
+ if !stdx::always!(file.tree().syntax().text().char_at(position.offset) == Some(char_typed)) {
+ return None;
+ }
+ let edit = on_char_typed_inner(file, position.offset, char_typed)?;
+ let mut sc = SourceChange::from_text_edit(position.file_id, edit.edit);
+ sc.is_snippet = edit.is_snippet;
+ Some(sc)
+}
+
+fn on_char_typed_inner(
+ file: &Parse<SourceFile>,
+ offset: TextSize,
+ char_typed: char,
+) -> Option<ExtendedTextEdit> {
+ if !stdx::always!(TRIGGER_CHARS.contains(char_typed)) {
+ return None;
+ }
+ return match char_typed {
+ '.' => conv(on_dot_typed(&file.tree(), offset)),
+ '=' => conv(on_eq_typed(&file.tree(), offset)),
+ '<' => on_left_angle_typed(&file.tree(), offset),
+ '>' => conv(on_right_angle_typed(&file.tree(), offset)),
+ '{' => conv(on_opening_brace_typed(file, offset)),
+ _ => return None,
+ };
+
+ fn conv(text_edit: Option<TextEdit>) -> Option<ExtendedTextEdit> {
+ Some(ExtendedTextEdit { edit: text_edit?, is_snippet: false })
+ }
+}
+
+/// Inserts a closing `}` when the user types an opening `{`, wrapping an existing expression in a
+/// block, or a part of a `use` item.
+fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option<TextEdit> {
+ if !stdx::always!(file.tree().syntax().text().char_at(offset) == Some('{')) {
+ return None;
+ }
+
+ let brace_token = file.tree().syntax().token_at_offset(offset).right_biased()?;
+ if brace_token.kind() != SyntaxKind::L_CURLY {
+ return None;
+ }
+
+ // Remove the `{` to get a better parse tree, and reparse.
+ let range = brace_token.text_range();
+ if !stdx::always!(range.len() == TextSize::of('{')) {
+ return None;
+ }
+ let file = file.reparse(&Indel::delete(range));
+
+ if let Some(edit) = brace_expr(&file.tree(), offset) {
+ return Some(edit);
+ }
+
+ if let Some(edit) = brace_use_path(&file.tree(), offset) {
+ return Some(edit);
+ }
+
+ return None;
+
+ fn brace_use_path(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
+ let segment: ast::PathSegment = find_node_at_offset(file.syntax(), offset)?;
+ if segment.syntax().text_range().start() != offset {
+ return None;
+ }
+
+ let tree: ast::UseTree = find_node_at_offset(file.syntax(), offset)?;
+
+ Some(TextEdit::insert(
+ tree.syntax().text_range().end() + TextSize::of("{"),
+ "}".to_string(),
+ ))
+ }
+
+ fn brace_expr(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
+ let mut expr: ast::Expr = find_node_at_offset(file.syntax(), offset)?;
+ if expr.syntax().text_range().start() != offset {
+ return None;
+ }
+
+ // Enclose the outermost expression starting at `offset`
+ while let Some(parent) = expr.syntax().parent() {
+ if parent.text_range().start() != expr.syntax().text_range().start() {
+ break;
+ }
+
+ match ast::Expr::cast(parent) {
+ Some(parent) => expr = parent,
+ None => break,
+ }
+ }
+
+ // If it's a statement in a block, we don't know how many statements should be included
+ if ast::ExprStmt::can_cast(expr.syntax().parent()?.kind()) {
+ return None;
+ }
+
+ // Insert `}` right after the expression.
+ Some(TextEdit::insert(
+ expr.syntax().text_range().end() + TextSize::of("{"),
+ "}".to_string(),
+ ))
+ }
+}
+
+/// Returns an edit which should be applied after `=` was typed. Primarily,
+/// this works when adding `let =`.
+// FIXME: use a snippet completion instead of this hack here.
+fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
+ if !stdx::always!(file.syntax().text().char_at(offset) == Some('=')) {
+ return None;
+ }
+
+ if let Some(edit) = let_stmt(file, offset) {
+ return Some(edit);
+ }
+ if let Some(edit) = assign_expr(file, offset) {
+ return Some(edit);
+ }
+ if let Some(edit) = assign_to_eq(file, offset) {
+ return Some(edit);
+ }
+
+ return None;
+
+ fn assign_expr(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
+ let binop: ast::BinExpr = find_node_at_offset(file.syntax(), offset)?;
+ if !matches!(binop.op_kind(), Some(ast::BinaryOp::Assignment { op: None })) {
+ return None;
+ }
+
+ // Parent must be `ExprStmt` or `StmtList` for `;` to be valid.
+ if let Some(expr_stmt) = ast::ExprStmt::cast(binop.syntax().parent()?) {
+ if expr_stmt.semicolon_token().is_some() {
+ return None;
+ }
+ } else {
+ if !ast::StmtList::can_cast(binop.syntax().parent()?.kind()) {
+ return None;
+ }
+ }
+
+ let expr = binop.rhs()?;
+ let expr_range = expr.syntax().text_range();
+ if expr_range.contains(offset) && offset != expr_range.start() {
+ return None;
+ }
+ if file.syntax().text().slice(offset..expr_range.start()).contains_char('\n') {
+ return None;
+ }
+ let offset = expr.syntax().text_range().end();
+ Some(TextEdit::insert(offset, ";".to_string()))
+ }
+
+ /// `a =$0 b;` removes the semicolon if an expression is valid in this context.
+ fn assign_to_eq(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
+ let binop: ast::BinExpr = find_node_at_offset(file.syntax(), offset)?;
+ if !matches!(binop.op_kind(), Some(ast::BinaryOp::CmpOp(ast::CmpOp::Eq { negated: false })))
+ {
+ return None;
+ }
+
+ let expr_stmt = ast::ExprStmt::cast(binop.syntax().parent()?)?;
+ let semi = expr_stmt.semicolon_token()?;
+
+ if expr_stmt.syntax().next_sibling().is_some() {
+ // Not the last statement in the list.
+ return None;
+ }
+
+ Some(TextEdit::delete(semi.text_range()))
+ }
+
+ fn let_stmt(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
+ let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?;
+ if let_stmt.semicolon_token().is_some() {
+ return None;
+ }
+ let expr = let_stmt.initializer()?;
+ let expr_range = expr.syntax().text_range();
+ if expr_range.contains(offset) && offset != expr_range.start() {
+ return None;
+ }
+ if file.syntax().text().slice(offset..expr_range.start()).contains_char('\n') {
+ return None;
+ }
+ let offset = let_stmt.syntax().text_range().end();
+ Some(TextEdit::insert(offset, ";".to_string()))
+ }
+}
+
+/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately.
+fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
+ if !stdx::always!(file.syntax().text().char_at(offset) == Some('.')) {
+ return None;
+ }
+ let whitespace =
+ file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?;
+
+ // if prior is fn call over multiple lines dont indent
+ // or if previous is method call over multiples lines keep that indent
+ let current_indent = {
+ let text = whitespace.text();
+ let (_prefix, suffix) = text.rsplit_once('\n')?;
+ suffix
+ };
+ let current_indent_len = TextSize::of(current_indent);
+
+ let parent = whitespace.syntax().parent()?;
+ // Make sure dot is a part of call chain
+ let receiver = if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) {
+ field_expr.expr()?
+ } else if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent.clone()) {
+ method_call_expr.receiver()?
+ } else {
+ return None;
+ };
+
+ let receiver_is_multiline = receiver.syntax().text().find_char('\n').is_some();
+ let target_indent = match (receiver, receiver_is_multiline) {
+ // if receiver is multiline field or method call, just take the previous `.` indentation
+ (ast::Expr::MethodCallExpr(expr), true) => {
+ expr.dot_token().as_ref().map(IndentLevel::from_token)
+ }
+ (ast::Expr::FieldExpr(expr), true) => {
+ expr.dot_token().as_ref().map(IndentLevel::from_token)
+ }
+ // if receiver is multiline expression, just keeps its indentation
+ (_, true) => Some(IndentLevel::from_node(&parent)),
+ _ => None,
+ };
+ let target_indent = match target_indent {
+ Some(x) => x,
+ // in all other cases, take previous indentation and indent once
+ None => IndentLevel::from_node(&parent) + 1,
+ }
+ .to_string();
+
+ if current_indent_len == TextSize::of(&target_indent) {
+ return None;
+ }
+
+ Some(TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent))
+}
+
+/// Add closing `>` for generic arguments/parameters.
+fn on_left_angle_typed(file: &SourceFile, offset: TextSize) -> Option<ExtendedTextEdit> {
+ let file_text = file.syntax().text();
+ if !stdx::always!(file_text.char_at(offset) == Some('<')) {
+ return None;
+ }
+
+ // Find the next non-whitespace char in the line.
+ let mut next_offset = offset + TextSize::of('<');
+ while file_text.char_at(next_offset) == Some(' ') {
+ next_offset += TextSize::of(' ')
+ }
+ if file_text.char_at(next_offset) == Some('>') {
+ return None;
+ }
+
+ let range = TextRange::at(offset, TextSize::of('<'));
+ if let Some(t) = file.syntax().token_at_offset(offset).left_biased() {
+ if T![impl] == t.kind() {
+ return Some(ExtendedTextEdit {
+ edit: TextEdit::replace(range, "<$0>".to_string()),
+ is_snippet: true,
+ });
+ }
+ }
+
+ if ancestors_at_offset(file.syntax(), offset)
+ .find(|n| {
+ ast::GenericParamList::can_cast(n.kind()) || ast::GenericArgList::can_cast(n.kind())
+ })
+ .is_some()
+ {
+ return Some(ExtendedTextEdit {
+ edit: TextEdit::replace(range, "<$0>".to_string()),
+ is_snippet: true,
+ });
+ }
+
+ None
+}
+
+/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }`
+fn on_right_angle_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
+ let file_text = file.syntax().text();
+ if !stdx::always!(file_text.char_at(offset) == Some('>')) {
+ return None;
+ }
+ let after_arrow = offset + TextSize::of('>');
+ if file_text.char_at(after_arrow) != Some('{') {
+ return None;
+ }
+ if find_node_at_offset::<ast::RetType>(file.syntax(), offset).is_none() {
+ return None;
+ }
+
+ Some(TextEdit::insert(after_arrow, " ".to_string()))
+}
+
+#[cfg(test)]
+mod tests {
+ use test_utils::{assert_eq_text, extract_offset};
+
+ use super::*;
+
+ impl ExtendedTextEdit {
+ fn apply(&self, text: &mut String) {
+ self.edit.apply(text);
+ }
+ }
+
+ fn do_type_char(char_typed: char, before: &str) -> Option<String> {
+ let (offset, mut before) = extract_offset(before);
+ let edit = TextEdit::insert(offset, char_typed.to_string());
+ edit.apply(&mut before);
+ let parse = SourceFile::parse(&before);
+ on_char_typed_inner(&parse, offset, char_typed).map(|it| {
+ it.apply(&mut before);
+ before.to_string()
+ })
+ }
+
+ fn type_char(char_typed: char, ra_fixture_before: &str, ra_fixture_after: &str) {
+ let actual = do_type_char(char_typed, ra_fixture_before)
+ .unwrap_or_else(|| panic!("typing `{}` did nothing", char_typed));
+
+ assert_eq_text!(ra_fixture_after, &actual);
+ }
+
+ fn type_char_noop(char_typed: char, ra_fixture_before: &str) {
+ let file_change = do_type_char(char_typed, ra_fixture_before);
+ assert!(file_change.is_none())
+ }
+
+ #[test]
+ fn test_semi_after_let() {
+ // do_check(r"
+ // fn foo() {
+ // let foo =$0
+ // }
+ // ", r"
+ // fn foo() {
+ // let foo =;
+ // }
+ // ");
+ type_char(
+ '=',
+ r#"
+fn foo() {
+ let foo $0 1 + 1
+}
+"#,
+ r#"
+fn foo() {
+ let foo = 1 + 1;
+}
+"#,
+ );
+ // do_check(r"
+ // fn foo() {
+ // let foo =$0
+ // let bar = 1;
+ // }
+ // ", r"
+ // fn foo() {
+ // let foo =;
+ // let bar = 1;
+ // }
+ // ");
+ }
+
+ #[test]
+ fn test_semi_after_assign() {
+ type_char(
+ '=',
+ r#"
+fn f() {
+ i $0 0
+}
+"#,
+ r#"
+fn f() {
+ i = 0;
+}
+"#,
+ );
+ type_char(
+ '=',
+ r#"
+fn f() {
+ i $0 0
+ i
+}
+"#,
+ r#"
+fn f() {
+ i = 0;
+ i
+}
+"#,
+ );
+ type_char_noop(
+ '=',
+ r#"
+fn f(x: u8) {
+ if x $0
+}
+"#,
+ );
+ type_char_noop(
+ '=',
+ r#"
+fn f(x: u8) {
+ if x $0 {}
+}
+"#,
+ );
+ type_char_noop(
+ '=',
+ r#"
+fn f(x: u8) {
+ if x $0 0 {}
+}
+"#,
+ );
+ type_char_noop(
+ '=',
+ r#"
+fn f() {
+ g(i $0 0);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn assign_to_eq() {
+ type_char(
+ '=',
+ r#"
+fn f(a: u8) {
+ a =$0 0;
+}
+"#,
+ r#"
+fn f(a: u8) {
+ a == 0
+}
+"#,
+ );
+ type_char(
+ '=',
+ r#"
+fn f(a: u8) {
+ a $0= 0;
+}
+"#,
+ r#"
+fn f(a: u8) {
+ a == 0
+}
+"#,
+ );
+ type_char_noop(
+ '=',
+ r#"
+fn f(a: u8) {
+ let e = a =$0 0;
+}
+"#,
+ );
+ type_char_noop(
+ '=',
+ r#"
+fn f(a: u8) {
+ let e = a =$0 0;
+ e
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn indents_new_chain_call() {
+ type_char(
+ '.',
+ r#"
+fn main() {
+ xs.foo()
+ $0
+}
+ "#,
+ r#"
+fn main() {
+ xs.foo()
+ .
+}
+ "#,
+ );
+ type_char_noop(
+ '.',
+ r#"
+fn main() {
+ xs.foo()
+ $0
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn indents_new_chain_call_with_semi() {
+ type_char(
+ '.',
+ r"
+fn main() {
+ xs.foo()
+ $0;
+}
+ ",
+ r#"
+fn main() {
+ xs.foo()
+ .;
+}
+ "#,
+ );
+ type_char_noop(
+ '.',
+ r#"
+fn main() {
+ xs.foo()
+ $0;
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn indents_new_chain_call_with_let() {
+ type_char(
+ '.',
+ r#"
+fn main() {
+ let _ = foo
+ $0
+ bar()
+}
+"#,
+ r#"
+fn main() {
+ let _ = foo
+ .
+ bar()
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn indents_continued_chain_call() {
+ type_char(
+ '.',
+ r#"
+fn main() {
+ xs.foo()
+ .first()
+ $0
+}
+ "#,
+ r#"
+fn main() {
+ xs.foo()
+ .first()
+ .
+}
+ "#,
+ );
+ type_char_noop(
+ '.',
+ r#"
+fn main() {
+ xs.foo()
+ .first()
+ $0
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn indents_middle_of_chain_call() {
+ type_char(
+ '.',
+ r#"
+fn source_impl() {
+ let var = enum_defvariant_list().unwrap()
+ $0
+ .nth(92)
+ .unwrap();
+}
+ "#,
+ r#"
+fn source_impl() {
+ let var = enum_defvariant_list().unwrap()
+ .
+ .nth(92)
+ .unwrap();
+}
+ "#,
+ );
+ type_char_noop(
+ '.',
+ r#"
+fn source_impl() {
+ let var = enum_defvariant_list().unwrap()
+ $0
+ .nth(92)
+ .unwrap();
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn dont_indent_freestanding_dot() {
+ type_char_noop(
+ '.',
+ r#"
+fn main() {
+ $0
+}
+ "#,
+ );
+ type_char_noop(
+ '.',
+ r#"
+fn main() {
+$0
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn adds_space_after_return_type() {
+ type_char(
+ '>',
+ r#"
+fn foo() -$0{ 92 }
+"#,
+ r#"
+fn foo() -> { 92 }
+"#,
+ );
+ }
+
+ #[test]
+ fn adds_closing_brace_for_expr() {
+ type_char(
+ '{',
+ r#"
+fn f() { match () { _ => $0() } }
+ "#,
+ r#"
+fn f() { match () { _ => {()} } }
+ "#,
+ );
+ type_char(
+ '{',
+ r#"
+fn f() { $0() }
+ "#,
+ r#"
+fn f() { {()} }
+ "#,
+ );
+ type_char(
+ '{',
+ r#"
+fn f() { let x = $0(); }
+ "#,
+ r#"
+fn f() { let x = {()}; }
+ "#,
+ );
+ type_char(
+ '{',
+ r#"
+fn f() { let x = $0a.b(); }
+ "#,
+ r#"
+fn f() { let x = {a.b()}; }
+ "#,
+ );
+ type_char(
+ '{',
+ r#"
+const S: () = $0();
+fn f() {}
+ "#,
+ r#"
+const S: () = {()};
+fn f() {}
+ "#,
+ );
+ type_char(
+ '{',
+ r#"
+const S: () = $0a.b();
+fn f() {}
+ "#,
+ r#"
+const S: () = {a.b()};
+fn f() {}
+ "#,
+ );
+ type_char(
+ '{',
+ r#"
+fn f() {
+ match x {
+ 0 => $0(),
+ 1 => (),
+ }
+}
+ "#,
+ r#"
+fn f() {
+ match x {
+ 0 => {()},
+ 1 => (),
+ }
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn noop_in_string_literal() {
+ // Regression test for #9351
+ type_char_noop(
+ '{',
+ r##"
+fn check_with(ra_fixture: &str, expect: Expect) {
+ let base = r#"
+enum E { T(), R$0, C }
+use self::E::X;
+const Z: E = E::C;
+mod m {}
+asdasdasdasdasdasda
+sdasdasdasdasdasda
+sdasdasdasdasd
+"#;
+ let actual = completion_list(&format!("{}\n{}", base, ra_fixture));
+ expect.assert_eq(&actual)
+}
+ "##,
+ );
+ }
+
+ #[test]
+ fn noop_in_item_position_with_macro() {
+ type_char_noop('{', r#"$0println!();"#);
+ type_char_noop(
+ '{',
+ r#"
+fn main() $0println!("hello");
+}"#,
+ );
+ }
+
+ #[test]
+ fn adds_closing_brace_for_use_tree() {
+ type_char(
+ '{',
+ r#"
+use some::$0Path;
+ "#,
+ r#"
+use some::{Path};
+ "#,
+ );
+ type_char(
+ '{',
+ r#"
+use some::{Path, $0Other};
+ "#,
+ r#"
+use some::{Path, {Other}};
+ "#,
+ );
+ type_char(
+ '{',
+ r#"
+use some::{$0Path, Other};
+ "#,
+ r#"
+use some::{{Path}, Other};
+ "#,
+ );
+ type_char(
+ '{',
+ r#"
+use some::path::$0to::Item;
+ "#,
+ r#"
+use some::path::{to::Item};
+ "#,
+ );
+ type_char(
+ '{',
+ r#"
+use some::$0path::to::Item;
+ "#,
+ r#"
+use some::{path::to::Item};
+ "#,
+ );
+ type_char(
+ '{',
+ r#"
+use $0some::path::to::Item;
+ "#,
+ r#"
+use {some::path::to::Item};
+ "#,
+ );
+ type_char(
+ '{',
+ r#"
+use some::path::$0to::{Item};
+ "#,
+ r#"
+use some::path::{to::{Item}};
+ "#,
+ );
+ type_char(
+ '{',
+ r#"
+use $0Thing as _;
+ "#,
+ r#"
+use {Thing as _};
+ "#,
+ );
+
+ type_char_noop(
+ '{',
+ r#"
+use some::pa$0th::to::Item;
+ "#,
+ );
+ }
+
+ #[test]
+ fn adds_closing_angle_bracket_for_generic_args() {
+ type_char(
+ '<',
+ r#"
+fn foo() {
+ bar::$0
+}
+ "#,
+ r#"
+fn foo() {
+ bar::<$0>
+}
+ "#,
+ );
+
+ type_char(
+ '<',
+ r#"
+fn foo(bar: &[u64]) {
+ bar.iter().collect::$0();
+}
+ "#,
+ r#"
+fn foo(bar: &[u64]) {
+ bar.iter().collect::<$0>();
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn adds_closing_angle_bracket_for_generic_params() {
+ type_char(
+ '<',
+ r#"
+fn foo$0() {}
+ "#,
+ r#"
+fn foo<$0>() {}
+ "#,
+ );
+ type_char(
+ '<',
+ r#"
+fn foo$0
+ "#,
+ r#"
+fn foo<$0>
+ "#,
+ );
+ type_char(
+ '<',
+ r#"
+struct Foo$0 {}
+ "#,
+ r#"
+struct Foo<$0> {}
+ "#,
+ );
+ type_char(
+ '<',
+ r#"
+struct Foo$0();
+ "#,
+ r#"
+struct Foo<$0>();
+ "#,
+ );
+ type_char(
+ '<',
+ r#"
+struct Foo$0
+ "#,
+ r#"
+struct Foo<$0>
+ "#,
+ );
+ type_char(
+ '<',
+ r#"
+enum Foo$0
+ "#,
+ r#"
+enum Foo<$0>
+ "#,
+ );
+ type_char(
+ '<',
+ r#"
+trait Foo$0
+ "#,
+ r#"
+trait Foo<$0>
+ "#,
+ );
+ type_char(
+ '<',
+ r#"
+type Foo$0 = Bar;
+ "#,
+ r#"
+type Foo<$0> = Bar;
+ "#,
+ );
+ type_char(
+ '<',
+ r#"
+impl$0 Foo {}
+ "#,
+ r#"
+impl<$0> Foo {}
+ "#,
+ );
+ type_char(
+ '<',
+ r#"
+impl<T> Foo$0 {}
+ "#,
+ r#"
+impl<T> Foo<$0> {}
+ "#,
+ );
+ type_char(
+ '<',
+ r#"
+impl Foo$0 {}
+ "#,
+ r#"
+impl Foo<$0> {}
+ "#,
+ );
+ }
+
+ #[test]
+ fn dont_add_closing_angle_bracket_for_comparison() {
+ type_char_noop(
+ '<',
+ r#"
+fn main() {
+ 42$0
+}
+ "#,
+ );
+ type_char_noop(
+ '<',
+ r#"
+fn main() {
+ 42 $0
+}
+ "#,
+ );
+ type_char_noop(
+ '<',
+ r#"
+fn main() {
+ let foo = 42;
+ foo $0
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn dont_add_closing_angle_bracket_if_it_is_already_there() {
+ type_char_noop(
+ '<',
+ r#"
+fn foo() {
+ bar::$0>
+}
+ "#,
+ );
+ type_char_noop(
+ '<',
+ r#"
+fn foo(bar: &[u64]) {
+ bar.iter().collect::$0 >();
+}
+ "#,
+ );
+ type_char_noop(
+ '<',
+ r#"
+fn foo$0>() {}
+ "#,
+ );
+ type_char_noop(
+ '<',
+ r#"
+fn foo$0>
+ "#,
+ );
+ type_char_noop(
+ '<',
+ r#"
+struct Foo$0> {}
+ "#,
+ );
+ type_char_noop(
+ '<',
+ r#"
+struct Foo$0>();
+ "#,
+ );
+ type_char_noop(
+ '<',
+ r#"
+struct Foo$0>
+ "#,
+ );
+ type_char_noop(
+ '<',
+ r#"
+enum Foo$0>
+ "#,
+ );
+ type_char_noop(
+ '<',
+ r#"
+trait Foo$0>
+ "#,
+ );
+ type_char_noop(
+ '<',
+ r#"
+type Foo$0> = Bar;
+ "#,
+ );
+ type_char_noop(
+ '<',
+ r#"
+impl$0> Foo {}
+ "#,
+ );
+ type_char_noop(
+ '<',
+ r#"
+impl<T> Foo$0> {}
+ "#,
+ );
+ type_char_noop(
+ '<',
+ r#"
+impl Foo$0> {}
+ "#,
+ );
+ }
+
+ #[test]
+ fn regression_629() {
+ type_char_noop(
+ '.',
+ r#"
+fn foo() {
+ CompletionItem::new(
+ CompletionKind::Reference,
+ ctx.source_range(),
+ field.name().to_string(),
+ )
+ .foo()
+ $0
+}
+"#,
+ );
+ type_char_noop(
+ '.',
+ r#"
+fn foo() {
+ CompletionItem::new(
+ CompletionKind::Reference,
+ ctx.source_range(),
+ field.name().to_string(),
+ )
+ $0
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/typing/on_enter.rs b/src/tools/rust-analyzer/crates/ide/src/typing/on_enter.rs
new file mode 100644
index 000000000..48c171327
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/typing/on_enter.rs
@@ -0,0 +1,616 @@
+//! Handles the `Enter` key press. At the momently, this only continues
+//! comments, but should handle indent some time in the future as well.
+
+use ide_db::base_db::{FilePosition, SourceDatabase};
+use ide_db::RootDatabase;
+use syntax::{
+ algo::find_node_at_offset,
+ ast::{self, edit::IndentLevel, AstToken},
+ AstNode, SmolStr, SourceFile,
+ SyntaxKind::*,
+ SyntaxNode, SyntaxToken, TextRange, TextSize, TokenAtOffset,
+};
+
+use text_edit::TextEdit;
+
+// Feature: On Enter
+//
+// rust-analyzer can override kbd:[Enter] key to make it smarter:
+//
+// - kbd:[Enter] inside triple-slash comments automatically inserts `///`
+// - kbd:[Enter] in the middle or after a trailing space in `//` inserts `//`
+// - kbd:[Enter] inside `//!` doc comments automatically inserts `//!`
+// - kbd:[Enter] after `{` indents contents and closing `}` of single-line block
+//
+// This action needs to be assigned to shortcut explicitly.
+//
+// Note that, depending on the other installed extensions, this feature can visibly slow down typing.
+// Similarly, if rust-analyzer crashes or stops responding, `Enter` might not work.
+// In that case, you can still press `Shift-Enter` to insert a newline.
+//
+// VS Code::
+//
+// Add the following to `keybindings.json`:
+// [source,json]
+// ----
+// {
+// "key": "Enter",
+// "command": "rust-analyzer.onEnter",
+// "when": "editorTextFocus && !suggestWidgetVisible && editorLangId == rust"
+// }
+// ----
+//
+// When using the Vim plugin:
+// [source,json]
+// ----
+// {
+// "key": "Enter",
+// "command": "rust-analyzer.onEnter",
+// "when": "editorTextFocus && !suggestWidgetVisible && editorLangId == rust && vim.mode == 'Insert'"
+// }
+// ----
+//
+// image::https://user-images.githubusercontent.com/48062697/113065578-04c21800-91b1-11eb-82b8-22b8c481e645.gif[]
+pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<TextEdit> {
+ let parse = db.parse(position.file_id);
+ let file = parse.tree();
+ let token = file.syntax().token_at_offset(position.offset).left_biased()?;
+
+ if let Some(comment) = ast::Comment::cast(token.clone()) {
+ return on_enter_in_comment(&comment, &file, position.offset);
+ }
+
+ if token.kind() == L_CURLY {
+ // Typing enter after the `{` of a block expression, where the `}` is on the same line
+ if let Some(edit) = find_node_at_offset(file.syntax(), position.offset - TextSize::of('{'))
+ .and_then(|block| on_enter_in_block(block, position))
+ {
+ cov_mark::hit!(indent_block_contents);
+ return Some(edit);
+ }
+
+ // Typing enter after the `{` of a use tree list.
+ if let Some(edit) = find_node_at_offset(file.syntax(), position.offset - TextSize::of('{'))
+ .and_then(|list| on_enter_in_use_tree_list(list, position))
+ {
+ cov_mark::hit!(indent_block_contents);
+ return Some(edit);
+ }
+ }
+
+ None
+}
+
+fn on_enter_in_comment(
+ comment: &ast::Comment,
+ file: &ast::SourceFile,
+ offset: TextSize,
+) -> Option<TextEdit> {
+ if comment.kind().shape.is_block() {
+ return None;
+ }
+
+ let prefix = comment.prefix();
+ let comment_range = comment.syntax().text_range();
+ if offset < comment_range.start() + TextSize::of(prefix) {
+ return None;
+ }
+
+ let mut remove_trailing_whitespace = false;
+ // Continuing single-line non-doc comments (like this one :) ) is annoying
+ if prefix == "//" && comment_range.end() == offset {
+ if comment.text().ends_with(' ') {
+ cov_mark::hit!(continues_end_of_line_comment_with_space);
+ remove_trailing_whitespace = true;
+ } else if !followed_by_comment(comment) {
+ return None;
+ }
+ }
+
+ let indent = node_indent(file, comment.syntax())?;
+ let inserted = format!("\n{}{} $0", indent, prefix);
+ let delete = if remove_trailing_whitespace {
+ let trimmed_len = comment.text().trim_end().len() as u32;
+ let trailing_whitespace_len = comment.text().len() as u32 - trimmed_len;
+ TextRange::new(offset - TextSize::from(trailing_whitespace_len), offset)
+ } else {
+ TextRange::empty(offset)
+ };
+ let edit = TextEdit::replace(delete, inserted);
+ Some(edit)
+}
+
+fn on_enter_in_block(block: ast::BlockExpr, position: FilePosition) -> Option<TextEdit> {
+ let contents = block_contents(&block)?;
+
+ if block.syntax().text().contains_char('\n') {
+ return None;
+ }
+
+ let indent = IndentLevel::from_node(block.syntax());
+ let mut edit = TextEdit::insert(position.offset, format!("\n{}$0", indent + 1));
+ edit.union(TextEdit::insert(contents.text_range().end(), format!("\n{}", indent))).ok()?;
+ Some(edit)
+}
+
+fn on_enter_in_use_tree_list(list: ast::UseTreeList, position: FilePosition) -> Option<TextEdit> {
+ if list.syntax().text().contains_char('\n') {
+ return None;
+ }
+
+ let indent = IndentLevel::from_node(list.syntax());
+ let mut edit = TextEdit::insert(position.offset, format!("\n{}$0", indent + 1));
+ edit.union(TextEdit::insert(
+ list.r_curly_token()?.text_range().start(),
+ format!("\n{}", indent),
+ ))
+ .ok()?;
+ Some(edit)
+}
+
+fn block_contents(block: &ast::BlockExpr) -> Option<SyntaxNode> {
+ let mut node = block.tail_expr().map(|e| e.syntax().clone());
+
+ for stmt in block.statements() {
+ if node.is_some() {
+ // More than 1 node in the block
+ return None;
+ }
+
+ node = Some(stmt.syntax().clone());
+ }
+
+ node
+}
+
+fn followed_by_comment(comment: &ast::Comment) -> bool {
+ let ws = match comment.syntax().next_token().and_then(ast::Whitespace::cast) {
+ Some(it) => it,
+ None => return false,
+ };
+ if ws.spans_multiple_lines() {
+ return false;
+ }
+ ws.syntax().next_token().and_then(ast::Comment::cast).is_some()
+}
+
+fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option<SmolStr> {
+ let ws = match file.syntax().token_at_offset(token.text_range().start()) {
+ TokenAtOffset::Between(l, r) => {
+ assert!(r == *token);
+ l
+ }
+ TokenAtOffset::Single(n) => {
+ assert!(n == *token);
+ return Some("".into());
+ }
+ TokenAtOffset::None => unreachable!(),
+ };
+ if ws.kind() != WHITESPACE {
+ return None;
+ }
+ let text = ws.text();
+ let pos = text.rfind('\n').map(|it| it + 1).unwrap_or(0);
+ Some(text[pos..].into())
+}
+
+#[cfg(test)]
+mod tests {
+ use stdx::trim_indent;
+ use test_utils::assert_eq_text;
+
+ use crate::fixture;
+
+ fn apply_on_enter(before: &str) -> Option<String> {
+ let (analysis, position) = fixture::position(before);
+ let result = analysis.on_enter(position).unwrap()?;
+
+ let mut actual = analysis.file_text(position.file_id).unwrap().to_string();
+ result.apply(&mut actual);
+ Some(actual)
+ }
+
+ fn do_check(ra_fixture_before: &str, ra_fixture_after: &str) {
+ let ra_fixture_after = &trim_indent(ra_fixture_after);
+ let actual = apply_on_enter(ra_fixture_before).unwrap();
+ assert_eq_text!(ra_fixture_after, &actual);
+ }
+
+ fn do_check_noop(ra_fixture_text: &str) {
+ assert!(apply_on_enter(ra_fixture_text).is_none())
+ }
+
+ #[test]
+ fn continues_doc_comment() {
+ do_check(
+ r"
+/// Some docs$0
+fn foo() {
+}
+",
+ r"
+/// Some docs
+/// $0
+fn foo() {
+}
+",
+ );
+
+ do_check(
+ r"
+impl S {
+ /// Some$0 docs.
+ fn foo() {}
+}
+",
+ r"
+impl S {
+ /// Some
+ /// $0 docs.
+ fn foo() {}
+}
+",
+ );
+
+ do_check(
+ r"
+///$0 Some docs
+fn foo() {
+}
+",
+ r"
+///
+/// $0 Some docs
+fn foo() {
+}
+",
+ );
+ }
+
+ #[test]
+ fn does_not_continue_before_doc_comment() {
+ do_check_noop(r"$0//! docz");
+ }
+
+ #[test]
+ fn continues_another_doc_comment() {
+ do_check(
+ r#"
+fn main() {
+ //! Documentation for$0 on enter
+ let x = 1 + 1;
+}
+"#,
+ r#"
+fn main() {
+ //! Documentation for
+ //! $0 on enter
+ let x = 1 + 1;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn continues_code_comment_in_the_middle_of_line() {
+ do_check(
+ r"
+fn main() {
+ // Fix$0 me
+ let x = 1 + 1;
+}
+",
+ r"
+fn main() {
+ // Fix
+ // $0 me
+ let x = 1 + 1;
+}
+",
+ );
+ }
+
+ #[test]
+ fn continues_code_comment_in_the_middle_several_lines() {
+ do_check(
+ r"
+fn main() {
+ // Fix$0
+ // me
+ let x = 1 + 1;
+}
+",
+ r"
+fn main() {
+ // Fix
+ // $0
+ // me
+ let x = 1 + 1;
+}
+",
+ );
+ }
+
+ #[test]
+ fn does_not_continue_end_of_line_comment() {
+ do_check_noop(
+ r"
+fn main() {
+ // Fix me$0
+ let x = 1 + 1;
+}
+",
+ );
+ }
+
+ #[test]
+ fn continues_end_of_line_comment_with_space() {
+ cov_mark::check!(continues_end_of_line_comment_with_space);
+ do_check(
+ r#"
+fn main() {
+ // Fix me $0
+ let x = 1 + 1;
+}
+"#,
+ r#"
+fn main() {
+ // Fix me
+ // $0
+ let x = 1 + 1;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn trims_all_trailing_whitespace() {
+ do_check(
+ "
+fn main() {
+ // Fix me \t\t $0
+ let x = 1 + 1;
+}
+",
+ "
+fn main() {
+ // Fix me
+ // $0
+ let x = 1 + 1;
+}
+",
+ );
+ }
+
+ #[test]
+ fn indents_fn_body_block() {
+ cov_mark::check!(indent_block_contents);
+ do_check(
+ r#"
+fn f() {$0()}
+ "#,
+ r#"
+fn f() {
+ $0()
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn indents_block_expr() {
+ do_check(
+ r#"
+fn f() {
+ let x = {$0()};
+}
+ "#,
+ r#"
+fn f() {
+ let x = {
+ $0()
+ };
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn indents_match_arm() {
+ do_check(
+ r#"
+fn f() {
+ match 6 {
+ 1 => {$0f()},
+ _ => (),
+ }
+}
+ "#,
+ r#"
+fn f() {
+ match 6 {
+ 1 => {
+ $0f()
+ },
+ _ => (),
+ }
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn indents_block_with_statement() {
+ do_check(
+ r#"
+fn f() {$0a = b}
+ "#,
+ r#"
+fn f() {
+ $0a = b
+}
+ "#,
+ );
+ do_check(
+ r#"
+fn f() {$0fn f() {}}
+ "#,
+ r#"
+fn f() {
+ $0fn f() {}
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn indents_nested_blocks() {
+ do_check(
+ r#"
+fn f() {$0{}}
+ "#,
+ r#"
+fn f() {
+ $0{}
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn does_not_indent_empty_block() {
+ do_check_noop(
+ r#"
+fn f() {$0}
+ "#,
+ );
+ do_check_noop(
+ r#"
+fn f() {{$0}}
+ "#,
+ );
+ }
+
+ #[test]
+ fn does_not_indent_block_with_too_much_content() {
+ do_check_noop(
+ r#"
+fn f() {$0 a = b; ()}
+ "#,
+ );
+ do_check_noop(
+ r#"
+fn f() {$0 a = b; a = b; }
+ "#,
+ );
+ }
+
+ #[test]
+ fn does_not_indent_multiline_block() {
+ do_check_noop(
+ r#"
+fn f() {$0
+}
+ "#,
+ );
+ do_check_noop(
+ r#"
+fn f() {$0
+
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn indents_use_tree_list() {
+ do_check(
+ r#"
+use crate::{$0};
+ "#,
+ r#"
+use crate::{
+ $0
+};
+ "#,
+ );
+ do_check(
+ r#"
+use crate::{$0Object, path::to::OtherThing};
+ "#,
+ r#"
+use crate::{
+ $0Object, path::to::OtherThing
+};
+ "#,
+ );
+ do_check(
+ r#"
+use {crate::{$0Object, path::to::OtherThing}};
+ "#,
+ r#"
+use {crate::{
+ $0Object, path::to::OtherThing
+}};
+ "#,
+ );
+ do_check(
+ r#"
+use {
+ crate::{$0Object, path::to::OtherThing}
+};
+ "#,
+ r#"
+use {
+ crate::{
+ $0Object, path::to::OtherThing
+ }
+};
+ "#,
+ );
+ }
+
+ #[test]
+ fn does_not_indent_use_tree_list_when_not_at_curly_brace() {
+ do_check_noop(
+ r#"
+use path::{Thing$0};
+ "#,
+ );
+ }
+
+ #[test]
+ fn does_not_indent_use_tree_list_without_curly_braces() {
+ do_check_noop(
+ r#"
+use path::Thing$0;
+ "#,
+ );
+ do_check_noop(
+ r#"
+use path::$0Thing;
+ "#,
+ );
+ do_check_noop(
+ r#"
+use path::Thing$0};
+ "#,
+ );
+ do_check_noop(
+ r#"
+use path::{$0Thing;
+ "#,
+ );
+ }
+
+ #[test]
+ fn does_not_indent_multiline_use_tree_list() {
+ do_check_noop(
+ r#"
+use path::{$0
+ Thing
+};
+ "#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/view_crate_graph.rs b/src/tools/rust-analyzer/crates/ide/src/view_crate_graph.rs
new file mode 100644
index 000000000..51291a645
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/view_crate_graph.rs
@@ -0,0 +1,93 @@
+use std::sync::Arc;
+
+use dot::{Id, LabelText};
+use ide_db::{
+ base_db::{CrateGraph, CrateId, Dependency, SourceDatabase, SourceDatabaseExt},
+ FxHashSet, RootDatabase,
+};
+
+// Feature: View Crate Graph
+//
+// Renders the currently loaded crate graph as an SVG graphic. Requires the `dot` tool, which
+// is part of graphviz, to be installed.
+//
+// Only workspace crates are included, no crates.io dependencies or sysroot crates.
+//
+// |===
+// | Editor | Action Name
+//
+// | VS Code | **Rust Analyzer: View Crate Graph**
+// |===
+pub(crate) fn view_crate_graph(db: &RootDatabase, full: bool) -> Result<String, String> {
+ let crate_graph = db.crate_graph();
+ let crates_to_render = crate_graph
+ .iter()
+ .filter(|krate| {
+ if full {
+ true
+ } else {
+ // Only render workspace crates
+ let root_id = db.file_source_root(crate_graph[*krate].root_file_id);
+ !db.source_root(root_id).is_library
+ }
+ })
+ .collect();
+ let graph = DotCrateGraph { graph: crate_graph, crates_to_render };
+
+ let mut dot = Vec::new();
+ dot::render(&graph, &mut dot).unwrap();
+ Ok(String::from_utf8(dot).unwrap())
+}
+
+struct DotCrateGraph {
+ graph: Arc<CrateGraph>,
+ crates_to_render: FxHashSet<CrateId>,
+}
+
+type Edge<'a> = (CrateId, &'a Dependency);
+
+impl<'a> dot::GraphWalk<'a, CrateId, Edge<'a>> for DotCrateGraph {
+ fn nodes(&'a self) -> dot::Nodes<'a, CrateId> {
+ self.crates_to_render.iter().copied().collect()
+ }
+
+ fn edges(&'a self) -> dot::Edges<'a, Edge<'a>> {
+ self.crates_to_render
+ .iter()
+ .flat_map(|krate| {
+ self.graph[*krate]
+ .dependencies
+ .iter()
+ .filter(|dep| self.crates_to_render.contains(&dep.crate_id))
+ .map(move |dep| (*krate, dep))
+ })
+ .collect()
+ }
+
+ fn source(&'a self, edge: &Edge<'a>) -> CrateId {
+ edge.0
+ }
+
+ fn target(&'a self, edge: &Edge<'a>) -> CrateId {
+ edge.1.crate_id
+ }
+}
+
+impl<'a> dot::Labeller<'a, CrateId, Edge<'a>> for DotCrateGraph {
+ fn graph_id(&'a self) -> Id<'a> {
+ Id::new("rust_analyzer_crate_graph").unwrap()
+ }
+
+ fn node_id(&'a self, n: &CrateId) -> Id<'a> {
+ Id::new(format!("_{}", n.0)).unwrap()
+ }
+
+ fn node_shape(&'a self, _node: &CrateId) -> Option<LabelText<'a>> {
+ Some(LabelText::LabelStr("box".into()))
+ }
+
+ fn node_label(&'a self, n: &CrateId) -> LabelText<'a> {
+ let name = self.graph[*n].display_name.as_ref().map_or("(unnamed crate)", |name| &*name);
+ LabelText::LabelStr(name.into())
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/view_hir.rs b/src/tools/rust-analyzer/crates/ide/src/view_hir.rs
new file mode 100644
index 000000000..7312afe53
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/view_hir.rs
@@ -0,0 +1,26 @@
+use hir::{Function, Semantics};
+use ide_db::base_db::FilePosition;
+use ide_db::RootDatabase;
+use syntax::{algo::find_node_at_offset, ast, AstNode};
+
+// Feature: View Hir
+//
+// |===
+// | Editor | Action Name
+//
+// | VS Code | **Rust Analyzer: View Hir**
+// |===
+// image::https://user-images.githubusercontent.com/48062697/113065588-068bdb80-91b1-11eb-9a78-0b4ef1e972fb.gif[]
+pub(crate) fn view_hir(db: &RootDatabase, position: FilePosition) -> String {
+ body_hir(db, position).unwrap_or_else(|| "Not inside a function body".to_string())
+}
+
+fn body_hir(db: &RootDatabase, position: FilePosition) -> Option<String> {
+ let sema = Semantics::new(db);
+ let source_file = sema.parse(position.file_id);
+
+ let function = find_node_at_offset::<ast::Fn>(source_file.syntax(), position.offset)?;
+
+ let function: Function = sema.to_def(&function)?;
+ Some(function.debug_hir(db))
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/view_item_tree.rs b/src/tools/rust-analyzer/crates/ide/src/view_item_tree.rs
new file mode 100644
index 000000000..3dc03085d
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/view_item_tree.rs
@@ -0,0 +1,16 @@
+use hir::db::DefDatabase;
+use ide_db::base_db::FileId;
+use ide_db::RootDatabase;
+
+// Feature: Debug ItemTree
+//
+// Displays the ItemTree of the currently open file, for debugging.
+//
+// |===
+// | Editor | Action Name
+//
+// | VS Code | **Rust Analyzer: Debug ItemTree**
+// |===
+pub(crate) fn view_item_tree(db: &RootDatabase, file_id: FileId) -> String {
+ db.file_item_tree(file_id.into()).pretty_print()
+}